Skip to content

Commit 1b5e256

Browse files
committed
UX improvements for connecting chatgpt
1 parent 25f9af5 commit 1b5e256

File tree

3 files changed

+111
-59
lines changed

3 files changed

+111
-59
lines changed

cli/src/components/chat-input-bar.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ export const ChatInputBar = ({
200200
return <InputModeBanner />
201201
}
202202

203+
// ChatGPT connect mode: show only the connect panel (no input box)
204+
if (inputMode === 'connect:chatgpt') {
205+
return <InputModeBanner />
206+
}
207+
203208
// Handle input changes with special mode entry detection
204209
const handleInputChange = (value: InputValue) => {
205210
// Detect entering bash mode: user typed exactly '!' when in default mode

cli/src/components/chatgpt-connect-banner.tsx

Lines changed: 105 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import React, { useEffect, useState } from 'react'
22

3-
import { BottomBanner } from './bottom-banner'
43
import { Button } from './button'
54
import { useTheme } from '../hooks/use-theme'
6-
import { useChatStore } from '../state/chat-store'
75
import {
86
connectChatGptOAuth,
97
disconnectChatGptOAuth,
108
exchangeChatGptCodeForTokens,
119
getChatGptOAuthStatus,
1210
stopChatGptOAuthServer,
1311
} from '../utils/chatgpt-oauth'
12+
import { BORDER_CHARS } from '../utils/ui-constants'
1413

1514
type FlowState =
1615
| 'checking'
@@ -20,36 +19,40 @@ type FlowState =
2019
| 'error'
2120

2221
export const ChatGptConnectBanner = () => {
23-
const setInputMode = useChatStore((state) => state.setInputMode)
2422
const theme = useTheme()
2523
const [flowState, setFlowState] = useState<FlowState>('checking')
2624
const [error, setError] = useState<string | null>(null)
25+
const [authUrl, setAuthUrl] = useState<string | null>(null)
26+
const [hovered, setHovered] = useState(false)
2727

2828
useEffect(() => {
2929
const status = getChatGptOAuthStatus()
30-
if (status.connected) {
30+
if (!status.connected) {
31+
setFlowState('waiting-for-code')
32+
const result = connectChatGptOAuth()
33+
setAuthUrl(result.authUrl)
34+
result.credentials
35+
.then(() => {
36+
setFlowState('connected')
37+
})
38+
.catch((err) => {
39+
setError(err instanceof Error ? err.message : 'Failed to connect')
40+
setFlowState('error')
41+
})
42+
} else {
3143
setFlowState('connected')
32-
return
3344
}
3445

35-
setFlowState('waiting-for-code')
36-
connectChatGptOAuth()
37-
.then(() => {
38-
setFlowState('connected')
39-
})
40-
.catch((err) => {
41-
setError(err instanceof Error ? err.message : 'Failed to connect')
42-
setFlowState('error')
43-
})
44-
4546
return () => {
4647
stopChatGptOAuthServer()
4748
}
4849
}, [])
4950

50-
const handleConnect = async () => {
51+
const handleConnect = () => {
5152
setFlowState('waiting-for-code')
52-
connectChatGptOAuth()
53+
const result = connectChatGptOAuth()
54+
setAuthUrl(result.authUrl)
55+
result.credentials
5356
.then(() => {
5457
setFlowState('connected')
5558
})
@@ -64,67 +67,111 @@ export const ChatGptConnectBanner = () => {
6467
setFlowState('not-connected')
6568
}
6669

67-
const handleClose = () => setInputMode('default')
70+
const panelStyle = {
71+
width: '100%' as const,
72+
borderStyle: 'single' as const,
73+
borderColor: theme.border,
74+
customBorderChars: BORDER_CHARS,
75+
paddingLeft: 1,
76+
paddingRight: 1,
77+
}
6878

69-
if (flowState === 'connected') {
70-
const status = getChatGptOAuthStatus()
71-
const connectedDate = status.connectedAt
72-
? new Date(status.connectedAt).toLocaleDateString()
73-
: 'Unknown'
79+
const actionButtonStyle = {
80+
flexDirection: 'row' as const,
81+
alignItems: 'center' as const,
82+
paddingLeft: 1,
83+
paddingRight: 1,
84+
borderStyle: 'single' as const,
85+
borderColor: hovered ? theme.foreground : theme.border,
86+
customBorderChars: BORDER_CHARS,
87+
}
88+
89+
const escHint = (
90+
<text style={{ fg: theme.muted }}> esc</text>
91+
)
7492

93+
if (flowState === 'connected') {
7594
return (
76-
<BottomBanner borderColorKey="success" onClose={handleClose}>
77-
<box style={{ flexDirection: 'column', gap: 0 }}>
78-
<text style={{ fg: theme.success }}>✓ Connected to ChatGPT</text>
79-
<text style={{ fg: theme.muted, marginTop: 1 }}>
80-
Streaming requests for supported OpenAI models can now route directly through your ChatGPT subscription.
81-
</text>
82-
<box style={{ flexDirection: 'row', gap: 2, marginTop: 1 }}>
83-
<text style={{ fg: theme.muted }}>Since {connectedDate}</text>
84-
<text style={{ fg: theme.muted }}>·</text>
85-
<Button onClick={handleDisconnect}>
86-
<text style={{ fg: theme.error }}>Disconnect</text>
87-
</Button>
88-
</box>
95+
<box style={{ ...panelStyle, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
96+
<text style={{ fg: theme.foreground }}>✓ ChatGPT connected</text>
97+
<box style={{ flexDirection: 'row', gap: 1, alignItems: 'center' }}>
98+
<Button
99+
style={actionButtonStyle}
100+
onClick={handleDisconnect}
101+
onMouseOver={() => setHovered(true)}
102+
onMouseOut={() => setHovered(false)}
103+
>
104+
<text wrapMode="none">
105+
<span fg={theme.muted}>Disconnect</span>
106+
</text>
107+
</Button>
108+
{escHint}
89109
</box>
90-
</BottomBanner>
110+
</box>
91111
)
92112
}
93113

94114
if (flowState === 'error') {
95115
return (
96-
<BottomBanner
97-
borderColorKey="error"
98-
text={`Error: ${error ?? 'Unknown error'}. Press Escape to close.`}
99-
onClose={handleClose}
100-
/>
116+
<box style={{ ...panelStyle, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
117+
<text style={{ fg: theme.error, flexShrink: 1 }}>
118+
{error ?? 'Unknown error'}
119+
</text>
120+
<box style={{ flexDirection: 'row', gap: 1, alignItems: 'center' }}>
121+
<Button
122+
style={actionButtonStyle}
123+
onClick={handleConnect}
124+
onMouseOver={() => setHovered(true)}
125+
onMouseOut={() => setHovered(false)}
126+
>
127+
<text wrapMode="none">
128+
<span fg={theme.foreground}>Retry</span>
129+
</text>
130+
</Button>
131+
{escHint}
132+
</box>
133+
</box>
101134
)
102135
}
103136

104137
if (flowState === 'waiting-for-code') {
105138
return (
106-
<BottomBanner borderColorKey="info" onClose={handleClose}>
107-
<box style={{ flexDirection: 'column', gap: 0 }}>
108-
<text style={{ fg: theme.info }}>Waiting for ChatGPT authorization</text>
109-
<text style={{ fg: theme.muted, marginTop: 1 }}>
110-
Complete sign-in in your browser — it should connect automatically.
111-
If not, paste the callback URL here.
112-
</text>
139+
<box style={{ ...panelStyle, flexDirection: 'column' }}>
140+
<box style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
141+
<text style={{ fg: theme.foreground }}>Connecting to ChatGPT...</text>
142+
{escHint}
113143
</box>
114-
</BottomBanner>
144+
<text style={{ fg: theme.muted }}>
145+
Sign in via your browser to connect.
146+
</text>
147+
{authUrl ? (
148+
<text style={{ fg: theme.muted }}>
149+
{authUrl}
150+
</text>
151+
) : null}
152+
</box>
115153
)
116154
}
117155

118-
return (
119-
<BottomBanner borderColorKey="info" onClose={handleClose}>
120-
<box style={{ flexDirection: 'column', gap: 0 }}>
121-
<text style={{ fg: theme.info }}>Connect to ChatGPT</text>
122-
<Button onClick={handleConnect}>
123-
<text style={{ fg: theme.link, marginTop: 1 }}>Click to connect →</text>
156+
if (flowState === 'not-connected') {
157+
return (
158+
<box style={{ ...panelStyle, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
159+
<Button
160+
style={actionButtonStyle}
161+
onClick={handleConnect}
162+
onMouseOver={() => setHovered(true)}
163+
onMouseOut={() => setHovered(false)}
164+
>
165+
<text wrapMode="none">
166+
<span fg={theme.link}>Connect to ChatGPT</span>
167+
</text>
124168
</Button>
169+
{escHint}
125170
</box>
126-
</BottomBanner>
127-
)
171+
)
172+
}
173+
174+
return null
128175
}
129176

130177
export async function handleChatGptAuthCode(code: string): Promise<{

cli/src/utils/input-modes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export const INPUT_MODE_CONFIGS: Record<InputMode, InputModeConfig> = {
123123
'connect:chatgpt': {
124124
icon: '🔐',
125125
color: 'info',
126-
placeholder: 'paste ChatGPT auth code or callback URL...',
126+
placeholder: 'authorizing in browser... press Escape to cancel',
127127
widthAdjustment: 3,
128128
showAgentModeToggle: false,
129129
disableSlashSuggestions: true,

0 commit comments

Comments
 (0)