feat: external knowledge bases (#1783)

* Initial plan

* Add backend support for external knowledge bases

Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com>

* Add frontend support for external knowledge bases with tabs UI

Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com>

* Add i18n translations for all languages (Traditional Chinese and Japanese)

Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com>

* Update knowledge base tab list styling to match plugins page

Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com>

* perf: margin-top for kb page

* refactor: switch RetrievalResultEntry to langbot_plugin pkg ones

* feat: knowledge retriever listing and creating

* stash

* refactor: unify sync mechanism for polymorphic components

* feat: use unified retireval result struct in retrieval test page

* chore: remove unused methods

* feat: retriever icon displaying

* feat: localagent retrieval with external kbs

* chore: bump version of langbot-plugin to 0.2.0b1

* fix: i18n

---------

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:
Copilot
2025-11-27 23:19:43 +08:00
committed by GitHub
parent 3c04eeaff9
commit a8481e43f0
33 changed files with 1924 additions and 161 deletions
+78 -27
View File
@@ -25,11 +25,14 @@ import { httpClient } from '@/app/infra/http/HttpClient';
import KBForm from '@/app/home/knowledge/components/kb-form/KBForm';
import KBDoc from '@/app/home/knowledge/components/kb-docs/KBDoc';
import KBRetrieve from '@/app/home/knowledge/components/kb-retrieve/KBRetrieve';
import ExternalKBForm from '@/app/home/knowledge/components/external-kb-form/ExternalKBForm';
import ExternalKBRetrieve from '@/app/home/knowledge/components/kb-retrieve/ExternalKBRetrieve';
interface KBDetailDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
kbId?: string;
kbType: 'builtin' | 'external';
onFormCancel: () => void;
onKbDeleted: () => void;
onNewKbCreated: (kbId: string) => void;
@@ -40,6 +43,7 @@ export default function KBDetailDialog({
open,
onOpenChange,
kbId: propKbId,
kbType,
onFormCancel,
onKbDeleted,
onNewKbCreated,
@@ -55,6 +59,7 @@ export default function KBDetailDialog({
setActiveMenu('metadata');
}, [propKbId, open]);
// Build menu based on KB type
const menu = [
{
key: 'metadata',
@@ -69,19 +74,24 @@ export default function KBDetailDialog({
</svg>
),
},
{
key: 'documents',
label: t('knowledge.documents'),
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M21 8V20.9932C21 21.5501 20.5552 22 20.0066 22H3.9934C3.44495 22 3 21.556 3 21.0082V2.9918C3 2.45531 3.4487 2 4.00221 2H14.9968L21 8ZM19 9H14V4H5V20H19V9ZM8 7H11V9H8V7ZM8 11H16V13H8V11ZM8 15H16V17H8V15Z"></path>
</svg>
),
},
// Only show documents for builtin KB
...(kbType === 'builtin'
? [
{
key: 'documents',
label: t('knowledge.documents'),
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M21 8V20.9932C21 21.5501 20.5552 22 20.0066 22H3.9934C3.44495 22 3 21.556 3 21.0082V2.9918C3 2.45531 3.4487 2 4.00221 2H14.9968L21 8ZM19 9H14V4H5V20H19V9ZM8 7H11V9H8V7ZM8 11H16V13H8V11ZM8 15H16V17H8V15Z"></path>
</svg>
),
},
]
: []),
{
key: 'retrieve',
label: t('knowledge.retrieve'),
@@ -98,7 +108,12 @@ export default function KBDetailDialog({
];
const confirmDelete = () => {
httpClient.deleteKnowledgeBase(kbId ?? '').then(() => {
const deletePromise =
kbType === 'builtin'
? httpClient.deleteKnowledgeBase(kbId ?? '')
: httpClient.deleteExternalKnowledgeBase(kbId ?? '');
deletePromise.then(() => {
onKbDeleted();
});
setShowDeleteConfirm(false);
@@ -111,22 +126,35 @@ export default function KBDetailDialog({
<DialogContent className="overflow-hidden p-0 !max-w-[40vw] max-h-[70vh] flex">
<main className="flex flex-1 flex-col h-[70vh]">
<DialogHeader className="px-6 pt-6 pb-4 shrink-0">
<DialogTitle>{t('knowledge.createKnowledgeBase')}</DialogTitle>
<DialogTitle>
{kbType === 'builtin'
? t('knowledge.createKnowledgeBase')
: t('knowledge.addExternal')}
</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-y-auto px-6 pb-6">
{activeMenu === 'metadata' && (
{kbType === 'builtin' ? (
<KBForm
initKbId={undefined}
onNewKbCreated={onNewKbCreated}
onKbUpdated={onKbUpdated}
/>
) : (
<ExternalKBForm
initKBId={undefined}
onFormSubmit={() => onOpenChange(false)}
onKBDeleted={() => {}}
onNewKBCreated={onNewKbCreated}
/>
)}
{activeMenu === 'documents' && <div>documents</div>}
</div>
{activeMenu === 'metadata' && (
<DialogFooter className="px-6 py-4 border-t shrink-0">
<div className="flex justify-end gap-2">
<Button type="submit" form="kb-form">
<Button
type="submit"
form={kbType === 'builtin' ? 'kb-form' : 'external-kb-form'}
>
{t('common.save')}
</Button>
<Button
@@ -188,15 +216,33 @@ export default function KBDetailDialog({
</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-y-auto px-6 pb-6">
{activeMenu === 'metadata' && (
<KBForm
initKbId={kbId}
onNewKbCreated={onNewKbCreated}
onKbUpdated={onKbUpdated}
/>
{activeMenu === 'metadata' &&
(kbType === 'builtin' ? (
<KBForm
initKbId={kbId}
onNewKbCreated={onNewKbCreated}
onKbUpdated={onKbUpdated}
/>
) : (
<ExternalKBForm
initKBId={kbId}
onFormSubmit={() => onOpenChange(false)}
onKBDeleted={() => {
onKbDeleted();
onOpenChange(false);
}}
onNewKBCreated={onNewKbCreated}
/>
))}
{activeMenu === 'documents' && kbType === 'builtin' && (
<KBDoc kbId={kbId} />
)}
{activeMenu === 'documents' && <KBDoc kbId={kbId} />}
{activeMenu === 'retrieve' && <KBRetrieve kbId={kbId} />}
{activeMenu === 'retrieve' &&
(kbType === 'builtin' ? (
<KBRetrieve kbId={kbId} />
) : (
<ExternalKBRetrieve kbId={kbId} />
))}
</div>
{activeMenu === 'metadata' && (
<DialogFooter className="px-6 py-4 border-t shrink-0">
@@ -208,7 +254,12 @@ export default function KBDetailDialog({
>
{t('common.delete')}
</Button>
<Button type="submit" form="kb-form">
<Button
type="submit"
form={
kbType === 'builtin' ? 'kb-form' : 'external-kb-form'
}
>
{t('common.save')}
</Button>
<Button