@@ -26,37 +26,148 @@ def setup(
2626 default = current_config .get ("provider" , "openai" )
2727 ).execute ()
2828
29- if not api_key :
30- # Check if we already have one
31- existing_key = current_config .get ("api_key" , "" )
32- key_masked = f"{ existing_key [:4 ]} ...{ existing_key [- 4 :]} " if len (existing_key ) > 8 else "********" if existing_key else ""
29+ if provider == "local" :
30+ import shutil
31+ from scrapewizard .llm .local_runtime import LocalRuntime
3332
34- api_key = inquirer .text (
35- message = f"Enter API Key (Current: { key_masked } ):" ,
36- default = existing_key ,
37- validate = lambda result : len (result ) > 0 or "API Key cannot be empty"
38- ).execute ()
39-
40- if not model :
41- default_models = {
42- "openai" : "gpt-4-turbo" ,
43- "anthropic" : "claude-3-5-sonnet-20240620" ,
44- "openrouter" : "google/gemini-pro" ,
45- "local" : "llama3"
33+ ollama_installed = shutil .which ("ollama" ) is not None
34+ if not ollama_installed :
35+ print ("⚠️ [yellow]Warning: 'ollama' executable not found on system PATH. Please ensure Ollama is installed.[/yellow]" )
36+
37+ local_base_url = current_config .get ("local_base_url" , "http://localhost:11434" )
38+ if not model :
39+ local_base_url = inquirer .text (
40+ message = "Enter Ollama Base URL:" ,
41+ default = local_base_url
42+ ).execute ()
43+
44+ runtime = LocalRuntime (base_url = local_base_url )
45+ daemon_status = runtime .check_daemon ()
46+
47+ if not daemon_status .running and not model :
48+ print ("❌ [red]Error: Ollama daemon is not running at configured URL.[/red]" )
49+ if not inquirer .confirm (message = "Ollama daemon is down. Proceed anyway?" , default = False ).execute ():
50+ log ("Setup aborted." )
51+ return
52+
53+ # Detect hardware
54+ hw = runtime .detect_hardware ()
55+ if not model :
56+ print (f"🖥️ [cyan]Hardware detected:[/cyan] { hw ['ram_gb' ]} GB RAM, GPU: { hw ['gpu_name' ]} " )
57+ print (f"📦 Suggested performance tier: [green]{ hw ['tier' ].upper ()} [/green]" )
58+
59+ recommended = runtime .recommend_model (hw ['tier' ])
60+
61+ selected_model = model
62+ if not selected_model :
63+ installed = runtime .list_models ()
64+ if installed :
65+ print ("Installed models:" )
66+ for m in installed :
67+ print (f" • { m } " )
68+ else :
69+ print ("No models found in Ollama." )
70+
71+ choices = installed .copy ()
72+ if recommended not in choices :
73+ choices .append (recommended )
74+ choices .append ("Other (enter custom name)" )
75+
76+ selected_model = inquirer .select (
77+ message = "Select Ollama model:" ,
78+ choices = choices ,
79+ default = recommended if recommended in choices else (installed [0 ] if installed else choices [0 ])
80+ ).execute ()
81+
82+ if selected_model == "Other (enter custom name)" :
83+ selected_model = inquirer .text (
84+ message = "Enter custom model name:" ,
85+ default = recommended
86+ ).execute ()
87+
88+ if selected_model not in installed :
89+ if inquirer .confirm (message = f"Model '{ selected_model } ' is not downloaded. Pull it now?" , default = True ).execute ():
90+ print (f"Downloading '{ selected_model } ' via Ollama. Please wait..." )
91+
92+ from rich .progress import Progress , SpinnerColumn , TextColumn , BarColumn , DownloadColumn
93+ with Progress (
94+ SpinnerColumn (),
95+ TextColumn ("[progress.description]{task.description}" ),
96+ BarColumn (),
97+ DownloadColumn (),
98+ ) as progress :
99+ task = progress .add_task (f"Pulling { selected_model } ..." , total = 100 )
100+
101+ def callback (data ):
102+ status = data .get ("status" , "" )
103+ total = data .get ("total" , 0 )
104+ completed = data .get ("completed" , 0 )
105+ if total > 0 :
106+ progress .update (task , completed = completed , total = total , description = f"Pulling { selected_model } : { status } " )
107+ else :
108+ progress .update (task , description = f"Pulling { selected_model } : { status } " )
109+
110+ success = runtime .pull_model (selected_model , callback )
111+ if success :
112+ print (f"✅ Model '{ selected_model } ' pulled successfully." )
113+ else :
114+ print (f"❌ Failed to pull model '{ selected_model } '." )
115+
116+ # Probe model latency
117+ if daemon_status .running and not model :
118+ print ("Probing model response latency..." )
119+ probe_res = runtime .probe (selected_model )
120+ if probe_res .success :
121+ print (f"✅ Connection successful! Probe latency: [green]{ probe_res .latency } s[/green]" )
122+ else :
123+ print (f"⚠️ Probe check failed: { probe_res .error } " )
124+
125+ offline_only = current_config .get ("offline_only" , False )
126+ if not model :
127+ offline_only = inquirer .confirm (message = "Enable offline-only mode (disable all cloud fallbacks)?" , default = False ).execute ()
128+
129+ new_config = {
130+ "provider" : "local" ,
131+ "model" : selected_model ,
132+ "local_base_url" : local_base_url ,
133+ "local_model" : selected_model ,
134+ "local_tier" : hw ['tier' ],
135+ "offline_only" : offline_only
46136 }
47- model = inquirer .text (
48- message = "Enter Model Name:" ,
49- default = current_config .get ("model" , default_models .get (provider , "" ))
50- ).execute ()
137+ ConfigManager .save_config (new_config )
138+ log ("Configuration saved successfully." )
139+
140+ else :
141+ if not api_key :
142+ # Check if we already have one
143+ existing_key = current_config .get ("api_key" , "" )
144+ key_masked = f"{ existing_key [:4 ]} ...{ existing_key [- 4 :]} " if len (existing_key ) > 8 else "********" if existing_key else ""
145+
146+ api_key = inquirer .text (
147+ message = f"Enter API Key (Current: { key_masked } ):" ,
148+ default = existing_key ,
149+ validate = lambda result : len (result ) > 0 or "API Key cannot be empty"
150+ ).execute ()
51151
52- # Save Config
53- new_config = {
54- "provider" : provider ,
55- "api_key" : api_key ,
56- "model" : model
57- }
58- ConfigManager .save_config (new_config )
59- log ("Configuration saved successfully." )
152+ if not model :
153+ default_models = {
154+ "openai" : "gpt-4-turbo" ,
155+ "anthropic" : "claude-3-5-sonnet-20240620" ,
156+ "openrouter" : "google/gemini-pro"
157+ }
158+ model = inquirer .text (
159+ message = "Enter Model Name:" ,
160+ default = current_config .get ("model" , default_models .get (provider , "" ))
161+ ).execute ()
162+
163+ # Save Config
164+ new_config = {
165+ "provider" : provider ,
166+ "api_key" : api_key ,
167+ "model" : model
168+ }
169+ ConfigManager .save_config (new_config )
170+ log ("Configuration saved successfully." )
60171
61172 # Proxy Setup
62173 if use_proxy or inquirer .confirm (message = "Configure Proxy?" , default = False ).execute ():
0 commit comments