diff --git a/web/app.py b/web/app.py index be2c471..2c6d5ca 100644 --- a/web/app.py +++ b/web/app.py @@ -280,13 +280,17 @@ def candidates_select(request: Request, id: int = Form(...), direction: str = Fo @app.post("/candidates/bulk") -def candidates_bulk(request: Request, action: str = Form(...), ids: list[int] = Form(...), +def candidates_bulk(request: Request, action: str = Form(...), ids: list[int] = Form(default_factory=list), direction: str = Form(""), status: str = Form(""), own: str = Form(""), q: str = Form(""), group_by: str = Form("source")): - """Bulk-операции: approve/reject/reject-old по отмеченным ids.""" + """Bulk-операции: approve/reject/reject-old/process по отмеченным ids.""" _require_auth(request) + back = f"/candidates?group_by={group_by}" + for k, v in (("direction", direction), ("status", status), ("own", own), ("q", q)): + if v: + back += f"&{k}={quote(v)}" if not ids: - return RedirectResponse(url="/candidates?error=no_selection", status_code=302) + return RedirectResponse(url=back + "&error=no_selection", status_code=302) conn = _db() ph = ",".join("?" * len(ids)) rows = conn.execute(f"SELECT * FROM posts WHERE id IN ({ph})", ids).fetchall() @@ -294,7 +298,7 @@ def candidates_bulk(request: Request, action: str = Form(...), ids: list[int] = conn.execute(f"UPDATE posts SET status='rejected' WHERE id IN ({ph})", ids) conn.commit() conn.close() - return RedirectResponse(url=f"/candidates?status=rejected&group_by={group_by}", status_code=302) + return RedirectResponse(url=back + "&status=rejected", status_code=302) if action == "reject-old": # все выбранные, кроме самых свежих (по 1 от источника) — «почистить старьё» keep = {} @@ -307,9 +311,22 @@ def candidates_bulk(request: Request, action: str = Form(...), ids: list[int] = conn.execute(f"UPDATE posts SET status='rejected' WHERE id IN ({ph2})", kill) conn.commit() conn.close() - return RedirectResponse(url=f"/candidates?status=rejected&group_by={group_by}", status_code=302) - # approve — каждый через существующую логику (публикация + бандл) + return RedirectResponse(url=back + "&status=rejected", status_code=302) conn.close() + if action == "process": + # обработка локальной моделью (переклассификация) по выбранным + errs = [] + done = 0 + for r in rows: + try: + impl_reclassify(int(r["id"])) + done += 1 + except Exception as e: + errs.append(f"{r['id']}({e})") + if errs: + return RedirectResponse(url=f"/candidates?error=process_partial:{','.join(map(str,errs))[:120]}&group_by={group_by}", status_code=302) + return RedirectResponse(url=back + "&error=processed:" + str(done), status_code=302) + # approve — каждый через существующую логику (публикация + бандл) errs = [] for r in rows: if r["status"] == "published": @@ -334,25 +351,30 @@ def post_comment(post_id: int, request: Request, comment: str = Form("")): @app.post("/posts/{post_id}/reclassify") def post_reclassify(post_id: int, request: Request): + """Переклассификация кандидата локальной моделью (qwen).""" _require_auth(request) + try: + impl_reclassify(post_id) + except Exception as e: + return RedirectResponse(url=f"/candidates?selected={post_id}&error=reclassify:{e}", status_code=302) + return RedirectResponse(url=f"/candidates?selected={post_id}", status_code=302) + + +def impl_reclassify(post_id: int): + """Перезапуск классификации локальной моделью. Бросает исключение при неудаче.""" from classifier.classify import classify_text conn = _db() p = conn.execute("SELECT * FROM posts WHERE id=?", (post_id,)).fetchone() if not p: conn.close() - return RedirectResponse(url="/candidates?error=notfound", status_code=302) - try: - res = classify_text(dict(p)) - except Exception as e: - conn.close() - return RedirectResponse(url=f"/candidates?selected={post_id}&error=reclassify:{e}", status_code=302) + raise ValueError("notfound") + res = classify_text(dict(p)) conn.execute( "UPDATE posts SET direction=?, relevance=?, interest=?, summary=?, classified=? WHERE id=?", (res["direction"], res["relevance"], res["interest"], res["summary"], 1 if res["classified"] else 0, post_id), ) conn.commit() conn.close() - return RedirectResponse(url=f"/candidates?selected={post_id}", status_code=302) @app.post("/posts/{post_id}/rewrite") diff --git a/web/templates/base.html b/web/templates/base.html index b778ea0..2266767 100644 --- a/web/templates/base.html +++ b/web/templates/base.html @@ -6,14 +6,14 @@ VESTI — {% block title %}Управление{% endblock %} -
+
{% block content %}{% endblock %}
diff --git a/web/templates/candidates.html b/web/templates/candidates.html index 8bc288d..a36d69b 100644 --- a/web/templates/candidates.html +++ b/web/templates/candidates.html @@ -48,15 +48,16 @@ {% set selected_qs = '&selected=' ~ selected.id if selected else '' %}
-
-
-
- Группировка: - {% for val, label in [('source', 'по источнику'), ('date', 'по дате'), ('status', 'по статусу')] %} + +
+
+
+ Группировка: + {% for val, label in [('source', 'источник'), ('date', 'дата'), ('status', 'статус')] %} {% if group_by == val %} - {{ label }} + {{ label }} {% else %} - {{ label }} {% endif %} {% endfor %} @@ -102,21 +103,23 @@ -
- - +
+
⚡ Массовые операции 0
+
+ + + + +
+
Отметьте галочками слева и нажмите действие. «Обработать» — переклассификация локальной моделью (qwen).
-
-
+ +
+
{% if selected %}
#{{ selected.id }} {{ selected.source_name or '?' }}
@@ -132,23 +135,25 @@ {% if selected.url %}Исходник{% endif %}
-
+
{% if selected.status != 'published' %}
- -
- {% endif %} - {% if selected.status != 'rejected' %} -
- +
{% endif %} {% if selected.status != 'published' %}
- +
+ {% endif %} + {% if selected.status != 'published' %}
- + +
+ {% endif %} + {% if selected.status != 'rejected' and selected.status != 'published' %} +
+
{% endif %}