Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6faafabc9d | ||
|
|
cfc9671179 |
Binary file not shown.
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 65 KiB |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "telegram-bot",
|
"name": "telegram-bot",
|
||||||
"version": "0.2.1",
|
"version": "0.2.6",
|
||||||
"description": "Telegram bot powered by your local AI CLI (OpenAI Codex or Claude Code) — zero-dependency, pm2-managed, with a web console to manage multiple bots",
|
"description": "Telegram bot powered by your local AI CLI (OpenAI Codex or Claude Code) — zero-dependency, pm2-managed, with a web console to manage multiple bots",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
|
|||||||
+22
-2
@@ -86,6 +86,10 @@ function createTelegram(token) {
|
|||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TRANSIENT_RE = /Bad Gateway|Gateway Timeout|Too Many Requests|Service Unavailable|Internal Server Error|ETIMEDOUT|ECONNRESET|fetch failed/i;
|
||||||
|
const SEND_FILE_RETRIES = 3;
|
||||||
|
const SEND_FILE_RETRY_DELAY = 4000;
|
||||||
|
|
||||||
// 上傳檔案到 Telegram:圖片走 sendPhoto,其餘(或 sendPhoto 失敗時)走 sendDocument
|
// 上傳檔案到 Telegram:圖片走 sendPhoto,其餘(或 sendPhoto 失敗時)走 sendDocument
|
||||||
async function sendFile(chatId, filePath, { caption, replyTo } = {}) {
|
async function sendFile(chatId, filePath, { caption, replyTo } = {}) {
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -106,13 +110,29 @@ function createTelegram(token) {
|
|||||||
return data.result;
|
return data.result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 帶重試的上傳:Telegram 暫時性故障(502/504/429 等)自動等一下重來
|
||||||
|
const uploadWithRetry = async (method, field) => {
|
||||||
|
for (let i = 1; ; i++) {
|
||||||
|
try {
|
||||||
|
return await upload(method, field);
|
||||||
|
} catch (err) {
|
||||||
|
if (i < SEND_FILE_RETRIES && TRANSIENT_RE.test(err.message)) {
|
||||||
|
console.log(`sendFile 重試 ${i}/${SEND_FILE_RETRIES}(${err.message})`);
|
||||||
|
await new Promise((r) => setTimeout(r, SEND_FILE_RETRY_DELAY * i));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// sendPhoto 上限 10MB,且某些格式會被拒,失敗就退回用檔案傳
|
// sendPhoto 上限 10MB,且某些格式會被拒,失敗就退回用檔案傳
|
||||||
if (isImage && size <= 9.5 * 1024 * 1024) {
|
if (isImage && size <= 9.5 * 1024 * 1024) {
|
||||||
try {
|
try {
|
||||||
return await upload('sendPhoto', 'photo');
|
return await uploadWithRetry('sendPhoto', 'photo');
|
||||||
} catch { /* fall through */ }
|
} catch { /* fall through */ }
|
||||||
}
|
}
|
||||||
return upload('sendDocument', 'document');
|
return uploadWithRetry('sendDocument', 'document');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadFile(fileId, destPath) {
|
async function downloadFile(fileId, destPath) {
|
||||||
|
|||||||
@@ -110,6 +110,7 @@
|
|||||||
.notice-steps b { color: #e2e8f0; background: rgba(88,101,242,0.18); padding: 1px 7px;
|
.notice-steps b { color: #e2e8f0; background: rgba(88,101,242,0.18); padding: 1px 7px;
|
||||||
border-radius: 5px; font-weight: 600; }
|
border-radius: 5px; font-weight: 600; }
|
||||||
.workdir { cursor: help; border-bottom: 1px dotted #475569; }
|
.workdir { cursor: help; border-bottom: 1px dotted #475569; }
|
||||||
|
.sandbox-cell { cursor: help; }
|
||||||
.tag { display: inline-block; padding: 1px 7px; border-radius: 5px; font-size: 10px; font-weight: 700;
|
.tag { display: inline-block; padding: 1px 7px; border-radius: 5px; font-size: 10px; font-weight: 700;
|
||||||
margin-left: 6px; vertical-align: middle; letter-spacing: .3px; }
|
margin-left: 6px; vertical-align: middle; letter-spacing: .3px; }
|
||||||
.tag-codex { background: rgba(16,163,127,0.18); color: #34d399; }
|
.tag-codex { background: rgba(16,163,127,0.18); color: #34d399; }
|
||||||
@@ -486,10 +487,13 @@ function renderBots(bots) {
|
|||||||
(b.error ? '<br><span style="font-size:11px;color:#ed4245">' + esc(b.error) + '</span>' : '') + '</td>' +
|
(b.error ? '<br><span style="font-size:11px;color:#ed4245">' + esc(b.error) + '</span>' : '') + '</td>' +
|
||||||
'<td>' + statusBadge(b.status) + '</td>' +
|
'<td>' + statusBadge(b.status) + '</td>' +
|
||||||
'<td><span class="mono workdir" title="' + esc(b.workDir) + '">' + esc(folderName(b.workDir)) + '</span></td>' +
|
'<td><span class="mono workdir" title="' + esc(b.workDir) + '">' + esc(folderName(b.workDir)) + '</span></td>' +
|
||||||
'<td class="mono">' + esc(b.sandbox || '-') +
|
(function() {
|
||||||
((b.model || b.reasoningEffort || b.sessionMode !== 'per-chat' || b.networkAccess)
|
var line2 = [b.model, b.reasoningEffort].filter(Boolean).join(' / ');
|
||||||
? '<br><span class="script-name">' + esc([b.model, b.reasoningEffort, b.sessionMode === 'shared' ? '共用會話' : b.sessionMode === 'stateless' ? '無記憶' : '', b.networkAccess ? '可連網' : '', (b.writeUsers && b.writeUsers.length) ? '限 @' + b.writeUsers.join(' @') + ' 可寫' : '', b.promptRules ? '有回覆規範' : ''].filter(Boolean).join(' / ')) + '</span>'
|
var extra = [b.sessionMode === 'shared' ? '共用會話' : b.sessionMode === 'stateless' ? '無記憶' : '', b.networkAccess ? '可連網' : '', (b.writeUsers && b.writeUsers.length) ? '限 @' + b.writeUsers.join(' @') + ' 可寫' : '', b.promptRules ? '有回覆規範' : ''].filter(Boolean).join(' / ');
|
||||||
: '') + '</td>' +
|
var tip = [b.sandbox, line2, extra].filter(Boolean).join('\n');
|
||||||
|
return '<td class="mono sandbox-cell" title="' + esc(tip) + '">' + esc(b.sandbox || '-') +
|
||||||
|
(line2 ? '<br><span class="script-name">' + esc(line2) + '</span>' : '') + '</td>';
|
||||||
|
})() +
|
||||||
'<td class="mono">' + esc(b.tokenMasked || '-') + '</td>' +
|
'<td class="mono">' + esc(b.tokenMasked || '-') + '</td>' +
|
||||||
'<td>' + fmtBytes(b.memory) + '</td>' +
|
'<td>' + fmtBytes(b.memory) + '</td>' +
|
||||||
'<td>' + (b.restarts ?? '-') + '</td>' +
|
'<td>' + (b.restarts ?? '-') + '</td>' +
|
||||||
|
|||||||
+4
-1
@@ -106,9 +106,12 @@ const CODEX_FALLBACK_MODELS = [
|
|||||||
|
|
||||||
const CLAUDE_EFFORTS = ['low', 'medium', 'high', 'max'];
|
const CLAUDE_EFFORTS = ['low', 'medium', 'high', 'max'];
|
||||||
const CLAUDE_MODELS = [
|
const CLAUDE_MODELS = [
|
||||||
{ slug: 'claude-fable-5', label: 'Fable 5(最強,Mythos 級)', efforts: CLAUDE_EFFORTS, defaultEffort: 'high' },
|
{ slug: 'claude-fable-5', label: 'Fable 5(最強)', efforts: CLAUDE_EFFORTS, defaultEffort: 'high' },
|
||||||
{ slug: 'claude-opus-5', label: 'Opus 5', efforts: CLAUDE_EFFORTS, defaultEffort: 'high' },
|
{ slug: 'claude-opus-5', label: 'Opus 5', efforts: CLAUDE_EFFORTS, defaultEffort: 'high' },
|
||||||
|
{ slug: 'claude-opus-4-8', label: 'Opus 4.8', efforts: CLAUDE_EFFORTS, defaultEffort: 'high' },
|
||||||
|
{ slug: 'claude-opus-4-6', label: 'Opus 4.6', efforts: CLAUDE_EFFORTS, defaultEffort: 'high' },
|
||||||
{ slug: 'claude-sonnet-5', label: 'Sonnet 5(較快)', efforts: CLAUDE_EFFORTS, defaultEffort: 'high' },
|
{ slug: 'claude-sonnet-5', label: 'Sonnet 5(較快)', efforts: CLAUDE_EFFORTS, defaultEffort: 'high' },
|
||||||
|
{ slug: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5(最快最便宜)', efforts: CLAUDE_EFFORTS, defaultEffort: 'high' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// codex 全域預設(~/.codex/config.toml),前端用來當下拉選單的預選值
|
// codex 全域預設(~/.codex/config.toml),前端用來當下拉選單的預選值
|
||||||
|
|||||||
Reference in New Issue
Block a user