From 887e52c89e45ec9453af2d07ca6186f953d25c89 Mon Sep 17 00:00:00 2001 From: DepsCian Date: Sun, 6 Apr 2025 15:23:15 +0300 Subject: [PATCH 1/3] refactor: complete Wave AI widget remake - Overhaul chat interface with improved message containers and styling - Enhance responsive layout for better space utilization - Redesign typing indicator with new animation and visual appearance - Add message actions for copy, edit, and repeat functionality --- frontend/app/element/typingindicator.scss | 58 +-- frontend/app/element/typingindicator.tsx | 23 +- frontend/app/view/waveai/waveai.scss | 302 +++++++++++++--- frontend/app/view/waveai/waveai.tsx | 416 ++++++++++++++++++---- 4 files changed, 646 insertions(+), 153 deletions(-) diff --git a/frontend/app/element/typingindicator.scss b/frontend/app/element/typingindicator.scss index dae78ed8f7..a8750de07f 100644 --- a/frontend/app/element/typingindicator.scss +++ b/frontend/app/element/typingindicator.scss @@ -1,46 +1,54 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -$dot-width: 11px; -$dot-color: var(--success-color); -$speed: 1.5s; +.typing-indicator { + display: inline-flex; + align-items: center; + gap: 8px; + margin: 0; + padding: 0; -.typing { - position: relative; - height: $dot-width; + &-bubble { + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(from var(--accent-color) r g b / 0.1); + border-radius: 20px; + padding: 6px 12px; + gap: 4px; + } - 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; + &-dot { + width: 6px; + height: 6px; border-radius: 50%; + background-color: var(--accent-color); + opacity: 0.7; + + &:nth-child(1) { + animation: typing-animation 1.4s infinite ease-in-out; + animation-delay: 0s; + } &:nth-child(2) { + animation: typing-animation 1.4s infinite ease-in-out; animation-delay: 0.2s; - margin-left: $dot-width * 1.5; } &:nth-child(3) { + animation: typing-animation 1.4s infinite ease-in-out; animation-delay: 0.4s; - margin-left: $dot-width * 3; } } } -@keyframes blink { - 0% { - opacity: 0.1; +@keyframes typing-animation { + 0%, + 100% { + transform: translateY(0); } - 20% { + 50% { + transform: translateY(-5px); opacity: 1; } - 100% { - opacity: 0.1; - } } diff --git a/frontend/app/element/typingindicator.tsx b/frontend/app/element/typingindicator.tsx index 7d39ba78c4..ac8495aa28 100644 --- a/frontend/app/element/typingindicator.tsx +++ b/frontend/app/element/typingindicator.tsx @@ -1,21 +1,22 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { clsx } from "clsx"; - +import React from "react"; import "./typingindicator.scss"; -type TypingIndicatorProps = { +export interface TypingIndicatorProps { + style?: React.CSSProperties; className?: string; -}; -const TypingIndicator = ({ className }: TypingIndicatorProps) => { +} + +export const TypingIndicator: React.FC = ({ style, className }) => { return ( -
- - - +
+
+
+
+
+
); }; - -export { TypingIndicator }; diff --git a/frontend/app/view/waveai/waveai.scss b/frontend/app/view/waveai/waveai.scss index 2d463fd88e..2fc4348ad6 100644 --- a/frontend/app/view/waveai/waveai.scss +++ b/frontend/app/view/waveai/waveai.scss @@ -18,7 +18,6 @@ .chat-window { flex-flow: column nowrap; display: flex; - gap: 8px; // This is the filler that will push the chat messages to the bottom until the chat window is full .filler { @@ -27,33 +26,43 @@ .chat-msg-container { display: flex; - gap: 8px; + flex-direction: column; + width: 100%; + border-bottom: 1px solid rgb(from var(--highlight-bg-color) r g b / 0.2); + position: relative; + + &:last-child { + border-bottom: none; + } + + &.user-msg-container { + background-color: rgb(from var(--highlight-bg-color) r g b / 0.07); + } + + &.error-msg-container { + background-color: rgb(from var(--error-color) r g b / 0.1); + } + .chat-msg { - margin: 10px 0; + width: 100%; + padding: 14px 16px 8px; display: flex; - align-items: flex-start; - border-radius: 8px; + flex-direction: column; + position: relative; &.chat-msg-header { - display: flex; - flex-direction: column; - justify-content: flex-start; - - .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; + padding: 0; + margin-bottom: -4px; + .msg-author { + font-size: 0.9em; + color: var(--dimmed-text-color); + font-weight: 500; + padding: 10px 16px 0; } } &.chat-msg-assistant { color: var(--main-text-color); - background-color: rgb(from var(--highlight-bg-color) r g b / 0.1); - margin-right: auto; - padding: 10px; - max-width: 85%; .markdown { width: 100%; @@ -63,23 +72,61 @@ word-break: break-word; max-width: 100%; overflow-x: auto; - margin-left: 0; + margin: 10px 0; + padding: 12px; + background-color: rgb(from var(--highlight-bg-color) r g b / 0.15); + border-radius: 6px; + } + } + + .streaming-text { + width: 100%; + line-height: 1.5; + margin: 0; + position: relative; + + .markdown { + width: 100%; + white-space: pre-wrap; + word-break: break-word; + + pre { + white-space: pre-wrap; + word-break: break-word; + max-width: 100%; + overflow-x: auto; + margin: 10px 0; + padding: 12px; + background-color: rgb(from var(--highlight-bg-color) r g b / 0.15); + border-radius: 6px; + } } } } + &.chat-msg-user { - margin-left: auto; - padding: 10px; - max-width: 85%; - background-color: rgb(from var(--accent-color) r g b / 0.15); + color: var(--main-text-color); + } + + &.chat-msg-edit { + padding-bottom: 16px; + + .edit-input { + width: 100%; + resize: none; + border: none; + outline: none; + background: transparent; + color: var(--main-text-color); + font-family: inherit; + font-size: inherit; + line-height: 1.5; + min-height: 60px; + } } &.chat-msg-error { color: var(--main-text-color); - background-color: rgb(from var(--error-color) r g b / 0.25); - margin-right: auto; - padding: 10px; - max-width: 85%; .markdown { width: 100%; @@ -95,7 +142,51 @@ } &.typing-indicator { - margin-top: 4px; + padding: 10px 16px; + } + } + + .msg-actions { + display: flex; + justify-content: flex-end; + padding: 0 16px 8px; + gap: 1px; + + .msg-action-btn { + background-color: transparent; + border: none; + width: 28px; + height: 28px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + color: rgb(from var(--highlight-bg-color) r g b / 0.35); + font-size: 0.8em; + transition: all 0.15s ease; + cursor: pointer; + + &:hover { + color: var(--accent-color); + } + + &.copy-btn { + i { + font-size: 0.95em; + } + } + + &.edit-btn { + i { + font-size: 0.9em; + } + } + + &.repeat-btn { + i { + font-size: 0.9em; + } + } } } } @@ -103,47 +194,148 @@ } } - .waveai-controls { + .waveai-input-container { flex: 0 0 auto; display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; - gap: 10px; - padding: 8px 6px; + flex-direction: column; + background-color: rgb(from var(--highlight-bg-color) r g b / 0.1); + border-radius: 8px; + border: 1px solid rgb(from var(--highlight-bg-color) r g b / 0.12); + padding: 12px; + margin: 8px; .waveai-input-wrapper { - padding: 8px 12px; - flex: 1 1 auto; 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); + flex-direction: row; + align-items: center; + width: 100%; .waveai-input { color: var(--main-text-color); - background-color: inherit; + background-color: transparent; resize: none; - width: 100%; - border: transparent; + flex: 1; + border: none; outline: none; - overflow: auto; - overflow-wrap: anywhere; - height: 21px; + overflow: hidden; + overflow-y: auto; + overflow-wrap: break-word; + min-height: 21px; + max-height: 120px; + padding: 0; } } - .waveai-submit-button { - border-radius: 100%; - width: 27px; - aspect-ratio: 1 /1; + .waveai-model-selector { display: flex; + justify-content: flex-end; + gap: 4px; align-items: center; - justify-content: center; - flex: 0 0 auto; - padding: 0; + margin-top: 8px; + width: 100%; + // overflow: hidden; + + .preset-selector { + position: relative; + max-width: 70%; + min-width: 100px; + + .preset-button { + background-color: transparent; + border: none; + display: flex; + align-items: center; + gap: 6px; + color: var(--dimmed-text-color); + padding: 4px 8px; + border-radius: 4px; + font-size: 0.9em; + transition: all 0.2s; + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + + span { + overflow: hidden; + text-overflow: ellipsis; + } + + &:hover, + &.active { + color: var(--accent-color); + background-color: rgb(from var(--highlight-bg-color) r g b / 0.15); + } + + i { + font-size: 0.8em; + flex-shrink: 0; + } + } + + .model-menu { + position: absolute; + bottom: 100%; + right: 0; + margin-bottom: 4px; + background-color: var(--main-bg-color); + border: 1px solid rgb(from var(--highlight-bg-color) r g b / 0.3); + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + min-width: 180px; + max-height: 300px; + overflow-y: auto; + z-index: 100; + + .model-menu-item { + padding: 8px 12px; + cursor: pointer; + + &:hover { + background-color: rgb(from var(--highlight-bg-color) r g b / 0.15); + } + } + } + } + + .waveai-submit-button { + flex: 0 0 auto; + height: 32px; + width: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + background-color: var(--accent-color); + color: white; + transition: all 0.2s; + + i { + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + } + + &:hover:not(:disabled) { + background-color: var(--accent-color-hover, var(--accent-color)); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + &.stop { + background-color: var(--error-color); + + &:hover { + background-color: var(--error-color-hover, var(--error-color)); + } + } + } } } } diff --git a/frontend/app/view/waveai/waveai.tsx b/frontend/app/view/waveai/waveai.tsx index 3ed852a780..3408779264 100644 --- a/frontend/app/view/waveai/waveai.tsx +++ b/frontend/app/view/waveai/waveai.tsx @@ -1,7 +1,6 @@ // Copyright 2025, 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 { RpcResponseHelper, WshClient } from "@/app/store/wshclient"; @@ -444,20 +443,145 @@ export class WaveAiModel implements ViewModel { } } +export interface StreamingTextProps { + text: string; + completed?: boolean; + style?: React.CSSProperties; + className?: string; + fontSize?: number; + fixedFontSize?: boolean; +} + +export const StreamingText: React.FC = ({ + text, + completed = false, + style, + className, + fontSize, + fixedFontSize = false, +}) => { + return ( +
+ +
+ ); +}; + const ChatItem = ({ chatItemAtom, model }: ChatItemProps) => { const chatItem = useAtomValue(chatItemAtom); - const { user, text } = chatItem; + const { user, text, id } = chatItem; const fontSize = useAtomValue(model.mergedPresets)?.["ai:fontsize"]; const fixedFontSize = useAtomValue(model.mergedPresets)?.["ai:fixedfontsize"]; + const [editing, setEditing] = useState(false); + const [editText, setEditText] = useState(text); + const [copied, setCopied] = useState(false); + const textAreaRef = useRef(null); + + useEffect(() => { + if (editing && textAreaRef.current) { + textAreaRef.current.focus(); + textAreaRef.current.style.height = "auto"; + textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px`; + } + }, [editing]); + + useEffect(() => { + setEditText(text); + }, [text]); + + const handleTextAreaInput = (e: React.FormEvent) => { + const target = e.target as HTMLTextAreaElement; + target.style.height = "auto"; + target.style.height = `${target.scrollHeight}px`; + setEditText(target.value); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + // submit + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + saveEdit(); + } + // cancel + else if (e.key === "Escape") { + e.preventDefault(); + cancelEditing(); + } + }; + + const copyToClipboard = () => { + if (text) { + navigator.clipboard + .writeText(text) + .then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }) + .catch((err) => { + console.error("Failed to copy text: ", err); + }); + } + }; + + const startEditing = () => { + if (user === "user") { + setEditing(true); + setEditText(text); + } + }; + + const cancelEditing = () => { + setEditing(false); + }; + + const saveEdit = () => { + if (editText.trim() === "") { + return; + } + + setEditing(false); + fireAndForget(async () => { + const history = await model.fetchAiData(); + const msgIndex = history.findIndex((msg) => msg.role === user && msg.content === text); + + if (msgIndex !== -1) { + const updatedHistory = history.slice(0, msgIndex); + await BlockService.SaveWaveAiData(model.blockId, updatedHistory); + await model.populateMessages(); + model.sendMessage(editText, user); + } + }); + }; + + const handleRepeat = () => { + if (user === "user") { + fireAndForget(async () => { + const history = await model.fetchAiData(); + const msgIndex = history.findIndex((msg) => msg.role === user && msg.content === text); + + if (msgIndex !== -1) { + const updatedHistory = history.slice(0, msgIndex); + await BlockService.SaveWaveAiData(model.blockId, updatedHistory); + await model.populateMessages(); + model.sendMessage(text, user); + } + }); + } + }; + + const containerClass = `chat-msg-container ${ + user === "user" ? "user-msg-container" : user === "error" ? "error-msg-container" : "" + }`; + const renderContent = useMemo(() => { if (user == "error") { return ( <> -
-
- -
-
{ fixedFontSizeOverride={fixedFontSize} />
+
+ +
); } if (user == "assistant") { return text ? ( <> -
-
- -
-
- + {chatItem.isUpdating ? ( + + ) : ( + + )} +
+
+
) : ( <> -
- -
); } + + if (editing) { + return ( + <> +
+ +
+
+ +
+
+
+ + + {showModelMenu && ( +
+ {Object.entries(presetMap) + .sort((a, b) => + (a[1]["display:order"] ?? 0) > (b[1]["display:order"] ?? 0) ? 1 : -1 + ) + .map(([id, preset]) => ( +
handleSelectModel(id)}> + {preset["display:name"]} +
+ ))} +
+ Add AI preset... +
+
+ )} +
+ +
+
); } ); @@ -837,14 +1142,6 @@ 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; @@ -858,22 +1155,17 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
-
-
- -
- -
+
); }; From 43d810b44ea186b8bdd009cac5862ccfaca159c4 Mon Sep 17 00:00:00 2001 From: DepsCian Date: Sun, 6 Apr 2025 18:43:00 +0300 Subject: [PATCH 2/3] feat: enhance code block functionality - Implement sending code to terminal --- frontend/app/element/markdown.scss | 31 +++++--- frontend/app/element/markdown.tsx | 73 +++++++++++++++++- frontend/app/element/typingindicator.scss | 2 +- frontend/app/view/waveai/waveai.tsx | 92 ----------------------- 4 files changed, 92 insertions(+), 106 deletions(-) diff --git a/frontend/app/element/markdown.scss b/frontend/app/element/markdown.scss index dee02633e8..60ca3d8b21 100644 --- a/frontend/app/element/markdown.scss +++ b/frontend/app/element/markdown.scss @@ -125,22 +125,31 @@ } .codeblock-actions { - visibility: hidden; - display: flex; position: absolute; - top: 0; - right: 0; + top: 5px; + right: 5px; + display: flex; + gap: 4px; + opacity: 0; + transition: opacity 0.1s ease-in-out; + background-color: rgba(0, 0, 0, 0.2); + backdrop-filter: blur(2px); border-radius: 4px; - backdrop-filter: blur(8px); - margin: 0.143em; - padding: 0.286em; - align-items: center; - justify-content: flex-end; - gap: 0.286em; + padding: 2px 4px; + + .iconbutton { + font-size: 14px; + padding: 3px; + border-radius: 3px; + + &:hover { + background-color: rgba(255, 255, 255, 0.1); + } + } } &:hover .codeblock-actions { - visibility: visible; + opacity: 1; } } diff --git a/frontend/app/element/markdown.tsx b/frontend/app/element/markdown.tsx index 96f2235e82..186784f7d8 100644 --- a/frontend/app/element/markdown.tsx +++ b/frontend/app/element/markdown.tsx @@ -9,7 +9,10 @@ import { resolveSrcSet, transformBlocks, } from "@/app/element/markdown-util"; -import { boundNumber, useAtomValueSafe } from "@/util/util"; +import { ContextMenuModel as contextMenuModel } from "@/app/store/contextmenu"; +import { RpcApi } from "@/app/store/wshclientapi"; +import { TabRpcClient } from "@/app/store/wshrpcutil"; +import { boundNumber, stringToBase64, useAtomValueSafe } from "@/util/util"; import { clsx } from "clsx"; import { Atom } from "jotai"; import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react"; @@ -21,7 +24,7 @@ import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; import rehypeSlug from "rehype-slug"; import RemarkFlexibleToc, { TocItem } from "remark-flexible-toc"; import remarkGfm from "remark-gfm"; -import { openLink } from "../store/global"; +import { atoms, getAllBlockComponentModels, globalStore, openLink } from "../store/global"; import { IconButton } from "./iconbutton"; import "./markdown.scss"; @@ -91,6 +94,64 @@ const CodeBlock = ({ children, onClickExecute }: CodeBlockProps) => { } }; + const handleSendToTerminal = async (e: React.MouseEvent) => { + let textToSend = getTextContent(children); + textToSend = textToSend.replace(/\n$/, ""); + + const allBCMs = getAllBlockComponentModels(); + const terminalBlocks = []; + + for (const bcm of allBCMs) { + if (bcm?.viewModel?.viewType === "term") { + terminalBlocks.push({ + id: (bcm.viewModel as any).blockId || "", + title: `Terminal ${terminalBlocks.length + 1}`, + }); + } + } + + const menuItems: ContextMenuItem[] = terminalBlocks.map((terminal) => ({ + label: terminal.title, + click: () => sendTextToTerminal(terminal.id, textToSend), + })); + + menuItems.push({ type: "separator" }); + menuItems.push({ + label: "Create New Terminal", + click: async () => { + const termBlockDef = { + meta: { + controller: "shell", + view: "term", + }, + }; + try { + const tabId = globalStore.get(atoms.staticTabId); + const oref = await RpcApi.CreateBlockCommand(TabRpcClient, { + tabid: tabId, + blockdef: termBlockDef, + }); + + const blockId = oref.split(":")[1]; + setTimeout(() => sendTextToTerminal(blockId, textToSend), 500); + } catch (error) { + console.error("Failed to create new terminal block:", error); + } + }, + }); + + contextMenuModel.showContextMenu(menuItems, e); + }; + + const sendTextToTerminal = (blockId: string, text: string) => { + const textWithReturn = text + "\n"; + const b64data = stringToBase64(textWithReturn); + RpcApi.ControllerInputCommand(TabRpcClient, { + blockid: blockId, + inputdata64: b64data, + }); + }; + return (
             {children}
@@ -105,6 +166,14 @@ const CodeBlock = ({ children, onClickExecute }: CodeBlockProps) => {
                         }}
                     />
                 )}
+                
             
); diff --git a/frontend/app/element/typingindicator.scss b/frontend/app/element/typingindicator.scss index a8750de07f..8d634cbd02 100644 --- a/frontend/app/element/typingindicator.scss +++ b/frontend/app/element/typingindicator.scss @@ -48,7 +48,7 @@ transform: translateY(0); } 50% { - transform: translateY(-5px); + transform: translateY(-2.5px); opacity: 1; } } diff --git a/frontend/app/view/waveai/waveai.tsx b/frontend/app/view/waveai/waveai.tsx index 3408779264..efa9e81255 100644 --- a/frontend/app/view/waveai/waveai.tsx +++ b/frontend/app/view/waveai/waveai.tsx @@ -190,98 +190,6 @@ export class WaveAiModel implements ViewModel { return opts; }); - this.viewText = atom((get) => { - const viewTextChildren: HeaderElem[] = []; - const aiOpts = get(this.aiOpts); - const presets = get(this.presetMap); - const presetKey = get(this.presetKey); - const presetName = presets[presetKey]?.["display:name"] ?? ""; - const isCloud = isBlank(aiOpts.apitoken) && isBlank(aiOpts.baseurl); - - // Handle known API providers - switch (aiOpts?.apitype) { - case "anthropic": - viewTextChildren.push({ - elemtype: "iconbutton", - icon: "globe", - title: `Using Remote Anthropic API (${aiOpts.model})`, - noAction: true, - }); - break; - case "perplexity": - viewTextChildren.push({ - elemtype: "iconbutton", - icon: "globe", - title: `Using Remote Perplexity API (${aiOpts.model})`, - noAction: true, - }); - break; - default: - if (isCloud) { - viewTextChildren.push({ - elemtype: "iconbutton", - icon: "cloud", - title: "Using Wave's AI Proxy (gpt-4o-mini)", - noAction: true, - }); - } else { - const baseUrl = aiOpts.baseurl ?? "OpenAI Default Endpoint"; - const modelName = aiOpts.model; - if (baseUrl.startsWith("http://localhost") || baseUrl.startsWith("http://127.0.0.1")) { - viewTextChildren.push({ - elemtype: "iconbutton", - icon: "location-dot", - title: `Using Local Model @ ${baseUrl} (${modelName})`, - noAction: true, - }); - } else { - viewTextChildren.push({ - elemtype: "iconbutton", - icon: "globe", - title: `Using Remote Model @ ${baseUrl} (${modelName})`, - noAction: true, - }); - } - } - } - - const dropdownItems = Object.entries(presets) - .sort((a, b) => ((a[1]["display:order"] ?? 0) > (b[1]["display:order"] ?? 0) ? 1 : -1)) - .map( - (preset) => - ({ - label: preset[1]["display:name"], - onClick: () => - fireAndForget(() => - ObjectService.UpdateObjectMeta(WOS.makeORef("block", this.blockId), { - "ai:preset": preset[0], - }) - ), - }) as MenuItem - ); - dropdownItems.push({ - label: "Add AI preset...", - onClick: () => { - fireAndForget(async () => { - const path = `${getApi().getConfigDir()}/presets/ai.json`; - const blockDef: BlockDef = { - meta: { - view: "preview", - file: path, - }, - }; - await createBlock(blockDef, false, true); - }); - }, - }); - viewTextChildren.push({ - elemtype: "menubutton", - text: presetName, - title: "Select AI Configuration", - items: dropdownItems, - }); - return viewTextChildren; - }); this.endIconButtons = atom((_) => { let clearButton: IconButtonDecl = { elemtype: "iconbutton", From 2042b8228eaae1a1fe1c231d0b0206a03d539dee Mon Sep 17 00:00:00 2001 From: DepsCian Date: Mon, 7 Apr 2025 18:54:55 +0300 Subject: [PATCH 3/3] refactor: minor enhancements for Wave AI --- frontend/app/view/waveai/waveai.tsx | 56 ++++++----------------------- 1 file changed, 10 insertions(+), 46 deletions(-) diff --git a/frontend/app/view/waveai/waveai.tsx b/frontend/app/view/waveai/waveai.tsx index efa9e81255..e68fad12d0 100644 --- a/frontend/app/view/waveai/waveai.tsx +++ b/frontend/app/view/waveai/waveai.tsx @@ -82,6 +82,7 @@ export class WaveAiModel implements ViewModel { simulateAssistantResponseAtom: WritableAtom>; textAreaRef: React.RefObject; locked: PrimitiveAtom; + noPadding: PrimitiveAtom; cancel: boolean; aiWshClient: AiWshClient; @@ -95,6 +96,7 @@ export class WaveAiModel implements ViewModel { this.blockAtom = WOS.getWaveObjectAtom(`block:${blockId}`); this.viewIcon = atom("sparkles"); this.viewName = atom("Wave AI"); + this.noPadding = atom(true); this.messagesAtom = atom([]); this.messagesSplitAtom = splitAtom(this.messagesAtom); this.latestMessageAtom = atom((get) => get(this.messagesAtom).slice(-1)[0]); @@ -351,35 +353,6 @@ export class WaveAiModel implements ViewModel { } } -export interface StreamingTextProps { - text: string; - completed?: boolean; - style?: React.CSSProperties; - className?: string; - fontSize?: number; - fixedFontSize?: boolean; -} - -export const StreamingText: React.FC = ({ - text, - completed = false, - style, - className, - fontSize, - fixedFontSize = false, -}) => { - return ( -
- -
- ); -}; - const ChatItem = ({ chatItemAtom, model }: ChatItemProps) => { const chatItem = useAtomValue(chatItemAtom); const { user, text, id } = chatItem; @@ -514,21 +487,12 @@ const ChatItem = ({ chatItemAtom, model }: ChatItemProps) => { return text ? ( <>
- {chatItem.isUpdating ? ( - - ) : ( - - )} +