@wenbin_wb/dsh-bridge 2.2.9 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +39 -1
- package/README.md +39 -1
- package/client/client.js +99 -156
- package/client/index.js +87 -118
- package/docs/feishu-usage.md +93 -0
- package/lib/feishu/gateway.js +389 -0
- package/lib/feishu/index.js +219 -0
- package/lib/feishu/node.js +351 -0
- package/lib/index.js +50 -0
- package/lib/platform/conversation-bridge.js +29 -4
- package/lib/qq/gateway.js +1 -1
- package/lib/wechat/gateway.js +3 -0
- package/package.json +5 -3
package/README.en.md
CHANGED
|
@@ -19,6 +19,7 @@ Seamlessly extend your local DeepSeek Harness to mobile phones, tablets, public
|
|
|
19
19
|
- **Custom Tunnel**: Connect to your own tunnel server with a fixed domain ([Setup Guide](docs/custom-tunnel.md))
|
|
20
20
|
- **WeChat Bot (ClawBot / iLink)**: Scan a QR code to log in a WeChat personal account, then chat with, control, and approve your DeepSeek Harness agents right inside WeChat. **Multi-workspace selection, restart-persistent sessions, grouped session listing with titles, media (image/file/voice) transfer, and permission approvals** — over Tencent's official iLink Bot API, no public server or tunnel required ([Usage Guide](docs/wechat-usage.md))
|
|
21
21
|
- **QQ Bot (OpenAPI v2)**: Connect your QQ Bot to receive private/group messages, send Markdown, button keyboards, and rich media. **Full event coverage (C2C / GROUP_AT_MESSAGE_CREATE), auto token refresh, reconnection with backoff, message deduplication** — over Tencent's official QQ Bot OpenAPI v2 ([Usage Guide](docs/qq-usage.md))
|
|
22
|
+
- **Feishu / Lark Bot (Official WebSocket)**: Connect enterprise self-built apps via Feishu's official WebSocket protocol. **No public IP / no Webhook required, Markdown table formatting, native interactive card permission approvals with 1-click button actions** ([Usage Guide](docs/feishu-usage.md))
|
|
22
23
|
- **Official Brand SVG Icons**: Authentic vector brand icons for WeChat, QQ, Feishu, Telegram with real-time status indicators
|
|
23
24
|
- **Fast Version Check & 1-Click Upgrade**: Dual-channel registry check (npmmirror + npmjs fallback in ~200ms) with seamless **1-click in-app upgrade**, no terminal copying required
|
|
24
25
|
- **Dark Mode Support**: Deep integration with DeepSeek Harness Design Tokens (`--dsw-alias-*`), QR code background protection for safe dark mode scanning
|
|
@@ -38,7 +39,7 @@ Seamlessly extend your local DeepSeek Harness to mobile phones, tablets, public
|
|
|
38
39
|
| **Platform Abstraction** | Platform-agnostic core (sessions / approvals / commands / digest) shared across IM channels | ✅ **Completed** (v2.0.0) |
|
|
39
40
|
| **WeChat** | Chat with your Agent directly in WeChat | ✅ Supported (workspaces / persisted sessions / media / approvals) |
|
|
40
41
|
| **QQ Bot** | QQ bot integration for group/private chat | ✅ **Completed** (v2.1.0) — Markdown / buttons / rich media |
|
|
41
|
-
| **Feishu** | Feishu
|
|
42
|
+
| **Feishu** | Feishu / Lark official persistent WebSocket bot | ✅ **Completed** (v2.3.0) — No-public-IP WS / Card Approvals |
|
|
42
43
|
| **Telegram** | IM channel suited for self-hosting and overseas | Planned |
|
|
43
44
|
| **OpenClaw** | Connect with the OpenClaw ecosystem | Planned |
|
|
44
45
|
|
|
@@ -245,6 +246,43 @@ Integrates Tencent's official QQ Bot platform. Supports private chat, group chat
|
|
|
245
246
|
|
|
246
247
|
---
|
|
247
248
|
|
|
249
|
+
### Feishu / Lark Bot (Official WebSocket)
|
|
250
|
+
|
|
251
|
+
Connect your enterprise self-built app via Feishu's official WebSocket protocol. Supports private chat and group mentions without public IP, domain name, or webhook configuration.
|
|
252
|
+
|
|
253
|
+
**Key Highlights**
|
|
254
|
+
|
|
255
|
+
- ⚡ **100% No Public IP Required**: Direct duplex WebSocket connection to Feishu Open Platform
|
|
256
|
+
- 📜 **Card JSON 2.0 Streaming**: In-place single-card incremental streaming updates, eliminating message bubble fragmentation
|
|
257
|
+
- 🛡️ **Card 2.0 Interactive Approvals**: Native orange approval card with `[✓ Approve]` / `[✕ Reject]` action buttons for 1-click execution
|
|
258
|
+
- 📝 **Full Markdown Rendering**: Native support for headings, tables, syntax highlighting, blockquotes, and lists
|
|
259
|
+
- 🔄 **Workspace & Session Management**: Table-formatted `/sessions`, `/use N` switching, and `/workspaces` listing
|
|
260
|
+
|
|
261
|
+
**Setup Steps**
|
|
262
|
+
|
|
263
|
+
1. Go to [Feishu Open Platform](https://open.feishu.cn/app) to create a self-built app, enable "Bot" capability, and publish a version ([Detailed Guide](docs/feishu-usage.md))
|
|
264
|
+
2. Under "Events & Callbacks", select "Use WebSocket to receive events", and add `im.message.receive_v1` & `card.action.trigger`
|
|
265
|
+
3. Open DSH Settings → "Remote Access" → "IM Bots" → select "Feishu"
|
|
266
|
+
4. Fill in App ID and App Secret, then click "Save & Connect"
|
|
267
|
+
|
|
268
|
+
**Feishu Bot Commands** (Full guide in [Feishu Bot Usage Guide](docs/feishu-usage.md))
|
|
269
|
+
|
|
270
|
+
| Command | Description |
|
|
271
|
+
|---------|-------------|
|
|
272
|
+
| *(plain text)* | Send to current active agent |
|
|
273
|
+
| `/new <prompt>` | Create and start a new session in current workspace |
|
|
274
|
+
| `/new <prompt> @N` | Create a new session in workspace N |
|
|
275
|
+
| `/sessions` (or `/list`) | List all sessions in a structured Markdown table |
|
|
276
|
+
| `/use N` (or `/resume N`) | Switch to/resume session N |
|
|
277
|
+
| `/workspaces` | List all available workspaces |
|
|
278
|
+
| `/end` | End current session |
|
|
279
|
+
| `/stop` | Stop currently executing task |
|
|
280
|
+
| `/status` | View agent status dashboard |
|
|
281
|
+
| `/yes` `/no` (or `1`/`2`) | Respond to permission approval requests (or click card buttons) |
|
|
282
|
+
| `/help` | Display full command help |
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
248
286
|
## Optional Configuration
|
|
249
287
|
|
|
250
288
|
The plugin is ready to use out of the box. To customize the proxy port, add to `cordis.yml`:
|
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
- **自建隧道**:连接自己的隧道服务器,获得固定域名([搭建教程](docs/custom-tunnel.md))
|
|
20
20
|
- **微信 Bot(ClawBot / iLink)**:扫码登录微信个人号后,直接在微信里对话、控制 DeepSeek Harness 的 agent。**支持多工作区选择、会话跨重启持久化、按工作区分组查看、媒体(图片/文件/语音)收发、权限审批**——走腾讯官方 iLink Bot API,无需公网([使用说明](docs/wechat-usage.md))
|
|
21
21
|
- **QQ Bot(OpenAPI v2)**:接入 QQ 机器人,私聊/群聊接收消息,发送 Markdown、按钮键盘和富媒体。**完整事件覆盖(C2C / GROUP_AT_MESSAGE_CREATE)、Token 自动刷新、断线重连、消息去重**——走腾讯官方 QQ Bot OpenAPI v2([使用说明](docs/qq-usage.md))
|
|
22
|
+
- **飞书 Bot(官方 WebSocket 长连接)**:接入飞书开放平台企业自建应用,私聊/群聊实时交互。**无需公网 IP / 无需 Webhook、支持飞书 Markdown 表格排版、原生交互卡片权限审批一键点击确认**——走飞书官方最新 WebSocket 长连接协议([使用说明](docs/feishu-usage.md))
|
|
22
23
|
- **IM 官方品牌矢量图标**:微信、QQ、飞书、Telegram 官方矢量图标与状态展示,直接在聊天软件里呼唤你的 Agent
|
|
23
24
|
- **极速版本检查与一键升级**:国内高速镜像(npmmirror)优先 + 官方源毫秒级双通道检查,检测到新版本支持**界面一键直接升级**,无需手动打开终端
|
|
24
25
|
- **深色模式深度适配**:完美适配 DeepSeek Harness 设计系统明暗主题切换,二维码自带白底安全垫,暗光下手机扫码 100% 极速识别
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
| **平台抽象层** | 平台无关的核心(会话/审批/命令/digest)跨 IM 渠道复用 | ✅ **已完成**(v2.0.0) |
|
|
39
40
|
| **微信** | 在微信里直接与你的 Agent 对话 | ✅ 已支持(多工作区 / 会话持久化 / 媒体 / 审批) |
|
|
40
41
|
| **QQ Bot** | 接入 QQ 机器人,群聊/私聊唤起 Agent | ✅ **已完成**(v2.1.0)— Markdown / 按钮 / 富媒体 |
|
|
41
|
-
| **飞书** |
|
|
42
|
+
| **飞书** | 飞书开放平台长连接机器人,办公场景直接调用 | ✅ **已完成**(v2.3.0)— 免公网 WS / 卡片审批 |
|
|
42
43
|
| **Telegram** | 适合自托管与海外的 IM 渠道 | 规划中 |
|
|
43
44
|
| **OpenClaw** | 与 OpenClaw 生态打通 | 规划中 |
|
|
44
45
|
|
|
@@ -245,6 +246,43 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
|
|
|
245
246
|
|
|
246
247
|
---
|
|
247
248
|
|
|
249
|
+
### 飞书 Bot(官方 WebSocket 长连接)
|
|
250
|
+
|
|
251
|
+
接入飞书开放平台企业自建应用,支持单聊与群聊(群聊需 @机器人)。走飞书官方最新 WebSocket 长连接协议,无需公网 IP、无需域名、免配置 Webhook。
|
|
252
|
+
|
|
253
|
+
**功能亮点**
|
|
254
|
+
|
|
255
|
+
- ⚡ **100% 免公网 IP**:官方 WebSocket 全双工长连接,本地电脑即可直连飞书开放平台
|
|
256
|
+
- 📜 **Card JSON 2.0 原生流式打字机**:单条卡片原地增量打字机更新,彻底告别气泡拆分碎片化
|
|
257
|
+
- 🛡️ **Card 2.0 交互卡片审批**:敏感操作触发审批时下发橙色告警卡片,手机/电脑端点击 `[✓ 批准执行]` / `[✕ 拒绝执行]` 按钮一键处理
|
|
258
|
+
- 📝 **全量 Markdown 渲染**:支持多级标题、表格、代码高亮、引用块与列表
|
|
259
|
+
- 🔄 **会话与工作区管理**:支持 `/sessions` 表格化查看、`/use N` 切换、`/workspaces` 查看工作区
|
|
260
|
+
|
|
261
|
+
**使用步骤**
|
|
262
|
+
|
|
263
|
+
1. 前往 [飞书开放平台](https://open.feishu.cn/app) 创建企业自建应用,开启「机器人」能力并发布版本(详见 [飞书接入指南](docs/feishu-usage.md))
|
|
264
|
+
2. 在「事件与回调」中开启「使用长连接接收事件」,添加 `im.message.receive_v1` 和 `card.action.trigger` 事件
|
|
265
|
+
3. 打开 DSH 设置页「远程访问」→「IM 机器人」→ 选中「飞书」
|
|
266
|
+
4. 填入 App ID 和 App Secret,点击「保存并连接」即可
|
|
267
|
+
|
|
268
|
+
**飞书里的命令**(完整说明见 [飞书 Bot 使用说明](docs/feishu-usage.md))
|
|
269
|
+
|
|
270
|
+
| 命令 | 说明 |
|
|
271
|
+
|------|------|
|
|
272
|
+
| *(普通文本)* | 发给当前活动 agent |
|
|
273
|
+
| `/new <提示词>` | 在当前工作区新建会话并开始 |
|
|
274
|
+
| `/new <提示词> @N` | 在指定工作区新建会话 |
|
|
275
|
+
| `/sessions`(或 `/list`) | 查看所有历史会话(结构化表格排版) |
|
|
276
|
+
| `/use N`(或 `/resume N`) | 切换到会话 N |
|
|
277
|
+
| `/workspaces` | 列出可用工作区 |
|
|
278
|
+
| `/end` | 结束当前会话 |
|
|
279
|
+
| `/stop` | 停止当前任务 |
|
|
280
|
+
| `/status` | 查看 agent 状态看板 |
|
|
281
|
+
| `/yes` `/no`(或 `1`/`2`) | 响应权限审批请求(或直接点击卡片按钮) |
|
|
282
|
+
| `/help` | 查看全部命令 |
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
248
286
|
## 可选配置
|
|
249
287
|
|
|
250
288
|
插件开箱即用,无需配置。如需修改代理端口,在 cordis.yml 中添加:
|
package/client/client.js
CHANGED
|
@@ -425,7 +425,9 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
425
425
|
sendChunkDelayMs: String(platform.config.sendChunkDelayMs ?? 1500),
|
|
426
426
|
appId: platform.config.appId ?? "",
|
|
427
427
|
// Secret 不由后端回传;空值表示沿用已保存密钥
|
|
428
|
-
clientSecret: ""
|
|
428
|
+
clientSecret: "",
|
|
429
|
+
appSecret: "",
|
|
430
|
+
domain: platform.config.domain ?? "feishu"
|
|
429
431
|
});
|
|
430
432
|
}
|
|
431
433
|
}, [platform?.config]);
|
|
@@ -501,10 +503,14 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
501
503
|
if (platformId === "qq") {
|
|
502
504
|
payload.appId = cfgDraft.appId.trim();
|
|
503
505
|
payload.clientSecret = cfgDraft.clientSecret.trim();
|
|
506
|
+
} else if (platformId === "feishu") {
|
|
507
|
+
payload.appId = cfgDraft.appId.trim();
|
|
508
|
+
payload.appSecret = cfgDraft.appSecret.trim();
|
|
509
|
+
payload.domain = cfgDraft.domain || "feishu";
|
|
504
510
|
}
|
|
505
511
|
await act(BRIDGE_ENDPOINTS.platformSetConfig, payload);
|
|
506
512
|
}, [act, cfgDraft, platformId]);
|
|
507
|
-
const cfgDirty = cfgDraft && platform?.config && (Number(cfgDraft.digestIntervalSec) !== platform.config.digestIntervalSec || Number(cfgDraft.approvalTimeoutSec) !== platform.config.approvalTimeoutSec || Number(cfgDraft.maxMessageChars) !== platform.config.maxMessageChars || Number(cfgDraft.sendChunkDelayMs) !== platform.config.sendChunkDelayMs || platformId === "qq" && (cfgDraft.appId !== (platform.config.appId ?? "") || cfgDraft.clientSecret !== (platform.config.clientSecret ?? "")));
|
|
513
|
+
const cfgDirty = cfgDraft && platform?.config && (Number(cfgDraft.digestIntervalSec) !== platform.config.digestIntervalSec || Number(cfgDraft.approvalTimeoutSec) !== platform.config.approvalTimeoutSec || Number(cfgDraft.maxMessageChars) !== platform.config.maxMessageChars || Number(cfgDraft.sendChunkDelayMs) !== platform.config.sendChunkDelayMs || platformId === "qq" && (cfgDraft.appId !== (platform.config.appId ?? "") || cfgDraft.clientSecret !== (platform.config.clientSecret ?? "")) || platformId === "feishu" && (cfgDraft.appId !== (platform.config.appId ?? "") || cfgDraft.appSecret !== (platform.config.appSecret ?? "")));
|
|
508
514
|
if (!platform && !err) {
|
|
509
515
|
return React.createElement(
|
|
510
516
|
"div",
|
|
@@ -561,6 +567,18 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
561
567
|
rel: "noopener noreferrer",
|
|
562
568
|
style: s.btnGhost
|
|
563
569
|
}, "\u{1F310} QQ \u5F00\u653E\u5E73\u53F0"),
|
|
570
|
+
platformId === "feishu" && React.createElement("a", {
|
|
571
|
+
href: "https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/feishu-usage.md",
|
|
572
|
+
target: "_blank",
|
|
573
|
+
rel: "noopener noreferrer",
|
|
574
|
+
style: s.btnGhost
|
|
575
|
+
}, "\u{1F4D6} \u98DE\u4E66\u4F7F\u7528\u8BF4\u660E"),
|
|
576
|
+
platformId === "feishu" && React.createElement("a", {
|
|
577
|
+
href: "https://open.feishu.cn/app",
|
|
578
|
+
target: "_blank",
|
|
579
|
+
rel: "noopener noreferrer",
|
|
580
|
+
style: s.btnGhost
|
|
581
|
+
}, "\u{1F310} \u98DE\u4E66\u5F00\u653E\u5E73\u53F0"),
|
|
564
582
|
React.createElement("button", {
|
|
565
583
|
style: s.btnGhost,
|
|
566
584
|
onClick: () => setShowHelp((v) => !v)
|
|
@@ -695,19 +713,57 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
695
713
|
React.createElement(
|
|
696
714
|
"div",
|
|
697
715
|
{ style: { display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap", alignItems: "center" } },
|
|
698
|
-
platform.status !== "connected" && platform.status !== "starting" && React.createElement("button", { style: s.btnPri, onClick: onLogin, disabled: busy }, "\u91CD\u65B0\
|
|
716
|
+
platform.status !== "connected" && platform.status !== "starting" && React.createElement("button", { style: s.btnPri, onClick: onLogin, disabled: busy }, "\u91CD\u65B0\u8FDE\u63A5"),
|
|
699
717
|
(platform.status === "connected" || platform.status === "starting") && React.createElement("button", { style: s.btnGhost, onClick: onStop, disabled: busy }, "\u65AD\u5F00"),
|
|
700
718
|
React.createElement("button", {
|
|
701
719
|
style: { ...s.btnGhost, color: "var(--dsw-alias-state-error-primary,#dc2626)", borderColor: "var(--dsw-alias-state-error-primary,#dc2626)", opacity: busy ? 0.5 : 1 },
|
|
702
720
|
disabled: busy,
|
|
703
721
|
onClick: () => {
|
|
704
|
-
if (window.confirm("\u786E\u8BA4\u89E3\u7ED1\uFF1F\u8FD9\u5C06\u6E05\u9664\
|
|
722
|
+
if (window.confirm("\u786E\u8BA4\u89E3\u7ED1\uFF1F\u8FD9\u5C06\u6E05\u9664\u4FDD\u5B58\u7684\u51ED\u8BC1\u3002")) act(BRIDGE_ENDPOINTS.platformUnbind, {});
|
|
705
723
|
},
|
|
706
|
-
title: "\u6E05\u9664\u767B\u5F55\u51ED\u8BC1\uFF0C\u4E0B\u6B21\u9700\u91CD\u65B0\
|
|
724
|
+
title: "\u6E05\u9664\u767B\u5F55\u51ED\u8BC1\uFF0C\u4E0B\u6B21\u9700\u91CD\u65B0\u914D\u7F6E"
|
|
707
725
|
}, "\u89E3\u7ED1\u8D26\u53F7")
|
|
726
|
+
),
|
|
727
|
+
// 飞书专属:手机扫码直达 Bot 对话
|
|
728
|
+
platformId === "feishu" && platform.botQr && React.createElement(
|
|
729
|
+
"div",
|
|
730
|
+
{
|
|
731
|
+
style: {
|
|
732
|
+
marginTop: 12,
|
|
733
|
+
padding: 12,
|
|
734
|
+
background: "var(--dsw-alias-bg-layer-1,#ffffff)",
|
|
735
|
+
border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
|
|
736
|
+
borderRadius: 8,
|
|
737
|
+
display: "flex",
|
|
738
|
+
gap: 14,
|
|
739
|
+
alignItems: "center",
|
|
740
|
+
flexWrap: "wrap"
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
React.createElement("img", { src: platform.botQr, alt: "Feishu Bot QR", style: { width: 110, height: 110, borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", padding: 4, background: "#fff" } }),
|
|
744
|
+
React.createElement(
|
|
745
|
+
"div",
|
|
746
|
+
{ style: { flex: 1, minWidth: 160 } },
|
|
747
|
+
React.createElement("div", { style: { ...s.label, fontSize: 13, fontWeight: 600 } }, "\u{1F4F1} \u624B\u673A\u98DE\u4E66\u626B\u7801\u76F4\u8FBE\u5BF9\u8BDD"),
|
|
748
|
+
React.createElement(
|
|
749
|
+
"div",
|
|
750
|
+
{ style: { ...s.muted, fontSize: 12, marginTop: 4, lineHeight: 1.5 } },
|
|
751
|
+
"\u7528\u98DE\u4E66\u624B\u673A App \u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801\uFF0C\u7ACB\u5373\u6253\u5F00\u4E0E Bot \u5BF9\u8BDD\uFF1B\u53D1\u9001\u9996\u6761\u6D88\u606F\u81EA\u52A8\u5B8C\u6210\u767D\u540D\u5355\u6388\u6743\u3002"
|
|
752
|
+
),
|
|
753
|
+
platform.botLink && React.createElement(
|
|
754
|
+
"div",
|
|
755
|
+
{ style: { display: "flex", gap: 8, marginTop: 8 } },
|
|
756
|
+
React.createElement("a", {
|
|
757
|
+
href: platform.botLink,
|
|
758
|
+
target: "_blank",
|
|
759
|
+
rel: "noopener noreferrer",
|
|
760
|
+
style: { ...s.btnGhost, textDecoration: "none", display: "inline-flex", alignItems: "center", gap: 4, padding: "4px 10px", fontSize: 12 }
|
|
761
|
+
}, "\u5728\u98DE\u4E66\u5BA2\u6237\u7AEF\u6253\u5F00 \u2197")
|
|
762
|
+
)
|
|
763
|
+
)
|
|
708
764
|
)
|
|
709
765
|
),
|
|
710
|
-
// 未配置 /
|
|
766
|
+
// 未配置 / 登录中:表单(QQ / 飞书)或二维码(微信)
|
|
711
767
|
(!platform?.configured || showQr) && React.createElement(
|
|
712
768
|
"div",
|
|
713
769
|
{ style: s.block },
|
|
@@ -721,39 +777,58 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
721
777
|
login.phase === "scaned" ? "\u5DF2\u626B\u7801\uFF0C\u8BF7\u5728\u624B\u673A\u4E0A\u786E\u8BA4\u2026" : platformId === "wechat" ? "\u8BF7\u4F7F\u7528\u5FAE\u4FE1\u626B\u7801\u767B\u5F55\uFF08ClawBot\uFF09" : "\u8BF7\u626B\u7801\u767B\u5F55"
|
|
722
778
|
),
|
|
723
779
|
login.error && React.createElement("div", { style: { ...s.muted, marginTop: 4, color: "var(--dsw-alias-state-warn-primary,#92400e)" } }, login.error)
|
|
724
|
-
) : platformId === "qq" ? React.createElement(
|
|
780
|
+
) : platformId === "qq" || platformId === "feishu" ? React.createElement(
|
|
725
781
|
"div",
|
|
726
782
|
{ style: { display: "flex", flexDirection: "column", gap: 10, marginTop: 4 } },
|
|
783
|
+
platformId === "feishu" && React.createElement(
|
|
784
|
+
"div",
|
|
785
|
+
{
|
|
786
|
+
style: {
|
|
787
|
+
background: "var(--dsw-alias-bg-layer-1,#ffffff)",
|
|
788
|
+
border: "1px dashed var(--dsw-alias-border-l2,#e5e7eb)",
|
|
789
|
+
borderRadius: 6,
|
|
790
|
+
padding: "8px 10px",
|
|
791
|
+
fontSize: 12,
|
|
792
|
+
lineHeight: 1.5
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
React.createElement("span", { style: s.label }, "\u{1F4A1} \u626B\u7801\u81EA\u52A8\u521B\u5EFA\u5F15\u5BFC\uFF1A"),
|
|
796
|
+
React.createElement("span", { style: s.muted }, " \u53EF\u5728\u7EC8\u7AEF\u8FD0\u884C "),
|
|
797
|
+
React.createElement("code", { style: { ...s.code, fontSize: 11, background: "var(--dsw-alias-bg-layer-2,#f3f4f6)", padding: "2px 4px", borderRadius: 4 } }, "npx feishu-bot-bootstrap"),
|
|
798
|
+
React.createElement("span", { style: s.muted }, " \u624B\u673A\u626B\u7801\u4E00\u952E\u81EA\u52A8\u521B\u5EFA\u5E94\u7528\u5E76\u8F93\u51FA\u51ED\u8BC1\uFF1B\u6216\u5728\u4E0B\u65B9\u624B\u52A8\u586B\u5165\u51ED\u8BC1\u3002")
|
|
799
|
+
),
|
|
727
800
|
React.createElement(
|
|
728
801
|
"div",
|
|
729
802
|
null,
|
|
730
|
-
React.createElement(
|
|
803
|
+
React.createElement(
|
|
804
|
+
"div",
|
|
805
|
+
{ style: { ...s.muted, marginBottom: 4 } },
|
|
806
|
+
platformId === "qq" ? "AppID \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5E94\u7528 ID" : "App ID \u2014 \u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u81EA\u5EFA\u5E94\u7528 ID (cli_xxx)"
|
|
807
|
+
),
|
|
731
808
|
React.createElement("input", {
|
|
732
809
|
style: { ...s.input, width: "100%" },
|
|
733
|
-
placeholder: "\u8BF7\u8F93\u5165 AppID",
|
|
810
|
+
placeholder: platformId === "qq" ? "\u8BF7\u8F93\u5165 AppID" : "\u8BF7\u8F93\u5165 App ID (\u5982 cli_a1b2c3d4...)",
|
|
734
811
|
value: cfgDraft?.appId ?? "",
|
|
735
|
-
onChange: (e) => setCfgDraft((d) => ({ ...d, appId: e.target.value }))
|
|
736
|
-
onKeyDown: (e) => {
|
|
737
|
-
if (e.key === "Enter" && cfgDraft?.appId?.trim() && cfgDraft?.clientSecret?.trim() && !busy) saveConfig();
|
|
738
|
-
}
|
|
812
|
+
onChange: (e) => setCfgDraft((d) => ({ ...d, appId: e.target.value }))
|
|
739
813
|
})
|
|
740
814
|
),
|
|
741
815
|
React.createElement(
|
|
742
816
|
"div",
|
|
743
817
|
null,
|
|
744
|
-
React.createElement(
|
|
818
|
+
React.createElement(
|
|
819
|
+
"div",
|
|
820
|
+
{ style: { ...s.muted, marginBottom: 4 } },
|
|
821
|
+
platformId === "qq" ? "ClientSecret \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5BC6\u94A5" : "App Secret \u2014 \u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u5E94\u7528\u5BC6\u94A5"
|
|
822
|
+
),
|
|
745
823
|
React.createElement(
|
|
746
824
|
"div",
|
|
747
825
|
{ style: { display: "flex", gap: 6, alignItems: "center" } },
|
|
748
826
|
React.createElement("input", {
|
|
749
827
|
style: { ...s.input, flex: 1 },
|
|
750
828
|
type: showSecret ? "text" : "password",
|
|
751
|
-
placeholder: "\u8BF7\u8F93\u5165 ClientSecret",
|
|
752
|
-
value: cfgDraft?.clientSecret ?? "",
|
|
753
|
-
onChange: (e) => setCfgDraft((d) =>
|
|
754
|
-
onKeyDown: (e) => {
|
|
755
|
-
if (e.key === "Enter" && cfgDraft?.appId?.trim() && cfgDraft?.clientSecret?.trim() && !busy) saveConfig();
|
|
756
|
-
}
|
|
829
|
+
placeholder: platformId === "qq" ? "\u8BF7\u8F93\u5165 ClientSecret" : "\u8BF7\u8F93\u5165 App Secret",
|
|
830
|
+
value: platformId === "qq" ? cfgDraft?.clientSecret ?? "" : cfgDraft?.appSecret ?? "",
|
|
831
|
+
onChange: (e) => setCfgDraft((d) => platformId === "qq" ? { ...d, clientSecret: e.target.value } : { ...d, appSecret: e.target.value })
|
|
757
832
|
}),
|
|
758
833
|
React.createElement("button", {
|
|
759
834
|
style: { ...s.btnGhost, height: 32, padding: "0 10px", fontSize: 13, flexShrink: 0 },
|
|
@@ -767,11 +842,11 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
767
842
|
"div",
|
|
768
843
|
null,
|
|
769
844
|
React.createElement("a", {
|
|
770
|
-
href: "https://bot.q.qq.com/wiki/develop/api-v2/",
|
|
845
|
+
href: platformId === "qq" ? "https://bot.q.qq.com/wiki/develop/api-v2/" : "https://open.feishu.cn/app",
|
|
771
846
|
target: "_blank",
|
|
772
847
|
rel: "noopener noreferrer",
|
|
773
848
|
style: s.btnLink
|
|
774
|
-
}, "\u{1F4D6} \u524D\u5F80 QQ \u5F00\u653E\u5E73\u53F0\u7533\u8BF7\u673A\u5668\u4EBA")
|
|
849
|
+
}, platformId === "qq" ? "\u{1F4D6} \u524D\u5F80 QQ \u5F00\u653E\u5E73\u53F0\u7533\u8BF7\u673A\u5668\u4EBA" : "\u{1F4D6} \u524D\u5F80\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u521B\u5EFA\u4F01\u4E1A\u81EA\u5EFA\u5E94\u7528")
|
|
775
850
|
),
|
|
776
851
|
React.createElement(
|
|
777
852
|
"div",
|
|
@@ -779,7 +854,7 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
779
854
|
React.createElement("button", {
|
|
780
855
|
style: { ...s.btnPri, opacity: busy ? 0.5 : 1 },
|
|
781
856
|
onClick: saveConfig,
|
|
782
|
-
disabled: busy || !cfgDraft?.appId?.trim() || !cfgDraft?.clientSecret?.trim()
|
|
857
|
+
disabled: busy || !cfgDraft?.appId?.trim() || (platformId === "qq" ? !cfgDraft?.clientSecret?.trim() : !cfgDraft?.appSecret?.trim())
|
|
783
858
|
}, busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u5E76\u8FDE\u63A5"),
|
|
784
859
|
login.phase === "error" && React.createElement("div", { style: { ...s.muted, fontSize: 12 } }, login.error ?? "\u8FDE\u63A5\u5931\u8D25")
|
|
785
860
|
)
|
|
@@ -794,138 +869,6 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
794
869
|
login.phase === "error" && React.createElement("div", { style: { ...s.muted, fontSize: 12 } }, login.error ?? "\u767B\u5F55\u5931\u8D25")
|
|
795
870
|
)
|
|
796
871
|
),
|
|
797
|
-
// 高级设置(紧凑 2x2 网格 + 恢复默认值)
|
|
798
|
-
cfgDraft && React.createElement(
|
|
799
|
-
"div",
|
|
800
|
-
{ style: s.block },
|
|
801
|
-
React.createElement("button", {
|
|
802
|
-
style: { ...s.btnLink, fontSize: 12, marginBottom: showAdvanced ? 10 : 0 },
|
|
803
|
-
onClick: () => setShowAdvanced((v) => !v)
|
|
804
|
-
}, showAdvanced ? "\u25BE \u9AD8\u7EA7\u8BBE\u7F6E" : "\u25B8 \u9AD8\u7EA7\u8BBE\u7F6E"),
|
|
805
|
-
showAdvanced && React.createElement(
|
|
806
|
-
"div",
|
|
807
|
-
{ style: { display: "flex", flexDirection: "column", gap: 10 } },
|
|
808
|
-
// 2x2 参数网格
|
|
809
|
-
React.createElement(
|
|
810
|
-
"div",
|
|
811
|
-
{
|
|
812
|
-
style: {
|
|
813
|
-
display: "grid",
|
|
814
|
-
gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))",
|
|
815
|
-
gap: 10
|
|
816
|
-
}
|
|
817
|
-
},
|
|
818
|
-
// 心跳间隔
|
|
819
|
-
React.createElement(
|
|
820
|
-
"div",
|
|
821
|
-
null,
|
|
822
|
-
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u5FC3\u8DF3\u8FDB\u5EA6\u95F4\u9694 (\u79D2)"),
|
|
823
|
-
React.createElement("input", {
|
|
824
|
-
style: { ...s.input, width: "100%" },
|
|
825
|
-
type: "number",
|
|
826
|
-
min: 30,
|
|
827
|
-
max: 3600,
|
|
828
|
-
value: cfgDraft.digestIntervalSec,
|
|
829
|
-
onChange: (e) => setCfgDraft((d) => ({ ...d, digestIntervalSec: e.target.value }))
|
|
830
|
-
})
|
|
831
|
-
),
|
|
832
|
-
// 审批超时
|
|
833
|
-
React.createElement(
|
|
834
|
-
"div",
|
|
835
|
-
null,
|
|
836
|
-
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u5BA1\u6279\u8D85\u65F6\u81EA\u52A8\u62D2\u7EDD (\u79D2)"),
|
|
837
|
-
React.createElement("input", {
|
|
838
|
-
style: { ...s.input, width: "100%" },
|
|
839
|
-
type: "number",
|
|
840
|
-
min: 30,
|
|
841
|
-
max: 86400,
|
|
842
|
-
value: cfgDraft.approvalTimeoutSec,
|
|
843
|
-
onChange: (e) => setCfgDraft((d) => ({ ...d, approvalTimeoutSec: e.target.value }))
|
|
844
|
-
})
|
|
845
|
-
),
|
|
846
|
-
// 每气泡字数
|
|
847
|
-
React.createElement(
|
|
848
|
-
"div",
|
|
849
|
-
null,
|
|
850
|
-
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u5355\u6761\u6700\u5927\u5B57\u6570\u9650\u5236 (\u5B57)"),
|
|
851
|
-
React.createElement("input", {
|
|
852
|
-
style: { ...s.input, width: "100%" },
|
|
853
|
-
type: "number",
|
|
854
|
-
min: 100,
|
|
855
|
-
max: 1e4,
|
|
856
|
-
value: cfgDraft.maxMessageChars,
|
|
857
|
-
onChange: (e) => setCfgDraft((d) => ({ ...d, maxMessageChars: e.target.value }))
|
|
858
|
-
})
|
|
859
|
-
),
|
|
860
|
-
// 分块延迟
|
|
861
|
-
React.createElement(
|
|
862
|
-
"div",
|
|
863
|
-
null,
|
|
864
|
-
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u5206\u5757\u53D1\u9001\u5EF6\u8FDF (\u6BEB\u79D2)"),
|
|
865
|
-
React.createElement("input", {
|
|
866
|
-
style: { ...s.input, width: "100%" },
|
|
867
|
-
type: "number",
|
|
868
|
-
min: 0,
|
|
869
|
-
max: 1e4,
|
|
870
|
-
value: cfgDraft.sendChunkDelayMs,
|
|
871
|
-
onChange: (e) => setCfgDraft((d) => ({ ...d, sendChunkDelayMs: e.target.value }))
|
|
872
|
-
})
|
|
873
|
-
)
|
|
874
|
-
),
|
|
875
|
-
// QQ 平台凭证
|
|
876
|
-
platformId === "qq" && React.createElement(
|
|
877
|
-
"div",
|
|
878
|
-
{ style: { display: "flex", flexDirection: "column", gap: 10, marginTop: 4 } },
|
|
879
|
-
React.createElement(
|
|
880
|
-
"div",
|
|
881
|
-
null,
|
|
882
|
-
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "AppID \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5E94\u7528 ID"),
|
|
883
|
-
React.createElement("input", {
|
|
884
|
-
style: { ...s.input, width: "100%" },
|
|
885
|
-
placeholder: "\u8BF7\u8F93\u5165 AppID",
|
|
886
|
-
value: cfgDraft.appId,
|
|
887
|
-
onChange: (e) => setCfgDraft((d) => ({ ...d, appId: e.target.value }))
|
|
888
|
-
})
|
|
889
|
-
),
|
|
890
|
-
React.createElement(
|
|
891
|
-
"div",
|
|
892
|
-
null,
|
|
893
|
-
React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "ClientSecret \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5BC6\u94A5\uFF08\u7559\u7A7A\u5219\u4FDD\u6301\u5DF2\u4FDD\u5B58\u5BC6\u94A5\uFF09"),
|
|
894
|
-
React.createElement(
|
|
895
|
-
"div",
|
|
896
|
-
{ style: { display: "flex", gap: 6, alignItems: "center" } },
|
|
897
|
-
React.createElement("input", {
|
|
898
|
-
style: { ...s.input, flex: 1 },
|
|
899
|
-
type: showSecret ? "text" : "password",
|
|
900
|
-
placeholder: "\u7559\u7A7A\u4FDD\u6301\u5DF2\u4FDD\u5B58\u5BC6\u94A5",
|
|
901
|
-
value: cfgDraft.clientSecret,
|
|
902
|
-
onChange: (e) => setCfgDraft((d) => ({ ...d, clientSecret: e.target.value }))
|
|
903
|
-
}),
|
|
904
|
-
React.createElement("button", {
|
|
905
|
-
style: { ...s.btnGhost, height: 32, padding: "0 10px", fontSize: 13, flexShrink: 0 },
|
|
906
|
-
onClick: () => setShowSecret((v) => !v),
|
|
907
|
-
type: "button",
|
|
908
|
-
title: showSecret ? "\u9690\u85CF\u5BC6\u94A5" : "\u663E\u793A\u660E\u6587"
|
|
909
|
-
}, showSecret ? "\u{1F648} \u9690\u85CF" : "\u{1F441}\uFE0F \u663E\u793A")
|
|
910
|
-
)
|
|
911
|
-
)
|
|
912
|
-
),
|
|
913
|
-
React.createElement(
|
|
914
|
-
"div",
|
|
915
|
-
{ style: { display: "flex", gap: 8, alignItems: "center", marginTop: 4 } },
|
|
916
|
-
React.createElement("button", {
|
|
917
|
-
style: { ...s.btnPri, opacity: cfgDirty && !busy ? 1 : 0.5 },
|
|
918
|
-
disabled: !cfgDirty || busy,
|
|
919
|
-
onClick: saveConfig
|
|
920
|
-
}, busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u8BBE\u7F6E"),
|
|
921
|
-
React.createElement("button", {
|
|
922
|
-
style: { ...s.btnGhost, height: 32, fontSize: 12 },
|
|
923
|
-
onClick: resetDefaults,
|
|
924
|
-
title: "\u6062\u590D\u63A8\u8350\u9ED8\u8BA4\u914D\u7F6E"
|
|
925
|
-
}, "\u21BA \u6062\u590D\u63A8\u8350\u9ED8\u8BA4")
|
|
926
|
-
)
|
|
927
|
-
)
|
|
928
|
-
),
|
|
929
872
|
React.createElement(
|
|
930
873
|
"div",
|
|
931
874
|
{ style: s.block },
|
|
@@ -1365,7 +1308,7 @@ function BridgePanel({ rpcCall }) {
|
|
|
1365
1308
|
const IM_PLATFORMS = [
|
|
1366
1309
|
{ id: "wechat", label: "\u5FAE\u4FE1", icon: Icons.wechat, brandColor: "#07C160", desc: "ClawBot \u626B\u7801\u76F4\u8FDE \xB7 \u65E0\u9700\u516C\u7F51" },
|
|
1367
1310
|
{ id: "qq", label: "QQ", icon: Icons.qq, brandColor: "#12B7F5", desc: "\u5B98\u65B9\u673A\u5668\u4EBA \xB7 \u79C1\u804A/\u7FA4\u804A/\u6309\u94AE" },
|
|
1368
|
-
{ id: "feishu", label: "\u98DE\u4E66", icon: Icons.feishu, brandColor: "#00D6B9", desc: "\u5B98\u65B9\
|
|
1311
|
+
{ id: "feishu", label: "\u98DE\u4E66", icon: Icons.feishu, brandColor: "#00D6B9", desc: "\u5B98\u65B9 WebSocket \u957F\u8FDE\u63A5 \xB7 \u514D\u516C\u7F51" },
|
|
1369
1312
|
{ id: "telegram", label: "Telegram", icon: Icons.telegram, brandColor: "#24A1DE", desc: "\u5B98\u65B9 Bot API" }
|
|
1370
1313
|
];
|
|
1371
1314
|
tabContent = React.createElement(
|