feat: add voice recordings feature with database integration
- Implemented PostgreSQL and SQLite schemas for voice recordings. - Created repository functions for inserting, updating, and listing voice recordings. - Developed uploader logic to handle file uploads and database updates. - Added routes for accessing voice recordings via API. - Enhanced UI state to include recordings tab. - Introduced moderation WebSocket event for uploaded recordings.
This commit is contained in:
+14
-2
@@ -5,6 +5,7 @@ import { MessagesPanel } from "./components/messages/MessagesPanel";
|
||||
import { ReviewPanel } from "./components/review/ReviewPanel";
|
||||
import { Tabs, TabsContent } from "./components/ui/tabs";
|
||||
import { VoicePanel } from "./components/voice/VoicePanel";
|
||||
import { RecordingsPanel } from "./components/recordings/RecordingsPanel";
|
||||
import { AuthOverlay } from "./components/layout/AuthOverlay";
|
||||
import { useDashboardSocket } from "./hooks/useDashboardSocket";
|
||||
import { mergeMessages, useMessages } from "./hooks/useMessages";
|
||||
@@ -75,6 +76,10 @@ export default function App() {
|
||||
onMessageAnalyzed: (message) => messages.setMessages((prev) => mergeMessages(prev, [message])),
|
||||
onAttachmentUploaded: () => messages.fetchMessages(selectedTextChannel).catch(() => undefined),
|
||||
onMediaState: media.setMediaState,
|
||||
onVoiceRecordingUploaded: (recording) => {
|
||||
const event = new CustomEvent("voice_recording_uploaded", { detail: recording });
|
||||
window.dispatchEvent(event);
|
||||
},
|
||||
onPcm: handleIncomingPcm,
|
||||
});
|
||||
|
||||
@@ -178,7 +183,7 @@ export default function App() {
|
||||
await patchUIState({ isListening: true });
|
||||
}, [isListening, patchUIState]);
|
||||
|
||||
const tabs = useMemo(() => ["voice", "media", "messages", "review"] as DashboardTab[], []);
|
||||
const tabs = useMemo(() => ["voice", "media", "messages", "recordings", "review"] as DashboardTab[], []);
|
||||
|
||||
return (
|
||||
<DashboardLayout
|
||||
@@ -189,7 +194,7 @@ export default function App() {
|
||||
>
|
||||
<div className="md:hidden">
|
||||
<Tabs value={activeTab} onValueChange={(value) => patchUIState({ activeTab: value as DashboardTab })}>
|
||||
<div className="mb-4 grid grid-cols-4 gap-2 rounded-2xl bg-muted p-1">
|
||||
<div className="mb-4 grid grid-cols-5 gap-2 rounded-2xl bg-muted p-1">
|
||||
{tabs.map((tab) => (
|
||||
<button key={tab} className={`rounded-xl px-2 py-2 text-xs font-medium ${activeTab === tab ? "bg-background text-foreground" : "text-muted-foreground"}`} onClick={() => patchUIState({ activeTab: tab })}>
|
||||
{tab}
|
||||
@@ -250,6 +255,13 @@ export default function App() {
|
||||
onReanalyze={messages.reanalyze}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="recordings">
|
||||
{!isAuthenticated ? (
|
||||
<AuthOverlay onAuthenticated={() => setIsAuthenticated(true)} />
|
||||
) : (
|
||||
<RecordingsPanel />
|
||||
)}
|
||||
</TabsContent>
|
||||
<TabsContent value="review">
|
||||
<ReviewPanel messages={messages.messages} onReanalyze={messages.reanalyze} />
|
||||
</TabsContent>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Bot, MessageSquare, Music2, ShieldAlert, Volume2 } from "lucide-react";
|
||||
import { Bot, MessageSquare, Music2, ShieldAlert, Volume2, Mic } from "lucide-react";
|
||||
import type { DashboardTab } from "../../types/ui";
|
||||
import { cn } from "../../lib/utils";
|
||||
import { Button } from "../ui/button";
|
||||
@@ -7,6 +7,7 @@ const navItems: Array<{ id: DashboardTab; label: string; icon: typeof Volume2 }>
|
||||
{ id: "voice", label: "Voice", icon: Volume2 },
|
||||
{ id: "media", label: "Media", icon: Music2 },
|
||||
{ id: "messages", label: "Messages", icon: MessageSquare },
|
||||
{ id: "recordings", label: "Recordings", icon: Mic },
|
||||
{ id: "review", label: "Review", icon: ShieldAlert },
|
||||
];
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface DashboardSocketHandlers {
|
||||
onMessageAnalyzed?: (message: MessageRecord) => void;
|
||||
onAttachmentUploaded?: () => void;
|
||||
onMediaState?: (state: MediaState) => void;
|
||||
onVoiceRecordingUploaded?: (recording: any) => void;
|
||||
onPcm?: (data: ArrayBuffer) => void;
|
||||
}
|
||||
|
||||
@@ -75,6 +76,9 @@ export function useDashboardSocket(handlers: DashboardSocketHandlers) {
|
||||
case "media_state":
|
||||
handlersRef.current.onMediaState?.(message.state);
|
||||
break;
|
||||
case "voice_recording_uploaded":
|
||||
handlersRef.current.onVoiceRecordingUploaded?.(message.data);
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// ignore malformed socket messages
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type DashboardTab = "voice" | "media" | "messages" | "review";
|
||||
export type DashboardTab = "voice" | "media" | "messages" | "review" | "recordings";
|
||||
|
||||
export interface UIState {
|
||||
selectedGuild?: string;
|
||||
|
||||
Reference in New Issue
Block a user