ex[amded prompt editor
This commit is contained in:
parent
4d8f403d78
commit
4fa4f29f0b
162
gadget-code/frontend/src/components/ExpandedPromptEditor.tsx
Normal file
162
gadget-code/frontend/src/components/ExpandedPromptEditor.tsx
Normal file
@ -0,0 +1,162 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
interface ExpandedPromptEditorProps {
|
||||
promptInput: string;
|
||||
onPromptChange: (value: string) => void;
|
||||
onSubmit: (e: React.FormEvent) => void;
|
||||
onClose: () => void;
|
||||
disabled: boolean;
|
||||
placeholder: string;
|
||||
isProcessing: boolean;
|
||||
isAborting: boolean;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export default function ExpandedPromptEditor({
|
||||
promptInput,
|
||||
onPromptChange,
|
||||
onSubmit,
|
||||
onClose,
|
||||
disabled,
|
||||
placeholder,
|
||||
isProcessing,
|
||||
isAborting,
|
||||
onCancel,
|
||||
}: ExpandedPromptEditorProps) {
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
// Auto-focus the textarea on mount
|
||||
useEffect(() => {
|
||||
textareaRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
// Escape closes the editor
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Enter or Cmd+Enter submits the prompt
|
||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
if (!disabled && promptInput.trim()) {
|
||||
onSubmit(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Regular Enter inserts a newline (default textarea behavior)
|
||||
// No special handling needed — this is the key difference from the simple prompt bar
|
||||
};
|
||||
|
||||
const handleSubmitClick = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!disabled && promptInput.trim()) {
|
||||
onSubmit(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex overflow-hidden">
|
||||
{/* Left Sidebar — Tools */}
|
||||
<div className="w-48 border-r border-border-subtle bg-bg-secondary flex flex-col shrink-0">
|
||||
{/* Sidebar Header */}
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-bg-tertiary border-b border-border-subtle shrink-0">
|
||||
<span className="text-xs font-medium text-text-secondary uppercase tracking-wider">Tools</span>
|
||||
</div>
|
||||
{/* Sidebar Content — Placeholder for future tools */}
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-3">
|
||||
<div className="text-text-muted text-xs space-y-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<svg className="w-3.5 h-3.5 text-text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" />
|
||||
</svg>
|
||||
<span className="text-text-secondary font-medium">Snippets</span>
|
||||
</div>
|
||||
<p className="pl-5.5 text-text-muted text-[11px] leading-relaxed">
|
||||
Quick text snippets for common prompt patterns
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<svg className="w-3.5 h-3.5 text-text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||
</svg>
|
||||
<span className="text-text-secondary font-medium">Prompt Library</span>
|
||||
</div>
|
||||
<p className="pl-5.5 text-text-muted text-[11px] leading-relaxed">
|
||||
Saved and shared prompt templates
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-4 border-t border-border-subtle">
|
||||
<p className="text-text-muted text-[11px] leading-relaxed">
|
||||
More tools coming soon...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Editor Area */}
|
||||
<div className="flex-1 flex flex-col min-w-0">
|
||||
{/* Header Bar */}
|
||||
<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">
|
||||
{isProcessing ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
disabled={isAborting}
|
||||
className="px-4 py-1.5 bg-red-600 text-white rounded hover:bg-red-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-sm"
|
||||
>
|
||||
{isAborting ? 'Aborting...' : 'Cancel'}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSubmitClick}
|
||||
disabled={disabled || !promptInput.trim()}
|
||||
className="px-4 py-1.5 bg-brand text-white rounded hover:bg-brand/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-sm"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="p-1.5 text-text-muted hover:text-text-primary hover:bg-bg-secondary rounded transition-colors"
|
||||
title="Close expanded editor (Esc)"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Textarea */}
|
||||
<div className="flex-1 min-h-0 p-4">
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
value={promptInput}
|
||||
onChange={(e) => onPromptChange(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
className="w-full h-full px-4 py-3 bg-bg-primary border border-border-default rounded font-mono text-sm text-text-primary leading-relaxed focus:border-brand focus:outline-none resize-none disabled:opacity-50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 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 className="font-mono">{promptInput.length} chars</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -10,6 +10,7 @@ import LogPanel from '../components/LogPanel';
|
||||
import ChatTurnComponent from '../components/ChatTurn';
|
||||
import EditorPanel from '../components/EditorPanel';
|
||||
import ErrorBoundary from '../components/ErrorBoundary';
|
||||
import ExpandedPromptEditor from '../components/ExpandedPromptEditor';
|
||||
import { AppContext } from '../App';
|
||||
|
||||
interface LogEntry {
|
||||
@ -80,6 +81,7 @@ export default function ChatSessionView() {
|
||||
const [isOtherTab, setIsOtherTab] = useState(false);
|
||||
const [reconnectAttempts, setReconnectAttempts] = useState(0);
|
||||
const [editorFilePath, setEditorFilePath] = useState<string | undefined>(undefined);
|
||||
const [isExpandedEditor, setIsExpandedEditor] = useState(false);
|
||||
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
@ -1134,8 +1136,24 @@ export default function ChatSessionView() {
|
||||
<div className="absolute inset-0 bg-bg-primary/80 z-30" />
|
||||
)}
|
||||
|
||||
{/* File Editor View (replaces Chat View when file is open) */}
|
||||
{editorFilePath ? (
|
||||
{/* Expanded Prompt Editor (replaces Chat View when active) */}
|
||||
{isExpandedEditor ? (
|
||||
<ExpandedPromptEditor
|
||||
promptInput={promptInput}
|
||||
onPromptChange={setPromptInput}
|
||||
onSubmit={(e) => {
|
||||
handleSubmitPrompt(e);
|
||||
setIsExpandedEditor(false);
|
||||
}}
|
||||
onClose={() => setIsExpandedEditor(false)}
|
||||
disabled={promptDisabled}
|
||||
placeholder={promptPlaceholder}
|
||||
isProcessing={isProcessing}
|
||||
isAborting={isAborting}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
) : editorFilePath ? (
|
||||
/* File Editor View (replaces Chat View when file is open) */
|
||||
<ErrorBoundary>
|
||||
<EditorPanel
|
||||
workspaceMode={workspaceMode}
|
||||
@ -1144,7 +1162,7 @@ export default function ChatSessionView() {
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
) : (
|
||||
/* Chat View (75%) */
|
||||
/* Chat View */
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Messages */}
|
||||
<div className="flex-1 overflow-y-auto p-3 space-y-2">
|
||||
@ -1160,6 +1178,17 @@ 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">
|
||||
<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"
|
||||
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>
|
||||
</button>
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={promptInput}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user