mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-30 11:26:24 +02:00
feat: migrated to surfsense deep agent
This commit is contained in:
parent
b14283e300
commit
4a0c3e368a
90 changed files with 5337 additions and 6029 deletions
|
|
@ -4,24 +4,22 @@ import { useAtomValue } from "jotai";
|
|||
import {
|
||||
AlertCircle,
|
||||
Bot,
|
||||
Brain,
|
||||
CheckCircle,
|
||||
FileText,
|
||||
Loader2,
|
||||
RefreshCw,
|
||||
RotateCcw,
|
||||
Save,
|
||||
Settings2,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { motion } from "motion/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { updateLLMPreferencesMutationAtom } from "@/atoms/llm-config/llm-config-mutation.atoms";
|
||||
import { updateLLMPreferencesMutationAtom } from "@/atoms/new-llm-config/new-llm-config-mutation.atoms";
|
||||
import {
|
||||
globalLLMConfigsAtom,
|
||||
llmConfigsAtom,
|
||||
globalNewLLMConfigsAtom,
|
||||
llmPreferencesAtom,
|
||||
} from "@/atoms/llm-config/llm-config-query.atoms";
|
||||
newLLMConfigsAtom,
|
||||
} from "@/atoms/new-llm-config/new-llm-config-query.atoms";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -36,29 +34,21 @@ import {
|
|||
} from "@/components/ui/select";
|
||||
|
||||
const ROLE_DESCRIPTIONS = {
|
||||
long_context: {
|
||||
icon: Brain,
|
||||
title: "Long Context LLM",
|
||||
description: "Handles summarization of long documents and complex Q&A",
|
||||
color: "bg-blue-100 text-blue-800 border-blue-200",
|
||||
examples: "Document analysis, research synthesis, complex Q&A",
|
||||
characteristics: ["Large context window", "Deep reasoning", "Complex analysis"],
|
||||
},
|
||||
fast: {
|
||||
icon: Zap,
|
||||
title: "Fast LLM",
|
||||
description: "Optimized for quick responses and real-time interactions",
|
||||
color: "bg-green-100 text-green-800 border-green-200",
|
||||
examples: "Quick searches, simple questions, instant responses",
|
||||
characteristics: ["Low latency", "Quick responses", "Real-time chat"],
|
||||
},
|
||||
strategic: {
|
||||
agent: {
|
||||
icon: Bot,
|
||||
title: "Strategic LLM",
|
||||
description: "Advanced reasoning for planning and strategic decision making",
|
||||
title: "Agent LLM",
|
||||
description: "Primary LLM for chat interactions and agent operations",
|
||||
color: "bg-blue-100 text-blue-800 border-blue-200",
|
||||
examples: "Chat responses, agent tasks, real-time interactions",
|
||||
characteristics: ["Fast responses", "Conversational", "Agent operations"],
|
||||
},
|
||||
document_summary: {
|
||||
icon: FileText,
|
||||
title: "Document Summary LLM",
|
||||
description: "Handles document summarization, long context analysis, and query reformulation",
|
||||
color: "bg-purple-100 text-purple-800 border-purple-200",
|
||||
examples: "Planning workflows, strategic analysis, complex problem solving",
|
||||
characteristics: ["Strategic thinking", "Long-term planning", "Complex reasoning"],
|
||||
examples: "Document analysis, podcasts, research synthesis",
|
||||
characteristics: ["Large context window", "Deep reasoning", "Summarization"],
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -67,18 +57,19 @@ interface LLMRoleManagerProps {
|
|||
}
|
||||
|
||||
export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
|
||||
// Use new LLM config system
|
||||
const {
|
||||
data: llmConfigs = [],
|
||||
data: newLLMConfigs = [],
|
||||
isFetching: configsLoading,
|
||||
error: configsError,
|
||||
refetch: refreshConfigs,
|
||||
} = useAtomValue(llmConfigsAtom);
|
||||
} = useAtomValue(newLLMConfigsAtom);
|
||||
const {
|
||||
data: globalConfigs = [],
|
||||
isFetching: globalConfigsLoading,
|
||||
error: globalConfigsError,
|
||||
refetch: refreshGlobalConfigs,
|
||||
} = useAtomValue(globalLLMConfigsAtom);
|
||||
} = useAtomValue(globalNewLLMConfigsAtom);
|
||||
const {
|
||||
data: preferences = {},
|
||||
isFetching: preferencesLoading,
|
||||
|
|
@ -89,9 +80,8 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
|
|||
const { mutateAsync: updatePreferences } = useAtomValue(updateLLMPreferencesMutationAtom);
|
||||
|
||||
const [assignments, setAssignments] = useState({
|
||||
long_context_llm_id: preferences.long_context_llm_id || "",
|
||||
fast_llm_id: preferences.fast_llm_id || "",
|
||||
strategic_llm_id: preferences.strategic_llm_id || "",
|
||||
agent_llm_id: preferences.agent_llm_id || "",
|
||||
document_summary_llm_id: preferences.document_summary_llm_id || "",
|
||||
});
|
||||
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
|
|
@ -99,9 +89,8 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
|
|||
|
||||
useEffect(() => {
|
||||
const newAssignments = {
|
||||
long_context_llm_id: preferences.long_context_llm_id || "",
|
||||
fast_llm_id: preferences.fast_llm_id || "",
|
||||
strategic_llm_id: preferences.strategic_llm_id || "",
|
||||
agent_llm_id: preferences.agent_llm_id || "",
|
||||
document_summary_llm_id: preferences.document_summary_llm_id || "",
|
||||
};
|
||||
setAssignments(newAssignments);
|
||||
setHasChanges(false);
|
||||
|
|
@ -117,9 +106,8 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
|
|||
|
||||
// Check if there are changes compared to current preferences
|
||||
const currentPrefs = {
|
||||
long_context_llm_id: preferences.long_context_llm_id || "",
|
||||
fast_llm_id: preferences.fast_llm_id || "",
|
||||
strategic_llm_id: preferences.strategic_llm_id || "",
|
||||
agent_llm_id: preferences.agent_llm_id || "",
|
||||
document_summary_llm_id: preferences.document_summary_llm_id || "",
|
||||
};
|
||||
|
||||
const hasChangesNow = Object.keys(newAssignments).some(
|
||||
|
|
@ -135,24 +123,18 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
|
|||
setIsSaving(true);
|
||||
|
||||
const numericAssignments = {
|
||||
long_context_llm_id:
|
||||
typeof assignments.long_context_llm_id === "string"
|
||||
? assignments.long_context_llm_id
|
||||
? parseInt(assignments.long_context_llm_id)
|
||||
agent_llm_id:
|
||||
typeof assignments.agent_llm_id === "string"
|
||||
? assignments.agent_llm_id
|
||||
? parseInt(assignments.agent_llm_id)
|
||||
: undefined
|
||||
: assignments.long_context_llm_id,
|
||||
fast_llm_id:
|
||||
typeof assignments.fast_llm_id === "string"
|
||||
? assignments.fast_llm_id
|
||||
? parseInt(assignments.fast_llm_id)
|
||||
: assignments.agent_llm_id,
|
||||
document_summary_llm_id:
|
||||
typeof assignments.document_summary_llm_id === "string"
|
||||
? assignments.document_summary_llm_id
|
||||
? parseInt(assignments.document_summary_llm_id)
|
||||
: undefined
|
||||
: assignments.fast_llm_id,
|
||||
strategic_llm_id:
|
||||
typeof assignments.strategic_llm_id === "string"
|
||||
? assignments.strategic_llm_id
|
||||
? parseInt(assignments.strategic_llm_id)
|
||||
: undefined
|
||||
: assignments.strategic_llm_id,
|
||||
: assignments.document_summary_llm_id,
|
||||
};
|
||||
|
||||
await updatePreferences({
|
||||
|
|
@ -168,21 +150,18 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
|
|||
|
||||
const handleReset = () => {
|
||||
setAssignments({
|
||||
long_context_llm_id: preferences.long_context_llm_id || "",
|
||||
fast_llm_id: preferences.fast_llm_id || "",
|
||||
strategic_llm_id: preferences.strategic_llm_id || "",
|
||||
agent_llm_id: preferences.agent_llm_id || "",
|
||||
document_summary_llm_id: preferences.document_summary_llm_id || "",
|
||||
});
|
||||
setHasChanges(false);
|
||||
};
|
||||
|
||||
const isAssignmentComplete =
|
||||
assignments.long_context_llm_id && assignments.fast_llm_id && assignments.strategic_llm_id;
|
||||
const assignedConfigIds = Object.values(assignments).filter((id) => id !== "");
|
||||
const isAssignmentComplete = assignments.agent_llm_id && assignments.document_summary_llm_id;
|
||||
|
||||
// Combine global and custom configs
|
||||
// Combine global and custom configs (new system)
|
||||
const allConfigs = [
|
||||
...globalConfigs.map((config) => ({ ...config, is_global: true })),
|
||||
...llmConfigs.filter((config) => config.id && config.id.toString().trim() !== ""),
|
||||
...newLLMConfigs.filter((config) => config.id && config.id.toString().trim() !== ""),
|
||||
];
|
||||
|
||||
const availableConfigs = allConfigs;
|
||||
|
|
@ -194,19 +173,6 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
|
|||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col space-y-4 lg:flex-row lg:items-center lg:justify-between lg:space-y-0">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-purple-500/10">
|
||||
<Settings2 className="h-5 w-5 text-purple-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">LLM Role Management</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Assign your LLM configurations to specific roles for different purposes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
@ -263,99 +229,6 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
|
|||
</Card>
|
||||
)}
|
||||
|
||||
{/* Stats Overview */}
|
||||
{!isLoading && !hasError && (
|
||||
<div className="grid gap-3 grid-cols-2 lg:grid-cols-4">
|
||||
<Card className="overflow-hidden">
|
||||
<div className="h-1 bg-blue-500" />
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="space-y-1 min-w-0">
|
||||
<p className="text-2xl font-bold tracking-tight">{availableConfigs.length}</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">Available Models</p>
|
||||
<div className="flex flex-wrap gap-x-2 gap-y-0.5 text-[10px] text-muted-foreground">
|
||||
<span>{globalConfigs.length} Global</span>
|
||||
<span>{llmConfigs.length} Custom</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-blue-500/10">
|
||||
<Bot className="h-4 w-4 text-blue-600" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="overflow-hidden">
|
||||
<div className="h-1 bg-purple-500" />
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="space-y-1 min-w-0">
|
||||
<p className="text-2xl font-bold tracking-tight">{assignedConfigIds.length}</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">Assigned Roles</p>
|
||||
</div>
|
||||
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-purple-500/10">
|
||||
<CheckCircle className="h-4 w-4 text-purple-600" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="overflow-hidden">
|
||||
<div className={`h-1 ${isAssignmentComplete ? "bg-green-500" : "bg-yellow-500"}`} />
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="space-y-1 min-w-0">
|
||||
<p className="text-2xl font-bold tracking-tight">
|
||||
{Math.round((assignedConfigIds.length / 3) * 100)}%
|
||||
</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">Completion</p>
|
||||
</div>
|
||||
<div
|
||||
className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-lg ${
|
||||
isAssignmentComplete ? "bg-green-500/10" : "bg-yellow-500/10"
|
||||
}`}
|
||||
>
|
||||
{isAssignmentComplete ? (
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
) : (
|
||||
<AlertCircle className="h-4 w-4 text-yellow-600" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="overflow-hidden">
|
||||
<div className={`h-1 ${isAssignmentComplete ? "bg-emerald-500" : "bg-orange-500"}`} />
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="space-y-1 min-w-0">
|
||||
<p
|
||||
className={`text-2xl font-bold tracking-tight ${
|
||||
isAssignmentComplete ? "text-emerald-600" : "text-orange-600"
|
||||
}`}
|
||||
>
|
||||
{isAssignmentComplete ? "Ready" : "Setup"}
|
||||
</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">Status</p>
|
||||
</div>
|
||||
<div
|
||||
className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-lg ${
|
||||
isAssignmentComplete ? "bg-emerald-500/10" : "bg-orange-500/10"
|
||||
}`}
|
||||
>
|
||||
{isAssignmentComplete ? (
|
||||
<CheckCircle className="h-4 w-4 text-emerald-600" />
|
||||
) : (
|
||||
<RefreshCw className="h-4 w-4 text-orange-600" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info Alert */}
|
||||
{!isLoading && !hasError && (
|
||||
<div className="space-y-6">
|
||||
|
|
@ -363,7 +236,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
|
|||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
No LLM configurations found. Please add at least one LLM provider in the Model
|
||||
No LLM configurations found. Please add at least one LLM provider in the Agent
|
||||
Configs tab before assigning roles.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
|
@ -459,12 +332,12 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
|
|||
)}
|
||||
|
||||
{/* Custom Configurations */}
|
||||
{llmConfigs.length > 0 && (
|
||||
{newLLMConfigs.length > 0 && (
|
||||
<>
|
||||
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">
|
||||
Your Configurations
|
||||
</div>
|
||||
{llmConfigs
|
||||
{newLLMConfigs
|
||||
.filter(
|
||||
(config) => config.id && config.id.toString().trim() !== ""
|
||||
)
|
||||
|
|
@ -536,38 +409,6 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) {
|
|||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Status Indicator */}
|
||||
{isAssignmentComplete && !hasChanges && (
|
||||
<div className="flex justify-center pt-4">
|
||||
<div className="flex items-center gap-2 px-4 py-2 bg-green-50 text-green-700 rounded-lg border border-green-200">
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
<span className="text-sm font-medium">All roles assigned and saved!</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Progress Indicator */}
|
||||
<div className="flex justify-center">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span>Progress:</span>
|
||||
<div className="flex gap-1">
|
||||
{Object.keys(ROLE_DESCRIPTIONS).map((key) => (
|
||||
<div
|
||||
key={key}
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
assignments[`${key}_llm_id` as keyof typeof assignments]
|
||||
? "bg-primary"
|
||||
: "bg-muted"
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<span>
|
||||
{assignedConfigIds.length} of {Object.keys(ROLE_DESCRIPTIONS).length} roles assigned
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue