fix:优化图片识别,优化webui配置和排版,优化聊天流监控,新增mcp显示,新增prompt修改面板,优化插件状态显示,优化长期记忆控制台,
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useNavigate } from '@tanstack/react-router'
|
||||
|
||||
import {
|
||||
Database,
|
||||
@@ -7,7 +6,6 @@ import {
|
||||
Loader2,
|
||||
RefreshCw,
|
||||
RotateCcw,
|
||||
Save,
|
||||
SlidersHorizontal,
|
||||
Sparkles,
|
||||
Upload,
|
||||
@@ -19,7 +17,6 @@ import {
|
||||
|
||||
import { CodeEditor } from '@/components/CodeEditor'
|
||||
import { MemoryDeleteDialog } from '@/components/memory/MemoryDeleteDialog'
|
||||
import { MemoryConfigEditor } from '@/components/memory/MemoryConfigEditor'
|
||||
import { MemoryMiniTabs } from '@/components/memory/MemoryMiniTabs'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -56,9 +53,6 @@ import {
|
||||
createMemoryPasteImport,
|
||||
createMemoryTuningTask,
|
||||
createMemoryUploadImport,
|
||||
getMemoryConfig,
|
||||
getMemoryConfigRaw,
|
||||
getMemoryConfigSchema,
|
||||
getMemoryDeleteOperation,
|
||||
getMemoryDeleteOperations,
|
||||
getMemoryImportTasks,
|
||||
@@ -78,9 +72,6 @@ import {
|
||||
resolveMemoryImportPath,
|
||||
retryMemoryImportTask,
|
||||
restoreMemoryDelete,
|
||||
updateMemoryConfig,
|
||||
updateMemoryConfigRaw,
|
||||
type MemoryConfigSchemaPayload,
|
||||
type MemoryDeleteExecutePayload,
|
||||
type MemoryDeleteOperationPayload,
|
||||
type MemoryFeedbackActionLogPayload,
|
||||
@@ -113,25 +104,18 @@ import { DeleteTab } from './knowledge-base/tabs/DeleteTab'
|
||||
import { FeedbackTab } from './knowledge-base/tabs/FeedbackTab'
|
||||
import { ImportTab } from './knowledge-base/tabs/ImportTab'
|
||||
import { TuningTab } from './knowledge-base/tabs/TuningTab'
|
||||
import { KnowledgeGraphPage } from './knowledge-graph'
|
||||
|
||||
export function KnowledgeBasePage() {
|
||||
const navigate = useNavigate()
|
||||
const { toast } = useToast()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [refreshingCheck, setRefreshingCheck] = useState(false)
|
||||
const [creatingImport, setCreatingImport] = useState(false)
|
||||
const [creatingTuning, setCreatingTuning] = useState(false)
|
||||
const [rawMode, setRawMode] = useState(false)
|
||||
const [activeTab, setActiveTab] = useState<
|
||||
'overview' | 'config' | 'import' | 'tuning' | 'delete' | 'feedback'
|
||||
'overview' | 'graph' | 'import' | 'tuning' | 'delete' | 'feedback'
|
||||
>('overview')
|
||||
|
||||
const [schemaPayload, setSchemaPayload] = useState<MemoryConfigSchemaPayload | null>(null)
|
||||
const [visualConfig, setVisualConfig] = useState<Record<string, unknown>>({})
|
||||
const [rawConfig, setRawConfig] = useState('')
|
||||
const [rawConfigExists, setRawConfigExists] = useState(true)
|
||||
const [rawConfigUsingDefault, setRawConfigUsingDefault] = useState(false)
|
||||
const [runtimeConfig, setRuntimeConfig] = useState<MemoryRuntimeConfigPayload | null>(null)
|
||||
const [selfCheckReport, setSelfCheckReport] = useState<Record<string, unknown> | null>(null)
|
||||
const [importSettings, setImportSettings] = useState<MemoryImportSettings>({})
|
||||
@@ -259,9 +243,6 @@ export function KnowledgeBasePage() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const [
|
||||
schema,
|
||||
configPayload,
|
||||
rawPayload,
|
||||
runtimePayload,
|
||||
importSettingsPayload,
|
||||
pathAliasPayload,
|
||||
@@ -272,9 +253,6 @@ export function KnowledgeBasePage() {
|
||||
deleteOperationPayload,
|
||||
feedbackCorrectionPayload,
|
||||
] = await Promise.all([
|
||||
getMemoryConfigSchema(),
|
||||
getMemoryConfig(),
|
||||
getMemoryConfigRaw(),
|
||||
getMemoryRuntimeConfig(),
|
||||
getMemoryImportSettings(),
|
||||
getMemoryImportPathAliases(),
|
||||
@@ -286,11 +264,6 @@ export function KnowledgeBasePage() {
|
||||
getMemoryFeedbackCorrections({ limit: FEEDBACK_CORRECTION_FETCH_LIMIT }),
|
||||
])
|
||||
|
||||
setSchemaPayload(schema)
|
||||
setVisualConfig(configPayload.config ?? {})
|
||||
setRawConfig(rawPayload.config ?? '')
|
||||
setRawConfigExists(rawPayload.exists ?? true)
|
||||
setRawConfigUsingDefault(rawPayload.using_default ?? false)
|
||||
setRuntimeConfig(runtimePayload)
|
||||
setImportSettings(importSettingsPayload.settings ?? {})
|
||||
setImportPathAliases(pathAliasPayload.path_aliases ?? {})
|
||||
@@ -331,9 +304,6 @@ export function KnowledgeBasePage() {
|
||||
void loadPage()
|
||||
}, [loadPage])
|
||||
|
||||
const configPath = schemaPayload?.path ?? 'config/a_memorix.toml'
|
||||
const schema = schemaPayload?.schema
|
||||
|
||||
const runtimeBadges = useMemo(() => {
|
||||
if (!runtimeConfig) {
|
||||
return []
|
||||
@@ -1613,58 +1583,6 @@ export function KnowledgeBasePage() {
|
||||
}
|
||||
}, [selectedOperationItemPage, selectedOperationItemPageCount])
|
||||
|
||||
const saveVisualConfig = useCallback(async () => {
|
||||
try {
|
||||
setSaving(true)
|
||||
await updateMemoryConfig(visualConfig)
|
||||
const [nextConfig, nextRaw, nextRuntime] = await Promise.all([
|
||||
getMemoryConfig(),
|
||||
getMemoryConfigRaw(),
|
||||
getMemoryRuntimeConfig(),
|
||||
])
|
||||
setVisualConfig(nextConfig.config)
|
||||
setRawConfig(nextRaw.config)
|
||||
setRawConfigExists(nextRaw.exists ?? true)
|
||||
setRawConfigUsingDefault(nextRaw.using_default ?? false)
|
||||
setRuntimeConfig(nextRuntime)
|
||||
toast({ title: '配置已保存', description: '长期记忆配置已经应用到运行时' })
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: '保存配置失败',
|
||||
description: error instanceof Error ? error.message : '未知错误',
|
||||
variant: 'destructive',
|
||||
})
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}, [toast, visualConfig])
|
||||
|
||||
const saveRaw = useCallback(async () => {
|
||||
try {
|
||||
setSaving(true)
|
||||
await updateMemoryConfigRaw(rawConfig)
|
||||
const [nextConfig, nextRaw, nextRuntime] = await Promise.all([
|
||||
getMemoryConfig(),
|
||||
getMemoryConfigRaw(),
|
||||
getMemoryRuntimeConfig(),
|
||||
])
|
||||
setVisualConfig(nextConfig.config)
|
||||
setRawConfig(nextRaw.config ?? '')
|
||||
setRawConfigExists(nextRaw.exists ?? true)
|
||||
setRawConfigUsingDefault(nextRaw.using_default ?? false)
|
||||
setRuntimeConfig(nextRuntime)
|
||||
toast({ title: '原始 TOML 已保存', description: '长期记忆配置已经重新加载' })
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: '保存原始配置失败',
|
||||
description: error instanceof Error ? error.message : '未知错误',
|
||||
variant: 'destructive',
|
||||
})
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}, [rawConfig, toast])
|
||||
|
||||
const refreshSelfCheck = useCallback(async () => {
|
||||
try {
|
||||
setRefreshingCheck(true)
|
||||
@@ -1794,12 +1712,12 @@ export function KnowledgeBasePage() {
|
||||
</div>
|
||||
<h1 className="mt-1 text-2xl font-bold leading-tight">长期记忆控制台</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
在这里完成配置、自检、导入资料和检索调优——一站式管理记忆库
|
||||
在这里完成自检、导入资料和检索调优——一站式管理记忆库
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => navigate({ to: '/resource/knowledge-graph' })}>
|
||||
<div className="hidden">
|
||||
<Button variant="outline" size="sm" onClick={() => setActiveTab('graph')}>
|
||||
<Database className="mr-2 h-4 w-4" />
|
||||
打开图谱
|
||||
</Button>
|
||||
@@ -1813,14 +1731,29 @@ export function KnowledgeBasePage() {
|
||||
|
||||
<div className="flex-1 overflow-auto">
|
||||
<div className="mx-auto flex w-full max-w-[1800px] flex-col gap-6 px-6 py-6">
|
||||
<div className="hidden">
|
||||
<Button variant="outline" size="sm" onClick={() => void loadPage()}>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
刷新数据
|
||||
</Button>
|
||||
</div>
|
||||
{/* 运行时状态条 —— 紧凑、常驻、一眼看完 */}
|
||||
{runtimeBadges.length > 0 ? (
|
||||
<div className="rounded-2xl border border-border/60 bg-card/60 p-4 shadow-sm backdrop-blur">
|
||||
<div className="mb-3 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2 text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<div className="mr-auto flex items-center gap-2 text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||
<Gauge className="h-3.5 w-3.5" />
|
||||
运行时状态
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-7 px-2 text-xs"
|
||||
onClick={() => void loadPage()}
|
||||
>
|
||||
<RefreshCw className="mr-1.5 h-3 w-3" />
|
||||
刷新数据
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@@ -1904,7 +1837,7 @@ export function KnowledgeBasePage() {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate({ to: '/resource/knowledge-graph' })}
|
||||
onClick={() => setActiveTab('graph')}
|
||||
className="group flex items-start gap-3 rounded-xl border border-border/70 bg-background/80 p-3.5 text-left transition hover:border-primary/50 hover:bg-background hover:shadow-md"
|
||||
>
|
||||
<div className="flex-none rounded-lg bg-violet-500/10 p-2 text-violet-500 transition-transform group-hover:scale-105">
|
||||
@@ -1929,8 +1862,8 @@ export function KnowledgeBasePage() {
|
||||
<div className="sticky top-0 z-10 -mx-6 border-b border-border/40 bg-background/85 px-6 pb-2 pt-1 backdrop-blur supports-[backdrop-filter]:bg-background/70">
|
||||
<MemoryMiniTabs
|
||||
items={[
|
||||
{ value: 'overview', label: '概览', description: '运行状态与配置摘要' },
|
||||
{ value: 'config', label: '配置', description: '可视化或 TOML 编辑配置' },
|
||||
{ value: 'overview', label: '概览', description: '运行状态与运行时摘要' },
|
||||
{ value: 'graph', label: '图谱', description: '实体关系图与证据视图' },
|
||||
{ value: 'import', label: '导入', description: '创建并管理导入任务' },
|
||||
{ value: 'tuning', label: '调优', description: '检索策略调优' },
|
||||
{ value: 'delete', label: '删除', description: '批量删除与历史回溯' },
|
||||
@@ -1940,6 +1873,10 @@ export function KnowledgeBasePage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TabsContent value="graph" className="h-[calc(100vh-220px)] min-h-[720px] overflow-hidden rounded-2xl border border-border/60 bg-background shadow-sm">
|
||||
<KnowledgeGraphPage embedded onOpenConsole={() => setActiveTab('overview')} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="overview" className="space-y-4">
|
||||
<div className="grid gap-4 xl:grid-cols-[1.15fr_0.85fr]">
|
||||
<Card>
|
||||
@@ -1959,7 +1896,7 @@ export function KnowledgeBasePage() {
|
||||
<CardContent className="space-y-3">
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
当前配置文件路径:<code>{configPath}</code>
|
||||
长期记忆配置已移动到主程序配置,请在“主程序配置 / 长期记忆”中调整。
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<CodeEditor
|
||||
@@ -2001,72 +1938,6 @@ export function KnowledgeBasePage() {
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="config" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<SlidersHorizontal className="h-4 w-4" />
|
||||
长期记忆配置
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
常用字段可在这里可视化编辑;高级配置仍可通过原始 TOML 维护。
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant={rawMode ? 'outline' : 'default'} onClick={() => setRawMode(false)}>
|
||||
可视化配置
|
||||
</Button>
|
||||
<Button variant={rawMode ? 'default' : 'outline'} onClick={() => setRawMode(true)}>
|
||||
原始 TOML
|
||||
</Button>
|
||||
<Button onClick={() => void (rawMode ? saveRaw() : saveVisualConfig())} disabled={saving}>
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
当前配置文件:<code>{configPath}</code>
|
||||
{schema?._note ? `;${schema._note}` : ''}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
{!rawConfigExists || rawConfigUsingDefault ? (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
检测到配置文件尚未保存。当前展示的是默认模板内容,点击“保存”后会自动创建配置文件:
|
||||
{' '}
|
||||
<code>{configPath}</code>
|
||||
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
{rawMode ? (
|
||||
<CodeEditor
|
||||
value={rawConfig}
|
||||
onChange={setRawConfig}
|
||||
language="toml"
|
||||
height="620px"
|
||||
/>
|
||||
) : schema ? (
|
||||
<MemoryConfigEditor
|
||||
schema={schema}
|
||||
config={visualConfig}
|
||||
onChange={setVisualConfig}
|
||||
disabled={saving}
|
||||
/>
|
||||
) : (
|
||||
<div className="rounded-lg border bg-muted/30 p-4 text-sm text-muted-foreground">
|
||||
当前未能加载配置 schema,请先刷新页面或检查后端日志
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<ImportTab
|
||||
importCreateMode={importCreateMode}
|
||||
setImportCreateMode={setImportCreateMode}
|
||||
|
||||
@@ -31,6 +31,7 @@ import type {
|
||||
import { IMPORT_CHUNK_PAGE_SIZE, IMPORT_KIND_OPTIONS, RUNNING_IMPORT_STATUS } from '../constants'
|
||||
import {
|
||||
formatImportTime,
|
||||
formatProgressPercent,
|
||||
getImportStatusLabel,
|
||||
getImportStatusVariant,
|
||||
getImportStepLabel,
|
||||
@@ -871,7 +872,7 @@ export function ImportTab(props: ImportTabProps) {
|
||||
</div>
|
||||
<div className="mt-2 flex items-center justify-between gap-2 text-xs text-muted-foreground">
|
||||
<span>{getImportStepLabel(String(task.current_step ?? 'running'))}</span>
|
||||
<span>{Number(task.progress ?? 0).toFixed(1)}%</span>
|
||||
<span>{formatProgressPercent(task.progress)}</span>
|
||||
</div>
|
||||
<Progress value={normalizeProgress(task.progress)} className="mt-2 h-1.5" />
|
||||
</button>
|
||||
@@ -966,7 +967,7 @@ export function ImportTab(props: ImportTabProps) {
|
||||
</div>
|
||||
<div className="mt-2 flex items-center justify-between gap-2 text-xs text-muted-foreground">
|
||||
<span>完成进度</span>
|
||||
<span>{Number(task.progress ?? 0).toFixed(1)}%</span>
|
||||
<span>{formatProgressPercent(task.progress)}</span>
|
||||
</div>
|
||||
<Progress value={normalizeProgress(task.progress)} className="mt-2 h-1.5" />
|
||||
</button>
|
||||
@@ -1155,11 +1156,11 @@ export function ImportTab(props: ImportTabProps) {
|
||||
</div>
|
||||
<div className="mt-2 flex items-center justify-between gap-2 text-xs text-muted-foreground">
|
||||
<span>{getImportStepLabel(String(file.current_step ?? ''))}</span>
|
||||
<span>{Number(file.progress ?? 0).toFixed(1)}%</span>
|
||||
<span>{formatProgressPercent(file.progress)}</span>
|
||||
</div>
|
||||
<Progress value={normalizeProgress(file.progress)} className="mt-2 h-1.5" />
|
||||
<div className="mt-2 text-xs text-muted-foreground">
|
||||
{Number(file.progress ?? 0).toFixed(1)}% · {Number(file.done_chunks ?? 0)} / {Number(file.total_chunks ?? 0)}
|
||||
{formatProgressPercent(file.progress)} · {Number(file.done_chunks ?? 0)} / {Number(file.total_chunks ?? 0)}
|
||||
</div>
|
||||
{file.error ? (
|
||||
<div className="mt-2 truncate text-xs text-destructive">{file.error}</div>
|
||||
@@ -1230,7 +1231,7 @@ export function ImportTab(props: ImportTabProps) {
|
||||
<TableCell>{chunk.index}</TableCell>
|
||||
<TableCell>{getImportStatusLabel(String(chunk.status ?? ''))}</TableCell>
|
||||
<TableCell>{getImportStepLabel(String(chunk.step ?? ''))}</TableCell>
|
||||
<TableCell>{Number(chunk.progress ?? 0).toFixed(1)}%</TableCell>
|
||||
<TableCell>{formatProgressPercent(chunk.progress)}</TableCell>
|
||||
<TableCell className="max-w-[360px]">
|
||||
<div className="space-y-2">
|
||||
{String(chunk.error ?? '').trim() ? (
|
||||
|
||||
@@ -20,13 +20,18 @@ export function normalizeProgress(value: number | string | null | undefined): nu
|
||||
if (!Number.isFinite(numeric)) {
|
||||
return 0
|
||||
}
|
||||
if (numeric < 0) {
|
||||
const percent = numeric > 0 && numeric <= 1 ? numeric * 100 : numeric
|
||||
if (percent < 0) {
|
||||
return 0
|
||||
}
|
||||
if (numeric > 100) {
|
||||
if (percent > 100) {
|
||||
return 100
|
||||
}
|
||||
return numeric
|
||||
return percent
|
||||
}
|
||||
|
||||
export function formatProgressPercent(value: number | string | null | undefined): string {
|
||||
return `${normalizeProgress(value).toFixed(1)}%`
|
||||
}
|
||||
|
||||
export function parseOptionalPositiveInt(input: string): number | undefined {
|
||||
|
||||
@@ -206,7 +206,12 @@ function buildParagraphFromMetadata(
|
||||
}
|
||||
}
|
||||
|
||||
export function KnowledgeGraphPage() {
|
||||
interface KnowledgeGraphPageProps {
|
||||
embedded?: boolean
|
||||
onOpenConsole?: () => void
|
||||
}
|
||||
|
||||
export function KnowledgeGraphPage({ embedded = false, onOpenConsole }: KnowledgeGraphPageProps = {}) {
|
||||
const navigate = useNavigate()
|
||||
const { toast } = useToast()
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -731,17 +736,26 @@ export function KnowledgeGraphPage() {
|
||||
|
||||
const activeGraph = viewMode === 'entity' ? graphData : evidenceGraph
|
||||
const canShowEvidence = Boolean(selectedNodeData || selectedEdgeData || nodeDetail || edgeDetail)
|
||||
const openConsole = useCallback(() => {
|
||||
if (onOpenConsole) {
|
||||
onOpenConsole()
|
||||
return
|
||||
}
|
||||
void navigate({ to: '/resource/knowledge-base' })
|
||||
}, [navigate, onOpenConsole])
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex-none border-b bg-card/60 px-6 py-4 backdrop-blur">
|
||||
<div className={embedded ? 'flex-none border-b bg-card/60 px-4 py-4 backdrop-blur' : 'flex-none border-b bg-card/60 px-6 py-4 backdrop-blur'}>
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">长期记忆图谱</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
基于 A_Memorix 的实体关系图与证据视图
|
||||
</p>
|
||||
</div>
|
||||
{!embedded && (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">长期记忆图谱</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
基于 A_Memorix 的实体关系图与证据视图
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="outline" className="gap-1">
|
||||
@@ -791,7 +805,7 @@ export function KnowledgeGraphPage() {
|
||||
<RefreshCw className={`mr-2 h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
|
||||
刷新图谱
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => navigate({ to: '/resource/knowledge-base' })}>
|
||||
<Button variant="outline" onClick={openConsole} className={embedded ? 'hidden' : undefined}>
|
||||
<SlidersHorizontal className="mr-2 h-4 w-4" />
|
||||
打开控制台
|
||||
</Button>
|
||||
@@ -873,7 +887,7 @@ export function KnowledgeGraphPage() {
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
先在长期记忆控制台里完成导入或记忆生成,再回来查看关系网络。
|
||||
</p>
|
||||
<Button className="mt-4" onClick={() => navigate({ to: '/resource/knowledge-base' })}>
|
||||
<Button className="mt-4" onClick={openConsole}>
|
||||
前往长期记忆控制台
|
||||
</Button>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user