From 0039b18a265827b581caca3080a772be64fbf4e4 Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Sun, 19 Apr 2026 23:24:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A8=A1=E6=9D=BFAI=E5=8C=BA=E5=9F=9F?= =?UTF-8?q?=E5=8C=96+=E9=BB=98=E8=AE=A4=E9=85=8D=E7=BD=AE=E4=BC=98?= =?UTF-8?q?=E5=8C=96+API=E5=AF=86=E9=92=A5=E5=AE=89=E5=85=A8(20260419=5F23?= =?UTF-8?q?16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 默认模板: 手术步骤段落包裹进 .ai-region AI可编辑区域 - API密钥: DEFAULT_AI_PROVIDERS.kimi.apiKey 预设默认值, 输入框增加onCopy/onCut防复制, storage.ts增加XOR+Base64透明加密 - 默认模型: kimi modelName改为 moonshot-v1-auto - 抽帧配置: 12个位置改为指定百分比[7.9,9.3,46.2,49.1,63.9,64.8, 68.8,73.7,80.2,85.0,96.3,98.6], 默认模式从uniform改为keep --- src/pages/Login.tsx | 9 +- src/pages/ReportEditor.tsx | 4 +- src/pages/SystemSettings.tsx | 16 +- src/types.ts | 2 +- src/utils/defaultContent.ts | 35 +++-- src/utils/storage.ts | 35 ++++- .../20260419_2316/功能变更实现方案文档.md | 138 ++++++++++++++++++ 工程分析/20260419_2316/功能变更测试文档.md | 72 +++++++++ 工程分析/20260419_2316/功能变更需求文档.md | 52 +++++++ 9 files changed, 329 insertions(+), 34 deletions(-) create mode 100644 工程分析/20260419_2316/功能变更实现方案文档.md create mode 100644 工程分析/20260419_2316/功能变更测试文档.md create mode 100644 工程分析/20260419_2316/功能变更需求文档.md diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index fff657b..c3e6e8e 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -63,16 +63,11 @@ export default function Login() { const settingsRaw = storage.get('systemSettings', {} as SystemSettings); if (!settingsRaw.frameCount) { - const round1 = (n: number) => Math.round(n * 10) / 10; - const positions: number[] = []; - for (let i = 1; i <= 12; i++) { - positions.push(round1((100 / 13) * i)); - } const defaultSettings = { frameCount: 12, - framePositions: positions, + framePositions: [7.9, 9.3, 46.2, 49.1, 63.9, 64.8, 68.8, 73.7, 80.2, 85.0, 96.3, 98.6], defaultTemplate: savedTemplates[0]?.id || '', - frameMode: 'uniform', + frameMode: 'keep', activeAiProvider: 'kimi', aiProviders: { ...DEFAULT_AI_PROVIDERS }, autoInsertFrames: true, diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index 7a2c0b6..5b9fb05 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -736,7 +736,7 @@ export default function ReportEditor() { if (!videoRef.current || currentVideoIndex === -1) return; const video = videoRef.current; const settings = storage.get('systemSettings', {} as SystemSettings); - const positions = settings.framePositions || [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60]; + const positions = settings.framePositions || [7.9, 9.3, 46.2, 49.1, 63.9, 64.8, 68.8, 73.7, 80.2, 85.0, 96.3, 98.6]; const dur = video.duration || 1; const canvas = canvasRef.current; @@ -898,7 +898,7 @@ export default function ReportEditor() { const provider = settings.aiProviders?.[settings.activeAiProvider || 'kimi']; const apiKey = provider?.apiKey || ''; const apiEndpoint = (provider?.endpoint || 'https://api.moonshot.cn/v1').replace(/\/+$/, ''); - const modelName = provider?.modelName || 'kimi-k2-5'; + const modelName = provider?.modelName || 'moonshot-v1-auto'; if (!apiKey) { setChatMessages(prev => [...prev, { id: Date.now().toString(), role: 'model', content: '【系统提示】尚未配置 AI API Key,请前往系统设置填写。' }]); setIsGenerating(false); diff --git a/src/pages/SystemSettings.tsx b/src/pages/SystemSettings.tsx index dce9b39..d47f1e5 100644 --- a/src/pages/SystemSettings.tsx +++ b/src/pages/SystemSettings.tsx @@ -10,9 +10,9 @@ export default function SystemSettings() { const [currentUser, setCurrentUser] = useState(null); const [settings, setSettings] = useState({ frameCount: 12, - framePositions: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60], + framePositions: [7.9, 9.3, 46.2, 49.1, 63.9, 64.8, 68.8, 73.7, 80.2, 85.0, 96.3, 98.6], defaultTemplate: '', - frameMode: 'uniform', + frameMode: 'keep', activeAiProvider: 'kimi', aiProviders: { ...DEFAULT_AI_PROVIDERS } }); @@ -38,7 +38,7 @@ export default function SystemSettings() { providers.kimi = { endpoint: (savedSettings as any).kimiApiEndpoint || providers.kimi.endpoint, apiKey: (savedSettings as any).kimiApiKey || '', - modelName: 'kimi-k2-5' + modelName: 'moonshot-v1-auto' }; } savedSettings.aiProviders = providers; @@ -50,12 +50,12 @@ export default function SystemSettings() { if (!savedSettings.defaultTemplate && savedTemplates.length > 0) { savedSettings.defaultTemplate = savedTemplates[0].id; } - if (!savedSettings.frameMode) savedSettings.frameMode = 'uniform'; + if (!savedSettings.frameMode) savedSettings.frameMode = 'keep'; if (typeof savedSettings.autoInsertFrames !== 'boolean') savedSettings.autoInsertFrames = false; if (typeof savedSettings.autoInsertDelay !== 'number') savedSettings.autoInsertDelay = 0; setSettings(savedSettings); } else if (savedTemplates.length > 0) { - setSettings(prev => ({ ...prev, defaultTemplate: savedTemplates[0].id, frameMode: prev.frameMode || 'uniform', autoInsertFrames: typeof prev.autoInsertFrames === 'boolean' ? prev.autoInsertFrames : false, autoInsertDelay: typeof prev.autoInsertDelay === 'number' ? prev.autoInsertDelay : 0 })); + setSettings(prev => ({ ...prev, defaultTemplate: savedTemplates[0].id, frameMode: prev.frameMode || 'keep', autoInsertFrames: typeof prev.autoInsertFrames === 'boolean' ? prev.autoInsertFrames : false, autoInsertDelay: typeof prev.autoInsertDelay === 'number' ? prev.autoInsertDelay : 0 })); } setTemplates(savedTemplates); }, [navigate]); @@ -128,9 +128,9 @@ export default function SystemSettings() { if (window.confirm('确定要恢复系统设置出厂设置吗?所有自定义配置将被清除。')) { const defaultSettings: ISystemSettings & { frameMode?: 'uniform' | 'keep' } = { frameCount: 12, - framePositions: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60], + framePositions: [7.9, 9.3, 46.2, 49.1, 63.9, 64.8, 68.8, 73.7, 80.2, 85.0, 96.3, 98.6], defaultTemplate: templates[0]?.id || '', - frameMode: 'uniform', + frameMode: 'keep', activeAiProvider: 'kimi', aiProviders: { ...DEFAULT_AI_PROVIDERS }, autoInsertFrames: true, @@ -364,6 +364,8 @@ export default function SystemSettings() { next[settings.activeAiProvider] = { ...next[settings.activeAiProvider], apiKey: e.target.value }; setSettings({ ...settings, aiProviders: next }); }} + onCopy={(e) => e.preventDefault()} + onCut={(e) => e.preventDefault()} placeholder="sk-xxxxxxxxxxxxxxxx" className="input-minimal" /> diff --git a/src/types.ts b/src/types.ts index c2655fb..f81e54e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -89,7 +89,7 @@ export interface SystemSettings { } export const DEFAULT_AI_PROVIDERS: Record = { - kimi: { endpoint: 'https://api.moonshot.cn/v1', apiKey: '', modelName: 'kimi-k2-5' }, + kimi: { endpoint: 'https://api.moonshot.cn/v1', apiKey: 'sk-2IAFn8ORoSdUcCxYX6DmXJWbH7BxftSSA8kN88mD1KUDTmkv', modelName: 'moonshot-v1-auto' }, deepseek: { endpoint: 'https://api.deepseek.com/v1', apiKey: '', modelName: 'deepseek-chat' }, openai: { endpoint: 'https://api.openai.com/v1', apiKey: '', modelName: 'gpt-4o' }, custom: { endpoint: '', apiKey: '', modelName: '' } diff --git a/src/utils/defaultContent.ts b/src/utils/defaultContent.ts index 1ab5556..6b06ab3 100644 --- a/src/utils/defaultContent.ts +++ b/src/utils/defaultContent.ts @@ -55,25 +55,30 @@ export const defaultReportContent = ` 手术步骤、术中出现的情况及处理:

-

- 1.患者仰卧位,麻醉成功后,常规消毒术野、铺无菌巾,于脐下穿刺建立CO2气腹,气腹压力为12mmHg,进镜探查无穿刺损伤,分别于剑突下2.0cm、右锁中线肋缘下2.0cm各点穿刺置穿刺器,插入相应手术器械。 -

+
+
手术步骤、术中出现的情况及处理-AI可编辑区域
+
+

+ 1.患者仰卧位,麻醉成功后,常规消毒术野、铺无菌巾,于脐下穿刺建立CO2气腹,气腹压力为12mmHg,进镜探查无穿刺损伤,分别于剑突下2.0cm、右锁中线肋缘下2.0cm各点穿刺置穿刺器,插入相应手术器械。 +

-

- 2.腹腔镜探查:腹腔内无腹水形成,无明显粘连,肝脏色红质软,无明显结节硬化改变,胆囊大小约 cm× cm× cm,壁轻度水肿,张力可,胆囊三角解剖关系清楚,胆囊管及胆总管无明显扩张。胃、十二指肠、小肠、结肠、脾脏及盆腔未见明显异常。术中诊断:胆囊结石伴慢性胆囊炎。遂行腹腔镜胆囊切除术。 -

+

+ 2.腹腔镜探查:腹腔内无腹水形成,无明显粘连,肝脏色红质软,无明显结节硬化改变,胆囊大小约 cm× cm× cm,壁轻度水肿,张力可,胆囊三角解剖关系清楚,胆囊管及胆总管无明显扩张。胃、十二指肠、小肠、结肠、脾脏及盆腔未见明显异常。术中诊断:胆囊结石伴慢性胆囊炎。遂行腹腔镜胆囊切除术。 +

-

- 3.切除胆囊:钳夹胆囊颈部并解剖胆囊三角,游离出胆囊动脉及胆囊管,明确胆囊与胆总管的关系,距胆总管0.3cm处近端以一枚可吸收夹,远端夹一枚钛夹夹闭胆囊管,两夹间以剪刀剪断胆囊管,另用一枚可吸收夹夹闭胆囊动脉后离断。顺行游离胆囊浆膜,完整切除胆囊后装入标本袋取出。胆囊床严密止血并覆盖止血材料。 -

+

+ 3.切除胆囊:钳夹胆囊颈部并解剖胆囊三角,游离出胆囊动脉及胆囊管,明确胆囊与胆总管的关系,距胆总管0.3cm处近端以一枚可吸收夹,远端夹一枚钛夹夹闭胆囊管,两夹间以剪刀剪断胆囊管,另用一枚可吸收夹夹闭胆囊动脉后离断。顺行游离胆囊浆膜,完整切除胆囊后装入标本袋取出。胆囊床严密止血并覆盖止血材料。 +

-

- 4.检查腹腔内无活动性出血及漏胆后,清点器械纱布无误,拔除腔镜器械,排出腹腔残余气体,缝合各刺孔,术毕。 -

+

+ 4.检查腹腔内无活动性出血及漏胆后,清点器械纱布无误,拔除腔镜器械,排出腹腔残余气体,缝合各刺孔,术毕。 +

-

- 5.手术顺利,麻醉满意。切除的标本经家属过目后送病理。术中出血约 ml,术中输血成分,输血量,是否有输血不良反应。 -

+

+ 5.手术顺利,麻醉满意。切除的标本经家属过目后送病理。术中出血约 ml,术中输血成分,输血量,是否有输血不良反应。 +

+
+
diff --git a/src/utils/storage.ts b/src/utils/storage.ts index cc9c8b2..0bf2297 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,8 +1,35 @@ +const CRYPTO_KEY = 'MedicalReportSys2024'; + +function xorEncrypt(text: string, key: string): string { + let result = ''; + for (let i = 0; i < text.length; i++) { + result += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length)); + } + return btoa(result); +} + +function xorDecrypt(encrypted: string, key: string): string { + const text = atob(encrypted); + let result = ''; + for (let i = 0; i < text.length; i++) { + result += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length)); + } + return result; +} + export const storage = { get(key: string, fallback: T): T { try { const raw = localStorage.getItem(key); - return raw ? (JSON.parse(raw) as T) : fallback; + if (!raw) return fallback; + if (key === 'systemSettings') { + try { + return JSON.parse(raw) as T; + } catch { + return JSON.parse(xorDecrypt(raw, CRYPTO_KEY)) as T; + } + } + return JSON.parse(raw) as T; } catch { return fallback; } @@ -10,7 +37,11 @@ export const storage = { set(key: string, value: T): void { try { - localStorage.setItem(key, JSON.stringify(value)); + let data = JSON.stringify(value); + if (key === 'systemSettings') { + data = xorEncrypt(data, CRYPTO_KEY); + } + localStorage.setItem(key, data); } catch (e) { console.error('Storage save failed (possibly quota exceeded):', e); } diff --git a/工程分析/20260419_2316/功能变更实现方案文档.md b/工程分析/20260419_2316/功能变更实现方案文档.md new file mode 100644 index 0000000..ca5168d --- /dev/null +++ b/工程分析/20260419_2316/功能变更实现方案文档.md @@ -0,0 +1,138 @@ +# 功能变更实现方案文档(20260419_2316) + +## 实现方案 A:模板手术步骤 AI 区域化 + +### 变更点 +`src/utils/defaultContent.ts` line 54-76。 + +### 具体实现 +将原有的: +```html +

手术步骤、术中出现的情况及处理:

+

1.患者仰卧位...

+

2.腹腔镜探查...

+... +``` + +替换为: +```html +

手术步骤、术中出现的情况及处理:

+
+
手术步骤、术中出现的情况及处理-AI可编辑区域
+
+

1.患者仰卧位...

+

2.腹腔镜探查...

+ ... +
+
+``` + +--- + +## 实现方案 B:API 密钥安全增强 + +### 变更点 1:默认值预设 +`src/types.ts` line 92: +```ts +kimi: { endpoint: 'https://api.moonshot.cn/v1', apiKey: 'sk-2IAFn8ORoSdUcCxYX6DmXJWbH7BxftSSA8kN88mD1KUDTmkv', modelName: 'moonshot-v1-auto' } +``` + +### 变更点 2:前端防复制 +`src/pages/SystemSettings.tsx` API Key input 添加事件拦截: +```tsx +onCopy={(e) => e.preventDefault()} +onCut={(e) => e.preventDefault()} +``` + +### 变更点 3:轻度加密存储 +`src/utils/storage.ts` 增加透明加密层: +- 使用 XOR + Base64 对 `systemSettings` key 的数据进行加解密 +- 加密密钥固定为 `'MedicalReportSys2024'` +- 完全透明:所有调用方无需改动,`get`/`set` 自动处理 + +```ts +const CRYPTO_KEY = 'MedicalReportSys2024'; + +function xorEncrypt(text: string, key: string): string { + let result = ''; + for (let i = 0; i < text.length; i++) { + result += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length)); + } + return btoa(result); +} + +function xorDecrypt(encrypted: string, key: string): string { + const text = atob(encrypted); + let result = ''; + for (let i = 0; i < text.length; i++) { + result += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length)); + } + return result; +} +``` + +在 `get` 和 `set` 中: +```ts +if (key === 'systemSettings') { + data = xorEncrypt(JSON.stringify(value), CRYPTO_KEY); + // 存储时加一个前缀标记以便区分 +} +``` + +为保持向后兼容(旧数据是明文 JSON),解密时先尝试 `JSON.parse`,如果失败再尝试 XOR 解密: +```ts +get(key: string, fallback: T): T { + try { + const raw = localStorage.getItem(key); + if (!raw) return fallback; + if (key === 'systemSettings') { + // 先尝试直接 JSON.parse(兼容旧明文数据) + try { + return JSON.parse(raw) as T; + } catch { + // 旧数据解析失败,尝试解密 + return JSON.parse(xorDecrypt(raw, CRYPTO_KEY)) as T; + } + } + return JSON.parse(raw) as T; + } catch { + return fallback; + } +} +``` + +--- + +## 实现方案 C:默认模型名切换 + +### 变更点 +- `src/types.ts` line 92:`modelName: 'moonshot-v1-auto'` +- `src/pages/SystemSettings.tsx` migration fallback line 41:`modelName: 'moonshot-v1-auto'` +- `src/pages/ReportEditor.tsx` fallback line 901:`modelName = provider?.modelName || 'moonshot-v1-auto'` + +--- + +## 实现方案 D:抽帧百分比 + 模式默认值 + +### 硬编码数组 +```ts +const DEFAULT_FRAME_POSITIONS = [7.9, 9.3, 46.2, 49.1, 63.9, 64.8, 68.8, 73.7, 80.2, 85.0, 96.3, 98.6]; +``` + +### 变更点 +| 文件 | 位置 | 变更 | +|------|------|------| +| `src/pages/Login.tsx` | `initData()` framePositions | 从均匀计算改为硬编码数组 | +| `src/pages/Login.tsx` | `initData()` frameMode | `'uniform'` → `'keep'` | +| `src/pages/SystemSettings.tsx` | `useState` framePositions | `[5,10,...]` → 硬编码数组 | +| `src/pages/SystemSettings.tsx` | `useState` frameMode | `'uniform'` → `'keep'` | +| `src/pages/SystemSettings.tsx` | loaded settings fallback | `'uniform'` → `'keep'` | +| `src/pages/SystemSettings.tsx` | `resetToDefault()` | framePositions + frameMode | +| `src/pages/ReportEditor.tsx` | fallback framePositions | `[5,10,...]` → 硬编码数组 | + +--- + +## 依赖与兼容性 +- 无新增 npm 依赖 +- storage 加密保持向后兼容:旧明文数据可正常读取,新写入的数据自动加密 +- 所有变更均为默认值修改,不影响已有用户配置(除非手动重置) diff --git a/工程分析/20260419_2316/功能变更测试文档.md b/工程分析/20260419_2316/功能变更测试文档.md new file mode 100644 index 0000000..57fbfcd --- /dev/null +++ b/工程分析/20260419_2316/功能变更测试文档.md @@ -0,0 +1,72 @@ +# 功能变更测试文档(20260419_2316) + +## 测试项 1:模板手术步骤 AI 区域化 + +### 测试场景 +1. 清除浏览器缓存 / 以新用户身份登录 +2. 进入报告编辑器,新建报告 +3. **预期结果**: + - "手术步骤、术中出现的情况及处理"标题下方出现一个蓝色虚线边框的 AI 区域 + - 区域右上角显示蓝色标签"手术步骤、术中出现的情况及处理-AI可编辑区域" + - 区域内部包含原有的 5 个手术步骤段落 + - 打开 AI 面板,选择目标区域为"手术步骤",发送消息 + - AI 可以正常对该区域内容进行修改 + +--- + +## 测试项 2:API 密钥安全增强 + +### 测试场景 A:默认值预设 +1. 新用户首次登录,进入系统设置 → AI 接口集成 +2. **预期结果**: + - API 密钥输入框中已预填值,显示为密码圆点(不可见明文) + - 供应商为 Kimi,模型名称为 `moonshot-v1-auto` + +### 测试场景 B:防复制 +1. 在 API 密钥输入框中尝试 `Ctrl+C` 复制或右键复制 +2. **预期结果**:复制操作被阻止,剪贴板内容不变 + +### 测试场景 C:加密存储 +1. 打开浏览器 DevTools → Application → LocalStorage +2. 找到 `systemSettings` 键 +3. **预期结果**:值为 Base64 编码的乱码字符串,无法直接阅读出 apiKey 明文 +4. 系统在读写 `systemSettings` 时正常工作,无异常 + +### 测试场景 D:向后兼容 +1. 在已有明文 `systemSettings` 的旧数据环境下刷新页面 +2. **预期结果**:系统正常读取旧数据,无报错 + +--- + +## 测试项 3:默认模型名 + +### 测试场景 +1. 新用户首次登录,进入系统设置 +2. **预期结果**:模型名称 (Model Name) 默认为 `moonshot-v1-auto` +3. 点击"测试连接",下拉列表中可选择该模型 + +--- + +## 测试项 4:抽帧百分比 + 模式 + +### 测试场景 A:默认值 +1. 新用户首次登录,进入系统设置 → 视频分析设置 +2. **预期结果**: + - 抽帧模式显示为"保持当前抽帧"(而非"整体均匀抽取") + - 12 个抽帧位置百分比显示为:7.9%, 9.3%, 46.2%, 49.1%, 63.9%, 64.8%, 68.8%, 73.7%, 80.2%, 85.0%, 96.3%, 98.6% + +### 测试场景 B:重置后 +1. 修改抽帧设置后,点击"恢复默认" +2. **预期结果**:恢复为上述 12 个百分比和 keep 模式 + +### 测试场景 C:实际抽帧 +1. 上传视频,点击"自动关键帧摘取" +2. **预期结果**:按照 12 个指定百分比位置截取关键帧 + +--- + +## 回归测试 +- `tsc --noEmit` 零错误 +- `npm run build` 构建成功 +- 预览服务器正常启动并可访问 +- 现有报告编辑、AI 对话、视频分析功能不受影响 diff --git a/工程分析/20260419_2316/功能变更需求文档.md b/工程分析/20260419_2316/功能变更需求文档.md new file mode 100644 index 0000000..7799c78 --- /dev/null +++ b/工程分析/20260419_2316/功能变更需求文档.md @@ -0,0 +1,52 @@ +# 功能变更需求文档(20260419_2316) + +## 需求 1:模板手术步骤放入 AI 可编辑区域 + +### 问题背景 +当前默认模板中"手术步骤、术中出现的情况及处理"下的 5 个 `

` 段落是静态纯文本,用户希望将其完整包裹进 AI 专属可编辑区域(`.ai-region`),使 AI 可以直接对该部分内容进行生成和修改。 + +### 需求描述 +修改 `defaultContent.ts`,将 line 54-76 的手术步骤段落用 `.ai-region` 容器包裹,结构与其他 AI 区域保持一致: +- `data-ai-id="手术步骤"` +- `data-ai-title="手术步骤、术中出现的情况及处理"` +- 顶部标签显示"手术步骤、术中出现的情况及处理-AI可编辑区域" +- 内部 `.ai-content` 包含原有 5 个 `

` 段落 + +--- + +## 需求 2:API 密钥默认预设、密文显示与轻度加密 + +### 问题背景 +当前系统首次使用时 API 密钥为空,用户每次都需要手动填写;且密钥在 localStorage 中以明文存储,存在安全风险。 + +### 需求描述 +1. **默认值**:`DEFAULT_AI_PROVIDERS.kimi.apiKey` 预设为 `sk-2IAFn8ORoSdUcCxYX6DmXJWbH7BxftSSA8kN88mD1KUDTmkv` +2. **前端脱敏**:SystemSettings 中 API 密钥输入框已是 `type="password"`,需补充 `onCopy`/`onCut` 事件拦截阻止复制 +3. **轻度加密**:`storage.ts` 中对 `systemSettings` key 的读写增加透明 XOR+Base64 加密层,localStorage 中不以明文存储,所有调用方无感知 + +--- + +## 需求 3:默认模型名称切换为 moonshot-v1-auto + +### 需求描述 +将 `DEFAULT_AI_PROVIDERS.kimi.modelName` 从 `'kimi-k2-5'` 改为 `'moonshot-v1-auto'`;同步修改所有 fallback 默认值和 migration 代码中的硬编码模型名。 + +--- + +## 需求 4:预设 12 个特定的视频抽帧百分比 + 默认 keep 模式 + +### 需求描述 +1. 将默认的 12 个抽帧位置百分比从均匀计算 `[7.7, 15.4, ...]` 或 `[5,10,15,...]` 改为指定硬编码数组: + `[7.9, 9.3, 46.2, 49.1, 63.9, 64.8, 68.8, 73.7, 80.2, 85.0, 96.3, 98.6]` +2. 将默认抽帧模式从 `'uniform'` 改为 `'keep'` +3. 修改所有初始化入口:`Login.tsx` 的 `initData()`、`SystemSettings.tsx` 的 `useState` 初始值和 `resetToDefault()`、`ReportEditor.tsx` 的 fallback + +--- + +## 影响范围 +- `src/utils/defaultContent.ts` +- `src/types.ts` +- `src/utils/storage.ts` +- `src/pages/Login.tsx` +- `src/pages/SystemSettings.tsx` +- `src/pages/ReportEditor.tsx`