feat(aurora-smart): add dual-stack orchestration with policy, audit, and UI toggle
This commit is contained in:
@@ -253,6 +253,23 @@ _aurora_live_samples: Dict[str, collections.deque] = {}
|
||||
_aurora_live_last: Dict[str, Dict[str, Any]] = {}
|
||||
_aurora_live_last_loaded = False
|
||||
_aurora_live_last_path = (AURORA_DATA_DIR.parent / "sofiia-console-cache" / "aurora_live_last.json")
|
||||
_aurora_smart_runs: Dict[str, Dict[str, Any]] = {}
|
||||
_aurora_smart_runs_loaded = False
|
||||
_aurora_smart_runs_path = (AURORA_DATA_DIR.parent / "sofiia-console-cache" / "aurora_smart_runs.json")
|
||||
_aurora_smart_policy: Dict[str, Any] = {
|
||||
"updated_at": None,
|
||||
"strategies": {
|
||||
"local_only": {"count": 0, "avg_score": 0.0, "wins": 0, "losses": 0},
|
||||
"local_then_kling": {"count": 0, "avg_score": 0.0, "wins": 0, "losses": 0},
|
||||
},
|
||||
}
|
||||
_aurora_smart_policy_loaded = False
|
||||
_aurora_smart_policy_path = (AURORA_DATA_DIR.parent / "sofiia-console-cache" / "aurora_smart_policy.json")
|
||||
_AURORA_SMART_MAX_RUNS = max(20, int(os.getenv("AURORA_SMART_MAX_RUNS", "200")))
|
||||
_AURORA_SMART_LOCAL_POLL_SEC = max(2.0, float(os.getenv("AURORA_SMART_LOCAL_POLL_SEC", "3.0")))
|
||||
_AURORA_SMART_KLING_POLL_SEC = max(3.0, float(os.getenv("AURORA_SMART_KLING_POLL_SEC", "6.0")))
|
||||
_AURORA_SMART_LOCAL_MAX_SEC = max(60.0, float(os.getenv("AURORA_SMART_LOCAL_MAX_SEC", "10800")))
|
||||
_AURORA_SMART_KLING_MAX_SEC = max(60.0, float(os.getenv("AURORA_SMART_KLING_MAX_SEC", "3600")))
|
||||
MEDIA_COMFY_AGENT_URL = os.getenv(
|
||||
"MEDIA_COMFY_AGENT_URL",
|
||||
"http://comfy-agent:8880" if _is_container_runtime() else "http://127.0.0.1:8880",
|
||||
@@ -352,6 +369,10 @@ async def lifespan(app_: Any):
|
||||
|
||||
task = asyncio.create_task(_nodes_poll_loop())
|
||||
logger.info("Nodes poll loop started (interval=%ds)", _NODES_POLL_INTERVAL)
|
||||
try:
|
||||
_smart_resume_active_monitors()
|
||||
except Exception as e:
|
||||
logger.warning("aurora smart monitor resume failed: %s", e)
|
||||
yield
|
||||
task.cancel()
|
||||
try:
|
||||
@@ -972,6 +993,505 @@ def _aurora_persist_live_last_to_disk() -> None:
|
||||
logger.debug("aurora live-last persist failed: %s", e)
|
||||
|
||||
|
||||
def _smart_now_iso() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def _smart_is_terminal(status: Any) -> bool:
|
||||
return str(status or "").lower() in {"completed", "failed", "cancelled"}
|
||||
|
||||
|
||||
def _smart_media_type(file_name: str, content_type: str) -> str:
|
||||
name = str(file_name or "").lower()
|
||||
ctype = str(content_type or "").lower()
|
||||
video_ext = (".mp4", ".avi", ".mov", ".mkv", ".webm")
|
||||
audio_ext = (".mp3", ".wav", ".flac", ".m4a", ".aac", ".ogg")
|
||||
image_ext = (".jpg", ".jpeg", ".png", ".webp", ".tif", ".tiff", ".bmp")
|
||||
if ctype.startswith("video/") or name.endswith(video_ext):
|
||||
return "video"
|
||||
if ctype.startswith("audio/") or name.endswith(audio_ext):
|
||||
return "audio"
|
||||
if ctype.startswith("image/") or name.endswith(image_ext):
|
||||
return "photo"
|
||||
return "unknown"
|
||||
|
||||
|
||||
def _smart_trim_runs() -> None:
|
||||
if len(_aurora_smart_runs) <= _AURORA_SMART_MAX_RUNS:
|
||||
return
|
||||
ordered = sorted(
|
||||
_aurora_smart_runs.items(),
|
||||
key=lambda kv: str((kv[1] or {}).get("created_at") or ""),
|
||||
reverse=True,
|
||||
)
|
||||
keep = dict(ordered[:_AURORA_SMART_MAX_RUNS])
|
||||
_aurora_smart_runs.clear()
|
||||
_aurora_smart_runs.update(keep)
|
||||
|
||||
|
||||
def _smart_load_runs_from_disk() -> None:
|
||||
global _aurora_smart_runs_loaded
|
||||
if _aurora_smart_runs_loaded:
|
||||
return
|
||||
_aurora_smart_runs_loaded = True
|
||||
try:
|
||||
if not _aurora_smart_runs_path.exists():
|
||||
return
|
||||
payload = json.loads(_aurora_smart_runs_path.read_text(encoding="utf-8"))
|
||||
if isinstance(payload, dict):
|
||||
runs = payload.get("runs")
|
||||
else:
|
||||
runs = payload
|
||||
if isinstance(runs, dict):
|
||||
for run_id, run in runs.items():
|
||||
if isinstance(run_id, str) and isinstance(run, dict):
|
||||
_aurora_smart_runs[run_id] = run
|
||||
_smart_trim_runs()
|
||||
except Exception as exc:
|
||||
logger.debug("aurora smart-runs load failed: %s", exc)
|
||||
|
||||
|
||||
def _smart_persist_runs() -> None:
|
||||
try:
|
||||
_smart_trim_runs()
|
||||
_aurora_smart_runs_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
payload = {
|
||||
"updated_at": _smart_now_iso(),
|
||||
"runs": _aurora_smart_runs,
|
||||
}
|
||||
_aurora_smart_runs_path.write_text(
|
||||
json.dumps(payload, ensure_ascii=False, separators=(",", ":")),
|
||||
encoding="utf-8",
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.debug("aurora smart-runs persist failed: %s", exc)
|
||||
|
||||
|
||||
def _smart_load_policy_from_disk() -> None:
|
||||
global _aurora_smart_policy_loaded
|
||||
if _aurora_smart_policy_loaded:
|
||||
return
|
||||
_aurora_smart_policy_loaded = True
|
||||
try:
|
||||
if not _aurora_smart_policy_path.exists():
|
||||
return
|
||||
payload = json.loads(_aurora_smart_policy_path.read_text(encoding="utf-8"))
|
||||
if isinstance(payload, dict):
|
||||
strategies = payload.get("strategies")
|
||||
if isinstance(strategies, dict):
|
||||
_aurora_smart_policy["strategies"] = strategies
|
||||
_aurora_smart_policy["updated_at"] = payload.get("updated_at")
|
||||
except Exception as exc:
|
||||
logger.debug("aurora smart-policy load failed: %s", exc)
|
||||
|
||||
|
||||
def _smart_persist_policy() -> None:
|
||||
try:
|
||||
_aurora_smart_policy["updated_at"] = _smart_now_iso()
|
||||
_aurora_smart_policy_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
_aurora_smart_policy_path.write_text(
|
||||
json.dumps(_aurora_smart_policy, ensure_ascii=False, separators=(",", ":")),
|
||||
encoding="utf-8",
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.debug("aurora smart-policy persist failed: %s", exc)
|
||||
|
||||
|
||||
def _smart_strategy_stats(strategy: str) -> Dict[str, Any]:
|
||||
_smart_load_policy_from_disk()
|
||||
strategies = _aurora_smart_policy.setdefault("strategies", {})
|
||||
stats = strategies.get(strategy)
|
||||
if not isinstance(stats, dict):
|
||||
stats = {"count": 0, "avg_score": 0.0, "wins": 0, "losses": 0}
|
||||
strategies[strategy] = stats
|
||||
return stats
|
||||
|
||||
|
||||
def _smart_update_strategy_score(strategy: str, score: float) -> None:
|
||||
stats = _smart_strategy_stats(strategy)
|
||||
try:
|
||||
count = int(stats.get("count") or 0) + 1
|
||||
avg = float(stats.get("avg_score") or 0.0)
|
||||
stats["avg_score"] = round(((avg * (count - 1)) + float(score)) / max(1, count), 4)
|
||||
stats["count"] = count
|
||||
_smart_persist_policy()
|
||||
except Exception:
|
||||
return
|
||||
|
||||
|
||||
def _smart_update_strategy_outcome(strategy: str, success: bool) -> None:
|
||||
stats = _smart_strategy_stats(strategy)
|
||||
key = "wins" if success else "losses"
|
||||
stats[key] = int(stats.get(key) or 0) + 1
|
||||
_smart_persist_policy()
|
||||
|
||||
|
||||
def _smart_new_run_id() -> str:
|
||||
stamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
||||
return f"smart_{stamp}_{uuid.uuid4().hex[:6]}"
|
||||
|
||||
|
||||
def _smart_append_audit(run: Dict[str, Any], event: str, detail: Optional[Dict[str, Any]] = None) -> None:
|
||||
audit = run.setdefault("audit", [])
|
||||
if not isinstance(audit, list):
|
||||
audit = []
|
||||
run["audit"] = audit
|
||||
item: Dict[str, Any] = {"ts": _smart_now_iso(), "event": str(event)}
|
||||
if isinstance(detail, dict) and detail:
|
||||
item["detail"] = detail
|
||||
audit.append(item)
|
||||
if len(audit) > 200:
|
||||
del audit[:-200]
|
||||
run["updated_at"] = item["ts"]
|
||||
|
||||
|
||||
def _smart_analysis_features(analysis: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
if not isinstance(analysis, dict):
|
||||
return {
|
||||
"faces": 0,
|
||||
"plates": 0,
|
||||
"noise": "unknown",
|
||||
"blur": "unknown",
|
||||
"quality_score": 0.0,
|
||||
}
|
||||
faces = len(analysis.get("faces") or []) if isinstance(analysis.get("faces"), list) else 0
|
||||
plates = len(analysis.get("license_plates") or []) if isinstance(analysis.get("license_plates"), list) else 0
|
||||
qa = analysis.get("quality_analysis") if isinstance(analysis.get("quality_analysis"), dict) else {}
|
||||
noise = str(qa.get("noise_level") or "unknown").lower()
|
||||
blur = str(qa.get("blur_level") or "unknown").lower()
|
||||
score = 0.0
|
||||
score += min(2.0, faces * 0.2)
|
||||
score += min(2.0, plates * 0.4)
|
||||
if noise in {"high", "very_high"}:
|
||||
score += 1.0
|
||||
if blur in {"high", "very_high"}:
|
||||
score += 1.0
|
||||
return {
|
||||
"faces": faces,
|
||||
"plates": plates,
|
||||
"noise": noise,
|
||||
"blur": blur,
|
||||
"quality_score": round(score, 3),
|
||||
}
|
||||
|
||||
|
||||
def _smart_decide_strategy(
|
||||
*,
|
||||
media_type: str,
|
||||
mode: str,
|
||||
requested_strategy: str,
|
||||
prefer_quality: bool,
|
||||
budget_tier: str,
|
||||
analysis: Optional[Dict[str, Any]],
|
||||
learning_enabled: bool,
|
||||
) -> Dict[str, Any]:
|
||||
strategy = str(requested_strategy or "auto").strip().lower()
|
||||
valid = {"auto", "local_only", "local_then_kling"}
|
||||
if strategy not in valid:
|
||||
strategy = "auto"
|
||||
|
||||
features = _smart_analysis_features(analysis)
|
||||
reasons: List[str] = []
|
||||
score = 0.0
|
||||
|
||||
if media_type != "video":
|
||||
chosen = "local_only"
|
||||
reasons.append("non-video media -> local stack only")
|
||||
return {"strategy": chosen, "reasons": reasons, "score": 0.0, "features": features}
|
||||
|
||||
if strategy in {"local_only", "local_then_kling"}:
|
||||
reasons.append(f"explicit strategy={strategy}")
|
||||
return {"strategy": strategy, "reasons": reasons, "score": features["quality_score"], "features": features}
|
||||
|
||||
score += float(features["quality_score"])
|
||||
if prefer_quality:
|
||||
score += 1.3
|
||||
reasons.append("prefer_quality=true")
|
||||
if str(mode).lower() == "forensic":
|
||||
score += 0.8
|
||||
reasons.append("forensic mode")
|
||||
|
||||
budget_norm = str(budget_tier or "normal").strip().lower()
|
||||
if budget_norm == "low":
|
||||
score -= 1.4
|
||||
reasons.append("budget_tier=low")
|
||||
elif budget_norm == "high":
|
||||
score += 0.6
|
||||
reasons.append("budget_tier=high")
|
||||
|
||||
if learning_enabled:
|
||||
stats = _smart_strategy_stats("local_then_kling")
|
||||
wins = int(stats.get("wins") or 0)
|
||||
losses = int(stats.get("losses") or 0)
|
||||
total = wins + losses
|
||||
if total >= 6:
|
||||
success_ratio = wins / max(1, total)
|
||||
if success_ratio >= 0.65:
|
||||
score += 0.5
|
||||
reasons.append(f"learned success ratio {success_ratio:.2f}")
|
||||
elif success_ratio <= 0.35:
|
||||
score -= 0.7
|
||||
reasons.append(f"learned low success ratio {success_ratio:.2f}")
|
||||
|
||||
chosen = "local_then_kling" if score >= 2.1 else "local_only"
|
||||
if not reasons:
|
||||
reasons.append("default heuristic")
|
||||
return {"strategy": chosen, "reasons": reasons, "score": round(score, 3), "features": features}
|
||||
|
||||
|
||||
def _smart_compact_result(result_payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
payload = {}
|
||||
if not isinstance(result_payload, dict):
|
||||
return payload
|
||||
payload["mode"] = result_payload.get("mode")
|
||||
payload["media_type"] = result_payload.get("media_type")
|
||||
payload["digital_signature"] = result_payload.get("digital_signature")
|
||||
output_files = result_payload.get("output_files")
|
||||
if isinstance(output_files, list):
|
||||
payload["output_files"] = output_files[:8]
|
||||
q = result_payload.get("quality_report")
|
||||
if isinstance(q, dict):
|
||||
payload["quality_report"] = q
|
||||
return payload
|
||||
|
||||
|
||||
async def _smart_fetch_run_status(run_id: str) -> Optional[Dict[str, Any]]:
|
||||
_smart_load_runs_from_disk()
|
||||
run = _aurora_smart_runs.get(run_id)
|
||||
if not isinstance(run, dict):
|
||||
return None
|
||||
return run
|
||||
|
||||
|
||||
async def _smart_monitor_run(run_id: str) -> None:
|
||||
run = await _smart_fetch_run_status(run_id)
|
||||
if not run:
|
||||
return
|
||||
|
||||
local = run.get("local") if isinstance(run.get("local"), dict) else {}
|
||||
local_job_id = str(local.get("job_id") or "")
|
||||
if not local_job_id:
|
||||
_smart_append_audit(run, "monitor.error", {"reason": "missing local job id"})
|
||||
run["status"] = "failed"
|
||||
run["phase"] = "failed"
|
||||
_smart_persist_runs()
|
||||
return
|
||||
|
||||
start = time.monotonic()
|
||||
while time.monotonic() - start <= _AURORA_SMART_LOCAL_MAX_SEC:
|
||||
try:
|
||||
st = await _aurora_request_json(
|
||||
"GET",
|
||||
f"/api/aurora/status/{quote(local_job_id, safe='')}",
|
||||
timeout=20.0,
|
||||
retries=2,
|
||||
retry_backoff_sec=0.25,
|
||||
)
|
||||
except Exception as exc:
|
||||
_smart_append_audit(run, "local.status.error", {"error": str(exc)[:220]})
|
||||
await asyncio.sleep(_AURORA_SMART_LOCAL_POLL_SEC)
|
||||
continue
|
||||
|
||||
status = str(st.get("status") or "").lower()
|
||||
if status in {"queued", "processing"}:
|
||||
run["phase"] = "local_processing"
|
||||
run["status"] = "processing"
|
||||
elif status == "completed":
|
||||
run["phase"] = "local_completed"
|
||||
run["status"] = "processing"
|
||||
else:
|
||||
run["phase"] = f"local_{status or 'unknown'}"
|
||||
run["status"] = status
|
||||
run["local"] = {
|
||||
**local,
|
||||
"job_id": local_job_id,
|
||||
"status": status,
|
||||
"progress": st.get("progress"),
|
||||
"current_stage": st.get("current_stage"),
|
||||
"eta_seconds": st.get("eta_seconds"),
|
||||
"live_fps": st.get("live_fps"),
|
||||
"error_message": st.get("error_message"),
|
||||
"updated_at": _smart_now_iso(),
|
||||
}
|
||||
_smart_persist_runs()
|
||||
|
||||
if status in {"queued", "processing"}:
|
||||
await asyncio.sleep(_AURORA_SMART_LOCAL_POLL_SEC)
|
||||
continue
|
||||
|
||||
if status != "completed":
|
||||
run["status"] = "failed"
|
||||
run["phase"] = "local_failed"
|
||||
_smart_append_audit(
|
||||
run,
|
||||
"local.failed",
|
||||
{"status": status, "error": str(st.get("error_message") or "")[:220]},
|
||||
)
|
||||
_smart_update_strategy_outcome(str(run.get("policy", {}).get("strategy") or "local_only"), False)
|
||||
_smart_persist_runs()
|
||||
return
|
||||
|
||||
_smart_append_audit(run, "local.completed", {"job_id": local_job_id})
|
||||
break
|
||||
else:
|
||||
run["status"] = "failed"
|
||||
run["phase"] = "local_timeout"
|
||||
_smart_append_audit(run, "local.timeout", {"max_sec": _AURORA_SMART_LOCAL_MAX_SEC})
|
||||
_smart_update_strategy_outcome(str(run.get("policy", {}).get("strategy") or "local_only"), False)
|
||||
_smart_persist_runs()
|
||||
return
|
||||
|
||||
try:
|
||||
local_result = await _aurora_request_json(
|
||||
"GET",
|
||||
f"/api/aurora/result/{quote(local_job_id, safe='')}",
|
||||
timeout=30.0,
|
||||
retries=2,
|
||||
retry_backoff_sec=0.25,
|
||||
)
|
||||
except Exception as exc:
|
||||
run["status"] = "failed"
|
||||
run["phase"] = "local_result_error"
|
||||
_smart_append_audit(run, "local.result.error", {"error": str(exc)[:240]})
|
||||
_smart_update_strategy_outcome(str(run.get("policy", {}).get("strategy") or "local_only"), False)
|
||||
_smart_persist_runs()
|
||||
return
|
||||
|
||||
run.setdefault("local", {})
|
||||
if isinstance(run["local"], dict):
|
||||
run["local"]["result"] = _smart_compact_result(local_result)
|
||||
run["local"]["result_ready"] = True
|
||||
run["selected_stack"] = "local"
|
||||
|
||||
policy = run.get("policy") if isinstance(run.get("policy"), dict) else {}
|
||||
strategy = str(policy.get("strategy") or "local_only")
|
||||
media_type = str(run.get("media_type") or "")
|
||||
kling = run.get("kling") if isinstance(run.get("kling"), dict) else {}
|
||||
if strategy != "local_then_kling" or media_type != "video":
|
||||
run["status"] = "completed"
|
||||
run["phase"] = "completed"
|
||||
_smart_append_audit(run, "smart.completed", {"selected_stack": "local", "reason": "strategy local_only or non-video"})
|
||||
_smart_update_strategy_outcome(strategy, True)
|
||||
_smart_persist_runs()
|
||||
return
|
||||
|
||||
run["phase"] = "kling_submitting"
|
||||
run["status"] = "processing"
|
||||
_smart_append_audit(run, "kling.submit.start")
|
||||
_smart_persist_runs()
|
||||
|
||||
try:
|
||||
submit = await _aurora_request_json(
|
||||
"POST",
|
||||
"/api/aurora/kling/enhance",
|
||||
data={
|
||||
"job_id": local_job_id,
|
||||
"prompt": str(kling.get("prompt") or "enhance video quality, improve sharpness and clarity"),
|
||||
"negative_prompt": str(kling.get("negative_prompt") or "noise, blur, artifacts, distortion"),
|
||||
"mode": str(kling.get("mode") or "pro"),
|
||||
"duration": str(kling.get("duration") or "5"),
|
||||
"cfg_scale": str(kling.get("cfg_scale") if kling.get("cfg_scale") is not None else "0.5"),
|
||||
},
|
||||
timeout=120.0,
|
||||
retries=1,
|
||||
retry_backoff_sec=0.25,
|
||||
)
|
||||
except Exception as exc:
|
||||
run["kling"] = {
|
||||
**kling,
|
||||
"status": "failed",
|
||||
"error": str(exc)[:320],
|
||||
}
|
||||
run["status"] = "completed"
|
||||
run["phase"] = "completed_with_kling_failure"
|
||||
run["selected_stack"] = "local"
|
||||
_smart_append_audit(run, "kling.submit.error", {"error": str(exc)[:220]})
|
||||
_smart_update_strategy_outcome(strategy, False)
|
||||
_smart_persist_runs()
|
||||
return
|
||||
|
||||
task_id = str(submit.get("kling_task_id") or "")
|
||||
run["kling"] = {
|
||||
**kling,
|
||||
"task_id": task_id,
|
||||
"status": str(submit.get("status") or "submitted").lower(),
|
||||
"submitted_at": _smart_now_iso(),
|
||||
}
|
||||
_smart_append_audit(run, "kling.submitted", {"task_id": task_id})
|
||||
_smart_persist_runs()
|
||||
|
||||
k_start = time.monotonic()
|
||||
while time.monotonic() - k_start <= _AURORA_SMART_KLING_MAX_SEC:
|
||||
try:
|
||||
kst = await _aurora_request_json(
|
||||
"GET",
|
||||
f"/api/aurora/kling/status/{quote(local_job_id, safe='')}",
|
||||
timeout=30.0,
|
||||
retries=1,
|
||||
retry_backoff_sec=0.2,
|
||||
)
|
||||
except Exception as exc:
|
||||
_smart_append_audit(run, "kling.status.error", {"error": str(exc)[:220]})
|
||||
await asyncio.sleep(_AURORA_SMART_KLING_POLL_SEC)
|
||||
continue
|
||||
|
||||
k_status = str(kst.get("status") or "").lower()
|
||||
k_url = kst.get("kling_result_url")
|
||||
run["phase"] = "kling_processing"
|
||||
run["kling"] = {
|
||||
**(run.get("kling") if isinstance(run.get("kling"), dict) else {}),
|
||||
"status": k_status,
|
||||
"result_url": k_url,
|
||||
"last_polled_at": _smart_now_iso(),
|
||||
}
|
||||
_smart_persist_runs()
|
||||
|
||||
if k_status in {"submitted", "queued", "running", "processing", "pending"}:
|
||||
await asyncio.sleep(_AURORA_SMART_KLING_POLL_SEC)
|
||||
continue
|
||||
|
||||
if k_status in {"succeed", "completed", "success"} and k_url:
|
||||
run["status"] = "completed"
|
||||
run["phase"] = "completed"
|
||||
run["selected_stack"] = "kling"
|
||||
_smart_append_audit(run, "smart.completed", {"selected_stack": "kling", "task_id": task_id})
|
||||
_smart_update_strategy_outcome(strategy, True)
|
||||
_smart_persist_runs()
|
||||
return
|
||||
|
||||
run["status"] = "completed"
|
||||
run["phase"] = "completed_with_kling_failure"
|
||||
run["selected_stack"] = "local"
|
||||
_smart_append_audit(
|
||||
run,
|
||||
"kling.terminal.non_success",
|
||||
{"status": k_status, "task_id": task_id},
|
||||
)
|
||||
_smart_update_strategy_outcome(strategy, False)
|
||||
_smart_persist_runs()
|
||||
return
|
||||
|
||||
run["status"] = "completed"
|
||||
run["phase"] = "completed_with_kling_timeout"
|
||||
run["selected_stack"] = "local"
|
||||
_smart_append_audit(run, "kling.timeout", {"max_sec": _AURORA_SMART_KLING_MAX_SEC})
|
||||
_smart_update_strategy_outcome(strategy, False)
|
||||
_smart_persist_runs()
|
||||
|
||||
|
||||
def _smart_resume_active_monitors() -> None:
|
||||
_smart_load_runs_from_disk()
|
||||
for run_id, run in list(_aurora_smart_runs.items()):
|
||||
if not isinstance(run, dict):
|
||||
continue
|
||||
if _smart_is_terminal(run.get("status")):
|
||||
continue
|
||||
try:
|
||||
asyncio.create_task(_smart_monitor_run(run_id))
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
@app.get("/api/aurora/health")
|
||||
async def api_aurora_health() -> Dict[str, Any]:
|
||||
return await _aurora_request_json("GET", "/health", timeout=10.0)
|
||||
@@ -1012,6 +1532,241 @@ async def api_aurora_upload(
|
||||
return payload
|
||||
|
||||
|
||||
@app.post("/api/aurora/process-smart")
|
||||
async def api_aurora_process_smart(
|
||||
file: UploadFile = File(...),
|
||||
mode: str = Form("tactical"),
|
||||
priority: str = Form("balanced"),
|
||||
export_options: str = Form(""),
|
||||
strategy: str = Form("auto"),
|
||||
prefer_quality: bool = Form(True),
|
||||
budget_tier: str = Form("normal"),
|
||||
learning_enabled: bool = Form(True),
|
||||
kling_prompt: str = Form("enhance video quality, improve sharpness and clarity"),
|
||||
kling_negative_prompt: str = Form("noise, blur, artifacts, distortion"),
|
||||
kling_mode: str = Form("pro"),
|
||||
kling_duration: str = Form("5"),
|
||||
kling_cfg_scale: float = Form(0.5),
|
||||
) -> Dict[str, Any]:
|
||||
_smart_load_runs_from_disk()
|
||||
_smart_load_policy_from_disk()
|
||||
|
||||
file_name = file.filename or "upload.bin"
|
||||
content_type = file.content_type or "application/octet-stream"
|
||||
media_type = _smart_media_type(file_name, content_type)
|
||||
|
||||
analysis: Optional[Dict[str, Any]] = None
|
||||
if media_type in {"video", "photo"}:
|
||||
try:
|
||||
await file.seek(0)
|
||||
files = {"file": (file_name, file.file, content_type)}
|
||||
analysis = await _aurora_request_json(
|
||||
"POST",
|
||||
"/api/aurora/analyze",
|
||||
files=files,
|
||||
timeout=120.0,
|
||||
retries=1,
|
||||
retry_backoff_sec=0.25,
|
||||
)
|
||||
except Exception as exc:
|
||||
analysis = None
|
||||
logger.warning("smart-process analyze skipped: %s", str(exc)[:220])
|
||||
|
||||
policy = _smart_decide_strategy(
|
||||
media_type=media_type,
|
||||
mode=mode,
|
||||
requested_strategy=strategy,
|
||||
prefer_quality=bool(prefer_quality),
|
||||
budget_tier=budget_tier,
|
||||
analysis=analysis,
|
||||
learning_enabled=bool(learning_enabled),
|
||||
)
|
||||
chosen_strategy = str(policy.get("strategy") or "local_only")
|
||||
policy.setdefault("requested_strategy", str(strategy or "auto"))
|
||||
policy["learning_enabled"] = bool(learning_enabled)
|
||||
policy["budget_tier"] = str(budget_tier or "normal")
|
||||
|
||||
await file.seek(0)
|
||||
files = {"file": (file_name, file.file, content_type)}
|
||||
local_payload = await _aurora_request_json(
|
||||
"POST",
|
||||
"/api/aurora/upload",
|
||||
files=files,
|
||||
data={
|
||||
"mode": mode,
|
||||
"priority": priority,
|
||||
"export_options": export_options,
|
||||
},
|
||||
timeout=120.0,
|
||||
)
|
||||
local_job_id = str(local_payload.get("job_id") or "")
|
||||
if not local_job_id:
|
||||
raise HTTPException(status_code=502, detail="Smart process failed: local job_id missing")
|
||||
|
||||
run_id = _smart_new_run_id()
|
||||
now = _smart_now_iso()
|
||||
run: Dict[str, Any] = {
|
||||
"run_id": run_id,
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
"status": "processing",
|
||||
"phase": "local_processing",
|
||||
"media_type": media_type,
|
||||
"selected_stack": None,
|
||||
"requested": {
|
||||
"mode": mode,
|
||||
"priority": priority,
|
||||
"export_options": export_options,
|
||||
"strategy": strategy,
|
||||
"prefer_quality": bool(prefer_quality),
|
||||
"budget_tier": budget_tier,
|
||||
"learning_enabled": bool(learning_enabled),
|
||||
},
|
||||
"policy": policy,
|
||||
"analysis_summary": _smart_analysis_features(analysis),
|
||||
"analysis": analysis if isinstance(analysis, dict) else None,
|
||||
"local": {
|
||||
"job_id": local_job_id,
|
||||
"status": "queued",
|
||||
"submit_payload": {
|
||||
"status_url": f"/api/aurora/status/{quote(local_job_id, safe='')}",
|
||||
"result_url": f"/api/aurora/result/{quote(local_job_id, safe='')}",
|
||||
},
|
||||
},
|
||||
"kling": {
|
||||
"enabled": chosen_strategy == "local_then_kling" and media_type == "video",
|
||||
"status": "pending",
|
||||
"prompt": kling_prompt,
|
||||
"negative_prompt": kling_negative_prompt,
|
||||
"mode": kling_mode,
|
||||
"duration": kling_duration,
|
||||
"cfg_scale": kling_cfg_scale,
|
||||
},
|
||||
"audit": [],
|
||||
}
|
||||
_smart_append_audit(
|
||||
run,
|
||||
"smart.submitted",
|
||||
{
|
||||
"local_job_id": local_job_id,
|
||||
"media_type": media_type,
|
||||
"strategy": chosen_strategy,
|
||||
"score": policy.get("score"),
|
||||
},
|
||||
)
|
||||
_aurora_smart_runs[run_id] = run
|
||||
_smart_persist_runs()
|
||||
|
||||
try:
|
||||
asyncio.create_task(_smart_monitor_run(run_id))
|
||||
except Exception as exc:
|
||||
_smart_append_audit(run, "monitor.spawn.error", {"error": str(exc)[:220]})
|
||||
_smart_persist_runs()
|
||||
|
||||
return {
|
||||
"smart_run_id": run_id,
|
||||
"status": run.get("status"),
|
||||
"phase": run.get("phase"),
|
||||
"media_type": media_type,
|
||||
"local_job_id": local_job_id,
|
||||
"policy": policy,
|
||||
"smart_status_url": f"/api/aurora/process-smart/{quote(run_id, safe='')}",
|
||||
"local_status_url": f"/api/aurora/status/{quote(local_job_id, safe='')}",
|
||||
"local_result_url": f"/api/aurora/result/{quote(local_job_id, safe='')}",
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/aurora/process-smart")
|
||||
async def api_aurora_process_smart_list(
|
||||
limit: int = Query(default=20, ge=1, le=200),
|
||||
status: Optional[str] = Query(default=None),
|
||||
) -> Dict[str, Any]:
|
||||
_smart_load_runs_from_disk()
|
||||
requested = str(status or "").strip().lower()
|
||||
rows = []
|
||||
for run in _aurora_smart_runs.values():
|
||||
if not isinstance(run, dict):
|
||||
continue
|
||||
run_status = str(run.get("status") or "")
|
||||
if requested and run_status.lower() != requested:
|
||||
continue
|
||||
local = run.get("local") if isinstance(run.get("local"), dict) else {}
|
||||
kling = run.get("kling") if isinstance(run.get("kling"), dict) else {}
|
||||
rows.append(
|
||||
{
|
||||
"run_id": run.get("run_id"),
|
||||
"status": run_status,
|
||||
"phase": run.get("phase"),
|
||||
"media_type": run.get("media_type"),
|
||||
"strategy": (run.get("policy") or {}).get("strategy") if isinstance(run.get("policy"), dict) else None,
|
||||
"selected_stack": run.get("selected_stack"),
|
||||
"created_at": run.get("created_at"),
|
||||
"updated_at": run.get("updated_at"),
|
||||
"local_job_id": local.get("job_id"),
|
||||
"local_status": local.get("status"),
|
||||
"kling_status": kling.get("status"),
|
||||
}
|
||||
)
|
||||
rows.sort(key=lambda x: str(x.get("created_at") or ""), reverse=True)
|
||||
return {"runs": rows[:limit], "count": min(limit, len(rows)), "total": len(rows)}
|
||||
|
||||
|
||||
@app.get("/api/aurora/process-smart/{run_id}")
|
||||
async def api_aurora_process_smart_status(run_id: str) -> Dict[str, Any]:
|
||||
run = await _smart_fetch_run_status(run_id)
|
||||
if not run:
|
||||
raise HTTPException(status_code=404, detail="smart run not found")
|
||||
return run
|
||||
|
||||
|
||||
@app.post("/api/aurora/process-smart/{run_id}/feedback")
|
||||
async def api_aurora_process_smart_feedback(
|
||||
run_id: str,
|
||||
payload: Optional[Dict[str, Any]] = Body(default=None),
|
||||
) -> Dict[str, Any]:
|
||||
run = await _smart_fetch_run_status(run_id)
|
||||
if not run:
|
||||
raise HTTPException(status_code=404, detail="smart run not found")
|
||||
body = payload if isinstance(payload, dict) else {}
|
||||
score_raw = body.get("score")
|
||||
score: Optional[float] = None
|
||||
try:
|
||||
if score_raw is not None:
|
||||
score = float(score_raw)
|
||||
except Exception:
|
||||
score = None
|
||||
selected_stack = str(body.get("selected_stack") or "").strip().lower() or None
|
||||
notes = str(body.get("notes") or "").strip()
|
||||
|
||||
feedback = {
|
||||
"ts": _smart_now_iso(),
|
||||
"score": score,
|
||||
"selected_stack": selected_stack,
|
||||
"notes": notes[:1000] if notes else None,
|
||||
}
|
||||
run["feedback"] = feedback
|
||||
strategy = str((run.get("policy") or {}).get("strategy") or "local_only")
|
||||
if score is not None:
|
||||
score = max(1.0, min(5.0, score))
|
||||
_smart_update_strategy_score(strategy, score)
|
||||
if selected_stack in {"local", "kling"}:
|
||||
run["selected_stack"] = selected_stack
|
||||
_smart_append_audit(run, "feedback.received", {"score": score, "selected_stack": selected_stack})
|
||||
_smart_persist_runs()
|
||||
return {
|
||||
"ok": True,
|
||||
"run_id": run_id,
|
||||
"feedback": feedback,
|
||||
"policy": _aurora_smart_policy,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/aurora/process-smart/policy/stats")
|
||||
async def api_aurora_process_smart_policy_stats() -> Dict[str, Any]:
|
||||
_smart_load_policy_from_disk()
|
||||
return _aurora_smart_policy
|
||||
|
||||
|
||||
@app.post("/api/aurora/analyze")
|
||||
async def api_aurora_analyze(file: UploadFile = File(...)) -> Dict[str, Any]:
|
||||
await file.seek(0)
|
||||
|
||||
@@ -836,6 +836,35 @@
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div style="margin-top:10px; border:1px solid rgba(255,255,255,0.08); border-radius:8px; padding:8px; background:var(--bg2);">
|
||||
<div class="aurora-note" style="margin-top:0;">Smart Orchestrator (Dual Stack)</div>
|
||||
<label class="aurora-checkline" style="margin-top:5px;">
|
||||
<input type="checkbox" id="auroraSmartEnabled" checked>
|
||||
Auto (Aurora + Kling when needed)
|
||||
</label>
|
||||
<div class="aurora-export-grid" style="margin-top:8px;">
|
||||
<label>Strategy
|
||||
<select id="auroraSmartStrategy">
|
||||
<option value="auto" selected>Auto</option>
|
||||
<option value="local_only">Local only</option>
|
||||
<option value="local_then_kling">Local then Kling</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Budget
|
||||
<select id="auroraSmartBudget">
|
||||
<option value="low">Low</option>
|
||||
<option value="normal" selected>Normal</option>
|
||||
<option value="high">High</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<label class="aurora-checkline" style="margin-top:6px;">
|
||||
<input type="checkbox" id="auroraSmartPreferQuality" checked>
|
||||
Prefer quality over speed
|
||||
</label>
|
||||
<div id="auroraSmartHint" class="aurora-note">policy: standby</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:8px; margin-top:12px;">
|
||||
<button id="auroraAnalyzeBtn" class="btn btn-ghost" onclick="auroraAnalyze()" disabled>🔍 Аналіз</button>
|
||||
<button id="auroraAudioProcessBtn" class="btn btn-ghost" style="display:none;" onclick="auroraStartAudio()">🎧 Audio process</button>
|
||||
@@ -847,6 +876,8 @@
|
||||
<div class="aurora-card">
|
||||
<div class="aurora-title">Job Status</div>
|
||||
<div class="aurora-kv"><span class="k">Job ID</span><span class="v" id="auroraJobId">—</span></div>
|
||||
<div class="aurora-kv"><span class="k">Smart Run</span><span class="v" id="auroraSmartRunId">—</span></div>
|
||||
<div class="aurora-kv"><span class="k">Smart Policy</span><span class="v" id="auroraSmartPolicy">—</span></div>
|
||||
<div class="aurora-kv"><span class="k">Статус</span><span class="v" id="auroraJobStatus">idle</span></div>
|
||||
<div class="aurora-kv"><span class="k">Етап</span><span class="v" id="auroraJobStage">—</span></div>
|
||||
<div class="aurora-kv"><span class="k">Черга</span><span class="v" id="auroraQueuePos">—</span></div>
|
||||
@@ -2018,6 +2049,8 @@ let currentAudio = null;
|
||||
let auroraMode = 'tactical';
|
||||
let auroraSelectedFile = null;
|
||||
let auroraJobId = null;
|
||||
let auroraSmartRunId = null;
|
||||
let auroraSmartStatusCache = null;
|
||||
let auroraPollTimer = null;
|
||||
let auroraResultCache = null;
|
||||
let auroraAnalysisCache = null;
|
||||
@@ -2035,8 +2068,10 @@ let auroraChatBusy = false;
|
||||
let auroraFolderPath = null;
|
||||
const AURORA_MAX_TRANSIENT_ERRORS = 12;
|
||||
const AURORA_ACTIVE_JOB_KEY = 'aurora_active_job_id';
|
||||
const AURORA_SMART_RUN_KEY = 'aurora_smart_run_id';
|
||||
const AURORA_TIMING_CACHE_PREFIX = 'aurora_timing_cache_v1:';
|
||||
try { auroraJobId = localStorage.getItem(AURORA_ACTIVE_JOB_KEY) || null; } catch (_) {}
|
||||
try { auroraSmartRunId = localStorage.getItem(AURORA_SMART_RUN_KEY) || null; } catch (_) {}
|
||||
let mediaTabBootstrapped = false;
|
||||
let aistalkTabBootstrapped = false;
|
||||
let aistalkRunId = null;
|
||||
@@ -2214,6 +2249,38 @@ function auroraPersistActiveJob() {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function auroraPersistSmartRun() {
|
||||
try {
|
||||
if (auroraSmartRunId) localStorage.setItem(AURORA_SMART_RUN_KEY, auroraSmartRunId);
|
||||
else localStorage.removeItem(AURORA_SMART_RUN_KEY);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function auroraSetSmartRunId(runId) {
|
||||
const normalized = String(runId || '').trim();
|
||||
auroraSmartRunId = normalized || null;
|
||||
const el = document.getElementById('auroraSmartRunId');
|
||||
if (el) el.textContent = auroraSmartRunId || '—';
|
||||
auroraPersistSmartRun();
|
||||
}
|
||||
|
||||
function auroraSetSmartPolicyText(text) {
|
||||
const el = document.getElementById('auroraSmartPolicy');
|
||||
const hint = document.getElementById('auroraSmartHint');
|
||||
const msg = String(text || '').trim() || '—';
|
||||
if (el) el.textContent = msg;
|
||||
if (hint) hint.textContent = `policy: ${msg}`;
|
||||
}
|
||||
|
||||
function auroraSmartConfig() {
|
||||
return {
|
||||
enabled: Boolean(document.getElementById('auroraSmartEnabled')?.checked),
|
||||
strategy: document.getElementById('auroraSmartStrategy')?.value || 'auto',
|
||||
budget_tier: document.getElementById('auroraSmartBudget')?.value || 'normal',
|
||||
prefer_quality: Boolean(document.getElementById('auroraSmartPreferQuality')?.checked),
|
||||
};
|
||||
}
|
||||
|
||||
function auroraTimingCacheKey(jobId) {
|
||||
const id = String(jobId || '').trim();
|
||||
return id ? `${AURORA_TIMING_CACHE_PREFIX}${id}` : '';
|
||||
@@ -2301,6 +2368,9 @@ function auroraSetSelectedFile(file) {
|
||||
auroraSuggestedPriority = 'balanced';
|
||||
auroraSuggestedExport = {};
|
||||
auroraPresetMode = 'balanced';
|
||||
auroraSetSmartRunId(null);
|
||||
auroraSmartStatusCache = null;
|
||||
auroraSetSmartPolicyText('standby');
|
||||
auroraResetAnalysisControls();
|
||||
auroraUpdateQueuePosition(null);
|
||||
auroraUpdateStorage(null);
|
||||
@@ -2474,6 +2544,9 @@ function auroraSelectJob(jobId) {
|
||||
const id = String(jobId || '').trim();
|
||||
if (!id) return;
|
||||
auroraSetActiveJobId(id);
|
||||
auroraSetSmartRunId(null);
|
||||
auroraSmartStatusCache = null;
|
||||
auroraSetSmartPolicyText('manual open');
|
||||
auroraStatusCache = null;
|
||||
auroraResultCache = null;
|
||||
auroraPollErrorCount = 0;
|
||||
@@ -2491,6 +2564,9 @@ function auroraSelectJob(jobId) {
|
||||
function auroraClearActiveJob() {
|
||||
auroraStopPolling();
|
||||
auroraSetActiveJobId(null);
|
||||
auroraSetSmartRunId(null);
|
||||
auroraSmartStatusCache = null;
|
||||
auroraSetSmartPolicyText('—');
|
||||
auroraStatusCache = null;
|
||||
auroraResultCache = null;
|
||||
auroraLastProgress = 0;
|
||||
@@ -3165,6 +3241,9 @@ async function auroraReprocess(options) {
|
||||
}
|
||||
const data = await r.json();
|
||||
auroraSetActiveJobId(data.job_id);
|
||||
auroraSetSmartRunId(null);
|
||||
auroraSmartStatusCache = null;
|
||||
auroraSetSmartPolicyText('audio local');
|
||||
auroraStatusCache = null;
|
||||
auroraResultCache = null;
|
||||
auroraPollErrorCount = 0;
|
||||
@@ -3301,6 +3380,9 @@ async function auroraStartAudio() {
|
||||
}
|
||||
const data = await r.json();
|
||||
auroraSetActiveJobId(data.job_id);
|
||||
auroraSetSmartRunId(null);
|
||||
auroraSmartStatusCache = null;
|
||||
auroraSetSmartPolicyText('reprocess local');
|
||||
auroraStatusCache = null;
|
||||
auroraResultCache = null;
|
||||
auroraPollErrorCount = 0;
|
||||
@@ -3331,6 +3413,29 @@ function auroraStopPolling() {
|
||||
auroraPollInFlight = false;
|
||||
}
|
||||
|
||||
async function auroraPollSmartStatus({ quiet = true } = {}) {
|
||||
if (!auroraSmartRunId) return null;
|
||||
try {
|
||||
const r = await fetch(`${API}/api/aurora/process-smart/${encodeURIComponent(auroraSmartRunId)}`);
|
||||
if (r.status === 404) {
|
||||
auroraSetSmartRunId(null);
|
||||
auroraSmartStatusCache = null;
|
||||
auroraSetSmartPolicyText('—');
|
||||
return null;
|
||||
}
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
const smart = await r.json();
|
||||
auroraSmartStatusCache = smart;
|
||||
const strategy = smart?.policy?.strategy || 'auto';
|
||||
const phase = smart?.phase || smart?.status || '—';
|
||||
auroraSetSmartPolicyText(`${strategy} · ${phase}`);
|
||||
return smart;
|
||||
} catch (e) {
|
||||
if (!quiet) console.warn('aurora smart status error:', e);
|
||||
return auroraSmartStatusCache;
|
||||
}
|
||||
}
|
||||
|
||||
async function auroraPollStatus() {
|
||||
if (!auroraJobId || auroraPollInFlight) return;
|
||||
auroraPollInFlight = true;
|
||||
@@ -3346,6 +3451,7 @@ async function auroraPollStatus() {
|
||||
}
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
const st = await r.json();
|
||||
const smart = await auroraPollSmartStatus({ quiet: true });
|
||||
auroraStatusCache = st;
|
||||
auroraPollErrorCount = 0;
|
||||
const cachedTiming = auroraGetPersistedTiming(auroraJobId) || {};
|
||||
@@ -3369,10 +3475,24 @@ async function auroraPollStatus() {
|
||||
const reBtn = document.getElementById('auroraReprocessBtn');
|
||||
if (reBtn) reBtn.disabled = !(st.status === 'completed' || st.status === 'failed' || st.status === 'cancelled');
|
||||
if (st.status === 'completed') {
|
||||
const smartActive = smart && !['completed', 'failed', 'cancelled'].includes(String(smart.status || '').toLowerCase());
|
||||
if (smartActive) {
|
||||
if (!auroraResultCache) {
|
||||
await auroraLoadResult(auroraJobId);
|
||||
}
|
||||
const kStat = smart?.kling?.status ? ` · Kling ${smart.kling.status}` : '';
|
||||
auroraSetProgress(99, 'processing', `smart orchestration (${smart.phase || 'running'}${kStat})`);
|
||||
const cancelBtn = document.getElementById('auroraCancelBtn');
|
||||
if (cancelBtn) cancelBtn.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
auroraStopPolling();
|
||||
await auroraLoadResult(auroraJobId);
|
||||
const cancelBtn = document.getElementById('auroraCancelBtn');
|
||||
if (cancelBtn) cancelBtn.style.display = 'none';
|
||||
if (smart && smart.selected_stack) {
|
||||
auroraChatAdd('assistant', `Smart run завершено. Selected stack: ${smart.selected_stack}.`);
|
||||
}
|
||||
await auroraRefreshJobs();
|
||||
} else if (st.status === 'failed' || st.status === 'cancelled') {
|
||||
auroraStopPolling();
|
||||
@@ -3406,20 +3526,29 @@ async function auroraStart() {
|
||||
return;
|
||||
}
|
||||
const analysisControls = auroraCollectAnalysisControls();
|
||||
const smartCfg = auroraSmartConfig();
|
||||
const fd = new FormData();
|
||||
fd.append('file', auroraSelectedFile);
|
||||
fd.append('mode', auroraMode);
|
||||
fd.append('priority', analysisControls.priority || auroraSuggestedPriority || 'balanced');
|
||||
const uiExport = auroraCollectExportOptions();
|
||||
const analysisExport = auroraBuildAnalysisExportHints(analysisControls);
|
||||
fd.append('export_options', JSON.stringify({ ...auroraSuggestedExport, ...uiExport, ...analysisExport }));
|
||||
const mergedExport = { ...auroraSuggestedExport, ...uiExport, ...analysisExport };
|
||||
fd.append('export_options', JSON.stringify(mergedExport));
|
||||
if (smartCfg.enabled) {
|
||||
fd.append('strategy', smartCfg.strategy || 'auto');
|
||||
fd.append('prefer_quality', smartCfg.prefer_quality ? 'true' : 'false');
|
||||
fd.append('budget_tier', smartCfg.budget_tier || 'normal');
|
||||
fd.append('learning_enabled', 'true');
|
||||
}
|
||||
_auroraLogLines = [];
|
||||
const startBtn = document.getElementById('auroraStartBtn');
|
||||
const quickStartBtn = document.getElementById('auroraStartFromAnalysisBtn');
|
||||
if (startBtn) startBtn.disabled = true;
|
||||
if (quickStartBtn) quickStartBtn.disabled = true;
|
||||
try {
|
||||
const r = await fetch(`${API}/api/aurora/upload`, {
|
||||
const endpoint = smartCfg.enabled ? '/api/aurora/process-smart' : '/api/aurora/upload';
|
||||
const r = await fetch(`${API}${endpoint}`, {
|
||||
method: 'POST',
|
||||
body: fd,
|
||||
});
|
||||
@@ -3428,7 +3557,23 @@ async function auroraStart() {
|
||||
throw new Error(body || `HTTP ${r.status}`);
|
||||
}
|
||||
const data = await r.json();
|
||||
auroraSetActiveJobId(data.job_id);
|
||||
const localJobId = data.local_job_id || data.job_id;
|
||||
if (!localJobId) {
|
||||
throw new Error('job_id missing in response');
|
||||
}
|
||||
auroraSetActiveJobId(localJobId);
|
||||
if (smartCfg.enabled) {
|
||||
auroraSetSmartRunId(data.smart_run_id || null);
|
||||
const policyStrategy = data?.policy?.strategy || smartCfg.strategy || 'auto';
|
||||
const policyScore = Number(data?.policy?.score);
|
||||
const scoreTxt = Number.isFinite(policyScore) ? ` (${policyScore.toFixed(2)})` : '';
|
||||
auroraSetSmartPolicyText(`${policyStrategy}${scoreTxt}`);
|
||||
auroraChatAdd('assistant', `Smart run ${data.smart_run_id || '—'}: strategy=${policyStrategy}`);
|
||||
} else {
|
||||
auroraSetSmartRunId(null);
|
||||
auroraSmartStatusCache = null;
|
||||
auroraSetSmartPolicyText('manual local');
|
||||
}
|
||||
auroraStatusCache = null;
|
||||
auroraResultCache = null;
|
||||
auroraPollErrorCount = 0;
|
||||
@@ -3441,7 +3586,7 @@ async function auroraStart() {
|
||||
document.getElementById('auroraResultCard').style.display = 'none';
|
||||
const reBtn = document.getElementById('auroraReprocessBtn');
|
||||
if (reBtn) reBtn.disabled = true;
|
||||
auroraSetProgress(1, 'processing', 'dispatching');
|
||||
auroraSetProgress(1, 'processing', smartCfg.enabled ? 'dispatching smart orchestration' : 'dispatching');
|
||||
const cancelBtn = document.getElementById('auroraCancelBtn');
|
||||
if (cancelBtn) cancelBtn.style.display = 'inline-block';
|
||||
auroraStopPolling();
|
||||
@@ -3449,7 +3594,7 @@ async function auroraStart() {
|
||||
await auroraPollStatus();
|
||||
await auroraRefreshJobs();
|
||||
} catch (e) {
|
||||
alert(`Aurora upload error: ${e.message || e}`);
|
||||
alert(`Aurora start error: ${e.message || e}`);
|
||||
auroraSetProgress(0, 'failed', 'upload_error');
|
||||
} finally {
|
||||
if (startBtn) startBtn.disabled = !auroraSelectedFile;
|
||||
@@ -3805,6 +3950,10 @@ function auroraInitTab() {
|
||||
auroraBindDropzone();
|
||||
auroraRefreshHealth();
|
||||
auroraUpdatePriorityLabel();
|
||||
auroraSetSmartRunId(auroraSmartRunId);
|
||||
if (!auroraSmartRunId) {
|
||||
auroraSetSmartPolicyText('standby');
|
||||
}
|
||||
const quickStartBtn = document.getElementById('auroraStartFromAnalysisBtn');
|
||||
if (quickStartBtn) quickStartBtn.disabled = !auroraSelectedFile;
|
||||
if (!auroraTabBootstrapped) {
|
||||
@@ -3827,6 +3976,15 @@ function auroraInitTab() {
|
||||
}
|
||||
auroraUpdateQueuePosition((auroraStatusCache || {}).queue_position || null);
|
||||
auroraUpdateStorage((auroraStatusCache || {}).storage || null);
|
||||
if (auroraSmartRunId) {
|
||||
auroraPollSmartStatus({ quiet: true }).then((smart) => {
|
||||
if (!smart || typeof smart !== 'object') return;
|
||||
const localJob = smart?.local?.job_id || null;
|
||||
if (!auroraJobId && localJob) {
|
||||
auroraSetActiveJobId(localJob);
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
auroraRefreshJobs();
|
||||
if (auroraJobId && !auroraPollTimer) {
|
||||
auroraSetProgress(Math.max(1, auroraLastProgress || 0), 'processing', 'restoring previous job...');
|
||||
|
||||
Reference in New Issue
Block a user