@@ -10,12 +10,15 @@ import {
1010 CornerDownLeftIcon ,
1111 DeleteIcon ,
1212 SendIcon ,
13+ Square ,
1314} from "lucide-react" ;
1415import { Tabs , TabsList , TabsTrigger } from "./ui/tabs" ;
16+ import type { ServerStatus } from "./chat-provider" ;
1517
1618interface MessageInputProps {
1719 onSendMessage : ( message : string , type : "user" | "raw" ) => void ;
1820 disabled ?: boolean ;
21+ serverStatus : ServerStatus ;
1922}
2023
2124interface SentChar {
@@ -24,9 +27,27 @@ interface SentChar {
2427 timestamp : number ;
2528}
2629
30+ // List of keys to send as raw input when in control mode
31+
32+ const specialKeys : Record < string , string > = {
33+ ArrowUp : "\x1b[A" , // Escape sequence for up arrow
34+ ArrowDown : "\x1b[B" , // Escape sequence for down arrow
35+ ArrowRight : "\x1b[C" , // Escape sequence for right arrow
36+ ArrowLeft : "\x1b[D" , // Escape sequence for left arrow
37+ Escape : "\x1b" , // Escape key
38+ Tab : "\t" , // Tab key
39+ Delete : "\x1b[3~" , // Delete key
40+ Home : "\x1b[H" , // Home key
41+ End : "\x1b[F" , // End key
42+ PageUp : "\x1b[5~" , // Page Up
43+ PageDown : "\x1b[6~" , // Page Down
44+ Backspace : "\b" , // Backspace key
45+ } ;
46+
2747export default function MessageInput ( {
2848 onSendMessage,
2949 disabled = false ,
50+ serverStatus,
3051} : MessageInputProps ) {
3152 const [ message , setMessage ] = useState ( "" ) ;
3253 const [ inputMode , setInputMode ] = useState ( "text" ) ;
@@ -69,22 +90,6 @@ export default function MessageInput({
6990 const handleKeyDown = ( e : KeyboardEvent < HTMLTextAreaElement > ) => {
7091 // In control mode, send special keys as raw messages
7192 if ( inputMode === "control" && ! disabled ) {
72- // List of keys to send as raw input when in control mode
73- const specialKeys : Record < string , string > = {
74- ArrowUp : "\x1b[A" , // Escape sequence for up arrow
75- ArrowDown : "\x1b[B" , // Escape sequence for down arrow
76- ArrowRight : "\x1b[C" , // Escape sequence for right arrow
77- ArrowLeft : "\x1b[D" , // Escape sequence for left arrow
78- Escape : "\x1b" , // Escape key
79- Tab : "\t" , // Tab key
80- Delete : "\x1b[3~" , // Delete key
81- Home : "\x1b[H" , // Home key
82- End : "\x1b[F" , // End key
83- PageUp : "\x1b[5~" , // Page Up
84- PageDown : "\x1b[6~" , // Page Down
85- Backspace : "\b" , // Backspace key
86- } ;
87-
8893 // Check if the pressed key is in our special keys map
8994 if ( specialKeys [ e . key ] ) {
9095 e . preventDefault ( ) ;
@@ -168,8 +173,13 @@ export default function MessageInput({
168173 value = { message }
169174 onChange = { ( e ) => setMessage ( e . target . value ) }
170175 onKeyDown = { handleKeyDown }
171- placeholder = { "Type a message..." }
176+ placeholder = {
177+ serverStatus === "running"
178+ ? "Running..."
179+ : "Type a message..."
180+ }
172181 className = "resize-none w-full text-sm outline-none p-4 h-20"
182+ disabled = { serverStatus !== "stable" }
173183 />
174184 ) }
175185 </ div >
@@ -194,7 +204,7 @@ export default function MessageInput({
194204 </ TabsTrigger >
195205 </ TabsList >
196206
197- { inputMode === "text" && (
207+ { inputMode === "text" && serverStatus !== "running" && (
198208 < Button
199209 type = "submit"
200210 disabled = { disabled || ! message . trim ( ) }
@@ -206,6 +216,20 @@ export default function MessageInput({
206216 </ Button >
207217 ) }
208218
219+ { inputMode === "text" && serverStatus === "running" && (
220+ < Button
221+ size = "icon"
222+ className = "rounded-full"
223+ disabled = { disabled }
224+ onClick = { ( ) => {
225+ onSendMessage ( specialKeys . Escape , "raw" ) ;
226+ } }
227+ >
228+ < Square />
229+ < span className = "sr-only" > Stop</ span >
230+ </ Button >
231+ ) }
232+
209233 { inputMode === "control" && ! disabled && (
210234 < div className = "flex items-center gap-1" >
211235 { sentChars . map ( ( char ) => (
0 commit comments