mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d9f429ee1 | |||
| bcf6e91f48 | |||
| 4758b7351d | |||
| c6207970ee | |||
| de271d3873 | |||
| 8aaea9f1c8 | |||
| c93b6e3d34 | |||
| 1307a77b56 | |||
| 2772c76728 | |||
| 4384525ca6 | |||
| d6b82e3792 | |||
| 8a22e73e60 | |||
| 35a5f1645d | |||
| 34837aabae | |||
| 918ff98ea1 | |||
| cc61b16cec | |||
| 4f06cd8095 | |||
| fbc7bc9688 | |||
| 67df71d1ca | |||
| d177fab6e3 | |||
| 8ea22c4c1f | |||
| af32c8f6f3 | |||
| f2e31a6483 | |||
| 8b8b7ea2b6 | |||
| dbc91d8a4e | |||
| de81464082 | |||
| 5e6cbf30e7 | |||
| 75ae8807eb | |||
| 84227a7128 | |||
| 498f91d7fe | |||
| 4d05ef9998 | |||
| 1eb7ff512e | |||
| ba5a089382 | |||
| e40f93b858 | |||
| 30b9453bcd | |||
| 2a79057314 | |||
| 268bfabec2 | |||
| 30d1e4dfad | |||
| c522d55a23 |
@@ -22,7 +22,7 @@ jobs:
|
||||
- uses: dashcamio/testdriver@main
|
||||
id: testdriver
|
||||
with:
|
||||
version: v2.10.2
|
||||
version: v2.12.5
|
||||
prerun: |
|
||||
rm ~/Desktop/WITH-LOVE-FROM-AMERICA.txt
|
||||
cd ~/actions-runner/_work/testdriver/testdriver/
|
||||
|
||||
Generated
+14710
File diff suppressed because it is too large
Load Diff
+2
-3
@@ -38,18 +38,17 @@
|
||||
"monaco-editor": "0.48.0",
|
||||
"mustache": "^4.2.0",
|
||||
"node-fetch": "^3.2.10",
|
||||
"overlayscrollbars": "^2.6.1",
|
||||
"overlayscrollbars": "^2.7.3",
|
||||
"overlayscrollbars-react": "^0.5.5",
|
||||
"papaparse": "^5.4.1",
|
||||
"react": "^18.1.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-markdown": "^9.0.0",
|
||||
"remark": "^15.0.1",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"sprintf-js": "^1.1.2",
|
||||
"throttle-debounce": "^5.0.0",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"tsx-control-statements": "^5.1.1",
|
||||
"uuid": "^9.0.0",
|
||||
"winston": "^3.8.2",
|
||||
|
||||
@@ -43,19 +43,17 @@ class CodeBlockMarkdown extends React.Component<
|
||||
this.blockIndex = GlobalModel.inputModel.addCodeBlockToCodeSelect(this.blockRef, this.props.uuid);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleClick(e: React.MouseEvent<HTMLPreElement>) {
|
||||
// console.log("this.blockIndex", this.blockIndex);
|
||||
GlobalModel.inputModel.setCodeSelectSelectedCodeBlock(this.blockIndex);
|
||||
}
|
||||
|
||||
render() {
|
||||
let clickHandler: (e: React.MouseEvent<HTMLElement>, blockIndex: number) => void;
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
clickHandler = (e: React.MouseEvent<HTMLElement>, blockIndex: number) => {
|
||||
inputModel.setCodeSelectSelectedCodeBlock(blockIndex);
|
||||
};
|
||||
// console.log("this.blockIndex", this.blockIndex);
|
||||
let selected = this.blockIndex == this.props.codeSelectSelectedIndex;
|
||||
return (
|
||||
<pre
|
||||
ref={this.blockRef}
|
||||
className={clsx({ selected: selected })}
|
||||
onClick={(event) => clickHandler(event, this.blockIndex)}
|
||||
>
|
||||
<pre ref={this.blockRef} className={clsx({ selected: selected })} onClick={this.handleClick}>
|
||||
{this.props.children}
|
||||
</pre>
|
||||
);
|
||||
@@ -75,7 +73,7 @@ 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}>
|
||||
@@ -95,7 +93,7 @@ class Markdown extends React.Component<
|
||||
}
|
||||
|
||||
render() {
|
||||
let text = this.props.text;
|
||||
let { text } = this.props;
|
||||
let codeSelect = this.props.codeSelect;
|
||||
let curCodeSelectIndex = GlobalModel.inputModel.getCodeSelectSelectedIndex();
|
||||
let markdownComponents = {
|
||||
@@ -107,7 +105,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={clsx("markdown content", this.props.extraClassName)} style={this.props.style}>
|
||||
|
||||
@@ -23,7 +23,7 @@ let MagicLayout = {
|
||||
MainSidebarDefaultWidth: 240,
|
||||
|
||||
RightSidebarMinWidth: 0,
|
||||
RightSidebarMaxWidth: 300,
|
||||
RightSidebarMaxWidth: 400,
|
||||
RightSidebarSnapThreshold: 90,
|
||||
RightSidebarDragResistance: 50,
|
||||
RightSidebarDefaultWidth: 240,
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
.sidebar-aichat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
|
||||
.titlebar {
|
||||
background-color: var(--app-panel-bg-color);
|
||||
color: var(--term-blue);
|
||||
padding: 6px 15px;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--app-border-color);
|
||||
font: var(--base-font);
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
line-height: 18px;
|
||||
overflow: hidden;
|
||||
|
||||
.title-string {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
> .content {
|
||||
flex-flow: column nowrap;
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
overflow-y: auto;
|
||||
|
||||
.chat-window {
|
||||
display: flex;
|
||||
// margin-bottom: 5px;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
// This is the filler that will push the chat messages to the bottom until the chat window is full
|
||||
.filler {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg {
|
||||
padding: calc(var(--termpad) * 2);
|
||||
display: flex;
|
||||
|
||||
.chat-msg-header {
|
||||
display: flex;
|
||||
margin-bottom: 2px;
|
||||
|
||||
i {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg-assistant {
|
||||
color: var(--app-text-color);
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg-error {
|
||||
color: var(--cmdinput-text-error);
|
||||
font-family: var(--markdown-font);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
padding: 16px 15px 7px;
|
||||
flex: 0 0 auto;
|
||||
border-top: 1px solid var(--app-border-color);
|
||||
|
||||
.chat-textarea {
|
||||
color: var(--app-text-primary-color);
|
||||
background-color: var(--cmdinput-textarea-bg);
|
||||
resize: none;
|
||||
width: 100%;
|
||||
border: transparent;
|
||||
outline: none;
|
||||
overflow: auto;
|
||||
overflow-wrap: anywhere;
|
||||
font-family: var(--termfontfamily);
|
||||
font-weight: normal;
|
||||
line-height: var(--termlineheight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
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";
|
||||
|
||||
class AIChatKeybindings extends React.Component<{ AIChatObject: AIChat }, {}> {
|
||||
componentDidMount(): void {
|
||||
const AIChatObject = this.props.AIChatObject;
|
||||
const keybindManager = GlobalModel.keybindManager;
|
||||
const 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.closeAuxView();
|
||||
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 ChatContent extends React.Component<{ chatWindowRef }, {}> {
|
||||
chatListKeyCount: number = 0;
|
||||
containerRef: React.RefObject<OverlayScrollbarsComponentRef> = React.createRef();
|
||||
osInstance: OverlayScrollbars = null;
|
||||
|
||||
componentDidUpdate() {
|
||||
this.chatListKeyCount = 0;
|
||||
if (this.containerRef?.current && this.osInstance) {
|
||||
const { viewport } = this.osInstance.elements();
|
||||
// viewport.scrollTo({
|
||||
// behavior: "auto",
|
||||
// top: this.props.chatWindowRef.current.scrollHeight,
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.osInstance) {
|
||||
this.osInstance.destroy();
|
||||
this.osInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onScrollbarInitialized(instance) {
|
||||
console.log("got here");
|
||||
GlobalModel.inputModel.setChatOsInstance(instance);
|
||||
this.osInstance = instance;
|
||||
const { viewport } = instance.elements();
|
||||
viewport.scrollTo({
|
||||
behavior: "auto",
|
||||
top: this.props.chatWindowRef.current.scrollHeight,
|
||||
});
|
||||
}
|
||||
|
||||
renderError(err: string): any {
|
||||
return <div className="chat-msg-error">{err}</div>;
|
||||
}
|
||||
|
||||
renderChatMessage(chatItem: OpenAICmdInfoChatMessageType): any {
|
||||
const curKey = "chatmsg-" + this.chatListKeyCount;
|
||||
this.chatListKeyCount++;
|
||||
const senderClassName = chatItem.isassistantresponse ? "chat-msg-assistant" : "chat-msg-user";
|
||||
const msgClassName = `chat-msg ${senderClassName}`;
|
||||
|
||||
let innerHTML: React.JSX.Element = (
|
||||
<>
|
||||
<div className="chat-msg-header">
|
||||
<i className="fa-sharp fa-solid fa-user"></i>
|
||||
</div>
|
||||
<div className="msg-text">{chatItem.userquery}</div>
|
||||
</>
|
||||
);
|
||||
if (chatItem.isassistantresponse) {
|
||||
if (chatItem.assistantresponse.error != null && chatItem.assistantresponse.error !== "") {
|
||||
innerHTML = this.renderError(chatItem.assistantresponse.error);
|
||||
} else {
|
||||
innerHTML = (
|
||||
<>
|
||||
<div className="chat-msg-header">
|
||||
<i className="fa-sharp fa-solid fa-sparkles"></i>
|
||||
</div>
|
||||
<Markdown text={chatItem.assistantresponse.message} codeSelect />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const cssVar = GlobalModel.isDev ? "--app-panel-bg-color-dev" : "--app-panel-bg-color";
|
||||
const panelBgColor = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim();
|
||||
const color = tinycolor(panelBgColor);
|
||||
const newColor = color.isValid() ? tinycolor(panelBgColor).darken(6).toString() : "none";
|
||||
const backgroundColor = this.chatListKeyCount % 2 === 0 ? "none" : newColor;
|
||||
|
||||
return (
|
||||
<div className={msgClassName} key={curKey} style={{ backgroundColor }}>
|
||||
{innerHTML}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
|
||||
const chitem: OpenAICmdInfoChatMessageType = null;
|
||||
return (
|
||||
<OverlayScrollbarsComponent
|
||||
ref={this.containerRef}
|
||||
className="content"
|
||||
options={{ scrollbars: { autoHide: "leave" } }}
|
||||
events={{ initialized: this.onScrollbarInitialized }}
|
||||
>
|
||||
<div ref={this.props.chatWindowRef} className="chat-window">
|
||||
<div className="filler"></div>
|
||||
<For each="chitem" index="idx" of={chatMessageItems}>
|
||||
{this.renderChatMessage(chitem)}
|
||||
</For>
|
||||
</div>
|
||||
</OverlayScrollbarsComponent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class AIChat extends React.Component<{}, {}> {
|
||||
textAreaRef: React.RefObject<HTMLTextAreaElement> = React.createRef();
|
||||
chatWindowRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
termFontSize: number = 14;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
mobx.makeObservable(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
|
||||
inputModel.openAIAssistantChat();
|
||||
|
||||
if (this.textAreaRef.current != null) {
|
||||
this.textAreaRef.current.focus();
|
||||
inputModel.setCmdInfoChatRefs(this.textAreaRef, this.chatWindowRef);
|
||||
}
|
||||
this.requestChatUpdate();
|
||||
this.onTextAreaChange(null);
|
||||
}
|
||||
|
||||
requestChatUpdate() {
|
||||
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
|
||||
if (chatMessageItems == null || chatMessageItems.length == 0) {
|
||||
this.submitChatMessage("");
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
submitChatMessage(messageStr: string) {
|
||||
const curLine = GlobalModel.inputModel.curLine;
|
||||
const prtn = GlobalModel.submitChatInfoCommand(messageStr, curLine, false);
|
||||
prtn.then((rtn) => {
|
||||
if (!rtn.success) {
|
||||
console.log("submit chat command error: " + rtn.error);
|
||||
}
|
||||
}).catch((_) => {});
|
||||
}
|
||||
|
||||
getLinePos(elem: any): { numLines: number; linePos: number } {
|
||||
const numLines = elem.value.split("\n").length;
|
||||
const linePos = elem.value.substr(0, elem.selectionStart).split("\n").length;
|
||||
return { numLines, linePos };
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
onTextAreaFocused(e: any) {
|
||||
GlobalModel.inputModel.setAuxViewFocus(true);
|
||||
GlobalModel.inputModel.setActiveAuxView(appconst.InputAuxView_AIChat);
|
||||
|
||||
this.onTextAreaChange(e);
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
onTextAreaBlur(e: any) {
|
||||
mobx.action(() => {
|
||||
GlobalModel.inputModel.setAuxViewFocus(false);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onTextAreaInput(e: any) {
|
||||
GlobalModel.inputModel.codeSelectDeselectAll();
|
||||
}
|
||||
|
||||
onEnterKeyPressed() {
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
const currentRef = this.textAreaRef.current;
|
||||
if (currentRef == null) {
|
||||
return;
|
||||
}
|
||||
if (inputModel.getCodeSelectSelectedIndex() == -1) {
|
||||
const messageStr = currentRef.value;
|
||||
this.submitChatMessage(messageStr);
|
||||
currentRef.value = "";
|
||||
} else {
|
||||
mobx.action(() => {
|
||||
inputModel.grabCodeSelectSelection();
|
||||
inputModel.setAuxViewFocus(false);
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
onExpandInputPressed() {
|
||||
const currentRef = this.textAreaRef.current;
|
||||
if (currentRef == null) {
|
||||
return;
|
||||
}
|
||||
currentRef.setRangeText("\n", currentRef.selectionStart, currentRef.selectionEnd, "end");
|
||||
GlobalModel.inputModel.codeSelectDeselectAll();
|
||||
}
|
||||
|
||||
onArrowUpPressed(): boolean {
|
||||
console.log("arrow up pressed==================");
|
||||
const currentRef = this.textAreaRef.current;
|
||||
console.log("inputModel.getCodeSelectSelectedIndex()", GlobalModel.inputModel.getCodeSelectSelectedIndex());
|
||||
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 {
|
||||
const currentRef = this.textAreaRef.current;
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
if (currentRef == null) {
|
||||
return false;
|
||||
}
|
||||
if (inputModel.getCodeSelectSelectedIndex() == inputModel.codeSelectBottom) {
|
||||
GlobalModel.inputModel.codeSelectDeselectAll();
|
||||
return false;
|
||||
}
|
||||
inputModel.codeSelectSelectNextNewestCodeBlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
onKeyDown(e: any) {}
|
||||
|
||||
renderError(err: string): any {
|
||||
return <div className="chat-msg-error">{err}</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
|
||||
const renderAIChatKeybindings = GlobalModel.inputModel.shouldRenderAuxViewKeybindings(
|
||||
appconst.InputAuxView_AIChat
|
||||
);
|
||||
console.log("renderAIChatKeybindings=====", renderAIChatKeybindings);
|
||||
console.log("codeSelectBlockRefArray", GlobalModel.inputModel.codeSelectBlockRefArray);
|
||||
|
||||
return (
|
||||
<div className="sidebar-aichat">
|
||||
<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 chatWindowRef={this.chatWindowRef} />
|
||||
</If>
|
||||
<div className="chat-input">
|
||||
<textarea
|
||||
key="main"
|
||||
ref={this.textAreaRef}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoFocus={true}
|
||||
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 }}
|
||||
placeholder="Send a Message..."
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { AIChat };
|
||||
+50
-44
@@ -13,58 +13,64 @@
|
||||
font-weight: var(--sidebar-font-weight);
|
||||
border-left: 1px solid var(--app-border-color);
|
||||
|
||||
.header {
|
||||
height: 39px;
|
||||
.sidebar-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0 5px;
|
||||
border-bottom: 1px solid var(--app-border-color);
|
||||
}
|
||||
|
||||
.rsb-modes {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px 10px;
|
||||
gap: 5px;
|
||||
border-bottom: 1px solid var(--app-border-color);
|
||||
|
||||
.icon-container {
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-container:hover {
|
||||
background-color: var(--app-selected-mask-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.keybind-debug-pane {
|
||||
padding: 10px;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
.keybind-pane-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
.header {
|
||||
height: 39px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 5px;
|
||||
border-bottom: 1px solid var(--app-border-color);
|
||||
}
|
||||
|
||||
.keybind-level {
|
||||
margin-top: 10px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
.rsb-modes {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px 10px;
|
||||
gap: 5px;
|
||||
|
||||
.icon-container {
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-container:hover {
|
||||
background-color: var(--app-selected-mask-color);
|
||||
}
|
||||
}
|
||||
|
||||
.keybind-domain {
|
||||
font-size: 14px;
|
||||
margin-left: 20px;
|
||||
white-space: nowrap;
|
||||
&.collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.keybind-debug-pane {
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
.keybind-pane-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.keybind-level {
|
||||
margin-top: 10px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.keybind-domain {
|
||||
font-size: 14px;
|
||||
margin-left: 20px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-21
@@ -11,6 +11,7 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { ResizableSidebar, Button } from "@/elements";
|
||||
import { WaveBookDisplay } from "./wavebook";
|
||||
import { AIChat } from "./aichat";
|
||||
|
||||
import "./right.less";
|
||||
|
||||
@@ -77,35 +78,45 @@ class RightSideBar extends React.Component<RightSideBarProps, {}> {
|
||||
{(toggleCollapse) => (
|
||||
<React.Fragment>
|
||||
<div className="header">
|
||||
<div className="rsb-modes">
|
||||
<div
|
||||
className="icon-container"
|
||||
title="Show Keybinding Debugger"
|
||||
onClick={() => this.setMode("ai")}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-sparkles fa-fw" />
|
||||
</div>
|
||||
<div className="flex-spacer" />
|
||||
<If condition={GlobalModel.isDev}>
|
||||
<div
|
||||
className="icon-container"
|
||||
title="Show Keybinding Debugger"
|
||||
onClick={() => this.setMode("keybind")}
|
||||
>
|
||||
<i className="fa-fw fa-sharp fa-keyboard fa-solid" />
|
||||
</div>
|
||||
</If>
|
||||
<div
|
||||
className="icon-container"
|
||||
title="Show Keybinding Debugger"
|
||||
onClick={() => this.setMode("wavebook")}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-book-sparkles"></i>
|
||||
</div>
|
||||
</div>
|
||||
<Button className="secondary ghost" onClick={toggleCollapse}>
|
||||
<i className="fa-sharp fa-regular fa-xmark"></i>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="rsb-modes">
|
||||
<div className="flex-spacer" />
|
||||
<If condition={GlobalModel.isDev}>
|
||||
<div
|
||||
className="icon-container"
|
||||
title="Show Keybinding Debugger"
|
||||
onClick={() => this.setMode("keybind")}
|
||||
>
|
||||
<i className="fa-fw fa-sharp fa-keyboard fa-solid" />
|
||||
</div>
|
||||
</If>
|
||||
<div
|
||||
className="icon-container"
|
||||
title="Show Keybinding Debugger"
|
||||
onClick={() => this.setMode("wavebook")}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-book-sparkles"></i>
|
||||
</div>
|
||||
</div>
|
||||
<If condition={this.mode.get() == "keybind"}>
|
||||
{/* <If condition={this.mode.get() == "keybind"}>
|
||||
<KeybindDevPane></KeybindDevPane>
|
||||
</If>
|
||||
</If> */}
|
||||
<If condition={this.mode.get() == "wavebook"}>
|
||||
<WaveBookDisplay></WaveBookDisplay>
|
||||
</If>
|
||||
<If condition={this.mode.get() == "ai"}>
|
||||
<AIChat />
|
||||
</If>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ResizableSidebar>
|
||||
|
||||
@@ -241,6 +241,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"
|
||||
@@ -263,14 +264,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>
|
||||
|
||||
@@ -10,7 +10,7 @@ import "./auxview.less";
|
||||
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
|
||||
|
||||
interface AuxiliaryCmdViewProps {
|
||||
title: string;
|
||||
title?: string;
|
||||
className?: string;
|
||||
iconClass?: string;
|
||||
titleBarContents?: React.ReactElement[];
|
||||
|
||||
@@ -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%);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -621,6 +621,8 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
|
||||
inputModel.shouldRenderAuxViewKeybindings(null) ||
|
||||
inputModel.shouldRenderAuxViewKeybindings(appconst.InputAuxView_Info);
|
||||
const renderHistoryKeybindings = inputModel.shouldRenderAuxViewKeybindings(appconst.InputAuxView_History);
|
||||
console.log("renderCmdInputKeybindings", renderCmdInputKeybindings);
|
||||
console.log("renderHistoryKeybindings", renderHistoryKeybindings);
|
||||
return (
|
||||
<div
|
||||
className="textareainput-div control is-expanded"
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { FC } from "react";
|
||||
|
||||
export const App: FC = () => {
|
||||
return <div className="container">Hello World!</div>;
|
||||
};
|
||||
+31
-35
@@ -34,9 +34,6 @@ let wasActive = true;
|
||||
let wasInFg = true;
|
||||
let currentGlobalShortcut: string | null = null;
|
||||
let initialClientData: ClientDataType = null;
|
||||
let windows: Windows = {};
|
||||
|
||||
interface Windows extends Record<string, Electron.BrowserWindow> {}
|
||||
|
||||
checkPromptMigrate();
|
||||
ensureDir(waveHome);
|
||||
@@ -325,10 +322,7 @@ function shFrameNavHandler(event: Electron.Event<Electron.WebContentsWillFrameNa
|
||||
console.log("frame navigation canceled");
|
||||
}
|
||||
|
||||
function createWindow(id: string, clientData: ClientDataType | null): Electron.BrowserWindow {
|
||||
if (windows[id]) {
|
||||
console.error(`createWindow called for existing window ${id}`);
|
||||
}
|
||||
function createWindow(clientData: ClientDataType | null): Electron.BrowserWindow {
|
||||
const bounds = calcBounds(clientData);
|
||||
setKeyUtilPlatform(platform());
|
||||
const win = new electron.BrowserWindow({
|
||||
@@ -376,18 +370,9 @@ function createWindow(id: string, clientData: ClientDataType | null): Electron.B
|
||||
wasInFg = true;
|
||||
wasActive = true;
|
||||
});
|
||||
win.on("close", () => {
|
||||
delete windows[id];
|
||||
});
|
||||
win.webContents.on("zoom-changed", (e) => {
|
||||
win.webContents.send("zoom-changed");
|
||||
});
|
||||
windows[id] = win;
|
||||
return win;
|
||||
}
|
||||
|
||||
function createMainWindow(clientData: ClientDataType | null) {
|
||||
const win = createWindow("main", clientData);
|
||||
win.webContents.setWindowOpenHandler(({ url, frameName }) => {
|
||||
if (url.startsWith("https://docs.waveterm.dev/")) {
|
||||
console.log("openExternal docs", url);
|
||||
@@ -408,6 +393,7 @@ function createMainWindow(clientData: ClientDataType | null) {
|
||||
console.log("window-open denied", url);
|
||||
return { action: "deny" };
|
||||
});
|
||||
return win;
|
||||
}
|
||||
|
||||
function mainResizeHandler(_: any, win: Electron.BrowserWindow) {
|
||||
@@ -673,13 +659,13 @@ async function getClientData(willRetry: boolean, retryNum: number): Promise<Clie
|
||||
}
|
||||
|
||||
function sendWSSC() {
|
||||
if (windows["main"] != null) {
|
||||
electron.BrowserWindow.getAllWindows().forEach((win) => {
|
||||
if (waveSrvProc == null) {
|
||||
windows["main"].webContents.send("wavesrv-status-change", false);
|
||||
return;
|
||||
win.webContents.send("wavesrv-status-change", false);
|
||||
} else {
|
||||
win.webContents.send("wavesrv-status-change", true, waveSrvProc.pid);
|
||||
}
|
||||
windows["main"].webContents.send("wavesrv-status-change", true, waveSrvProc.pid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function runWaveSrv() {
|
||||
@@ -747,7 +733,7 @@ electron.ipcMain.on("context-editmenu", (_, { x, y }, opts) => {
|
||||
menu.popup({ x, y });
|
||||
});
|
||||
|
||||
async function createMainWindowWrap() {
|
||||
async function createWindowWrap() {
|
||||
let clientData: ClientDataType | null = null;
|
||||
try {
|
||||
clientData = await getClientDataPoll(1);
|
||||
@@ -755,9 +741,9 @@ async function createMainWindowWrap() {
|
||||
} catch (e) {
|
||||
console.log("error getting wavesrv clientdata", e.toString());
|
||||
}
|
||||
createMainWindow(clientData);
|
||||
const win = createWindow(clientData);
|
||||
if (clientData?.winsize.fullscreen) {
|
||||
windows["main"].setFullScreen(true);
|
||||
win.setFullScreen(true);
|
||||
}
|
||||
configureAutoUpdaterStartup(clientData);
|
||||
}
|
||||
@@ -776,7 +762,7 @@ function logActiveState() {
|
||||
console.log("error logging active state", err);
|
||||
});
|
||||
// for next iteration
|
||||
wasInFg = windows["main"]?.isFocused();
|
||||
wasInFg = electron.BrowserWindow.getFocusedWindow()?.isFocused() ?? false;
|
||||
wasActive = false;
|
||||
}
|
||||
|
||||
@@ -802,9 +788,13 @@ function reregisterGlobalShortcut(shortcut: string) {
|
||||
currentGlobalShortcut = null;
|
||||
return;
|
||||
}
|
||||
const ok = electron.globalShortcut.register(shortcut, () => {
|
||||
const ok = electron.globalShortcut.register(shortcut, async () => {
|
||||
console.log("global shortcut triggered, showing window");
|
||||
windows["main"]?.show();
|
||||
if (electron.BrowserWindow.getAllWindows().length == 0) {
|
||||
await createWindowWrap();
|
||||
}
|
||||
const winToShow = electron.BrowserWindow.getFocusedWindow() ?? electron.BrowserWindow.getAllWindows()[0];
|
||||
winToShow?.show();
|
||||
});
|
||||
console.log("registered global shortcut", shortcut, ok ? "ok" : "failed");
|
||||
if (!ok) {
|
||||
@@ -829,9 +819,9 @@ let lastUpdateCheck: Date = null;
|
||||
*/
|
||||
function setAppUpdateStatus(status: string) {
|
||||
appUpdateStatus = status;
|
||||
if (windows["main"] != null) {
|
||||
windows["main"].webContents.send("app-update-status", appUpdateStatus);
|
||||
}
|
||||
electron.BrowserWindow.getAllWindows().forEach((window) => {
|
||||
window.webContents.send("app-update-status", appUpdateStatus);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -915,9 +905,14 @@ async function installAppUpdate() {
|
||||
detail: "A new version has been downloaded. Restart the application to apply the updates.",
|
||||
};
|
||||
|
||||
await electron.dialog.showMessageBox(windows["main"], dialogOpts).then(({ response }) => {
|
||||
if (response === 0) autoUpdater.quitAndInstall();
|
||||
});
|
||||
const allWindows = electron.BrowserWindow.getAllWindows();
|
||||
if (allWindows.length > 0) {
|
||||
await electron.dialog
|
||||
.showMessageBox(electron.BrowserWindow.getFocusedWindow() ?? allWindows[0], dialogOpts)
|
||||
.then(({ response }) => {
|
||||
if (response === 0) autoUpdater.quitAndInstall();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
electron.ipcMain.on("install-app-update", () => fireAndForget(() => installAppUpdate()));
|
||||
@@ -989,10 +984,11 @@ function configureAutoUpdater(enabled: boolean) {
|
||||
}
|
||||
setTimeout(runActiveTimer, 5000); // start active timer, wait 5s just to be safe
|
||||
await app.whenReady();
|
||||
await createMainWindowWrap();
|
||||
await createWindowWrap();
|
||||
|
||||
app.on("activate", () => {
|
||||
if (electron.BrowserWindow.getAllWindows().length === 0) {
|
||||
createMainWindowWrap().then();
|
||||
createWindowWrap().then();
|
||||
}
|
||||
checkForUpdates();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import { Model } from "./model";
|
||||
|
||||
class AIChatModel {
|
||||
globalModel: Model;
|
||||
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;
|
||||
|
||||
constructor(globalModel: Model) {
|
||||
this.globalModel = globalModel;
|
||||
mobx.makeObservable(this);
|
||||
mobx.action(() => {
|
||||
this.codeSelectSelectedIndex.set(-1);
|
||||
this.codeSelectBlockRefArray = [];
|
||||
})();
|
||||
this.codeSelectUuid = "";
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
focus(): void {
|
||||
if (this.chatTextAreaRef?.current != null) {
|
||||
this.chatTextAreaRef.current.focus();
|
||||
}
|
||||
this.isFocused.set(true);
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
unFocus() {
|
||||
if (this.chatTextAreaRef?.current != null) {
|
||||
this.chatTextAreaRef.current.blur();
|
||||
}
|
||||
this.isFocused.set(false);
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
setOpenAICmdInfoChat(chat: OpenAICmdInfoChatMessageType[]): void {
|
||||
this.aiCmdInfoChatItems.replace(chat);
|
||||
this.codeSelectBlockRefArray = [];
|
||||
}
|
||||
|
||||
close(): void {
|
||||
// close and give focus back to main input
|
||||
}
|
||||
|
||||
shouldRenderKeybindings(view: InputAuxViewType): boolean {
|
||||
// when aichat sidebar is mounted, it will render the keybindings
|
||||
return true;
|
||||
}
|
||||
|
||||
setRefs(textAreaRef: React.RefObject<HTMLTextAreaElement>, chatWindowRef: React.RefObject<HTMLDivElement>) {
|
||||
this.chatTextAreaRef = textAreaRef;
|
||||
this.chatWindowRef = chatWindowRef;
|
||||
}
|
||||
|
||||
unsetRefs() {
|
||||
this.chatTextAreaRef = null;
|
||||
this.chatWindowRef = null;
|
||||
}
|
||||
|
||||
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.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.chatWindowRef.current.scrollTop = elemBottom - this.chatWindowRef.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.chatWindowRef?.current != null) {
|
||||
this.chatWindowRef.current.scrollTop = this.chatWindowRef.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.chatWindowRef?.current != null) {
|
||||
this.chatWindowRef.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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
+116
-36
@@ -7,6 +7,8 @@ import { isBlank } from "@/util/util";
|
||||
import * as appconst from "@/app/appconst";
|
||||
import type { Model } from "./model";
|
||||
import { GlobalCommandRunner, GlobalModel } from "./global";
|
||||
import type { OverlayScrollbars } from "overlayscrollbars";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
|
||||
function getDefaultHistoryQueryOpts(): HistoryQueryOpts {
|
||||
return {
|
||||
@@ -28,17 +30,17 @@ class InputModel {
|
||||
cmdInputHeight: OV<number> = mobx.observable.box(0);
|
||||
aiChatTextAreaRef: React.RefObject<HTMLTextAreaElement>;
|
||||
aiChatWindowRef: React.RefObject<HTMLDivElement>;
|
||||
chatOsInstance: OverlayScrollbars;
|
||||
codeSelectBlockRefArray: Array<React.RefObject<HTMLElement>>;
|
||||
codeSelectSelectedIndex: OV<number> = mobx.observable.box(-1);
|
||||
codeSelectSelectedIndex: OV<number> = mobx.observable.box(null, {
|
||||
name: "codeSelectSelectedIndex",
|
||||
});
|
||||
codeSelectUuid: string;
|
||||
inputPopUpType: OV<string> = mobx.observable.box("none");
|
||||
|
||||
AICmdInfoChatItems: mobx.IObservableArray<OpenAICmdInfoChatMessageType> = mobx.observable.array([], {
|
||||
name: "aicmdinfo-chat",
|
||||
});
|
||||
readonly codeSelectTop: number = -2;
|
||||
readonly codeSelectBottom: number = -1;
|
||||
|
||||
historyType: mobx.IObservableValue<HistoryTypeStrs> = mobx.observable.box("screen");
|
||||
historyLoading: mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
||||
historyAfterLoadIndex: number = 0;
|
||||
@@ -139,6 +141,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 +193,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;
|
||||
}
|
||||
@@ -460,16 +463,21 @@ class InputModel {
|
||||
}
|
||||
|
||||
shouldRenderAuxViewKeybindings(view: InputAuxViewType): 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();
|
||||
}
|
||||
}
|
||||
@@ -541,6 +549,11 @@ class InputModel {
|
||||
this.aiChatWindowRef = chatWindowRef;
|
||||
}
|
||||
|
||||
setChatOsInstance(osInstance: OverlayScrollbars) {
|
||||
console.log("triggered***********");
|
||||
this.chatOsInstance = osInstance;
|
||||
}
|
||||
|
||||
setAIChatFocus() {
|
||||
if (this.aiChatTextAreaRef?.current != null) {
|
||||
this.aiChatTextAreaRef.current.focus();
|
||||
@@ -559,12 +572,15 @@ class InputModel {
|
||||
}
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
addCodeBlockToCodeSelect(blockRef: React.RefObject<HTMLElement>, uuid: string): number {
|
||||
let rtn = -1;
|
||||
if (uuid != this.codeSelectUuid) {
|
||||
this.codeSelectUuid = uuid;
|
||||
this.codeSelectBlockRefArray = [];
|
||||
}
|
||||
// Why is codeSelectBlockRefArray being reset here? This causes a bug where multiple code blocks are highlighted
|
||||
// because multiple code blocks have the same index.
|
||||
// if (uuid != this.codeSelectUuid) {
|
||||
// this.codeSelectUuid = uuid;
|
||||
// this.codeSelectBlockRefArray = [];
|
||||
// }
|
||||
rtn = this.codeSelectBlockRefArray.length;
|
||||
this.codeSelectBlockRefArray.push(blockRef);
|
||||
return rtn;
|
||||
@@ -572,17 +588,37 @@ class InputModel {
|
||||
|
||||
@mobx.action
|
||||
setCodeSelectSelectedCodeBlock(blockIndex: number) {
|
||||
// const { viewport, scrollOffsetElement } = this.chatOsInstance.elements();
|
||||
// const { scrollTop } = scrollOffsetElement;
|
||||
// console.log("setCodeSelectSelectedCodeBlock", scrollTop);
|
||||
// console.log("clientHeight", this.aiChatWindowRef.current.clientHeight);
|
||||
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 chatWindowTop = this.aiChatWindowRef.current.scrollTop;
|
||||
const { viewport, scrollOffsetElement } = this.chatOsInstance.elements();
|
||||
const chatWindowTop = scrollOffsetElement.scrollTop;
|
||||
|
||||
console.log("chatWindowTop", chatWindowTop);
|
||||
const chatWindowBottom = chatWindowTop + this.aiChatWindowRef.current.clientHeight - 100;
|
||||
console.log("chatWindowTop", chatWindowTop);
|
||||
console.log("this.aiChatWindowRef.current.clientHeight", this.aiChatWindowRef.current.clientHeight);
|
||||
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;
|
||||
console.log("elemBottom", elemBottom);
|
||||
console.log(
|
||||
"this.aiChatWindowRef.current.clientHeight",
|
||||
this.aiChatWindowRef.current.clientHeight,
|
||||
this.aiChatWindowRef.current.clientHeight / 2
|
||||
);
|
||||
// this.aiChatWindowRef.current.scrollTop = elemBottom - this.aiChatWindowRef.current.clientHeight / 3;
|
||||
viewport.scrollTo({
|
||||
behavior: "auto",
|
||||
top: elemTop - 20,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -591,22 +627,58 @@ class InputModel {
|
||||
this.setAuxViewFocus(true);
|
||||
}
|
||||
|
||||
// @mobx.action
|
||||
// setCodeSelectSelectedCodeBlock(blockIndex: number) {
|
||||
// const { viewport, scrollOffsetElement } = this.chatOsInstance.elements();
|
||||
// const { scrollTop } = scrollOffsetElement;
|
||||
// console.log("setCodeSelectSelectedCodeBlock", scrollTop);
|
||||
// console.log("clientHeight", this.aiChatWindowRef.current.clientHeight);
|
||||
// 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 = 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) {
|
||||
// console.log(
|
||||
// "elemBottom - this.aiChatWindowRef.current.clientHeight / 3",
|
||||
// elemBottom - this.aiChatWindowRef.current.clientHeight / 3
|
||||
// );
|
||||
// viewport.scrollTo({
|
||||
// behavior: "auto",
|
||||
// top: elemBottom - this.aiChatWindowRef.current.clientHeight / 3,
|
||||
// });
|
||||
// // this.aiChatWindowRef.current.scrollTop = elemBottom - this.aiChatWindowRef.current.clientHeight / 3;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// this.codeSelectBlockRefArray = [];
|
||||
// this.setActiveAuxView(appconst.InputAuxView_AIChat);
|
||||
// this.setAuxViewFocus(true);
|
||||
// }
|
||||
|
||||
@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;
|
||||
}
|
||||
// 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 (this.codeSelectSelectedIndex.get() == this.codeSelectBlockRefArray.length - 1) {
|
||||
// // this.codeSelectDeselectAll();
|
||||
// // if (this.aiChatWindowRef?.current != null) {
|
||||
// // this.aiChatWindowRef.current.scrollTop = this.aiChatWindowRef.current.scrollHeight;
|
||||
// // }
|
||||
// }
|
||||
// console.log("incBlockIndex", incBlockIndex);
|
||||
// console.log("this.codeSelectBlockRefArray.length", this.codeSelectBlockRefArray.length);
|
||||
if (incBlockIndex >= 0 && incBlockIndex < this.codeSelectBlockRefArray.length) {
|
||||
this.setCodeSelectSelectedCodeBlock(incBlockIndex);
|
||||
}
|
||||
@@ -614,23 +686,29 @@ class InputModel {
|
||||
|
||||
@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) {
|
||||
if (this.codeSelectSelectedIndex.get() == null && this.codeSelectBlockRefArray.length > 0) {
|
||||
console.log("triggered====== 1");
|
||||
// if (this.codeSelectBlockRefArray.length > 0) {
|
||||
// this.codeSelectSelectedIndex.set(this.codeSelectBlockRefArray.length - 1);
|
||||
// }
|
||||
this.setCodeSelectSelectedCodeBlock(this.codeSelectBlockRefArray.length - 1);
|
||||
return;
|
||||
// } else if (this.codeSelectSelectedIndex.get() == this.codeSelectTop) {
|
||||
// console.log("triggered====== 2");
|
||||
// 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) {
|
||||
// console.log("triggered====== 3");
|
||||
|
||||
// this.codeSelectDeselectAll(this.codeSelectTop);
|
||||
// if (this.aiChatWindowRef?.current != null) {
|
||||
// this.aiChatWindowRef.current.scrollTop = 0;
|
||||
// }
|
||||
// }
|
||||
if (decBlockIndex >= 0 && decBlockIndex < this.codeSelectBlockRefArray.length) {
|
||||
console.log("triggered====== 4");
|
||||
|
||||
this.setCodeSelectSelectedCodeBlock(decBlockIndex);
|
||||
}
|
||||
}
|
||||
@@ -647,7 +725,7 @@ class InputModel {
|
||||
return blockIndex == this.codeSelectSelectedIndex.get();
|
||||
}
|
||||
|
||||
codeSelectDeselectAll(direction: number = this.codeSelectBottom) {
|
||||
codeSelectDeselectAll(direction: number) {
|
||||
if (this.codeSelectSelectedIndex.get() == direction) {
|
||||
return;
|
||||
}
|
||||
@@ -659,6 +737,7 @@ class InputModel {
|
||||
|
||||
@mobx.action
|
||||
openAIAssistantChat(): void {
|
||||
console.log("openAIAssistantChat");
|
||||
this.setActiveAuxView(appconst.InputAuxView_AIChat);
|
||||
this.setAuxViewFocus(true);
|
||||
this.globalModel.sendActivity("aichat-open");
|
||||
@@ -775,6 +854,7 @@ class InputModel {
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -1041,6 +1044,7 @@ class Model {
|
||||
} else if (update.cmdline != null) {
|
||||
this.inputModel.updateCmdLine(update.cmdline);
|
||||
} else if (update.openaicmdinfochat != null) {
|
||||
this.aichatModel.setOpenAICmdInfoChat(update.openaicmdinfochat);
|
||||
this.inputModel.setOpenAICmdInfoChat(update.openaicmdinfochat);
|
||||
} else if (update.screenstatusindicator != null) {
|
||||
this.updateScreenStatusIndicators([update.screenstatusindicator]);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user