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
parent 3c7d722973
commit 82025a19b2
51 changed files with 2865 additions and 1204 deletions

View File

@@ -49,7 +49,7 @@ class ApiError extends Error {
}
}
async function request<T>(path: string, init?: RequestInit): Promise<T> {
export async function request<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(path, {
headers: { "Content-Type": "application/json" },
...init,

21
frontend/src/api/media.ts Normal file
View File

@@ -0,0 +1,21 @@
import { request } from "./client";
import type { MediaMode, MediaState } from "../types/media";
export function getMediaStatus(): Promise<MediaState> {
return request<MediaState>('/api/media/status');
}
export function queueMedia(source: string, mode: MediaMode): Promise<MediaState> {
return request<MediaState>('/api/media/queue', {
method: 'POST',
body: JSON.stringify({ source, mode }),
});
}
export function skipMedia(): Promise<MediaState> {
return request<MediaState>('/api/media/skip', { method: 'POST' });
}
export function stopMedia(): Promise<MediaState> {
return request<MediaState>('/api/media/stop', { method: 'POST' });
}

View File

@@ -0,0 +1,3 @@
import { listMessages, listReview, reanalyzeMessage } from "./client";
export { listMessages, listReview, reanalyzeMessage };

View File

@@ -0,0 +1,13 @@
import { request } from "./client";
import type { UIState } from "../types/ui";
export function getUIState(): Promise<UIState> {
return request<UIState>('/api/ui-state');
}
export function updateUIState(patch: Partial<UIState>): Promise<UIState> {
return request<UIState>('/api/ui-state', {
method: 'POST',
body: JSON.stringify(patch),
});
}

33
frontend/src/api/voice.ts Normal file
View File

@@ -0,0 +1,33 @@
import { request } from "./client";
import type { Channel, Guild, VoiceStatus } from "../types/voice";
export function getGuilds(): Promise<Guild[]> {
return request<Guild[]>('/api/guilds');
}
export function getVoiceChannels(guildId: string): Promise<Channel[]> {
return request<Channel[]>(`/api/guilds/${guildId}/voice-channels`);
}
export function getTextChannels(guildId: string): Promise<Channel[]> {
return request<Channel[]>(`/api/guilds/${guildId}/channels`);
}
export function getThreads(guildId: string): Promise<Channel[]> {
return request<Channel[]>(`/api/guilds/${guildId}/threads`);
}
export function getVoiceStatus(): Promise<VoiceStatus> {
return request<VoiceStatus>('/api/status');
}
export function connectVoice(guildId: string, channelId: string): Promise<VoiceStatus> {
return request<VoiceStatus>('/api/connect', {
method: 'POST',
body: JSON.stringify({ guildId, channelId }),
});
}
export function disconnectVoice(): Promise<VoiceStatus> {
return request<VoiceStatus>('/api/disconnect', { method: 'POST' });
}