diff --git a/surfsense_backend/app/agents/new_chat/tools/registry.py b/surfsense_backend/app/agents/new_chat/tools/registry.py index db15105ae..af00cc44d 100644 --- a/surfsense_backend/app/agents/new_chat/tools/registry.py +++ b/surfsense_backend/app/agents/new_chat/tools/registry.py @@ -214,7 +214,7 @@ BUILTIN_TOOLS: list[ToolDefinition] = [ # ========================================================================= ToolDefinition( name="update_memory", - description="Update the memory document (personal or team) with curated long-term information", + description="Save important long-term facts, preferences, and instructions to the (personal or team) memory", factory=lambda deps: ( create_update_team_memory_tool( search_space_id=deps["search_space_id"], diff --git a/surfsense_web/components/homepage/hero-section.tsx b/surfsense_web/components/homepage/hero-section.tsx index ba5a9d9ac..936facef7 100644 --- a/surfsense_web/components/homepage/hero-section.tsx +++ b/surfsense_web/components/homepage/hero-section.tsx @@ -2,7 +2,7 @@ import { ChevronDown, Download, Monitor } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; import Link from "next/link"; -import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import React, { memo, useCallback, useEffect, useRef, useState } from "react"; import Balancer from "react-wrap-balancer"; import { DropdownMenu, @@ -12,6 +12,11 @@ import { } from "@/components/ui/dropdown-menu"; import { ExpandedMediaOverlay, useExpandedMedia } from "@/components/ui/expanded-gif-overlay"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { + GITHUB_RELEASES_URL, + getAssetLabel, + usePrimaryDownload, +} from "@/lib/desktop-download-utils"; import { AUTH_TYPE, BACKEND_URL } from "@/lib/env-config"; import { trackLoginAttempt } from "@/lib/posthog/events"; import { cn } from "@/lib/utils"; @@ -200,107 +205,8 @@ function GetStartedButton() { ); } -type OSInfo = { - os: "macOS" | "Windows" | "Linux"; - arch: "arm64" | "x64"; -}; - -function useUserOS(): OSInfo { - const [info, setInfo] = useState({ os: "macOS", arch: "arm64" }); - useEffect(() => { - const ua = navigator.userAgent; - let os: OSInfo["os"] = "macOS"; - let arch: OSInfo["arch"] = "x64"; - - if (/Windows/i.test(ua)) { - os = "Windows"; - arch = "x64"; - } else if (/Linux/i.test(ua)) { - os = "Linux"; - arch = "x64"; - } else { - os = "macOS"; - arch = /Mac/.test(ua) && !/Intel/.test(ua) ? "arm64" : "arm64"; - } - - const uaData = (navigator as Navigator & { userAgentData?: { architecture?: string } }) - .userAgentData; - if (uaData?.architecture === "arm") arch = "arm64"; - else if (uaData?.architecture === "x86") arch = "x64"; - - setInfo({ os, arch }); - }, []); - return info; -} - -interface ReleaseAsset { - name: string; - url: string; -} - -function useLatestRelease() { - const [assets, setAssets] = useState([]); - - useEffect(() => { - const controller = new AbortController(); - fetch("https://api.github.com/repos/MODSetter/SurfSense/releases/latest", { - signal: controller.signal, - }) - .then((r) => r.json()) - .then((data) => { - if (data?.assets) { - setAssets( - data.assets - .filter((a: { name: string }) => /\.(exe|dmg|AppImage|deb)$/.test(a.name)) - .map((a: { name: string; browser_download_url: string }) => ({ - name: a.name, - url: a.browser_download_url, - })) - ); - } - }) - .catch(() => {}); - return () => controller.abort(); - }, []); - - return assets; -} - -const ASSET_LABELS: Record = { - ".exe": "Windows (exe)", - "-arm64.dmg": "macOS Apple Silicon (dmg)", - "-x64.dmg": "macOS Intel (dmg)", - "-arm64.zip": "macOS Apple Silicon (zip)", - "-x64.zip": "macOS Intel (zip)", - ".AppImage": "Linux (AppImage)", - ".deb": "Linux (deb)", -}; - -function getAssetLabel(name: string): string { - for (const [suffix, label] of Object.entries(ASSET_LABELS)) { - if (name.endsWith(suffix)) return label; - } - return name; -} - function DownloadButton() { - const { os, arch } = useUserOS(); - const assets = useLatestRelease(); - - const { primary, alternatives } = useMemo(() => { - if (assets.length === 0) return { primary: null, alternatives: [] }; - - const matchers: Record boolean> = { - Windows: (n) => n.endsWith(".exe"), - macOS: (n) => n.endsWith(`-${arch}.dmg`), - Linux: (n) => n.endsWith(".AppImage"), - }; - - const match = matchers[os]; - const primary = assets.find((a) => match(a.name)) ?? null; - const alternatives = assets.filter((a) => a !== primary); - return { primary, alternatives }; - }, [assets, os, arch]); + const { os, primary, alternatives } = usePrimaryDownload(); const fallbackUrl = GITHUB_RELEASES_URL; @@ -504,5 +410,3 @@ const TabVideo = memo(function TabVideo({ src }: { src: string }) { ); }); - -const GITHUB_RELEASES_URL = "https://github.com/MODSetter/SurfSense/releases/latest"; diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx index 6faa81f22..81fbeef91 100644 --- a/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx +++ b/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx @@ -3,6 +3,7 @@ import { Check, ChevronUp, + Download, ExternalLink, Info, Languages, @@ -29,6 +30,8 @@ import { } from "@/components/ui/dropdown-menu"; import { Spinner } from "@/components/ui/spinner"; import { useLocaleContext } from "@/contexts/LocaleContext"; +import { usePlatform } from "@/hooks/use-platform"; +import { GITHUB_RELEASES_URL, usePrimaryDownload } from "@/lib/desktop-download-utils"; import { APP_VERSION } from "@/lib/env-config"; import { cn } from "@/lib/utils"; import type { User } from "../../types/layout.types"; @@ -149,10 +152,13 @@ export function SidebarUserProfile({ }: SidebarUserProfileProps) { const t = useTranslations("sidebar"); const { locale, setLocale } = useLocaleContext(); + const { isDesktop } = usePlatform(); + const { os, primary } = usePrimaryDownload(); const [isLoggingOut, setIsLoggingOut] = useState(false); const bgColor = stringToColor(user.email); const initials = getInitials(user.email); const displayName = user.name || user.email.split("@")[0]; + const downloadUrl = primary?.url ?? GITHUB_RELEASES_URL; const handleLanguageChange = (newLocale: "en" | "es" | "pt" | "hi" | "zh") => { setLocale(newLocale); @@ -294,6 +300,15 @@ export function SidebarUserProfile({ + {!isDesktop && ( + + + + {t("download_for_os", { os })} + + + )} + @@ -439,6 +454,15 @@ export function SidebarUserProfile({ + {!isDesktop && ( + + + + {t("download_for_os", { os })} + + + )} + diff --git a/surfsense_web/components/settings/llm-role-manager.tsx b/surfsense_web/components/settings/llm-role-manager.tsx index e8088716e..e280db493 100644 --- a/surfsense_web/components/settings/llm-role-manager.tsx +++ b/surfsense_web/components/settings/llm-role-manager.tsx @@ -439,7 +439,6 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { - diff --git a/surfsense_web/components/settings/more-pages-content.tsx b/surfsense_web/components/settings/more-pages-content.tsx index 29245775c..944f7418f 100644 --- a/surfsense_web/components/settings/more-pages-content.tsx +++ b/surfsense_web/components/settings/more-pages-content.tsx @@ -113,11 +113,11 @@ export function MorePagesContent() { {isLoading ? ( - +
- +
- +
) : ( diff --git a/surfsense_web/components/settings/search-space-settings-dialog.tsx b/surfsense_web/components/settings/search-space-settings-dialog.tsx index a19841b2a..56ad0ab8f 100644 --- a/surfsense_web/components/settings/search-space-settings-dialog.tsx +++ b/surfsense_web/components/settings/search-space-settings-dialog.tsx @@ -2,15 +2,15 @@ import { useAtom } from "jotai"; import { - Bot, BookText, + Bot, Brain, CircleUser, Earth, Eye, ImageIcon, ListChecks, - UserKey + UserKey, } from "lucide-react"; import dynamic from "next/dynamic"; import { useTranslations } from "next-intl"; diff --git a/surfsense_web/lib/desktop-download-utils.ts b/surfsense_web/lib/desktop-download-utils.ts new file mode 100644 index 000000000..d4e2d4e68 --- /dev/null +++ b/surfsense_web/lib/desktop-download-utils.ts @@ -0,0 +1,108 @@ +import { useEffect, useMemo, useState } from "react"; + +export type OSInfo = { + os: "macOS" | "Windows" | "Linux"; + arch: "arm64" | "x64"; +}; + +export function useUserOS(): OSInfo { + const [info, setInfo] = useState({ os: "macOS", arch: "arm64" }); + useEffect(() => { + const ua = navigator.userAgent; + let os: OSInfo["os"] = "macOS"; + let arch: OSInfo["arch"] = "x64"; + + if (/Windows/i.test(ua)) { + os = "Windows"; + arch = "x64"; + } else if (/Linux/i.test(ua)) { + os = "Linux"; + arch = "x64"; + } else { + os = "macOS"; + arch = /Mac/.test(ua) && !/Intel/.test(ua) ? "arm64" : "arm64"; + } + + const uaData = (navigator as Navigator & { userAgentData?: { architecture?: string } }) + .userAgentData; + if (uaData?.architecture === "arm") arch = "arm64"; + else if (uaData?.architecture === "x86") arch = "x64"; + + setInfo({ os, arch }); + }, []); + return info; +} + +export interface ReleaseAsset { + name: string; + url: string; +} + +export function useLatestRelease() { + const [assets, setAssets] = useState([]); + + useEffect(() => { + const controller = new AbortController(); + fetch("https://api.github.com/repos/MODSetter/SurfSense/releases/latest", { + signal: controller.signal, + }) + .then((r) => r.json()) + .then((data) => { + if (data?.assets) { + setAssets( + data.assets + .filter((a: { name: string }) => /\.(exe|dmg|AppImage|deb)$/.test(a.name)) + .map((a: { name: string; browser_download_url: string }) => ({ + name: a.name, + url: a.browser_download_url, + })) + ); + } + }) + .catch(() => {}); + return () => controller.abort(); + }, []); + + return assets; +} + +export const ASSET_LABELS: Record = { + ".exe": "Windows (exe)", + "-arm64.dmg": "macOS Apple Silicon (dmg)", + "-x64.dmg": "macOS Intel (dmg)", + "-arm64.zip": "macOS Apple Silicon (zip)", + "-x64.zip": "macOS Intel (zip)", + ".AppImage": "Linux (AppImage)", + ".deb": "Linux (deb)", +}; + +export function getAssetLabel(name: string): string { + for (const [suffix, label] of Object.entries(ASSET_LABELS)) { + if (name.endsWith(suffix)) return label; + } + return name; +} + +export const GITHUB_RELEASES_URL = "https://github.com/MODSetter/SurfSense/releases/latest"; + +export function usePrimaryDownload() { + const { os, arch } = useUserOS(); + const assets = useLatestRelease(); + + const { primary, alternatives } = useMemo(() => { + if (assets.length === 0) return { primary: null, alternatives: [] }; + + const matchers: Record boolean> = { + Windows: (n) => n.endsWith(".exe"), + macOS: (n) => n.endsWith(`-${arch}.dmg`), + Linux: (n) => n.endsWith(".AppImage"), + }; + + const match = matchers[os]; + const primary = assets.find((a) => match(a.name)) ?? null; + const alternatives = assets.filter((a) => a !== primary); + return { primary, alternatives }; + }, [assets, os, arch]); + + return { os, arch, assets, primary, alternatives }; +} diff --git a/surfsense_web/messages/en.json b/surfsense_web/messages/en.json index a3a4e8853..3e407e28b 100644 --- a/surfsense_web/messages/en.json +++ b/surfsense_web/messages/en.json @@ -693,6 +693,7 @@ "learn_more": "Learn more", "documentation": "Documentation", "github": "GitHub", + "download_for_os": "Download for {os}", "inbox": "Inbox", "search_inbox": "Search inbox", "mark_all_read": "Mark all as read", diff --git a/surfsense_web/messages/es.json b/surfsense_web/messages/es.json index fa620e271..f41aba2cc 100644 --- a/surfsense_web/messages/es.json +++ b/surfsense_web/messages/es.json @@ -693,6 +693,7 @@ "learn_more": "Más información", "documentation": "Documentación", "github": "GitHub", + "download_for_os": "Descargar para {os}", "inbox": "Bandeja de entrada", "search_inbox": "Buscar en bandeja de entrada", "mark_all_read": "Marcar todo como leído", diff --git a/surfsense_web/messages/hi.json b/surfsense_web/messages/hi.json index faeb4cb94..514acc06f 100644 --- a/surfsense_web/messages/hi.json +++ b/surfsense_web/messages/hi.json @@ -693,6 +693,7 @@ "learn_more": "और जानें", "documentation": "दस्तावेज़ीकरण", "github": "GitHub", + "download_for_os": "{os} के लिए डाउनलोड करें", "inbox": "इनबॉक्स", "search_inbox": "इनबॉक्स में खोजें", "mark_all_read": "सभी पढ़ा हुआ चिह्नित करें", diff --git a/surfsense_web/messages/pt.json b/surfsense_web/messages/pt.json index 0bed7c6cc..421033810 100644 --- a/surfsense_web/messages/pt.json +++ b/surfsense_web/messages/pt.json @@ -693,6 +693,7 @@ "learn_more": "Saiba mais", "documentation": "Documentação", "github": "GitHub", + "download_for_os": "Baixar para {os}", "inbox": "Caixa de entrada", "search_inbox": "Pesquisar caixa de entrada", "mark_all_read": "Marcar tudo como lido", diff --git a/surfsense_web/messages/zh.json b/surfsense_web/messages/zh.json index 0d4f7e1c9..1e931726b 100644 --- a/surfsense_web/messages/zh.json +++ b/surfsense_web/messages/zh.json @@ -677,6 +677,7 @@ "learn_more": "了解更多", "documentation": "文档", "github": "GitHub", + "download_for_os": "下载 {os} 版本", "inbox": "收件箱", "search_inbox": "搜索收件箱", "mark_all_read": "全部标记为已读",