diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index ca7759f4c..54c996695 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -7,7 +7,6 @@ use tauri_plugin_opener::OpenerExt; use crate::app_config::AppType; use crate::codex_config; use crate::config::{self, get_claude_settings_path, ConfigStatus}; -use crate::settings; #[tauri::command] pub async fn get_claude_config_status() -> Result { @@ -16,18 +15,6 @@ pub async fn get_claude_config_status() -> Result { use std::str::FromStr; -fn invalid_json_format_error(error: serde_json::Error) -> String { - let lang = settings::get_settings() - .language - .unwrap_or_else(|| "zh".to_string()); - - match lang.as_str() { - "en" => format!("Invalid JSON format: {error}"), - "ja" => format!("JSON形式が無効です: {error}"), - _ => format!("无效的 JSON 格式: {error}"), - } -} - #[tauri::command] pub async fn get_config_status(app: String) -> Result { match AppType::from_str(&app).map_err(|e| e.to_string())? { @@ -163,71 +150,3 @@ pub async fn open_app_config_folder(handle: AppHandle) -> Result { Ok(true) } - -#[tauri::command] -pub async fn get_common_config_snippet( - app_type: String, - state: tauri::State<'_, crate::store::AppState>, -) -> Result, String> { - state - .db - .get_config_snippet(&app_type) - .map_err(|e| e.to_string()) -} - -#[tauri::command] -pub async fn set_common_config_snippet( - app_type: String, - snippet: String, - state: tauri::State<'_, crate::store::AppState>, -) -> Result<(), String> { - if !snippet.trim().is_empty() { - match app_type.as_str() { - "claude" | "gemini" | "omo" | "omo-slim" => { - serde_json::from_str::(&snippet) - .map_err(invalid_json_format_error)?; - } - "codex" => {} - _ => {} - } - } - - let value = if snippet.trim().is_empty() { - None - } else { - Some(snippet) - }; - - state - .db - .set_config_snippet(&app_type, value) - .map_err(|e| e.to_string())?; - - if app_type == "omo" - && state - .db - .get_current_omo_provider("opencode", "omo") - .map_err(|e| e.to_string())? - .is_some() - { - crate::services::OmoService::write_config_to_file( - state.inner(), - &crate::services::omo::STANDARD, - ) - .map_err(|e| e.to_string())?; - } - if app_type == "omo-slim" - && state - .db - .get_current_omo_provider("opencode", "omo-slim") - .map_err(|e| e.to_string())? - .is_some() - { - crate::services::OmoService::write_config_to_file( - state.inner(), - &crate::services::omo::SLIM, - ) - .map_err(|e| e.to_string())?; - } - Ok(()) -} diff --git a/src-tauri/src/database/dao/mod.rs b/src-tauri/src/database/dao/mod.rs index 53ea93dfa..81ac6a6c1 100644 --- a/src-tauri/src/database/dao/mod.rs +++ b/src-tauri/src/database/dao/mod.rs @@ -4,7 +4,6 @@ pub mod failover; pub mod mcp; -pub mod omo; pub mod prompts; pub mod providers; pub mod proxy; @@ -16,4 +15,3 @@ pub mod universal_providers; // 所有 DAO 方法都通过 Database impl 提供,无需单独导出 // 导出 FailoverQueueItem 供外部使用 pub use failover::FailoverQueueItem; -pub use omo::OmoGlobalConfig; diff --git a/src-tauri/src/database/dao/omo.rs b/src-tauri/src/database/dao/omo.rs deleted file mode 100644 index 85be1ab22..000000000 --- a/src-tauri/src/database/dao/omo.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::database::Database; -use crate::error::AppError; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct OmoGlobalConfig { - pub id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub schema_url: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub sisyphus_agent: Option, - #[serde(default)] - pub disabled_agents: Vec, - #[serde(default)] - pub disabled_mcps: Vec, - #[serde(default)] - pub disabled_hooks: Vec, - #[serde(default)] - pub disabled_skills: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub lsp: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub experimental: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub background_task: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub browser_automation_engine: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub claude_code: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub other_fields: Option, - pub updated_at: String, -} - -impl Default for OmoGlobalConfig { - fn default() -> Self { - Self { - id: "global".to_string(), - schema_url: None, - sisyphus_agent: None, - disabled_agents: vec![], - disabled_mcps: vec![], - disabled_hooks: vec![], - disabled_skills: vec![], - lsp: None, - experimental: None, - background_task: None, - browser_automation_engine: None, - claude_code: None, - other_fields: None, - updated_at: chrono::Utc::now().to_rfc3339(), - } - } -} - -impl Database { - pub fn get_omo_global_config(&self, key: &str) -> Result { - let json_str = self.get_setting(key)?; - match json_str { - Some(s) => serde_json::from_str::(&s) - .map_err(|e| AppError::Config(format!("Failed to parse {key}: {e}"))), - None => Ok(OmoGlobalConfig::default()), - } - } - - pub fn save_omo_global_config( - &self, - key: &str, - config: &OmoGlobalConfig, - ) -> Result<(), AppError> { - let json_str = serde_json::to_string(config) - .map_err(|e| AppError::Config(format!("JSON serialization failed: {e}")))?; - self.set_setting(key, &json_str)?; - Ok(()) - } -} diff --git a/src-tauri/src/database/dao/settings.rs b/src-tauri/src/database/dao/settings.rs index 8056a01cb..0b51a62e7 100644 --- a/src-tauri/src/database/dao/settings.rs +++ b/src-tauri/src/database/dao/settings.rs @@ -38,31 +38,6 @@ impl Database { Ok(()) } - // --- Config Snippets 辅助方法 --- - - /// 获取通用配置片段 - pub fn get_config_snippet(&self, app_type: &str) -> Result, AppError> { - self.get_setting(&format!("common_config_{app_type}")) - } - - /// 设置通用配置片段 - pub fn set_config_snippet( - &self, - app_type: &str, - snippet: Option, - ) -> Result<(), AppError> { - let key = format!("common_config_{app_type}"); - if let Some(value) = snippet { - self.set_setting(&key, &value) - } else { - // 如果为 None 则删除 - let conn = lock_conn!(self.conn); - conn.execute("DELETE FROM settings WHERE key = ?1", params![key]) - .map_err(|e| AppError::Database(e.to_string()))?; - Ok(()) - } - } - // --- 全局出站代理 --- /// 全局代理 URL 的存储键名 diff --git a/src-tauri/src/database/mod.rs b/src-tauri/src/database/mod.rs index 9ec7ea608..b5ac00677 100644 --- a/src-tauri/src/database/mod.rs +++ b/src-tauri/src/database/mod.rs @@ -33,7 +33,6 @@ mod tests; // DAO 类型导出供外部使用 pub use dao::FailoverQueueItem; -pub use dao::OmoGlobalConfig; use crate::config::get_app_config_dir; use crate::error::AppError; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e2cefc38c..2ed14d9f9 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -845,8 +845,6 @@ pub fn run() { commands::get_skills_migration_result, commands::get_app_config_path, commands::open_app_config_folder, - commands::get_common_config_snippet, - commands::set_common_config_snippet, commands::read_live_provider_settings, commands::patch_claude_live_settings, commands::get_settings, diff --git a/src-tauri/src/services/omo.rs b/src-tauri/src/services/omo.rs index b62ac26ff..49d5be92c 100644 --- a/src-tauri/src/services/omo.rs +++ b/src-tauri/src/services/omo.rs @@ -1,5 +1,4 @@ use crate::config::write_json_file; -use crate::database::OmoGlobalConfig; use crate::error::AppError; use crate::opencode_config::get_opencode_dir; use crate::store::AppState; @@ -13,12 +12,11 @@ pub struct OmoLocalFileData { pub agents: Option, pub categories: Option, pub other_fields: Option, - pub global: OmoGlobalConfig, pub file_path: String, pub last_modified: Option, } -type OmoProfileData = (Option, Option, Option, bool); +type OmoProfileData = (Option, Option, Option); // ── Variant descriptor ───────────────────────────────────────── @@ -28,9 +26,7 @@ pub struct OmoVariant { pub provider_prefix: &'static str, pub plugin_name: &'static str, pub plugin_prefix: &'static str, - pub known_keys: &'static [&'static str], pub has_categories: bool, - pub config_key: &'static str, pub label: &'static str, pub import_label: &'static str, } @@ -41,23 +37,7 @@ pub const STANDARD: OmoVariant = OmoVariant { provider_prefix: "omo-", plugin_name: "oh-my-opencode@latest", plugin_prefix: "oh-my-opencode", - known_keys: &[ - "$schema", - "agents", - "categories", - "sisyphus_agent", - "disabled_agents", - "disabled_mcps", - "disabled_hooks", - "disabled_skills", - "lsp", - "experimental", - "background_task", - "browser_automation_engine", - "claude_code", - ], has_categories: true, - config_key: "common_config_omo", label: "OMO", import_label: "Imported", }; @@ -68,18 +48,7 @@ pub const SLIM: OmoVariant = OmoVariant { provider_prefix: "omo-slim-", plugin_name: "oh-my-opencode-slim@latest", plugin_prefix: "oh-my-opencode-slim", - known_keys: &[ - "$schema", - "agents", - "sisyphus_agent", - "disabled_agents", - "disabled_mcps", - "disabled_hooks", - "lsp", - "experimental", - ], has_categories: false, - config_key: "common_config_omo_slim", label: "OMO Slim", import_label: "Imported Slim", }; @@ -135,47 +104,6 @@ impl OmoService { other } - fn extract_string_array(val: &Value) -> Vec { - val.as_array() - .map(|arr| { - arr.iter() - .filter_map(|v| v.as_str().map(String::from)) - .collect() - }) - .unwrap_or_default() - } - - fn merge_global_from_obj(obj: &Map, global: &mut OmoGlobalConfig) { - if let Some(v) = obj.get("$schema") { - global.schema_url = v.as_str().map(|s| s.to_string()); - } - for (key, target) in [ - ("disabled_agents", &mut global.disabled_agents), - ("disabled_mcps", &mut global.disabled_mcps), - ("disabled_hooks", &mut global.disabled_hooks), - ("disabled_skills", &mut global.disabled_skills), - ] { - if let Some(v) = obj.get(key) { - *target = Self::extract_string_array(v); - } - } - for (key, target) in [ - ("sisyphus_agent", &mut global.sisyphus_agent), - ("lsp", &mut global.lsp), - ("experimental", &mut global.experimental), - ("background_task", &mut global.background_task), - ( - "browser_automation_engine", - &mut global.browser_automation_engine, - ), - ("claude_code", &mut global.claude_code), - ] { - if let Some(v) = obj.get(key) { - *target = Some(v.clone()); - } - } - } - // ── Merge helpers ────────────────────────────────────── fn insert_opt_value(result: &mut Map, key: &str, value: &Option) { @@ -184,15 +112,6 @@ impl OmoService { } } - fn insert_string_array(result: &mut Map, key: &str, values: &[String]) { - if !values.is_empty() { - result.insert( - key.to_string(), - serde_json::to_value(values).unwrap_or(Value::Array(vec![])), - ); - } - } - fn insert_object_entries(result: &mut Map, value: Option<&Value>) { if let Some(Value::Object(map)) = value { for (k, v) in map { @@ -214,9 +133,7 @@ impl OmoService { } pub fn write_config_to_file(state: &AppState, v: &OmoVariant) -> Result<(), AppError> { - let global = state.db.get_omo_global_config(v.config_key)?; let current_omo = state.db.get_current_omo_provider("opencode", v.category)?; - let profile_data = current_omo.as_ref().map(|p| { let agents = p.settings_config.get("agents").cloned(); let categories = if v.has_categories { @@ -225,15 +142,10 @@ impl OmoService { None }; let other_fields = p.settings_config.get("otherFields").cloned(); - let use_common_config = p - .settings_config - .get("useCommonConfig") - .and_then(|val| val.as_bool()) - .unwrap_or(true); - (agents, categories, other_fields, use_common_config) + (agents, categories, other_fields) }); - let merged = Self::merge_config(v, &global, profile_data.as_ref()); + let merged = Self::build_config(v, profile_data.as_ref()); let config_path = Self::config_path(v); if let Some(parent) = config_path.parent() { @@ -241,56 +153,20 @@ impl OmoService { } write_json_file(&config_path, &merged)?; - crate::opencode_config::add_plugin(v.plugin_name)?; - log::info!("{} config written to {config_path:?}", v.label); Ok(()) } - fn merge_config( - v: &OmoVariant, - global: &OmoGlobalConfig, - profile_data: Option<&OmoProfileData>, - ) -> Value { + fn build_config(v: &OmoVariant, profile_data: Option<&OmoProfileData>) -> Value { let mut result = Map::new(); - let use_common_config = profile_data.map(|(_, _, _, uc)| *uc).unwrap_or(true); - - if use_common_config { - if let Some(url) = &global.schema_url { - result.insert("$schema".to_string(), Value::String(url.clone())); - } - - Self::insert_opt_value(&mut result, "sisyphus_agent", &global.sisyphus_agent); - Self::insert_string_array(&mut result, "disabled_agents", &global.disabled_agents); - Self::insert_string_array(&mut result, "disabled_mcps", &global.disabled_mcps); - Self::insert_string_array(&mut result, "disabled_hooks", &global.disabled_hooks); - - if v.has_categories { - Self::insert_string_array(&mut result, "disabled_skills", &global.disabled_skills); - Self::insert_opt_value(&mut result, "background_task", &global.background_task); - Self::insert_opt_value( - &mut result, - "browser_automation_engine", - &global.browser_automation_engine, - ); - Self::insert_opt_value(&mut result, "claude_code", &global.claude_code); - } - - Self::insert_opt_value(&mut result, "lsp", &global.lsp); - Self::insert_opt_value(&mut result, "experimental", &global.experimental); - - Self::insert_object_entries(&mut result, global.other_fields.as_ref()); - } - - if let Some((agents, categories, other_fields, _)) = profile_data { + if let Some((agents, categories, other_fields)) = profile_data { + Self::insert_object_entries(&mut result, other_fields.as_ref()); Self::insert_opt_value(&mut result, "agents", agents); if v.has_categories { Self::insert_opt_value(&mut result, "categories", categories); } - Self::insert_object_entries(&mut result, other_fields.as_ref()); } - Value::Object(result) } @@ -310,18 +186,12 @@ impl OmoService { settings.insert("categories".to_string(), categories.clone()); } } - settings.insert("useCommonConfig".to_string(), Value::Bool(true)); - let other = Self::extract_other_fields_with_keys(&obj, v.known_keys); + let other = Self::extract_other_fields_with_keys(&obj, &["agents", "categories"]); if !other.is_empty() { settings.insert("otherFields".to_string(), Value::Object(other)); } - let mut global = state.db.get_omo_global_config(v.config_key)?; - Self::merge_global_from_obj(&obj, &mut global); - global.updated_at = chrono::Utc::now().to_rfc3339(); - state.db.save_omo_global_config(v.config_key, &global)?; - let provider_id = format!("{}{}", v.provider_prefix, uuid::Uuid::new_v4()); let name = format!( "{} {}", @@ -384,22 +254,17 @@ impl OmoService { None }; - let other = Self::extract_other_fields_with_keys(obj, v.known_keys); + let other = Self::extract_other_fields_with_keys(obj, &["agents", "categories"]); let other_fields = if other.is_empty() { None } else { Some(Value::Object(other)) }; - let mut global = OmoGlobalConfig::default(); - Self::merge_global_from_obj(obj, &mut global); - global.other_fields = other_fields.clone(); - OmoLocalFileData { agents, categories, other_fields, - global, file_path, last_modified, } @@ -482,26 +347,24 @@ mod tests { } #[test] - fn test_merge_config_empty() { - let global = OmoGlobalConfig::default(); - let merged = OmoService::merge_config(&STANDARD, &global, None); + fn test_build_config_empty() { + let merged = OmoService::build_config(&STANDARD, None); assert!(merged.is_object()); + assert!(merged.as_object().unwrap().is_empty()); } #[test] - fn test_merge_config_with_profile() { - let global = OmoGlobalConfig { - schema_url: Some("https://example.com/schema.json".to_string()), - disabled_agents: vec!["explore".to_string()], - ..Default::default() - }; + fn test_build_config_with_profile() { let agents = Some(serde_json::json!({ "sisyphus": { "model": "claude-opus-4-5" } })); let categories = None; - let other_fields = None; - let profile_data = (agents, categories, other_fields, true); - let merged = OmoService::merge_config(&STANDARD, &global, Some(&profile_data)); + let other_fields = Some(serde_json::json!({ + "$schema": "https://example.com/schema.json", + "disabled_agents": ["explore"] + })); + let profile_data = (agents, categories, other_fields); + let merged = OmoService::build_config(&STANDARD, Some(&profile_data)); let obj = merged.as_object().unwrap(); assert_eq!(obj["$schema"], "https://example.com/schema.json"); @@ -511,28 +374,7 @@ mod tests { } #[test] - fn test_merge_config_without_common_config() { - let global = OmoGlobalConfig { - schema_url: Some("https://example.com/schema.json".to_string()), - disabled_agents: vec!["explore".to_string()], - ..Default::default() - }; - let agents = Some(serde_json::json!({ - "sisyphus": { "model": "claude-opus-4-5" } - })); - let categories = None; - let other_fields = None; - let profile_data = (agents, categories, other_fields, false); - let merged = OmoService::merge_config(&STANDARD, &global, Some(&profile_data)); - let obj = merged.as_object().unwrap(); - - assert!(!obj.contains_key("$schema")); - assert!(!obj.contains_key("disabled_agents")); - assert!(obj.contains_key("agents")); - } - - #[test] - fn test_build_local_file_data_keeps_unknown_top_level_fields_in_global() { + fn test_build_local_file_data_keeps_all_non_agent_category_fields_in_other() { let obj = serde_json::json!({ "$schema": "https://example.com/schema.json", "disabled_agents": ["oracle"], @@ -555,64 +397,53 @@ mod tests { None, ); + // All non-agents/categories fields should be in other_fields + let other = data.other_fields.unwrap(); + let other_obj = other.as_object().unwrap(); assert_eq!( - data.global.schema_url.as_deref(), - Some("https://example.com/schema.json") + other_obj.get("$schema").unwrap(), + "https://example.com/schema.json" ); - assert_eq!(data.global.disabled_agents, vec!["oracle".to_string()]); - assert_eq!( - data.other_fields, - Some(serde_json::json!({ - "custom_top_level": { "enabled": true } - })) + other_obj.get("disabled_agents").unwrap(), + &serde_json::json!(["oracle"]) ); - assert_eq!(data.global.other_fields, data.other_fields); + assert_eq!( + other_obj.get("custom_top_level").unwrap(), + &serde_json::json!({"enabled": true}) + ); + // agents and categories should NOT be in other_fields + assert!(!other_obj.contains_key("agents")); + assert!(!other_obj.contains_key("categories")); } #[test] - fn test_merge_config_ignores_non_object_other_fields() { - let global = OmoGlobalConfig { - other_fields: Some(serde_json::json!(["global_non_object"])), - ..Default::default() - }; + fn test_build_config_ignores_non_object_other_fields() { let agents = None; let categories = None; let other_fields = Some(serde_json::json!("profile_non_object")); - let profile_data = (agents, categories, other_fields, true); + let profile_data = (agents, categories, other_fields); - let merged = OmoService::merge_config(&STANDARD, &global, Some(&profile_data)); + let merged = OmoService::build_config(&STANDARD, Some(&profile_data)); let obj = merged.as_object().unwrap(); - assert!(!obj.contains_key("0")); - assert!(!obj.contains_key("global_non_object")); assert!(!obj.contains_key("profile_non_object")); } #[test] - fn test_merge_config_slim_excludes_categories_and_extra_fields() { - let global = OmoGlobalConfig { - schema_url: Some("https://slim.schema".to_string()), - disabled_agents: vec!["oracle".to_string()], - disabled_skills: vec!["playwright".to_string()], - background_task: Some(serde_json::json!({"key": "val"})), - browser_automation_engine: Some(serde_json::json!({"provider": "pw"})), - claude_code: Some(serde_json::json!({"mcp": true})), - ..Default::default() - }; + fn test_build_config_slim_excludes_categories() { let agents = Some(serde_json::json!({"orchestrator": {"model": "k2"}})); let categories = Some(serde_json::json!({"code": {"model": "gpt"}})); - let other_fields = None; - let profile_data = (agents, categories, other_fields, true); + let other_fields = Some(serde_json::json!({ + "$schema": "https://slim.schema", + "disabled_agents": ["oracle"] + })); + let profile_data = (agents, categories, other_fields); - let merged = OmoService::merge_config(&SLIM, &global, Some(&profile_data)); + let merged = OmoService::build_config(&SLIM, Some(&profile_data)); let obj = merged.as_object().unwrap(); - // Slim should NOT include these - assert!(!obj.contains_key("disabled_skills")); - assert!(!obj.contains_key("background_task")); - assert!(!obj.contains_key("browser_automation_engine")); - assert!(!obj.contains_key("claude_code")); + // Slim should NOT include categories assert!(!obj.contains_key("categories")); // Slim SHOULD include these diff --git a/src-tauri/src/services/provider/mod.rs b/src-tauri/src/services/provider/mod.rs index bde2dd925..17aa759a6 100644 --- a/src-tauri/src/services/provider/mod.rs +++ b/src-tauri/src/services/provider/mod.rs @@ -525,9 +525,8 @@ impl ProviderService { .set_omo_provider_current(app_type.as_str(), id, "omo-slim")?; crate::services::OmoService::write_config_to_file(state, &crate::services::omo::SLIM)?; // OMO ↔ OMO Slim mutually exclusive: remove Standard config - let _ = crate::services::OmoService::delete_config_file( - &crate::services::omo::STANDARD, - ); + let _ = + crate::services::OmoService::delete_config_file(&crate::services::omo::STANDARD); return Ok(SwitchResult::default()); } diff --git a/src/components/providers/forms/OmoCommonConfigEditor.tsx b/src/components/providers/forms/OmoCommonConfigEditor.tsx deleted file mode 100644 index 9f0a4a3d9..000000000 --- a/src/components/providers/forms/OmoCommonConfigEditor.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { useEffect, useState } from "react"; -import { FullScreenPanel } from "@/components/common/FullScreenPanel"; -import { Label } from "@/components/ui/label"; -import { Button } from "@/components/ui/button"; -import { Save, FolderInput, Loader2 } from "lucide-react"; -import JsonEditor from "@/components/JsonEditor"; -import { - OmoGlobalConfigFields, - type OmoGlobalConfigFieldsRef, -} from "./OmoGlobalConfigFields"; -import type { OmoGlobalConfig } from "@/types/omo"; - -interface OmoCommonConfigEditorProps { - previewValue: string; - useCommonConfig: boolean; - onCommonConfigToggle: (checked: boolean) => void; - isModalOpen: boolean; - onEditClick: () => void; - onModalClose: () => void; - onSave: () => Promise; - isSaving: boolean; - onGlobalConfigStateChange: (config: OmoGlobalConfig) => void; - globalConfigRef: React.RefObject; - fieldsKey: number; - isSlim?: boolean; -} - -export function OmoCommonConfigEditor({ - previewValue, - useCommonConfig, - onCommonConfigToggle, - isModalOpen, - onEditClick, - onModalClose, - onSave, - isSaving, - onGlobalConfigStateChange, - globalConfigRef, - fieldsKey, - isSlim = false, -}: OmoCommonConfigEditorProps) { - const { t } = useTranslation(); - const [isDarkMode, setIsDarkMode] = useState(false); - const [isImporting, setIsImporting] = useState(false); - useEffect(() => { - const syncDarkMode = () => - setIsDarkMode(document.documentElement.classList.contains("dark")); - syncDarkMode(); - const observer = new MutationObserver(syncDarkMode); - observer.observe(document.documentElement, { - attributes: true, - attributeFilter: ["class"], - }); - return () => observer.disconnect(); - }, []); - const handleImportLocal = async () => { - if (!globalConfigRef.current) return; - setIsImporting(true); - try { - await globalConfigRef.current.importFromLocal(); - } finally { - setIsImporting(false); - } - }; - return ( - <> -
-
- -
- -
-
-
- -
- {}} - darkMode={isDarkMode} - rows={14} - showValidation={false} - language="json" - /> -
- - - - - - } - > -
-

