mirror of
https://github.com/arkorty/B.Tech-Project-III.git
synced 2026-04-19 20:51:49 +00:00
243 lines
9.6 KiB
TypeScript
243 lines
9.6 KiB
TypeScript
"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>
|
|
);
|
|
}
|