feat(core): add reasoning icon button (#11941)

Close [AI-58](https://linear.app/affine-design/issue/AI-58).

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

## Summary by CodeRabbit

- **New Features**
  - Introduced a toggleable AI reasoning feature in the chat interface, allowing users to enable or disable advanced reasoning during AI chat interactions.
  - Added a new reasoning button to the chat input for quick access and control.
- **Enhancements**
  - Improved chat configuration options to include reasoning settings, providing more flexibility for AI responses.
  - Streamlined network search and image upload interactions for a smoother user experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
akumatus
2025-04-24 14:32:54 +00:00
parent fd90b2541e
commit 807cba03ee
14 changed files with 175 additions and 38 deletions

View File

@@ -134,6 +134,7 @@ declare global {
sessionId?: string; sessionId?: string;
isRootSession?: boolean; isRootSession?: boolean;
mustSearch?: boolean; mustSearch?: boolean;
reasoning?: boolean;
contexts?: { contexts?: {
docs: AIDocContextOption[]; docs: AIDocContextOption[];
files: AIFileContextOption[]; files: AIFileContextOption[];

View File

@@ -17,7 +17,10 @@ import type {
DocDisplayConfig, DocDisplayConfig,
SearchMenuConfig, SearchMenuConfig,
} from '../components/ai-chat-chips'; } 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 { type HistoryMessage } from '../components/ai-chat-messages';
import { AIProvider } from '../provider'; import { AIProvider } from '../provider';
import { extractSelectedContent } from '../utils/extract'; import { extractSelectedContent } from '../utils/extract';
@@ -197,6 +200,9 @@ export class ChatPanel extends SignalWatcher(
@property({ attribute: false }) @property({ attribute: false })
accessor networkSearchConfig!: AINetworkSearchConfig; accessor networkSearchConfig!: AINetworkSearchConfig;
@property({ attribute: false })
accessor reasoningConfig!: AIReasoningConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor appSidebarConfig!: AppSidebarConfig; accessor appSidebarConfig!: AppSidebarConfig;
@@ -415,6 +421,7 @@ export class ChatPanel extends SignalWatcher(
.onHistoryCleared=${this._updateHistory} .onHistoryCleared=${this._updateHistory}
.isVisible=${this._isSidebarOpen} .isVisible=${this._isSidebarOpen}
.networkSearchConfig=${this.networkSearchConfig} .networkSearchConfig=${this.networkSearchConfig}
.reasoningConfig=${this.reasoningConfig}
.docDisplayConfig=${this.docDisplayConfig} .docDisplayConfig=${this.docDisplayConfig}
.searchMenuConfig=${this.searchMenuConfig} .searchMenuConfig=${this.searchMenuConfig}
.trackOptions=${{ .trackOptions=${{

View File

@@ -28,6 +28,7 @@ import { isCollectionChip, isDocChip, isTagChip } from '../ai-chat-chips';
import type { import type {
AIChatInputContext, AIChatInputContext,
AINetworkSearchConfig, AINetworkSearchConfig,
AIReasoningConfig,
} from '../ai-chat-input'; } from '../ai-chat-input';
export class AIChatComposer extends SignalWatcher( export class AIChatComposer extends SignalWatcher(
@@ -81,6 +82,9 @@ export class AIChatComposer extends SignalWatcher(
@property({ attribute: false }) @property({ attribute: false })
accessor networkSearchConfig!: AINetworkSearchConfig; accessor networkSearchConfig!: AINetworkSearchConfig;
@property({ attribute: false })
accessor reasoningConfig!: AIReasoningConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor searchMenuConfig!: SearchMenuConfig; accessor searchMenuConfig!: SearchMenuConfig;
@@ -125,6 +129,7 @@ export class AIChatComposer extends SignalWatcher(
.chatContextValue=${this.chatContextValue} .chatContextValue=${this.chatContextValue}
.updateContext=${this.updateContext} .updateContext=${this.updateContext}
.networkSearchConfig=${this.networkSearchConfig} .networkSearchConfig=${this.networkSearchConfig}
.reasoningConfig=${this.reasoningConfig}
.docDisplayConfig=${this.docDisplayConfig} .docDisplayConfig=${this.docDisplayConfig}
.cleanupHistories=${this._cleanupHistories} .cleanupHistories=${this._cleanupHistories}
.onChatSuccess=${this.onChatSuccess} .onChatSuccess=${this.onChatSuccess}

View File

@@ -8,6 +8,7 @@ import {
CloseIcon, CloseIcon,
ImageIcon, ImageIcon,
PublishIcon, PublishIcon,
ThinkingIcon,
} from '@blocksuite/icons/lit'; } from '@blocksuite/icons/lit';
import { css, html, LitElement, nothing } from 'lit'; import { css, html, LitElement, nothing } from 'lit';
import { property, query, state } from 'lit/decorators.js'; import { property, query, state } from 'lit/decorators.js';
@@ -30,7 +31,11 @@ import {
isTagChip, isTagChip,
} from '../ai-chat-chips/utils'; } from '../ai-chat-chips/utils';
import type { ChatMessage } from '../ai-chat-messages'; import type { ChatMessage } from '../ai-chat-messages';
import type { AIChatInputContext, AINetworkSearchConfig } from './type'; import type {
AIChatInputContext,
AINetworkSearchConfig,
AIReasoningConfig,
} from './type';
const MaximumImageCount = 32; const MaximumImageCount = 32;
@@ -241,6 +246,9 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
@property({ attribute: false }) @property({ attribute: false })
accessor networkSearchConfig!: AINetworkSearchConfig; accessor networkSearchConfig!: AINetworkSearchConfig;
@property({ attribute: false })
accessor reasoningConfig!: AIReasoningConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor docDisplayConfig!: DocDisplayConfig; accessor docDisplayConfig!: DocDisplayConfig;
@@ -259,16 +267,12 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
private get _isNetworkActive() { private get _isNetworkActive() {
return ( return (
!!this.networkSearchConfig.visible.value && !!this.networkSearchConfig.visible.value &&
!!this.networkSearchConfig.enabled.value && !!this.networkSearchConfig.enabled.value
!this._isNetworkDisabled
); );
} }
private get _isNetworkDisabled() { private get _isReasoningActive() {
return ( return !!this.reasoningConfig.enabled.value;
!!this.chatContextValue.images.length ||
!!this.chips.filter(chip => chip.state === 'finished').length
);
} }
private get _isClearDisabled() { private get _isClearDisabled() {
@@ -299,7 +303,6 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
const { images, status } = this.chatContextValue; const { images, status } = this.chatContextValue;
const hasImages = images.length > 0; const hasImages = images.length > 0;
const maxHeight = hasImages ? 272 + 2 : 200 + 2; const maxHeight = hasImages ? 272 + 2 : 200 + 2;
const uploadDisabled = this._isNetworkActive;
return html` <div return html` <div
class="chat-panel-input" class="chat-panel-input"
data-if-focused=${this.focused} data-if-focused=${this.focused}
@@ -364,7 +367,6 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
<div <div
class="chat-network-search" class="chat-network-search"
data-testid="chat-network-search" data-testid="chat-network-search"
aria-disabled=${this._isNetworkDisabled}
data-active=${this._isNetworkActive} data-active=${this._isNetworkActive}
@click=${this._toggleNetworkSearch} @click=${this._toggleNetworkSearch}
@pointerdown=${stopPropagation} @pointerdown=${stopPropagation}
@@ -373,12 +375,20 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
</div> </div>
` `
: nothing} : nothing}
<div
class="chat-network-search"
data-testid="chat-reasoning"
data-active=${this._isReasoningActive}
@click=${this._toggleReasoning}
@pointerdown=${stopPropagation}
>
${ThinkingIcon()}
</div>
${images.length < MaximumImageCount ${images.length < MaximumImageCount
? html`<div ? html`<div
data-testid="chat-panel-input-image-upload" data-testid="chat-panel-input-image-upload"
class="image-upload" class="image-upload"
aria-disabled=${uploadDisabled} @click=${this._uploadImageFiles}
@click=${uploadDisabled ? undefined : this._uploadImageFiles}
> >
${ImageIcon()} ${ImageIcon()}
</div>` </div>`
@@ -457,13 +467,18 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (this._isNetworkDisabled) {
return;
}
const enable = this.networkSearchConfig.enabled.value; const enable = this.networkSearchConfig.enabled.value;
this.networkSearchConfig.setEnabled(!enable); this.networkSearchConfig.setEnabled(!enable);
}; };
private readonly _toggleReasoning = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const enable = this.reasoningConfig.enabled.value;
this.reasoningConfig.setEnabled(!enable);
};
private _addImages(images: File[]) { private _addImages(images: File[]) {
const oldImages = this.chatContextValue.images; const oldImages = this.chatContextValue.images;
this.updateContext({ this.updateContext({
@@ -545,6 +560,7 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
where: this.trackOptions.where, where: this.trackOptions.where,
control: this.trackOptions.control, control: this.trackOptions.control,
mustSearch: this._isNetworkActive, mustSearch: this._isNetworkActive,
reasoning: this._isReasoningActive,
}); });
for await (const text of stream) { for await (const text of stream) {

View File

@@ -9,6 +9,11 @@ export interface AINetworkSearchConfig {
setEnabled: (state: boolean) => void; setEnabled: (state: boolean) => void;
} }
export interface AIReasoningConfig {
enabled: Signal<boolean | undefined>;
setEnabled: (state: boolean) => void;
}
// TODO: remove this type // TODO: remove this type
export type AIChatInputContext = { export type AIChatInputContext = {
messages: HistoryMessage[]; messages: HistoryMessage[];

View File

@@ -28,7 +28,10 @@ import type {
DocDisplayConfig, DocDisplayConfig,
SearchMenuConfig, SearchMenuConfig,
} from '../components/ai-chat-chips'; } from '../components/ai-chat-chips';
import type { AINetworkSearchConfig } from '../components/ai-chat-input'; import type {
AINetworkSearchConfig,
AIReasoningConfig,
} from '../components/ai-chat-input';
import type { ChatMessage } from '../components/ai-chat-messages'; import type { ChatMessage } from '../components/ai-chat-messages';
import { ChatMessagesSchema } from '../components/ai-chat-messages'; import { ChatMessagesSchema } from '../components/ai-chat-messages';
import type { TextRendererOptions } from '../components/text-renderer'; import type { TextRendererOptions } from '../components/text-renderer';
@@ -519,6 +522,7 @@ export class AIChatBlockPeekView extends LitElement {
control: 'chat-send', control: 'chat-send',
}} }}
.portalContainer=${this.parentElement} .portalContainer=${this.parentElement}
.reasoningConfig=${this.reasoningConfig}
></ai-chat-composer> ></ai-chat-composer>
</div> `; </div> `;
} }
@@ -535,6 +539,9 @@ export class AIChatBlockPeekView extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
accessor networkSearchConfig!: AINetworkSearchConfig; accessor networkSearchConfig!: AINetworkSearchConfig;
@property({ attribute: false })
accessor reasoningConfig!: AIReasoningConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor docDisplayConfig!: DocDisplayConfig; accessor docDisplayConfig!: DocDisplayConfig;
@@ -568,7 +575,8 @@ export const AIChatBlockPeekViewTemplate = (
host: EditorHost, host: EditorHost,
docDisplayConfig: DocDisplayConfig, docDisplayConfig: DocDisplayConfig,
searchMenuConfig: SearchMenuConfig, searchMenuConfig: SearchMenuConfig,
networkSearchConfig: AINetworkSearchConfig networkSearchConfig: AINetworkSearchConfig,
reasoningConfig: AIReasoningConfig
) => { ) => {
return html`<ai-chat-block-peek-view return html`<ai-chat-block-peek-view
.blockModel=${blockModel} .blockModel=${blockModel}
@@ -576,5 +584,6 @@ export const AIChatBlockPeekViewTemplate = (
.networkSearchConfig=${networkSearchConfig} .networkSearchConfig=${networkSearchConfig}
.docDisplayConfig=${docDisplayConfig} .docDisplayConfig=${docDisplayConfig}
.searchMenuConfig=${searchMenuConfig} .searchMenuConfig=${searchMenuConfig}
.reasoningConfig=${reasoningConfig}
></ai-chat-block-peek-view>`; ></ai-chat-block-peek-view>`;
}; };

View File

@@ -350,15 +350,21 @@ export class CopilotClient {
async chatText({ async chatText({
sessionId, sessionId,
messageId, messageId,
reasoning,
signal, signal,
}: { }: {
sessionId: string; sessionId: string;
messageId?: string; messageId?: string;
reasoning?: boolean;
signal?: AbortSignal; signal?: AbortSignal;
}) { }) {
let url = `/api/copilot/chat/${sessionId}`; let url = `/api/copilot/chat/${sessionId}`;
if (messageId) { const queryString = this.paramsToQueryString({
url += `?messageId=${encodeURIComponent(messageId)}`; messageId,
reasoning,
});
if (queryString) {
url += `?${queryString}`;
} }
const response = await this.fetcher(url.toString(), { signal }); const response = await this.fetcher(url.toString(), { signal });
return response.text(); return response.text();
@@ -369,15 +375,21 @@ export class CopilotClient {
{ {
sessionId, sessionId,
messageId, messageId,
reasoning,
}: { }: {
sessionId: string; sessionId: string;
messageId?: string; messageId?: string;
reasoning?: boolean;
}, },
endpoint = 'stream' endpoint = 'stream'
) { ) {
let url = `/api/copilot/chat/${sessionId}/${endpoint}`; let url = `/api/copilot/chat/${sessionId}/${endpoint}`;
if (messageId) { const queryString = this.paramsToQueryString({
url += `?messageId=${encodeURIComponent(messageId)}`; messageId,
reasoning,
});
if (queryString) {
url += `?${queryString}`;
} }
return this.eventSource(url); return this.eventSource(url);
} }
@@ -390,17 +402,27 @@ export class CopilotClient {
endpoint = 'images' endpoint = 'images'
) { ) {
let url = `/api/copilot/chat/${sessionId}/${endpoint}`; let url = `/api/copilot/chat/${sessionId}/${endpoint}`;
const queryString = this.paramsToQueryString({
if (messageId || seed) { messageId,
url += '?'; seed,
url += new URLSearchParams( });
Object.fromEntries( if (queryString) {
Object.entries({ messageId, seed }).filter( url += `?${queryString}`;
([_, v]) => v !== undefined
)
) as Record<string, string>
).toString();
} }
return this.eventSource(url); return this.eventSource(url);
} }
paramsToQueryString(params: Record<string, string | boolean | undefined>) {
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();
}
} }

View File

@@ -19,6 +19,7 @@ export type TextToTextOptions = {
workflow?: boolean; workflow?: boolean;
isRootSession?: boolean; isRootSession?: boolean;
postfix?: (text: string) => string; postfix?: (text: string) => string;
reasoning?: boolean;
}; };
export type ToImageOptions = TextToTextOptions & { export type ToImageOptions = TextToTextOptions & {
@@ -113,6 +114,7 @@ export function textToText({
retry = false, retry = false,
workflow = false, workflow = false,
postfix, postfix,
reasoning,
}: TextToTextOptions) { }: TextToTextOptions) {
let messageId: string | undefined; let messageId: string | undefined;
@@ -132,6 +134,7 @@ export function textToText({
{ {
sessionId, sessionId,
messageId, messageId,
reasoning,
}, },
workflow ? 'workflow' : undefined workflow ? 'workflow' : undefined
); );
@@ -191,6 +194,7 @@ export function textToText({
return client.chatText({ return client.chatText({
sessionId, sessionId,
messageId, messageId,
reasoning,
}); });
})(), })(),
]); ]);

View File

@@ -1,5 +1,6 @@
// packages/frontend/core/src/blocksuite/ai/hooks/useChatPanelConfig.ts // packages/frontend/core/src/blocksuite/ai/hooks/useChatPanelConfig.ts
import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search'; 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 { CollectionService } from '@affine/core/modules/collection';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
@@ -19,6 +20,7 @@ export function useAIChatConfig() {
const framework = useFramework(); const framework = useFramework();
const searchService = framework.get(AINetworkSearchService); const searchService = framework.get(AINetworkSearchService);
const reasoningService = framework.get(AIReasoningService);
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);
@@ -33,6 +35,11 @@ export function useAIChatConfig() {
setEnabled: searchService.setEnabled, setEnabled: searchService.setEnabled,
}; };
const reasoningConfig = {
enabled: reasoningService.enabled,
setEnabled: reasoningService.setEnabled,
};
const docDisplayConfig = { const docDisplayConfig = {
getIcon: (docId: string) => { getIcon: (docId: string) => {
return docDisplayMetaService.icon$(docId, { type: 'lit' }).value; return docDisplayMetaService.icon$(docId, { type: 'lit' }).value;
@@ -114,6 +121,7 @@ export function useAIChatConfig() {
return { return {
networkSearchConfig, networkSearchConfig,
reasoningConfig,
docDisplayConfig, docDisplayConfig,
searchMenuConfig, searchMenuConfig,
}; };

View File

@@ -41,8 +41,12 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
} }
}, [onLoad, ref]); }, [onLoad, ref]);
const { docDisplayConfig, searchMenuConfig, networkSearchConfig } = const {
useAIChatConfig(); docDisplayConfig,
searchMenuConfig,
networkSearchConfig,
reasoningConfig,
} = useAIChatConfig();
useEffect(() => { useEffect(() => {
if (!editor || !editor.host) return; if (!editor || !editor.host) return;
@@ -67,6 +71,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
chatPanelRef.current.docDisplayConfig = docDisplayConfig; chatPanelRef.current.docDisplayConfig = docDisplayConfig;
chatPanelRef.current.searchMenuConfig = searchMenuConfig; chatPanelRef.current.searchMenuConfig = searchMenuConfig;
chatPanelRef.current.networkSearchConfig = networkSearchConfig; chatPanelRef.current.networkSearchConfig = networkSearchConfig;
chatPanelRef.current.reasoningConfig = reasoningConfig;
chatPanelRef.current.extensions = editor.host.std chatPanelRef.current.extensions = editor.host.std
.get(ViewExtensionManagerIdentifier) .get(ViewExtensionManagerIdentifier)
.get('preview-page'); .get('preview-page');
@@ -98,6 +103,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
framework, framework,
networkSearchConfig, networkSearchConfig,
searchMenuConfig, searchMenuConfig,
reasoningConfig,
]); ]);
return <div className={styles.root} ref={containerRef} />; return <div className={styles.root} ref={containerRef} />;

View File

@@ -8,6 +8,7 @@ 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 { AINetworkSearchService } from './services/network-search'; import { AINetworkSearchService } from './services/network-search';
import { AIReasoningService } from './services/reasoning';
export const configureAIButtonModule = (framework: Framework) => { export const configureAIButtonModule = (framework: Framework) => {
framework.service(AIButtonService, container => { framework.service(AIButtonService, container => {
@@ -21,3 +22,7 @@ export function configureAINetworkSearchModule(framework: Framework) {
FeatureFlagService, FeatureFlagService,
]); ]);
} }
export function configureAIReasoningModule(framework: Framework) {
framework.service(AIReasoningService, [GlobalStateService]);
}

View File

@@ -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<boolean | undefined>(
this._enabled$,
undefined
);
this.enabled = enabled;
this.disposables.push(enabledCleanup);
}
enabled: Signal<boolean | undefined>;
private readonly _enabled$ = LiveData.from(
this.globalStateService.globalState.watch<boolean>(AI_REASONING_KEY),
undefined
);
setEnabled = (enabled: boolean) => {
this.globalStateService.globalState.set(AI_REASONING_KEY, enabled);
};
}

View File

@@ -4,6 +4,7 @@ import { type Framework } from '@toeverything/infra';
import { import {
configureAIButtonModule, configureAIButtonModule,
configureAINetworkSearchModule, configureAINetworkSearchModule,
configureAIReasoningModule,
} from './ai-button'; } from './ai-button';
import { configureAppSidebarModule } from './app-sidebar'; import { configureAppSidebarModule } from './app-sidebar';
import { configAtMenuConfigModule } from './at-menu-config'; import { configAtMenuConfigModule } from './at-menu-config';
@@ -101,6 +102,7 @@ export function configureCommonModules(framework: Framework) {
configureDndModule(framework); configureDndModule(framework);
configureCommonGlobalStorageImpls(framework); configureCommonGlobalStorageImpls(framework);
configureAINetworkSearchModule(framework); configureAINetworkSearchModule(framework);
configureAIReasoningModule(framework);
configureAIButtonModule(framework); configureAIButtonModule(framework);
configureTemplateDocModule(framework); configureTemplateDocModule(framework);
configureBlobManagementModule(framework); configureBlobManagementModule(framework);

View File

@@ -14,16 +14,29 @@ export const AIChatBlockPeekView = ({
model, model,
host, host,
}: AIChatBlockPeekViewProps) => { }: AIChatBlockPeekViewProps) => {
const { docDisplayConfig, searchMenuConfig, networkSearchConfig } = const {
useAIChatConfig(); docDisplayConfig,
searchMenuConfig,
networkSearchConfig,
reasoningConfig,
} = useAIChatConfig();
return useMemo(() => { return useMemo(() => {
const template = AIChatBlockPeekViewTemplate( const template = AIChatBlockPeekViewTemplate(
model, model,
host, host,
docDisplayConfig, docDisplayConfig,
searchMenuConfig, searchMenuConfig,
networkSearchConfig networkSearchConfig,
reasoningConfig
); );
return toReactNode(template); return toReactNode(template);
}, [model, host, docDisplayConfig, searchMenuConfig, networkSearchConfig]); }, [
model,
host,
docDisplayConfig,
searchMenuConfig,
networkSearchConfig,
reasoningConfig,
]);
}; };