diff --git a/CLAUDE.md b/CLAUDE.md index d9fd14e..dc17f09 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,7 +80,7 @@ Express 5 + Helmet HTTP server with WebSocket (ws) on port 3001 (default). - `POST /api/messages/:id/moderate` — Dispatch moderation action - `GET /api/review` — Flagged/warned messages - `GET /api/analysis/search` — Full-text search with `?q=` -- `POST /api/chat` — Mascot AI chat +- `POST /api/chat` — AI chatbot chat - `GET /api/chat/history` — Chat history - `POST /api/chat/clear` — Clear chat history - `POST /api/voice/command` — Send voice transmit commands @@ -105,7 +105,7 @@ Express 5 + Helmet HTTP server with WebSocket (ws) on port 3001 (default). - `voice/` — Voice connection, guilds, channels - `media/` — Music/screenshare player control - `analysis/` — Full-text search across analyzed messages -- `mascot-chat/` — AI chatbot with server context +- `chatbot/` — AI chatbot with server context - `recordings/` — Voice recording listing - `ui-state/` — Persistent UI state for dashboard - `config/` — App config endpoint @@ -205,7 +205,7 @@ Next.js 16 (React 19) static export dashboard, built with TypeScript + Tailwind **Feature structure:** - `src/app/` — App Router pages (login, dashboard with tabs) -- `src/features/` — Feature components (dashboard, messages, live, mascot) +- `src/features/` — Feature components (dashboard, messages, live, chatbot) - `src/lib/` — Shared utilities (types, API client, WebSocket, hooks) - `src/components/` — Shared UI components (layout, ui) diff --git a/docs/superpowers/plans/2026-07-28-discord-automod-redesign.md b/docs/superpowers/plans/2026-07-28-discord-automod-redesign.md index ae0122d..f142165 100644 --- a/docs/superpowers/plans/2026-07-28-discord-automod-redesign.md +++ b/docs/superpowers/plans/2026-07-28-discord-automod-redesign.md @@ -2,7 +2,7 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Full frontend redesign with glassmorphic dark theme, floating top nav, Live2D mascot, and rich analytics dashboard. +**Goal:** Full frontend redesign with glassmorphic dark theme, floating top nav, Live2D chatbot, and rich analytics dashboard. **Architecture:** No routing or state management changes — same Next.js App Router, TanStack Query, WebSocket context. Only visual layer and component structure rewritten. New glass component system wraps existing logic. @@ -33,7 +33,7 @@ | `src/app/globals.css` | Complete rewrite — new tokens, glass system, animations | | `src/app/layout.tsx` | Fonts (Inter + JetBrains Mono), metadata | | `src/app/page.tsx` | Redirect `/dashboard` not `/messages` | -| `src/app/(dashboard)/layout.tsx` | Top nav, no sidebar, mascot context, media context, WS provider | +| `src/app/(dashboard)/layout.tsx` | Top nav, no sidebar, chatbot context, media context, WS provider | | `src/lib/navigation.ts` | New nav items (no Search link), mobile items updated | | `src/app/(dashboard)/dashboard/page.tsx` | Full rewrite — Ops Center | | `src/app/(dashboard)/messages/page.tsx` | Full rewrite — Split pane | @@ -69,23 +69,23 @@ | `src/components/voice/activity-timeline.tsx` | Voice activity chart | | `src/components/recordings/recording-card.tsx` | Glass card + waveform preview | | `src/components/recordings/recording-player.tsx` | Inline audio player | -| `src/components/mascot/mascot-container.tsx` | Floating L2D container | -| `src/components/mascot/mascot-canvas.tsx` | WebGL Live2D renderer | -| `src/components/mascot/chat-panel.tsx` | Chat input + history | -| `src/components/mascot/mascot-context.tsx` | Context provider | +| `src/components/chatbot/chatbot-container.tsx` | Floating L2D container | +| `src/components/chatbot/chatbot-canvas.tsx` | WebGL Live2D renderer | +| `src/components/chatbot/chat-panel.tsx` | Chat input + history | +| `src/components/chatbot/chatbot-context.tsx` | Context provider | | `src/components/media/mini-player.tsx` | Floating media player | | `src/components/shared/error-boundary.tsx` | Per-page error boundary | | `src/components/shared/loading-skeleton.tsx` | Glass shimmer skeleton | | `src/components/shared/empty-state.tsx` | Empty state | | `src/lib/hooks/use-media-player.ts` | Global media player context | -| `src/lib/hooks/use-mascot.ts` | Mascot context hook | +| `src/lib/hooks/use-chatbot.ts` | Chatbot context hook | ### Deleted files (replaced by new components): | File | Replaced by | |------|-------------| | `src/components/layout/app-sidebar.tsx` | `top-nav.tsx` + `hidden-sidebar.tsx` | | `src/components/layout/app-header.tsx` | `top-nav.tsx` + `sub-nav.tsx` | -| `src/components/chatbot/chatbot.tsx` | `mascot/` components | +| `src/components/chatbot/chatbot.tsx` | `chatbot/` components | | `src/components/shared/stat-card.tsx` | `dashboard/stat-card.tsx` | | `src/components/shared/detail-stat.tsx` | inline in detail views | | `src/components/messages/images-grid.tsx` | `attachments-grid.tsx` | @@ -749,8 +749,8 @@ import { Suspense } from "react"; import { TopNav } from "@/components/layout/top-nav"; import { MobileNav } from "@/components/layout/mobile-nav"; import { WsProvider } from "@/lib/ws/context"; -import { MascotProvider } from "@/components/mascot/mascot-context"; -import { MascotContainer } from "@/components/mascot/mascot-container"; +import { ChatbotProvider } from "@/components/chatbot/chatbot-context"; +import { ChatbotContainer } from "@/components/chatbot/chatbot-container"; import { MiniPlayer } from "@/components/media/mini-player"; import { MediaPlayerProvider } from "@/lib/hooks/use-media-player"; import { HiddenSidebar } from "@/components/layout/hidden-sidebar"; @@ -777,7 +777,7 @@ export default function DashboardLayout({ - +
setGuildId(g ?? "")} /> @@ -799,9 +799,9 @@ export default function DashboardLayout({ - +
-
+
@@ -822,7 +822,7 @@ rm src/components/chatbot/chatbot.tsx ```bash git add src/app/\(dashboard\)/layout.tsx git rm src/components/layout/app-sidebar.tsx src/components/layout/app-header.tsx src/components/chatbot/chatbot.tsx -git commit -m "feat: rewrite dashboard layout with top nav, hidden sidebar, mascot, mini-player" +git commit -m "feat: rewrite dashboard layout with top nav, hidden sidebar, chatbot, mini-player" ``` --- @@ -3117,38 +3117,38 @@ git commit -m "feat: add media player context and floating mini player" --- -### Task 21: Mascot — Context, Container & Canvas +### Task 21: Chatbot — Context, Container & Canvas **Files:** -- Create: `src/components/mascot/mascot-context.tsx` -- Create: `src/components/mascot/mascot-container.tsx` -- Create: `src/components/mascot/mascot-canvas.tsx` -- Create: `src/components/mascot/chat-panel.tsx` +- Create: `src/components/chatbot/chatbot-context.tsx` +- Create: `src/components/chatbot/chatbot-container.tsx` +- Create: `src/components/chatbot/chatbot-canvas.tsx` +- Create: `src/components/chatbot/chat-panel.tsx` -- [ ] **Step 1: Create MascotContext** +- [ ] **Step 1: Create ChatbotContext** ```tsx "use client"; import { createContext, useContext, useState, type ReactNode } from "react"; -type MascotExpression = "idle" | "listening" | "surprise" | "happy" | "sad" | "talking"; +type ChatbotExpression = "idle" | "listening" | "surprise" | "happy" | "sad" | "talking"; -interface MascotContextType { - expression: MascotExpression; +interface ChatbotContextType { + expression: ChatbotExpression; minimized: boolean; chatOpen: boolean; chatHistory: { role: "user" | "assistant"; text: string }[]; - setExpression: (expr: MascotExpression) => void; + setExpression: (expr: ChatbotExpression) => void; setMinimized: (v: boolean) => void; setChatOpen: (v: boolean) => void; addChat: (role: "user" | "assistant", text: string) => void; } -const MascotContext = createContext(null); +const ChatbotContext = createContext(null); -export function MascotProvider({ children }: { children: ReactNode }) { - const [expression, setExpression] = useState("idle"); +export function ChatbotProvider({ children }: { children: ReactNode }) { + const [expression, setExpression] = useState("idle"); const [minimized, setMinimized] = useState(true); const [chatOpen, setChatOpen] = useState(false); const [chatHistory, setChatHistory] = useState<{ role: "user" | "assistant"; text: string }[]>([]); @@ -3158,28 +3158,28 @@ export function MascotProvider({ children }: { children: ReactNode }) { }; return ( - {children} - + ); } -export function useMascot() { - const ctx = useContext(MascotContext); - if (!ctx) throw new Error("useMascot must be used within MascotProvider"); +export function useChatbot() { + const ctx = useContext(ChatbotContext); + if (!ctx) throw new Error("useChatbot must be used within ChatbotProvider"); return ctx; } ``` -- [ ] **Step 2: Create MascotCanvas (Live2D placeholder)** +- [ ] **Step 2: Create ChatbotCanvas (Live2D placeholder)** ```tsx "use client"; import { useEffect, useRef } from "react"; -import { useMascot } from "./mascot-context"; +import { useChatbot } from "./chatbot-context"; /** * Live2D Cubism WebGL canvas. @@ -3188,15 +3188,15 @@ import { useMascot } from "./mascot-context"; * Integration requires: * 1. Live2D Cubism SDK for Web (npm: @live2d/cubism) * 2. Model files: .model3.json, .moc3, .physics3.json, textures - * 3. Place model files in public/mascot/ + * 3. Place model files in public/chatbot/ * * The current implementation shows a placeholder character. * Replace with actual Cubism SDK integration when model files are available. */ -export function MascotCanvas() { +export function ChatbotCanvas() { const canvasRef = useRef(null); - const { expression } = useMascot(); + const { expression } = useChatbot(); // Placeholder: draw a simple avatar face that responds to expression useEffect(() => { @@ -3308,19 +3308,19 @@ export function MascotCanvas() { } ``` -- [ ] **Step 3: Create MascotContainer** +- [ ] **Step 3: Create ChatbotContainer** ```tsx "use client"; import { MessageCircle, X, Minimize2, Maximize2 } from "lucide-react"; -import { useMascot } from "./mascot-context"; -import { MascotCanvas } from "./mascot-canvas"; +import { useChatbot } from "./chatbot-context"; +import { ChatbotCanvas } from "./chatbot-canvas"; import { ChatPanel } from "./chat-panel"; import { useState } from "react"; -export function MascotContainer() { - const { minimized, setMinimized, chatOpen, setChatOpen } = useMascot(); +export function ChatbotContainer() { + const { minimized, setMinimized, chatOpen, setChatOpen } = useChatbot(); const [position, setPosition] = useState({ x: 0, y: 0 }); const [dragging, setDragging] = useState(false); const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); @@ -3345,7 +3345,7 @@ export function MascotContainer() { onMouseUp={handleMouseUp} onMouseLeave={handleMouseUp} > - {/* Main mascot bubble */} + {/* Main chatbot bubble */}
- Mascot + Chatbot
@@ -67,7 +67,7 @@ export function MascotContainer() { onMouseDown={handleMouseDown} > - Mascot + Chatbot
- - -
-

- - {channel.channel_name ?? channel.channel_id.slice(0, 8)} -

-

- {channel.channel_id} -

-
-
- - - -
- {channel.culture_summary && ( -
-
- -

- Channel Culture -

-
-

- “{channel.culture_summary}” -

-
- )} - {channel.recent_messages.length > 0 && ( -
-

- Recent - Messages -

-
- {channel.recent_messages.slice(0, 5).map((msg) => ( -
-
- - {msg.username} - - - {new Date(msg.created_at).toLocaleString()} - -
-

{msg.content}

-
- ))} -
-
- )} -
-
-
- ); -} diff --git a/services/frontend/src/components/dashboard/channels-section.tsx b/services/frontend/src/components/dashboard/channels-section.tsx deleted file mode 100644 index db4b97b..0000000 --- a/services/frontend/src/components/dashboard/channels-section.tsx +++ /dev/null @@ -1,78 +0,0 @@ -"use client"; - -import { ChevronRight, Hash, Search } from "lucide-react"; -import { useState } from "react"; - -import { EmptyState, LoadingSkeleton } from "@/components/shared"; -import { Card, CardContent } from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { useChannels } from "@/hooks"; - -export function ChannelsSection({ - guildId, - onSelect, -}: { - guildId: string; - onSelect: (id: string) => void; -}) { - const [search, setSearch] = useState(""); - const { - data: channels, - isLoading, - refetch, - } = useChannels(guildId, search || undefined); - - return ( -
-
- - setSearch(e.target.value)} - className="pl-9 h-9" - /> -
- {isLoading ? ( - - ) : !channels || channels.length === 0 ? ( - - ) : ( -
- {channels.map((ch) => ( - onSelect(ch.channel_id)} - > - -
-
-
- -

- {ch.channel_name ?? ch.channel_id.slice(0, 8)} -

-
-

- {ch.total_messages} messages - {ch.flagged_count > 0 - ? ` · ${ch.flagged_count} flagged` - : ""} -

-
- -
- {ch.culture_summary && ( -

- “{ch.culture_summary}” -

- )} -
-
- ))} -
- )} -
- ); -} diff --git a/services/frontend/src/components/dashboard/index.ts b/services/frontend/src/components/dashboard/index.ts deleted file mode 100644 index d496c3a..0000000 --- a/services/frontend/src/components/dashboard/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { ChannelDetailSection } from "./channel-detail-section"; -export { ChannelsSection } from "./channels-section"; -export { StatsSection } from "./stats-section"; -export { UserDetailSection } from "./user-detail-section"; -export { UsersSection } from "./users-section"; diff --git a/services/frontend/src/components/dashboard/stats-section.tsx b/services/frontend/src/components/dashboard/stats-section.tsx deleted file mode 100644 index 27b46ee..0000000 --- a/services/frontend/src/components/dashboard/stats-section.tsx +++ /dev/null @@ -1,145 +0,0 @@ -"use client"; - -import { - AlertCircle, - Clock, - Hash, - Shield, - Sparkles, - Users, -} from "lucide-react"; - -import { ErrorState, LoadingSkeleton, StatCard } from "@/components/shared"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Progress } from "@/components/ui/progress"; -import { useStats } from "@/hooks"; -import { formatNumber } from "@/lib/format"; - -export function StatsSection() { - const { data: stats, isLoading, error, refetch } = useStats(); - if (error) return ; - if (isLoading || !stats) - return ( -
- -
- ); - - return ( -
-
- - - - - - - - -
-
- - - - Top Channels - - - - {stats.top_channels.length === 0 ? ( -

- No channel data yet. -

- ) : ( -
- {stats.top_channels.map((ch) => { - const max = stats.top_channels[0].message_count; - const pct = max > 0 ? (ch.message_count / max) * 100 : 0; - return ( -
-
- - #{ch.channel_name ?? ch.channel_id.slice(0, 8)} - - - {formatNumber(ch.message_count)} - -
- -
- ); - })} -
- )} -
-
- - - - Moderation - Queue - - - -
- {[ - { - label: "Pending", - value: stats.moderation_overview.pending, - cls: "bg-muted/50", - }, - { - label: "Processing", - value: stats.moderation_overview.processing, - cls: "bg-yellow-500/10 text-yellow-500", - }, - { - label: "Errors", - value: stats.moderation_overview.error, - cls: "bg-destructive/10 text-destructive", - }, - ].map(({ label, value, cls }) => ( -
-
- {value} -
-
{label}
-
- ))} -
-
-
-
-
- ); -} diff --git a/services/frontend/src/components/dashboard/user-detail-section.tsx b/services/frontend/src/components/dashboard/user-detail-section.tsx deleted file mode 100644 index c8cf18e..0000000 --- a/services/frontend/src/components/dashboard/user-detail-section.tsx +++ /dev/null @@ -1,106 +0,0 @@ -"use client"; - -import { ArrowLeft, Clock, Sparkles } from "lucide-react"; -import Image from "next/image"; - -import { DetailStat, ErrorState, LoadingSkeleton } from "@/components/shared"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { useUserDetail } from "@/hooks"; - -export function UserDetailSection({ - userId, - onBack, -}: { - userId: string; - onBack: () => void; -}) { - const { data: user, isLoading } = useUserDetail(userId); - if (isLoading) return ; - if (!user) return ; - - return ( -
- - - -
-
- {user.avatar_url ? ( - - ) : ( - (user.username ?? "?").charAt(0).toUpperCase() - )} -
-
-

- {user.username ?? "Unknown"} -

-

- {user.user_id} -

-
-
-
- - - - -
- {user.profile_summary && ( -
-
- -

- AI Profile -

-
-

{user.profile_summary}

-
- )} - {user.recent_messages.length > 0 && ( -
-

- Recent - Messages -

-
- {user.recent_messages.slice(0, 5).map((msg) => ( -
-

- - {new Date(msg.created_at).toLocaleString()} -

-

{msg.content}

-
- ))} -
-
- )} -
-
-
- ); -} diff --git a/services/frontend/src/components/dashboard/users-section.tsx b/services/frontend/src/components/dashboard/users-section.tsx deleted file mode 100644 index 3477ce0..0000000 --- a/services/frontend/src/components/dashboard/users-section.tsx +++ /dev/null @@ -1,80 +0,0 @@ -"use client"; - -import { ChevronRight, Search, Users } from "lucide-react"; -import Image from "next/image"; -import { useState } from "react"; - -import { EmptyState, LoadingSkeleton } from "@/components/shared"; -import { Badge } from "@/components/ui/badge"; -import { Card, CardContent } from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { useUsers } from "@/hooks"; - -export function UsersSection({ onSelect }: { onSelect: (id: string) => void }) { - const [search, setSearch] = useState(""); - const { data: users, isLoading } = useUsers(search || undefined); - - return ( -
-
- - setSearch(e.target.value)} - className="pl-9 h-9" - /> -
- {isLoading ? ( - - ) : !users || users.length === 0 ? ( - - ) : ( -
- {users.map((u) => ( - onSelect(u.user_id)} - > - -
-
- {u.avatar_url ? ( - - ) : ( - (u.username ?? "?").charAt(0).toUpperCase() - )} -
-
-

- {u.username ?? "Unknown"} -

-

- {u.total_messages} messages - {u.flagged_count > 0 && ( - - {u.flagged_count} flagged - - )} -

-
- -
-
-
- ))} -
- )} -
- ); -} diff --git a/services/frontend/src/components/mascot/index.ts b/services/frontend/src/components/mascot/index.ts deleted file mode 100644 index 361e7df..0000000 --- a/services/frontend/src/components/mascot/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { MascotProvider, useMascot } from "./mascot-context"; -export { MascotContainer } from "./mascot-container"; -export { MascotCanvas } from "./mascot-canvas"; -export { ChatPanel } from "./chat-panel"; diff --git a/services/frontend/src/components/messages/images-grid.tsx b/services/frontend/src/components/messages/images-grid.tsx deleted file mode 100644 index 15e5997..0000000 --- a/services/frontend/src/components/messages/images-grid.tsx +++ /dev/null @@ -1,62 +0,0 @@ -"use client"; - -import { ImageIcon } from "lucide-react"; -import { Card } from "@/components/ui/card"; -import type { MessageRecord } from "@/lib/types"; -import { extractFirstImage } from "./message-card"; - -export function ImagesGrid({ - images, - onSelect, -}: { - images: MessageRecord[]; - onSelect: (id: string) => void; -}) { - if (!images || images.length === 0) { - return ( -
- -

No images yet.

-
- ); - } - - return ( -
- {images.map((msg) => { - const imgUrl = extractFirstImage(msg.metadata); - return ( - onSelect(msg.id)} - > -
- {imgUrl ? ( - {msg.content - ) : ( -
- No image -
- )} - {msg.content && ( -
-

- {msg.username}: {msg.content} -

-
- )} -
-
- ); - })} -
- ); -} diff --git a/services/frontend/src/components/messages/message-detail-view.tsx b/services/frontend/src/components/messages/message-detail-view.tsx deleted file mode 100644 index 84136a5..0000000 --- a/services/frontend/src/components/messages/message-detail-view.tsx +++ /dev/null @@ -1,165 +0,0 @@ -"use client"; - -import { ExternalLink, Sparkles } from "lucide-react"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Badge } from "@/components/ui/badge"; -import { Card, CardContent } from "@/components/ui/card"; -import { formatBytes, safeParseJsonArray } from "@/lib/format"; -import type { MessageRecord } from "@/lib/types"; -import { cn } from "@/lib/utils"; - -function MiniStat({ - label, - value, - destructive, - capitalize, -}: { - label: string; - value: string; - destructive?: boolean; - capitalize?: boolean; -}) { - return ( - - -

{label}

-

- {value} -

-
-
- ); -} - -export function MessageDetailView({ - message, - attachments, -}: { - message: MessageRecord; - attachments: { - id: string; - filename: string; - type: string; - size: number; - uploaded_url?: string | null; - discord_url?: string | null; - }[]; -}) { - return ( -
-
- - - - {message.username.charAt(0).toUpperCase()} - - -
-
- {message.username} - - {new Date(message.created_at).toLocaleString()} - - {message.type === "deleted" && ( - - deleted - - )} - {message.type === "edited" && ( - - edited - - )} -
-

- {message.content} -

-
-
- {message.ai_analysis && ( -
-
- -

- AI Analysis -

-
-

{message.ai_analysis}

-
- )} - {message.ai_moderation_flags && message.ai_moderation_flags !== "[]" && ( -
-

- Moderation Flags -

-
- {safeParseJsonArray(message.ai_moderation_flags).map((f) => ( - - {f} - - ))} -
-
- )} -
- {message.ai_status && ( - - )} - {message.ai_severity && message.ai_severity !== "none" && ( - - )} - {message.ai_confidence != null && ( - - )} - {message.ai_recommended_action && - message.ai_recommended_action !== "none" && ( - - )} -
- {attachments.length > 0 && ( -
-

- Attachments ({attachments.length}) -

- -
- )} -
- ); -} diff --git a/services/frontend/src/components/messages/review-list.tsx b/services/frontend/src/components/messages/review-list.tsx deleted file mode 100644 index ed5624a..0000000 --- a/services/frontend/src/components/messages/review-list.tsx +++ /dev/null @@ -1,39 +0,0 @@ -"use client"; - -import { Flag } from "lucide-react"; -import type { MessageRecord } from "@/lib/types"; -import { MessageCard } from "./message-card"; - -export function ReviewList({ - reviews, - onSelect, - onReanalyze, -}: { - reviews: MessageRecord[]; - onSelect: (id: string) => void; - onReanalyze: (id: string) => void; -}) { - if (!reviews || reviews.length === 0) { - return ( -
- -

- No flagged messages to review. -

-
- ); - } - - return ( -
- {reviews.map((msg) => ( - - ))} -
- ); -} diff --git a/services/frontend/src/components/recordings/recording-list.tsx b/services/frontend/src/components/recordings/recording-list.tsx deleted file mode 100644 index f9e164a..0000000 --- a/services/frontend/src/components/recordings/recording-list.tsx +++ /dev/null @@ -1,94 +0,0 @@ -"use client"; - -import { Download, Headphones, Trash2 } from "lucide-react"; - -import { EmptyState, LoadingSkeleton } from "@/components/shared"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { - useDeleteRecording, - useRecordings, - useRecordingsWsSync, -} from "@/hooks"; -import { formatBytes } from "@/lib/format"; -import type { WsHook } from "@/lib/ws-hook"; - -interface RecordingListProps { - ws: WsHook; -} - -export function RecordingList({ ws }: RecordingListProps) { - const { data: recordings, isLoading } = useRecordings(); - const deleteMut = useDeleteRecording(); - - useRecordingsWsSync(ws); - - return ( - - - - - Voice Recordings - - - - {isLoading ? ( - - ) : !recordings || recordings.length === 0 ? ( - - ) : ( -
- {recordings.map((rec) => ( -
- - - - {(rec.username ?? "?").charAt(0).toUpperCase()} - - -
-

{rec.username}

-

- {rec.channel_name ?? rec.channel_id ?? "Unknown channel"} —{" "} - {new Date(rec.created_at).toLocaleString()} -

-
- - {formatBytes(rec.size_bytes)} - - {rec.download_url && ( - - )} - -
- ))} -
- )} -
-
- ); -} diff --git a/services/frontend/src/components/shared/detail-stat.tsx b/services/frontend/src/components/shared/detail-stat.tsx deleted file mode 100644 index daa0a73..0000000 --- a/services/frontend/src/components/shared/detail-stat.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Card, CardContent } from "@/components/ui/card"; -import { formatNumber } from "@/lib/format"; -import { cn } from "@/lib/utils"; - -interface DetailStatProps { - label: string; - value: number; - variant?: "default" | "danger" | "success"; - suffix?: string; -} - -const valueColor = { - default: "", - danger: "text-red-400", - success: "text-emerald-400", -}; - -/** - * Small stat label used inside detail views. - */ -export function DetailStat({ - label, - value, - variant = "default", - suffix, -}: DetailStatProps) { - return ( - - -

- {label} -

-

- {formatNumber(value)} - {suffix} -

-
-
- ); -} diff --git a/services/frontend/src/components/shared/stat-card.tsx b/services/frontend/src/components/shared/stat-card.tsx deleted file mode 100644 index 3073a50..0000000 --- a/services/frontend/src/components/shared/stat-card.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import type { LucideIcon } from "lucide-react"; - -import { Card, CardContent } from "@/components/ui/card"; -import { formatNumber } from "@/lib/format"; -import { cn } from "@/lib/utils"; - -interface StatCardProps { - label: string; - value: number; - icon: LucideIcon; - variant?: "default" | "danger" | "success" | "warning"; -} - -const variantStyles = { - default: "from-cyan-500/10 to-teal-500/5 border-cyan-500/20", - danger: "from-red-500/10 to-rose-500/5 border-red-500/20", - success: "from-emerald-500/10 to-green-500/5 border-emerald-500/20", - warning: "from-amber-500/10 to-yellow-500/5 border-amber-500/20", -}; - -const iconBg = { - default: "bg-cyan-500/15 text-cyan-400", - danger: "bg-red-500/15 text-red-400", - success: "bg-emerald-500/15 text-emerald-400", - warning: "bg-amber-500/15 text-amber-400", -}; - -const valueColor = { - default: "", - danger: "text-red-400", - success: "text-emerald-400", - warning: "text-amber-400", -}; - -/** - * Metric card used across dashboard and landing pages. - */ -export function StatCard({ - label, - value, - icon: Icon, - variant = "default", -}: StatCardProps) { - return ( - - -
-
-

- {label} -

-

- {formatNumber(value)} -

-
-
- -
-
-
-
- ); -} diff --git a/services/frontend/src/lib/api/chatbot.ts b/services/frontend/src/lib/api/chatbot.ts index 7f64a70..6a69d80 100644 --- a/services/frontend/src/lib/api/chatbot.ts +++ b/services/frontend/src/lib/api/chatbot.ts @@ -3,9 +3,9 @@ import { api } from "./client"; export const chatbotApi = { send: (message: string) => - api.post("/api/mascot/chat", { message }), + api.post("/api/chat", { message }), - getHistory: () => api.get("/api/mascot/chat/history"), + getHistory: () => api.get("/api/chat/history"), - clearHistory: () => api.delete<{ ok: boolean }>("/api/mascot/chat/history"), + clearHistory: () => api.delete<{ ok: boolean }>("/api/chat/history"), }; diff --git a/services/frontend/tsconfig.json b/services/frontend/tsconfig.json index cf9c65d..295acdb 100644 --- a/services/frontend/tsconfig.json +++ b/services/frontend/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "ES2017", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -19,7 +23,9 @@ } ], "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ] } }, "include": [ @@ -28,7 +34,10 @@ "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts", - "**/*.mts" + "**/*.mts", + ".next/dev/dev/types/**/*.ts" ], - "exclude": ["node_modules"] + "exclude": [ + "node_modules" + ] }