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
@@ -3,11 +3,21 @@ export { AIButtonService } from './services/ai-button';
import type { Framework } from '@toeverything/infra';
import { FeatureFlagService } from '../feature-flag';
import { GlobalStateService } from '../storage';
import { AIButtonProvider } from './provider/ai-button';
import { AIButtonService } from './services/ai-button';
import { AINetworkSearchService } from './services/network-search';
export const configureAIButtonModule = (framework: Framework) => {
framework.service(AIButtonService, container => {
return new AIButtonService(container.getOptional(AIButtonProvider));
});
};
export function configureAINetworkSearchModule(framework: Framework) {
framework.service(AINetworkSearchService, [
GlobalStateService,
FeatureFlagService,
]);
}
@@ -0,0 +1,45 @@
import {
createSignalFromObservable,
type Signal,
} from '@blocksuite/affine-shared/utils';
import { LiveData, Service } from '@toeverything/infra';
import type { FeatureFlagService } from '../../feature-flag';
import type { GlobalStateService } from '../../storage';
const AI_NETWORK_SEARCH_KEY = 'AINetworkSearch';
export class AINetworkSearchService extends Service {
constructor(
private readonly globalStateService: GlobalStateService,
private readonly featureFlagService: FeatureFlagService
) {
super();
const { signal: enabled, cleanup: enabledCleanup } =
createSignalFromObservable<boolean | undefined>(this._enabled$, false);
this.enabled = enabled;
this.disposables.push(enabledCleanup);
const { signal: visible, cleanup: visibleCleanup } =
createSignalFromObservable<boolean | undefined>(this._visible$, false);
this.visible = visible;
this.disposables.push(visibleCleanup);
}
visible: Signal<boolean | undefined>;
enabled: Signal<boolean | undefined>;
private readonly _visible$ =
this.featureFlagService.flags.enable_ai_network_search.$;
private readonly _enabled$ = LiveData.from(
this.globalStateService.globalState.watch<boolean>(AI_NETWORK_SEARCH_KEY),
false
);
setEnabled = (enabled: boolean) => {
this.globalStateService.globalState.set(AI_NETWORK_SEARCH_KEY, enabled);
};
}
@@ -16,6 +16,15 @@ export const AFFINE_FLAGS = {
configurable: true,
defaultState: true,
},
enable_ai_network_search: {
category: 'affine',
displayName:
'com.affine.settings.workspace.experimental-features.enable-ai-network-search.name',
description:
'com.affine.settings.workspace.experimental-features.enable-ai-network-search.description',
configurable: true,
defaultState: false,
},
enable_database_full_width: {
category: 'blocksuite',
bsFlag: 'enable_database_full_width',
+5 -1
View File
@@ -1,7 +1,10 @@
import { configureQuotaModule } from '@affine/core/modules/quota';
import { type Framework } from '@toeverything/infra';
import { configureAIButtonModule } from './ai-button';
import {
configureAIButtonModule,
configureAINetworkSearchModule,
} from './ai-button';
import { configureAppSidebarModule } from './app-sidebar';
import { configAtMenuConfigModule } from './at-menu-config';
import { configureCloudModule } from './cloud';
@@ -89,5 +92,6 @@ export function configureCommonModules(framework: Framework) {
configAtMenuConfigModule(framework);
configureDndModule(framework);
configureCommonGlobalStorageImpls(framework);
configureAINetworkSearchModule(framework);
configureAIButtonModule(framework);
}