AI Update: فاز ۱ و ۲ نقشه راه را روی ربات تلگرام پیاده‌سازی کن

This commit is contained in:
Antigravity Bot
2026-08-30 12:45:15 +03:30
parent f477963a56
commit 03d3e92760
4 changed files with 275 additions and 10 deletions
+131 -1
View File
@@ -320,6 +320,7 @@ async def build_git_menu(chat_id: int) -> tuple[str, InlineKeyboardMarkup]:
],
[
InlineKeyboardButton("📜 تاریخچه ۲۰ کامیت اخیر", callback_data="git_hist:1"),
InlineKeyboardButton("↩️ لغو آخرین تغییر (Undo)", callback_data="git_ask_undo_head"),
],
[
InlineKeyboardButton("🔄 همگام‌سازی گیت (Sync)", callback_data="btn_git_sync"),
@@ -341,6 +342,7 @@ async def build_git_menu(chat_id: int) -> tuple[str, InlineKeyboardMarkup]:
],
[
InlineKeyboardButton("📜 Recent Commits (History)", callback_data="git_hist:1"),
InlineKeyboardButton("↩️ Undo Last Change", callback_data="git_ask_undo_head"),
],
[
InlineKeyboardButton("🔄 Git Sync", callback_data="btn_git_sync"),
@@ -454,13 +456,15 @@ async def build_git_commit_detail_menu(chat_id: int, commit_hash: str) -> tuple[
if not is_curr:
keyboard.append([
InlineKeyboardButton("🔀 سوییچ به این کامیت (Checkout)" if is_fa else "🔀 Switch to this commit (Checkout)", callback_data=f"git_co:{short_h}"),
InlineKeyboardButton("↩️ لغو این کامیت (Revert)" if is_fa else "↩️ Revert This Commit", callback_data=f"git_ask_revert:{short_h}"),
])
keyboard.append([
InlineKeyboardButton("⏪ بازگردانی پروژه به این کامیت (Reset)" if is_fa else "⏪ Restore Project to this Commit (Reset)", callback_data=f"git_ask_reset:{short_h}"),
])
else:
keyboard.append([
InlineKeyboardButton("✅ این کامیت هم‌اکنون فعال است" if is_fa else "✅ Currently Active Commit", callback_data=f"git_view:{short_h}"),
InlineKeyboardButton("↩️ لغو تغییرات این کامیت (Revert)" if is_fa else "↩️ Revert This Commit", callback_data=f"git_ask_revert:{short_h}"),
InlineKeyboardButton("✅ کامیت فعال فعلی" if is_fa else "✅ Currently Active Commit", callback_data=f"git_view:{short_h}"),
])
keyboard.append([
@@ -2089,6 +2093,11 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
f"• <code>/delproject &lt;name&gt;</code> - حذف یک پروژه\n"
f"• <code>/renameproject &lt;قدیم&gt; &lt;جدید&gt;</code> - تغییر نام پروژه\n"
f"• <code>/backup [نام]</code> - تهیه فایل Zip و ارسال بکاپ پروژه در پارت‌های ۵۰ مگابایتی\n\n"
f"<b>🐙 کنترل نسخه و لغو تغییرات (Git &amp; Undo):</b>\n"
f"• <code>/git</code> یا <code>/repo</code> - داشبورد مدیریت گیت و مشاهده وضعیت مخزن Gitea\n"
f"• <code>/sync</code> - همگام‌سازی سریع با مخزن (Pull &amp; Push)\n"
f"• <code>/commit [پیام]</code> - ثبت و ارسال دستی کامیت\n"
f"• <code>/undo [hash]</code> یا <code>/revert</code> - لغو امن آخرین تغییر (HEAD) یا یک کامیت مشخص از گذشته\n\n"
f"<b>⏰ زمان‌بندی تسک‌ها (Scheduled Tasks &amp; Cron):</b>\n"
f"• <code>/tasks</code> یا <code>/schedule</code> - کنترل‌پنل تعاملی تسک‌های زمان‌بندی شده\n"
f"• <code>/schedule &lt;زمان&gt; | &lt;پرامپت/دستور&gt;</code> - ایجاد و زمان‌بندی تسک جدید\n"
@@ -3343,6 +3352,50 @@ async def commit_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
)
await status_msg.edit_text(out, parse_mode=constants.ParseMode.HTML, disable_web_page_preview=True)
# Command: /undo or /revert
@check_auth
async def undo_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
chat_id = update.effective_chat.id
session = session_manager.get_or_create(chat_id)
curr_proj = session_manager.get_current_project(chat_id)
is_fa = (session.language or "").lower() in ("fa", "farsi", "persian", "🇮🇷 persian / farsi (فارسی)")
if not curr_proj:
msg = "⚠️ شما هنوز هیچ پروژه‌ای ایجاد نکرده‌اید." if is_fa else "⚠️ No active project found."
await update.message.reply_html(msg)
return
commit_target = context.args[0].strip() if context.args else "HEAD"
status_msg = await update.message.reply_html("↩️ <i>در حال لغو تغییرات (Git Revert)...</i>" if is_fa else "↩️ <i>Reverting commit...</i>")
ok, res_msg, extra = await git_manager.git_revert(curr_proj.workspace, commit_target=commit_target)
urls = git_manager.get_repo_urls(curr_proj.name)
buttons = [
[
InlineKeyboardButton("📜 تاریخچه کامیت‌ها" if is_fa else "📜 Commit History", callback_data="git_hist:1"),
InlineKeyboardButton("🐙 منوی گیت" if is_fa else "🐙 Git Menu", callback_data="btn_git_menu"),
]
]
if is_fa:
out = (
f"↩️ <b>گزارش لغو تغییرات (Git Undo / Revert):</b>\n\n"
f"• 📁 <b>پروژه:</b> <code>{escape_html(curr_proj.name)}</code>\n"
f"• 🎯 <b>کامیت هدف:</b> <code>{escape_html(commit_target)}</code>\n"
f"• 🌐 <b>مخزن:</b> <a href=\"{urls['web_url']}\">{urls['web_url']}</a>\n\n"
f"{res_msg}"
)
else:
out = (
f"↩️ <b>Git Undo / Revert Report:</b>\n\n"
f"• 📁 <b>Project:</b> <code>{escape_html(curr_proj.name)}</code>\n"
f"• 🎯 <b>Target Commit:</b> <code>{escape_html(commit_target)}</code>\n"
f"• 🌐 <b>Repository:</b> <a href=\"{urls['web_url']}\">{urls['web_url']}</a>\n\n"
f"{res_msg}"
)
await status_msg.edit_text(out, parse_mode=constants.ParseMode.HTML, disable_web_page_preview=True, reply_markup=InlineKeyboardMarkup(buttons))
# Command: /model
@check_auth
async def model_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
@@ -4566,6 +4619,82 @@ async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
except Exception:
await query.message.reply_html(text, reply_markup=markup, disable_web_page_preview=True)
elif data == "git_ask_undo_head":
confirm_text = (
f"⚠️ <b>تأیید لغو آخرین تغییرات (Git Revert HEAD)</b>\n\n"
f"آیا مطمئن هستید که می‌خواهید آخرین کامیت ثبت‌شده در پروژه <code>{escape_html(curr_proj.name if curr_proj else '')}</code> را لغو کنید؟\n\n"
f"<i>یک کامیت معکوس ایجاد خواهد شد و تاریخچهٔ پروژه با امنیت کامل حفظ می‌شود.</i>"
if is_fa else
f"⚠️ <b>Confirm Undo Last Commit (Git Revert HEAD)</b>\n\n"
f"Are you sure you want to revert the latest commit on project <code>{escape_html(curr_proj.name if curr_proj else '')}</code>?\n\n"
f"<i>A safe reverse commit will be created preserving your Git history.</i>"
)
confirm_markup = InlineKeyboardMarkup([
[
InlineKeyboardButton("↩️ بله، لغو شود (Revert)" if is_fa else "↩️ Yes, Revert HEAD", callback_data="git_do_undo_head"),
],
[
InlineKeyboardButton("❌ انصراف و بازگشت" if is_fa else "❌ Cancel", callback_data="btn_git_menu"),
],
])
try:
await query.edit_message_text(confirm_text, parse_mode=constants.ParseMode.HTML, reply_markup=confirm_markup)
except Exception:
await query.message.reply_html(confirm_text, reply_markup=confirm_markup)
elif data == "git_do_undo_head":
await query.answer("↩️ در حال لغو آخرین تغییرات..." if is_fa else "↩️ Reverting latest commit...")
if curr_proj:
ok, res_msg, extra = await git_manager.git_revert(curr_proj.workspace, commit_target="HEAD")
if ok:
await query.answer("✅ آخرین تغییر با موفقیت لغو شد." if is_fa else "✅ Successfully reverted latest commit.", show_alert=True)
else:
await query.answer("" + res_msg, show_alert=True)
text, markup = await build_git_menu(chat_id)
try:
await query.edit_message_text(text, parse_mode=constants.ParseMode.HTML, reply_markup=markup, disable_web_page_preview=True)
except Exception:
await query.message.reply_html(text, reply_markup=markup, disable_web_page_preview=True)
elif data.startswith("git_ask_revert:"):
commit_hash = data.split(":", 1)[1]
confirm_text = (
f"⚠️ <b>تأیید لغو کامیت (Git Revert)</b>\n\n"
f"آیا می‌خواهید تغییرات اعمال‌شده در کامیت <code>{escape_html(commit_hash)}</code> را در پروژه <code>{escape_html(curr_proj.name if curr_proj else '')}</code> لغو کنید؟\n\n"
f"<i>تغییرات این کامیت به‌صورت یک کامیت معکوس جدید ثبت خواهد شد.</i>"
if is_fa else
f"⚠️ <b>Confirm Git Revert</b>\n\n"
f"Are you sure you want to revert commit <code>{escape_html(commit_hash)}</code> on project <code>{escape_html(curr_proj.name if curr_proj else '')}</code>?\n\n"
f"<i>Changes from this commit will be inverted in a new commit.</i>"
)
confirm_markup = InlineKeyboardMarkup([
[
InlineKeyboardButton("↩️ بله، لغو شود" if is_fa else "↩️ Yes, Revert", callback_data=f"git_do_revert:{commit_hash}"),
],
[
InlineKeyboardButton("❌ انصراف و بازگشت" if is_fa else "❌ Cancel", callback_data=f"git_view:{commit_hash}"),
],
])
try:
await query.edit_message_text(confirm_text, parse_mode=constants.ParseMode.HTML, reply_markup=confirm_markup)
except Exception:
await query.message.reply_html(confirm_text, reply_markup=confirm_markup)
elif data.startswith("git_do_revert:"):
commit_hash = data.split(":", 1)[1]
await query.answer("↩️ در حال لغو کامیت..." if is_fa else "↩️ Reverting commit...")
if curr_proj:
ok, res_msg, extra = await git_manager.git_revert(curr_proj.workspace, commit_target=commit_hash)
if ok:
await query.answer("✅ کامیت با موفقیت لغو شد." if is_fa else "✅ Commit reverted successfully.", show_alert=True)
else:
await query.answer("" + res_msg, show_alert=True)
text, markup = await build_git_commit_detail_menu(chat_id, commit_hash)
try:
await query.edit_message_text(text, parse_mode=constants.ParseMode.HTML, reply_markup=markup, disable_web_page_preview=True)
except Exception:
await query.message.reply_html(text, reply_markup=markup, disable_web_page_preview=True)
elif data.startswith("aibtn:"):
btn_id = data.split(":", 1)[1].strip()
from bot_actions import get_ai_button_payload
@@ -6812,6 +6941,7 @@ def main():
application.add_handler(CommandHandler(["git", "repo", "repository", "gitea"], git_command))
application.add_handler(CommandHandler(["sync", "gitsync", "pull"], sync_command))
application.add_handler(CommandHandler(["commit", "gitcommit"], commit_command))
application.add_handler(CommandHandler(["undo", "revert", "gitundo", "gitrevert", "laghv"], undo_command))
# Scheduled Tasks Handlers
application.add_handler(CommandHandler(["tasks", "task", "schedule", "schedules", "cron", "timer"], tasks_command))
+30 -1
View File
@@ -1362,7 +1362,36 @@ async def execute_action(
return badge, side_effects, created_task
# -------------------------------------------------------------
# 30. SAVE_MEMORY / STORE_MEMORY / SET_MEMORY
# 30. GIT_REVERT / GIT_UNDO
# -------------------------------------------------------------
elif act in ("GIT_REVERT", "REVERT_GIT", "GIT_UNDO", "UNDO_GIT", "UNDO"):
p_name = attrs.get("project") or attrs.get("name") or current_project_name
proj = session.projects.get(p_name) if p_name else curr_proj
if not proj:
return f"\n⚠️ پروژه <code>{escape_html(p_name or '')}</code> یافت نشد." if is_fa else f"\n⚠️ Project <code>{escape_html(p_name or '')}</code> not found.", side_effects, created_task
target = attrs.get("commit") or attrs.get("target") or attrs.get("_default") or "HEAD"
from git_manager import git_manager
ok, msg, extra = await git_manager.git_revert(proj.workspace, commit_target=target)
if ok:
badge = (
f"\n\n↩️ <b>عملیات لغو تغییرات (Git Revert) با موفقیت انجام شد:</b>\n"
f"• 📁 <b>پروژه:</b> <code>{escape_html(proj.name)}</code>\n"
f"• 🎯 <b>کامیت هدف:</b> <code>{escape_html(target)}</code>\n"
f"• 📋 {msg}"
if is_fa else
f"\n\n↩️ <b>Git Revert Succeeded:</b>\n"
f"• 📁 <b>Project:</b> <code>{escape_html(proj.name)}</code>\n"
f"• 🎯 <b>Target Commit:</b> <code>{escape_html(target)}</code>\n"
f"• 📋 {msg}"
)
else:
badge = f"\n⚠️ خطا در لغو کامیت: {msg}"
return badge, side_effects, created_task
# -------------------------------------------------------------
# 31. SAVE_MEMORY / STORE_MEMORY / SET_MEMORY
# -------------------------------------------------------------
elif act in ("SAVE_MEMORY", "STORE_MEMORY", "SET_MEMORY", "ADD_MEMORY"):
from memory_manager import memory_manager
+106
View File
@@ -486,6 +486,112 @@ class GitManager:
logger.error(f"Error resetting to {commit_hash} in {ws}: {e}")
return False, f"خطا در بازگردانی به کامیت {commit_hash}: {e}"
async def git_revert(
self,
workspace_path: str,
commit_target: str = "HEAD",
strategy: Optional[str] = None,
) -> Tuple[bool, str, Dict[str, Any]]:
"""
Reverts the specified commit (or HEAD) cleanly.
Attempts deterministic Git revert first without using AI tokens.
If conflicts occur, detects them and safely aborts to keep working tree clean.
"""
ws = Path(workspace_path).expanduser().resolve()
if not (ws / ".git").exists():
return False, "مسیر مشخص‌شده یک مخزن گیت نیست.", {}
try:
# 1. Check dirty files, stash if necessary
porcelain = await self._run_cmd(["git", "status", "--porcelain"], cwd=str(ws))
has_stash = False
if porcelain.strip():
try:
await self._run_cmd(["git", "stash", "save", "Auto-stash before revert"], cwd=str(ws))
has_stash = True
except Exception:
pass
# 2. Get target commit info before revert
target_info = (await self._run_cmd(["git", "log", "-1", "--format=%h - %s", commit_target], cwd=str(ws))).strip()
# 3. Attempt revert
cmd = ["git", "revert", "--no-edit", commit_target]
if strategy:
cmd.extend(["--strategy", strategy])
try:
out = await self._run_cmd(cmd, cwd=str(ws))
except Exception as e:
# Check for conflicts
status_out = await self._run_cmd(["git", "status", "--porcelain"], cwd=str(ws))
conflicted_files = []
for line in status_out.splitlines():
line_s = line.strip()
if line_s.startswith(("UU ", "AA ", "UD ", "DU ", "U ", "DD ")):
parts = line_s.split(maxsplit=1)
if len(parts) > 1:
conflicted_files.append(parts[1])
if conflicted_files:
# Abort revert to leave working directory clean
try:
await self._run_cmd(["git", "revert", "--abort"], cwd=str(ws))
except Exception:
pass
if has_stash:
try:
await self._run_cmd(["git", "stash", "pop"], cwd=str(ws))
except Exception:
pass
file_list_str = "\n".join(f"• <code>{f}</code>" for f in conflicted_files)
return False, f"⚠️ تداخل (Conflict) در لغو کامیت رخ داد:\n{file_list_str}\n\n<i>فرآیند لغو متوقف شد تا کدها بدون دستکاری باقی بمانند.</i>", {
"conflict": True,
"conflicted_files": conflicted_files,
"target": commit_target,
}
# If other revert error, abort
try:
await self._run_cmd(["git", "revert", "--abort"], cwd=str(ws))
except Exception:
pass
if has_stash:
try:
await self._run_cmd(["git", "stash", "pop"], cwd=str(ws))
except Exception:
pass
return False, f"خطا در اجرای revert: {e}", {}
# 4. Push to remote Gitea
try:
await self._run_cmd(["git", "push", "origin", "main"], cwd=str(ws))
except Exception as pe:
logger.warning(f"Failed to push after revert in {ws}: {pe}")
# 5. Pop stash if we stashed earlier
if has_stash:
try:
await self._run_cmd(["git", "stash", "pop"], cwd=str(ws))
except Exception:
pass
# 6. New commit log
new_log = (await self._run_cmd(["git", "log", "-1", "--format=%h - %s"], cwd=str(ws))).strip()
return True, f"✅ تغییرات کامیت <code>{target_info}</code> با موفقیت لغو شد (Reverted).\nکامیت جدید ثبت و پوش شد:\n<code>{new_log}</code>", {
"success": True,
"revert_log": new_log,
"target_info": target_info,
}
except Exception as e:
logger.error(f"Error reverting {commit_target} in {ws}: {e}")
return False, f"❌ خطا در لغو کامیت: {e}", {}
async def git_undo_last_action(self, workspace_path: str) -> Tuple[bool, str, Dict[str, Any]]:
"""Undoes the last commit by reverting HEAD."""
return await self.git_revert(workspace_path, commit_target="HEAD")
async def _run_cmd(self, cmd: List[str], cwd: str) -> str:
"""Helper to run a shell command asynchronously."""
proc = await asyncio.create_subprocess_exec(
File diff suppressed because one or more lines are too long