mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 01:49:51 +08:00
feat(core): support gemini model switch in ai (#13631)
<img width="757" height="447" alt="截屏2025-09-22 17 49 34" src="https://github.com/user-attachments/assets/bab96f45-112e-4d74-bc38-54429d8a54ab" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Subscription-aware AI model picker in chat: browse models with version and category, see active selection, switch models, and receive notifications when choosing pro models without a subscription. Selections persist across sessions. - Central AI model service wired into chat UI for consistent model selection and availability. - Changes - Streamlined AI model availability: reduced to a curated set for a more focused experience. - Context menu buttons can display supplemental info next to labels. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -8,12 +8,14 @@ export {
|
||||
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
import { GraphQLService, ServerScope, SubscriptionService } from '../cloud';
|
||||
import { FeatureFlagService } from '../feature-flag';
|
||||
import { CacheStorage, GlobalStateService } from '../storage';
|
||||
import { WorkspaceScope } from '../workspace';
|
||||
import { AIButtonProvider } from './provider/ai-button';
|
||||
import { AIButtonService } from './services/ai-button';
|
||||
import { AIDraftService } from './services/ai-draft';
|
||||
import { AIModelService } from './services/models';
|
||||
import { AINetworkSearchService } from './services/network-search';
|
||||
import { AIPlaygroundService } from './services/playground';
|
||||
import { AIReasoningService } from './services/reasoning';
|
||||
@@ -49,3 +51,13 @@ export function configureAIDraftModule(framework: Framework) {
|
||||
export function configureAIToolsConfigModule(framework: Framework) {
|
||||
framework.service(AIToolsConfigService, [GlobalStateService]);
|
||||
}
|
||||
|
||||
export function configureAIModelModule(framework: Framework) {
|
||||
framework
|
||||
.scope(ServerScope)
|
||||
.service(AIModelService, [
|
||||
GlobalStateService,
|
||||
GraphQLService,
|
||||
SubscriptionService,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { getPromptModelsQuery, SubscriptionStatus } from '@affine/graphql';
|
||||
import {
|
||||
createSignalFromObservable,
|
||||
type Signal,
|
||||
} from '@blocksuite/affine/shared/utils';
|
||||
import { signal } from '@preact/signals-core';
|
||||
import { LiveData, Service } from '@toeverything/infra';
|
||||
|
||||
import type { GraphQLService, SubscriptionService } from '../../cloud';
|
||||
import type { GlobalStateService } from '../../storage';
|
||||
|
||||
const AI_MODEL_ID_KEY = 'AIModelId';
|
||||
|
||||
export interface AIModel {
|
||||
name: string;
|
||||
id: string;
|
||||
version: string;
|
||||
category: string;
|
||||
isPro: boolean;
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
||||
export class AIModelService extends Service {
|
||||
modelId: Signal<string | undefined>;
|
||||
|
||||
models: Signal<AIModel[]> = signal([]);
|
||||
|
||||
private readonly modelId$ = LiveData.from(
|
||||
this.globalStateService.globalState.watch<string>(AI_MODEL_ID_KEY),
|
||||
undefined
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly globalStateService: GlobalStateService,
|
||||
private readonly gqlService: GraphQLService,
|
||||
private readonly subscriptionService: SubscriptionService
|
||||
) {
|
||||
super();
|
||||
|
||||
const { signal: modelId, cleanup } = createSignalFromObservable<
|
||||
string | undefined
|
||||
>(this.modelId$, undefined);
|
||||
this.modelId = modelId;
|
||||
this.disposables.push(cleanup);
|
||||
|
||||
this.init().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
resetModel = () => {
|
||||
this.globalStateService.globalState.set(AI_MODEL_ID_KEY, undefined);
|
||||
};
|
||||
|
||||
setModel = (modelId: string) => {
|
||||
const isSubscribed =
|
||||
this.subscriptionService.subscription.ai$.value?.status ===
|
||||
SubscriptionStatus.Active;
|
||||
const model = this.models.value.find(model => model.id === modelId);
|
||||
if (!isSubscribed && model?.isPro) {
|
||||
return;
|
||||
}
|
||||
this.globalStateService.globalState.set(AI_MODEL_ID_KEY, modelId);
|
||||
};
|
||||
|
||||
private readonly init = async () => {
|
||||
await this.initModels();
|
||||
|
||||
// subscribe to ai purchase status
|
||||
const sub = this.subscriptionService.subscription.ai$.subscribe(
|
||||
subscription => {
|
||||
const isSubscribed = subscription?.status === SubscriptionStatus.Active;
|
||||
const model = this.models.value.find(
|
||||
model => model.id === this.modelId.value
|
||||
);
|
||||
if (!isSubscribed && model?.isPro) {
|
||||
this.resetModel();
|
||||
}
|
||||
}
|
||||
);
|
||||
this.disposables.push(() => sub.unsubscribe());
|
||||
};
|
||||
|
||||
private readonly initModels = async (prompt?: string) => {
|
||||
const promptName = prompt || 'Chat With AFFiNE AI';
|
||||
const models = await this.getModelsByPrompt(promptName);
|
||||
if (models) {
|
||||
const { defaultModel, optionalModels, proModels } = models;
|
||||
this.models.value = optionalModels.map(model => {
|
||||
const [category] = model.name.split(' ');
|
||||
const version = model.name.slice(category.length + 1);
|
||||
return {
|
||||
name: model.name,
|
||||
id: model.id,
|
||||
version,
|
||||
category,
|
||||
isPro: proModels.some(proModel => proModel.id === model.id),
|
||||
isDefault: model.id === defaultModel,
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private readonly getModelsByPrompt = async (promptName: string) => {
|
||||
return this.gqlService
|
||||
.gql({
|
||||
query: getPromptModelsQuery,
|
||||
variables: { promptName },
|
||||
})
|
||||
.then(res => res.currentUser?.copilot?.models);
|
||||
};
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { type Framework } from '@toeverything/infra';
|
||||
import {
|
||||
configureAIButtonModule,
|
||||
configureAIDraftModule,
|
||||
configureAIModelModule,
|
||||
configureAINetworkSearchModule,
|
||||
configureAIPlaygroundModule,
|
||||
configureAIReasoningModule,
|
||||
@@ -117,6 +118,7 @@ export function configureCommonModules(framework: Framework) {
|
||||
configureAIButtonModule(framework);
|
||||
configureAIDraftModule(framework);
|
||||
configureAIToolsConfigModule(framework);
|
||||
configureAIModelModule(framework);
|
||||
configureTemplateDocModule(framework);
|
||||
configureBlobManagementModule(framework);
|
||||
configureMediaModule(framework);
|
||||
|
||||
Reference in New Issue
Block a user