From 709218e2aca8db4445afd6ee03ca4ea763b94270 Mon Sep 17 00:00:00 2001 From: matevip Date: Fri, 21 Aug 2026 22:24:37 -0400 Subject: [PATCH] fix(channel): expose execution trace switch for WeChat channels (#613) --- .../components/channels/ChannelEditModal.vue | 8 ++------ mateclaw-ui/src/types/index.ts | 2 ++ .../__tests__/channelMessageFilter.test.ts | 20 +++++++++++++++++++ mateclaw-ui/src/utils/channelConfigJson.ts | 9 +++++++++ 4 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 mateclaw-ui/src/utils/__tests__/channelMessageFilter.test.ts diff --git a/mateclaw-ui/src/components/channels/ChannelEditModal.vue b/mateclaw-ui/src/components/channels/ChannelEditModal.vue index 1ba35c29..740470ee 100644 --- a/mateclaw-ui/src/components/channels/ChannelEditModal.vue +++ b/mateclaw-ui/src/components/channels/ChannelEditModal.vue @@ -472,6 +472,7 @@ import { extractChannelFields, extractRenderConfig, parseConfigJson, + supportsChannelMessageFilter, type AccessControlValue, type RenderConfigValue, } from '@/utils/channelConfigJson' @@ -633,13 +634,8 @@ const needsWebhookUrl = computed(() => { return true }) -// Browser-rendered channels stream structured message parts over SSE and let -// the client decide what to draw (thinking panel, tool cards). They never go -// through the adapter's outbound text render path, which is the only place the -// message-filter config is read — so the controls would be inert there. -const BROWSER_RENDERED_TYPES = ['web', 'webchat'] const supportsMessageFilter = computed( - () => !BROWSER_RENDERED_TYPES.includes(form.value.channelType || ''), + () => supportsChannelMessageFilter(form.value.channelType), ) const isLocalhost = computed(() => { diff --git a/mateclaw-ui/src/types/index.ts b/mateclaw-ui/src/types/index.ts index 9d9ec1f2..11c703f3 100644 --- a/mateclaw-ui/src/types/index.ts +++ b/mateclaw-ui/src/types/index.ts @@ -673,6 +673,7 @@ export const CHANNEL_FIELD_DEFS: Record = { { key: 'bot_id', label: '机器人 ID', placeholder: 'bot_xxxxxxxxxx', required: true, type: 'text', tooltip: '企业微信智能机器人的 Bot ID(在企业微信后台创建智能机器人后获取)' }, { key: 'secret', label: 'Secret', placeholder: 'xxxxxxxxxxxxxxxx', required: true, sensitive: true, type: 'password', tooltip: '企业微信智能机器人的 Secret' }, { key: 'welcome_text', label: '欢迎消息', placeholder: '你好!我是你的 AI 助手', type: 'text', tooltip: '用户首次进入对话时自动发送的欢迎消息(留空则不发送)' }, + { key: 'stream_progress', label: '执行轨迹', placeholder: '', type: 'switch', defaultValue: true, tooltip: '展示处理期间的实时进度与阶段旁白;关闭后仅发送最终答复。原始思考和工具名称仍受下方过滤开关控制' }, { key: 'media_download_enabled', label: '媒体下载', placeholder: '', type: 'switch', defaultValue: false, tooltip: '下载消息中的图片和文件到本地并解密(需要本地磁盘空间)' }, { key: 'media_dir', label: '媒体目录', placeholder: 'data/media', type: 'text', tooltip: '媒体文件保存目录(默认 data/media)' }, { key: 'max_reconnect_attempts', label: '最大重连次数', placeholder: '-1 表示无限重连', type: 'number', defaultValue: -1, tooltip: 'WebSocket 断线后最大重连次数,-1 为无限重连' }, @@ -680,6 +681,7 @@ export const CHANNEL_FIELD_DEFS: Record = { weixin: [ { key: 'bot_token', label: 'Bot Token', placeholder: '扫码登录后自动获取', required: true, sensitive: true, type: 'password', tooltip: '微信 iLink Bot Token,通过扫描二维码登录获取' }, { key: 'base_url', label: 'API 地址', placeholder: 'https://ilinkai.weixin.qq.com', type: 'text', defaultValue: 'https://ilinkai.weixin.qq.com', tooltip: 'iLink Bot API 基础地址(通常无需修改)' }, + { key: 'stream_progress', label: '执行轨迹', placeholder: '', type: 'switch', defaultValue: true, tooltip: '展示处理期间的实时进度与阶段旁白;关闭后仅发送最终答复。原始思考和工具名称仍受下方过滤开关控制' }, { key: 'media_download_enabled', label: '媒体下载', placeholder: '', type: 'switch', defaultValue: false, tooltip: '下载消息中的图片、文件、视频到本地并解密' }, { key: 'media_dir', label: '媒体目录', placeholder: 'data/media', type: 'text', tooltip: '媒体文件保存目录(默认 data/media)' }, ], diff --git a/mateclaw-ui/src/utils/__tests__/channelMessageFilter.test.ts b/mateclaw-ui/src/utils/__tests__/channelMessageFilter.test.ts new file mode 100644 index 00000000..b98db62b --- /dev/null +++ b/mateclaw-ui/src/utils/__tests__/channelMessageFilter.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest' +import { supportsChannelMessageFilter } from '../channelConfigJson' +import { CHANNEL_FIELD_DEFS } from '../../types' + +describe('supportsChannelMessageFilter', () => { + it('exposes message filter controls for WeCom and Weixin channels', () => { + expect(supportsChannelMessageFilter('wecom')).toBe(true) + expect(supportsChannelMessageFilter('weixin')).toBe(true) + }) + + it('hides message filter controls for browser-rendered channels', () => { + expect(supportsChannelMessageFilter('web')).toBe(false) + expect(supportsChannelMessageFilter('webchat')).toBe(false) + }) + + it('exposes execution trace controls for WeCom and Weixin channels', () => { + expect(CHANNEL_FIELD_DEFS.wecom.map(field => field.key)).toContain('stream_progress') + expect(CHANNEL_FIELD_DEFS.weixin.map(field => field.key)).toContain('stream_progress') + }) +}) diff --git a/mateclaw-ui/src/utils/channelConfigJson.ts b/mateclaw-ui/src/utils/channelConfigJson.ts index f1c0499b..abcede4f 100644 --- a/mateclaw-ui/src/utils/channelConfigJson.ts +++ b/mateclaw-ui/src/utils/channelConfigJson.ts @@ -29,6 +29,15 @@ export const defaultRenderConfig = (): RenderConfigValue => ({ message_format: 'auto', }) +// Browser-rendered channels stream structured message parts over SSE and let +// the client decide what to draw. They never go through the adapter's outbound +// text render path, where message-filter config is read. +const BROWSER_RENDERED_TYPES = new Set(['web', 'webchat']) + +export function supportsChannelMessageFilter(channelType?: string | null): boolean { + return !BROWSER_RENDERED_TYPES.has(channelType || '') +} + export function parseConfigJson(json?: string): Record { if (!json) return {} try { return JSON.parse(json) } catch { return {} }