Skip to content

Commit 0a264f2

Browse files
authored
Merge pull request #42 from beekeeper-studio/fix/input-history
Fix/input history
2 parents abb6271 + f6ba8a5 commit 0a264f2

File tree

4 files changed

+392
-184
lines changed

4 files changed

+392
-184
lines changed

src/components/ChatInterface.vue

Lines changed: 11 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -68,46 +68,8 @@
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";
12688
import { mapActions, mapGetters, mapState, mapWritableState } from "pinia";
12789
import { RootBinding } from "@/plugins/appEvent";
12890
import { useInternalDataStore } from "@/stores/internalData";
129-
import { matchModel } from "@/utils";
13091
import BaseInput from "@/components/common/BaseInput.vue";
131-
132-
const maxHistorySize = 50;
92+
import PromptInput from "@/components/common/PromptInput.vue";
13393
13494
export 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

Comments
 (0)