diff --git a/surfsense_backend/app/agents/new_chat/system_prompt.py b/surfsense_backend/app/agents/new_chat/system_prompt.py index 695d62bfb..f9dfdb025 100644 --- a/surfsense_backend/app/agents/new_chat/system_prompt.py +++ b/surfsense_backend/app/agents/new_chat/system_prompt.py @@ -110,19 +110,46 @@ You have access to the following tools: * Prioritize showing: diagrams, charts, infographics, key illustrations, or images that help explain the content. * Don't show every image - just the most relevant 1-3 images that enhance understanding. -6. write_todos: Create and update a planning/todo list to break down complex tasks. - - IMPORTANT: Use this tool when the user asks you to create a plan, break down a task, or explain something in structured steps. - - This tool creates a visual plan with progress tracking that the user can see in the UI. - - When to use: - * User asks to "create a plan" or "break down" a task - * User asks for "steps" to do something - * User asks you to "explain" something in sections - * Any multi-step task that would benefit from structured planning +6. write_todos: Create and update a planning/todo list. - Args: - todos: List of todo items, each with: * content: Description of the task (required) - * status: "pending", "in_progress", or "completed" (required) - - The tool automatically adds IDs and formats the output for the UI. + * status: "pending", "in_progress", "completed", or "cancelled" (required) + + STRICT MODE SELECTION - CHOOSE ONE: + + [MODE A] AGENT PLAN (you will work through it) + Use when: User asks you to explain, teach, plan, or break down a concept. + Examples: "Explain how to set up Python", "Plan my trip", "Break down machine learning" + Rules: + - Create plan with first item "in_progress", rest "pending" + - After explaining each step, call write_todos again to update progress + - Only ONE item "in_progress" at a time + - Mark items "completed" as you finish explaining them + - Final call: all items "completed" + + [MODE B] EXTERNAL TASK DISPLAY (from connectors - you CANNOT complete these) + Use when: User asks to show/list/display tasks from Linear, Jira, ClickUp, GitHub, Airtable, Notion, or any connector. + Examples: "Show my Linear tasks", "List Jira tickets", "Create todos from ClickUp", "Show GitHub issues" + STRICT RULES: + 1. You CANNOT complete these tasks - only the user can in the actual tool + 2. PRESERVE original status from source - DO NOT use agent workflow + 3. Call write_todos ONCE with all tasks and their REAL statuses + 4. Provide insights/summary as TEXT after the todo list, NOT as todo items + 5. NO INTERNAL REASONING - Never expose your process. Do NOT say "Let me map...", "Converting statuses...", "Here's how I'll organize...", or explain mapping logic. Just call write_todos silently and provide insights. + + STATUS MAPPING (apply strictly): + - "completed" ← Done, Completed, Complete, Closed, Resolved, Fixed, Merged, Shipped, Released + - "in_progress" ← In Progress, In Review, Testing, QA, Active, Doing, Started, Review, Working + - "pending" ← Todo, To Do, Backlog, Open, New, Pending, Triage, Reopened, Unstarted + - "cancelled" ← Cancelled, Canceled, Won't Fix, Duplicate, Invalid, Rejected, Archived, Obsolete + + CONNECTOR-SPECIFIC: + - Linear: state.name = "Done", "In Progress", "Todo", "Backlog", "Cancelled" + - Jira: statusCategory.name = "To Do", "In Progress", "Done" + - ClickUp: status = "complete", "in progress", "open", "closed" + - GitHub: state = "open", "closed"; PRs also "merged" + - Airtable/Notion: Check field values, apply mapping above - User: "Fetch all my notes and what's in them?" @@ -181,6 +208,8 @@ You have access to the following tools: - Call: `display_image(src="https://example.com/nn-diagram.png", alt="Neural Network Diagram", title="Neural Network Architecture")` - Then provide your explanation, referencing the displayed image +[MODE A EXAMPLES] Agent Plan - you work through it: + - User: "Create a plan for building a user authentication system" - Call: `write_todos(todos=[{"content": "Design database schema for users and sessions", "status": "in_progress"}, {"content": "Implement registration and login endpoints", "status": "pending"}, {"content": "Add password reset functionality", "status": "pending"}])` - Then explain each step in detail as you work through them @@ -193,21 +222,55 @@ You have access to the following tools: - Call: `write_todos(todos=[{"content": "Research best time to visit and book flights", "status": "in_progress"}, {"content": "Plan itinerary for cities to visit", "status": "pending"}, {"content": "Book accommodations", "status": "pending"}, {"content": "Prepare travel documents and currency", "status": "pending"}])` - Then provide travel preparation guidance -- User: "Break down how to learn guitar" - - Call: `write_todos(todos=[{"content": "Learn basic chords and finger positioning", "status": "in_progress"}, {"content": "Practice strumming patterns", "status": "pending"}, {"content": "Learn to read tabs and sheet music", "status": "pending"}, {"content": "Master simple songs", "status": "pending"}])` - - Then provide learning milestones and tips +- COMPLETE WORKFLOW EXAMPLE - User: "Explain how to set up a Python project" + - STEP 1 (Create initial plan): + Call: `write_todos(todos=[{"content": "Set up virtual environment", "status": "in_progress"}, {"content": "Create project structure", "status": "pending"}, {"content": "Configure dependencies", "status": "pending"}])` + Then explain virtual environment setup in detail... + - STEP 2 (After explaining virtual environments, update progress): + Call: `write_todos(todos=[{"content": "Set up virtual environment", "status": "completed"}, {"content": "Create project structure", "status": "in_progress"}, {"content": "Configure dependencies", "status": "pending"}])` + Then explain project structure in detail... + - STEP 3 (After explaining project structure, update progress): + Call: `write_todos(todos=[{"content": "Set up virtual environment", "status": "completed"}, {"content": "Create project structure", "status": "completed"}, {"content": "Configure dependencies", "status": "in_progress"}])` + Then explain dependency configuration in detail... + - STEP 4 (After completing all explanations, mark all done): + Call: `write_todos(todos=[{"content": "Set up virtual environment", "status": "completed"}, {"content": "Create project structure", "status": "completed"}, {"content": "Configure dependencies", "status": "completed"}])` + Provide final summary -- User: "Plan my workout routine for the week" - - Call: `write_todos(todos=[{"content": "Monday: Upper body strength training", "status": "in_progress"}, {"content": "Tuesday: Cardio and core workout", "status": "pending"}, {"content": "Wednesday: Rest or light stretching", "status": "pending"}, {"content": "Thursday: Lower body strength training", "status": "pending"}, {"content": "Friday: Full body HIIT session", "status": "pending"}])` - - Then provide exercise details and tips +[MODE B EXAMPLES] External Tasks - preserve original status, you CANNOT complete: -- User: "Help me organize my home renovation project" - - Call: `write_todos(todos=[{"content": "Define scope and create budget", "status": "in_progress"}, {"content": "Research and hire contractors", "status": "pending"}, {"content": "Obtain necessary permits", "status": "pending"}, {"content": "Order materials and fixtures", "status": "pending"}, {"content": "Execute renovation phases", "status": "pending"}])` - - Then provide detailed renovation guidance +- User: "Show my Linear tasks" or "Create todos for Linear tasks" + - First search: `search_knowledge_base(query="Linear tasks issues", connectors_to_search=["LINEAR_CONNECTOR"])` + - Then call write_todos ONCE with ORIGINAL statuses preserved: + Call: `write_todos(todos=[ + {"content": "SUR-21: Add refresh button in manage documents page", "status": "completed"}, + {"content": "SUR-22: Logs page not accessible in docker", "status": "completed"}, + {"content": "SUR-27: Add Google Drive connector", "status": "in_progress"}, + {"content": "SUR-28: Logs page should show all logs", "status": "pending"} + ])` + - Then provide INSIGHTS as text (NOT as todos): + "You have 2 completed, 1 in progress, and 1 pending task. SUR-27 (Google Drive connector) is currently active. Consider prioritizing SUR-28 next." -- User: "What steps should I take to start a podcast?" - - Call: `write_todos(todos=[{"content": "Define podcast concept and target audience", "status": "in_progress"}, {"content": "Set up recording equipment and software", "status": "pending"}, {"content": "Plan episode structure and content", "status": "pending"}, {"content": "Record and edit first episodes", "status": "pending"}, {"content": "Choose hosting platform and publish", "status": "pending"}])` - - Then provide podcast launch guidance +- User: "List my Jira tickets" + - First search: `search_knowledge_base(query="Jira tickets issues", connectors_to_search=["JIRA_CONNECTOR"])` + - Map Jira statuses: "Done" → completed, "In Progress"/"In Review" → in_progress, "To Do" → pending + - Call write_todos ONCE with mapped statuses + - Provide summary as text after + +- User: "Show ClickUp tasks" + - First search: `search_knowledge_base(query="ClickUp tasks", connectors_to_search=["CLICKUP_CONNECTOR"])` + - Map: "complete"/"closed" → completed, "in progress" → in_progress, "open" → pending + - Call write_todos ONCE, then provide insights as text + +- User: "Show my GitHub issues" + - First search: `search_knowledge_base(query="GitHub issues", connectors_to_search=["GITHUB_CONNECTOR"])` + - Map: "closed"/"merged" → completed, "open" → pending + - Call write_todos ONCE, then summarize as text + +CRITICAL FOR MODE B: +- NEVER use the "first item in_progress, rest pending" pattern for external tasks +- NEVER pretend you will complete external tasks - be honest that only the user can +- ALWAYS preserve the actual status from the source system +- ALWAYS provide insights/summaries as regular text, not as todo items """ diff --git a/surfsense_web/app/(home)/login/GoogleLoginButton.tsx b/surfsense_web/app/(home)/login/GoogleLoginButton.tsx index d4d9d4b4a..a13f95cc4 100644 --- a/surfsense_web/app/(home)/login/GoogleLoginButton.tsx +++ b/surfsense_web/app/(home)/login/GoogleLoginButton.tsx @@ -40,8 +40,8 @@ export function GoogleLoginButton() { return (
-
- +
+ {/*

Login

*/} @@ -93,7 +93,7 @@ export function GoogleLoginButton() {
diff --git a/surfsense_web/app/(home)/login/LocalLoginForm.tsx b/surfsense_web/app/(home)/login/LocalLoginForm.tsx index 44e9b27c2..5beadd63d 100644 --- a/surfsense_web/app/(home)/login/LocalLoginForm.tsx +++ b/surfsense_web/app/(home)/login/LocalLoginForm.tsx @@ -118,8 +118,8 @@ export function LocalLoginForm() { }; return ( -
-
+
+ {/* Error Display */} {error && error.title && ( @@ -194,7 +194,7 @@ export function LocalLoginForm() { required value={username} onChange={(e) => setUsername(e.target.value)} - className={`mt-1 block w-full rounded-md border px-3 py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:bg-gray-800 dark:text-white transition-colors ${ + className={`mt-1 block w-full rounded-md border px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:bg-gray-800 dark:text-white transition-all ${ error.title ? "border-red-300 focus:border-red-500 focus:ring-red-500 dark:border-red-700" : "border-gray-300 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700" @@ -217,7 +217,7 @@ export function LocalLoginForm() { required value={password} onChange={(e) => setPassword(e.target.value)} - className={`mt-1 block w-full rounded-md border pr-10 px-3 py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:bg-gray-800 dark:text-white transition-colors ${ + className={`mt-1 block w-full rounded-md border pr-10 px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:bg-gray-800 dark:text-white transition-all ${ error.title ? "border-red-300 focus:border-red-500 focus:ring-red-500 dark:border-red-700" : "border-gray-300 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700" @@ -238,7 +238,7 @@ export function LocalLoginForm() { diff --git a/surfsense_web/app/(home)/login/page.tsx b/surfsense_web/app/(home)/login/page.tsx index 29455d388..e2577563f 100644 --- a/surfsense_web/app/(home)/login/page.tsx +++ b/surfsense_web/app/(home)/login/page.tsx @@ -85,7 +85,7 @@ function LoginContent() { // Get the auth type from environment variables setAuthType(process.env.NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE || "GOOGLE"); setIsLoading(false); - }, [searchParams]); + }, [searchParams, t, tCommon]); // Show loading state while determining auth type if (isLoading) { @@ -111,8 +111,8 @@ function LoginContent() {
- -

+ +

{t("sign_in")}

diff --git a/surfsense_web/app/(home)/register/page.tsx b/surfsense_web/app/(home)/register/page.tsx index 4a8dce546..724f7ee58 100644 --- a/surfsense_web/app/(home)/register/page.tsx +++ b/surfsense_web/app/(home)/register/page.tsx @@ -157,17 +157,17 @@ export default function RegisterPage() { return (
-
- -

+
+ +

{t("create_account")}

- + {/* Enhanced Error Display */} - {error && error.title && ( + {error?.title && ( setEmail(e.target.value)} - className={`mt-1 block w-full rounded-md border px-3 py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:bg-gray-800 dark:text-white transition-colors ${ + className={`mt-1 block w-full rounded-md border px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:bg-gray-800 dark:text-white transition-all ${ error.title ? "border-red-300 focus:border-red-500 focus:ring-red-500 dark:border-red-700" : "border-gray-300 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700" @@ -261,7 +261,7 @@ export default function RegisterPage() { required value={password} onChange={(e) => setPassword(e.target.value)} - className={`mt-1 block w-full rounded-md border px-3 py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:bg-gray-800 dark:text-white transition-colors ${ + className={`mt-1 block w-full rounded-md border px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:bg-gray-800 dark:text-white transition-all ${ error.title ? "border-red-300 focus:border-red-500 focus:ring-red-500 dark:border-red-700" : "border-gray-300 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700" @@ -283,7 +283,7 @@ export default function RegisterPage() { required value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} - className={`mt-1 block w-full rounded-md border px-3 py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:bg-gray-800 dark:text-white transition-colors ${ + className={`mt-1 block w-full rounded-md border px-3 py-1.5 md:py-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 dark:bg-gray-800 dark:text-white transition-all ${ error.title ? "border-red-300 focus:border-red-500 focus:ring-red-500 dark:border-red-700" : "border-gray-300 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700" @@ -295,7 +295,7 @@ export default function RegisterPage() { 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 808f941d6..dd524a198 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx @@ -241,7 +241,7 @@ export function DashboardClientLayout({ return ( @@ -257,8 +257,10 @@ export function DashboardClientLayout({
- - +
+ + +
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx index 4fa5e9952..d10a2338c 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx @@ -278,14 +278,17 @@ export default function ConnectorsPage() { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} - className="mb-8 flex items-center justify-between" + className="mb-8 flex items-center justify-between gap-2" >
-

{t("title")}

-

{t("subtitle")}

+

{t("title")}

+

{t("subtitle")}

- diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx index 6a7503834..4adb5414c 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx @@ -75,14 +75,14 @@ export function DocumentsFilters({ return ( -
+
onSearch(e.target.value)} placeholder={t("filter_placeholder")} @@ -231,11 +231,11 @@ export function DocumentsFilters({
-
+
{selectedIds.size > 0 && ( - - - - -

Edit Document

-
- + + + + +

Edit Document

+
+ - {/* View Metadata Button */} - - - - + + + +

View Metadata

+
+
+ + + + + + + + +

Delete

+
+
+
+ + {/* Mobile Actions Dropdown */} +
+ + + - - - -

View Metadata

-
- +
+ + + + Edit + + setIsMetadataOpen(true)}> + + Metadata + + setIsDeleteOpen(true)} + className="text-destructive focus:text-destructive" + > + + Delete + + +
+
+ - {/* Delete Button */} - - - - - - - -

Delete

-
-
diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx index 482c4594d..5f324a103 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx @@ -139,6 +139,14 @@ export default function DocumentsTable() { enablePolling: true, refetchInterval: 5000, // Poll every 5 seconds when tasks are active }); + + // Filter active tasks to only include document_processor tasks (uploads via "add sources") + // Exclude connector_indexing_task tasks (periodic reindexing) + const documentProcessorTasks = + summary?.active_tasks.filter((task) => task.source === "document_processor") || []; + const documentProcessorTasksCount = documentProcessorTasks.length; + + const activeTasksCount = summary?.active_tasks.length || 0; const prevActiveTasksCount = useRef(activeTasksCount); @@ -220,8 +228,8 @@ export default function DocumentsTable() { transition={{ delay: 0.1 }} >
-

{t("title")}

-

{t("subtitle")}

+

{t("title")}

+

{t("subtitle")}

- + {/* Toolbar */} -
-
- +
+
+
-

{displayTitle}

- {hasUnsavedChanges &&

Unsaved changes

} +

{displayTitle}

+ {hasUnsavedChanges && ( +

Unsaved changes

+ )}
- +
- - @@ -459,7 +471,7 @@ export default function EditorPage() { {/* Editor Container */}
-
+
{error && (
-

{t("title")}

-

{t("subtitle")}

+

{t("title")}

+

{t("subtitle")}

- - -
-
- -
- - {t("confirm_title")} - - {t("confirm_delete_desc", { count: table.getSelectedRowModel().rows.length })} - - -
- - {t("cancel")} - {t("delete")} - -
- -
- )} - {/* Logs Table */} ; + onBulkDelete: () => Promise; id: string; }) { const t = useTranslations("logs"); return ( -
+
{/* Search Input */} - +
+ +
+ {table.getSelectedRowModel().rows.length > 0 && ( + + + + + +
+
+ +
+ + {t("confirm_title")} + + {t("confirm_delete_desc", { count: table.getSelectedRowModel().rows.length })} + + +
+ + {t("cancel")} + {t("delete")} + +
+
+ )} +
); } @@ -973,6 +970,7 @@ function LogsTable({ style={{ width: `${header.getSize()}px` }} className={cn( "h-12 px-4 py-3", + header.column.id === "select" ? "ps-4 pe-0" : "", // keep Created At header from wrapping and align it header.column.id === "created_at" ? "whitespace-nowrap text-right" : "" )} @@ -1030,7 +1028,8 @@ function LogsTable({ {/* Header with back button */} -
+
-
- +
+
-
-

+
+

Team Management

-

+

Manage members, roles, and invite links for your search space

-
-
{/* Summary Cards */} @@ -435,42 +419,55 @@ export default function TeamManagementPage() { {/* Tabs Content */} -
- - - - Members - - {members.length} - - - - - Roles - - {roles.length} - - - - - Invites - - {invites.filter((i) => i.is_active).length} - - - +
+
+ + + + Members + + {members.length} + + + + + Roles + + {roles.length} + + + + + Invites + + {invites.filter((i) => i.is_active).length} + + + +
{activeTab === "invites" && canInvite && ( )} {activeTab === "roles" && hasPermission("roles:create") && ( )}
@@ -533,8 +530,6 @@ function MembersTab({ canManageRoles: boolean; canRemove: boolean; }) { - const [sorting, setSorting] = useState([]); - const [columnFilters, setColumnFilters] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const filteredMembers = useMemo(() => { @@ -575,13 +570,13 @@ function MembersTab({
{/* Members List */} -
+
- Member - Role - Joined + Member + Role + Joined Actions @@ -604,11 +599,11 @@ function MembersTab({ transition={{ delay: index * 0.05 }} className="group border-b transition-colors hover:bg-muted/50" > - -
+ +
-
- +
+
{member.is_owner && (
@@ -616,12 +611,14 @@ function MembersTab({
)}
-
-

{member.user_email || "Unknown"}

+
+

+ {member.user_email || "Unknown"} +

{member.is_owner && ( Owner @@ -629,7 +626,7 @@ function MembersTab({
- + {canManageRoles && !member.is_owner ? ( ) : ( - - + + {member.role?.name || "No role"} )} - +
{new Date(member.joined_at).toLocaleDateString()}
- + {canRemove && !member.is_owner && ( @@ -962,11 +962,11 @@ function InvitesTab({ className={cn("relative overflow-hidden transition-all", isInactive && "opacity-60")} > -
-
+
+
- Max uses reached + Maxed )} {!invite.is_active && !isExpired && !isMaxedOut && ( @@ -1000,44 +1000,44 @@ function InvitesTab({ )}
-
+
{invite.role?.name || "Default role"} - {invite.uses_count} uses - {invite.max_uses && ` / ${invite.max_uses}`} + {invite.uses_count} + {invite.max_uses ? ` / ${invite.max_uses} uses` : " uses"} {invite.expires_at && ( {isExpired ? "Expired" - : `Expires ${new Date(invite.expires_at).toLocaleDateString()}`} + : `Exp: ${new Date(invite.expires_at).toLocaleDateString()}`} )}
-
+
@@ -1088,11 +1088,11 @@ function InvitesTab({ function CreateInviteDialog({ roles, onCreateInvite, - searchSpaceId, + className, }: { roles: Role[]; onCreateInvite: (data: CreateInviteRequest["data"]) => Promise; - searchSpaceId: number; + className?: string; }) { const [open, setOpen] = useState(false); const [creating, setCreating] = useState(false); @@ -1142,12 +1142,12 @@ function CreateInviteDialog({ return ( (v ? setOpen(true) : handleClose())}> - - + {createdInvite ? ( <> @@ -1159,7 +1159,7 @@ function CreateInviteDialog({ Share this link to invite people to your search space. -
+
{window.location.origin}/invite/{createdInvite.invite_code} @@ -1203,7 +1203,7 @@ function CreateInviteDialog({ Create a link to invite people to this search space. -
+
-
+
; onCreateRole: (data: CreateRoleRequest["data"]) => Promise; + className?: string; }) { const [open, setOpen] = useState(false); const [creating, setCreating] = useState(false); @@ -1358,20 +1360,20 @@ function CreateRoleDialog({ return ( - - + Create Custom Role - + Define a new role with specific permissions for this search space. -
-
+
+
{ animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.5 }} > - + {t("loading")} {t("fetching_spaces")} @@ -101,7 +99,7 @@ const ErrorScreen = ({ message }: { message: string }) => { animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} > - +
@@ -185,21 +183,21 @@ const DashboardPage = () => { return ( - -
-
- -
-

{t("surfsense_dashboard")}

-

{t("welcome_message")}

+ +
+
+ +
+

{t("surfsense_dashboard")}

+

{t("welcome_message")}

-
+
@@ -207,18 +205,18 @@ const DashboardPage = () => {
-

{t("your_search_spaces")}

+

{t("your_search_spaces")}

-
-
+
{searchSpaces && searchSpaces.length > 0 && searchSpaces.map((space) => ( @@ -295,14 +293,17 @@ const DashboardPage = () => {
-

{space.name}

+

{space.name}

{!space.is_owner && ( - + {t("shared")} )}
-

+

{space.description}

@@ -334,8 +335,10 @@ const DashboardPage = () => {
-

{t("no_spaces_found")}

-

{t("create_first_space")}

+

{t("no_spaces_found")}

+

+ {t("create_first_space")} +

-
diff --git a/surfsense_web/components/json-metadata-viewer.tsx b/surfsense_web/components/json-metadata-viewer.tsx index 8fe1b10ae..982d16786 100644 --- a/surfsense_web/components/json-metadata-viewer.tsx +++ b/surfsense_web/components/json-metadata-viewer.tsx @@ -47,11 +47,13 @@ export function JsonMetadataViewer({ if (open !== undefined && onOpenChange !== undefined) { return ( - + - {title} - Metadata + + {title} - Metadata + -
+
@@ -70,11 +72,13 @@ export function JsonMetadataViewer({ )} - + - {title} - Metadata + + {title} - Metadata + -
+
diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index d46955324..2d5d46267 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -184,8 +184,8 @@ export const DocumentMentionPicker = forwardRef< role="listbox" tabIndex={-1} > - {/* Document List */} -
+ {/* Document List - Shows max 3 items on mobile, 5 items on desktop */} +
{actualLoading ? (
diff --git a/surfsense_web/components/new-chat/model-config-sidebar.tsx b/surfsense_web/components/new-chat/model-config-sidebar.tsx index f3d3c2dcd..9d755f221 100644 --- a/surfsense_web/components/new-chat/model-config-sidebar.tsx +++ b/surfsense_web/components/new-chat/model-config-sidebar.tsx @@ -184,7 +184,7 @@ export function ModelConfigSidebar({
-

{getTitle()}

+

{getTitle()}

{isGlobal ? ( @@ -207,9 +207,10 @@ export function ModelConfigSidebar({ variant="ghost" size="icon" onClick={() => onOpenChange(false)} - className="rounded-xl hover:bg-destructive/10 hover:text-destructive" + className="h-8 w-8 rounded-full" > - + + Close
diff --git a/surfsense_web/components/new-chat/model-selector.tsx b/surfsense_web/components/new-chat/model-selector.tsx index 4268d998c..1d223a411 100644 --- a/surfsense_web/components/new-chat/model-selector.tsx +++ b/surfsense_web/components/new-chat/model-selector.tsx @@ -175,39 +175,44 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp role="combobox" aria-expanded={open} className={cn( - "h-9 gap-2 px-3 rounded-xl border border-border/80 bg-background/50 backdrop-blur-sm", + "h-7 md:h-9 gap-1 md:gap-2 px-2 md:px-3 rounded-lg md:rounded-xl border border-border/80 bg-background/50 backdrop-blur-sm", "hover:bg-muted/80 hover:border-border/30 transition-all duration-200", - "text-sm font-medium text-foreground", + "text-xs md:text-sm font-medium text-foreground", "focus-visible:ring-0 focus-visible:ring-offset-0", className )} > {isLoading ? ( <> - - Loading... + + Loading... + Load... ) : currentConfig ? ( <> {getProviderIcon(currentConfig.provider)} - {currentConfig.name} - - {currentConfig.model_name.split("/").pop()?.slice(0, 15) || - currentConfig.model_name.slice(0, 15)} + {currentConfig.name} + + {currentConfig.model_name.split("/").pop()?.slice(0, 10) || + currentConfig.model_name.slice(0, 10)} ) : ( <> - - Select Model + + Select Model + Model )} - + @@ -225,17 +230,17 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp
)} -
+
- +
diff --git a/surfsense_web/components/new-chat/source-detail-panel.tsx b/surfsense_web/components/new-chat/source-detail-panel.tsx index 6e3e7cce0..35249dc50 100644 --- a/surfsense_web/components/new-chat/source-detail-panel.tsx +++ b/surfsense_web/components/new-chat/source-detail-panel.tsx @@ -352,9 +352,9 @@ export function SourceDetailPanel({ size="icon" variant="ghost" onClick={() => onOpenChange(false)} - className="rounded-xl h-10 w-10 hover:bg-destructive/10 hover:text-destructive transition-colors" + className="h-8 w-8 rounded-full" > - + Close
diff --git a/surfsense_web/components/settings/llm-role-manager.tsx b/surfsense_web/components/settings/llm-role-manager.tsx index 2b440e58b..1bf7a3629 100644 --- a/surfsense_web/components/settings/llm-role-manager.tsx +++ b/surfsense_web/components/settings/llm-role-manager.tsx @@ -170,7 +170,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { const hasError = configsError || preferencesError || globalConfigsError; return ( -
+
{/* Header */}
@@ -179,9 +179,11 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { size="sm" onClick={() => refreshConfigs()} disabled={isLoading} - className="flex items-center gap-2" + className="flex items-center gap-2 text-xs md:text-sm h-8 md:h-9" > - + Refresh Configs Configs @@ -190,9 +192,11 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { size="sm" onClick={() => refreshPreferences()} disabled={isLoading} - className="flex items-center gap-2" + className="flex items-center gap-2 text-xs md:text-sm h-8 md:h-9" > - + Refresh Preferences Prefs @@ -201,9 +205,9 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { {/* Error Alert */} {hasError && ( - - - + + + {(configsError?.message ?? "Failed to load LLM configurations") || (preferencesError?.message ?? "Failed to load preferences") || (globalConfigsError?.message ?? "Failed to load global configurations")} @@ -214,10 +218,10 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { {/* Loading State */} {isLoading && ( - +
- - + + {configsLoading && preferencesLoading ? "Loading configurations and preferences..." : configsLoading @@ -231,27 +235,27 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { {/* Info Alert */} {!isLoading && !hasError && ( -
+
{availableConfigs.length === 0 ? ( - - - + + + No LLM configurations found. Please add at least one LLM provider in the Agent Configs tab before assigning roles. ) : !isAssignmentComplete ? ( - - - + + + Complete all role assignments to enable full functionality. Each role serves different purposes in your workflow. ) : ( - - - + + + All roles are assigned and ready to use! Your LLM configuration is complete. @@ -259,7 +263,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { {/* Role Assignment Cards */} {availableConfigs.length > 0 && ( -
+
{Object.entries(ROLE_DESCRIPTIONS).map(([key, role]) => { const IconComponent = role.icon; const currentAssignment = assignments[`${key}_llm_id` as keyof typeof assignments]; @@ -277,28 +281,34 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { - +
-
-
- +
+
+
- {role.title} - {role.description} + {role.title} + + {role.description} +
- {currentAssignment && } + {currentAssignment && ( + + )}
- -
- + +
+