diff --git a/docs/advanced-features/new-ai-plugin-usage.md b/docs/advanced-features/new-ai-plugin-usage.md index 94d57a4875..e4dafb2c2f 100644 --- a/docs/advanced-features/new-ai-plugin-usage.md +++ b/docs/advanced-features/new-ai-plugin-usage.md @@ -65,8 +65,8 @@ MCP工具管理面板允许用户: label: 'DeepSeek', value: 'https://api.deepseek.com/v1', model: [ - { label: 'deepseek-chat', value: 'deepseek-chat', maxTokens: 64000 }, - { label: 'deepseek-reasoner', value: 'deepseek-reasoner', maxTokens: 64000 } + { label: 'deepseek-chat', value: 'deepseek-chat'}, + { label: 'deepseek-reasoner', value: 'deepseek-reasoner'} ] } ] diff --git a/docs/extension-capabilities-tutorial/ai-plugin-configuration.md b/docs/extension-capabilities-tutorial/ai-plugin-configuration.md index 9c4c508ba8..90b91e934d 100644 --- a/docs/extension-capabilities-tutorial/ai-plugin-configuration.md +++ b/docs/extension-capabilities-tutorial/ai-plugin-configuration.md @@ -16,7 +16,7 @@ ### 前端代码改动 -在`tiny-engine/packages/plugins/robot/src/js/robotSetting.js`文件改动如下 +在`tiny-engine/packages/plugins/robot/src/js/useRobot.ts`文件改动如下 ![](./imgs/ai-image23.png) diff --git a/packages/plugins/robot/index.ts b/packages/plugins/robot/index.ts index 25336f8c83..ef4fba0da0 100644 --- a/packages/plugins/robot/index.ts +++ b/packages/plugins/robot/index.ts @@ -14,8 +14,10 @@ import entry from './src/Main.vue' import metaData from './meta' import './src/styles/vars.less' import '@opentiny/tiny-robot/dist/style.css' +import { RobotService } from './src/js/index' export default { ...metaData, - entry + entry, + metas: [RobotService] } diff --git a/packages/plugins/robot/src/Main.vue b/packages/plugins/robot/src/Main.vue index 16d9a720a2..91b92944ac 100644 --- a/packages/plugins/robot/src/Main.vue +++ b/packages/plugins/robot/src/Main.vue @@ -25,7 +25,6 @@ > @@ -72,9 +71,7 @@ placeholder="请输入问题或“/”唤起指令,支持粘贴文档" :clearable="true" :showWordLimit="true" - :allowFiles=" - singleAttachmentItems.length < 1 && VISUAL_MODEL.includes(selectedModel.model) && aiType === BUILD_TYPE - " + :allowFiles="singleAttachmentItems.length < 1 && isVisualModel() && aiType === AI_MODES['Builder']" uploadTooltip="支持上传1张图片" @submit="sendContent(inputContent, false)" @files-selected="handleSingleFilesSelected" @@ -94,7 +91,7 @@ @@ -122,7 +119,14 @@ import { type Component } from 'vue' import { Notify, Loading, TinyPopover, TinyDialogBox } from '@opentiny/vue' -import { useHistory, useCanvas, useModal, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register' +import { + useRobot, + useHistory, + useCanvas, + useModal, + getMetaApi, + META_SERVICE +} from '@opentiny/tiny-engine-meta-register' import { ToolbarBase } from '@opentiny/tiny-engine-common' import { TrContainer, @@ -138,17 +142,6 @@ import { IconNewSession } from '@opentiny/tiny-robot-svgs' import SchemaRenderer from '@opentiny/tiny-schema-renderer' import { utils } from '@opentiny/tiny-engine-utils' import RobotSettingPopover from './RobotSettingPopover.vue' -import { - getBlockContent, - initBlockList, - getAIModelOptions, - defaultSelectedModel, - isValidFastJsonPatch, - VISUAL_MODEL, - TALK_TYPE, - MCP_TYPE, - BUILD_TYPE -} from './js/robotSetting' import { PROMPTS } from './js/prompts' import * as jsonpatch from 'fast-json-patch' import { chatStream, checkComponentNameExists } from './js/utils' @@ -190,6 +183,15 @@ export default { }, emits: ['close-chat'], setup() { + const { + getBlockContent, + initBlockList, + getAIModelOptions, + isValidFastJsonPatch, + VISUAL_MODEL, + AI_MODES, + robotSettingState + } = useRobot() const { pageState, importSchema, setSaved } = useCanvas() const AIModelOptions = getAIModelOptions() const robotVisible = ref(false) @@ -201,7 +203,6 @@ export default { const connectedFailed = ref(false) const inputContent = ref('') const inProcesing = ref(false) - const selectedModel = ref(defaultSelectedModel) const { confirm } = useModal() const showPopover = ref(false) const searchContent = ref('') @@ -210,7 +211,7 @@ export default { const singleAttachmentItems = ref([]) const imageUrl = ref('') const MESSAGE_TIP = '已生成新的页面效果。' - const aiType = ref(TALK_TYPE) + const aiType = ref(AI_MODES['Chat']) const chatContainerRef = ref(null) const showTeleport = ref(false) const { deepClone, string2Obj, reactiveObj2String: obj2String } = utils @@ -226,7 +227,7 @@ export default { ? JSON.stringify(sessionProcess) : JSON.stringify({ foundationModel: { - ...selectedModel.value + ...robotSettingState.selectedModel }, messages: [], displayMessages: [], // 专门用来进行展示的消息,非原始消息,仅作为展示但是不作为请求的发送 @@ -260,7 +261,7 @@ export default { const sendProcess = { ...sessionProcess } const firstMessage = sendProcess.messages[0] let firstContent = firstMessage.content - if (aiType.value === BUILD_TYPE) { + if (aiType.value === AI_MODES['Builder']) { firstContent = firstMessage.content.map((item) => { if (item.type === 'text') { item.text = `[指令] ${PROMPTS}\n[知识] ${searchContent.value}\n[当前schema] ${JSON.stringify( @@ -270,7 +271,7 @@ export default { return item }) } - if (useMcpServer().isToolsEnabled && aiType.value === TALK_TYPE) { + if (useMcpServer().isToolsEnabled && aiType.value === AI_MODES['Chat']) { firstContent = `${getBlockContent()}\n${codeRules}\n${firstMessage.content[0]?.text || ''}` } @@ -313,7 +314,7 @@ export default { // 处理响应 const handleResponse = ({ id, chatMessage }: { id: string; chatMessage: any }, currentJson) => { try { - if (aiType.value === BUILD_TYPE) { + if (aiType.value === AI_MODES['Builder']) { const regex = /```json([\s\S]*?)```/ const match = chatMessage?.content.match(regex) @@ -337,7 +338,7 @@ export default { inProcesing.value = false connectedFailed.value = false } - if (aiType.value === TALK_TYPE) { + if (aiType.value === AI_MODES['Chat']) { sessionProcess.messages.push(getAiRespMessage(chatMessage?.content)) sessionProcess.displayMessages.push(getAiRespMessage(chatMessage?.content)) messages.value[messages.value.length - 1].content = chatMessage?.content @@ -353,16 +354,16 @@ export default { // 发送流式请求 const sendStreamRequest = async () => { const requestData = getSendSeesionProcess() - if (useMcpServer().isToolsEnabled && aiType.value === TALK_TYPE) { + if (useMcpServer().isToolsEnabled && aiType.value === AI_MODES['Chat']) { try { requestLoading.value = true await scrollContent() await sendMcpRequest(messages.value, { - model: selectedModel.value.model, + model: robotSettingState.selectedModel.model, headers: { - Authorization: `Bearer ${selectedModel.value.apiKey || import.meta.env.VITE_API_TOKEN}` + Authorization: `Bearer ${robotSettingState.selectedModel.apiKey || import.meta.env.VITE_API_TOKEN}` }, - baseUrl: selectedModel.value.baseUrl + baseUrl: robotSettingState.selectedModel.baseUrl }) } catch (error) { const { renderContent } = messages.value.at(-1)! @@ -458,7 +459,7 @@ export default { } }, { - Authorization: `Bearer ${selectedModel.value.apiKey || import.meta.env.VITE_API_TOKEN}` + Authorization: `Bearer ${robotSettingState.selectedModel.apiKey || import.meta.env.VITE_API_TOKEN}` } ) } @@ -499,7 +500,7 @@ export default { text } ] - if (singleAttachmentItems.value.length > 0 && aiType.value === BUILD_TYPE) { + if (singleAttachmentItems.value.length > 0 && aiType.value === AI_MODES['Builder']) { content.push({ type: 'image_url', image_url: { @@ -529,7 +530,7 @@ export default { if (chatWindowOpened.value === false) { await resizeChatWindow() } - if (!sessionProcess?.messages?.length && aiType.value !== TALK_TYPE) { + if (!sessionProcess?.messages?.length && aiType.value !== AI_MODES['Chat']) { sessionProcess?.messages.push({ role: 'system', content: [ @@ -545,7 +546,7 @@ export default { messages.value.push(message) sessionProcess?.messages.push(getSessionMessage(realContent)) sessionProcess?.displayMessages.push(message) - if (aiType.value === BUILD_TYPE && (!searchContent.value || !sessionProcess.messages?.length)) { + if (aiType.value === AI_MODES['Builder'] && (!searchContent.value || !sessionProcess.messages?.length)) { await search(realContent) } @@ -564,10 +565,10 @@ export default { // 根据localstorage初始化AI大模型 const initCurrentModel = (aiSession) => { - selectedModel.value = { + robotSettingState.selectedModel = { ...JSON.parse(aiSession)?.foundationModel } - aiType.value = JSON.parse(aiSession)?.aiType + aiType.value = JSON.parse(aiSession)?.aiType || aiType.value } const initChat = () => { @@ -614,16 +615,20 @@ export default { } const changeModel = (model) => { - if (selectedModel.value.baseUrl !== model.baseUrl || selectedModel.value !== model.model) { + if ( + robotSettingState.selectedModel.baseUrl !== model.baseUrl || + robotSettingState.selectedModel.model !== model.model + ) { confirm({ title: '切换AI大模型', message: '切换AI大模型将导致当前会话被清空,重新开启新会话,是否继续?', exec() { - selectedModel.value = { + robotSettingState.selectedModel = { label: model.label || model.model, activeName: model.activeName, baseUrl: model.baseUrl, model: model.model, + completeModel: model.completeModel, apiKey: model.apiKey } singleAttachmentItems.value = [] @@ -633,11 +638,11 @@ export default { }) } if ( - selectedModel.value.apiKey !== model.apiKey && - selectedModel.value.baseUrl === model.baseUrl && - selectedModel.value.model === model.model + robotSettingState.selectedModel.apiKey !== model.apiKey && + robotSettingState.selectedModel.baseUrl === model.baseUrl && + robotSettingState.selectedModel.model === model.model ) { - selectedModel.value.apiKey = model.apiKey + robotSettingState.selectedModel.apiKey = model.apiKey changeApiKey() } } @@ -791,6 +796,12 @@ export default { } }) + const isVisualModel = () => { + const platform = AIModelOptions.find((option) => option.value === robotSettingState.selectedModel.baseUrl) + const modelAbility = platform.model.find((item) => item.value === robotSettingState.selectedModel.model) + return modelAbility?.ability?.includes('visual') || false + } + return { chatContainerRef, robotVisible, @@ -800,7 +811,7 @@ export default { inputContent, connectedFailed, AIModelOptions, - selectedModel, + robotSettingState, showPopover, fullscreen, welcomeIcon, @@ -813,9 +824,7 @@ export default { MarkdownRenderer, requestLoading, aiType, - TALK_TYPE, - MCP_TYPE, - BUILD_TYPE, + AI_MODES, showTeleport, sendContent, endContent, @@ -830,6 +839,7 @@ export default { handleSingleFileRemove, handleSingleFileRetry, typeChange, + isVisualModel, contentRenderers, mcpDrawerPosition } diff --git a/packages/plugins/robot/src/RobotSettingPopover.vue b/packages/plugins/robot/src/RobotSettingPopover.vue index bcb3ec32cd..a7ab36b61d 100644 --- a/packages/plugins/robot/src/RobotSettingPopover.vue +++ b/packages/plugins/robot/src/RobotSettingPopover.vue @@ -3,7 +3,13 @@
设置
- + + + + @@ -50,6 +62,13 @@ + + + - - - - @@ -97,7 +103,7 @@ import { TinyTabItem, TinyAlert } from '@opentiny/vue' -import { EXISTING_MODELS, CUSTOMIZE, getAIModelOptions } from './js/robotSetting' +import { useRobot } from '@opentiny/tiny-engine-meta-register' export default { components: { @@ -111,19 +117,12 @@ export default { TinyTabItem, TinyAlert }, - props: { - typeValue: { - type: Object, - default: () => ({}) - } - }, setup(props, { emit }) { + const { EXISTING_MODELS, CUSTOMIZE, getAIModelOptions, robotSettingState } = useRobot() const robotSettingExistForm = ref(null) const robotSettingCustomizeForm = ref(null) const apiKeyTip = 'API Key 是用于身份验证和权限控制的密钥,允许开发者通过API访问云服务商提供的大模型(如通义千问、deepseek等)。' - const maxTokensTip = - '上下文长度表示模型单次响应中最多生成的token数量(包括输入和输出的总和)。若该项不设置,默认为64K。' const AIModelOptions = getAIModelOptions() const state = reactive({ @@ -133,20 +132,23 @@ export default { label: '', baseUrl: '', model: '', - maxTokens: null, + completeModel: '', apiKey: '' }, customizeFormData: { baseUrl: '', model: '', - maxTokens: null, + completeModel: '', apiKey: '' } }) const customizeFormRules = { baseUrl: [{ required: true, message: '必填', trigger: 'blur' }], - model: [{ required: true, message: '必填', trigger: 'blur' }], + apiKey: [{ required: true, message: '必填', trigger: 'blur' }] + } + + const existFormRules = { apiKey: [{ required: true, message: '必填', trigger: 'blur' }] } @@ -159,14 +161,8 @@ export default { const options = AIModelOptions.find((option) => option.value === state.existFormData.baseUrl) state.modelOptions = options?.model state.existFormData.label = options?.label - state.existFormData.model = state.modelOptions[0]?.value - state.existFormData.maxTokens = state.modelOptions[0]?.maxTokens - } - - const changeModel = () => { - state.existFormData.maxTokens = state.modelOptions.find( - (option) => option.value === state.existFormData.model - )?.maxTokens + state.existFormData.model = state.modelOptions[0]?.value || '' + state.existFormData.completeModel = state.modelOptions[0]?.value || '' } const confirm = () => { @@ -178,9 +174,6 @@ export default { } else { formData = { ...state.customizeFormData } form = robotSettingCustomizeForm - if (!formData.maxTokens) { - formData.maxTokens = 64000 - } } form.value.validate((valid) => { @@ -188,6 +181,14 @@ export default { return } + robotSettingState.selectedModel.completeModel = formData.completeModel + localStorage.setItem( + 'AICompleteModel', + JSON.stringify({ + existModel: state.existFormData.completeModel, + customizeModel: state.customizeFormData.completeModel + }) + ) emit('changeType', { activeName: state.activeName, ...formData @@ -197,11 +198,11 @@ export default { } const initFormData = () => { - const initModel = props.typeValue + const smallModel = JSON.parse(localStorage.getItem('AICompleteModel')) || null + const initModel = robotSettingState.selectedModel const data = { baseUrl: initModel.baseUrl, model: initModel.model, - maxTokens: initModel.maxTokens, apiKey: initModel.apiKey } state.activeName = initModel.activeName @@ -209,13 +210,17 @@ export default { if (state.activeName === EXISTING_MODELS) { state.existFormData = { label: initModel.label, + completeModel: smallModel?.existModel ? smallModel.existModel : initModel.completeModel, ...data } const options = AIModelOptions.find((option) => option.value === state.existFormData.baseUrl) state.modelOptions = options?.model } if (state.activeName === CUSTOMIZE) { - state.customizeFormData = data + state.customizeFormData = { ...data } + state.customizeFormData.completeModel = smallModel?.customizeModel + ? smallModel.customizeModel + : initModel.completeModel } } @@ -229,12 +234,11 @@ export default { robotSettingCustomizeForm, state, customizeFormRules, + existFormRules, confirm, closePanel, changeBaseUrl, - changeModel, - apiKeyTip, - maxTokensTip + apiKeyTip } } } diff --git a/packages/plugins/robot/src/RobotTypeSelect.vue b/packages/plugins/robot/src/RobotTypeSelect.vue index 579e1f58c5..58b711c682 100644 --- a/packages/plugins/robot/src/RobotTypeSelect.vue +++ b/packages/plugins/robot/src/RobotTypeSelect.vue @@ -1,7 +1,7 @@