feat: 升级至 v0.3.1 — 全量代码审计修复 + 安全增强
本次升级基于完整代码审查,修复 Critical/High/Medium/Low 四级共 96 项问题, 并通过返工审计修复 10 项遗留问题,tsc 双端类型检查零错误。 Critical (10/10 完成): - C-4: command.ts 接入 shell-quote 进行 token-level 注入检测,替代原有正则匹配 可防御 r"m" -rf /、$'rm'、$(echo rm) 等字符串拼接绕过 High (11/11 完成): - 竞态保护、Promise.allSettled、AbortController 资源泄漏、IPC 参数校验等 Medium (55/55 完成): - 事务保护、敏感数据脱敏、枚举校验、MUI v9 Stack prop 迁移、 React 组件 cancelled 标志、类型收窄等 Low (20/20 完成): - 辅助方法提取(flushToolCallBuffer/scoreAndPushMemory/tryAddColumn 等) - nanoid 统一替代 Date.now()+Math.random() - confirm() 替换为 MUI Dialog、useMemo 缓存、魔法数字命名化等 返工审计修复 (10/10 完成): - L-11: LogsSettings 残留的原生 confirm()/alert() 全部替换为 MUI Dialog/Alert - M-53: MemoryViewer handleSearch 独立 ref,修复 searching 状态卡死 - M-42: 脱敏短值(length <= 4)泄露修复 - M-47: tasks:update 补全 title/description 类型校验 - L-9: ollama.adapter 非流式路径 nanoid 统一 - M-45: audit:query limit 策略与 memory:listAll 一致化 - SettingsModal handleConfirmRemove 补全 try/catch + loadServers cleanup - L-15: CommandPalette useMemo 补全 sessions 响应式依赖 - useAgentStream 事件类型补全 seq/timestamp 字段 新增依赖: shell-quote + @types/shell-quote 版本号: 0.3.0 -> 0.3.1
This commit is contained in:
@@ -10,8 +10,9 @@
|
||||
*/
|
||||
|
||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import { Box, Typography, IconButton, Tooltip, Stack, Button, Paper } from '@mui/material';
|
||||
import { Box, Typography, IconButton, Tooltip, Stack, Button, Paper, InputBase } from '@mui/material';
|
||||
import { Send, Paperclip, Square, X, FileText, Image as ImageIcon } from 'lucide-react';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useAgentStore } from '@renderer/stores/agent-store';
|
||||
import { useSessionStore } from '@renderer/stores/session-store';
|
||||
import { useUIStore } from '@renderer/stores/ui-store';
|
||||
@@ -67,7 +68,8 @@ export function ChatInput(): React.JSX.Element {
|
||||
|
||||
const processFile = useCallback(async (file: File): Promise<Attachment> => {
|
||||
const type = classifyFile(file);
|
||||
const attachment: Attachment = { id: `att_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`, file, type };
|
||||
// L-10 修复: 统一使用 nanoid 生成附件 ID(与项目其他位置一致)
|
||||
const attachment: Attachment = { id: `att_${nanoid(6)}`, file, type };
|
||||
|
||||
if (type === 'image') {
|
||||
// 图片转 base64 data URL → 压缩(限制 1024px, JPEG 0.7)
|
||||
@@ -241,8 +243,18 @@ export function ChatInput(): React.JSX.Element {
|
||||
const handleAbort = useCallback(() => { abort(); }, [abort]);
|
||||
|
||||
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
||||
// Ctrl+Enter — 换行
|
||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
// H-9 修复: 快捷键对齐规范
|
||||
// @see docs/MetonaAI-Desktop UI UX 设计集成方案.html — 快捷键规范
|
||||
// 规范要求: Cmd/Ctrl+Enter = 发送消息, Cmd/Ctrl+Shift+Enter = 换行
|
||||
// 之前代码是 Ctrl+Enter=换行, Enter=发送,与规范相反
|
||||
// 修复后:
|
||||
// Cmd/Ctrl+Enter = 发送消息(规范要求)
|
||||
// Cmd/Ctrl+Shift+Enter = 换行(规范要求)
|
||||
// Enter = 发送消息(保持聊天应用习惯)
|
||||
// Shift+Enter = 换行(textarea 默认行为,无需处理)
|
||||
|
||||
// Cmd/Ctrl+Shift+Enter — 换行(规范要求)
|
||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey) && e.shiftKey) {
|
||||
e.preventDefault();
|
||||
const t = e.currentTarget as HTMLTextAreaElement;
|
||||
const s = t.selectionStart;
|
||||
@@ -251,12 +263,19 @@ export function ChatInput(): React.JSX.Element {
|
||||
requestAnimationFrame(() => { t.selectionStart = t.selectionEnd = s + 1; });
|
||||
return;
|
||||
}
|
||||
// Enter — 发送
|
||||
// Cmd/Ctrl+Enter — 发送消息(规范要求,优先于 Enter)
|
||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
return;
|
||||
}
|
||||
// Enter(不带修饰键)— 发送消息(保持聊天应用习惯)
|
||||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); return; }
|
||||
if (e.key === 'Escape' && showSlashMenu) { setShowSlashMenu(false); return; }
|
||||
}, [handleSend, showSlashMenu]);
|
||||
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
// H-8 修复: 使用 InputBase 后,onChange 类型需兼容 HTMLInputElement | HTMLTextAreaElement
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
||||
const v = e.target.value; setInput(v);
|
||||
if (v === '/') { setShowSlashMenu(true); setSlashFilter(''); }
|
||||
else if (v.startsWith('/') && !v.includes(' ')) { setShowSlashMenu(true); setSlashFilter(v.slice(1).toLowerCase()); }
|
||||
@@ -298,12 +317,35 @@ export function ChatInput(): React.JSX.Element {
|
||||
|
||||
<input ref={fileInputRef} type="file" multiple accept={supportsImages ? 'image/*,.txt,.md,.json,.csv,.ts,.tsx,.js,.jsx,.py,.rb,.go,.rs,.java,.c,.cpp,.h,.css,.html,.xml,.yaml,.yml,.toml,.ini,.sh,.sql,.log,.pdf' : '.txt,.md,.json,.csv,.ts,.tsx,.js,.jsx,.py,.rb,.go,.rs,.java,.c,.cpp,.h,.css,.html,.xml,.yaml,.yml,.toml,.ini,.sh,.sql,.log,.pdf'} style={{ display: 'none' }} onChange={handleFileChange} />
|
||||
|
||||
<textarea
|
||||
ref={textareaRef} data-chat-input value={input} onChange={handleChange} onKeyDown={handleKeyDown}
|
||||
{/* H-8 修复: 使用 MUI InputBase 替代原生 textarea — 遵循 MUI 强制使用规范 */}
|
||||
{/* @see standard/开发规范.md — MUI 强制使用、禁止自写 UI 组件 */}
|
||||
<InputBase
|
||||
inputRef={textareaRef}
|
||||
data-chat-input
|
||||
value={input}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onPaste={handlePaste}
|
||||
placeholder="输入消息... (Enter 发送, Ctrl+Enter 换行, / 命令)" disabled={isStreaming}
|
||||
placeholder="输入消息... (Cmd/Ctrl+Enter 发送, Cmd/Ctrl+Shift+Enter 换行, / 命令)"
|
||||
disabled={isStreaming}
|
||||
multiline
|
||||
rows={1}
|
||||
style={{ width: '100%', background: 'transparent', border: 'none', outline: 'none', color: 'inherit', fontSize: 14, lineHeight: '28px', resize: 'none', fontFamily: 'inherit', minHeight: 42, maxHeight: 160 }}
|
||||
sx={{
|
||||
width: '100%',
|
||||
color: 'inherit',
|
||||
fontSize: 14,
|
||||
lineHeight: '28px',
|
||||
fontFamily: 'inherit',
|
||||
'& .MuiInputBase-input': {
|
||||
padding: 0,
|
||||
resize: 'none',
|
||||
minHeight: 42,
|
||||
maxHeight: 160,
|
||||
},
|
||||
'&::before, &::after': {
|
||||
display: 'none',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack direction="row" sx={{ mt: 1, minHeight: 32, justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
|
||||
Reference in New Issue
Block a user