@wu529778790/open-im 1.9.3-beta.6 → 1.9.3-beta.8

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.
@@ -40,7 +40,7 @@ export declare const PAGE_TEXTS: {
40
40
  readonly serviceIdleMeta: "Bridge has not been started yet";
41
41
  readonly listSeparator: ", ";
42
42
  readonly platformsTitle: "Platforms";
43
- readonly platformsHint: "Disabled platforms keep their saved values.";
43
+ readonly platformsHint: "Follow the setup checklist on Overview first. Disabled platforms still keep saved values.";
44
44
  readonly enabled: "Enabled";
45
45
  readonly readyState: "Ready";
46
46
  readonly setupRequired: "Setup required";
@@ -81,7 +81,7 @@ export declare const PAGE_TEXTS: {
81
81
  readonly optional: "Optional";
82
82
  readonly commaSeparatedIds: "Comma-separated IDs";
83
83
  readonly aiTitle: "AI Tooling";
84
- readonly aiHint: "";
84
+ readonly aiHint: "Pick the default tool first. Claude SDK uses API keys in ~/.claude/settings.json (editable below). Codex / CodeBuddy need a CLI path.";
85
85
  readonly claudeNote: "Claude credentials are still read from environment variables or ~/.claude/settings.json. This page manages local bridge config, not Claude account auth.";
86
86
  readonly aiCommonTitle: "Shared defaults";
87
87
  readonly aiCommonHint: "Choose the default AI tool first. Tool-specific fields are shown below.";
@@ -134,6 +134,36 @@ export declare const PAGE_TEXTS: {
134
134
  readonly saveBtn: "Save";
135
135
  readonly jsonValid: "Valid JSON";
136
136
  readonly jsonInvalid: "Invalid JSON: {error}";
137
+ readonly wizardTitle: "First-time setup";
138
+ readonly wizardStep1Title: "Connect an IM channel";
139
+ readonly wizardStep1Desc: "Enable at least one platform below and fill every required field. Use Check config when available.";
140
+ readonly wizardStep2Title: "Set the default AI tool";
141
+ readonly wizardStep2Desc: "Claude SDK: API keys live in ~/.claude/settings.json (expand on this page). Codex / CodeBuddy: set the CLI path in the AI section.";
142
+ readonly wizardStep3Title: "Validate and save";
143
+ readonly wizardStep3Desc: "Click Validate, then Save config. Fix any errors shown in the banner above.";
144
+ readonly wizardStep4Title: "Start the bridge";
145
+ readonly wizardStep4Desc: "Open Service and click Start bridge. If it exits, read ~/.open-im/logs.";
146
+ readonly wizardStatusDone: "Done";
147
+ readonly wizardStatusTodo: "To do";
148
+ readonly wizardJumpPlatforms: "Open Platforms";
149
+ readonly wizardJumpAi: "Open AI";
150
+ readonly wizardJumpService: "Open Service";
151
+ readonly validationNoPlatformEnabled: "Enable at least one IM platform and fill its required credentials before saving.";
152
+ readonly validationPlatformIncomplete: "Platform \"{platform}\" is enabled but these required fields are empty: {fields}";
153
+ readonly validationAiCodexNoCli: "Default AI is Codex but Codex CLI path is empty. Set it under AI Tooling or change the default tool.";
154
+ readonly validationAiCodebuddyNoCli: "Default AI is CodeBuddy but CodeBuddy CLI path is empty. Set it under AI Tooling or change the default tool.";
155
+ readonly onboardingTitle: "Welcome to open-im";
156
+ readonly onboardingDismiss: "Got it";
157
+ readonly onboardingReadme: "README (setup)";
158
+ readonly onboardingBody: "<p><strong>open-im</strong> bridges Telegram, Feishu, QQ, WeWork, DingTalk, and WorkBuddy to Claude / Codex / CodeBuddy on your machine.</p><ol style=\"margin:12px 0 12px 18px;line-height:1.65\"><li>Pick <strong>one</strong> chat app in <strong>Platforms</strong> and paste credentials (see hints under each field).</li><li>Set the <strong>default AI</strong> and, for Claude SDK, API keys in <strong>~/.claude/settings.json</strong> on this page.</li><li>Use <strong>Validate</strong> then <strong>Save config</strong>, then <strong>Start bridge</strong> under Service.</li><li>CLI alternative: run <code style=\"background:var(--bg-tertiary);padding:2px 6px;border-radius:4px\">open-im init</code> in a terminal.</li></ol>";
159
+ readonly tipTelegramToken: "From Telegram, open <a href=\"https://t.me/BotFather\" target=\"_blank\" rel=\"noopener\">@BotFather</a> → /newbot → copy the token.";
160
+ readonly tipFeishuAppId: "Feishu Open Platform → your app → Credentials → App ID.";
161
+ readonly tipFeishuSecret: "Same page → App Secret (click show / reset if needed).";
162
+ readonly tipQqAppId: "<a href=\"https://bot.q.qq.com\" target=\"_blank\" rel=\"noopener\">QQ bot console</a> → your bot → App ID.";
163
+ readonly tipQqSecret: "Same console → App Secret.";
164
+ readonly tipWeworkCorp: "WeCom admin → app → view Corp ID (or smart-bot Bot ID) and Secret.";
165
+ readonly tipDingtalkClient: "<a href=\"https://open-dev.dingtalk.com\" target=\"_blank\" rel=\"noopener\">DingTalk Open Platform</a> → internal app → App Key & App Secret.";
166
+ readonly tipWorkbuddyToken: "Use terminal <code>open-im init</code> for WorkBuddy OAuth, or paste tokens from CodeBuddy login.";
137
167
  };
138
168
  readonly zh: {
139
169
  readonly pageTitle: "open-im 本地控制台";
@@ -174,7 +204,7 @@ export declare const PAGE_TEXTS: {
174
204
  readonly serviceIdleMeta: "桥接服务尚未启动";
175
205
  readonly listSeparator: "、";
176
206
  readonly platformsTitle: "平台配置";
177
- readonly platformsHint: "禁用的平台会保留已保存的值。";
207
+ readonly platformsHint: "建议先在概览页按引导步骤操作。禁用的平台仍保留已保存的值。";
178
208
  readonly enabled: "启用";
179
209
  readonly readyState: "已就绪";
180
210
  readonly setupRequired: "需要完成配置";
@@ -214,7 +244,7 @@ export declare const PAGE_TEXTS: {
214
244
  readonly optional: "可选";
215
245
  readonly commaSeparatedIds: "多个 ID 用逗号分隔";
216
246
  readonly aiTitle: "AI 工具配置";
217
- readonly aiHint: "";
247
+ readonly aiHint: "先选默认 AI。Claude SDK 的 API Key 写在 ~/.claude/settings.json(下方可编辑)。Codex / CodeBuddy 需填 CLI 路径。";
218
248
  readonly claudeNote: "Claude 凭证仍然从环境变量或 ~/.claude/settings.json 读取。这个页面只管理本地桥接配置,不负责 Claude 账号登录。";
219
249
  readonly aiCommonTitle: "公共默认配置";
220
250
  readonly aiCommonHint: "先选择默认 AI 工具,下方再显示对应的工具专属配置。";
@@ -264,5 +294,35 @@ export declare const PAGE_TEXTS: {
264
294
  readonly saveBtn: "保存";
265
295
  readonly jsonValid: "JSON 有效";
266
296
  readonly jsonInvalid: "JSON 无效:{error}";
297
+ readonly wizardTitle: "首次使用引导";
298
+ readonly wizardStep1Title: "接入一个 IM 渠道";
299
+ readonly wizardStep1Desc: "在下方启用至少一个平台,并填写所有必填项。有条件时用「校验配置」测试凭证。";
300
+ readonly wizardStep2Title: "设置默认 AI 工具";
301
+ readonly wizardStep2Desc: "Claude SDK:API Key 写在 ~/.claude/settings.json(本页可展开编辑)。Codex / CodeBuddy:在 AI 区填 CLI 路径。";
302
+ readonly wizardStep3Title: "校验并保存";
303
+ readonly wizardStep3Desc: "先点「校验」,再点「保存配置」。按顶部提示修复错误。";
304
+ readonly wizardStep4Title: "启动桥接";
305
+ readonly wizardStep4Desc: "到「服务」区点「启动桥接」。若马上退出,查 ~/.open-im/logs 日志。";
306
+ readonly wizardStatusDone: "已完成";
307
+ readonly wizardStatusTodo: "待完成";
308
+ readonly wizardJumpPlatforms: "打开平台配置";
309
+ readonly wizardJumpAi: "打开 AI 配置";
310
+ readonly wizardJumpService: "打开服务控制";
311
+ readonly validationNoPlatformEnabled: "保存前请至少启用一个 IM 平台,并填完必填凭证。";
312
+ readonly validationPlatformIncomplete: "平台「{platform}」已启用,但以下必填项为空:{fields}";
313
+ readonly validationAiCodexNoCli: "默认 AI 为 Codex,但 Codex CLI 路径为空。请在 AI 区填写,或改默认工具。";
314
+ readonly validationAiCodebuddyNoCli: "默认 AI 为 CodeBuddy,但 CodeBuddy CLI 路径为空。请在 AI 区填写,或改默认工具。";
315
+ readonly onboardingTitle: "欢迎使用 open-im";
316
+ readonly onboardingDismiss: "知道了";
317
+ readonly onboardingReadme: "README(部署说明)";
318
+ readonly onboardingBody: "<p><strong>open-im</strong> 在本机把 Telegram、飞书、QQ、企微、钉钉、WorkBuddy 等渠道连到 Claude / Codex / CodeBuddy。</p><ol style=\"margin:12px 0 12px 18px;line-height:1.65\"><li>在<strong>平台配置</strong>选一个聊天应用,按字段下方提示填凭证。</li><li>设<strong>默认 AI</strong>;用 Claude SDK 时把 API Key 写进<strong>~/.claude/settings.json</strong>(本页可编辑)。</li><li>先<strong>校验</strong>再<strong>保存配置</strong>,然后到<strong>服务</strong>启动桥接。</li><li>也可在终端运行 <code style=\"background:var(--bg-tertiary);padding:2px 6px;border-radius:4px\">open-im init</code> 交互配置。</li></ol>";
319
+ readonly tipTelegramToken: "在 Telegram 搜 <a href=\"https://t.me/BotFather\" target=\"_blank\" rel=\"noopener\">@BotFather</a>,发 /newbot 创建机器人后复制 Token。";
320
+ readonly tipFeishuAppId: "飞书开放平台 → 应用 → 凭证与基础信息 → App ID。";
321
+ readonly tipFeishuSecret: "同一页 App Secret(可重置后查看)。";
322
+ readonly tipQqAppId: "<a href=\"https://bot.q.qq.com\" target=\"_blank\" rel=\"noopener\">QQ 开放平台</a> → 机器人 → App ID。";
323
+ readonly tipQqSecret: "同一处获取 App Secret。";
324
+ readonly tipWeworkCorp: "企业微信管理后台 → 应用 → 查省 Corp ID / 智能机器人 Bot ID 与 Secret。";
325
+ readonly tipDingtalkClient: "<a href=\"https://open-dev.dingtalk.com\" target=\"_blank\" rel=\"noopener\">钉钉开放平台</a> → 企业内部应用 → AppKey / AppSecret。";
326
+ readonly tipWorkbuddyToken: "建议在终端运行 <code>open-im init</code> 完成 WorkBuddy 授权;或粘贴 CodeBuddy 登录后的 Token。";
267
327
  };
268
328
  };
@@ -40,7 +40,7 @@ export const PAGE_TEXTS = {
40
40
  serviceIdleMeta: "Bridge has not been started yet",
41
41
  listSeparator: ", ",
42
42
  platformsTitle: "Platforms",
43
- platformsHint: "Disabled platforms keep their saved values.",
43
+ platformsHint: "Follow the setup checklist on Overview first. Disabled platforms still keep saved values.",
44
44
  enabled: "Enabled",
45
45
  readyState: "Ready",
46
46
  setupRequired: "Setup required",
@@ -81,7 +81,7 @@ export const PAGE_TEXTS = {
81
81
  optional: "Optional",
82
82
  commaSeparatedIds: "Comma-separated IDs",
83
83
  aiTitle: "AI Tooling",
84
- aiHint: "",
84
+ aiHint: "Pick the default tool first. Claude SDK uses API keys in ~/.claude/settings.json (editable below). Codex / CodeBuddy need a CLI path.",
85
85
  claudeNote: "Claude credentials are still read from environment variables or ~/.claude/settings.json. This page manages local bridge config, not Claude account auth.",
86
86
  aiCommonTitle: "Shared defaults",
87
87
  aiCommonHint: "Choose the default AI tool first. Tool-specific fields are shown below.",
@@ -134,6 +134,36 @@ export const PAGE_TEXTS = {
134
134
  saveBtn: "Save",
135
135
  jsonValid: "Valid JSON",
136
136
  jsonInvalid: "Invalid JSON: {error}",
137
+ wizardTitle: "First-time setup",
138
+ wizardStep1Title: "Connect an IM channel",
139
+ wizardStep1Desc: "Enable at least one platform below and fill every required field. Use Check config when available.",
140
+ wizardStep2Title: "Set the default AI tool",
141
+ wizardStep2Desc: "Claude SDK: API keys live in ~/.claude/settings.json (expand on this page). Codex / CodeBuddy: set the CLI path in the AI section.",
142
+ wizardStep3Title: "Validate and save",
143
+ wizardStep3Desc: "Click Validate, then Save config. Fix any errors shown in the banner above.",
144
+ wizardStep4Title: "Start the bridge",
145
+ wizardStep4Desc: "Open Service and click Start bridge. If it exits, read ~/.open-im/logs.",
146
+ wizardStatusDone: "Done",
147
+ wizardStatusTodo: "To do",
148
+ wizardJumpPlatforms: "Open Platforms",
149
+ wizardJumpAi: "Open AI",
150
+ wizardJumpService: "Open Service",
151
+ validationNoPlatformEnabled: "Enable at least one IM platform and fill its required credentials before saving.",
152
+ validationPlatformIncomplete: "Platform \"{platform}\" is enabled but these required fields are empty: {fields}",
153
+ validationAiCodexNoCli: "Default AI is Codex but Codex CLI path is empty. Set it under AI Tooling or change the default tool.",
154
+ validationAiCodebuddyNoCli: "Default AI is CodeBuddy but CodeBuddy CLI path is empty. Set it under AI Tooling or change the default tool.",
155
+ onboardingTitle: "Welcome to open-im",
156
+ onboardingDismiss: "Got it",
157
+ onboardingReadme: "README (setup)",
158
+ onboardingBody: "<p><strong>open-im</strong> bridges Telegram, Feishu, QQ, WeWork, DingTalk, and WorkBuddy to Claude / Codex / CodeBuddy on your machine.</p><ol style=\"margin:12px 0 12px 18px;line-height:1.65\"><li>Pick <strong>one</strong> chat app in <strong>Platforms</strong> and paste credentials (see hints under each field).</li><li>Set the <strong>default AI</strong> and, for Claude SDK, API keys in <strong>~/.claude/settings.json</strong> on this page.</li><li>Use <strong>Validate</strong> then <strong>Save config</strong>, then <strong>Start bridge</strong> under Service.</li><li>CLI alternative: run <code style=\"background:var(--bg-tertiary);padding:2px 6px;border-radius:4px\">open-im init</code> in a terminal.</li></ol>",
159
+ tipTelegramToken: 'From Telegram, open <a href="https://t.me/BotFather" target="_blank" rel="noopener">@BotFather</a> → /newbot → copy the token.',
160
+ tipFeishuAppId: "Feishu Open Platform → your app → Credentials → App ID.",
161
+ tipFeishuSecret: "Same page → App Secret (click show / reset if needed).",
162
+ tipQqAppId: '<a href="https://bot.q.qq.com" target="_blank" rel="noopener">QQ bot console</a> → your bot → App ID.',
163
+ tipQqSecret: "Same console → App Secret.",
164
+ tipWeworkCorp: "WeCom admin → app → view Corp ID (or smart-bot Bot ID) and Secret.",
165
+ tipDingtalkClient: '<a href="https://open-dev.dingtalk.com" target="_blank" rel="noopener">DingTalk Open Platform</a> → internal app → App Key & App Secret.',
166
+ tipWorkbuddyToken: 'Use terminal <code>open-im init</code> for WorkBuddy OAuth, or paste tokens from CodeBuddy login.',
137
167
  },
138
168
  zh: {
139
169
  pageTitle: "open-im \u672c\u5730\u63a7\u5236\u53f0",
@@ -174,7 +204,7 @@ export const PAGE_TEXTS = {
174
204
  serviceIdleMeta: "\u6865\u63a5\u670d\u52a1\u5c1a\u672a\u542f\u52a8",
175
205
  listSeparator: "\u3001",
176
206
  platformsTitle: "\u5e73\u53f0\u914d\u7f6e",
177
- platformsHint: "\u7981\u7528\u7684\u5e73\u53f0\u4f1a\u4fdd\u7559\u5df2\u4fdd\u5b58\u7684\u503c\u3002",
207
+ platformsHint: "\u5efa\u8bae\u5148\u5728\u6982\u89c8\u9875\u6309\u5f15\u5bfc\u6b65\u9aa4\u64cd\u4f5c\u3002\u7981\u7528\u7684\u5e73\u53f0\u4ecd\u4fdd\u7559\u5df2\u4fdd\u5b58\u7684\u503c\u3002",
178
208
  enabled: "\u542f\u7528",
179
209
  readyState: "\u5df2\u5c31\u7eea",
180
210
  setupRequired: "\u9700\u8981\u5b8c\u6210\u914d\u7f6e",
@@ -214,7 +244,7 @@ export const PAGE_TEXTS = {
214
244
  optional: "\u53ef\u9009",
215
245
  commaSeparatedIds: "\u591a\u4e2a ID \u7528\u9017\u53f7\u5206\u9694",
216
246
  aiTitle: "AI \u5de5\u5177\u914d\u7f6e",
217
- aiHint: "",
247
+ aiHint: "\u5148\u9009\u9ed8\u8ba4 AI\u3002Claude SDK \u7684 API Key \u5199\u5728 ~/.claude/settings.json\uff08\u4e0b\u65b9\u53ef\u7f16\u8f91\uff09\u3002Codex / CodeBuddy \u9700\u586b CLI \u8def\u5f84\u3002",
218
248
  claudeNote: "Claude \u51ed\u8bc1\u4ecd\u7136\u4ece\u73af\u5883\u53d8\u91cf\u6216 ~/.claude/settings.json \u8bfb\u53d6\u3002\u8fd9\u4e2a\u9875\u9762\u53ea\u7ba1\u7406\u672c\u5730\u6865\u63a5\u914d\u7f6e\uff0c\u4e0d\u8d1f\u8d23 Claude \u8d26\u53f7\u767b\u5f55\u3002",
219
249
  aiCommonTitle: "\u516c\u5171\u9ed8\u8ba4\u914d\u7f6e",
220
250
  aiCommonHint: "\u5148\u9009\u62e9\u9ed8\u8ba4 AI \u5de5\u5177\uff0c\u4e0b\u65b9\u518d\u663e\u793a\u5bf9\u5e94\u7684\u5de5\u5177\u4e13\u5c5e\u914d\u7f6e\u3002",
@@ -264,5 +294,35 @@ export const PAGE_TEXTS = {
264
294
  saveBtn: "\u4fdd\u5b58",
265
295
  jsonValid: "JSON \u6709\u6548",
266
296
  jsonInvalid: "JSON \u65e0\u6548\uff1a{error}",
297
+ wizardTitle: "\u9996\u6b21\u4f7f\u7528\u5f15\u5bfc",
298
+ wizardStep1Title: "\u63a5\u5165\u4e00\u4e2a IM \u6e20\u9053",
299
+ wizardStep1Desc: "\u5728\u4e0b\u65b9\u542f\u7528\u81f3\u5c11\u4e00\u4e2a\u5e73\u53f0\uff0c\u5e76\u586b\u5199\u6240\u6709\u5fc5\u586b\u9879\u3002\u6709\u6761\u4ef6\u65f6\u7528\u300c\u6821\u9a8c\u914d\u7f6e\u300d\u6d4b\u8bd5\u51ed\u8bc1\u3002",
300
+ wizardStep2Title: "\u8bbe\u7f6e\u9ed8\u8ba4 AI \u5de5\u5177",
301
+ wizardStep2Desc: "Claude SDK\uff1aAPI Key \u5199\u5728 ~/.claude/settings.json\uff08\u672c\u9875\u53ef\u5c55\u5f00\u7f16\u8f91\uff09\u3002Codex / CodeBuddy\uff1a\u5728 AI \u533a\u586b CLI \u8def\u5f84\u3002",
302
+ wizardStep3Title: "\u6821\u9a8c\u5e76\u4fdd\u5b58",
303
+ wizardStep3Desc: "\u5148\u70b9\u300c\u6821\u9a8c\u300d\uff0c\u518d\u70b9\u300c\u4fdd\u5b58\u914d\u7f6e\u300d\u3002\u6309\u9876\u90e8\u63d0\u793a\u4fee\u590d\u9519\u8bef\u3002",
304
+ wizardStep4Title: "\u542f\u52a8\u6865\u63a5",
305
+ wizardStep4Desc: "\u5230\u300c\u670d\u52a1\u300d\u533a\u70b9\u300c\u542f\u52a8\u6865\u63a5\u300d\u3002\u82e5\u9a6c\u4e0a\u9000\u51fa\uff0c\u67e5 ~/.open-im/logs \u65e5\u5fd7\u3002",
306
+ wizardStatusDone: "\u5df2\u5b8c\u6210",
307
+ wizardStatusTodo: "\u5f85\u5b8c\u6210",
308
+ wizardJumpPlatforms: "\u6253\u5f00\u5e73\u53f0\u914d\u7f6e",
309
+ wizardJumpAi: "\u6253\u5f00 AI \u914d\u7f6e",
310
+ wizardJumpService: "\u6253\u5f00\u670d\u52a1\u63a7\u5236",
311
+ validationNoPlatformEnabled: "\u4fdd\u5b58\u524d\u8bf7\u81f3\u5c11\u542f\u7528\u4e00\u4e2a IM \u5e73\u53f0\uff0c\u5e76\u586b\u5b8c\u5fc5\u586b\u51ed\u8bc1\u3002",
312
+ validationPlatformIncomplete: "\u5e73\u53f0\u300c{platform}\u300d\u5df2\u542f\u7528\uff0c\u4f46\u4ee5\u4e0b\u5fc5\u586b\u9879\u4e3a\u7a7a\uff1a{fields}",
313
+ validationAiCodexNoCli: "\u9ed8\u8ba4 AI \u4e3a Codex\uff0c\u4f46 Codex CLI \u8def\u5f84\u4e3a\u7a7a\u3002\u8bf7\u5728 AI \u533a\u586b\u5199\uff0c\u6216\u6539\u9ed8\u8ba4\u5de5\u5177\u3002",
314
+ validationAiCodebuddyNoCli: "\u9ed8\u8ba4 AI \u4e3a CodeBuddy\uff0c\u4f46 CodeBuddy CLI \u8def\u5f84\u4e3a\u7a7a\u3002\u8bf7\u5728 AI \u533a\u586b\u5199\uff0c\u6216\u6539\u9ed8\u8ba4\u5de5\u5177\u3002",
315
+ onboardingTitle: "\u6b22\u8fce\u4f7f\u7528 open-im",
316
+ onboardingDismiss: "\u77e5\u9053\u4e86",
317
+ onboardingReadme: "README\uff08\u90e8\u7f72\u8bf4\u660e\uff09",
318
+ onboardingBody: "<p><strong>open-im</strong> \u5728\u672c\u673a\u628a Telegram\u3001\u98de\u4e66\u3001QQ\u3001\u4f01\u5fae\u3001\u9489\u9489\u3001WorkBuddy \u7b49\u6e20\u9053\u8fde\u5230 Claude / Codex / CodeBuddy\u3002</p><ol style=\"margin:12px 0 12px 18px;line-height:1.65\"><li>\u5728<strong>\u5e73\u53f0\u914d\u7f6e</strong>\u9009\u4e00\u4e2a\u804a\u5929\u5e94\u7528\uff0c\u6309\u5b57\u6bb5\u4e0b\u65b9\u63d0\u793a\u586b\u51ed\u8bc1\u3002</li><li>\u8bbe<strong>\u9ed8\u8ba4 AI</strong>\uff1b\u7528 Claude SDK \u65f6\u628a API Key \u5199\u8fdb<strong>~/.claude/settings.json</strong>\uff08\u672c\u9875\u53ef\u7f16\u8f91\uff09\u3002</li><li>\u5148<strong>\u6821\u9a8c</strong>\u518d<strong>\u4fdd\u5b58\u914d\u7f6e</strong>\uff0c\u7136\u540e\u5230<strong>\u670d\u52a1</strong>\u542f\u52a8\u6865\u63a5\u3002</li><li>\u4e5f\u53ef\u5728\u7ec8\u7aef\u8fd0\u884c <code style=\"background:var(--bg-tertiary);padding:2px 6px;border-radius:4px\">open-im init</code> \u4ea4\u4e92\u914d\u7f6e\u3002</li></ol>",
319
+ tipTelegramToken: '\u5728 Telegram \u641c <a href="https://t.me/BotFather" target="_blank" rel="noopener">@BotFather</a>\uff0c\u53d1 /newbot \u521b\u5efa\u673a\u5668\u4eba\u540e\u590d\u5236 Token\u3002',
320
+ tipFeishuAppId: "\u98de\u4e66\u5f00\u653e\u5e73\u53f0 \u2192 \u5e94\u7528 \u2192 \u51ed\u8bc1\u4e0e\u57fa\u7840\u4fe1\u606f \u2192 App ID\u3002",
321
+ tipFeishuSecret: "\u540c\u4e00\u9875 App Secret\uff08\u53ef\u91cd\u7f6e\u540e\u67e5\u770b\uff09\u3002",
322
+ tipQqAppId: '<a href="https://bot.q.qq.com" target="_blank" rel="noopener">QQ \u5f00\u653e\u5e73\u53f0</a> \u2192 \u673a\u5668\u4eba \u2192 App ID\u3002',
323
+ tipQqSecret: "\u540c\u4e00\u5904\u83b7\u53d6 App Secret\u3002",
324
+ tipWeworkCorp: "\u4f01\u4e1a\u5fae\u4fe1\u7ba1\u7406\u540e\u53f0 \u2192 \u5e94\u7528 \u2192 \u67e5\u7701 Corp ID / \u667a\u80fd\u673a\u5668\u4eba Bot ID \u4e0e Secret\u3002",
325
+ tipDingtalkClient: '<a href="https://open-dev.dingtalk.com" target="_blank" rel="noopener">\u9489\u9489\u5f00\u653e\u5e73\u53f0</a> \u2192 \u4f01\u4e1a\u5185\u90e8\u5e94\u7528 \u2192 AppKey / AppSecret\u3002',
326
+ tipWorkbuddyToken: "\u5efa\u8bae\u5728\u7ec8\u7aef\u8fd0\u884c <code>open-im init</code> \u5b8c\u6210 WorkBuddy \u6388\u6743\uff1b\u6216\u7c98\u8d34 CodeBuddy \u767b\u5f55\u540e\u7684 Token\u3002",
267
327
  }
268
328
  };
@@ -10,6 +10,8 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
10
10
  const aiTools = ["claude", "codex", "codebuddy"];
11
11
  const STORAGE_KEY_LANG = "open-im-web-lang";
12
12
  const STORAGE_KEY_DARK_MODE = "open-im-web-dark-mode";
13
+ const STORAGE_KEY_ONBOARDING = "open-im-dashboard-onboarding-v1";
14
+ const STORAGE_KEY_SAVED_SESSION = "open-im-setup-saved-session";
13
15
  const POLLING_INTERVAL = 10000;
14
16
  const toolLabels = { claude: "Claude", codex: "Codex", codebuddy: "CodeBuddy" };
15
17
 
@@ -67,6 +69,7 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
67
69
  const texts = __PAGE_TEXTS__;
68
70
  let currentMeta = null;
69
71
  let cachedServiceData = null;
72
+ let lastHealthPayload = null;
70
73
  let currentLang = (localStorage.getItem(STORAGE_KEY_LANG) || "").startsWith("zh") ? "zh" : ((navigator.language || "").startsWith("zh") ? "zh" : "en");
71
74
 
72
75
  // Translation helper
@@ -166,6 +169,22 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
166
169
  { id: "resetJsonButtonText", key: "resetJson" },
167
170
  { id: "saveClaudeSettingsBtnText", key: "saveBtn" },
168
171
  { id: "saveOpenImConfigBtnText", key: "saveBtn" },
172
+ { id: "wizardTitle", key: "wizardTitle" },
173
+ { id: "wizardStep1Title", key: "wizardStep1Title" },
174
+ { id: "wizardStep1Desc", key: "wizardStep1Desc" },
175
+ { id: "wizardStep2Title", key: "wizardStep2Title" },
176
+ { id: "wizardStep2Desc", key: "wizardStep2Desc" },
177
+ { id: "wizardStep3Title", key: "wizardStep3Title" },
178
+ { id: "wizardStep3Desc", key: "wizardStep3Desc" },
179
+ { id: "wizardStep4Title", key: "wizardStep4Title" },
180
+ { id: "wizardStep4Desc", key: "wizardStep4Desc" },
181
+ { id: "wizardJumpPlatforms", key: "wizardJumpPlatforms" },
182
+ { id: "wizardJumpAi", key: "wizardJumpAi" },
183
+ { id: "wizardJumpService", key: "wizardJumpService" },
184
+ { id: "wizardJumpService2", key: "wizardJumpService" },
185
+ { id: "onboardingTitle", key: "onboardingTitle" },
186
+ { id: "onboardingDismiss", key: "onboardingDismiss" },
187
+ { id: "onboardingReadme", key: "onboardingReadme" },
169
188
  ],
170
189
  platformLabels: {
171
190
  enabled: { suffix: "-label", key: "enabled" },
@@ -274,6 +293,24 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
274
293
  if (helpBlock) helpBlock.innerHTML = t(key);
275
294
  });
276
295
 
296
+ // Short per-field tips (HTML)
297
+ [
298
+ ["telegram-botToken-tip", "tipTelegramToken"],
299
+ ["feishu-appId-tip", "tipFeishuAppId"],
300
+ ["feishu-appSecret-tip", "tipFeishuSecret"],
301
+ ["qq-appId-tip", "tipQqAppId"],
302
+ ["qq-secret-tip", "tipQqSecret"],
303
+ ["wework-corpId-tip", "tipWeworkCorp"],
304
+ ["dingtalk-clientId-tip", "tipDingtalkClient"],
305
+ ["workbuddy-accessToken-tip", "tipWorkbuddyToken"],
306
+ ].forEach(([tipId, tipKey]) => {
307
+ const tipEl = el(tipId);
308
+ if (tipEl) tipEl.innerHTML = t(tipKey);
309
+ });
310
+
311
+ const onboardBody = el("onboardingBody");
312
+ if (onboardBody) onboardBody.innerHTML = t("onboardingBody");
313
+
277
314
  // AI labels
278
315
  LANGUAGE_UPDATES.aiLabels.forEach(({ id, key }) => {
279
316
  const label = el(id + "-label");
@@ -300,6 +337,14 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
300
337
  // Dark mode toggle aria-label
301
338
  const darkModeToggle = el("darkModeToggle");
302
339
  if (darkModeToggle) darkModeToggle.setAttribute("aria-label", t("darkModeToggle"));
340
+
341
+ const readmeLink = el("onboardingReadmeLink");
342
+ if (readmeLink && readmeLink instanceof HTMLAnchorElement) {
343
+ readmeLink.href = isZh
344
+ ? "https://github.com/wu529778790/open-im/blob/main/README.zh-CN.md"
345
+ : "https://github.com/wu529778790/open-im/blob/main/README.md";
346
+ }
347
+ updateSetupWizard();
303
348
  }
304
349
 
305
350
  // AI tool switcher
@@ -331,6 +376,66 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
331
376
  const liveSummary = el("liveSummary");
332
377
  if (liveSummary) liveSummary.textContent = summary;
333
378
  updateAiToolVisibility();
379
+ updateSetupWizard();
380
+ }
381
+
382
+ function hasReadyPlatform() {
383
+ return platformDefinitions.some((platform) => {
384
+ if (!getChecked(platform.key + "-enabled")) return false;
385
+ return platform.requiredFields.every((field) => getValue(platform.key + "-" + field).trim().length > 0);
386
+ });
387
+ }
388
+
389
+ function aiStepComplete() {
390
+ const cmd = getValue("ai-aiCommand");
391
+ if (cmd === "codex") return getValue("ai-codexCliPath").trim().length > 0;
392
+ if (cmd === "codebuddy") return getValue("ai-codebuddyCliPath").trim().length > 0;
393
+ return true;
394
+ }
395
+
396
+ function updateSetupWizard() {
397
+ const s1 = hasReadyPlatform();
398
+ const s2 = aiStepComplete();
399
+ const s3 = sessionStorage.getItem(STORAGE_KEY_SAVED_SESSION) === "1";
400
+ const s4 = Boolean(lastHealthPayload && lastHealthPayload.serviceStatus && lastHealthPayload.serviceStatus.running);
401
+ const done = [s1, s2, s3, s4];
402
+ for (let i = 0; i < 4; i += 1) {
403
+ const step = el("wizard-step-" + (i + 1));
404
+ if (step) step.classList.toggle("wizard-step--done", done[i]);
405
+ setText("wizardStep" + (i + 1) + "Status", done[i] ? t("wizardStatusDone") : t("wizardStatusTodo"));
406
+ }
407
+ }
408
+
409
+ function collectClientValidationErrors() {
410
+ const errors = [];
411
+ const anyEnabled = platformDefinitions.some((p) => getChecked(p.key + "-enabled"));
412
+ if (!anyEnabled) {
413
+ errors.push(t("validationNoPlatformEnabled"));
414
+ }
415
+ platformDefinitions.forEach((platform) => {
416
+ if (!getChecked(platform.key + "-enabled")) return;
417
+ const missing = platform.requiredFields.filter((field) => !getValue(platform.key + "-" + field).trim());
418
+ if (missing.length > 0) {
419
+ errors.push(t("validationPlatformIncomplete", { platform: platform.label, fields: missing.join(", ") }));
420
+ }
421
+ });
422
+ const cmd = getValue("ai-aiCommand");
423
+ if (cmd === "codex" && !getValue("ai-codexCliPath").trim()) {
424
+ errors.push(t("validationAiCodexNoCli"));
425
+ }
426
+ if (cmd === "codebuddy" && !getValue("ai-codebuddyCliPath").trim()) {
427
+ errors.push(t("validationAiCodebuddyNoCli"));
428
+ }
429
+ return errors;
430
+ }
431
+
432
+ function validateClientSideOrAbort() {
433
+ const errors = collectClientValidationErrors();
434
+ if (errors.length > 0) {
435
+ setMessage(errors.join(" "), "error");
436
+ return false;
437
+ }
438
+ return true;
334
439
  }
335
440
 
336
441
  // Update dashboard with health status
@@ -386,6 +491,9 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
386
491
  el("statEnabledValue").textContent = String(enabledCount);
387
492
  el("statServiceValue").textContent = serviceStatus.running ? t("serviceRunningShort") : t("serviceIdleShort");
388
493
 
494
+ lastHealthPayload = data;
495
+ updateSetupWizard();
496
+
389
497
  return data;
390
498
  } catch (error) {
391
499
  console.error("Failed to update dashboard:", error);
@@ -684,6 +792,26 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
684
792
  el("navAiBtn").onclick = () => scrollToSection("aiSection", "navAiBtn");
685
793
  el("navServiceBtn").onclick = () => scrollToSection("serviceSection", "navServiceBtn");
686
794
 
795
+ document.querySelectorAll(".wizard-jump").forEach((jumpBtn) => {
796
+ jumpBtn.addEventListener("click", () => {
797
+ const sec = jumpBtn.getAttribute("data-section");
798
+ const nav = jumpBtn.getAttribute("data-nav");
799
+ if (sec && nav) scrollToSection(sec, nav);
800
+ });
801
+ });
802
+
803
+ const onboardBackdrop = el("onboardingBackdrop");
804
+ const onboardDismiss = el("onboardingDismiss");
805
+ if (onboardBackdrop && onboardDismiss) {
806
+ if (!localStorage.getItem(STORAGE_KEY_ONBOARDING)) {
807
+ onboardBackdrop.classList.remove("hidden");
808
+ }
809
+ onboardDismiss.onclick = () => {
810
+ localStorage.setItem(STORAGE_KEY_ONBOARDING, "1");
811
+ onboardBackdrop.classList.add("hidden");
812
+ };
813
+ }
814
+
687
815
  // Language toggle
688
816
  el("langButton").onclick = () => {
689
817
  currentLang = currentLang === "zh" ? "en" : "zh";
@@ -700,12 +828,12 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
700
828
  // Service buttons
701
829
  el("validateButton").onclick = validate;
702
830
  el("saveButton").onclick = async () => {
703
- // 先保存 JSON,再保存主配置
831
+ if (!validateClientSideOrAbort()) return;
704
832
  await saveClaudeSettings();
705
833
  await save();
706
834
  };
707
835
  el("startButton").onclick = async () => {
708
- // 启动前也顺带保存 JSON
836
+ if (!validateClientSideOrAbort()) return;
709
837
  await saveClaudeSettings();
710
838
  await startService();
711
839
  };
@@ -725,6 +853,7 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
725
853
  async function validate() {
726
854
  setBusy(true);
727
855
  try {
856
+ if (!validateClientSideOrAbort()) return;
728
857
  await request("/api/config/validate", { method: "POST", body: JSON.stringify(payload()) });
729
858
  setMessage(t("validationOk"), "success");
730
859
  } catch (error) {
@@ -737,11 +866,14 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
737
866
  async function save() {
738
867
  setBusy(true);
739
868
  try {
869
+ if (!validateClientSideOrAbort()) return;
740
870
  // First save JSON editor content if changed
741
871
  await saveOpenImConfig();
742
872
  // Then save form data
743
873
  await request("/api/config/save?final=1", { method: "POST", body: JSON.stringify(payload()) });
874
+ sessionStorage.setItem(STORAGE_KEY_SAVED_SESSION, "1");
744
875
  setMessage(t("saveOk"), "success");
876
+ updateSetupWizard();
745
877
  } catch (error) {
746
878
  setMessage(error.message || String(error), "error");
747
879
  } finally {
@@ -778,9 +910,11 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
778
910
  async function startService() {
779
911
  setBusy(true);
780
912
  try {
913
+ if (!validateClientSideOrAbort()) return;
781
914
  await request("/api/config/save", { method: "POST", body: JSON.stringify(payload()) });
782
915
  await request("/api/service/start", { method: "POST" });
783
916
  await refreshStatus();
917
+ await updateDashboard();
784
918
  setMessage(t("startOk"), "success");
785
919
  } catch (error) {
786
920
  setMessage(error.message || String(error), "error");
@@ -356,6 +356,149 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
356
356
  line-height: 1;
357
357
  }
358
358
 
359
+ /* Setup wizard (overview) */
360
+ .setup-wizard-card {
361
+ background: var(--bg-card);
362
+ border: 1px solid var(--border-subtle);
363
+ border-radius: var(--radius-lg);
364
+ padding: 20px 24px;
365
+ margin-bottom: 24px;
366
+ box-shadow: var(--shadow-sm);
367
+ }
368
+ .setup-wizard-card h2 {
369
+ margin: 0 0 16px;
370
+ font-size: 16px;
371
+ font-weight: 600;
372
+ color: var(--text-primary);
373
+ }
374
+ .wizard-steps {
375
+ display: grid;
376
+ gap: 12px;
377
+ }
378
+ @media (min-width: 900px) {
379
+ .wizard-steps {
380
+ grid-template-columns: repeat(2, 1fr);
381
+ }
382
+ }
383
+ .wizard-step {
384
+ display: flex;
385
+ gap: 14px;
386
+ align-items: flex-start;
387
+ padding: 14px 16px;
388
+ border-radius: var(--radius-md);
389
+ border: 1px solid var(--border-subtle);
390
+ background: var(--bg-secondary);
391
+ transition: border-color var(--transition-fast), background var(--transition-fast);
392
+ }
393
+ .wizard-step--done {
394
+ border-color: var(--success-border);
395
+ background: var(--success-bg);
396
+ }
397
+ .wizard-step-badge {
398
+ flex-shrink: 0;
399
+ width: 28px;
400
+ height: 28px;
401
+ border-radius: 999px;
402
+ display: flex;
403
+ align-items: center;
404
+ justify-content: center;
405
+ font-size: 13px;
406
+ font-weight: 700;
407
+ background: var(--bg-tertiary);
408
+ color: var(--text-secondary);
409
+ }
410
+ .wizard-step--done .wizard-step-badge {
411
+ background: var(--success-text);
412
+ color: #fff;
413
+ }
414
+ .wizard-step-body {
415
+ flex: 1;
416
+ min-width: 0;
417
+ }
418
+ .wizard-step-title {
419
+ font-weight: 600;
420
+ font-size: 14px;
421
+ color: var(--text-primary);
422
+ margin-bottom: 4px;
423
+ }
424
+ .wizard-step-desc {
425
+ font-size: 13px;
426
+ color: var(--text-secondary);
427
+ line-height: 1.5;
428
+ margin-bottom: 8px;
429
+ }
430
+ .wizard-step-status {
431
+ font-size: 11px;
432
+ font-weight: 600;
433
+ text-transform: uppercase;
434
+ letter-spacing: 0.04em;
435
+ margin-bottom: 6px;
436
+ color: var(--text-tertiary);
437
+ }
438
+ .wizard-step--done .wizard-step-status {
439
+ color: var(--success-text);
440
+ }
441
+ .field-inline-tip {
442
+ font-size: 12px;
443
+ color: var(--text-tertiary);
444
+ line-height: 1.45;
445
+ margin-top: 6px;
446
+ }
447
+ .field-inline-tip a {
448
+ color: var(--accent-primary);
449
+ }
450
+ .field-inline-tip code {
451
+ font-size: 11px;
452
+ background: var(--bg-tertiary);
453
+ padding: 1px 5px;
454
+ border-radius: var(--radius-xs);
455
+ }
456
+
457
+ /* First-visit onboarding */
458
+ .onboarding-backdrop {
459
+ position: fixed;
460
+ inset: 0;
461
+ background: rgba(15, 23, 42, 0.45);
462
+ z-index: 100;
463
+ display: flex;
464
+ align-items: center;
465
+ justify-content: center;
466
+ padding: 24px;
467
+ }
468
+ .onboarding-backdrop.hidden {
469
+ display: none;
470
+ }
471
+ .onboarding-card {
472
+ background: var(--bg-card);
473
+ color: var(--text-primary);
474
+ border-radius: var(--radius-xl);
475
+ max-width: 520px;
476
+ width: 100%;
477
+ padding: 28px 28px 24px;
478
+ box-shadow: var(--shadow-lg);
479
+ border: 1px solid var(--border-subtle);
480
+ }
481
+ .onboarding-card h2 {
482
+ margin: 0 0 12px;
483
+ font-size: 20px;
484
+ font-weight: 700;
485
+ }
486
+ .onboarding-card .onboarding-body {
487
+ font-size: 14px;
488
+ color: var(--text-secondary);
489
+ line-height: 1.55;
490
+ }
491
+ .onboarding-card .onboarding-body p {
492
+ margin: 0 0 8px;
493
+ }
494
+ .onboarding-actions {
495
+ display: flex;
496
+ flex-wrap: wrap;
497
+ gap: 10px;
498
+ margin-top: 20px;
499
+ align-items: center;
500
+ }
501
+
359
502
  /* Platform Cards */
360
503
  .platform-grid {
361
504
  display: grid;
@@ -839,6 +982,48 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
839
982
  <div class="stat-value" id="statServiceValue">Idle</div>
840
983
  </div>
841
984
  </div>
985
+
986
+ <div class="setup-wizard-card">
987
+ <h2 id="wizardTitle">First-time setup</h2>
988
+ <div class="wizard-steps" id="wizardSteps">
989
+ <div class="wizard-step" id="wizard-step-1">
990
+ <div class="wizard-step-badge">1</div>
991
+ <div class="wizard-step-body">
992
+ <div class="wizard-step-status" id="wizardStep1Status">To do</div>
993
+ <div class="wizard-step-title" id="wizardStep1Title">Connect an IM channel</div>
994
+ <div class="wizard-step-desc" id="wizardStep1Desc"></div>
995
+ <button type="button" class="btn btn-secondary btn-sm wizard-jump" data-section="configSection" data-nav="navPlatformsBtn" id="wizardJumpPlatforms">Open Platforms</button>
996
+ </div>
997
+ </div>
998
+ <div class="wizard-step" id="wizard-step-2">
999
+ <div class="wizard-step-badge">2</div>
1000
+ <div class="wizard-step-body">
1001
+ <div class="wizard-step-status" id="wizardStep2Status">To do</div>
1002
+ <div class="wizard-step-title" id="wizardStep2Title">Set the default AI tool</div>
1003
+ <div class="wizard-step-desc" id="wizardStep2Desc"></div>
1004
+ <button type="button" class="btn btn-secondary btn-sm wizard-jump" data-section="aiSection" data-nav="navAiBtn" id="wizardJumpAi">Open AI</button>
1005
+ </div>
1006
+ </div>
1007
+ <div class="wizard-step" id="wizard-step-3">
1008
+ <div class="wizard-step-badge">3</div>
1009
+ <div class="wizard-step-body">
1010
+ <div class="wizard-step-status" id="wizardStep3Status">To do</div>
1011
+ <div class="wizard-step-title" id="wizardStep3Title">Validate and save</div>
1012
+ <div class="wizard-step-desc" id="wizardStep3Desc"></div>
1013
+ <button type="button" class="btn btn-secondary btn-sm wizard-jump" data-section="serviceSection" data-nav="navServiceBtn" id="wizardJumpService">Open Service</button>
1014
+ </div>
1015
+ </div>
1016
+ <div class="wizard-step" id="wizard-step-4">
1017
+ <div class="wizard-step-badge">4</div>
1018
+ <div class="wizard-step-body">
1019
+ <div class="wizard-step-status" id="wizardStep4Status">To do</div>
1020
+ <div class="wizard-step-title" id="wizardStep4Title">Start the bridge</div>
1021
+ <div class="wizard-step-desc" id="wizardStep4Desc"></div>
1022
+ <button type="button" class="btn btn-secondary btn-sm wizard-jump" data-section="serviceSection" data-nav="navServiceBtn" id="wizardJumpService2">Open Service</button>
1023
+ </div>
1024
+ </div>
1025
+ </div>
1026
+ </div>
842
1027
  </section>
843
1028
 
844
1029
  <!-- Platforms Section -->
@@ -864,6 +1049,7 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
864
1049
  <div class="form-group">
865
1050
  <label class="form-label" id="telegram-botToken-label">Bot Token</label>
866
1051
  <input id="telegram-botToken" class="form-input mono" type="password" />
1052
+ <div class="field-inline-tip" id="telegram-botToken-tip"></div>
867
1053
  </div>
868
1054
  <div class="form-group">
869
1055
  <label class="form-label" id="telegram-proxy-label">Proxy (optional)</label>
@@ -906,10 +1092,12 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
906
1092
  <div class="form-group">
907
1093
  <label class="form-label" id="feishu-appId-label">App ID</label>
908
1094
  <input id="feishu-appId" class="form-input mono" type="text" />
1095
+ <div class="field-inline-tip" id="feishu-appId-tip"></div>
909
1096
  </div>
910
1097
  <div class="form-group">
911
1098
  <label class="form-label" id="feishu-appSecret-label">App Secret</label>
912
1099
  <input id="feishu-appSecret" class="form-input mono" type="password" />
1100
+ <div class="field-inline-tip" id="feishu-appSecret-tip"></div>
913
1101
  </div>
914
1102
  <div class="form-group">
915
1103
  <label class="form-label" id="feishu-aiCommand-label">AI Tool</label>
@@ -947,10 +1135,12 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
947
1135
  <div class="form-group">
948
1136
  <label class="form-label" id="qq-appId-label">App ID</label>
949
1137
  <input id="qq-appId" class="form-input mono" type="text" />
1138
+ <div class="field-inline-tip" id="qq-appId-tip"></div>
950
1139
  </div>
951
1140
  <div class="form-group">
952
1141
  <label class="form-label" id="qq-secret-label">App Secret</label>
953
1142
  <input id="qq-secret" class="form-input mono" type="password" />
1143
+ <div class="field-inline-tip" id="qq-secret-tip"></div>
954
1144
  </div>
955
1145
  <div class="form-group">
956
1146
  <label class="form-label" id="qq-aiCommand-label">AI Tool</label>
@@ -988,6 +1178,7 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
988
1178
  <div class="form-group">
989
1179
  <label class="form-label" id="wework-corpId-label">Corp ID / Bot ID</label>
990
1180
  <input id="wework-corpId" class="form-input mono" type="text" />
1181
+ <div class="field-inline-tip" id="wework-corpId-tip"></div>
991
1182
  </div>
992
1183
  <div class="form-group">
993
1184
  <label class="form-label" id="wework-secret-label">Secret</label>
@@ -1029,6 +1220,7 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
1029
1220
  <div class="form-group">
1030
1221
  <label class="form-label" id="dingtalk-clientId-label">Client ID / AppKey</label>
1031
1222
  <input id="dingtalk-clientId" class="form-input mono" type="password" />
1223
+ <div class="field-inline-tip" id="dingtalk-clientId-tip"></div>
1032
1224
  </div>
1033
1225
  <div class="form-group">
1034
1226
  <label class="form-label" id="dingtalk-clientSecret-label">Client Secret / AppSecret</label>
@@ -1083,6 +1275,7 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
1083
1275
  <div class="form-group">
1084
1276
  <label class="form-label" id="workbuddy-accessToken-label">Access Token</label>
1085
1277
  <input id="workbuddy-accessToken" class="form-input mono" type="password" autocomplete="off" />
1278
+ <div class="field-inline-tip" id="workbuddy-accessToken-tip"></div>
1086
1279
  </div>
1087
1280
  <div class="form-group">
1088
1281
  <label class="form-label" id="workbuddy-refreshToken-label">Refresh Token</label>
@@ -1259,6 +1452,17 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
1259
1452
  </div>
1260
1453
  </section>
1261
1454
 
1455
+ <div id="onboardingBackdrop" class="onboarding-backdrop hidden" role="dialog" aria-modal="true" aria-labelledby="onboardingTitle">
1456
+ <div class="onboarding-card">
1457
+ <h2 id="onboardingTitle">Welcome</h2>
1458
+ <div id="onboardingBody" class="onboarding-body"></div>
1459
+ <div class="onboarding-actions">
1460
+ <button type="button" class="btn btn-primary" id="onboardingDismiss">Got it</button>
1461
+ <a href="https://github.com/wu529778790/open-im/blob/main/README.zh-CN.md" target="_blank" rel="noopener" class="btn btn-ghost" id="onboardingReadmeLink"><span id="onboardingReadme">README</span></a>
1462
+ </div>
1463
+ </div>
1464
+ </div>
1465
+
1262
1466
  </div>
1263
1467
  </main>
1264
1468
  </div>
@@ -4,7 +4,7 @@
4
4
  import { resolvePlatformAiCommand } from '../config.js';
5
5
  import { AccessControl } from '../access/access-control.js';
6
6
  import { RequestQueue } from '../queue/request-queue.js';
7
- import { sendThinkingMessage, updateMessage, sendFinalMessages, sendTextReply, sendImageReply, startTypingLoop, } from './message-sender.js';
7
+ import { sendThinkingMessage, updateMessage, sendFinalMessages, sendTextReply, sendImageReply, sendDirectorySelection, startTypingLoop, } from './message-sender.js';
8
8
  import { CommandHandler } from '../commands/handler.js';
9
9
  import { getAdapter } from '../adapters/registry.js';
10
10
  import { runAITask } from '../shared/ai-task.js';
@@ -164,11 +164,19 @@ export function setupWeWorkHandlers(config, sessionManager) {
164
164
  const requestQueue = new RequestQueue();
165
165
  const runningTasks = new Map();
166
166
  const stopTaskCleanup = startTaskCleanup(runningTasks);
167
+ // Mutable ref that captures the req_id of the message currently being handled.
168
+ // WeWork requires req_id to reply; CommandHandler doesn't carry it, so we inject
169
+ // it via a closure. WeWork delivers messages sequentially over WebSocket, so
170
+ // there is no race condition between concurrent messages from the same bot.
171
+ const senderCtx = { reqId: '' };
167
172
  const commandHandler = new CommandHandler({
168
173
  config,
169
174
  sessionManager,
170
175
  requestQueue,
171
- sender: { sendTextReply },
176
+ sender: {
177
+ sendTextReply: (chatId, text) => sendTextReply(chatId, text, senderCtx.reqId),
178
+ sendDirectorySelection: (chatId, currentDir, userId) => sendDirectorySelection(chatId, currentDir, userId, senderCtx.reqId),
179
+ },
172
180
  getRunningTasksSize: () => runningTasks.size,
173
181
  });
174
182
  async function handleAIRequest(userId, chatId, prompt, workDir, convId, _threadCtx, replyToMessageId, reqId) {
@@ -265,6 +273,7 @@ export function setupWeWorkHandlers(config, sessionManager) {
265
273
  async function handleEvent(data) {
266
274
  log.info('[handleEvent] Called with data:', JSON.stringify(data).slice(0, 800));
267
275
  const reqId = data.headers?.req_id ?? '';
276
+ senderCtx.reqId = reqId;
268
277
  try {
269
278
  const body = data.body;
270
279
  const msgType = body.msgtype;
@@ -21,7 +21,7 @@ export declare function sendProactiveTextReply(chatId: string, text: string): Pr
21
21
  */
22
22
  export declare function sendTextReply(chatId: string, text: string, threadCtxOrReqId?: import('../shared/types.js').ThreadContext | string): Promise<void>;
23
23
  export declare function sendImageReply(chatId: string, imagePath: string): Promise<void>;
24
- export declare function sendDirectorySelection(chatId: string, currentDir: string, _userId: string): Promise<void>;
24
+ export declare function sendDirectorySelection(chatId: string, currentDir: string, _userId: string, reqId?: string): Promise<void>;
25
25
  export declare function startTypingLoop(_chatId: string): () => void;
26
26
  export declare function sendErrorMessage(chatId: string, error: string, reqId?: string): Promise<void>;
27
27
  export {};
@@ -325,8 +325,8 @@ export async function sendImageReply(chatId, imagePath) {
325
325
  await sendTextReply(chatId, `Generated image saved at: ${imagePath}`);
326
326
  }
327
327
  }
328
- export async function sendDirectorySelection(chatId, currentDir, _userId) {
329
- await sendTextReply(chatId, buildDirectoryMessage(currentDir));
328
+ export async function sendDirectorySelection(chatId, currentDir, _userId, reqId) {
329
+ await sendTextReply(chatId, buildDirectoryMessage(currentDir), reqId);
330
330
  }
331
331
  export function startTypingLoop(_chatId) {
332
332
  return () => { };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.9.3-beta.6",
3
+ "version": "1.9.3-beta.8",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",