diff --git a/services/sofiia-console/app/main.py b/services/sofiia-console/app/main.py index 15f908ec..2da7718d 100644 --- a/services/sofiia-console/app/main.py +++ b/services/sofiia-console/app/main.py @@ -1392,9 +1392,11 @@ async def api_aurora_compare(job_id: str) -> Dict[str, Any]: "download_url": (result_file or {}).get("url"), } + output_media_path: Optional[Path] = None if result_file and output_dir: out_path = Path(output_dir) / result_file["name"] if out_path.exists(): + output_media_path = out_path after["file_size_mb"] = round(out_path.stat().st_size / (1024 * 1024), 2) _probe = _ffprobe_quick(out_path) if _probe: @@ -1419,6 +1421,14 @@ async def api_aurora_compare(job_id: str) -> Dict[str, Any]: "time_ms": step.get("time_ms"), }) + frame_preview = _aurora_ensure_compare_frame_preview( + job_id=job_id, + media_type=str(status.get("media_type") or ""), + input_path=Path(input_path) if input_path else None, + output_path=output_media_path, + output_dir=Path(output_dir) if output_dir else None, + ) + return { "job_id": job_id, "status": status.get("status"), @@ -1429,11 +1439,97 @@ async def api_aurora_compare(job_id: str) -> Dict[str, Any]: "after": after, "faces_detected": faces_total, "enhance_steps": enhance_steps, + "frame_preview": frame_preview, "folder_path": output_dir, "input_path": input_path, } +def _aurora_extract_frame_preview(source: Path, target: Path, *, second: float = 1.0) -> bool: + """Write a JPEG preview frame for image/video sources.""" + if not source.exists(): + return False + target.parent.mkdir(parents=True, exist_ok=True) + ext = source.suffix.lower() + if ext in {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".tif", ".tiff"}: + try: + target.write_bytes(source.read_bytes()) + return True + except Exception: + return False + + ffmpeg = [ + "ffmpeg", + "-hide_banner", + "-loglevel", + "error", + "-y", + "-ss", + f"{max(0.0, float(second)):.3f}", + "-i", + str(source), + "-frames:v", + "1", + "-q:v", + "2", + str(target), + ] + try: + run = subprocess.run(ffmpeg, capture_output=True, text=True, timeout=20) + if run.returncode == 0 and target.exists() and target.stat().st_size > 0: + return True + except Exception: + pass + + # Fallback for short videos / odd timestamps. + ffmpeg_fallback = ffmpeg[:] + ffmpeg_fallback[6] = "0.0" + try: + run = subprocess.run(ffmpeg_fallback, capture_output=True, text=True, timeout=20) + return run.returncode == 0 and target.exists() and target.stat().st_size > 0 + except Exception: + return False + + +def _aurora_ensure_compare_frame_preview( + *, + job_id: str, + media_type: str, + input_path: Optional[Path], + output_path: Optional[Path], + output_dir: Optional[Path], +) -> Optional[Dict[str, Any]]: + if not output_dir or not output_dir.exists(): + return None + if not input_path or not input_path.exists(): + return None + if not output_path or not output_path.exists(): + return None + + before_name = "_compare_before.jpg" + after_name = "_compare_after.jpg" + before_path = output_dir / before_name + after_path = output_dir / after_name + ts = 1.0 if media_type == "video" else 0.0 + + if not before_path.exists() or before_path.stat().st_size == 0: + _aurora_extract_frame_preview(input_path, before_path, second=ts) + if not after_path.exists() or after_path.stat().st_size == 0: + _aurora_extract_frame_preview(output_path, after_path, second=ts) + + if not before_path.exists() or not after_path.exists(): + return None + if before_path.stat().st_size <= 0 or after_path.stat().st_size <= 0: + return None + + quoted_job = quote(job_id, safe="") + return { + "timestamp_sec": ts, + "before_url": f"/api/aurora/files/{quoted_job}/{quote(before_name, safe='')}", + "after_url": f"/api/aurora/files/{quoted_job}/{quote(after_name, safe='')}", + } + + def _ffprobe_quick(filepath: Path) -> Dict[str, Any]: """Quick ffprobe for resolution, codec, duration, fps, frame count.""" if not filepath.exists(): diff --git a/services/sofiia-console/static/index.html b/services/sofiia-console/static/index.html new file mode 100644 index 00000000..a47b6a91 --- /dev/null +++ b/services/sofiia-console/static/index.html @@ -0,0 +1,8467 @@ + + + + + + SOFIIA — Control Console + + + + + +
+
+
SOFIIA
+
Network Control Panel · DAARION
+ + + + +
+ Ключ знаходиться у файлі .env.console.local
+ або у змінній середовища SOFIIA_CONSOLE_API_KEY на сервері. +
+
+
+ +
+
+ +
CTO DAARION · AI Control Console
+
+ loading… + + Перевірка... + +
+ + + +
+ +
+
+ + + +
+
+ + + + + + Готовий + + 🌐 Remote +
+ +
+ +
+ + + + + + + + +
+
+
+
+ + +
+
+
+
Media Forensics Upload
+
+ + +
+
+
📂
+
Перетягніть файл або натисніть для вибору
+
Video: MP4/AVI/MOV · Audio: MP3/WAV/FLAC · Photo: JPG/PNG/TIFF
+
+ + +
+ Файл +
+ +
+ Сервіс Aurorachecking... +
+ +
+ ⚙ Export options +
+ + + + +
+
+ +
+ + + + +
+
+ +
+
Job Status
+
Job ID
+
Статусidle
+
Етап
+
Черга
+
Минуло
+
ETA
+
FPS (confidence)
+
Збереження
+
+ + +
+
+
+
+
+
0%
+
+ +
+
+ + + + + + + + + + + + + +
+
Recent Jobs
+
+ loading... + + + +
+
+
Завантаження історії job...
+
+
+ +
+
Aurora Operator Chat
+
+
+
+ + +
+
+
+ + +
+
+
+
AISTALK Status
+
Adapterchecking...
+
Relay
+
Supervisorchecking...
+
Graphs
+
Aurora
+
CPU / RAM
+
Parallel Team Runs
+
Parallel Chat
+
+ + + + + +
+
rule: loading...
+
+ + + 📄 Contract + 🧭 Supervisor +
+
+ +
+
AISTALK Capabilities
+ +
+ AISTALK: crypto-detective & network-security command unit (OSINT, threat analysis, red/blue/purple teaming, risk, forensic via Aurora). +
+
+ +
+
Subagents Team
+
+
loading subagents...
+
+
+ +
+
AISTALK Chat
+
+
+ + + +
+
+ + +
+ +
+ + +
+
+ +
+
Memory Timeline
+
+ + session: — +
+
+
loading timeline...
+
+
+ +
+
Run AISTALK Team (LangGraph)
+
AISTALK orchestration через Supervisor. Для пари з Aurora можна взяти поточний Aurora job як objective.
+
+ + +
+
+ + + + +
+ + +
+ +
+
+ + +
+
Run ID
+
Statusidle
+
+ +
+
Run Output
+
Очікування запуску...
+
+
+
+ + +
+
+
+
Service Status
+
Routerchecking...
+
Comfy Agentchecking...
+
ComfyUIchecking...
+
Swapperchecking...
+
Image Gen Servicechecking...
+
Image modelschecking...
+
+ + +
+
+
+
Generate
+ +
+ + + + + + +
+
+ + +
+
Готово
+
+
+
+
Recent Media Jobs
+
+ loading... + +
+
+
Завантаження...
+
+
+
+
+ + + + + +
+
Governance операції
+
+ + + +
+ + +
+
+
Інтеграції CTO
+ + + + + + + +
+
+
Завантаження...
+
+
+ + +
+
+
🖥 Network Control Panel
+ + + +
+ + + + +
Завантаження...
+
+ + +
+
+
Memory Service
+ +
+ +
+

