diff --git a/surfsense_backend/.env.example b/surfsense_backend/.env.example
index 349cb0307..2a0351d7b 100644
--- a/surfsense_backend/.env.example
+++ b/surfsense_backend/.env.example
@@ -1,5 +1,12 @@
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/surfsense
+SECRET_KEY=SECRET
+NEXT_FRONTEND_URL=http://localhost:3000
+
+#Celery Config
+CELERY_BROKER_URL=redis://localhost:6379/0
+CELERY_RESULT_BACKEND=redis://localhost:6379/0
+
#Celery Config
CELERY_BROKER_URL=redis://localhost:6379/0
CELERY_RESULT_BACKEND=redis://localhost:6379/0
diff --git a/surfsense_backend/alembic/versions/32_add_podcast_staleness_detection.py b/surfsense_backend/alembic/versions/32_add_podcast_staleness_detection.py
deleted file mode 100644
index d63e6cba2..000000000
--- a/surfsense_backend/alembic/versions/32_add_podcast_staleness_detection.py
+++ /dev/null
@@ -1,50 +0,0 @@
-"""Add podcast staleness detection columns to chats and podcasts tables
-
-This feature allows the system to detect when a podcast is outdated compared to the current state of the chat it was generated from, enabling users to regenerate podcasts when needed.
-
-Revision ID: 32
-Revises: 31
-"""
-
-from collections.abc import Sequence
-
-import sqlalchemy as sa
-
-from alembic import op
-
-# revision identifiers
-revision: str = "32"
-down_revision: str | None = "31"
-branch_labels: str | Sequence[str] | None = None
-depends_on: str | Sequence[str] | None = None
-
-
-def upgrade() -> None:
- """Add state_version, chat_state_version, and chat_id to chats and podcasts tables."""
-
- # Add state_version column to chats table with default value of 1
- op.add_column(
- "chats",
- sa.Column("state_version", sa.BigInteger(), nullable=False, server_default="1"),
- )
-
- # Add chat_state_version column to podcasts table (nullable, set when podcast is generated)
- op.add_column(
- "podcasts", sa.Column("chat_state_version", sa.BigInteger(), nullable=True)
- )
-
- # Add chat_id column to podcasts table (nullable, set when podcast is generated from a chat)
- op.add_column("podcasts", sa.Column("chat_id", sa.Integer(), nullable=True))
-
-
-def downgrade() -> None:
- """Remove state_version, chat_state_version, and chat_id columns."""
-
- # Remove chat_state_version from podcasts table
- op.drop_column("podcasts", "chat_state_version")
-
- # Remove chat_id from podcasts table
- op.drop_column("podcasts", "chat_id")
-
- # Remove state_version from chats table
- op.drop_column("chats", "state_version")
diff --git a/surfsense_backend/alembic/versions/34_add_podcast_staleness_detection.py b/surfsense_backend/alembic/versions/34_add_podcast_staleness_detection.py
new file mode 100644
index 000000000..4991cd58e
--- /dev/null
+++ b/surfsense_backend/alembic/versions/34_add_podcast_staleness_detection.py
@@ -0,0 +1,60 @@
+"""Add podcast staleness detection columns to chats and podcasts tables
+
+This feature allows the system to detect when a podcast is outdated compared to
+the current state of the chat it was generated from, enabling users to regenerate
+podcasts when needed.
+
+Revision ID: 34
+Revises: 33
+"""
+
+from collections.abc import Sequence
+
+from alembic import op
+
+# revision identifiers
+revision: str = "34"
+down_revision: str | None = "33"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+ """Add columns only if they don't already exist (safe for re-runs)."""
+
+ # Add 'state_version' column to chats table (default 1)
+ op.execute("""
+ ALTER TABLE chats
+ ADD COLUMN IF NOT EXISTS state_version BIGINT DEFAULT 1 NOT NULL
+ """)
+
+ # Add 'chat_state_version' column to podcasts table
+ op.execute("""
+ ALTER TABLE podcasts
+ ADD COLUMN IF NOT EXISTS chat_state_version BIGINT
+ """)
+
+ # Add 'chat_id' column to podcasts table
+ op.execute("""
+ ALTER TABLE podcasts
+ ADD COLUMN IF NOT EXISTS chat_id INTEGER
+ """)
+
+
+def downgrade() -> None:
+ """Remove columns only if they exist."""
+
+ op.execute("""
+ ALTER TABLE podcasts
+ DROP COLUMN IF EXISTS chat_state_version
+ """)
+
+ op.execute("""
+ ALTER TABLE podcasts
+ DROP COLUMN IF EXISTS chat_id
+ """)
+
+ op.execute("""
+ ALTER TABLE chats
+ DROP COLUMN IF EXISTS state_version
+ """)
diff --git a/surfsense_backend/app/agents/podcaster/nodes.py b/surfsense_backend/app/agents/podcaster/nodes.py
index bce9882d6..51d748a3e 100644
--- a/surfsense_backend/app/agents/podcaster/nodes.py
+++ b/surfsense_backend/app/agents/podcaster/nodes.py
@@ -29,6 +29,7 @@ async def create_podcast_transcript(
configuration = Configuration.from_runnable_config(config)
user_id = configuration.user_id
search_space_id = configuration.search_space_id
+ podcast_title = configuration.podcast_title
# Get user's long context LLM
llm = await get_user_long_context_llm(state.db_session, user_id, search_space_id)
@@ -37,8 +38,8 @@ async def create_podcast_transcript(
print(error_message)
raise RuntimeError(error_message)
- # Get the prompt
- prompt = get_podcast_generation_prompt()
+ # Get the prompt with podcast_title as user_prompt
+ prompt = get_podcast_generation_prompt(user_prompt=podcast_title)
# Create the messages
messages = [
diff --git a/surfsense_backend/app/agents/podcaster/prompts.py b/surfsense_backend/app/agents/podcaster/prompts.py
index a3d6c3147..3e0981f32 100644
--- a/surfsense_backend/app/agents/podcaster/prompts.py
+++ b/surfsense_backend/app/agents/podcaster/prompts.py
@@ -1,11 +1,19 @@
import datetime
-def get_podcast_generation_prompt():
+def get_podcast_generation_prompt(user_prompt: str | None = None):
+ user_prompt_section = ""
+ if user_prompt:
+ user_prompt_section = f"""
+
+{user_prompt}
+
+"""
+
return f"""
Today's date: {datetime.datetime.now().strftime("%Y-%m-%d")}
-You are a master podcast scriptwriter, adept at transforming diverse input content into a lively, engaging, and natural-sounding conversation between two distinct podcast hosts. Your primary objective is to craft authentic, flowing dialogue that captures the spontaneity and chemistry of a real podcast discussion, completely avoiding any hint of robotic scripting or stiff formality. Think dynamic interplay, not just information delivery.
+You are a master podcast scriptwriter, adept at transforming diverse input content into a lively, engaging, and natural-sounding conversation between two distinct podcast hosts. Your primary objective is to craft authentic, flowing dialogue that captures the spontaneity and chemistry of a real podcast discussion, completely avoiding any hint of robotic scripting or stiff formality. Think dynamic interplay, not just information delivery.{user_prompt_section}
- '': A block of text containing the information to be discussed in the podcast. This could be research findings, an article summary, a detailed outline, user chat history related to the topic, or any other relevant raw information. The content might be unstructured but serves as the factual basis for the podcast dialogue.
diff --git a/surfsense_backend/app/tasks/celery_tasks/podcast_tasks.py b/surfsense_backend/app/tasks/celery_tasks/podcast_tasks.py
index 59a3bb2b1..1e938d829 100644
--- a/surfsense_backend/app/tasks/celery_tasks/podcast_tasks.py
+++ b/surfsense_backend/app/tasks/celery_tasks/podcast_tasks.py
@@ -46,7 +46,7 @@ def generate_chat_podcast_task(
Args:
chat_id: ID of the chat to generate podcast from
search_space_id: ID of the search space
- podcast_title: Title for the podcast
+ podcast_title: Title for the podcast (used as user prompt for generation)
user_id: ID of the user
"""
loop = asyncio.new_event_loop()
diff --git a/surfsense_backend/app/tasks/podcast_tasks.py b/surfsense_backend/app/tasks/podcast_tasks.py
index 51e750f4d..ea125fa11 100644
--- a/surfsense_backend/app/tasks/podcast_tasks.py
+++ b/surfsense_backend/app/tasks/podcast_tasks.py
@@ -96,7 +96,7 @@ async def generate_chat_podcast(
config = {
"configurable": {
- "podcast_title": "SurfSense",
+ "podcast_title": podcast_title,
"user_id": str(user_id),
"search_space_id": search_space_id,
}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx b/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx
index 55da177a4..e1e51c601 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx
@@ -1,10 +1,12 @@
"use client";
-import { Loader2 } from "lucide-react";
+import { useAtom } from "jotai";
+import { Loader2, PanelRight } from "lucide-react";
import { usePathname, useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import type React from "react";
import { useEffect, useMemo, useState } from "react";
+import { ChatPanelContainer } from "@/components/chat/ChatPanel/ChatPanelContainer";
import { DashboardBreadcrumb } from "@/components/dashboard-breadcrumb";
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
import { AppSidebarProvider } from "@/components/sidebar/AppSidebarProvider";
@@ -13,6 +15,8 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
import { Separator } from "@/components/ui/separator";
import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { useLLMPreferences } from "@/hooks/use-llm-configs";
+import { cn } from "@/lib/utils";
+import { chatUIAtom } from "@/stores/chat/chat-ui.atom";
export function DashboardClientLayout({
children,
@@ -30,6 +34,10 @@ export function DashboardClientLayout({
const pathname = usePathname();
const searchSpaceIdNum = Number(searchSpaceId);
+ const [chatUIState, setChatUIState] = useAtom(chatUIAtom);
+
+ const { isChatPannelOpen } = chatUIState;
+
const { loading, error, isOnboardingComplete } = useLLMPreferences(searchSpaceIdNum);
const [hasCheckedOnboarding, setHasCheckedOnboarding] = useState(false);
@@ -129,28 +137,49 @@ export function DashboardClientLayout({
}
return (
-
+
{/* Use AppSidebarProvider which fetches user, search space, and recent chats */}
-
-
-