fix:优化图片识别,优化webui配置和排版,优化聊天流监控,新增mcp显示,新增prompt修改面板,优化插件状态显示,优化长期记忆控制台,

This commit is contained in:
SengokuCola
2026-05-04 16:25:31 +08:00
parent c5cd47adc2
commit 120acb835f
51 changed files with 1764 additions and 493 deletions

View File

@@ -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}

View File

@@ -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() ? (

View File

@@ -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 {

View File

@@ -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>
</>