sync: UI animations, select styling, TLS verify flag via proxy second line, brand spacing
This commit is contained in:
@@ -11,6 +11,7 @@ from agentui.pipeline.executor import PipelineExecutor
|
||||
from agentui.pipeline.defaults import default_pipeline
|
||||
from agentui.pipeline.storage import load_pipeline, save_pipeline, list_presets, load_preset, save_preset, load_var_store
|
||||
from agentui.common.vendors import detect_vendor
|
||||
from agentui.common.cancel import request_cancel, clear_cancel, is_cancelled
|
||||
|
||||
|
||||
class UnifiedParams(BaseModel):
|
||||
@@ -677,6 +678,36 @@ def create_app() -> FastAPI:
|
||||
raise HTTPException(status_code=400, detail="Invalid pipeline format")
|
||||
save_preset(name, payload)
|
||||
return JSONResponse({"ok": True})
|
||||
|
||||
# --- Manual cancel/clear for pipeline execution ---
|
||||
@app.post("/admin/cancel")
|
||||
async def admin_cancel() -> JSONResponse:
|
||||
try:
|
||||
p = load_pipeline()
|
||||
pid = p.get("id", "pipeline_editor")
|
||||
except Exception:
|
||||
p = default_pipeline()
|
||||
pid = p.get("id", "pipeline_editor")
|
||||
try:
|
||||
request_cancel(pid)
|
||||
except Exception:
|
||||
pass
|
||||
return JSONResponse({"ok": True, "pipeline_id": pid, "cancelled": True})
|
||||
|
||||
@app.post("/admin/cancel/clear")
|
||||
async def admin_cancel_clear() -> JSONResponse:
|
||||
try:
|
||||
p = load_pipeline()
|
||||
pid = p.get("id", "pipeline_editor")
|
||||
except Exception:
|
||||
p = default_pipeline()
|
||||
pid = p.get("id", "pipeline_editor")
|
||||
try:
|
||||
clear_cancel(pid)
|
||||
except Exception:
|
||||
pass
|
||||
return JSONResponse({"ok": True, "pipeline_id": pid, "cancelled": False})
|
||||
|
||||
# --- SSE endpoint for live pipeline trace ---
|
||||
@app.get("/admin/trace/stream")
|
||||
async def sse_trace() -> StreamingResponse:
|
||||
|
||||
30
agentui/common/cancel.py
Normal file
30
agentui/common/cancel.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict
|
||||
import threading
|
||||
|
||||
# Simple in-process cancel flags storage (per pipeline_id)
|
||||
# Thread-safe for FastAPI workers in same process
|
||||
_cancel_flags: Dict[str, bool] = {}
|
||||
_lock = threading.Lock()
|
||||
|
||||
|
||||
def request_cancel(pipeline_id: str) -> None:
|
||||
"""Set cancel flag for given pipeline id."""
|
||||
pid = str(pipeline_id or "pipeline_editor")
|
||||
with _lock:
|
||||
_cancel_flags[pid] = True
|
||||
|
||||
|
||||
def clear_cancel(pipeline_id: str) -> None:
|
||||
"""Clear cancel flag for given pipeline id."""
|
||||
pid = str(pipeline_id or "pipeline_editor")
|
||||
with _lock:
|
||||
_cancel_flags.pop(pid, None)
|
||||
|
||||
|
||||
def is_cancelled(pipeline_id: str) -> bool:
|
||||
"""Check cancel flag for given pipeline id."""
|
||||
pid = str(pipeline_id or "pipeline_editor")
|
||||
with _lock:
|
||||
return bool(_cancel_flags.get(pid, False))
|
||||
@@ -25,6 +25,7 @@ from agentui.pipeline.templating import (
|
||||
eval_condition_expr,
|
||||
)
|
||||
from agentui.pipeline.storage import load_var_store, save_var_store, clear_var_store
|
||||
from agentui.common.cancel import is_cancelled, clear_cancel
|
||||
|
||||
|
||||
# --- Templating helpers are imported from agentui.pipeline.templating ---
|
||||
@@ -186,6 +187,12 @@ class PipelineExecutor:
|
||||
except Exception:
|
||||
self._store = {}
|
||||
|
||||
# Перед стартом прогона сбрасываем возможный «старый» флаг отмены
|
||||
try:
|
||||
clear_cancel(self.pipeline_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
mode = (self.loop_mode or "dag").lower()
|
||||
if mode == "iterative":
|
||||
res = await self._run_iterative(context, trace)
|
||||
@@ -388,6 +395,36 @@ class PipelineExecutor:
|
||||
|
||||
wave_idx = 0
|
||||
while ready:
|
||||
# Ручная отмена исполнения (DAG)
|
||||
try:
|
||||
if is_cancelled(self.pipeline_id):
|
||||
if trace is not None:
|
||||
try:
|
||||
await trace({
|
||||
"event": "cancelled",
|
||||
"node_id": "",
|
||||
"node_type": "Pipeline",
|
||||
"wave": wave_idx,
|
||||
"ts": int(time.time() * 1000),
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
# Снимок и финальный EXEC TRACE
|
||||
try:
|
||||
self._commit_snapshot(context, values, last_node_id or "")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
summary = " -> ".join(self._exec_log) if getattr(self, "_exec_log", None) else ""
|
||||
if summary and isinstance(self._store.get("snapshot"), dict):
|
||||
self._store["snapshot"]["EXEC_TRACE"] = summary
|
||||
save_var_store(self.pipeline_id, self._store)
|
||||
except Exception:
|
||||
pass
|
||||
return last_result
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
wave_nodes = list(ready)
|
||||
ready = []
|
||||
wave_results: Dict[str, Dict[str, Any]] = {}
|
||||
@@ -745,6 +782,36 @@ class PipelineExecutor:
|
||||
|
||||
# Главный цикл
|
||||
while q:
|
||||
# Ручная отмена исполнения (iterative)
|
||||
try:
|
||||
if is_cancelled(self.pipeline_id):
|
||||
if trace is not None:
|
||||
try:
|
||||
await trace({
|
||||
"event": "cancelled",
|
||||
"node_id": "",
|
||||
"node_type": "Pipeline",
|
||||
"wave": step,
|
||||
"ts": int(time.time() * 1000),
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
# Снимок и финальный EXEC TRACE
|
||||
try:
|
||||
self._commit_snapshot(context, values, last_node_id or "")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
summary = " -> ".join(self._exec_log) if getattr(self, "_exec_log", None) else ""
|
||||
if summary and isinstance(self._store.get("snapshot"), dict):
|
||||
self._store["snapshot"]["EXEC_TRACE"] = summary
|
||||
save_var_store(self.pipeline_id, self._store)
|
||||
except Exception:
|
||||
pass
|
||||
return last_result
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Проверяем лимиты
|
||||
if total_runs >= self.loop_max_iters:
|
||||
raise ExecutionError(f"Iterative mode exceeded loop_max_iters={self.loop_max_iters}")
|
||||
|
||||
Reference in New Issue
Block a user