Skip to content

Commit

Permalink
Fixed command backstack and added ability to paste images (#670)
Browse files Browse the repository at this point in the history
- fixed command back stack to remove empty entries
- fixed drag/drop so that HTML is not interpreted
- command back stack now includes images
- can now paste images into input field
- can now drag/drop text into input field
  • Loading branch information
robgruen authored Feb 5, 2025
1 parent 2dd2b77 commit 449fc56
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 19 deletions.
66 changes: 55 additions & 11 deletions ts/packages/shell/src/renderer/src/chatInput.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import DOMPurify from "dompurify";
import { _arrayBufferToBase64 } from "./chatView";
import {
iconMicrophone,
Expand Down Expand Up @@ -57,11 +58,12 @@ export class ExpandableTextarea {
if (this.entryHandlers.onChange !== undefined) {
this.entryHandlers.onChange(this);
}

});
this.textEntry.onchange = () => {
if (sendButton !== undefined) {
sendButton.disabled = this.textEntry.innerHTML.length == 0;
}
});
};
this.textEntry.onwheel = (event) => {
if (this.entryHandlers.onMouseWheel !== undefined) {
this.entryHandlers.onMouseWheel(this, event);
Expand All @@ -80,12 +82,14 @@ export class ExpandableTextarea {

// Set the cursor to the end of the text
const r = document.createRange();
r.setEnd(this.textEntry.childNodes[0], content?.length ?? 0);
r.collapse(false);
const s = document.getSelection();
if (s) {
s.removeAllRanges();
s.addRange(r);
if (this.textEntry.childNodes.length > 0) {
r.setEnd(this.textEntry.childNodes[0], content?.length ?? 0);
r.collapse(false);
const s = document.getSelection();
if (s) {
s.removeAllRanges();
s.addRange(r);
}
}
}

Expand Down Expand Up @@ -187,6 +191,12 @@ export class ChatInput {
this.sendButton,
);

this.textarea.getTextEntry().onpaste = (e: ClipboardEvent) => {
if (e.clipboardData !== null) {
this.getTextFromDataTransfer(e.clipboardData);
}
};

this.textarea.getTextEntry().ondragenter = (e: DragEvent) => {
if (!this.dragEnabled) {
return;
Expand All @@ -201,7 +211,8 @@ export class ChatInput {

console.log("enter " + this.dragTemp);

this.textarea.getTextEntry().innerText = "Drop image files here...";
this.textarea.getTextEntry().innerText =
"Drop image files or text here...";
this.textarea.getTextEntry().classList.add("chat-input-drag");
};

Expand Down Expand Up @@ -237,8 +248,8 @@ export class ChatInput {

this.dragTemp = undefined;

if (e.dataTransfer != null && e.dataTransfer.files.length > 0) {
this.loadImageFile(e.dataTransfer.files[0]);
if (e.dataTransfer != null) {
this.getTextFromDataTransfer(e.dataTransfer);
}

e.preventDefault();
Expand Down Expand Up @@ -378,4 +389,37 @@ export class ChatInput {
public focus() {
this.textarea.focus();
}

/**
* Takes dataTransfer and gets a plain text representation from the data there
* and loads it into the input box
*
* @param dataTransfer The dataTransfer object from drag/drop/paste events
*/
public getTextFromDataTransfer(dataTransfer: DataTransfer) {
if (dataTransfer.files.length > 0) {
this.loadImageFile(dataTransfer.files[0]);
} else if (dataTransfer.items.length > 0) {
let index: number = dataTransfer.types.indexOf("text/plain");
let plainText: boolean = true;
if (index === -1) {
index = dataTransfer.types.indexOf("text/html");
plainText = false;
}

if (index === -1) {
this.textarea.getTextEntry().innerText = `Unsupported drag/drop data type '${dataTransfer.types.join(", ")}'`;
} else {
dataTransfer.items[index].getAsString((s) => {
if (plainText) {
this.textarea.getTextEntry().innerText = s;
} else {
// strip out all HTML from supplied input
this.textarea.getTextEntry().innerText +=
DOMPurify.sanitize(s, { ALLOWED_TAGS: [] });
}
});
}
}
}
}
24 changes: 16 additions & 8 deletions ts/packages/shell/src/renderer/src/chatView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class ChatView {
chatInput: ChatInput;
private partialCompletion: PartialCompletion | undefined;

private commandBackStack: (string | null)[] = [];
private commandBackStack: string[] = [];
private commandBackStackIndex = 0;

private hideMetrics = true;
Expand Down Expand Up @@ -109,25 +109,32 @@ export class ChatView {
// history
if (!ev.altKey && !ev.ctrlKey) {
if (ev.key == "ArrowUp" || ev.key == "ArrowDown") {
const currentContent =
this.chatInput.textarea.getTextEntry().textContent;
const currentContent: string =
this.chatInput.textarea.getTextEntry().innerHTML ??
"";

if (
this.commandBackStack.length === 0 ||
this.commandBackStack[
this.commandBackStackIndex
] !== currentContent
) {
const messages = this.messageDiv.querySelectorAll(
".chat-message-user:not(.chat-message-hidden) .chat-message-content",
);
const messages: NodeListOf<Element> =
this.messageDiv.querySelectorAll(
".chat-message-container-user:not(.chat-message-hidden) .chat-message-content",
);
this.commandBackStack = Array.from(messages).map(
(m) => m.textContent,
(m: Element) =>
m.firstElementChild?.innerHTML.replace(
'class="chat-input-image"',
'class="chat-input-dropImage"',
) ?? "",
);

this.commandBackStack.unshift(currentContent);
this.commandBackStackIndex = 0;
}

if (
ev.key == "ArrowUp" &&
this.commandBackStackIndex <
Expand All @@ -143,7 +150,8 @@ export class ChatView {

const content =
this.commandBackStack[this.commandBackStackIndex];
this.chatInput.textarea.setContent(content);
this.chatInput.textarea.getTextEntry().innerHTML =
content;

return false;
}
Expand Down

0 comments on commit 449fc56

Please sign in to comment.