Skip to content
This repository was archived by the owner on Jan 30, 2026. It is now read-only.

Commit dab3dbb

Browse files
authored
fix: don't disable input while waiting for session + persist drafts (#107)
* fix: don't disable input while waiting for session + persist drafts Previously, the input was disabled when hasSession was false, showing 'Waiting for chat session to be established...' which could leave users stuck unable to type. Changes: - Remove hasSession check from isDisabled condition - Remove hasSession-based placeholder message - Add localStorage persistence for message drafts per conversation - Drafts are cleared automatically when message is sent This allows users to start typing immediately while the session establishes in the background. If they try to send before the session is ready, they'll get an error toast rather than being blocked. Fixes #58 * fix: remove unused _hasSession variable to fix TypeScript error * fix: remove unused hasSession$ prop from ChatInput Previously ChatInput accepted hasSession$ prop to control when input was enabled. After the last change that removed the hasSession check from isDisabled, this prop became unused but was still in the interface. This commit: - Removes hasSession$ from ChatInput Props interface - Removes hasSession$ from component destructuring - Removes hasSession$ prop from ConversationContent and WelcomeView - Removes unused hasSession$ variable from WelcomeView Fixes TypeScript error TS6133: 'hasSession$' is declared but never read * fix: preserve draft in localStorage until overwritten to prevent data loss Previously, drafts were cleared from localStorage immediately when the message field became empty (after submit). This caused data loss if the send operation failed, as the draft was already gone. Now we only persist non-empty messages without auto-clearing. The draft remains in localStorage until the user types a new message, ensuring it's recoverable if send fails. Addresses Greptile review feedback on PR #107.
1 parent c21b7bc commit dab3dbb

File tree

3 files changed

+22
-12
lines changed

3 files changed

+22
-12
lines changed

src/components/ChatInput.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ interface Props {
3131
isReadOnly?: boolean;
3232
defaultModel?: string;
3333
autoFocus$: Observable<boolean>;
34-
hasSession$: Observable<boolean>;
3534
value?: string;
3635
onChange?: (value: string) => void;
3736
}
@@ -206,7 +205,6 @@ export const ChatInput: FC<Props> = ({
206205
isReadOnly,
207206
defaultModel = '',
208207
autoFocus$,
209-
hasSession$,
210208
value,
211209
onChange,
212210
}) => {
@@ -221,9 +219,26 @@ export const ChatInput: FC<Props> = ({
221219
const conversation$ = conversationId ? conversations$.get(conversationId) : null;
222220
const conversationModel = conversation$?.chatConfig?.get()?.chat?.model;
223221

224-
const [internalMessage, setInternalMessage] = useState('');
222+
// Initialize message from localStorage for persistence across page reloads
223+
const storageKey = conversationId ? `gptme-draft-${conversationId}` : 'gptme-draft-new';
224+
const [internalMessage, setInternalMessage] = useState(() => {
225+
if (typeof window !== 'undefined') {
226+
return localStorage.getItem(storageKey) || '';
227+
}
228+
return '';
229+
});
225230
const [streamingEnabled, setStreamingEnabled] = useState(true);
226231

232+
// Persist message to localStorage when it changes
233+
// Note: We only save non-empty messages, we don't clear on empty.
234+
// This ensures drafts persist until a new message is typed, preventing
235+
// data loss if send fails (the draft would already be cleared otherwise).
236+
useEffect(() => {
237+
if (typeof window !== 'undefined' && internalMessage) {
238+
localStorage.setItem(storageKey, internalMessage);
239+
}
240+
}, [internalMessage, storageKey]);
241+
227242
// Track if user has explicitly selected a model (temporary override)
228243
const [hasExplicitModelSelection, setHasExplicitModelSelection] = useState(false);
229244
const [selectedModel, setSelectedModel] = useState('');
@@ -273,17 +288,15 @@ export const ChatInput: FC<Props> = ({
273288
const autoFocus = use$(autoFocus$);
274289
const conversation = conversationId ? use$(conversations$.get(conversationId)) : undefined;
275290
const isGenerating = conversation?.isGenerating || !!conversation?.executingTool;
276-
const hasSession = use$(hasSession$);
277-
278291
const placeholder = isReadOnly
279292
? 'This is a demo conversation (read-only)'
280293
: !isConnected
281294
? 'Connect to gptme to send messages'
282-
: !hasSession
283-
? 'Waiting for chat session to be established...'
284-
: "What's on your mind...";
295+
: "What's on your mind...";
285296

286-
const isDisabled = isReadOnly || !isConnected || !hasSession;
297+
// Don't disable input while waiting for session - let users type
298+
// Session will be established by the time they finish typing
299+
const isDisabled = isReadOnly || !isConnected;
287300

288301
// Focus the textarea when autoFocus is true and component is interactive
289302
useEffect(() => {

src/components/ConversationContent.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,6 @@ export const ConversationContent: FC<Props> = ({ conversationId, isReadOnly }) =
249249
onSend={handleSendMessage}
250250
onInterrupt={interruptGeneration}
251251
isReadOnly={isReadOnly}
252-
hasSession$={hasSession$}
253252
defaultModel={defaultModel || undefined}
254253
autoFocus$={shouldFocus$}
255254
/>

src/components/WelcomeView.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export const WelcomeView = ({ onToggleHistory }: { onToggleHistory: () => void }
2020

2121
// Create observables that ChatInput expects
2222
const autoFocus$ = observable(true);
23-
const hasSession$ = observable(true); // Always true for welcome view
2423

2524
const handleSend = async (message: string, options?: ChatOptions) => {
2625
if (!message.trim() || !isConnected) return;
@@ -97,7 +96,6 @@ export const WelcomeView = ({ onToggleHistory }: { onToggleHistory: () => void }
9796
<ChatInput
9897
onSend={handleSend}
9998
autoFocus$={autoFocus$}
100-
hasSession$={hasSession$}
10199
value={inputValue}
102100
onChange={setInputValue}
103101
/>

0 commit comments

Comments
 (0)