import { useEffect, useMemo, useState } from "react"; import type { SessionUser } from "@/lib/api"; import type { PendingPrompt } from "@/lib/agents/pendingPrompts"; import type { AgentThread, Message } from "@/lib/agents/types"; import type { ModelSelection } from "@/lib/agents/useModelOptions"; import { AgentPromptBar } from "@/components/agents/AgentPromptBar"; import { AgentsShell } from "@/components/agents/AgentsSidebar"; import { MessageView } from "@/components/agents/ported"; import { useSendAgentMessage } from "@/lib/agents/queries"; import { dropPendingPrompts, getPendingPrompts } from "@/lib/agents/pendingPrompts"; import { useAgentThreadStream } from "@/lib/agents/useThreadStream"; import { useModelOptions } from "@/lib/agents/useModelOptions"; interface AgentThreadViewProps { user: SessionUser; thread: AgentThread; } export function AgentThreadView({ user, thread }: AgentThreadViewProps) { const sendMessage = useSendAgentMessage(thread.id); useAgentThreadStream(thread.id, thread.status === "running"); const [pendingPrompts, setPendingPrompts] = useState>(() => getPendingPrompts(thread.id), ); const { models, defaultSelection } = useModelOptions(); const threadSelection = useMemo(() => { if (!thread.model || !thread.effort) return null; const supported = models.some( (m) => m.id === thread.model && m.efforts.includes(thread.effort ?? ""), ); if (!supported) return null; return { modelId: thread.model, effort: thread.effort }; }, [models, thread.model, thread.effort]); const [selection, setSelection] = useState(null); const activeSelection = selection ?? threadSelection ?? defaultSelection; const userMessageTexts = useMemo(() => { return new Set( thread.messages .filter((m) => m.author === "user") .map((m) => m.chunks .filter((c) => c.kind === "text") .map((c) => c.text) .join(""), ), ); }, [thread.messages]); useEffect(() => { setPendingPrompts((prev) => { if (prev.length === 0) return prev; const next = dropPendingPrompts(thread.id, (entry) => userMessageTexts.has(entry.prompt), ); return next.length === prev.length ? prev : next; }); }, [thread.id, userMessageTexts]); const displayMessages = useMemo>(() => { if (pendingPrompts.length === 0) return thread.messages; const baseTimestamp = new Date().toISOString(); const result = thread.messages.slice(); pendingPrompts.forEach((entry, i) => { const synth: Message = { id: `pending-user-${i}`, author: "user", timestamp: baseTimestamp, chunks: [{ kind: "text", text: entry.prompt }], }; const at = Math.min(Math.max(entry.insertAt, 0), result.length); result.splice(at, 0, synth); }); return result; }, [thread.messages, pendingPrompts]); const hasMessages = displayMessages.length > 0; const isStreaming = thread.status === "running" || pendingPrompts.length > 0; return (
{hasMessages ? (
sendMessage.mutate({ content, model_id: activeSelection?.modelId ?? null, effort: activeSelection?.effort ?? null, }) } models={models} selection={activeSelection} onSelectionChange={setSelection} />
) : (

This thread has no messages yet.

sendMessage.mutate({ content, model_id: activeSelection?.modelId ?? null, effort: activeSelection?.effort ?? null, }) } models={models} selection={activeSelection} onSelectionChange={setSelection} />
)}
); }