From cc61b16cec587f064ab8b222d70456720009f134 Mon Sep 17 00:00:00 2001 From: Red Adaya Date: Thu, 2 May 2024 22:16:38 +0800 Subject: [PATCH] separate model for aichat sidebar --- src/app/bookmarks/bookmarks.tsx | 2 +- src/app/common/elements/markdown.tsx | 25 +-- src/app/common/modals/alert.tsx | 6 +- src/app/common/modals/userinput.tsx | 6 +- src/app/pluginsview/pluginsview.tsx | 2 +- src/app/sidebar/aichat.tsx | 7 +- src/app/workspace/cmdinput/aichat.tsx | 6 +- src/models/aichat.ts | 234 ++++++++++++++++++++++++++ src/models/index.ts | 1 + src/models/input.ts | 6 + src/models/model.ts | 4 + src/plugins/markdown/markdown.tsx | 2 + src/plugins/openai/openai.tsx | 7 +- 13 files changed, 290 insertions(+), 18 deletions(-) create mode 100644 src/models/aichat.ts diff --git a/src/app/bookmarks/bookmarks.tsx b/src/app/bookmarks/bookmarks.tsx index 31e31953..b17120f7 100644 --- a/src/app/bookmarks/bookmarks.tsx +++ b/src/app/bookmarks/bookmarks.tsx @@ -207,7 +207,7 @@ class Bookmark extends React.Component {
{bm.bookmarkid.substr(0, 8)}
- + { blockIndex: number; @@ -40,13 +40,12 @@ class CodeBlockMarkdown extends React.Component< constructor(props) { super(props); this.blockRef = React.createRef(); - this.blockIndex = GlobalModel.inputModel.addCodeBlockToCodeSelect(this.blockRef, this.props.uuid); + this.blockIndex = props.inputModel.addCodeBlockToCodeSelect(this.blockRef, this.props.uuid); } render() { - let clickHandler: (e: React.MouseEvent, blockIndex: number) => void; - let inputModel = GlobalModel.inputModel; - clickHandler = (e: React.MouseEvent, blockIndex: number) => { + const inputModel = this.props.inputModel; + const clickHandler = (e: React.MouseEvent, blockIndex: number) => { inputModel.setCodeSelectSelectedCodeBlock(blockIndex); }; let selected = this.blockIndex == this.props.codeSelectSelectedIndex; @@ -64,7 +63,7 @@ class CodeBlockMarkdown extends React.Component< @mobxReact.observer class Markdown extends React.Component< - { text: string; style?: any; extraClassName?: string; codeSelect?: boolean }, + { inputModel: any; text: string; style?: any; extraClassName?: string; codeSelect?: boolean }, {} > { curUuid: string; @@ -75,10 +74,14 @@ class Markdown extends React.Component< } @boundMethod - CodeBlockRenderer(props: any, codeSelect: boolean, codeSelectIndex: number, curUuid: string): any { + codeBlockRenderer(props: any, codeSelect: boolean, codeSelectIndex: number, curUuid: string): any { if (codeSelect) { return ( - + {props.children} ); @@ -95,9 +98,9 @@ class Markdown extends React.Component< } render() { - let text = this.props.text; + let { text, inputModel } = this.props; let codeSelect = this.props.codeSelect; - let curCodeSelectIndex = GlobalModel.inputModel.getCodeSelectSelectedIndex(); + let curCodeSelectIndex = inputModel.getCodeSelectSelectedIndex(); let markdownComponents = { a: LinkRenderer, h1: (props) => HeaderRenderer(props, 1), @@ -107,7 +110,7 @@ class Markdown extends React.Component< h5: (props) => HeaderRenderer(props, 5), h6: (props) => HeaderRenderer(props, 6), code: (props) => CodeRenderer(props), - pre: (props) => this.CodeBlockRenderer(props, codeSelect, curCodeSelectIndex, this.curUuid), + pre: (props) => this.codeBlockRenderer(props, codeSelect, curCodeSelectIndex, this.curUuid), }; return (
diff --git a/src/app/common/modals/alert.tsx b/src/app/common/modals/alert.tsx index 245bdd62..a550255d 100644 --- a/src/app/common/modals/alert.tsx +++ b/src/app/common/modals/alert.tsx @@ -42,7 +42,11 @@ class AlertModal extends React.Component<{}, {}> {
- + {message?.message} diff --git a/src/app/common/modals/userinput.tsx b/src/app/common/modals/userinput.tsx index 98e40a3b..30c97418 100644 --- a/src/app/common/modals/userinput.tsx +++ b/src/app/common/modals/userinput.tsx @@ -64,7 +64,11 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
- + {userInputRequest.querytext}
diff --git a/src/app/pluginsview/pluginsview.tsx b/src/app/pluginsview/pluginsview.tsx index 7cfab2f0..9061303f 100644 --- a/src/app/pluginsview/pluginsview.tsx +++ b/src/app/pluginsview/pluginsview.tsx @@ -78,7 +78,7 @@ class PluginsView extends React.Component<{}, {}> { {plugin.readme && (
{"Readme"}
- +
)}
diff --git a/src/app/sidebar/aichat.tsx b/src/app/sidebar/aichat.tsx index 4923171a..f694484a 100644 --- a/src/app/sidebar/aichat.tsx +++ b/src/app/sidebar/aichat.tsx @@ -115,7 +115,11 @@ class ChatContent extends React.Component<{}, {}> {
- + ); } @@ -179,6 +183,7 @@ 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) { diff --git a/src/app/workspace/cmdinput/aichat.tsx b/src/app/workspace/cmdinput/aichat.tsx index b671a733..5c67d579 100644 --- a/src/app/workspace/cmdinput/aichat.tsx +++ b/src/app/workspace/cmdinput/aichat.tsx @@ -224,7 +224,11 @@ class AIChat extends React.Component<{}, {}> {
AI Assistant
- + ); } diff --git a/src/models/aichat.ts b/src/models/aichat.ts new file mode 100644 index 00000000..288241f7 --- /dev/null +++ b/src/models/aichat.ts @@ -0,0 +1,234 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import type React from "react"; +import * as mobx from "mobx"; +import type { Model } from "./model"; +import { GlobalCommandRunner } from "./global"; + +class AIChatModel { + globalModel: Model; + activeAuxView: OV = mobx.observable.box(null); + auxViewFocus: OV = mobx.observable.box(false); + cmdInputHeight: OV = mobx.observable.box(0); + aiChatTextAreaRef: React.RefObject; + aiChatWindowRef: React.RefObject; + codeSelectBlockRefArray: Array>; + codeSelectSelectedIndex: OV = mobx.observable.box(-1); + codeSelectUuid: string; + + AICmdInfoChatItems: mobx.IObservableArray = mobx.observable.array([], { + name: "aicmdinfo-chat", + }); + readonly codeSelectTop: number = -2; + readonly codeSelectBottom: number = -1; + + infoMsg: OV = mobx.observable.box(null); + infoTimeoutId: any = null; + inputExpanded: OV = mobx.observable.box(false, { + name: "inputExpanded", + }); + + // focus + inputFocused: OV = mobx.observable.box(false); + lineFocused: OV = mobx.observable.box(false); + physicalInputFocused: OV = mobx.observable.box(false); + forceInputFocus: boolean = false; + + lastCurLine: string = ""; + + constructor(globalModel: Model) { + this.globalModel = globalModel; + mobx.makeObservable(this); + mobx.action(() => { + this.codeSelectSelectedIndex.set(-1); + this.codeSelectBlockRefArray = []; + })(); + this.codeSelectUuid = ""; + } + + // Focuses the main input or the auxiliary view, depending on the active auxiliary view + @mobx.action + giveFocus(): void { + // focus aichat sidebar input + } + + @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"); + } + } + } + } + + 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; + } + + @mobx.action + setOpenAICmdInfoChat(chat: OpenAICmdInfoChatMessageType[]): void { + this.AICmdInfoChatItems.replace(chat); + this.codeSelectBlockRefArray = []; + } + + closeAuxView(): void { + // close and give focus back to main input + } + + shouldRenderAuxViewKeybindings(view: InputAuxViewType): boolean { + // when aichat sidebar is mounted, it will render the keybindings + return true; + } + + setCmdInfoChatRefs( + textAreaRef: React.RefObject, + chatWindowRef: React.RefObject + ) { + this.aiChatTextAreaRef = textAreaRef; + this.aiChatWindowRef = chatWindowRef; + } + + setAIChatFocus() { + if (this.aiChatTextAreaRef?.current != null) { + this.aiChatTextAreaRef.current.focus(); + } + } + + addCodeBlockToCodeSelect(blockRef: React.RefObject, uuid: string): number { + let rtn = -1; + if (uuid != this.codeSelectUuid) { + this.codeSelectUuid = uuid; + this.codeSelectBlockRefArray = []; + } + rtn = this.codeSelectBlockRefArray.length; + this.codeSelectBlockRefArray.push(blockRef); + return rtn; + } + + @mobx.action + setCodeSelectSelectedCodeBlock(blockIndex: number) { + 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; + 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.codeSelectBlockRefArray = []; + } + + @mobx.action + codeSelectSelectNextNewestCodeBlock() { + // oldest code block = index 0 in array + // this decrements codeSelectSelected index + if (this.codeSelectSelectedIndex.get() == this.codeSelectTop) { + this.codeSelectSelectedIndex.set(this.codeSelectBottom); + } else if (this.codeSelectSelectedIndex.get() == this.codeSelectBottom) { + return; + } + 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 (incBlockIndex >= 0 && incBlockIndex < this.codeSelectBlockRefArray.length) { + this.setCodeSelectSelectedCodeBlock(incBlockIndex); + } + } + + @mobx.action + codeSelectSelectNextOldestCodeBlock() { + if (this.codeSelectSelectedIndex.get() == this.codeSelectBottom) { + if (this.codeSelectBlockRefArray.length > 0) { + this.codeSelectSelectedIndex.set(this.codeSelectBlockRefArray.length); + } else { + return; + } + } else if (this.codeSelectSelectedIndex.get() == this.codeSelectTop) { + return; + } + const decBlockIndex = this.codeSelectSelectedIndex.get() - 1; + if (decBlockIndex < 0) { + this.codeSelectDeselectAll(this.codeSelectTop); + if (this.aiChatWindowRef?.current != null) { + this.aiChatWindowRef.current.scrollTop = 0; + } + } + if (decBlockIndex >= 0 && decBlockIndex < this.codeSelectBlockRefArray.length) { + this.setCodeSelectSelectedCodeBlock(decBlockIndex); + } + } + + getCodeSelectSelectedIndex() { + return this.codeSelectSelectedIndex.get(); + } + + getCodeSelectRefArrayLength() { + return this.codeSelectBlockRefArray.length; + } + + codeBlockIsSelected(blockIndex: number): boolean { + return blockIndex == this.codeSelectSelectedIndex.get(); + } + + codeSelectDeselectAll(direction: number = this.codeSelectBottom) { + if (this.codeSelectSelectedIndex.get() == direction) { + return; + } + mobx.action(() => { + this.codeSelectSelectedIndex.set(direction); + this.codeSelectBlockRefArray = []; + })(); + } + + @mobx.action + openAIAssistantChat(): void { + // open aichat sidebar + } + + clearAIAssistantChat(): void { + const prtn = this.globalModel.submitChatInfoCommand("", "", true); + prtn.then((rtn) => { + if (!rtn.success) { + console.log("submit chat command error: " + rtn.error); + } + }).catch((error) => { + console.log("submit chat command error: ", error); + }); + } + + _clearInfoTimeout(): void { + if (this.infoTimeoutId != null) { + clearTimeout(this.infoTimeoutId); + this.infoTimeoutId = null; + } + } +} + +export { AIChatModel }; diff --git a/src/models/index.ts b/src/models/index.ts index 0fb91807..27ca1087 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -6,6 +6,7 @@ export { ClientSettingsViewModel } from "./clientsettingsview"; export { Cmd } from "./cmd"; export { ConnectionsViewModel } from "./connectionsview"; export { InputModel } from "./input"; +export { AIChatModel } from "./aichat"; export { MainSidebarModel } from "./mainsidebar"; export { RightSidebarModel } from "./rightsidebar"; export { ModalsModel } from "./modals"; diff --git a/src/models/input.ts b/src/models/input.ts index dede987f..e1f18700 100644 --- a/src/models/input.ts +++ b/src/models/input.ts @@ -759,22 +759,28 @@ 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; } set curLine(val: string) { + console.log("triggered set curLine"); this.lastCurLine = this.curLine; const hidx = this.historyIndex.get(); mobx.action(() => { diff --git a/src/models/model.ts b/src/models/model.ts index 02346804..ac89ad48 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -21,6 +21,7 @@ import { KeybindManager, adaptFromReactOrNativeKeyEvent, setKeyUtilPlatform } fr import { Session } from "./session"; import { ScreenLines } from "./screenlines"; import { InputModel } from "./input"; +import { AIChatModel } from "./aichat"; import { PluginsModel } from "./plugins"; import { BookmarksModel } from "./bookmarks"; import { HistoryViewModel } from "./historyview"; @@ -113,6 +114,7 @@ class Model { keybindManager: KeybindManager; inputModel: InputModel; + aichatModel: AIChatModel; pluginsModel: PluginsModel; bookmarksModel: BookmarksModel; historyViewModel: HistoryViewModel; @@ -162,6 +164,7 @@ class Model { this.initSystemKeybindings(); this.initAppKeybindings(); this.inputModel = new InputModel(this); + this.aichatModel = new AIChatModel(this); this.pluginsModel = new PluginsModel(this); this.bookmarksModel = new BookmarksModel(this); this.historyViewModel = new HistoryViewModel(this); @@ -1341,6 +1344,7 @@ class Model { interactive: boolean, runUpdate: boolean = true ): Promise { + console.log("cmdPk", cmdPk); if (this.debugCmds > 0) { console.log("[cmd]", cmdPacketString(cmdPk)); if (this.debugCmds > 1) { diff --git a/src/plugins/markdown/markdown.tsx b/src/plugins/markdown/markdown.tsx index 93994921..26ea7ccb 100644 --- a/src/plugins/markdown/markdown.tsx +++ b/src/plugins/markdown/markdown.tsx @@ -6,6 +6,7 @@ import * as mobx from "mobx"; import * as mobxReact from "mobx-react"; import { sprintf } from "sprintf-js"; import { Markdown } from "@/elements"; +import { GlobalModel } from "@/models/global"; import "./markdown.less"; @@ -78,6 +79,7 @@ class SimpleMarkdownRenderer extends React.Component< }} > diff --git a/src/plugins/openai/openai.tsx b/src/plugins/openai/openai.tsx index 00309225..0288d53a 100644 --- a/src/plugins/openai/openai.tsx +++ b/src/plugins/openai/openai.tsx @@ -8,6 +8,7 @@ import { debounce } from "throttle-debounce"; import { boundMethod } from "autobind-decorator"; import { PacketDataBuffer } from "../core/ptydata"; import { Markdown } from "@/elements"; +import { GlobalModel } from "@/models/global"; import "./openai.less"; @@ -207,7 +208,11 @@ class OpenAIRenderer extends React.Component<{ model: OpenAIRendererModel }> { paddingRight: 5, }} > - +