feat(roundtable): taskId 会商记录独立存储(不分权) + 第三步生成过程对话/步骤条入库
taskId 深链会商存储独立化: - 新增独立表 roundtable_task_drafts + 路由 /api/roundtable-task-drafts,只按 task_id 归属、完全不按 user 分权(created_by 仅审计),与个人 roundtable_drafts 物理分离; 个人草稿表恢复严格按用户隔离(去掉原 task 放权)。 - 后台研讨作业按 taskId 共享:roundtable_jobs 加 task_id,get/get_by_draft/stream/ resume/cancel 支持 ?task_id= 跨用户读取与控制,作业报告写回 task-draft 存储。 - 前端:新增 api/task-drafts.ts 客户端,useDraftPersistence / useRoundtableDrafts / roundtable-jobs / DispatchChainJobModal / RoundtablePlanningPage 按 taskId 路由读写。 - 迁移 20260618_01 + column_additions_mysql.sql(jobs.task_id) + 数据搬迁脚本 scripts/migrate_roundtable_task_drafts.py(把历史 task_id 行迁出个人表)。 第三步聊天记录像第二步一样存全: - 总结报告 / 经典看板 / 大屏 三条流的生成过程对话(中间文字 + 步骤条 + stub)落库, 加载草稿整段回显,而非只剩最终 md / 流程图 / HTML。DraftStep3Snapshot 扩展 dialogues/seatStubMessages 槽位;存稿走页面 render 期 ref,恢复走各 hook hydrateSaved (旧草稿无字段则回退单条「历史记录」气泡)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
500a562bb6
commit
a8da6c2632
29 files changed
+1191
-263
No files matched your search
@@ -81,6 +81,8 @@ function toStartBody(p: StartJobPayload) {
|
||||
const seat = p.seatMode ? seatModeToRunParams(p.seatMode) : null;
|
||||
return {
|
||||
draftId: p.draftId ?? null,
|
||||
// taskId 非空 → task 作业(按 task 共享、不分权,报告写回独立 task-draft 存储)。
|
||||
...(p.taskId ? { taskId: p.taskId } : {}),
|
||||
intent: p.intent,
|
||||
agents: p.agents,
|
||||
model: p.model,
|
||||
@@ -131,23 +133,31 @@ export function startJobKeepalive(payload: StartJobPayload): void {
|
||||
}
|
||||
}
|
||||
|
||||
/** 查作业快照。404 → null。 */
|
||||
export async function getJob(jobId: string): Promise<RoundtableJob | null> {
|
||||
const res = await apiFetch(`${BASE()}/${encodeURIComponent(jobId)}`, { method: "GET" });
|
||||
/** taskId 非空 → 追加 `task_id` 查询参(task 作业按 task 共享、不分权读取/控制)。 */
|
||||
function taskQuery(taskId: string | null | undefined, sep: "?" | "&"): string {
|
||||
return taskId ? `${sep}task_id=${encodeURIComponent(taskId)}` : "";
|
||||
}
|
||||
|
||||
/** 查作业快照。404 → null。`taskId` 非空走 task 共享读取(不分权)。 */
|
||||
export async function getJob(jobId: string, taskId?: string | null): Promise<RoundtableJob | null> {
|
||||
const res = await apiFetch(`${BASE()}/${encodeURIComponent(jobId)}${taskQuery(taskId, "?")}`, { method: "GET" });
|
||||
if (res.status === 404) return null;
|
||||
return mapJob(await readJson<Record<string, unknown>>(res, "get job"));
|
||||
}
|
||||
|
||||
/** 查某草稿最近一个作业。无 → null。 */
|
||||
export async function getJobByDraft(draftId: string): Promise<RoundtableJob | null> {
|
||||
const res = await apiFetch(`${BASE()}?draft_id=${encodeURIComponent(draftId)}`, { method: "GET" });
|
||||
/** 查某草稿最近一个作业。无 → null。`taskId` 非空走 task 共享读取(不分权)。 */
|
||||
export async function getJobByDraft(draftId: string, taskId?: string | null): Promise<RoundtableJob | null> {
|
||||
const res = await apiFetch(
|
||||
`${BASE()}?draft_id=${encodeURIComponent(draftId)}${taskQuery(taskId, "&")}`,
|
||||
{ method: "GET" },
|
||||
);
|
||||
const data = await readJson<Record<string, unknown> | null>(res, "get job by draft");
|
||||
return data ? mapJob(data) : null;
|
||||
}
|
||||
|
||||
/** 回答澄清后续跑。 */
|
||||
export async function resumeJob(jobId: string, answer: string): Promise<RoundtableJob> {
|
||||
const res = await apiFetch(`${BASE()}/${encodeURIComponent(jobId)}/resume`, {
|
||||
/** 回答澄清后续跑。`taskId` 非空 → task 共享续跑(不分权)。 */
|
||||
export async function resumeJob(jobId: string, answer: string, taskId?: string | null): Promise<RoundtableJob> {
|
||||
const res = await apiFetch(`${BASE()}/${encodeURIComponent(jobId)}/resume${taskQuery(taskId, "?")}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ answer }),
|
||||
@@ -155,9 +165,9 @@ export async function resumeJob(jobId: string, answer: string): Promise<Roundtab
|
||||
return mapJob(await readJson<Record<string, unknown>>(res, "resume job"));
|
||||
}
|
||||
|
||||
/** 取消作业。 */
|
||||
export async function cancelJob(jobId: string): Promise<RoundtableJob> {
|
||||
const res = await apiFetch(`${BASE()}/${encodeURIComponent(jobId)}/cancel`, { method: "POST" });
|
||||
/** 取消作业。`taskId` 非空 → task 共享取消(不分权)。 */
|
||||
export async function cancelJob(jobId: string, taskId?: string | null): Promise<RoundtableJob> {
|
||||
const res = await apiFetch(`${BASE()}/${encodeURIComponent(jobId)}/cancel${taskQuery(taskId, "?")}`, { method: "POST" });
|
||||
return mapJob(await readJson<Record<string, unknown>>(res, "cancel job"));
|
||||
}
|
||||
|
||||
@@ -177,12 +187,12 @@ export interface StreamJobHandlers {
|
||||
*
|
||||
* 终态帧后后端会主动关闭流;本函数也在收到终态快照时调 onDone,调用方可据此 unsubscribe。
|
||||
*/
|
||||
export function streamJob(jobId: string, handlers: StreamJobHandlers): () => void {
|
||||
export function streamJob(jobId: string, handlers: StreamJobHandlers, taskId?: string | null): () => void {
|
||||
const controller = new AbortController();
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
const res = await apiFetch(`${BASE()}/${encodeURIComponent(jobId)}/stream`, {
|
||||
const res = await apiFetch(`${BASE()}/${encodeURIComponent(jobId)}/stream${taskQuery(taskId, "?")}`, {
|
||||
method: "GET",
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user