mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-10 16:22:38 +02:00
feat: no login experience and prem tokens
Some checks are pending
Build and Push Docker Images / tag_release (push) Waiting to run
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (web, surfsense-web) (push) Blocked by required conditions
Some checks are pending
Build and Push Docker Images / tag_release (push) Waiting to run
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (web, surfsense-web) (push) Blocked by required conditions
This commit is contained in:
parent
87452bb315
commit
ff4e0f9b62
68 changed files with 5914 additions and 121 deletions
262
surfsense_web/components/free-chat/free-composer.tsx
Normal file
262
surfsense_web/components/free-chat/free-composer.tsx
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
"use client";
|
||||
|
||||
import { ComposerPrimitive, useAui, useAuiState } from "@assistant-ui/react";
|
||||
import { ArrowUpIcon, Globe, Paperclip, SquareIcon } from "lucide-react";
|
||||
import { type FC, useCallback, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useAnonymousMode } from "@/contexts/anonymous-mode";
|
||||
import { useLoginGate } from "@/contexts/login-gate";
|
||||
import { BACKEND_URL } from "@/lib/env-config";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const ANON_ALLOWED_EXTENSIONS = new Set([
|
||||
".md",
|
||||
".markdown",
|
||||
".txt",
|
||||
".text",
|
||||
".json",
|
||||
".jsonl",
|
||||
".yaml",
|
||||
".yml",
|
||||
".toml",
|
||||
".ini",
|
||||
".cfg",
|
||||
".conf",
|
||||
".xml",
|
||||
".css",
|
||||
".scss",
|
||||
".py",
|
||||
".js",
|
||||
".jsx",
|
||||
".ts",
|
||||
".tsx",
|
||||
".java",
|
||||
".kt",
|
||||
".go",
|
||||
".rs",
|
||||
".rb",
|
||||
".php",
|
||||
".c",
|
||||
".h",
|
||||
".cpp",
|
||||
".hpp",
|
||||
".cs",
|
||||
".swift",
|
||||
".sh",
|
||||
".sql",
|
||||
".log",
|
||||
".rst",
|
||||
".tex",
|
||||
".vue",
|
||||
".svelte",
|
||||
".astro",
|
||||
".tf",
|
||||
".proto",
|
||||
".csv",
|
||||
".tsv",
|
||||
".html",
|
||||
".htm",
|
||||
".xhtml",
|
||||
]);
|
||||
|
||||
const ACCEPT_EXTENSIONS = Array.from(ANON_ALLOWED_EXTENSIONS).join(",");
|
||||
|
||||
export const FreeComposer: FC = () => {
|
||||
const aui = useAui();
|
||||
const isRunning = useAuiState(({ thread }) => thread.isRunning);
|
||||
const isEmpty = useAuiState(({ thread }) => thread.isEmpty);
|
||||
const { gate } = useLoginGate();
|
||||
const anonMode = useAnonymousMode();
|
||||
const [text, setText] = useState("");
|
||||
const [webSearchEnabled, setWebSearchEnabled] = useState(true);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const hasUploadedDoc = anonMode.isAnonymous && anonMode.uploadedDoc !== null;
|
||||
|
||||
const handleTextChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setText(e.target.value);
|
||||
aui.composer().setText(e.target.value);
|
||||
},
|
||||
[aui]
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === "/" && text === "") {
|
||||
e.preventDefault();
|
||||
gate("use saved prompts");
|
||||
return;
|
||||
}
|
||||
if (e.key === "@") {
|
||||
e.preventDefault();
|
||||
gate("mention documents");
|
||||
return;
|
||||
}
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
if (text.trim()) {
|
||||
aui.composer().send();
|
||||
setText("");
|
||||
}
|
||||
}
|
||||
},
|
||||
[text, aui, gate]
|
||||
);
|
||||
|
||||
const handleUploadClick = useCallback(() => {
|
||||
if (hasUploadedDoc) {
|
||||
gate("upload more documents");
|
||||
return;
|
||||
}
|
||||
fileInputRef.current?.click();
|
||||
}, [hasUploadedDoc, gate]);
|
||||
|
||||
const handleFileChange = useCallback(
|
||||
async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
e.target.value = "";
|
||||
|
||||
const ext = `.${file.name.split(".").pop()?.toLowerCase()}`;
|
||||
if (!ANON_ALLOWED_EXTENSIONS.has(ext)) {
|
||||
gate("upload PDFs, Word documents, images, and more");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
const res = await fetch(`${BACKEND_URL}/api/v1/public/anon-chat/upload`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (res.status === 409) {
|
||||
gate("upload more documents");
|
||||
return;
|
||||
}
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}));
|
||||
throw new Error(body.detail || `Upload failed: ${res.status}`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
if (anonMode.isAnonymous) {
|
||||
anonMode.setUploadedDoc({
|
||||
filename: data.filename,
|
||||
sizeBytes: data.size_bytes,
|
||||
});
|
||||
}
|
||||
toast.success(`Uploaded "${data.filename}"`);
|
||||
} catch (err) {
|
||||
console.error("Upload failed:", err);
|
||||
toast.error(err instanceof Error ? err.message : "Upload failed");
|
||||
}
|
||||
},
|
||||
[gate, anonMode]
|
||||
);
|
||||
|
||||
return (
|
||||
<ComposerPrimitive.Root className="aui-composer-root relative mx-auto flex w-full max-w-(--thread-max-width) flex-col rounded-2xl border border-border/40 bg-background shadow-xs transition-shadow focus-within:shadow-md dark:bg-neutral-900">
|
||||
{hasUploadedDoc && anonMode.isAnonymous && (
|
||||
<div className="flex items-center gap-2 px-3 pt-2">
|
||||
<Paperclip className="size-3.5 text-muted-foreground" />
|
||||
<span className="text-xs text-muted-foreground truncate">
|
||||
{anonMode.uploadedDoc?.filename}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground/60">(1/1)</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<textarea
|
||||
placeholder="Ask anything..."
|
||||
value={text}
|
||||
onChange={handleTextChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
rows={1}
|
||||
className={cn(
|
||||
"w-full resize-none bg-transparent px-4 pt-3 pb-0 text-sm",
|
||||
"placeholder:text-muted-foreground focus:outline-none",
|
||||
"min-h-[44px] max-h-[200px]"
|
||||
)}
|
||||
style={{ fieldSizing: "content" } as React.CSSProperties}
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between gap-2 px-3 pb-2 pt-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept={ACCEPT_EXTENSIONS}
|
||||
className="hidden"
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleUploadClick}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-md px-2 py-1 text-xs transition-colors",
|
||||
"text-muted-foreground hover:text-foreground hover:bg-accent/50",
|
||||
hasUploadedDoc && "text-primary"
|
||||
)}
|
||||
>
|
||||
<Paperclip className="size-3.5" />
|
||||
{hasUploadedDoc ? "1/1" : "Upload"}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{hasUploadedDoc
|
||||
? "Document limit reached. Create an account for more."
|
||||
: "Upload a document (text files only)"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<div className="h-4 w-px bg-border/60" />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<label htmlFor="free-web-search-toggle" className="flex items-center gap-1.5 cursor-pointer select-none rounded-md px-2 py-1 text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors">
|
||||
<Globe className="size-3.5" />
|
||||
<span className="hidden sm:inline">Web</span>
|
||||
<Switch
|
||||
id="free-web-search-toggle"
|
||||
checked={webSearchEnabled}
|
||||
onCheckedChange={setWebSearchEnabled}
|
||||
className="scale-75"
|
||||
/>
|
||||
</label>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Toggle web search</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
{!isRunning ? (
|
||||
<ComposerPrimitive.Send asChild>
|
||||
<TooltipIconButton tooltip="Send" variant="default" className="size-8 rounded-full">
|
||||
<ArrowUpIcon />
|
||||
</TooltipIconButton>
|
||||
</ComposerPrimitive.Send>
|
||||
) : (
|
||||
<ComposerPrimitive.Cancel asChild>
|
||||
<TooltipIconButton
|
||||
tooltip="Cancel"
|
||||
variant="destructive"
|
||||
className="size-8 rounded-full"
|
||||
>
|
||||
<SquareIcon className="size-3.5" />
|
||||
</TooltipIconButton>
|
||||
</ComposerPrimitive.Cancel>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ComposerPrimitive.Root>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue