11from __future__ import annotations
22
3- import os
4- from dataclasses import asdict
3+ import platform
4+ import shutil
5+ import sys
56from datetime import datetime , timezone
7+ from importlib .util import find_spec
68
79from fastapi import Depends , FastAPI , Header , HTTPException , Query
8- from fastapi .responses import Response
10+ from fastapi .responses import HTMLResponse , Response
911from pydantic import BaseModel
1012
13+ from adapter_gui import render_setup_page
14+ from core_config import Config
15+ from service_media import runtime_dependency_report
1116from service_pipeline import Pipeline
1217
1318
@@ -22,17 +27,13 @@ class ProcessRequest(BaseModel):
2227 title : str | None = None
2328
2429
25- def create_app (pipeline : Pipeline , api_key : str | None = None ) -> FastAPI :
30+ def create_app (pipeline : Pipeline , config : Config ) -> FastAPI :
2631 app = FastAPI (title = "Video RSS Aggregator" , version = "0.1.0" )
2732
28- rss_title = os .environ .get ("VRA_RSS_TITLE" , "Video RSS Aggregator" )
29- rss_link = os .environ .get ("VRA_RSS_LINK" , "http://localhost:8080/rss" )
30- rss_desc = os .environ .get ("VRA_RSS_DESCRIPTION" , "Video summaries" )
31-
3233 def _check_auth (
3334 authorization : str | None = Header (None ), x_api_key : str | None = Header (None )
3435 ):
35- if api_key is None :
36+ if config . api_key is None :
3637 return
3738 token = None
3839 if authorization :
@@ -41,33 +42,124 @@ def _check_auth(
4142 token = parts [1 ]
4243 if token is None :
4344 token = x_api_key
44- if token != api_key :
45+ if token != config . api_key :
4546 raise HTTPException (status_code = 401 , detail = "unauthorized" )
4647
4748 @app .get ("/health" )
4849 async def health ():
4950 return {"status" : "ok" , "timestamp" : datetime .now (timezone .utc ).isoformat ()}
5051
52+ @app .get ("/" , response_class = HTMLResponse )
53+ async def setup_home ():
54+ return render_setup_page (config )
55+
56+ @app .get ("/setup/config" )
57+ async def setup_config ():
58+ return {
59+ "bind_address" : f"{ config .bind_host } :{ config .bind_port } " ,
60+ "storage_dir" : config .storage_dir ,
61+ "database_path" : config .database_path ,
62+ "ollama_base_url" : config .ollama_base_url ,
63+ "model_priority" : list (config .model_priority ),
64+ "vram_budget_mb" : config .vram_budget_mb ,
65+ "model_selection_reserve_mb" : config .model_selection_reserve_mb ,
66+ "max_frames" : config .max_frames ,
67+ "frame_scene_detection" : config .frame_scene_detection ,
68+ "frame_scene_threshold" : config .frame_scene_threshold ,
69+ "frame_scene_min_frames" : config .frame_scene_min_frames ,
70+ "api_key_required" : config .api_key is not None ,
71+ "quick_commands" : {
72+ "bootstrap" : "python -m vra bootstrap" ,
73+ "status" : "python -m vra status" ,
74+ "serve" : "python -m vra serve --bind 127.0.0.1:8080" ,
75+ },
76+ }
77+
78+ @app .get ("/setup/diagnostics" )
79+ async def setup_diagnostics ():
80+ media_tools = runtime_dependency_report ()
81+ yt_dlp_cmd = shutil .which ("yt-dlp" )
82+ ytdlp = {
83+ "command" : yt_dlp_cmd ,
84+ "module_available" : find_spec ("yt_dlp" ) is not None ,
85+ }
86+ ytdlp ["available" ] = bool (ytdlp ["command" ] or ytdlp ["module_available" ])
87+
88+ ollama : dict [str , object ] = {
89+ "base_url" : config .ollama_base_url ,
90+ "reachable" : False ,
91+ "version" : None ,
92+ "models_found" : 0 ,
93+ "error" : None ,
94+ }
95+ try :
96+ runtime = await pipeline .runtime_status ()
97+ ollama ["reachable" ] = True
98+ ollama ["version" ] = runtime .get ("ollama_version" )
99+ local_models = runtime .get ("local_models" , {})
100+ ollama ["models_found" ] = len (local_models )
101+ except Exception as exc :
102+ ollama ["error" ] = str (exc )
103+
104+ ffmpeg_ok = bool (media_tools ["ffmpeg" ].get ("available" ))
105+ ffprobe_ok = bool (media_tools ["ffprobe" ].get ("available" ))
106+ ytdlp_ok = bool (ytdlp ["available" ])
107+ ollama_ok = bool (ollama ["reachable" ])
108+
109+ return {
110+ "platform" : {
111+ "system" : platform .system (),
112+ "release" : platform .release (),
113+ "python_version" : sys .version .split ()[0 ],
114+ "python_executable" : sys .executable ,
115+ },
116+ "dependencies" : {
117+ "ffmpeg" : media_tools ["ffmpeg" ],
118+ "ffprobe" : media_tools ["ffprobe" ],
119+ "yt_dlp" : ytdlp ,
120+ "ollama" : ollama ,
121+ },
122+ "ready" : ffmpeg_ok and ffprobe_ok and ytdlp_ok and ollama_ok ,
123+ }
124+
125+ @app .post ("/setup/bootstrap" )
126+ async def setup_bootstrap (_ = Depends (_check_auth )):
127+ return await pipeline .bootstrap_models ()
128+
51129 @app .post ("/ingest" )
52130 async def ingest (req : IngestRequest , _ = Depends (_check_auth )):
53131 report = await pipeline .ingest_feed (req .feed_url , req .process , req .max_items )
54- return asdict (report )
132+ return {
133+ "feed_title" : report .feed_title ,
134+ "item_count" : report .item_count ,
135+ "processed_count" : report .processed_count ,
136+ }
55137
56138 @app .post ("/process" )
57139 async def process (req : ProcessRequest , _ = Depends (_check_auth )):
58140 report = await pipeline .process_source (req .source_url , req .title )
59141 return {
60142 "source_url" : report .source_url ,
61143 "title" : report .title ,
62- "transcription" : asdict (report .transcription )
63- if report .transcription
64- else None ,
65- "summary" : asdict (report .summary ) if report .summary else None ,
144+ "transcript_chars" : report .transcript_chars ,
145+ "frame_count" : report .frame_count ,
146+ "summary" : {
147+ "summary" : report .summary .summary ,
148+ "key_points" : report .summary .key_points ,
149+ "visual_highlights" : report .summary .visual_highlights ,
150+ "model_used" : report .summary .model_used ,
151+ "vram_mb" : report .summary .vram_mb ,
152+ "error" : report .summary .error ,
153+ },
66154 }
67155
68156 @app .get ("/rss" )
69157 async def rss_feed (limit : int = Query (20 , ge = 1 , le = 200 )):
70- xml = await pipeline .rss_feed (rss_title , rss_link , rss_desc , limit )
158+ xml = await pipeline .rss_feed (limit )
71159 return Response (content = xml , media_type = "application/rss+xml" )
72160
161+ @app .get ("/runtime" )
162+ async def runtime (_ = Depends (_check_auth )):
163+ return await pipeline .runtime_status ()
164+
73165 return app
0 commit comments