refactor: 清理 P1 类型债与 React key 债

- 消除 useChat.ts 中 13 处 (message as any).timestamp,改用已收窄变量的 .timestamp
- 给 ExecutionCancelled 接口补 timestamp?: number 字段,与后端对齐
- 修复 MessageInput 附件列表 key={index}: 给 FileAttachment 加 id 字段(crypto.randomUUID())
- 修复 ConfigPage TagEditor key={i} → key={t}(tag 值唯一)
- 修复 ConfigPage SourceEditor customPaths key={i} → key={p}(路径值唯一)
This commit is contained in:
oudecheng 2026-07-07 17:53:53 +08:00
parent 7eb2933ca5
commit f8d5f0253a
4 changed files with 21 additions and 17 deletions

View File

@ -16,6 +16,7 @@ interface MessageInputProps {
} }
interface FileAttachment { interface FileAttachment {
id: string
file: File file: File
attachment: Attachment attachment: Attachment
preview?: string // 用于图片预览 preview?: string // 用于图片预览
@ -92,6 +93,7 @@ export function MessageInput({
} }
const fileAttachment: FileAttachment = { const fileAttachment: FileAttachment = {
id: crypto.randomUUID(),
file, file,
attachment, attachment,
preview: mediaType === 'image' ? base64 : undefined, preview: mediaType === 'image' ? base64 : undefined,
@ -170,6 +172,7 @@ export function MessageInput({
} }
const fileAttachment: FileAttachment = { const fileAttachment: FileAttachment = {
id: crypto.randomUUID(),
file, file,
attachment, attachment,
preview: mediaType === 'image' ? base64 : undefined, preview: mediaType === 'image' ? base64 : undefined,
@ -295,7 +298,7 @@ export function MessageInput({
<div className="mb-2 flex flex-wrap gap-2"> <div className="mb-2 flex flex-wrap gap-2">
{attachments.map((att, index) => ( {attachments.map((att, index) => (
<div <div
key={index} key={att.id}
className="flex items-center gap-2 rounded-lg border border-[var(--border-color)] bg-[var(--bg-tertiary)] px-2 py-1.5 text-sm" className="flex items-center gap-2 rounded-lg border border-[var(--border-color)] bg-[var(--bg-tertiary)] px-2 py-1.5 text-sm"
> >
{att.preview ? ( {att.preview ? (

View File

@ -192,7 +192,7 @@ function TagEditor({ tags, onChange }: { tags: string[]; onChange: (t: string[])
<div className="space-y-2"> <div className="space-y-2">
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1.5">
{tags.map((t, i) => ( {tags.map((t, i) => (
<span key={i} className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-[var(--accent-cyan)]/10 border border-[var(--accent-cyan)]/20 text-xs text-[var(--accent-cyan)]"> <span key={t} className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-[var(--accent-cyan)]/10 border border-[var(--accent-cyan)]/20 text-xs text-[var(--accent-cyan)]">
{t} {t}
<button onClick={() => onChange(tags.filter((_, j) => j !== i))} className="hover:text-white transition-colors"><X className="h-3 w-3" /></button> <button onClick={() => onChange(tags.filter((_, j) => j !== i))} className="hover:text-white transition-colors"><X className="h-3 w-3" /></button>
</span> </span>
@ -286,8 +286,8 @@ function SourceEditor({
<div className="text-xs font-medium text-[var(--text-muted)] uppercase tracking-wider"></div> <div className="text-xs font-medium text-[var(--text-muted)] uppercase tracking-wider"></div>
{customPaths.length > 0 && ( {customPaths.length > 0 && (
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1.5">
{customPaths.map((p, i) => ( {customPaths.map((p) => (
<span key={i} className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-[var(--accent-cyan)]/10 border border-[var(--accent-cyan)]/20 text-xs text-[var(--accent-cyan)] font-mono"> <span key={p} className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-[var(--accent-cyan)]/10 border border-[var(--accent-cyan)]/20 text-xs text-[var(--accent-cyan)] font-mono">
{p} {p}
<button onClick={() => removeCustom(p)} className="hover:text-white transition-colors"><X className="h-3 w-3" /></button> <button onClick={() => removeCustom(p)} className="hover:text-white transition-colors"><X className="h-3 w-3" /></button>
</span> </span>

View File

@ -236,7 +236,7 @@ export function useChat(): UseChatReturn {
id: msg.id, id: msg.id,
role: role as ChatMessage['role'], role: role as ChatMessage['role'],
content: msg.content, content: msg.content,
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000), timestamp: msg.timestamp ?? Math.floor(Date.now() / 1000),
type: 'message', type: 'message',
attachments: msg.attachments, attachments: msg.attachments,
subagentTaskId: msg.subagent_task_id, subagentTaskId: msg.subagent_task_id,
@ -249,7 +249,7 @@ export function useChat(): UseChatReturn {
id: msg.id, id: msg.id,
role: 'tool', role: 'tool',
content: msg.content, content: msg.content,
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000), timestamp: msg.timestamp ?? Math.floor(Date.now() / 1000),
type: 'tool_call', type: 'tool_call',
toolName: msg.tool_name, toolName: msg.tool_name,
toolCallId: msg.tool_call_id, toolCallId: msg.tool_call_id,
@ -264,7 +264,7 @@ export function useChat(): UseChatReturn {
id: msg.id, id: msg.id,
role: 'tool', role: 'tool',
content: msg.content, content: msg.content,
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000), timestamp: msg.timestamp ?? Math.floor(Date.now() / 1000),
type: 'tool_result', type: 'tool_result',
toolName: msg.tool_name, toolName: msg.tool_name,
toolCallId: msg.tool_call_id, toolCallId: msg.tool_call_id,
@ -278,7 +278,7 @@ export function useChat(): UseChatReturn {
id: msg.id, id: msg.id,
role: 'tool', role: 'tool',
content: `${msg.content}\n\n${msg.resume_hint}`, content: `${msg.content}\n\n${msg.resume_hint}`,
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000), timestamp: msg.timestamp ?? Math.floor(Date.now() / 1000),
type: 'tool_pending', type: 'tool_pending',
toolName: msg.tool_name, toolName: msg.tool_name,
toolCallId: msg.tool_call_id, toolCallId: msg.tool_call_id,
@ -302,7 +302,7 @@ export function useChat(): UseChatReturn {
id: generateMessageId(), id: generateMessageId(),
role: 'assistant', role: 'assistant',
content: `Error: ${message.message}`, content: `Error: ${message.message}`,
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000), timestamp: message.timestamp ?? Math.floor(Date.now() / 1000),
type: 'message', type: 'message',
} }
} }
@ -362,7 +362,7 @@ export function useChat(): UseChatReturn {
id: generateMessageId(), id: generateMessageId(),
role: 'assistant', role: 'assistant',
content: `Error: ${errMsg.message}`, content: `Error: ${errMsg.message}`,
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000), timestamp: errMsg.timestamp ?? Math.floor(Date.now() / 1000),
type: 'message', type: 'message',
} }
setSubAgentStack((prev) => { setSubAgentStack((prev) => {
@ -423,7 +423,7 @@ export function useChat(): UseChatReturn {
id: generateMessageId(), id: generateMessageId(),
role: 'assistant', role: 'assistant',
content: `Error: ${errMsg.message}`, content: `Error: ${errMsg.message}`,
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000), timestamp: errMsg.timestamp ?? Math.floor(Date.now() / 1000),
type: 'message', type: 'message',
} }
const newStack = [...prev] const newStack = [...prev]
@ -800,7 +800,7 @@ export function useChat(): UseChatReturn {
id: msg.id, id: msg.id,
role, role,
content: msg.content, content: msg.content,
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000), timestamp: msg.timestamp ?? Math.floor(Date.now() / 1000),
type: 'message', type: 'message',
attachments: msg.attachments, attachments: msg.attachments,
reasoningContent: msg.reasoning_content, reasoningContent: msg.reasoning_content,
@ -833,7 +833,7 @@ export function useChat(): UseChatReturn {
id: msg.id, id: msg.id,
role: 'tool', role: 'tool',
content: msg.content, content: msg.content,
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000), timestamp: msg.timestamp ?? Math.floor(Date.now() / 1000),
type: 'tool_call', type: 'tool_call',
toolName: msg.tool_name, toolName: msg.tool_name,
toolCallId: msg.tool_call_id, toolCallId: msg.tool_call_id,
@ -856,7 +856,7 @@ export function useChat(): UseChatReturn {
id: msg.id, id: msg.id,
role: 'tool', role: 'tool',
content: msg.content, content: msg.content,
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000), timestamp: msg.timestamp ?? Math.floor(Date.now() / 1000),
type: 'tool_result', type: 'tool_result',
toolName: msg.tool_name, toolName: msg.tool_name,
toolCallId: msg.tool_call_id, toolCallId: msg.tool_call_id,
@ -877,7 +877,7 @@ export function useChat(): UseChatReturn {
id: msg.id, id: msg.id,
role: 'tool', role: 'tool',
content: `${msg.content}\n\n${msg.resume_hint}`, content: `${msg.content}\n\n${msg.resume_hint}`,
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000), timestamp: msg.timestamp ?? Math.floor(Date.now() / 1000),
type: 'tool_pending', type: 'tool_pending',
toolName: msg.tool_name, toolName: msg.tool_name,
toolCallId: msg.tool_call_id, toolCallId: msg.tool_call_id,
@ -893,7 +893,7 @@ export function useChat(): UseChatReturn {
id: generateMessageId(), id: generateMessageId(),
role: 'assistant', role: 'assistant',
content: (message as { type: 'execution_cancelled'; message: string }).message, content: (message as { type: 'execution_cancelled'; message: string }).message,
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000), timestamp: message.timestamp ?? Math.floor(Date.now() / 1000),
type: 'message', type: 'message',
}, },
]) ])
@ -910,7 +910,7 @@ export function useChat(): UseChatReturn {
id: generateMessageId(), id: generateMessageId(),
role: 'assistant', role: 'assistant',
content: `Error: ${message.message}`, content: `Error: ${message.message}`,
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000), timestamp: message.timestamp ?? Math.floor(Date.now() / 1000),
type: 'message', type: 'message',
}, },
]) ])

View File

@ -260,6 +260,7 @@ export interface TaskMessagesLoaded {
export interface ExecutionCancelled { export interface ExecutionCancelled {
type: 'execution_cancelled' type: 'execution_cancelled'
message: string message: string
timestamp?: number
} }
export interface StreamDelta { export interface StreamDelta {