diff --git a/CHANGELOG.md b/CHANGELOG.md index 34098df2..f766ba5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### 变更 +- 知识库桌面 / 首次打开默认可用:打开「知识库」先看到本地文件夹挂接与云知识库挂接(ima 等),不必先找开关。向量模型、图片识别、扫描件 OCR 仍在「基础设置」里,留给后续清洗、标注、蒸馏。原文件夹只读;解析结果仍写入用户另选的新文件夹。 - 侧栏「组织 OS」更名为「组织」(英文 Organization)。 diff --git a/dashboard/src/locales/en.json b/dashboard/src/locales/en.json index 1df40e4f..3af678f3 100644 --- a/dashboard/src/locales/en.json +++ b/dashboard/src/locales/en.json @@ -416,15 +416,32 @@ }, "knowledgeBases": { "title": "Knowledge Bases", - "subtitle": "Index documents for chat retrieval. Optionally share them with users on this instance.", + "subtitle": "Mount a local folder or cloud knowledge base first. Cleaning, labeling, and distillation come later.", "featureTitle": "Enable Knowledge Bases", "settingsTitle": "Knowledge base settings", - "settingsLead": "Instance-wide switch. When on, configure an embedding model. When off, knowledge bases are unavailable for everyone.", - "settingsOpen": "Enable knowledge bases", + "settingsFoundation": "Foundation settings", + "settingsFoundationLead": "These settings are for later cleaning, labeling, and distillation. You can mount sources first without changing them.", + "settingsTabVector": "Vector model", + "settingsTabImage": "Images", + "settingsTabOcr": "Scanned-document OCR", + "settingsLead": "Advanced switch. It is on by default on the desktop. Turning it off hides knowledge bases from colleagues.", + "settingsOpen": "Knowledge base feature", + "homeTitle": "Mount your sources first", + "homeDesc": "Pick a folder on this computer, or attach a cloud knowledge base such as ima. The original folder stays read-only. Parse results go to a new folder you choose. Colleagues can look them up in chat.", + "homeAdvancedHint": "Vector, image, and scanned-document OCR settings are for later cleaning, labeling, and distillation.", + "defaultLocalName": "Local files", + "defaultCloudName": "Cloud knowledge", + "pickFolder": "Choose folder", + "pickFolderUnavailable": "This window cannot open a folder picker. Type the path instead.", + "imageSettingsTitle": "Read text in images", + "imageSettingsDesc": "Turn this on to recognize text in pictures, so you can clean and label them later.", + "ocrFoundationHint": "Turn this on so scanned PDFs can be read as text for later distillation.", + "disabledTitle": "Knowledge bases are turned off", + "disabledDesc": "You can turn the feature back on in foundation settings. After that, mount a local folder or a cloud knowledge base to start.", "manageModels": "Manage models", "unavailableTitle": "Knowledge bases are not ready", - "unavailableDescription": "Open Settings in the top-right to enable knowledge bases and configure an embedding model.", - "unavailableDescriptionNonAdmin": "Knowledge bases are not enabled or not configured yet. Please contact an administrator.", + "unavailableDescription": "Open Foundation settings in the top-right to turn knowledge bases on.", + "unavailableDescriptionNonAdmin": "Knowledge bases are turned off. Please contact an administrator.", "create": "Create knowledge base", "edit": "Edit knowledge base", "addToChat": "Add to chat", @@ -449,12 +466,12 @@ "enableGuideStepModel": "Configure an embedding model", "enableGuideStepModelDetail": "Choose local ONNX (download the model first) or an online embedding provider, then save.", "noDescription": "No description", - "selectBase": "Select a knowledge base to view its documents", - "mountTitle": "Local directory mount", - "mountHint": "Mount a folder as read-only. FreeOS never deletes or edits files in that folder. Parse copies must go to a new folder you choose.", + "selectBase": "Select a knowledge base, or mount a source first", + "mountTitle": "Local folder mount", + "mountHint": "Attach a folder on this computer. It stays read-only. Parse or distillation results must go to a new folder you choose.", "mountSource": "Local folder path", - "mountSourceRequired": "Enter a local folder to mount", - "mountAction": "Mount read-only", + "mountSourceRequired": "Choose a local folder to mount", + "mountAction": "Mount folder", "mountSaved": "Folder mounted (read-only)", "mountFailed": "Could not mount the folder", "mountDir": "Folder", @@ -463,8 +480,8 @@ "distillDest": "Choose a new folder for parse results (not the mounted folder)", "distillDone": "Copied {{count}} files to {{path}}", "distillFailed": "Could not copy parse results", - "cloudMountTitle": "Cloud knowledge (ima and similar)", - "cloudMountHint": "Attach a cloud corpus such as ima. FreeOS does not download or parse those files locally. Distillation writes a pointer into a new folder you choose, for later agent strengthening.", + "cloudMountTitle": "Cloud knowledge mount", + "cloudMountHint": "Attach a cloud knowledge base such as ima. Files are not parsed locally. Distillation writes a pointer into a new folder you choose, for later assistant strengthening.", "cloudUrl": "Cloud knowledge URL", "cloudUrlRequired": "Enter a cloud knowledge URL", "cloudProvider": "Provider (ima…)", diff --git a/dashboard/src/locales/zh.json b/dashboard/src/locales/zh.json index 6b4a2101..6d093a9c 100644 --- a/dashboard/src/locales/zh.json +++ b/dashboard/src/locales/zh.json @@ -416,15 +416,32 @@ }, "knowledgeBases": { "title": "知识库", - "subtitle": "上传文档建立索引,在对话中检索引用;可按需共享给同实例用户。", + "subtitle": "先挂接本地文件夹或云知识库,再按需做清洗、标注和蒸馏。", "featureTitle": "启用知识库", "settingsTitle": "知识库设置", - "settingsLead": "实例级开关。启用后需配置向量模型;关闭后全实例不可使用知识库。", - "settingsOpen": "启用知识库", + "settingsFoundation": "基础设置", + "settingsFoundationLead": "这些是后续清洗、标注、蒸馏用的基础设置。先挂接资料即可开始,不必先改这里。", + "settingsTabVector": "向量模型", + "settingsTabImage": "图片", + "settingsTabOcr": "扫描件 OCR", + "settingsLead": "高级开关。桌面默认已打开。关掉后,同事都无法再使用知识库。", + "settingsOpen": "知识库功能", + "homeTitle": "先把资料挂进来", + "homeDesc": "选电脑上的文件夹,或挂接 ima 等云知识库。原来的文件夹只读,不会被改动;解析结果会存到你另选的新文件夹。同事可以在对话里查阅。", + "homeAdvancedHint": "向量模型、图片和扫描件 OCR 留给后续清洗、标注、蒸馏。", + "defaultLocalName": "本地资料", + "defaultCloudName": "云知识库", + "pickFolder": "选择文件夹", + "pickFolderUnavailable": "当前窗口无法弹出文件夹选择,请直接填写路径。", + "imageSettingsTitle": "识别图片里的文字", + "imageSettingsDesc": "打开后,可以把图片里的字认出来,方便以后清洗和标注。", + "ocrFoundationHint": "打开后,扫描件 PDF 也能认成文字,方便以后蒸馏。", + "disabledTitle": "知识库功能已关闭", + "disabledDesc": "可以在基础设置里重新打开。打开后,先挂接本地文件夹或云知识库即可使用。", "manageModels": "管理模型", "unavailableTitle": "知识库尚未就绪", - "unavailableDescription": "请先在右上角设置中启用知识库,并配置可用的向量模型。", - "unavailableDescriptionNonAdmin": "知识库尚未启用或未配置向量模型,请联系管理员。", + "unavailableDescription": "请先在右上角基础设置中打开知识库。", + "unavailableDescriptionNonAdmin": "知识库尚未打开,请联系管理员。", "create": "创建知识库", "edit": "编辑知识库", "addToChat": "添加到会话", @@ -449,12 +466,12 @@ "enableGuideStepModel": "配置向量模型", "enableGuideStepModelDetail": "选择本地 ONNX(需先下载模型)或在线 Embedding 提供商,保存后即可使用。", "noDescription": "暂无描述", - "selectBase": "选择一个知识库以查看文档", - "mountTitle": "挂载本地目录", - "mountHint": "以只读方式挂载本地文件夹。FreeOS 不会删除或修改该目录中的文件。解析结果必须保存到你另外指定的新文件夹。", + "selectBase": "选一个知识库查看文档,或先挂接资料", + "mountTitle": "本地文件夹挂接", + "mountHint": "把电脑上的文件夹挂进来,只读,不会改原来的文件。解析或蒸馏结果必须存到你另选的新文件夹。", "mountSource": "本地文件夹路径", - "mountSourceRequired": "请输入要挂载的本地文件夹", - "mountAction": "只读挂载", + "mountSourceRequired": "请选择要挂接的本地文件夹", + "mountAction": "挂接文件夹", "mountSaved": "已只读挂载该文件夹", "mountFailed": "挂载失败", "mountDir": "文件夹", @@ -463,8 +480,8 @@ "distillDest": "请选择保存解析结果的新文件夹(不能是已挂载的目录)", "distillDone": "已复制 {{count}} 个文件到 {{path}}", "distillFailed": "无法复制解析结果", - "cloudMountTitle": "云知识库(ima 等)", - "cloudMountHint": "挂载 ima 等云端语料。FreeOS 不会在本地解析这些文件。蒸馏时只在你指定的新文件夹写入指针,供后续强化智能体。", + "cloudMountTitle": "云知识库挂接", + "cloudMountHint": "挂接 ima 等云知识库。不会在本地解析原文。蒸馏时只在你另选的新文件夹写入指针,供以后强化智能助手。", "cloudUrl": "云知识库地址", "cloudUrlRequired": "请输入云知识库地址", "cloudProvider": "来源(ima…)", diff --git a/dashboard/src/pages/KnowledgeBases/CloudMountPanel.tsx b/dashboard/src/pages/KnowledgeBases/CloudMountPanel.tsx index 04949764..75b89513 100644 --- a/dashboard/src/pages/KnowledgeBases/CloudMountPanel.tsx +++ b/dashboard/src/pages/KnowledgeBases/CloudMountPanel.tsx @@ -3,20 +3,33 @@ import { Button, Input, Space, Typography } from "antd"; import { useTranslation } from "react-i18next"; import { knowledgeBasesApi } from "../../api/modules/knowledgeBases"; import { message } from "../../utils/antdMessage"; +import { + canPickKnowledgeFolder, + pickKnowledgeFolder, +} from "./pickKnowledgeFolder"; interface CloudMountPanelProps { - kbId: string; + kbId?: string; + ensureKb?: () => Promise; + onMounted?: () => void; + prominent?: boolean; } -export function CloudMountPanel({ kbId }: CloudMountPanelProps) { +export function CloudMountPanel({ + kbId, + ensureKb, + onMounted, + prominent = false, +}: CloudMountPanelProps) { const { t } = useTranslation(); const [url, setUrl] = useState(""); const [provider, setProvider] = useState("ima"); const [mounted, setMounted] = useState(false); const [busy, setBusy] = useState(false); + const canPick = canPickKnowledgeFolder(); - const load = async () => { - const next = await knowledgeBasesApi.getMount(kbId); + const load = async (id: string) => { + const next = await knowledgeBasesApi.getMount(id); if (next.kind === "cloud") { setMounted(next.mounted); setUrl(next.cloud_url || ""); @@ -25,9 +38,18 @@ export function CloudMountPanel({ kbId }: CloudMountPanelProps) { }; useEffect(() => { - void load().catch(() => undefined); + if (!kbId) return; + void load(kbId).catch(() => undefined); }, [kbId]); + const resolveKb = async () => { + if (kbId) return kbId; + if (!ensureKb) { + throw new Error(t("knowledgeBases.cloudMountFailed")); + } + return ensureKb(); + }; + const mount = async () => { if (!url.trim()) { message.error(t("knowledgeBases.cloudUrlRequired")); @@ -35,13 +57,15 @@ export function CloudMountPanel({ kbId }: CloudMountPanelProps) { } setBusy(true); try { - await knowledgeBasesApi.setMount(kbId, "", "", { + const id = await resolveKb(); + await knowledgeBasesApi.setMount(id, "", "", { kind: "cloud", cloud_url: url.trim(), cloud_provider: provider.trim() || "ima", }); message.success(t("knowledgeBases.cloudMountSaved")); - await load(); + await load(id); + onMounted?.(); } catch (err) { message.error( err instanceof Error @@ -54,11 +78,15 @@ export function CloudMountPanel({ kbId }: CloudMountPanelProps) { }; const distill = async () => { - const folder = window.prompt(t("knowledgeBases.cloudDistillDest")); + let folder = await pickKnowledgeFolder(); + if (!folder && !canPick) { + folder = window.prompt(t("knowledgeBases.cloudDistillDest")) || ""; + } if (!folder) return; setBusy(true); try { - const result = await knowledgeBasesApi.distillMount(kbId, folder); + const id = await resolveKb(); + const result = await knowledgeBasesApi.distillMount(id, folder); message.success( t("knowledgeBases.cloudDistillDone", { path: result.distill_path }), ); @@ -72,7 +100,7 @@ export function CloudMountPanel({ kbId }: CloudMountPanelProps) { }; return ( -
+
{t("knowledgeBases.cloudMountTitle")} @@ -90,7 +118,7 @@ export function CloudMountPanel({ kbId }: CloudMountPanelProps) { value={url} onChange={(event) => setUrl(event.target.value)} placeholder={t("knowledgeBases.cloudUrl")} - style={{ minWidth: 280 }} + style={{ minWidth: 240 }} /> +
+ ) : null} +
+ ); +} diff --git a/dashboard/src/pages/KnowledgeBases/LocalMountPanel.test.tsx b/dashboard/src/pages/KnowledgeBases/LocalMountPanel.test.tsx new file mode 100644 index 00000000..4769cedc --- /dev/null +++ b/dashboard/src/pages/KnowledgeBases/LocalMountPanel.test.tsx @@ -0,0 +1,59 @@ +import { describe, expect, it, vi, beforeEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +const pickKnowledgeFolder = vi.fn(); +const setMount = vi.fn(); +const getMount = vi.fn(); + +vi.mock("./pickKnowledgeFolder", () => ({ + pickKnowledgeFolder: (...args: unknown[]) => pickKnowledgeFolder(...args), + canPickKnowledgeFolder: () => true, +})); + +vi.mock("../../api/modules/knowledgeBases", () => ({ + knowledgeBasesApi: { + getMount: (...args: unknown[]) => getMount(...args), + setMount: (...args: unknown[]) => setMount(...args), + distillMount: vi.fn(), + }, +})); + +import { LocalMountPanel } from "./LocalMountPanel"; + +describe("", () => { + beforeEach(() => { + pickKnowledgeFolder.mockReset(); + setMount.mockReset(); + getMount.mockReset(); + getMount.mockResolvedValue({ + mounted: false, + source_path: "", + entries: [], + }); + setMount.mockResolvedValue({ + mounted: true, + source_path: "/docs", + entries: [], + }); + }); + + it("fills the path from the folder picker and mounts", async () => { + const user = userEvent.setup(); + pickKnowledgeFolder.mockResolvedValue("/Users/ada/Notes"); + const ensureKb = vi.fn().mockResolvedValue("kb-new"); + + render(); + + await user.click( + screen.getByRole("button", { name: "knowledgeBases.pickFolder" }), + ); + expect(pickKnowledgeFolder).toHaveBeenCalled(); + + await user.click( + screen.getByRole("button", { name: "knowledgeBases.mountAction" }), + ); + expect(ensureKb).toHaveBeenCalled(); + expect(setMount).toHaveBeenCalledWith("kb-new", "/Users/ada/Notes"); + }); +}); diff --git a/dashboard/src/pages/KnowledgeBases/LocalMountPanel.tsx b/dashboard/src/pages/KnowledgeBases/LocalMountPanel.tsx index 50cef9ae..57ce90ec 100644 --- a/dashboard/src/pages/KnowledgeBases/LocalMountPanel.tsx +++ b/dashboard/src/pages/KnowledgeBases/LocalMountPanel.tsx @@ -1,35 +1,67 @@ import { useEffect, useState } from "react"; import { Button, Input, List, Space, Tag, Typography } from "antd"; +import { FolderOpen } from "lucide-react"; import { useTranslation } from "react-i18next"; import { knowledgeBasesApi } from "../../api/modules/knowledgeBases"; import { message } from "../../utils/antdMessage"; +import { + canPickKnowledgeFolder, + pickKnowledgeFolder, +} from "./pickKnowledgeFolder"; interface LocalMountPanelProps { - kbId: string; + kbId?: string; + ensureKb?: () => Promise; + onMounted?: () => void; + prominent?: boolean; } -export function LocalMountPanel({ kbId }: LocalMountPanelProps) { +export function LocalMountPanel({ + kbId, + ensureKb, + onMounted, + prominent = false, +}: LocalMountPanelProps) { const { t } = useTranslation(); const [source, setSource] = useState(""); - const [dest, setDest] = useState(""); const [entries, setEntries] = useState< Array<{ path: string; name: string; is_dir: boolean; size: number }> >([]); const [mounted, setMounted] = useState(false); const [busy, setBusy] = useState(false); + const canPick = canPickKnowledgeFolder(); - const load = async () => { - const next = await knowledgeBasesApi.getMount(kbId); + const load = async (id: string) => { + const next = await knowledgeBasesApi.getMount(id); setMounted(next.mounted); setSource(next.source_path || ""); - setDest(next.distill_path || ""); setEntries(next.entries || []); }; useEffect(() => { - void load().catch(() => undefined); + if (!kbId) return; + void load(kbId).catch(() => undefined); }, [kbId]); + const resolveKb = async () => { + if (kbId) return kbId; + if (!ensureKb) { + throw new Error(t("knowledgeBases.mountFailed")); + } + return ensureKb(); + }; + + const pickSource = async () => { + const path = await pickKnowledgeFolder(); + if (path) { + setSource(path); + return; + } + if (!canPick) { + message.info(t("knowledgeBases.pickFolderUnavailable")); + } + }; + const mount = async () => { if (!source.trim()) { message.error(t("knowledgeBases.mountSourceRequired")); @@ -37,9 +69,11 @@ export function LocalMountPanel({ kbId }: LocalMountPanelProps) { } setBusy(true); try { - await knowledgeBasesApi.setMount(kbId, source.trim(), dest.trim()); + const id = await resolveKb(); + await knowledgeBasesApi.setMount(id, source.trim()); message.success(t("knowledgeBases.mountSaved")); - await load(); + await load(id); + onMounted?.(); } catch (err) { message.error( err instanceof Error ? err.message : t("knowledgeBases.mountFailed"), @@ -50,18 +84,22 @@ export function LocalMountPanel({ kbId }: LocalMountPanelProps) { }; const distill = async () => { - const folder = window.prompt(t("knowledgeBases.distillDest")); + let folder = await pickKnowledgeFolder(); + if (!folder && !canPick) { + folder = window.prompt(t("knowledgeBases.distillDest")) || ""; + } if (!folder) return; setBusy(true); try { - const result = await knowledgeBasesApi.distillMount(kbId, folder); + const id = await resolveKb(); + const result = await knowledgeBasesApi.distillMount(id, folder); message.success( t("knowledgeBases.distillDone", { count: result.copied, path: result.distill_path, }), ); - await load(); + await load(id); } catch (err) { message.error( err instanceof Error ? err.message : t("knowledgeBases.distillFailed"), @@ -72,7 +110,7 @@ export function LocalMountPanel({ kbId }: LocalMountPanelProps) { }; return ( -
+
{t("knowledgeBases.mountTitle")} {t("knowledgeBases.mountHint")} @@ -82,8 +120,14 @@ export function LocalMountPanel({ kbId }: LocalMountPanelProps) { value={source} onChange={(event) => setSource(event.target.value)} placeholder={t("knowledgeBases.mountSource")} - style={{ minWidth: 280 }} + style={{ minWidth: 240 }} /> + diff --git a/dashboard/src/pages/KnowledgeBases/index.module.less b/dashboard/src/pages/KnowledgeBases/index.module.less index 9a3f64a1..45bf1e1a 100644 --- a/dashboard/src/pages/KnowledgeBases/index.module.less +++ b/dashboard/src/pages/KnowledgeBases/index.module.less @@ -791,6 +791,78 @@ flex: 1; } +.mountHome { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 8px; + width: min(920px, 100%); + margin: 0 auto; + padding: 28px 24px 32px; +} + +.mountHomeTitle { + margin: 8px 0 0 !important; + text-align: center; +} + +.mountHomeLead { + margin: 0 auto 8px !important; + max-width: 640px; + text-align: center; + color: var(--fn-text-secondary); +} + +.mountHomeCards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 16px; + margin-top: 8px; +} + +.mountCard { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; + padding: 16px; + border: 1px solid var(--fn-border-secondary); + border-radius: var(--fn-radius-lg); + background: var(--fn-bg-primary); +} + +.mountCardIcon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + margin-bottom: 4px; + border-radius: 10px; + background: color-mix(in srgb, var(--fn-color-brand) 12%, transparent); + color: var(--fn-color-brand); +} + +.mountHomeAdvanced { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 4px 8px; + margin-top: 12px; + text-align: center; +} + +.settingsTabs { + margin-top: 4px; +} + +.settingsAdvanced { + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid var(--fn-border-secondary); +} + .setupMascot { display: block; width: 120px; diff --git a/dashboard/src/pages/KnowledgeBases/index.tsx b/dashboard/src/pages/KnowledgeBases/index.tsx index eaa89e5e..917ba2fa 100644 --- a/dashboard/src/pages/KnowledgeBases/index.tsx +++ b/dashboard/src/pages/KnowledgeBases/index.tsx @@ -26,6 +26,7 @@ import { Select, Spin, Switch, + Tabs, Tag, Tooltip, Typography, @@ -118,6 +119,7 @@ import { isKnowledgeMarkdownDocument, } from "../../utils/knowledgeDocPreview"; import { CloudMountPanel } from "./CloudMountPanel"; +import { KnowledgeMountHome } from "./KnowledgeMountHome"; import { LocalMountPanel } from "./LocalMountPanel"; import TextDocumentEditorModal, { type TextDocumentFormat, @@ -1850,8 +1852,32 @@ export default function KnowledgeBasesPage() { const showListPane = !isMobile || mobilePane === "list"; const showDetailPane = !isMobile || mobilePane === "detail"; const showListPanel = showListPane && (isMobile || !listPanelCollapsed); - const showEnableGuide = !loading && !usable; - const showEmptyGuide = !loading && usable && bases.length === 0; + const featureEnabled = Boolean(capability?.feature_enabled); + const showDisabled = !loading && !featureEnabled; + const showMountHome = !loading && featureEnabled && bases.length === 0; + + const ensureKb = async (name: string) => { + if (atBaseLimit) { + message.warning( + t("knowledgeBases.baseLimitReached", { + count: limits.max_bases_per_owner, + }), + ); + throw new Error( + t("knowledgeBases.baseLimitReached", { + count: limits.max_bases_per_owner, + }), + ); + } + const created = await knowledgeBasesApi.create({ + name, + default_open: true, + }); + await loadBases(); + await loadDetail(created.id); + if (isMobile) setMobilePane("detail"); + return created.id; + }; const emptyLayoutClassName = `${styles.emptyLayout}${ isMobile ? ` ${styles.emptyLayoutMobile}` : "" }`; @@ -1871,7 +1897,7 @@ export default function KnowledgeBasesPage() { actions={ canConfigureKb ? ( ) : undefined } @@ -1882,7 +1908,7 @@ export default function KnowledgeBasesPage() {
- ) : showEnableGuide ? ( + ) : showDisabled ? (
, } @@ -1927,34 +1936,13 @@ export default function KnowledgeBasesPage() { } />
- ) : showEmptyGuide ? ( + ) : showMountHome ? (
- , - disabled: atBaseLimit, - }} + void refresh()} />
) : ( @@ -3080,7 +3068,7 @@ export default function KnowledgeBasesPage() { } > -
-
-
- - {t("knowledgeBases.settingsOpen")} - - - {t("knowledgeBases.settingsLead")} - -
- -
-
- - {featureEnabledDraft ? ( - -
-
- {t("knowledgeBases.selectModel")} -
- { - setFeatureBackend(event.target.value); - setFeatureModel(undefined); - }} - > - {t("knowledgeBases.localOnnx")} - - {t("knowledgeBases.remoteEmbedding")} - - - {featureBackend === "remote" ? ( -
- { - setOcrProviderId(id); - setOcrModel(undefined); + setFeatureProviderId(id); + setFeatureModel(undefined); }} placeholder={t("knowledgeBases.selectProvider")} - options={ocrRemoteProviders.map((provider) => ({ + options={remoteProviders.map((provider) => ({ value: provider.provider_id, label: provider.provider_name, }))} - notFoundContent={t("knowledgeBases.ocrNoProviders")} + notFoundContent={t("knowledgeBases.noProviders")} /> - { + setOcrProviderId(id); + setOcrModel(undefined); + }} + placeholder={t("knowledgeBases.selectProvider")} + options={ocrRemoteProviders.map((provider) => ({ + value: provider.provider_id, + label: provider.provider_name, + }))} + notFoundContent={t( + "knowledgeBases.ocrNoProviders", + )} + /> +