diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc629320c566ef96e8851abb5d74563d7b66bc7..a48442939293b8fd12f702149ad0126251ac6278 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - 抽象auth-guard区分全代码和全动,runApp提供替换auth-guard的可选参数 - 新增用户可自定义路由添加页面能力 - 支持移动端全代码模式,门户视图支持绘制插槽部件 +- 新增全局工具AI聊天 ### Change diff --git a/src/locale/en/index.ts b/src/locale/en/index.ts index af264caaed5ae7fda5d6a344f794e1cbbd3b7398..b509b7aa910b51688bd49b6ac7a525216523fd33 100644 --- a/src/locale/en/index.ts +++ b/src/locale/en/index.ts @@ -324,7 +324,7 @@ export default { auto: 'Follow system', light: 'Light', dark: 'Dark', - } + }, }, // 工具 util: { @@ -373,6 +373,29 @@ export default { missingToolbarModel: 'Missing toolbar component model', notReceivedPrompt: 'No appDataElementId received', }, + aiChartUtil: { + feedback: 'Feedback', + description: 'Description', + regardingIssue: 'Regarding the problem', + understandProblem: 'Not understanding the problem', + forgotContext: 'Forgot the previous text', + notFollowingRequire: 'Not Following Requirements', + regardingResponse: 'Regarding the effectiveness of the answer', + incorrectAswer: 'Incorrect answer', + logicalConfusion: 'Logical confusion', + poorTimeliness: 'Poor timeliness', + poorReadability: 'Poor readability', + incompleteAnswer: 'Incomplete answer', + unprofessional: 'The answer is vague and unprofessional', + report: 'Report', + pornographicVulgar: 'Pornographic Vulgar', + politicallySensitive: 'Politically sensitive', + illegalCriminal: 'Illegal crime', + discriminationPrejudice: 'Discrimination or Prejudice Answer', + violationPrivacy: 'Violation of Privacy', + contentInfringement: 'Content infringement', + placeholder: 'Please enter', + }, // runTime ...runTimeEn, // vue3Util diff --git a/src/locale/zh-CN/index.ts b/src/locale/zh-CN/index.ts index dcad276a9c19d14133c3f584d6f3a51888aaa2d1..373bf555f4bfa01ff62cddffa5255eaa63d2c72f 100644 --- a/src/locale/zh-CN/index.ts +++ b/src/locale/zh-CN/index.ts @@ -12,6 +12,8 @@ export default { retract: '收起', close: '关闭', search: '搜索', + confirm: '确认', + cancel: '取消', }, // 组件 component: { @@ -310,8 +312,8 @@ export default { themeToggling: { auto: '跟随系统', light: '亮色主题', - dark: '暗色主题' - } + dark: '暗色主题', + }, }, // 工具 util: { @@ -329,6 +331,29 @@ export default { insecureContextError: '只有在安全的情况下才允许访问摄像头。使用HTTPS或localhost而不是HTTP。', }, + aiChartUtil: { + feedback: '反馈', + description: '描述', + regardingIssue: '针对问题', + understandProblem: '不理解问题', + forgotContext: '遗忘了上文', + notFollowingRequire: '不遵循要求', + regardingResponse: '针对回答效果', + incorrectAswer: '回答错误', + logicalConfusion: '逻辑混乱', + poorTimeliness: '时效性差', + poorReadability: '可读性差', + incompleteAnswer: '回答不完整', + unprofessional: '回答笼统不专业', + report: '举报', + pornographicVulgar: '色情低俗', + politicallySensitive: '政治敏感', + illegalCriminal: '违法犯罪', + discriminationPrejudice: '歧视或偏见回答', + violationPrivacy: '侵犯隐私', + contentInfringement: '内容侵权', + placeholder: '请输入', + }, }, // 视图 view: { diff --git a/src/mob-app/main.ts b/src/mob-app/main.ts index 9f735b3c978f30e27e5818bd80641a2ae2836afc..ecde5ea3ee22a0d2aeaab4bd967dd611fc27ae4a 100644 --- a/src/mob-app/main.ts +++ b/src/mob-app/main.ts @@ -35,6 +35,7 @@ import { OverlayController, FullscreenUtil, QrcodeUtil, + AIChatUtil, } from '../util'; import { AuthGuard, DynaAuthGuard } from './guard'; @@ -132,6 +133,7 @@ export async function runApp( return app.config.globalProperties.$textFormat(value, code); }; ibiz.qrcodeUtil = new QrcodeUtil(); + ibiz.aiChatUtil = new AIChatUtil(); await ibiz.i18n.init(); diff --git a/src/util/ai-chat-util/ai-chat-util.ts b/src/util/ai-chat-util/ai-chat-util.ts new file mode 100644 index 0000000000000000000000000000000000000000..c35cbf93e9e059362a16be2b2966f5c77457c1cd --- /dev/null +++ b/src/util/ai-chat-util/ai-chat-util.ts @@ -0,0 +1,1227 @@ +/* eslint-disable no-unsafe-finally */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-shadow */ +/* eslint-disable no-prototype-builtins */ +/* eslint-disable no-unused-vars */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { h } from 'vue'; +import { AxiosProgressEvent } from 'axios'; +import { createUUID } from 'qx-util'; +import { + IAppUtil, + IAppDEACMode, + IAppDEUIActionGroupDetail, +} from '@ibiz/model-core'; +import { + IModal, + IModalData, + UtilService, + UIActionUtil, + ConfigService, + AIUtilService, + IAIToolbarItem, + IApiAIChatUtil, + SysUIActionTag, + ViewController, + IViewController, + IControlController, +} from '@ibiz-template/runtime'; +import { + StringUtil, + IBizContext, + IChatMessage, + IPortalAsyncAction, +} from '@ibiz-template/core'; +import { AIFeedback } from './ai-feedback/ai-feedback'; + +export class AIChatUtil implements IApiAIChatUtil { + private TOPIC_CHAT_PREFIX: string = 'topic'; + + private INLINE_CHAT_SUFFIX: string = 'inline'; + + private TEMP_CHAT_SUFFIX: string = 'temp'; + + private UNKOWN_CHAT_SUFFIX: string = 'unknow'; + + /** + * 获取AI聊天对象 + */ + async getAIChat(): Promise { + const module = await import('@ibiz-template-plugin/ibiz-mob-ai-chat'); + const chatInstance = module.chat || module.default.chat; + return chatInstance; + } + + /** + * 获取编辑器扩展AI聊天参数 + * @param editorParams + * @param context + * @param params + * @param data + */ + async getEditorExAIChatParams( + editorParams: IData, + context: IContext, + params: IParams, + data: IData, + deACMode: IAppDEACMode, + args: { + chatInstance: IData; + view: IViewController; + ctrl?: IControlController; + [key: string]: any; + }, + ): Promise<{ + containerOptions: IData; + topicOptions: IData; + chatOptions: IData; + }> { + // 容器参数 + const containerOptions: IData = {}; + // 容器的自动关闭模式 + if (editorParams.autoclose) { + try { + containerOptions.autoClose = JSON.parse(editorParams.autoclose); + } catch (error) { + ibiz.log.error(error); + } + } + // 窗口的打开模式(禁用窗口最小化不识别) + if (editorParams.openmode) + containerOptions.openMode = editorParams.openmode; + + // 会话参数 + const topicOptions: IData = {}; + + // 聊天参数 + const chatOptions: IData = {}; + // 编辑器参数srfaiappendcurdata,是否传入对象参数,用于历史查询传参 + if (editorParams.srfaiappendcurdata) { + chatOptions.appendCurData = + editorParams.srfaiappendcurdata === 'true' ? data : undefined; + } + // 编辑器参数srfaiappendcurcontent,传入编辑内容作为用户消息,获取历史数据后附加 + chatOptions.appendCurContent = editorParams.srfaiappendcurcontent + ? StringUtil.fill( + editorParams.srfaiappendcurcontent, + context, + params, + data, + ) + : undefined; + // 编辑器参数autoquestion,自动提问 + chatOptions.autoQuestion = editorParams.autoquestion !== 'false'; + // 编辑器参数autofill,自动填充 + chatOptions.autoFill = editorParams.autofill === 'true'; + // 编辑器参数srfaiappendresource,附加资源数据 + chatOptions.appendCurResource = editorParams.srfaiappendresource + ? StringUtil.fill(editorParams.srfaiappendresource, context, params, data) + : undefined; + // 编辑器参数srfmode数据 + if (editorParams.srfmode) { + chatOptions.srfMode = editorParams.srfmode; + } + // 编辑器参数srfenableaiagentchange设置智能体是否可切换 + let enableAIAgentChange = ibiz.config.common.enableAIAgentChange; + if (editorParams.srfenableaiagentchange) { + enableAIAgentChange = editorParams.srfenableaiagentchange === 'true'; + } + chatOptions.enableAIAgentChange = enableAIAgentChange; + // 编辑器参数srfaiagent设置默认智能体 + if (editorParams.srfaiagent) { + chatOptions.activeAIAgentID = editorParams.srfaiagent; + } + // 智能体选项 + const aiAgentlist: any = await this.getAIAgentList( + context, + params, + editorParams, + ); + chatOptions.aiAgentlist = aiAgentlist; + // 扩展工具栏 + if (deACMode) { + const { + contentToolbarItems, + footerToolbarItems, + questionToolbarItems, + otherToolbarItems, + } = this.calcAiToolbarItemsByAc(deACMode); + chatOptions.contentToolbarItems = contentToolbarItems; + chatOptions.footerToolbarItems = footerToolbarItems; + chatOptions.questionToolbarItems = questionToolbarItems; + chatOptions.otherToolbarItems = otherToolbarItems; + } + // 会话ID + const sessionid = this.getChatSessionId('TEMP'); + chatOptions.sessionid = sessionid; + + let id: string = ''; + let abortController: AbortController; + let asyncacitonid: string = ''; + const { chatInstance, view, ctrl } = args; + + // 请求历史 + chatOptions.history = async ( + ctx: IContext, + param: IParams, + other: IParams, + ) => { + const deService = await ibiz.hub + .getApp(ctx.srfappid) + .deService.getService(ctx, other.appDataEntityId); + const historyRequestData: IData = {}; + if (other.appendCurData) { + Object.assign(historyRequestData, { + ...other.appendCurData, + }); + } + if (other.sessionid) { + Object.assign(historyRequestData, { + sessionid: other.sessionid, + }); + } + if (other.srfaiagent) { + Object.assign(historyRequestData, { + srfaiagent: other.srfaiagent, + }); + } + if (other.srfmode) { + Object.assign(historyRequestData, { + mode: other.srfmode, + }); + } + const result = await deService.aiChatHistory( + ctx, + param, + historyRequestData, + ); + if (result.data && Array.isArray(result.data)) { + let preMsg: IData | undefined; + result.data.forEach(item => { + if (item.role === 'TOOL') { + if (preMsg && item.content) { + chatInstance.aiChat!.updateRecommendPrompt( + preMsg as any, + item.content, + ); + } + } else { + const msg = { + messageid: createUUID(), + state: 30, + type: 'DEFAULT', + role: item.role, + content: item.content, + completed: true, + } as const; + preMsg = msg; + chatInstance.aiChat!.addMessage(msg); + } + }); + } + return true; + }; + // 提问 + chatOptions.question = async ( + aiChat: any, + ctx: IContext, + param: IParams, + other: IParams, + arr: IChatMessage[], + sessionid: string, + srfaiagent: string | undefined, + srfmode: string | undefined, + ) => { + id = createUUID(); + abortController = new AbortController(); + const deService = await ibiz.hub + .getApp(ctx.srfappid) + .deService.getService(ctx, other.appDataEntityId); + try { + const questionRequestData: IData = { + messages: arr, + sessionid, + }; + if (srfaiagent) { + questionRequestData.srfaiagent = srfaiagent; + } + if (srfmode) { + questionRequestData.mode = srfmode; + } + await deService.aiChatSse( + (msg: IPortalAsyncAction) => { + // 20: 持续回答中,消息会持续推送。同一个消息 id 会显示在同一个框内 + if (msg.actionstate === 20 && msg.actionresult) { + asyncacitonid = msg.asyncacitonid; + aiChat.addMessage({ + messageid: id, + state: msg.actionstate, + type: 'DEFAULT', + role: 'ASSISTANT', + content: msg.actionresult as string, + status: 'pending', + }); + } + // 30: 回答完成,包含具体所有消息内容。直接覆盖之前的临时拼接消息 + else if (msg.actionstate === 30 && msg.actionresult) { + const result = JSON.parse(msg.actionresult as string); + const choices = result.choices; + if (choices && choices.length > 0) { + aiChat.replaceMessage({ + messageid: id, + state: msg.actionstate, + type: 'DEFAULT', + role: 'ASSISTANT', + content: choices[0].content || '', + realmessageid: choices[0].messageid, + status: 'sent', + }); + } + } + // 40: 回答报错,展示错误信息 + else if (msg.actionstate === 40) { + aiChat.replaceMessage({ + messageid: id, + state: msg.actionstate, + type: 'ERROR', + role: 'ASSISTANT', + content: msg.actionresult as string, + status: 'failed', + }); + } + }, + abortController, + ctx, + param, + { ...questionRequestData }, + ); + } catch (error) { + aiChat.replaceMessage({ + messageid: id, + state: 40, + type: 'ERROR', + role: 'ASSISTANT', + content: (error as IData).message || ibiz.i18n.t('app.aiError'), + status: 'failed', + }); + abortController?.abort(); + } finally { + // 标记当前消息已经交互完成 + aiChat.completeMessage(id, true); + return true; + } + }; + // 中止提问 + chatOptions.abortQuestion = async ( + aiChat: any, + ctx: IContext, + param: IParams, + other: IParams, + ) => { + abortController?.abort(); + if (asyncacitonid) { + const deService = await ibiz.hub + .getApp(ctx.srfappid) + .deService.getService(ctx, other.appDataEntityId); + const abortRequestData: IData = { asyncacitonid }; + if (other.sessionid) { + Object.assign(abortRequestData, { + sessionid: other.sessionid, + }); + } + const result = await deService.aiChatCancel( + ctx, + param, + abortRequestData, + ); + asyncacitonid = ''; + } + await aiChat.stopMessage({ + messageid: id, + state: 30, + type: 'DEFAULT', + role: 'ASSISTANT', + content: '', + status: 'canceled', + }); + // 标记当前消息已经交互完成 + await aiChat.completeMessage(id, true); + }; + // 推荐提示词 + chatOptions.recommendPrompt = async ( + ctx: IContext, + param: IParams, + other: IParams, + ) => { + const deService = await ibiz.hub + .getApp(ctx.srfappid) + .deService.getService(ctx, other.appDataEntityId); + const tempParams = { ...param }; + if (other.srfaiagent) { + tempParams.srfaiagent = other.srfaiagent; + } + const result = await deService.aiChatRecommendPrompt( + ctx, + tempParams, + other.message, + ); + if (result.ok && result.data) { + const choices = result.data.choices; + if (choices && choices.length > 0) { + return choices[0]; + } + return null; + } + return null; + }; + // 上传相关 + chatOptions.uploader = { + onUpload: async ( + file: File, + reportProgress: (progress: number) => void, + options?: IData, + ) => { + const fileMeata = ibiz.util.file.calcFileUpDownUrl( + options?.context || context, + options?.params || params, + {}, + { enableNoAccess: true }, + ); + const fielUploadHeaders = ibiz.util.file.getUploadHeaders(); + const formData = new FormData(); + formData.append('file', file); + const res = await ibiz.net.axios({ + url: fileMeata.uploadUrl, + method: 'post', + headers: fielUploadHeaders, + data: formData, + onUploadProgress: (progressEvent: AxiosProgressEvent) => { + const percent = (progressEvent.loaded / progressEvent.total!) * 100; + reportProgress(percent); + }, + }); + return res.data; + }, + }; + // 扩展工具栏点击 + chatOptions.extendToolbarClick = async ( + event: MouseEvent, + source: IData, + context: IData, + params: IData, + data: IData, + ) => { + const { id, isPluginApp } = source; + let appId = source.appId; + const tempContext = IBizContext.create(context); + // 插件应用界面行为修正上下文种appId + if (isPluginApp) { + const mainApp = ibiz.hub.getApp(); + const targetApp = mainApp.model.subAppRefs?.find(subAppRef => + subAppRef.appId.endsWith(appId), + ); + if (targetApp) { + const targetAppId = targetApp.appId; + tempContext.srfappid = targetAppId; + appId = targetAppId; + } + } + const result = await UIActionUtil.exec( + id, + { + view, + ctrl, + context: tempContext, + params, + data: [data], + event, + }, + appId, + ); + if (result.closeView) { + // 修复编辑器失焦后,调整数据后直接点击关闭按钮导致无法触发自动保存 + // params.view.modal.ignoreDismissCheck = true; + view.closeView({ ok: true }); + } else if (result.refresh) { + switch (result.refreshMode) { + case 1: + view.callUIAction(SysUIActionTag.REFRESH); + break; + case 2: + view.parentView?.callUIAction(SysUIActionTag.REFRESH); + break; + case 3: + view.getTopView()?.callUIAction(SysUIActionTag.REFRESH); + break; + default: + } + } + return result; + }; + + return { containerOptions, topicOptions, chatOptions }; + } + + /** + * 获取界面行为扩展AI聊天参数 + * @param context + * @param params + * @param data + * @param deACMode + * @param args + * @returns + */ + async getUIActionExAIChatParams( + context: IContext, + params: IParams, + data: IData, + deACMode: IAppDEACMode, + args: { + chatInstance: IData; + view: IViewController; + ctrl?: IControlController; + [key: string]: any; + }, + ): Promise<{ + containerOptions: IData; + topicOptions: IData; + chatOptions: IData; + }> { + // 容器参数 + const containerOptions: IData = {}; + // 容器参数的自动关闭模式(禁用窗口最小化不识别窗口最小化关闭模式) + if (params.hasOwnProperty('autoclose')) { + try { + containerOptions.autoClose = JSON.parse(params.autoclose); + delete params.autoclose; + } catch (error) { + ibiz.log.error(error); + } + } + // 窗口的打开模式 + if (params.hasOwnProperty('openmode')) { + containerOptions.openMode = params.openmode; + delete params.openmode; + } + // 会话参数 + const topicOptions: IData = {}; + // 视图参数topiccaptionmode,可选值'default' | 'snippet' | 'summary',设置会话主题 + if (params.hasOwnProperty('topiccaptionmode')) { + topicOptions.captionMode = params.topiccaptionmode; + delete params.topiccaptionmode; + } else { + topicOptions.captionMode = ibiz.config.common.aiChatTopicCaptionMode; + } + // 视图参数disabletopicstorage,禁用存储 + topicOptions.disableStorage = false; + if (params.hasOwnProperty('disabletopicstorage')) { + topicOptions.disableStorage = params.disabletopicstorage === 'true'; + delete params.disabletopicstorage; + } + topicOptions.beforeDelete = async (...args: any[]) => { + const isBatchRemove = args[4]; + const result = await ibiz.confirm.warning({ + title: ibiz.i18n.t( + `util.appUtil.${isBatchRemove ? 'clearTopic' : 'aiTitle'}`, + ), + desc: ibiz.i18n.t( + `util.appUtil.${isBatchRemove ? 'clearTopicDesc' : 'aiDesc'}`, + ), + }); + return result; + }; + topicOptions.action = async ( + action: string, + context: IContext, + params: IParams, + data: IData, + event: MouseEvent, + ) => { + if (action === 'LINK') await ibiz.openView.push(data.url); + return true; + }; + topicOptions.configService = ( + appid: string, + storageType: string, + subType: string, + ) => { + return new ConfigService(appid, storageType, subType); + }; + + // 聊天参数 + const chatOptions: IData = {}; + // 是否传入对象参数,用于历史查询传参 + // 上下文参数和视图参数srfaiappendcurdata均识别 + if (context.srfaiappendcurdata === 'true') { + chatOptions.appendCurData = data; + } else if (params.hasOwnProperty('srfaiappendcurdata')) { + chatOptions.appendCurData = + params.srfaiappendcurdata === 'true' ? data : undefined; + delete params.srfaiappendcurdata; + } + // 传入内容作为用户消息,获取历史数据后附加 + if (params.hasOwnProperty('srfaiappendcurcontent')) { + chatOptions.appendCurContent = StringUtil.fill( + params.srfaiappendcurcontent, + context, + params, + data, + ); + delete params.srfaiappendcurcontent; + } + // 自动提问 + if (params.hasOwnProperty('autoquestion')) { + chatOptions.autoQuestion = params.autoquestion !== 'false'; + delete params.autoquestion; + } + // 附加资源数据 + if (params.hasOwnProperty('srfaiappendresource')) { + chatOptions.appendCurResource = StringUtil.fill( + params.srfaiappendresource, + context, + params, + data, + ); + delete params.srfaiappendresource; + } + // 附加srfmode数据 + if (params.hasOwnProperty('srfmode')) { + chatOptions.srfMode = params.srfmode; + delete params.srfmode; + } + // 默认智能体 + if (params.hasOwnProperty('srfaiagent')) { + chatOptions.activeAIAgentID = params.srfaiagent; + delete params.srfaiagent; + } + + // 智能体是否可切换 + let enableAIAgentChange = ibiz.config.common.enableAIAgentChange; + if (params.hasOwnProperty('srfenableaiagentchange')) { + enableAIAgentChange = params.srfenableaiagentchange === 'true'; + delete params.srfenableaiagentchange; + } + chatOptions.enableAIAgentChange = enableAIAgentChange; + + // 智能体选项 + const aiAgentlist: any = await this.getAIAgentList(context, params); + chatOptions.aiAgentlist = aiAgentlist; + // 扩展工具栏 + if (deACMode) { + const { + contentToolbarItems, + footerToolbarItems, + questionToolbarItems, + otherToolbarItems, + } = this.calcAiToolbarItemsByAc(deACMode); + chatOptions.contentToolbarItems = contentToolbarItems; + chatOptions.footerToolbarItems = footerToolbarItems; + chatOptions.questionToolbarItems = questionToolbarItems; + chatOptions.otherToolbarItems = otherToolbarItems; + } + + let id: string = ''; + let abortController: AbortController; + let asyncacitonid: string = ''; + const { chatInstance, view, ctrl } = args; + // 查询历史 + chatOptions.history = async ( + ctx: IContext, + param: IParams, + other: IParams, + ) => { + const deService = await ibiz.hub + .getApp(ctx.srfappid) + .deService.getService(ctx, other.appDataEntityId); + const historyRequestData: IData = {}; + if (other.appendCurData) { + Object.assign(historyRequestData, { + ...other.appendCurData, + }); + } + if (other.sessionid) { + Object.assign(historyRequestData, { + sessionid: other.sessionid, + }); + } + if (other.srfaiagent) { + Object.assign(historyRequestData, { + srfaiagent: other.srfaiagent, + }); + } + if (other.srfmode) { + Object.assign(historyRequestData, { + mode: other.srfmode, + }); + } + const result = await deService.aiChatHistory( + ctx, + param, + historyRequestData, + ); + if (result.data && Array.isArray(result.data)) { + let preMsg: IData | undefined; + result.data.forEach(item => { + if (item.role === 'TOOL') { + if (preMsg && item.content) { + chatInstance.aiChat!.updateRecommendPrompt( + preMsg as any, + item.content, + ); + } + } else { + const msg = { + messageid: createUUID(), + state: 30, + type: 'DEFAULT', + role: item.role, + content: item.content, + completed: true, + } as const; + preMsg = msg; + chatInstance.aiChat!.addMessage(msg); + } + }); + } + return true; + }; + // 提问 + chatOptions.question = async ( + aiChat: any, + ctx: IContext, + param: IParams, + other: IParams, + arr: IChatMessage[], + sessionid: string, + srfaiagent: string | undefined, + srfmode: string | undefined, + ) => { + id = createUUID(); + abortController = new AbortController(); + const deService = await ibiz.hub + .getApp(ctx.srfappid) + .deService.getService(ctx, other.appDataEntityId); + try { + const questionRequestData: IData = { + messages: arr, + sessionid, + }; + if (srfaiagent) { + questionRequestData.srfaiagent = srfaiagent; + } + if (srfmode) { + questionRequestData.mode = srfmode; + } + await deService.aiChatSse( + (msg: IPortalAsyncAction) => { + // 20: 持续回答中,消息会持续推送。同一个消息 id 会显示在同一个框内 + if (msg.actionstate === 20 && msg.actionresult) { + asyncacitonid = msg.asyncacitonid; + aiChat.addMessage({ + messageid: id, + state: msg.actionstate, + type: 'DEFAULT', + role: 'ASSISTANT', + content: msg.actionresult as string, + status: 'pending', + }); + } + // 30: 回答完成,包含具体所有消息内容。直接覆盖之前的临时拼接消息 + else if (msg.actionstate === 30 && msg.actionresult) { + const result = JSON.parse(msg.actionresult as string); + const choices = result.choices; + if (choices && choices.length > 0) { + aiChat.replaceMessage({ + messageid: id, + state: msg.actionstate, + type: 'DEFAULT', + role: 'ASSISTANT', + content: choices[0].content || '', + realmessageid: choices[0].messageid, + status: 'sent', + }); + } + } + // 40: 回答报错,展示错误信息 + else if (msg.actionstate === 40) { + aiChat.replaceMessage({ + messageid: id, + state: msg.actionstate, + type: 'ERROR', + role: 'ASSISTANT', + content: msg.actionresult as string, + status: 'failed', + }); + } + }, + abortController, + ctx, + param, + { ...questionRequestData }, + ); + } catch (error) { + aiChat.replaceMessage({ + messageid: id, + state: 40, + type: 'ERROR', + role: 'ASSISTANT', + content: (error as IData).message || ibiz.i18n.t('app.aiError'), + status: 'failed', + }); + abortController?.abort(); + } finally { + // 标记当前消息已经交互完成 + aiChat.completeMessage(id, true); + return true; + } + }; + // 中止提问 + chatOptions.abortQuestion = async ( + aiChat: any, + ctx: IContext, + param: IParams, + other: IParams, + ) => { + abortController?.abort(); + if (asyncacitonid) { + const deService = await ibiz.hub + .getApp(ctx.srfappid) + .deService.getService(ctx, other.appDataEntityId); + const abortRequestData: IData = { asyncacitonid }; + if (other.sessionid) { + Object.assign(abortRequestData, { + sessionid: other.sessionid, + }); + } + const result = await deService.aiChatCancel( + ctx, + param, + abortRequestData, + ); + asyncacitonid = ''; + } + await aiChat.stopMessage({ + messageid: id, + state: 30, + type: 'DEFAULT', + role: 'ASSISTANT', + content: '', + status: 'canceled', + }); + // 标记当前消息已经交互完成 + await aiChat.completeMessage(id, true); + }; + // 推荐提示词 + chatOptions.recommendPrompt = async ( + ctx: IContext, + param: IParams, + other: IParams, + ) => { + const deService = await ibiz.hub + .getApp(ctx.srfappid) + .deService.getService(ctx, other.appDataEntityId); + const tempParams = { ...param }; + if (other.srfaiagent) { + tempParams.srfaiagent = other.srfaiagent; + } + const result = await deService.aiChatRecommendPrompt( + ctx, + tempParams, + other.message, + ); + if (result.ok && result.data) { + const choices = result.data.choices; + if (choices && choices.length > 0) { + return choices[0]; + } + return null; + } + return null; + }; + // 上传相关 + chatOptions.uploader = { + onUpload: async ( + file: File, + reportProgress: (progress: number) => void, + options?: IData, + ) => { + const { uploadUrl } = ibiz.util.file.calcFileUpDownUrl( + options?.context || context, + options?.params || params, + {}, + { enableNoAccess: true }, + ); + const headers = ibiz.util.file.getUploadHeaders(); + const formData = new FormData(); + formData.append('file', file); + const res = await ibiz.net.axios({ + url: uploadUrl, + method: 'post', + headers, + data: formData, + onUploadProgress: (progressEvent: AxiosProgressEvent) => { + const percent = (progressEvent.loaded / progressEvent.total!) * 100; + reportProgress(percent); + }, + }); + return res.data; + }, + }; + // 扩展工具栏点击 + chatOptions.extendToolbarClick = async ( + event: MouseEvent, + source: IData, + context: IData, + params: IData, + data: IData, + ) => { + const { id, isPluginApp } = source; + let appId = source.appId; + const tempContext = IBizContext.create(context); + // 插件应用界面行为修正上下文种appId + if (isPluginApp) { + const mainApp = ibiz.hub.getApp(); + const targetApp = mainApp.model.subAppRefs?.find(subAppRef => + subAppRef.appId.endsWith(appId), + ); + if (targetApp) { + const targetAppId = targetApp.appId; + tempContext.srfappid = targetAppId; + appId = targetAppId; + } + } + const result = await UIActionUtil.exec( + id, + { + view: view as ViewController, + ctrl: ctrl as IControlController, + context: tempContext, + params, + data: [data], + event, + }, + appId, + ); + if (result.closeView) { + // 修复编辑器失焦后,调整数据后直接点击关闭按钮导致无法触发自动保存 + // params.view.modal.ignoreDismissCheck = true; + view.closeView({ ok: true }); + } else if (result.refresh) { + switch (result.refreshMode) { + case 1: + (view as ViewController).callUIAction(SysUIActionTag.REFRESH); + break; + case 2: + view.parentView?.callUIAction(SysUIActionTag.REFRESH); + break; + case 3: + (view as ViewController) + .getTopView() + ?.callUIAction(SysUIActionTag.REFRESH); + break; + default: + } + } + return result; + }; + + return { containerOptions, topicOptions, chatOptions }; + } + + /** + * 计算界面行为扩展AI聊天工具栏项 + * @param deACMode 自填模式 + */ + calcAiToolbarItemsByAc(deACMode: IAppDEACMode | undefined): { + contentToolbarItems: IAIToolbarItem[]; + footerToolbarItems: IAIToolbarItem[]; + questionToolbarItems: IAIToolbarItem[]; + otherToolbarItems: IAIToolbarItem[]; + functionToolbarItems: IAIToolbarItem[]; + inlineToolbarItems: IAIToolbarItem[]; + } { + const contentToolbarItems: IAIToolbarItem[] = []; + const footerToolbarItems: IAIToolbarItem[] = []; + const questionToolbarItems: IAIToolbarItem[] = []; + const functionToolbarItems: IAIToolbarItem[] = []; + const inlineToolbarItems: IAIToolbarItem[] = []; + const otherToolbarItems: IAIToolbarItem[] = []; + if (!deACMode || !deACMode.deuiactionGroup) { + return { + contentToolbarItems, + footerToolbarItems, + questionToolbarItems, + otherToolbarItems, + functionToolbarItems, + inlineToolbarItems, + }; + } + deACMode.deuiactionGroup.uiactionGroupDetails?.forEach( + (item: IAppDEUIActionGroupDetail) => { + const toolbarItem: IAIToolbarItem = { + appId: item.appId, + id: item.uiactionId, + label: item.showCaption ? item.caption : '', + title: item.tooltip, + icon: { + showIcon: item.showIcon, + cssClass: item.sysImage?.cssClass, + imagePath: item.sysImage?.imagePath, + }, + }; + // 修正图片路径 + if ( + item.sysImage && + item.sysImage.imagePath && + !item.sysImage.imagePath.startsWith('http') + ) { + toolbarItem.icon.imagePath = `${ibiz.env.assetsUrl}/images/${item.sysImage.imagePath}`; + } + if (item.uiactionId?.startsWith('mob_msg_content_')) { + contentToolbarItems.push(toolbarItem); + } else if (item.uiactionId?.startsWith('mob_msg_footer_')) { + footerToolbarItems.push(toolbarItem); + } else if (item.uiactionId?.startsWith('mob_question_')) { + questionToolbarItems.push(toolbarItem); + // 代码编辑器`/`触发 + } else if (item.uiactionId?.startsWith('mob_function_')) { + functionToolbarItems.push(toolbarItem); + // 集成类编辑器悬停ai按钮触发 + } else if ( + item.uiactionId?.startsWith('mob_inline') || + (item.refUIActionGroup && + item.refUIActionGroup.id?.startsWith('mob_inline')) + ) { + inlineToolbarItems.push(toolbarItem); + } else { + otherToolbarItems.push(toolbarItem); + } + }, + ); + return { + contentToolbarItems, + footerToolbarItems, + questionToolbarItems, + otherToolbarItems, + functionToolbarItems, + inlineToolbarItems, + }; + } + + /** + * 获取AI代理列表 + * @param context + * @param params + */ + async getAIAgentList( + context: IContext, + params: IParams, + editorParams?: IData, + ): Promise { + const emptyList: IData[] = []; + const app = ibiz.hub.getApp(ibiz.env.appId); + const aiAgentUtil = app.getAppUtil('DYNAMICAIGENT', 'CUSTOM'); + if (!aiAgentUtil) { + return emptyList; + } + const utilService = new UtilService(aiAgentUtil); + const resultParams = { ...params, page: 0, size: 1000 }; + // 准备agent查询参数: + // 1.自定义srfaiagentscope(编辑器参数) > 视图参数srfaiagentscope(自定义视图参数) + // 2.全局ai交互(插件实现):前面都没拿到,直接读取activedata所属实体名称 + if (editorParams && editorParams.srfaiagentscope) { + Object.assign(resultParams, { + srfaiagentscope: editorParams.srfaiagentscope, + }); + } + const data = await utilService.load('', context, resultParams); + if (!data || data.length === 0) { + return emptyList; + } + return data as IData[]; + } + + /** + * 获取会话标识(TOPIC:适用于多话题场景;INLINE:适用于ai行内会话场景;TEMP:适用于传统ai编辑器会话场景) + * @param sessionID + */ + getChatSessionId( + type: 'TOPIC' | 'INLINE' | 'TEMP', + sessionID?: string, + ): string { + let tempSessionID = ''; + switch (type) { + case 'TOPIC': + tempSessionID += this.TOPIC_CHAT_PREFIX; + break; + case 'INLINE': + tempSessionID += this.INLINE_CHAT_SUFFIX; + break; + case 'TEMP': + tempSessionID += this.TEMP_CHAT_SUFFIX; + break; + default: + tempSessionID += this.UNKOWN_CHAT_SUFFIX; + break; + } + tempSessionID += `@${sessionID || createUUID()}@${new Date().getTime()}`; + return tempSessionID; + } + + /** + * 获取AI会话应用功能 + * @returns + */ + private async getAIResourceUtil(): Promise { + const app = ibiz.hub.getApp(ibiz.env.appId); + const aiSessionUtil = app.getAppUtil('DYNAMICAISESSION', 'CUSTOM'); + return aiSessionUtil; + } + + /** + * 获取AI资源参数 + * @returns + */ + async getAIResourceOptions( + context: IContext, + params: IParams, + ): Promise { + const resourceOptions: IData = {}; + // 优先读取应用配置 + if (ibiz.config.common.aiResourceMode) { + resourceOptions.resourceMode = ibiz.config.common.aiResourceMode; + return resourceOptions; + } + // 没有应用配置,则判断当前应用是否存在AI会话应用功能,若存在,则使用远程模式 + const aiSessionUtil = await this.getAIResourceUtil(); + // REMOTE:远程模式,话题存储config和远程session,消息存储消息表;LOCAL:本地模式,话题存储config,消息存储客户端;默认本地模式 + resourceOptions.resourceMode = aiSessionUtil ? 'REMOTE' : 'LOCAL'; + if (!aiSessionUtil) { + return resourceOptions; + } + // 准备参数 + const utilService = new AIUtilService(aiSessionUtil); + + // 获取会话清单 + resourceOptions.getSessionList = async ( + args: IParams = {}, + ): Promise => { + const tempParams = { ...params, page: 0, size: 1000, ...args }; + const result = await utilService.getSessionList(context, tempParams); + return result; + }; + // 更新会话 + resourceOptions.updateSession = async ( + realID: string, + data: IData, + ): Promise => { + const result = await utilService.updateSession( + context, + params, + realID, + data, + ); + return result; + }; + // 删除会话(多个以逗号分割) + resourceOptions.deleteSession = async ( + realID: string, + ): Promise => { + const result = await utilService.deleteSession(context, params, realID); + return result; + }; + // 获取指定会话所有消息 + resourceOptions.getMessages = async ( + args: IParams = {}, + ): Promise => { + const tempParams = { ...params, page: 0, size: 1000, ...args }; + const result = await utilService.getMessageList(context, tempParams); + return result; + }; + // 删除指定会话消息(多个以逗号分割) + resourceOptions.deleteMessage = async ( + messageID: string, + ): Promise => { + const result = await utilService.deleteMessage( + context, + params, + messageID, + ); + return result; + }; + // 点赞指定会话消息 + resourceOptions.likeMessage = async ( + messageID: string, + ): Promise => { + const result = await utilService.likeMessage(context, params, messageID); + return result; + }; + // 点踩指定会话消息 + resourceOptions.dislikeMessage = async ( + messageID: string, + feedbackContent: string, + ): Promise => { + const overlay = ibiz.overlay.createDrawer( + (modal: IModal) => { + return h(AIFeedback, { + modal, + content: feedbackContent, + }); + }, + undefined, + { + height: 'auto', + placement: 'bottom', + attrs: { + closeable: false, + }, + } as any, + ); + overlay.present(); + const result: IModalData = await overlay.onWillDismiss(); + if (!result.ok) return false; + const content = result.data?.[0]?.feedbackContent; + const _result = await utilService.dislikeMessage( + context, + params, + messageID, + content, + ); + return _result; + }; + // 取消点赞或者点踩 + resourceOptions.cancelFeedback = async ( + messageID: string, + ): Promise => { + const result = await utilService.cancelFeedback( + context, + params, + messageID, + ); + return result; + }; + // 清空所有会话 + resourceOptions.clearAllSession = async ( + excludeSessionID: string, + ): Promise => { + const result = await utilService.clearAllSession( + context, + params, + excludeSessionID, + ); + return result; + }; + // 删除指定会话的所有的消息 + resourceOptions.clearAllMessageBySessionId = async ( + realID: string, + ): Promise => { + const result = await utilService.clearAllMessageBySessionId( + context, + params, + realID, + ); + return result; + }; + return resourceOptions; + } +} diff --git a/src/util/ai-chat-util/ai-feedback/ai-feedback.scss b/src/util/ai-chat-util/ai-feedback/ai-feedback.scss new file mode 100644 index 0000000000000000000000000000000000000000..c06ba19326add273777be99ce86c3a18378e5249 --- /dev/null +++ b/src/util/ai-chat-util/ai-feedback/ai-feedback.scss @@ -0,0 +1,101 @@ +$ai-feedback: ( + // 颜色 + 'color-header-close-icon': getCssVar(color, text, 3), + 'color-group-title': getCssVar(color, text, 2), + 'color-description-input-border': getCssVar(color, border), + // 间距 + 'spacing-padding': getCssVar('spacing', 'base'), + 'spacing-group-title-padding': getCssVar('spacing', 'tight') 0, + 'spacing-group-item-padding': getCssVar('spacing', 'tight'), + 'spacing-description-title-padding': getCssVar('spacing', 'tight') 0, + 'spacing-description-input-padding': getCssVar('spacing', 'extra-tight'), + // 宽高 + 'height-max-height': 90vh, + 'height-content-max-height': 70vh, + 'height-header-min-height': getCssVar('height-control', 'default'), + 'width-group-item': 50%, + // 字体大小 + 'font-header-font-size': getCssVar('font-size', 'header', 6), + 'font-header-close-icon-font-size': getCssVar('font-size', 'header', 3), + 'font-content-font-size': getCssVar('font-size', 'regular'), + // 圆角 + 'radius-description-input-border-radius': getCssVar(border, radius, small), + // 其他 + 'gap': getCssVar('spacing', 'base-tight'), + 'gap-footer': getCssVar('spacing', 'tight') +); + +@include b(ai-feedback) { + @include set-component-css-var('ai-feedback', $ai-feedback); + + display: flex; + flex-direction: column; + gap: getCssVar('ai-feedback', 'gap'); + max-height: getCssVar('ai-feedback', 'height-max-height'); + padding: getCssVar('ai-feedback', 'spacing-padding'); + overflow-y: hidden; + @include e('header') { + display: flex; + align-items: center; + justify-content: space-between; + min-height: getCssVar('ai-feedback', 'height-header-min-height'); + font-size: getCssVar('ai-feedback', 'font-header-font-size'); + @include m('close-icon') { + font-size: getCssVar('ai-feedback', 'font-header-close-icon-font-size'); + color: getCssVar('ai-feedback', 'color-header-close-icon'); + cursor: pointer; + } + } + @include e(content) { + max-height: getCssVar('ai-feedback', 'height-content-max-height'); + overflow-y: auto; + font-size: getCssVar('ai-feedback', 'font-content-font-size'); + .#{bem('ai-feedback', 'group')} { + &:first-child .#{bem('ai-feedback','group','title')} { + padding-top: 0; + } + } + } + @include e(group) { + @include m(title) { + padding: getCssVar('ai-feedback', 'spacing-group-title-padding'); + color: getCssVar('ai-feedback', 'color-group-title'); + } + @include m('container') { + display: flex; + flex-wrap: wrap; + } + @include m(item) { + width: getCssVar('ai-feedback', 'width-group-item'); + padding: getCssVar('ai-feedback', 'spacing-group-item-padding'); + } + } + + @include e(description) { + @include m(title) { + padding: getCssVar('ai-feedback', 'spacing-description-title-padding'); + } + @include m(input) { + border: 1px solid + getCssVar('ai-feedback', 'color-description-input-border'); + border-radius: getCssVar( + 'ai-feedback', + 'radius-description-input-border-radius' + ); + + &.van-cell { + padding: getCssVar('ai-feedback', 'spacing-description-input-padding'); + font-size: inherit; + } + } + } + @include e(footer) { + display: flex; + gap: getCssVar('ai-feedback', 'gap-footer'); + justify-content: space-between; + + @include m(button) { + flex: 1; + } + } +} diff --git a/src/util/ai-chat-util/ai-feedback/ai-feedback.tsx b/src/util/ai-chat-util/ai-feedback/ai-feedback.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d55173cad237047fa873f30d2233f1786b6abcce --- /dev/null +++ b/src/util/ai-chat-util/ai-feedback/ai-feedback.tsx @@ -0,0 +1,197 @@ +import { reactive, PropType, defineComponent, onMounted } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { IModal } from '@ibiz-template/runtime'; +import './ai-feedback.scss'; + +interface FeedbackItem { + title: string; + value?: string; + children: string[]; +} + +interface FeedbackState { + description: string; + feedbackItems: FeedbackItem[]; +} + +export const AIFeedback = defineComponent({ + name: 'IBizAIFeedback', + props: { + content: { type: String }, + modal: { type: Object as PropType, required: true }, + }, + setup(props) { + const ns = useNamespace('ai-feedback'); + + const feedback = reactive({ + description: '', + feedbackItems: [], + }); + + const FEEDBACK_CATEGORIES = [ + { + title: ibiz.i18n.t('util.aiChartUtil.regardingIssue'), + children: [ + ibiz.i18n.t('util.aiChartUtil.understandProblem'), + ibiz.i18n.t('util.aiChartUtil.forgotContext'), + ibiz.i18n.t('util.aiChartUtil.notFollowingRequire'), + ], + }, + { + title: ibiz.i18n.t('util.aiChartUtil.regardingResponse'), + children: [ + ibiz.i18n.t('util.aiChartUtil.incorrectAswer'), + ibiz.i18n.t('util.aiChartUtil.logicalConfusion'), + ibiz.i18n.t('util.aiChartUtil.poorTimeliness'), + ibiz.i18n.t('util.aiChartUtil.poorReadability'), + ibiz.i18n.t('util.aiChartUtil.incompleteAnswer'), + ibiz.i18n.t('util.aiChartUtil.unprofessional'), + ], + }, + { + title: ibiz.i18n.t('util.aiChartUtil.report'), + children: [ + ibiz.i18n.t('util.aiChartUtil.pornographicVulgar'), + ibiz.i18n.t('util.aiChartUtil.politicallySensitive'), + ibiz.i18n.t('util.aiChartUtil.illegalCriminal'), + ibiz.i18n.t('util.aiChartUtil.discriminationPrejudice'), + ibiz.i18n.t('util.aiChartUtil.violationPrivacy'), + ibiz.i18n.t('util.aiChartUtil.contentInfringement'), + ], + }, + ]; + + /** + * @description 初始化反馈 + */ + const onInitFeedback = (): void => { + feedback.feedbackItems = FEEDBACK_CATEGORIES.map(category => ({ + title: category.title, + value: undefined, + children: [...category.children], + })); + if (props.content) { + const contentArray = props.content + .split(';') + .filter(item => item.trim()); + if (contentArray.length > 0) { + // 前 N 个是分类值(N 为分类数量) + const feedbackValues = contentArray.slice( + 0, + FEEDBACK_CATEGORIES.length, + ); + // 设置分类值 + feedbackValues.forEach((value, index) => { + feedback.feedbackItems[index].value = value; + }); + // 剩余的是描述内容 + const descriptionParts = contentArray.slice( + FEEDBACK_CATEGORIES.length, + ); + // 设置描述 + feedback.description = descriptionParts.join(';'); + } + } + }; + + /** + * @description 取消 + */ + const onCancel = (): void => { + props.modal.dismiss(); + }; + + /** + * @description 确定 + */ + const onConfirm = (): void => { + const contentArray = feedback.feedbackItems + .map(item => item.value || '') + .filter(item => !!item); + contentArray.push(feedback.description); + props.modal.dismiss({ + ok: true, + data: [{ feedbackContent: contentArray.join(';') }], + }); + }; + + onMounted(() => { + onInitFeedback(); + }); + + return { + ns, + feedback, + onCancel, + onConfirm, + }; + }, + render() { + return ( +
+
+
+ {ibiz.i18n.t('util.aiChartUtil.feedback')} +
+ +
+
+ {this.feedback.feedbackItems.map(item => { + return ( +
+
{item.title}
+ + {item.children.map(child => { + return ( + + {child} + + ); + })} + +
+ ); + })} +
+
+ {ibiz.i18n.t('util.aiChartUtil.description')} +
+ +
+
+
+ + {ibiz.i18n.t('app.cancel')} + + + {ibiz.i18n.t('app.confirm')} + +
+
+ ); + }, +}); diff --git a/src/util/app-util/app-util.ts b/src/util/app-util/app-util.ts index bc2927d19b38a96ad9a23d85ab1db3e8bf4113da..db1408966e1ea65803ec969cb5995e2f9192b605 100644 --- a/src/util/app-util/app-util.ts +++ b/src/util/app-util/app-util.ts @@ -3,13 +3,19 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Router } from 'vue-router'; import { - IAiChatParam, - IApiViewController, IAppUtil, IAuthResult, + getDeACMode, + IAiChatParam, + IApiViewController, + calcDeCodeNameById, } from '@ibiz-template/runtime'; -import { IChatMessage, RuntimeError } from '@ibiz-template/core'; -import { route2routePath, routePath2string } from '@ibiz-template/vue3-util'; +import { IChatMessage } from '@ibiz-template/core'; +import { + route2routePath, + routePath2string, + useUIStore, +} from '@ibiz-template/vue3-util'; export class AppUtil implements IAppUtil { /** @@ -228,8 +234,81 @@ export class AppUtil implements IAppUtil { * @return {*} {Promise} * @memberof AppUtil */ - async openAiChat(params: IAiChatParam): Promise { - throw new RuntimeError(ibiz.i18n.t('app.noSupport')); + async openAiChat(chartParams: IAiChatParam): Promise { + const { + data, + view, + ctrl, + params, + context, + appDEACModeId, + appDataEntityId, + } = chartParams; + const deACMode = await getDeACMode( + appDEACModeId, + appDataEntityId, + context.srfappid, + ); + if (!deACMode) return Promise.resolve([]); + const chatInstance = await ibiz.aiChatUtil.getAIChat(); + const appDataEntityName = calcDeCodeNameById(appDataEntityId!); + let topicId = `mob@${appDataEntityId}@${appDEACModeId}@`; + topicId += context[appDataEntityName] + ? context[appDataEntityName] + : 'default'; + const sessionid = ibiz.aiChatUtil.getChatSessionId('TOPIC', topicId); + const topicCaption = `[${deACMode.logicName}]${data?.srfmajortext || ''}`; + const tempParams = { ...params, ...{ srfactag: deACMode.codeName } }; + const { zIndex } = useUIStore(); + const containerZIndex = zIndex.increment(); + const { containerOptions, topicOptions, chatOptions } = + await ibiz.aiChatUtil.getUIActionExAIChatParams( + context, + params, + data, + deACMode, + { chatInstance, view, ctrl }, + ); + const resourceOptions = await ibiz.aiChatUtil.getAIResourceOptions( + context, + params, + ); + return new Promise(resolve => { + chatInstance.create({ + mode: 'TOPIC', + resourceOptions, + containerOptions: { + zIndex: containerZIndex, + enableBackFill: false, + ...containerOptions, + }, + topicOptions: { + appid: ibiz.env.appId, + id: topicId, + caption: topicCaption, + url: window.location.hash.substring(1), + type: context.srftopicpath || 'default', + ...topicOptions, + }, + chatOptions: { + caption: deACMode.logicName, + context: { ...context }, + params: tempParams, + appDataEntityId, + sessionid, + // 扩展参数 + ...chatOptions, + // 关闭回调 + closed: ( + context: IContext, + params: IParams, + messages: IChatMessage[], + ) => { + resolve(messages); + }, + }, + }); + }); } /** diff --git a/src/util/index.ts b/src/util/index.ts index 41a78623faf21eb165018c30151464807d2b1d70..0141200d0bef18cab4adf2e0fae9e0e7645b3f97 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -18,3 +18,4 @@ export { splitPathToSegments, validateRouteSegments, } from './user-route-util/user-route-util'; +export { AIChatUtil } from './ai-chat-util/ai-chat-util';