-
-
setRebuildSource(event.target.value)} />
+
+
+
+
重新生成来源 Episode
+
+ 适用于导入内容变化、反馈纠错后,需要用来源下的段落替换旧 Episode 的场景。
+
-
-
-
setRebuildSources(event.target.value)} />
+
-
-
-
-
+
+
+
+
处理待生成任务
+
+ 适用于后台已有待生成段落时,手动推进这些段落生成 Episode。
+
+
+
+
+
+ setPendingLimit(event.target.value)} />
+
+
+
+ setPendingMaxRetry(event.target.value)} />
+
+
+
+
diff --git a/dashboard/src/components/memory/MemoryProfileManager.tsx b/dashboard/src/components/memory/MemoryProfileManager.tsx
index 848a683b..588b6a59 100644
--- a/dashboard/src/components/memory/MemoryProfileManager.tsx
+++ b/dashboard/src/components/memory/MemoryProfileManager.tsx
@@ -1,11 +1,12 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import { Loader2, RefreshCw, Save, Search, Trash2 } from 'lucide-react'
+import { ChevronDown, Loader2, RefreshCw, Save, Search, Trash2 } from 'lucide-react'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Checkbox } from '@/components/ui/checkbox'
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { ScrollArea } from '@/components/ui/scroll-area'
@@ -16,6 +17,7 @@ import {
deleteMemoryProfileOverride,
getMemoryProfiles,
queryMemoryProfile,
+ searchMemoryProfiles,
setMemoryProfileOverride,
type MemoryProfileItemPayload,
type MemoryProfileQueryPayload,
@@ -77,6 +79,7 @@ function resolveProfileText(queryResult: MemoryProfileQueryPayload | null, selec
export function MemoryProfileManager() {
const { toast } = useToast()
const [profiles, setProfiles] = useState
([])
+ const [profileListMode, setProfileListMode] = useState<'library' | 'search'>('library')
const [selectedPersonId, setSelectedPersonId] = useState('')
const [queryPersonId, setQueryPersonId] = useState('')
const [queryKeyword, setQueryKeyword] = useState('')
@@ -84,6 +87,8 @@ export function MemoryProfileManager() {
const [queryUserId, setQueryUserId] = useState('')
const [queryLimit, setQueryLimit] = useState('12')
const [forceRefresh, setForceRefresh] = useState(false)
+ const [showAdvancedPersonId, setShowAdvancedPersonId] = useState(false)
+ const [showRawProfilePayload, setShowRawProfilePayload] = useState(false)
const [overrideText, setOverrideText] = useState('')
const [queryResult, setQueryResult] = useState(null)
const [loading, setLoading] = useState(false)
@@ -96,6 +101,7 @@ export function MemoryProfileManager() {
[profiles, selectedPersonId],
)
const profileText = resolveProfileText(queryResult, selectedProfile)
+ const selectedDisplayName = selectedProfile?.person_name || selectedPersonId || String(queryResult?.person_id ?? '未选择')
const loadProfiles = useCallback(async () => {
setLoading(true)
@@ -103,6 +109,7 @@ export function MemoryProfileManager() {
const payload = await getMemoryProfiles(80)
const nextItems = payload.items ?? []
setProfiles(nextItems)
+ setProfileListMode('library')
if (!selectedPersonId && nextItems.length > 0) {
setSelectedPersonId(nextItems[0].person_id)
}
@@ -127,42 +134,74 @@ export function MemoryProfileManager() {
useEffect(() => {
setOverrideText(stringifyOverride(selectedProfile?.manual_override))
- if (selectedProfile?.person_id) {
- setQueryPersonId(selectedProfile.person_id)
- }
}, [selectedProfile])
const submitQuery = useCallback(async () => {
- const hasAccountLocator = queryPlatform.trim() && queryUserId.trim()
- if (!queryPersonId.trim() && !queryKeyword.trim() && !hasAccountLocator) {
+ const directPersonId = showAdvancedPersonId ? queryPersonId.trim() : ''
+ const cleanKeyword = queryKeyword.trim()
+ const cleanPlatform = queryPlatform.trim()
+ const cleanUserId = queryUserId.trim()
+ const hasAccountLocator = Boolean(cleanPlatform && cleanUserId)
+ if (!directPersonId && !cleanKeyword && !hasAccountLocator) {
toast({
title: '请输入查询条件',
- description: 'person_id、关键词、或平台与账号至少填写一种。',
+ description: '用户账号、关键词、或高级 person_id 至少填写一种。',
variant: 'destructive',
})
return
}
setQuerying(true)
try {
+ if (!directPersonId && !hasAccountLocator) {
+ const searchPayload = await searchMemoryProfiles({
+ personKeyword: cleanKeyword,
+ limit: 80,
+ })
+ const nextItems = searchPayload.items ?? []
+ setProfiles(nextItems)
+ setProfileListMode('search')
+ setQueryResult(null)
+ setSelectedPersonId(nextItems[0]?.person_id ?? '')
+ toast({
+ title: '人物画像检索完成',
+ description: `命中 ${nextItems.length} 个画像。`,
+ })
+ return
+ }
+
const payload = await queryMemoryProfile({
- personId: queryPersonId.trim(),
- personKeyword: queryKeyword.trim(),
- platform: queryPlatform.trim(),
- userId: queryUserId.trim(),
+ personId: directPersonId,
+ personKeyword: cleanKeyword,
+ platform: cleanPlatform,
+ userId: cleanUserId,
limit: parsePositiveInt(queryLimit, 12),
forceRefresh,
})
+ if (payload.success === false) {
+ throw new Error(String(payload.error ?? '人物画像查询失败'))
+ }
setQueryResult(payload)
- const nextPersonId = String(payload.person_id ?? payload.profile?.person_id ?? queryPersonId ?? '')
+ const nextPersonId = String(payload.person_id ?? payload.profile?.person_id ?? directPersonId ?? '')
+ const searchPayload = await searchMemoryProfiles({
+ personId: nextPersonId || directPersonId,
+ personKeyword: cleanKeyword,
+ platform: cleanPlatform,
+ userId: cleanUserId,
+ limit: 80,
+ })
+ const nextItems = searchPayload.items ?? []
+ setProfiles(nextItems)
+ setProfileListMode('search')
if (nextPersonId) {
setSelectedPersonId(nextPersonId)
setQueryPersonId(nextPersonId)
+ } else if (nextItems.length > 0) {
+ setSelectedPersonId(nextItems[0].person_id)
}
toast({
title: '人物画像查询完成',
description: forceRefresh ? '已请求强制刷新画像。' : '已获取画像结果。',
})
- await loadProfiles()
} catch (error) {
toast({
title: '人物画像查询失败',
@@ -172,7 +211,7 @@ export function MemoryProfileManager() {
} finally {
setQuerying(false)
}
- }, [forceRefresh, loadProfiles, queryKeyword, queryLimit, queryPersonId, queryPlatform, queryUserId, toast])
+ }, [forceRefresh, queryKeyword, queryLimit, queryPersonId, queryPlatform, queryUserId, showAdvancedPersonId, toast])
const saveOverride = useCallback(async () => {
const personId = selectedPersonId || queryPersonId.trim()
@@ -238,18 +277,10 @@ export function MemoryProfileManager() {
人物画像查询
- 查看最近画像快照,或按 person_id、关键词、平台账号触发查询与刷新。
+ 按平台账号定位人物画像,可用关键词辅助检索;person_id 查询放在高级入口。
+
+
+
+
+
+
+
+ setQueryPersonId(event.target.value)}
+ placeholder="调试或后台管理时直接输入"
+ />
+
+
+
+ {selectedPersonId || queryPersonId ? (
+
+
当前定位 person_id
+
{selectedPersonId || queryPersonId}
+
+ ) : null}
+
+
+
{profileListMode === 'search' ? '检索结果' : '画像库'}
+
+ {profileListMode === 'search'
+ ? '根据当前平台账号、关键词或 person_id 筛选出的画像候选。'
+ : '系统中已生成的最新人物画像快照,按更新时间排序。'}
+
+
+
@@ -311,7 +381,8 @@ export function MemoryProfileManager() {
onClick={() => setSelectedPersonId(item.person_id)}
>
- {item.person_id}
+ {item.person_name || item.person_id}
+ {item.person_name ? {item.person_id}
: null}
{item.has_manual_override ?
手动 override : null}
{item.source_note ?
{item.source_note} : null}
@@ -323,7 +394,7 @@ export function MemoryProfileManager() {
)) : (
- {loading ? '正在加载人物画像...' : '还没有人物画像快照'}
+ {loading ? '正在加载人物画像...' : profileListMode === 'search' ? '没有匹配的人物画像' : '还没有人物画像快照'}
)}
@@ -353,9 +424,19 @@ export function MemoryProfileManager() {
{selectedProfile?.expires_at ?
过期时间 {formatMemoryTime(selectedProfile.expires_at)} : null}
-
- {JSON.stringify(queryResult ?? selectedProfile ?? {}, null, 2)}
-
+
+
+
+
+
+
+ {JSON.stringify(queryResult ?? selectedProfile ?? {}, null, 2)}
+
+
+
>
) : (
@@ -376,6 +457,7 @@ export function MemoryProfileManager() {
请选择或输入 person_id 后再编辑 override。
) : null}
+ {selectedDisplayName ?
当前编辑对象:{selectedDisplayName}
: null}