feat: migrate and redesign dashboard to modern React

- Full rewrite of legacy vanilla JS UI into React SPA
- Implement modern design system using Tailwind CSS and shadcn/ui primitives
- Create typed API modules and hooks for voice, media, and moderation
- Add new features: separated Music and Screen Share panels, Image Grid
- Implement unified WebSocket hook for real-time state and PCM audio
- Improve visualizer with smooth CSS transitions and live state sync
- Add __dirname polyfill for ES module compatibility
- Ensure responsive layout for mobile and desktop

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-05-16 19:17:34 +07:00
co-authored by Claude Opus 4.7
parent 3c7d722973
commit 82025a19b2
51 changed files with 2865 additions and 1204 deletions
+54
View File
@@ -0,0 +1,54 @@
import { useCallback, useEffect, useState } from "react";
import { listMessages, reanalyzeMessage } from "../api/messages";
import type { MessageRecord } from "../types/messages";
export function mergeMessages(current: MessageRecord[], incoming: MessageRecord[]): MessageRecord[] {
const byId = new Map(current.map((message) => [message.id, message]));
for (const message of incoming) {
byId.set(message.id, { ...byId.get(message.id), ...message });
}
return Array.from(byId.values())
.sort((a, b) => b.created_at - a.created_at || b.id.localeCompare(a.id))
.slice(0, 200);
}
export function useMessages() {
const [messages, setMessages] = useState<MessageRecord[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchMessages = useCallback(async (channelId?: string) => {
setLoading(true);
setError(null);
try {
const params = new URLSearchParams({ limit: "80" });
if (channelId) params.set("channel", channelId);
const result = await listMessages(params);
setMessages(result.data);
return result.data;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
setError(message);
throw err;
} finally {
setLoading(false);
}
}, []);
const reanalyze = useCallback(async (id: string) => {
setMessages((prev) =>
prev.map((message) =>
message.id === id
? { ...message, ai_status: "pending", ai_error: null, ai_analysis: null }
: message,
),
);
await reanalyzeMessage(id);
}, []);
useEffect(() => {
fetchMessages().catch(() => undefined);
}, [fetchMessages]);
return { messages, setMessages, loading, error, fetchMessages, reanalyze };
}