From e54c87a660290f7509af349eaaac55b947aacf9d Mon Sep 17 00:00:00 2001 From: mlogclub Date: Wed, 29 Jul 2026 00:12:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=9F=A5=E8=AF=86=E5=BA=93?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E4=BA=A4=E4=BA=92=E5=92=8C=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs | 2 +- .../_components/knowledge-base-list.tsx | 528 ------------------ web/app/dashboard/knowledge/detail/page.tsx | 163 ++++++ web/app/dashboard/knowledge/page.tsx | 439 ++++++--------- web/messages/en-US.json | 13 + web/messages/zh-CN.json | 13 + 6 files changed, 348 insertions(+), 810 deletions(-) delete mode 100644 web/app/dashboard/knowledge/_components/knowledge-base-list.tsx create mode 100644 web/app/dashboard/knowledge/detail/page.tsx diff --git a/docs b/docs index d4a2681..c5048b7 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit d4a26815971c60478389342951f44dbdaaa8bbc8 +Subproject commit c5048b78eb3ea99b4a8cc9088df684f3d563a3d1 diff --git a/web/app/dashboard/knowledge/_components/knowledge-base-list.tsx b/web/app/dashboard/knowledge/_components/knowledge-base-list.tsx deleted file mode 100644 index 075db61..0000000 --- a/web/app/dashboard/knowledge/_components/knowledge-base-list.tsx +++ /dev/null @@ -1,528 +0,0 @@ -"use client"; - -import { - DndContext, - KeyboardSensor, - MouseSensor, - TouchSensor, - closestCenter, - useSensor, - useSensors, - type DragEndEvent, -} from "@dnd-kit/core"; -import { - SortableContext, - arrayMove, - sortableKeyboardCoordinates, - useSortable, - verticalListSortingStrategy, -} from "@dnd-kit/sortable"; -import { CSS } from "@dnd-kit/utilities"; -import { - CircleHelpIcon, - FileTextIcon, - MoreHorizontalIcon, - PencilIcon, - PlusIcon, - RefreshCwIcon, - SearchIcon, - Trash2Icon, -} from "lucide-react"; -import type { CSSProperties } from "react"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { toast } from "sonner"; - -import { Button } from "@/components/ui/button"; -import { - ContextMenu, - ContextMenuContent, - ContextMenuItem, - ContextMenuTrigger, -} from "@/components/ui/context-menu"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Input } from "@/components/ui/input"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { OptionCombobox } from "@/components/option-combobox"; -import { - createKnowledgeBase, - deleteKnowledgeBase, - fetchKnowledgeBases, - rebuildKnowledgeBaseIndex, - updateKnowledgeBase, - updateKnowledgeBaseSort, - type CreateKnowledgeBasePayload, - type KnowledgeBase, -} from "@/lib/api/admin"; -import { useI18n } from "@/i18n/provider"; -import { KnowledgeBaseType, Status } from "@/lib/generated/enums"; -import { cn } from "@/lib/utils"; -import { EditDialog } from "./knowledge-base-edit"; - -type KnowledgeBaseListProps = { - selectedKnowledgeBaseId: number | null; - onSelectKnowledgeBase: (knowledgeBase: KnowledgeBase | null) => void; -}; - -type TFunction = (key: string, values?: Record) => string; - -function getStatusOptions(t: TFunction) { - return [ - { value: "all", label: t("knowledge.allStatus") }, - { value: String(Status.Ok), label: t("knowledge.statusOk") }, - { value: String(Status.Disabled), label: t("knowledge.statusDisabled") }, - { value: String(Status.Deleted), label: t("knowledge.statusDeleted") }, - ]; -} - -type SortableKnowledgeBaseCardProps = { - item: KnowledgeBase; - isSelected: boolean; - isContextMenuOpen: boolean; - disabled: boolean; - onContextMenuOpenChange: (open: boolean) => void; - onSelect: () => void; - onEdit: () => void; - onDelete: () => void; - onRebuildIndex: () => void; - deleteLoadingId: number | null; - rebuildIndexLoadingId: number | null; - t: TFunction; -}; - -function SortableKnowledgeBaseCard({ - item, - isSelected, - isContextMenuOpen, - disabled, - onContextMenuOpenChange, - onSelect, - onEdit, - onDelete, - onRebuildIndex, - deleteLoadingId, - rebuildIndexLoadingId, - t, -}: SortableKnowledgeBaseCardProps) { - const { - attributes, - listeners, - setNodeRef, - transform, - transition, - isDragging, - } = useSortable({ - id: item.id, - disabled, - }); - - const style: CSSProperties = { - transform: CSS.Transform.toString(transform), - transition, - }; - - return ( - - { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - onSelect(); - } - }} - {...attributes} - {...listeners} - > - {item.knowledgeType === KnowledgeBaseType.FAQ ? ( - - ) : ( - - )} - {item.name} - - {item.knowledgeType === "faq" ? item.faqCount : item.documentCount} - - - - } - aria-label={t("knowledge.moreActions", { name: item.name })} - > - - - - { - e.stopPropagation(); - onEdit(); - }} - > - - {t("knowledge.edit")} - - { - e.stopPropagation(); - onRebuildIndex(); - }} - > - - {rebuildIndexLoadingId === item.id ? t("knowledge.rebuilding") : t("knowledge.rebuildIndex")} - - { - e.stopPropagation(); - onDelete(); - }} - className="text-destructive focus:text-destructive" - > - - {deleteLoadingId === item.id ? t("knowledge.deleting") : t("knowledge.delete")} - - - - - - { - e.stopPropagation(); - onEdit(); - }} - > - - {t("knowledge.edit")} - - { - e.stopPropagation(); - onRebuildIndex(); - }} - > - - {rebuildIndexLoadingId === item.id ? t("knowledge.rebuilding") : t("knowledge.rebuildIndex")} - - { - e.stopPropagation(); - onDelete(); - }} - variant="destructive" - > - - {deleteLoadingId === item.id ? t("knowledge.deleting") : t("knowledge.delete")} - - - - ); -} - -export function KnowledgeBaseList({ - selectedKnowledgeBaseId, - onSelectKnowledgeBase, -}: KnowledgeBaseListProps) { - const t = useI18n(); - const [keywordInput, setKeywordInput] = useState(""); - const [statusFilterInput, setStatusFilterInput] = useState("all"); - const [keyword, setKeyword] = useState(""); - const [statusFilter, setStatusFilter] = useState("all"); - const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(false); - const [sorting, setSorting] = useState(false); - const [deleteLoadingId, setDeleteLoadingId] = useState(null); - const [rebuildIndexLoadingId, setRebuildIndexLoadingId] = useState< - number | null - >(null); - const [dialogOpen, setDialogOpen] = useState(false); - const [editingItemId, setEditingItemId] = useState(null); - const [knowledgeBases, setKnowledgeBases] = useState([]); - const [contextMenuKnowledgeBaseId, setContextMenuKnowledgeBaseId] = useState(null); - const statusOptions = useMemo(() => getStatusOptions(t), [t]); - - const sensors = useSensors( - useSensor(MouseSensor, { - activationConstraint: { distance: 8 }, - }), - useSensor(TouchSensor, { - activationConstraint: { delay: 150, tolerance: 8 }, - }), - useSensor(KeyboardSensor, { - coordinateGetter: sortableKeyboardCoordinates, - }), - ); - - const loadData = useCallback(async () => { - setLoading(true); - try { - const data = await fetchKnowledgeBases({ - name: keyword.trim() || undefined, - status: statusFilter === "all" ? undefined : statusFilter, - limit: 1000, - }); - setKnowledgeBases(data.results); - } catch (error) { - toast.error(error instanceof Error ? error.message : t("knowledge.loadBasesFailed")); - } finally { - setLoading(false); - } - }, [keyword, statusFilter, t]); - - useEffect(() => { - void loadData(); - }, [loadData]); - - useEffect(() => { - if ( - selectedKnowledgeBaseId === null && - knowledgeBases.length > 0 && - !loading - ) { - onSelectKnowledgeBase(knowledgeBases[0]); - } - }, [selectedKnowledgeBaseId, knowledgeBases, loading, onSelectKnowledgeBase]); - - function handleStatusFilterChange(value: string | null) { - setStatusFilterInput(value ?? "all"); - } - - function applyFilters() { - setKeyword(keywordInput); - setStatusFilter(statusFilterInput); - } - - function handleFilterKeyDown(event: React.KeyboardEvent) { - if (event.key !== "Enter") { - return; - } - event.preventDefault(); - applyFilters(); - } - - function openCreateDialog() { - setEditingItemId(null); - setDialogOpen(true); - } - - function openEditDialog(item: KnowledgeBase) { - setEditingItemId(item.id); - setDialogOpen(true); - } - - function handleDialogOpenChange(open: boolean) { - if (saving) { - return; - } - if (!open) { - setEditingItemId(null); - } - setDialogOpen(open); - } - - async function handleSubmit(payload: CreateKnowledgeBasePayload) { - if (saving) { - return; - } - - setSaving(true); - try { - if (editingItemId) { - await updateKnowledgeBase({ - id: editingItemId, - ...payload, - }); - const editingItem = knowledgeBases.find( - (item) => item.id === editingItemId, - ); - toast.success(t("knowledge.baseUpdated", { name: editingItem?.name || payload.name })); - } else { - await createKnowledgeBase(payload); - toast.success(t("knowledge.baseCreated", { name: payload.name })); - } - setDialogOpen(false); - setEditingItemId(null); - await loadData(); - } catch (error) { - toast.error(error instanceof Error ? error.message : t("knowledge.baseSaveFailed")); - } finally { - setSaving(false); - } - } - - async function handleDelete(item: KnowledgeBase) { - setDeleteLoadingId(item.id); - try { - await deleteKnowledgeBase(item.id); - toast.success(t("knowledge.baseDeleted", { name: item.name })); - if (selectedKnowledgeBaseId === item.id) { - onSelectKnowledgeBase(null); - } - await loadData(); - } catch (error) { - toast.error(error instanceof Error ? error.message : t("knowledge.baseDeleteFailed")); - } finally { - setDeleteLoadingId(null); - } - } - - async function handleRebuildIndex(item: KnowledgeBase) { - setRebuildIndexLoadingId(item.id); - try { - await rebuildKnowledgeBaseIndex(item.id); - toast.success(t("knowledge.rebuildStarted", { name: item.name })); - } catch (error) { - toast.error( - error instanceof Error ? error.message : t("knowledge.rebuildFailed"), - ); - } finally { - setRebuildIndexLoadingId(null); - } - } - - async function handleDragEnd(event: DragEndEvent) { - const { active, over } = event; - if (!over || active.id === over.id || sorting) { - return; - } - - const previousResults = knowledgeBases; - const oldIndex = previousResults.findIndex((item) => item.id === active.id); - const newIndex = previousResults.findIndex((item) => item.id === over.id); - if (oldIndex < 0 || newIndex < 0) { - return; - } - - const nextResults = arrayMove(previousResults, oldIndex, newIndex); - setKnowledgeBases(nextResults); - setSorting(true); - - try { - await updateKnowledgeBaseSort(nextResults.map((item) => item.id)); - toast.success(t("knowledge.sortUpdated")); - await loadData(); - } catch (error) { - setKnowledgeBases(previousResults); - toast.error( - error instanceof Error ? error.message : t("knowledge.sortUpdateFailed"), - ); - } finally { - setSorting(false); - } - } - - return ( - <> -
-
-
-

{t("knowledge.title")}

-
- - -
-
-
-
- - setKeywordInput(event.target.value)} - onKeyDown={handleFilterKeyDown} - placeholder={t("knowledge.searchBase")} - className="h-8 pl-8 text-xs" - /> -
-
- -
-
-
- -
- void handleDragEnd(event)} - > - item.id)} - strategy={verticalListSortingStrategy} - > - {knowledgeBases.map((item) => ( - - setContextMenuKnowledgeBaseId(open ? item.id : null) - } - onSelect={() => onSelectKnowledgeBase(item)} - onEdit={() => openEditDialog(item)} - onDelete={() => void handleDelete(item)} - onRebuildIndex={() => void handleRebuildIndex(item)} - deleteLoadingId={deleteLoadingId} - rebuildIndexLoadingId={rebuildIndexLoadingId} - t={t} - /> - ))} - - - {!loading && knowledgeBases.length === 0 ? ( -
- {t("knowledge.emptyBases")} -
- ) : null} -
-
-
- - - ); -} diff --git a/web/app/dashboard/knowledge/detail/page.tsx b/web/app/dashboard/knowledge/detail/page.tsx new file mode 100644 index 0000000..04b4da6 --- /dev/null +++ b/web/app/dashboard/knowledge/detail/page.tsx @@ -0,0 +1,163 @@ +"use client" + +import { Button } from "@/components/ui/button" +import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { useI18n } from "@/i18n/provider" +import { exportKnowledgeFAQs, fetchKnowledgeBase, type KnowledgeBase } from "@/lib/api/admin" +import { + ArrowLeftIcon, + BugIcon, + DownloadIcon, + FileTextIcon, + LayoutGridIcon, + LayoutListIcon, + PlusIcon, + RefreshCwIcon, + UploadIcon, +} from "lucide-react" +import { useRouter, useSearchParams } from "next/navigation" +import { useCallback, useEffect, useState } from "react" +import { toast } from "sonner" +import { DebugPanel } from "../_components/debug-panel" +import { DocumentList, type DocumentListActionState } from "../_components/document-list" +import { FAQList, type FAQListActionState } from "../_components/faq-list" +import { RetrieveLogList } from "../_components/retrieve-log-list" + +export default function KnowledgeBaseDetailPage() { + const t = useI18n() + const router = useRouter() + const searchParams = useSearchParams() + const knowledgeBaseId = Number(searchParams.get("id")) + const [knowledgeBase, setKnowledgeBase] = useState(null) + const [loading, setLoading] = useState(true) + const [activeTab, setActiveTab] = useState("documents") + const [debugPanelOpen, setDebugPanelOpen] = useState(false) + const [documentActionState, setDocumentActionState] = useState(null) + const [faqActionState, setFAQActionState] = useState(null) + const [exportingFAQ, setExportingFAQ] = useState(false) + + const loadKnowledgeBase = useCallback(async () => { + if (!Number.isInteger(knowledgeBaseId) || knowledgeBaseId <= 0) { + setKnowledgeBase(null) + setLoading(false) + return + } + setLoading(true) + try { + setKnowledgeBase(await fetchKnowledgeBase(knowledgeBaseId)) + } catch (error) { + setKnowledgeBase(null) + toast.error(error instanceof Error ? error.message : t("knowledge.loadBaseFailed")) + } finally { + setLoading(false) + } + }, [knowledgeBaseId, t]) + + useEffect(() => { + void loadKnowledgeBase() + }, [loadKnowledgeBase]) + + async function handleExportFAQ() { + if (!knowledgeBase || exportingFAQ) { + return + } + setExportingFAQ(true) + try { + await exportKnowledgeFAQs(knowledgeBase.id) + toast.success(t("knowledge.exportFAQSuccess")) + } catch (error) { + toast.error(error instanceof Error ? error.message : t("knowledge.exportFAQFailed")) + } finally { + setExportingFAQ(false) + } + } + + if (!loading && !knowledgeBase) { + return ( +
+ +

{t("knowledge.baseNotFound")}

+ +
+ ) + } + + const isFAQKnowledgeBase = knowledgeBase?.knowledgeType === "faq" + + return ( +
+ +
+ +
+

{knowledgeBase?.name ?? t("knowledge.title")}

+ {knowledgeBase?.description ? ( + <> +
+ {activeTab === "documents" && !isFAQKnowledgeBase && documentActionState ? ( +
+ + + + + +
+ ) : null} + {activeTab === "documents" && isFAQKnowledgeBase && faqActionState ? ( +
+ + + + + +
+ ) : null} + + + {isFAQKnowledgeBase ? t("knowledge.faq") : t("knowledge.document")} + + + {t("knowledge.retrieveLogs")} + + +
+ + {isFAQKnowledgeBase ? : } + + + + +
+ + + {t("knowledge.ragDebug")} + + + +
+ ) +} diff --git a/web/app/dashboard/knowledge/page.tsx b/web/app/dashboard/knowledge/page.tsx index 3aa4a86..ce15e1e 100644 --- a/web/app/dashboard/knowledge/page.tsx +++ b/web/app/dashboard/knowledge/page.tsx @@ -1,293 +1,170 @@ "use client" -import { Button } from "@/components/ui/button" -import { - Sheet, - SheetContent, - SheetHeader, - SheetTitle, -} from "@/components/ui/sheet" -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" -import type { KnowledgeBase } from "@/lib/api/admin" -import { exportKnowledgeFAQs } from "@/lib/api/admin" -import { useI18n } from "@/i18n/provider" -import { - Bug, - DownloadIcon, - LayoutGridIcon, - LayoutListIcon, - PanelLeftCloseIcon, - PanelLeftOpenIcon, - PlusIcon, - RefreshCwIcon, - UploadIcon, -} from "lucide-react" -import { useEffect, useState, type PointerEvent } from "react" +import { CircleHelpIcon, FileTextIcon, RefreshCwIcon } from "lucide-react" +import Link from "next/link" import { toast } from "sonner" -import { DebugPanel } from "./_components/debug-panel" -import { DocumentList, type DocumentListActionState } from "./_components/document-list" -import { FAQList, type FAQListActionState } from "./_components/faq-list" -import { KnowledgeBaseList } from "./_components/knowledge-base-list" -import { RetrieveLogList } from "./_components/retrieve-log-list" -const KNOWLEDGE_BASE_LIST_WIDTH_STORAGE_KEY = "knowledge-base-list-width" -const KNOWLEDGE_BASE_LIST_MIN_WIDTH = 240 -const KNOWLEDGE_BASE_LIST_MAX_WIDTH = 520 -const KNOWLEDGE_BASE_LIST_DEFAULT_WIDTH = 320 +import { DashboardCrudPage } from "@/components/dashboard/crud" +import { Badge } from "@/components/ui/badge" +import { EditDialog } from "./_components/knowledge-base-edit" +import { + createKnowledgeBase, + deleteKnowledgeBase, + fetchKnowledgeBases, + rebuildKnowledgeBaseIndex, + updateKnowledgeBase, + updateKnowledgeBaseSort, + type CreateKnowledgeBasePayload, + type KnowledgeBase, +} from "@/lib/api/admin" +import { KnowledgeBaseType, Status } from "@/lib/generated/enums" +import { useI18n } from "@/i18n/provider" +import { formatDateTime } from "@/lib/utils" -export default function DashboardKnowledgeDocumentsPage() { +export default function DashboardKnowledgePage() { const t = useI18n() - const [selectedKnowledgeBase, setSelectedKnowledgeBase] = useState(null) - const [sidebarCollapsed, setSidebarCollapsed] = useState(false) - const [sidebarResizing, setSidebarResizing] = useState(false) - const [sidebarWidth, setSidebarWidth] = useState(() => { - if (typeof window === "undefined") { - return KNOWLEDGE_BASE_LIST_DEFAULT_WIDTH - } - const saved = Number(localStorage.getItem(KNOWLEDGE_BASE_LIST_WIDTH_STORAGE_KEY)) - if (!Number.isFinite(saved)) { - return KNOWLEDGE_BASE_LIST_DEFAULT_WIDTH - } - return Math.min( - KNOWLEDGE_BASE_LIST_MAX_WIDTH, - Math.max(KNOWLEDGE_BASE_LIST_MIN_WIDTH, saved), - ) - }) - const [debugPanelOpen, setDebugPanelOpen] = useState(false) - const [activeTab, setActiveTab] = useState("documents") - const [documentActionState, setDocumentActionState] = useState(null) - const [faqActionState, setFAQActionState] = useState(null) - const [exportingFAQ, setExportingFAQ] = useState(false) - const isFAQKnowledgeBase = selectedKnowledgeBase?.knowledgeType === "faq" - useEffect(() => { - localStorage.setItem(KNOWLEDGE_BASE_LIST_WIDTH_STORAGE_KEY, String(sidebarWidth)) - }, [sidebarWidth]) - - function handleSidebarResizePointerDown(event: PointerEvent) { - event.preventDefault() - const startX = event.clientX - const startWidth = sidebarWidth - - function handlePointerMove(moveEvent: globalThis.PointerEvent) { - const nextWidth = startWidth + moveEvent.clientX - startX - setSidebarWidth( - Math.min( - KNOWLEDGE_BASE_LIST_MAX_WIDTH, - Math.max(KNOWLEDGE_BASE_LIST_MIN_WIDTH, nextWidth), - ), - ) - } - - function handlePointerEnd() { - window.removeEventListener("pointermove", handlePointerMove) - window.removeEventListener("pointerup", handlePointerEnd) - window.removeEventListener("pointercancel", handlePointerEnd) - document.body.style.cursor = "" - document.body.style.userSelect = "" - setSidebarResizing(false) - } - - setSidebarResizing(true) - document.body.style.cursor = "col-resize" - document.body.style.userSelect = "none" - window.addEventListener("pointermove", handlePointerMove) - window.addEventListener("pointerup", handlePointerEnd) - window.addEventListener("pointercancel", handlePointerEnd) - } - - async function handleExportFAQ() { - if (!selectedKnowledgeBase || exportingFAQ) { - return - } - setExportingFAQ(true) - try { - await exportKnowledgeFAQs(selectedKnowledgeBase.id) - toast.success(t("knowledge.exportFAQSuccess")) - } catch (error) { - toast.error(error instanceof Error ? error.message : t("knowledge.exportFAQFailed")) - } finally { - setExportingFAQ(false) - } - } + const statusOptions = [ + { value: "all", label: t("knowledge.allStatus") }, + { value: String(Status.Ok), label: t("knowledge.statusOk") }, + { value: String(Status.Disabled), label: t("knowledge.statusDisabled") }, + { value: String(Status.Deleted), label: t("knowledge.statusDeleted") }, + ] return ( -
-
- + filters={[ + { + name: "name", + label: t("knowledge.searchBase"), + placeholder: t("knowledge.searchBase"), + defaultValue: "", + trim: true, + className: "w-full sm:w-80", + }, + { + name: "status", + label: t("knowledge.allStatus"), + type: "select", + defaultValue: "all", + allValue: "all", + options: statusOptions, + className: "w-full sm:w-36", + }, + ]} + columns={[ + { + key: "knowledgeBase", + label: t("knowledge.columnBase"), + className: "w-[36rem] max-w-[36rem]", + render: (item) => { + const isFAQ = item.knowledgeType === KnowledgeBaseType.FAQ + const Icon = isFAQ ? CircleHelpIcon : FileTextIcon + return ( +
+
+ +
+
+ + {item.name} + +

+ {item.description || t("knowledge.noDescription")} +

+
+
+ ) + }, + }, + { + key: "type", + label: t("knowledge.columnType"), + render: (item) => {item.knowledgeTypeName}, + }, + { + key: "contentCount", + label: t("knowledge.columnContent"), + render: (item) => ( + + {item.knowledgeType === KnowledgeBaseType.FAQ + ? `${item.faqCount} ${t("knowledge.faq")}` + : `${item.documentCount} ${t("knowledge.document")}`} + + ), + }, + { + key: "status", + label: t("knowledge.columnStatus"), + render: (item) => ( + + {item.statusName} + + ), + }, + { + key: "updatedAt", + label: t("knowledge.columnUpdatedAt"), + className: "whitespace-nowrap", + render: (item) => ( + {formatDateTime(item.updatedAt)} + ), + }, + ]} + fetchList={fetchKnowledgeBases} + getItemId={(item) => item.id} + createItem={createKnowledgeBase} + updateItem={(item, payload) => updateKnowledgeBase({ id: item.id, ...payload })} + deleteItem={(item) => deleteKnowledgeBase(item.id)} + renderEditDialog={({ open, saving, itemId, onOpenChange, onSubmit }) => ( + - {!sidebarCollapsed ? ( -
- ) : null} -
-
- -
-
- -
-
- - {isFAQKnowledgeBase ? t("knowledge.faq") : t("knowledge.document")} - {t("knowledge.retrieveLogs")} - - {activeTab === "documents" && !isFAQKnowledgeBase && documentActionState ? ( -
- - - - - -
- ) : null} - {activeTab === "documents" && isFAQKnowledgeBase && faqActionState ? ( -
- - - - - -
- ) : null} -
-
- - {isFAQKnowledgeBase ? ( - - ) : ( - - )} - - - - -
-
- - - - {t("knowledge.ragDebug")} - - - - -
+ )} + rowActions={[ + { + key: "rebuild-index", + label: (item) => t("knowledge.rebuildIndex"), + icon: , + run: async ({ item }) => { + await rebuildKnowledgeBaseIndex(item.id) + toast.success(t("knowledge.rebuildStarted", { name: item.name })) + }, + }, + ]} + sort={{ + enabled: true, + onReorder: (items) => updateKnowledgeBaseSort(items.map((item) => item.id)), + successMessage: t("knowledge.sortUpdated"), + errorMessage: t("knowledge.sortUpdateFailed"), + handleLabel: t("knowledge.sortHandle"), + }} + pageSize={1000} + labels={{ + refresh: t("knowledge.refreshBases"), + create: t("knowledge.createBaseTitle"), + query: t("knowledge.query"), + loading: t("knowledge.loading"), + empty: t("knowledge.emptyBases"), + actions: t("knowledge.columnActions"), + edit: t("knowledge.edit"), + delete: t("knowledge.delete"), + processing: t("knowledge.processing"), + moreActions: (item) => t("knowledge.moreActions", { name: item.name }), + loadFailed: t("knowledge.loadBasesFailed"), + saveFailed: t("knowledge.baseSaveFailed"), + deleteFailed: t("knowledge.baseDeleteFailed"), + created: (payload) => t("knowledge.baseCreated", { name: payload.name }), + updated: (item, payload) => t("knowledge.baseUpdated", { name: payload.name || item.name }), + deleted: (item) => t("knowledge.baseDeleted", { name: item.name }), + }} + /> ) } diff --git a/web/messages/en-US.json b/web/messages/en-US.json index b7917a4..b1f780e 100644 --- a/web/messages/en-US.json +++ b/web/messages/en-US.json @@ -1702,6 +1702,19 @@ "newFAQ": "New FAQ", "ragDebug": "RAG Debug", "title": "Knowledge Base", + "columnBase": "Knowledge base", + "columnType": "Type", + "columnContent": "Items", + "columnStatus": "Status", + "columnUpdatedAt": "Last updated", + "columnActions": "Actions", + "sortHandle": "Drag to reorder", + "processing": "Processing...", + "noDescription": "No description", + "refreshBases": "Refresh knowledge bases", + "backToList": "Back to knowledge bases", + "baseNotFound": "This knowledge base was not found or has been deleted.", + "loadBaseFailed": "Could not load the knowledge base.", "allStatus": "All statuses", "status": "Status", "statusOk": "Enabled", diff --git a/web/messages/zh-CN.json b/web/messages/zh-CN.json index 2a7fa43..64aec18 100644 --- a/web/messages/zh-CN.json +++ b/web/messages/zh-CN.json @@ -1703,6 +1703,19 @@ "newFAQ": "新增FAQ", "ragDebug": "RAG 调试", "title": "知识库", + "columnBase": "知识库", + "columnType": "类型", + "columnContent": "内容数量", + "columnStatus": "状态", + "columnUpdatedAt": "最近更新", + "columnActions": "操作", + "sortHandle": "拖拽排序", + "processing": "处理中...", + "noDescription": "暂无描述", + "refreshBases": "刷新知识库", + "backToList": "返回知识库列表", + "baseNotFound": "未找到该知识库,或它已被删除。", + "loadBaseFailed": "加载知识库失败", "allStatus": "全部状态", "statusOk": "启用", "statusDisabled": "禁用",