@@ -369,6 +369,134 @@ fn save_provider_contexts(contexts: &serde_json::Map<String, Value>) -> Result<(
369369 Ok ( ( ) )
370370}
371371
372+ // ============================================================================
373+ // MaaS Registry (provider + model mappings for empty-state cascading picker)
374+ // ============================================================================
375+
376+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
377+ #[ serde( rename_all = "camelCase" ) ]
378+ struct MaasModel {
379+ id : String ,
380+ display_name : String ,
381+ model_name : String ,
382+ }
383+
384+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
385+ #[ serde( rename_all = "camelCase" ) ]
386+ struct MaasProvider {
387+ key : String ,
388+ label : String ,
389+ base_url : String ,
390+ auth_env_key : String ,
391+ models : Vec < MaasModel > ,
392+ }
393+
394+ fn get_maas_registry_path ( ) -> PathBuf {
395+ get_lovstudio_dir ( ) . join ( "maas_registry.json" )
396+ }
397+
398+ fn default_maas_registry ( ) -> Vec < MaasProvider > {
399+ fn m ( id : & str , display : & str , name : & str ) -> MaasModel {
400+ MaasModel {
401+ id : id. to_string ( ) ,
402+ display_name : display. to_string ( ) ,
403+ model_name : name. to_string ( ) ,
404+ }
405+ }
406+ let anthropic_native_models = vec ! [
407+ m( "opus-4-7" , "Claude Opus 4.7" , "claude-opus-4-7-20251101" ) ,
408+ m( "sonnet-4-6" , "Claude Sonnet 4.6" , "claude-sonnet-4-6-20251001" ) ,
409+ m( "haiku-4-5" , "Claude Haiku 4.5" , "claude-haiku-4-5-20250930" ) ,
410+ ] ;
411+ vec ! [
412+ MaasProvider {
413+ key: "anthropic-subscription" . into( ) ,
414+ label: "Anthropic Subscription" . into( ) ,
415+ base_url: "" . into( ) ,
416+ auth_env_key: "CLAUDE_CODE_USE_OAUTH" . into( ) ,
417+ models: anthropic_native_models. clone( ) ,
418+ } ,
419+ MaasProvider {
420+ key: "native" . into( ) ,
421+ label: "Anthropic API" . into( ) ,
422+ base_url: "https://api.anthropic.com" . into( ) ,
423+ auth_env_key: "ANTHROPIC_API_KEY" . into( ) ,
424+ models: anthropic_native_models,
425+ } ,
426+ MaasProvider {
427+ key: "zenmux" . into( ) ,
428+ label: "ZenMux" . into( ) ,
429+ base_url: "https://zenmux.ai/api/anthropic" . into( ) ,
430+ auth_env_key: "ZENMUX_API_KEY" . into( ) ,
431+ models: vec![
432+ m( "sonnet-4-6" , "Claude Sonnet 4.6" , "anthropic/claude-sonnet-4-6-20251001" ) ,
433+ m( "sonnet-4-5" , "Claude Sonnet 4.5" , "anthropic/claude-sonnet-4.5" ) ,
434+ m( "haiku-4-5" , "Claude Haiku 4.5" , "anthropic/claude-haiku-4.5" ) ,
435+ ] ,
436+ } ,
437+ MaasProvider {
438+ key: "modelgate" . into( ) ,
439+ label: "ModelGate" . into( ) ,
440+ base_url: "https://mg.aid.pub/claude-proxy" . into( ) ,
441+ auth_env_key: "MODELGATE_API_KEY" . into( ) ,
442+ models: vec![
443+ m( "sonnet-4-6" , "Claude Sonnet 4.6" , "anthropic/claude-sonnet-4-6-20251001" ) ,
444+ m( "sonnet-4-5" , "Claude Sonnet 4.5" , "anthropic/claude-sonnet-4.5" ) ,
445+ m( "haiku-4-5" , "Claude Haiku 4.5" , "anthropic/claude-haiku-4.5" ) ,
446+ ] ,
447+ } ,
448+ MaasProvider {
449+ key: "qiniu" . into( ) ,
450+ label: "Qiniu Cloud" . into( ) ,
451+ base_url: "https://api.qnaigc.com" . into( ) ,
452+ auth_env_key: "QINIU_API_KEY" . into( ) ,
453+ models: vec![
454+ m( "sonnet-4-6" , "Claude Sonnet 4.6" , "claude-sonnet-4-6-20251001" ) ,
455+ m( "haiku-4-5" , "Claude Haiku 4.5" , "claude-haiku-4-5-20250930" ) ,
456+ ] ,
457+ } ,
458+ MaasProvider {
459+ key: "siliconflow" . into( ) ,
460+ label: "SiliconFlow" . into( ) ,
461+ base_url: "https://api.siliconflow.com/v1" . into( ) ,
462+ auth_env_key: "SILICONFLOW_API_KEY" . into( ) ,
463+ models: vec![
464+ m( "sonnet-4-5" , "Claude Sonnet 4.5" , "claude-sonnet-4-5" ) ,
465+ m( "haiku-4-5" , "Claude Haiku 4.5" , "claude-haiku-4-5" ) ,
466+ ] ,
467+ } ,
468+ MaasProvider {
469+ key: "univibe" . into( ) ,
470+ label: "UniVibe" . into( ) ,
471+ base_url: "https://api.univibe.cc/anthropic" . into( ) ,
472+ auth_env_key: "UNIVIBE_API_KEY" . into( ) ,
473+ models: vec![
474+ m( "sonnet-4-6" , "Claude Sonnet 4.6" , "claude-sonnet-4-6-20251001" ) ,
475+ m( "haiku-4-5" , "Claude Haiku 4.5" , "claude-haiku-4-5-20250930" ) ,
476+ ] ,
477+ } ,
478+ ]
479+ }
480+
481+ fn load_maas_registry ( ) -> Result < Vec < MaasProvider > , String > {
482+ let path = get_maas_registry_path ( ) ;
483+ if !path. exists ( ) {
484+ return Ok ( default_maas_registry ( ) ) ;
485+ }
486+ let content = fs:: read_to_string ( & path) . map_err ( |e| e. to_string ( ) ) ?;
487+ serde_json:: from_str ( & content) . map_err ( |e| e. to_string ( ) )
488+ }
489+
490+ fn persist_maas_registry ( registry : & [ MaasProvider ] ) -> Result < ( ) , String > {
491+ let path = get_maas_registry_path ( ) ;
492+ if let Some ( parent) = path. parent ( ) {
493+ fs:: create_dir_all ( parent) . map_err ( |e| e. to_string ( ) ) ?;
494+ }
495+ let output = serde_json:: to_string_pretty ( registry) . map_err ( |e| e. to_string ( ) ) ?;
496+ fs:: write ( & path, output) . map_err ( |e| e. to_string ( ) ) ?;
497+ Ok ( ( ) )
498+ }
499+
372500/// Get path to ~/.claude.json (MCP servers config)
373501fn get_claude_json_path ( ) -> PathBuf {
374502 dirs:: home_dir ( ) . unwrap ( ) . join ( ".claude.json" )
@@ -5560,6 +5688,39 @@ fn snapshot_provider_context(
55605688 Ok ( ( ) )
55615689}
55625690
5691+ // ============================================================================
5692+ // MaaS Registry Commands
5693+ // ============================================================================
5694+
5695+ #[ tauri:: command]
5696+ fn get_maas_registry ( ) -> Result < Vec < MaasProvider > , String > {
5697+ load_maas_registry ( )
5698+ }
5699+
5700+ #[ tauri:: command]
5701+ fn save_maas_registry ( registry : Vec < MaasProvider > ) -> Result < ( ) , String > {
5702+ persist_maas_registry ( & registry)
5703+ }
5704+
5705+ #[ tauri:: command]
5706+ fn upsert_maas_provider ( provider : MaasProvider ) -> Result < Vec < MaasProvider > , String > {
5707+ let mut registry = load_maas_registry ( ) ?;
5708+ match registry. iter ( ) . position ( |p| p. key == provider. key ) {
5709+ Some ( idx) => registry[ idx] = provider,
5710+ None => registry. push ( provider) ,
5711+ }
5712+ persist_maas_registry ( & registry) ?;
5713+ Ok ( registry)
5714+ }
5715+
5716+ #[ tauri:: command]
5717+ fn delete_maas_provider ( key : String ) -> Result < Vec < MaasProvider > , String > {
5718+ let mut registry = load_maas_registry ( ) ?;
5719+ registry. retain ( |p| p. key != key) ;
5720+ persist_maas_registry ( & registry) ?;
5721+ Ok ( registry)
5722+ }
5723+
55635724// ============================================================================
55645725// Settings Field Update Commands
55655726// ============================================================================
@@ -7903,6 +8064,39 @@ pub fn run() {
79038064 }
79048065 } ) ;
79058066
8067+ // Start watching ~/.claude/projects/ for session changes (new/updated jsonl files)
8068+ let app_handle = app. handle ( ) . clone ( ) ;
8069+ std:: thread:: spawn ( move || {
8070+ let projects_dir = get_claude_dir ( ) . join ( "projects" ) ;
8071+ if !projects_dir. exists ( ) {
8072+ let _ = fs:: create_dir_all ( & projects_dir) ;
8073+ }
8074+
8075+ let ( tx, rx) = channel ( ) ;
8076+ let mut watcher: RecommendedWatcher = match notify:: recommended_watcher ( move |res : Result < Event , notify:: Error > | {
8077+ if let Ok ( event) = res {
8078+ if event. kind . is_create ( ) || event. kind . is_modify ( ) || event. kind . is_remove ( ) {
8079+ let _ = tx. send ( ( ) ) ;
8080+ }
8081+ }
8082+ } ) {
8083+ Ok ( w) => w,
8084+ Err ( _) => return ,
8085+ } ;
8086+
8087+ if watcher. watch ( & projects_dir, RecursiveMode :: Recursive ) . is_err ( ) {
8088+ return ;
8089+ }
8090+
8091+ loop {
8092+ if rx. recv ( ) . is_ok ( ) {
8093+ // Debounce burst of writes from jsonl appends
8094+ while rx. recv_timeout ( Duration :: from_millis ( 500 ) ) . is_ok ( ) { }
8095+ let _ = app_handle. emit ( "sessions-changed" , ( ) ) ;
8096+ }
8097+ }
8098+ } ) ;
8099+
79068100 let settings = MenuItemBuilder :: with_id ( "settings" , "Settings..." )
79078101 . accelerator ( "CmdOrCtrl+," )
79088102 . build ( app) ?;
@@ -8076,6 +8270,10 @@ pub fn run() {
80768270 get_provider_contexts,
80778271 set_provider_context_env,
80788272 snapshot_provider_context,
8273+ get_maas_registry,
8274+ save_maas_registry,
8275+ upsert_maas_provider,
8276+ delete_maas_provider,
80798277 update_settings_field,
80808278 update_settings_permission_field,
80818279 add_permission_directory,
0 commit comments