diff --git a/app/backend/main.py b/app/backend/main.py index 4b3724e5..3ce50f43 100644 --- a/app/backend/main.py +++ b/app/backend/main.py @@ -83,6 +83,7 @@ async def stream_chat(request: Request) -> Response: # Restore session state from incoming AG-UI state (look under namespaced key) initial_qa_history: list[QAResponse] = [] session_id: str | None = None + document_filter: list[str] = [] state = getattr(run_input, "state", None) if state: chat_state = state.get(AGUI_STATE_KEY, state) @@ -91,6 +92,7 @@ async def stream_chat(request: Request) -> Response: QAResponse(**qa) for qa in chat_state.get("qa_history", []) ] session_id = chat_state.get("session_id") + document_filter = chat_state.get("document_filter", []) # Determine session_id: prefer state, fall back to thread_id, generate UUID if neither thread_id = getattr(run_input, "thread_id", None) @@ -103,6 +105,7 @@ async def stream_chat(request: Request) -> Response: session_state=ChatSessionState( session_id=session_id, qa_history=initial_qa_history, + document_filter=document_filter, ), state_key=AGUI_STATE_KEY, ) diff --git a/app/frontend/app/api/documents/route.ts b/app/frontend/app/api/documents/route.ts new file mode 100644 index 00000000..0f41863f --- /dev/null +++ b/app/frontend/app/api/documents/route.ts @@ -0,0 +1,16 @@ +import { NextResponse } from "next/server"; + +export async function GET() { + const backendUrl = process.env.BACKEND_URL || "http://backend:8000"; + + try { + const response = await fetch(`${backendUrl}/api/documents`); + const data = await response.json(); + return NextResponse.json(data); + } catch { + return NextResponse.json( + { documents: [], error: "Backend unavailable" }, + { status: 503 }, + ); + } +} diff --git a/app/frontend/components/Chat.tsx b/app/frontend/components/Chat.tsx index 247ce0f5..da553fe4 100644 --- a/app/frontend/components/Chat.tsx +++ b/app/frontend/components/Chat.tsx @@ -12,6 +12,7 @@ import "@copilotkit/react-ui/styles.css"; import CitationBlock from "./CitationBlock"; import ContextPanel from "./ContextPanel"; import DbInfo from "./DbInfo"; +import DocumentFilter from "./DocumentFilter"; // Must match AGUI_STATE_KEY from haiku.rag.agents.chat const AGUI_STATE_KEY = "haiku.rag.chat"; @@ -44,6 +45,7 @@ interface ChatSessionState { citations: Citation[]; qa_history: QAResponse[]; session_context: SessionContext | null; + document_filter: string[]; } // AG-UI state is namespaced under AGUI_STATE_KEY @@ -369,23 +371,59 @@ function ToolCallIndicator({ ); } +function FilterIcon() { + return ( + + + + ); +} + function ChatContentInner() { const [contextOpen, setContextOpen] = useState(false); + const [filterOpen, setFilterOpen] = useState(false); - const { state: agentState } = useCoAgent({ - name: "chat_agent", - initialState: { - [AGUI_STATE_KEY]: { - session_id: "", - citations: [], - qa_history: [], - session_context: null, + const { state: agentState, setState: setAgentState } = useCoAgent( + { + name: "chat_agent", + initialState: { + [AGUI_STATE_KEY]: { + session_id: "", + citations: [], + qa_history: [], + session_context: null, + document_filter: [], + }, }, }, - }); + ); - // Extract session context from agent state + // Extract session context and document filter from agent state const sessionContext = agentState?.[AGUI_STATE_KEY]?.session_context ?? null; + const documentFilter = agentState?.[AGUI_STATE_KEY]?.document_filter ?? []; + + const handleFilterApply = (selected: string[]) => { + setAgentState({ + ...agentState, + [AGUI_STATE_KEY]: { + ...agentState?.[AGUI_STATE_KEY], + session_id: agentState?.[AGUI_STATE_KEY]?.session_id ?? "", + citations: agentState?.[AGUI_STATE_KEY]?.citations ?? [], + qa_history: agentState?.[AGUI_STATE_KEY]?.qa_history ?? [], + session_context: agentState?.[AGUI_STATE_KEY]?.session_context ?? null, + document_filter: selected, + }, + }); + }; useCoAgentStateRender({ name: "chat_agent", @@ -513,6 +551,21 @@ function ChatContentInner() {
+ + + ) : ( + "No filter (all documents)" + )} +
+
+ + +
+
+
+ + + ); +}