import { useEffect, useRef, useState } from 'react' import type { FormEvent } from 'react' import { Send, RefreshCw } from 'lucide-react' import { Button } from '@/components/ui/button' import { Textarea } from '@/components/ui/textarea' import { Alert, AlertDescription } from '@/components/ui/alert' import { PageHeading } from '@/components/shared' import { useWorkbench } from '@/lib/context' import { request, errorText, messageKey } from '@/lib/api' import type { Message } from '@/lib/types' export default function Communication() { const { state, token, refresh } = useWorkbench() const primary = state.primary_session_id || 'human-progress' const selected = primary const session = state.sessions.find(s => s.id === selected) const [messages, setMessages] = useState<{ session: string; items: Message[] }>({ session: '', items: [] }) const [drafts, setDrafts] = useState>({}) const [error, setError] = useState('') const [sending, setSending] = useState(false) const [refreshVersion, setRefreshVersion] = useState(0) const pending = useRef<{ session_id: string; text: string; idempotency_key: string } | null>(null) const scroll = useRef(null) const loaded = useRef('') const known = Boolean(session) useEffect(() => { if (!known) return const controller = new AbortController() let inFlight = false const poll = async () => { if (inFlight) return inFlight = true try { const result = await request<{ messages: Message[] }>(token, `/api/conversation?session_id=${encodeURIComponent(selected)}`, undefined, controller.signal) if (!controller.signal.aborted) { setMessages({ session: selected, items: result.messages }); setError('') } } catch (e) { if (!controller.signal.aborted) setError(errorText(e)) } finally { inFlight = false } } void poll() const timer = setInterval(() => void poll(), 3000) return () => { controller.abort(); clearInterval(timer) } }, [selected, token, known, refreshVersion]) const items = messages.session === selected ? messages.items : [] const lastId = items.at(-1)?.id useEffect(() => { const box = scroll.current if (!box || messages.session !== selected) return if (loaded.current !== selected || box.scrollHeight - box.scrollTop - box.clientHeight < 300) box.scrollTop = box.scrollHeight loaded.current = selected }, [lastId, selected, messages.session]) const text = drafts[selected] || '' async function send(event: FormEvent) { event.preventDefault() if (!session || !text.trim() || sending) return const sid = selected, body = text if (!pending.current || pending.current.session_id !== sid || pending.current.text !== body) pending.current = { session_id: sid, text: body, idempotency_key: messageKey() } setSending(true); setError('') try { await request(token, '/api/messages', { ...pending.current, source: 'human' }) pending.current = null setDrafts(old => old[sid] === body ? { ...old, [sid]: '' } : old) setRefreshVersion(n => n + 1) await refresh() } catch (e) { setError(errorText(e)) } finally { setSending(false) } } return
{ setRefreshVersion(n => n + 1); void refresh() }}>刷新} /> {error && {error}}
{items.length ? items.map(m =>

{m.source === 'human' ? '你' : '项目助手'}

{m.text}

{m.source === 'human' && ['queued', 'claimed'].includes(m.status) &&

正在等待回复…

} {m.source === 'human' && ['failed', 'uncertain'].includes(m.status) &&

暂未确认回复,请稍后查看;无需重复发送。

}
) :
{known ? '有什么想了解的?从这里开始交流。' : '会话准备好后,即可在这里交流。'}
}