mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
separate model for aichat sidebar
This commit is contained in:
@@ -207,7 +207,7 @@ class Bookmark extends React.Component<BookmarkProps, {}> {
|
||||
<div className="bookmark-id-div">{bm.bookmarkid.substr(0, 8)}</div>
|
||||
<div className="bookmark-content">
|
||||
<If condition={hasDesc}>
|
||||
<Markdown text={markdown} extraClassName="bottom-margin" />
|
||||
<Markdown inputModel={GlobalModel.inputModel} text={markdown} extraClassName="bottom-margin" />
|
||||
</If>
|
||||
<CmdStrCode
|
||||
cmdstr={bm.cmdstr}
|
||||
|
||||
@@ -31,7 +31,7 @@ function CodeRenderer(props: any): any {
|
||||
|
||||
@mobxReact.observer
|
||||
class CodeBlockMarkdown extends React.Component<
|
||||
{ children: React.ReactNode; codeSelectSelectedIndex?: number; uuid: string },
|
||||
{ inputModel: any; children: React.ReactNode; codeSelectSelectedIndex?: number; uuid: string },
|
||||
{}
|
||||
> {
|
||||
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<HTMLElement>, blockIndex: number) => void;
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
clickHandler = (e: React.MouseEvent<HTMLElement>, blockIndex: number) => {
|
||||
const inputModel = this.props.inputModel;
|
||||
const clickHandler = (e: React.MouseEvent<HTMLElement>, 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 (
|
||||
<CodeBlockMarkdown codeSelectSelectedIndex={codeSelectIndex} uuid={curUuid}>
|
||||
<CodeBlockMarkdown
|
||||
inputModel={this.props.inputModel}
|
||||
codeSelectSelectedIndex={codeSelectIndex}
|
||||
uuid={curUuid}
|
||||
>
|
||||
{props.children}
|
||||
</CodeBlockMarkdown>
|
||||
);
|
||||
@@ -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 (
|
||||
<div className={cn("markdown content", this.props.extraClassName)} style={this.props.style}>
|
||||
|
||||
@@ -42,7 +42,11 @@ class AlertModal extends React.Component<{}, {}> {
|
||||
<Modal.Header onClose={this.closeModal} title={title} keybindings={true} />
|
||||
<div className="wave-modal-body">
|
||||
<If condition={message?.markdown}>
|
||||
<Markdown text={message?.message ?? ""} extraClassName="bottom-margin" />
|
||||
<Markdown
|
||||
inputModel={GlobalModel.inputModel}
|
||||
text={message?.message ?? ""}
|
||||
extraClassName="bottom-margin"
|
||||
/>
|
||||
</If>
|
||||
<If condition={!message?.markdown}>{message?.message}</If>
|
||||
<If condition={message?.confirmflag}>
|
||||
|
||||
@@ -64,7 +64,11 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
|
||||
<div className="wave-modal-dialog">
|
||||
<div className="userinput-query">
|
||||
<If condition={userInputRequest.markdown}>
|
||||
<Markdown text={userInputRequest.querytext} extraClassName="bottom-margin" />
|
||||
<Markdown
|
||||
inputModel={GlobalModel.inputModel}
|
||||
text={userInputRequest.querytext}
|
||||
extraClassName="bottom-margin"
|
||||
/>
|
||||
</If>
|
||||
<If condition={!userInputRequest.markdown}>{userInputRequest.querytext}</If>
|
||||
</div>
|
||||
|
||||
@@ -78,7 +78,7 @@ class PluginsView extends React.Component<{}, {}> {
|
||||
{plugin.readme && (
|
||||
<div className="plugin-readme">
|
||||
<div className="plugin-label">{"Readme"}</div>
|
||||
<Markdown text={plugin.readme} />
|
||||
<Markdown inputModel={GlobalModel.inputModel} text={plugin.readme} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -115,7 +115,11 @@ class ChatContent extends React.Component<{}, {}> {
|
||||
<div className="chat-msg-header">
|
||||
<i className="fa-sharp fa-solid fa-sparkles"></i>
|
||||
</div>
|
||||
<Markdown text={chatItem.assistantresponse.message} codeSelect />
|
||||
<Markdown
|
||||
inputModel={GlobalModel.aichatModel}
|
||||
text={chatItem.assistantresponse.message}
|
||||
codeSelect
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -224,7 +224,11 @@ class AIChat extends React.Component<{}, {}> {
|
||||
<i className="fa-sharp fa-solid fa-sparkles"></i>
|
||||
<div className="chat-username">AI Assistant</div>
|
||||
</div>
|
||||
<Markdown text={chatItem.assistantresponse.message} codeSelect />
|
||||
<Markdown
|
||||
inputModel={GlobalModel.inputModel}
|
||||
text={chatItem.assistantresponse.message}
|
||||
codeSelect
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<InputAuxViewType> = mobx.observable.box(null);
|
||||
auxViewFocus: OV<boolean> = mobx.observable.box(false);
|
||||
cmdInputHeight: OV<number> = mobx.observable.box(0);
|
||||
aiChatTextAreaRef: React.RefObject<HTMLTextAreaElement>;
|
||||
aiChatWindowRef: 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",
|
||||
});
|
||||
readonly codeSelectTop: number = -2;
|
||||
readonly codeSelectBottom: number = -1;
|
||||
|
||||
infoMsg: OV<InfoType> = mobx.observable.box(null);
|
||||
infoTimeoutId: any = null;
|
||||
inputExpanded: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "inputExpanded",
|
||||
});
|
||||
|
||||
// focus
|
||||
inputFocused: OV<boolean> = mobx.observable.box(false);
|
||||
lineFocused: OV<boolean> = mobx.observable.box(false);
|
||||
physicalInputFocused: OV<boolean> = 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<HTMLTextAreaElement>,
|
||||
chatWindowRef: React.RefObject<HTMLDivElement>
|
||||
) {
|
||||
this.aiChatTextAreaRef = textAreaRef;
|
||||
this.aiChatWindowRef = chatWindowRef;
|
||||
}
|
||||
|
||||
setAIChatFocus() {
|
||||
if (this.aiChatTextAreaRef?.current != null) {
|
||||
this.aiChatTextAreaRef.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
addCodeBlockToCodeSelect(blockRef: React.RefObject<HTMLElement>, 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 };
|
||||
@@ -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";
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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<CommandRtnType> {
|
||||
console.log("cmdPk", cmdPk);
|
||||
if (this.debugCmds > 0) {
|
||||
console.log("[cmd]", cmdPacketString(cmdPk));
|
||||
if (this.debugCmds > 1) {
|
||||
|
||||
@@ -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<
|
||||
}}
|
||||
>
|
||||
<Markdown
|
||||
inputModel={GlobalModel.inputModel}
|
||||
text={this.markdownText.get()}
|
||||
style={{ maxHeight: opts.maxSize.height, maxWidth: DefaultMaxMarkdownWidth }}
|
||||
/>
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
>
|
||||
<Markdown text={message} style={{ maxHeight: opts.maxSize.height }} />
|
||||
<Markdown
|
||||
inputModel={GlobalModel.aichatModel}
|
||||
text={message}
|
||||
style={{ maxHeight: opts.maxSize.height }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user