fix(rag): 全文索引在容器里根本没持久化 —— 补 /data 卷 + BLEVE_PATH #8

Merged
Blizzard merged 15 commits from feat/site into main 2026-07-20 09:58:15 +00:00
Showing only changes of commit 00ad6c8ac0 - Show all commits
@@ -9,7 +9,38 @@ import { adminTaskDetail, type AdminTask, type AdminTaskDetail } from "../api";
// //
// 不含审批操作:审批是客户端用户的行为(桌面端 ApprovalBar),管理端只做观测。 // 不含审批操作:审批是客户端用户的行为(桌面端 ApprovalBar),管理端只做观测。
type ExecEvent = { node?: string; type?: string; msg?: string; at?: string; [k: string]: unknown }; // 字段名以库里真实轨迹为准(不是猜的):seq/ts/node/kind/phase/label/detail。
// ts 是毫秒时间戳;kind ∈ system|model|tool|agent|plan|section|render|approval
// phase ∈ info|start|end|await|error。
type ExecEvent = {
seq?: number;
ts?: number;
node?: string;
kind?: string;
phase?: string;
label?: string;
detail?: string;
[k: string]: unknown;
};
const KIND_TONE: Record<string, string> = {
system: "bg-gray-100 text-gray-600",
model: "bg-violet-50 text-violet-600",
tool: "bg-sky-50 text-sky-600",
agent: "bg-indigo-50 text-indigo-600",
plan: "bg-amber-50 text-amber-700",
section: "bg-emerald-50 text-emerald-600",
render: "bg-teal-50 text-teal-600",
approval: "bg-orange-50 text-orange-600",
};
// 只有 error/await 值得跳出来:前者是失败点,后者是卡在人工审批。
const PHASE_TONE: Record<string, string> = {
error: "text-rose-600 font-medium",
await: "text-orange-600",
};
const hhmmss = (ms?: number) => (ms ? new Date(ms).toLocaleTimeString("zh-CN", { hour12: false }) : "");
export function TaskDetailDrawer({ task, onClose }: { task: AdminTask; onClose: () => void }) { export function TaskDetailDrawer({ task, onClose }: { task: AdminTask; onClose: () => void }) {
const [d, setD] = useState<AdminTaskDetail | null>(null); const [d, setD] = useState<AdminTaskDetail | null>(null);
@@ -31,9 +62,12 @@ export function TaskDetailDrawer({ task, onClose }: { task: AdminTask; onClose:
}; };
}, [task.task_id]); }, [task.task_id]);
// flags 是库里存的 JSON 字符串,实测可能是 "null"(不是 "[]")——JSON.parse 出来是 null
// 不校验就会在 flags.length 上抛 TypeError 把整个控制台白屏掉。必须确认真是数组。
const flags: string[] = (() => { const flags: string[] = (() => {
try { try {
return d?.eval?.flags ? (JSON.parse(d.eval.flags) as string[]) : []; const v: unknown = d?.eval?.flags ? JSON.parse(d.eval.flags) : null;
return Array.isArray(v) ? v.filter((x): x is string => typeof x === "string") : [];
} catch { } catch {
return []; return [];
} }
@@ -104,15 +138,29 @@ export function TaskDetailDrawer({ task, onClose }: { task: AdminTask; onClose:
) : ( ) : (
<ol className="space-y-2"> <ol className="space-y-2">
{exec.map((e, i) => ( {exec.map((e, i) => (
<li key={i} className="rounded-lg border border-gray-100 bg-gray-50/60 p-2.5 text-[11px]"> <li
key={e.seq ?? i}
className={`rounded-lg border p-2.5 text-[11px] ${
e.phase === "error" ? "border-rose-200 bg-rose-50/50" : "border-gray-100 bg-gray-50/60"
}`}
>
<div className="flex items-baseline gap-2"> <div className="flex items-baseline gap-2">
<span className="shrink-0 text-gray-300">#{i + 1}</span> <span className="shrink-0 tabular-nums text-gray-300">#{e.seq ?? i + 1}</span>
{e.node && <span className="font-medium text-gray-700">{e.node}</span>} {e.label && <span className={`font-medium text-gray-700 ${PHASE_TONE[e.phase ?? ""] ?? ""}`}>{e.label}</span>}
{e.type && <span className="rounded bg-white px-1.5 py-0.5 text-[10px] text-violet-600">{e.type}</span>} {e.kind && (
{e.at && <span className="ml-auto shrink-0 text-gray-300">{e.at}</span>} <span className={`rounded px-1.5 py-0.5 text-[10px] ${KIND_TONE[e.kind] ?? "bg-gray-100 text-gray-500"}`}>
{e.kind}
</span>
)}
{e.phase && e.phase !== "info" && (
<span className={`text-[10px] ${PHASE_TONE[e.phase] ?? "text-gray-400"}`}>{e.phase}</span>
)}
<span className="ml-auto shrink-0 tabular-nums text-gray-300">{hhmmss(e.ts)}</span>
</div> </div>
{e.msg && <p className="mt-1 whitespace-pre-wrap break-words text-gray-600">{e.msg}</p>} {e.node && <div className="mt-0.5 font-mono text-[10px] text-gray-400">{e.node}</div>}
{!e.msg && !e.node && ( {e.detail && <p className="mt-1 whitespace-pre-wrap break-words text-gray-600">{e.detail}</p>}
{/* 兜底:字段形状变了也别把线索吞掉(上一版就是猜错字段名,白渲染了一列空行) */}
{!e.label && !e.detail && (
<pre className="mt-1 overflow-x-auto text-[10px] text-gray-500">{JSON.stringify(e)}</pre> <pre className="mt-1 overflow-x-auto text-[10px] text-gray-500">{JSON.stringify(e)}</pre>
)} )}
</li> </li>