🧠 Статус Memory Service

+
Завантаження...
+
+ +
+

🎙️ Голосові налаштування

+
+ STT (розпізнавання) + whisper-large-v3-turbo +
+
+ TTS (синтез мовлення) + edge-tts + macOS say +
+
+ Голос TTS + +
+
Polina (uk-UA)
+
Ostap (uk-UA)
+
Milena (macOS)
+
Yuri (macOS)
+
+
+
+
+ +
+

🧪 Тест TTS

+
+ + +
+
+
+ + +
+
+
🎯 CTO Strategic Dashboard
+ + + + + + +
+ +
+ +
+
📊 Стан зараз
+
+
+
+
WIP
+
+
+
+
Виконано
+
+
+
+
Ризики
+
+
+
+
Runs
+
+
+
+
Цикл (д)
+
+
+
+
Якість
+
+
+ + +
🚀 Workflow Launcher
+
+ + + + +
+ + +
+
🧾 Lessons
+
+ + +
+
+
Завантаження...
+
+
+ + +
+
🛡 Governance Gates
+
+ + +
+
+
Оберіть проєкт і натисніть Preview
+
+
+ + +
+ +
+
+ + +
+
⚡ Відкриті сигнали
+
+
Немає даних — оберіть проєкт і натисніть «⚡ Сигнали»
+
+ +
🔴 Топ ризик-задачі
+
+
Завантаження...
+
+
+ + +
+
🕸 Mini Graph
+
+ + Оберіть проєкт + +
+ + + +
+
+
+ + + + + + + + +
+
+
🌐 Portfolio View
+ + + + + + +
+ + + + +
+ +
+
📁 Проєкти
+ + + + + + + + + + + + + + + + + +
ПроєктWIPDone[RISK]RunsЦиклЯкістьUpdatedLesson
Завантаження...
+
+ + +
+ +
+
+ 📡 Portfolio Drift + +
+ +
+ Autopilot: + OFF + +
+
+
Натисніть ↻ для завантаження
+
+
+
+
⚡ Топ Сигнали
+
+
Завантаження...
+
+
+
+
+ + + +
+
+
💰 Бюджет провайдерів AI
+ + + + +
+ + +
+
+
24 год
+
$0.00000
+
витрачено
+
+
+
7 днів
+
$0.00000
+
витрачено
+
+
+
30 днів
+
$0.00000
+
витрачено
+
+
+ + +
+
Завантаження...
+
+ + +
+
+
🗂 Каталог моделей
+ + +
+
+
Натисніть "Оновити" для завантаження
+
+
+ + +
+
🧠 Тест Auto-Router (Cursor-style)
+
+ +
+ + + + +
+
+ + +
+ + + +
+ +
+ + + +