feat(core): support ai network search (#9357)

### What Changed?
- Add `PerplexityProvider` in backend.
- Update session prompt name if user toggle network search mode in chat panel.
- Add experimental flag for AI network search feature.
- Add unit tests and e2e tests.

Search results are streamed and appear word for word:

<div class='graphite__hidden'>
          <div>🎥 Video uploaded on Graphite:</div>
            <a href="https://app.graphite.dev/media/video/sJGviKxfE3Ap685cl5bj/56f6ec7b-4b21-405f-9612-43e083f6fb84.mov">
              <img src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/sJGviKxfE3Ap685cl5bj/56f6ec7b-4b21-405f-9612-43e083f6fb84.mov">
            </a>
          </div>
<video src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/sJGviKxfE3Ap685cl5bj/56f6ec7b-4b21-405f-9612-43e083f6fb84.mov">录屏2024-12-27 18.58.40.mov</video>

Click the little globe icon to manually turn on/off Internet search:
<div class='graphite__hidden'>
          <div>🎥 Video uploaded on Graphite:</div>
            <a href="https://app.graphite.dev/media/video/sJGviKxfE3Ap685cl5bj/778f1406-bf29-498e-a90d-7dad813392d1.mov">
              <img src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/sJGviKxfE3Ap685cl5bj/778f1406-bf29-498e-a90d-7dad813392d1.mov">
            </a>
          </div>
<video src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/sJGviKxfE3Ap685cl5bj/778f1406-bf29-498e-a90d-7dad813392d1.mov">录屏2024-12-27 19.01.16.mov</video>

When there is an image, it will automatically switch to the openai model:

<div class='graphite__hidden'>
          <div>🎥 Video uploaded on Graphite:</div>
            <a href="https://app.graphite.dev/media/video/sJGviKxfE3Ap685cl5bj/56431d8e-75e1-4d84-ab4a-b6636042cc6a.mov">
              <img src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/sJGviKxfE3Ap685cl5bj/56431d8e-75e1-4d84-ab4a-b6636042cc6a.mov">
            </a>
          </div>
<video src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/sJGviKxfE3Ap685cl5bj/56431d8e-75e1-4d84-ab4a-b6636042cc6a.mov">录屏2024-12-27 19.02.13.mov</video>
This commit is contained in:
akumatus
2025-01-09 04:00:58 +00:00
parent 4f10457815
commit 58ce86533e
49 changed files with 1274 additions and 169 deletions

View File

@@ -10,7 +10,10 @@ import {
getBlockSuiteEditorTitle,
waitForEditorLoad,
} from '@affine-test/kit/utils/page-logic';
import { clickSideBarAllPageButton } from '@affine-test/kit/utils/sidebar';
import {
clickSideBarAllPageButton,
clickSideBarUseAvatar,
} from '@affine-test/kit/utils/sidebar';
import { createLocalWorkspace } from '@affine-test/kit/utils/workspace';
import { expect, type Page } from '@playwright/test';
@@ -48,14 +51,15 @@ function getUser() {
};
}
test.skip(
() =>
!process.env.COPILOT_OPENAI_API_KEY ||
!process.env.COPILOT_FAL_API_KEY ||
process.env.COPILOT_OPENAI_API_KEY === '1' ||
process.env.COPILOT_FAL_API_KEY === '1',
'skip test if no copilot api key'
);
const isCopilotConfigured =
!!process.env.COPILOT_OPENAI_API_KEY &&
!!process.env.COPILOT_FAL_API_KEY &&
!!process.env.COPILOT_PERPLEXITY_API_KEY &&
process.env.COPILOT_OPENAI_API_KEY !== '1' &&
process.env.COPILOT_FAL_API_KEY !== '1' &&
process.env.COPILOT_PERPLEXITY_API_KEY !== '1';
test.skip(() => !isCopilotConfigured, 'skip test if no copilot api key');
test('can open chat side panel', async ({ page }) => {
await openHomePage(page);
@@ -68,13 +72,17 @@ test('can open chat side panel', async ({ page }) => {
await expect(page.getByTestId('sidebar-tab-content-chat')).toBeVisible();
});
const makeChat = async (page: Page, content: string) => {
const openChat = async (page: Page) => {
if (await page.getByTestId('sidebar-tab-chat').isHidden()) {
await page.getByTestId('right-sidebar-toggle').click({
delay: 200,
});
}
await page.getByTestId('sidebar-tab-chat').click();
};
const makeChat = async (page: Page, content: string) => {
await openChat(page);
await page.getByTestId('chat-panel-input').focus();
await page.keyboard.type(content);
await page.keyboard.press('Enter');
@@ -83,6 +91,7 @@ const makeChat = async (page: Page, content: string) => {
const clearChat = async (page: Page) => {
await page.getByTestId('chat-panel-clear').click();
await page.getByTestId('confirm-modal-confirm').click();
await page.waitForTimeout(500);
};
const collectChat = async (page: Page) => {
@@ -343,6 +352,48 @@ test.describe('chat panel', () => {
expect(editorContent).toBe('');
}
});
test('can open and close network search', async ({ page }) => {
await page.reload();
await clickSideBarAllPageButton(page);
await page.waitForTimeout(200);
await createLocalWorkspace({ name: 'test' }, page);
await clickNewPageButton(page);
await clickSideBarUseAvatar(page);
await page.getByTestId('workspace-modal-account-settings-option').click();
await page.getByTestId('experimental-features-trigger').click();
await page
.getByTestId('experimental-prompt')
.getByTestId('affine-checkbox')
.click();
await page.getByTestId('experimental-confirm-button').click();
await page.getByTestId('enable_ai_network_search').click();
await page.getByTestId('modal-close-button').click();
await openChat(page);
await page.getByTestId('chat-network-search').click();
await makeChat(page, 'hello');
let history = await collectChat(page);
expect(history[0]).toEqual({
name: 'You',
content: 'hello',
});
expect(history[1].name).toBe('AFFiNE AI');
expect(
await page.locator('chat-panel affine-link').count()
).toBeGreaterThan(0);
await clearChat(page);
expect((await collectChat(page)).length).toBe(0);
await page.getByTestId('chat-network-search').click();
await makeChat(page, 'hello');
history = await collectChat(page);
expect(history[0]).toEqual({
name: 'You',
content: 'hello',
});
expect(history[1].name).toBe('AFFiNE AI');
expect(await page.locator('chat-panel affine-link').count()).toBe(0);
});
});
test.describe('chat with block', () => {