feat: add Telegram slash command parity and menu sync (#125)
feat: add Telegram slash command parity and menu sync
This commit is contained in:
@@ -118,7 +118,7 @@ streamlit run frontends/stapp2.py # Alternative Streamlit UI
|
|||||||
|
|
||||||
### Common Chat Commands
|
### Common Chat Commands
|
||||||
|
|
||||||
The default Streamlit desktop UI started by `python launch.pyw`, plus the QQ / Feishu / WeCom / DingTalk frontends, support these chat commands:
|
The default Streamlit desktop UI started by `python launch.pyw`, plus the QQ / Telegram / Feishu / WeCom / DingTalk frontends, support these chat commands:
|
||||||
|
|
||||||
- `/new` - start a fresh conversation and clear the current context
|
- `/new` - start a fresh conversation and clear the current context
|
||||||
- `/continue` - list recoverable conversation snapshots
|
- `/continue` - list recoverable conversation snapshots
|
||||||
@@ -394,7 +394,7 @@ streamlit run frontends/stapp2.py # 另一种 Streamlit 风格 UI
|
|||||||
|
|
||||||
### 通用聊天命令
|
### 通用聊天命令
|
||||||
|
|
||||||
默认通过 `python launch.pyw` 启动的 Streamlit 桌面 UI,以及 QQ / 飞书 / 企业微信 / 钉钉前端,都支持以下命令:
|
默认通过 `python launch.pyw` 启动的 Streamlit 桌面 UI,以及 QQ / Telegram / 飞书 / 企业微信 / 钉钉前端,都支持以下命令:
|
||||||
|
|
||||||
- `/new` - 开启新对话并清空当前上下文
|
- `/new` - 开启新对话并清空当前上下文
|
||||||
- `/continue` - 列出可恢复会话快照
|
- `/continue` - 列出可恢复会话快照
|
||||||
|
|||||||
@@ -1,6 +1,32 @@
|
|||||||
import ast, asyncio, glob, json, os, queue as Q, re, socket, sys, time
|
import ast, asyncio, glob, json, os, queue as Q, re, socket, sys, time
|
||||||
|
|
||||||
HELP_TEXT = "📖 命令列表:\n/help - 显示帮助\n/status - 查看状态\n/stop - 停止当前任务\n/new - 开启新对话并清空当前上下文\n/restore - 恢复上次对话历史\n/continue - 列出可恢复会话\n/continue [n] - 恢复第 n 个会话\n/llm [n] - 查看或切换模型"
|
HELP_COMMANDS = (
|
||||||
|
("/help", "显示帮助"),
|
||||||
|
("/status", "查看状态"),
|
||||||
|
("/stop", "停止当前任务"),
|
||||||
|
("/new", "开启新对话并清空当前上下文"),
|
||||||
|
("/restore", "恢复上次对话历史"),
|
||||||
|
("/continue", "列出可恢复会话"),
|
||||||
|
("/continue [n]", "恢复第 n 个会话"),
|
||||||
|
("/llm", "查看当前模型列表"),
|
||||||
|
("/llm [n]", "切换到第 n 个模型"),
|
||||||
|
)
|
||||||
|
TELEGRAM_MENU_COMMANDS = (
|
||||||
|
("help", "显示帮助"),
|
||||||
|
("status", "查看状态"),
|
||||||
|
("stop", "停止当前任务"),
|
||||||
|
("new", "开启新对话并清空当前上下文"),
|
||||||
|
("restore", "恢复上次对话历史"),
|
||||||
|
("continue", "列出可恢复会话;/continue n 恢复第 n 个"),
|
||||||
|
("llm", "查看模型列表;/llm n 切换到指定模型"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_help_text(commands=HELP_COMMANDS):
|
||||||
|
return "📖 命令列表:\n" + "\n".join(f"{cmd} - {desc}" for cmd, desc in commands)
|
||||||
|
|
||||||
|
|
||||||
|
HELP_TEXT = build_help_text()
|
||||||
FILE_HINT = "If you need to show files to user, use [FILE:filepath] in your response."
|
FILE_HINT = "If you need to show files to user, use [FILE:filepath] in your response."
|
||||||
TAG_PATS = [r"<" + t + r">.*?</" + t + r">" for t in ("thinking", "summary", "tool_use", "file_content")]
|
TAG_PATS = [r"<" + t + r">.*?</" + t + r">" for t in ("thinking", "summary", "tool_use", "file_content")]
|
||||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|||||||
_TEMP_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'temp')
|
_TEMP_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'temp')
|
||||||
from agentmain import GeneraticAgent
|
from agentmain import GeneraticAgent
|
||||||
try:
|
try:
|
||||||
from telegram import Update
|
from telegram import BotCommand
|
||||||
from telegram.ext import ApplicationBuilder, MessageHandler, CommandHandler, filters, ContextTypes
|
from telegram.ext import ApplicationBuilder, MessageHandler, filters, ContextTypes
|
||||||
from telegram.request import HTTPXRequest
|
from telegram.request import HTTPXRequest
|
||||||
except:
|
except:
|
||||||
print("Please ask the agent install python-telegram-bot to use telegram module.")
|
print("Please ask the agent install python-telegram-bot to use telegram module.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
from chatapp_common import HELP_TEXT, TELEGRAM_MENU_COMMANDS, format_restore
|
||||||
|
from continue_cmd import handle_frontend_command, reset_conversation
|
||||||
from llmcore import mykeys
|
from llmcore import mykeys
|
||||||
|
|
||||||
agent = GeneraticAgent()
|
agent = GeneraticAgent()
|
||||||
@@ -82,6 +84,26 @@ async def _stream(dq, msg):
|
|||||||
except Exception: pass
|
except Exception: pass
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def _normalized_command(text):
|
||||||
|
parts = (text or '').strip().split(None, 1)
|
||||||
|
if not parts:
|
||||||
|
return ''
|
||||||
|
head = parts[0].lower()
|
||||||
|
if head.startswith('/'):
|
||||||
|
head = '/' + head[1:].split('@', 1)[0]
|
||||||
|
return head + (f" {parts[1].strip()}" if len(parts) > 1 and parts[1].strip() else '')
|
||||||
|
|
||||||
|
|
||||||
|
def _cancel_stream_task(ctx):
|
||||||
|
task = ctx.user_data.pop('stream_task', None)
|
||||||
|
if task and not task.done():
|
||||||
|
task.cancel()
|
||||||
|
|
||||||
|
|
||||||
|
async def _sync_commands(application):
|
||||||
|
await application.bot.set_my_commands([BotCommand(command, description) for command, description in TELEGRAM_MENU_COMMANDS])
|
||||||
|
|
||||||
async def handle_msg(update, ctx):
|
async def handle_msg(update, ctx):
|
||||||
uid = update.effective_user.id
|
uid = update.effective_user.id
|
||||||
if ALLOWED and uid not in ALLOWED:
|
if ALLOWED and uid not in ALLOWED:
|
||||||
@@ -93,11 +115,9 @@ async def handle_msg(update, ctx):
|
|||||||
ctx.user_data['stream_task'] = task
|
ctx.user_data['stream_task'] = task
|
||||||
|
|
||||||
async def cmd_abort(update, ctx):
|
async def cmd_abort(update, ctx):
|
||||||
|
_cancel_stream_task(ctx)
|
||||||
agent.abort()
|
agent.abort()
|
||||||
task = ctx.user_data.get('stream_task')
|
await update.message.reply_text("⏹️ 正在停止...")
|
||||||
if task and not task.done():
|
|
||||||
task.cancel()
|
|
||||||
await update.message.reply_text("Aborted")
|
|
||||||
|
|
||||||
async def cmd_llm(update, ctx):
|
async def cmd_llm(update, ctx):
|
||||||
args = (update.message.text or '').split()
|
args = (update.message.text or '').split()
|
||||||
@@ -105,13 +125,50 @@ async def cmd_llm(update, ctx):
|
|||||||
try:
|
try:
|
||||||
n = int(args[1])
|
n = int(args[1])
|
||||||
agent.next_llm(n)
|
agent.next_llm(n)
|
||||||
await update.message.reply_text(f"Switched to [{agent.llm_no}] {agent.get_llm_name()}")
|
await update.message.reply_text(f"✅ 已切换到 [{agent.llm_no}] {agent.get_llm_name()}")
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
await update.message.reply_text(f"Usage: /llm <0-{len(agent.list_llms())-1}>")
|
await update.message.reply_text(f"用法: /llm <0-{len(agent.list_llms())-1}>")
|
||||||
else:
|
else:
|
||||||
lines = [f"{'→' if cur else ' '} [{i}] {name}" for i, name, cur in agent.list_llms()]
|
lines = [f"{'→' if cur else ' '} [{i}] {name}" for i, name, cur in agent.list_llms()]
|
||||||
await update.message.reply_text("LLMs:\n" + "\n".join(lines))
|
await update.message.reply_text("LLMs:\n" + "\n".join(lines))
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_command(update, ctx):
|
||||||
|
uid = update.effective_user.id
|
||||||
|
if ALLOWED and uid not in ALLOWED:
|
||||||
|
return await update.message.reply_text("no")
|
||||||
|
cmd = _normalized_command(update.message.text)
|
||||||
|
op = cmd.split()[0] if cmd else ''
|
||||||
|
if op == '/help':
|
||||||
|
return await update.message.reply_text(HELP_TEXT)
|
||||||
|
if op == '/status':
|
||||||
|
llm = agent.get_llm_name() if agent.llmclient else '未配置'
|
||||||
|
return await update.message.reply_text(f"状态: {'🔴 运行中' if agent.is_running else '🟢 空闲'}\nLLM: [{agent.llm_no}] {llm}")
|
||||||
|
if op == '/stop':
|
||||||
|
return await cmd_abort(update, ctx)
|
||||||
|
if op == '/llm':
|
||||||
|
return await cmd_llm(update, ctx)
|
||||||
|
if op == '/new':
|
||||||
|
_cancel_stream_task(ctx)
|
||||||
|
return await update.message.reply_text(reset_conversation(agent))
|
||||||
|
if op == '/restore':
|
||||||
|
_cancel_stream_task(ctx)
|
||||||
|
try:
|
||||||
|
restored_info, err = format_restore()
|
||||||
|
if err:
|
||||||
|
return await update.message.reply_text(err)
|
||||||
|
restored, fname, count = restored_info
|
||||||
|
agent.abort()
|
||||||
|
agent.history.extend(restored)
|
||||||
|
return await update.message.reply_text(f"✅ 已恢复 {count} 轮对话\n来源: {fname}\n(仅恢复上下文,请输入新问题继续)")
|
||||||
|
except Exception as e:
|
||||||
|
return await update.message.reply_text(f"❌ 恢复失败: {e}")
|
||||||
|
if op == '/continue':
|
||||||
|
if cmd != '/continue':
|
||||||
|
_cancel_stream_task(ctx)
|
||||||
|
return await update.message.reply_text(handle_frontend_command(agent, cmd))
|
||||||
|
return await update.message.reply_text(HELP_TEXT)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Single instance lock using socket
|
# Single instance lock using socket
|
||||||
try:
|
try:
|
||||||
@@ -141,9 +198,9 @@ if __name__ == '__main__':
|
|||||||
.token(mykeys['tg_bot_token'])
|
.token(mykeys['tg_bot_token'])
|
||||||
.request(request)
|
.request(request)
|
||||||
.get_updates_request(request)
|
.get_updates_request(request)
|
||||||
|
.post_init(_sync_commands)
|
||||||
.build())
|
.build())
|
||||||
app.add_handler(CommandHandler("stop", cmd_abort))
|
app.add_handler(MessageHandler(filters.COMMAND, handle_command))
|
||||||
app.add_handler(CommandHandler("llm", cmd_llm))
|
|
||||||
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_msg))
|
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_msg))
|
||||||
app.add_error_handler(_error_handler)
|
app.add_error_handler(_error_handler)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user