- {t("omo.commonConfigHint", { - defaultValue: - "OMO common config will be merged into all OMO configs that enable it", - })} -

- } - onStateChange={onGlobalConfigStateChange} - hideSaveButtons - isSlim={isSlim} - /> -
-
- - ); -} diff --git a/src/components/providers/forms/OmoGlobalConfigFields.tsx b/src/components/providers/forms/OmoGlobalConfigFields.tsx deleted file mode 100644 index 3e8e1ab9d..000000000 --- a/src/components/providers/forms/OmoGlobalConfigFields.tsx +++ /dev/null @@ -1,773 +0,0 @@ -import { - useState, - useEffect, - useCallback, - forwardRef, - useImperativeHandle, -} from "react"; -import { useTranslation } from "react-i18next"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Textarea } from "@/components/ui/textarea"; -import { - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { - Save, - Loader2, - X, - FolderInput, - RotateCcw, - ChevronsUpDown, -} from "lucide-react"; -import { cn } from "@/lib/utils"; -import { toast } from "sonner"; -import type { OmoGlobalConfig } from "@/types/omo"; -import { - OMO_DISABLEABLE_AGENTS, - OMO_DISABLEABLE_MCPS, - OMO_DISABLEABLE_HOOKS, - OMO_DISABLEABLE_SKILLS, - OMO_DEFAULT_SCHEMA_URL, - OMO_SISYPHUS_AGENT_PLACEHOLDER, - OMO_LSP_PLACEHOLDER, - OMO_EXPERIMENTAL_PLACEHOLDER, - OMO_BACKGROUND_TASK_PLACEHOLDER, - OMO_BROWSER_AUTOMATION_PLACEHOLDER, - OMO_CLAUDE_CODE_PLACEHOLDER, - OMO_SLIM_DISABLEABLE_AGENTS, - OMO_SLIM_DISABLEABLE_MCPS, - OMO_SLIM_DISABLEABLE_HOOKS, - OMO_SLIM_DEFAULT_SCHEMA_URL, -} from "@/types/omo"; -import { - useOmoGlobalConfig, - useSaveOmoGlobalConfig, - useReadOmoLocalFile, - useOmoSlimGlobalConfig, - useSaveOmoSlimGlobalConfig, - useReadOmoSlimLocalFile, -} from "@/lib/query/omo"; - -interface PresetOption { - readonly value: string; - readonly label: string; -} - -export interface OmoGlobalConfigFieldsRef { - buildCurrentConfig: () => OmoGlobalConfig; - buildCurrentConfigStrict: () => OmoGlobalConfig; - importFromLocal: () => Promise; -} - -interface OmoGlobalConfigFieldsProps { - onStateChange?: (config: OmoGlobalConfig) => void; - hideSaveButtons?: boolean; - isSlim?: boolean; -} - -type OmoAdvancedFieldKey = - | "lspStr" - | "experimentalStr" - | "backgroundTaskStr" - | "browserStr" - | "claudeCodeStr"; - -const OMO_ADVANCED_JSON_FIELDS: ReadonlyArray<{ - key: OmoAdvancedFieldKey; - labelKey: string; - defaultLabel: string; - placeholder: string; - minHeight: string; -}> = [ - { - key: "lspStr", - labelKey: "omo.advancedLsp", - defaultLabel: "LSP Config", - placeholder: OMO_LSP_PLACEHOLDER, - minHeight: "200px", - }, - { - key: "experimentalStr", - labelKey: "omo.advancedExperimental", - defaultLabel: "Experimental Features", - placeholder: OMO_EXPERIMENTAL_PLACEHOLDER, - minHeight: "120px", - }, - { - key: "backgroundTaskStr", - labelKey: "omo.advancedBackgroundTask", - defaultLabel: "Background Tasks", - placeholder: OMO_BACKGROUND_TASK_PLACEHOLDER, - minHeight: "250px", - }, - { - key: "browserStr", - labelKey: "omo.advancedBrowserAutomation", - defaultLabel: "Browser Automation", - placeholder: OMO_BROWSER_AUTOMATION_PLACEHOLDER, - minHeight: "80px", - }, - { - key: "claudeCodeStr", - labelKey: "omo.advancedClaudeCode", - defaultLabel: "Claude Code", - placeholder: OMO_CLAUDE_CODE_PLACEHOLDER, - minHeight: "180px", - }, -]; - -const OMO_SLIM_ADVANCED_KEYS: ReadonlySet = new Set([ - "lspStr", - "experimentalStr", -]); - -function TagListEditor({ - label, - values, - onChange, - placeholder, - presets, -}: { - label: string; - values: string[]; - onChange: (values: string[]) => void; - placeholder?: string; - presets?: readonly PresetOption[]; -}) { - const { t } = useTranslation(); - const [open, setOpen] = useState(false); - const [search, setSearch] = useState(""); - - const toggleValue = (v: string) => { - if (values.includes(v)) { - onChange(values.filter((x) => x !== v)); - } else { - onChange([...values, v]); - } - }; - const customValue = search.trim(); - const canAddCustom = customValue.length > 0 && !values.includes(customValue); - const triggerText = - values.length === 0 - ? placeholder || t("omo.selectPlaceholder", { defaultValue: "Select..." }) - : values.length === 1 - ? values[0] - : `${values[0]} +${values.length - 1}`; - - const availablePresets = presets?.filter( - (p) => - !search.trim() || - p.label.toLowerCase().includes(search.toLowerCase()) || - p.value.toLowerCase().includes(search.toLowerCase()), - ); - - return ( -
-
- - {values.length > 0 && ( - - )} -
- {values.length > 0 && ( -
- {values.map((v, i) => ( - - {v} - - - ))} -
- )} - - - - - -
- setSearch(e.target.value)} - onKeyDown={(e) => { - e.stopPropagation(); - if (e.key === "Enter" && canAddCustom) { - e.preventDefault(); - onChange([...values, customValue]); - setSearch(""); - } - }} - placeholder={ - placeholder || - t("omo.searchOrType", { - defaultValue: "Search or type custom value...", - }) - } - className="h-7 text-sm" - autoFocus - /> -
- {canAddCustom && ( - - )} -
- {availablePresets && availablePresets.length > 0 ? ( - availablePresets.map((p) => { - const checked = values.includes(p.value); - return ( - e.preventDefault()} - onCheckedChange={() => toggleValue(p.value)} - className="text-sm" - > - {p.label} - - ); - }) - ) : ( -
- {t("omo.noMatches", { defaultValue: "No matches" })} -
- )} -
-
-
-
- ); -} - -function JsonTextareaField({ - label, - value, - onChange, - placeholder, - minHeight, -}: { - label: string; - value: string; - onChange: (value: string) => void; - placeholder?: string; - minHeight?: string; -}) { - return ( -
- -