From 427691290b8a166cf46f190eee5a0e9693269c57 Mon Sep 17 00:00:00 2001 From: WangDL Date: Fri, 22 May 2026 19:09:17 +0800 Subject: [PATCH] feat: pixel-level DeepSeek UI clone --- src/pages/TaskAssistant.tsx | 203 ++++++++++++++++++++---------------- 1 file changed, 114 insertions(+), 89 deletions(-) diff --git a/src/pages/TaskAssistant.tsx b/src/pages/TaskAssistant.tsx index 2426478..2fd31a6 100644 --- a/src/pages/TaskAssistant.tsx +++ b/src/pages/TaskAssistant.tsx @@ -1,8 +1,9 @@ import { useState, useRef, useEffect, useCallback } from 'react' import { Input, Button, theme, Typography, App } from 'antd' import { - SendOutlined, RobotOutlined, PlusOutlined, ToolOutlined, + SendOutlined, PlusOutlined, ToolOutlined, DeleteOutlined, StopOutlined, MessageOutlined, + ArrowUpOutlined, } from '@ant-design/icons' import { streamChat, resolveApproval, type StreamEvent } from '@/services/ai-chat' import { @@ -30,7 +31,6 @@ function ChatPage() { const [waitingApproval, setWaitingApproval] = useState(false) const [editingId, setEditingId] = useState(null) const [editTitle, setEditTitle] = useState('') - const [deleting, setDeleting] = useState(false) const composingRef = useRef(false) const abortRef = useRef(null) const messagesEndRef = useRef(null) @@ -54,11 +54,9 @@ function ChatPage() { } const handleDelete = (id: string) => modal.confirm({ - title: '删除对话', okText: '删除', okType: 'danger', cancelText: '取消', + title: 'Delete conversation?', okText: 'Delete', okType: 'danger', cancelText: 'Cancel', onOk: async () => { - setDeleting(true) try { await deleteConversation(id); setConversations(prev => prev.filter(c => c.id !== id)); if (activeId === id) { setActiveId(null); setMessages([]) } } catch {} - setDeleting(false) }, }) @@ -73,7 +71,7 @@ function ChatPage() { setMessages(prev => prev.map(m => m.id === approvalMsg.id ? { ...m, approval: { ...m.approval!, resolved: true } } : m)) setWaitingApproval(false) await resolveApproval(approvalMsg.approval!.runId, choice) - message.success(choice === 'deny' ? '已拒绝' : '已批准') + message.success(choice === 'deny' ? 'Denied' : 'Approved') } const handleSend = async () => { @@ -136,134 +134,161 @@ function ChatPage() { const handleStop = () => { abortRef.current?.abort(); setStreaming(false) } return ( -
- {/* Sidebar */} -
-
- +
+ {/* Sidebar — DeepSeek style: no border, subtle bg */} +
+
+
-
+
{conversations.map(conv => ( -
activeId !== conv.id && switchConversation(conv.id)} - style={{ display: 'flex', alignItems: 'center', padding: '8px 12px', borderRadius: 8, cursor: 'pointer', marginBottom: 2, - background: activeId === conv.id ? token.colorFillSecondary : 'transparent' }}> - +
activeId !== conv.id && switchConversation(conv.id)} + style={{ + display: 'flex', alignItems: 'center', padding: '10px 12px', borderRadius: 8, cursor: 'pointer', marginBottom: 2, + background: activeId === conv.id ? '#e8e8ed' : 'transparent', + transition: 'background 0.15s', + }} + onMouseEnter={e => { if (activeId !== conv.id) e.currentTarget.style.background = '#ececf0' }} + onMouseLeave={e => { if (activeId !== conv.id) e.currentTarget.style.background = 'transparent' }}> + {editingId === conv.id ? ( setEditTitle(e.target.value)} onBlur={() => saveTitle(conv.id)} onPressEnter={() => saveTitle(conv.id)} onClick={e => e.stopPropagation()} style={{ flex: 1, fontSize: 13 }} bordered={false} /> ) : ( - { e.stopPropagation(); startEdit(conv) }}>{conv.title} + { e.stopPropagation(); startEdit(conv) }}>{conv.title} )} -
))}
+
- {/* Chat area */} -
-
+ {/* Chat */} +
+ {/* Messages */} +
{messages.length === 0 ? ( -
-
- -
- 有什么可以帮你的? - Hermes Agent · 可执行任务、操作文件 +
+ What can I help with?
) : ( -
+
{messages.map(msg => ( -
- {/* Header */} -
-
- {msg.role === 'user' ? 'Y' : 'H'} -
- {msg.role === 'user' ? 'You' : 'Hermes'} - {msg.streaming && !msg.content && Thinking...} +
+ {/* Role label — DeepSeek style: small bold text, no avatar */} +
+ + {msg.role === 'user' ? 'You' : 'Hermes'} + + {msg.streaming && !msg.content && !msg.toolCalls?.length && !msg.approval && ( + + )}
- {/* Approval card */} + {/* Approval */} {msg.approval && !msg.approval.resolved && ( -
-
-
- -
-
- 需要确认操作 -
- {msg.approval.command} -
- {msg.approval.description} -
- {msg.approval.choices.map(c => ( - - ))} -
-
+
+ Approval Required +
+ $ {msg.approval.command} +
+ {msg.approval.description} +
+ {msg.approval.choices.map(c => ( + + ))}
)} - {/* Tool calls */} + {/* Tool calls — subtle pills */} {msg.toolCalls && msg.toolCalls.length > 0 && ( -
+
{msg.toolCalls.map((t, i) => ( -
- - {t.preview || t.tool} - {t.done && {t.error ? 'failed' : t.duration?.toFixed(1) + 's'}} +
+ + {t.preview || t.tool} + {t.done && {t.error ? 'failed' : 'done'}}
))}
)} - {/* Content */} -
-
- {msg.role === 'assistant' - ? (msg.content ? : (!msg.toolCalls?.length && !msg.approval ? Thinking... : null)) - :
{msg.content}
} -
+ {/* Content — DeepSeek style: no bubble, just clean text */} +
+ {msg.role === 'assistant' + ? (msg.content ? : null) + : {msg.content}}
))} -
+
)}
- {/* Input */} -
-
-
- setInput(e.target.value)} - onCompositionStart={() => { composingRef.current = true }} onCompositionEnd={() => { composingRef.current = false }} - onKeyDown={e => { if (composingRef.current) return; if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend() } }} - placeholder={activeId ? 'Message Hermes...' : 'Select or create a conversation'} - autoSize={{ minRows: 1, maxRows: 6 }} disabled={streaming || !activeId || waitingApproval} - variant="borderless" style={{ flex: 1, resize: 'none', padding: '4px 0', fontSize: 14 }} /> + {/* Input — DeepSeek style: minimal, centered */} +
+
+
+ setInput(e.target.value)} + onCompositionStart={() => { composingRef.current = true }} + onCompositionEnd={() => { composingRef.current = false }} + onKeyDown={e => { + if (composingRef.current) return + if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend() } + }} + placeholder={activeId ? 'Ask anything' : 'Select a conversation'} + autoSize={{ minRows: 1, maxRows: 6 }} + disabled={streaming || !activeId || waitingApproval} + variant="borderless" + style={{ flex: 1, resize: 'none', padding: '4px 0', fontSize: 15, lineHeight: 1.5 }} + /> {streaming ? ( -
+ + Hermes Agent · DeepSeek · xhigh reasoning +
+ +
) }