6868 <span class =" material-symbols-outlined" >keyboard_arrow_down</span >
6969 </button >
7070 <div class =" chat-input-container-container" >
71- <div class =" chat-input-container" >
72- <BaseInput
73- type =" textarea"
74- v-model =" input"
75- @keydown.enter =" handleEnterKey"
76- @keydown.up =" handleUpArrow"
77- @keydown.down =" handleDownArrow"
78- placeholder =" Type your message here..."
79- rows =" 1"
80- />
81- <div class =" actions" >
82- <Dropdown
83- :model-value =" model"
84- placeholder =" Select Model"
85- aria-label =" Model"
86- >
87- <DropdownOption
88- v-for =" optionModel in filteredModels"
89- :key =" optionModel.id"
90- :value =" optionModel.id"
91- :text =" optionModel.id"
92- :selected =" matchModel(optionModel, model)"
93- @select =" selectModel(optionModel)"
94- />
95- <div class =" dropdown-separator" ></div >
96- <button class =" dropdown-action" @click =" $emit('manage-models')" >
97- Manage models
98- </button >
99- </Dropdown >
100- <button
101- v-if =" canSendMessage"
102- @click =" submit"
103- class =" submit-btn"
104- :disabled =" !input.trim()"
105- >
106- <span class =" material-symbols-outlined" >send</span >
107- </button >
108- <button v-else @click =" stop" class =" stop-btn" />
109- </div >
110- </div >
71+ <PromptInput storage-key =" inputHistory" :processing =" processing" :selected-model =" model"
72+ @select-model =" selectModel" @manage-models =" $emit('manage-models')" @submit =" submit" @stop =" stop" />
11173 </div >
11274 </div >
11375</template >
@@ -126,10 +88,8 @@ import { PropType } from "vue";
12688import { mapActions , mapGetters , mapState , mapWritableState } from " pinia" ;
12789import { RootBinding } from " @/plugins/appEvent" ;
12890import { useInternalDataStore } from " @/stores/internalData" ;
129- import { matchModel } from " @/utils" ;
13091import BaseInput from " @/components/common/BaseInput.vue" ;
131-
132- const maxHistorySize = 50 ;
92+ import PromptInput from " @/components/common/PromptInput.vue" ;
13393
13494export default {
13595 name: " ChatInterface" ,
@@ -141,6 +101,7 @@ export default {
141101 ToolMessage ,
142102 Markdown ,
143103 BaseInput ,
104+ PromptInput ,
144105 },
145106
146107 emits: [" manage-models" , " open-configuration" ],
@@ -167,7 +128,6 @@ export default {
167128 send: ai .send ,
168129 abort: ai .abort ,
169130 messages: ai .messages ,
170- input: ai .input ,
171131 error: ai .error ,
172132 status: ai .status ,
173133 pendingToolCallIds: ai .pendingToolCallIds ,
@@ -179,13 +139,7 @@ export default {
179139 },
180140
181141 data() {
182- const inputHistoryStr = localStorage .getItem (" inputHistory" ) || " []" ;
183- const inputHistory = JSON .parse (inputHistoryStr );
184142 return {
185- tempInput: " " ,
186- inputHistory ,
187- historyIndex: inputHistory .length ,
188- isNavigatingHistory: false ,
189143 isAtBottom: true ,
190144 showFullError: false ,
191145 noModelError: false ,
@@ -195,14 +149,9 @@ export default {
195149 computed: {
196150 ... mapGetters (useChatStore , [" systemPrompt" ]),
197151 ... mapWritableState (useChatStore , [" model" ]),
198- ... mapState (useChatStore , {
199- filteredModels(store ) {
200- return store .models .filter ((m ) => m .enabled );
201- },
202- }),
203- canSendMessage() {
204- if (this .askingPermission && this .input .trim ().length > 0 ) return true ;
205- return this .status === " ready" || this .status === " error" ;
152+ processing() {
153+ if (this .askingPermission ) return false ;
154+ return this .status !== " ready" && this .status !== " error" ;
206155 },
207156 showSpinner() {
208157 return (
@@ -272,148 +221,27 @@ export default {
272221
273222 methods: {
274223 ... mapActions (useInternalDataStore , [" setInternal" ]),
275- matchModel ,
276- handleEnterKey(e ) {
277- if (e .shiftKey ) {
278- // Allow default behavior (new line) when Shift+Enter is pressed
279- return ;
280- }
281-
282- if (this .canSendMessage ) {
283- e .preventDefault ();
284- e .stopPropagation ();
285- this .submit ();
286- }
287- },
288-
289- // Handle up/down arrow keys for history navigation
290- handleUpArrow(e ) {
291- const textarea = e .target ;
292- const text = textarea .value ;
293-
294- // Is cursor at first line?
295- const cursorPos = textarea .selectionStart ;
296- const textBeforeCursor = text .substring (0 , cursorPos );
297-
298- // If there's no newline before cursor or cursor is at position 0, we're at the first line
299- if (
300- (cursorPos === 0 || textBeforeCursor .lastIndexOf (" \n " ) === - 1 ) &&
301- this .historyIndex > 0
302- ) {
303- e .preventDefault ();
304- this .navigateHistory (- 1 ); // Go back in history
305- }
306- },
307-
308- handleDownArrow(e ) {
309- const textarea = e .target ;
310- const text = textarea .value ;
311-
312- // Is cursor at last line?
313- const cursorPos = textarea .selectionStart ;
314- const textAfterCursor = text .substring (cursorPos );
315-
316- // If there's no newline after cursor or cursor is at end of text, we're at the last line
317- if (
318- (cursorPos === text .length || textAfterCursor .indexOf (" \n " ) === - 1 ) &&
319- this .historyIndex < this .inputHistory .length - 1
320- ) {
321- e .preventDefault ();
322- this .navigateHistory (1 ); // Go forward in history
323- }
324- },
325-
326- // Navigate through input history
327- navigateHistory(direction ) {
328- // If no history or navigating beyond bounds, do nothing
329- if (this .inputHistory .length === 0 ) return ;
330-
331- if (this .historyIndex === 0 && direction === - 1 ) return ;
332-
333- if (
334- this .historyIndex >= this .inputHistory .length - 1 &&
335- direction === 1
336- ) {
337- // We are at the last history item
338- this .historyIndex = this .inputHistory .length ;
339- this .input = this .tempInput ;
340- this .isNavigatingHistory = false ;
341- return ;
342- }
343-
344- if (! this .isNavigatingHistory ) {
345- // save current input before navigating
346- this .tempInput = this .input ;
347- this .isNavigatingHistory = true ;
348- }
349-
350- // Calculate new index
351- const newIndex = this .historyIndex + direction ;
352-
353- this .input = this .inputHistory [newIndex ];
354-
355- this .historyIndex = newIndex ;
356-
357- // Place cursor at the end of the input text
358- this .$nextTick (() => {
359- const textarea = document .querySelector (" textarea" );
360- if (textarea ) {
361- textarea .selectionStart = textarea .selectionEnd =
362- textarea .value .length ;
363- }
364- });
365- },
366-
367- submit() {
368- const message = this .input .trim ();
369-
370- // Don't send empty messages
371- if (! message ) return ;
372224
225+ submit(input : string ) {
373226 if (! this .model ) {
374227 // FIXME we should catch this and show it on screen
375228 this .noModelError = true ;
376229 return ;
377230 }
378231
379- this .addToHistory (message );
380-
381232 this .noModelError = false ;
382- this .tempInput = " " ;
383- this .input = " " ;
384233
385234 if (this .askingPermission ) {
386- this .rejectPermission (message );
235+ this .rejectPermission (input );
387236 } else {
388- this .send (message , this .getSendOptions ());
237+ this .send (input , this .getSendOptions ());
389238 }
390239 },
391240
392241 async reload() {
393242 await this .retry (this .getSendOptions ());
394243 },
395244
396- addToHistory(input : string ) {
397- const oldHistory = JSON .parse (localStorage .getItem (" inputHistory" )! );
398-
399- let newHistory = [... this .inputHistory , input ];
400- if (this .historyIndex < this .inputHistory .length ) {
401- newHistory [this .historyIndex ] = oldHistory [this .historyIndex ];
402- }
403-
404- // Limit history size
405- if (newHistory .length > maxHistorySize ) {
406- newHistory = newHistory .slice (- maxHistorySize );
407- }
408-
409- localStorage .setItem (" inputHistory" , JSON .stringify (newHistory ));
410-
411- // Reset history navigation
412- this .inputHistory = newHistory ;
413- this .historyIndex = newHistory .length ;
414- this .isNavigatingHistory = false ;
415- },
416-
417245 stop() {
418246 if (this .askingPermission ) {
419247 this .rejectPermission ();
@@ -436,6 +264,7 @@ export default {
436264 this .$refs .scrollContainerRef .scrollHeight ;
437265 }
438266 },
267+
439268 selectModel(model : Model ) {
440269 this .setInternal (" lastUsedModelId" , model .id );
441270 this .model = model ;
0 commit comments