feat: migrated to surfsense deep agent

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2025-12-23 01:16:25 -08:00
parent b14283e300
commit 4a0c3e368a
90 changed files with 5337 additions and 6029 deletions

View file

@ -1,30 +1,14 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { useAtomValue } from "jotai";
import {
ChevronDown,
ChevronUp,
ExternalLink,
Info,
RotateCcw,
Save,
Sparkles,
User,
} from "lucide-react";
import { AlertTriangle, Info, RotateCcw, Save } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { communityPromptsAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Switch } from "@/components/ui/switch";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
import { authenticatedFetch } from "@/lib/auth-utils";
@ -44,20 +28,14 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps)
queryFn: () => searchSpacesApiService.getSearchSpace({ id: searchSpaceId }),
enabled: !!searchSpaceId,
});
const { data: prompts = [], isPending: loadingPrompts } = useAtomValue(communityPromptsAtom);
const [enableCitations, setEnableCitations] = useState(true);
const [customInstructions, setCustomInstructions] = useState("");
const [saving, setSaving] = useState(false);
const [hasChanges, setHasChanges] = useState(false);
const [selectedPromptKey, setSelectedPromptKey] = useState<string | null>(null);
const [expandedPrompts, setExpandedPrompts] = useState<Set<string>>(new Set());
const [selectedCategory, setSelectedCategory] = useState("all");
// Initialize state from fetched search space
useEffect(() => {
if (searchSpace) {
setEnableCitations(searchSpace.citations_enabled);
setCustomInstructions(searchSpace.qna_custom_instructions || "");
setHasChanges(false);
}
@ -67,50 +45,39 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps)
useEffect(() => {
if (searchSpace) {
const currentCustom = searchSpace.qna_custom_instructions || "";
const changed =
searchSpace.citations_enabled !== enableCitations || currentCustom !== customInstructions;
const changed = currentCustom !== customInstructions;
setHasChanges(changed);
}
}, [searchSpace, enableCitations, customInstructions]);
}, [searchSpace, customInstructions]);
const handleSave = async () => {
try {
setSaving(true);
// Prepare payload with simplified schema
const payload: any = {
citations_enabled: enableCitations,
const payload = {
qna_custom_instructions: customInstructions.trim() || "",
};
// Only send request if we have something to update
if (Object.keys(payload).length > 0) {
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || "Failed to save prompt configuration");
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
);
toast.success("Prompt configuration saved successfully");
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || "Failed to save system instructions");
}
toast.success("System instructions saved successfully");
setHasChanges(false);
// Refresh to get updated data
await fetchSearchSpace();
} catch (error: any) {
console.error("Error saving prompt configuration:", error);
toast.error(error.message || "Failed to save prompt configuration");
console.error("Error saving system instructions:", error);
toast.error(error.message || "Failed to save system instructions");
} finally {
setSaving(false);
}
@ -118,41 +85,11 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps)
const handleReset = () => {
if (searchSpace) {
setEnableCitations(searchSpace.citations_enabled);
setCustomInstructions(searchSpace.qna_custom_instructions || "");
setSelectedPromptKey(null);
setHasChanges(false);
}
};
const handleSelectCommunityPrompt = (promptKey: string, promptValue: string) => {
setCustomInstructions(promptValue);
setSelectedPromptKey(promptKey);
toast.success("Community prompt applied");
};
const toggleExpand = (promptKey: string) => {
const newExpanded = new Set(expandedPrompts);
if (newExpanded.has(promptKey)) {
newExpanded.delete(promptKey);
} else {
newExpanded.add(promptKey);
}
setExpandedPrompts(newExpanded);
};
// Get unique categories
const categories = Array.from(new Set(prompts.map((p) => p.category || "general")));
const filteredPrompts =
selectedCategory === "all"
? prompts
: prompts.filter((p) => (p.category || "general") === selectedCategory);
const truncateText = (text: string, maxLength: number = 150) => {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength) + "...";
};
if (loading) {
return (
<div className="space-y-6">
@ -172,225 +109,47 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps)
return (
<div className="space-y-6">
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
Configure how the AI responds to your queries. Citations add source references, and the
system instructions personalize the response style.
{/* Work in Progress Notice */}
<Alert
variant="default"
className="bg-amber-50 dark:bg-amber-950/30 border-amber-300 dark:border-amber-700"
>
<AlertTriangle className="h-4 w-4 text-amber-600 dark:text-amber-500" />
<AlertDescription className="text-amber-800 dark:text-amber-300">
<span className="font-semibold">Work in Progress:</span> This functionality is currently
under development and not yet connected to the backend. Your instructions will be saved
but won't affect AI behavior until the feature is fully implemented.
</AlertDescription>
</Alert>
{/* Citations Card */}
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
System instructions apply to all AI interactions in this search space. They guide how the
AI responds, its tone, focus areas, and behavior patterns.
</AlertDescription>
</Alert>
{/* System Instructions Card */}
<Card>
<CardHeader>
<CardTitle>Citation Configuration</CardTitle>
<CardTitle>Custom System Instructions</CardTitle>
<CardDescription>
Control whether AI responses include citations to source documents
Provide specific guidelines for how you want the AI to respond. These instructions will
be applied to all answers in this search space.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between space-x-4 p-4 rounded-lg border bg-card">
<div className="flex-1 space-y-1">
<Label htmlFor="enable-citations-settings" className="text-base font-medium">
Enable Citations
</Label>
<p className="text-sm text-muted-foreground">
When enabled, AI responses will include citations in [citation:id] format linking to
source documents.
</p>
</div>
<Switch
id="enable-citations-settings"
checked={enableCitations}
onCheckedChange={setEnableCitations}
/>
</div>
{!enableCitations && (
<Alert
variant="default"
className="bg-yellow-50 dark:bg-yellow-950/20 border-yellow-200 dark:border-yellow-800"
>
<Info className="h-4 w-4 text-yellow-600 dark:text-yellow-500" />
<AlertDescription className="text-yellow-800 dark:text-yellow-300">
Citations are currently disabled. AI responses will not include source references.
You can re-enable this anytime.
</AlertDescription>
</Alert>
)}
{enableCitations && (
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
Citations are enabled. When answering questions, the AI will reference source
documents using the [citation:id] format.
</AlertDescription>
</Alert>
)}
</CardContent>
</Card>
{/* SearchSpace System Instructions Card */}
<Card>
<CardHeader>
<CardTitle>SearchSpace System Instructions</CardTitle>
<CardDescription>
Add system instructions to guide the AI's response style and behavior
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* Community Prompts Section */}
{!loadingPrompts && prompts.length > 0 && (
<div className="space-y-2">
<Label className="text-base font-medium flex items-center gap-2">
<Sparkles className="h-4 w-4" />
Community Prompts Library
</Label>
<p className="text-sm text-muted-foreground">
Browse {prompts.length} curated prompts from the community
</p>
<Card className="border-dashed">
<CardContent className="pt-4">
<Tabs
value={selectedCategory}
onValueChange={setSelectedCategory}
className="w-full"
>
<TabsList className="grid w-full grid-cols-5 mb-4">
<TabsTrigger value="all" className="text-xs">
All ({prompts.length})
</TabsTrigger>
{categories.map((category) => (
<TabsTrigger key={category} value={category} className="text-xs capitalize">
{category} (
{prompts.filter((p) => (p.category || "general") === category).length})
</TabsTrigger>
))}
</TabsList>
<ScrollArea className="h-[350px] pr-4">
<div className="space-y-3">
{filteredPrompts.map((prompt) => {
const isExpanded = expandedPrompts.has(prompt.key);
const isSelected = selectedPromptKey === prompt.key;
const displayText = isExpanded
? prompt.value
: truncateText(prompt.value, 120);
return (
<div
key={prompt.key}
className={`p-4 rounded-lg border transition-all ${
isSelected
? "border-primary bg-accent/50"
: "border-border hover:border-primary/50 hover:bg-accent/30"
}`}
>
<div className="flex items-start justify-between gap-2 mb-2">
<div className="flex items-center gap-2 flex-wrap flex-1">
<Badge variant="outline" className="text-xs font-medium">
{prompt.key.replace(/_/g, " ")}
</Badge>
{prompt.category && (
<Badge variant="secondary" className="text-xs capitalize">
{prompt.category}
</Badge>
)}
{isSelected && (
<Badge variant="default" className="text-xs">
Selected
</Badge>
)}
</div>
{prompt.link && (
<a
href={prompt.link}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-primary shrink-0"
title="View source"
>
<ExternalLink className="h-4 w-4" />
</a>
)}
</div>
<p className="text-sm text-foreground mb-3 whitespace-pre-wrap">
{displayText}
</p>
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<User className="h-3 w-3" />
<span>{prompt.author}</span>
</div>
<div className="flex items-center gap-2">
{prompt.value.length > 120 && (
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => toggleExpand(prompt.key)}
className="h-7 text-xs"
>
{isExpanded ? (
<>
<ChevronUp className="h-3 w-3 mr-1" />
Show less
</>
) : (
<>
<ChevronDown className="h-3 w-3 mr-1" />
Read more
</>
)}
</Button>
)}
<Button
type="button"
variant={isSelected ? "default" : "secondary"}
size="sm"
onClick={() =>
handleSelectCommunityPrompt(prompt.key, prompt.value)
}
className="h-7 text-xs"
>
{isSelected ? "Applied" : "Use This"}
</Button>
</div>
</div>
</div>
);
})}
</div>
</ScrollArea>
</Tabs>
</CardContent>
</Card>
</div>
)}
<Separator />
<div className="space-y-2">
<Label htmlFor="custom-instructions-settings" className="text-base font-medium">
Your System Instructions
Your Instructions
</Label>
<p className="text-sm text-muted-foreground">
Provide specific guidelines for how you want the AI to respond. These instructions
will be applied to all answers.
</p>
<Textarea
id="custom-instructions-settings"
placeholder="E.g., Always provide practical examples, be concise, focus on technical details, use simple language..."
placeholder="E.g., Always provide practical examples, be concise, focus on technical details, use simple language, respond in a specific format..."
value={customInstructions}
onChange={(e) => {
setCustomInstructions(e.target.value);
setSelectedPromptKey(null);
}}
rows={8}
onChange={(e) => setCustomInstructions(e.target.value)}
rows={12}
className="resize-none font-mono text-sm"
/>
<div className="flex items-center justify-between">
@ -401,10 +160,7 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps)
<Button
variant="ghost"
size="sm"
onClick={() => {
setCustomInstructions("");
setSelectedPromptKey(null);
}}
onClick={() => setCustomInstructions("")}
className="h-auto py-1 px-2 text-xs"
>
Clear
@ -441,7 +197,7 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps)
className="flex items-center gap-2"
>
<Save className="h-4 w-4" />
{saving ? "Saving..." : "Save Configuration"}
{saving ? "Saving..." : "Save Instructions"}
</Button>
</div>
@ -452,7 +208,7 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps)
>
<Info className="h-4 w-4 text-blue-600 dark:text-blue-500" />
<AlertDescription className="text-blue-800 dark:text-blue-300">
You have unsaved changes. Click "Save Configuration" to apply them.
You have unsaved changes. Click "Save Instructions" to apply them.
</AlertDescription>
</Alert>
)}