feat(system): implement graceful shutdown and expand websocket events

Improve system reliability and real-time capabilities by implementing a robust lifecycle management system and adding new broadcast events for attachments and voice recordings.

- Implement asynchronous graceful shutdown in backend to close HTTP, WebSocket, Redis, and database connections.
- Add new WebSocket broadcast events: `attachment_created`, `voice_recording_started`, `voice_recording_stopped`, `voice_recording_uploaded`, and `analysis_queue_status`.
- Refactor media status handling to use boolean `playing` state instead of string-based status.
- Centralize `PageResult` and `VoiceRecording` types to improve consistency between frontend and backend.
- Update frontend API client to include `listRecordings` and handle new WebSocket event types.
- Fix type mismatches in voice command handling and media status reporting.
This commit is contained in:
MythEclipse
2026-06-09 16:56:44 +07:00
parent 3a7b005d95
commit 7108f6bb47
14 changed files with 161 additions and 90 deletions
@@ -1,43 +1,24 @@
// ─── Recordings Sub-Panel — BUG 1 FIX: useEffect instead of useMemo for side effects ──
// ─── Recordings Sub-Panel ──
import { Download, Mic } from "lucide-react";
import { useEffect, useState } from "react";
import type { VoiceRecording } from "../../../shared/api/client";
import { listRecordings } from "../../../shared/api/client";
import { formatBytes, formatDate } from "../../../shared/lib/utils";
import { Badge, Button, EmptyStateMascot, Skeleton } from "../../../shared/ui";
interface VoiceRecording {
id: string;
user_id: string;
username: string;
avatar_url: string | null;
guild_id: string | null;
channel_id: string | null;
channel_name: string | null;
filename: string;
size_bytes: number;
download_url: string | null;
upload_status: "pending" | "uploaded" | "failed";
upload_error: string | null;
created_at: number;
uploaded_at: number | null;
}
export function RecordingsSubPanel() {
const [recordings, setRecordings] = useState<VoiceRecording[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// BUG 1 FIX: proper useEffect for async data fetching
useEffect(() => {
let cancelled = false;
async function loadRecordings() {
try {
setLoading(true);
setError(null);
const response = await fetch("/api/recordings");
if (!response.ok)
throw new Error(`Failed to load recordings: ${response.status}`);
const data = (await response.json()) as VoiceRecording[];
const data = await listRecordings();
if (!cancelled) setRecordings(data);
} catch (err) {
if (!cancelled)
@@ -15,6 +15,9 @@ export function useVoiceControl() {
const [textChannels, setTextChannels] = useState<Channel[]>([]);
const [voiceStatus, setVoiceStatus] = useState<VoiceStatus>({
connected: false,
activeGuildId: null,
activeChannelId: null,
activeChannelName: null,
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);