ishaq101 commited on
Commit
60222c1
Β·
1 Parent(s): 7cf66f8

[KM-591] [KM][FE] Highlight ingest button and show last ingested time for DB clients

Browse files

- Show pulsing orange button when a DB has never been ingested to prompt user action
- Revert to muted style after first ingest, with relative timestamp ("5m ago", "2h ago") below the button
- Persist ingest timestamps per client in localStorage (km_ingest_timestamps)
- Fix incorrect toast message referencing non-existent chunks_ingested field; now shows table count from IngestResponse

src/app/components/KnowledgeManagement.tsx CHANGED
@@ -93,6 +93,13 @@ export default function KnowledgeManagement({
93
  const [loadingDbTypes, setLoadingDbTypes] = useState(false);
94
  const [ingesting, setIngesting] = useState<string | null>(null);
95
  const [deletingClient, setDeletingClient] = useState<string | null>(null);
 
 
 
 
 
 
 
96
  const pollingTimers = useRef<Map<string, ReturnType<typeof setInterval>>>(new Map());
97
 
98
  // ── Data Catalog state ──────────────────────────────────────────────────────
@@ -307,7 +314,14 @@ export default function KnowledgeManagement({
307
  setIngesting(clientId);
308
  try {
309
  const res = await ingestDatabaseClient(clientId, userId);
310
- toast.success(`Ingested ${res.chunks_ingested} chunks successfully`);
 
 
 
 
 
 
 
311
  } catch (err) {
312
  toast.error(err instanceof Error ? err.message : "Ingestion failed");
313
  } finally {
@@ -550,19 +564,39 @@ export default function KnowledgeManagement({
550
  <p className="text-xs text-slate-400 capitalize">{client.db_type} Β· {client.status}</p>
551
  </div>
552
  <div className="flex items-center gap-1.5 flex-shrink-0">
553
- <button
554
- onClick={() => handleIngest(client.id)}
555
- disabled={ingesting === client.id || client.status === "inactive"}
556
- title="Ingest schema to knowledge base"
557
- className="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-slate-100 hover:bg-orange-100 hover:text-[#FF8F00] text-slate-500 transition disabled:opacity-40 disabled:cursor-not-allowed"
558
- >
559
- {ingesting === client.id ? (
560
- <Loader2 className="w-3 h-3 animate-spin" />
561
- ) : (
562
- <Database className="w-3 h-3" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563
  )}
564
- {ingesting === client.id ? "Ingesting…" : "Ingest"}
565
- </button>
566
  <button
567
  onClick={() => handleDeleteClient(client.id)}
568
  disabled={deletingClient === client.id}
 
93
  const [loadingDbTypes, setLoadingDbTypes] = useState(false);
94
  const [ingesting, setIngesting] = useState<string | null>(null);
95
  const [deletingClient, setDeletingClient] = useState<string | null>(null);
96
+ const [ingestTimestamps, setIngestTimestamps] = useState<Record<string, string>>(() => {
97
+ try {
98
+ return JSON.parse(localStorage.getItem("km_ingest_timestamps") ?? "{}");
99
+ } catch {
100
+ return {};
101
+ }
102
+ });
103
  const pollingTimers = useRef<Map<string, ReturnType<typeof setInterval>>>(new Map());
104
 
105
  // ── Data Catalog state ──────────────────────────────────────────────────────
 
314
  setIngesting(clientId);
315
  try {
316
  const res = await ingestDatabaseClient(clientId, userId);
317
+ const tableCount = res.tables?.length ?? 0;
318
+ toast.success(`Ingested ${tableCount} table${tableCount !== 1 ? "s" : ""} successfully`);
319
+ const now = new Date().toISOString();
320
+ setIngestTimestamps((prev) => {
321
+ const updated = { ...prev, [clientId]: now };
322
+ localStorage.setItem("km_ingest_timestamps", JSON.stringify(updated));
323
+ return updated;
324
+ });
325
  } catch (err) {
326
  toast.error(err instanceof Error ? err.message : "Ingestion failed");
327
  } finally {
 
564
  <p className="text-xs text-slate-400 capitalize">{client.db_type} Β· {client.status}</p>
565
  </div>
566
  <div className="flex items-center gap-1.5 flex-shrink-0">
567
+ <div className="flex flex-col items-end gap-0.5">
568
+ <button
569
+ onClick={() => handleIngest(client.id)}
570
+ disabled={ingesting === client.id || client.status === "inactive"}
571
+ title={ingestTimestamps[client.id] ? `Last ingested: ${new Date(ingestTimestamps[client.id]).toLocaleString()}` : "Never ingested β€” click to ingest schema"}
572
+ className={[
573
+ "flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg transition disabled:opacity-40 disabled:cursor-not-allowed",
574
+ ingestTimestamps[client.id]
575
+ ? "bg-slate-100 hover:bg-orange-100 hover:text-[#FF8F00] text-slate-500"
576
+ : "bg-orange-500 hover:bg-orange-600 text-white shadow-sm shadow-orange-200 animate-pulse hover:animate-none",
577
+ ].join(" ")}
578
+ >
579
+ {ingesting === client.id ? (
580
+ <Loader2 className="w-3 h-3 animate-spin" />
581
+ ) : (
582
+ <Database className="w-3 h-3" />
583
+ )}
584
+ {ingesting === client.id ? "Ingesting…" : "Ingest"}
585
+ </button>
586
+ {ingestTimestamps[client.id] && (
587
+ <span className="text-[10px] text-slate-300 leading-none">
588
+ {(() => {
589
+ const diff = Date.now() - new Date(ingestTimestamps[client.id]).getTime();
590
+ const mins = Math.floor(diff / 60000);
591
+ if (mins < 1) return "just now";
592
+ if (mins < 60) return `${mins}m ago`;
593
+ const hrs = Math.floor(mins / 60);
594
+ if (hrs < 24) return `${hrs}h ago`;
595
+ return `${Math.floor(hrs / 24)}d ago`;
596
+ })()}
597
+ </span>
598
  )}
599
+ </div>
 
600
  <button
601
  onClick={() => handleDeleteClient(client.id)}
602
  disabled={deletingClient === client.id}