Skip to content

Commit e128cd5

Browse files
committed
updating aichat accepting code as input
1 parent 8ba5998 commit e128cd5

3 files changed

Lines changed: 116 additions & 89 deletions

File tree

internal/ui/aichat.go

Lines changed: 86 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strings"
1313
"time"
1414

15+
"github.com/atotto/clipboard"
1516
tea "github.com/charmbracelet/bubbletea"
1617
"github.com/charmbracelet/lipgloss"
1718
"github.com/user/terminal-intelligence/internal/ai"
@@ -45,46 +46,47 @@ import (
4546
// - DisplayNotification: Shows fix results with distinct styling
4647
// - GetLastAssistantResponse: Retrieves last AI response for insertion
4748
type AIChatPane struct {
48-
messages []types.ChatMessage // Conversation history
49-
inputBuffer string // Current input being typed
50-
aiClient ai.AIClient // AI service client
51-
model string // AI model to use
52-
provider string // "ollama" or "gemini"
53-
scrollOffset int // Vertical scroll offset for responses
54-
width int // Pane width
55-
height int // Pane height
56-
focused bool // Whether this pane is focused
57-
streaming bool // Whether AI is currently generating
58-
copyMode bool // Whether in code block selection mode
59-
viewMode bool // Whether viewing a code block
60-
codeBlocks []string // Extracted code blocks from responses
61-
selectedBlock int // Currently selected code block index
62-
lastSelectedCode string // Last selected code block content
63-
lastKeyPressed string // Last key pressed (for debugging)
64-
activeArea int // 0: Input, 1: Response
65-
viewModeScroll int // Scroll offset for code block view mode
66-
viewModeScrollX int // Horizontal scroll offset for code block view mode
67-
configMode bool // Whether in config editor mode
68-
configFields []string // Config field names
69-
configValues []string // Config field values
70-
selectedField int // Currently selected config field
71-
editingField bool // Whether currently editing a field
72-
editBuffer string // Buffer for editing field value
73-
editCursorPos int // Cursor position within edit buffer
74-
terminalMode bool // Whether terminal execution is active
75-
cmdRunning bool // Whether the command is currently running
76-
terminalOutput []string // Output lines from terminal execution
77-
stdinWriter io.WriteCloser // Stdin pipe for sending input to running command
78-
terminalInput string // Current input line being typed in terminal mode
79-
aiAvailable bool // Whether the AI service is reachable
80-
aiChecked bool // Whether the availability check has completed
81-
suggestedFile string // Filename suggested by AI for the current code block
82-
workingDir string // Directory from which to execute commands
83-
codeBlockInfos []dirtracker.CodeBlockInfo // Code blocks with language tags
84-
blockDirMappings []string // Effective dir per code block index
85-
workspaceRoot string // From AppConfig.WorkspaceDir
86-
runningCmd *exec.Cmd // Currently running command process (for kill support)
87-
processKilled bool // Whether the process was killed by user (Ctrl+K)
49+
messages []types.ChatMessage // Conversation history
50+
inputBuffer string // Current input being typed
51+
aiClient ai.AIClient // AI service client
52+
model string // AI model to use
53+
provider string // "ollama" or "gemini"
54+
scrollOffset int // Vertical scroll offset for responses
55+
width int // Pane width
56+
height int // Pane height
57+
focused bool // Whether this pane is focused
58+
streaming bool // Whether AI is currently generating
59+
copyMode bool // Whether in code block selection mode
60+
viewMode bool // Whether viewing a code block
61+
codeBlocks []string // Extracted code blocks from responses
62+
selectedBlock int // Currently selected code block index
63+
lastSelectedCode string // Last selected code block content
64+
lastKeyPressed string // Last key pressed (for debugging)
65+
activeArea int // 0: Input, 1: Response
66+
viewModeScroll int // Scroll offset for code block view mode
67+
viewModeScrollX int // Horizontal scroll offset for code block view mode
68+
configMode bool // Whether in config editor mode
69+
configFields []string // Config field names
70+
configValues []string // Config field values
71+
selectedField int // Currently selected config field
72+
editingField bool // Whether currently editing a field
73+
editBuffer string // Buffer for editing field value
74+
editCursorPos int // Cursor position within edit buffer
75+
terminalMode bool // Whether terminal execution is active
76+
cmdRunning bool // Whether the command is currently running
77+
terminalOutput []string // Output lines from terminal execution
78+
stdinWriter io.WriteCloser // Stdin pipe for sending input to running command
79+
terminalInput string // Current input line being typed in terminal mode
80+
aiAvailable bool // Whether the AI service is reachable
81+
aiChecked bool // Whether the availability check has completed
82+
suggestedFile string // Filename suggested by AI for the current code block
83+
workingDir string // Directory from which to execute commands
84+
codeBlockInfos []dirtracker.CodeBlockInfo // Code blocks with language tags
85+
blockDirMappings []string // Effective dir per code block index
86+
workspaceRoot string // From AppConfig.WorkspaceDir
87+
runningCmd *exec.Cmd // Currently running command process (for kill support)
88+
processKilled bool // Whether the process was killed by user (Ctrl+K)
89+
lastKeystrokeTime time.Time // Last keypress timestamp to detect rapid/terminal paste
8890
}
8991

9092
// AIResponseMsg is sent when AI response chunk is received.
@@ -201,20 +203,21 @@ func NewAIChatPane(client ai.AIClient, model string, provider string, workspaceR
201203
workspaceRoot = cwd
202204
}
203205
return &AIChatPane{
204-
messages: []types.ChatMessage{},
205-
inputBuffer: "",
206-
aiClient: client,
207-
model: model,
208-
provider: provider,
209-
scrollOffset: 0,
210-
width: 0,
211-
height: 0,
212-
focused: false,
213-
streaming: false,
214-
activeArea: 0, // 0: Input, 1: Response
215-
terminalOutput: []string{},
216-
workingDir: cwd,
217-
workspaceRoot: workspaceRoot,
206+
messages: []types.ChatMessage{},
207+
inputBuffer: "",
208+
aiClient: client,
209+
model: model,
210+
provider: provider,
211+
scrollOffset: 0,
212+
width: 0,
213+
height: 0,
214+
focused: false,
215+
streaming: false,
216+
activeArea: 0, // 0: Input, 1: Response
217+
terminalOutput: []string{},
218+
workingDir: cwd,
219+
workspaceRoot: workspaceRoot,
220+
lastKeystrokeTime: time.Now(),
218221
}
219222
}
220223

