mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-22 20:36:08 +00:00
feat: Support multiple knowledge base binding in pipelines (#1766)
* Initial plan * Add multi-knowledge base support to pipelines - Created database migration dbm010 to convert knowledge-base field from string to array - Updated default pipeline config to use knowledge-bases array - Updated pipeline metadata to use knowledge-base-multi-selector type - Modified localagent.py to retrieve from multiple knowledge bases and concatenate results - Added KNOWLEDGE_BASE_MULTI_SELECTOR type to frontend form entities - Implemented multi-selector UI component with dialog for selecting multiple knowledge bases Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> * Add i18n translations for multi-knowledge base selector Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> * Fix prettier formatting errors in DynamicFormItemComponent Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> * Add accessibility attributes to knowledge base selector checkbox Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> * fix: minor fix --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> Co-authored-by: Junyan Qin <rockchinq@gmail.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
|||||||
|
from .. import migration
|
||||||
|
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
from ...entity.persistence import pipeline as persistence_pipeline
|
||||||
|
|
||||||
|
|
||||||
|
@migration.migration_class(10)
|
||||||
|
class DBMigratePipelineMultiKnowledgeBase(migration.DBMigration):
|
||||||
|
"""Pipeline support multiple knowledge base binding"""
|
||||||
|
|
||||||
|
async def upgrade(self):
|
||||||
|
"""Upgrade"""
|
||||||
|
# read all pipelines
|
||||||
|
pipelines = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_pipeline.LegacyPipeline))
|
||||||
|
|
||||||
|
for pipeline in pipelines:
|
||||||
|
serialized_pipeline = self.ap.persistence_mgr.serialize_model(persistence_pipeline.LegacyPipeline, pipeline)
|
||||||
|
|
||||||
|
config = serialized_pipeline['config']
|
||||||
|
|
||||||
|
# Convert knowledge-base from string to array
|
||||||
|
if 'local-agent' in config['ai']:
|
||||||
|
current_kb = config['ai']['local-agent'].get('knowledge-base', '')
|
||||||
|
|
||||||
|
# If it's already a list, skip
|
||||||
|
if isinstance(current_kb, list):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Convert string to list
|
||||||
|
if current_kb and current_kb != '__none__':
|
||||||
|
config['ai']['local-agent']['knowledge-bases'] = [current_kb]
|
||||||
|
else:
|
||||||
|
config['ai']['local-agent']['knowledge-bases'] = []
|
||||||
|
|
||||||
|
# Remove old field
|
||||||
|
if 'knowledge-base' in config['ai']['local-agent']:
|
||||||
|
del config['ai']['local-agent']['knowledge-base']
|
||||||
|
|
||||||
|
await self.ap.persistence_mgr.execute_async(
|
||||||
|
sqlalchemy.update(persistence_pipeline.LegacyPipeline)
|
||||||
|
.where(persistence_pipeline.LegacyPipeline.uuid == serialized_pipeline['uuid'])
|
||||||
|
.values(
|
||||||
|
{
|
||||||
|
'config': config,
|
||||||
|
'for_version': self.ap.ver_mgr.get_current_version(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def downgrade(self):
|
||||||
|
"""Downgrade"""
|
||||||
|
# read all pipelines
|
||||||
|
pipelines = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_pipeline.LegacyPipeline))
|
||||||
|
|
||||||
|
for pipeline in pipelines:
|
||||||
|
serialized_pipeline = self.ap.persistence_mgr.serialize_model(persistence_pipeline.LegacyPipeline, pipeline)
|
||||||
|
|
||||||
|
config = serialized_pipeline['config']
|
||||||
|
|
||||||
|
# Convert knowledge-bases from array back to string
|
||||||
|
if 'local-agent' in config['ai']:
|
||||||
|
current_kbs = config['ai']['local-agent'].get('knowledge-bases', [])
|
||||||
|
|
||||||
|
# If it's already a string, skip
|
||||||
|
if isinstance(current_kbs, str):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Convert list to string (take first one or empty)
|
||||||
|
if current_kbs and len(current_kbs) > 0:
|
||||||
|
config['ai']['local-agent']['knowledge-base'] = current_kbs[0]
|
||||||
|
else:
|
||||||
|
config['ai']['local-agent']['knowledge-base'] = ''
|
||||||
|
|
||||||
|
# Remove new field
|
||||||
|
if 'knowledge-bases' in config['ai']['local-agent']:
|
||||||
|
del config['ai']['local-agent']['knowledge-bases']
|
||||||
|
|
||||||
|
await self.ap.persistence_mgr.execute_async(
|
||||||
|
sqlalchemy.update(persistence_pipeline.LegacyPipeline)
|
||||||
|
.where(persistence_pipeline.LegacyPipeline.uuid == serialized_pipeline['uuid'])
|
||||||
|
.values(
|
||||||
|
{
|
||||||
|
'config': config,
|
||||||
|
'for_version': self.ap.ver_mgr.get_current_version(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -40,10 +40,14 @@ class LocalAgentRunner(runner.RequestRunner):
|
|||||||
"""运行请求"""
|
"""运行请求"""
|
||||||
pending_tool_calls = []
|
pending_tool_calls = []
|
||||||
|
|
||||||
kb_uuid = query.pipeline_config['ai']['local-agent']['knowledge-base']
|
# Get knowledge bases list (new field)
|
||||||
|
kb_uuids = query.pipeline_config['ai']['local-agent'].get('knowledge-bases', [])
|
||||||
if kb_uuid == '__none__':
|
|
||||||
kb_uuid = None
|
# Fallback to old field for backward compatibility
|
||||||
|
if not kb_uuids:
|
||||||
|
old_kb_uuid = query.pipeline_config['ai']['local-agent'].get('knowledge-base', '')
|
||||||
|
if old_kb_uuid and old_kb_uuid != '__none__':
|
||||||
|
kb_uuids = [old_kb_uuid]
|
||||||
|
|
||||||
user_message = copy.deepcopy(query.user_message)
|
user_message = copy.deepcopy(query.user_message)
|
||||||
|
|
||||||
@@ -57,21 +61,28 @@ class LocalAgentRunner(runner.RequestRunner):
|
|||||||
user_message_text += ce.text
|
user_message_text += ce.text
|
||||||
break
|
break
|
||||||
|
|
||||||
if kb_uuid and user_message_text:
|
if kb_uuids and user_message_text:
|
||||||
# only support text for now
|
# only support text for now
|
||||||
kb = await self.ap.rag_mgr.get_knowledge_base_by_uuid(kb_uuid)
|
all_results = []
|
||||||
|
|
||||||
|
# Retrieve from each knowledge base
|
||||||
|
for kb_uuid in kb_uuids:
|
||||||
|
kb = await self.ap.rag_mgr.get_knowledge_base_by_uuid(kb_uuid)
|
||||||
|
|
||||||
if not kb:
|
if not kb:
|
||||||
self.ap.logger.warning(f'Knowledge base {kb_uuid} not found')
|
self.ap.logger.warning(f'Knowledge base {kb_uuid} not found, skipping')
|
||||||
raise ValueError(f'Knowledge base {kb_uuid} not found')
|
continue
|
||||||
|
|
||||||
result = await kb.retrieve(user_message_text, kb.knowledge_base_entity.top_k)
|
result = await kb.retrieve(user_message_text, kb.knowledge_base_entity.top_k)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
all_results.extend(result)
|
||||||
|
|
||||||
final_user_message_text = ''
|
final_user_message_text = ''
|
||||||
|
|
||||||
if result:
|
if all_results:
|
||||||
rag_context = '\n\n'.join(
|
rag_context = '\n\n'.join(
|
||||||
f'[{i + 1}] {entry.metadata.get("text", "")}' for i, entry in enumerate(result)
|
f'[{i + 1}] {entry.metadata.get("text", "")}' for i, entry in enumerate(all_results)
|
||||||
)
|
)
|
||||||
final_user_message_text = rag_combined_prompt_template.format(
|
final_user_message_text = rag_combined_prompt_template.format(
|
||||||
rag_context=rag_context, user_message=user_message_text
|
rag_context=rag_context, user_message=user_message_text
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
semantic_version = 'v4.4.1'
|
semantic_version = 'v4.4.1'
|
||||||
|
|
||||||
required_database_version = 9
|
required_database_version = 10
|
||||||
"""Tag the version of the database schema, used to check if the database needs to be migrated"""
|
"""Tag the version of the database schema, used to check if the database needs to be migrated"""
|
||||||
|
|
||||||
debug_mode = False
|
debug_mode = False
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
"content": "You are a helpful assistant."
|
"content": "You are a helpful assistant."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"knowledge-base": ""
|
"knowledge-bases": []
|
||||||
},
|
},
|
||||||
"dify-service-api": {
|
"dify-service-api": {
|
||||||
"base-url": "https://api.dify.ai/v1",
|
"base-url": "https://api.dify.ai/v1",
|
||||||
|
|||||||
@@ -80,16 +80,16 @@ stages:
|
|||||||
zh_Hans: 除非您了解消息结构,否则请只使用 system 单提示词
|
zh_Hans: 除非您了解消息结构,否则请只使用 system 单提示词
|
||||||
type: prompt-editor
|
type: prompt-editor
|
||||||
required: true
|
required: true
|
||||||
- name: knowledge-base
|
- name: knowledge-bases
|
||||||
label:
|
label:
|
||||||
en_US: Knowledge Base
|
en_US: Knowledge Bases
|
||||||
zh_Hans: 知识库
|
zh_Hans: 知识库
|
||||||
description:
|
description:
|
||||||
en_US: Configure the knowledge base to use for the agent, if not selected, the agent will directly use the LLM to reply
|
en_US: Configure the knowledge bases to use for the agent, if not selected, the agent will directly use the LLM to reply
|
||||||
zh_Hans: 配置用于提升回复质量的知识库,若不选择,则直接使用大模型回复
|
zh_Hans: 配置用于提升回复质量的知识库,若不选择,则直接使用大模型回复
|
||||||
type: knowledge-base-selector
|
type: knowledge-base-multi-selector
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: []
|
||||||
- name: tbox-app-api
|
- name: tbox-app-api
|
||||||
label:
|
label:
|
||||||
en_US: Tbox App API
|
en_US: Tbox App API
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ export default function DynamicFormComponent({
|
|||||||
case 'knowledge-base-selector':
|
case 'knowledge-base-selector':
|
||||||
fieldSchema = z.string();
|
fieldSchema = z.string();
|
||||||
break;
|
break;
|
||||||
|
case 'knowledge-base-multi-selector':
|
||||||
|
fieldSchema = z.array(z.string());
|
||||||
|
break;
|
||||||
case 'bot-selector':
|
case 'bot-selector':
|
||||||
fieldSchema = z.string();
|
fieldSchema = z.string();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -29,6 +29,15 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { extractI18nObject } from '@/i18n/I18nProvider';
|
import { extractI18nObject } from '@/i18n/I18nProvider';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogFooter,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { Plus, X } from 'lucide-react';
|
||||||
|
|
||||||
export default function DynamicFormItemComponent({
|
export default function DynamicFormItemComponent({
|
||||||
config,
|
config,
|
||||||
@@ -44,6 +53,8 @@ export default function DynamicFormItemComponent({
|
|||||||
const [knowledgeBases, setKnowledgeBases] = useState<KnowledgeBase[]>([]);
|
const [knowledgeBases, setKnowledgeBases] = useState<KnowledgeBase[]>([]);
|
||||||
const [bots, setBots] = useState<Bot[]>([]);
|
const [bots, setBots] = useState<Bot[]>([]);
|
||||||
const [uploading, setUploading] = useState<boolean>(false);
|
const [uploading, setUploading] = useState<boolean>(false);
|
||||||
|
const [kbDialogOpen, setKbDialogOpen] = useState(false);
|
||||||
|
const [tempSelectedKBIds, setTempSelectedKBIds] = useState<string[]>([]);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleFileUpload = async (file: File): Promise<IFileConfig | null> => {
|
const handleFileUpload = async (file: File): Promise<IFileConfig | null> => {
|
||||||
@@ -90,7 +101,10 @@ export default function DynamicFormItemComponent({
|
|||||||
}, [config.type]);
|
}, [config.type]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (config.type === DynamicFormItemType.KNOWLEDGE_BASE_SELECTOR) {
|
if (
|
||||||
|
config.type === DynamicFormItemType.KNOWLEDGE_BASE_SELECTOR ||
|
||||||
|
config.type === DynamicFormItemType.KNOWLEDGE_BASE_MULTI_SELECTOR
|
||||||
|
) {
|
||||||
httpClient
|
httpClient
|
||||||
.getKnowledgeBases()
|
.getKnowledgeBases()
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
@@ -336,6 +350,128 @@ export default function DynamicFormItemComponent({
|
|||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
case DynamicFormItemType.KNOWLEDGE_BASE_MULTI_SELECTOR:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{field.value && field.value.length > 0 ? (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{field.value.map((kbId: string) => {
|
||||||
|
const kb = knowledgeBases.find((base) => base.uuid === kbId);
|
||||||
|
if (!kb) return null;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={kbId}
|
||||||
|
className="flex items-center justify-between rounded-lg border p-3 hover:bg-accent"
|
||||||
|
>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="font-medium">{kb.name}</div>
|
||||||
|
{kb.description && (
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{kb.description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => {
|
||||||
|
const newValue = field.value.filter(
|
||||||
|
(id: string) => id !== kbId,
|
||||||
|
);
|
||||||
|
field.onChange(newValue);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-32 items-center justify-center rounded-lg border-2 border-dashed border-border">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t('knowledge.noKnowledgeBaseSelected')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setTempSelectedKBIds(field.value || []);
|
||||||
|
setKbDialogOpen(true);
|
||||||
|
}}
|
||||||
|
variant="outline"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
{t('knowledge.addKnowledgeBase')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Knowledge Base Selection Dialog */}
|
||||||
|
<Dialog open={kbDialogOpen} onOpenChange={setKbDialogOpen}>
|
||||||
|
<DialogContent className="max-w-2xl max-h-[80vh] overflow-hidden flex flex-col">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{t('knowledge.selectKnowledgeBases')}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex-1 overflow-y-auto space-y-2 pr-2">
|
||||||
|
{knowledgeBases.map((base) => {
|
||||||
|
const isSelected = tempSelectedKBIds.includes(
|
||||||
|
base.uuid ?? '',
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={base.uuid}
|
||||||
|
className="flex items-center gap-3 rounded-lg border p-3 hover:bg-accent cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
const kbId = base.uuid ?? '';
|
||||||
|
setTempSelectedKBIds((prev) =>
|
||||||
|
prev.includes(kbId)
|
||||||
|
? prev.filter((id) => id !== kbId)
|
||||||
|
: [...prev, kbId],
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={isSelected}
|
||||||
|
aria-label={`Select ${base.name}`}
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="font-medium">{base.name}</div>
|
||||||
|
{base.description && (
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{base.description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setKbDialogOpen(false)}
|
||||||
|
>
|
||||||
|
{t('common.cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
field.onChange(tempSelectedKBIds);
|
||||||
|
setKbDialogOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('common.confirm')}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
case DynamicFormItemType.BOT_SELECTOR:
|
case DynamicFormItemType.BOT_SELECTOR:
|
||||||
return (
|
return (
|
||||||
<Select value={field.value} onValueChange={field.onChange}>
|
<Select value={field.value} onValueChange={field.onChange}>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export enum DynamicFormItemType {
|
|||||||
PROMPT_EDITOR = 'prompt-editor',
|
PROMPT_EDITOR = 'prompt-editor',
|
||||||
UNKNOWN = 'unknown',
|
UNKNOWN = 'unknown',
|
||||||
KNOWLEDGE_BASE_SELECTOR = 'knowledge-base-selector',
|
KNOWLEDGE_BASE_SELECTOR = 'knowledge-base-selector',
|
||||||
|
KNOWLEDGE_BASE_MULTI_SELECTOR = 'knowledge-base-multi-selector',
|
||||||
PLUGIN_SELECTOR = 'plugin-selector',
|
PLUGIN_SELECTOR = 'plugin-selector',
|
||||||
BOT_SELECTOR = 'bot-selector',
|
BOT_SELECTOR = 'bot-selector',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -488,6 +488,9 @@ const enUS = {
|
|||||||
createKnowledgeBase: 'Create Knowledge Base',
|
createKnowledgeBase: 'Create Knowledge Base',
|
||||||
editKnowledgeBase: 'Edit Knowledge Base',
|
editKnowledgeBase: 'Edit Knowledge Base',
|
||||||
selectKnowledgeBase: 'Select Knowledge Base',
|
selectKnowledgeBase: 'Select Knowledge Base',
|
||||||
|
selectKnowledgeBases: 'Select Knowledge Bases',
|
||||||
|
addKnowledgeBase: 'Add Knowledge Base',
|
||||||
|
noKnowledgeBaseSelected: 'No knowledge bases selected',
|
||||||
empty: 'Empty',
|
empty: 'Empty',
|
||||||
editDocument: 'Documents',
|
editDocument: 'Documents',
|
||||||
description: 'Configuring knowledge bases for improved LLM responses',
|
description: 'Configuring knowledge bases for improved LLM responses',
|
||||||
|
|||||||
@@ -491,6 +491,9 @@ const jaJP = {
|
|||||||
createKnowledgeBase: '知識ベースを作成',
|
createKnowledgeBase: '知識ベースを作成',
|
||||||
editKnowledgeBase: '知識ベースを編集',
|
editKnowledgeBase: '知識ベースを編集',
|
||||||
selectKnowledgeBase: '知識ベースを選択',
|
selectKnowledgeBase: '知識ベースを選択',
|
||||||
|
selectKnowledgeBases: '知識ベースを選択',
|
||||||
|
addKnowledgeBase: '知識ベースを追加',
|
||||||
|
noKnowledgeBaseSelected: '知識ベースが選択されていません',
|
||||||
empty: 'なし',
|
empty: 'なし',
|
||||||
editDocument: 'ドキュメント',
|
editDocument: 'ドキュメント',
|
||||||
description: 'LLMの回答品質向上のための知識ベースを設定します',
|
description: 'LLMの回答品質向上のための知識ベースを設定します',
|
||||||
|
|||||||
@@ -471,6 +471,9 @@ const zhHans = {
|
|||||||
createKnowledgeBase: '创建知识库',
|
createKnowledgeBase: '创建知识库',
|
||||||
editKnowledgeBase: '编辑知识库',
|
editKnowledgeBase: '编辑知识库',
|
||||||
selectKnowledgeBase: '选择知识库',
|
selectKnowledgeBase: '选择知识库',
|
||||||
|
selectKnowledgeBases: '选择知识库',
|
||||||
|
addKnowledgeBase: '添加知识库',
|
||||||
|
noKnowledgeBaseSelected: '未选择知识库',
|
||||||
empty: '无',
|
empty: '无',
|
||||||
editDocument: '文档',
|
editDocument: '文档',
|
||||||
description: '配置可用于提升模型回复质量的知识库',
|
description: '配置可用于提升模型回复质量的知识库',
|
||||||
|
|||||||
@@ -468,6 +468,9 @@ const zhHant = {
|
|||||||
createKnowledgeBase: '建立知識庫',
|
createKnowledgeBase: '建立知識庫',
|
||||||
editKnowledgeBase: '編輯知識庫',
|
editKnowledgeBase: '編輯知識庫',
|
||||||
selectKnowledgeBase: '選擇知識庫',
|
selectKnowledgeBase: '選擇知識庫',
|
||||||
|
selectKnowledgeBases: '選擇知識庫',
|
||||||
|
addKnowledgeBase: '新增知識庫',
|
||||||
|
noKnowledgeBaseSelected: '未選擇知識庫',
|
||||||
empty: '無',
|
empty: '無',
|
||||||
editDocument: '文檔',
|
editDocument: '文檔',
|
||||||
description: '設定可用於提升模型回覆品質的知識庫',
|
description: '設定可用於提升模型回覆品質的知識庫',
|
||||||
|
|||||||
Reference in New Issue
Block a user