mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-14 09:46:03 +00:00
* 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>
249 lines
7.6 KiB
TypeScript
249 lines
7.6 KiB
TypeScript
'use client';
|
|
|
|
import CreateCardComponent from '@/app/infra/basic-component/create-card-component/CreateCardComponent';
|
|
import styles from './knowledgeBase.module.css';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useEffect, useState } from 'react';
|
|
import { KnowledgeBaseVO } from '@/app/home/knowledge/components/kb-card/KBCardVO';
|
|
import { ExternalKBCardVO } from '@/app/home/knowledge/components/external-kb-card/ExternalKBCardVO';
|
|
import KBCard from '@/app/home/knowledge/components/kb-card/KBCard';
|
|
import ExternalKBCard from '@/app/home/knowledge/components/external-kb-card/ExternalKBCard';
|
|
import KBDetailDialog from '@/app/home/knowledge/KBDetailDialog';
|
|
import { httpClient } from '@/app/infra/http/HttpClient';
|
|
import {
|
|
KnowledgeBase,
|
|
ExternalKnowledgeBase,
|
|
ApiRespPluginSystemStatus,
|
|
} from '@/app/infra/entities/api';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
|
|
export default function KnowledgePage() {
|
|
const { t } = useTranslation();
|
|
const [activeTab, setActiveTab] = useState('builtin');
|
|
const [knowledgeBaseList, setKnowledgeBaseList] = useState<KnowledgeBaseVO[]>(
|
|
[],
|
|
);
|
|
const [externalKBList, setExternalKBList] = useState<ExternalKBCardVO[]>([]);
|
|
const [selectedKbId, setSelectedKbId] = useState<string>('');
|
|
const [selectedKbType, setSelectedKbType] = useState<'builtin' | 'external'>(
|
|
'builtin',
|
|
);
|
|
const [detailDialogOpen, setDetailDialogOpen] = useState(false);
|
|
const [pluginSystemStatus, setPluginSystemStatus] =
|
|
useState<ApiRespPluginSystemStatus | null>(null);
|
|
|
|
useEffect(() => {
|
|
getKnowledgeBaseList();
|
|
getExternalKBList();
|
|
fetchPluginSystemStatus();
|
|
}, []);
|
|
|
|
async function fetchPluginSystemStatus() {
|
|
try {
|
|
const status = await httpClient.getPluginSystemStatus();
|
|
setPluginSystemStatus(status);
|
|
} catch (error) {
|
|
console.error('Failed to fetch plugin system status:', error);
|
|
}
|
|
}
|
|
|
|
async function getKnowledgeBaseList() {
|
|
const resp = await httpClient.getKnowledgeBases();
|
|
setKnowledgeBaseList(
|
|
resp.bases.map((kb: KnowledgeBase) => {
|
|
const currentTime = new Date();
|
|
const lastUpdatedTimeAgo = Math.floor(
|
|
(currentTime.getTime() -
|
|
new Date(kb.updated_at ?? currentTime.getTime()).getTime()) /
|
|
1000 /
|
|
60 /
|
|
60 /
|
|
24,
|
|
);
|
|
|
|
const lastUpdatedTimeAgoText =
|
|
lastUpdatedTimeAgo > 0
|
|
? ` ${lastUpdatedTimeAgo} ${t('knowledge.daysAgo')}`
|
|
: t('knowledge.today');
|
|
|
|
return new KnowledgeBaseVO({
|
|
id: kb.uuid || '',
|
|
name: kb.name,
|
|
description: kb.description,
|
|
embeddingModelUUID: kb.embedding_model_uuid,
|
|
top_k: kb.top_k ?? 5,
|
|
lastUpdatedTimeAgo: lastUpdatedTimeAgoText,
|
|
});
|
|
}),
|
|
);
|
|
}
|
|
|
|
async function getExternalKBList() {
|
|
try {
|
|
const resp = await httpClient.getExternalKnowledgeBases();
|
|
setExternalKBList(
|
|
resp.bases.map((kb: ExternalKnowledgeBase) => {
|
|
const currentTime = new Date();
|
|
const lastUpdatedTimeAgo = Math.floor(
|
|
(currentTime.getTime() -
|
|
new Date(kb.created_at ?? currentTime.getTime()).getTime()) /
|
|
1000 /
|
|
60 /
|
|
60 /
|
|
24,
|
|
);
|
|
|
|
const lastUpdatedTimeAgoText =
|
|
lastUpdatedTimeAgo > 0
|
|
? ` ${lastUpdatedTimeAgo} ${t('knowledge.daysAgo')}`
|
|
: t('knowledge.today');
|
|
|
|
return new ExternalKBCardVO({
|
|
id: kb.uuid || '',
|
|
name: kb.name,
|
|
description: kb.description,
|
|
retrieverName: `${kb.plugin_author}/${kb.plugin_name}/${kb.retriever_name}`,
|
|
retrieverConfig: kb.retriever_config || {},
|
|
lastUpdatedTimeAgo: lastUpdatedTimeAgoText,
|
|
pluginAuthor: kb.plugin_author,
|
|
pluginName: kb.plugin_name,
|
|
});
|
|
}),
|
|
);
|
|
} catch (error) {
|
|
console.error('Failed to load external knowledge bases:', error);
|
|
}
|
|
}
|
|
|
|
const handleKBCardClick = (kbId: string) => {
|
|
setSelectedKbId(kbId);
|
|
setSelectedKbType('builtin');
|
|
setDetailDialogOpen(true);
|
|
};
|
|
|
|
const handleCreateKBClick = () => {
|
|
setSelectedKbId('');
|
|
setSelectedKbType('builtin');
|
|
setDetailDialogOpen(true);
|
|
};
|
|
|
|
const handleExternalKBCardClick = (kbId: string) => {
|
|
setSelectedKbId(kbId);
|
|
setSelectedKbType('external');
|
|
setDetailDialogOpen(true);
|
|
};
|
|
|
|
const handleCreateExternalKB = () => {
|
|
setSelectedKbId('');
|
|
setSelectedKbType('external');
|
|
setDetailDialogOpen(true);
|
|
};
|
|
|
|
const handleFormCancel = () => {
|
|
setDetailDialogOpen(false);
|
|
};
|
|
|
|
const handleKbDeleted = () => {
|
|
if (selectedKbType === 'builtin') {
|
|
getKnowledgeBaseList();
|
|
} else {
|
|
getExternalKBList();
|
|
}
|
|
setDetailDialogOpen(false);
|
|
};
|
|
|
|
const handleNewKbCreated = (newKbId: string) => {
|
|
if (selectedKbType === 'builtin') {
|
|
getKnowledgeBaseList();
|
|
} else {
|
|
getExternalKBList();
|
|
}
|
|
setSelectedKbId(newKbId);
|
|
setDetailDialogOpen(true);
|
|
};
|
|
|
|
const handleKbUpdated = () => {
|
|
if (selectedKbType === 'builtin') {
|
|
getKnowledgeBaseList();
|
|
} else {
|
|
getExternalKBList();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<KBDetailDialog
|
|
open={detailDialogOpen}
|
|
onOpenChange={setDetailDialogOpen}
|
|
kbId={selectedKbId || undefined}
|
|
kbType={selectedKbType}
|
|
onFormCancel={handleFormCancel}
|
|
onKbDeleted={handleKbDeleted}
|
|
onNewKbCreated={handleNewKbCreated}
|
|
onKbUpdated={handleKbUpdated}
|
|
/>
|
|
|
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
|
<div className="flex flex-row justify-between items-center px-[0.8rem]">
|
|
<TabsList className="shadow-md py-5 bg-[#f0f0f0] dark:bg-[#2a2a2e]">
|
|
<TabsTrigger value="builtin" className="px-6 py-4 cursor-pointer">
|
|
{t('knowledge.builtIn')}
|
|
</TabsTrigger>
|
|
{/* Only show external tab if plugin system is enabled and connected */}
|
|
{pluginSystemStatus?.is_enable &&
|
|
pluginSystemStatus?.is_connected && (
|
|
<TabsTrigger
|
|
value="external"
|
|
className="px-6 py-4 cursor-pointer"
|
|
>
|
|
{t('knowledge.external')}
|
|
</TabsTrigger>
|
|
)}
|
|
</TabsList>
|
|
</div>
|
|
|
|
<TabsContent value="builtin">
|
|
<div className={styles.knowledgeListContainer}>
|
|
<CreateCardComponent
|
|
width={'100%'}
|
|
height={'10rem'}
|
|
plusSize={'90px'}
|
|
onClick={handleCreateKBClick}
|
|
/>
|
|
|
|
{knowledgeBaseList.map((kb) => {
|
|
return (
|
|
<div key={kb.id} onClick={() => handleKBCardClick(kb.id)}>
|
|
<KBCard kbCardVO={kb} />
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="external">
|
|
<div className={styles.knowledgeListContainer}>
|
|
<CreateCardComponent
|
|
width={'100%'}
|
|
height={'10rem'}
|
|
plusSize={'90px'}
|
|
onClick={handleCreateExternalKB}
|
|
/>
|
|
|
|
{externalKBList.map((kb) => {
|
|
return (
|
|
<div
|
|
key={kb.id}
|
|
onClick={() => handleExternalKBCardClick(kb.id)}
|
|
>
|
|
<ExternalKBCard kbCardVO={kb} />
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
);
|
|
}
|