'use client'; import { useState } from 'react'; import { Ban, Send } from 'lucide-react'; import { toast } from 'sonner'; import type { Doc } from '@spoon/backend/convex/_generated/dataModel.js'; import { Badge, Button, Textarea } from '@spoon/ui'; export const AgentThread = ({ jobId, messages, events, interactions, disabled, agentTurnActive, }: { jobId: string; messages: Doc<'agentJobMessages'>[]; events: Doc<'agentJobEvents'>[]; interactions: Doc<'agentInteractionRequests'>[]; disabled: boolean; agentTurnActive: boolean; }) => { const [content, setContent] = useState(''); const [sending, setSending] = useState(false); const [replying, setReplying] = useState(); const send = async () => { if (!content.trim()) return; setSending(true); try { const response = await fetch(`/api/agent-jobs/${jobId}/message`, { method: 'POST', body: JSON.stringify({ content }), }); if (!response.ok) throw new Error(await response.text()); setContent(''); } catch (error) { console.error(error); toast.error('Could not send message.'); } finally { setSending(false); } }; const abort = async () => { try { const response = await fetch(`/api/agent-jobs/${jobId}/agent/abort`, { method: 'POST', }); if (!response.ok) throw new Error(await response.text()); toast.success('Agent turn aborted.'); } catch (error) { console.error(error); toast.error('Could not abort agent.'); } }; const reply = async ( interaction: Doc<'agentInteractionRequests'>, responseValue: string, ) => { setReplying(interaction._id); try { const response = await fetch( `/api/agent-jobs/${jobId}/interactions/${interaction._id}/reply`, { method: 'POST', body: JSON.stringify({ externalRequestId: interaction.externalRequestId, response: responseValue, }), }, ); if (!response.ok) throw new Error(await response.text()); toast.success('Response sent.'); } catch (error) { console.error(error); toast.error('Could not answer interaction.'); } finally { setReplying(undefined); } }; return (

Agent thread

Messages, tool activity, and requests persist with this workspace.

{interactions.map((interaction) => (
{interaction.title} {interaction.status}

{interaction.body}

{interaction.status === 'pending' ? (
) : null}
))} {messages.map((message) => (
{message.role} {message.status}

{message.content || (message.status === 'streaming' ? 'Working...' : '')}

))} {events.slice(-20).map((event) => (
{event.phase} / {event.level} {new Date(event.createdAt).toLocaleTimeString()}

{event.message}

))}