feat(coding): add conversation archive and first-message titles

This commit is contained in:
2026-09-20 12:43:51 +08:00
parent d61226532a
commit fd0293fe3d
22 changed files with 698 additions and 69 deletions

View File

@@ -109,6 +109,7 @@ async function installCodingFirstChatHost(
};
mainGlobal.__makelorePiFirstChatE2E = state;
const now = '2026-08-24T00:00:00.000Z';
const metadata = new Map<string, Record<string, unknown>>();
const project = {
id: 'project-pi-first-chat',
name: 'PI first chat',
@@ -545,11 +546,11 @@ async function installCodingFirstChatHost(
}
if (path === `/api/coding/projects/conversations?projectId=${project.id}`) {
return respond({
conversations: featureComplete
conversations: (featureComplete
? [conversation, secondConversation, historyConversation]
: state.conversationCreated
? [conversation]
: [],
: []).map((item) => ({ ...item, ...metadata.get(item.id) })),
});
}
if (path.startsWith('/api/agent-browser/state?')) {
@@ -712,7 +713,14 @@ async function installCodingFirstChatHost(
return respond({ conversation: { ...conversation, id: 'conversation-forked', title: 'Feature UI branch' } }, 201);
}
if (/^\/api\/coding\/conversations\/[^/]+$/.test(path) && method === 'PATCH') {
return respond({ conversation: { ...conversation, ...(body?.title ? { title: body.title } : {}), unread: body?.unread === true, archivedAt: body?.archived === true ? now : null } });
const target = [conversation, secondConversation, historyConversation].find((item) => path.endsWith('/' + item.id)) ?? conversation;
const updated = { ...target, ...metadata.get(target.id),
...(body?.title ? { title: body.title } : {}),
...(typeof body?.unread === 'boolean' ? { unread: body.unread } : {}),
...(typeof body?.archived === 'boolean' ? { archivedAt: body.archived ? now : null } : {}),
};
metadata.set(target.id, updated);
return respond({ conversation: updated });
}
if (/^\/api\/coding\/interactions\/[^/]+\/respond$/.test(path) && method === 'POST') {
state.interactionAnswered = true;
@@ -770,6 +778,54 @@ async function settleSnapshot(electronApp: ElectronApplication): Promise<void> {
});
}
test('conversation menus rename, archive and restore without selecting or stopping the target', async ({ launchElectronApp }) => {
const electronApp = await launchElectronApp({ skipSetup: true });
let page = await getStableWindow(electronApp);
const connection = await page.evaluate(async () => ({
token: await window.electron.ipcRenderer.invoke('hostapi:token') as string,
baseUrl: await window.electron.ipcRenderer.invoke('hostapi:base-url') as string,
}));
await installCodingFirstChatHost(electronApp, connection, true);
await settleSnapshot(electronApp);
await disableCodingEventSource(page);
try {
await page.reload();
page = await getStableWindow(electronApp);
await page.getByTestId('ai-module-option-programming').click();
await page.evaluate(() => { window.location.hash = '/chat'; });
const sidebar = page.getByTestId('coding-conversation-sidebar');
const header = page.getByTestId('coding-conversation-header');
await expect(header).toContainText('新对话');
await sidebar.getByRole('button', { name: '对话操作Second Conversation' }).focus();
await page.keyboard.press('Enter');
await page.getByRole('menuitem', { name: '重命名', exact: true }).click();
const title = page.getByRole('textbox', { name: '对话标题' });
await expect(title).toHaveValue('Second Conversation');
await title.fill('重命名后的会话');
await title.press('Enter');
await expect(page.getByRole('dialog')).toHaveCount(0);
await expect(header).toContainText('新对话');
await sidebar.getByRole('button', { name: '对话操作:重命名后的会话' }).focus();
await page.keyboard.press('Enter');
await page.getByRole('menuitem', { name: '归档', exact: true }).click();
await expect(sidebar.getByRole('button', { name: '重命名后的会话', exact: true })).toHaveCount(0);
await sidebar.getByRole('button', { name: '已归档1' }).click();
await sidebar.getByRole('button', { name: '重命名后的会话', exact: true }).click();
await expect(page.getByRole('button', { name: '恢复对话', exact: true })).toBeVisible();
await expect(page.getByRole('button', { name: '发送', exact: true })).toHaveCount(0);
await page.getByRole('button', { name: '恢复对话', exact: true }).click();
await expect(header).toContainText('重命名后的会话');
await expect(sidebar.getByText('最近对话', { exact: true })).toBeVisible();
const requests = (await readState(electronApp)).captured;
expect(requests.some((request) => request.path.endsWith('/abort'))).toBe(false);
expect(requests.filter((request) => request.method === 'PATCH' && request.path.endsWith('/conversation-pi-second'))
.map((request) => request.body)).toEqual([
{ title: '重命名后的会话' }, { archived: true }, { archived: false },
]);
await page.screenshot({ path: 'test-results/coding-session-controls.png' });
} finally { await releaseSnapshot(electronApp); }
});
test('managed capabilities expose native xhigh and block unsupported image input', async ({ launchElectronApp }) => {
const electronApp = await launchElectronApp({ skipSetup: true });
let page = await getStableWindow(electronApp);