diff --git a/ui/web/src/components/layout/system-settings-modal.tsx b/ui/web/src/components/layout/system-settings-modal.tsx index f9700817..d6cf545e 100644 --- a/ui/web/src/components/layout/system-settings-modal.tsx +++ b/ui/web/src/components/layout/system-settings-modal.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { Settings2, Loader2, Save, AlertTriangle, Info, ExternalLink, - Brain, Eye, MessageSquareText, Archive, Clock, Hash, CheckCircle2, XCircle, + Brain, Eye, MessageSquareText, Archive, Clock, Hash, CheckCircle2, XCircle, Network, } from "lucide-react"; import { Link } from "react-router"; import { @@ -74,6 +74,9 @@ interface InitState { compThreshold: string; compKeepRecent: string; compMaxTokens: string; + kgProvider: string; + kgModel: string; + kgMinConfidence: string; } const DEFAULTS: InitState = { @@ -82,6 +85,7 @@ const DEFAULTS: InitState = { toolStatus: true, blockReply: false, intentClassify: true, compProvider: "", compModel: "", compThreshold: "", compKeepRecent: "", compMaxTokens: "", + kgProvider: "", kgModel: "", kgMinConfidence: "0.75", }; function parseBool(v: string | undefined, fallback: boolean): boolean { @@ -117,7 +121,15 @@ export function SystemSettingsModal({ open, onOpenChange }: SystemSettingsModalP const [compKeepRecent, setCompKeepRecent] = useState(""); const [compMaxTokens, setCompMaxTokens] = useState(""); - const applyConfigs = useCallback((configs: Record) => { + // Knowledge Graph + const [kgProvider, setKgProvider] = useState(""); + const [kgModel, setKgModel] = useState(""); + const [kgMinConfidence, setKgMinConfidence] = useState("0.75"); + + const applyConfigs = useCallback(( + configs: Record, + kgSettings?: { extraction_provider?: string; extraction_model?: string; min_confidence?: number }, + ) => { const s: InitState = { embProvider: configs["embedding.provider"] ?? "", embModel: configs["embedding.model"] ?? "", @@ -131,6 +143,9 @@ export function SystemSettingsModal({ open, onOpenChange }: SystemSettingsModalP compThreshold: configs["compaction.threshold"] ?? "", compKeepRecent: configs["compaction.keep_recent"] ?? "", compMaxTokens: configs["compaction.max_tokens"] ?? "", + kgProvider: kgSettings?.extraction_provider ?? "", + kgModel: kgSettings?.extraction_model ?? "", + kgMinConfidence: String(kgSettings?.min_confidence ?? 0.75), }; setInit(s); setEmbProvider(s.embProvider); @@ -145,15 +160,22 @@ export function SystemSettingsModal({ open, onOpenChange }: SystemSettingsModalP setCompThreshold(s.compThreshold); setCompKeepRecent(s.compKeepRecent); setCompMaxTokens(s.compMaxTokens); + setKgProvider(s.kgProvider); + setKgModel(s.kgModel); + setKgMinConfidence(s.kgMinConfidence); resetEmb(); }, [resetEmb]); useEffect(() => { if (!open) return; setLoading(true); - http - .get>("/v1/system-configs") - .then(applyConfigs) + Promise.all([ + http.get>("/v1/system-configs"), + http.get<{ settings?: Record }>("/v1/tools/builtin/knowledge_graph_search") + .then((r) => r.settings as { extraction_provider?: string; extraction_model?: string; min_confidence?: number } | undefined) + .catch(() => undefined), + ]) + .then(([configs, kgSettings]) => applyConfigs(configs, kgSettings)) .catch((err) => toast.error(err instanceof Error ? err.message : t("loadFailed"))) .finally(() => setLoading(false)); }, [open, http, applyConfigs, t]); @@ -195,6 +217,19 @@ export function SystemSettingsModal({ open, onOpenChange }: SystemSettingsModalP for (const [key, value] of Object.entries(updates)) { await http.put(`/v1/system-configs/${key}`, { value }); } + + // KG extraction — save via builtin tools API + const kgChanged = kgProvider !== init.kgProvider || kgModel !== init.kgModel || kgMinConfidence !== init.kgMinConfidence; + if (kgChanged) { + await http.put("/v1/tools/builtin/knowledge_graph_search", { + settings: { + extraction_provider: kgProvider, + extraction_model: kgModel, + min_confidence: Number(kgMinConfidence) || 0.75, + extract_on_memory_write: !!(kgProvider && kgModel), + }, + }); + } toast.success(t("saved")); onOpenChange(false); } catch (err) { @@ -344,6 +379,51 @@ export function SystemSettingsModal({ open, onOpenChange }: SystemSettingsModalP + {/* ── Knowledge Graph Extraction ── */} + + + + + {t("kg.title")} + + {t("kg.description")} + + + { setKgProvider(v); setKgModel(""); }} + model={kgModel} + onModelChange={setKgModel} + allowEmpty + providerLabel={t("kg.provider")} + modelLabel={t("kg.model")} + providerTip={t("kg.providerTip")} + modelTip={t("kg.modelTip")} + providerPlaceholder={t("kg.providerPlaceholder")} + modelPlaceholder={t("kg.modelPlaceholder")} + /> + +
+ + setKgMinConfidence(e.target.value)} + className="max-w-[120px] text-base md:text-sm" + /> +

{t("kg.minConfidenceHint")}

+
+ +
+ + {t("kg.info")} +
+
+
+ {/* ── UX Behavior — uses FeatureSwitchGroup (same as config page) ── */} +
{lang || "code"}
-
+      
         {children}
       
diff --git a/ui/web/src/i18n/locales/en/system-settings.json b/ui/web/src/i18n/locales/en/system-settings.json index 0b56c5bd..3f81e78b 100644 --- a/ui/web/src/i18n/locales/en/system-settings.json +++ b/ui/web/src/i18n/locales/en/system-settings.json @@ -40,6 +40,19 @@ "intentClassifyHint": "Classify user intent before routing to the agent.", "intentClassifyInfo": "Messages are classified to optimize agent routing and behavior." }, + "kg": { + "title": "Knowledge Graph", + "description": "Extract entities and relationships from agent memory into a searchable knowledge graph.", + "provider": "Extraction Provider", + "model": "Extraction Model", + "providerTip": "LLM provider used for extracting entities from memory.", + "modelTip": "Model for entity/relation extraction (e.g. claude-sonnet-4-20250514).", + "providerPlaceholder": "Select provider", + "modelPlaceholder": "Select model", + "minConfidence": "Min Confidence", + "minConfidenceHint": "Minimum confidence threshold (0–1) for extracted entities.", + "info": "Automatically extracts entities and relationships when agents write to memory. Leave provider empty to disable." + }, "compaction": { "title": "Pending Message Compaction", "description": "Summarize long pending message history to stay within context limits.", diff --git a/ui/web/src/i18n/locales/vi/system-settings.json b/ui/web/src/i18n/locales/vi/system-settings.json index 078b015f..b07b34c1 100644 --- a/ui/web/src/i18n/locales/vi/system-settings.json +++ b/ui/web/src/i18n/locales/vi/system-settings.json @@ -40,6 +40,19 @@ "intentClassifyHint": "Phân loại ý định người dùng trước khi chuyển tới agent.", "intentClassifyInfo": "Tin nhắn được phân loại để tối ưu định tuyến agent." }, + "kg": { + "title": "Đồ thị tri thức", + "description": "Trích xuất thực thể và quan hệ từ bộ nhớ agent vào đồ thị tri thức có thể tìm kiếm.", + "provider": "Provider trích xuất", + "model": "Model trích xuất", + "providerTip": "Provider LLM dùng để trích xuất thực thể từ bộ nhớ.", + "modelTip": "Model trích xuất thực thể/quan hệ (vd: claude-sonnet-4-20250514).", + "providerPlaceholder": "Chọn provider", + "modelPlaceholder": "Chọn model", + "minConfidence": "Độ tin cậy tối thiểu", + "minConfidenceHint": "Ngưỡng độ tin cậy (0–1) cho thực thể được trích xuất.", + "info": "Tự động trích xuất thực thể và quan hệ khi agent ghi vào bộ nhớ. Để trống provider để tắt." + }, "compaction": { "title": "Nén tin nhắn chờ", "description": "Tóm tắt lịch sử tin nhắn chờ dài để nằm trong giới hạn context.", diff --git a/ui/web/src/i18n/locales/zh/system-settings.json b/ui/web/src/i18n/locales/zh/system-settings.json index 398ad2a1..a92db2f6 100644 --- a/ui/web/src/i18n/locales/zh/system-settings.json +++ b/ui/web/src/i18n/locales/zh/system-settings.json @@ -40,6 +40,19 @@ "intentClassifyHint": "在路由到Agent之前分类用户意图。", "intentClassifyInfo": "消息被分类以优化Agent路由和行为。" }, + "kg": { + "title": "知识图谱", + "description": "从Agent记忆中提取实体和关系到可搜索的知识图谱。", + "provider": "提取Provider", + "model": "提取模型", + "providerTip": "用于从记忆中提取实体的LLM Provider。", + "modelTip": "实体/关系提取模型(如claude-sonnet-4-20250514)。", + "providerPlaceholder": "选择Provider", + "modelPlaceholder": "选择模型", + "minConfidence": "最低置信度", + "minConfidenceHint": "提取实体的最低置信度阈值(0–1)。", + "info": "当Agent写入记忆时自动提取实体和关系。留空Provider以禁用。" + }, "compaction": { "title": "待处理消息压缩", "description": "压缩长待处理消息历史以保持在上下文限制内。",