Skip to content

Commit b5eeecb

Browse files
znsoftznsoft
authored andcommitted
refactor(config): Implement multi-tool configuration schema and migration logic
1 parent 42fcd5b commit b5eeecb

File tree

7 files changed

+118
-169
lines changed

7 files changed

+118
-169
lines changed

app.go

Lines changed: 90 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,18 @@ type ProjectConfig struct {
4141
YoloMode bool `json:"yolo_mode"`
4242
}
4343

44+
type ToolConfig struct {
45+
CurrentModel string `json:"current_model"`
46+
Models []ModelConfig `json:"models"`
47+
}
48+
4449
type AppConfig struct {
45-
CurrentModel string `json:"current_model"`
46-
ProjectDir string `json:"project_dir"` // Deprecated, kept for migration
47-
Models []ModelConfig `json:"models"`
50+
Claude ToolConfig `json:"claude"`
51+
Gemini ToolConfig `json:"gemini"`
52+
Codex ToolConfig `json:"codex"`
4853
Projects []ProjectConfig `json:"projects"`
4954
CurrentProject string `json:"current_project"` // ID of the current project
55+
ActiveTool string `json:"active_tool"` // "claude", "gemini", or "codex"
5056
}
5157

5258
// NewApp creates a new App application struct
@@ -182,8 +188,8 @@ func (a *App) syncToClaudeSettings(config AppConfig) error {
182188
settingsPath := filepath.Join(claudeDir, "settings.json")
183189

184190
var selectedModel *ModelConfig
185-
for _, m := range config.Models {
186-
if m.ModelName == config.CurrentModel {
191+
for _, m := range config.Claude.Models {
192+
if m.ModelName == config.Claude.CurrentModel {
187193
selectedModel = &m
188194
break
189195
}
@@ -294,7 +300,7 @@ func (a *App) getConfigPath() (string, error) {
294300
if err != nil {
295301
return "", err
296302
}
297-
return filepath.Join(home, ".claude_model_config.json"), nil
303+
return filepath.Join(home, ".aicoder_config.json"), nil
298304
}
299305

300306
func (a *App) LoadConfig() (AppConfig, error) {
@@ -303,10 +309,75 @@ func (a *App) LoadConfig() (AppConfig, error) {
303309
return AppConfig{}, err
304310
}
305311

312+
// Helper for default models
313+
defaultClaudeModels := []ModelConfig{
314+
{ModelName: "GLM", ModelUrl: "https://open.bigmodel.cn/api/anthropic", ApiKey: ""},
315+
{ModelName: "kimi", ModelUrl: "https://api.kimi.com/coding", ApiKey: ""},
316+
{ModelName: "doubao", ModelUrl: "https://ark.cn-beijing.volces.com/api/coding", ApiKey: ""},
317+
{ModelName: "MiniMax", ModelUrl: "https://api.minimaxi.com/anthropic", ApiKey: ""},
318+
{ModelName: "Custom", ModelUrl: "", ApiKey: "", IsCustom: true},
319+
}
320+
defaultGeminiModels := []ModelConfig{
321+
{ModelName: "Gemini 1.5 Pro", ModelUrl: "", ApiKey: ""},
322+
{ModelName: "Gemini 1.5 Flash", ModelUrl: "", ApiKey: ""},
323+
}
324+
defaultCodexModels := []ModelConfig{
325+
{ModelName: "Codex", ModelUrl: "", ApiKey: ""},
326+
}
327+
306328
if _, err := os.Stat(path); os.IsNotExist(err) {
307-
// Create default config
329+
// Check for old config file for migration
308330
home, _ := os.UserHomeDir()
331+
oldPath := filepath.Join(home, ".claude_model_config.json")
332+
if _, err := os.Stat(oldPath); err == nil {
333+
// Migrate old config
334+
data, err := os.ReadFile(oldPath)
335+
if err == nil {
336+
var oldConfig struct {
337+
CurrentModel string `json:"current_model"`
338+
Models []ModelConfig `json:"models"`
339+
Projects []ProjectConfig `json:"projects"`
340+
CurrentProj string `json:"current_project"`
341+
}
342+
if err := json.Unmarshal(data, &oldConfig); err == nil {
343+
config := AppConfig{
344+
Claude: ToolConfig{
345+
CurrentModel: oldConfig.CurrentModel,
346+
Models: oldConfig.Models,
347+
},
348+
Gemini: ToolConfig{
349+
CurrentModel: "Gemini 1.5 Pro",
350+
Models: defaultGeminiModels,
351+
},
352+
Codex: ToolConfig{
353+
CurrentModel: "Codex",
354+
Models: defaultCodexModels,
355+
},
356+
Projects: oldConfig.Projects,
357+
CurrentProject: oldConfig.CurrentProj,
358+
ActiveTool: "claude",
359+
}
360+
a.SaveConfig(config)
361+
// Optional: os.Remove(oldPath)
362+
return config, nil
363+
}
364+
}
365+
}
366+
367+
// Create default config
309368
defaultConfig := AppConfig{
369+
Claude: ToolConfig{
370+
CurrentModel: "GLM",
371+
Models: defaultClaudeModels,
372+
},
373+
Gemini: ToolConfig{
374+
CurrentModel: "Gemini 1.5 Pro",
375+
Models: defaultGeminiModels,
376+
},
377+
Codex: ToolConfig{
378+
CurrentModel: "Codex",
379+
Models: defaultCodexModels,
380+
},
310381
Projects: []ProjectConfig{
311382
{
312383
Id: "default",
@@ -316,37 +387,7 @@ func (a *App) LoadConfig() (AppConfig, error) {
316387
},
317388
},
318389
CurrentProject: "default",
319-
Models: []ModelConfig{
320-
{
321-
ModelName: "GLM",
322-
ModelUrl: "https://open.bigmodel.cn/api/anthropic",
323-
ApiKey: "",
324-
},
325-
{
326-
ModelName: "kimi",
327-
ModelUrl: "https://api.kimi.com/coding",
328-
ApiKey: "",
329-
},
330-
{
331-
ModelName: "doubao",
332-
ModelUrl: "https://ark.cn-beijing.volces.com/api/coding",
333-
ApiKey: "",
334-
},
335-
{
336-
ModelName: "MiniMax",
337-
ModelUrl: "https://api.minimaxi.com/anthropic",
338-
ApiKey: "",
339-
},
340-
{
341-
ModelName: "Custom",
342-
ModelUrl: "",
343-
ApiKey: "",
344-
IsCustom: true,
345-
},
346-
},
347-
}
348-
if len(defaultConfig.Models) > 0 {
349-
defaultConfig.CurrentModel = defaultConfig.Models[0].ModelName
390+
ActiveTool: "claude",
350391
}
351392

352393
err = a.SaveConfig(defaultConfig)
@@ -364,110 +405,20 @@ func (a *App) LoadConfig() (AppConfig, error) {
364405
return config, err
365406
}
366407

367-
if config.CurrentModel == "" && len(config.Models) > 0 {
368-
config.CurrentModel = config.Models[0].ModelName
369-
}
370-
371-
// Migration: If Projects list is empty but ProjectDir exists
372-
if len(config.Projects) == 0 {
373-
pDir := config.ProjectDir
374-
if pDir == "" {
375-
pDir, _ = os.UserHomeDir()
376-
}
377-
config.Projects = []ProjectConfig{
378-
{
379-
Id: "default",
380-
Name: "Project 1",
381-
Path: pDir,
382-
YoloMode: false, // Default to false for safety
383-
},
384-
}
385-
config.CurrentProject = "default"
386-
}
387-
388-
// Ensure CurrentProject is valid
389-
validProj := false
390-
for _, p := range config.Projects {
391-
if p.Id == config.CurrentProject {
392-
validProj = true
393-
break
394-
}
395-
}
396-
if !validProj && len(config.Projects) > 0 {
397-
config.CurrentProject = config.Projects[0].Id
408+
// Ensure defaults for new fields
409+
if config.Claude.CurrentModel == "" && len(config.Claude.Models) > 0 {
410+
config.Claude.CurrentModel = config.Claude.Models[0].ModelName
398411
}
399-
400-
// Ensure ModelUrls are populated and migrate names for existing configs
401-
hasCustom := false
402-
hasMiniMax := false
403-
for i := range config.Models {
404-
// Migrate to "GLM" for display
405-
lowerName := strings.ToLower(config.Models[i].ModelName)
406-
if lowerName == "glm" || lowerName == "glm-4.7" {
407-
config.Models[i].ModelName = "GLM"
408-
if strings.ToLower(config.CurrentModel) == "glm" || strings.ToLower(config.CurrentModel) == "glm-4.7" {
409-
config.CurrentModel = "GLM"
410-
}
411-
}
412-
413-
if lowerName == "minimax" {
414-
hasMiniMax = true
415-
config.Models[i].ModelName = "MiniMax"
416-
if strings.ToLower(config.CurrentModel) == "minimax" {
417-
config.CurrentModel = "MiniMax"
418-
}
419-
}
420-
421-
if config.Models[i].IsCustom || config.Models[i].ModelName == "Custom" {
422-
hasCustom = true
423-
config.Models[i].IsCustom = true
424-
}
425-
if config.Models[i].ModelUrl == "" {
426-
switch strings.ToLower(config.Models[i].ModelName) {
427-
case "glm", "glm-4.7":
428-
config.Models[i].ModelUrl = "https://open.bigmodel.cn/api/anthropic"
429-
case "kimi":
430-
config.Models[i].ModelUrl = "https://api.kimi.com/coding"
431-
case "doubao":
432-
config.Models[i].ModelUrl = "https://ark.cn-beijing.volces.com/api/coding"
433-
case "minimax":
434-
config.Models[i].ModelUrl = "https://api.minimaxi.com/anthropic"
435-
}
436-
}
412+
if config.Gemini.Models == nil {
413+
config.Gemini.Models = defaultGeminiModels
414+
config.Gemini.CurrentModel = "Gemini 1.5 Pro"
437415
}
438-
439-
if !hasMiniMax {
440-
// Insert MiniMax before Custom if Custom exists, otherwise append
441-
newModels := []ModelConfig{}
442-
inserted := false
443-
for _, m := range config.Models {
444-
if (m.IsCustom || m.ModelName == "Custom") && !inserted {
445-
newModels = append(newModels, ModelConfig{
446-
ModelName: "MiniMax",
447-
ModelUrl: "https://api.minimaxi.com/anthropic",
448-
ApiKey: "your_minimax_api_key_here",
449-
})
450-
inserted = true
451-
}
452-
newModels = append(newModels, m)
453-
}
454-
if !inserted {
455-
newModels = append(newModels, ModelConfig{
456-
ModelName: "MiniMax",
457-
ModelUrl: "https://api.minimaxi.com/anthropic",
458-
ApiKey: "your_minimax_api_key_here",
459-
})
460-
}
461-
config.Models = newModels
416+
if config.Codex.Models == nil {
417+
config.Codex.Models = defaultCodexModels
418+
config.Codex.CurrentModel = "Codex"
462419
}
463-
464-
if !hasCustom {
465-
config.Models = append(config.Models, ModelConfig{
466-
ModelName: "Custom",
467-
ModelUrl: "",
468-
ApiKey: "",
469-
IsCustom: true,
470-
})
420+
if config.ActiveTool == "" {
421+
config.ActiveTool = "claude"
471422
}
472423

473424
return config, nil

app_test.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ func TestSyncToClaudeSettings(t *testing.T) {
3434

3535
// Define a test configuration
3636
config := AppConfig{
37-
CurrentModel: "TestModel",
38-
Models: []ModelConfig{
39-
{
40-
ModelName: "TestModel",
41-
ApiKey: "sk-test-key-123",
42-
ModelUrl: "https://api.test.com",
37+
Claude: ToolConfig{
38+
CurrentModel: "TestModel",
39+
Models: []ModelConfig{
40+
{
41+
ModelName: "TestModel",
42+
ApiKey: "sk-test-key-123",
43+
ModelUrl: "https://api.test.com",
44+
},
4345
},
4446
},
4547
}
@@ -136,7 +138,7 @@ func TestGetCurrentProjectPath(t *testing.T) {
136138
app := &App{}
137139

138140
// Setup test config file
139-
configPath := filepath.Join(tmpHome, ".claude_model_config.json")
141+
configPath := filepath.Join(tmpHome, ".aicoder_config.json")
140142
config := AppConfig{
141143
CurrentProject: "proj2",
142144
Projects: []ProjectConfig{

conductor/tracks/aicoder_20251231/plan.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ Expansion of Claude Code Easy Suite into "AICoder", a multi-model dashboard supp
55
## Phase 1: Rebranding & Configuration Schema Migration
66
Goal: Rename the application and prepare the configuration system for multiple tools.
77

8-
- [~] Task: Update project metadata (`wails.json`, `main.go`, `app.go`) to "AICoder".
9-
- [ ] Task: Refactor `AppConfig` in `app.go` to support independent settings for Codex, Gemini, and Claude Code.
8+
- [x] Task: Update project metadata (`wails.json`, `main.go`, `app.go`) to "AICoder". 42fcd5b
9+
- [~] Task: Refactor `AppConfig` in `app.go` to support independent settings for Codex, Gemini, and Claude Code.
1010
- [ ] Task: Implement migration logic to safely move existing Claude settings into the new multi-tool schema.
1111
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Rebranding & Config' (Protocol in workflow.md)
1212

platform_windows.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,8 +382,8 @@ func (a *App) LaunchClaude(yoloMode bool, projectDir string) {
382382

383383
func (a *App) syncToSystemEnv(config AppConfig) {
384384
var selectedModel *ModelConfig
385-
for _, m := range config.Models {
386-
if m.ModelName == config.CurrentModel {
385+
for _, m := range config.Claude.Models {
386+
if m.ModelName == config.Claude.CurrentModel {
387387
selectedModel = &m
388388
break
389389
}

tray_darwin.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@ func setupTray(app *App, appOptions *options.App) {
4040

4141
// Load config to populate tray
4242
config, _ := app.LoadConfig()
43-
for _, model := range config.Models {
43+
for _, model := range config.Claude.Models {
4444
modelName := model.ModelName
45-
m := systray.AddMenuItemCheckbox(modelName, "Switch to "+modelName, modelName == config.CurrentModel)
45+
m := systray.AddMenuItemCheckbox(modelName, "Switch to "+modelName, modelName == config.Claude.CurrentModel)
4646
modelItems[modelName] = m
4747

4848
m.Click(func() {
4949
go func() {
5050
currentConfig, _ := app.LoadConfig()
5151
// Check if target model has API key
52-
for _, m := range currentConfig.Models {
52+
for _, m := range currentConfig.Claude.Models {
5353
if m.ModelName == modelName {
5454
if m.ApiKey == "" {
5555
runtime.WindowShow(app.ctx)
@@ -58,7 +58,7 @@ func setupTray(app *App, appOptions *options.App) {
5858
break
5959
}
6060
}
61-
currentConfig.CurrentModel = modelName
61+
currentConfig.Claude.CurrentModel = modelName
6262
app.SaveConfig(currentConfig)
6363
}()
6464
})
@@ -85,7 +85,7 @@ func setupTray(app *App, appOptions *options.App) {
8585
return
8686
}
8787
for name, item := range modelItems {
88-
if name == cfg.CurrentModel {
88+
if name == cfg.Claude.CurrentModel {
8989
item.Check()
9090
} else {
9191
item.Uncheck()

0 commit comments

Comments
 (0)