From b25759c2649a072cf0e8ce21203f5187233e9572 Mon Sep 17 00:00:00 2001 From: Wu Yue Date: Mon, 22 Sep 2025 21:25:11 +0800 Subject: [PATCH] feat(core): support gemini model switch in ai (#13631) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 截屏2025-09-22 17 49 34 ## 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. --- .../components/src/context-menu/button.ts | 3 +- .../src/plugins/copilot/prompt/prompts.ts | 15 +-- .../src/blocksuite/ai/chat-panel/index.ts | 10 ++ .../ai-chat-composer/ai-chat-composer.ts | 11 ++ .../ai-chat-content/ai-chat-content.ts | 10 ++ .../components/ai-chat-input/ai-chat-input.ts | 27 ++-- .../ai-chat-input/preference-popup.ts | 120 ++++++++++++++---- .../desktop/pages/workspace/chat/index.tsx | 4 + .../pages/workspace/detail-page/tabs/chat.tsx | 5 + .../core/src/modules/ai-button/index.ts | 12 ++ .../src/modules/ai-button/services/models.ts | 112 ++++++++++++++++ packages/frontend/core/src/modules/index.ts | 2 + 12 files changed, 281 insertions(+), 50 deletions(-) create mode 100644 packages/frontend/core/src/modules/ai-button/services/models.ts diff --git a/blocksuite/affine/components/src/context-menu/button.ts b/blocksuite/affine/components/src/context-menu/button.ts index 9aef8269a..aab7bb939 100644 --- a/blocksuite/affine/components/src/context-menu/button.ts +++ b/blocksuite/affine/components/src/context-menu/button.ts @@ -193,6 +193,7 @@ export const menuButtonItems = { (config: { name: string; label?: () => TemplateResult; + info?: TemplateResult; prefix?: TemplateResult; postfix?: TemplateResult; isSelected?: boolean; @@ -211,7 +212,7 @@ export const menuButtonItems = { return html` ${config.prefix}
- ${config.label?.() ?? config.name} + ${config.label?.() ?? config.name} ${config.info}
${config.postfix ?? (config.isSelected ? DoneIcon() : undefined)} `; diff --git a/packages/backend/server/src/plugins/copilot/prompt/prompts.ts b/packages/backend/server/src/plugins/copilot/prompt/prompts.ts index 07e9d1ad5..d4e30b496 100644 --- a/packages/backend/server/src/plugins/copilot/prompt/prompts.ts +++ b/packages/backend/server/src/plugins/copilot/prompt/prompts.ts @@ -1930,16 +1930,9 @@ Now apply the \`updates\` to the \`content\`, following the intent in \`op\`, an const CHAT_PROMPT: Omit = { model: 'gemini-2.5-flash', optionalModels: [ - 'gpt-4.1', - 'gpt-5', - 'o3', - 'o4-mini', 'gemini-2.5-flash', 'gemini-2.5-pro', - 'claude-opus-4@20250514', 'claude-sonnet-4@20250514', - 'claude-3-7-sonnet@20250219', - 'claude-3-5-sonnet-v2@20241022', ], messages: [ { @@ -2099,13 +2092,7 @@ Below is the user's query. Please respond in the user's preferred language witho 'codeArtifact', 'blobRead', ], - proModels: [ - 'gemini-2.5-pro', - 'claude-opus-4@20250514', - 'claude-sonnet-4@20250514', - 'claude-3-7-sonnet@20250219', - 'claude-3-5-sonnet-v2@20241022', - ], + proModels: ['gemini-2.5-pro', 'claude-sonnet-4@20250514'], }, }; diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts index c9689b017..50ffdbbe5 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts @@ -2,6 +2,8 @@ import type { AIDraftService, AIToolsConfigService, } from '@affine/core/modules/ai-button'; +import type { AIModelService } from '@affine/core/modules/ai-button/services/models'; +import type { SubscriptionService } from '@affine/core/modules/cloud'; import type { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import type { FeatureFlagService } from '@affine/core/modules/feature-flag'; import type { PeekViewService } from '@affine/core/modules/peek-view'; @@ -129,6 +131,12 @@ export class ChatPanel extends SignalWatcher( @property({ attribute: false }) accessor peekViewService!: PeekViewService; + @property({ attribute: false }) + accessor subscriptionService!: SubscriptionService; + + @property({ attribute: false }) + accessor aiModelService!: AIModelService; + @state() accessor session: CopilotChatHistoryFragment | null | undefined; @@ -426,6 +434,8 @@ export class ChatPanel extends SignalWatcher( .aiDraftService=${this.aiDraftService} .aiToolsConfigService=${this.aiToolsConfigService} .peekViewService=${this.peekViewService} + .subscriptionService=${this.subscriptionService} + .aiModelService=${this.aiModelService} .onEmbeddingProgressChange=${this.onEmbeddingProgressChange} .onContextChange=${this.onContextChange} .width=${this.sidebarWidth} diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-composer/ai-chat-composer.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-composer/ai-chat-composer.ts index 2ec606390..c40825251 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-composer/ai-chat-composer.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-composer/ai-chat-composer.ts @@ -4,6 +4,8 @@ import type { AIDraftService, AIToolsConfigService, } from '@affine/core/modules/ai-button'; +import type { AIModelService } from '@affine/core/modules/ai-button/services/models'; +import type { SubscriptionService } from '@affine/core/modules/cloud'; import type { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import type { ContextEmbedStatus, @@ -141,6 +143,12 @@ export class AIChatComposer extends SignalWatcher( @property({ attribute: false }) accessor affineFeatureFlagService!: FeatureFlagService; + @property({ attribute: false }) + accessor subscriptionService!: SubscriptionService; + + @property({ attribute: false }) + accessor aiModelService!: AIModelService; + @state() accessor chips: ChatChip[] = []; @@ -189,6 +197,9 @@ export class AIChatComposer extends SignalWatcher( .affineFeatureFlagService=${this.affineFeatureFlagService} .aiDraftService=${this.aiDraftService} .aiToolsConfigService=${this.aiToolsConfigService} + .notificationService=${this.notificationService} + .subscriptionService=${this.subscriptionService} + .aiModelService=${this.aiModelService} .portalContainer=${this.portalContainer} .onChatSuccess=${this.onChatSuccess} .trackOptions=${this.trackOptions} diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-content/ai-chat-content.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-content/ai-chat-content.ts index 2e51e465d..bf7e40e56 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-content/ai-chat-content.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-content/ai-chat-content.ts @@ -3,6 +3,8 @@ import type { AIToolsConfigService, } from '@affine/core/modules/ai-button'; import type { AIDraftState } from '@affine/core/modules/ai-button/services/ai-draft'; +import type { AIModelService } from '@affine/core/modules/ai-button/services/models'; +import type { SubscriptionService } from '@affine/core/modules/cloud'; import type { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import type { FeatureFlagService } from '@affine/core/modules/feature-flag'; import type { PeekViewService } from '@affine/core/modules/peek-view'; @@ -167,6 +169,9 @@ export class AIChatContent extends SignalWatcher( @property({ attribute: false }) accessor aiToolsConfigService!: AIToolsConfigService; + @property({ attribute: false }) + accessor aiModelService!: AIModelService; + @property({ attribute: false }) accessor onEmbeddingProgressChange: | ((count: Record) => void) @@ -184,6 +189,9 @@ export class AIChatContent extends SignalWatcher( @property({ attribute: false }) accessor peekViewService!: PeekViewService; + @property({ attribute: false }) + accessor subscriptionService!: SubscriptionService; + @state() accessor chatContextValue: ChatContextValue = DEFAULT_CHAT_CONTEXT_VALUE; @@ -462,6 +470,8 @@ export class AIChatContent extends SignalWatcher( .notificationService=${this.notificationService} .aiDraftService=${this.aiDraftService} .aiToolsConfigService=${this.aiToolsConfigService} + .subscriptionService=${this.subscriptionService} + .aiModelService=${this.aiModelService} .trackOptions=${{ where: 'chat-panel', control: 'chat-send', diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-input/ai-chat-input.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-input/ai-chat-input.ts index 1a4134c0b..51e546d42 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-input/ai-chat-input.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-input/ai-chat-input.ts @@ -2,12 +2,15 @@ import type { AIDraftService, AIToolsConfigService, } from '@affine/core/modules/ai-button'; +import type { AIModelService } from '@affine/core/modules/ai-button/services/models'; +import type { SubscriptionService } from '@affine/core/modules/cloud'; import type { FeatureFlagService } from '@affine/core/modules/feature-flag'; import type { CopilotChatHistoryFragment } from '@affine/graphql'; import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; import type { EditorHost } from '@blocksuite/affine/std'; import { ShadowlessElement } from '@blocksuite/affine/std'; +import type { NotificationService } from '@blocksuite/affine-shared/services'; import { ArrowUpBigIcon, CloseIcon } from '@blocksuite/icons/lit'; import { css, html, nothing, type PropertyValues } from 'lit'; import { property, query, state } from 'lit/decorators.js'; @@ -324,9 +327,6 @@ export class AIChatInput extends SignalWatcher( @state() accessor focused = false; - @state() - accessor modelId: string | undefined = undefined; - @property({ attribute: false }) accessor chatContextValue!: AIChatInputContext; @@ -368,6 +368,15 @@ export class AIChatInput extends SignalWatcher( @property({ attribute: false }) accessor affineFeatureFlagService!: FeatureFlagService; + @property({ attribute: false }) + accessor notificationService!: NotificationService; + + @property({ attribute: false }) + accessor subscriptionService!: SubscriptionService; + + @property({ attribute: false }) + accessor aiModelService!: AIModelService; + @property({ attribute: false }) accessor isRootSession: boolean = true; @@ -516,14 +525,15 @@ export class AIChatInput extends SignalWatcher( ${status === 'transmitting' || status === 'loading' ? html`