v1.8.0: YandexGPT-only NLP, unknown questions system, callback dedup, consent flow fixes, DELETE cascade, UTC+3, auto-detect sidebar menu, tel:/mailto: links
This commit is contained in:
@@ -1158,6 +1158,11 @@ async def bot_kb_page(request: Request, db: AsyncSession = Depends(get_db), user
|
||||
return templates.TemplateResponse("pages/bot_kb.html", ctx(request, user=user, active_page="bot-kb", title="База знаний чат-бота"))
|
||||
|
||||
|
||||
@router.get("/service/bot-unknown")
|
||||
async def bot_unknown_page(request: Request, db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
|
||||
return templates.TemplateResponse("pages/bot_unknown.html", ctx(request, user=user, active_page="bot-unknown", title="Неизвестные вопросы"))
|
||||
|
||||
|
||||
@router.get("/service/bot-test")
|
||||
async def bot_test_page_redirect():
|
||||
return RedirectResponse(url="/service/bot-settings", status_code=302)
|
||||
@@ -1180,6 +1185,13 @@ async def bot_tickets_page(request: Request, db: AsyncSession = Depends(get_db),
|
||||
return templates.TemplateResponse("pages/bot_tickets.html", ctx(request, user=user, active_page="bot-tickets", title="Заявки от чат-бота"))
|
||||
|
||||
|
||||
@router.get("/service/bot-dialogues")
|
||||
async def bot_dialogues_page(request: Request, user: dict = Depends(get_current_user)):
|
||||
if not user:
|
||||
return RedirectResponse(url="/service/login", status_code=302)
|
||||
return templates.TemplateResponse("pages/bot_dialogues.html", ctx(request, user=user, active_page="bot-dialogues", title="Диалоги чат-бота"))
|
||||
|
||||
|
||||
# ---- Role permissions API ----
|
||||
@router.get("/service/api/role-permissions")
|
||||
async def api_role_permissions_get(db: AsyncSession = Depends(get_db), user: dict = Depends(require_role("owner"))):
|
||||
@@ -1208,6 +1220,44 @@ async def api_role_permissions_save(request: Request, db: AsyncSession = Depends
|
||||
return JSONResponse({"ok": True})
|
||||
|
||||
|
||||
import re
|
||||
import os
|
||||
|
||||
def parse_sidebar_menu():
|
||||
sidebar_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "templates", "_sidebar.html")
|
||||
try:
|
||||
with open(sidebar_path, "r", encoding="utf-8") as f:
|
||||
html = f.read()
|
||||
except FileNotFoundError:
|
||||
return []
|
||||
items = []
|
||||
current_section = ""
|
||||
for line in html.split("\n"):
|
||||
sec = re.search(r'sidebar-section[^>]*>([^<]+)', line)
|
||||
if sec:
|
||||
current_section = sec.group(1).strip()
|
||||
perm = re.search(r"role_permissions\.get\('(\w+)'", line)
|
||||
label = re.search(r'>([^<]+)</a>', line)
|
||||
if perm and label:
|
||||
items.append({"key": perm.group(1), "label": label.group(1).strip(), "section": current_section})
|
||||
return items
|
||||
|
||||
|
||||
@router.get("/service/api/menu-items")
|
||||
async def api_menu_items(db: AsyncSession = Depends(get_db), user: dict = Depends(require_role("owner"))):
|
||||
from app.models.models import RoleMenuPermission
|
||||
from sqlalchemy import select as sa_select
|
||||
items = parse_sidebar_menu()
|
||||
existing = await db.execute(sa_select(RoleMenuPermission))
|
||||
existing_keys = {(r.role, r.menu_key) for r in existing.scalars().all()}
|
||||
for item in items:
|
||||
for role in ("engineer", "technician"):
|
||||
if (role, item["key"]) not in existing_keys:
|
||||
db.add(RoleMenuPermission(role=role, menu_key=item["key"], visible=False))
|
||||
await db.commit()
|
||||
return JSONResponse(items)
|
||||
|
||||
|
||||
# ===================== ROLE SWITCH =====================
|
||||
|
||||
@router.post("/service/api/role/switch")
|
||||
@@ -1334,6 +1384,13 @@ async def proxy_bot_broadcast_preview(request: Request, user: dict = Depends(get
|
||||
return await _proxy("POST", "/api/bot/broadcast/preview", body)
|
||||
|
||||
|
||||
@router.post("/api/bot/test/clear")
|
||||
async def proxy_bot_test_clear(user: dict = Depends(get_current_user)):
|
||||
if not user:
|
||||
return JSONResponse({"ok": False, "error": "Not authenticated"}, status_code=401)
|
||||
return await _proxy("POST", "/api/bot/test/clear")
|
||||
|
||||
|
||||
@router.post("/api/bot/webhook")
|
||||
async def proxy_bot_webhook(request: Request):
|
||||
body = await request.json()
|
||||
@@ -1341,8 +1398,39 @@ async def proxy_bot_webhook(request: Request):
|
||||
secret = request.headers.get("X-Max-Bot-Api-Secret")
|
||||
if secret:
|
||||
headers["X-Max-Bot-Api-Secret"] = secret
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.post(f"{BOT_SERVICE_URL}/webhook", json=body, headers=headers)
|
||||
return JSONResponse(resp.json(), status_code=resp.status_code)
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=120.0) as client:
|
||||
resp = await client.post(f"{BOT_SERVICE_URL}/webhook", json=body, headers=headers)
|
||||
return JSONResponse(resp.json(), status_code=resp.status_code)
|
||||
except httpx.TimeoutException:
|
||||
return JSONResponse({"ok": False, "error": "Max bot timeout"}, status_code=504)
|
||||
except Exception as e:
|
||||
return JSONResponse({"ok": False, "error": str(e)}, status_code=502)
|
||||
|
||||
|
||||
@router.get("/api/bot/conversations")
|
||||
async def proxy_bot_conversations(user: dict = Depends(get_current_user)):
|
||||
if not user:
|
||||
return JSONResponse({"ok": False, "error": "Not authenticated"}, status_code=401)
|
||||
return await _proxy("GET", "/api/bot/conversations")
|
||||
|
||||
|
||||
@router.get("/api/bot/conversations/{conv_id}/messages")
|
||||
async def proxy_bot_conversation_messages(conv_id: int, user: dict = Depends(get_current_user)):
|
||||
if not user:
|
||||
return JSONResponse({"ok": False, "error": "Not authenticated"}, status_code=401)
|
||||
return await _proxy("GET", f"/api/bot/conversations/{conv_id}/messages")
|
||||
|
||||
|
||||
@router.get("/api/bot/conversations/{conv_id}/logs")
|
||||
async def proxy_bot_conversation_logs(conv_id: int, user: dict = Depends(get_current_user)):
|
||||
if not user:
|
||||
return JSONResponse({"ok": False, "error": "Not authenticated"}, status_code=401)
|
||||
return await _proxy("GET", f"/api/bot/conversations/{conv_id}/logs")
|
||||
|
||||
|
||||
@router.delete("/api/bot/conversations/{conv_id}")
|
||||
async def proxy_delete_conversation(conv_id: int, request: Request, user: dict = Depends(require_role("owner"))):
|
||||
return await _proxy("DELETE", f"/api/bot/conversations/{conv_id}")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user