mirror of
https://github.com/kuaifan/dootask.git
synced 2026-01-22 09:48:11 +00:00
新增三个 MCP 工具的前端支持: - get_page_context: 基于 ARIA 角色收集页面元素,支持分页和区域筛选 - execute_action: 执行导航操作(打开任务/对话、切换项目/页面) - execute_element_action: 元素级操作(click/type/select/focus/scroll/hover) 新增文件: - operation-client.js: WebSocket 客户端,处理与 MCP Server 的通信 - page-context-collector.js: 页面上下文收集器,ref 系统和 cursor:pointer 扫描 - action-executor.js: 操作执行器,支持智能解析如 open_task_123 - operation-module.js: 模块编排,整合上述模块 修改文件: - float-button.vue: 集成 operation-module,AI 助手打开时启用 - index.vue: 发射关闭事件供 float-button 监听
221 lines
5.2 KiB
JavaScript
Vendored
221 lines
5.2 KiB
JavaScript
Vendored
/**
|
||
* AI 助手前端操作模块
|
||
*
|
||
* 集成 WebSocket 客户端、页面上下文收集器和操作执行器,
|
||
* 提供给 AI 助手组件使用。
|
||
*/
|
||
|
||
import { OperationClient } from './operation-client';
|
||
import { collectPageContext } from './page-context-collector';
|
||
import { createActionExecutor } from './action-executor';
|
||
|
||
/**
|
||
* 创建操作模块实例
|
||
* @param {Object} options
|
||
* @param {Object} options.store - Vuex store 实例
|
||
* @param {Object} options.router - Vue Router 实例
|
||
* @returns {Object} 操作模块实例
|
||
*/
|
||
export function createOperationModule(options = {}) {
|
||
return new OperationModule(options);
|
||
}
|
||
|
||
class OperationModule {
|
||
constructor(options) {
|
||
this.store = options.store;
|
||
this.router = options.router;
|
||
this.enabled = false;
|
||
this.client = null;
|
||
this.executor = null;
|
||
this.sessionId = null;
|
||
|
||
// 回调函数
|
||
this.onSessionReady = options.onSessionReady;
|
||
this.onSessionLost = options.onSessionLost;
|
||
this.onError = options.onError;
|
||
}
|
||
|
||
/**
|
||
* 启用操作模块
|
||
*/
|
||
enable() {
|
||
if (this.enabled) {
|
||
return;
|
||
}
|
||
|
||
this.enabled = true;
|
||
|
||
// 创建操作执行器
|
||
this.executor = createActionExecutor(this.store, this.router);
|
||
|
||
// 创建 WebSocket 客户端
|
||
this.client = new OperationClient({
|
||
getToken: () => this.store.state.userToken,
|
||
onRequest: this.handleRequest.bind(this),
|
||
onConnected: this.handleConnected.bind(this),
|
||
onDisconnected: this.handleDisconnected.bind(this),
|
||
onError: this.handleError.bind(this),
|
||
});
|
||
|
||
// 建立连接
|
||
this.client.connect();
|
||
|
||
// 设置心跳
|
||
this.heartbeatTimer = setInterval(() => {
|
||
if (this.client) {
|
||
this.client.ping();
|
||
}
|
||
}, 30000);
|
||
}
|
||
|
||
/**
|
||
* 禁用操作模块
|
||
*/
|
||
disable() {
|
||
if (!this.enabled) {
|
||
return;
|
||
}
|
||
|
||
this.enabled = false;
|
||
|
||
if (this.heartbeatTimer) {
|
||
clearInterval(this.heartbeatTimer);
|
||
this.heartbeatTimer = null;
|
||
}
|
||
|
||
if (this.client) {
|
||
this.client.disconnect();
|
||
this.client = null;
|
||
}
|
||
|
||
this.executor = null;
|
||
this.sessionId = null;
|
||
}
|
||
|
||
/**
|
||
* 处理来自 MCP 的请求
|
||
*/
|
||
async handleRequest(action, payload) {
|
||
switch (action) {
|
||
case 'get_page_context':
|
||
return this.getPageContext(payload);
|
||
|
||
case 'execute_action':
|
||
return this.executeAction(payload);
|
||
|
||
case 'execute_element_action':
|
||
return this.executeElementAction(payload);
|
||
|
||
default:
|
||
throw new Error(`未知的操作类型: ${action}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取页面上下文
|
||
*/
|
||
getPageContext(payload) {
|
||
const includeElements = payload?.include_elements !== false;
|
||
const interactiveOnly = payload?.interactive_only || false;
|
||
const maxElements = payload?.max_elements || 100;
|
||
|
||
const context = collectPageContext(this.store, {
|
||
include_elements: includeElements,
|
||
interactive_only: interactiveOnly,
|
||
max_elements: maxElements,
|
||
});
|
||
|
||
// 将 refMap 存储到 executor,供后续元素操作使用
|
||
if (context.ref_map && this.executor) {
|
||
this.executor.setRefMap(context.ref_map);
|
||
}
|
||
|
||
return context;
|
||
}
|
||
|
||
/**
|
||
* 执行业务操作
|
||
*/
|
||
async executeAction(payload) {
|
||
if (!this.executor) {
|
||
throw new Error('操作执行器未初始化');
|
||
}
|
||
|
||
const actionName = payload?.name;
|
||
const params = payload?.params || {};
|
||
|
||
if (!actionName) {
|
||
throw new Error('缺少操作名称');
|
||
}
|
||
|
||
return this.executor.executeAction(actionName, params);
|
||
}
|
||
|
||
/**
|
||
* 执行元素操作
|
||
*/
|
||
async executeElementAction(payload) {
|
||
if (!this.executor) {
|
||
throw new Error('操作执行器未初始化');
|
||
}
|
||
|
||
const elementUid = payload?.element_uid;
|
||
const action = payload?.action;
|
||
const value = payload?.value;
|
||
|
||
if (!elementUid || !action) {
|
||
throw new Error('缺少必要参数');
|
||
}
|
||
|
||
return this.executor.executeElementAction(elementUid, action, value);
|
||
}
|
||
|
||
/**
|
||
* 处理连接成功
|
||
*/
|
||
handleConnected(sessionId) {
|
||
this.sessionId = sessionId;
|
||
this.onSessionReady?.(sessionId);
|
||
}
|
||
|
||
/**
|
||
* 处理连接断开
|
||
*/
|
||
handleDisconnected() {
|
||
this.sessionId = null;
|
||
this.onSessionLost?.();
|
||
}
|
||
|
||
/**
|
||
* 处理错误
|
||
*/
|
||
handleError(error) {
|
||
this.onError?.(error);
|
||
}
|
||
|
||
/**
|
||
* 获取当前 session ID
|
||
*/
|
||
getSessionId() {
|
||
return this.sessionId;
|
||
}
|
||
|
||
/**
|
||
* 检查是否已连接
|
||
*/
|
||
isConnected() {
|
||
return this.client?.isConnected() || false;
|
||
}
|
||
|
||
/**
|
||
* 重新连接
|
||
*/
|
||
reconnect() {
|
||
if (this.client) {
|
||
this.client.connect();
|
||
}
|
||
}
|
||
}
|
||
|
||
export default createOperationModule;
|