* Copyright (c) 2023 - present TinyEngine Authors.
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import { removeLoading, serializeError } from '../../utils'
import useModelConfig from '../core/useConfig'
import useMcpServer from '../features/useMcp'
import type { ModeHooks } from '../../types/mode.types'
import { ChatMode } from '../../types/mode.types'
const updateToolCallState = (
tool: Record<string, unknown>,
currentMessage: any,
{ status, result }: { status?: string; result?: object | string } = {}
) => {
if (!tool.id) {
return
}
currentMessage.state ||= {}
currentMessage.state.toolCall ||= {}
currentMessage.state.toolCall[tool.id as string] = {
...(currentMessage.state.toolCall[tool.id as string] || {}),
status: status || 'running'
}
if (result) {
currentMessage.state.toolCallResults ||= {}
currentMessage.state.toolCallResults[tool.id as string] = result
}
}
const syncToolCallRenderContent = (currentMessage: any) => {
if (!currentMessage.tool_calls?.length) {
return
}
currentMessage.renderContent ||= []
if (!currentMessage.renderContent.some((item: any) => item.type === 'tool')) {
currentMessage.renderContent.push({ type: 'tool' })
}
}
* Chat 模式实现
* 特点:
* - 标准的对话模式
* - 支持 MCP 工具调用
* - 简单的 loading 处理
* - 无需 schema 更新
*/
export default function useChatMode(): ModeHooks {
const { getSelectedModelInfo } = useModelConfig()
const getApiUrl = () => 'app-center/api/chat/completions'
const getContentType = () => 'markdown'
const onConversationStart = (conversationState: any, messages: any[], apis: any) => {
const conversation = conversationState.conversations.find((item: any) => item.id === conversationState.currentId)
if (!conversation.metadata?.chatMode || conversation.metadata.chatMode !== ChatMode.Chat) {
apis.updateMetadata(conversationState.currentId, { chatMode: ChatMode.Chat })
}
removeLoading(messages)
}
const onMessageSent = () => {
}
const onBeforeRequest = async (requestParams: any) => {
const tools = await useMcpServer().getLLMTools()
const { model, baseUrl, config, capabilities } = getSelectedModelInfo()
if (!requestParams.tools && tools?.length && capabilities?.toolCalling !== false) {
Object.assign(requestParams, { tools })
}
requestParams.baseUrl = baseUrl
requestParams.model = model
if (capabilities?.reasoning?.extraBody) {
const extraBody = config?.enableThinking
? capabilities.reasoning.extraBody.enable
: capabilities.reasoning.extraBody.disable
if (extraBody) {
Object.assign(requestParams, extraBody)
}
}
return requestParams
}
const onStreamStart = (messages: any[]) => {
removeLoading(messages)
}
const onStreamData = (_data: object, _content: string | object, _messages: any[]) => {
}
const onRequestEnd = async (
finishReason: string,
_content: string,
messages: any[],
extraData?: Record<string, unknown>
) => {
if (finishReason === 'aborted') {
removeLoading(messages)
return
}
if (finishReason === 'error') {
removeLoading(messages)
const errorContent = serializeError(extraData?.error) || '请求失败'
messages.at(-1)!.renderContent.push({ type: 'text', content: errorContent })
}
}
const onStreamTools = (tools: Record<string, unknown>[], { currentMessage }: { currentMessage: any }) => {
tools.forEach((tool) => updateToolCallState(tool, currentMessage))
syncToolCallRenderContent(currentMessage)
}
const onBeforeCallTool = (tool: Record<string, unknown>, { currentMessage }: { currentMessage: any }) => {
updateToolCallState(tool, currentMessage)
syncToolCallRenderContent(currentMessage)
}
const onPostCallTool = (
tool: Record<string, unknown>,
toolCallResult: object | string,
toolCallStatus: string,
{ currentMessage }: { currentMessage: any }
) => {
updateToolCallState(tool, currentMessage, { status: toolCallStatus, result: toolCallResult })
syncToolCallRenderContent(currentMessage)
}
const onPostCallTools = (_toolsResult: Record<string, unknown>[], _context: { currentMessage: any }) => {
}
const onMessageProcessed = async (
_finishReason: string,
_content: string,
_messages: any[],
_context: { abortControllerMap: Record<string, AbortController> }
) => {
}
const onConversationEnd = (_conversationId: string) => {
}
return {
getApiUrl,
getContentType,
getLoadingType: () => 'loading',
onConversationStart,
onMessageSent,
onBeforeRequest,
onStreamStart,
onStreamData,
onRequestEnd,
onStreamTools,
onBeforeCallTool,
onPostCallTool,
onPostCallTools,
onMessageProcessed,
onConversationEnd
}
}