feat(core): add ai playground feature flag and remove model switch feature flag (#12934)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added support for the "AI Playground" experimental feature, including
new settings and localization entries.

* **Refactor**
* Renamed configuration and service references from "Model Switch" to
"Playground" across the AI chat and playground interfaces.
* Updated feature flag from "enable_ai_model_switch" to
"enable_ai_playground" for consistency.

* **Bug Fixes**
* The "Model" submenu in AI chat preferences is now always visible,
simplifying menu options.

* **Chores**
  * Removed outdated Claude model options from the chat prompt.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Wu Yue 2025-06-26 17:04:57 +08:00 committed by GitHub
parent 5e193b58c0
commit 2171d1bfe2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 52 additions and 64 deletions

View File

@ -1650,10 +1650,6 @@ const CHAT_PROMPT: Omit<Prompt, 'name'> = {
'gpt-4.1', 'gpt-4.1',
'o3', 'o3',
'o4-mini', 'o4-mini',
'claude-opus-4-20250514',
'claude-sonnet-4-20250514',
'claude-3-7-sonnet-20250219',
'claude-3-5-sonnet-20241022',
'gemini-2.5-flash', 'gemini-2.5-flash',
'gemini-2.5-pro', 'gemini-2.5-pro',
'claude-opus-4@20250514', 'claude-opus-4@20250514',

View File

@ -20,8 +20,8 @@ import type {
SearchMenuConfig, SearchMenuConfig,
} from '../components/ai-chat-chips'; } from '../components/ai-chat-chips';
import type { import type {
AIModelSwitchConfig,
AINetworkSearchConfig, AINetworkSearchConfig,
AIPlaygroundConfig,
AIReasoningConfig, AIReasoningConfig,
} from '../components/ai-chat-input'; } from '../components/ai-chat-input';
import { import {
@ -224,7 +224,7 @@ export class ChatPanel extends SignalWatcher(
accessor reasoningConfig!: AIReasoningConfig; accessor reasoningConfig!: AIReasoningConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor modelSwitchConfig!: AIModelSwitchConfig; accessor playgroundConfig!: AIPlaygroundConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor appSidebarConfig!: AppSidebarConfig; accessor appSidebarConfig!: AppSidebarConfig;
@ -298,7 +298,7 @@ export class ChatPanel extends SignalWatcher(
.doc=${this.doc} .doc=${this.doc}
.networkSearchConfig=${this.networkSearchConfig} .networkSearchConfig=${this.networkSearchConfig}
.reasoningConfig=${this.reasoningConfig} .reasoningConfig=${this.reasoningConfig}
.modelSwitchConfig=${this.modelSwitchConfig} .playgroundConfig=${this.playgroundConfig}
.appSidebarConfig=${this.appSidebarConfig} .appSidebarConfig=${this.appSidebarConfig}
.searchMenuConfig=${this.searchMenuConfig} .searchMenuConfig=${this.searchMenuConfig}
.docDisplayConfig=${this.docDisplayConfig} .docDisplayConfig=${this.docDisplayConfig}
@ -437,7 +437,7 @@ export class ChatPanel extends SignalWatcher(
>` >`
: 'AFFiNE AI'} : 'AFFiNE AI'}
</div> </div>
${this.modelSwitchConfig.visible.value ${this.playgroundConfig.visible.value
? html` ? html`
<div class="chat-panel-playground" @click=${this._openPlayground}> <div class="chat-panel-playground" @click=${this._openPlayground}>
${CenterPeekIcon()} ${CenterPeekIcon()}
@ -478,7 +478,7 @@ export class ChatPanel extends SignalWatcher(
.isVisible=${this._isSidebarOpen} .isVisible=${this._isSidebarOpen}
.networkSearchConfig=${this.networkSearchConfig} .networkSearchConfig=${this.networkSearchConfig}
.reasoningConfig=${this.reasoningConfig} .reasoningConfig=${this.reasoningConfig}
.modelSwitchConfig=${this.modelSwitchConfig} .playgroundConfig=${this.playgroundConfig}
.docDisplayConfig=${this.docDisplayConfig} .docDisplayConfig=${this.docDisplayConfig}
.searchMenuConfig=${this.searchMenuConfig} .searchMenuConfig=${this.searchMenuConfig}
.trackOptions=${{ .trackOptions=${{

View File

@ -29,7 +29,6 @@ import type {
import { isCollectionChip, isDocChip, isTagChip } from '../ai-chat-chips'; import { isCollectionChip, isDocChip, isTagChip } from '../ai-chat-chips';
import type { import type {
AIChatInputContext, AIChatInputContext,
AIModelSwitchConfig,
AINetworkSearchConfig, AINetworkSearchConfig,
AIReasoningConfig, AIReasoningConfig,
} from '../ai-chat-input'; } from '../ai-chat-input';
@ -93,9 +92,6 @@ export class AIChatComposer extends SignalWatcher(
@property({ attribute: false }) @property({ attribute: false })
accessor searchMenuConfig!: SearchMenuConfig; accessor searchMenuConfig!: SearchMenuConfig;
@property({ attribute: false })
accessor modelSwitchConfig!: AIModelSwitchConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor onChatSuccess: (() => void) | undefined; accessor onChatSuccess: (() => void) | undefined;
@ -151,7 +147,6 @@ export class AIChatComposer extends SignalWatcher(
.updateContext=${this.updateContext} .updateContext=${this.updateContext}
.networkSearchConfig=${this.networkSearchConfig} .networkSearchConfig=${this.networkSearchConfig}
.reasoningConfig=${this.reasoningConfig} .reasoningConfig=${this.reasoningConfig}
.modelSwitchConfig=${this.modelSwitchConfig}
.docDisplayConfig=${this.docDisplayConfig} .docDisplayConfig=${this.docDisplayConfig}
.onChatSuccess=${this.onChatSuccess} .onChatSuccess=${this.onChatSuccess}
.trackOptions=${this.trackOptions} .trackOptions=${this.trackOptions}

View File

@ -27,7 +27,6 @@ import {
import { MAX_IMAGE_COUNT } from './const'; import { MAX_IMAGE_COUNT } from './const';
import type { import type {
AIChatInputContext, AIChatInputContext,
AIModelSwitchConfig,
AINetworkSearchConfig, AINetworkSearchConfig,
AIReasoningConfig, AIReasoningConfig,
} from './type'; } from './type';
@ -320,9 +319,6 @@ export class AIChatInput extends SignalWatcher(
@property({ attribute: false }) @property({ attribute: false })
accessor reasoningConfig!: AIReasoningConfig; accessor reasoningConfig!: AIReasoningConfig;
@property({ attribute: false })
accessor modelSwitchConfig: AIModelSwitchConfig | undefined = undefined;
@property({ attribute: false }) @property({ attribute: false })
accessor docDisplayConfig!: DocDisplayConfig; accessor docDisplayConfig!: DocDisplayConfig;
@ -442,7 +438,6 @@ export class AIChatInput extends SignalWatcher(
</div> </div>
<div class="chat-input-footer-spacer"></div> <div class="chat-input-footer-spacer"></div>
<chat-input-preference <chat-input-preference
.modelSwitchConfig=${this.modelSwitchConfig}
.session=${this.session} .session=${this.session}
.onModelChange=${this._handleModelChange} .onModelChange=${this._handleModelChange}
.modelId=${this.modelId} .modelId=${this.modelId}

View File

@ -15,8 +15,6 @@ import { ShadowlessElement } from '@blocksuite/std';
import { css, html } from 'lit'; import { css, html } from 'lit';
import { property } from 'lit/decorators.js'; import { property } from 'lit/decorators.js';
import type { AIModelSwitchConfig } from './type';
export class ChatInputPreference extends SignalWatcher( export class ChatInputPreference extends SignalWatcher(
WithDisposable(ShadowlessElement) WithDisposable(ShadowlessElement)
) { ) {
@ -51,10 +49,6 @@ export class ChatInputPreference extends SignalWatcher(
@property({ attribute: false }) @property({ attribute: false })
accessor session!: CopilotSessionType | undefined; accessor session!: CopilotSessionType | undefined;
// --------- model props start ---------
@property({ attribute: false })
accessor modelSwitchConfig: AIModelSwitchConfig | undefined = undefined;
@property({ attribute: false }) @property({ attribute: false })
accessor onModelChange: ((modelId: string) => void) | undefined; accessor onModelChange: ((modelId: string) => void) | undefined;
@ -96,22 +90,20 @@ export class ChatInputPreference extends SignalWatcher(
const searchItems = []; const searchItems = [];
// model switch // model switch
if (this.modelSwitchConfig?.visible.value) { modelItems.push(
modelItems.push( menu.subMenu({
menu.subMenu({ name: 'Model',
name: 'Model', prefix: AiOutlineIcon(),
prefix: AiOutlineIcon(), options: {
options: { items: (this.session?.optionalModels ?? []).map(modelId => {
items: (this.session?.optionalModels ?? []).map(modelId => { return menu.action({
return menu.action({ name: modelId,
name: modelId, select: () => this._onModelChange(modelId),
select: () => this._onModelChange(modelId), });
}); }),
}), },
}, })
}) );
);
}
modelItems.push( modelItems.push(
menu.toggleSwitch({ menu.toggleSwitch({

View File

@ -14,7 +14,7 @@ export interface AIReasoningConfig {
setEnabled: (state: boolean) => void; setEnabled: (state: boolean) => void;
} }
export interface AIModelSwitchConfig { export interface AIPlaygroundConfig {
visible: Signal<boolean | undefined>; visible: Signal<boolean | undefined>;
} }

View File

@ -18,8 +18,8 @@ import type { ChatPanelMessages } from '../../chat-panel/chat-panel-messages';
import { AIProvider } from '../../provider'; import { AIProvider } from '../../provider';
import type { DocDisplayConfig, SearchMenuConfig } from '../ai-chat-chips'; import type { DocDisplayConfig, SearchMenuConfig } from '../ai-chat-chips';
import type { import type {
AIModelSwitchConfig,
AINetworkSearchConfig, AINetworkSearchConfig,
AIPlaygroundConfig,
AIReasoningConfig, AIReasoningConfig,
} from '../ai-chat-input'; } from '../ai-chat-input';
import { import {
@ -136,7 +136,7 @@ export class PlaygroundChat extends SignalWatcher(
accessor reasoningConfig!: AIReasoningConfig; accessor reasoningConfig!: AIReasoningConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor modelSwitchConfig!: AIModelSwitchConfig; accessor playgroundConfig!: AIPlaygroundConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor appSidebarConfig!: AppSidebarConfig; accessor appSidebarConfig!: AppSidebarConfig;
@ -320,7 +320,7 @@ export class PlaygroundChat extends SignalWatcher(
.isVisible=${this._isVisible} .isVisible=${this._isVisible}
.networkSearchConfig=${this.networkSearchConfig} .networkSearchConfig=${this.networkSearchConfig}
.reasoningConfig=${this.reasoningConfig} .reasoningConfig=${this.reasoningConfig}
.modelSwitchConfig=${this.modelSwitchConfig} .playgroundConfig=${this.playgroundConfig}
.docDisplayConfig=${this.docDisplayConfig} .docDisplayConfig=${this.docDisplayConfig}
.searchMenuConfig=${this.searchMenuConfig} .searchMenuConfig=${this.searchMenuConfig}
></ai-chat-composer> ></ai-chat-composer>

View File

@ -12,8 +12,8 @@ import type { AppSidebarConfig } from '../../chat-panel/chat-config';
import { AIProvider } from '../../provider'; import { AIProvider } from '../../provider';
import type { DocDisplayConfig, SearchMenuConfig } from '../ai-chat-chips'; import type { DocDisplayConfig, SearchMenuConfig } from '../ai-chat-chips';
import type { import type {
AIModelSwitchConfig,
AINetworkSearchConfig, AINetworkSearchConfig,
AIPlaygroundConfig,
AIReasoningConfig, AIReasoningConfig,
} from '../ai-chat-input'; } from '../ai-chat-input';
@ -66,7 +66,7 @@ export class PlaygroundContent extends SignalWatcher(
accessor reasoningConfig!: AIReasoningConfig; accessor reasoningConfig!: AIReasoningConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor modelSwitchConfig!: AIModelSwitchConfig; accessor playgroundConfig!: AIPlaygroundConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor appSidebarConfig!: AppSidebarConfig; accessor appSidebarConfig!: AppSidebarConfig;
@ -329,7 +329,7 @@ export class PlaygroundContent extends SignalWatcher(
.doc=${this.doc} .doc=${this.doc}
.networkSearchConfig=${this.networkSearchConfig} .networkSearchConfig=${this.networkSearchConfig}
.reasoningConfig=${this.reasoningConfig} .reasoningConfig=${this.reasoningConfig}
.modelSwitchConfig=${this.modelSwitchConfig} .playgroundConfig=${this.playgroundConfig}
.appSidebarConfig=${this.appSidebarConfig} .appSidebarConfig=${this.appSidebarConfig}
.searchMenuConfig=${this.searchMenuConfig} .searchMenuConfig=${this.searchMenuConfig}
.docDisplayConfig=${this.docDisplayConfig} .docDisplayConfig=${this.docDisplayConfig}

View File

@ -1,6 +1,6 @@
// packages/frontend/core/src/blocksuite/ai/hooks/useChatPanelConfig.ts // packages/frontend/core/src/blocksuite/ai/hooks/useChatPanelConfig.ts
import { AIModelSwitchService } from '@affine/core/modules/ai-button/services/model-switch';
import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search'; import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search';
import { AIPlaygroundService } from '@affine/core/modules/ai-button/services/playground';
import { AIReasoningService } from '@affine/core/modules/ai-button/services/reasoning'; import { AIReasoningService } from '@affine/core/modules/ai-button/services/reasoning';
import { CollectionService } from '@affine/core/modules/collection'; import { CollectionService } from '@affine/core/modules/collection';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
@ -22,7 +22,7 @@ export function useAIChatConfig() {
const searchService = framework.get(AINetworkSearchService); const searchService = framework.get(AINetworkSearchService);
const reasoningService = framework.get(AIReasoningService); const reasoningService = framework.get(AIReasoningService);
const modelSwitchService = framework.get(AIModelSwitchService); const playgroundService = framework.get(AIPlaygroundService);
const docDisplayMetaService = framework.get(DocDisplayMetaService); const docDisplayMetaService = framework.get(DocDisplayMetaService);
const workspaceService = framework.get(WorkspaceService); const workspaceService = framework.get(WorkspaceService);
const searchMenuService = framework.get(SearchMenuService); const searchMenuService = framework.get(SearchMenuService);
@ -42,8 +42,8 @@ export function useAIChatConfig() {
setEnabled: reasoningService.setEnabled, setEnabled: reasoningService.setEnabled,
}; };
const modelSwitchConfig = { const playgroundConfig = {
visible: modelSwitchService.visible, visible: playgroundService.visible,
}; };
const docDisplayConfig = { const docDisplayConfig = {
@ -130,6 +130,6 @@ export function useAIChatConfig() {
reasoningConfig, reasoningConfig,
docDisplayConfig, docDisplayConfig,
searchMenuConfig, searchMenuConfig,
modelSwitchConfig, playgroundConfig,
}; };
} }

View File

@ -47,7 +47,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
searchMenuConfig, searchMenuConfig,
networkSearchConfig, networkSearchConfig,
reasoningConfig, reasoningConfig,
modelSwitchConfig, playgroundConfig,
} = useAIChatConfig(); } = useAIChatConfig();
useEffect(() => { useEffect(() => {
@ -74,7 +74,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
chatPanelRef.current.searchMenuConfig = searchMenuConfig; chatPanelRef.current.searchMenuConfig = searchMenuConfig;
chatPanelRef.current.networkSearchConfig = networkSearchConfig; chatPanelRef.current.networkSearchConfig = networkSearchConfig;
chatPanelRef.current.reasoningConfig = reasoningConfig; chatPanelRef.current.reasoningConfig = reasoningConfig;
chatPanelRef.current.modelSwitchConfig = modelSwitchConfig; chatPanelRef.current.playgroundConfig = playgroundConfig;
chatPanelRef.current.extensions = editor.host.std chatPanelRef.current.extensions = editor.host.std
.get(ViewExtensionManagerIdentifier) .get(ViewExtensionManagerIdentifier)
.get('preview-page'); .get('preview-page');
@ -109,7 +109,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
networkSearchConfig, networkSearchConfig,
searchMenuConfig, searchMenuConfig,
reasoningConfig, reasoningConfig,
modelSwitchConfig, playgroundConfig,
]); ]);
return <div className={styles.root} ref={containerRef} />; return <div className={styles.root} ref={containerRef} />;

View File

@ -7,8 +7,8 @@ import { FeatureFlagService } from '../feature-flag';
import { GlobalStateService } from '../storage'; import { GlobalStateService } from '../storage';
import { AIButtonProvider } from './provider/ai-button'; import { AIButtonProvider } from './provider/ai-button';
import { AIButtonService } from './services/ai-button'; import { AIButtonService } from './services/ai-button';
import { AIModelSwitchService } from './services/model-switch';
import { AINetworkSearchService } from './services/network-search'; import { AINetworkSearchService } from './services/network-search';
import { AIPlaygroundService } from './services/playground';
import { AIReasoningService } from './services/reasoning'; import { AIReasoningService } from './services/reasoning';
export const configureAIButtonModule = (framework: Framework) => { export const configureAIButtonModule = (framework: Framework) => {
@ -28,6 +28,6 @@ export function configureAIReasoningModule(framework: Framework) {
framework.service(AIReasoningService, [GlobalStateService]); framework.service(AIReasoningService, [GlobalStateService]);
} }
export function configureAIModelSwitchModule(framework: Framework) { export function configureAIPlaygroundModule(framework: Framework) {
framework.service(AIModelSwitchService, [FeatureFlagService]); framework.service(AIPlaygroundService, [FeatureFlagService]);
} }

View File

@ -6,7 +6,7 @@ import { Service } from '@toeverything/infra';
import type { FeatureFlagService } from '../../feature-flag'; import type { FeatureFlagService } from '../../feature-flag';
export class AIModelSwitchService extends Service { export class AIPlaygroundService extends Service {
constructor(private readonly featureFlagService: FeatureFlagService) { constructor(private readonly featureFlagService: FeatureFlagService) {
super(); super();
@ -22,5 +22,5 @@ export class AIModelSwitchService extends Service {
visible: Signal<boolean | undefined>; visible: Signal<boolean | undefined>;
private readonly _visible$ = private readonly _visible$ =
this.featureFlagService.flags.enable_ai_model_switch.$; this.featureFlagService.flags.enable_ai_playground.$;
} }

View File

@ -26,7 +26,7 @@ export const AFFINE_FLAGS = {
configurable: false, configurable: false,
defaultState: true, defaultState: true,
}, },
enable_ai_model_switch: { enable_ai_playground: {
category: 'affine', category: 'affine',
displayName: displayName:
'com.affine.settings.workspace.experimental-features.enable-ai-model-switch.name', 'com.affine.settings.workspace.experimental-features.enable-ai-model-switch.name',

View File

@ -3,8 +3,8 @@ import { type Framework } from '@toeverything/infra';
import { import {
configureAIButtonModule, configureAIButtonModule,
configureAIModelSwitchModule,
configureAINetworkSearchModule, configureAINetworkSearchModule,
configureAIPlaygroundModule,
configureAIReasoningModule, configureAIReasoningModule,
} from './ai-button'; } from './ai-button';
import { configureAppSidebarModule } from './app-sidebar'; import { configureAppSidebarModule } from './app-sidebar';
@ -107,7 +107,7 @@ export function configureCommonModules(framework: Framework) {
configureCommonGlobalStorageImpls(framework); configureCommonGlobalStorageImpls(framework);
configureAINetworkSearchModule(framework); configureAINetworkSearchModule(framework);
configureAIReasoningModule(framework); configureAIReasoningModule(framework);
configureAIModelSwitchModule(framework); configureAIPlaygroundModule(framework);
configureAIButtonModule(framework); configureAIButtonModule(framework);
configureTemplateDocModule(framework); configureTemplateDocModule(framework);
configureBlobManagementModule(framework); configureBlobManagementModule(framework);

View File

@ -5692,6 +5692,14 @@ export function useAFFiNEI18N(): {
* `Enable or disable AI model switch feature.` * `Enable or disable AI model switch feature.`
*/ */
["com.affine.settings.workspace.experimental-features.enable-ai-model-switch.description"](): string; ["com.affine.settings.workspace.experimental-features.enable-ai-model-switch.description"](): string;
/**
* `Enable AI Playground`
*/
["com.affine.settings.workspace.experimental-features.enable-ai-playground.name"](): string;
/**
* `Enable or disable AI playground feature.`
*/
["com.affine.settings.workspace.experimental-features.enable-ai-playground.description"](): string;
/** /**
* `Database Full Width` * `Database Full Width`
*/ */

View File

@ -1423,6 +1423,8 @@
"com.affine.settings.workspace.experimental-features.enable-ai-network-search.description": "Enable or disable AI Network Search feature.", "com.affine.settings.workspace.experimental-features.enable-ai-network-search.description": "Enable or disable AI Network Search feature.",
"com.affine.settings.workspace.experimental-features.enable-ai-model-switch.name": "Enable AI Model Switch", "com.affine.settings.workspace.experimental-features.enable-ai-model-switch.name": "Enable AI Model Switch",
"com.affine.settings.workspace.experimental-features.enable-ai-model-switch.description": "Enable or disable AI model switch feature.", "com.affine.settings.workspace.experimental-features.enable-ai-model-switch.description": "Enable or disable AI model switch feature.",
"com.affine.settings.workspace.experimental-features.enable-ai-playground.name": "Enable AI Playground",
"com.affine.settings.workspace.experimental-features.enable-ai-playground.description": "Enable or disable AI playground feature.",
"com.affine.settings.workspace.experimental-features.enable-database-full-width.name": "Database Full Width", "com.affine.settings.workspace.experimental-features.enable-database-full-width.name": "Database Full Width",
"com.affine.settings.workspace.experimental-features.enable-database-full-width.description": "The database will be displayed in full-width mode.", "com.affine.settings.workspace.experimental-features.enable-database-full-width.description": "The database will be displayed in full-width mode.",
"com.affine.settings.workspace.experimental-features.enable-database-attachment-note.name": "Database Attachment Note", "com.affine.settings.workspace.experimental-features.enable-database-attachment-note.name": "Database Attachment Note",