知识是备忘录,备忘录不能只有字:手册、日志、导出表格都得能挂上去。
附件本来就落在 item 上(`ALLOWED_KINDS` 早含 knowledge),缺的是"新建时
还没有条目 id"这一段。
- 懒建草稿:`KnowledgeComposer` 第一次传附件(或往正文贴图)时先 `POST /knowledge`
存一条草稿拿到 id,`draftingRef` 去重防止贴图与上传同时打出两条草稿;
之后的保存走 `PATCH /knowledge/{id}`。没传过附件就取消,库里不留空条目。
- `RichTextEditor` 增 `ensureItemId`:没有 itemId 时先把草稿存下来再传图,
于是"新建时不能贴图"这条限制也没了;工具栏上传按钮按是否有落点显隐。
- 新建面板加了附件区:有草稿 id 就是正常的 `AttachmentPanel`(上传/下载/删),
没有就先给一个「添加附件」按钮,点一下建草稿。
- 安卓知识详情加附件区:列表(文件名 + 大小)、`OpenDocument` 上传任意文件、
点「打开」用浏览器看签名直链。编辑器原本只能插图,现在手册/日志也能传。
- 文档:API.md 说明附件三类事项共用同一套接口,以及懒建草稿的先后顺序;
ROADMAP 加 3.13。
验证:后端 pytest 94 passed、ruff 全绿;Web `tsc --noEmit` 通过、`vite build` 通过;
`knowledge-notebook.mjs` 15 项全绿(新增「新建面板里能上传附件」「详情抽屉里能看到
刚传的附件」「附件挂在知识上,详情回读得到」)。安卓端仍是 JDK 25 + Gradle 8.7
不兼容,本次未编译验证。
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
* 前面造数据走接口,界面部分点真实按钮。
|
||||
*/
|
||||
import { chromium } from "playwright-core";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
|
||||
const WEB = process.env.WEB ?? "http://127.0.0.1:5180";
|
||||
const API = process.env.API ?? "http://127.0.0.1:8100/api/v1";
|
||||
@@ -98,6 +99,28 @@ try {
|
||||
(await composerBody.getAttribute("aria-label")) === "正文",
|
||||
);
|
||||
await page.screenshot({ path: "e2e/artifacts/knowledge-composer-editor.png" });
|
||||
|
||||
// ②½ 附件:新建面板里就能传——点「添加附件」先把这条存成草稿,再上传
|
||||
mkdirSync("e2e/artifacts", { recursive: true });
|
||||
const samplePath = "e2e/artifacts/附件样例.txt";
|
||||
writeFileSync(samplePath, "命令行回滚手册:kubectl rollout undo deployment/api\n");
|
||||
await page.getByRole("button", { name: "添加附件", exact: true }).click();
|
||||
// 面板出现后再走真实的「上传」按钮 + filechooser:页面上不止一个 file input(编辑器也有)
|
||||
await page.getByRole("button", { name: "上传", exact: true }).waitFor({ timeout: 15_000 });
|
||||
const [chooser] = await Promise.all([
|
||||
page.waitForEvent("filechooser", { timeout: 15_000 }),
|
||||
page.getByRole("button", { name: "上传", exact: true }).click(),
|
||||
]);
|
||||
const [uploadResp] = await Promise.all([
|
||||
page.waitForResponse(
|
||||
(r) => r.url().includes("/attachments") && r.request().method() === "POST",
|
||||
{ timeout: 20_000 },
|
||||
),
|
||||
chooser.setFiles(samplePath),
|
||||
]);
|
||||
await page.getByText("附件样例.txt", { exact: false }).first().waitFor({ timeout: 10_000 });
|
||||
check("新建面板里能上传附件(先懒建草稿)", uploadResp.ok(), `HTTP ${uploadResp.status()}`);
|
||||
await page.screenshot({ path: "e2e/artifacts/knowledge-composer-attachment.png" });
|
||||
await page.getByRole("button", { name: "保存并继续写", exact: true }).click();
|
||||
|
||||
// 保存后直接打开详情:标题、分类路径、标签都在
|
||||
@@ -107,6 +130,7 @@ try {
|
||||
"新建面板能把分类与标签一起存下来",
|
||||
drawerText.includes("研发 / 部署") && drawerText.includes("发布"),
|
||||
);
|
||||
check("详情抽屉里能看到刚传的附件", drawerText.includes("附件样例.txt"));
|
||||
await page.screenshot({ path: "e2e/artifacts/knowledge-composer-saved.png", fullPage: false });
|
||||
|
||||
// ③ 列表卡片上能看到分类路径与标签(不用点开)
|
||||
@@ -188,7 +212,17 @@ try {
|
||||
doc?.category?.full_path === "研发 / 部署",
|
||||
`tags=${JSON.stringify((doc?.tags ?? []).map((t) => t.name))} category=${doc?.category?.full_path}`,
|
||||
);
|
||||
check("正文按 Markdown 存下来了", String(doc?.body_md ?? "").includes("停掉灰度流量"));
|
||||
check(
|
||||
"正文按 Markdown 存下来了",
|
||||
String(doc?.body_md ?? "").includes("停掉灰度流量"),
|
||||
);
|
||||
// 列表只带 attachment_count(省一次 N+1),文件清单要问详情
|
||||
const detail = await call("GET", `/workspaces/${ws.id}/knowledge/${doc.id}`, undefined, token);
|
||||
check(
|
||||
"附件挂在这条知识上(详情回读得到)",
|
||||
(detail?.attachments ?? []).some((a) => a.filename === "附件样例.txt"),
|
||||
`attachments=${JSON.stringify((detail?.attachments ?? []).map((a) => a.filename))}`,
|
||||
);
|
||||
|
||||
// ⑥ 分类重名与三级上限:接口层面确认约束还在
|
||||
const dup = await fetch(
|
||||
|
||||
Reference in New Issue
Block a user