diff --git a/.gitignore b/.gitignore index dd30f6b..bbe56bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules *.tsbuildinfo +electron.vite.config.[0-9]*.mjs dist out .env @@ -24,4 +25,4 @@ skills-lock.json findings.md progress.md -task_plan.md \ No newline at end of file +task_plan.md diff --git a/electron.vite.config.1789066213093.mjs b/electron.vite.config.1789066213093.mjs deleted file mode 100644 index 1d0f7b7..0000000 --- a/electron.vite.config.1789066213093.mjs +++ /dev/null @@ -1,35 +0,0 @@ -// electron.vite.config.ts -import { resolve } from "path"; -import { defineConfig } from "electron-vite"; -import react from "@vitejs/plugin-react"; -var electron_vite_config_default = defineConfig({ - main: { - build: { - rollupOptions: { - input: { - index: resolve("src/main/index.ts"), - reportTemplateTest: resolve("src/main/report-template-test-entry.ts"), - queryAgentPoc: resolve("src/main/query-agent-poc-entry.ts"), - voiceRecognitionWorker: resolve("src/main/voice-pipeline/voice-recognition-worker.ts"), - knowledgeWorker: resolve("src/main/knowledge/knowledge-worker.ts") - }, - output: { - entryFileNames: "[name].js" - }, - external: ["koffi", "sherpa-onnx-node"] - } - } - }, - preload: {}, - renderer: { - resolve: { - alias: { - "@renderer": resolve("src/renderer/src") - } - }, - plugins: [react()] - } -}); -export { - electron_vite_config_default as default -}; diff --git a/src/main/services/local-query-api-service.ts b/src/main/services/local-query-api-service.ts index 015361b..cbfe864 100644 --- a/src/main/services/local-query-api-service.ts +++ b/src/main/services/local-query-api-service.ts @@ -86,9 +86,9 @@ function rangeEndMs(range: ResolvedTimeRange, fallbackNowMs: number): number { /** 本地时间(`MM-DD HH:mm`);只用于给模型一句可引用的人话,不参与任何判断。 */ function formatLocalMinute(ms: number): string { - const date = new Date(ms) + const date = new Date(ms + 8 * 60 * 60 * 1000) const pad = (value: number): string => String(value).padStart(2, '0') - return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}` + return `${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}` } /** diff --git a/src/shared/ai-search.ts b/src/shared/ai-search.ts index 78b59ae..5e848b1 100644 --- a/src/shared/ai-search.ts +++ b/src/shared/ai-search.ts @@ -399,14 +399,27 @@ export const aiSearchRangeStart = (range: AiSearchRange): number | undefined => return Math.floor(Date.now() / 1000) - (range === '7d' ? 7 : 30) * 86400 } -const dayStart = (date: Date): number => - Math.floor(new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() / 1000) +// Natural-language dates use the app's explicit China Standard Time calendar, +// independent of the host OS timezone (CI may run in UTC). +const APP_TIMEZONE_OFFSET_MS = 8 * 60 * 60 * 1000 +const appCalendar = (date: Date): Date => new Date(date.getTime() + APP_TIMEZONE_OFFSET_MS) +const appDateEpochSeconds = (year: number, month: number, day: number): number => + Math.floor((Date.UTC(year, month, day) - APP_TIMEZONE_OFFSET_MS) / 1000) -const currentYearStart = (date: Date): number => - Math.floor(new Date(date.getFullYear(), 0, 1).getTime() / 1000) +const dayStart = (date: Date): number => { + const local = appCalendar(date) + return appDateEpochSeconds(local.getUTCFullYear(), local.getUTCMonth(), local.getUTCDate()) +} -const currentMonthStart = (date: Date): number => - Math.floor(new Date(date.getFullYear(), date.getMonth(), 1).getTime() / 1000) +const currentYearStart = (date: Date): number => { + const local = appCalendar(date) + return appDateEpochSeconds(local.getUTCFullYear(), 0, 1) +} + +const currentMonthStart = (date: Date): number => { + const local = appCalendar(date) + return appDateEpochSeconds(local.getUTCFullYear(), local.getUTCMonth(), 1) +} const CHINESE_NUMBERS: Record = { 一: 1, @@ -468,9 +481,12 @@ export const inferAiSearchTimeRange = ( } if (/这个月|本月/.test(query)) return fromQuery(currentMonthStart(now), '本月', '用户说“这个月”') if (/上个月/.test(query)) { - const startDate = new Date(now.getFullYear(), now.getMonth() - 1, 1) - const end = Math.floor(new Date(now.getFullYear(), now.getMonth(), 1).getTime() / 1000) - 1 - const start = Math.floor(startDate.getTime() / 1000) + const local = appCalendar(now) + const year = local.getUTCFullYear() + const month = local.getUTCMonth() + const startDate = new Date(Date.UTC(year, month - 1, 1)) + const end = appDateEpochSeconds(year, month, 1) - 1 + const start = appDateEpochSeconds(startDate.getUTCFullYear(), startDate.getUTCMonth(), 1) return { startTime: start, endTime: end, @@ -492,10 +508,11 @@ export const inferAiSearchTimeRange = ( } } if (/去年/.test(query)) { - const startDate = new Date(now.getFullYear() - 1, 0, 1) + const year = appCalendar(now).getUTCFullYear() + const startDate = new Date(Date.UTC(year - 1, 0, 1)) return { - startTime: Math.floor(startDate.getTime() / 1000), - endTime: Math.floor(new Date(now.getFullYear(), 0, 1).getTime() / 1000) - 1, + startTime: appDateEpochSeconds(year - 1, 0, 1), + endTime: appDateEpochSeconds(year, 0, 1) - 1, label: `${startDate.getFullYear()}年`, reason: '用户说“去年”', source: 'query' diff --git a/tests/unit/ai-search-time-range.test.ts b/tests/unit/ai-search-time-range.test.ts index 273f263..7435fd8 100644 --- a/tests/unit/ai-search-time-range.test.ts +++ b/tests/unit/ai-search-time-range.test.ts @@ -23,7 +23,7 @@ describe('AI search natural-language time ranges', () => { }) expect(inferAiSearchTimeRange('我和张三今年聊了什么?', 'all', NOW)).toMatchObject({ label: '今年', - startTime: Math.floor(new Date(2026, 0, 1).getTime() / 1000) + startTime: Math.floor(new Date('2026-01-01T00:00:00+08:00').getTime() / 1000) }) }) diff --git a/tests/unit/settings-navigation.test.ts b/tests/unit/settings-navigation.test.ts index 9fd700c..0ade876 100644 --- a/tests/unit/settings-navigation.test.ts +++ b/tests/unit/settings-navigation.test.ts @@ -9,6 +9,7 @@ describe('settings navigation', () => { const intelligent = SETTINGS_NAVIGATION.find((group) => group.label === '智能能力') expect(intelligent?.items.map((item) => item.id)).toEqual([ 'wechat-send', + 'wechat-action-logs', 'voice-recognition', 'text-to-speech', 'ai-model'