+
+
{used.toLocaleString()} / {limit.toLocaleString()} tokens
{isExceeded ? (
- Limit reached
+ Limit reached
) : isWarning ? (
-
-
+
+
{remaining.toLocaleString()} remaining
) : (
{percentage.toFixed(0)}%
)}
-
);
diff --git a/surfsense_web/components/free-chat/quota-warning-banner.tsx b/surfsense_web/components/free-chat/quota-warning-banner.tsx
index 828e8006e..e6aa89d42 100644
--- a/surfsense_web/components/free-chat/quota-warning-banner.tsx
+++ b/surfsense_web/components/free-chat/quota-warning-banner.tsx
@@ -3,6 +3,7 @@
import { OctagonAlert, Orbit, X } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
@@ -27,61 +28,46 @@ export function QuotaWarningBanner({
if (isExceeded) {
return (
-
-
-
-
-
- Free token limit reached
-
-
- You've used all {limit.toLocaleString()} free tokens. Create a free account to
- get $5 of premium credit and access to all models.
-
-
-
+
+
+ Free token limit reached
+
+
+ You've used all {limit.toLocaleString()} free tokens. Create a free account to get
+ $5 of premium credit and access to all models.
+
+
-
-
+
+
+
);
}
return (
-
-
-
-
- You've used {used.toLocaleString()} of {limit.toLocaleString()} free tokens.{" "}
-
- Create an account
- {" "}
- for $5 of premium credit.
-
-
-
-
+
+
+ Running low on free tokens
+
+ You've used {used.toLocaleString()} of {limit.toLocaleString()} free tokens.{" "}
+
+ Create an account
+ {" "}
+ for $5 of premium credit.
+
+
+
);
}
diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx
index 881fbe2b0..a90d6b32e 100644
--- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx
+++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx
@@ -89,10 +89,7 @@ const DesktopLocalTabContent = dynamic(
{ ssr: false }
);
-const NON_DELETABLE_DOCUMENT_TYPES: readonly string[] = [
- "USER_MEMORY",
- "TEAM_MEMORY",
-];
+const NON_DELETABLE_DOCUMENT_TYPES: readonly string[] = ["USER_MEMORY", "TEAM_MEMORY"];
const MEMORY_DOCUMENTS: DocumentNodeDoc[] = [
{
id: -1001,
diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx
index 769327e1e..43a5cad74 100644
--- a/surfsense_web/components/new-chat/document-mention-picker.tsx
+++ b/surfsense_web/components/new-chat/document-mention-picker.tsx
@@ -220,13 +220,7 @@ export const DocumentMentionPicker = forwardRef<
DocumentMentionPickerRef,
DocumentMentionPickerProps
>(function DocumentMentionPicker(
- {
- searchSpaceId,
- onSelectionChange,
- onDone,
- initialSelectedDocuments = [],
- externalSearch = "",
- },
+ { searchSpaceId, onSelectionChange, onDone, initialSelectedDocuments = [], externalSearch = "" },
ref
) {
const search = externalSearch;
diff --git a/surfsense_web/components/ui/empty.tsx b/surfsense_web/components/ui/empty.tsx
new file mode 100644
index 000000000..79145502f
--- /dev/null
+++ b/surfsense_web/components/ui/empty.tsx
@@ -0,0 +1,94 @@
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+function Empty({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+const emptyMediaVariants = cva(
+ "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+);
+
+function EmptyMedia({
+ className,
+ variant = "default",
+ ...props
+}: React.ComponentProps<"div"> & VariantProps
) {
+ return (
+
+ );
+}
+
+function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
+ return (
+ a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
+ className
+ )}
+ {...props}
+ />
+ );
+}
+
+function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia };
diff --git a/surfsense_web/contexts/anonymous-mode.tsx b/surfsense_web/contexts/anonymous-mode.tsx
index eaf0996c3..c24a5cb1b 100644
--- a/surfsense_web/contexts/anonymous-mode.tsx
+++ b/surfsense_web/contexts/anonymous-mode.tsx
@@ -9,6 +9,8 @@ export interface AnonymousModeContextValue {
setModelSlug: (slug: string) => void;
uploadedDoc: { filename: string; sizeBytes: number } | null;
setUploadedDoc: (doc: { filename: string; sizeBytes: number } | null) => void;
+ webSearchEnabled: boolean;
+ setWebSearchEnabled: (enabled: boolean) => void;
resetKey: number;
resetChat: () => void;
}
@@ -34,6 +36,7 @@ export function AnonymousModeProvider({
const [uploadedDoc, setUploadedDoc] = useState<{ filename: string; sizeBytes: number } | null>(
null
);
+ const [webSearchEnabled, setWebSearchEnabled] = useState(true);
const [resetKey, setResetKey] = useState(0);
const resetChat = () => setResetKey((k) => k + 1);
@@ -56,10 +59,12 @@ export function AnonymousModeProvider({
setModelSlug,
uploadedDoc,
setUploadedDoc,
+ webSearchEnabled,
+ setWebSearchEnabled,
resetKey,
resetChat,
}),
- [modelSlug, uploadedDoc, resetKey]
+ [modelSlug, uploadedDoc, webSearchEnabled, resetKey]
);
return
{children};