mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
Various Bug Fixes and UI Improvements (#386)
This change adds - performance improvements for ai chat - new ai chat user interface - open blank files with codeedit - fix for userinput password modal
This commit is contained in:
parent
09f4616ae0
commit
ea19444710
@ -16,7 +16,6 @@
|
||||
font-family: var(--markdown-font);
|
||||
font-size: 14px;
|
||||
overflow-wrap: break-word;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.heading {
|
||||
&:first-of-type {
|
||||
@ -35,15 +34,6 @@
|
||||
color: #32afff;
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
dl,
|
||||
table,
|
||||
details table {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
list-style-position: outside;
|
||||
|
@ -1,13 +1,13 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
.userinput-header {
|
||||
.tips-header {
|
||||
font-weight: bold;
|
||||
color: var(--main-text-color);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.userinput-body {
|
||||
.tips-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
@ -17,16 +17,16 @@
|
||||
font: var(--fixed-font);
|
||||
color: var(--main-text-color);
|
||||
|
||||
.userinput-markdown {
|
||||
.tips-markdown {
|
||||
color: inherit;
|
||||
height: 300px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.userinput-text {
|
||||
.tips-text {
|
||||
}
|
||||
|
||||
.userinput-inputbox {
|
||||
.tips-inputbox {
|
||||
resize: none;
|
||||
background-color: var(--panel-bg-color);
|
||||
border-radius: 6px;
|
||||
|
@ -33,9 +33,9 @@ const TipsModal = (tipsContent: UserInputRequest) => {
|
||||
|
||||
const queryText = useMemo(() => {
|
||||
if (tipsContent.markdown) {
|
||||
return <Markdown text={tipsContent.querytext} className="userinput-markdown" />;
|
||||
return <Markdown text={tipsContent.querytext} className="tips-markdown" />;
|
||||
}
|
||||
return <span className="userinput-text">{tipsContent.querytext}</span>;
|
||||
return <span className="tips-text">{tipsContent.querytext}</span>;
|
||||
}, [tipsContent.markdown, tipsContent.querytext]);
|
||||
|
||||
const inputBox = useMemo(() => {
|
||||
@ -48,7 +48,7 @@ const TipsModal = (tipsContent: UserInputRequest) => {
|
||||
onChange={(e) => setResponseText(e.target.value)}
|
||||
value={responseText}
|
||||
maxLength={400}
|
||||
className="userinput-inputbox"
|
||||
className="tips-inputbox"
|
||||
autoFocus={true}
|
||||
onKeyDown={(e) => keyutil.keydownWrapper(handleKeyDown)(e)}
|
||||
/>
|
||||
@ -57,8 +57,8 @@ const TipsModal = (tipsContent: UserInputRequest) => {
|
||||
|
||||
return (
|
||||
<Modal onOk={() => handleClose()} onCancel={() => handleClose()} onClose={() => handleClose()}>
|
||||
<div className="userinput-header">{tipsContent.title}</div>
|
||||
<div className="userinput-body">
|
||||
<div className="tips-header">{tipsContent.title}</div>
|
||||
<div className="tips-body">
|
||||
{queryText}
|
||||
{inputBox}
|
||||
</div>
|
||||
|
@ -524,7 +524,7 @@ export class PreviewModel implements ViewModel {
|
||||
}
|
||||
return { specializedView: "markdown" };
|
||||
}
|
||||
if (isTextFile(mimeType)) {
|
||||
if (isTextFile(mimeType) || fileInfo.size == 0) {
|
||||
return { specializedView: "codeedit" };
|
||||
}
|
||||
return { errorStr: `Preview (${mimeType})` };
|
||||
|
@ -19,6 +19,7 @@
|
||||
// margin-bottom: 5px;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
gap: 8px;
|
||||
|
||||
// This is the filler that will push the chat messages to the bottom until the chat window is full
|
||||
.filler {
|
||||
@ -26,24 +27,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
.chat-msg {
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
border-radius: 8px;
|
||||
|
||||
.chat-msg-header {
|
||||
&.chat-msg-header {
|
||||
display: flex;
|
||||
margin-bottom: 2px;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
i {
|
||||
margin-top: 2px;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.icon-box {
|
||||
padding-top: 0;
|
||||
border-radius: 4px;
|
||||
background-color: rgb(from var(--highlight-bg-color) r g b / 0.05);
|
||||
display: flex;
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg-assistant {
|
||||
&.chat-msg-assistant {
|
||||
color: var(--app-text-color);
|
||||
background-color: rgb(from var(--highlight-bg-color) r g b / 0.1);
|
||||
margin-right: auto;
|
||||
padding: 10px;
|
||||
|
||||
.markdown {
|
||||
width: 100%;
|
||||
@ -57,27 +68,48 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
&.chat-msg-user {
|
||||
margin-left: auto;
|
||||
padding: 10px;
|
||||
background-color: rgb(from var(--accent-color) r g b / 0.15);
|
||||
}
|
||||
|
||||
.chat-msg-error {
|
||||
&.chat-msg-error {
|
||||
color: var(--cmdinput-text-error);
|
||||
font-family: var(--markdown-font);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.typing-indicator {
|
||||
&.typing-indicator {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.waveai-controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
|
||||
.waveai-input-wrapper {
|
||||
padding: 16px 15px 7px;
|
||||
flex: 0 0 auto;
|
||||
flex-shrink: 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: 8px 12px;
|
||||
flex: 1 1 auto;
|
||||
background-color: rgb(from var(--block-bg-color) r g b / 0.39);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgb(from var(--highlight-bg-color) r g b / 0.42);
|
||||
|
||||
.waveai-input {
|
||||
color: var(--main-text-color);
|
||||
background-color: var(--cmdinput-textarea-bg);
|
||||
opacity: 0.4;
|
||||
background-color: inherit;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
border: transparent;
|
||||
@ -90,4 +122,23 @@
|
||||
height: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
.waveai-submit-button {
|
||||
border-radius: 100%;
|
||||
width: 27px;
|
||||
aspect-ratio: 1 /1;
|
||||
color: var(--block-bg-color);
|
||||
background-color: var(--main-text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
padding: 0;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--grey-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,20 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { Button } from "@/app/element/button";
|
||||
import { Markdown } from "@/app/element/markdown";
|
||||
import { TypingIndicator } from "@/app/element/typingindicator";
|
||||
import { useDimensions } from "@/app/hook/useDimensions";
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { WindowRpcClient } from "@/app/store/wshrpcutil";
|
||||
import { atoms, fetchWaveFile, getUserName, globalStore, WOS } from "@/store/global";
|
||||
import { atoms, fetchWaveFile, globalStore, WOS } from "@/store/global";
|
||||
import { BlockService } from "@/store/services";
|
||||
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil";
|
||||
import { isBlank } from "@/util/util";
|
||||
import { isBlank, makeIconClass } from "@/util/util";
|
||||
import { atom, Atom, PrimitiveAtom, useAtomValue, useSetAtom, WritableAtom } from "jotai";
|
||||
import type { OverlayScrollbars } from "overlayscrollbars";
|
||||
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
|
||||
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
|
||||
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
||||
import tinycolor from "tinycolor2";
|
||||
import "./waveai.less";
|
||||
|
||||
@ -29,7 +31,6 @@ const outline = "2px solid var(--accent-color)";
|
||||
|
||||
interface ChatItemProps {
|
||||
chatItem: ChatMessageType;
|
||||
itemCount: number;
|
||||
}
|
||||
|
||||
function promptToMsg(prompt: OpenAIPromptMessageType): ChatMessageType {
|
||||
@ -41,6 +42,8 @@ function promptToMsg(prompt: OpenAIPromptMessageType): ChatMessageType {
|
||||
};
|
||||
}
|
||||
|
||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
export class WaveAiModel implements ViewModel {
|
||||
viewType: string;
|
||||
blockId: string;
|
||||
@ -53,10 +56,15 @@ export class WaveAiModel implements ViewModel {
|
||||
messagesAtom: PrimitiveAtom<Array<ChatMessageType>>;
|
||||
addMessageAtom: WritableAtom<unknown, [message: ChatMessageType], void>;
|
||||
updateLastMessageAtom: WritableAtom<unknown, [text: string, isUpdating: boolean], void>;
|
||||
removeLastMessageAtom: WritableAtom<unknown, [], void>;
|
||||
simulateAssistantResponseAtom: WritableAtom<unknown, [userMessage: ChatMessageType], Promise<void>>;
|
||||
textAreaRef: React.RefObject<HTMLTextAreaElement>;
|
||||
locked: PrimitiveAtom<boolean>;
|
||||
cancel: boolean;
|
||||
|
||||
constructor(blockId: string) {
|
||||
this.locked = atom(false);
|
||||
this.cancel = false;
|
||||
this.viewType = "waveai";
|
||||
this.blockId = blockId;
|
||||
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
|
||||
@ -79,7 +87,13 @@ export class WaveAiModel implements ViewModel {
|
||||
set(this.messagesAtom, [...messages.slice(0, -1), updatedMessage]);
|
||||
}
|
||||
});
|
||||
this.removeLastMessageAtom = atom(null, (get, set) => {
|
||||
const messages = get(this.messagesAtom);
|
||||
messages.pop();
|
||||
set(this.messagesAtom, [...messages]);
|
||||
});
|
||||
this.simulateAssistantResponseAtom = atom(null, async (get, set, userMessage: ChatMessageType) => {
|
||||
// unused at the moment. can replace the temp() function in the future
|
||||
const typingMessage: ChatMessageType = {
|
||||
id: crypto.randomUUID(),
|
||||
user: "assistant",
|
||||
@ -89,22 +103,16 @@ export class WaveAiModel implements ViewModel {
|
||||
|
||||
// Add a typing indicator
|
||||
set(this.addMessageAtom, typingMessage);
|
||||
|
||||
setTimeout(() => {
|
||||
await sleep(1500);
|
||||
const parts = userMessage.text.split(" ");
|
||||
let currentPart = 0;
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
if (currentPart < parts.length) {
|
||||
while (currentPart < parts.length) {
|
||||
const part = parts[currentPart] + " ";
|
||||
set(this.updateLastMessageAtom, part, true);
|
||||
currentPart++;
|
||||
} else {
|
||||
clearInterval(intervalId);
|
||||
set(this.updateLastMessageAtom, "", false);
|
||||
await sleep(100);
|
||||
}
|
||||
}, 100);
|
||||
}, 1500);
|
||||
set(this.updateLastMessageAtom, "", false);
|
||||
});
|
||||
this.viewText = atom((get) => {
|
||||
const settings = get(atoms.settingsAtom);
|
||||
@ -151,8 +159,10 @@ export class WaveAiModel implements ViewModel {
|
||||
const simulateResponse = useSetAtom(this.simulateAssistantResponseAtom);
|
||||
const clientId = useAtomValue(atoms.clientId);
|
||||
const blockId = this.blockId;
|
||||
const setLocked = useSetAtom(this.locked);
|
||||
|
||||
const sendMessage = (text: string, user: string = "user") => {
|
||||
setLocked(true);
|
||||
const newMessage: ChatMessageType = {
|
||||
id: crypto.randomUUID(),
|
||||
user,
|
||||
@ -173,10 +183,16 @@ export class WaveAiModel implements ViewModel {
|
||||
role: "user",
|
||||
content: text,
|
||||
};
|
||||
if (newPrompt.name == "*username") {
|
||||
newPrompt.name = getUserName();
|
||||
}
|
||||
const temp = async () => {
|
||||
const typingMessage: ChatMessageType = {
|
||||
id: crypto.randomUUID(),
|
||||
user: "assistant",
|
||||
text: "",
|
||||
isAssistant: true,
|
||||
};
|
||||
|
||||
// Add a typing indicator
|
||||
globalStore.set(this.addMessageAtom, typingMessage);
|
||||
const history = await this.fetchAiData();
|
||||
const beMsg: OpenAiStreamRequest = {
|
||||
clientid: clientId,
|
||||
@ -187,21 +203,25 @@ export class WaveAiModel implements ViewModel {
|
||||
let fullMsg = "";
|
||||
for await (const msg of aiGen) {
|
||||
fullMsg += msg.text ?? "";
|
||||
globalStore.set(this.updateLastMessageAtom, msg.text ?? "", true);
|
||||
if (this.cancel) {
|
||||
if (fullMsg == "") {
|
||||
globalStore.set(this.removeLastMessageAtom);
|
||||
}
|
||||
const response: ChatMessageType = {
|
||||
id: newMessage.id,
|
||||
user: newMessage.user,
|
||||
text: fullMsg,
|
||||
isAssistant: true,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
await sleep(100);
|
||||
}
|
||||
globalStore.set(this.updateLastMessageAtom, "", false);
|
||||
if (fullMsg != "") {
|
||||
const responsePrompt: OpenAIPromptMessageType = {
|
||||
role: "assistant",
|
||||
content: fullMsg,
|
||||
};
|
||||
const writeToHistory = BlockService.SaveWaveAiData(blockId, [...history, newPrompt, responsePrompt]);
|
||||
const typeResponse = simulateResponse(response);
|
||||
Promise.all([writeToHistory, typeResponse]);
|
||||
await BlockService.SaveWaveAiData(blockId, [...history, newPrompt, responsePrompt]);
|
||||
}
|
||||
setLocked(false);
|
||||
this.cancel = false;
|
||||
};
|
||||
temp();
|
||||
};
|
||||
@ -218,63 +238,64 @@ function makeWaveAiViewModel(blockId): WaveAiModel {
|
||||
return waveAiModel;
|
||||
}
|
||||
|
||||
const ChatItem = ({ chatItem, itemCount }: ChatItemProps) => {
|
||||
const ChatItem = ({ chatItem }: ChatItemProps) => {
|
||||
const { isAssistant, text, isError } = chatItem;
|
||||
const senderClassName = isAssistant ? "chat-msg-assistant" : "chat-msg-user";
|
||||
const msgClassName = `chat-msg ${senderClassName}`;
|
||||
const cssVar = "--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 = itemCount % 2 === 0 ? "none" : newColor;
|
||||
|
||||
const renderError = (err: string): React.JSX.Element => <div className="chat-msg-error">{err}</div>;
|
||||
|
||||
const renderContent = (): React.JSX.Element => {
|
||||
const renderContent = useMemo(() => {
|
||||
if (isAssistant) {
|
||||
if (isError) {
|
||||
return renderError(isError);
|
||||
}
|
||||
return text ? (
|
||||
<>
|
||||
<div className="chat-msg-header">
|
||||
<div className="chat-msg chat-msg-header">
|
||||
<div className="icon-box">
|
||||
<i className="fa-sharp fa-solid fa-sparkles"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="chat-msg chat-msg-assistant"
|
||||
style={{ maxWidth: "calc(var(--aichat-msg-width) * 1px)" }}
|
||||
>
|
||||
<Markdown text={text} />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="chat-msg-header">
|
||||
<i className="fa-sharp fa-solid fa-sparkles"></i>
|
||||
</div>
|
||||
<TypingIndicator className="typing-indicator" />
|
||||
<TypingIndicator className="chat-msg typing-indicator" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="chat-msg-header">
|
||||
<i className="fa-sharp fa-solid fa-user"></i>
|
||||
</div>
|
||||
<div className="chat-msg chat-msg-user" style={{ maxWidth: "calc(var(--aichat-msg-width) * 1px)" }}>
|
||||
<Markdown className="msg-text" text={text} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}, [text, isAssistant, isError]);
|
||||
|
||||
return (
|
||||
<div className={msgClassName} style={{ backgroundColor }}>
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
return <div className={"chat-msg-container"}>{renderContent}</div>;
|
||||
};
|
||||
|
||||
interface ChatWindowProps {
|
||||
chatWindowRef: React.RefObject<HTMLDivElement>;
|
||||
messages: ChatMessageType[];
|
||||
msgWidths: Object;
|
||||
}
|
||||
|
||||
const ChatWindow = memo(
|
||||
forwardRef<OverlayScrollbarsComponentRef, ChatWindowProps>(({ chatWindowRef, messages }, ref) => {
|
||||
forwardRef<OverlayScrollbarsComponentRef, ChatWindowProps>(({ chatWindowRef, messages, msgWidths }, ref) => {
|
||||
const [isUserScrolling, setIsUserScrolling] = useState(false);
|
||||
|
||||
const osRef = useRef<OverlayScrollbarsComponentRef>(null);
|
||||
@ -334,10 +355,10 @@ const ChatWindow = memo(
|
||||
options={{ scrollbars: { autoHide: "leave" } }}
|
||||
events={{ initialized: handleScrollbarInitialized }}
|
||||
>
|
||||
<div ref={chatWindowRef} className="chat-window">
|
||||
<div ref={chatWindowRef} className="chat-window" style={msgWidths}>
|
||||
<div className="filler"></div>
|
||||
{messages.map((chitem, idx) => (
|
||||
<ChatItem key={idx} chatItem={chitem} itemCount={idx + 1} />
|
||||
<ChatItem key={idx} chatItem={chitem} />
|
||||
))}
|
||||
</div>
|
||||
</OverlayScrollbarsComponent>
|
||||
@ -394,7 +415,7 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
style={{ fontSize: termFontSize }}
|
||||
placeholder="Send a Message..."
|
||||
placeholder="Ask anything..."
|
||||
value={value}
|
||||
></textarea>
|
||||
);
|
||||
@ -407,42 +428,21 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
|
||||
const chatWindowRef = useRef<HTMLDivElement>(null);
|
||||
const osRef = useRef<OverlayScrollbarsComponentRef>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const submitTimeoutRef = useRef<NodeJS.Timeout>(null);
|
||||
|
||||
const [value, setValue] = useState("");
|
||||
const [selectedBlockIdx, setSelectedBlockIdx] = useState<number | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const termFontSize: number = 14;
|
||||
const windowDims = useDimensions(chatWindowRef);
|
||||
const msgWidths = {};
|
||||
const locked = useAtomValue(model.locked);
|
||||
msgWidths["--aichat-msg-width"] = windowDims.width * 0.85;
|
||||
|
||||
// a weird workaround to initialize ansynchronously
|
||||
useEffect(() => {
|
||||
model.populateMessages();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (submitTimeoutRef.current) {
|
||||
clearTimeout(submitTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const submit = useCallback(
|
||||
(messageStr: string) => {
|
||||
if (!isSubmitting) {
|
||||
setIsSubmitting(true);
|
||||
sendMessage(messageStr);
|
||||
|
||||
clearTimeout(submitTimeoutRef.current);
|
||||
submitTimeoutRef.current = setTimeout(() => {
|
||||
setIsSubmitting(false);
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
[isSubmitting, sendMessage, setValue]
|
||||
);
|
||||
|
||||
const handleTextAreaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setValue(e.target.value);
|
||||
};
|
||||
@ -479,10 +479,14 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
|
||||
};
|
||||
|
||||
const handleEnterKeyPressed = useCallback(() => {
|
||||
const isCurrentlyUpdating = messages.some((message) => message.isUpdating);
|
||||
if (isCurrentlyUpdating || value === "") return;
|
||||
// using globalStore to avoid potential timing problems
|
||||
// useAtom means the component must rerender once before
|
||||
// the unlock is detected. this automatically checks on the
|
||||
// callback firing instead
|
||||
const locked = globalStore.get(model.locked);
|
||||
if (locked || value === "") return;
|
||||
|
||||
submit(value);
|
||||
sendMessage(value);
|
||||
setValue("");
|
||||
setSelectedBlockIdx(null);
|
||||
}, [messages, value]);
|
||||
@ -589,9 +593,26 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
|
||||
}
|
||||
};
|
||||
|
||||
let buttonClass = "waveai-submit-button";
|
||||
let buttonIcon = makeIconClass("arrow-up", false);
|
||||
let buttonTitle = "run";
|
||||
if (locked) {
|
||||
buttonClass = "waveai-submit-button stop";
|
||||
buttonIcon = makeIconClass("stop", false);
|
||||
buttonTitle = "stop";
|
||||
}
|
||||
const handleButtonPress = useCallback(() => {
|
||||
if (locked) {
|
||||
model.cancel = true;
|
||||
} else {
|
||||
handleEnterKeyPressed();
|
||||
}
|
||||
}, [locked, handleEnterKeyPressed]);
|
||||
|
||||
return (
|
||||
<div ref={waveaiRef} className="waveai" onClick={handleContainerClick}>
|
||||
<ChatWindow ref={osRef} chatWindowRef={chatWindowRef} messages={messages} />
|
||||
<ChatWindow ref={osRef} chatWindowRef={chatWindowRef} messages={messages} msgWidths={msgWidths} />
|
||||
<div className="waveai-controls">
|
||||
<div className="waveai-input-wrapper">
|
||||
<ChatInput
|
||||
ref={inputRef}
|
||||
@ -603,6 +624,10 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
|
||||
termFontSize={termFontSize}
|
||||
/>
|
||||
</div>
|
||||
<Button className={buttonClass} onClick={handleButtonPress}>
|
||||
<i className={buttonIcon} title={buttonTitle} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user