mirror of
https://github.com/arkorty/B.Tech-Project-III.git
synced 2026-04-19 20:51:49 +00:00
init
This commit is contained in:
242
thirdeye/dashboard/app/knowledge-base/EntityPanel.tsx
Normal file
242
thirdeye/dashboard/app/knowledge-base/EntityPanel.tsx
Normal file
@@ -0,0 +1,242 @@
|
||||
"use client";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import "./knowledge.css";
|
||||
import {
|
||||
fetchGroups,
|
||||
queryKnowledge,
|
||||
Group,
|
||||
fetchSignals,
|
||||
Signal,
|
||||
parseMetaList,
|
||||
} from "../lib/api";
|
||||
|
||||
export default function EntityPanel() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [groups, setGroups] = useState<Group[]>([]);
|
||||
const [selectedGroup, setSelectedGroup] = useState<string>("");
|
||||
const [queryResult, setQueryResult] = useState<string | null>(null);
|
||||
const [querying, setQuerying] = useState(false);
|
||||
const [topSignals, setTopSignals] = useState<Signal[]>([]);
|
||||
const [loadingSignals, setLoadingSignals] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroups().then((grps) => {
|
||||
setGroups(grps);
|
||||
if (grps.length > 0) {
|
||||
setSelectedGroup(grps[0].group_id);
|
||||
loadSignals(grps[0].group_id);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
async function loadSignals(groupId: string) {
|
||||
setLoadingSignals(true);
|
||||
try {
|
||||
const sigs = await fetchSignals(groupId);
|
||||
setTopSignals(sigs.slice(0, 2));
|
||||
} catch {
|
||||
setTopSignals([]);
|
||||
} finally {
|
||||
setLoadingSignals(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handleGroupChange = (groupId: string) => {
|
||||
setSelectedGroup(groupId);
|
||||
setQueryResult(null);
|
||||
loadSignals(groupId);
|
||||
};
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (!searchQuery.trim() || !selectedGroup) return;
|
||||
setQuerying(true);
|
||||
setQueryResult(null);
|
||||
try {
|
||||
const res = await queryKnowledge(selectedGroup, searchQuery);
|
||||
setQueryResult(res.answer);
|
||||
} catch {
|
||||
setQueryResult("⚠ Backend unavailable or group has no signals yet.");
|
||||
} finally {
|
||||
setQuerying(false);
|
||||
}
|
||||
};
|
||||
|
||||
const selectedGroupData = groups.find((g) => g.group_id === selectedGroup);
|
||||
const topEntities = topSignals
|
||||
.flatMap((s) => parseMetaList(s.metadata.entities))
|
||||
.filter(Boolean)
|
||||
.slice(0, 4);
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-3 animate-fade-in-right">
|
||||
{/* Group Selector */}
|
||||
{groups.length > 1 && (
|
||||
<div className="px-4 py-3 rounded-[1.25rem] bg-[#110D1A]/90 backdrop-blur-2xl border border-[#A78BFA]/10 shadow-[0_8px_32px_rgba(0,0,0,0.5)]">
|
||||
<p className="text-[9px] font-mono-data text-[#A78BFA]/60 uppercase tracking-widest mb-2">
|
||||
Select Group
|
||||
</p>
|
||||
<select
|
||||
value={selectedGroup}
|
||||
onChange={(e) => handleGroupChange(e.target.value)}
|
||||
className="w-full bg-transparent text-[#E9D9FF] text-[12px] font-medium focus:outline-none cursor-pointer"
|
||||
style={{ appearance: "none" }}
|
||||
>
|
||||
{groups.map((g) => (
|
||||
<option key={g.group_id} value={g.group_id} style={{ backgroundColor: "#110D1A" }}>
|
||||
{g.group_name} ({g.signal_count} signals)
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Search Header */}
|
||||
<div className="px-4 py-3.5 rounded-[1.25rem] bg-[#110D1A]/90 backdrop-blur-2xl border border-[#A78BFA]/10 flex items-center gap-3 shadow-[0_8px_32px_rgba(0,0,0,0.5)]">
|
||||
<button
|
||||
onClick={handleSearch}
|
||||
className="material-symbols-outlined text-[#A78BFA]/50 text-[20px] hover:text-[#A78BFA] transition-colors"
|
||||
disabled={querying}
|
||||
>
|
||||
{querying ? "hourglass_empty" : "search"}
|
||||
</button>
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
|
||||
placeholder="Query knowledge base..."
|
||||
className="flex-1 bg-transparent text-[#E9D9FF] placeholder-[#8B7BB1] focus:outline-none text-[13px] font-medium tracking-wide"
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchQuery("");
|
||||
setQueryResult(null);
|
||||
}}
|
||||
className="material-symbols-outlined text-[#A78BFA]/50 text-[20px] hover:text-[#A78BFA] transition-colors"
|
||||
>
|
||||
close
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="rounded-[1.25rem] flex flex-col shadow-[0_16px_48px_rgba(0,0,0,0.6)] bg-[#110D1A]/95 backdrop-blur-2xl border border-[#A78BFA]/10 overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="p-6 relative bg-[#1A132B]/30">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<span className="text-[9px] font-mono-data bg-[#2D1B4E]/80 text-[#B79FFF] px-2.5 py-1 rounded-md tracking-[0.15em] border border-[#A78BFA]/20">
|
||||
{selectedGroupData ? "ACTIVE_GROUP" : "NO_GROUP"}
|
||||
</span>
|
||||
<span className="material-symbols-outlined text-[#8B7BB1] text-[20px] cursor-pointer hover:text-white transition-colors">
|
||||
close
|
||||
</span>
|
||||
</div>
|
||||
<h2
|
||||
className="text-[20px] font-bold text-white tracking-tight leading-none mb-1.5 shadow-sm"
|
||||
style={{ fontFamily: "'Space Grotesk', sans-serif" }}
|
||||
>
|
||||
{selectedGroup ? selectedGroup.replace(/-/g, "_").toUpperCase() : "Select a group"}
|
||||
</h2>
|
||||
<p className="text-[11px] text-[#8B7BB1] font-mono-data opacity-90 tracking-wide">
|
||||
{selectedGroupData
|
||||
? `${selectedGroupData.signal_count} signals · lens: ${selectedGroupData.lens || "unknown"}`
|
||||
: "No data"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full h-[1px] bg-gradient-to-r from-transparent via-[#A78BFA]/10 to-transparent" />
|
||||
|
||||
{/* Query Result */}
|
||||
{queryResult && (
|
||||
<>
|
||||
<div className="p-6 bg-[#0C0814]/60">
|
||||
<h4 className="text-[9px] font-mono-data text-[#8B7BB1] uppercase tracking-[0.2em] mb-3 opacity-90">
|
||||
Query Result
|
||||
</h4>
|
||||
<p className="text-[12px] text-[#E9D9FF] leading-relaxed">
|
||||
{queryResult}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full h-[1px] bg-gradient-to-r from-transparent via-[#A78BFA]/10 to-transparent" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6 space-y-7 relative">
|
||||
{/* Primary Insights */}
|
||||
<div>
|
||||
<h4 className="text-[9px] font-mono-data text-[#8B7BB1] uppercase tracking-[0.2em] mb-2.5 opacity-90">
|
||||
Signal Overview
|
||||
</h4>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="p-3.5 rounded-xl bg-[#0C0814]/80 border border-[#A78BFA]/5 shadow-inner">
|
||||
<p className="text-[8px] font-mono-data text-[#8B7BB1] mb-1.5 uppercase tracking-[0.15em] opacity-80">
|
||||
Signals
|
||||
</p>
|
||||
<p className="text-[16px] font-mono-data text-[#00daf3] font-bold tracking-tight drop-shadow-[0_0_8px_rgba(0,218,243,0.2)]">
|
||||
{selectedGroupData?.signal_count ?? "—"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3.5 rounded-xl bg-[#0C0814]/80 border border-[#A78BFA]/5 shadow-inner">
|
||||
<p className="text-[8px] font-mono-data text-[#8B7BB1] mb-1.5 uppercase tracking-[0.15em] opacity-80">
|
||||
Lens
|
||||
</p>
|
||||
<p className="text-[14px] font-mono-data text-[#E9D9FF] font-bold tracking-tight uppercase">
|
||||
{selectedGroupData?.lens?.slice(0, 8) ?? "—"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Entities */}
|
||||
{topEntities.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-[9px] font-mono-data text-[#8B7BB1] uppercase tracking-[0.2em] mb-3 opacity-90">
|
||||
Recent Entities
|
||||
</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{topEntities.map((entity, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="text-[10px] font-mono-data text-[#B79FFF] bg-[#2D1B4E]/60 px-2.5 py-1 rounded-md border border-[#A78BFA]/20"
|
||||
>
|
||||
{entity}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Loading state */}
|
||||
{loadingSignals && (
|
||||
<div className="flex items-center gap-2 text-zinc-600 text-[10px] font-mono-data">
|
||||
<span className="material-symbols-outlined animate-spin text-sm">autorenew</span>
|
||||
Loading signals...
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Empty state */}
|
||||
{!loadingSignals && selectedGroupData?.signal_count === 0 && (
|
||||
<p className="text-[11px] text-zinc-600 font-mono-data">
|
||||
No signals yet. Send messages to the monitored Telegram group to populate the knowledge base.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer Action */}
|
||||
<div className="p-5 border-t border-[#A78BFA]/10 bg-[#0C0814]/40">
|
||||
<button
|
||||
onClick={handleSearch}
|
||||
disabled={!searchQuery.trim() || querying}
|
||||
className="w-full bg-[#1A102A] hover:bg-[#2D1B4E] border border-[#A78BFA]/20 text-[#B79FFF] hover:text-white font-mono-data text-[9.5px] font-bold tracking-[0.25em] py-3.5 rounded-xl transition-all uppercase flex items-center justify-center gap-3 group relative overflow-hidden btn-interactive shadow-lg disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span className="relative z-10">
|
||||
{querying ? "Querying..." : "Execute Deep Query"}
|
||||
</span>
|
||||
<span className="material-symbols-outlined text-[16px] relative z-10 group-hover:scale-110 transition-transform text-[#00daf3]">
|
||||
{querying ? "hourglass_empty" : "bolt"}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user