diff --git a/packages/frontend/core/src/blocksuite/ai/actions/types.ts b/packages/frontend/core/src/blocksuite/ai/actions/types.ts
index 01a87e61fb..c7d07df4ad 100644
--- a/packages/frontend/core/src/blocksuite/ai/actions/types.ts
+++ b/packages/frontend/core/src/blocksuite/ai/actions/types.ts
@@ -134,6 +134,7 @@ declare global {
sessionId?: string;
isRootSession?: boolean;
mustSearch?: boolean;
+ reasoning?: boolean;
contexts?: {
docs: AIDocContextOption[];
files: AIFileContextOption[];
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 f9e59769e0..ad1105a94e 100644
--- a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts
+++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts
@@ -17,7 +17,10 @@ import type {
DocDisplayConfig,
SearchMenuConfig,
} from '../components/ai-chat-chips';
-import type { AINetworkSearchConfig } from '../components/ai-chat-input';
+import type {
+ AINetworkSearchConfig,
+ AIReasoningConfig,
+} from '../components/ai-chat-input';
import { type HistoryMessage } from '../components/ai-chat-messages';
import { AIProvider } from '../provider';
import { extractSelectedContent } from '../utils/extract';
@@ -197,6 +200,9 @@ export class ChatPanel extends SignalWatcher(
@property({ attribute: false })
accessor networkSearchConfig!: AINetworkSearchConfig;
+ @property({ attribute: false })
+ accessor reasoningConfig!: AIReasoningConfig;
+
@property({ attribute: false })
accessor appSidebarConfig!: AppSidebarConfig;
@@ -415,6 +421,7 @@ export class ChatPanel extends SignalWatcher(
.onHistoryCleared=${this._updateHistory}
.isVisible=${this._isSidebarOpen}
.networkSearchConfig=${this.networkSearchConfig}
+ .reasoningConfig=${this.reasoningConfig}
.docDisplayConfig=${this.docDisplayConfig}
.searchMenuConfig=${this.searchMenuConfig}
.trackOptions=${{
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 089accdc09..1536cdae3e 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
@@ -28,6 +28,7 @@ import { isCollectionChip, isDocChip, isTagChip } from '../ai-chat-chips';
import type {
AIChatInputContext,
AINetworkSearchConfig,
+ AIReasoningConfig,
} from '../ai-chat-input';
export class AIChatComposer extends SignalWatcher(
@@ -81,6 +82,9 @@ export class AIChatComposer extends SignalWatcher(
@property({ attribute: false })
accessor networkSearchConfig!: AINetworkSearchConfig;
+ @property({ attribute: false })
+ accessor reasoningConfig!: AIReasoningConfig;
+
@property({ attribute: false })
accessor searchMenuConfig!: SearchMenuConfig;
@@ -125,6 +129,7 @@ export class AIChatComposer extends SignalWatcher(
.chatContextValue=${this.chatContextValue}
.updateContext=${this.updateContext}
.networkSearchConfig=${this.networkSearchConfig}
+ .reasoningConfig=${this.reasoningConfig}
.docDisplayConfig=${this.docDisplayConfig}
.cleanupHistories=${this._cleanupHistories}
.onChatSuccess=${this.onChatSuccess}
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 29a3a6de62..7783648f05 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
@@ -8,6 +8,7 @@ import {
CloseIcon,
ImageIcon,
PublishIcon,
+ ThinkingIcon,
} from '@blocksuite/icons/lit';
import { css, html, LitElement, nothing } from 'lit';
import { property, query, state } from 'lit/decorators.js';
@@ -30,7 +31,11 @@ import {
isTagChip,
} from '../ai-chat-chips/utils';
import type { ChatMessage } from '../ai-chat-messages';
-import type { AIChatInputContext, AINetworkSearchConfig } from './type';
+import type {
+ AIChatInputContext,
+ AINetworkSearchConfig,
+ AIReasoningConfig,
+} from './type';
const MaximumImageCount = 32;
@@ -241,6 +246,9 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
@property({ attribute: false })
accessor networkSearchConfig!: AINetworkSearchConfig;
+ @property({ attribute: false })
+ accessor reasoningConfig!: AIReasoningConfig;
+
@property({ attribute: false })
accessor docDisplayConfig!: DocDisplayConfig;
@@ -259,16 +267,12 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
private get _isNetworkActive() {
return (
!!this.networkSearchConfig.visible.value &&
- !!this.networkSearchConfig.enabled.value &&
- !this._isNetworkDisabled
+ !!this.networkSearchConfig.enabled.value
);
}
- private get _isNetworkDisabled() {
- return (
- !!this.chatContextValue.images.length ||
- !!this.chips.filter(chip => chip.state === 'finished').length
- );
+ private get _isReasoningActive() {
+ return !!this.reasoningConfig.enabled.value;
}
private get _isClearDisabled() {
@@ -299,7 +303,6 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
const { images, status } = this.chatContextValue;
const hasImages = images.length > 0;
const maxHeight = hasImages ? 272 + 2 : 200 + 2;
- const uploadDisabled = this._isNetworkActive;
return html`
`;
}
@@ -535,6 +539,9 @@ export class AIChatBlockPeekView extends LitElement {
@property({ attribute: false })
accessor networkSearchConfig!: AINetworkSearchConfig;
+ @property({ attribute: false })
+ accessor reasoningConfig!: AIReasoningConfig;
+
@property({ attribute: false })
accessor docDisplayConfig!: DocDisplayConfig;
@@ -568,7 +575,8 @@ export const AIChatBlockPeekViewTemplate = (
host: EditorHost,
docDisplayConfig: DocDisplayConfig,
searchMenuConfig: SearchMenuConfig,
- networkSearchConfig: AINetworkSearchConfig
+ networkSearchConfig: AINetworkSearchConfig,
+ reasoningConfig: AIReasoningConfig
) => {
return html``;
};
diff --git a/packages/frontend/core/src/blocksuite/ai/provider/copilot-client.ts b/packages/frontend/core/src/blocksuite/ai/provider/copilot-client.ts
index 5bbc602b31..ddb6c0b41b 100644
--- a/packages/frontend/core/src/blocksuite/ai/provider/copilot-client.ts
+++ b/packages/frontend/core/src/blocksuite/ai/provider/copilot-client.ts
@@ -350,15 +350,21 @@ export class CopilotClient {
async chatText({
sessionId,
messageId,
+ reasoning,
signal,
}: {
sessionId: string;
messageId?: string;
+ reasoning?: boolean;
signal?: AbortSignal;
}) {
let url = `/api/copilot/chat/${sessionId}`;
- if (messageId) {
- url += `?messageId=${encodeURIComponent(messageId)}`;
+ const queryString = this.paramsToQueryString({
+ messageId,
+ reasoning,
+ });
+ if (queryString) {
+ url += `?${queryString}`;
}
const response = await this.fetcher(url.toString(), { signal });
return response.text();
@@ -369,15 +375,21 @@ export class CopilotClient {
{
sessionId,
messageId,
+ reasoning,
}: {
sessionId: string;
messageId?: string;
+ reasoning?: boolean;
},
endpoint = 'stream'
) {
let url = `/api/copilot/chat/${sessionId}/${endpoint}`;
- if (messageId) {
- url += `?messageId=${encodeURIComponent(messageId)}`;
+ const queryString = this.paramsToQueryString({
+ messageId,
+ reasoning,
+ });
+ if (queryString) {
+ url += `?${queryString}`;
}
return this.eventSource(url);
}
@@ -390,17 +402,27 @@ export class CopilotClient {
endpoint = 'images'
) {
let url = `/api/copilot/chat/${sessionId}/${endpoint}`;
-
- if (messageId || seed) {
- url += '?';
- url += new URLSearchParams(
- Object.fromEntries(
- Object.entries({ messageId, seed }).filter(
- ([_, v]) => v !== undefined
- )
- ) as Record
- ).toString();
+ const queryString = this.paramsToQueryString({
+ messageId,
+ seed,
+ });
+ if (queryString) {
+ url += `?${queryString}`;
}
return this.eventSource(url);
}
+
+ paramsToQueryString(params: Record) {
+ const queryString = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (typeof value === 'boolean') {
+ if (value) {
+ queryString.append(key, 'true');
+ }
+ } else if (typeof value === 'string') {
+ queryString.append(key, value);
+ }
+ });
+ return queryString.toString();
+ }
}
diff --git a/packages/frontend/core/src/blocksuite/ai/provider/request.ts b/packages/frontend/core/src/blocksuite/ai/provider/request.ts
index 764d479c9b..8a64e00696 100644
--- a/packages/frontend/core/src/blocksuite/ai/provider/request.ts
+++ b/packages/frontend/core/src/blocksuite/ai/provider/request.ts
@@ -19,6 +19,7 @@ export type TextToTextOptions = {
workflow?: boolean;
isRootSession?: boolean;
postfix?: (text: string) => string;
+ reasoning?: boolean;
};
export type ToImageOptions = TextToTextOptions & {
@@ -113,6 +114,7 @@ export function textToText({
retry = false,
workflow = false,
postfix,
+ reasoning,
}: TextToTextOptions) {
let messageId: string | undefined;
@@ -132,6 +134,7 @@ export function textToText({
{
sessionId,
messageId,
+ reasoning,
},
workflow ? 'workflow' : undefined
);
@@ -191,6 +194,7 @@ export function textToText({
return client.chatText({
sessionId,
messageId,
+ reasoning,
});
})(),
]);
diff --git a/packages/frontend/core/src/components/hooks/affine/use-ai-chat-config.ts b/packages/frontend/core/src/components/hooks/affine/use-ai-chat-config.ts
index a91eda7463..556cc55c90 100644
--- a/packages/frontend/core/src/components/hooks/affine/use-ai-chat-config.ts
+++ b/packages/frontend/core/src/components/hooks/affine/use-ai-chat-config.ts
@@ -1,5 +1,6 @@
// packages/frontend/core/src/blocksuite/ai/hooks/useChatPanelConfig.ts
import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search';
+import { AIReasoningService } from '@affine/core/modules/ai-button/services/reasoning';
import { CollectionService } from '@affine/core/modules/collection';
import { DocsService } from '@affine/core/modules/doc';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
@@ -19,6 +20,7 @@ export function useAIChatConfig() {
const framework = useFramework();
const searchService = framework.get(AINetworkSearchService);
+ const reasoningService = framework.get(AIReasoningService);
const docDisplayMetaService = framework.get(DocDisplayMetaService);
const workspaceService = framework.get(WorkspaceService);
const searchMenuService = framework.get(SearchMenuService);
@@ -33,6 +35,11 @@ export function useAIChatConfig() {
setEnabled: searchService.setEnabled,
};
+ const reasoningConfig = {
+ enabled: reasoningService.enabled,
+ setEnabled: reasoningService.setEnabled,
+ };
+
const docDisplayConfig = {
getIcon: (docId: string) => {
return docDisplayMetaService.icon$(docId, { type: 'lit' }).value;
@@ -114,6 +121,7 @@ export function useAIChatConfig() {
return {
networkSearchConfig,
+ reasoningConfig,
docDisplayConfig,
searchMenuConfig,
};
diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/chat.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/chat.tsx
index b3bcacabdb..d2da110d79 100644
--- a/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/chat.tsx
+++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/chat.tsx
@@ -41,8 +41,12 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
}
}, [onLoad, ref]);
- const { docDisplayConfig, searchMenuConfig, networkSearchConfig } =
- useAIChatConfig();
+ const {
+ docDisplayConfig,
+ searchMenuConfig,
+ networkSearchConfig,
+ reasoningConfig,
+ } = useAIChatConfig();
useEffect(() => {
if (!editor || !editor.host) return;
@@ -67,6 +71,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
chatPanelRef.current.docDisplayConfig = docDisplayConfig;
chatPanelRef.current.searchMenuConfig = searchMenuConfig;
chatPanelRef.current.networkSearchConfig = networkSearchConfig;
+ chatPanelRef.current.reasoningConfig = reasoningConfig;
chatPanelRef.current.extensions = editor.host.std
.get(ViewExtensionManagerIdentifier)
.get('preview-page');
@@ -98,6 +103,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
framework,
networkSearchConfig,
searchMenuConfig,
+ reasoningConfig,
]);
return ;
diff --git a/packages/frontend/core/src/modules/ai-button/index.ts b/packages/frontend/core/src/modules/ai-button/index.ts
index 2eace60e8e..576892059a 100644
--- a/packages/frontend/core/src/modules/ai-button/index.ts
+++ b/packages/frontend/core/src/modules/ai-button/index.ts
@@ -8,6 +8,7 @@ import { GlobalStateService } from '../storage';
import { AIButtonProvider } from './provider/ai-button';
import { AIButtonService } from './services/ai-button';
import { AINetworkSearchService } from './services/network-search';
+import { AIReasoningService } from './services/reasoning';
export const configureAIButtonModule = (framework: Framework) => {
framework.service(AIButtonService, container => {
@@ -21,3 +22,7 @@ export function configureAINetworkSearchModule(framework: Framework) {
FeatureFlagService,
]);
}
+
+export function configureAIReasoningModule(framework: Framework) {
+ framework.service(AIReasoningService, [GlobalStateService]);
+}
diff --git a/packages/frontend/core/src/modules/ai-button/services/reasoning.ts b/packages/frontend/core/src/modules/ai-button/services/reasoning.ts
new file mode 100644
index 0000000000..8df7e8b150
--- /dev/null
+++ b/packages/frontend/core/src/modules/ai-button/services/reasoning.ts
@@ -0,0 +1,34 @@
+import {
+ createSignalFromObservable,
+ type Signal,
+} from '@blocksuite/affine/shared/utils';
+import { LiveData, Service } from '@toeverything/infra';
+
+import type { GlobalStateService } from '../../storage';
+
+const AI_REASONING_KEY = 'AIReasoning';
+
+export class AIReasoningService extends Service {
+ constructor(private readonly globalStateService: GlobalStateService) {
+ super();
+
+ const { signal: enabled, cleanup: enabledCleanup } =
+ createSignalFromObservable(
+ this._enabled$,
+ undefined
+ );
+ this.enabled = enabled;
+ this.disposables.push(enabledCleanup);
+ }
+
+ enabled: Signal;
+
+ private readonly _enabled$ = LiveData.from(
+ this.globalStateService.globalState.watch(AI_REASONING_KEY),
+ undefined
+ );
+
+ setEnabled = (enabled: boolean) => {
+ this.globalStateService.globalState.set(AI_REASONING_KEY, enabled);
+ };
+}
diff --git a/packages/frontend/core/src/modules/index.ts b/packages/frontend/core/src/modules/index.ts
index 888366faeb..dac1223389 100644
--- a/packages/frontend/core/src/modules/index.ts
+++ b/packages/frontend/core/src/modules/index.ts
@@ -4,6 +4,7 @@ import { type Framework } from '@toeverything/infra';
import {
configureAIButtonModule,
configureAINetworkSearchModule,
+ configureAIReasoningModule,
} from './ai-button';
import { configureAppSidebarModule } from './app-sidebar';
import { configAtMenuConfigModule } from './at-menu-config';
@@ -101,6 +102,7 @@ export function configureCommonModules(framework: Framework) {
configureDndModule(framework);
configureCommonGlobalStorageImpls(framework);
configureAINetworkSearchModule(framework);
+ configureAIReasoningModule(framework);
configureAIButtonModule(framework);
configureTemplateDocModule(framework);
configureBlobManagementModule(framework);
diff --git a/packages/frontend/core/src/modules/peek-view/view/ai-chat-block-peek-view/index.tsx b/packages/frontend/core/src/modules/peek-view/view/ai-chat-block-peek-view/index.tsx
index 6c917a6537..667289b27e 100644
--- a/packages/frontend/core/src/modules/peek-view/view/ai-chat-block-peek-view/index.tsx
+++ b/packages/frontend/core/src/modules/peek-view/view/ai-chat-block-peek-view/index.tsx
@@ -14,16 +14,29 @@ export const AIChatBlockPeekView = ({
model,
host,
}: AIChatBlockPeekViewProps) => {
- const { docDisplayConfig, searchMenuConfig, networkSearchConfig } =
- useAIChatConfig();
+ const {
+ docDisplayConfig,
+ searchMenuConfig,
+ networkSearchConfig,
+ reasoningConfig,
+ } = useAIChatConfig();
+
return useMemo(() => {
const template = AIChatBlockPeekViewTemplate(
model,
host,
docDisplayConfig,
searchMenuConfig,
- networkSearchConfig
+ networkSearchConfig,
+ reasoningConfig
);
return toReactNode(template);
- }, [model, host, docDisplayConfig, searchMenuConfig, networkSearchConfig]);
+ }, [
+ model,
+ host,
+ docDisplayConfig,
+ searchMenuConfig,
+ networkSearchConfig,
+ reasoningConfig,
+ ]);
};