初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
commit 887c1f449e
963 files changed
+166869

No files matched your search

@@ -0,0 +1,169 @@
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/Button";
import { SensitivityBadge } from "@/components/ui/StatusBadge";
import { describeError } from "@/lib/utils/error";
import { MEMORY_SOURCE_LABEL, MEMORY_TYPE_LABEL } from "@/lib/constants/labels";
import { memoryApi } from "@/lib/api";
import { formatDateTime, formatPercent } from "@/lib/utils/format";
import { cn } from "@/lib/utils/cn";
import type { CreateMemoryRequest, Memory } from "@/types/api";
import { MemoryEditor } from "./MemoryEditor";
/** 单条记忆卡片(设计稿 memory-preview.html §mem-card)
* 类型色标(.mem-type)、置信度/重要性进度条(.meta-bar .fill)、
* 敏感度徽章、证据来源(.mem-foot)*/
export function MemoryCard({
memory,
characterId,
onChanged,
onError,
}: {
memory: Memory;
characterId?: string;
onChanged: (kind: "update" | "delete", payload: Memory | string) => void;
onError: (message: string) => void;
}) {
const [editing, setEditing] = useState(false);
const [confirming, setConfirming] = useState(false);
const [busy, setBusy] = useState(false);
const handleUpdate = async (body: CreateMemoryRequest) => {
setBusy(true);
try {
const updated = await memoryApi.updateMemory(memory.id, body);
onChanged("update", updated);
setEditing(false);
} catch (err) {
onError(describeError(err));
} finally {
setBusy(false);
}
};
const handleDelete = async () => {
setBusy(true);
try {
await memoryApi.deleteMemory(memory.id);
onChanged("delete", memory.id);
} catch (err) {
onError(describeError(err));
setConfirming(false);
} finally {
setBusy(false);
}
};
if (editing) {
return (
<MemoryEditor
characterId={characterId}
initial={memory}
onSubmit={handleUpdate}
onCancel={() => setEditing(false)}
/>
);
}
const sensClass = memory.sensitivity === "high" ? "high-sens" : "";
return (
<div className={cn("mem-card", sensClass)}>
{/* 标题行(设计稿 .mem-head) */}
<div className="mem-head">
<span className="mem-title">{memory.title || "无标题记忆"}</span>
<span className={cn("mem-type", memory.type)}>
{MEMORY_TYPE_LABEL[memory.type] ?? memory.type}
</span>
<SensitivityBadge level={memory.sensitivity} />
</div>
{/* 记忆内容(设计稿 .mem-content) */}
<p className="mem-content">{memory.content}</p>
{/* 元数据行:置信度 + 重要性进度条(设计稿 .mem-meta) */}
<div className="mem-meta">
<span className="meta-item conf">
<span className="lab">置信</span>
<span className="meta-bar">
<span
className="fill"
style={{
width: `${Math.round((memory.confidence ?? 0) * 100)}%`,
}}
/>
</span>
<span className="vv">{formatPercent(memory.confidence)}</span>
</span>
<span className="meta-item imp">
<span className="lab">重要</span>
<span className="meta-bar">
<span
className="fill"
style={{
width: `${Math.round((memory.importance ?? 0) * 100)}%`,
background: "var(--gilt)",
}}
/>
</span>
<span className="vv">{formatPercent(memory.importance)}</span>
</span>
</div>
{/* 脚注(设计稿 .mem-foot) */}
<div className="mem-foot">
<span className="evi">
来源:{MEMORY_SOURCE_LABEL[memory.source] ?? memory.source}
</span>
<span className="evi">
{formatDateTime(memory.updated_at ?? memory.created_at)}
</span>
{memory.evidence_ids && memory.evidence_ids.length > 0 && (
<span className="evi">证据·{memory.evidence_ids.length} 条</span>
)}
<span className="act">
<button type="button" onClick={() => setEditing(true)}>
编辑
</button>
<button
type="button"
className="primary"
onClick={() => setConfirming(true)}
disabled={busy}
>
删除
</button>
</span>
</div>
{/* 删除确认 */}
{confirming && (
<div className="mt-3 flex flex-wrap items-center justify-between gap-2 rounded-lg border border-cinnabar/30 bg-cinnabar/10 px-3 py-2 text-xs text-cinnabar backdrop-blur-sm">
<span>删除后此记忆将不再注入对话上下文,确认删除?</span>
<div className="flex gap-2">
<Button
size="sm"
variant="ghost"
onClick={() => setConfirming(false)}
disabled={busy}
>
取消
</Button>
<Button
size="sm"
variant="danger"
onClick={handleDelete}
loading={busy}
>
确认删除
</Button>
</div>
</div>
)}
</div>
);
}