added RAG, Multiuser, TG bot
This commit is contained in:
+155
-153
@@ -1,153 +1,155 @@
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.config import get_settings
|
||||
from app.integrations.taiga import TaigaClient
|
||||
from app.projects.service import ProjectService
|
||||
|
||||
MAX_PROJECTS_IN_CONTEXT = 20
|
||||
MAX_OPEN_PER_PROJECT = 8
|
||||
PROJECTS_CACHE_SEC = 120
|
||||
|
||||
_cache: dict[str, Any] = {"data": None, "expires_at": 0.0}
|
||||
|
||||
|
||||
def invalidate_projects_snapshot_cache() -> None:
|
||||
_cache["data"] = None
|
||||
_cache["expires_at"] = 0.0
|
||||
|
||||
|
||||
def get_projects_snapshot(db: Session, *, force: bool = False) -> dict[str, Any]:
|
||||
now = time.time()
|
||||
if not force and _cache["data"] is not None and now < _cache["expires_at"]:
|
||||
return _cache["data"]
|
||||
|
||||
snapshot = _fetch_projects_snapshot(db)
|
||||
_cache["data"] = snapshot
|
||||
_cache["expires_at"] = now + PROJECTS_CACHE_SEC
|
||||
return snapshot
|
||||
|
||||
|
||||
def _fetch_projects_snapshot(db: Session) -> dict[str, Any]:
|
||||
settings = get_settings()
|
||||
service = ProjectService(db)
|
||||
|
||||
if not settings.taiga_configured:
|
||||
return {"configured": False, "projects": [], "open_items": [], "taiga_open": []}
|
||||
|
||||
projects = service.list_projects()
|
||||
if not projects:
|
||||
try:
|
||||
projects = service.sync_taiga_projects()
|
||||
except Exception as exc:
|
||||
return {
|
||||
"configured": True,
|
||||
"projects": [],
|
||||
"open_items": [],
|
||||
"taiga_open": [],
|
||||
"error": str(exc),
|
||||
}
|
||||
|
||||
open_items = service.list_work_items(limit=15, status="open")
|
||||
taiga_open: list[dict[str, Any]] = []
|
||||
fetch_error: str | None = None
|
||||
|
||||
try:
|
||||
client = TaigaClient()
|
||||
for proj in projects[:MAX_PROJECTS_IN_CONTEXT]:
|
||||
stories = client.list_open_userstories(
|
||||
proj["taiga_id"], limit=MAX_OPEN_PER_PROJECT
|
||||
)
|
||||
tasks = client.list_open_tasks(proj["taiga_id"], limit=MAX_OPEN_PER_PROJECT)
|
||||
taiga_open.append(
|
||||
{
|
||||
"slug": proj["slug"],
|
||||
"name": proj["name"],
|
||||
"stories": [
|
||||
{
|
||||
"ref": s.get("ref"),
|
||||
"subject": s.get("subject", "")[:120],
|
||||
}
|
||||
for s in stories
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"ref": t.get("ref"),
|
||||
"subject": t.get("subject", "")[:120],
|
||||
}
|
||||
for t in tasks
|
||||
],
|
||||
}
|
||||
)
|
||||
except Exception as exc:
|
||||
fetch_error = str(exc)
|
||||
|
||||
return {
|
||||
"configured": True,
|
||||
"projects": projects,
|
||||
"open_items": open_items,
|
||||
"taiga_open": taiga_open,
|
||||
"error": fetch_error,
|
||||
}
|
||||
|
||||
|
||||
def format_projects_context(snapshot: dict[str, Any]) -> str:
|
||||
if not snapshot.get("configured"):
|
||||
return "[Taiga/Gitea]\nНе настроено (нет TAIGA_USERNAME/PASSWORD в .env)."
|
||||
|
||||
lines = ["[Проекты и задачи — снимок на начало ответа]"]
|
||||
|
||||
if snapshot.get("error"):
|
||||
lines.append(f"⚠ Ошибка загрузки задач из Taiga: {snapshot['error']}")
|
||||
|
||||
projects = snapshot.get("projects") or []
|
||||
if not projects:
|
||||
lines.append("Проекты Taiga: кэш пуст. Вызови sync_taiga_projects.")
|
||||
else:
|
||||
lines.append(f"Проекты Taiga ({len(projects)}):")
|
||||
for p in projects[:MAX_PROJECTS_IN_CONTEXT]:
|
||||
gitea = (
|
||||
f"{p.get('gitea_owner')}/{p.get('gitea_repo')}"
|
||||
if p.get("gitea_configured")
|
||||
else "Gitea не привязан"
|
||||
)
|
||||
lines.append(f"- `{p.get('slug')}`: {p.get('name')} · {gitea}")
|
||||
|
||||
taiga_open = snapshot.get("taiga_open") or []
|
||||
if taiga_open:
|
||||
lines.append("")
|
||||
lines.append("Открытые задачи в Taiga (live):")
|
||||
for block in taiga_open:
|
||||
stories = block.get("stories") or []
|
||||
tasks = block.get("tasks") or []
|
||||
if not stories and not tasks:
|
||||
lines.append(f" `{block.get('slug')}`: нет открытых")
|
||||
continue
|
||||
lines.append(f" `{block.get('slug')}`:")
|
||||
for story in stories:
|
||||
lines.append(f" story #{story.get('ref')} {story.get('subject')}")
|
||||
for task in tasks:
|
||||
lines.append(f" task #{task.get('ref')} {task.get('subject')}")
|
||||
|
||||
open_items = snapshot.get("open_items") or []
|
||||
if open_items:
|
||||
lines.append("")
|
||||
lines.append("Work items созданные ассистентом (локальная БД):")
|
||||
for item in open_items[:10]:
|
||||
gitea_part = f", gitea #{item.get('gitea_issue')}" if item.get("gitea_issue") else ""
|
||||
lines.append(
|
||||
f"- #{item.get('taiga_ref')} {item.get('title')} "
|
||||
f"({item.get('taiga_slug')}{gitea_part})"
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
lines.append(
|
||||
"Правила: "
|
||||
"«какие задачи» → list_taiga_tasks (Taiga API), НЕ list_work_items. "
|
||||
"list_work_items — только созданные через ассистента. "
|
||||
"Не пиши «ожидаю систему» — сразу вызывай tool или отвечай из снимка выше. "
|
||||
"create_work_item — для новых фич/багов из вольного текста."
|
||||
)
|
||||
return "\n".join(lines)
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.config import get_settings
|
||||
from app.integrations.taiga import TaigaClient
|
||||
from app.projects.service import ProjectService
|
||||
|
||||
MAX_PROJECTS_IN_CONTEXT = 20
|
||||
MAX_OPEN_PER_PROJECT = 8
|
||||
PROJECTS_CACHE_SEC = 120
|
||||
|
||||
_cache: dict[int, dict[str, Any]] = {}
|
||||
|
||||
|
||||
def invalidate_projects_snapshot_cache(user_id: int | None = None) -> None:
|
||||
if user_id is None:
|
||||
_cache.clear()
|
||||
else:
|
||||
_cache.pop(user_id, None)
|
||||
|
||||
|
||||
def get_projects_snapshot(db: Session, user_id: int, *, force: bool = False) -> dict[str, Any]:
|
||||
now = time.time()
|
||||
entry = _cache.get(user_id)
|
||||
if not force and entry and now < entry.get("expires_at", 0):
|
||||
return entry["data"]
|
||||
|
||||
snapshot = _fetch_projects_snapshot(db, user_id)
|
||||
_cache[user_id] = {"data": snapshot, "expires_at": now + PROJECTS_CACHE_SEC}
|
||||
return snapshot
|
||||
|
||||
|
||||
def _fetch_projects_snapshot(db: Session, user_id: int) -> dict[str, Any]:
|
||||
settings = get_settings()
|
||||
service = ProjectService(db, user_id)
|
||||
|
||||
if not settings.taiga_configured:
|
||||
return {"configured": False, "projects": [], "open_items": [], "taiga_open": []}
|
||||
|
||||
projects = service.list_projects()
|
||||
if not projects:
|
||||
try:
|
||||
projects = service.sync_taiga_projects()
|
||||
except Exception as exc:
|
||||
return {
|
||||
"configured": True,
|
||||
"projects": [],
|
||||
"open_items": [],
|
||||
"taiga_open": [],
|
||||
"error": str(exc),
|
||||
}
|
||||
|
||||
open_items = service.list_work_items(limit=15, status="open")
|
||||
taiga_open: list[dict[str, Any]] = []
|
||||
fetch_error: str | None = None
|
||||
|
||||
try:
|
||||
client = TaigaClient()
|
||||
for proj in projects[:MAX_PROJECTS_IN_CONTEXT]:
|
||||
stories = client.list_open_userstories(
|
||||
proj["taiga_id"], limit=MAX_OPEN_PER_PROJECT
|
||||
)
|
||||
tasks = client.list_open_tasks(proj["taiga_id"], limit=MAX_OPEN_PER_PROJECT)
|
||||
taiga_open.append(
|
||||
{
|
||||
"slug": proj["slug"],
|
||||
"name": proj["name"],
|
||||
"stories": [
|
||||
{
|
||||
"ref": s.get("ref"),
|
||||
"subject": s.get("subject", "")[:120],
|
||||
}
|
||||
for s in stories
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"ref": t.get("ref"),
|
||||
"subject": t.get("subject", "")[:120],
|
||||
}
|
||||
for t in tasks
|
||||
],
|
||||
}
|
||||
)
|
||||
except Exception as exc:
|
||||
fetch_error = str(exc)
|
||||
|
||||
return {
|
||||
"configured": True,
|
||||
"projects": projects,
|
||||
"open_items": open_items,
|
||||
"taiga_open": taiga_open,
|
||||
"error": fetch_error,
|
||||
}
|
||||
|
||||
|
||||
def format_projects_context(snapshot: dict[str, Any]) -> str:
|
||||
if not snapshot.get("configured"):
|
||||
return "[Taiga/Gitea]\nНе настроено (нет TAIGA_USERNAME/PASSWORD в .env)."
|
||||
|
||||
lines = ["[Проекты и задачи — снимок на начало ответа]"]
|
||||
|
||||
if snapshot.get("error"):
|
||||
lines.append(f"⚠ Ошибка загрузки задач из Taiga: {snapshot['error']}")
|
||||
|
||||
projects = snapshot.get("projects") or []
|
||||
if not projects:
|
||||
lines.append("Проекты Taiga: кэш пуст. Вызови sync_taiga_projects.")
|
||||
else:
|
||||
lines.append(f"Проекты Taiga ({len(projects)}):")
|
||||
for p in projects[:MAX_PROJECTS_IN_CONTEXT]:
|
||||
gitea = (
|
||||
f"{p.get('gitea_owner')}/{p.get('gitea_repo')}"
|
||||
if p.get("gitea_configured")
|
||||
else "Gitea не привязан"
|
||||
)
|
||||
lines.append(f"- `{p.get('slug')}`: {p.get('name')} · {gitea}")
|
||||
|
||||
taiga_open = snapshot.get("taiga_open") or []
|
||||
if taiga_open:
|
||||
lines.append("")
|
||||
lines.append("Открытые задачи в Taiga (live):")
|
||||
for block in taiga_open:
|
||||
stories = block.get("stories") or []
|
||||
tasks = block.get("tasks") or []
|
||||
if not stories and not tasks:
|
||||
lines.append(f" `{block.get('slug')}`: нет открытых")
|
||||
continue
|
||||
lines.append(f" `{block.get('slug')}`:")
|
||||
for story in stories:
|
||||
lines.append(f" story #{story.get('ref')} {story.get('subject')}")
|
||||
for task in tasks:
|
||||
lines.append(f" task #{task.get('ref')} {task.get('subject')}")
|
||||
|
||||
open_items = snapshot.get("open_items") or []
|
||||
if open_items:
|
||||
lines.append("")
|
||||
lines.append("Work items созданные ассистентом (локальная БД):")
|
||||
for item in open_items[:10]:
|
||||
gitea_part = f", gitea #{item.get('gitea_issue')}" if item.get("gitea_issue") else ""
|
||||
lines.append(
|
||||
f"- #{item.get('taiga_ref')} {item.get('title')} "
|
||||
f"({item.get('taiga_slug')}{gitea_part})"
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
lines.append(
|
||||
"Правила: "
|
||||
"«какие задачи» → list_taiga_tasks (Taiga API), НЕ list_work_items. "
|
||||
"list_work_items — только созданные через ассистента. "
|
||||
"Не пиши «ожидаю систему» — сразу вызывай tool или отвечай из снимка выше. "
|
||||
"create_work_item — для новых фич/багов из вольного текста."
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
Reference in New Issue
Block a user