feat: 模板AI区域化+默认配置优化+API密钥安全(20260419_2316)
- 默认模板: 手术步骤段落包裹进 .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
This commit is contained in:
@@ -63,16 +63,11 @@ export default function Login() {
|
||||
|
||||
const settingsRaw = storage.get<SystemSettings>('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,
|
||||
|
||||
@@ -736,7 +736,7 @@ export default function ReportEditor() {
|
||||
if (!videoRef.current || currentVideoIndex === -1) return;
|
||||
const video = videoRef.current;
|
||||
const settings = storage.get<SystemSettings>('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);
|
||||
|
||||
@@ -10,9 +10,9 @@ export default function SystemSettings() {
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const [settings, setSettings] = useState<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: '',
|
||||
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"
|
||||
/>
|
||||
|
||||
@@ -89,7 +89,7 @@ export interface SystemSettings {
|
||||
}
|
||||
|
||||
export const DEFAULT_AI_PROVIDERS: Record<string, AiProviderConfig> = {
|
||||
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: '' }
|
||||
|
||||
@@ -55,6 +55,9 @@ export const defaultReportContent = `
|
||||
<strong>手术步骤、术中出现的情况及处理:</strong>
|
||||
</p>
|
||||
|
||||
<div class="ai-region" data-ai-id="手术步骤" data-ai-title="手术步骤、术中出现的情况及处理" style="border: 1px dashed #3b82f6; padding: 16px 12px 12px; margin: 8px 0; position: relative; min-height: 60px; background: #f8fafc; border-radius: 6px;">
|
||||
<div contenteditable="false" style="position: absolute; top: -10px; right: 10px; background: #3b82f6; color: white; font-size: 10px; padding: 2px 8px; border-radius: 12px; z-index: 10; user-select: none;">手术步骤、术中出现的情况及处理-AI可编辑区域</div>
|
||||
<div class="ai-content" style="min-height: 20px;">
|
||||
<p style="font-family: SimSun; font-size: 12pt; line-height: 1.5; margin: 0; padding: 0;">
|
||||
1.患者仰卧位,麻醉成功后,常规消毒术野、铺无菌巾,于脐下穿刺建立CO2气腹,气腹压力为12mmHg,进镜探查无穿刺损伤,分别于剑突下2.0cm、右锁中线肋缘下2.0cm各点穿刺置穿刺器,插入相应手术器械。
|
||||
</p>
|
||||
@@ -74,6 +77,8 @@ export const defaultReportContent = `
|
||||
<p style="font-family: SimSun; font-size: 12pt; line-height: 1.5; margin: 0; padding: 0;">
|
||||
5.手术顺利,麻醉满意。切除的标本经家属过目后送病理。术中出血约 ml,术中输血成分,输血量,是否有输血不良反应。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 手术图片说明表格 -->
|
||||
<table style="width: 100%; border-collapse: collapse; margin: 20px 0; table-layout: fixed;">
|
||||
|
||||
@@ -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<T>(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<T>(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);
|
||||
}
|
||||
|
||||
138
工程分析/20260419_2316/功能变更实现方案文档.md
Normal file
138
工程分析/20260419_2316/功能变更实现方案文档.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# 功能变更实现方案文档(20260419_2316)
|
||||
|
||||
## 实现方案 A:模板手术步骤 AI 区域化
|
||||
|
||||
### 变更点
|
||||
`src/utils/defaultContent.ts` line 54-76。
|
||||
|
||||
### 具体实现
|
||||
将原有的:
|
||||
```html
|
||||
<p style="..."><strong>手术步骤、术中出现的情况及处理:</strong></p>
|
||||
<p>1.患者仰卧位...</p>
|
||||
<p>2.腹腔镜探查...</p>
|
||||
...
|
||||
```
|
||||
|
||||
替换为:
|
||||
```html
|
||||
<p style="..."><strong>手术步骤、术中出现的情况及处理:</strong></p>
|
||||
<div class="ai-region" data-ai-id="手术步骤" data-ai-title="手术步骤、术中出现的情况及处理" style="border: 1px dashed #3b82f6; padding: 16px 12px 12px; margin: 8px 0; position: relative; min-height: 60px; background: #f8fafc; border-radius: 6px;">
|
||||
<div contenteditable="false" style="position: absolute; top: -10px; right: 10px; background: #3b82f6; color: white; font-size: 10px; padding: 2px 8px; border-radius: 12px; z-index: 10; user-select: none;">手术步骤、术中出现的情况及处理-AI可编辑区域</div>
|
||||
<div class="ai-content" style="min-height: 20px;">
|
||||
<p>1.患者仰卧位...</p>
|
||||
<p>2.腹腔镜探查...</p>
|
||||
...
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实现方案 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<T>(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 加密保持向后兼容:旧明文数据可正常读取,新写入的数据自动加密
|
||||
- 所有变更均为默认值修改,不影响已有用户配置(除非手动重置)
|
||||
72
工程分析/20260419_2316/功能变更测试文档.md
Normal file
72
工程分析/20260419_2316/功能变更测试文档.md
Normal file
@@ -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 对话、视频分析功能不受影响
|
||||
52
工程分析/20260419_2316/功能变更需求文档.md
Normal file
52
工程分析/20260419_2316/功能变更需求文档.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# 功能变更需求文档(20260419_2316)
|
||||
|
||||
## 需求 1:模板手术步骤放入 AI 可编辑区域
|
||||
|
||||
### 问题背景
|
||||
当前默认模板中"手术步骤、术中出现的情况及处理"下的 5 个 `<p>` 段落是静态纯文本,用户希望将其完整包裹进 AI 专属可编辑区域(`.ai-region`),使 AI 可以直接对该部分内容进行生成和修改。
|
||||
|
||||
### 需求描述
|
||||
修改 `defaultContent.ts`,将 line 54-76 的手术步骤段落用 `.ai-region` 容器包裹,结构与其他 AI 区域保持一致:
|
||||
- `data-ai-id="手术步骤"`
|
||||
- `data-ai-title="手术步骤、术中出现的情况及处理"`
|
||||
- 顶部标签显示"手术步骤、术中出现的情况及处理-AI可编辑区域"
|
||||
- 内部 `.ai-content` 包含原有 5 个 `<p>` 段落
|
||||
|
||||
---
|
||||
|
||||
## 需求 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`
|
||||
Reference in New Issue
Block a user