@wu529778790/open-im 1.11.2-beta.23 → 1.11.2-beta.25
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/dist/index.js
CHANGED
|
@@ -159,32 +159,30 @@ function getEnabledPlugins() {
|
|
|
159
159
|
/**
|
|
160
160
|
* 统一通知格式模板
|
|
161
161
|
* 所有发送给 IM 的通知都使用这个格式,保持一致性
|
|
162
|
+
* 注意:ClawBot/微信不支持 \n 换行,使用 | 分隔
|
|
162
163
|
*/
|
|
163
164
|
function buildNotification(opts) {
|
|
164
|
-
const
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
//
|
|
168
|
-
const details = [];
|
|
165
|
+
const parts = [];
|
|
166
|
+
// 标题
|
|
167
|
+
parts.push(`${opts.emoji} ${opts.title}`);
|
|
168
|
+
// 详情
|
|
169
169
|
if (opts.platform)
|
|
170
|
-
|
|
170
|
+
parts.push(`📱 ${opts.platform}`);
|
|
171
171
|
if (opts.aiCommand)
|
|
172
|
-
|
|
172
|
+
parts.push(`🤖 ${opts.aiCommand}`);
|
|
173
173
|
if (opts.dir)
|
|
174
|
-
|
|
174
|
+
parts.push(`📁 ${opts.dir}`);
|
|
175
175
|
const plugins = getEnabledPlugins();
|
|
176
176
|
if (plugins.length > 0)
|
|
177
|
-
|
|
177
|
+
parts.push(`🧩 ${plugins.join(", ")}`);
|
|
178
178
|
if (opts.uptime)
|
|
179
|
-
|
|
180
|
-
if (details.length > 0) {
|
|
181
|
-
lines.push("", ...details);
|
|
182
|
-
}
|
|
179
|
+
parts.push(`⏱️ ${opts.uptime}`);
|
|
183
180
|
// 额外信息
|
|
184
181
|
if (opts.extra?.length) {
|
|
185
|
-
|
|
182
|
+
parts.push(...opts.extra);
|
|
186
183
|
}
|
|
187
|
-
|
|
184
|
+
// 使用 | 分隔(ClawBot/微信不支持 \n)
|
|
185
|
+
return parts.join(" | ");
|
|
188
186
|
}
|
|
189
187
|
function buildStartupMessage(platform, appVersion, aiCommand, defaultWorkDir, sessionManager) {
|
|
190
188
|
let sessionDir;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 选择检测器 — 从 AI 输出中检测并提取编号选项
|
|
3
|
+
*
|
|
4
|
+
* 当 AI 问"请选择 1/2/3"时,提取选项并返回结构化数据。
|
|
5
|
+
*/
|
|
6
|
+
export interface DetectedChoice {
|
|
7
|
+
number: number;
|
|
8
|
+
text: string;
|
|
9
|
+
}
|
|
10
|
+
export interface DetectionResult {
|
|
11
|
+
/** 是否检测到选择 */
|
|
12
|
+
hasChoices: boolean;
|
|
13
|
+
/** 提取的选项列表 */
|
|
14
|
+
choices: DetectedChoice[];
|
|
15
|
+
/** 去除选项后的纯文本(用于显示) */
|
|
16
|
+
cleanText: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 检测 AI 输出中的编号选择
|
|
20
|
+
*
|
|
21
|
+
* 支持格式:
|
|
22
|
+
* - "1. Option A\n2. Option B\n3. Option C"
|
|
23
|
+
* - "1)Option A\n2)Option B"
|
|
24
|
+
* - "**选择 1:** Option A"
|
|
25
|
+
*/
|
|
26
|
+
export declare function detectChoices(text: string): DetectionResult;
|
|
27
|
+
/**
|
|
28
|
+
* 构建 Telegram inline keyboard
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildChoiceKeyboard(choices: DetectedChoice[], userId: string): {
|
|
31
|
+
inline_keyboard: Array<Array<{
|
|
32
|
+
text: string;
|
|
33
|
+
callback_data: string;
|
|
34
|
+
}>>;
|
|
35
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 选择检测器 — 从 AI 输出中检测并提取编号选项
|
|
3
|
+
*
|
|
4
|
+
* 当 AI 问"请选择 1/2/3"时,提取选项并返回结构化数据。
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* 检测 AI 输出中的编号选择
|
|
8
|
+
*
|
|
9
|
+
* 支持格式:
|
|
10
|
+
* - "1. Option A\n2. Option B\n3. Option C"
|
|
11
|
+
* - "1)Option A\n2)Option B"
|
|
12
|
+
* - "**选择 1:** Option A"
|
|
13
|
+
*/
|
|
14
|
+
export function detectChoices(text) {
|
|
15
|
+
// 匹配 "1." 或 "1)" 或 "1:" 开头的行
|
|
16
|
+
const choicePattern = /^\s*(\d+)[.):]\s*(.+)$/gm;
|
|
17
|
+
const matches = [];
|
|
18
|
+
let match;
|
|
19
|
+
while ((match = choicePattern.exec(text)) !== null) {
|
|
20
|
+
const num = parseInt(match[1], 10);
|
|
21
|
+
const content = match[2].trim();
|
|
22
|
+
if (num >= 1 && num <= 9 && content.length > 0) {
|
|
23
|
+
matches.push({ number: num, text: content });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// 检查是否有选择提示(如"请选择"、"选择哪个"等)
|
|
27
|
+
const hasChoicePrompt = /请选择|选择哪个|选一个|pick|choose|select/i.test(text);
|
|
28
|
+
// 至少需要 2 个选项且有选择提示
|
|
29
|
+
const hasChoices = matches.length >= 2 && hasChoicePrompt;
|
|
30
|
+
if (!hasChoices) {
|
|
31
|
+
return { hasChoices: false, choices: [], cleanText: text };
|
|
32
|
+
}
|
|
33
|
+
// 去除选项行,保留其他内容
|
|
34
|
+
const lines = text.split('\n');
|
|
35
|
+
const cleanLines = lines.filter(line => {
|
|
36
|
+
const trimmed = line.trim();
|
|
37
|
+
return !/^\s*\d+[.):]\s*.+/.test(trimmed);
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
hasChoices: true,
|
|
41
|
+
choices: matches,
|
|
42
|
+
cleanText: cleanLines.join('\n').trim(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 构建 Telegram inline keyboard
|
|
47
|
+
*/
|
|
48
|
+
export function buildChoiceKeyboard(choices, userId) {
|
|
49
|
+
const buttons = [];
|
|
50
|
+
// 每行 1 个按钮(选项通常较长)
|
|
51
|
+
for (const choice of choices) {
|
|
52
|
+
buttons.push([{
|
|
53
|
+
text: `${choice.number}. ${choice.text}`,
|
|
54
|
+
callback_data: `choice:${userId}:${choice.number}`,
|
|
55
|
+
}]);
|
|
56
|
+
}
|
|
57
|
+
return { inline_keyboard: buttons };
|
|
58
|
+
}
|
|
@@ -310,6 +310,36 @@ export function setupTelegramHandlers(bot, config, sessionManager) {
|
|
|
310
310
|
await ctx.answerCbQuery("任务已完成或不存在");
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
|
+
// 处理选择回调
|
|
314
|
+
if (data.startsWith("choice:")) {
|
|
315
|
+
const parts = data.split(":");
|
|
316
|
+
if (parts.length >= 3) {
|
|
317
|
+
const choiceNum = parts[2];
|
|
318
|
+
const chatId = String(ctx.chat?.id ?? "");
|
|
319
|
+
// 将用户选择作为消息发送给 AI
|
|
320
|
+
const { handleTextFlow } = await import("../platform/handle-text-flow.js");
|
|
321
|
+
await handleTextFlow({
|
|
322
|
+
platform: "telegram",
|
|
323
|
+
userId,
|
|
324
|
+
chatId,
|
|
325
|
+
text: choiceNum,
|
|
326
|
+
ctx: createPlatformEventContext({
|
|
327
|
+
platform: "telegram",
|
|
328
|
+
allowedUserIds: config.telegramAllowedUserIds,
|
|
329
|
+
config,
|
|
330
|
+
sessionManager,
|
|
331
|
+
sender: { sendTextReply: async () => { } },
|
|
332
|
+
}),
|
|
333
|
+
handleAIRequest: async () => { },
|
|
334
|
+
sendTextReply: async (c, t) => {
|
|
335
|
+
await sendTextReply(c, t);
|
|
336
|
+
},
|
|
337
|
+
workDir: sessionManager.getWorkDir(userId),
|
|
338
|
+
convId: sessionManager.getConvId(userId),
|
|
339
|
+
});
|
|
340
|
+
await ctx.answerCbQuery(`已选择 ${choiceNum}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
313
343
|
});
|
|
314
344
|
bot.on(message("text"), async (tgCtx) => {
|
|
315
345
|
try {
|
|
@@ -7,6 +7,7 @@ import { buildMessageTitle, OPEN_IM_SYSTEM_TITLE } from "../shared/message-title
|
|
|
7
7
|
import { buildTextNote } from "../shared/message-note.js";
|
|
8
8
|
import { MAX_TELEGRAM_MESSAGE_LENGTH } from "../constants.js";
|
|
9
9
|
import { listDirectories, buildDirectoryKeyboard, } from "../commands/handler.js";
|
|
10
|
+
import { detectChoices, buildChoiceKeyboard } from "../shared/choice-detector.js";
|
|
10
11
|
const log = createLogger("TgSender");
|
|
11
12
|
const lastSentByMsg = new Map();
|
|
12
13
|
// Periodic cleanup of orphaned entries (entries not cleaned by done/error)
|
|
@@ -121,13 +122,31 @@ export async function sendTextReply(chatId, text) {
|
|
|
121
122
|
const bot = getBot();
|
|
122
123
|
try {
|
|
123
124
|
const formatted = formatMessage(text, "done", undefined, OPEN_IM_SYSTEM_TITLE);
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
// 检测是否有编号选择
|
|
126
|
+
const detection = detectChoices(text);
|
|
127
|
+
if (detection.hasChoices) {
|
|
128
|
+
// 有选择:发送纯文本 + 按钮
|
|
129
|
+
const keyboard = buildChoiceKeyboard(detection.choices, chatId);
|
|
130
|
+
try {
|
|
131
|
+
await bot.telegram.sendMessage(Number(chatId), detection.cleanText || formatted, {
|
|
132
|
+
parse_mode: "Markdown",
|
|
133
|
+
reply_markup: keyboard,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
await bot.telegram.sendMessage(Number(chatId), detection.cleanText || formatted, {
|
|
138
|
+
reply_markup: keyboard,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
127
141
|
}
|
|
128
|
-
|
|
129
|
-
//
|
|
130
|
-
|
|
142
|
+
else {
|
|
143
|
+
// 无选择:普通消息
|
|
144
|
+
try {
|
|
145
|
+
await bot.telegram.sendMessage(Number(chatId), formatted, { parse_mode: "Markdown" });
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
await bot.telegram.sendMessage(Number(chatId), formatted);
|
|
149
|
+
}
|
|
131
150
|
}
|
|
132
151
|
}
|
|
133
152
|
catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wu529778790/open-im",
|
|
3
|
-
"version": "1.11.2-beta.
|
|
3
|
+
"version": "1.11.2-beta.25",
|
|
4
4
|
"description": "Your AI coding assistant, in every chat app. Multi-platform IM bridge for Claude Code, Codex, and CodeBuddy.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|