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>
  );
}