"use client"; import { CopilotKit, useCoAgent, useCopilotAction, useCopilotContext, } from "@copilotkit/react-core"; import { CopilotChat } from "@copilotkit/react-ui"; import "@copilotkit/react-ui/styles.css"; import { useState } from "react"; import DocumentSelector from "./DocumentSelector"; import StateDisplay from "./StateDisplay"; interface SearchAnswer { query: string; answer: string; confidence: number; cited_chunks: string[]; citations: { document_id: string; chunk_id: string; document_uri: string; document_title?: string; page_numbers: number[]; headings?: string[]; content: string; }[]; } interface ResearchState { context: { original_question: string; sub_questions: string[]; qa_responses: SearchAnswer[]; }; iterations: number; max_iterations: number; confidence_threshold: number; max_concurrency: number; last_eval: { new_questions: string[]; confidence_score: number; is_sufficient: boolean; reasoning: string; } | null; result?: { title: string; executive_summary: string; main_findings: string[]; conclusions: string[]; limitations: string[]; recommendations: string[]; sources_summary: string; }; current_activity?: string; current_activity_message?: string; documentFilter?: string[]; } interface DecisionArgs { original_question: string; sub_questions: string[]; qa_responses: SearchAnswer[]; } type DecisionAction = "search" | "synthesize" | "modify_questions"; interface DecisionResult { action: DecisionAction; questions?: string[]; } function DecisionUI({ args, onResolve, }: { args: DecisionArgs; onResolve: (result: DecisionResult) => void | Promise; }) { const [editableQuestions, setEditableQuestions] = useState( args.sub_questions || [], ); const [newQuestion, setNewQuestion] = useState(""); const [submitting, setSubmitting] = useState(false); const qaCount = args.qa_responses?.length || 0; const hasQuestions = editableQuestions.length > 0; const canSearch = hasQuestions && !submitting; const canSynthesize = qaCount > 0 && !submitting; const questionsModified = editableQuestions.length !== args.sub_questions.length || editableQuestions.some((q, i) => q !== args.sub_questions[i]); const handleSubmit = (action: DecisionAction, questions?: string[]) => { setSubmitting(true); onResolve({ action, questions }); }; const handleSearch = () => { handleSubmit( questionsModified ? "modify_questions" : "search", editableQuestions, ); }; const handleSynthesize = () => { handleSubmit("synthesize"); }; const handleRemoveQuestion = (index: number) => { if (submitting) return; setEditableQuestions(editableQuestions.filter((_, i) => i !== index)); }; const handleAddQuestion = () => { if (submitting || !newQuestion.trim()) return; setEditableQuestions([...editableQuestions, newQuestion.trim()]); setNewQuestion(""); }; if (submitting) { return null; } return (
Research Decision Point
{qaCount} answers collected
Pending Questions ({editableQuestions.length}):
{editableQuestions.map((q, idx) => (
{q}
))}
setNewQuestion(e.target.value)} placeholder="Add a new question..." style={{ flex: 1, padding: "0.5rem", border: "1px solid #cbd5e1", borderRadius: "4px", fontSize: "0.85rem", }} onKeyDown={(e) => { if (e.key === "Enter") handleAddQuestion(); }} />
); } const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8000"; function AgentContent() { const { state, setState, running } = useCoAgent({ name: "research_agent", initialState: { context: { original_question: "", sub_questions: [], qa_responses: [], }, iterations: 0, max_iterations: 3, confidence_threshold: 0.8, max_concurrency: 1, last_eval: null, documentFilter: [], }, }); const { threadId } = useCopilotContext(); const handleDocumentFilterChange = (ids: string[]) => { setState({ ...state, documentFilter: ids }); }; const sendToolResult = async (result: DecisionResult) => { if (!threadId) { console.error("No threadId available to send tool result"); return; } try { const response = await fetch(`${BACKEND_URL}/v1/research/stream`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ threadId, messages: [ { id: crypto.randomUUID(), role: "tool", content: JSON.stringify(result), }, ], }), }); if (!response.ok) { console.error("Failed to send tool result:", response.status); } } catch (error) { console.error("Error sending tool result:", error); } }; useCopilotAction({ name: "human_decision", description: "Pause for human decision on research direction", parameters: [ { name: "original_question", type: "string", description: "The original research question", }, { name: "sub_questions", type: "string[]", description: "Pending sub-questions to search", }, { name: "qa_responses", type: "object[]", description: "Answers collected so far", }, ], renderAndWaitForResponse: ({ args, status }) => { if (status === "complete") { return null; } return ( ); }, }); return ( <>

Research State

Live updates from the research agent

{!running && (
)}
); } export default function Agent() { return ( ); }