diff --git a/lib/client.js b/lib/client.js index 9612371..f8776db 100644 --- a/lib/client.js +++ b/lib/client.js @@ -35,6 +35,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru var index_exports = {}; __export(index_exports, { IMSettingsTab: () => IMSettingsTab, + IM_PLUGIN_VERSION: () => IM_PLUGIN_VERSION, LoopbackRecoveryNotice: () => LoopbackRecoveryNotice, apply: () => apply, inject: () => inject, @@ -43,6 +44,115 @@ __export(index_exports, { module.exports = __toCommonJS(index_exports); var React20 = __toESM(require("react"), 1); +// package.json +var package_default = { + name: "@xmanrui/dsh-im", + version: "2.4.0", + description: "\u628A\u4E5D\u79CD IM \u673A\u5668\u4EBA\u548C\u516C\u7F51 AI Office \u63A5\u5165\u672C\u673A DeepSeek Harness\u3002 Connect nine IM channels and a public AI Office to a local DeepSeek Harness.", + keywords: [ + "deepseek-harness", + "dsh", + "dsh-plugin", + "ai-agent", + "im", + "instant-messaging", + "chatbot", + "feishu", + "lark", + "wechat", + "wecom", + "dingtalk", + "qq", + "slack", + "telegram", + "discord", + "whatsapp", + "ai-office" + ], + author: { + name: "xmanrui", + url: "https://github.com/xmanrui" + }, + contributors: [ + { + name: "C3H3-AI", + url: "https://github.com/C3H3-AI" + } + ], + license: "MIT", + type: "module", + repository: { + type: "git", + url: "git+https://github.com/xmanrui/dsh-im.git" + }, + homepage: "https://github.com/xmanrui/dsh-im#readme", + bugs: "https://github.com/xmanrui/dsh-im/issues", + publishConfig: { + access: "public" + }, + bin: { + "dsh-im": "bin/dsh-im.mjs" + }, + main: "./lib/index.js", + exports: { + ".": "./lib/index.js", + "./client": "./lib/client.js", + "./package.json": "./package.json" + }, + files: [ + "assets", + "bin", + "cordis.patch.yml", + "lib", + "plugin-src", + "scripts", + "src", + "LICENSE", + "README.md", + "README.en.md", + "THIRD_PARTY_NOTICES.md" + ], + dsh: { + bundle: { + patch: "./cordis.patch.yml" + }, + client: { + inject: [ + "@deepseek-ai/dsh-client-connection", + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-settings", + "@deepseek-ai/dsh-client-ui-slots", + "@deepseek-ai/dsh-client-locale" + ], + platform: "web" + } + }, + scripts: { + build: "node plugin-src/client/build.mjs && node plugin-src/host/build.mjs", + test: "node --test test/*.test.mjs test/channels/*/*.test.mjs", + check: "npm test && npm run build && node scripts/verify-package.mjs" + }, + engines: { + node: ">=22.19" + }, + dependencies: { + "@tencent-connect/qqbot-connector": "1.2.0", + "@tencent-connect/qqbot-nodejs": "1.0.4", + "@wecom/aibot-node-sdk": "1.0.7", + "dingtalk-stream": "2.1.4", + qrcode: "1.5.4" + }, + devDependencies: { + "@larksuiteoapi/node-sdk": "1.73.0", + "@whiskeysockets/baileys": "7.0.0-rc14", + esbuild: "0.25.9", + "https-proxy-agent": "5.0.1", + react: "18.3.1", + "react-dom": "18.3.1", + "react-test-renderer": "18.3.1" + } +}; + // plugin-src/client/channel-logos.js var React = __toESM(require("react"), 1); var h = React.createElement; @@ -199,6 +309,7 @@ var EN = Object.freeze({ "IM\u673A\u5668\u4EBA\u8BBE\u7F6E": "IM bot settings", "IM \u6E20\u9053": "IM channels", "\u8BA9 DeepSeek Harness \u89E6\u624B\u53EF\u53CA": "DeepSeek Harness, always within reach", + "\u5F53\u524D\u7248\u672C": "Current version", "AI Office": "AI Office", "\uFF08\u5B9E\u9A8C\u529F\u80FD\uFF09": "(Experimental)", "AI Office \u8BBE\u7F6E": "AI Office settings", @@ -10131,9 +10242,13 @@ var CSS11 = String.raw` } .dim-page *, .dim-page *::before, .dim-page *::after { box-sizing: border-box; } .dim-title { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 0 0 18px; } -.dim-brand { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 1px; } +.dim-brand { position: relative; min-width: 0; width: max-content; max-width: 100%; display: flex; flex-direction: column; align-items: flex-start; gap: 1px; margin: -2px -6px; padding: 2px 6px; border-radius: 8px; cursor: help; } +.dim-brand:focus-visible { outline: 2px solid color-mix(in srgb, var(--dim-blue) 70%, white); outline-offset: 2px; } .dim-brandName { color: var(--dsw-alias-label-primary, #1f2329); font-size: 20px; line-height: 24px; font-weight: 800; letter-spacing: .04em; } .dim-title p { margin: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; font-weight: 500; white-space: nowrap; } +.dim-versionTooltip { position: absolute; left: 0; bottom: calc(100% + 8px); z-index: 20; width: max-content; max-width: min(220px, 80vw); display: inline-flex; align-items: center; gap: 6px; padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; } +.dim-versionTooltip strong { color: var(--dsw-alias-label-primary, #1f2329); font: 600 11px/16px ui-monospace, SFMono-Regular, Menlo, monospace; } +.dim-brand:hover .dim-versionTooltip, .dim-brand:focus .dim-versionTooltip { opacity: 1; visibility: visible; transform: translateY(0); } .dim-githubAction { position: relative; display: inline-flex; flex: none; } .dim-githubLink { min-height: 30px; display: inline-flex; align-items: center; gap: 5px; flex: none; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-1, #fff); font-size: 12px; line-height: normal; font-weight: 560; text-decoration: none; transition: border-color .15s ease, color .15s ease, background .15s ease; } .dim-githubLink:hover { border-color: #aeb3bb; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); } @@ -10425,6 +10540,7 @@ function installImStyles() { // plugin-src/client/index.js var name = "im-settings"; var inject = ["slots", "connection", "locale", "workspaces"]; +var IM_PLUGIN_VERSION = package_default.version; var CHANNELS = Object.freeze([ { id: "weixin", label: "\u5FAE\u4FE1" }, { id: "feishu", label: "\u98DE\u4E66" }, @@ -10549,6 +10665,7 @@ function IMSettingsTab({ }) { const [selected, setSelected] = React20.useState("weixin"); const [loopbackRecovery, setLoopbackRecovery] = React20.useState(null); + const versionTooltipId = React20.useId(); const githubTooltipId = React20.useId(); const active = CHANNELS.find((channel4) => channel4.id === selected) ?? CHANNELS[0]; const reportLoopbackRecovery = React20.useCallback((recovery) => { @@ -10593,9 +10710,23 @@ function IMSettingsTab({ { className: "dim-title" }, h2( "div", - { className: "dim-brand" }, + { + className: "dim-brand", + tabIndex: 0, + "aria-describedby": versionTooltipId + }, h2("strong", { className: "dim-brandName" }, "DSH-IM"), - h2("p", null, "\u8BA9 DeepSeek Harness \u89E6\u624B\u53EF\u53CA") + h2("p", null, "\u8BA9 DeepSeek Harness \u89E6\u624B\u53EF\u53CA"), + h2( + "span", + { + id: versionTooltipId, + className: "dim-versionTooltip", + role: "tooltip" + }, + h2("span", null, "\u5F53\u524D\u7248\u672C"), + h2("strong", null, `v${IM_PLUGIN_VERSION}`) + ) ), h2( "span", diff --git a/plugin-src/client/i18n.js b/plugin-src/client/i18n.js index 3236cb1..6e70923 100644 --- a/plugin-src/client/i18n.js +++ b/plugin-src/client/i18n.js @@ -8,6 +8,7 @@ const EN = Object.freeze({ 'IM机器人设置': 'IM bot settings', 'IM 渠道': 'IM channels', '让 DeepSeek Harness 触手可及': 'DeepSeek Harness, always within reach', + '当前版本': 'Current version', 'AI Office': 'AI Office', '(实验功能)': '(Experimental)', 'AI Office 设置': 'AI Office settings', diff --git a/plugin-src/client/index.js b/plugin-src/client/index.js index 1a61a27..e2dfe61 100644 --- a/plugin-src/client/index.js +++ b/plugin-src/client/index.js @@ -1,4 +1,5 @@ import * as React from 'react'; +import manifest from '../../package.json' with { type: 'json' }; import { DingtalkLogoGlyph, @@ -51,6 +52,7 @@ import { WorkspaceDirectoryPickerContext } from './workspace-editor.js'; export const name = 'im-settings'; export const inject = ['slots', 'connection', 'locale', 'workspaces']; +export const IM_PLUGIN_VERSION = manifest.version; const CHANNELS = Object.freeze([ { id: 'weixin', label: '微信' }, @@ -159,6 +161,7 @@ export function IMSettingsTab({ }) { const [selected, setSelected] = React.useState('weixin'); const [loopbackRecovery, setLoopbackRecovery] = React.useState(null); + const versionTooltipId = React.useId(); const githubTooltipId = React.useId(); const active = CHANNELS.find((channel) => channel.id === selected) ?? CHANNELS[0]; const reportLoopbackRecovery = React.useCallback((recovery) => { @@ -195,9 +198,20 @@ export function IMSettingsTab({ return h(WorkspaceDirectoryPickerContext.Provider, { value: workspaceDirectoryPicker }, h('section', { className: 'dim-page', 'aria-label': 'IM机器人设置' }, h('header', { className: 'dim-title' }, - h('div', { className: 'dim-brand' }, + h('div', { + className: 'dim-brand', + tabIndex: 0, + 'aria-describedby': versionTooltipId, + }, h('strong', { className: 'dim-brandName' }, 'DSH-IM'), - h('p', null, '让 DeepSeek Harness 触手可及')), + h('p', null, '让 DeepSeek Harness 触手可及'), + h('span', { + id: versionTooltipId, + className: 'dim-versionTooltip', + role: 'tooltip', + }, + h('span', null, '当前版本'), + h('strong', null, `v${IM_PLUGIN_VERSION}`))), h('span', { className: 'dim-githubAction' }, h('a', { className: 'dim-githubLink', diff --git a/plugin-src/client/styles.js b/plugin-src/client/styles.js index e8cbae9..29d0fe3 100644 --- a/plugin-src/client/styles.js +++ b/plugin-src/client/styles.js @@ -12,9 +12,13 @@ const CSS = String.raw` } .dim-page *, .dim-page *::before, .dim-page *::after { box-sizing: border-box; } .dim-title { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 0 0 18px; } -.dim-brand { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 1px; } +.dim-brand { position: relative; min-width: 0; width: max-content; max-width: 100%; display: flex; flex-direction: column; align-items: flex-start; gap: 1px; margin: -2px -6px; padding: 2px 6px; border-radius: 8px; cursor: help; } +.dim-brand:focus-visible { outline: 2px solid color-mix(in srgb, var(--dim-blue) 70%, white); outline-offset: 2px; } .dim-brandName { color: var(--dsw-alias-label-primary, #1f2329); font-size: 20px; line-height: 24px; font-weight: 800; letter-spacing: .04em; } .dim-title p { margin: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; font-weight: 500; white-space: nowrap; } +.dim-versionTooltip { position: absolute; left: 0; bottom: calc(100% + 8px); z-index: 20; width: max-content; max-width: min(220px, 80vw); display: inline-flex; align-items: center; gap: 6px; padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; } +.dim-versionTooltip strong { color: var(--dsw-alias-label-primary, #1f2329); font: 600 11px/16px ui-monospace, SFMono-Regular, Menlo, monospace; } +.dim-brand:hover .dim-versionTooltip, .dim-brand:focus .dim-versionTooltip { opacity: 1; visibility: visible; transform: translateY(0); } .dim-githubAction { position: relative; display: inline-flex; flex: none; } .dim-githubLink { min-height: 30px; display: inline-flex; align-items: center; gap: 5px; flex: none; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-1, #fff); font-size: 12px; line-height: normal; font-weight: 560; text-decoration: none; transition: border-color .15s ease, color .15s ease, background .15s ease; } .dim-githubLink:hover { border-color: #aeb3bb; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); } diff --git a/test/client-ui.test.mjs b/test/client-ui.test.mjs index 9ae2bd4..1e12ccd 100644 --- a/test/client-ui.test.mjs +++ b/test/client-ui.test.mjs @@ -7,6 +7,7 @@ import { renderToStaticMarkup } from 'react-dom/server'; import { apply as applyClient, + IM_PLUGIN_VERSION, IMSettingsTab, inject as clientInject, } from '../plugin-src/client/index.js'; @@ -98,6 +99,10 @@ const QQ_SOURCE_URL = new URL( ); test('IM settings renders nine IM channels plus the AI Office connector', async () => { + const { default: packageMetadata } = await import('../package.json', { + with: { type: 'json' }, + }); + const { version: packageVersion } = packageMetadata; const styles = await readFile(STYLES_URL, 'utf8'); const markup = renderToStaticMarkup(React.createElement(IMSettingsTab, { feishuRpcCall: async () => ({ ok: true, value: {} }), @@ -115,7 +120,12 @@ test('IM settings renders nine IM channels plus the AI Office connector', async assert.match(markup, /IM机器人/); assert.match(markup, /让 DeepSeek Harness 触手可及/); assert.match(markup, /class="dim-brand"/); + assert.equal(IM_PLUGIN_VERSION, packageVersion); + assert.match(markup, /class="dim-brand" tabindex="0" aria-describedby="[^"]+"/); assert.match(markup, /DSH-IM<\/strong>/); + assert.match(markup, new RegExp( + `class="dim-versionTooltip" role="tooltip">当前版本<\\/span>v${packageVersion.replaceAll('.', '\\.')}`, + )); assert.doesNotMatch(markup, /dim-brandLogo|]*>帮助与反馈 · 前往 GitHubCurrent version<\\/span>v${IM_PLUGIN_VERSION.replaceAll('.', '\\.')}`, + )); assert.match(markup, /Help & feedback · Open GitHub/); assert.match(markup, />WeChat<|>Feishu<|>DingTalk<|>WeComQQ<[^]*>Slack<[^]*>Telegram<[^]*>Discord<[^]*>WhatsApp