diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bb8dc26..4eea1e8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## 0.5.76 (2026-02-05) + +### 🚀 Features + +- add base64 image preview functionality ([fba4fe7](https://github.com/ljquan/aitu/commit/fba4fe7)) + +### 🔧 Chores + +- docs: 添加配置对象深拷贝规则 ([0002654](https://github.com/ljquan/aitu/commit/0002654)) + +### ❤️ Thank You + +- liquidliang + ## 0.5.75 (2026-02-05) ### 🚀 Features diff --git a/apps/web/public/version.json b/apps/web/public/version.json index d572c812..29e52a29 100644 --- a/apps/web/public/version.json +++ b/apps/web/public/version.json @@ -1,15 +1,6 @@ { - "version": "0.5.75", - "buildTime": "2026-02-05T07:51:18.129Z", + "version": "0.5.76", + "buildTime": "2026-02-05T08:54:33.109Z", "gitCommit": "unknown", - "changelog": [ - "support opening tools via URL parameter and display tool icons in window title", - "move ai_analyze to SW and improve debug panel logging", - "integrate fake-indexeddb and refactor media executor", - "implement LLM API log selection and batch actions", - "enhance video handling and task queue management", - "perf: 优化同步上传性能,避免 UI 阻塞", - "任务队列同步下载远程数据时双向合并", - "改进工作流恢复机制和错误处理" - ] + "changelog": [] } \ No newline at end of file diff --git a/apps/web/src/sw/task-queue/channel-manager.ts b/apps/web/src/sw/task-queue/channel-manager.ts index 1bbd2025..cda1e8d7 100644 --- a/apps/web/src/sw/task-queue/channel-manager.ts +++ b/apps/web/src/sw/task-queue/channel-manager.ts @@ -611,6 +611,14 @@ export class SWChannelManager { private workflowHandlerInitialized = false; private async handleInit(data: { geminiConfig: GeminiConfig; videoConfig: VideoAPIConfig }): Promise<{ success: boolean; error?: string }> { + console.log('[SWChannelManager] handleInit called with:', { + hasGeminiConfig: !!data?.geminiConfig, + hasVideoConfig: !!data?.videoConfig, + geminiApiKey: data?.geminiConfig?.apiKey ? `${data.geminiConfig.apiKey.slice(0, 8)}...` : 'missing', + geminiBaseUrl: data?.geminiConfig?.baseUrl || 'missing', + videoBaseUrl: data?.videoConfig?.baseUrl || 'missing', + }); + if (!data || !data.geminiConfig || !data.videoConfig) { console.error('[SWChannelManager] handleInit: Missing config data'); return { success: false, error: 'Missing config data' }; @@ -621,7 +629,9 @@ export class SWChannelManager { await this.cleanupDisconnectedClients(); // 初始化任务队列 + console.log('[SWChannelManager] handleInit: Calling taskQueue.initialize...'); await this.taskQueue?.initialize(data.geminiConfig, data.videoConfig); + console.log('[SWChannelManager] handleInit: taskQueue.initialize completed'); // 初始化工作流处理器 // 注意:不能只依赖 workflowHandlerInitialized 标志,因为 SW 空闲后模块级变量可能被重置 diff --git a/apps/web/src/sw/task-queue/queue.ts b/apps/web/src/sw/task-queue/queue.ts index d90fdd19..f0824a77 100644 --- a/apps/web/src/sw/task-queue/queue.ts +++ b/apps/web/src/sw/task-queue/queue.ts @@ -143,13 +143,24 @@ export class SWTaskQueue { private async restoreFromStorage(): Promise { try { // Load saved config + console.log('[SWTaskQueue] restoreFromStorage: Loading config from IndexedDB...'); const { geminiConfig, videoConfig } = await taskQueueStorage.loadConfig(); + console.log('[SWTaskQueue] restoreFromStorage: Loaded config:', { + hasGeminiConfig: !!geminiConfig, + hasVideoConfig: !!videoConfig, + apiKey: geminiConfig?.apiKey ? `${geminiConfig.apiKey.slice(0, 8)}...` : 'missing', + baseUrl: geminiConfig?.baseUrl || 'missing', + }); + if (geminiConfig && videoConfig) { this.geminiConfig = geminiConfig; this.videoConfig = videoConfig; this.initialized = true; this.hadSavedConfig = true; // Mark that we had valid config + console.log('[SWTaskQueue] restoreFromStorage: Config restored successfully'); + } else { + console.log('[SWTaskQueue] restoreFromStorage: No valid config found in IndexedDB'); } // Load all tasks @@ -491,11 +502,20 @@ export class SWTaskQueue { * - 后台操作完成后广播状态并恢复任务 */ async initialize(geminiConfig: GeminiConfig, videoConfig: VideoAPIConfig): Promise { + console.log('[SWTaskQueue] initialize called with:', { + apiKey: geminiConfig?.apiKey ? `${geminiConfig.apiKey.slice(0, 8)}...` : 'missing', + baseUrl: geminiConfig?.baseUrl || 'missing', + modelName: geminiConfig?.modelName || 'missing', + videoBaseUrl: videoConfig?.baseUrl || 'missing', + }); + // 立即保存配置到内存,不等待 IndexedDB this.geminiConfig = geminiConfig; this.videoConfig = videoConfig; this.initialized = true; + console.log('[SWTaskQueue] Config saved to memory, initialized:', this.initialized); + // 记录是否是首次初始化(在后台任务中使用) const isFirstTimeInit = !this.hadSavedConfig; this.hadSavedConfig = true; diff --git a/package.json b/package.json index 6a80b6d1..8e4722c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aitu/source", - "version": "0.5.75", + "version": "0.5.76", "license": "MIT", "packageManager": "pnpm@10.21.0", "description": "Opentu - An open-source whiteboard tool with AI-powered image/video creation, mind mapping, flowcharts, and freehand drawing", @@ -27,6 +27,7 @@ "build": "node scripts/update-version.js && nx run-many -t=build", "build:web": "node scripts/update-version.js && nx build web", "preview": "npx serve dist/apps/web -l 7288", + "preview:watch": "concurrently -k \"NODE_OPTIONS='--max-old-space-size=8192' nx build web --watch\" \"npx wait-on dist/apps/web/index.html && npx serve dist/apps/web -l 7288\"", "package": "npm run build && node scripts/create-deploy-package.js", "test": "nx run-many -t=test", "e2e": "nx e2e web-e2e", @@ -139,6 +140,7 @@ "@vitejs/plugin-react": "^4.2.0", "@vitest/ui": "^3.0.8", "autoprefixer": "^10.4.22", + "concurrently": "^9.2.1", "eslint": "~8.57.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-import": "2.27.5", @@ -161,6 +163,7 @@ "typescript": "~5.4.2", "vite": "^6.2.2", "vite-plugin-dts": "^4.5.3", - "vitest": "^3.0.8" + "vitest": "^3.0.8", + "wait-on": "^9.0.3" } } diff --git a/packages/drawnix/src/components/settings-dialog/settings-dialog.tsx b/packages/drawnix/src/components/settings-dialog/settings-dialog.tsx index b1d51a74..686dd3bd 100644 --- a/packages/drawnix/src/components/settings-dialog/settings-dialog.tsx +++ b/packages/drawnix/src/components/settings-dialog/settings-dialog.tsx @@ -17,6 +17,7 @@ import { VIDEO_MODELS, TEXT_MODELS, } from '../../constants/model-config'; +import { swChannelClient } from '../../services/sw-channel/client'; // 为了向后兼容,重新导出这些常量 export { IMAGE_MODEL_GROUPED_SELECT_OPTIONS as IMAGE_MODEL_GROUPED_OPTIONS } from '../../constants/model-config'; @@ -47,16 +48,56 @@ export const SettingsDialog = ({ } }, [appState.openSettings]); - const handleSave = () => { - // 使用全局设置管理器更新配置 - geminiSettings.update({ - apiKey: apiKey.trim(), - baseUrl: baseUrl.trim() || 'https://api.tu-zi.com/v1', - imageModelName: imageModelName.trim() || getDefaultImageModel(), - videoModelName: videoModelName.trim() || getDefaultVideoModel(), - textModelName: textModelName.trim() || getDefaultTextModel(), + const handleSave = async () => { + const trimmedApiKey = apiKey.trim(); + const trimmedBaseUrl = baseUrl.trim() || 'https://api.tu-zi.com/v1'; + const trimmedImageModel = imageModelName.trim() || getDefaultImageModel(); + const trimmedVideoModel = videoModelName.trim() || getDefaultVideoModel(); + const trimmedTextModel = textModelName.trim() || getDefaultTextModel(); + + // 使用全局设置管理器更新配置(必须等待完成) + await geminiSettings.update({ + apiKey: trimmedApiKey, + baseUrl: trimmedBaseUrl, + imageModelName: trimmedImageModel, + videoModelName: trimmedVideoModel, + textModelName: trimmedTextModel, }); + // 同步配置到 Service Worker(异步执行,不阻塞 UI) + (async () => { + try { + console.log('[SettingsDialog] Syncing config to SW...', { + apiKey: trimmedApiKey ? `${trimmedApiKey.slice(0, 8)}...` : 'empty', + baseUrl: trimmedBaseUrl, + imageModel: trimmedImageModel, + }); + + // 确保 SW channel 已初始化 + if (!swChannelClient.isInitialized()) { + console.log('[SettingsDialog] SW channel not initialized, initializing...'); + await swChannelClient.initialize(); + } + + // 发送最新配置到 SW + console.log('[SettingsDialog] Calling swChannelClient.init...'); + const result = await swChannelClient.init({ + geminiConfig: { + apiKey: trimmedApiKey, + baseUrl: trimmedBaseUrl, + modelName: trimmedImageModel, + }, + videoConfig: { + apiKey: trimmedApiKey, + baseUrl: trimmedBaseUrl, + }, + }); + console.log('[SettingsDialog] SW init result:', result); + } catch (error) { + console.warn('[SettingsDialog] Failed to sync config to SW:', error); + } + })(); + // 关闭弹窗 setAppState({ ...appState, openSettings: false }); }; diff --git a/packages/drawnix/src/utils/gemini-api/auth.ts b/packages/drawnix/src/utils/gemini-api/auth.ts index 67140aac..abe23568 100644 --- a/packages/drawnix/src/utils/gemini-api/auth.ts +++ b/packages/drawnix/src/utils/gemini-api/auth.ts @@ -161,6 +161,18 @@ export async function validateAndEnsureConfig(config: GeminiConfig): Promise(path: string, newValue: T): Promise { + // 对 apiKey 相关字段进行占位符检查 + if ((path.endsWith('.apiKey') || path === 'apiKey') && this.isPlaceholderValue(newValue)) { + console.warn(`[SettingsManager] Detected placeholder value for ${path}, ignoring:`, newValue); + return; + } + const keys = path.split('.'); const lastKey = keys.pop()!; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7f6c6a9b..e9892688 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -219,6 +219,9 @@ importers: autoprefixer: specifier: ^10.4.22 version: 10.4.23(postcss@8.5.6) + concurrently: + specifier: ^9.2.1 + version: 9.2.1 eslint: specifier: ~8.57.0 version: 8.57.1 @@ -288,6 +291,9 @@ importers: vitest: specifier: ^3.0.8 version: 3.2.4(@types/debug@4.1.12)(@types/node@18.16.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@22.1.0)(lightningcss@1.30.2)(sass@1.97.2)(terser@5.46.0) + wait-on: + specifier: ^9.0.3 + version: 9.0.3 packages/drawnix: dependencies: @@ -1323,6 +1329,26 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@hapi/address@5.1.1': + resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} + engines: {node: '>=14.0.0'} + + '@hapi/formula@3.0.2': + resolution: {integrity: sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==} + + '@hapi/hoek@11.0.7': + resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==} + + '@hapi/pinpoint@2.0.1': + resolution: {integrity: sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==} + + '@hapi/tlds@1.1.4': + resolution: {integrity: sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA==} + engines: {node: '>=14.0.0'} + + '@hapi/topo@6.0.2': + resolution: {integrity: sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==} + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -2674,6 +2700,9 @@ packages: peerDependencies: size-limit: 12.0.0 + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@stitches/core@1.2.8': resolution: {integrity: sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==} @@ -4044,6 +4073,11 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concurrently@9.2.1: + resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} + engines: {node: '>=18'} + hasBin: true + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -5507,6 +5541,10 @@ packages: jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + joi@18.0.2: + resolution: {integrity: sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==} + engines: {node: '>= 20'} + js-cookie@3.0.5: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} @@ -7008,6 +7046,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -7401,6 +7443,10 @@ packages: resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} engines: {node: '>=14'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -7787,6 +7833,11 @@ packages: resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} engines: {node: '>=14'} + wait-on@9.0.3: + resolution: {integrity: sha512-13zBnyYvFDW1rBvWiJ6Av3ymAaq8EDQuvxZnPIw3g04UqGi4TyoIJABmfJ6zrvKo9yeFQExNkOk7idQbDJcuKA==} + engines: {node: '>=20.0.0'} + hasBin: true + warning@4.0.3: resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} @@ -9202,6 +9253,22 @@ snapshots: '@floating-ui/utils@0.2.10': {} + '@hapi/address@5.1.1': + dependencies: + '@hapi/hoek': 11.0.7 + + '@hapi/formula@3.0.2': {} + + '@hapi/hoek@11.0.7': {} + + '@hapi/pinpoint@2.0.1': {} + + '@hapi/tlds@1.1.4': {} + + '@hapi/topo@6.0.2': + dependencies: + '@hapi/hoek': 11.0.7 + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -11022,6 +11089,8 @@ snapshots: - supports-color - utf-8-validate + '@standard-schema/spec@1.1.0': {} + '@stitches/core@1.2.8': {} '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.6)': @@ -12568,6 +12637,15 @@ snapshots: concat-map@0.0.1: {} + concurrently@9.2.1: + dependencies: + chalk: 4.1.2 + rxjs: 7.8.2 + shell-quote: 1.8.3 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + confbox@0.1.8: {} confbox@0.2.2: {} @@ -14279,6 +14357,16 @@ snapshots: jju@1.4.0: {} + joi@18.0.2: + dependencies: + '@hapi/address': 5.1.1 + '@hapi/formula': 3.0.2 + '@hapi/hoek': 11.0.7 + '@hapi/pinpoint': 2.0.1 + '@hapi/tlds': 1.1.4 + '@hapi/topo': 6.0.2 + '@standard-schema/spec': 1.1.0 + js-cookie@3.0.5: {} js-tokens@4.0.0: {} @@ -16425,6 +16513,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.8.3: {} + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -16901,6 +16991,8 @@ snapshots: dependencies: punycode: 2.3.1 + tree-kill@1.2.2: {} + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -17351,6 +17443,16 @@ snapshots: dependencies: xml-name-validator: 4.0.0 + wait-on@9.0.3: + dependencies: + axios: 1.13.2 + joi: 18.0.2 + lodash: 4.17.21 + minimist: 1.2.8 + rxjs: 7.8.2 + transitivePeerDependencies: + - debug + warning@4.0.3: dependencies: loose-envify: 1.4.0