diff --git a/Cargo.toml b/Cargo.toml index 502df438..1e976e99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ ] [workspace.package] -version = "0.3.10" +version = "0.3.11" edition = "2021" license = "Apache-2.0 OR MIT" repository = "https://github.com/RightNow-AI/openfang" diff --git a/crates/openfang-api/static/index_body.html b/crates/openfang-api/static/index_body.html index 76d3e2d1..079856c3 100644 --- a/crates/openfang-api/static/index_body.html +++ b/crates/openfang-api/static/index_body.html @@ -3067,7 +3067,7 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path"]
- + diff --git a/crates/openfang-api/static/js/pages/settings.js b/crates/openfang-api/static/js/pages/settings.js index 4a5d2dff..8b14788e 100644 --- a/crates/openfang-api/static/js/pages/settings.js +++ b/crates/openfang-api/static/js/pages/settings.js @@ -258,6 +258,17 @@ function settingsPage() { } }, + async deleteCustomModel(modelId) { + if (!confirm('Delete custom model "' + modelId + '"?')) return; + try { + await OpenFangAPI.del('/api/models/custom/' + encodeURIComponent(modelId)); + OpenFangToast.success('Model deleted'); + await this.loadModels(); + } catch(e) { + OpenFangToast.error('Failed to delete: ' + (e.message || 'Unknown error')); + } + }, + async loadConfigSchema() { try { var results = await Promise.all([ diff --git a/crates/openfang-cli/src/main.rs b/crates/openfang-cli/src/main.rs index 21a858d3..90599daf 100644 --- a/crates/openfang-cli/src/main.rs +++ b/crates/openfang-cli/src/main.rs @@ -1306,8 +1306,14 @@ fn detect_best_provider() -> (&'static str, &'static str, &'static str) { ui::success("Detected Gemini (GOOGLE_API_KEY)"); return ("gemini", "GOOGLE_API_KEY", "gemini-2.5-flash"); } + // Check if Ollama is running locally (no API key needed) + if check_ollama_available() { + ui::success("Detected Ollama running locally (no API key needed)"); + return ("ollama", "OLLAMA_API_KEY", "llama3.2"); + } ui::hint("No LLM provider API keys found"); ui::hint("Groq offers a free tier: https://console.groq.com"); + ui::hint("Or install Ollama for local models: https://ollama.com"); ("groq", "GROQ_API_KEY", "llama-3.3-70b-versatile") } @@ -1333,6 +1339,15 @@ fn provider_list() -> Vec<(&'static str, &'static str, &'static str, &'static st ] } +/// Quick probe to check if Ollama is running on localhost. +fn check_ollama_available() -> bool { + std::net::TcpStream::connect_timeout( + &std::net::SocketAddr::from(([127, 0, 0, 1], 11434)), + std::time::Duration::from_millis(500), + ) + .is_ok() +} + /// Write config.toml if it doesn't already exist. fn write_config_if_missing( openfang_dir: &std::path::Path, diff --git a/crates/openfang-kernel/src/kernel.rs b/crates/openfang-kernel/src/kernel.rs index 4d3c282b..405ef7dc 100644 --- a/crates/openfang-kernel/src/kernel.rs +++ b/crates/openfang-kernel/src/kernel.rs @@ -50,7 +50,8 @@ impl LlmDriver for StubDriver { async fn complete(&self, _request: CompletionRequest) -> Result { Err(LlmError::MissingApiKey( "No LLM provider configured. Set an API key (e.g. GROQ_API_KEY) and restart, \ - or configure a provider via the dashboard." + configure a provider via the dashboard, \ + or use Ollama for local models (no API key needed)." .to_string(), )) }
ModelProviderTierContextInput CostOutput CostStatus
ModelProviderTierContextInput CostOutput CostStatus