mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
aichat improvements (#667)
This commit is contained in:
parent
1234919229
commit
d7173c970c
@ -313,6 +313,10 @@
|
||||
"command": "aichat:clearHistory",
|
||||
"keys": ["Ctrl:l"]
|
||||
},
|
||||
{
|
||||
"command": "aichat:setCmdInputValue",
|
||||
"keys": ["Ctrl:Shift:e"]
|
||||
},
|
||||
{
|
||||
"command": "terminal:copy",
|
||||
"keys": ["Ctrl:Shift:c"]
|
||||
|
@ -31,12 +31,20 @@ class App extends React.Component<{}, {}> {
|
||||
dcWait: OV<boolean> = mobx.observable.box(false, { name: "dcWait" });
|
||||
mainContentRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
termThemesLoaded: OV<boolean> = mobx.observable.box(false, { name: "termThemesLoaded" });
|
||||
chatFocusTimeoutId: NodeJS.Timeout = null;
|
||||
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
if (GlobalModel.isDev) document.body.classList.add("is-dev");
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.chatFocusTimeoutId) {
|
||||
clearTimeout(this.chatFocusTimeoutId);
|
||||
this.chatFocusTimeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleContextMenu(e: any) {
|
||||
let isInNonTermInput = false;
|
||||
@ -68,16 +76,15 @@ class App extends React.Component<{}, {}> {
|
||||
|
||||
@boundMethod
|
||||
openMainSidebar() {
|
||||
const mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||
const width = mainSidebarModel.getWidth(true);
|
||||
mainSidebarModel.saveState(width, false);
|
||||
GlobalModel.mainSidebarModel.setCollapsed(false);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
openRightSidebar() {
|
||||
const rightSidebarModel = GlobalModel.rightSidebarModel;
|
||||
const width = rightSidebarModel.getWidth(true);
|
||||
rightSidebarModel.saveState(width, false);
|
||||
GlobalModel.rightSidebarModel.setCollapsed(false);
|
||||
this.chatFocusTimeoutId = setTimeout(() => {
|
||||
GlobalModel.inputModel.setChatSidebarFocus();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
|
@ -128,7 +128,8 @@ class LineActions extends React.Component<{ screen: LineContainerType; line: Lin
|
||||
screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, 300) * 2,
|
||||
cmdShouldMarkError(cmd)
|
||||
);
|
||||
GlobalModel.sidebarchatModel.setFocus("input", true);
|
||||
GlobalModel.inputModel.setChatSidebarFocus();
|
||||
GlobalModel.sidebarchatModel.resetSelectedCodeBlockIndex();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,11 @@
|
||||
.filler {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
> * {
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg {
|
||||
@ -78,6 +83,7 @@
|
||||
font-family: var(--termfontfamily);
|
||||
font-weight: normal;
|
||||
line-height: var(--termlineheight);
|
||||
height: 21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import "./aichat.less";
|
||||
|
||||
const outline = "2px solid var(--markdown-outline-color)";
|
||||
|
||||
class ChatKeyBindings extends React.Component<{ component: ChatSidebar; bindArrowUpDownKeys: boolean }, {}> {
|
||||
class ChatKeyBindings extends React.Component<{ component: ChatSidebar }, {}> {
|
||||
componentDidMount(): void {
|
||||
const { component } = this.props;
|
||||
const keybindManager = GlobalModel.keybindManager;
|
||||
@ -34,26 +34,19 @@ class ChatKeyBindings extends React.Component<{ component: ChatSidebar; bindArro
|
||||
inputModel.clearAIAssistantChat();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(): void {
|
||||
const { component, bindArrowUpDownKeys } = this.props;
|
||||
const keybindManager = GlobalModel.keybindManager;
|
||||
if (bindArrowUpDownKeys) {
|
||||
keybindManager.registerKeybinding("pane", "aichat:arrowupdown", "generic:selectAbove", (waveEvent) => {
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:selectAbove", (waveEvent) => {
|
||||
return component.onArrowUpPressed();
|
||||
});
|
||||
keybindManager.registerKeybinding("pane", "aichat:arrowupdown", "generic:selectBelow", (waveEvent) => {
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:selectBelow", (waveEvent) => {
|
||||
return component.onArrowDownPressed();
|
||||
});
|
||||
} else {
|
||||
GlobalModel.keybindManager.unregisterDomain("aichat:arrowupdown");
|
||||
}
|
||||
keybindManager.registerKeybinding("pane", "aichat", "aichat:setCmdInputValue", (waveEvent) => {
|
||||
return component.onSetCmdInputValue();
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
GlobalModel.keybindManager.unregisterDomain("aichat");
|
||||
GlobalModel.keybindManager.unregisterDomain("aichat:arrowupdown");
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -62,20 +55,16 @@ class ChatKeyBindings extends React.Component<{ component: ChatSidebar; bindArro
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class ChatItem extends React.Component<{ chatItem: OpenAICmdInfoChatMessageType; itemCount: number }, {}> {
|
||||
handleExecuteCommand(cmd: string) {
|
||||
GlobalModel.sidebarchatModel.setCmdToExec(cmd);
|
||||
GlobalModel.sidebarchatModel.resetFocus();
|
||||
GlobalModel.inputModel.curLine = cmd;
|
||||
GlobalModel.inputModel.giveFocus();
|
||||
}
|
||||
|
||||
class ChatItem extends React.Component<
|
||||
{ chatItem: OpenAICmdInfoChatMessageType; itemCount: number; onSetCmdInputValue: (cmd: string) => void },
|
||||
{}
|
||||
> {
|
||||
renderError(err: string): any {
|
||||
return <div className="chat-msg-error">{err}</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { chatItem, itemCount } = this.props;
|
||||
const { chatItem, itemCount, onSetCmdInputValue } = this.props;
|
||||
const { isassistantresponse, assistantresponse } = chatItem;
|
||||
const curKey = "chatmsg-" + itemCount;
|
||||
const senderClassName = isassistantresponse ? "chat-msg-assistant" : "chat-msg-user";
|
||||
@ -108,7 +97,7 @@ class ChatItem extends React.Component<{ chatItem: OpenAICmdInfoChatMessageType;
|
||||
<div className="chat-msg-header">
|
||||
<i className="fa-sharp fa-solid fa-sparkles"></i>
|
||||
</div>
|
||||
<Markdown2 text={assistantresponse.message} onClickExecute={this.handleExecuteCommand} />
|
||||
<Markdown2 text={assistantresponse.message} onClickExecute={onSetCmdInputValue} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -130,7 +119,14 @@ class ChatItem extends React.Component<{ chatItem: OpenAICmdInfoChatMessageType;
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class ChatWindow extends React.Component<{ chatWindowRef; onRendered }, {}> {
|
||||
class ChatWindow extends React.Component<
|
||||
{
|
||||
chatWindowRef: React.RefObject<HTMLDivElement>;
|
||||
onRendered: (osInstance: OverlayScrollbars) => void;
|
||||
onSetCmdInputValue: (cmd: string) => void;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
itemCount: number = 0;
|
||||
containerRef: React.RefObject<OverlayScrollbarsComponentRef> = React.createRef();
|
||||
osInstance: OverlayScrollbars = null;
|
||||
@ -164,6 +160,7 @@ class ChatWindow extends React.Component<{ chatWindowRef; onRendered }, {}> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onSetCmdInputValue } = this.props;
|
||||
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
|
||||
const chitem: OpenAICmdInfoChatMessageType = null;
|
||||
let idx;
|
||||
@ -177,7 +174,12 @@ class ChatWindow extends React.Component<{ chatWindowRef; onRendered }, {}> {
|
||||
<div ref={this.props.chatWindowRef} className="chat-window">
|
||||
<div className="filler"></div>
|
||||
<For each="chitem" index="idx" of={chatMessageItems}>
|
||||
<ChatItem key={idx} chatItem={chitem} itemCount={idx + 1} />
|
||||
<ChatItem
|
||||
key={idx}
|
||||
chatItem={chitem}
|
||||
itemCount={idx + 1}
|
||||
onSetCmdInputValue={onSetCmdInputValue}
|
||||
/>
|
||||
</For>
|
||||
</div>
|
||||
</OverlayScrollbarsComponent>
|
||||
@ -190,11 +192,11 @@ class ChatSidebar extends React.Component<{}, {}> {
|
||||
sidebarRef: React.RefObject<HTMLDivElement> = React.createRef<HTMLDivElement>();
|
||||
textAreaRef: React.RefObject<HTMLTextAreaElement> = React.createRef<HTMLTextAreaElement>();
|
||||
chatWindowRef: React.RefObject<HTMLDivElement> = React.createRef<HTMLDivElement>();
|
||||
bindArrowUpDownKeys: OV<boolean> = mobx.observable.box(false, { name: "bindArrowUpDownKeys" });
|
||||
value: OV<string> = mobx.observable.box("", { deep: false, name: "value" });
|
||||
osInstance: OverlayScrollbars;
|
||||
termFontSize: number = 14;
|
||||
blockIndex: number;
|
||||
disposeReaction: () => void;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -202,27 +204,32 @@ class ChatSidebar extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (GlobalModel.sidebarchatModel.focused == "input") {
|
||||
this.textAreaRef.current.focus();
|
||||
}
|
||||
if (GlobalModel.sidebarchatModel.hasCmdAndOutput()) {
|
||||
const newCmdAndOutput = GlobalModel.sidebarchatModel.getCmdAndOutput();
|
||||
const newValue = this.formChatMessage(newCmdAndOutput);
|
||||
if (newValue !== this.value.get()) {
|
||||
this.value.set(newValue);
|
||||
GlobalModel.sidebarchatModel.resetCmdAndOutput();
|
||||
}
|
||||
}
|
||||
this.adjustTextAreaHeight();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
GlobalModel.sidebarchatModel.setFocus("input", true);
|
||||
this.textAreaRef.current.focus();
|
||||
this.disposeReaction = mobx.reaction(
|
||||
() => [
|
||||
GlobalModel.sidebarchatModel.hasCmdAndOutput(),
|
||||
GlobalModel.sidebarchatModel.getSelectedCodeBlockIndex(),
|
||||
],
|
||||
([hasCmdAndOutput, selectedCodeBlockIndex]) => {
|
||||
if (hasCmdAndOutput) {
|
||||
const newCmdAndOutput = GlobalModel.sidebarchatModel.getCmdAndOutput();
|
||||
const newValue = this.formChatMessage(newCmdAndOutput);
|
||||
this.value.set(newValue);
|
||||
GlobalModel.sidebarchatModel.resetCmdAndOutput();
|
||||
}
|
||||
|
||||
if (selectedCodeBlockIndex == null) {
|
||||
this.updatePreTagOutline();
|
||||
}
|
||||
}
|
||||
);
|
||||
if (this.sidebarRef.current) {
|
||||
this.sidebarRef.current.addEventListener("click", this.handleSidebarClick);
|
||||
}
|
||||
document.addEventListener("mousedown", this.handleClickOutside);
|
||||
document.addEventListener("click", this.handleClickOutside);
|
||||
this.requestChatUpdate();
|
||||
}
|
||||
|
||||
@ -230,13 +237,19 @@ class ChatSidebar extends React.Component<{}, {}> {
|
||||
if (this.sidebarRef.current) {
|
||||
this.sidebarRef.current.removeEventListener("click", this.handleSidebarClick);
|
||||
}
|
||||
document.removeEventListener("mousedown", this.handleClickOutside);
|
||||
document.removeEventListener("click", this.handleClickOutside);
|
||||
GlobalModel.sidebarchatModel.resetFocus();
|
||||
if (this.disposeReaction) {
|
||||
this.disposeReaction();
|
||||
}
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
handleClickOutside(event) {
|
||||
if (this.sidebarRef.current && !this.sidebarRef.current.contains(event.target)) {
|
||||
this.onClickOutsideSidebar();
|
||||
handleClickOutside(e: MouseEvent) {
|
||||
const sidebar = this.sidebarRef.current;
|
||||
if (sidebar && !sidebar.contains(e.target as Node)) {
|
||||
GlobalModel.sidebarchatModel.resetFocus();
|
||||
GlobalModel.inputModel.giveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,20 +294,20 @@ class ChatSidebar extends React.Component<{}, {}> {
|
||||
}).catch((_) => {});
|
||||
}
|
||||
|
||||
onClickOutsideSidebar() {
|
||||
GlobalModel.sidebarchatModel.resetFocus();
|
||||
@mobx.action.bound
|
||||
onTextAreaFocus() {
|
||||
GlobalModel.inputModel.setChatSidebarFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
onTextAreaFocused(e) {
|
||||
GlobalModel.sidebarchatModel.setFocus("input", true);
|
||||
this.bindArrowUpDownKeys.set(false);
|
||||
const pres = this.chatWindowRef.current?.querySelectorAll("pre");
|
||||
this.blockIndex = pres.length - 1;
|
||||
this.onTextAreaChange(e);
|
||||
onTextAreaMouseDown(e) {
|
||||
this.updatePreTagOutline();
|
||||
// Reset blockIndex to null
|
||||
GlobalModel.sidebarchatModel.resetSelectedCodeBlockIndex();
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
onEnterKeyPressed() {
|
||||
const messageStr = this.value.get();
|
||||
this.submitChatMessage(messageStr);
|
||||
@ -302,6 +315,7 @@ class ChatSidebar extends React.Component<{}, {}> {
|
||||
GlobalModel.sidebarchatModel.resetCmdAndOutput();
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
onExpandInputPressed() {
|
||||
const currentRef = this.textAreaRef.current;
|
||||
if (currentRef == null) {
|
||||
@ -317,7 +331,7 @@ class ChatSidebar extends React.Component<{}, {}> {
|
||||
}
|
||||
pres.forEach((preElement, idx) => {
|
||||
if (preElement === clickedPre) {
|
||||
this.blockIndex = idx;
|
||||
GlobalModel.sidebarchatModel.setSelectedCodeBlockIndex(idx);
|
||||
preElement.style.outline = outline;
|
||||
} else {
|
||||
preElement.style.outline = "none";
|
||||
@ -327,36 +341,36 @@ class ChatSidebar extends React.Component<{}, {}> {
|
||||
|
||||
@mobx.action.bound
|
||||
handleSidebarClick(event) {
|
||||
let detection = 0;
|
||||
const target = event.target as HTMLElement;
|
||||
|
||||
if (target.closest(".copy-button") || target.closest(".fa-square-terminal")) {
|
||||
if (
|
||||
target.closest(".copy-button") ||
|
||||
target.closest(".fa-square-terminal") ||
|
||||
target.closest(".chat-textarea")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chatWindow = target.closest(".chat-window");
|
||||
if (chatWindow) {
|
||||
detection++;
|
||||
}
|
||||
|
||||
const pre = target.closest("pre");
|
||||
if (pre) {
|
||||
detection++;
|
||||
const pres = this.chatWindowRef.current?.querySelectorAll("pre");
|
||||
if (pres) {
|
||||
pres.forEach((preElement, idx) => {
|
||||
if (preElement === pre) {
|
||||
GlobalModel.sidebarchatModel.setSelectedCodeBlockIndex(idx);
|
||||
this.updatePreTagOutline(pre);
|
||||
}
|
||||
|
||||
if (detection > 0) {
|
||||
this.bindArrowUpDownKeys.set(true);
|
||||
GlobalModel.sidebarchatModel.setFocus("block", true);
|
||||
});
|
||||
}
|
||||
}
|
||||
GlobalModel.inputModel.setChatSidebarFocus();
|
||||
}
|
||||
|
||||
updateScrollTop() {
|
||||
const pres = this.chatWindowRef.current?.querySelectorAll("pre");
|
||||
if (pres == null) {
|
||||
return;
|
||||
}
|
||||
const block = pres[this.blockIndex];
|
||||
const block = pres[GlobalModel.sidebarchatModel.getSelectedCodeBlockIndex()];
|
||||
if (block == null) {
|
||||
return;
|
||||
}
|
||||
@ -387,41 +401,99 @@ class ChatSidebar extends React.Component<{}, {}> {
|
||||
this.osInstance = osInstance;
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
onArrowUpPressed() {
|
||||
if (this.onTextAreaKeyDown("ArrowUp")) {
|
||||
const pres = this.chatWindowRef.current?.querySelectorAll("pre");
|
||||
let blockIndex = GlobalModel.sidebarchatModel.getSelectedCodeBlockIndex();
|
||||
if (pres == null) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (this.blockIndex == null) {
|
||||
this.blockIndex = pres.length - 1;
|
||||
} else if (this.blockIndex > 0) {
|
||||
this.blockIndex--;
|
||||
if (blockIndex == null) {
|
||||
GlobalModel.sidebarchatModel.setSelectedCodeBlockIndex(pres.length - 1);
|
||||
} else if (blockIndex > 0) {
|
||||
blockIndex--;
|
||||
GlobalModel.sidebarchatModel.setSelectedCodeBlockIndex(blockIndex);
|
||||
}
|
||||
this.updatePreTagOutline(pres[this.blockIndex]);
|
||||
blockIndex = GlobalModel.sidebarchatModel.getSelectedCodeBlockIndex();
|
||||
this.updatePreTagOutline(pres[blockIndex]);
|
||||
this.updateScrollTop();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
onArrowDownPressed() {
|
||||
if (this.onTextAreaKeyDown("ArrowDown")) {
|
||||
const pres = this.chatWindowRef.current?.querySelectorAll("pre");
|
||||
let blockIndex = GlobalModel.sidebarchatModel.getSelectedCodeBlockIndex();
|
||||
if (pres == null) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (this.blockIndex == null) {
|
||||
return;
|
||||
if (blockIndex == null) {
|
||||
return false;
|
||||
}
|
||||
if (this.blockIndex < pres.length && this.blockIndex >= 0) {
|
||||
this.blockIndex++;
|
||||
this.updatePreTagOutline(pres[this.blockIndex]);
|
||||
if (blockIndex < pres.length - 1 && blockIndex >= 0) {
|
||||
GlobalModel.sidebarchatModel.setSelectedCodeBlockIndex(blockIndex++);
|
||||
this.updatePreTagOutline(pres[blockIndex]);
|
||||
} else {
|
||||
this.bindArrowUpDownKeys.set(false);
|
||||
GlobalModel.sidebarchatModel.setFocus(true);
|
||||
this.textAreaRef.current.focus();
|
||||
this.updatePreTagOutline();
|
||||
GlobalModel.sidebarchatModel.setSelectedCodeBlockIndex(null);
|
||||
}
|
||||
this.updateScrollTop();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
onTextAreaKeyDown(key: "ArrowUp" | "ArrowDown") {
|
||||
const textarea = this.textAreaRef.current;
|
||||
const cursorPosition = textarea.selectionStart;
|
||||
const textBeforeCursor = textarea.value.slice(0, cursorPosition);
|
||||
const blockIndex = GlobalModel.sidebarchatModel.getSelectedCodeBlockIndex();
|
||||
|
||||
// Check if the cursor is at the first line for ArrowUp
|
||||
if ((textBeforeCursor.indexOf("\n") == -1 && cursorPosition == 0 && key == "ArrowUp") || blockIndex != null) {
|
||||
return true;
|
||||
}
|
||||
GlobalModel.sidebarchatModel.setFocus(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
onSetCmdInputValue(cmd?: string) {
|
||||
console.log("got here");
|
||||
if (cmd) {
|
||||
this.setCmdInputValue(cmd);
|
||||
} else {
|
||||
const pres = this.chatWindowRef.current?.querySelectorAll("pre");
|
||||
if (pres) {
|
||||
const selectedIdx = GlobalModel.sidebarchatModel.getSelectedCodeBlockIndex();
|
||||
pres.forEach((preElement, idx) => {
|
||||
if (selectedIdx === idx) {
|
||||
const codeElement = preElement.querySelector("code");
|
||||
if (codeElement) {
|
||||
const command = codeElement.textContent.replace(/\n$/, "");
|
||||
this.setCmdInputValue(command);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
setCmdInputValue(cmd: string) {
|
||||
GlobalModel.sidebarchatModel.setCmdToExec(cmd);
|
||||
GlobalModel.sidebarchatModel.resetFocus();
|
||||
GlobalModel.inputModel.curLine = cmd;
|
||||
GlobalModel.inputModel.giveFocus();
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
formChatMessage(cmdAndOutput) {
|
||||
@ -430,7 +502,7 @@ class ChatSidebar extends React.Component<{}, {}> {
|
||||
return "";
|
||||
}
|
||||
// Escape backticks in the output
|
||||
let escapedOutput = output ? output.replace(/```/g, "\\`\\`\\`") : "";
|
||||
let escapedOutput = output ? output.replace(/`/g, "\\`") : "";
|
||||
// Truncate the output if usedRows is over 100
|
||||
if (usedRows > 100) {
|
||||
const outputLines = escapedOutput.split("\n");
|
||||
@ -450,38 +522,22 @@ class ChatSidebar extends React.Component<{}, {}> {
|
||||
return chatMessage;
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
handleKeyDown(e) {
|
||||
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
||||
if (this.bindArrowUpDownKeys.get()) {
|
||||
GlobalModel.sidebarchatModel.setFocus("block", true);
|
||||
this.textAreaRef.current.blur();
|
||||
}
|
||||
|
||||
const textarea = this.textAreaRef.current;
|
||||
const cursorPosition = textarea.selectionStart;
|
||||
const textBeforeCursor = textarea.value.slice(0, cursorPosition);
|
||||
|
||||
// Check if the cursor is at the first line
|
||||
if (textBeforeCursor.indexOf("\n") === -1 && cursorPosition === 0 && e.key === "ArrowUp") {
|
||||
this.bindArrowUpDownKeys.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
|
||||
const renderAIChatKeybindings = GlobalModel.sidebarchatModel.hasFocus;
|
||||
const renderAIChatKeybindings = GlobalModel.sidebarchatModel.hasFocus();
|
||||
const textAreaValue = this.value.get();
|
||||
const bindArrowUpDownKeys = this.bindArrowUpDownKeys.get();
|
||||
|
||||
return (
|
||||
<div ref={this.sidebarRef} className="sidebarchat">
|
||||
<If condition={renderAIChatKeybindings}>
|
||||
<ChatKeyBindings component={this} bindArrowUpDownKeys={bindArrowUpDownKeys} />
|
||||
<ChatKeyBindings component={this} />
|
||||
</If>
|
||||
{chatMessageItems.length > 0 && (
|
||||
<ChatWindow chatWindowRef={this.chatWindowRef} onRendered={this.onChatWindowRendered} />
|
||||
<ChatWindow
|
||||
chatWindowRef={this.chatWindowRef}
|
||||
onRendered={this.onChatWindowRendered}
|
||||
onSetCmdInputValue={this.onSetCmdInputValue}
|
||||
/>
|
||||
)}
|
||||
<div className="sidebarchat-input-wrapper">
|
||||
<textarea
|
||||
@ -490,8 +546,8 @@ class ChatSidebar extends React.Component<{}, {}> {
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
className="sidebarchat-input chat-textarea"
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onMouseDown={this.onTextAreaFocused}
|
||||
onFocus={this.onTextAreaFocus}
|
||||
onMouseDown={this.onTextAreaMouseDown} // When the user clicks on the textarea
|
||||
onChange={this.onTextAreaChange}
|
||||
style={{ fontSize: this.termFontSize }}
|
||||
placeholder="Send a Message..."
|
||||
|
@ -53,16 +53,12 @@ class KeybindDevPane extends React.Component<{}, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
class SidebarKeyBindings extends React.Component<{ component: RightSideBar; isOpen: boolean }, {}> {
|
||||
class SidebarKeyBindings extends React.Component<{ component: RightSideBar }, {}> {
|
||||
componentDidMount(): void {
|
||||
const { component } = this.props;
|
||||
const keybindManager = GlobalModel.keybindManager;
|
||||
keybindManager.registerKeybinding("pane", "rightsidebar", "rightsidebar:toggle", (waveEvent) => {
|
||||
if (this.props.isOpen) {
|
||||
return component.onClose();
|
||||
} else {
|
||||
return component.onOpen();
|
||||
}
|
||||
return component.toggleCollapse();
|
||||
});
|
||||
}
|
||||
|
||||
@ -87,13 +83,21 @@ class RightSideBar extends React.Component<
|
||||
{}
|
||||
> {
|
||||
mode: OV<string> = mobx.observable.box("aichat", { name: "RightSideBar-mode" });
|
||||
timeoutId: NodeJS.Timeout = null;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
mobx.makeObservable(this);
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
componentWillUnmount() {
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
setMode(mode: string) {
|
||||
if (mode == this.mode.get()) {
|
||||
return;
|
||||
@ -101,15 +105,19 @@ class RightSideBar extends React.Component<
|
||||
this.mode.set(mode);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onOpen() {
|
||||
GlobalModel.rightSidebarModel.setCollapsed(false);
|
||||
return true;
|
||||
@mobx.action.bound
|
||||
toggleCollapse() {
|
||||
const isCollapsed = GlobalModel.rightSidebarModel.getCollapsed();
|
||||
GlobalModel.rightSidebarModel.setCollapsed(!isCollapsed);
|
||||
if (this.mode.get() == "aichat") {
|
||||
if (isCollapsed) {
|
||||
this.timeoutId = setTimeout(() => {
|
||||
GlobalModel.inputModel.setChatSidebarFocus();
|
||||
}, 100);
|
||||
} else {
|
||||
GlobalModel.inputModel.setChatSidebarFocus(false);
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onClose() {
|
||||
GlobalModel.rightSidebarModel.setCollapsed(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -124,9 +132,9 @@ class RightSideBar extends React.Component<
|
||||
enableSnap={true}
|
||||
parentRef={this.props.parentRef}
|
||||
>
|
||||
{(toggleCollapse) => (
|
||||
{() => (
|
||||
<React.Fragment>
|
||||
<SidebarKeyBindings component={this} isOpen={!isCollapsed} />
|
||||
<SidebarKeyBindings component={this} />
|
||||
<div className="header">
|
||||
<div className="rsb-modes">
|
||||
<div
|
||||
@ -148,7 +156,7 @@ class RightSideBar extends React.Component<
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
<Button className="secondary ghost close" onClick={toggleCollapse}>
|
||||
<Button className="secondary ghost close" onClick={this.toggleCollapse}>
|
||||
<i className="fa-sharp fa-solid fa-xmark-large" />
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -139,6 +139,7 @@ class InputModel {
|
||||
const activeAuxView = this.getAuxViewFocus() ? this.getActiveAuxView() : null;
|
||||
switch (activeAuxView) {
|
||||
case appconst.InputAuxView_History:
|
||||
console.log("focus history");
|
||||
const elem: HTMLElement = document.querySelector(".cmd-input input.history-input");
|
||||
if (elem) {
|
||||
elem.focus();
|
||||
@ -148,8 +149,12 @@ class InputModel {
|
||||
this.setAIChatFocus();
|
||||
break;
|
||||
case null:
|
||||
if (GlobalModel.sidebarchatModel.hasFocus) {
|
||||
if (GlobalModel.sidebarchatModel.hasFocus()) {
|
||||
this.auxViewFocus.set(false);
|
||||
const elem: HTMLElement = document.querySelector(".sidebarchat-input");
|
||||
if (elem != null) {
|
||||
elem.focus();
|
||||
}
|
||||
} else {
|
||||
const elem = document.getElementById("main-cmd-input");
|
||||
if (elem) {
|
||||
@ -159,6 +164,7 @@ class InputModel {
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
console.log("focus auxview");
|
||||
const elem: HTMLElement = document.querySelector(".cmd-input .auxview");
|
||||
if (elem != null) {
|
||||
elem.focus();
|
||||
@ -282,6 +288,12 @@ class InputModel {
|
||||
}
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
setChatSidebarFocus(focus = true): void {
|
||||
GlobalModel.sidebarchatModel.setFocus(focus);
|
||||
this.giveFocus();
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
updateCmdLine(cmdLine: StrWithPos): void {
|
||||
this.curLine = cmdLine.str;
|
||||
@ -468,7 +480,7 @@ class InputModel {
|
||||
}
|
||||
// (view == null) means standard cmdinput keybindings
|
||||
if (view == null) {
|
||||
return !this.getAuxViewFocus() && !GlobalModel.sidebarchatModel.hasFocus;
|
||||
return !this.getAuxViewFocus() && !GlobalModel.sidebarchatModel.hasFocus();
|
||||
} else {
|
||||
return this.getAuxViewFocus() && view == this.getActiveAuxView();
|
||||
}
|
||||
|
@ -82,6 +82,11 @@ class MainSidebarModel {
|
||||
return collapsed;
|
||||
}
|
||||
|
||||
setCollapsed(collapsed: boolean): void {
|
||||
const width = this.getWidth(true);
|
||||
this.saveState(width, collapsed);
|
||||
}
|
||||
|
||||
saveState(width: number, collapsed: boolean): void {
|
||||
GlobalCommandRunner.clientSetMainSidebar(width, collapsed).finally(() => {
|
||||
mobx.action(() => {
|
||||
|
@ -738,10 +738,12 @@ class Model {
|
||||
setTimeout(() => {
|
||||
// allows for the session view to load
|
||||
this.inputModel.setAuxViewFocus(false);
|
||||
this.inputModel.setChatSidebarFocus(false);
|
||||
}, 100);
|
||||
})();
|
||||
} else {
|
||||
this.inputModel.setAuxViewFocus(false);
|
||||
this.inputModel.setChatSidebarFocus(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,104 +1,91 @@
|
||||
import * as mobx from "mobx";
|
||||
import { Model } from "./model";
|
||||
|
||||
type SidebarChatFocus = {
|
||||
input: boolean;
|
||||
block: boolean;
|
||||
};
|
||||
|
||||
class SidebarChatModel {
|
||||
globalModel: Model;
|
||||
sidebarChatFocus: SidebarChatFocus;
|
||||
cmdAndOutput: CmdAndOutput;
|
||||
cmdFromChat: string;
|
||||
sidebarChatFocused: OV<boolean> = mobx.observable.box(false, { name: "SidebarChatModel-sidebarChatFocused" });
|
||||
cmdAndOutput: OV<{ cmd: string; output: string; usedRows: number; isError: boolean }> = mobx.observable.box(
|
||||
{ cmd: "", output: "", usedRows: 0, isError: false },
|
||||
{ name: "SidebarChatModel-cmdAndOutput" }
|
||||
);
|
||||
cmdFromChat: OV<string> = mobx.observable.box("", { name: "SidebarChatModel-cmdFromChat" });
|
||||
selectedCodeBlockIndex: OV<number> = mobx.observable.box(null, { name: "SidebarChatModel-codeBlockIndex" });
|
||||
|
||||
constructor(globalModel: Model) {
|
||||
this.globalModel = globalModel;
|
||||
mobx.makeObservable(this, {
|
||||
sidebarChatFocus: mobx.observable,
|
||||
cmdAndOutput: mobx.observable,
|
||||
setFocus: mobx.action,
|
||||
resetFocus: mobx.action,
|
||||
setCmdAndOutput: mobx.action,
|
||||
resetCmdAndOutput: mobx.action,
|
||||
setCmdToExec: mobx.action,
|
||||
resetCmdToExec: mobx.action,
|
||||
hasFocus: mobx.computed,
|
||||
focused: mobx.computed,
|
||||
cmdToExec: mobx.computed,
|
||||
mobx.makeObservable(this);
|
||||
}
|
||||
|
||||
// block can be the chat-window in terms of focus
|
||||
@mobx.action
|
||||
setFocus(focus: boolean): void {
|
||||
this.resetFocus();
|
||||
this.sidebarChatFocused.set(focus);
|
||||
}
|
||||
|
||||
hasFocus(): boolean {
|
||||
return this.sidebarChatFocused.get();
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
resetFocus(): void {
|
||||
this.sidebarChatFocused.set(false);
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
setCmdAndOutput(cmd: string, output: string, usedRows: number, isError: boolean): void {
|
||||
console.log("cmd", cmd);
|
||||
this.cmdAndOutput.set({
|
||||
cmd: cmd,
|
||||
output: output,
|
||||
usedRows: usedRows,
|
||||
isError: isError,
|
||||
});
|
||||
this.sidebarChatFocus = {
|
||||
input: false,
|
||||
block: false,
|
||||
};
|
||||
this.cmdAndOutput = {
|
||||
}
|
||||
|
||||
getCmdAndOutput(): { cmd: string; output: string; usedRows: number; isError: boolean } {
|
||||
return this.cmdAndOutput.get();
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
resetCmdAndOutput(): void {
|
||||
this.cmdAndOutput.set({
|
||||
cmd: "",
|
||||
output: "",
|
||||
usedRows: 0,
|
||||
isError: false,
|
||||
};
|
||||
this.cmdFromChat = "";
|
||||
}
|
||||
|
||||
// block can be the chat-window in terms of focus
|
||||
setFocus(section: "input" | "block", focus: boolean): void {
|
||||
document.querySelector(".sidebarchat .sidebarchat-input");
|
||||
this.resetFocus();
|
||||
this.sidebarChatFocus[section] = focus;
|
||||
}
|
||||
|
||||
get hasFocus(): boolean {
|
||||
return this.sidebarChatFocus.input || this.sidebarChatFocus.block;
|
||||
}
|
||||
|
||||
get focused(): "input" | "block" | null {
|
||||
if (this.sidebarChatFocus.input) return "input";
|
||||
if (this.sidebarChatFocus.block) return "block";
|
||||
return null;
|
||||
}
|
||||
|
||||
resetFocus(): void {
|
||||
this.sidebarChatFocus.input = false;
|
||||
this.sidebarChatFocus.block = false;
|
||||
}
|
||||
|
||||
setCmdAndOutput(cmd: string, output: string, usedRows: number, isError: boolean): void {
|
||||
this.cmdAndOutput.cmd = cmd;
|
||||
this.cmdAndOutput.output = output;
|
||||
this.cmdAndOutput.usedRows = usedRows;
|
||||
this.cmdAndOutput.isError = isError;
|
||||
}
|
||||
|
||||
getCmdAndOutput(): CmdAndOutput {
|
||||
return {
|
||||
cmd: this.cmdAndOutput.cmd,
|
||||
output: this.cmdAndOutput.output,
|
||||
usedRows: this.cmdAndOutput.usedRows,
|
||||
isError: this.cmdAndOutput.isError,
|
||||
};
|
||||
}
|
||||
|
||||
resetCmdAndOutput(): void {
|
||||
this.cmdAndOutput.cmd = "";
|
||||
this.cmdAndOutput.output = "";
|
||||
this.cmdAndOutput.usedRows = 0;
|
||||
this.cmdAndOutput.isError = false;
|
||||
});
|
||||
}
|
||||
|
||||
hasCmdAndOutput(): boolean {
|
||||
return this.cmdAndOutput.cmd.length > 0 || this.cmdAndOutput.output.length > 0;
|
||||
const { cmd, output } = this.cmdAndOutput.get();
|
||||
return cmd.length > 0 || output.length > 0;
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
setCmdToExec(cmd: string): void {
|
||||
this.cmdFromChat = cmd;
|
||||
this.cmdFromChat.set(cmd);
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
resetCmdToExec(): void {
|
||||
this.cmdFromChat = "";
|
||||
this.cmdFromChat.set("");
|
||||
}
|
||||
|
||||
get cmdToExec(): string {
|
||||
return this.cmdFromChat;
|
||||
getCmdToExec(): string {
|
||||
return this.cmdFromChat.get();
|
||||
}
|
||||
|
||||
getSelectedCodeBlockIndex(): number {
|
||||
return this.selectedCodeBlockIndex.get();
|
||||
}
|
||||
|
||||
setSelectedCodeBlockIndex(index: number): void {
|
||||
this.selectedCodeBlockIndex.set(index);
|
||||
}
|
||||
|
||||
resetSelectedCodeBlockIndex(): void {
|
||||
this.selectedCodeBlockIndex.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
7
src/types/custom.d.ts
vendored
7
src/types/custom.d.ts
vendored
@ -990,13 +990,6 @@ declare global {
|
||||
click?: () => void; // not required if role is set
|
||||
submenu?: ContextMenuItem[];
|
||||
};
|
||||
|
||||
type CmdAndOutput = {
|
||||
cmd: string;
|
||||
output: string;
|
||||
usedRows: number;
|
||||
isError: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export {};
|
||||
|
Loading…
Reference in New Issue
Block a user