@@ -1159,6 +1162,11 @@ func (a *AIChatPane) handleKeyPress(msg tea.KeyMsg) tea.Cmd {
11591162
return nil
11601163
}
11611164

1165+
// Input area active - handle typing
1166+
now := time.Now()
1167+
isRapid := now.Sub(a.lastKeystrokeTime) < 25*time.Millisecond
1168+
a.lastKeystrokeTime = now
1169+
11621170
// Handle input based on active area
11631171
if a.activeArea == 1 {
11641172
// Response area active - handle scrolling
@@ -1192,7 +1200,21 @@ func (a *AIChatPane) handleKeyPress(msg tea.KeyMsg) tea.Cmd {
11921200
} else {
11931201
// Input area active - handle typing
11941202
switch msg.String() {
1203+
case "ctrl+v":
1204+
// Paste directly from clipboard into input buffer
1205+
content, err := clipboard.ReadAll()
1206+
if err == nil {
1207+
// Normalize line endings
1208+
content = strings.ReplaceAll(content, "\r\n", "\n")
1209+
a.inputBuffer += content
1210+
}
1211+
return nil
11951212
case "enter":
1213+
// Treat rapid enters (e.g., from native right-click paste) as newlines
1214+
if msg.Paste || isRapid {
1215+
a.inputBuffer += "\n"
1216+
return nil
1217+
}
11961218
// Send message when Enter is pressed
11971219
if a.inputBuffer != "" {
11981220
message := a.inputBuffer
@@ -1205,9 +1227,18 @@ func (a *AIChatPane) handleKeyPress(msg tea.KeyMsg) tea.Cmd {
12051227
case "backspace":
12061228
// Delete last character from input buffer
12071229
if len(a.inputBuffer) > 0 {
1208-
a.inputBuffer = a.inputBuffer[:len(a.inputBuffer)-1]
1230+
runes := []rune(a.inputBuffer)
1231+
if len(runes) > 0 {
1232+
a.inputBuffer = string(runes[:len(runes)-1])
1233+
}
12091234
}
12101235
default:
1236+
if msg.Paste {
1237+
if len(msg.Runes) > 0 {
1238+
a.inputBuffer += string(msg.Runes)
1239+
}
1240+
return nil
1241+
}
12111242
// Add character to input buffer
12121243
// Check for printable characters to avoid control chars
12131244
if len(msg.String()) == 1 {

internal/ui/app.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@ func New(config *types.AppConfig, buildNumber string) *App {
184184
// This is part of the Bubble Tea Model interface.
185185
// Currently returns nil as no initial commands are needed.
186186
func (a *App) Init() tea.Cmd {
187-
return a.aiPane.CheckAIAvailability()
187+
return tea.Batch(
188+
a.aiPane.CheckAIAvailability(),
189+
tea.EnableBracketedPaste,
190+
)
188191
}
189192

190193
// Update handles messages and updates application state.

main.go

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,73 +11,66 @@ import (
1111
)
1212

1313
var version = "0.0.2.3"
14-
15-
// Build number (injected at compile time via -ldflags)
1614
var buildNumber = "dev"
1715

1816
func main() {
19-
// Load configuration
17+
if len(os.Args) > 1 && (os.Args[1] == "-v" || os.Args[1] == "--version") {
18+
fmt.Printf("Terminal Intelligence v%s (build %s)\n", version, buildNumber)
19+
return
20+
}
21+
22+
// 1. Initialize Default Config
2023
appCfg := types.DefaultConfig()
2124

22-
// Check for config file and load if present
25+
// 2. Load Configuration Logic
2326
configPath, err := config.ConfigFilePath()
2427
if err != nil {
25-
fmt.Fprintf(os.Stderr, "Error determining config file path: %v\n", err)
28+
// Fatal error: cannot even determine where config should be
29+
fmt.Fprintf(os.Stderr, "Error determining config path: %v\n", err)
2630
os.Exit(1)
2731
}
2832

2933
if _, err := os.Stat(configPath); err == nil {
30-
// Config file exists — load, validate, and apply
34+
// File exists: Load and validate
3135
jcfg, err := config.LoadFromFile(configPath)
3236
if err != nil {
33-
fmt.Fprintf(os.Stderr, "Error loading config file: %v\n", err)
37+
fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
3438
os.Exit(1)
3539
}
3640
if err := config.Validate(jcfg); err != nil {
37-
fmt.Fprintf(os.Stderr, "Invalid config file: %v\n", err)
41+
fmt.Fprintf(os.Stderr, "Invalid config: %v\n", err)
3842
os.Exit(1)
3943
}
4044
config.ApplyToAppConfig(jcfg, appCfg)
4145
} else if os.IsNotExist(err) {
42-
// Config file doesn't exist — create default config
43-
createdPath, err := config.CreateDefaultConfig()
44-
if err != nil {
45-
fmt.Fprintf(os.Stderr, "Warning: Could not create default config file: %v\n", err)
46-
fmt.Fprintf(os.Stderr, "Continuing with defaults...\n")
47-
} else {
48-
fmt.Fprintf(os.Stderr, "Created default config file at: %s\n", createdPath)
49-
fmt.Fprintf(os.Stderr, "Edit this file to customize your settings.\n")
50-
fmt.Fprintf(os.Stderr, "Continuing with defaults for this session...\n\n")
51-
}
46+
// File missing: Create it once and move on
47+
_, _ = config.CreateDefaultConfig()
48+
// Note: We don't exit here; we just continue with appCfg defaults
5249
}
5350

54-
// Default workspace to current working directory if not set
51+
// 3. Runtime Defaults (Do NOT save these back to the JSON file)
52+
// This ensures the app respects the current folder it's opened in
53+
// unless the user has explicitly hardcoded a path in their config.
5554
if appCfg.WorkspaceDir == "" {
56-
cwd, err := os.Getwd()
57-
if err == nil {
55+
if cwd, err := os.Getwd(); err == nil {
5856
appCfg.WorkspaceDir = cwd
57+
} else {
58+
appCfg.WorkspaceDir = "." // Fallback
5959
}
6060
}
6161

62-
// Save updated workspace back to config.json
63-
jcfgToSave := config.AppConfigToJSONConfig(appCfg)
64-
if data, err := config.ToJSON(jcfgToSave); err == nil {
65-
_ = os.WriteFile(configPath, data, 0644)
66-
}
67-
68-
// Create workspace directory if it doesn't exist
62+
// 4. Ensure Workspace exists
6963
if err := os.MkdirAll(appCfg.WorkspaceDir, 0755); err != nil {
70-
fmt.Fprintf(os.Stderr, "Error creating workspace directory: %v\n", err)
64+
fmt.Fprintf(os.Stderr, "Error creating workspace: %v\n", err)
7165
os.Exit(1)
7266
}
7367

74-
// Create the application
68+
// 5. Run Application
7569
app := ui.New(appCfg, buildNumber)
76-
77-
// Start the Bubble Tea program
7870
p := tea.NewProgram(app, tea.WithAltScreen())
71+
7972
if _, err := p.Run(); err != nil {
80-
fmt.Fprintf(os.Stderr, "Error running application: %v\n", err)
73+
fmt.Fprintf(os.Stderr, "Application error: %v\n", err)
8174
os.Exit(1)
8275
}
8376
}

0 commit comments

Comments
 (0)