Refactor moderation and recordings views; enhance UI components and improve performance
- Cleaned up imports and removed unused hooks in ModerationView and RecordingsView. - Updated UI elements for better visual consistency and accessibility. - Improved performance by optimizing rendering logic and reducing unnecessary state updates. - Added filtering functionality in ChannelCultureGlossary and TermGlossary components. - Enhanced LiveModerationFeed with GSAP animations for smoother transitions. - Updated chatbot component for better user experience and responsiveness. - Refined NavRail component for cleaner code and improved navigation item rendering.
This commit is contained in:
1 parent
3b688e0d0f
commit
ccfe769b2f
12 files changed
+693
-509
No files matched your search
@@ -8,6 +8,7 @@ import { useAmbient } from "@/components/ambient/ambient-context";
|
||||
import { Avatar, Badge, GlassPanel, Input } from "@/components/primitives";
|
||||
import { EmptyState, SectionHeader, SkeletonRows } from "@/components/shared";
|
||||
import { useChannels, useMessageSearch, useTopReactors } from "@/hooks";
|
||||
import { useStaggerReveal } from "@/hooks/use-gsap-animation";
|
||||
import { aiTone } from "@/lib/ai-status";
|
||||
import {
|
||||
formatDuration,
|
||||
@@ -29,6 +30,12 @@ export function AnalysisView() {
|
||||
const reactorsRef = useRef<HTMLDivElement>(null);
|
||||
const channelsRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const resultsRef = useStaggerReveal<HTMLDivElement>(".search-result-card", {
|
||||
stagger: 0.03,
|
||||
y: 8,
|
||||
dependencies: [search.data?.length, query],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
ambient.set(
|
||||
query ? "amber" : "signal",
|
||||
@@ -109,90 +116,90 @@ export function AnalysisView() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Tactical HUD Header Bar */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-hairline pb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative flex size-3 items-center justify-center">
|
||||
<span className="absolute inline-flex size-full animate-ping rounded-full bg-signal opacity-75" />
|
||||
<span className="relative inline-flex size-2 rounded-full bg-signal" />
|
||||
</div>
|
||||
<h1 className="font-mono text-xs font-semibold tracking-widest text-ink uppercase">
|
||||
ANALYSIS ENGINE · DEEP_SCAN
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-white/[0.06] pb-3">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="h-2 w-2 rounded-full bg-[#7170ff] shadow-[0_0_8px_#7170ff]" />
|
||||
<h1 className="font-mono text-xs font-semibold tracking-wide text-[#f7f8f8] uppercase">
|
||||
Deep Scan · Semantic Search & Telemetry Analysis
|
||||
</h1>
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-1.5 rounded-sm bg-surface px-2 py-0.5 font-mono text-[11px] text-ink-soft">
|
||||
<span className="text-ink-faint">MODE:</span>
|
||||
<span className="font-bold text-signal">
|
||||
{query.trim().length >= 2 ? "SEARCHING" : "IDLE"}
|
||||
<div className="flex items-center gap-2 font-mono text-[11px] text-[#8a8f98]">
|
||||
<span>ENGINE:</span>
|
||||
<span className="rounded bg-white/[0.04] px-1.5 py-0.5 font-medium text-[#7170ff] border border-white/[0.06]">
|
||||
{query.trim().length >= 2 ? "SCANNING" : "STANDBY"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<GlassPanel glow className="relative overflow-hidden">
|
||||
<div className="scan-line absolute inset-x-0 top-0" />
|
||||
{/* Hero Query Stage */}
|
||||
<GlassPanel className="p-5">
|
||||
<div className="flex items-center gap-3">
|
||||
<Sparkles className="size-5 text-signal" />
|
||||
<span className="flex size-8 items-center justify-center rounded-[6px] border border-white/[0.08] bg-[#7170ff]/10 text-[#7170ff]">
|
||||
<Sparkles className="size-4" />
|
||||
</span>
|
||||
<div>
|
||||
<div className="eyebrow">Semantic search</div>
|
||||
<h2 className="display text-balance text-2xl text-ink">
|
||||
Search the archive
|
||||
<h2 className="text-base font-semibold text-ink">
|
||||
Cross-Guild Archive Intelligence
|
||||
</h2>
|
||||
<p className="font-mono text-[11px] text-[#8a8f98]">
|
||||
Query vectorized messages, heuristic flags, and user behavior
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-4">
|
||||
<Search className="absolute left-4 top-1/2 size-5 -translate-y-1/2 text-ink-faint" />
|
||||
<Search className="absolute left-3.5 top-1/2 size-4 -translate-y-1/2 text-ink-faint" />
|
||||
<Input
|
||||
className="h-12 pl-12 text-base"
|
||||
placeholder="Find messages, patterns, flags…"
|
||||
className="h-10 pl-10 text-xs text-ink"
|
||||
placeholder="Search keywords, behavioral patterns, infraction terms..."
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
{query.trim().length > 0 && query.trim().length < 2 && (
|
||||
<div className="mono mt-2 text-xs text-ink-faint">
|
||||
Type at least 2 characters…
|
||||
</div>
|
||||
)}
|
||||
</GlassPanel>
|
||||
|
||||
<div className="grid gap-5 lg:grid-cols-5">
|
||||
<div className="grid gap-3 lg:grid-cols-5">
|
||||
{/* Search Results Column */}
|
||||
<GlassPanel className="lg:col-span-3">
|
||||
<SectionHeader
|
||||
eyebrow="results"
|
||||
title="Matches"
|
||||
eyebrow="search output"
|
||||
title="Matched Log Packets"
|
||||
action={
|
||||
<span className="mono text-xs text-ink-faint">
|
||||
{(search.data ?? []).length}
|
||||
<span className="mono text-xs text-[#8a8f98]">
|
||||
{(search.data ?? []).length} results
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
{query.trim().length >= 2 && search.isLoading && (
|
||||
<SkeletonRows rows={6} />
|
||||
)}
|
||||
{(search.data ?? []).length === 0 ? (
|
||||
{(search.data ?? []).length === 0 && !search.isLoading ? (
|
||||
<EmptyState
|
||||
icon={<Search className="size-7" />}
|
||||
title="No matches yet"
|
||||
description="Run a search to surface messages across the guild."
|
||||
title="No packets matched"
|
||||
description="Enter a query string above to scan across all captured guild communication."
|
||||
/>
|
||||
) : (
|
||||
<div className="space-y-1.5">
|
||||
<div ref={resultsRef} className="space-y-1.5 mt-3">
|
||||
{(search.data ?? []).map((m) => (
|
||||
<div
|
||||
key={m.id}
|
||||
className="flex items-start gap-3 rounded-[12px] border border-hairline bg-white/[0.03] p-3"
|
||||
className="search-result-card flex items-start gap-3 rounded-[6px] border border-white/[0.06] bg-white/[0.02] p-3 transition-all hover:border-white/[0.12] hover:bg-white/[0.04]"
|
||||
>
|
||||
<Avatar src={m.avatar_url} name={m.username} size={32} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-semibold text-ink">
|
||||
<span className="text-xs font-semibold text-ink">
|
||||
{m.username}
|
||||
</span>
|
||||
<span className="mono text-[0.65rem] text-ink-faint">
|
||||
<span className="font-mono text-[10px] text-ink-faint">
|
||||
{getMessageChannelLabel(m)}
|
||||
</span>
|
||||
{m.ai_status && (
|
||||
<Badge tone={aiTone(m.ai_status)} className="ml-auto">
|
||||
<Badge
|
||||
tone={aiTone(m.ai_status)}
|
||||
className="ml-auto font-mono text-[9px]"
|
||||
>
|
||||
{m.ai_analysis_duration_ms &&
|
||||
m.ai_analysis_duration_ms > 0
|
||||
? `${m.ai_status} · ${formatDuration(m.ai_analysis_duration_ms)}`
|
||||
@@ -200,7 +207,7 @@ export function AnalysisView() {
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-0.5 text-sm text-ink-soft">
|
||||
<div className="mt-1 text-xs text-ink-soft leading-relaxed">
|
||||
{renderMessageContent(m.content, m.metadata) || "(embed)"}
|
||||
</div>
|
||||
</div>
|
||||
@@ -210,17 +217,19 @@ export function AnalysisView() {
|
||||
)}
|
||||
</GlassPanel>
|
||||
|
||||
<div className="space-y-5 lg:col-span-2">
|
||||
{/* Culture & Leaderboard Side Column */}
|
||||
<div className="space-y-3 lg:col-span-2">
|
||||
<GlassPanel>
|
||||
<SectionHeader
|
||||
eyebrow="culture"
|
||||
eyebrow="engagement"
|
||||
title={
|
||||
<span className="flex items-center gap-2">
|
||||
<TrendingUp className="size-4 text-signal" /> Top reactors
|
||||
<span className="flex items-center gap-1.5">
|
||||
<TrendingUp className="size-3.5 text-[#7170ff]" /> Top
|
||||
Reactors
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<div ref={reactorsRef} className="space-y-2">
|
||||
<div ref={reactorsRef} className="space-y-2 mt-3">
|
||||
{(reactors ?? []).slice(0, 6).map((r, i) => {
|
||||
const maxNet = reactors?.[0]?.net_count || 1;
|
||||
const pct = Math.max(
|
||||
@@ -230,20 +239,22 @@ export function AnalysisView() {
|
||||
return (
|
||||
<div
|
||||
key={r.user_id}
|
||||
className="flex items-center gap-3 text-sm"
|
||||
className="flex items-center gap-3 text-xs"
|
||||
>
|
||||
<span className="mono w-5 text-ink-faint">{i + 1}</span>
|
||||
<span className="w-28 shrink-0 truncate text-ink-soft sm:w-40">
|
||||
<span className="font-mono w-4 text-[10px] text-ink-faint">
|
||||
0{i + 1}
|
||||
</span>
|
||||
<span className="w-24 shrink-0 truncate font-mono text-xs text-ink sm:w-32">
|
||||
{r.username}
|
||||
</span>
|
||||
<div className="h-1.5 flex-1 overflow-hidden rounded-full bg-white/8">
|
||||
<div className="h-1 flex-1 overflow-hidden rounded-full bg-white/[0.06]">
|
||||
<div
|
||||
className="reactor-bar-fill h-full rounded-full bg-signal/70"
|
||||
className="reactor-bar-fill h-full rounded-full bg-gradient-to-r from-[#5e6ad2] to-[#7170ff]"
|
||||
style={{ width: `${pct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className="reactor-count mono w-12 text-right text-xs text-signal"
|
||||
className="reactor-count font-mono w-10 text-right text-[11px] font-semibold text-[#7170ff]"
|
||||
data-target={r.net_count}
|
||||
>
|
||||
+0
|
||||
@@ -252,8 +263,8 @@ export function AnalysisView() {
|
||||
);
|
||||
})}
|
||||
{(reactors ?? []).length === 0 && (
|
||||
<div className="py-4 text-center text-xs text-ink-faint">
|
||||
No data
|
||||
<div className="py-4 text-center font-mono text-xs text-ink-faint">
|
||||
NO REACTOR DATA
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -261,30 +272,32 @@ export function AnalysisView() {
|
||||
|
||||
<GlassPanel>
|
||||
<SectionHeader
|
||||
eyebrow="channels"
|
||||
eyebrow="volume"
|
||||
title={
|
||||
<span className="flex items-center gap-2">
|
||||
<Hash className="size-4 text-signal" /> Top channels
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Hash className="size-3.5 text-[#7170ff]" /> Top Active
|
||||
Channels
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<div ref={channelsRef} className="space-y-2">
|
||||
<div ref={channelsRef} className="space-y-2 mt-3">
|
||||
{(channels ?? []).slice(0, 6).map((c) => (
|
||||
<div
|
||||
key={c.channel_id}
|
||||
className="channel-row flex items-center gap-3 text-sm"
|
||||
className="channel-row flex items-center justify-between rounded-[6px] border border-white/[0.04] bg-white/[0.01] p-2 text-xs hover:bg-white/[0.03]"
|
||||
>
|
||||
<span className="flex-1 truncate text-ink-soft">
|
||||
{c.channel_name ?? c.channel_id.slice(0, 8)}
|
||||
<span className="flex items-center gap-1.5 truncate font-mono text-xs text-ink">
|
||||
<Hash className="size-3 text-[#7170ff]" />
|
||||
{c.channel_name ?? c.channel_id.slice(0, 10)}
|
||||
</span>
|
||||
<span className="mono text-xs text-ink-faint">
|
||||
{c.total_messages}
|
||||
<span className="font-mono text-[10px] text-[#8a8f98]">
|
||||
{c.total_messages.toLocaleString()} msgs
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{(channels ?? []).length === 0 && (
|
||||
<div className="py-4 text-center text-xs text-ink-faint">
|
||||
No data
|
||||
<div className="py-4 text-center font-mono text-xs text-ink-faint">
|
||||
NO CHANNEL DATA
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user