chore: bump version to 0.5.76

This commit is contained in:
liquidliang committed 2026-02-05 16:54:33 +08:00
1 parent fba4fe7f57
commit 22ef45ad02
9 files changed
+252 -24

No files matched your search

+14
View File
@@ -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 <ljquan@qq.com>
## 0.5.75 (2026-02-05)
### 🚀 Features
+3 -12
View File
@@ -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": []
}
@@ -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 空闲后模块级变量可能被重置
+20
View File
@@ -143,13 +143,24 @@ export class SWTaskQueue {
private async restoreFromStorage(): Promise<void> {
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<void> {
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;
+5 -2
View File
@@ -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"
}
}
@@ -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 });
};
+29 -2
View File
@@ -161,6 +161,18 @@ export async function validateAndEnsureConfig(config: GeminiConfig): Promise<Gem
return config;
}
/**
* 检查字符串是否是占位符格式
* 如 {key}、${key}、{{key}}、{apiKey} 等
*/
function isPlaceholder(value: string | null | undefined): boolean {
if (!value || typeof value !== 'string') return false;
// 匹配 {xxx}、${xxx}、{{xxx}} 等占位符格式
return /^[{$]*\{?\w+\}?\}*$/.test(value) ||
value.includes('{key}') ||
value.includes('${');
}
/**
* 从URL参数中获取apiKey
*/
@@ -168,7 +180,15 @@ function getApiKeyFromUrl(): string | null {
if (typeof window === 'undefined') return null;
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('apiKey');
const apiKey = urlParams.get('apiKey');
// 验证 apiKey 不是占位符格式
if (isPlaceholder(apiKey)) {
console.warn('[Auth] Detected placeholder in URL apiKey, ignoring:', apiKey);
return null;
}
return apiKey;
}
/**
@@ -185,8 +205,15 @@ function getSettingsFromUrl(): { apiKey?: string; baseUrl?: string } | null {
try {
const decoded = decodeURIComponent(settingsParam);
const settings = JSON.parse(decoded);
// 验证 apiKey 不是占位符格式
const apiKey = isPlaceholder(settings.key) ? undefined : settings.key;
if (settings.key && isPlaceholder(settings.key)) {
console.warn('[Auth] Detected placeholder in settings.key, ignoring:', settings.key);
}
return {
apiKey: settings.key,
apiKey,
baseUrl: settings.url
};
} catch (error) {
@@ -239,6 +239,7 @@ class SettingsManager {
const decoded = decodeURIComponent(settingsParam);
const urlSettings = JSON.parse(decoded);
// updateSetting 已有占位符检查,会自动忽略无效值
if (urlSettings.key) {
this.updateSetting('gemini.apiKey', urlSettings.key);
}
@@ -248,6 +249,7 @@ class SettingsManager {
}
// 处理apiKey参数
// updateSetting 已有占位符检查,会自动忽略无效值
const apiKey = urlParams.get('apiKey');
if (apiKey) {
this.updateSetting('gemini.apiKey', apiKey);
@@ -339,10 +341,28 @@ class SettingsManager {
return value as T;
}
/**
* 检查字符串是否是占位符格式
* 如 {key}、${key}、{{key}}、{apiKey} 等
*/
private isPlaceholderValue(value: unknown): boolean {
if (!value || typeof value !== 'string') return false;
// 匹配 {xxx}、${xxx}、{{xxx}} 等占位符格式
return /^[{$]*\{?\w+\}?\}*$/.test(value) ||
value.includes('{key}') ||
value.includes('${');
}
/**
* 更新特定设置值(支持点记号法)
*/
public async updateSetting<T = any>(path: string, newValue: T): Promise<void> {
// 对 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()!;
+102
View File
@@ -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