feat(intent): 意图删除支持假删除/真删除两种模式
删除入口区分两种模式(待领/运行中/已暂停的意图均可删除): - 假删除(默认):意图置 state='deleted'、删除原因记入新字段 delete_reason, 保留节点与全部产出/血缘,不再在图上另挂 fact。 - 真删除:物理删除意图及「仅由它支撑」的独占子孙(沿 yields/derived_from 级联到叶子),避免孤立数据;共享节点、目标、任务根事实保留。被删意图先做 token rollup 并清理 activity/副会话,被删 finding 先删 findings 行。 两种模式都照常用 cancelled 触发通知 planner(带删除前捕获的意图摘要 + 原因), planner 文案不再声称原因挂为事实。 - DB: 新增 SoftDeleteIntent、重写 CancelIntent 为深度独占级联;schema 加 delete_reason 列与 intent 的 deleted 状态(含旧库迁移)。 - 前端: 删除对话框加模式切换与动态说明,状态徽章/图标新增「已删除」, 播报板展开展示删除原因。
This commit is contained in:
1 parent
6e3dbf6452
commit
4756bb1c45
17 files changed
+593
-238
No files matched your search
+10
-3
@@ -315,12 +315,19 @@ export const api = {
|
||||
deleteTaskArchive: (id: number) => del<TaskArchive>(`/task-archives/${id}`),
|
||||
deleteTaskArchives: (archiveIds: number[]) =>
|
||||
post<{ items: ArchiveBatchItem[] }>("/task-archives/delete/batch", { archive_ids: archiveIds }),
|
||||
controlIntent: (taskId: string, intentId: string, action: "pause" | "resume" | "cancel", reason?: string) =>
|
||||
controlIntent: (
|
||||
taskId: string,
|
||||
intentId: string,
|
||||
action: "pause" | "resume" | "cancel",
|
||||
reason?: string,
|
||||
// cancel 专用:soft(默认,假删除,意图置 deleted + 记删除原因)| hard(真删除,级联移除独占子孙)。
|
||||
mode?: "soft" | "hard",
|
||||
) =>
|
||||
post<{
|
||||
id: number;
|
||||
state: "paused" | "open" | "stopped";
|
||||
state: "paused" | "open" | "deleted" | "";
|
||||
deleted?: { intents: number; facts: number; findings: number; activities: number };
|
||||
}>(`/tasks/${taskId}/intents/${intentId}/control`, { action, reason: reason ?? "" }),
|
||||
}>(`/tasks/${taskId}/intents/${intentId}/control`, { action, reason: reason ?? "", mode: mode ?? "soft" }),
|
||||
sendWorkerMessage: (taskId: string, intentId: string, message: string, requestId: string) =>
|
||||
post<{
|
||||
id: number;
|
||||
|
||||
+24
-12
@@ -1452,20 +1452,32 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
|
||||
if (b.action === "cancel") {
|
||||
const index = mockIntents.findIndex((item) => item.id === id);
|
||||
const intent = mockIntents[index];
|
||||
if (!intent || intent.inherited || (intent.state !== "running" && intent.state !== "paused")) {
|
||||
if (
|
||||
!intent ||
|
||||
intent.inherited ||
|
||||
(intent.state !== "running" && intent.state !== "paused" && intent.state !== "open")
|
||||
) {
|
||||
throw new Error("Worker 状态已变化");
|
||||
}
|
||||
mockIntents.splice(index, 1);
|
||||
return {
|
||||
id: Number(id.replace(/\D/g, "")) || 0,
|
||||
state: "cancelled",
|
||||
deleted: {
|
||||
intents: 1,
|
||||
facts: 1,
|
||||
findings: 1,
|
||||
activities: mockActivity.filter((item) => item.intent_id === id).length,
|
||||
},
|
||||
};
|
||||
const numId = Number(id.replace(/\D/g, "")) || 0;
|
||||
if (b.mode === "hard") {
|
||||
// 真删除:从列表移除,返回级联删除计数。
|
||||
mockIntents.splice(index, 1);
|
||||
return {
|
||||
id: numId,
|
||||
state: "",
|
||||
deleted: {
|
||||
intents: 1,
|
||||
facts: 1,
|
||||
findings: 1,
|
||||
activities: mockActivity.filter((item) => item.intent_id === id).length,
|
||||
},
|
||||
};
|
||||
}
|
||||
// 假删除(默认):置 deleted + 记删除原因,保留节点。
|
||||
intent.state = "deleted";
|
||||
intent.delete_reason = String(b.reason ?? "");
|
||||
return { id: numId, state: "deleted" };
|
||||
}
|
||||
const action = b.action === "resume" ? "resume" : "pause";
|
||||
const result = controlMockIntent(id, action);
|
||||
|
||||
+9
-20
@@ -1,31 +1,18 @@
|
||||
// Centralised status → color/label semantics, reused across the whole app.
|
||||
// Spec §8.3: 意图 / 覆盖 / 任务 / 严重度 each have a consistent color set.
|
||||
|
||||
export type Tone =
|
||||
| "neutral"
|
||||
| "blue"
|
||||
| "green"
|
||||
| "amber"
|
||||
| "red"
|
||||
| "rose"
|
||||
| "violet"
|
||||
| "slate";
|
||||
export type Tone = "neutral" | "blue" | "green" | "amber" | "red" | "rose" | "violet" | "slate";
|
||||
|
||||
export const toneClasses: Record<Tone, string> = {
|
||||
neutral:
|
||||
"bg-muted text-muted-foreground border-transparent",
|
||||
neutral: "bg-muted text-muted-foreground border-transparent",
|
||||
blue: "bg-blue-500/15 text-blue-600 dark:text-blue-400 border-blue-500/20",
|
||||
green:
|
||||
"bg-emerald-500/15 text-emerald-600 dark:text-emerald-400 border-emerald-500/20",
|
||||
amber:
|
||||
"bg-amber-500/15 text-amber-600 dark:text-amber-400 border-amber-500/20",
|
||||
green: "bg-emerald-500/15 text-emerald-600 dark:text-emerald-400 border-emerald-500/20",
|
||||
amber: "bg-amber-500/15 text-amber-600 dark:text-amber-400 border-amber-500/20",
|
||||
red: "bg-red-500/15 text-red-600 dark:text-red-400 border-red-500/20",
|
||||
// rose 用作「严重」——实心高强调,视觉上明显高于「高危」的软红描边。
|
||||
rose: "bg-rose-600 text-white border-rose-600 dark:bg-rose-600 dark:text-white",
|
||||
violet:
|
||||
"bg-violet-500/15 text-violet-600 dark:text-violet-400 border-violet-500/20",
|
||||
slate:
|
||||
"bg-slate-500/15 text-slate-600 dark:text-slate-400 border-slate-500/20",
|
||||
violet: "bg-violet-500/15 text-violet-600 dark:text-violet-400 border-violet-500/20",
|
||||
slate: "bg-slate-500/15 text-slate-600 dark:text-slate-400 border-slate-500/20",
|
||||
};
|
||||
|
||||
export const toneDot: Record<Tone, string> = {
|
||||
@@ -53,8 +40,10 @@ const intent: Record<string, StatusMeta> = {
|
||||
blocked: { label: "执行出错", tone: "red" },
|
||||
// exhausted = 达到步数/时间预算被中途掐断、只写回部分结果(非方向已探尽)。
|
||||
exhausted: { label: "预算耗尽", tone: "violet" },
|
||||
// stopped = 用户手动删除/停止了该 work(保留已产出的事实)。
|
||||
// stopped = 历史软删除状态(保留,历史数据)。
|
||||
stopped: { label: "已停止", tone: "slate" },
|
||||
// deleted = 用户假删除了该意图(保留节点与血缘,删除原因见 delete_reason 字段)。
|
||||
deleted: { label: "已删除", tone: "slate" },
|
||||
};
|
||||
|
||||
const task: Record<string, StatusMeta> = {
|
||||
|
||||
@@ -418,6 +418,7 @@ export interface TaskNode {
|
||||
ts: string;
|
||||
source_task_id?: string;
|
||||
inherited?: boolean;
|
||||
delete_reason?: string; // 意图假删除(state='deleted')时的删除原因
|
||||
}
|
||||
|
||||
// 播报板一页:按创建顺序分页的节点 + 这一页涉及的边 + 边另一端的节点(refs,按 id 索引),
|
||||
@@ -754,7 +755,7 @@ export interface LogLine {
|
||||
}
|
||||
|
||||
export type SessionRole = "mainagent" | "planner" | "worker" | "system";
|
||||
export type SessionStatus = "running" | "paused" | "done" | "blocked" | "exhausted" | "pending" | "stopped";
|
||||
export type SessionStatus = "running" | "paused" | "done" | "blocked" | "exhausted" | "pending" | "stopped" | "deleted";
|
||||
|
||||
// Daily token aggregate bucket (GET /api/tokens/daily).
|
||||
export interface DailyTokenBucket {
|
||||
|
||||
Reference in new issue
Block a user