Spaces:
Sleeping
Sleeping
File size: 5,376 Bytes
30cd0c9 3baad7e 30cd0c9 3baad7e 30cd0c9 3baad7e 30cd0c9 15d8afd 30cd0c9 3baad7e 30cd0c9 3baad7e 30cd0c9 6b435ec 30cd0c9 3baad7e 30cd0c9 3baad7e 30cd0c9 3baad7e 30cd0c9 3baad7e 30cd0c9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | 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>;
unusable?: boolean;
}
export function AnalysisHeader({ analysis, staleSources = [], onUpdateDataBind, unusable = false }: 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 = () => {
if (unusable) return;
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={unusable ? "Bound sources can no longer be updated for this analysis" : "Update bound knowledge sources for this analysis"}
onClick={openSourceEditor}
disabled={unusable}
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 disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent disabled:hover:text-slate-500"
>
<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">
{unusable
? "Some bound sources are no longer available — this analysis can no longer be used."
: "Some bound sources are no longer available."}
</p>
<p className="mt-1 text-xs leading-5 text-amber-800">
{unusable
? `Missing sources: ${staleSources.map((source) => source.name).join(", ")}. Chat, help, suggested questions, report generation, and source updates are disabled for this analysis. You can still view its chat history and previously generated reports.`
: `Update sources before relying on this analysis: ${staleSources.map((source) => source.name).join(", ")}.`}
</p>
</div>
</div>
{!unusable && (
<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 && !unusable && (
<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>
);
}
|