Skip to content

Commit

Permalink
Ai chat keybindings (#481)
Browse files Browse the repository at this point in the history
* added aichat keybindings

* remove console.log
  • Loading branch information
MrStashley authored Mar 22, 2024
1 parent f705a4d commit 923cf71
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 55 deletions.
175 changes: 120 additions & 55 deletions src/app/workspace/cmdinput/aichat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,64 @@ import { GlobalModel } from "@/models";
import { isBlank } from "@/util/util";
import { boundMethod } from "autobind-decorator";
import cn from "classnames";
import { For } from "tsx-control-statements/components";
import { If, For } from "tsx-control-statements/components";
import { Markdown } from "@/elements";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";

class AIChatKeybindings extends React.Component<{ AIChatObject: AIChat }, {}> {
componentDidMount(): void {
let AIChatObject = this.props.AIChatObject;
let keybindManager = GlobalModel.keybindManager;
let inputModel = GlobalModel.inputModel;

keybindManager.registerKeybinding("pane", "aichat", "generic:confirm", (waveEvent) => {
AIChatObject.onEnterKeyPressed();
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:expandTextInput", (waveEvent) => {
AIChatObject.onExpandInputPressed();
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:cancel", (waveEvent) => {
inputModel.closeAIAssistantChat(true);
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "aichat:clearHistory", (waveEvent) => {
inputModel.clearAIAssistantChat();
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:selectAbove", (waveEvent) => {
return AIChatObject.onArrowUpPressed();
});
keybindManager.registerKeybinding("pane", "aichat", "generic:selectBelow", (waveEvent) => {
return AIChatObject.onArrowDownPressed();
});
}

componentWillUnmount(): void {
GlobalModel.keybindManager.unregisterDomain("aichat");
}

render() {
return null;
}
}

@mobxReact.observer
class AIChat extends React.Component<{}, {}> {
chatListKeyCount: number = 0;
textAreaNumLines: mobx.IObservableValue<number> = mobx.observable.box(1, { name: "textAreaNumLines" });
chatWindowScrollRef: React.RefObject<HTMLDivElement>;
textAreaRef: React.RefObject<HTMLTextAreaElement>;
isFocused: OV<boolean>;

constructor(props: any) {
super(props);
this.chatWindowScrollRef = React.createRef();
this.textAreaRef = React.createRef();
this.isFocused = mobx.observable.box(false, {
name: "aichat-isfocused",
});
}

componentDidMount() {
Expand Down Expand Up @@ -66,67 +109,82 @@ class AIChat extends React.Component<{}, {}> {
return { numLines, linePos };
}

@mobx.action
@boundMethod
onKeyDown(e: any) {
onTextAreaFocused(e: any) {
mobx.action(() => {
let model = GlobalModel;
let inputModel = model.inputModel;
let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift");
let resetCodeSelect = !ctrlMod;
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Enter")) {
e.preventDefault();
if (!ctrlMod) {
if (inputModel.getCodeSelectSelectedIndex() == -1) {
let messageStr = e.target.value;
this.submitChatMessage(messageStr);
e.target.value = "";
} else {
inputModel.grabCodeSelectSelection();
}
} else {
e.target.setRangeText("\n", e.target.selectionStart, e.target.selectionEnd, "end");
}
}
if (checkKeyPressed(waveEvent, "Escape")) {
e.preventDefault();
e.stopPropagation();
inputModel.closeAIAssistantChat(true);
}

if (checkKeyPressed(waveEvent, "Ctrl:l")) {
e.preventDefault();
e.stopPropagation();
inputModel.clearAIAssistantChat();
}
if (checkKeyPressed(waveEvent, "ArrowUp")) {
if (this.getLinePos(e.target).linePos > 1) {
// normal up arrow
return;
}
e.preventDefault();
inputModel.codeSelectSelectNextOldestCodeBlock();
resetCodeSelect = false;
}
if (checkKeyPressed(waveEvent, "ArrowDown")) {
if (inputModel.getCodeSelectSelectedIndex() == inputModel.codeSelectBottom) {
return;
}
e.preventDefault();
inputModel.codeSelectSelectNextNewestCodeBlock();
resetCodeSelect = false;
}
this.isFocused.set(true);
})();
}

if (resetCodeSelect) {
inputModel.codeSelectDeselectAll();
}
onTextAreaBlur(e: any) {
mobx.action(() => {
this.isFocused.set(false);
})();
}

// set height of textarea based on number of newlines
onTextAreaChange(e: any) {
// set height of textarea based on number of newlines
mobx.action(() => {
this.textAreaNumLines.set(e.target.value.split(/\n/).length);
GlobalModel.inputModel.codeSelectDeselectAll();
})();
}

onEnterKeyPressed() {
let inputModel = GlobalModel.inputModel;
let currentRef = this.textAreaRef.current;
if (currentRef == null) {
return;
}
if (inputModel.getCodeSelectSelectedIndex() == -1) {
let messageStr = currentRef.value;
this.submitChatMessage(messageStr);
currentRef.value = "";
} else {
inputModel.grabCodeSelectSelection();
}
}

onExpandInputPressed() {
let currentRef = this.textAreaRef.current;
if (currentRef == null) {
return;
}
currentRef.setRangeText("\n", currentRef.selectionStart, currentRef.selectionEnd, "end");
GlobalModel.inputModel.codeSelectDeselectAll();
}

onArrowUpPressed(): boolean {
let currentRef = this.textAreaRef.current;
if (currentRef == null) {
return false;
}
if (this.getLinePos(currentRef).linePos > 1) {
// normal up arrow
GlobalModel.inputModel.codeSelectDeselectAll();
return false;
}
GlobalModel.inputModel.codeSelectSelectNextOldestCodeBlock();
return true;
}

onArrowDownPressed(): boolean {
let currentRef = this.textAreaRef.current;
let inputModel = GlobalModel.inputModel;
if (currentRef == null) {
return false;
}
if (inputModel.getCodeSelectSelectedIndex() == inputModel.codeSelectBottom) {
GlobalModel.inputModel.codeSelectDeselectAll();
return false;
}
inputModel.codeSelectSelectNextNewestCodeBlock();
return true;
}

@mobx.action
@boundMethod
onKeyDown(e: any) {}

renderError(err: string): any {
return <div className="chat-msg-error">{err}</div>;
}
Expand Down Expand Up @@ -192,9 +250,13 @@ class AIChat extends React.Component<{}, {}> {
const textAreaPadding = 2 * 0.5 * termFontSize;
let textAreaMaxHeight = textAreaLineHeight * textAreaMaxLines + textAreaPadding;
let textAreaInnerHeight = this.textAreaNumLines.get() * textAreaLineHeight + textAreaPadding;
let isFocused = this.isFocused.get();

return (
<div className="cmd-aichat">
<If condition={isFocused}>
<AIChatKeybindings AIChatObject={this}></AIChatKeybindings>
</If>
<div className="cmdinput-titlebar">
<div className="title-icon">
<i className="fa-sharp fa-solid fa-sparkles" />
Expand All @@ -217,6 +279,9 @@ class AIChat extends React.Component<{}, {}> {
autoComplete="off"
autoCorrect="off"
id="chat-cmd-input"
onFocus={this.onTextAreaFocused.bind(this)}
onBlur={this.onTextAreaBlur.bind(this)}
onChange={this.onTextAreaChange.bind(this)}
onKeyDown={this.onKeyDown}
style={{ height: textAreaInnerHeight, maxHeight: textAreaMaxHeight, fontSize: termFontSize }}
className={cn("chat-textarea")}
Expand Down
3 changes: 3 additions & 0 deletions src/models/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,9 @@ class InputModel {
}

codeSelectDeselectAll(direction: number = this.codeSelectBottom) {
if (this.codeSelectSelectedIndex.get() == direction) {
return;
}
mobx.action(() => {
this.codeSelectSelectedIndex.set(direction);
this.codeSelectBlockRefArray = [];
Expand Down

0 comments on commit 923cf71

Please sign in to comment.