diff --git a/app/frontend/components/Chat.tsx b/app/frontend/components/Chat.tsx index dafca4a9..bf663ed2 100644 --- a/app/frontend/components/Chat.tsx +++ b/app/frontend/components/Chat.tsx @@ -1,5 +1,6 @@ "use client"; +import { useCallback, useEffect, useState } from "react"; import { CopilotKit, useCoAgent, @@ -10,6 +11,7 @@ import { CopilotChat } from "@copilotkit/react-ui"; import "@copilotkit/react-ui/styles.css"; import CitationBlock from "./CitationBlock"; import DbInfo from "./DbInfo"; +import SettingsPanel, { STORAGE_KEY } from "./SettingsPanel"; // Must match AGUI_STATE_KEY from haiku.rag.agents.chat const AGUI_STATE_KEY = "haiku.rag.chat"; @@ -36,6 +38,7 @@ interface ChatSessionState { session_id: string; citations: Citation[]; qa_history: QAResponse[]; + initial_context: string | null; } // AG-UI state is namespaced under AGUI_STATE_KEY @@ -131,6 +134,24 @@ function FileIcon() { ); } +function SettingsIcon() { + return ( + + + + + ); +} + function ToolCallIndicator({ toolName, status, @@ -336,7 +357,27 @@ function ToolCallIndicator({ ); } -function ChatContent() { +function ChatContentInner({ + initialContext, + setInitialContext, +}: { + initialContext: string; + setInitialContext: (value: string) => void; +}) { + const [settingsOpen, setSettingsOpen] = useState(false); + + const handleSaveContext = useCallback( + (value: string) => { + setInitialContext(value); + if (value) { + localStorage.setItem(STORAGE_KEY, value); + } else { + localStorage.removeItem(STORAGE_KEY); + } + }, + [setInitialContext], + ); + useCoAgent({ name: "chat_agent", initialState: { @@ -344,6 +385,7 @@ function ChatContent() { session_id: "", citations: [], qa_history: [], + initial_context: initialContext || null, }, }, }); @@ -425,6 +467,40 @@ function ChatContent() { display: flex; flex-direction: column; } + .chat-header { + display: flex; + justify-content: flex-end; + padding: 0.5rem 0.75rem; + border-bottom: 1px solid #e2e8f0; + background: #f8fafc; + } + .settings-btn { + display: flex; + align-items: center; + gap: 0.375rem; + padding: 0.375rem 0.625rem; + background: white; + border: 1px solid #e2e8f0; + border-radius: 6px; + cursor: pointer; + color: #64748b; + font-size: 0.8125rem; + transition: all 0.15s; + } + .settings-btn:hover { + background: #f1f5f9; + border-color: #cbd5e1; + color: #475569; + } + .settings-btn.has-context { + background: #eff6ff; + border-color: #bfdbfe; + color: #2563eb; + } + .settings-btn.has-context:hover { + background: #dbeafe; + border-color: #93c5fd; + } .chat-content { flex: 1; min-height: 0; @@ -438,6 +514,17 @@ function ChatContent() { `}
+
+ +
+ setSettingsOpen(false)} + onSave={handleSaveContext} + currentValue={initialContext} + /> ); } +function ChatContent() { + const [initialContext, setInitialContext] = useState(null); + + useEffect(() => { + const stored = localStorage.getItem(STORAGE_KEY); + setInitialContext(stored || ""); + }, []); + + if (initialContext === null) { + return null; + } + + return ( + + ); +} + export default function Chat() { return ( diff --git a/app/frontend/components/SettingsPanel.tsx b/app/frontend/components/SettingsPanel.tsx new file mode 100644 index 00000000..76638e7b --- /dev/null +++ b/app/frontend/components/SettingsPanel.tsx @@ -0,0 +1,188 @@ +"use client"; + +import { useCallback, useEffect, useState } from "react"; + +const STORAGE_KEY = "haiku.rag.settings.initial_context"; + +interface SettingsPanelProps { + isOpen: boolean; + onClose: () => void; + onSave: (initialContext: string) => void; + currentValue: string; +} + +export default function SettingsPanel({ + isOpen, + onClose, + onSave, + currentValue, +}: SettingsPanelProps) { + const [value, setValue] = useState(currentValue); + + useEffect(() => { + if (isOpen) { + setValue(currentValue); + } + }, [isOpen, currentValue]); + + const handleSave = useCallback(() => { + onSave(value); + onClose(); + }, [value, onSave, onClose]); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === "Escape") { + onClose(); + } + }, + [onClose], + ); + + if (!isOpen) { + return null; + } + + return ( + <> + + {/* biome-ignore lint/a11y/useKeyWithClickEvents: handled via onKeyDown on overlay */} +
+ {/* biome-ignore lint/a11y/noStaticElementInteractions: modal content wrapper */} +
e.stopPropagation()} + onKeyDown={(e) => e.stopPropagation()} + > +

+ Background Context +

+

+ Provide background information that will be used throughout your + conversation. This helps the assistant understand domain-specific + context, terminology, or any relevant details about your questions. +

+