mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
AI Chat Sidebar (#657)
This commit is contained in:
@@ -332,5 +332,9 @@
|
||||
{
|
||||
"command": "codeedit:togglePreview",
|
||||
"keys": ["Cmd:p"]
|
||||
},
|
||||
{
|
||||
"command": "rightsidebar:open",
|
||||
"keys": ["Cmd:Shift:Space"]
|
||||
}
|
||||
]
|
||||
Generated
+14710
File diff suppressed because it is too large
Load Diff
@@ -49,6 +49,7 @@
|
||||
"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",
|
||||
@@ -78,6 +79,7 @@
|
||||
"@types/semver": "^7.5.6",
|
||||
"@types/sprintf-js": "^1.1.3",
|
||||
"@types/throttle-debounce": "^5.0.1",
|
||||
"@types/tinycolor2": "^1",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@types/webpack-env": "^1.18.3",
|
||||
"@withfig/autocomplete-types": "^1.30.0",
|
||||
|
||||
+3
-3
@@ -159,13 +159,13 @@ class App extends React.Component<{}, {}> {
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={GlobalModel.isDev && rightSidebarCollapsed && activeMainView == "session"}>
|
||||
<div className="right-sidebar-triggers">
|
||||
<If condition={rightSidebarCollapsed && activeMainView == "session"}>
|
||||
<div className="right-sidebar-triggers" title="Open Wave AI">
|
||||
<Button
|
||||
className="secondary ghost right-sidebar-trigger"
|
||||
onClick={this.openRightSidebar}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-sidebar-flip"></i>
|
||||
<i className="fa-sharp fa-regular fa-sparkles"></i>
|
||||
</Button>
|
||||
</div>
|
||||
</If>
|
||||
|
||||
@@ -3,11 +3,13 @@ import { Button } from "./button";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
import "./copybutton.less";
|
||||
|
||||
type CopyButtonProps = {
|
||||
title: string;
|
||||
className?: string;
|
||||
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
};
|
||||
|
||||
@@ -34,10 +36,14 @@ class CopyButton extends React.Component<CopyButtonProps, {}> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { title, onClick } = this.props;
|
||||
const { title, className } = this.props;
|
||||
const isCopied = this.isCopied.get();
|
||||
return (
|
||||
<Button onClick={this.handleOnClick} className="copy-button secondary ghost" title={title}>
|
||||
<Button
|
||||
onClick={this.handleOnClick}
|
||||
className={clsx("copy-button secondary ghost", className)}
|
||||
title={title}
|
||||
>
|
||||
{isCopied ? (
|
||||
<i className="fa-sharp fa-solid fa-check"></i>
|
||||
) : (
|
||||
|
||||
@@ -7,6 +7,7 @@ export { InlineSettingsTextEdit } from "./inlinesettingstextedit";
|
||||
export { InputDecoration } from "./inputdecoration";
|
||||
export { LinkButton } from "./linkbutton";
|
||||
export { Markdown } from "./markdown";
|
||||
export { Markdown2 } from "./markdown2";
|
||||
export { Modal } from "./modal";
|
||||
export { PasswordField } from "./passwordfield";
|
||||
export { ResizableSidebar } from "./resizablesidebar";
|
||||
@@ -19,3 +20,4 @@ export { TabIcon } from "./tabicon";
|
||||
export { DatePicker } from "./datepicker";
|
||||
export { TermStyleList } from "./termstyle";
|
||||
export { CopyButton } from "./copybutton";
|
||||
export { TypingIndicator } from "./typingindicator";
|
||||
|
||||
@@ -46,30 +46,69 @@
|
||||
padding: 2px 4px 2px 6px;
|
||||
}
|
||||
|
||||
pre {
|
||||
pre.codeblock {
|
||||
background-color: var(--markdown-bg-color);
|
||||
margin: 4px 10px 4px 10px;
|
||||
padding: 0.7em;
|
||||
margin: 4px 10px;
|
||||
padding: 0.4em 0.7em;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
|
||||
code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
line-height: normal;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
pre.selected {
|
||||
outline: 2px solid var(--markdown-outline-color);
|
||||
.codeblock-actions {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
border-radius: 4px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
background-color: var(--line-actions-bg-color);
|
||||
backdrop-filter: blur(8px);
|
||||
|
||||
i {
|
||||
color: var(--line-actions-inactive-color);
|
||||
|
||||
&:first-child {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--line-actions-active-color);
|
||||
}
|
||||
|
||||
&.fa-check {
|
||||
color: var(--app-success-color);
|
||||
}
|
||||
|
||||
&.fa-square-terminal {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .codeblock-actions {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
color: var(--app-text-color);
|
||||
background-color: var(--markdown-bg-color);
|
||||
font-family: var(--termfontfamily);
|
||||
border-radius: 4px;
|
||||
background-color: var(--markdown-bg-color);
|
||||
padding: 0.15em 0.5em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
pre.selected {
|
||||
outline: 2px solid var(--markdown-outline-color);
|
||||
}
|
||||
|
||||
.title {
|
||||
@@ -101,6 +140,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.markdown.content {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.markdown > *:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { CopyButton } from "@/elements";
|
||||
import { clsx } from "clsx";
|
||||
import * as mobx from "mobx";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
|
||||
import "./markdown.less";
|
||||
|
||||
function Link(props: any): JSX.Element {
|
||||
let newUrl = "https://extern?" + encodeURIComponent(props.href);
|
||||
return (
|
||||
<a href={newUrl} target="_blank" rel={"noopener"}>
|
||||
{props.children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function Header(props: any, hnum: number): JSX.Element {
|
||||
return <div className={clsx("title", "is-" + hnum)}>{props.children}</div>;
|
||||
}
|
||||
|
||||
function Code(props: any): JSX.Element {
|
||||
return <code>{props.children}</code>;
|
||||
}
|
||||
|
||||
const CodeBlock = mobxReact.observer(
|
||||
(props: { children: React.ReactNode; onClickExecute?: (cmd: string) => void }): JSX.Element => {
|
||||
const copied: OV<boolean> = mobx.observable.box(false, { name: "copied" });
|
||||
|
||||
const getTextContent = (children: any) => {
|
||||
if (typeof children === "string") {
|
||||
return children;
|
||||
} else if (Array.isArray(children)) {
|
||||
return children.map(getTextContent).join("");
|
||||
} else if (children.props && children.props.children) {
|
||||
return getTextContent(children.props.children);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const handleCopy = async (e: React.MouseEvent) => {
|
||||
let textToCopy = getTextContent(props.children);
|
||||
textToCopy = textToCopy.replace(/\n$/, ""); // remove trailing newline
|
||||
await navigator.clipboard.writeText(textToCopy);
|
||||
copied.set(true);
|
||||
setTimeout(() => copied.set(false), 2000); // Reset copied state after 2 seconds
|
||||
};
|
||||
|
||||
const handleExecute = (e: React.MouseEvent) => {
|
||||
let textToCopy = getTextContent(props.children);
|
||||
textToCopy = textToCopy.replace(/\n$/, ""); // remove trailing newline
|
||||
if (props.onClickExecute) {
|
||||
props.onClickExecute(textToCopy);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<pre className="codeblock">
|
||||
{props.children}
|
||||
<div className="codeblock-actions">
|
||||
<CopyButton className="copy-button" onClick={handleCopy} title="Copy" />
|
||||
<If condition={props.onClickExecute}>
|
||||
<i className="fa-regular fa-square-terminal" onClick={handleExecute}></i>
|
||||
</If>
|
||||
</div>
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@mobxReact.observer
|
||||
class Markdown2 extends React.Component<
|
||||
{
|
||||
text: string;
|
||||
style?: any;
|
||||
className?: string;
|
||||
onClickExecute?: (cmd: string) => void;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
render() {
|
||||
let { text, className, onClickExecute } = this.props;
|
||||
let markdownComponents = {
|
||||
a: Link,
|
||||
h1: (props) => <Header {...props} hnum={1} />,
|
||||
h2: (props) => <Header {...props} hnum={2} />,
|
||||
h3: (props) => <Header {...props} hnum={3} />,
|
||||
h4: (props) => <Header {...props} hnum={4} />,
|
||||
h5: (props) => <Header {...props} hnum={5} />,
|
||||
h6: (props) => <Header {...props} hnum={6} />,
|
||||
code: Code,
|
||||
pre: (props) => <CodeBlock {...props} onClickExecute={onClickExecute} />,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx("markdown content", className)} style={this.props.style}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
||||
{text}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { Markdown2 };
|
||||
@@ -0,0 +1,43 @@
|
||||
@dot-width: 11px;
|
||||
@dot-color: var(--app-success-color);
|
||||
@speed: 1.5s;
|
||||
|
||||
.typing {
|
||||
position: relative;
|
||||
height: @dot-width;
|
||||
|
||||
span {
|
||||
content: "";
|
||||
animation: blink @speed infinite;
|
||||
animation-fill-mode: both;
|
||||
height: @dot-width;
|
||||
width: @dot-width;
|
||||
background: @dot-color;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-radius: 50%;
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
margin-left: @dot-width * 1.5;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
margin-left: @dot-width * 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0% {
|
||||
opacity: 0.1;
|
||||
}
|
||||
20% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
import "./typingindicator.less";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
const TypingIndicator: React.FC<Props> = (props: { className?: string }) => {
|
||||
return (
|
||||
<div className={clsx("typing", props.className)}>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { TypingIndicator };
|
||||
@@ -135,14 +135,12 @@ export const StatusIndicator: React.FC<StatusIndicatorProps> = (props) => {
|
||||
*/
|
||||
React.useEffect(() => {
|
||||
if (runningCommands && !timeoutState) {
|
||||
console.log("show spinner");
|
||||
setTimeoutState(
|
||||
setTimeout(() => {
|
||||
setSpinnerVisible(true);
|
||||
}, 100)
|
||||
);
|
||||
} else if (!runningCommands) {
|
||||
console.log("clear spinner");
|
||||
clearSpinnerTimeout();
|
||||
}
|
||||
return () => {
|
||||
|
||||
@@ -114,6 +114,24 @@ class LineActions extends React.Component<{ screen: LineContainerType; line: Lin
|
||||
GlobalCommandRunner.lineRestart(line.lineid, true);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickChat(e: any) {
|
||||
e.stopPropagation();
|
||||
const { line, screen } = this.props;
|
||||
const termWrap = screen.getTermWrap(line.lineid);
|
||||
const cmd = screen.getCmd(line);
|
||||
if (termWrap && cmd) {
|
||||
GlobalModel.sidebarchatModel.setCmdAndOutput(
|
||||
screen.getCmd(line)?.getCmdStr(),
|
||||
termWrap?.getOutput(false),
|
||||
// multiply by 2 to get a rough estimate of the number of rows within the chatbox
|
||||
screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, 300) * 2,
|
||||
cmdShouldMarkError(cmd)
|
||||
);
|
||||
GlobalModel.sidebarchatModel.setFocus("input", true);
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickMinimize() {
|
||||
const { line } = this.props;
|
||||
@@ -154,10 +172,14 @@ class LineActions extends React.Component<{ screen: LineContainerType; line: Lin
|
||||
const { line, screen } = this.props;
|
||||
const isMinimized = line.linestate["wave:min"];
|
||||
const containerType = screen.getContainerType();
|
||||
// console.log("******************", screen.getTermWrap(line.lineid));
|
||||
return (
|
||||
<div className="line-actions">
|
||||
<Choose>
|
||||
<When condition={containerType == appconst.LineContainer_Main}>
|
||||
<div key="chat" title="Restart Command" className="line-icon" onClick={this.clickChat}>
|
||||
<i className="fa-sharp fa-regular fa-sparkles fa-fw" />
|
||||
</div>
|
||||
<div key="restart" title="Restart Command" className="line-icon" onClick={this.clickRestart}>
|
||||
<i className="fa-sharp fa-regular fa-arrows-rotate fa-fw" />
|
||||
</div>
|
||||
@@ -514,7 +536,6 @@ class LineCmd extends React.Component<
|
||||
const lastHeight = this.lastHeight;
|
||||
this.lastHeight = curHeight;
|
||||
this.props.onHeightChange(line.linenum, curHeight, lastHeight);
|
||||
// console.log("line height change: ", line.linenum, lastHeight, "=>", curHeight);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
|
||||
@@ -23,7 +23,7 @@ let MagicLayout = {
|
||||
MainSidebarDefaultWidth: 240,
|
||||
|
||||
RightSidebarMinWidth: 0,
|
||||
RightSidebarMaxWidth: 300,
|
||||
RightSidebarMaxWidth: 700,
|
||||
RightSidebarSnapThreshold: 90,
|
||||
RightSidebarDragResistance: 50,
|
||||
RightSidebarDefaultWidth: 240,
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
.sidebarchat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
|
||||
> .scrollable {
|
||||
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;
|
||||
align-items: flex-start;
|
||||
|
||||
.chat-msg-header {
|
||||
display: flex;
|
||||
margin-bottom: 2px;
|
||||
|
||||
i {
|
||||
margin-top: 2px;
|
||||
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;
|
||||
}
|
||||
|
||||
.typing-indicator {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebarchat-input-wrapper {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+57
-44
@@ -13,58 +13,71 @@
|
||||
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: 11px;
|
||||
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;
|
||||
gap: 5px;
|
||||
|
||||
.icon-container {
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-container:hover {
|
||||
background-color: var(--app-selected-mask-color);
|
||||
}
|
||||
}
|
||||
|
||||
.keybind-domain {
|
||||
font-size: 14px;
|
||||
margin-left: 20px;
|
||||
white-space: nowrap;
|
||||
&.collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.close {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+87
-28
@@ -11,15 +11,13 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { ResizableSidebar, Button } from "@/elements";
|
||||
import { WaveBookDisplay } from "./wavebook";
|
||||
import { ChatSidebar } from "./aichat";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
|
||||
import "./right.less";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
interface RightSideBarProps {
|
||||
parentRef: React.RefObject<HTMLElement>;
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class KeybindDevPane extends React.Component<{}, {}> {
|
||||
render() {
|
||||
@@ -55,17 +53,73 @@ class KeybindDevPane extends React.Component<{}, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class RightSideBar extends React.Component<RightSideBarProps, {}> {
|
||||
mode: OV<string> = mobx.observable.box(null, { name: "RightSideBar-mode" });
|
||||
class SidebarKeyBindings extends React.Component<{ component: RightSideBar; isOpen: boolean }, {}> {
|
||||
componentDidMount(): void {
|
||||
const { component } = this.props;
|
||||
const keybindManager = GlobalModel.keybindManager;
|
||||
keybindManager.registerKeybinding("pane", "rightsidebar", "rightsidebar:open", (waveEvent) => {
|
||||
return component.onOpen();
|
||||
});
|
||||
}
|
||||
|
||||
setMode(mode: string) {
|
||||
mobx.action(() => {
|
||||
this.mode.set(mode);
|
||||
})();
|
||||
componentDidUpdate(): void {
|
||||
if (this.props.isOpen) {
|
||||
const { component } = this.props;
|
||||
const keybindManager = GlobalModel.keybindManager;
|
||||
keybindManager.registerKeybinding("pane", "rightsidebar:close", "generic:cancel", (waveEvent) => {
|
||||
return component.onClose();
|
||||
});
|
||||
} else {
|
||||
GlobalModel.keybindManager.unregisterDomain("rightsidebar:close");
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
GlobalModel.keybindManager.unregisterDomain("rightsidebar");
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class RightSideBar extends React.Component<
|
||||
{
|
||||
parentRef: React.RefObject<HTMLElement>;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
mode: OV<string> = mobx.observable.box("aichat", { name: "RightSideBar-mode" });
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
mobx.makeObservable(this);
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
setMode(mode: string) {
|
||||
if (mode == this.mode.get()) {
|
||||
return;
|
||||
}
|
||||
this.mode.set(mode);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onOpen() {
|
||||
GlobalModel.rightSidebarModel.setCollapsed(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onClose() {
|
||||
GlobalModel.rightSidebarModel.setCollapsed(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
const isCollapsed = GlobalModel.rightSidebarModel.getCollapsed();
|
||||
const mode = this.mode.get();
|
||||
return (
|
||||
<ResizableSidebar
|
||||
model={GlobalModel.rightSidebarModel}
|
||||
@@ -76,36 +130,41 @@ class RightSideBar extends React.Component<RightSideBarProps, {}> {
|
||||
>
|
||||
{(toggleCollapse) => (
|
||||
<React.Fragment>
|
||||
<SidebarKeyBindings component={this} isOpen={!isCollapsed} />
|
||||
<div className="header">
|
||||
<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="rsb-modes">
|
||||
<div
|
||||
className="icon-container"
|
||||
title="Show Keybinding Debugger"
|
||||
onClick={() => this.setMode("keybind")}
|
||||
onClick={() => this.setMode("aichat")}
|
||||
>
|
||||
<i className="fa-fw fa-sharp fa-keyboard fa-solid" />
|
||||
<i className="fa-sharp fa-regular fa-sparkles fa-fw" />
|
||||
<span>Wave AI</span>
|
||||
</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 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>
|
||||
<Button className="secondary ghost close" onClick={toggleCollapse}>
|
||||
<i className="fa-sharp fa-solid fa-xmark-large" />
|
||||
</Button>
|
||||
</div>
|
||||
<If condition={this.mode.get() == "keybind"}>
|
||||
<KeybindDevPane></KeybindDevPane>
|
||||
</If>
|
||||
<If condition={this.mode.get() == "wavebook"}>
|
||||
<If condition={mode == "wavebook"}>
|
||||
<WaveBookDisplay></WaveBookDisplay>
|
||||
</If>
|
||||
<If condition={mode == "aichat" && !isCollapsed}>
|
||||
<ChatSidebar />
|
||||
</If>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ResizableSidebar>
|
||||
|
||||
@@ -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,8 @@
|
||||
|
||||
// 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-aicmdinfo,
|
||||
&.has-history,
|
||||
&.has-info,
|
||||
&.has-suggestions {
|
||||
@@ -21,8 +22,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.has-history,
|
||||
&.has-aichat {
|
||||
// &.has-aichat,
|
||||
&.has-history {
|
||||
height: max(300px, 70%);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,18 +81,6 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
GlobalModel.inputModel.setAuxViewFocus(false);
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
clickAIAction(e: any): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
if (inputModel.getActiveAuxView() === appconst.InputAuxView_AIChat) {
|
||||
inputModel.closeAuxView();
|
||||
} else {
|
||||
inputModel.openAIAssistantChat();
|
||||
}
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
clickHistoryAction(e: any): void {
|
||||
e.preventDefault();
|
||||
@@ -106,6 +94,13 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
clickAIChatAction(e: any): void {
|
||||
const rightSidebarModel = GlobalModel.rightSidebarModel;
|
||||
const width = rightSidebarModel.getWidth(true);
|
||||
rightSidebarModel.saveState(width, false);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickConnectRemote(remoteId: string): void {
|
||||
GlobalCommandRunner.connectRemote(remoteId);
|
||||
@@ -254,10 +249,10 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
</div>
|
||||
</If>
|
||||
<div
|
||||
key="ai"
|
||||
key="aichat"
|
||||
title="Wave AI (Ctrl-Space)"
|
||||
className="cmdinput-icon"
|
||||
onClick={this.clickAIAction}
|
||||
onClick={this.clickAIChatAction}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-sparkles fa-fw" />
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user