@@ -70,6 +70,8 @@ type AIChatPane struct {
7070 terminalOutput []string // Output lines from terminal execution
7171 stdinWriter io.WriteCloser // Stdin pipe for sending input to running command
7272 terminalInput string // Current input line being typed in terminal mode
73+ aiAvailable bool // Whether the AI service is reachable
74+ aiChecked bool // Whether the availability check has completed
7375}
7476
7577// AIResponseMsg is sent when AI response chunk is received.
@@ -114,6 +116,11 @@ type TerminalDoneMsg struct {
114116 Err error
115117}
116118
119+ // AIAvailabilityMsg is sent when the AI availability check completes.
120+ type AIAvailabilityMsg struct {
121+ Available bool
122+ }
123+
117124// NewAIChatPane creates a new AI chat pane.
118125// Initializes an empty conversation with the specified AI client and model.
119126// Defaults to "llama2" model if none is specified.
@@ -145,6 +152,53 @@ func NewAIChatPane(client ai.AIClient, model string, provider string) *AIChatPan
145152 }
146153}
147154
155+ // CheckAIAvailability returns a tea.Cmd that checks if the configured AI
156+ // provider and model are reachable, sending an AIAvailabilityMsg with the result.
157+ func (a * AIChatPane ) CheckAIAvailability () tea.Cmd {
158+ client := a .aiClient
159+ model := a .model
160+ provider := a .provider
161+
162+ return func () tea.Msg {
163+ if client == nil {
164+ return AIAvailabilityMsg {Available : false }
165+ }
166+
167+ // Check if the service is reachable
168+ available , err := client .IsAvailable ()
169+ if err != nil || ! available {
170+ return AIAvailabilityMsg {Available : false }
171+ }
172+
173+ // For Ollama, also verify the specific model exists
174+ if provider == "ollama" {
175+ models , err := client .ListModels ()
176+ if err != nil {
177+ return AIAvailabilityMsg {Available : false }
178+ }
179+ found := false
180+ for _ , m := range models {
181+ if m == model || m == model + ":latest" {
182+ found = true
183+ break
184+ }
185+ }
186+ if ! found {
187+ return AIAvailabilityMsg {Available : false }
188+ }
189+ }
190+
191+ // For Gemini, try listing models to confirm API key + model access
192+ if provider == "gemini" {
193+ // IsAvailable already checks API key; do a lightweight generate test
194+ // by just confirming the client was created with a key.
195+ // The real validation happens on first request.
196+ }
197+
198+ return AIAvailabilityMsg {Available : true }
199+ }
200+ }
201+
148202// SetActiveArea sets the active area (0: Input, 1: Response).
149203// Used to switch focus between input and response areas.
150204//
@@ -383,6 +437,10 @@ func (a *AIChatPane) Update(msg tea.Msg) tea.Cmd {
383437 a .viewModeScroll = len (a .terminalOutput )
384438 }
385439 return nil
440+ case AIAvailabilityMsg :
441+ a .aiChecked = true
442+ a .aiAvailable = msg .Available
443+ return nil
386444 }
387445
388446 return nil
@@ -983,13 +1041,13 @@ func (a *AIChatPane) View() string {
9831041
9841042 // Calculate heights - input gets 2 lines (prompt + status), rest for responses
9851043 maxInputLines := 3
986- inputWidth := a .width - 8 // Account for border, padding, and "TI > " prefix
1044+ inputWidth := a .width - 15 // Account for border, padding, and "ai-assist > " prefix
9871045 if inputWidth < 10 {
9881046 inputWidth = 10
9891047 }
9901048
9911049 // Calculate how many lines the input will actually take
992- promptText := "TI > " + a .inputBuffer
1050+ promptText := "ai-assist > " + a .inputBuffer
9931051 if a .focused && a .activeArea == 0 {
9941052 promptText += "█" // Cursor
9951053 }
@@ -1011,13 +1069,23 @@ func (a *AIChatPane) View() string {
10111069
10121070 // Build status line to show inside the input box
10131071 statusText := "⚡ " + a .provider + "/" + a .model
1014- if a . streaming {
1015- statusText += " ⏳ generating... "
1016- } else {
1072+ if ! a . aiChecked {
1073+ statusText += " … checking "
1074+ } else if a . aiAvailable {
10171075 statusText += " ✓ ready"
1076+ } else {
1077+ statusText += " ✗ No AI Accessible"
1078+ }
1079+ statusColor := "15" // white
1080+ if a .aiChecked && ! a .aiAvailable {
1081+ statusColor = "196" // red for unavailable
1082+ }
1083+ if a .streaming {
1084+ statusText = "⚡ " + a .provider + "/" + a .model + " ⏳ generating..."
1085+ statusColor = "15"
10181086 }
10191087 statusLine := lipgloss .NewStyle ().
1020- Foreground (lipgloss .Color ("240" )).
1088+ Foreground (lipgloss .Color (statusColor )).
10211089 Render (statusText )
10221090
10231091 // Add status as an extra line inside the input box
0 commit comments