fix: close PI core chat review gaps
This commit is contained in:
@@ -5,9 +5,16 @@ type CapturedRequest = {
|
||||
path: string;
|
||||
method: string;
|
||||
body?: Record<string, unknown>;
|
||||
byteLength?: number;
|
||||
contentType?: string;
|
||||
at: number;
|
||||
};
|
||||
|
||||
type HostConnection = {
|
||||
baseUrl: string;
|
||||
token: string;
|
||||
};
|
||||
|
||||
async function disableCodingEventSource(page: Page): Promise<void> {
|
||||
await page.addInitScript(() => {
|
||||
class LocalEventSource extends EventTarget {
|
||||
@@ -43,8 +50,11 @@ async function disableCodingEventSource(page: Page): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
async function installCodingFirstChatHost(electronApp: ElectronApplication): Promise<void> {
|
||||
await electronApp.evaluate(async () => {
|
||||
async function installCodingFirstChatHost(
|
||||
electronApp: ElectronApplication,
|
||||
hostConnection: HostConnection,
|
||||
): Promise<void> {
|
||||
await electronApp.evaluate(async (_, connection) => {
|
||||
const { ipcMain } = process.mainModule!.require('electron') as typeof import('electron');
|
||||
type MainState = {
|
||||
captured: CapturedRequest[];
|
||||
@@ -148,14 +158,71 @@ async function installCodingFirstChatHost(electronApp: ElectronApplication): Pro
|
||||
ipcMain.removeHandler('hostapi:fetch');
|
||||
ipcMain.handle('hostapi:fetch', async (
|
||||
_event,
|
||||
request: { path?: string; method?: string; body?: unknown },
|
||||
request: {
|
||||
path?: string;
|
||||
method?: string;
|
||||
headers?: Record<string, string>;
|
||||
body?: unknown;
|
||||
},
|
||||
) => {
|
||||
const path = request.path ?? '';
|
||||
const method = request.method ?? 'GET';
|
||||
const body = typeof request.body === 'string' && request.body
|
||||
? JSON.parse(request.body) as Record<string, unknown>
|
||||
: undefined;
|
||||
state.captured.push({ path, method, ...(body ? { body } : {}), at: Date.now() });
|
||||
const binaryBody = request.body instanceof ArrayBuffer
|
||||
? new Uint8Array(request.body)
|
||||
: ArrayBuffer.isView(request.body)
|
||||
? new Uint8Array(
|
||||
request.body.buffer,
|
||||
request.body.byteOffset,
|
||||
request.body.byteLength,
|
||||
)
|
||||
: undefined;
|
||||
state.captured.push({
|
||||
path,
|
||||
method,
|
||||
...(body ? { body } : {}),
|
||||
...(binaryBody ? { byteLength: binaryBody.byteLength } : {}),
|
||||
...(request.headers?.['Content-Type']
|
||||
? { contentType: request.headers['Content-Type'] }
|
||||
: {}),
|
||||
at: Date.now(),
|
||||
});
|
||||
|
||||
if (path === '/api/coding/attachments'
|
||||
|| /^\/api\/coding\/attachments\/[^/]+\/content$/.test(path)) {
|
||||
const response = await fetch(`${connection.baseUrl}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
Authorization: `Bearer ${connection.token}`,
|
||||
...(request.headers ?? {}),
|
||||
},
|
||||
...(binaryBody ? { body: binaryBody } : {}),
|
||||
});
|
||||
const responseContentType = response.headers.get('content-type') ?? '';
|
||||
if (responseContentType.includes('application/json')) {
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
status: response.status,
|
||||
ok: response.ok,
|
||||
json: await response.json(),
|
||||
transport: 'loopback',
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
status: response.status,
|
||||
ok: response.ok,
|
||||
bytes: new Uint8Array(await response.arrayBuffer()),
|
||||
contentType: responseContentType.split(';', 1)[0]?.trim(),
|
||||
transport: 'loopback',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (path === '/api/coding/projects') {
|
||||
return respond({ projects: [project], activeProjectId: project.id });
|
||||
@@ -205,7 +272,7 @@ async function installCodingFirstChatHost(electronApp: ElectronApplication): Pro
|
||||
}
|
||||
return respond({ success: false, error: `Unhandled E2E route: ${method} ${path}` }, 404);
|
||||
});
|
||||
});
|
||||
}, hostConnection);
|
||||
}
|
||||
|
||||
async function readState(electronApp: ElectronApplication): Promise<{
|
||||
@@ -239,8 +306,12 @@ test('first PI Conversation is editable under 500 ms and submits before runtime
|
||||
launchElectronApp,
|
||||
}) => {
|
||||
const electronApp = await launchElectronApp({ skipSetup: true });
|
||||
await installCodingFirstChatHost(electronApp);
|
||||
let page = await getStableWindow(electronApp);
|
||||
const hostConnection = 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, hostConnection);
|
||||
await disableCodingEventSource(page);
|
||||
|
||||
try {
|
||||
@@ -260,6 +331,15 @@ test('first PI Conversation is editable under 500 ms and submits before runtime
|
||||
));
|
||||
expect(editableMs).toBeLessThan(500);
|
||||
|
||||
const pixelPng = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
|
||||
'base64',
|
||||
);
|
||||
await page.getByTestId('coding-file-attachment-input').setInputFiles({
|
||||
name: 'pixel.png',
|
||||
mimeType: 'image/png',
|
||||
buffer: pixelPng,
|
||||
});
|
||||
await composer.fill('Build the first PI scene');
|
||||
await expect(page.getByRole('button', { name: '发送' })).toBeEnabled();
|
||||
await page.getByTestId('coding-message-composer').evaluate(
|
||||
@@ -279,7 +359,28 @@ test('first PI Conversation is editable under 500 ms and submits before runtime
|
||||
await expect(
|
||||
page.getByTestId('coding-conversation-timeline').getByText('Build the first PI scene'),
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('img', { name: '对话图片附件' })).toBeVisible();
|
||||
await expect(page.getByText('1 条消息已被本地 Agent 接收。')).toBeVisible();
|
||||
const state = await readState(electronApp);
|
||||
const uploads = state.captured.filter((request) => (
|
||||
request.path === '/api/coding/attachments' && request.method === 'POST'
|
||||
));
|
||||
expect(uploads).toHaveLength(1);
|
||||
expect(uploads[0]).toMatchObject({
|
||||
byteLength: pixelPng.byteLength,
|
||||
contentType: 'image/png',
|
||||
});
|
||||
expect(state.captured.some((request) => (
|
||||
/^\/api\/coding\/attachments\/[^/]+\/content$/.test(request.path)
|
||||
&& request.method === 'GET'
|
||||
))).toBe(true);
|
||||
const prompt = state.captured.find((request) => (
|
||||
request.path === '/api/coding/conversations/conversation-pi-first-chat/prompt'
|
||||
));
|
||||
expect(prompt?.body?.attachments).toEqual([
|
||||
{ attachmentId: expect.any(String) },
|
||||
]);
|
||||
expect(JSON.stringify(state.captured)).not.toContain(pixelPng.toString('base64'));
|
||||
} finally {
|
||||
await releaseSnapshot(electronApp);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user