import { MessageDelivery } from '@/components/message-delivery' import { useEffect, useRef, useState } from 'react' import type { FormEvent } from 'react' import { Link } from 'react-router-dom' import { Send } from 'lucide-react' import { Button } from '@/components/ui/button' import { Textarea } from '@/components/ui/textarea' import { Alert, AlertDescription } from '@/components/ui/alert' import { useWorkbench } from '@/lib/context' import { request, errorText, messageKey } from '@/lib/api' import { useLocalDraft } from '@/lib/use-local-draft' 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 local = useLocalDraft(state.instance_id ? [state.instance_id, 'communication', selected] : null, { text: '', attempt: undefined as {text:string;key:string}|undefined }, (v): v is {text:string;attempt?:{text:string;key:string}} => Boolean(v && typeof v === 'object' && typeof (v as {text?:unknown}).text === 'string')) const [error, setError] = useState('') const [sending, setSending] = useState(false) const [refreshVersion, setRefreshVersion] = useState(0) const sendingRef = useRef(false) 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]) useEffect(() => { const reload = () => { setRefreshVersion(n => n + 1); void refresh().catch(e => setError(errorText(e))) } window.addEventListener('workbench-refresh-conversation', reload) return () => window.removeEventListener('workbench-refresh-conversation', reload) }, [refresh]) 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 decisions = (state as { decisions?: { id: string; status: string }[] }).decisions?.filter(d => ['open','needs_reconsideration'].includes(d.status)) || [] const text = local.value.text async function send(event: FormEvent) { event.preventDefault() if (!session || !text.trim() || sendingRef.current || !local.ready) return const sid = selected, body = text const attempt = local.value.attempt?.text === body ? local.value.attempt : {text:body,key:messageKey()} if (!local.save({text:body,attempt})) return sendingRef.current = true setSending(true); setError('') try { await request(token, '/api/messages', { session_id:sid, text:body, idempotency_key:attempt.key, source:'human' }) local.clear() setRefreshVersion(n => n + 1) await refresh() } catch (e) { setError(errorText(e)) } finally { sendingRef.current=false; setSending(false) } } return
{decisions.length > 0 &&

有 {decisions.length} 项需要你决定,查看决策

} {error && {error}}
{items.length ? items.map(m =>

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

{m.text}

{m.log_id && 查看日志}
) :
{known ? '有什么想了解的?从这里开始交流。' : '会话准备好后,即可在这里交流。'}
}
{local.error &&

{local.error}

}