diff --git a/.infer/config.yaml b/.infer/config.yaml index 439a0793..e57651b8 100644 --- a/.infer/config.yaml +++ b/.infer/config.yaml @@ -1,31 +1,33 @@ gateway: - url: http://localhost:8080 - api_key: "" - timeout: 30 + url: http://localhost:8080 + api_key: "" + timeout: 30 output: - format: text - quiet: false + format: text + quiet: false tools: - enabled: true - whitelist: - commands: - - ls - - pwd - - echo - - cat - - head - - tail - - grep - - find - - wc - - sort - - uniq - patterns: - - ^git status$ - - ^git log --oneline -n [0-9]+$ - - ^docker ps$ - - ^kubectl get pods$ - safety: - require_approval: true + enabled: true + whitelist: + commands: + - ls + - pwd + - echo + - cat + - head + - tail + - grep + - find + - wc + - sort + - uniq + patterns: + - ^git status$ + - ^git log --oneline -n [0-9]+$ + - ^docker ps$ + - ^kubectl get pods$ + safety: + require_approval: true compact: - output_dir: .infer + output_dir: .infer +chat: + default_model: deepseek/deepseek-chat diff --git a/CLAUDE.md b/CLAUDE.md index 01c3eb24..d6642a5b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -154,6 +154,8 @@ tools: require_approval: true # Prompt user before executing any command compact: output_dir: ".infer" # Directory for compact command exports (default: project root/.infer) +chat: + default_model: "" # Default model for chat sessions (when set, skips model selection) ``` ### Command Structure @@ -162,7 +164,9 @@ compact: - Subcommands: - `init [--overwrite]`: Initialize local project configuration - `status`: Gateway status - - `chat`: Interactive chat with model selection + - `chat`: Interactive chat with model selection (or uses default model if configured) + - `config`: Manage CLI configuration + - `set-model [MODEL_NAME]`: Set default model for chat sessions - `version`: Version information ## Dependencies @@ -178,10 +182,32 @@ compact: - Architecture follows SOLID principles with dependency injection - Configuration loading handles missing config files gracefully by returning defaults - The project uses modern Go project structure with `internal/` for private packages +- Default model configuration allows skipping model selection in chat sessions when a preferred model is set + +## Usage Examples + +### Setting a Default Model +```bash +# Set a default model for chat sessions +infer config set-model gpt-4-turbo + +# Now chat will automatically use this model without showing selection +infer chat +``` + +### Configuration Management +```bash +# View current configuration (check .infer/config.yaml) +cat .infer/config.yaml + +# The default model will be saved in the chat section: +# chat: +# default_model: "gpt-4-turbo" +``` ## Code Style Guidelines -- **No Redundant Comments**: The codebase has been cleaned of redundant inline comments. Avoid adding comments that simply restate what the code does or explain obvious operations. +- **Inline Comments**: Do not write inline comments unless the code is genuinely unclear or requires specific explanation. - **Comment Policy**: Only add comments for: - Complex business logic that isn't immediately clear - External API interactions or protocol specifications diff --git a/cmd/chat.go b/cmd/chat.go index e7f1431f..1f329a08 100644 --- a/cmd/chat.go +++ b/cmd/chat.go @@ -43,7 +43,12 @@ func startChatSession() error { return fmt.Errorf("no models available from inference gateway") } - application := app.NewChatApplication(services, models) + defaultModel := cfg.Chat.DefaultModel + if defaultModel != "" { + defaultModel = validateAndSetDefaultModel(services, models, defaultModel) + } + + application := app.NewChatApplication(services, models, defaultModel) program := tea.NewProgram(application, tea.WithAltScreen()) @@ -56,6 +61,29 @@ func startChatSession() error { return nil } +func validateAndSetDefaultModel(services *container.ServiceContainer, models []string, defaultModel string) string { + modelFound := false + for _, model := range models { + if model == defaultModel { + modelFound = true + break + } + } + + if !modelFound { + fmt.Printf("⚠️ Default model '%s' is not available, showing model selection...\n", defaultModel) + return "" + } + + if err := services.GetModelService().SelectModel(defaultModel); err != nil { + fmt.Printf("⚠️ Failed to set default model: %v, showing model selection...\n", err) + return "" + } + + fmt.Printf("🤖 Using default model: %s\n", defaultModel) + return defaultModel +} + func init() { rootCmd.AddCommand(chatCmd) } diff --git a/cmd/config.go b/cmd/config.go new file mode 100644 index 00000000..3d7e344e --- /dev/null +++ b/cmd/config.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "fmt" + + "github.com/inference-gateway/cli/config" + "github.com/spf13/cobra" +) + +var configCmd = &cobra.Command{ + Use: "config", + Short: "Manage CLI configuration", + Long: `Manage the Inference Gateway CLI configuration settings.`, +} + +var setModelCmd = &cobra.Command{ + Use: "set-model [MODEL_NAME]", + Short: "Set the default model for chat sessions", + Long: `Set the default model for chat sessions. When a default model is configured, +the chat command will skip the model selection view and use the configured model directly.`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + modelName := args[0] + return setDefaultModel(modelName) + }, +} + +func setDefaultModel(modelName string) error { + cfg, err := config.LoadConfig("") + if err != nil { + return fmt.Errorf("failed to load config: %w", err) + } + + cfg.Chat.DefaultModel = modelName + + if err := cfg.SaveConfig(""); err != nil { + return fmt.Errorf("failed to save config: %w", err) + } + + fmt.Printf("✅ Default model set to: %s\n", modelName) + fmt.Println("The chat command will now use this model by default and skip model selection.") + return nil +} + +func init() { + configCmd.AddCommand(setModelCmd) + rootCmd.AddCommand(configCmd) +} diff --git a/config/config.go b/config/config.go index 49b4357b..16049b89 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,7 @@ package config import ( + "bytes" "fmt" "os" "path/filepath" @@ -15,6 +16,7 @@ type Config struct { Output OutputConfig `yaml:"output"` Tools ToolsConfig `yaml:"tools"` Compact CompactConfig `yaml:"compact"` + Chat ChatConfig `yaml:"chat"` } // GatewayConfig contains gateway connection settings @@ -53,6 +55,11 @@ type CompactConfig struct { OutputDir string `yaml:"output_dir"` } +// ChatConfig contains chat-related settings +type ChatConfig struct { + DefaultModel string `yaml:"default_model"` +} + // DefaultConfig returns a default configuration func DefaultConfig() *Config { return &Config{ @@ -86,6 +93,9 @@ func DefaultConfig() *Config { Compact: CompactConfig{ OutputDir: ".infer", }, + Chat: ChatConfig{ + DefaultModel: "", + }, } } @@ -136,12 +146,22 @@ func (c *Config) SaveConfig(configPath string) error { return fmt.Errorf("failed to create config directory: %w", err) } - data, err := yaml.Marshal(c) - if err != nil { + var buf bytes.Buffer + encoder := yaml.NewEncoder(&buf) + encoder.SetIndent(2) + defer func() { + if err := encoder.Close(); err != nil { + logger.Error("Failed to close YAML encoder", "error", err) + } + }() + + if err := encoder.Encode(c); err != nil { logger.Error("Failed to marshal config", "error", err) return fmt.Errorf("failed to marshal config: %w", err) } + data := buf.Bytes() + logger.Debug("Writing config file", "path", configPath, "size", len(data)) if err := os.WriteFile(configPath, data, 0644); err != nil { logger.Error("Failed to write config file", "path", configPath, "error", err) diff --git a/internal/app/chat_application.go b/internal/app/chat_application.go index 18307108..c261d621 100644 --- a/internal/app/chat_application.go +++ b/internal/app/chat_application.go @@ -39,12 +39,17 @@ type ChatApplication struct { } // NewChatApplication creates a new chat application with all dependencies injected -func NewChatApplication(services *container.ServiceContainer, models []string) *ChatApplication { +func NewChatApplication(services *container.ServiceContainer, models []string, defaultModel string) *ChatApplication { + initialView := handlers.ViewModelSelection + if defaultModel != "" { + initialView = handlers.ViewChat + } + app := &ChatApplication{ services: services, availableModels: models, state: &handlers.AppState{ - CurrentView: handlers.ViewModelSelection, + CurrentView: initialView, Width: 80, Height: 24, Data: make(map[string]interface{}), @@ -58,7 +63,11 @@ func NewChatApplication(services *container.ServiceContainer, models []string) * app.modelSelector = ui.NewModelSelector(models, services.GetModelService(), services.GetTheme()) - app.focusedComponent = nil + if initialView == handlers.ViewChat { + app.focusedComponent = app.inputView + } else { + app.focusedComponent = nil + } app.messageRouter = services.GetMessageRouter()