mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 16:56:22 +02:00
feat: migrate createLLMConfig to jotai mutation atom and add query atoms for LLM configs
This commit is contained in:
parent
0c0491cd49
commit
5b7e5770be
6 changed files with 93 additions and 71 deletions
|
|
@ -60,34 +60,30 @@ import { LANGUAGES } from "@/contracts/enums/languages";
|
|||
import { getModelsByProvider } from "@/contracts/enums/llm-models";
|
||||
import { LLM_PROVIDERS } from "@/contracts/enums/llm-providers";
|
||||
import {
|
||||
type CreateLLMConfig,
|
||||
type LLMConfig,
|
||||
useGlobalLLMConfigs,
|
||||
useLLMConfigs,
|
||||
} from "@/hooks/use-llm-configs";
|
||||
import { cn } from "@/lib/utils";
|
||||
import InferenceParamsEditor from "../inference-params-editor";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { createLLMConfigMutationAtom, deleteLLMConfigMutationAtom, updateLLMConfigMutationAtom } from "@/atoms/llm-config/llm-config-mutation.atoms";
|
||||
import { CreateLLMConfigRequest, CreateLLMConfigResponse, LLMConfig, UpdateLLMConfigResponse } from "@/contracts/types/llm-config.types";
|
||||
import { llmConfigsAtom } from "@/atoms/llm-config/llm-config-query.atoms";
|
||||
|
||||
interface ModelConfigManagerProps {
|
||||
searchSpaceId: number;
|
||||
}
|
||||
|
||||
export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
||||
const {
|
||||
llmConfigs,
|
||||
loading,
|
||||
error,
|
||||
createLLMConfig,
|
||||
updateLLMConfig,
|
||||
deleteLLMConfig,
|
||||
refreshConfigs,
|
||||
} = useLLMConfigs(searchSpaceId);
|
||||
const { mutateAsync : createLLMConfig, isPending : isCreatingLLMConfig, error : createLLMConfigError, isError : isCreateLLMConfigError } = useAtomValue(createLLMConfigMutationAtom)
|
||||
const { mutateAsync : updateLLMConfig, isPending : isUpdatingLLMConfig, error : updateLLMConfigError, isError : isUpdateLLMConfigError} = useAtomValue(updateLLMConfigMutationAtom)
|
||||
const { mutateAsync : deleteLLMConfig, isPending : isDeletingLLMConfig, error : deleteLLMConfigError, isError : isDeleteLLMConfigError } = useAtomValue(deleteLLMConfigMutationAtom)
|
||||
const { data : llmConfigs, isFetching : isFetchingLLMConfigs, error : LLMConfigsFetchError, isError : isLLMConfigsFetchError, refetch : refreshConfigs} = useAtomValue(llmConfigsAtom)
|
||||
const { globalConfigs } = useGlobalLLMConfigs();
|
||||
const [isAddingNew, setIsAddingNew] = useState(false);
|
||||
const [editingConfig, setEditingConfig] = useState<LLMConfig | null>(null);
|
||||
const [formData, setFormData] = useState<CreateLLMConfig>({
|
||||
const [formData, setFormData] = useState<CreateLLMConfigRequest>({
|
||||
name: "",
|
||||
provider: "",
|
||||
provider: "" as CreateLLMConfigRequest["provider"], // Allow it as Default,
|
||||
custom_provider: "",
|
||||
model_name: "",
|
||||
api_key: "",
|
||||
|
|
@ -96,7 +92,9 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
litellm_params: {},
|
||||
search_space_id: searchSpaceId,
|
||||
});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const isSubmitting = isCreatingLLMConfig || isUpdatingLLMConfig
|
||||
const errors = [createLLMConfigError, updateLLMConfigError, deleteLLMConfigError, LLMConfigsFetchError] as Error[]
|
||||
const isError = Boolean(errors.filter(Boolean).length)
|
||||
const [modelComboboxOpen, setModelComboboxOpen] = useState(false);
|
||||
const [configToDelete, setConfigToDelete] = useState<LLMConfig | null>(null);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
|
@ -118,12 +116,12 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
}
|
||||
}, [editingConfig, searchSpaceId]);
|
||||
|
||||
const handleInputChange = (field: keyof CreateLLMConfig, value: string) => {
|
||||
const handleInputChange = (field: keyof CreateLLMConfigRequest, value: string) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
// Handle provider change with auto-fill API Base URL and reset model / 处理 Provider 变更并自动填充 API Base URL 并重置模型
|
||||
const handleProviderChange = (providerValue: string) => {
|
||||
const handleProviderChange = (providerValue : CreateLLMConfigRequest["provider"]) => {
|
||||
const provider = LLM_PROVIDERS.find((p) => p.value === providerValue);
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
|
|
@ -134,6 +132,8 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
}));
|
||||
};
|
||||
|
||||
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!formData.name || !formData.provider || !formData.model_name || !formData.api_key) {
|
||||
|
|
@ -141,23 +141,19 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
let result: LLMConfig | null = null;
|
||||
let result: CreateLLMConfigResponse | UpdateLLMConfigResponse | null = null;
|
||||
if (editingConfig) {
|
||||
// Update existing config
|
||||
result = await updateLLMConfig(editingConfig.id, formData);
|
||||
result = await updateLLMConfig({id : editingConfig.id, data : formData});
|
||||
} else {
|
||||
// Create new config
|
||||
result = await createLLMConfig(formData);
|
||||
}
|
||||
|
||||
setIsSubmitting(false);
|
||||
|
||||
if (result) {
|
||||
setFormData({
|
||||
name: "",
|
||||
provider: "",
|
||||
provider: "" as CreateLLMConfigRequest["provider"],
|
||||
custom_provider: "",
|
||||
model_name: "",
|
||||
api_key: "",
|
||||
|
|
@ -177,14 +173,11 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
|
||||
const handleConfirmDelete = async () => {
|
||||
if (!configToDelete) return;
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
await deleteLLMConfig(configToDelete.id);
|
||||
toast.success("Configuration deleted successfully");
|
||||
await deleteLLMConfig({id : configToDelete.id});
|
||||
} catch (error) {
|
||||
toast.error("Failed to delete configuration");
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setConfigToDelete(null);
|
||||
}
|
||||
};
|
||||
|
|
@ -217,26 +210,28 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={refreshConfigs}
|
||||
disabled={loading}
|
||||
onClick={() => refreshConfigs()}
|
||||
disabled={isFetchingLLMConfigs}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} />
|
||||
<RefreshCw className={`h-4 w-4 ${isFetchingLLMConfigs ? "animate-spin" : ""}`} />
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Error Alert */}
|
||||
{error && (
|
||||
{isError && errors.filter(Boolean).map(err => {
|
||||
return (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
<AlertDescription>{err?.message ?? "Something went wrong"}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
)
|
||||
}) }
|
||||
|
||||
{/* Global Configs Info Alert */}
|
||||
{!loading && !error && globalConfigs.length > 0 && (
|
||||
{!isFetchingLLMConfigs && !isError && globalConfigs.length > 0 && (
|
||||
<Alert>
|
||||
<CheckCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
|
|
@ -250,7 +245,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
)}
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
{isFetchingLLMConfigs && (
|
||||
<Card>
|
||||
<CardContent className="flex items-center justify-center py-12">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
|
|
@ -262,14 +257,14 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
)}
|
||||
|
||||
{/* Stats Overview */}
|
||||
{!loading && !error && (
|
||||
{!isFetchingLLMConfigs && !isError&& (
|
||||
<div className="grid gap-3 grid-cols-3">
|
||||
<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">{llmConfigs.length}</p>
|
||||
<p className="text-2xl font-bold tracking-tight">{llmConfigs?.length}</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">Total Configs</p>
|
||||
</div>
|
||||
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-blue-500/10">
|
||||
|
|
@ -285,7 +280,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
<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">
|
||||
{new Set(llmConfigs.map((c) => c.provider)).size}
|
||||
{new Set(llmConfigs?.map((c) => c.provider)).size}
|
||||
</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">Providers</p>
|
||||
</div>
|
||||
|
|
@ -314,7 +309,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
)}
|
||||
|
||||
{/* Configuration Management */}
|
||||
{!loading && !error && (
|
||||
{!isFetchingLLMConfigs && !isError && (
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-col space-y-4 sm:flex-row sm:items-center sm:justify-between sm:space-y-0">
|
||||
<div>
|
||||
|
|
@ -329,7 +324,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
{llmConfigs.length === 0 ? (
|
||||
{llmConfigs?.length === 0 ? (
|
||||
<Card className="border-dashed border-2 border-muted-foreground/25">
|
||||
<CardContent className="flex flex-col items-center justify-center py-16 text-center">
|
||||
<div className="rounded-full bg-muted p-4 mb-6">
|
||||
|
|
@ -350,7 +345,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
) : (
|
||||
<div className="grid gap-4">
|
||||
<AnimatePresence>
|
||||
{llmConfigs.map((config) => {
|
||||
{llmConfigs?.map((config) => {
|
||||
const providerInfo = getProviderInfo(config.provider);
|
||||
return (
|
||||
<motion.div
|
||||
|
|
@ -466,7 +461,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
setEditingConfig(null);
|
||||
setFormData({
|
||||
name: "",
|
||||
provider: "",
|
||||
provider: "" as LLMConfig["provider"],
|
||||
custom_provider: "",
|
||||
model_name: "",
|
||||
api_key: "",
|
||||
|
|
@ -538,7 +533,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
<Input
|
||||
id="custom_provider"
|
||||
placeholder="e.g., my-custom-provider"
|
||||
value={formData.custom_provider}
|
||||
value={formData.custom_provider ?? ""}
|
||||
onChange={(e) => handleInputChange("custom_provider", e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
|
@ -683,7 +678,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
<Input
|
||||
id="api_base"
|
||||
placeholder={selectedProvider?.apiBase || "e.g., https://api.openai.com/v1"}
|
||||
value={formData.api_base}
|
||||
value={formData.api_base ?? ""}
|
||||
onChange={(e) => handleInputChange("api_base", e.target.value)}
|
||||
/>
|
||||
{selectedProvider?.apiBase && formData.api_base === selectedProvider.apiBase && (
|
||||
|
|
@ -765,7 +760,7 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
|
|||
setEditingConfig(null);
|
||||
setFormData({
|
||||
name: "",
|
||||
provider: "",
|
||||
provider: "" as LLMConfig["provider"],
|
||||
custom_provider: "",
|
||||
model_name: "",
|
||||
api_key: "",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue