import { Save } from 'lucide-react'; import type { FormEvent } from 'react'; import { useState } from 'react'; import { ConfigError, Field, RetrievalNumberField, } from '@/components/admin/admin-form-parts'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { Checkbox } from '@/components/ui/checkbox'; import { Input } from '@/components/ui/input'; import { Spinner } from '@/components/ui/spinner'; import { stringValue } from '@/lib/admin'; import { patchRetrievalConfig, patchVectorDbConfig } from '@/lib/rag-client'; import type { RetrievalConfig, RetrievalForm, VectorDbConfig, VectorDbForm, } from '@/types/admin'; export function AdminVectorDbForm({ vectorDb, }: { vectorDb?: VectorDbConfig | null; }) { const cfg = vectorDb ?? {}; const [formData, setFormData] = useState({ chunk_overlap: stringValue(cfg.chunk_overlap), chunk_size: stringValue(cfg.chunk_size), embedding_model: stringValue(cfg.embedding_model), persist_path: stringValue(cfg.persist_path), }); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(); function setField( key: K, value: VectorDbForm[K], ): void { setFormData((prev) => ({ ...prev, [key]: value })); } async function handleSubmit( event: FormEvent, ): Promise { event.preventDefault(); setIsSubmitting(true); setError(undefined); try { const payload: Record = {}; if (formData.embedding_model) payload.embedding_model = formData.embedding_model; if (formData.persist_path) payload.persist_path = formData.persist_path; if (formData.chunk_size) payload.chunk_size = parseInt(formData.chunk_size, 10); if (formData.chunk_overlap) payload.chunk_overlap = parseInt(formData.chunk_overlap, 10); const result = await patchVectorDbConfig(payload); setFormData({ chunk_overlap: stringValue(result.chunk_overlap), chunk_size: stringValue(result.chunk_size), embedding_model: stringValue(result.embedding_model), persist_path: stringValue(result.persist_path), }); } catch (e) { setError( e instanceof Error ? e.message : 'Gagal menyimpan konfigurasi Vector DB.', ); } finally { setIsSubmitting(false); } } return (
{ void handleSubmit(e); }} >
setField( 'embedding_model', event.target.value, ) } /> setField('persist_path', event.target.value) } /> setField('chunk_size', event.target.value) } /> setField('chunk_overlap', event.target.value) } />
); } export function AdminRetrievalForm({ retrieval, }: { retrieval?: RetrievalConfig | null; }) { const cfg = retrieval ?? {}; const [formData, setFormData] = useState({ bm25_weight: stringValue(cfg.bm25_weight), candidate_pool_size: stringValue(cfg.candidate_pool_size), dense_weight: stringValue(cfg.dense_weight), enable_reranker: Boolean(cfg.enable_reranker), history_turns: stringValue(cfg.history_turns), lexical_weight: stringValue(cfg.lexical_weight), max_context_chars: stringValue(cfg.max_context_chars), neighbor_window: stringValue(cfg.neighbor_window), reranker_model: stringValue(cfg.reranker_model), similarity_threshold: stringValue(cfg.similarity_threshold), top_k: stringValue(cfg.top_k), }); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(); function setField( key: K, value: RetrievalForm[K], ): void { setFormData((prev) => ({ ...prev, [key]: value })); } async function handleSubmit( event: FormEvent, ): Promise { event.preventDefault(); setIsSubmitting(true); setError(undefined); try { const payload: Record = { enable_reranker: formData.enable_reranker, }; if (formData.top_k) payload.top_k = parseInt(formData.top_k, 10); if (formData.max_context_chars) payload.max_context_chars = parseInt(formData.max_context_chars, 10); if (formData.bm25_weight) payload.bm25_weight = parseFloat(formData.bm25_weight); if (formData.dense_weight) payload.dense_weight = parseFloat(formData.dense_weight); if (formData.lexical_weight) payload.lexical_weight = parseFloat(formData.lexical_weight); if (formData.similarity_threshold) payload.similarity_threshold = parseFloat(formData.similarity_threshold); if (formData.history_turns) payload.history_turns = parseInt(formData.history_turns, 10); if (formData.candidate_pool_size) payload.candidate_pool_size = parseInt(formData.candidate_pool_size, 10); if (formData.neighbor_window) payload.neighbor_window = parseInt(formData.neighbor_window, 10); if (formData.reranker_model) payload.reranker_model = formData.reranker_model; const result = await patchRetrievalConfig(payload); setFormData({ bm25_weight: stringValue(result.bm25_weight), candidate_pool_size: stringValue(result.candidate_pool_size), dense_weight: stringValue(result.dense_weight), enable_reranker: Boolean(result.enable_reranker), history_turns: stringValue(result.history_turns), lexical_weight: stringValue(result.lexical_weight), max_context_chars: stringValue(result.max_context_chars), neighbor_window: stringValue(result.neighbor_window), reranker_model: stringValue(result.reranker_model), similarity_threshold: stringValue(result.similarity_threshold), top_k: stringValue(result.top_k), }); } catch (e) { setError( e instanceof Error ? e.message : 'Gagal menyimpan konfigurasi retrieval.', ); } finally { setIsSubmitting(false); } } return (
{ void handleSubmit(e); }} >
setField('top_k', v)} /> setField('max_context_chars', v)} /> setField('bm25_weight', v)} /> setField('dense_weight', v)} /> setField('lexical_weight', v)} /> setField('similarity_threshold', v) } /> setField('history_turns', v)} /> setField('candidate_pool_size', v) } /> setField('neighbor_window', v)} /> setField( 'reranker_model', event.target.value, ) } />
); } export function AdminVectorRetrievalForms({ retrieval, vectorDb, }: { retrieval?: RetrievalConfig | null; vectorDb?: VectorDbConfig | null; }) { return (
); }