192 lines
6.6 KiB
Python
192 lines
6.6 KiB
Python
import json
|
|
from typing import Any
|
|
|
|
from app.db.models import PomodoroSession
|
|
from app.pomodoro.cycle import PHASE_LONG_BREAK, PHASE_SHORT_BREAK, PHASE_WORK
|
|
|
|
PHASE_LABELS = {
|
|
PHASE_WORK: "Работа",
|
|
PHASE_SHORT_BREAK: "Короткий перерыв",
|
|
PHASE_LONG_BREAK: "Длинный перерыв",
|
|
}
|
|
|
|
|
|
def _format_time(seconds: int) -> str:
|
|
minutes, secs = divmod(max(0, seconds), 60)
|
|
return f"{minutes:02d}:{secs:02d}"
|
|
|
|
|
|
def format_phase_completed_notice(
|
|
session: PomodoroSession,
|
|
next_phase: str | None,
|
|
) -> str:
|
|
phase_label = PHASE_LABELS.get(session.phase, session.phase)
|
|
task = session.task_note or "без описания"
|
|
lines = [f"⏱ **{phase_label} завершена** · {session.duration_min} мин · _{task}_"]
|
|
|
|
if next_phase == PHASE_SHORT_BREAK:
|
|
lines.append("Дальше: короткий перерыв ☕")
|
|
elif next_phase == PHASE_LONG_BREAK:
|
|
lines.append("Дальше: длинный перерыв 🌴 · цикл почти завершён")
|
|
elif next_phase == PHASE_WORK:
|
|
lines.append("Дальше: снова работа 💪")
|
|
else:
|
|
lines.append("Цикл сброшен. Можно отдохнуть и начать заново.")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def format_pomodoro_notice(tool_name: str, raw_result: str) -> str | None:
|
|
try:
|
|
data = json.loads(raw_result)
|
|
except json.JSONDecodeError:
|
|
return None
|
|
|
|
if isinstance(data, dict) and "error" in data:
|
|
return f"⏱ Помидоро: {data['error']}"
|
|
|
|
if tool_name == "reset_pomodoro_cycle":
|
|
cycle = data.get("cycle", data)
|
|
return (
|
|
"⏱ **Цикл помидоро сброшен** · "
|
|
f"прогресс: {cycle.get('completed_work_sessions', 0)}/"
|
|
f"{cycle.get('sessions_until_long_break', 4)}"
|
|
)
|
|
|
|
if tool_name in (
|
|
"get_pomodoro_status",
|
|
"start_pomodoro",
|
|
"start_work",
|
|
"start_short_break",
|
|
"start_long_break",
|
|
"stop_pomodoro",
|
|
"skip_pomodoro_phase",
|
|
):
|
|
return _format_status_notice(data)
|
|
|
|
if tool_name == "get_pomodoro_history":
|
|
return _format_history_notice(data)
|
|
|
|
if tool_name == "create_work_item":
|
|
return _format_work_item_notice(data)
|
|
|
|
if tool_name == "list_work_items":
|
|
return _format_work_items_list_notice(data)
|
|
|
|
if tool_name == "sync_taiga_projects":
|
|
return f"📋 Синхронизировано проектов Taiga: **{len(data)}**"
|
|
|
|
if tool_name == "list_taiga_projects":
|
|
if not isinstance(data, list) or not data:
|
|
return "📋 Проекты Taiga не найдены. Вызовите sync_taiga_projects."
|
|
lines = ["📋 **Проекты:**"]
|
|
for p in data:
|
|
gitea = f"{p.get('gitea_owner')}/{p.get('gitea_repo')}" if p.get("gitea_configured") else "—"
|
|
lines.append(f"- `{p.get('slug')}`: {p.get('name')} · Gitea: {gitea}")
|
|
return "\n".join(lines)
|
|
|
|
return None
|
|
|
|
|
|
def _format_work_item_notice(data: dict[str, Any]) -> str | None:
|
|
if data.get("error"):
|
|
return f"📋 {data['error']}"
|
|
if not data.get("ok"):
|
|
return None
|
|
taiga = data.get("taiga", {})
|
|
gitea = data.get("gitea", {})
|
|
lines = [
|
|
"📋 **Создано:**",
|
|
f"- Taiga: #{taiga.get('ref')} — {taiga.get('subject')}",
|
|
f"- URL: {taiga.get('url')}",
|
|
]
|
|
if gitea.get("url"):
|
|
lines.append(f"- Gitea: {gitea.get('url')}")
|
|
if data.get("branch"):
|
|
lines.append(f"- Ветка: `{data['branch']}`")
|
|
subtasks = data.get("subtasks") or []
|
|
if subtasks:
|
|
lines.append("**Подзадачи:**")
|
|
for t in subtasks:
|
|
lines.append(f"- #{t.get('ref')} {t.get('subject')}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def _format_work_items_list_notice(data: Any) -> str | None:
|
|
if not isinstance(data, list) or not data:
|
|
return "📋 Work items не найдены."
|
|
lines = ["📋 **Work items:**"]
|
|
for item in data[:15]:
|
|
lines.append(
|
|
f"- [{item.get('status')}] #{item.get('taiga_ref')} {item.get('title')} "
|
|
f"({item.get('taiga_slug')})"
|
|
)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def _format_status_notice(data: dict[str, Any]) -> str:
|
|
status = data.get("status", "idle")
|
|
phase = data.get("phase", PHASE_WORK)
|
|
phase_label = PHASE_LABELS.get(phase, phase)
|
|
task = data.get("task_note") or "без описания"
|
|
remaining = data.get("remaining_seconds", 0)
|
|
duration = data.get("duration_min", 25)
|
|
cycle = data.get("cycle", {})
|
|
cycle_info = ""
|
|
if cycle:
|
|
cycle_info = (
|
|
f" · цикл {cycle.get('completed_work_sessions', 0)}/"
|
|
f"{cycle.get('sessions_until_long_break', 4)}"
|
|
)
|
|
|
|
if status == "idle":
|
|
return f"⏱ **Помидоро:** таймер не запущен{cycle_info}."
|
|
|
|
if status == "running":
|
|
return (
|
|
f"⏱ **{phase_label}** · осталось **{_format_time(remaining)}** "
|
|
f"из {duration} мин · _{task}_{cycle_info}"
|
|
)
|
|
|
|
if status == "paused":
|
|
elapsed = data.get("elapsed_seconds", 0)
|
|
return (
|
|
f"⏱ **{phase_label} на паузе** · прошло {_format_time(elapsed)} "
|
|
f"из {duration} мин · _{task}_{cycle_info}"
|
|
)
|
|
|
|
if status == "completed":
|
|
return f"⏱ **{phase_label} завершена** · {duration} мин · _{task}_"
|
|
|
|
if status == "cancelled":
|
|
return f"⏱ **{phase_label} отменена** · _{task}_"
|
|
|
|
return f"⏱ Помидоро: {status}"
|
|
|
|
|
|
def _format_history_notice(data: Any) -> str:
|
|
if not isinstance(data, list) or not data:
|
|
return "⏱ **История помидоро** пуста."
|
|
|
|
lines = ["⏱ **История помидоро:**"]
|
|
for item in data[:10]:
|
|
task = item.get("task_note") or "без описания"
|
|
phase = PHASE_LABELS.get(item.get("phase", ""), item.get("phase", "?"))
|
|
duration = item.get("duration_min", "?")
|
|
lines.append(f"- {phase}: {task} ({duration} мин)")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def format_pomodoro_context(status: dict[str, Any]) -> str:
|
|
notice = _format_status_notice(status)
|
|
cycle = status.get("cycle", {})
|
|
extra = ""
|
|
if cycle:
|
|
extra = (
|
|
f"\nНастройки цикла: работа {cycle.get('work_duration_min')} мин, "
|
|
f"перерыв {cycle.get('short_break_min')} мин, "
|
|
f"длинный {cycle.get('long_break_min')} мин."
|
|
)
|
|
return f"[Актуальный статус помидоро]\n{notice}{extra}"
|