- #7a reveal 超时:长文按总时长 ≤8s 反推 interval(下限 12ms),短路径 30ms/字符不变(step=1);跳过按钮与 reduced-motion 行为不变 - #7b 标题编号:heading_open 注入 h1「一、」/h2「1.1」/h3「1.2.3」, 层级变化归零、自带编号跳过、md-hN class 恒挂、计数器逐次渲染重置; DocPane md 回退预览与私有参考块用 renderMarkdownPlain 关闭注入 (防止编号被序列化写回正本) - #27 会话聚合:删除前端二次聚合,会话域直接以后端 sources 为准; SourcesPane/SourceRow 以 paper_id/id 为 key 与高亮依据,徽标显示 真实 paper_id(截断+悬浮),无 paper_id 退 index+1; setHighlightSource 参数 index→key,chip 编号契约保持 - #31 节点详情:KgEntityDetail fetchExtra 序号守卫;KgPane :key 强制 重建;KgGraphView pointer 事件重构(pointerdown 不捕获、位移>4px 才捕获+标记 dragging、pointerup release、onClickNode dragging 拦截) - #39 日志刷新:打开总是重拉+「刷新日志」按钮+有 running 时 5s 独立 轮询(onBeforeUnmount 清理) - #40 轮询暂停:任何离开 extract 的切换 pausePolling;详情/日志展开态 提升到 store(detailOpenId/logOpen/runLog/logError),切 TAB 保留 - #42 模板选择:Composer docgen 模式模板下拉(自动推荐+内置+as_template 懒加载,失败降级),template_id 经 sendDocgen 传入 - #47b 会话切换 resetSession(selectSession 动态 import,只清不丢) - #55 文档 TAB:docgen store viewMode + DocPane「资料库」/「关闭文档」/ 「返回文档」按钮 - #28 检索文案:searchNoAnswer/searchTimeoutEmpty;200 降级体按 payload.degraded 分流,不再走 _errorMarker TIMEOUT 报错 - #52 异步轮询:api getTask/listTasks;sendDocgen 兼容 200/202, 202 后释放 busy 锁(生成期间可继续对话)、消息块记录 task_id、 2s 轮询 getTask、progress 驱动「生成中 x/y 节」、done 复用现有 artifact 挂块+落库+adoptArtifact+切 TAB、error 可重试; 会话恢复时 running 块含 task_id 续轮询(不误标「已中断」) 自测:29 个新增 r3debug 单测 + 23 个既有 private_docs 测试全绿; npm run build 通过;playwright 冒烟(dev server 5199)无 JS 错误, 模板下拉/提取面板/日志刷新按钮渲染正常。 未 push;Debug-2 9090 未就绪,#52/#28 联调留待 9090 就绪后执行。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
91 lines
4.5 KiB
JavaScript
91 lines
4.5 KiB
JavaScript
// api/docgen.js —— 文档生成端点(P3/dev3 归属)
|
||
// 端点契约:P2 任务书 sdk/app/routers/docgen.py(dev2 开发中,联调后生效)。
|
||
// 全部经 http.js 封装;默认读超时 600s 兜底(生成/编辑最长路径不早于 BFF 超时自断)。
|
||
import { get, post, patch, del } from './http.js';
|
||
|
||
// 缺陷 #52:generate 兼容 200(旧同步契约)/202(新异步契约)两种响应形态。
|
||
// 202 由 http.js 返回 payload(202 在 fetch 语义下非错误,body 直接透传);
|
||
// 旧 BFF 无 202 时直接返回同步产物——两者统一经 generate() 归一,
|
||
// chat.sendDocgen 按 `payload.task_id` 有无区分走轮询还是直接落块。
|
||
|
||
/**
|
||
* 上传入库(用户拖拽即走此路):multipart → vstore /ingest,入库主向量库持久可用。
|
||
* @param {File} file
|
||
* @param {{ name?: string, category?: string, description?: string, tags?: string, as_template?: boolean }} meta
|
||
* @param {{ onUploadProgress?: (e: {loaded:number, total:number, pct:number}) => void }} [opts]
|
||
* onUploadProgress:真实上传字节进度(http.js XHR 分支回调)
|
||
* @returns {Promise<{ok:boolean, items:[{library_id, name, size, as_template}]}>}
|
||
*/
|
||
export function uploadLibrary(file, meta = {}, opts = {}) {
|
||
const form = new FormData();
|
||
form.append('file', file);
|
||
if (meta.name != null && meta.name !== '') form.append('name', meta.name);
|
||
if (meta.category != null && meta.category !== '') form.append('category', meta.category);
|
||
if (meta.description != null && meta.description !== '') form.append('description', meta.description);
|
||
if (meta.tags != null && meta.tags !== '') form.append('tags', meta.tags);
|
||
if (meta.as_template != null) form.append('as_template', meta.as_template ? 'true' : 'false');
|
||
return post('/api/docgen/library', form, opts);
|
||
}
|
||
|
||
/**
|
||
* 生成(600s 兜底读超时;兼容 200 同步 / 202 异步两种响应)。
|
||
* @returns {Promise<{ok, task_id?, status?, artifact?, progress?, markdown?}>}
|
||
* 202 → {ok, task_id, status:"queued"};200 → 旧同步产物(artifact 内嵌)。
|
||
*/
|
||
export function generate(payload) {
|
||
return post('/api/docgen/generate', payload);
|
||
}
|
||
|
||
/** 缺陷 #52:任务状态轮询(GET /api/docgen/tasks/{task_id})
|
||
* → 200 {ok, task:{id, session_id, status, progress:[{section,title,state}], artifact|null, error}} */
|
||
export function getTask(taskId) {
|
||
return get(`/api/docgen/tasks/${encodeURIComponent(taskId)}`);
|
||
}
|
||
|
||
/** 缺陷 #52:会话级任务列表(GET /api/docgen/tasks?session_id=)——会话恢复时兜底查询 */
|
||
export function listTasks(sessionId) {
|
||
return get('/api/docgen/tasks', { session_id: sessionId });
|
||
}
|
||
|
||
/** 取 artifact 的 md 原文 */
|
||
export function getArtifactContent(artifactId) {
|
||
return get(`/api/docgen/artifacts/${encodeURIComponent(artifactId)}/content`);
|
||
}
|
||
|
||
/** 取 artifact 的模版 HTML({html, css, template_key},预览与导出同源) */
|
||
export function getArtifactHtml(artifactId) {
|
||
return get(`/api/docgen/artifacts/${encodeURIComponent(artifactId)}/html`);
|
||
}
|
||
|
||
/** 保存编辑后的 markdown(debounce PATCH);letterhead 为红头文字(2026-09-04 就地编辑持久化) */
|
||
export function saveArtifactContent(artifactId, markdown, letterhead) {
|
||
return patch(`/api/docgen/artifacts/${encodeURIComponent(artifactId)}/content`, { markdown, letterhead });
|
||
}
|
||
|
||
/** 「用 prompt 修改」:后端按 instruction 返回完整新 md */
|
||
export function editArtifact(artifactId, instruction) {
|
||
return post(`/api/docgen/artifacts/${encodeURIComponent(artifactId)}/edit`, { instruction });
|
||
}
|
||
|
||
/** 导出:后端生成产物并返回 download_url(PDF/DOCX) */
|
||
export function exportArtifact(artifactId, format) {
|
||
return post(`/api/docgen/artifacts/${encodeURIComponent(artifactId)}/export`, { format });
|
||
}
|
||
|
||
/** 主库管理(透传 vstore;批量导入/模版浏览用) */
|
||
export function listLibrary({ q, category, as_template } = {}) {
|
||
return get('/api/docgen/library', { q, category, as_template });
|
||
}
|
||
|
||
/** 删除资料(FR-PRIV-006):BFF DELETE 返回 204 空体——http.del 解析不出 JSON 时
|
||
* 返回 null,此处兜底为 {ok:true}(假设 10:不抛 JSON 解析错);信封错误仍抛 ApiError。 */
|
||
export async function removeLibrary(id) {
|
||
const res = await del(`/api/docgen/library/${encodeURIComponent(id)}`);
|
||
return res == null ? { ok: true } : res;
|
||
}
|
||
|
||
/** 下载地址(附件导出:后端带 RFC 5987 文件名) */
|
||
export function downloadUrl(artifactId, format) {
|
||
return `/api/docgen/artifacts/${encodeURIComponent(artifactId)}/download?format=${encodeURIComponent(format)}`;
|
||
}
|