From 2fb86ad6871c741d0ff23aa2660bdd47af22a089 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Tue, 12 Aug 2025 16:48:03 -0700 Subject: [PATCH] refactor: integrate voice selection utility in podcast audio generation - remove NavUser component and update sidebar layout; --- .../app/agents/podcaster/nodes.py | 13 +- .../app/agents/podcaster/utils.py | 69 +++++++ .../components/sidebar/app-sidebar.tsx | 4 - surfsense_web/components/sidebar/nav-user.tsx | 176 ------------------ 4 files changed, 71 insertions(+), 191 deletions(-) create mode 100644 surfsense_backend/app/agents/podcaster/utils.py delete mode 100644 surfsense_web/components/sidebar/nav-user.tsx diff --git a/surfsense_backend/app/agents/podcaster/nodes.py b/surfsense_backend/app/agents/podcaster/nodes.py index 2309a29..35e676b 100644 --- a/surfsense_backend/app/agents/podcaster/nodes.py +++ b/surfsense_backend/app/agents/podcaster/nodes.py @@ -16,6 +16,7 @@ from app.services.llm_service import get_user_long_context_llm from .configuration import Configuration from .prompts import get_podcast_generation_prompt from .state import PodcastTranscriptEntry, PodcastTranscripts, State +from .utils import get_voice_for_provider async def create_podcast_transcript( @@ -121,16 +122,6 @@ async def create_merged_podcast_audio( output_path = f"podcasts/{session_id}_podcast.mp3" os.makedirs("podcasts", exist_ok=True) - # Map of speaker_id to voice - voice_mapping = { - 0: "alloy", # Default/intro voice - 1: "echo", # First speaker - # 2: "fable", # Second speaker - # 3: "onyx", # Third speaker - # 4: "nova", # Fourth speaker - # 5: "shimmer" # Fifth speaker - } - # Generate audio for each transcript segment audio_files = [] @@ -144,7 +135,7 @@ async def create_merged_podcast_audio( dialog = segment.get("dialog", "") # Select voice based on speaker_id - voice = voice_mapping.get(speaker_id, "alloy") + voice = get_voice_for_provider(app_config.TTS_SERVICE, speaker_id) # Generate a unique filename for this segment filename = f"{temp_dir}/{session_id}_{index}.mp3" diff --git a/surfsense_backend/app/agents/podcaster/utils.py b/surfsense_backend/app/agents/podcaster/utils.py new file mode 100644 index 0000000..fb33761 --- /dev/null +++ b/surfsense_backend/app/agents/podcaster/utils.py @@ -0,0 +1,69 @@ +def get_voice_for_provider(provider: str, speaker_id: int) -> dict | str: + """ + Get the appropriate voice configuration based on the TTS provider and speaker ID. + + Args: + provider: The TTS provider (e.g., "openai/tts-1", "vertex_ai/test") + speaker_id: The ID of the speaker (0-5) + + Returns: + Voice configuration - string for OpenAI, dict for Vertex AI + """ + # Extract provider type from the model string + provider_type = ( + provider.split("/")[0].lower() if "/" in provider else provider.lower() + ) + + if provider_type == "openai": + # OpenAI voice mapping - simple string values + openai_voices = { + 0: "alloy", # Default/intro voice + 1: "echo", # First speaker + 2: "fable", # Second speaker + 3: "onyx", # Third speaker + 4: "nova", # Fourth speaker + 5: "shimmer", # Fifth speaker + } + return openai_voices.get(speaker_id, "alloy") + + elif provider_type == "vertex_ai": + # Vertex AI voice mapping - dict with languageCode and name + vertex_voices = { + 0: { + "languageCode": "en-US", + "name": "en-US-Studio-O", + }, + 1: { + "languageCode": "en-US", + "name": "en-US-Studio-M", + }, + 2: { + "languageCode": "en-UK", + "name": "en-UK-Studio-A", + }, + 3: { + "languageCode": "en-UK", + "name": "en-UK-Studio-B", + }, + 4: { + "languageCode": "en-AU", + "name": "en-AU-Studio-A", + }, + 5: { + "languageCode": "en-AU", + "name": "en-AU-Studio-B", + }, + } + return vertex_voices.get(speaker_id, vertex_voices[0]) + + else: + # Default fallback to OpenAI format for unknown providers + default_voices = { + 0: "alloy", + 1: "echo", + 2: "fable", + 3: "onyx", + 4: "nova", + 5: "shimmer", + } + return default_voices.get(speaker_id, "alloy") diff --git a/surfsense_web/components/sidebar/app-sidebar.tsx b/surfsense_web/components/sidebar/app-sidebar.tsx index 70eb3fb..98e913a 100644 --- a/surfsense_web/components/sidebar/app-sidebar.tsx +++ b/surfsense_web/components/sidebar/app-sidebar.tsx @@ -23,7 +23,6 @@ import { Logo } from "@/components/Logo"; import { NavMain } from "@/components/sidebar/nav-main"; import { NavProjects } from "@/components/sidebar/nav-projects"; import { NavSecondary } from "@/components/sidebar/nav-secondary"; -import { NavUser } from "@/components/sidebar/nav-user"; import { Sidebar, SidebarContent, @@ -240,9 +239,6 @@ export const AppSidebar = memo(function AppSidebar({ - - {/* User Profile Section */} - ); diff --git a/surfsense_web/components/sidebar/nav-user.tsx b/surfsense_web/components/sidebar/nav-user.tsx deleted file mode 100644 index 114f42a..0000000 --- a/surfsense_web/components/sidebar/nav-user.tsx +++ /dev/null @@ -1,176 +0,0 @@ -"use client"; - -import { BadgeCheck, ChevronsUpDown, LogOut, Settings, User as UserIcon } from "lucide-react"; -import { useParams, useRouter } from "next/navigation"; -import { memo, useCallback, useEffect, useState } from "react"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { - SidebarGroup, - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, - useSidebar, -} from "@/components/ui/sidebar"; -import { apiClient } from "@/lib/api"; - -interface User { - id: string; - email: string; - is_active: boolean; - is_superuser: boolean; - is_verified: boolean; -} - -interface UserData { - name: string; - email: string; - avatar: string; -} - -// Memoized NavUser component for better performance -export const NavUser = memo(function NavUser() { - const { isMobile } = useSidebar(); - const router = useRouter(); - const { search_space_id } = useParams(); - - // User state management - const [user, setUser] = useState(null); - const [isLoadingUser, setIsLoadingUser] = useState(true); - const [userError, setUserError] = useState(null); - - // Fetch user details - useEffect(() => { - const fetchUser = async () => { - try { - if (typeof window === "undefined") return; - - try { - const userData = await apiClient.get("users/me"); - setUser(userData); - setUserError(null); - } catch (error) { - console.error("Error fetching user:", error); - setUserError(error instanceof Error ? error.message : "Unknown error occurred"); - } finally { - setIsLoadingUser(false); - } - } catch (error) { - console.error("Error in fetchUser:", error); - setIsLoadingUser(false); - } - }; - - fetchUser(); - }, []); - - // Create user object for display - const userData: UserData = { - name: user?.email ? user.email.split("@")[0] : "User", - email: - user?.email || - (isLoadingUser ? "Loading..." : userError ? "Error loading user" : "Unknown User"), - avatar: "/icon-128.png", // Default avatar - }; - - // Memoized logout handler - const handleLogout = useCallback(() => { - if (typeof window !== "undefined") { - localStorage.removeItem("surfsense_bearer_token"); - router.push("/"); - } - }, [router]); - - // Get user initials for avatar fallback - const userInitials = userData.name - .split(" ") - .map((n: string) => n[0]) - .join("") - .toUpperCase() - .slice(0, 2); - - return ( - - - - - - - - - - {userInitials || } - - -
- {userData.name} - {userData.email} -
- -
-
- - -
- - - - {userInitials || } - - -
- {userData.name} - {userData.email} -
-
-
- - - router.push(`/dashboard/${search_space_id}/api-key`)} - aria-label="Manage API key" - > - - API Key - - - - router.push(`/settings`)} - aria-label="Go to settings" - > - - Settings - - - - Sign out - -
-
-
-
-
- ); -});