From 6b98c416832ab0422e2273e8ae0d747b9889af08 Mon Sep 17 00:00:00 2001
From: lijianxiong <1518062161@qq.com>
Date: Thu, 15 May 2025 21:14:03 +0800
Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E7=B3=BB=E7=BB=9F=E7=95=8C?=
=?UTF-8?q?=E9=9D=A2=E6=A0=B7=E5=BC=8F=E8=A1=A8=E8=AE=BE=E8=AE=A1=E6=8F=92?=
=?UTF-8?q?=E4=BB=B6=E6=96=B0=E5=A2=9EAI=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/layout-design/package.json | 1 +
.../sys-css-design.controller.ts | 42 ++
.../sys-css-design/sys-css-design.tsx | 385 +++++++++++++++++-
packages/model-design/package.json | 2 +-
.../design-code-editor/design-code-editor.tsx | 61 +++
5 files changed, 484 insertions(+), 7 deletions(-)
diff --git a/packages/layout-design/package.json b/packages/layout-design/package.json
index f0306a6e..92d37f73 100644
--- a/packages/layout-design/package.json
+++ b/packages/layout-design/package.json
@@ -47,6 +47,7 @@
"publish:npm": "npm run build && npm publish --access public --registry=http://172.16.240.221:8081/repository/local/"
},
"dependencies": {
+ "@ibiz-template-plugin/ai-chat": "^0.0.28",
"@ibiz-template-plugin/design-base": "^0.0.3-alpha.100",
"@ibiz-template-plugin/model-design": "^0.0.3-alpha.95",
"@ibiz-template/core": "0.7.39-alpha.2",
diff --git a/packages/layout-design/src/panel-items/sys-css-design/sys-css-design.controller.ts b/packages/layout-design/src/panel-items/sys-css-design/sys-css-design.controller.ts
index 46208106..b8b5a23e 100644
--- a/packages/layout-design/src/panel-items/sys-css-design/sys-css-design.controller.ts
+++ b/packages/layout-design/src/panel-items/sys-css-design/sys-css-design.controller.ts
@@ -1,10 +1,36 @@
import {
+ getDeACMode,
EditFormController,
PanelItemController,
} from '@ibiz-template/runtime';
+import { IAppDEACMode, ICode } from '@ibiz/model-core';
import { SysCssDesignPanelItemState } from './sys-css-design.state';
export class SysCssDesignPanelItemController extends PanelItemController {
+ /**
+ * 编辑器模型
+ *
+ * @type {ICode}
+ * @memberof SysCssDesignPanelItemController
+ */
+ public editorModel?: ICode;
+
+ /**
+ * 编辑器参数
+ *
+ * @type {IData}
+ * @memberof SysCssDesignPanelItemController
+ */
+ public editorParams: IData = {};
+
+ /**
+ * 自填模式
+ *
+ * @type {IAppDEACMode}
+ * @memberof SysCssDesignPanelItemController
+ */
+ public deACMode?: IAppDEACMode;
+
/**
* 面板项状态
*
@@ -53,6 +79,22 @@ export class SysCssDesignPanelItemController extends PanelItemController {
this.panel.view.evt.on('onMounted', () => {
this.subscribe();
});
+ this.editorModel = (this.model as IData).editor as ICode;
+ if (this.editorModel) {
+ // TODO 找到编辑器模型
+ const { appDEACModeId, appDataEntityId, editorParams } = this.editorModel;
+ if (editorParams) {
+ Object.keys(editorParams).forEach(key => {
+ this.editorParams[key] = editorParams![key];
+ });
+ }
+ if (appDEACModeId)
+ this.deACMode = await getDeACMode(
+ appDEACModeId,
+ appDataEntityId!,
+ this.panel.context.srfappid,
+ );
+ }
}
/**
diff --git a/packages/layout-design/src/panel-items/sys-css-design/sys-css-design.tsx b/packages/layout-design/src/panel-items/sys-css-design/sys-css-design.tsx
index 4e8bb676..34da3818 100644
--- a/packages/layout-design/src/panel-items/sys-css-design/sys-css-design.tsx
+++ b/packages/layout-design/src/panel-items/sys-css-design/sys-css-design.tsx
@@ -1,6 +1,22 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
import { PropType, defineComponent } from 'vue';
-import { useNamespace } from '@ibiz-template/vue3-util';
-import { IPanelItem } from '@ibiz/model-core';
+import { useNamespace, useUIStore } from '@ibiz-template/vue3-util';
+import {
+ IAppDEACMode,
+ IAppDEUIActionGroupDetail,
+ IPanelItem,
+} from '@ibiz/model-core';
+import {
+ CoreConst,
+ getAppCookie,
+ IBizContext,
+ IChatMessage,
+ IPortalAsyncAction,
+ StringUtil,
+} from '@ibiz-template/core';
+import { AxiosProgressEvent } from 'axios';
+import { SysUIActionTag, UIActionUtil } from '@ibiz-template/runtime';
+import { createUUID } from 'qx-util';
import { SysCssDesignPanelItemController } from './sys-css-design.controller';
import './sys-css-design.scss';
@@ -16,10 +32,363 @@ export default defineComponent({
required: true,
},
},
- setup() {
+ setup(props) {
const ns = useNamespace('sys-css-design');
- return { ns };
+ const c = props.controller;
+
+ const { zIndex } = useUIStore();
+
+ type IAIToolbarItem = {
+ appId: string;
+ id: string | undefined;
+ label: string | undefined;
+ title: string | undefined;
+ icon: IData;
+ };
+
+ /**
+ * 通过Ac计算Ai工具栏
+ *
+ * @export
+ * @param {IAppDEACMode} deACMode
+ * @param {IAiUIActionParams} args
+ * @return {*} {{
+ * contentToolbarItems: IData[];
+ * footerToolbarItems: IData[];
+ * }}
+ */
+ function calcAiToolbarItemsByAc(deACMode: IAppDEACMode): {
+ contentToolbarItems: IAIToolbarItem[];
+ footerToolbarItems: IAIToolbarItem[];
+ questionToolbarItems: IAIToolbarItem[];
+ otherToolbarItems: IAIToolbarItem[];
+ } {
+ const contentToolbarItems: IAIToolbarItem[] = [];
+ const footerToolbarItems: IAIToolbarItem[] = [];
+ const questionToolbarItems: IAIToolbarItem[] = [];
+ const otherToolbarItems: IAIToolbarItem[] = [];
+ 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('msg_content_')) {
+ contentToolbarItems.push(toolbarItem);
+ } else if (item.uiactionId?.startsWith('msg_footer_')) {
+ footerToolbarItems.push(toolbarItem);
+ } else if (item.uiactionId?.startsWith('question_')) {
+ questionToolbarItems.push(toolbarItem);
+ } else {
+ otherToolbarItems.push(toolbarItem);
+ }
+ },
+ );
+ return {
+ contentToolbarItems,
+ footerToolbarItems,
+ questionToolbarItems,
+ otherToolbarItems,
+ };
+ }
+
+ const getCurData = (): IData => {
+ if (c.form) {
+ return c.form.state.data;
+ }
+ return c.panel.data;
+ };
+
+ /**
+ * 打开AI
+ *
+ */
+ const openAIChat = async () => {
+ if (!c.deACMode || !c.editorModel?.appDataEntityId) return;
+ const {
+ contentToolbarItems,
+ footerToolbarItems,
+ questionToolbarItems,
+ otherToolbarItems,
+ } = calcAiToolbarItemsByAc(c.deACMode) as IParams;
+ const curData = getCurData();
+ // eslint-disable-next-line import/no-extraneous-dependencies
+ const module = await import('@ibiz-template-plugin/ai-chat');
+ const chatInstance = module.chat || module.default.chat;
+ let id: string = '';
+ let abortController: AbortController;
+ chatInstance.create({
+ containerOptions: {
+ zIndex: zIndex.increment(),
+ },
+ chatOptions: {
+ caption: c.deACMode.logicName,
+ context: { ...c.panel.context },
+ params: { ...c.panel.params, srfactag: c.deACMode.codeName },
+ // 编辑器参数srfaiappendcurdata,是否传入对象参数,用于历史查询传参
+ appendCurData:
+ c.editorParams.srfaiappendcurdata === 'true' ? curData : undefined,
+ // 编辑器参数srfaiappendcurcontent,传入编辑内容作为用户消息,获取历史数据后附加
+ appendCurContent: c.editorParams.srfaiappendcurcontent
+ ? StringUtil.fill(
+ c.editorParams.srfaiappendcurcontent,
+ c.panel.context,
+ c.panel.params,
+ curData,
+ )
+ : undefined,
+ appDataEntityId: c.editorModel?.appDataEntityId,
+ contentToolbarItems,
+ footerToolbarItems,
+ questionToolbarItems,
+ otherToolbarItems,
+ question: async (
+ aiChat: any,
+ ctx: IContext,
+ param: IParams,
+ other: IParams,
+ arr: IChatMessage[],
+ ) => {
+ id = createUUID();
+ abortController = new AbortController();
+ const deService = await ibiz.hub
+ .getApp(ctx.srfappid)
+ .deService.getService(ctx, other.appDataEntityId);
+ try {
+ await deService.aiChatSse(
+ (msg: IPortalAsyncAction) => {
+ // 20: 持续回答中,消息会持续推送。同一个消息 id 会显示在同一个框内
+ if (msg.actionstate === 20 && msg.actionresult) {
+ aiChat.addMessage({
+ messageid: id,
+ state: msg.actionstate,
+ type: 'DEFAULT',
+ role: 'ASSISTANT',
+ content: msg.actionresult as string,
+ });
+ }
+ // 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 || '',
+ });
+ }
+ }
+ // 40: 回答报错,展示错误信息
+ else if (msg.actionstate === 40) {
+ aiChat.replaceMessage({
+ messageid: id,
+ state: msg.actionstate,
+ type: 'ERROR',
+ role: 'ASSISTANT',
+ content: msg.actionresult as string,
+ });
+ }
+ },
+ abortController,
+ ctx,
+ param,
+ {
+ messages: arr,
+ },
+ );
+ } catch (error) {
+ aiChat.replaceMessage({
+ messageid: id,
+ state: 40,
+ type: 'ERROR',
+ role: 'ASSISTANT',
+ content: (error as IData).message || ibiz.i18n.t('app.aiError'),
+ });
+ abortController?.abort();
+ } finally {
+ // 标记当前消息已经交互完成
+ aiChat.completeMessage(id, true);
+ // eslint-disable-next-line no-unsafe-finally
+ return true;
+ }
+ },
+ abortQuestion: async (aiChat: any) => {
+ abortController?.abort();
+ await aiChat.stopMessage({
+ messageid: id,
+ state: 30,
+ type: 'DEFAULT',
+ role: 'ASSISTANT',
+ content: '',
+ });
+ // 标记当前消息已经交互完成
+ await aiChat.completeMessage(id, true);
+ },
+ // TODO
+ action: ((action: string, message: IData) => {
+ if (action === 'backfill')
+ c.state.customValue = message.realcontent;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ }) as any,
+ history: async (ctx: IContext, param: IParams, other: IParams) => {
+ const deService = await ibiz.hub
+ .getApp(ctx.srfappid)
+ .deService.getService(ctx, other.appDataEntityId);
+ const historyData = other.appendCurData ? other.appendCurData : {};
+ const result = await deService.aiChatHistory(
+ ctx,
+ param,
+ historyData,
+ );
+ 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;
+ },
+ recommendPrompt: async (
+ ctx: IContext,
+ param: IParams,
+ other: IParams,
+ ) => {
+ const deService = await ibiz.hub
+ .getApp(ctx.srfappid)
+ .deService.getService(ctx, other.appDataEntityId);
+ const result = await deService.aiChatRecommendPrompt(
+ ctx,
+ param,
+ other.message,
+ );
+ if (result.ok && result.data) {
+ const choices = result.data.choices;
+ if (choices && choices.length > 0) {
+ return choices[0];
+ }
+ return null;
+ }
+ return null;
+ },
+ uploader: {
+ onUpload: async (
+ file: File,
+ reportProgress: (progress: number) => void,
+ options?: IData,
+ ) => {
+ const fileMeata = ibiz.util.file.calcFileUpDownUrl(
+ options?.context || c.panel.context,
+ options?.params || c.panel.params,
+ {},
+ );
+ const uploadHeaders = {};
+ const token = getAppCookie(CoreConst.TOKEN);
+ if (token) {
+ Object.assign(uploadHeaders, {
+ [`${ibiz.env.tokenHeader}Authorization`]: `${ibiz.env.tokenPrefix}Bearer ${token}`,
+ });
+ }
+ const formData = new FormData();
+ formData.append('file', file);
+ const res = await ibiz.net.axios({
+ url: fileMeata.uploadUrl,
+ method: 'post',
+ headers: uploadHeaders,
+ data: formData,
+ onUploadProgress: (progressEvent: AxiosProgressEvent) => {
+ const percent =
+ (progressEvent.loaded / progressEvent.total!) * 100;
+ reportProgress(percent);
+ },
+ } as IData);
+ return res.data;
+ },
+ },
+ extendToolbarClick: async (
+ event: MouseEvent,
+ source: IData,
+ context: IData,
+ params: IData,
+ data: IData,
+ ) => {
+ const result = await UIActionUtil.exec(
+ source.id,
+ {
+ view: c.panel.view,
+ ctrl: c.panel,
+ context: IBizContext.create(context),
+ params,
+ data: [data],
+ event,
+ },
+ source.appId,
+ );
+ if (result.closeView) {
+ // 修复编辑器失焦后,调整数据后直接点击关闭按钮导致无法触发自动保存
+ // params.view.modal.ignoreDismissCheck = true;
+ c.panel.view.closeView({ ok: true });
+ } else if (result.refresh) {
+ switch (result.refreshMode) {
+ case 1:
+ c.panel.view.callUIAction(SysUIActionTag.REFRESH);
+ break;
+ case 2:
+ c.panel.view.parentView?.callUIAction(SysUIActionTag.REFRESH);
+ break;
+ case 3:
+ c.panel.view
+ .getTopView()
+ ?.callUIAction(SysUIActionTag.REFRESH);
+ break;
+ default:
+ }
+ }
+ return result;
+ },
+ },
+ });
+
+ return chatInstance;
+ };
+
+ return { ns, openAIChat };
},
render() {
const { formValue, customValue, cssName, sysCssName } =
@@ -37,7 +406,8 @@ export default defineComponent({
合成样式