Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 29 additions & 27 deletions .infer/config.yaml
Original file line number Diff line number Diff line change
@@ -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
30 changes: 28 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
30 changes: 29 additions & 1 deletion cmd/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand All @@ -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)
}
48 changes: 48 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
@@ -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)
}
24 changes: 22 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"bytes"
"fmt"
"os"
"path/filepath"
Expand All @@ -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
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -86,6 +93,9 @@ func DefaultConfig() *Config {
Compact: CompactConfig{
OutputDir: ".infer",
},
Chat: ChatConfig{
DefaultModel: "",
},
}
}

Expand Down Expand Up @@ -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)
Expand Down
15 changes: 12 additions & 3 deletions internal/app/chat_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}),
Expand All @@ -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()

Expand Down