import { test, expect, type Page } from '@playwright/test';
import { build } from 'vite';
import { createServer, type Server } from 'node:http';
import { mkdtemp, readFile, realpath, rm, symlink, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join, resolve, sep } from 'node:path';
import { createRequire } from 'node:module';
import { EMPTY_CLOUD_CONFIGURATION, type CloudAgentDraft, type CloudKnowledge, type CloudKnowledgeFile } from '../../shared/cloud-agents';
let server: Server;
let baseUrl: string;
let fixtureDir: string;
const fixtureRequire = createRequire(resolve('tests/e2e/cloud-agent-knowledge.spec.ts'));
const viteDependencies = createRequire(fixtureRequire.resolve('vite/package.json'));
test.use({ headless: true, channel: process.env.MAKELORE_LAYOUT_BROWSER_CHANNEL, video: 'off' });
// Render the real editor while replacing only the Main IPC boundary. No Electron,
// native picker, real upload or cloud request is started by this suite.
test.beforeAll(async () => {
test.setTimeout(90_000);
fixtureDir = await realpath(await mkdtemp(join(tmpdir(), 'makelore-knowledge-fixture-')));
await symlink(resolve('node_modules'), join(fixtureDir, 'node_modules'), 'dir');
const html = join(fixtureDir, 'index.html');
const output = join(fixtureDir, 'dist');
await writeFile(html, '
');
const entry = resolve('tests/e2e/__cloud-agent-knowledge-harness.tsx');
// Build the virtual entry once. A static fixture needs no Vite dev server,
// background dependency discovery, file watcher, or HMR connection.
await build({
configFile: false, root: fixtureDir, publicDir: false, logLevel: 'warn',
resolve: { alias: { '@': resolve('src') } }, esbuild: { jsx: 'automatic' },
css: { postcss: resolve('.') },
build: { outDir: output, emptyOutDir: true, rollupOptions: { input: html } },
plugins: [{
name: 'isolated-cloud-agent-knowledge', enforce: 'pre',
resolveId(id) { if (id === '/__cloud-agent-knowledge-harness.tsx') return entry; },
load(id) {
if (id === entry) return `
import { createRoot } from 'react-dom/client';
import { createMemoryRouter, RouterProvider } from 'react-router-dom';
import CloudAgents from ${JSON.stringify(resolve('src/pages/CloudAgents/index.tsx'))};
import ${JSON.stringify(resolve('src/styles/globals.css'))};
const router = createMemoryRouter([{ path: '/cloud-agents', element: }], { initialEntries: ['/cloud-agents?view=mine'] });
createRoot(document.getElementById('root')).render(
);
`;
if (id.endsWith('/src/stores/auth.ts')) return 'export function useAuthStore(select) { return select({ user: { userId: "knowledge-fixture" } }); }';
},
}],
});
const mime: Record = { html: 'text/html', js: 'text/javascript', css: 'text/css',
svg: 'image/svg+xml', png: 'image/png', woff2: 'font/woff2', woff: 'font/woff' };
server = createServer(async (req, res) => {
const pathname = decodeURIComponent(new URL(req.url ?? '/', 'http://127.0.0.1').pathname);
const file = resolve(output, '.' + (pathname === '/' ? '/index.html' : pathname));
if (!file.startsWith(output + sep)) { res.writeHead(403); res.end(); return; }
try { const bytes = await readFile(file); res.setHeader('Content-Type', mime[file.split('.').pop()!] ?? 'application/octet-stream'); res.end(bytes); }
catch { res.writeHead(404); res.end(); }
});
await new Promise((done, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', done); });
const address = server.address();
if (!address || typeof address === 'string') throw new Error('Fixture HTTP address is unavailable');
baseUrl = `http://127.0.0.1:${address.port}`;
});
test.afterAll(async () => {
try {
if (server?.listening) { server.closeAllConnections(); await new Promise((done, reject) => server.close(error => error ? reject(error) : done())); }
} finally {
try { if (fixtureDir) await rm(fixtureDir, { recursive: true, force: true }); }
finally { (viteDependencies('esbuild') as { stop(): void }).stop(); }
}
});
const agent: CloudAgentDraft = {
slug: 'ml-' + 'a'.repeat(32), name: '资料小助手', purpose: '用我的资料回答问题。',
system_prompt: '请先查资料,再用简单的中文回答。', draft_revision: 1,
configuration: { ...EMPTY_CLOUD_CONFIGURATION, model: 'fixture-model' },
published_version: null, enabled: true, updated_at: '2026-10-12T00:00:00Z',
};
// Existing facade wire format deliberately has no source/can_manage metadata.
const libraries: CloudKnowledge[] = [
{ kb_id: 'kb_fixturescience', name: '科学知识', description: '平台开放的科学资料', embedding_model: 'fixture-embedding' },
{ kb_id: 'kb_fixturereading', name: '阅读知识', description: '平台开放的阅读资料', embedding_model: 'fixture-embedding' },
{ kb_id: 'kb_fixturepersonal', name: '我的观察记录', description: '自己的资料', embedding_model: 'fixture-embedding' },
];
type FixtureCall = { path: string; method: string; input: Record | null };
type KnowledgeFixture = {
agent: CloudAgentDraft; libraries: CloudKnowledge[]; files: Record;
calls: FixtureCall[]; ownedIds: string[]; upload: 'success' | 'cancel' | 'fail'; processFailure: boolean;
};
type FixtureWindow = Window & { __knowledgeFixture: KnowledgeFixture };
async function installFixture(page: Page) {
page.on('pageerror', error => { throw error; });
await page.addInitScript(({ agent, libraries }) => {
const scope = window as unknown as FixtureWindow;
const stored = sessionStorage.getItem('knowledge-fixture');
const state: KnowledgeFixture = stored ? JSON.parse(stored) : {
agent, libraries, files: {}, calls: [], ownedIds: ['kb_fixturepersonal'], upload: 'success', processFailure: false,
};
scope.__knowledgeFixture = state;
const persist = () => sessionStorage.setItem('knowledge-fixture', JSON.stringify(state));
const result = (json: unknown) => { persist(); return { ok: true, data: { status: 200, ok: true, json: structuredClone(json) } }; };
const failure = (message: string) => { persist(); return { ok: true, data: { status: 503, ok: false, json: { error: message } } }; };
const managed = (id: unknown) => state.ownedIds.includes(String(id));
Object.defineProperty(window, 'electron', { value: { ipcRenderer: {
invoke: async (channel: string, request?: { path?: string; method?: string; body?: string }) => {
if (channel === 'hostapi:token') return 'fixture';
if (channel === 'hostapi:base-url') return 'http://fixture.invalid';
if (channel !== 'hostapi:fetch') throw new Error('Unexpected fixture IPC ' + channel);
const path = request?.path ?? ''; const method = request?.method ?? 'GET';
const input = request?.body ? JSON.parse(request.body) as Record : null;
state.calls.push({ path, method, input });
if (path === '/api/cloud-agents/recovery') return result({ recent: null, pending: [] });
if (path === '/api/cloud-agents/recovery/recent') return result({ saved: true });
if (path.startsWith('/api/cloud-agents/agents')) {
if (path === '/api/cloud-agents/agents') return result({ agents: [state.agent], next_cursor: null });
if (method === 'PATCH') Object.assign(state.agent, input, { draft_revision: state.agent.draft_revision + 1 });
return result(state.agent);
}
if (path === '/api/cloud-agents/knowledge/pick') {
if (!managed(input?.kb_id)) throw new Error('Document upload reached a read-only library');
if (state.upload === 'cancel') return result(null);
if (state.upload === 'fail') return failure('上传服务暂不可用');
const file: CloudKnowledgeFile = { file_id: 'observations-file', name: '春天观察记录.md', size: 1200,
status: 'uploaded', error: null, chunk_count: 0, available: false };
state.files[String(input?.kb_id)] = [file]; return result(file);
}
if (path !== '/api/cloud-agents/actions') throw new Error('Unexpected fixture request ' + path);
const args = input?.input as Record | undefined;
switch (input?.operation) {
case 'catalog': return result({ models: [{ id: 'fixture-model', name: '创作模型' }],
resources: { knowledges: state.libraries.map(kb => ({ key: kb.kb_id, name: kb.name, description: kb.description })) }, pricing: null });
case 'knowledge': return result({ databases: state.libraries.filter(kb => managed(kb.kb_id)),
models: [{ id: 'fixture-embedding', name: '平台向量模型', dimension: 768 }] });
case 'access': return result({ published_version: null, enabled: true, share_url: 'niancode://agents/' + agent.slug,
api_url: 'http://fixture.invalid', grants: [], applications: [], versions: [] });
case 'createKnowledge': {
const created: CloudKnowledge = { kb_id: 'kb_fixturecreated', name: String(args?.name), description: '', embedding_model: String(args?.embedding_model) };
state.libraries.push(created); state.ownedIds.push(created.kb_id); return result(created);
}
case 'knowledgeFiles':
if (!managed(args?.kb_id)) throw new Error('Document management reached a read-only library');
return result({ files: state.files[String(args?.kb_id)] ?? [], next_offset: null });
case 'received': return result({ agents: [] });
case 'processKnowledge': {
if (!managed(args?.kb_id)) throw new Error('Processing reached a read-only library');
if (state.processFailure) return failure('处理请求暂不可用');
const file = state.files[String(args?.kb_id)]?.find(file => file.file_id === args?.file_id);
if (!file) throw new Error('Unknown fixture document');
file.processing_task = { task_id: 'processing-task', status: 'pending', error: null };
return result({ task_id: 'processing-task' });
}
default: throw new Error('Unexpected fixture operation ' + String(input?.operation));
}
},
} } });
}, { agent, libraries });
}
async function openKnowledge(page: Page) {
await page.getByRole('button', { name: '继续制作', exact: true }).click();
const editor = page.getByTestId('cloud-agent-editor');
await editor.getByRole('tab', { name: '高级设置', exact: true }).click();
const knowledge = editor.getByRole('group', { name: '知识库', exact: true });
await knowledge.scrollIntoViewIfNeeded();
await expect(knowledge.getByRole('combobox', { name: '选择知识库', exact: true })).toBeVisible();
await expect(editor.getByRole('combobox', { name: '模型', exact: true })).toBeEnabled();
return { editor, knowledge };
}
async function calls(page: Page) { return page.evaluate(() => (window as unknown as FixtureWindow).__knowledgeFixture.calls); }
async function expectNoClipping(page: Page) {
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
const clipped = await page.getByTestId('cloud-agent-editor').evaluate(editor => {
const scroll = editor.querySelector('.agent-config-scroll')!; const edge = scroll.getBoundingClientRect();
return [...scroll.querySelectorAll('button, input, textarea, select, h2, summary, fieldset')]
.filter(element => element.getClientRects().length > 0 && !element.classList.contains('sr-only'))
.filter(element => { const bounds = element.getBoundingClientRect(); return bounds.left < edge.left - 1 || bounds.right > edge.right + 1; })
.map(element => element.getAttribute('aria-label') ?? element.textContent?.trim());
});
expect(clipped).toEqual([]);
}
async function expectKnowledgeLayout(page: Page) {
const knowledge = page.getByTestId('cloud-agent-editor').getByRole('group', { name: '知识库', exact: true });
const creation = knowledge.getByRole('region', { name: '上传自己的文件', exact: true });
const management = knowledge.getByRole('region', { name: '查看我的知识库', exact: true });
const selection = knowledge.getByRole('region', { name: '选择知识库', exact: true });
await expect(creation.getByRole('button', { name: '新建知识库', exact: true })).toBeVisible();
await expect(creation.getByRole('combobox', { name: '管理的知识库', exact: true })).toHaveCount(0);
await expect(creation.getByRole('button', { name: '从电脑上传', exact: true })).toHaveCount(0);
await expect(management.getByRole('combobox', { name: '管理的知识库', exact: true })).toBeVisible();
await expect(management.getByRole('button', { name: '从电脑上传', exact: true })).toBeVisible();
await expect(management.getByRole('button', { name: '新建知识库', exact: true })).toHaveCount(0);
await expect(selection.getByRole('combobox', { name: '选择知识库', exact: true })).toBeVisible();
await expect(knowledge.getByText('添加到我的知识库', { exact: true })).toHaveCount(0);
await expect(knowledge.getByText('选择或新建自己的知识库,再从电脑上传资料。选用会加入右侧配置,保存并应用后生效。', { exact: true })).toHaveCount(0);
const creationBounds = await creation.boundingBox();
const managementBounds = await management.boundingBox();
const selectionBounds = await selection.boundingBox();
const ownedBounds = await knowledge.locator('.agent-knowledge-owned-workspace').boundingBox();
expect(creationBounds).not.toBeNull(); expect(managementBounds).not.toBeNull(); expect(selectionBounds).not.toBeNull();
expect(ownedBounds).not.toBeNull();
if (ownedBounds!.width > 620) {
expect(creationBounds!.x + creationBounds!.width).toBeLessThanOrEqual(managementBounds!.x + 1);
expect(Math.abs(creationBounds!.y - managementBounds!.y)).toBeLessThanOrEqual(1);
expect(selectionBounds!.y).toBeGreaterThanOrEqual(Math.max(creationBounds!.y + creationBounds!.height, managementBounds!.y + managementBounds!.height));
expect(Math.abs(selectionBounds!.x + selectionBounds!.width - managementBounds!.x - managementBounds!.width)).toBeLessThanOrEqual(1);
} else {
expect(creationBounds!.y + creationBounds!.height).toBeLessThanOrEqual(managementBounds!.y + 1);
expect(managementBounds!.y + managementBounds!.height).toBeLessThanOrEqual(selectionBounds!.y + 1);
expect(Math.abs(creationBounds!.x - managementBounds!.x)).toBeLessThanOrEqual(1);
expect(Math.abs(creationBounds!.width - managementBounds!.width)).toBeLessThanOrEqual(1);
expect(Math.abs(creationBounds!.width - selectionBounds!.width)).toBeLessThanOrEqual(1);
}
expect(Math.abs(creationBounds!.x - selectionBounds!.x)).toBeLessThanOrEqual(1);
await expectNoClipping(page);
}
async function screenshotKnowledge(page: Page, name: string) {
const viewport = page.viewportSize()!;
const knowledge = page.getByTestId('cloud-agent-editor').getByRole('group', { name: '知识库', exact: true });
const bounds = await knowledge.boundingBox();
// Fit this scrollable card in the viewport so screenshots show every panel.
await page.setViewportSize({ width: viewport.width, height: Math.ceil(bounds!.height + 260) });
await knowledge.screenshot({ path: test.info().outputPath(name), animations: 'disabled' });
await page.setViewportSize(viewport);
}
for (const width of [375, 1440]) {
test(`multiple shared/personal choices stay draft-only and survive save/reopen at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 850 }); await installFixture(page); await page.goto(baseUrl);
const { editor, knowledge } = await openKnowledge(page);
const choose = knowledge.getByRole('combobox', { name: '选择知识库', exact: true });
await expect(choose.locator('optgroup[label="MakeLore知识"] option')).toHaveCount(2);
await expect(choose.locator('optgroup[label="我的知识库"] option')).toHaveCount(1);
await choose.selectOption('kb_fixturescience'); await choose.selectOption('kb_fixturereading'); await choose.selectOption('kb_fixturepersonal');
const selected = knowledge.getByRole('list', { name: '已选知识库', exact: true });
await expect(selected.getByRole('listitem')).toHaveCount(3);
await selected.getByRole('button', { name: '移除知识库 我的观察记录', exact: true }).click();
await expect(selected.getByRole('listitem')).toHaveCount(2);
await choose.selectOption('kb_fixturepersonal');
await expect(selected.getByRole('listitem')).toHaveCount(3);
await expect(selected.getByRole('button', { name: '移除知识库 科学知识', exact: true })).toBeVisible();
await expect(selected.getByRole('button', { name: /上传|删除|处理|替换/ })).toHaveCount(0);
expect((await calls(page)).filter(call => call.method === 'PATCH' || call.path.endsWith('/knowledge/pick'))).toEqual([]);
await expect(knowledge.getByRole('heading', { name: '上传自己的文件', exact: true })).toBeVisible();
const management = knowledge.getByRole('combobox', { name: '管理的知识库', exact: true });
await expect(management.locator('option[value="kb_fixturescience"], option[value="kb_fixturereading"]')).toHaveCount(0);
await expect(knowledge.getByRole('button', { name: '从电脑上传', exact: true })).toBeDisabled();
await expectKnowledgeLayout(page); await screenshotKnowledge(page, `knowledge-shared-${width}.png`);
await editor.getByRole('button', { name: '保存草稿', exact: true }).click();
await expect(editor.getByRole('button', { name: '已保存', exact: true })).toBeVisible();
expect((await calls(page)).filter(call => call.method === 'PATCH').map(call => (call.input?.configuration as { knowledges: string[] }).knowledges))
.toEqual([['kb_fixturescience', 'kb_fixturereading', 'kb_fixturepersonal']]);
await page.reload(); const reopened = await openKnowledge(page);
await expect(reopened.knowledge.getByRole('list', { name: '已选知识库', exact: true }).getByRole('listitem')).toHaveCount(3);
await expectNoClipping(page);
if (width === 1440) {
for (const intermediateWidth of [768, 1024]) {
await page.setViewportSize({ width: intermediateWidth, height: 850 });
await expectKnowledgeLayout(page);
await screenshotKnowledge(page, `knowledge-shared-${intermediateWidth}.png`);
}
}
});
test(`own library browsing and creation stay unbound while computer upload processes a document at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 850 }); await installFixture(page); await page.goto(baseUrl);
const { editor, knowledge } = await openKnowledge(page);
const selection = knowledge.getByRole('region', { name: '选择知识库', exact: true });
const choose = selection.getByRole('combobox', { name: '选择知识库', exact: true });
const selected = selection.getByRole('list', { name: '已选知识库', exact: true });
await choose.selectOption('kb_fixturescience');
await expect(knowledge.getByRole('heading', { name: '上传自己的文件', exact: true })).toBeVisible();
expect((await calls(page)).filter(call => call.path.endsWith('/knowledge/pick') || ['createKnowledge', 'processKnowledge'].includes(String(call.input?.operation)))).toEqual([]);
const creation = knowledge.getByRole('region', { name: '上传自己的文件', exact: true });
const management = knowledge.getByRole('region', { name: '查看我的知识库', exact: true });
const manager = management.getByRole('combobox', { name: '管理的知识库', exact: true });
const upload = management.getByRole('button', { name: '从电脑上传', exact: true });
const create = creation.getByRole('button', { name: '新建知识库', exact: true });
await expect(upload).toBeDisabled();
await expectKnowledgeLayout(page);
await manager.selectOption('kb_fixturepersonal');
await expect(upload).toBeEnabled();
await expect(selected.getByRole('listitem')).toHaveCount(1);
await expect(selected).not.toContainText('我的观察记录');
await manager.selectOption('');
await expect(upload).toBeDisabled();
await create.click();
await creation.getByRole('textbox', { name: '知识库名称', exact: true }).fill('我的春天资料');
await creation.getByRole('combobox', { name: '向量模型', exact: true }).selectOption('fixture-embedding');
await expectKnowledgeLayout(page);
await creation.getByRole('button', { name: '创建知识库', exact: true }).click();
await expect(manager).toHaveValue('kb_fixturecreated');
await expect(selected.getByRole('listitem')).toHaveCount(1);
await expect(selected).not.toContainText('我的春天资料');
await expect(upload).toBeEnabled();
await manager.selectOption('kb_fixturepersonal');
await manager.selectOption('kb_fixturecreated');
await expect(selected.getByRole('listitem')).toHaveCount(1);
await expect(knowledge.getByRole('button', { name: '从聊天选择文件', exact: true })).toHaveCount(0);
expect((await calls(page)).filter(call => call.path.endsWith('/knowledge/pick') || call.input?.operation === 'processKnowledge')).toEqual([]);
await upload.click();
const document = management.getByRole('article', { name: '春天观察记录.md', exact: true });
await expect(document.getByText('等待处理', { exact: true })).toBeVisible();
const mutations = (await calls(page)).filter(call => call.path.endsWith('/knowledge/pick') || call.input?.operation === 'processKnowledge');
expect(mutations).toHaveLength(2);
expect(mutations[0].input).toMatchObject({ kb_id: 'kb_fixturecreated' });
expect(mutations[1].input?.input).toMatchObject({ kb_id: 'kb_fixturecreated', file_id: 'observations-file' });
await page.evaluate(() => {
const file = (window as unknown as FixtureWindow).__knowledgeFixture.files['kb_fixturecreated'][0];
Object.assign(file, { status: 'indexed', chunk_count: 6, available: true,
processing_task: { task_id: 'processing-task', status: 'completed', error: null } });
});
await expect(document.getByText('可用于回答', { exact: true })).toBeVisible({ timeout: 12_000 });
await expect(document.getByText('2 KB · 6 段可检索内容', { exact: true })).toBeVisible();
await expect(selected.getByRole('listitem')).toHaveCount(1);
await choose.selectOption('kb_fixturecreated');
await expect(selected.getByRole('listitem')).toHaveCount(2);
await expect(selected).toContainText('我的春天资料');
await document.scrollIntoViewIfNeeded();
await expectKnowledgeLayout(page); await screenshotKnowledge(page, `knowledge-upload-ready-${width}.png`);
// Document mutations never implicitly save the Agent draft.
expect((await calls(page)).filter(call => call.method === 'PATCH')).toEqual([]);
await editor.getByRole('button', { name: '保存草稿', exact: true }).click();
await expect(editor.getByRole('button', { name: '已保存', exact: true })).toBeVisible();
expect(await page.evaluate(() => (window as unknown as FixtureWindow).__knowledgeFixture.agent.configuration.knowledges))
.toEqual(['kb_fixturescience', 'kb_fixturecreated']);
});
}
test('cancelled and failed uploads preserve shared choices and the personal draft', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 850 }); await installFixture(page); await page.goto(baseUrl);
const { editor, knowledge } = await openKnowledge(page);
await editor.getByRole('tab', { name: '介绍与提示词', exact: true }).click();
await editor.getByRole('textbox', { name: '提示词', exact: true }).fill('这是我还没有保存的提示词。');
await editor.getByRole('tab', { name: '高级设置', exact: true }).click();
await knowledge.getByRole('combobox', { name: '选择知识库', exact: true }).selectOption('kb_fixturescience');
await expect(knowledge.getByRole('heading', { name: '上传自己的文件', exact: true })).toBeVisible();
await knowledge.getByRole('combobox', { name: '管理的知识库', exact: true }).selectOption('kb_fixturepersonal');
await page.evaluate(() => { (window as unknown as FixtureWindow).__knowledgeFixture.upload = 'cancel'; });
await knowledge.getByRole('button', { name: '从电脑上传', exact: true }).click();
await expect(knowledge.getByRole('button', { name: '从电脑上传', exact: true })).toBeEnabled();
await expect(knowledge.getByRole('article')).toHaveCount(0);
expect((await calls(page)).filter(call => call.input?.operation === 'processKnowledge')).toEqual([]);
await page.evaluate(() => { (window as unknown as FixtureWindow).__knowledgeFixture.upload = 'fail'; });
await knowledge.getByRole('button', { name: '从电脑上传', exact: true }).click();
await expect(knowledge.getByRole('alert')).toContainText('上传服务暂不可用');
await expect(knowledge.getByRole('combobox', { name: '管理的知识库', exact: true })).toHaveValue('kb_fixturepersonal');
await expect(knowledge.getByRole('list', { name: '已选知识库', exact: true })).toContainText('科学知识');
await expect(knowledge.getByRole('list', { name: '已选知识库', exact: true })).not.toContainText('我的观察记录');
await expect(knowledge.getByRole('list', { name: '已选知识库', exact: true }).getByRole('listitem')).toHaveCount(1);
// An uncertain upload response keeps its retry attached to the same library.
await expect(knowledge.getByRole('combobox', { name: '管理的知识库', exact: true })).toBeDisabled();
await expect(knowledge.getByRole('button', { name: '新建知识库', exact: true })).toBeDisabled();
const failedUpload = (await calls(page)).filter(call => call.path.endsWith('/knowledge/pick')).at(-1)!;
await page.evaluate(() => { (window as unknown as FixtureWindow).__knowledgeFixture.upload = 'cancel'; });
await knowledge.getByRole('button', { name: '重新选择同一文件并重试', exact: true }).click();
await expect(knowledge.getByRole('button', { name: '重新选择同一文件并重试', exact: true })).toBeEnabled();
await expect(knowledge.getByRole('combobox', { name: '管理的知识库', exact: true })).toBeDisabled();
const cancelledRetry = (await calls(page)).filter(call => call.path.endsWith('/knowledge/pick')).at(-1)!;
expect(cancelledRetry.input?.operation_id).toBe(failedUpload.input?.operation_id);
expect((await calls(page)).filter(call => call.method === 'PATCH' || call.input?.operation === 'processKnowledge')).toEqual([]);
await expectNoClipping(page);
await editor.getByRole('tab', { name: '介绍与提示词', exact: true }).click();
await expect(editor.getByRole('textbox', { name: '提示词', exact: true })).toHaveValue('这是我还没有保存的提示词。');
});