feat: update ui for ollama host (#912)

This commit is contained in:
Yingjie He
2025-01-30 09:24:52 -08:00
committed by GitHub
parent aea45dfaf2
commit 654e5c6e63
14 changed files with 125 additions and 159 deletions
@@ -8,23 +8,23 @@ use serde_json::Value;
use std::collections::HashMap;
#[derive(Serialize)]
struct SecretResponse {
struct ConfigResponse {
error: bool,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct SecretRequest {
struct ConfigRequest {
key: String,
value: String,
is_secret: bool,
}
async fn store_secret(
async fn store_config(
State(state): State<AppState>,
headers: HeaderMap,
Json(request): Json<SecretRequest>,
) -> Result<Json<SecretResponse>, StatusCode> {
Json(request): Json<ConfigRequest>,
) -> Result<Json<ConfigResponse>, StatusCode> {
// Verify secret key
let secret_key = headers
.get("X-Secret-Key")
@@ -42,18 +42,18 @@ async fn store_secret(
config.set(&request.key, Value::String(request.value))
};
match result {
Ok(_) => Ok(Json(SecretResponse { error: false })),
Err(_) => Ok(Json(SecretResponse { error: true })),
Ok(_) => Ok(Json(ConfigResponse { error: false })),
Err(_) => Ok(Json(ConfigResponse { error: true })),
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ProviderSecretRequest {
pub struct ProviderConfigRequest {
pub providers: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SecretStatus {
pub struct ConfigStatus {
pub is_set: bool,
pub location: Option<String>,
}
@@ -64,7 +64,7 @@ pub struct ProviderResponse {
pub name: Option<String>,
pub description: Option<String>,
pub models: Option<Vec<String>>,
pub secret_status: HashMap<String, SecretStatus>,
pub config_status: HashMap<String, ConfigStatus>,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -80,30 +80,33 @@ static PROVIDER_ENV_REQUIREMENTS: Lazy<HashMap<String, ProviderConfig>> = Lazy::
serde_json::from_str(contents).expect("Failed to parse providers_and_keys.json")
});
fn check_key_status(key: &str) -> (bool, Option<String>) {
fn check_key_status(config: &Config, key: &str) -> (bool, Option<String>) {
if let Ok(_value) = std::env::var(key) {
(true, Some("env".to_string()))
} else if Config::global().get_secret::<String>(key).is_ok() {
} else if config.get::<String>(key).is_ok() {
(true, Some("yaml".to_string()))
} else if config.get_secret::<String>(key).is_ok() {
(true, Some("keyring".to_string()))
} else {
(false, None)
}
}
async fn check_provider_secrets(
Json(request): Json<ProviderSecretRequest>,
async fn check_provider_configs(
Json(request): Json<ProviderConfigRequest>,
) -> Result<Json<HashMap<String, ProviderResponse>>, StatusCode> {
let mut response = HashMap::new();
let config = Config::global();
for provider_name in request.providers {
if let Some(provider_config) = PROVIDER_ENV_REQUIREMENTS.get(&provider_name) {
let mut secret_status = HashMap::new();
let mut config_status = HashMap::new();
for key in &provider_config.required_keys {
let (key_set, key_location) = check_key_status(key);
secret_status.insert(
let (key_set, key_location) = check_key_status(config, key);
config_status.insert(
key.to_string(),
SecretStatus {
ConfigStatus {
is_set: key_set,
location: key_location,
},
@@ -117,7 +120,7 @@ async fn check_provider_secrets(
name: Some(provider_config.name.clone()),
description: Some(provider_config.description.clone()),
models: Some(provider_config.models.clone()),
secret_status,
config_status,
},
);
} else {
@@ -128,7 +131,7 @@ async fn check_provider_secrets(
name: None,
description: None,
models: None,
secret_status: HashMap::new(),
config_status: HashMap::new(),
},
);
}
@@ -138,14 +141,16 @@ async fn check_provider_secrets(
}
#[derive(Deserialize)]
struct DeleteSecretRequest {
#[serde(rename_all = "camelCase")]
struct DeleteConfigRequest {
key: String,
is_secret: bool,
}
async fn delete_secret(
async fn delete_config(
State(state): State<AppState>,
headers: HeaderMap,
Json(request): Json<DeleteSecretRequest>,
Json(request): Json<DeleteConfigRequest>,
) -> Result<StatusCode, StatusCode> {
// Verify secret key
let secret_key = headers
@@ -158,7 +163,13 @@ async fn delete_secret(
}
// Attempt to delete the key
match Config::global().delete_secret(&request.key) {
let config = Config::global();
let result = if request.is_secret {
config.delete_secret(&request.key)
} else {
config.delete(&request.key)
};
match result {
Ok(_) => Ok(StatusCode::NO_CONTENT),
Err(_) => Err(StatusCode::NOT_FOUND),
}
@@ -166,9 +177,9 @@ async fn delete_secret(
pub fn routes(state: AppState) -> Router {
Router::new()
.route("/secrets/providers", post(check_provider_secrets))
.route("/secrets/store", post(store_secret))
.route("/secrets/delete", delete(delete_secret))
.route("/configs/providers", post(check_provider_configs))
.route("/configs/store", post(store_config))
.route("/configs/delete", delete(delete_config))
.with_state(state)
}
@@ -179,12 +190,12 @@ mod tests {
#[tokio::test]
async fn test_unsupported_provider() {
// Setup
let request = ProviderSecretRequest {
let request = ProviderConfigRequest {
providers: vec!["unsupported_provider".to_string()],
};
// Execute
let result = check_provider_secrets(Json(request)).await;
let result = check_provider_configs(Json(request)).await;
// Assert
assert!(result.is_ok());
@@ -194,6 +205,6 @@ mod tests {
.get("unsupported_provider")
.expect("Provider should exist");
assert!(!provider_status.supported);
assert!(provider_status.secret_status.is_empty());
assert!(provider_status.config_status.is_empty());
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
// Export route modules
pub mod agent;
pub mod configs;
pub mod extension;
pub mod health;
pub mod reply;
pub mod secrets;
use axum::Router;
@@ -14,5 +14,5 @@ pub fn configure(state: crate::state::AppState) -> Router {
.merge(reply::routes(state.clone()))
.merge(agent::routes(state.clone()))
.merge(extension::routes(state.clone()))
.merge(secrets::routes(state))
.merge(configs::routes(state))
}
@@ -33,7 +33,7 @@
"name": "Ollama",
"description": "Lorem ipsum",
"models": ["qwen2.5"],
"required_keys": []
"required_keys": ["OLLAMA_HOST"]
},
"openrouter": {
"name": "OpenRouter",
+52 -9
View File
@@ -165,6 +165,21 @@ impl Config {
}
}
// Save current values to the config file
fn save_values(&self, values: HashMap<String, Value>) -> Result<(), ConfigError> {
// Convert to YAML for storage
let yaml_value = serde_yaml::to_string(&values)?;
// Ensure the directory exists
if let Some(parent) = self.config_path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| ConfigError::DirectoryError(e.to_string()))?;
}
std::fs::write(&self.config_path, yaml_value)?;
Ok(())
}
// Load current secrets from the keyring
fn load_secrets(&self) -> Result<HashMap<String, Value>, ConfigError> {
let entry = Entry::new(&self.keyring_service, KEYRING_USERNAME)?;
@@ -231,17 +246,27 @@ impl Config {
let mut values = self.load_values()?;
values.insert(key.to_string(), value);
// Convert to YAML for storage
let yaml_value = serde_yaml::to_string(&values)?;
self.save_values(values)
}
// Ensure the directory exists
if let Some(parent) = self.config_path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| ConfigError::DirectoryError(e.to_string()))?;
}
/// Delete a configuration value in the config file.
///
/// This will immediately write the value to the config file. The value
/// can be any type that can be serialized to JSON/YAML.
///
/// Note that this does not affect environment variables - those can only
/// be set through the system environment.
///
/// # Errors
///
/// Returns a ConfigError if:
/// - There is an error reading or writing the config file
/// - There is an error serializing the value
pub fn delete(&self, key: &str) -> Result<(), ConfigError> {
let mut values = self.load_values()?;
values.remove(key);
std::fs::write(&self.config_path, yaml_value)?;
Ok(())
self.save_values(values)
}
/// Get a secret value.
@@ -408,6 +433,24 @@ mod tests {
Ok(())
}
#[test]
fn test_value_management() -> Result<(), ConfigError> {
let temp_file = NamedTempFile::new().unwrap();
let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?;
config.set("key", Value::String("value".to_string()))?;
let value: String = config.get("key")?;
assert_eq!(value, "value");
config.delete("key")?;
let result: Result<String, ConfigError> = config.get("key");
assert!(matches!(result, Err(ConfigError::NotFound(_))));
Ok(())
}
#[test]
#[serial]
fn test_secret_management() -> Result<(), ConfigError> {