harryagasi
fix: keep suggested questions reachable, simplify sources control, fix dark-mode logo contrast
6b435ec | import { useState } from "react"; | |
| import { AlertTriangle, Database, Loader2, Save } from "lucide-react"; | |
| import type { Analysis, DataBindItem } from "@/services/orchestrationApi"; | |
| import { DataBindSelector } from "./DataBindSelector"; | |
| interface AnalysisHeaderProps { | |
| analysis: Analysis | null; | |
| staleSources?: DataBindItem[]; | |
| onUpdateDataBind: (items: DataBindItem[]) => Promise<void>; | |
| } | |
| export function AnalysisHeader({ analysis, staleSources = [], onUpdateDataBind }: AnalysisHeaderProps) { | |
| const [editingSources, setEditingSources] = useState(false); | |
| const [draftBind, setDraftBind] = useState<DataBindItem[]>(analysis?.data_bind ?? []); | |
| const [saving, setSaving] = useState(false); | |
| const [error, setError] = useState<string | null>(null); | |
| if (!analysis) { | |
| return ( | |
| <header className="border-b border-slate-200 bg-white px-5 py-4"> | |
| <p className="text-sm font-medium text-slate-900">Select or create an analysis</p> | |
| <p className="text-xs text-slate-500">Knowledge setup comes first, then analysis, then agent conversation.</p> | |
| </header> | |
| ); | |
| } | |
| const save = async () => { | |
| setSaving(true); | |
| setError(null); | |
| try { | |
| await onUpdateDataBind(draftBind); | |
| setEditingSources(false); | |
| } catch (err) { | |
| setError(err instanceof Error ? err.message : "Failed to update sources"); | |
| } finally { | |
| setSaving(false); | |
| } | |
| }; | |
| const openSourceEditor = () => { | |
| setDraftBind(analysis.data_bind); | |
| setEditingSources((open) => !open); | |
| }; | |
| return ( | |
| <header className="border-b border-slate-200 bg-white px-5 py-3"> | |
| <div className="flex items-center justify-between gap-3"> | |
| <h1 className="min-w-0 truncate text-sm font-semibold text-slate-950" title={analysis.objective}> | |
| {analysis.analysis_title} | |
| </h1> | |
| <div className="flex flex-shrink-0 items-center gap-2 text-xs text-slate-500"> | |
| <button | |
| type="button" | |
| title="Update bound knowledge sources for this analysis" | |
| onClick={openSourceEditor} | |
| className="inline-flex items-center gap-1 rounded-md border border-slate-200 px-2 py-1 hover:bg-slate-50 hover:text-slate-800" | |
| > | |
| <Database className="h-3.5 w-3.5" /> | |
| {analysis.data_bind.length} source{analysis.data_bind.length !== 1 ? "s" : ""} | |
| </button> | |
| </div> | |
| </div> | |
| {staleSources.length > 0 && ( | |
| <div className="mt-4 flex flex-col gap-3 rounded-md border border-amber-200 bg-amber-50 px-3 py-3 text-sm text-amber-900 sm:flex-row sm:items-center sm:justify-between"> | |
| <div className="flex items-start gap-2"> | |
| <AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0" /> | |
| <div> | |
| <p className="font-medium">Some bound sources are no longer available.</p> | |
| <p className="mt-1 text-xs leading-5 text-amber-800"> | |
| Update sources before relying on this analysis: {staleSources.map((source) => source.name).join(", ")}. | |
| </p> | |
| </div> | |
| </div> | |
| <button | |
| type="button" | |
| title="Open source binding editor" | |
| onClick={() => { | |
| setDraftBind(analysis.data_bind); | |
| setEditingSources(true); | |
| }} | |
| className="rounded-md bg-amber-900 px-3 py-2 text-xs font-medium text-white hover:bg-amber-800" | |
| > | |
| Update binding | |
| </button> | |
| </div> | |
| )} | |
| {editingSources && ( | |
| <div className="mt-4 rounded-lg border border-slate-200 bg-slate-50 p-3"> | |
| <DataBindSelector value={draftBind} onChange={setDraftBind} disabled={saving} /> | |
| {error && <p className="mt-2 text-xs text-red-600">{error}</p>} | |
| <div className="mt-3 flex justify-end gap-2"> | |
| <button type="button" onClick={() => setEditingSources(false)} className="rounded-md px-3 py-2 text-sm text-slate-600 hover:bg-white"> | |
| Cancel | |
| </button> | |
| <button | |
| type="button" | |
| onClick={save} | |
| disabled={saving || draftBind.length === 0} | |
| className="inline-flex items-center gap-2 rounded-md bg-slate-900 px-3 py-2 text-sm font-medium text-white hover:bg-slate-800 disabled:opacity-50" | |
| > | |
| {saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />} | |
| Save sources | |
| </button> | |
| </div> | |
| </div> | |
| )} | |
| </header> | |
| ); | |
| } | |