From a19822a617ebed75291ac9ba6fe1ce08f5bdbb16 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Wed, 17 Jun 2026 03:09:38 +0000 Subject: [PATCH] =?UTF-8?q?feat(ai-assistant):=20=E6=96=B0=E5=A2=9E=20clos?= =?UTF-8?q?e=5Fapp=20=E5=8A=A8=E4=BD=9C=E6=94=AF=E6=8C=81=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E5=BD=93=E5=89=8D=E5=BA=94=E7=94=A8=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 打开微应用后 AI 页面操作默认锁定在该应用 iframe 内部,无法关闭应用本身 (关闭控件在主程序外壳层)。新增外壳层 close_app 动作绕过 iframe 作用域: - action-executor 注册 close_app,先用 store 状态判断有无打开应用(无则报错 不假报成功),再经事件总线 observeMicroApp:close 投递给 MicroApps 组件。 - MicroApps 复用现成 onAssistClose(findLast(isOpen)+closeByName)作为该事件 处理器,关闭最前打开的应用,零重复逻辑。 - page-context-collector 在有微应用打开时向模型注入 close_app 可用动作。 - 同步 ai-kb page-action chunk 与 events-map。 Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/events-map.md | 150 +++++++++--------- .../zh/concept/ai-assistant/page-action.md | 3 +- .../zh/howto/ai-assistant/page-action.md | 3 +- .../components/AIAssistant/action-executor.js | 21 +++ .../AIAssistant/page-context-collector.js | 11 +- .../assets/js/components/MicroApps/index.vue | 5 +- 6 files changed, 112 insertions(+), 81 deletions(-) diff --git a/docs/events-map.md b/docs/events-map.md index bc7864c1b..178bdff33 100644 --- a/docs/events-map.md +++ b/docs/events-map.md @@ -3,11 +3,11 @@ > **本文件由脚本自动生成,请勿手改。** > > - 生成命令: `node scripts/gen-events-map.mjs` -> - 扫描范围: `resources/assets/js` 下所有 `.js` / `.vue` 文件(共 268 个) +> - 扫描范围: `resources/assets/js` 下所有 `.js` / `.vue` 文件(共 267 个) > - 事件总线: `resources/assets/js/store/events.js`(mitt 实例) > - 仅匹配裸 `emitter.emit/on/off(` 调用;`xxx.emitter.emit(`(如 Quill 内部 emitter)不属于本总线,已排除 -共 **29** 个静态可解析事件,**125** 处 `emitter.emit/on/off` 调用。 +共 **29** 个静态可解析事件,**121** 处 `emitter.emit/on/off` 调用。 ## 事件清单 @@ -15,14 +15,14 @@ - **emit(10)** - `resources/assets/js/App.vue:420` - - `resources/assets/js/pages/manage.vue:1236` - - `resources/assets/js/pages/manage.vue:1243` - - `resources/assets/js/pages/manage/application.vue:1188` - - `resources/assets/js/pages/manage/application.vue:1194` + - `resources/assets/js/pages/manage.vue:1192` + - `resources/assets/js/pages/manage.vue:1199` + - `resources/assets/js/pages/manage/application.vue:1181` + - `resources/assets/js/pages/manage/application.vue:1187` - `resources/assets/js/pages/manage/components/ChatInput/index.vue:1882` - `resources/assets/js/pages/manage/components/DialogView/index.vue:621` - - `resources/assets/js/pages/manage/components/DialogWrapper.vue:2017` - - `resources/assets/js/pages/manage/components/DialogWrapper.vue:2025` + - `resources/assets/js/pages/manage/components/DialogWrapper.vue:2012` + - `resources/assets/js/pages/manage/components/DialogWrapper.vue:2020` - `resources/assets/js/pages/manage/messenger.vue:1219` - **on(1)** - `resources/assets/js/pages/manage/components/MeetingManager/index.vue:187` @@ -33,17 +33,17 @@ - **emit(3)** - `resources/assets/js/pages/manage/calendar.vue:255` - - `resources/assets/js/pages/manage/components/DialogWrapper.vue:3513` + - `resources/assets/js/pages/manage/components/DialogWrapper.vue:3508` - `resources/assets/js/pages/manage/components/ProjectPanel.vue:1357` - **on(1)** - - `resources/assets/js/pages/manage.vue:621` + - `resources/assets/js/pages/manage.vue:592` - **off(1)** - - `resources/assets/js/pages/manage.vue:641` + - `resources/assets/js/pages/manage.vue:610` ### `aiAssistantClosed` - **emit(1)** - - `resources/assets/js/components/AIAssistant/index.vue:420` + - `resources/assets/js/components/AIAssistant/index.vue:442` - **on(1)** - `resources/assets/js/components/AIAssistant/float-button.vue:154` - **off(1)** @@ -52,22 +52,12 @@ ### `aiOperationRequest` - **emit(1)** - - `resources/assets/js/store/actions.js:4781` + - `resources/assets/js/store/actions.js:4752` - **on(1)** - `resources/assets/js/components/AIAssistant/float-button.vue:155` - **off(1)** - `resources/assets/js/components/AIAssistant/float-button.vue:163` -### `approveDetails` - -- **emit(2)** - - `resources/assets/js/pages/manage/approve/index.vue:497` - - `resources/assets/js/pages/manage/components/DialogWrapper.vue:3826` -- **on(1)** - - `resources/assets/js/pages/manage.vue:624` -- **off(1)** - - `resources/assets/js/pages/manage.vue:644` - ### `clickAgainDialog` - **emit(1)** @@ -80,30 +70,30 @@ ### `createGroup` - **emit(3)** - - `resources/assets/js/pages/manage/components/DialogWrapper.vue:2871` + - `resources/assets/js/pages/manage/components/DialogWrapper.vue:2866` - `resources/assets/js/pages/manage/components/UserDetail.vue:288` - `resources/assets/js/pages/manage/messenger.vue:1224` - **on(1)** - - `resources/assets/js/pages/manage.vue:622` + - `resources/assets/js/pages/manage.vue:593` - **off(1)** - - `resources/assets/js/pages/manage.vue:642` + - `resources/assets/js/pages/manage.vue:611` ### `dialogMsgPush` - **emit(1)** - - `resources/assets/js/store/actions.js:4852` + - `resources/assets/js/store/actions.js:4823` - **on(2)** - `resources/assets/js/components/Mobile/Tabbar.vue:49` - - `resources/assets/js/pages/manage.vue:623` + - `resources/assets/js/pages/manage.vue:594` - **off(2)** - `resources/assets/js/components/Mobile/Tabbar.vue:53` - - `resources/assets/js/pages/manage.vue:643` + - `resources/assets/js/pages/manage.vue:612` ### `handleMoveTop` - **emit(2)** - - `resources/assets/js/store/actions.js:2727` - - `resources/assets/js/store/actions.js:3720` + - `resources/assets/js/store/actions.js:2698` + - `resources/assets/js/store/actions.js:3691` - **on(2)** - `resources/assets/js/pages/manage/components/DialogModal.vue:41` - `resources/assets/js/pages/manage/components/TaskModal.vue:49` @@ -111,43 +101,52 @@ - `resources/assets/js/pages/manage/components/DialogModal.vue:45` - `resources/assets/js/pages/manage/components/TaskModal.vue:53` +### `observeMicroApp:close` + +- **emit(1)** + - `resources/assets/js/components/AIAssistant/action-executor.js:234` +- **on(1)** + - `resources/assets/js/components/MicroApps/index.vue:145` +- **off(1)** + - `resources/assets/js/components/MicroApps/index.vue:151` + ### `observeMicroApp:open` - **emit(1)** - - `resources/assets/js/store/actions.js:5361` + - `resources/assets/js/store/actions.js:5321` - **on(1)** - `resources/assets/js/components/MicroApps/index.vue:144` - **off(1)** - - `resources/assets/js/components/MicroApps/index.vue:149` + - `resources/assets/js/components/MicroApps/index.vue:150` ### `observeMicroApp:updatedOrUninstalled` - **emit(1)** - `resources/assets/js/store/mutations.js:429` - **on(1)** - - `resources/assets/js/components/MicroApps/index.vue:145` + - `resources/assets/js/components/MicroApps/index.vue:146` - **off(1)** - - `resources/assets/js/components/MicroApps/index.vue:150` + - `resources/assets/js/components/MicroApps/index.vue:152` ### `openAIAssistant` - **emit(7)** - `resources/assets/js/components/AIAssistant/float-button.vue:476` - `resources/assets/js/components/SearchBox.vue:582` - - `resources/assets/js/pages/manage.vue:1267` + - `resources/assets/js/pages/manage.vue:1223` - `resources/assets/js/pages/manage/components/ChatInput/index.vue:1925` - `resources/assets/js/pages/manage/components/ReportDetail.vue:176` - `resources/assets/js/pages/manage/components/ReportEdit.vue:267` - - `resources/assets/js/pages/manage/components/TaskAdd.vue:703` + - `resources/assets/js/pages/manage/components/TaskAdd.vue:710` - **on(1)** - - `resources/assets/js/components/AIAssistant/index.vue:361` + - `resources/assets/js/components/AIAssistant/index.vue:382` - **off(1)** - - `resources/assets/js/components/AIAssistant/index.vue:368` + - `resources/assets/js/components/AIAssistant/index.vue:389` ### `openAIAssistantGlobal` - **emit(1)** - - `resources/assets/js/pages/manage.vue:1255` + - `resources/assets/js/pages/manage.vue:1211` - **on(1)** - `resources/assets/js/components/AIAssistant/float-button.vue:153` - **off(1)** @@ -156,7 +155,7 @@ ### `openDownloadClient` - **emit(1)** - - `resources/assets/js/pages/manage.vue:1128` + - `resources/assets/js/pages/manage.vue:1092` - **on(1)** - `resources/assets/js/components/RightBottom.vue:73` - **off(1)** @@ -165,25 +164,25 @@ ### `openFavorite` - **emit(1)** - - `resources/assets/js/pages/manage/application.vue:1061` + - `resources/assets/js/pages/manage/application.vue:1054` - **on(1)** - - `resources/assets/js/pages/manage.vue:626` + - `resources/assets/js/pages/manage.vue:596` - **off(1)** - - `resources/assets/js/pages/manage.vue:646` + - `resources/assets/js/pages/manage.vue:614` ### `openManageExport` - **emit(1)** - - `resources/assets/js/pages/manage/application.vue:1108` + - `resources/assets/js/pages/manage/application.vue:1101` - **on(1)** - - `resources/assets/js/pages/manage.vue:628` + - `resources/assets/js/pages/manage.vue:598` - **off(1)** - - `resources/assets/js/pages/manage.vue:648` + - `resources/assets/js/pages/manage.vue:616` ### `openMobileNotification` - **emit(1)** - - `resources/assets/js/pages/manage.vue:1641` + - `resources/assets/js/pages/manage.vue:1597` - **on(1)** - `resources/assets/js/components/Mobile/Notification.vue:38` - **off(1)** @@ -201,20 +200,20 @@ ### `openRecent` - **emit(1)** - - `resources/assets/js/pages/manage/application.vue:1064` + - `resources/assets/js/pages/manage/application.vue:1057` - **on(1)** - - `resources/assets/js/pages/manage.vue:627` + - `resources/assets/js/pages/manage.vue:597` - **off(1)** - - `resources/assets/js/pages/manage.vue:647` + - `resources/assets/js/pages/manage.vue:615` ### `openReport` - **emit(1)** - - `resources/assets/js/pages/manage/application.vue:1058` + - `resources/assets/js/pages/manage/application.vue:1051` - **on(1)** - - `resources/assets/js/pages/manage.vue:625` + - `resources/assets/js/pages/manage.vue:595` - **off(1)** - - `resources/assets/js/pages/manage.vue:645` + - `resources/assets/js/pages/manage.vue:613` ### `openSearch` @@ -227,11 +226,10 @@ ### `openUser` -- **emit(5)** +- **emit(4)** - `resources/assets/js/components/UserAvatar/index.vue:184` - - `resources/assets/js/pages/manage/approve/details.vue:546` - - `resources/assets/js/pages/manage/components/DialogWrapper.vue:2940` - - `resources/assets/js/pages/manage/components/DialogWrapper.vue:4494` + - `resources/assets/js/pages/manage/components/DialogWrapper.vue:2935` + - `resources/assets/js/pages/manage/components/DialogWrapper.vue:4485` - `resources/assets/js/pages/manage/messenger.vue:1229` - **on(1)** - `resources/assets/js/pages/manage/components/UserDetail.vue:166` @@ -241,7 +239,7 @@ ### `receiveTask` - **emit(2)** - - `resources/assets/js/pages/manage/components/ProjectPanel.vue:1768` + - `resources/assets/js/pages/manage/components/ProjectPanel.vue:1775` - `resources/assets/js/pages/manage/components/TaskRow.vue:280` - **on(1)** - `resources/assets/js/pages/manage/components/TaskDetail.vue:738` @@ -251,16 +249,16 @@ ### `streamMsgData` - **emit(1)** - - `resources/assets/js/store/actions.js:4469` + - `resources/assets/js/store/actions.js:4440` - **on(1)** - - `resources/assets/js/pages/manage/components/DialogWrapper.vue:946` + - `resources/assets/js/pages/manage/components/DialogWrapper.vue:943` - **off(1)** - - `resources/assets/js/pages/manage/components/DialogWrapper.vue:956` + - `resources/assets/js/pages/manage/components/DialogWrapper.vue:953` ### `taskRelationUpdate` - **emit(1)** - - `resources/assets/js/store/actions.js:4992` + - `resources/assets/js/store/actions.js:4963` - **on(1)** - `resources/assets/js/pages/manage/components/TaskDetail.vue:739` - **off(1)** @@ -269,7 +267,7 @@ ### `updateNotification` - **emit(2)** - - `resources/assets/js/pages/manage.vue:1125` + - `resources/assets/js/pages/manage.vue:1089` - `resources/assets/js/pages/manage/setting/index.vue:191` - **on(1)** - `resources/assets/js/components/RightBottom.vue:65` @@ -281,16 +279,16 @@ - **emit(1)** - `resources/assets/js/components/RightBottom.vue:231` - **on(1)** - - `resources/assets/js/pages/login.vue:217` + - `resources/assets/js/pages/login.vue:219` - **off(1)** - - `resources/assets/js/pages/login.vue:222` + - `resources/assets/js/pages/login.vue:224` ### `userActive` - **emit(3)** - - `resources/assets/js/store/actions.js:870` - - `resources/assets/js/store/actions.js:952` - - `resources/assets/js/store/actions.js:4769` + - `resources/assets/js/store/actions.js:841` + - `resources/assets/js/store/actions.js:923` + - `resources/assets/js/store/actions.js:4740` - **on(1)** - `resources/assets/js/components/UserAvatar/index.vue:43` - **off(1)** @@ -299,21 +297,19 @@ ### `websocketMsg` - **emit(1)** - - `resources/assets/js/store/actions.js:4786` -- **on(3)** - - `resources/assets/js/pages/manage/approve/index.vue:380` - - `resources/assets/js/pages/manage/components/DialogWrapper.vue:945` + - `resources/assets/js/store/actions.js:4757` +- **on(2)** + - `resources/assets/js/pages/manage/components/DialogWrapper.vue:942` - `resources/assets/js/pages/manage/components/FileContent.vue:202` -- **off(3)** - - `resources/assets/js/pages/manage/approve/index.vue:383` - - `resources/assets/js/pages/manage/components/DialogWrapper.vue:957` +- **off(2)** + - `resources/assets/js/pages/manage/components/DialogWrapper.vue:954` - `resources/assets/js/pages/manage/components/FileContent.vue:225` ## 动态事件名(无法静态解析) 以下调用的第一参数不是字符串字面量,无法静态解析事件名: -- `resources/assets/js/components/MicroApps/index.vue:365` — `emitter.emit(actionName...)` +- `resources/assets/js/components/MicroApps/index.vue:375` — `emitter.emit(actionName...)` ## 统计 diff --git a/resources/ai-kb/zh/concept/ai-assistant/page-action.md b/resources/ai-kb/zh/concept/ai-assistant/page-action.md index 596bfa951..ab3c876bb 100644 --- a/resources/ai-kb/zh/concept/ai-assistant/page-action.md +++ b/resources/ai-kb/zh/concept/ai-assistant/page-action.md @@ -22,7 +22,7 @@ negative: - 一次只能操作当前会话所在的页面,不能开新标签页 - 关闭浏览器或切到别的标签页时,页面操作会断连失败 - 跨源(外部站点)微应用 iframe 的内部不可操作,仅同源微应用插件可 -last_verified: v1.7.90 +last_verified: v1.7.91 --- # AI 操作页面的机制 @@ -37,6 +37,7 @@ AI 助手通过高层导航和低层元素操作两类能力操作用户当前 ## 受支持的高层动作 - `open_task`、`open_dialog`、`open_project`、`open_file`、`open_folder` - `navigate_to_dashboard / messenger / calendar / files` +- `close_app`:关闭当前打开的应用窗口(仅在有微应用打开时出现)。属外壳层动作,哪怕 AI 正停在该应用内部、甚至应用跨源读不到内部,也能直接关闭 ## 受支持的低层元素动作 - `click`、`type`、`select`、`focus`、`scroll`、`hover` diff --git a/resources/ai-kb/zh/howto/ai-assistant/page-action.md b/resources/ai-kb/zh/howto/ai-assistant/page-action.md index 1714e5f6e..98103045f 100644 --- a/resources/ai-kb/zh/howto/ai-assistant/page-action.md +++ b/resources/ai-kb/zh/howto/ai-assistant/page-action.md @@ -22,7 +22,7 @@ negative: - 只在当前浏览器标签内导航,不会新开标签 - AI 不能打开你没权限访问的资源(会被后端拒绝) - 若任务被删,AI 跳转后会落到 404 / 提示「任务不存在」 -last_verified: v1.7.90 +last_verified: v1.7.91 --- # 让 AI 帮我跳页面/打开任务 @@ -37,6 +37,7 @@ last_verified: v1.7.90 - "帮我打开仪表盘" - "跳到日历" - "打开文件 ID 5678" +- "关闭这个应用" / "把当前应用关掉"(打开微应用插件后,可让 AI 直接关闭当前应用窗口,无需自己点关闭按钮) ## 支持的跳转目标 - 任务详情(open_task / goto_task / navigate_to_task) diff --git a/resources/assets/js/components/AIAssistant/action-executor.js b/resources/assets/js/components/AIAssistant/action-executor.js index db4833937..7090c720e 100644 --- a/resources/assets/js/components/AIAssistant/action-executor.js +++ b/resources/assets/js/components/AIAssistant/action-executor.js @@ -11,6 +11,7 @@ import { resolveActiveContext } from './active-context'; import { selectBackend } from './input-backends'; +import emitter from '../../store/events'; /** * 创建操作执行器 @@ -44,6 +45,9 @@ class ActionExecutor { navigate_to_calendar: this.navigateToCalendar.bind(this), navigate_to_files: this.navigateToFiles.bind(this), + // 关闭当前应用窗口(外壳层,不受 iframe 作用域限制) + close_app: this.closeApp.bind(this), + // 别名支持 goto_task: this.openTask.bind(this), goto_project: this.openProject.bind(this), @@ -214,6 +218,23 @@ class ActionExecutor { return { navigated: true, page: 'files' }; } + // ========== 应用窗口 ========== + + /** + * 关闭当前打开的应用窗口(最前那个) + * + * 关闭属于外壳层动作,不受 iframe 作用域限制:投递事件给 MicroApps 组件, + * 复用其现成的关闭逻辑。先用 store 状态判断有无打开应用,避免无应用时假报成功。 + */ + async closeApp() { + const hasOpen = (this.store?.state?.microApps || []).some(a => a && a.isOpen); + if (!hasOpen) { + throw new Error('当前没有打开的应用'); + } + emitter.emit('observeMicroApp:close'); + return { closed: true }; + } + // ========== 元素级操作 ========== /** diff --git a/resources/assets/js/components/AIAssistant/page-context-collector.js b/resources/assets/js/components/AIAssistant/page-context-collector.js index a5bb926c8..41a73e5f9 100644 --- a/resources/assets/js/components/AIAssistant/page-context-collector.js +++ b/resources/assets/js/components/AIAssistant/page-context-collector.js @@ -156,6 +156,15 @@ function getAvailableActions(routeName, store) { }, ]; + // 关闭当前应用 - 仅在有微应用打开时可用(外壳层,不受 iframe 作用域限制) + const appWindowActions = []; + if ((store?.state?.microApps || []).some(a => a && a.isOpen)) { + appWindowActions.push({ + name: 'close_app', + description: '关闭当前打开的应用窗口', + }); + } + // 根据页面类型添加特定操作 const pageSpecificActions = []; @@ -231,7 +240,7 @@ function getAvailableActions(routeName, store) { ); } - return [...pageSpecificActions, ...commonActions]; + return [...appWindowActions, ...pageSpecificActions, ...commonActions]; } /** diff --git a/resources/assets/js/components/MicroApps/index.vue b/resources/assets/js/components/MicroApps/index.vue index 814adbf7f..4cb5df955 100644 --- a/resources/assets/js/components/MicroApps/index.vue +++ b/resources/assets/js/components/MicroApps/index.vue @@ -142,11 +142,13 @@ export default { mounted() { emitter.on('observeMicroApp:open', this.onOpen); + emitter.on('observeMicroApp:close', this.onAssistClose); emitter.on('observeMicroApp:updatedOrUninstalled', this.onUpdatedOrUninstalled); }, beforeDestroy() { emitter.off('observeMicroApp:open', this.onOpen); + emitter.off('observeMicroApp:close', this.onAssistClose); emitter.off('observeMicroApp:updatedOrUninstalled', this.onUpdatedOrUninstalled); }, @@ -664,7 +666,8 @@ export default { }, /** - * 关闭之前判断(助理) + * 关闭之前判断(助理);同时作为 observeMicroApp:close 事件处理器, + * 供 AI 助手等调用方触发关闭最前打开的应用 * @returns {Promise} */ onAssistClose() {