import { ChevronDown, Eye, EyeOff, KeyRound, Save, Trash2 } from 'lucide-react'; import type { FormEvent } from 'react'; import { useRef, useState } from 'react'; import { toast } from 'sonner'; import { ConfigError, Field } from '@/components/admin/admin-form-parts'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Spinner } from '@/components/ui/spinner'; import { stringValue } from '@/lib/admin'; import { cn } from '@/lib/utils'; import { patchLlmConfig } from '@/lib/rag-client'; import type { LlmConfig, LlmForm } from '@/types/admin'; type ApiKeyMode = 'configured' | 'editing' | 'clearing'; const REASONING_EFFORT_OPTIONS: Record = { deepseek: ['high', 'max'], openai: ['none', 'minimal', 'low', 'medium', 'high', 'xhigh'], }; function SearchableSelect({ disabled = false, onChange, options, placeholder = 'Pilih...', searchPlaceholder = 'Cari...', value, }: { disabled?: boolean; onChange: (value: string) => void; options: string[]; placeholder?: string; searchPlaceholder?: string; value: string; }) { const [open, setOpen] = useState(false); const [query, setQuery] = useState(''); const containerRef = useRef(null); const filtered = options.filter((o) => o.toLowerCase().includes(query.toLowerCase()), ); return (
{open && (
e.stopPropagation()} >
setQuery(e.target.value)} onBlur={() => { setTimeout(() => setOpen(false), 150); }} />
{filtered.length > 0 ? ( filtered.map((option) => ( )) ) : (

Tidak ditemukan.

)}
)}
); } function CreatableBaseUrlSelect({ onChange, options, value, }: { onChange: (value: string) => void; options: string[]; value: string; }) { const [customMode, setCustomMode] = useState( value !== '' && !options.includes(value), ); const selectValue = customMode ? '__custom__' : options.includes(value) ? value : ''; return (
{customMode && ( onChange(e.target.value)} /> )}
); } function ApiKeyInput({ mode, onModeChange, onChange, value, }: { mode: ApiKeyMode; onModeChange: (mode: ApiKeyMode) => void; onChange: (value: string) => void; value: string; }) { const [show, setShow] = useState(false); if (mode === 'configured') { return (
API key tersimpan
); } if (mode === 'clearing') { return (
API key akan dihapus
); } return (
onChange(e.target.value)} />
); } export function AdminLlmForm({ llm }: { llm?: LlmConfig | null }) { const cfg = llm ?? {}; const providers = cfg.providers ?? {}; const systemPromptsMap = cfg.system_prompts ?? {}; const systemPromptKeys = Object.keys(systemPromptsMap); const initialSystemPromptKey = systemPromptKeys[0] ?? 'default'; const initialProviderInfo = providers[cfg.active_provider ?? '']; const [formData, setFormData] = useState({ active_model: stringValue(cfg.active_model), active_provider: stringValue(cfg.active_provider), api_key: stringValue(cfg.api_key), base_url: stringValue(initialProviderInfo?.base_url), max_tokens: stringValue(cfg.max_tokens), reasoning_effort: stringValue(cfg.reasoning_effort), request_timeout: stringValue(cfg.request_timeout), system_prompt: systemPromptsMap[initialSystemPromptKey] ?? '', temperature: stringValue(cfg.temperature), }); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(); const [selectedTemplate, setSelectedTemplate] = useState( initialSystemPromptKey, ); const [apiKeyMode, setApiKeyMode] = useState( cfg.api_key_set ? 'configured' : 'editing', ); function setField( key: K, value: LlmForm[K], ): void { setFormData((prev) => ({ ...prev, [key]: value })); } function handleProviderChange(provider: string): void { const config = providers[provider]; setFormData((prev) => ({ ...prev, active_provider: provider, active_model: '', reasoning_effort: '', base_url: config?.base_url ?? '', })); } async function handleSubmit( event: FormEvent, ): Promise { event.preventDefault(); setIsSubmitting(true); setError(undefined); try { const payload: Record = {}; if (formData.active_provider) { payload.active_provider = formData.active_provider; payload.providers = { [formData.active_provider]: { base_url: formData.base_url || null, }, }; } if (formData.active_model) payload.active_model = formData.active_model; if (apiKeyMode === 'clearing') { payload.api_key = null; } else if (apiKeyMode === 'editing' && formData.api_key) { payload.api_key = formData.api_key; } payload.reasoning_effort = formData.reasoning_effort || null; if (formData.max_tokens) payload.max_tokens = parseInt(formData.max_tokens, 10); if (formData.request_timeout) payload.request_timeout = parseInt( formData.request_timeout, 10, ); if (formData.temperature) payload.temperature = parseFloat(formData.temperature); if (formData.system_prompt) { payload.system_prompts = { [selectedTemplate]: formData.system_prompt, }; } const result = await patchLlmConfig(payload); const savedProviders = result.providers ?? {}; const savedSystemPrompts = result.system_prompts ?? {}; const savedKey = savedSystemPrompts[selectedTemplate] !== undefined ? selectedTemplate : (Object.keys(savedSystemPrompts)[0] ?? selectedTemplate); setSelectedTemplate(savedKey); const savedProviderInfo = savedProviders[result.active_provider ?? '']; setFormData({ active_model: stringValue(result.active_model), active_provider: stringValue(result.active_provider), api_key: '', base_url: stringValue(savedProviderInfo?.base_url), max_tokens: stringValue(result.max_tokens), reasoning_effort: stringValue(result.reasoning_effort), request_timeout: stringValue(result.request_timeout), system_prompt: savedSystemPrompts[savedKey] ?? '', temperature: stringValue(result.temperature), }); setApiKeyMode(result.api_key_set ? 'configured' : 'editing'); toast.success('Konfigurasi LLM berhasil disimpan'); } catch (e) { setError( e instanceof Error ? e.message : 'Gagal menyimpan konfigurasi LLM.', ); } finally { setIsSubmitting(false); } } const providerConfig = providers[formData.active_provider]; const reasoningEffortOptions = REASONING_EFFORT_OPTIONS[formData.active_provider] ?? []; const baseUrlOptions = providerConfig?.base_url ? [providerConfig.base_url] : []; return (
{ void handleSubmit(e); }} > {/* Model */}

Model

setField('active_model', v) } />
setField('base_url', v) } />
{/* Parameter */}

Parameter

setField( 'temperature', event.target.value, ) } /> setField( 'max_tokens', event.target.value, ) } /> setField( 'request_timeout', event.target.value, ) } />
{/* Autentikasi */}

Autentikasi

setField('api_key', v)} onModeChange={setApiKeyMode} />
{/* System Prompt */}

System Prompt