Skip to content

Commit 4c361d9

Browse files
feat: Add default model configuration for chat sessions (#18)
Closes #17 Implements the ability to configure default models for chat sessions using `infer config set-model <MODEL_NAME>`. When a default model is configured, the chat command skips the model selection view and uses the configured model directly. ## Changes - Add ChatConfig struct with default_model field - Implement `infer config set-model` command to set default model - Modify chat command to skip model selection when default is set - Update chat application to start in chat view when default model is configured - Add comprehensive documentation with usage examples - Refactor complex nested blocks to pass linter checks ## Testing - All existing tests pass - Linting passes without issues - Manual testing confirms feature works as expected Generated with [Claude Code](https://claude.ai/code) --------- Signed-off-by: Eden Reich <[email protected]> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Eden Reich <[email protected]>
1 parent 8aa39c6 commit 4c361d9

File tree

6 files changed

+168
-35
lines changed

6 files changed

+168
-35
lines changed

.infer/config.yaml

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,33 @@
11
gateway:
2-
url: http://localhost:8080
3-
api_key: ""
4-
timeout: 30
2+
url: http://localhost:8080
3+
api_key: ""
4+
timeout: 30
55
output:
6-
format: text
7-
quiet: false
6+
format: text
7+
quiet: false
88
tools:
9-
enabled: true
10-
whitelist:
11-
commands:
12-
- ls
13-
- pwd
14-
- echo
15-
- cat
16-
- head
17-
- tail
18-
- grep
19-
- find
20-
- wc
21-
- sort
22-
- uniq
23-
patterns:
24-
- ^git status$
25-
- ^git log --oneline -n [0-9]+$
26-
- ^docker ps$
27-
- ^kubectl get pods$
28-
safety:
29-
require_approval: true
9+
enabled: true
10+
whitelist:
11+
commands:
12+
- ls
13+
- pwd
14+
- echo
15+
- cat
16+
- head
17+
- tail
18+
- grep
19+
- find
20+
- wc
21+
- sort
22+
- uniq
23+
patterns:
24+
- ^git status$
25+
- ^git log --oneline -n [0-9]+$
26+
- ^docker ps$
27+
- ^kubectl get pods$
28+
safety:
29+
require_approval: true
3030
compact:
31-
output_dir: .infer
31+
output_dir: .infer
32+
chat:
33+
default_model: deepseek/deepseek-chat

CLAUDE.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ tools:
154154
require_approval: true # Prompt user before executing any command
155155
compact:
156156
output_dir: ".infer" # Directory for compact command exports (default: project root/.infer)
157+
chat:
158+
default_model: "" # Default model for chat sessions (when set, skips model selection)
157159
```
158160
159161
### Command Structure
@@ -162,7 +164,9 @@ compact:
162164
- Subcommands:
163165
- `init [--overwrite]`: Initialize local project configuration
164166
- `status`: Gateway status
165-
- `chat`: Interactive chat with model selection
167+
- `chat`: Interactive chat with model selection (or uses default model if configured)
168+
- `config`: Manage CLI configuration
169+
- `set-model [MODEL_NAME]`: Set default model for chat sessions
166170
- `version`: Version information
167171

168172
## Dependencies
@@ -178,10 +182,32 @@ compact:
178182
- Architecture follows SOLID principles with dependency injection
179183
- Configuration loading handles missing config files gracefully by returning defaults
180184
- The project uses modern Go project structure with `internal/` for private packages
185+
- Default model configuration allows skipping model selection in chat sessions when a preferred model is set
186+
187+
## Usage Examples
188+
189+
### Setting a Default Model
190+
```bash
191+
# Set a default model for chat sessions
192+
infer config set-model gpt-4-turbo
193+
194+
# Now chat will automatically use this model without showing selection
195+
infer chat
196+
```
197+
198+
### Configuration Management
199+
```bash
200+
# View current configuration (check .infer/config.yaml)
201+
cat .infer/config.yaml
202+
203+
# The default model will be saved in the chat section:
204+
# chat:
205+
# default_model: "gpt-4-turbo"
206+
```
181207

182208
## Code Style Guidelines
183209

184-
- **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.
210+
- **Inline Comments**: Do not write inline comments unless the code is genuinely unclear or requires specific explanation.
185211
- **Comment Policy**: Only add comments for:
186212
- Complex business logic that isn't immediately clear
187213
- External API interactions or protocol specifications

cmd/chat.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ func startChatSession() error {
4343
return fmt.Errorf("no models available from inference gateway")
4444
}
4545

46-
application := app.NewChatApplication(services, models)
46+
defaultModel := cfg.Chat.DefaultModel
47+
if defaultModel != "" {
48+
defaultModel = validateAndSetDefaultModel(services, models, defaultModel)
49+
}
50+
51+
application := app.NewChatApplication(services, models, defaultModel)
4752

4853
program := tea.NewProgram(application, tea.WithAltScreen())
4954

@@ -56,6 +61,29 @@ func startChatSession() error {
5661
return nil
5762
}
5863

64+
func validateAndSetDefaultModel(services *container.ServiceContainer, models []string, defaultModel string) string {
65+
modelFound := false
66+
for _, model := range models {
67+
if model == defaultModel {
68+
modelFound = true
69+
break
70+
}
71+
}
72+
73+
if !modelFound {
74+
fmt.Printf("⚠️ Default model '%s' is not available, showing model selection...\n", defaultModel)
75+
return ""
76+
}
77+
78+
if err := services.GetModelService().SelectModel(defaultModel); err != nil {
79+
fmt.Printf("⚠️ Failed to set default model: %v, showing model selection...\n", err)
80+
return ""
81+
}
82+
83+
fmt.Printf("🤖 Using default model: %s\n", defaultModel)
84+
return defaultModel
85+
}
86+
5987
func init() {
6088
rootCmd.AddCommand(chatCmd)
6189
}

cmd/config.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/inference-gateway/cli/config"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
var configCmd = &cobra.Command{
11+
Use: "config",
12+
Short: "Manage CLI configuration",
13+
Long: `Manage the Inference Gateway CLI configuration settings.`,
14+
}
15+
16+
var setModelCmd = &cobra.Command{
17+
Use: "set-model [MODEL_NAME]",
18+
Short: "Set the default model for chat sessions",
19+
Long: `Set the default model for chat sessions. When a default model is configured,
20+
the chat command will skip the model selection view and use the configured model directly.`,
21+
Args: cobra.ExactArgs(1),
22+
RunE: func(cmd *cobra.Command, args []string) error {
23+
modelName := args[0]
24+
return setDefaultModel(modelName)
25+
},
26+
}
27+
28+
func setDefaultModel(modelName string) error {
29+
cfg, err := config.LoadConfig("")
30+
if err != nil {
31+
return fmt.Errorf("failed to load config: %w", err)
32+
}
33+
34+
cfg.Chat.DefaultModel = modelName
35+
36+
if err := cfg.SaveConfig(""); err != nil {
37+
return fmt.Errorf("failed to save config: %w", err)
38+
}
39+
40+
fmt.Printf("✅ Default model set to: %s\n", modelName)
41+
fmt.Println("The chat command will now use this model by default and skip model selection.")
42+
return nil
43+
}
44+
45+
func init() {
46+
configCmd.AddCommand(setModelCmd)
47+
rootCmd.AddCommand(configCmd)
48+
}

config/config.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"bytes"
45
"fmt"
56
"os"
67
"path/filepath"
@@ -15,6 +16,7 @@ type Config struct {
1516
Output OutputConfig `yaml:"output"`
1617
Tools ToolsConfig `yaml:"tools"`
1718
Compact CompactConfig `yaml:"compact"`
19+
Chat ChatConfig `yaml:"chat"`
1820
}
1921

2022
// GatewayConfig contains gateway connection settings
@@ -53,6 +55,11 @@ type CompactConfig struct {
5355
OutputDir string `yaml:"output_dir"`
5456
}
5557

58+
// ChatConfig contains chat-related settings
59+
type ChatConfig struct {
60+
DefaultModel string `yaml:"default_model"`
61+
}
62+
5663
// DefaultConfig returns a default configuration
5764
func DefaultConfig() *Config {
5865
return &Config{
@@ -86,6 +93,9 @@ func DefaultConfig() *Config {
8693
Compact: CompactConfig{
8794
OutputDir: ".infer",
8895
},
96+
Chat: ChatConfig{
97+
DefaultModel: "",
98+
},
8999
}
90100
}
91101

@@ -136,12 +146,22 @@ func (c *Config) SaveConfig(configPath string) error {
136146
return fmt.Errorf("failed to create config directory: %w", err)
137147
}
138148

139-
data, err := yaml.Marshal(c)
140-
if err != nil {
149+
var buf bytes.Buffer
150+
encoder := yaml.NewEncoder(&buf)
151+
encoder.SetIndent(2)
152+
defer func() {
153+
if err := encoder.Close(); err != nil {
154+
logger.Error("Failed to close YAML encoder", "error", err)
155+
}
156+
}()
157+
158+
if err := encoder.Encode(c); err != nil {
141159
logger.Error("Failed to marshal config", "error", err)
142160
return fmt.Errorf("failed to marshal config: %w", err)
143161
}
144162

163+
data := buf.Bytes()
164+
145165
logger.Debug("Writing config file", "path", configPath, "size", len(data))
146166
if err := os.WriteFile(configPath, data, 0644); err != nil {
147167
logger.Error("Failed to write config file", "path", configPath, "error", err)

internal/app/chat_application.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,17 @@ type ChatApplication struct {
3939
}
4040

4141
// NewChatApplication creates a new chat application with all dependencies injected
42-
func NewChatApplication(services *container.ServiceContainer, models []string) *ChatApplication {
42+
func NewChatApplication(services *container.ServiceContainer, models []string, defaultModel string) *ChatApplication {
43+
initialView := handlers.ViewModelSelection
44+
if defaultModel != "" {
45+
initialView = handlers.ViewChat
46+
}
47+
4348
app := &ChatApplication{
4449
services: services,
4550
availableModels: models,
4651
state: &handlers.AppState{
47-
CurrentView: handlers.ViewModelSelection,
52+
CurrentView: initialView,
4853
Width: 80,
4954
Height: 24,
5055
Data: make(map[string]interface{}),
@@ -58,7 +63,11 @@ func NewChatApplication(services *container.ServiceContainer, models []string) *
5863

5964
app.modelSelector = ui.NewModelSelector(models, services.GetModelService(), services.GetTheme())
6065

61-
app.focusedComponent = nil
66+
if initialView == handlers.ViewChat {
67+
app.focusedComponent = app.inputView
68+
} else {
69+
app.focusedComponent = nil
70+
}
6271

6372
app.messageRouter = services.GetMessageRouter()
6473

0 commit comments

Comments
 (0)