prompt draft save/restore in Chat Session view

This commit is contained in:
Rob Colbert 2026-05-17 17:39:34 -04:00
parent e7857016e3
commit a23224279e
4 changed files with 68 additions and 13 deletions

View File

@ -30,6 +30,7 @@
"@replit/codemirror-lang-csharp": "^6.2.0",
"@uiw/codemirror-theme-tomorrow-night-blue": "^4.25.9",
"@uiw/react-codemirror": "^4.25.9",
"lucide-react": "^1.16.0",
"marked": "^16.4.2",
"react": "^19.2.5",
"react-dom": "^19.2.5",

View File

@ -5,6 +5,7 @@ interface ExpandedPromptEditorProps {
onPromptChange: (value: string) => void;
onSubmit: (e: React.FormEvent) => void;
onClose: () => void;
onClearPrompt: () => void;
disabled: boolean;
placeholder: string;
isProcessing: boolean;
@ -17,6 +18,7 @@ export default function ExpandedPromptEditor({
onPromptChange,
onSubmit,
onClose,
onClearPrompt,
disabled,
placeholder,
isProcessing,
@ -106,6 +108,15 @@ export default function ExpandedPromptEditor({
<div className="flex items-center justify-between px-4 py-2 bg-bg-tertiary border-b border-border-subtle shrink-0">
<span className="text-sm font-medium text-text-secondary">Prompt Editor</span>
<div className="flex items-center gap-2">
<button
type="button"
onClick={onClearPrompt}
disabled={!promptInput}
className="px-4 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-secondary rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-sm"
title="Clear prompt text"
>
Clear
</button>
{isProcessing ? (
<button
type="button"
@ -153,7 +164,7 @@ export default function ExpandedPromptEditor({
{/* Footer — Keyboard Shortcuts */}
<div className="px-4 py-2 text-xs text-text-muted border-t border-border-subtle shrink-0 flex items-center justify-between">
<span>Esc to close · Ctrl+Enter to send</span>
<span>Esc to close · Ctrl+Enter to send · Clear to reset</span>
<span className="font-mono">{promptInput.length} chars</span>
</div>
</div>

View File

@ -1,5 +1,6 @@
import { useState, useEffect, useRef, useContext, useCallback } from 'react';
import { useParams, useNavigate, useLocation } from 'react-router-dom';
import { BrushCleaning, Maximize2 } from 'lucide-react';
import { socketClient } from '../lib/socket';
import { chatSessionApi, projectApi, providerApi, type ChatSession, type ChatTurn, type ChatTurnBlock, ChatTurnStats, ChatSessionMode, type AiProvider, type Project } from '../lib/api';
import { WorkspaceMode } from '../lib/types';
@ -66,7 +67,13 @@ export default function ChatSessionView() {
const [project, setProject] = useState<Project | null>(null);
const [session, setSession] = useState<ChatSession | null>(null);
const [turns, setTurns] = useState<ChatTurn[]>([]);
const [promptInput, setPromptInput] = useState('');
const [promptInput, setPromptInput] = useState(() => {
if (sessionId) {
const saved = localStorage.getItem(`dtp_draft_prompt_${sessionId}`);
if (saved) return saved;
}
return '';
});
const [isProcessing, setIsProcessing] = useState(false);
const [isAborting, setIsAborting] = useState(false);
const [loading, setLoading] = useState(true);
@ -125,6 +132,17 @@ export default function ChatSessionView() {
scrollToBottom();
}, [turns]);
// Persist draft prompt to localStorage so it survives page refreshes and navigation
useEffect(() => {
if (!sessionId) return;
const key = `dtp_draft_prompt_${sessionId}`;
if (promptInput) {
localStorage.setItem(key, promptInput);
} else {
localStorage.removeItem(key);
}
}, [promptInput, sessionId]);
useEffect(() => {
return () => {
if (toastTimerRef.current) {
@ -1283,6 +1301,7 @@ export default function ChatSessionView() {
setIsExpandedEditor(false);
}}
onClose={() => setIsExpandedEditor(false)}
onClearPrompt={() => setPromptInput('')}
disabled={promptDisabled}
placeholder={promptPlaceholder}
isProcessing={isProcessing}
@ -1315,17 +1334,26 @@ export default function ChatSessionView() {
{/* Prompt Input */}
<div className="border-t border-border-subtle p-4 bg-bg-secondary shrink-0">
<form onSubmit={handleSubmitPrompt} className="flex gap-2">
<div className="flex flex-col self-end">
<button
type="button"
onClick={() => setIsExpandedEditor(true)}
disabled={promptDisabled || !!editorFilePath}
className="p-2 text-text-muted hover:text-text-primary hover:bg-bg-tertiary rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed self-end"
className="p-2 text-text-muted hover:text-text-primary hover:bg-bg-tertiary rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
title="Open expanded prompt editor"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
</svg>
<Maximize2 size={16} />
</button>
<button
type="button"
onClick={() => setPromptInput('')}
disabled={!promptInput}
className="p-2 text-text-muted hover:text-text-primary hover:bg-bg-tertiary rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
title="Clear prompt"
>
<BrushCleaning size={16} />
</button>
</div>
<textarea
ref={inputRef}
value={promptInput}

View File

@ -285,9 +285,15 @@ importers:
'@uiw/react-codemirror':
specifier: ^4.25.9
version: 4.25.9(@babel/runtime@7.29.2)(@codemirror/autocomplete@6.20.2)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.6)(@codemirror/search@6.7.0)(@codemirror/state@6.6.0)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.42.1)(codemirror@6.0.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
lucide-react:
specifier: ^1.16.0
version: 1.16.0(react@19.2.5)
marked:
specifier: ^16.4.2
version: 16.4.2
react:
specifier: ^19.2.5
version: 19.2.5
slug:
specifier: ^11.0.1
version: 11.0.1
@ -2332,6 +2338,11 @@ packages:
resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==}
engines: {node: 20 || >=22}
lucide-react@1.16.0:
resolution: {integrity: sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==}
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
luxon@3.6.1:
resolution: {integrity: sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==}
engines: {node: '>=12'}
@ -5487,6 +5498,10 @@ snapshots:
lru-cache@11.3.5: {}
lucide-react@1.16.0(react@19.2.5):
dependencies:
react: 19.2.5
luxon@3.6.1: {}
lz-string@1.5.0: {}