Improve memory manager UI and auth persistence
This commit is contained in:
@@ -586,6 +586,9 @@ function AccountMenu({
|
||||
(sum, item) => sum + item.totalTokens,
|
||||
0,
|
||||
);
|
||||
const selectedSkillMemory = selectedMemorySkill
|
||||
? skillMemories.find((item) => item.skill === selectedMemorySkill)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
|
||||
@@ -757,43 +760,90 @@ function AccountMenu({
|
||||
</PopoverPrimitive.Content>
|
||||
</PopoverPrimitive.Portal>
|
||||
<Dialog open={memoryDialogOpen} onOpenChange={setMemoryDialogOpen}>
|
||||
<DialogContent className="max-h-[86vh] overflow-hidden sm:max-w-3xl">
|
||||
<DialogContent className="max-h-[88vh] overflow-hidden sm:max-w-5xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>个性化记忆</DialogTitle>
|
||||
<DialogDescription>
|
||||
这里展示当前账号的记忆内容和异步整理队列,保存后会影响后续对话注入。
|
||||
用户记忆会全局注入;Skill 记忆按 Skill 文件保存,后续按启用的
|
||||
Skill 注入。
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid min-h-0 gap-4 md:grid-cols-[1fr_240px]">
|
||||
<div className="grid min-h-0 gap-2">
|
||||
<div className="flex gap-1 overflow-x-auto pb-1">
|
||||
<div className="grid min-h-0 gap-4 md:grid-cols-[220px_1fr_240px]">
|
||||
<div className="min-h-0 rounded-md border bg-muted/20">
|
||||
<div className="border-b px-3 py-2 font-medium text-xs">
|
||||
记忆文件
|
||||
</div>
|
||||
<div className="max-h-[520px] overflow-y-auto p-2">
|
||||
<Button
|
||||
size="xs"
|
||||
className="mb-2 h-auto w-full justify-start px-2 py-2 text-left"
|
||||
size="sm"
|
||||
variant={selectedMemorySkill ? "outline" : "secondary"}
|
||||
onClick={() => {
|
||||
setSelectedMemorySkill("");
|
||||
setMemoryDraft(userMemory?.content ?? "");
|
||||
}}
|
||||
>
|
||||
用户记忆
|
||||
<div className="min-w-0">
|
||||
<div className="truncate font-medium">用户记忆</div>
|
||||
<div className="text-muted-foreground text-xs">
|
||||
{userMemory?.line_count ?? 0} 行
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
{skillMemories.map((item) => (
|
||||
<Button
|
||||
key={item.skill}
|
||||
size="xs"
|
||||
variant={
|
||||
selectedMemorySkill === item.skill
|
||||
? "secondary"
|
||||
: "outline"
|
||||
}
|
||||
onClick={() => {
|
||||
setSelectedMemorySkill(item.skill);
|
||||
setMemoryDraft(item.content);
|
||||
}}
|
||||
>
|
||||
{item.skill}
|
||||
</Button>
|
||||
))}
|
||||
<div className="mb-1 px-1 text-muted-foreground text-xs">
|
||||
Skill 记忆
|
||||
</div>
|
||||
{skillMemories.length ? (
|
||||
<div className="grid gap-1">
|
||||
{skillMemories.map((item) => (
|
||||
<Button
|
||||
key={item.skill}
|
||||
className="h-auto w-full justify-start px-2 py-2 text-left"
|
||||
size="sm"
|
||||
variant={
|
||||
selectedMemorySkill === item.skill
|
||||
? "secondary"
|
||||
: "ghost"
|
||||
}
|
||||
onClick={() => {
|
||||
setSelectedMemorySkill(item.skill);
|
||||
setMemoryDraft(item.content);
|
||||
}}
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate font-medium">
|
||||
{item.skill}
|
||||
</div>
|
||||
<div className="text-muted-foreground text-xs">
|
||||
{item.line_count ?? 0} 行
|
||||
{item.updated_at
|
||||
? ` · ${formatShortTime(item.updated_at)}`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-md border border-dashed p-3 text-muted-foreground text-xs">
|
||||
还没有 Skill 记忆。后续使用 Skill
|
||||
时,如果出现可沉淀经验,会按 Skill 单独生成文件。
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid min-h-0 content-start gap-2">
|
||||
<div className="rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div className="font-medium text-sm">
|
||||
{selectedMemorySkill
|
||||
? `${selectedMemorySkill} 使用记忆`
|
||||
: "用户记忆"}
|
||||
</div>
|
||||
<div className="mt-1 text-muted-foreground text-xs">
|
||||
{selectedMemorySkill
|
||||
? "仅在该 Skill 启用并进入模型可见列表时注入。"
|
||||
: "作为当前账号的长期偏好和通用经验注入。"}
|
||||
</div>
|
||||
</div>
|
||||
<textarea
|
||||
className="min-h-[420px] resize-none rounded-md border bg-background p-3 font-mono text-xs outline-none"
|
||||
@@ -802,10 +852,11 @@ function AccountMenu({
|
||||
/>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="truncate text-muted-foreground text-xs">
|
||||
当前编辑:
|
||||
{selectedMemorySkill
|
||||
? `${selectedMemorySkill} 使用记忆`
|
||||
: "用户记忆"}
|
||||
{selectedMemorySkill && selectedSkillMemory?.updated_at
|
||||
? `最近更新 ${formatShortTime(selectedSkillMemory.updated_at)}`
|
||||
: userMemory?.updated_at
|
||||
? `最近更新 ${formatShortTime(userMemory.updated_at)}`
|
||||
: "暂无更新时间"}
|
||||
</span>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -925,6 +976,17 @@ function formatCompactNumber(value: number) {
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
function formatShortTime(value: string) {
|
||||
const timestamp = Date.parse(value);
|
||||
if (Number.isNaN(timestamp)) return value;
|
||||
return new Intl.DateTimeFormat("zh-CN", {
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}).format(new Date(timestamp));
|
||||
}
|
||||
|
||||
function AccountStat({
|
||||
label,
|
||||
value,
|
||||
|
||||
Reference in New Issue
Block a user