fix: use App.useApp modal + Button delete for reliable click handling
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 8s
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 8s
This commit is contained in:
parent
7438323e96
commit
cf2dfc1351
@ -1,5 +1,5 @@
|
||||
import { useState, useRef, useEffect, useCallback } from 'react'
|
||||
import { Input, Button, Avatar, Spin, theme, Modal, Tooltip, Typography } from 'antd'
|
||||
import { Input, Button, Avatar, Spin, theme, Typography, App } from 'antd'
|
||||
import {
|
||||
SendOutlined, RobotOutlined, UserOutlined, PlusOutlined,
|
||||
DeleteOutlined, StopOutlined, MessageOutlined,
|
||||
@ -20,7 +20,8 @@ interface Message {
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export default function TaskAssistant() {
|
||||
function ChatPage() {
|
||||
const { modal } = App.useApp()
|
||||
const [conversations, setConversations] = useState<Conversation[]>([])
|
||||
const [activeId, setActiveId] = useState<string | null>(null)
|
||||
const [messages, setMessages] = useState<Message[]>([])
|
||||
@ -28,23 +29,21 @@ export default function TaskAssistant() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [editingId, setEditingId] = useState<string | null>(null)
|
||||
const [editTitle, setEditTitle] = useState('')
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const abortRef = useRef<AbortController | null>(null)
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||
const editInputRef = useRef<any>(null)
|
||||
const { token } = theme.useToken()
|
||||
|
||||
// Load conversations
|
||||
const loadConversations = useCallback(async () => {
|
||||
try { setConversations(await listConversations()) } catch { /* */ }
|
||||
}, [])
|
||||
useEffect(() => { loadConversations() }, [loadConversations])
|
||||
|
||||
// Scroll to bottom
|
||||
useEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
||||
}, [messages])
|
||||
|
||||
// Load messages when switching conversation
|
||||
const switchConversation = useCallback(async (id: string) => {
|
||||
if (loading) { abortRef.current?.abort(); setLoading(false) }
|
||||
setActiveId(id)
|
||||
@ -58,7 +57,6 @@ export default function TaskAssistant() {
|
||||
} catch { /* */ }
|
||||
}, [loading])
|
||||
|
||||
// New conversation
|
||||
const handleNew = async () => {
|
||||
if (loading) { abortRef.current?.abort(); setLoading(false) }
|
||||
try {
|
||||
@ -70,27 +68,31 @@ export default function TaskAssistant() {
|
||||
} catch { /* */ }
|
||||
}
|
||||
|
||||
// Delete conversation
|
||||
const handleDelete = async (id: string) => {
|
||||
Modal.confirm({
|
||||
title: '删除对话', content: '确定要删除这个对话吗?',
|
||||
okText: '删除', okType: 'danger', cancelText: '取消',
|
||||
const handleDelete = (id: string) => {
|
||||
modal.confirm({
|
||||
title: '删除对话',
|
||||
content: '确定要删除这个对话吗?',
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
await deleteConversation(id).catch(() => {})
|
||||
setDeleting(true)
|
||||
try {
|
||||
await deleteConversation(id)
|
||||
setConversations(prev => prev.filter(c => c.id !== id))
|
||||
if (activeId === id) { setActiveId(null); setMessages([]) }
|
||||
} catch { /* */ }
|
||||
setDeleting(false)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Start editing title
|
||||
const startEdit = (conv: Conversation) => {
|
||||
setEditingId(conv.id)
|
||||
setEditTitle(conv.title)
|
||||
setTimeout(() => editInputRef.current?.focus(), 50)
|
||||
}
|
||||
|
||||
// Save title
|
||||
const saveTitle = async (id: string) => {
|
||||
const title = editTitle.trim()
|
||||
if (title && title !== conversations.find(c => c.id === id)?.title) {
|
||||
@ -100,7 +102,6 @@ export default function TaskAssistant() {
|
||||
setEditingId(null)
|
||||
}
|
||||
|
||||
// Send message
|
||||
const handleSend = async () => {
|
||||
const text = input.trim()
|
||||
if (!text || loading) return
|
||||
@ -128,11 +129,10 @@ export default function TaskAssistant() {
|
||||
loadConversations()
|
||||
}
|
||||
|
||||
const assistantMsg: Message = {
|
||||
setMessages(prev => [...prev, {
|
||||
id: (Date.now() + 1).toString(), role: 'assistant',
|
||||
content: result.content, timestamp: Date.now(),
|
||||
}
|
||||
setMessages(prev => [...prev, assistantMsg])
|
||||
}])
|
||||
loadConversations()
|
||||
} catch (err: any) {
|
||||
if (err.name === 'AbortError') return
|
||||
@ -155,7 +155,6 @@ export default function TaskAssistant() {
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', height: 'calc(100vh - 112px)', gap: 12 }}>
|
||||
{/* Sidebar */}
|
||||
<div style={{
|
||||
width: 260, flexShrink: 0, display: 'flex', flexDirection: 'column',
|
||||
background: token.colorBgContainer, borderRadius: token.borderRadiusLG,
|
||||
@ -184,18 +183,18 @@ export default function TaskAssistant() {
|
||||
style={{ flex: 1, fontSize: 13 }}
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
ellipsis
|
||||
style={{ flex: 1, fontSize: 13, lineHeight: '28px' }}
|
||||
onDoubleClick={e => { e.stopPropagation(); startEdit(conv) }}
|
||||
>{conv.title}</Text>
|
||||
<Text ellipsis style={{ flex: 1, fontSize: 13, lineHeight: '28px' }}
|
||||
onDoubleClick={e => { e.stopPropagation(); startEdit(conv) }}>
|
||||
{conv.title}
|
||||
</Text>
|
||||
)}
|
||||
<Tooltip title="删除">
|
||||
<DeleteOutlined
|
||||
<Button
|
||||
type="text" size="small" danger
|
||||
icon={<DeleteOutlined />}
|
||||
disabled={deleting}
|
||||
onClick={e => { e.stopPropagation(); handleDelete(conv.id) }}
|
||||
style={{ fontSize: 12, color: token.colorTextQuaternary, marginLeft: 4, cursor: 'pointer' }}
|
||||
style={{ marginLeft: 4, flexShrink: 0 }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
))}
|
||||
{conversations.length === 0 && (
|
||||
@ -208,9 +207,7 @@ export default function TaskAssistant() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chat area */}
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
|
||||
{/* Messages */}
|
||||
<div style={{
|
||||
flex: 1, overflowY: 'auto', background: token.colorBgContainer,
|
||||
borderRadius: `${token.borderRadiusLG}px ${token.borderRadiusLG}px 0 0`,
|
||||
@ -270,9 +267,8 @@ export default function TaskAssistant() {
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{/* Input area — redesigned */}
|
||||
<div style={{
|
||||
background: token.colorBgContainer, borderTop: 'none',
|
||||
background: token.colorBgContainer,
|
||||
border: `1px solid ${token.colorBorderSecondary}`,
|
||||
borderRadius: `0 0 ${token.borderRadiusLG}px ${token.borderRadiusLG}px`,
|
||||
padding: '16px 20px',
|
||||
@ -293,18 +289,12 @@ export default function TaskAssistant() {
|
||||
style={{ flex: 1, resize: 'none', padding: '4px 0', fontSize: 14 }}
|
||||
/>
|
||||
{loading ? (
|
||||
<Button
|
||||
danger type="primary" icon={<StopOutlined />} onClick={handleStop}
|
||||
style={{ borderRadius: 8, height: 34, minWidth: 72 }}>
|
||||
停止
|
||||
</Button>
|
||||
<Button danger type="primary" icon={<StopOutlined />} onClick={handleStop}
|
||||
style={{ borderRadius: 8, height: 34, minWidth: 72 }}>停止</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary" icon={<SendOutlined />} onClick={handleSend}
|
||||
<Button type="primary" icon={<SendOutlined />} onClick={handleSend}
|
||||
disabled={!input.trim() || !activeId}
|
||||
style={{ borderRadius: 8, height: 34, minWidth: 72 }}>
|
||||
发送
|
||||
</Button>
|
||||
style={{ borderRadius: 8, height: 34, minWidth: 72 }}>发送</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@ -312,3 +302,11 @@ export default function TaskAssistant() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function TaskAssistant() {
|
||||
return (
|
||||
<App>
|
||||
<ChatPage />
|
||||
</App>
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user