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
@@ -16,6 +16,7 @@ import {
CopilotProviderService,
FalProvider,
OpenAIProvider,
PerplexityProvider,
registerCopilotProvider,
unregisterCopilotProvider,
} from '../src/plugins/copilot/providers';
@@ -41,6 +42,7 @@ import {
sse2array,
textToEventStream,
unsplashSearch,
updateCopilotSession,
} from './utils/copilot';
const test = ava as TestFn<{
@@ -63,6 +65,9 @@ test.beforeEach(async t => {
fal: {
apiKey: '1',
},
perplexity: {
apiKey: '1',
},
unsplashKey: process.env.UNSPLASH_ACCESS_KEY || '1',
},
},
@@ -91,6 +96,7 @@ test.beforeEach(async t => {
unregisterCopilotProvider(OpenAIProvider.type);
unregisterCopilotProvider(FalProvider.type);
unregisterCopilotProvider(PerplexityProvider.type);
registerCopilotProvider(MockCopilotTestProvider);
await prompt.set(promptName, 'test', [
@@ -156,6 +162,85 @@ test('should create session correctly', async t => {
}
});
test('should update session correctly', async t => {
const { app } = t.context;
const assertUpdateSession = async (
sessionId: string,
error: string,
asserter = async (x: any) => {
t.truthy(await x, error);
}
) => {
await asserter(updateCopilotSession(app, token, sessionId, promptName));
};
{
const { id: workspaceId } = await createWorkspace(app, token);
const docId = randomUUID();
const sessionId = await createCopilotSession(
app,
token,
workspaceId,
docId,
promptName
);
await assertUpdateSession(
sessionId,
'should be able to update session with cloud workspace that user can access'
);
}
{
const sessionId = await createCopilotSession(
app,
token,
randomUUID(),
randomUUID(),
promptName
);
await assertUpdateSession(
sessionId,
'should be able to update session with local workspace'
);
}
{
const aToken = (await signUp(app, 'test', 'test@affine.pro', '123456'))
.token.token;
const { id: workspaceId } = await createWorkspace(app, aToken);
const inviteId = await inviteUser(
app,
aToken,
workspaceId,
'darksky@affine.pro'
);
await acceptInviteById(app, workspaceId, inviteId, false);
const sessionId = await createCopilotSession(
app,
token,
workspaceId,
randomUUID(),
promptName
);
await assertUpdateSession(
sessionId,
'should able to update session after user have permission'
);
}
{
const sessionId = '123456';
await assertUpdateSession(sessionId, '', async x => {
await t.throwsAsync(
x,
{ instanceOf: Error },
'should not able to update invalid session id'
);
});
}
});
test('should fork session correctly', async t => {
const { app } = t.context;