save work

This commit is contained in:
Red Adaya 2024-05-06 20:32:43 +08:00
parent 918ff98ea1
commit 34837aabae
9 changed files with 110 additions and 102 deletions

View File

@ -11,6 +11,7 @@ import { Markdown } from "@/elements";
import type { OverlayScrollbars } from "overlayscrollbars";
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
import tinycolor from "tinycolor2";
import * as appconst from "@/app/appconst";
import "./aichat.less";
@ -54,10 +55,9 @@ class AIChatKeybindings extends React.Component<{ AIChatObject: AIChat }, {}> {
}
@mobxReact.observer
class ChatContent extends React.Component<{}, {}> {
class ChatContent extends React.Component<{ chatWindowRef }, {}> {
chatListKeyCount: number = 0;
containerRef: React.RefObject<OverlayScrollbarsComponentRef> = React.createRef();
chatWindowRef: React.RefObject<HTMLDivElement> = React.createRef();
osInstance: OverlayScrollbars = null;
componentDidUpdate() {
@ -66,7 +66,7 @@ class ChatContent extends React.Component<{}, {}> {
const { viewport } = this.osInstance.elements();
viewport.scrollTo({
behavior: "auto",
top: this.chatWindowRef.current.scrollHeight,
top: this.props.chatWindowRef.current.scrollHeight,
});
}
}
@ -84,7 +84,7 @@ class ChatContent extends React.Component<{}, {}> {
const { viewport } = instance.elements();
viewport.scrollTo({
behavior: "auto",
top: this.chatWindowRef.current.scrollHeight,
top: this.props.chatWindowRef.current.scrollHeight,
});
}
@ -116,7 +116,7 @@ class ChatContent extends React.Component<{}, {}> {
<i className="fa-sharp fa-solid fa-sparkles"></i>
</div>
<Markdown
inputModel={GlobalModel.aichatModel}
inputModel={GlobalModel.inputModel}
text={chatItem.assistantresponse.message}
codeSelect
/>
@ -148,7 +148,7 @@ class ChatContent extends React.Component<{}, {}> {
options={{ scrollbars: { autoHide: "leave" } }}
events={{ initialized: this.onScrollbarInitialized }}
>
<div ref={this.chatWindowRef} className="chat-window">
<div ref={this.props.chatWindowRef} className="chat-window">
<div className="filler"></div>
<For each="chitem" index="idx" of={chatMessageItems}>
{this.renderChatMessage(chitem)}
@ -162,13 +162,39 @@ class ChatContent extends React.Component<{}, {}> {
@mobxReact.observer
class AIChat extends React.Component<{}, {}> {
textAreaRef: React.RefObject<HTMLTextAreaElement> = React.createRef();
chatWindowRef: React.RefObject<HTMLDivElement> = React.createRef();
termFontSize: number = 14;
// Adjust the height of the textarea to fit the text
@boundMethod
onTextAreaChange(e: any) {
if (this.textAreaRef.current == null) {
return;
}
// Calculate the bounding height of the text area
const textAreaMaxLines = 4;
const textAreaLineHeight = this.termFontSize * 1.5;
const textAreaMinHeight = textAreaLineHeight;
const textAreaMaxHeight = textAreaLineHeight * textAreaMaxLines;
// Get the height of the wrapped text area content. Courtesy of https://stackoverflow.com/questions/995168/textarea-to-resize-based-on-content-length
this.textAreaRef.current.style.height = "1px";
const scrollHeight: number = this.textAreaRef.current.scrollHeight;
// Set the new height of the text area, bounded by the min and max height.
const newHeight = Math.min(Math.max(scrollHeight, textAreaMinHeight), textAreaMaxHeight);
this.textAreaRef.current.style.height = newHeight + "px";
GlobalModel.inputModel.codeSelectDeselectAll();
}
componentDidMount() {
const inputModel = GlobalModel.inputModel;
inputModel.openAIAssistantChat();
if (this.textAreaRef.current != null) {
this.textAreaRef.current.focus();
// inputModel.setCmdInfoChatRefs(this.textAreaRef, this.chatWindowScrollRef);
inputModel.setCmdInfoChatRefs(this.textAreaRef, this.chatWindowRef);
}
this.requestChatUpdate();
this.onTextAreaChange(null);
@ -183,7 +209,6 @@ class AIChat extends React.Component<{}, {}> {
submitChatMessage(messageStr: string) {
const curLine = GlobalModel.inputModel.curLine;
console.log("enter key pressed", messageStr, "curLine: ", curLine);
const prtn = GlobalModel.submitChatInfoCommand(messageStr, curLine, false);
prtn.then((rtn) => {
if (!rtn.success) {
@ -198,9 +223,13 @@ class AIChat extends React.Component<{}, {}> {
return { numLines, linePos };
}
@mobx.action.bound
@mobx.action
@boundMethod
onTextAreaFocused(e: any) {
console.log("focused");
GlobalModel.inputModel.setAuxViewFocus(true);
GlobalModel.inputModel.setActiveAuxView(appconst.InputAuxView_AIChat);
this.onTextAreaChange(e);
}
@ -211,22 +240,8 @@ class AIChat extends React.Component<{}, {}> {
})();
}
// Adjust the height of the textarea to fit the text
@boundMethod
onTextAreaChange(e: any) {
// Calculate the bounding height of the text area
const textAreaMaxLines = 4;
const textAreaLineHeight = this.termFontSize * 1.5;
const textAreaMinHeight = textAreaLineHeight;
const textAreaMaxHeight = textAreaLineHeight * textAreaMaxLines;
// Get the height of the wrapped text area content. Courtesy of https://stackoverflow.com/questions/995168/textarea-to-resize-based-on-content-length
this.textAreaRef.current.style.height = "1px";
const scrollHeight: number = this.textAreaRef.current.scrollHeight;
// Set the new height of the text area, bounded by the min and max height.
const newHeight = Math.min(Math.max(scrollHeight, textAreaMinHeight), textAreaMaxHeight);
this.textAreaRef.current.style.height = newHeight + "px";
onTextAreaInput(e: any) {
GlobalModel.inputModel.codeSelectDeselectAll();
}
@ -272,6 +287,7 @@ class AIChat extends React.Component<{}, {}> {
}
onArrowDownPressed(): boolean {
console.log("got here");
const currentRef = this.textAreaRef.current;
const inputModel = GlobalModel.inputModel;
if (currentRef == null) {
@ -294,15 +310,22 @@ class AIChat extends React.Component<{}, {}> {
}
render() {
const chatMessageItems = GlobalModel.aichatModel.aiCmdInfoChatItems.slice();
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
const renderAIChatKeybindings = GlobalModel.inputModel.shouldRenderAuxViewKeybindings(
appconst.InputAuxView_AIChat,
"sidebar"
);
console.log("renderAIChatKeybindings=====", renderAIChatKeybindings);
return (
<div className="sidebar-aichat">
<AIChatKeybindings AIChatObject={this}></AIChatKeybindings>
<If condition={renderAIChatKeybindings}>
<AIChatKeybindings AIChatObject={this}></AIChatKeybindings>
</If>
<div className="titlebar">
<div className="title-string">Wave AI</div>
</div>
<If condition={chatMessageItems.length > 0}>
<ChatContent />
<ChatContent chatWindowRef={this.chatWindowRef} />
</If>
<div className="chat-input">
<textarea
@ -311,11 +334,13 @@ class AIChat extends React.Component<{}, {}> {
autoComplete="off"
autoCorrect="off"
autoFocus={true}
id="chat-cmd-input"
className="chat-cmd-input chat-textarea"
onFocus={this.onTextAreaFocused}
onBlur={this.onTextAreaBlur}
onChange={this.onTextAreaChange}
onInput={this.onTextAreaInput}
onKeyDown={this.onKeyDown}
style={{ fontSize: this.termFontSize }}
className="chat-textarea"
placeholder="Send a Message..."
></textarea>
</div>

View File

@ -245,6 +245,7 @@ class AIChat extends React.Component<{}, {}> {
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
const chitem: OpenAICmdInfoChatMessageType = null;
const renderKeybindings = GlobalModel.inputModel.shouldRenderAuxViewKeybindings(appconst.InputAuxView_AIChat);
console.log("renderKeybindings", renderKeybindings);
return (
<AuxiliaryCmdView
title="Wave AI"
@ -267,14 +268,13 @@ class AIChat extends React.Component<{}, {}> {
ref={this.textAreaRef}
autoComplete="off"
autoCorrect="off"
id="chat-cmd-input"
className="chat-cmd-input chat-textarea"
onFocus={this.onTextAreaFocused}
onBlur={this.onTextAreaBlur}
onChange={this.onTextAreaChange}
onInput={this.onTextAreaInput}
onKeyDown={this.onKeyDown}
style={{ fontSize: this.termFontSize }}
className="chat-textarea"
placeholder="Send a Message..."
></textarea>
</div>

View File

@ -12,7 +12,7 @@
// Apply a border between the base cmdinput and any views shown above it
// TODO: use a generic selector for this
&.has-aichat,
// &.has-aichat,
&.has-history,
&.has-info,
&.has-suggestions {
@ -21,8 +21,8 @@
}
}
&.has-history,
&.has-aichat {
// &.has-aichat,
&.has-history {
height: max(300px, 70%);
}

View File

@ -86,9 +86,9 @@ class CmdInput extends React.Component<{}, {}> {
e.stopPropagation();
const inputModel = GlobalModel.inputModel;
if (inputModel.getActiveAuxView() === appconst.InputAuxView_AIChat) {
inputModel.closeAuxView();
// inputModel.closeAuxView();
} else {
inputModel.openAIAssistantChat();
// inputModel.openAIAssistantChat();
}
}
@ -191,10 +191,10 @@ class CmdInput extends React.Component<{}, {}> {
<div className="cmd-input-grow-spacer"></div>
<HistoryInfo />
</When>
<When condition={openView === appconst.InputAuxView_AIChat}>
{/* <When condition={openView === appconst.InputAuxView_AIChat}>
<div className="cmd-input-grow-spacer"></div>
<AIChat />
</When>
</When> */}
<When condition={openView === appconst.InputAuxView_Info}>
<InfoMsg key="infomsg" />
</When>

View File

@ -620,6 +620,8 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
const renderCmdInputKeybindings = inputModel.shouldRenderAuxViewKeybindings(null);
const renderHistoryKeybindings = inputModel.shouldRenderAuxViewKeybindings(appconst.InputAuxView_History);
console.log("renderCmdInputKeybindings", renderCmdInputKeybindings);
console.log("renderHistoryKeybindings", renderHistoryKeybindings);
return (
<div
className="textareainput-div control is-expanded"

View File

@ -3,26 +3,25 @@
import type React from "react";
import * as mobx from "mobx";
import type { Model } from "./model";
import { Model } from "./model";
import { GlobalCommandRunner } from "./global";
class AIChatModel {
globalModel: Model;
aiChatTextAreaRef: React.RefObject<HTMLTextAreaElement>;
aiChatWindowRef: React.RefObject<HTMLDivElement>;
chatTextAreaRef: React.RefObject<HTMLTextAreaElement>;
chatWindowRef: React.RefObject<HTMLDivElement>;
codeSelectBlockRefArray: Array<React.RefObject<HTMLElement>>;
codeSelectSelectedIndex: OV<number> = mobx.observable.box(-1);
codeSelectUuid: string;
aiCmdInfoChatItems: mobx.IObservableArray<OpenAICmdInfoChatMessageType> = mobx.observable.array([], {
name: "aicmdinfo-chat",
});
isFocused: OV<boolean> = mobx.observable.box(false, {
name: "isFocused",
});
readonly codeSelectTop: number = -2;
readonly codeSelectBottom: number = -1;
// focus
physicalInputFocused: OV<boolean> = mobx.observable.box(false);
constructor(globalModel: Model) {
this.globalModel = globalModel;
mobx.makeObservable(this);
@ -33,39 +32,20 @@ class AIChatModel {
this.codeSelectUuid = "";
}
// Focuses the main input or the auxiliary view, depending on the active auxiliary view
@mobx.action
giveFocus(): void {
// focus aichat sidebar input
focus(): void {
if (this.chatTextAreaRef?.current != null) {
this.chatTextAreaRef.current.focus();
}
this.isFocused.set(true);
}
@mobx.action
setPhysicalInputFocused(isFocused: boolean): void {
this.physicalInputFocused.set(isFocused);
if (isFocused) {
const screen = this.globalModel.getActiveScreen();
if (screen != null) {
if (screen.focusType.get() != "input") {
GlobalCommandRunner.screenSetFocus("input");
}
}
unFocus() {
if (this.chatTextAreaRef?.current != null) {
this.chatTextAreaRef.current.blur();
}
}
hasFocus(): boolean {
const mainInputElem = document.getElementById("main-cmd-input");
if (document.activeElement == mainInputElem) {
return true;
}
const historyInputElem = document.querySelector(".cmd-input input.history-input");
if (document.activeElement == historyInputElem) {
return true;
}
let aiChatInputElem = document.querySelector(".cmd-input chat-cmd-input");
if (document.activeElement == aiChatInputElem) {
return true;
}
return false;
this.isFocused.set(false);
}
@mobx.action
@ -74,27 +54,23 @@ class AIChatModel {
this.codeSelectBlockRefArray = [];
}
closeAuxView(): void {
close(): void {
// close and give focus back to main input
}
shouldRenderAuxViewKeybindings(view: InputAuxViewType): boolean {
shouldRenderKeybindings(view: InputAuxViewType): boolean {
// when aichat sidebar is mounted, it will render the keybindings
return true;
}
setCmdInfoChatRefs(
textAreaRef: React.RefObject<HTMLTextAreaElement>,
chatWindowRef: React.RefObject<HTMLDivElement>
) {
this.aiChatTextAreaRef = textAreaRef;
this.aiChatWindowRef = chatWindowRef;
setRefs(textAreaRef: React.RefObject<HTMLTextAreaElement>, chatWindowRef: React.RefObject<HTMLDivElement>) {
this.chatTextAreaRef = textAreaRef;
this.chatWindowRef = chatWindowRef;
}
setAIChatFocus() {
if (this.aiChatTextAreaRef?.current != null) {
this.aiChatTextAreaRef.current.focus();
}
unsetRefs() {
this.chatTextAreaRef = null;
this.chatWindowRef = null;
}
addCodeBlockToCodeSelect(blockRef: React.RefObject<HTMLElement>, uuid: string): number {
@ -113,14 +89,14 @@ class AIChatModel {
if (blockIndex >= 0 && blockIndex < this.codeSelectBlockRefArray.length) {
this.codeSelectSelectedIndex.set(blockIndex);
const currentRef = this.codeSelectBlockRefArray[blockIndex].current;
if (currentRef != null && this.aiChatWindowRef?.current != null) {
const chatWindowTop = this.aiChatWindowRef.current.scrollTop;
const chatWindowBottom = chatWindowTop + this.aiChatWindowRef.current.clientHeight - 100;
if (currentRef != null && this.chatWindowRef?.current != null) {
const chatWindowTop = this.chatWindowRef.current.scrollTop;
const chatWindowBottom = chatWindowTop + this.chatWindowRef.current.clientHeight - 100;
const elemTop = currentRef.offsetTop;
let elemBottom = elemTop - currentRef.offsetHeight;
const elementIsInView = elemBottom < chatWindowBottom && elemTop > chatWindowTop;
if (!elementIsInView) {
this.aiChatWindowRef.current.scrollTop = elemBottom - this.aiChatWindowRef.current.clientHeight / 3;
this.chatWindowRef.current.scrollTop = elemBottom - this.chatWindowRef.current.clientHeight / 3;
}
}
}
@ -139,8 +115,8 @@ class AIChatModel {
const incBlockIndex = this.codeSelectSelectedIndex.get() + 1;
if (this.codeSelectSelectedIndex.get() == this.codeSelectBlockRefArray.length - 1) {
this.codeSelectDeselectAll();
if (this.aiChatWindowRef?.current != null) {
this.aiChatWindowRef.current.scrollTop = this.aiChatWindowRef.current.scrollHeight;
if (this.chatWindowRef?.current != null) {
this.chatWindowRef.current.scrollTop = this.chatWindowRef.current.scrollHeight;
}
}
if (incBlockIndex >= 0 && incBlockIndex < this.codeSelectBlockRefArray.length) {
@ -162,8 +138,8 @@ class AIChatModel {
const decBlockIndex = this.codeSelectSelectedIndex.get() - 1;
if (decBlockIndex < 0) {
this.codeSelectDeselectAll(this.codeSelectTop);
if (this.aiChatWindowRef?.current != null) {
this.aiChatWindowRef.current.scrollTop = 0;
if (this.chatWindowRef?.current != null) {
this.chatWindowRef.current.scrollTop = 0;
}
}
if (decBlockIndex >= 0 && decBlockIndex < this.codeSelectBlockRefArray.length) {

View File

@ -139,6 +139,7 @@ class InputModel {
giveFocus(): void {
// Override active view to the main input if aux view does not have focus
const activeAuxView = this.getAuxViewFocus() ? this.getActiveAuxView() : null;
console.log("activeAuxView", activeAuxView);
switch (activeAuxView) {
case appconst.InputAuxView_History: {
const elem: HTMLElement = document.querySelector(".cmd-input input.history-input");
@ -190,7 +191,7 @@ class InputModel {
if (document.activeElement == historyInputElem) {
return true;
}
let aiChatInputElem = document.querySelector(".cmd-input chat-cmd-input");
let aiChatInputElem = document.querySelector(".cmd-input .chat-cmd-input");
if (document.activeElement == aiChatInputElem) {
return true;
}
@ -459,17 +460,22 @@ class InputModel {
this.giveFocus();
}
shouldRenderAuxViewKeybindings(view: InputAuxViewType): boolean {
shouldRenderAuxViewKeybindings(view: InputAuxViewType, test?: string): boolean {
console.log("view", view, this.getAuxViewFocus(), this.getActiveAuxView());
if (GlobalModel.activeMainView.get() != "session") {
console.log("1");
return false;
}
if (GlobalModel.getActiveScreen()?.getFocusType() != "input") {
console.log("2");
return false;
}
// (view == null) means standard cmdinput keybindings
if (view == null) {
console.log("3");
return !this.getAuxViewFocus();
} else {
console.log("4");
return this.getAuxViewFocus() && view == this.getActiveAuxView();
}
}
@ -659,6 +665,7 @@ class InputModel {
@mobx.action
openAIAssistantChat(): void {
console.log("openAIAssistantChat");
this.setActiveAuxView(appconst.InputAuxView_AIChat);
this.setAuxViewFocus(true);
this.globalModel.sendActivity("aichat-open");
@ -759,23 +766,18 @@ class InputModel {
@mobx.computed
get curLine(): string {
console.log("triggered get curLine");
const hidx = this.historyIndex.get();
if (hidx < this.modHistory.length && this.modHistory[hidx] != null) {
console.log("this.modHistory[hidx]", this.modHistory[hidx]);
return this.modHistory[hidx];
}
const hitems = this.filteredHistoryItems;
if (hidx == 0 || hitems == null || hidx > hitems.length) {
console.log("returning empty string 1");
return "";
}
const hitem = hitems[hidx - 1];
if (hitem == null) {
console.log("returning empty string 2");
return "";
}
console.log("returning hitem.cmdstr", hitem.cmdstr);
return hitem.cmdstr;
}

View File

@ -1345,7 +1345,6 @@ class Model {
interactive: boolean,
runUpdate: boolean = true
): Promise<CommandRtnType> {
console.log("cmdPk", cmdPk);
if (this.debugCmds > 0) {
console.log("[cmd]", cmdPacketString(cmdPk));
if (this.debugCmds > 1) {

View File

@ -461,7 +461,11 @@ class SourceCodeRenderer extends React.Component<
ref={this.markdownRef}
onScroll={() => this.handleDivScroll()}
>
<Markdown text={this.state.code} style={{ width: "100%", padding: "1rem" }} />
<Markdown
inputModel={GlobalModel.inputModel}
text={this.state.code}
style={{ width: "100%", padding: "1rem" }}
/>
</div>
);
}