Skip to content

Conversation

DepsCian
Copy link

@DepsCian DepsCian commented Apr 7, 2025

DepsCian added 3 commits April 7, 2025 19:10
- 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
- Implement sending code to terminal
@CLAassistant
Copy link

CLAassistant commented Apr 7, 2025

CLA assistant check
All committers have signed the CLA.

Copy link
Contributor

coderabbitai bot commented Apr 7, 2025

Walkthrough

The PR updates multiple frontend components: markdown styles for codeblock actions now use opacity transitions, repositioning, and new icon button styles; the CodeBlock component adds methods and UI to send code to terminal blocks via a context menu and RPC calls; typing indicator SCSS/TSX were reworked into a typed, exportable React component with a bubble/dot animation and style prop; WaveAI chat styles and TSX were reorganized—message layout, model/preset selector, per-message editing/copy actions, and WaveAiModel state were modified (including removal of viewText and addition of noPadding).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
frontend/app/element/markdown.tsx (1)

97-144: Implemented terminal text sending with context menu

The handleSendToTerminal function effectively:

  1. Collects text from code blocks
  2. Finds existing terminal blocks
  3. Creates a well-structured context menu
  4. Includes option to create a new terminal

The implementation follows good patterns for context menu creation, but the function is quite lengthy and could be refactored into smaller, more focused functions.

Consider breaking this large function into smaller helper functions like getAvailableTerminals() and buildTerminalMenu() for better maintainability.

frontend/app/view/waveai/waveai.tsx (1)

358-584: Smooth and well-structured editing logic.
Your new local states (editing, editText, copied) and respective handlers (focus, copy to clipboard, and text area resizing) are implemented cleanly. The approach of re-prompting after editing (lines 423-440) looks intentional. However, note that each re-submit can lead to a new backend call, potentially consuming additional tokens or resources.

If the cost or resource usage becomes an issue, consider an alternative strategy, such as partial in-memory edits without triggering a re-prompt until the user explicitly requests it.

frontend/app/view/waveai/waveai.scss (1)

47-60: Refined chat message padding and spacing.
The added padding around messages (e.g., padding: 14px 16px 8px) should enhance readability. Be mindful of potential overlap if new action buttons or other elements are added in the future.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7ecd9e and 2042b82.

📒 Files selected for processing (6)
  • frontend/app/element/markdown.scss (1 hunks)
  • frontend/app/element/markdown.tsx (4 hunks)
  • frontend/app/element/typingindicator.scss (1 hunks)
  • frontend/app/element/typingindicator.tsx (1 hunks)
  • frontend/app/view/waveai/waveai.scss (3 hunks)
  • frontend/app/view/waveai/waveai.tsx (9 hunks)
🧰 Additional context used
🧬 Code Definitions (2)
frontend/app/element/markdown.tsx (4)
frontend/app/store/global.ts (3)
  • getAllBlockComponentModels (771-771)
  • globalStore (783-783)
  • atoms (762-762)
frontend/app/store/wshclientapi.ts (1)
  • RpcApi (492-492)
frontend/util/util.ts (1)
  • stringToBase64 (401-401)
frontend/app/element/iconbutton.tsx (1)
  • IconButton (12-34)
frontend/app/view/waveai/waveai.tsx (6)
frontend/app/view/webview/webview.tsx (1)
  • handleKeyDown (310-321)
frontend/util/util.ts (1)
  • fireAndForget (388-388)
frontend/app/store/services.ts (2)
  • BlockService (23-23)
  • ObjectService (88-88)
frontend/app/element/typingindicator.tsx (1)
  • TypingIndicator (12-22)
frontend/app/store/global.ts (1)
  • getApi (772-772)
pkg/waveobj/wtype.go (1)
  • BlockDef (241-244)
🔇 Additional comments (23)
frontend/app/element/typingindicator.tsx (1)

4-4: Component refactored with improved TypeScript typing

The TypingIndicator component has been properly refactored to use modern React patterns and TypeScript features:

  1. Using a proper exported interface instead of a type
  2. Adding style prop support for inline styling customization
  3. Using React.FC typing for better type checking
  4. Improved HTML structure with semantic class names that match the CSS

These changes enhance component reusability and maintainability.

Also applies to: 7-10, 12-19

frontend/app/element/markdown.scss (3)

129-139: Improved code block actions with modern UI effects

The codeblock-actions styling has been enhanced with:

  • Better positioning with 5px offsets from edges
  • Smooth opacity transitions instead of abrupt visibility changes
  • Semi-transparent background with blur effect
  • Consistent spacing with gap property

These changes create a more polished and modern UI component.


140-148: Added hover effects for better interaction feedback

Well-implemented styling for icon buttons with appropriate sizing and hover state feedback. The 14px font size and 3px padding create a compact but usable button, while the hover effect provides clear user feedback.


152-153: Proper transition handling on hover

The hover state change from opacity:0 to opacity:1 pairs well with the transition definition, creating a smooth fade-in effect.

frontend/app/element/markdown.tsx (3)

12-15: Added necessary imports for new terminal functionality

The new imports support the added "Send to Terminal" feature, correctly importing the required dependencies for context menus, RPC API communication, and terminal block management.

Also applies to: 27-27


146-153: Well-implemented terminal text sending function

The sendTextToTerminal function correctly:

  1. Appends a newline to simulate Enter key press
  2. Base64 encodes the text for proper transmission
  3. Uses the appropriate RPC API call to send the input

This implementation ensures reliable text transfer to terminal blocks.


169-176: Added user-friendly terminal button to code blocks

Great addition of a terminal icon button that allows users to send code block text to terminal instances. The button uses consistent styling with other action buttons and provides a helpful tooltip.

frontend/app/element/typingindicator.scss (4)

4-9: Redesigned typing indicator with flexible layout

The new typing indicator uses an inline-flex layout with appropriate spacing, making it more flexible for different UI contexts while maintaining consistent alignment.


11-19: Added stylish bubble container for typing indicator

The bubble design with semi-transparent background derived from the accent color creates a more polished and visually appealing container for the typing dots. The rounded corners and padding provide good visual separation.


21-42: Improved animation timing for typing dots

The dots now have consistent size and styling with a staggered animation effect that creates a natural typing rhythm. Using the accent color with opacity ensures the indicator matches the overall UI theme.


45-54: Enhanced animation with vertical movement

The typing-animation keyframes create a smooth up-and-down bouncing effect that's more visually interesting than the previous animation. The combination of position change and opacity adjustment creates a natural-looking typing indicator.

frontend/app/view/waveai/waveai.tsx (4)

85-99: Consider confirming usage or removing noPadding.
You introduced the noPadding atom at line 85 and set it to true at line 99, but there doesn't appear to be a direct usage within the file. It may be a placeholder for future styling. If it's unused, consider removing it or add references to ensure it's functional.

Could you verify whether other files in the project reference noPadding? If not, it might be safe to remove this property.


700-730: New onButtonPress and locked props.
The extension of ChatInputProps and integration of these props offers clearer separation of concerns for the chat button state. This approach is straightforward and makes the button logic more transparent.


760-794: Model menu handling and addition logic.
Your code for toggling the preset menu, selecting a model, and adding a new preset is cohesive. The external click detection in lines 719-730 is a common pattern and appears correct.


811-859: Responsive input container and usage of locked.
The updated markup in the ChatInput component and how it's placed in the final WaveAi layout look solid. The submit button changing icons based on locked neatly communicates the ongoing or stoppable operation.

Also applies to: 1030-1040

frontend/app/view/waveai/waveai.scss (8)

29-46: Improved container layout and visual separators.
You switched to a column layout (flex-direction: column) and added a bottom border to separate messages. This provides a clearer distinction between messages. Good move.


75-79: Assistant message code blocks styling.
The background color and border radius around <pre> improves the distinction for code snippets. The alpha-based color usage is consistent with the rest of your theme.

Also applies to: 82-105


108-109: User and edit message classes.
The .chat-msg-user and .chat-msg-edit classes correctly apply different styling needs. The edit-input focusing on transparent background with no border is a neat approach that feels minimalistic.

Also applies to: 111-126


145-147: Enhanced typing indicator spacing.
Increased padding around the typing indicator (lines 145-147) improves visual alignment. Good tiny UX detail.


149-191: Action buttons layout with .msg-actions.
The flex-based approach for right-alignment of the action buttons is tidy, and the hover effect that changes to var(--accent-color) is a thoughtful UX touch.


197-206: .waveai-input-container refactor.
Renaming from .waveai-controls and reorganizing into a column layout clarifies the architecture. The slightly translucent background and border signals a distinct interactive area, aligning well with the chat theme.

Also applies to: 209-227


229-300: Preset selector UI enhancements.
The new .preset-selector block with a dropdown (.model-menu) is concise, ensuring intuitive user discovery of model options. The alpha-based hover effect on .model-menu-item maintains stylistic consistency.


302-338: Streamlined submit button styling.
Switching to a circular design with color-coded feedback for normal vs. stop states is modern and visually consistent. The new transitions in lines 313 and 322 ensure a smooth hover experience.

Comment on lines +129 to +139
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fixed potential issue with hardcoded timeout

The 500ms hardcoded timeout when creating a new terminal may not always be sufficient for terminal initialization, which could lead to missed text input.

Consider implementing a callback or event-based approach instead of a timeout to ensure the terminal is ready before sending text:

- setTimeout(() => sendTextToTerminal(blockId, textToSend), 500);
+ // Use an event listener or callback to detect when terminal is ready
+ const checkTerminalReady = () => {
+   const terminal = getAllBlockComponentModels().find(
+     bcm => bcm?.viewModel?.blockId === blockId && bcm?.viewModel?.viewType === "term"
+   );
+   if (terminal && terminal.isReady) {
+     sendTextToTerminal(blockId, textToSend);
+   } else {
+     setTimeout(checkTerminalReady, 100);
+   }
+ };
+ checkTerminalReady();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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);
}
const tabId = globalStore.get(atoms.staticTabId);
const oref = await RpcApi.CreateBlockCommand(TabRpcClient, {
tabid: tabId,
blockdef: termBlockDef,
});
const blockId = oref.split(":")[1];
// Use an event listener or callback to detect when terminal is ready
const checkTerminalReady = () => {
const terminal = getAllBlockComponentModels().find(
bcm => bcm?.viewModel?.blockId === blockId && bcm?.viewModel?.viewType === "term"
);
if (terminal && terminal.isReady) {
sendTextToTerminal(blockId, textToSend);
} else {
setTimeout(checkTerminalReady, 100);
}
};
checkTerminalReady();
} catch (error) {
console.error("Failed to create new terminal block:", error);
}

@DepsCian DepsCian changed the title Wave AI UI Remake refactor: enhance AI block UI/UX Aug 29, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
frontend/app/element/typingindicator.tsx (2)

9-12: Broaden props to standard div attributes for extensibility/a11y.

Let consumers pass aria-, data-, id, onClick, etc., without changing this component again.

Apply:

-export interface TypingIndicatorProps {
-    style?: React.CSSProperties;
-    className?: string;
-}
+export interface TypingIndicatorProps extends React.HTMLAttributes<HTMLDivElement> {}

14-21: Use clsx and wire through rest props; add basic a11y.

  • Currently clsx is imported but unused; use it to compose classes safely.
  • Add role="status" and aria-live="polite" for screen reader announcements.
  • Hide decorative dots from AT.

Apply:

-export const TypingIndicator: React.FC<TypingIndicatorProps> = ({ style, className }) => {
+export const TypingIndicator: React.FC<TypingIndicatorProps> = ({ style, className, ...rest }) => {
   return (
-        <div className={`typing-indicator ${className || ""}`} style={style}>
-            <div className="typing-indicator-bubble">
-                <div className="typing-indicator-dot"></div>
-                <div className="typing-indicator-dot"></div>
-                <div className="typing-indicator-dot"></div>
+        <div
+            className={clsx("typing-indicator", className)}
+            style={style}
+            role="status"
+            aria-live="polite"
+            aria-label="Assistant is typing"
+            {...rest}
+        >
+            <div className="typing-indicator-bubble" aria-hidden="true">
+                <div className="typing-indicator-dot" aria-hidden="true"></div>
+                <div className="typing-indicator-dot" aria-hidden="true"></div>
+                <div className="typing-indicator-dot" aria-hidden="true"></div>
             </div>
         </div>
   );
 };
frontend/app/view/waveai/waveai.tsx (4)

399-411: Clipboard UX: clear timeout on unmount and guard API availability.

Avoid state updates after unmount; add a small cleanup and feature detection.

Apply this diff:

@@
-    const [copied, setCopied] = useState(false);
+    const [copied, setCopied] = useState(false);
+    const copyTimerRef = useRef<number | null>(null);
@@
-    useEffect(() => {
+    useEffect(() => {
         setEditText(text);
     }, [text]);
+    useEffect(() => {
+        return () => {
+            if (copyTimerRef.current != null) {
+                clearTimeout(copyTimerRef.current);
+            }
+        };
+    }, []);
@@
-    const copyToClipboard = () => {
-        if (text) {
-            navigator.clipboard
-                .writeText(text)
+    const copyToClipboard = () => {
+        if (text && navigator.clipboard?.writeText) {
+            navigator.clipboard
+                .writeText(text)
                 .then(() => {
                     setCopied(true);
-                    setTimeout(() => setCopied(false), 2000);
+                    copyTimerRef.current = window.setTimeout(() => setCopied(false), 2000);
                 })
                 .catch((err) => {
                     console.error("Failed to copy text: ", err);
                 });
         }
     };

Also applies to: 362-366, 375-378


838-841: Comparator should handle equality deterministically.

Current comparator returns -1 for equals; use numeric diff and name tiebreaker.

Apply this diff:

-                                    .sort((a, b) =>
-                                        (a[1]["display:order"] ?? 0) > (b[1]["display:order"] ?? 0) ? 1 : -1
-                                    )
+                                    .sort((a, b) => {
+                                        const ao = a[1]["display:order"] ?? 0;
+                                        const bo = b[1]["display:order"] ?? 0;
+                                        return ao - bo || String(a[1]["display:name"] ?? "").localeCompare(String(b[1]["display:name"] ?? ""));
+                                    })

853-859: Button semantics & a11y.

Add type="button" to avoid implicit submit behavior and aria-label for screen readers.

Apply this diff:

-                    <button
+                    <button
                         className={`waveai-submit-button ${locked ? "stop" : ""}`}
                         onClick={onButtonPress}
                         disabled={!locked && value.trim() === ""}
+                        type="button"
+                        aria-label={buttonTitle}
                     >
                         <i className={buttonIcon} title={buttonTitle} />
                     </button>

281-289: Streaming cancel should propagate to backend.

Breaking the for-await loop may not abort the RPC stream server-side. Consider adding an AbortController (or RPC cancel) and wiring model.cancel to signal it so the backend stops work and billing.

I can sketch an AbortController-based variant if StreamWaveAiCommand supports a signal option.

Also applies to: 1018-1025

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2042b82 and 9798c7d.

📒 Files selected for processing (2)
  • frontend/app/element/typingindicator.tsx (1 hunks)
  • frontend/app/view/waveai/waveai.tsx (10 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/app/view/waveai/waveai.tsx (4)
frontend/util/util.ts (2)
  • fireAndForget (411-411)
  • makeIconClass (421-421)
frontend/app/store/services.ts (2)
  • BlockService (23-23)
  • ObjectService (88-88)
frontend/app/element/typingindicator.tsx (1)
  • TypingIndicator (14-24)
frontend/app/store/global.ts (3)
  • WOS (809-809)
  • getApi (772-772)
  • createBlock (766-766)
🔇 Additional comments (3)
frontend/app/element/typingindicator.tsx (2)

4-4: Prefer a type-only React import
Avoid a runtime import if you’re only using React types (React.FC, React.CSSProperties). Ensure your root tsconfig.json is configured for the new JSX transform ("jsx": "react-jsx" or "react-jsxdev") before applying this change.

-import React from "react";
+import type React from "react";

17-21: SCSS class names align: .typing-indicator-bubble and .typing-indicator-dot are defined in typingindicator.scss.

frontend/app/view/waveai/waveai.tsx (1)

85-85: Verify noPadding is actually respected by the view host.

You added model.noPadding = true but I don’t see it being read/used by the container. Please confirm the ViewModel contract consumes this flag; otherwise it’s dead state.

Also applies to: 99-100

Comment on lines 360 to +366
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<HTMLTextAreaElement>(null);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid index keys; use message id for stable reconciliation.

ChatItem holds local UI state (editing/copied). Using key={idx} risks state bleed/shuffling on insert/remove. Use the message id as the key.

Apply this diff:

-                    {splitMessages.map((chitem, idx) => (
-                        <ChatItem key={idx} chatItemAtom={chitem} model={model} />
-                    ))}
+                    {splitMessages.map((chitem) => {
+                        const { id } = globalStore.get(chitem) as ChatMessageType;
+                        return <ChatItem key={id} chatItemAtom={chitem} model={model} />;
+                    })}

Also applies to: 685-687

🤖 Prompt for AI Agents
In frontend/app/view/waveai/waveai.tsx around lines 360-366 (and likewise at
~685-687), ChatItem instances are being keyed by array index which causes local
UI state (editing/copied) to bleed when items are inserted/removed; replace
key={idx} with a stable identifier such as key={message.id} (or message?.id)
when rendering the list, ensure message.id is present and unique (or generate a
stable id if not), and update any typing or null checks accordingly so the
component uses a stable key for reconciliation.

Comment on lines +413 to +418
const startEditing = () => {
if (user === "user") {
setEditing(true);
setEditText(text);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Disable edit/repeat actions while a request is in-flight.

Prevents truncation races with the streaming loop and inconsistent history while locked.

Apply this diff:

@@
-    const [editing, setEditing] = useState(false);
+    const [editing, setEditing] = useState(false);
     const [editText, setEditText] = useState(text);
     const [copied, setCopied] = useState(false);
     const textAreaRef = useRef<HTMLTextAreaElement>(null);
+    const locked = useAtomValue(model.locked);
@@
-    const startEditing = () => {
-        if (user === "user") {
+    const startEditing = () => {
+        if (user === "user" && !locked) {
             setEditing(true);
             setEditText(text);
         }
     };
@@
-                        <button
+                        <button
                             className="msg-action-btn repeat-btn"
                             onClick={handleRepeat}
                             title="Repeat (deletes this and all following messages)"
+                            disabled={locked}
                         >
@@
-                        <button className="msg-action-btn edit-btn" onClick={cancelEditing} title="Cancel">
+                        <button className="msg-action-btn edit-btn" onClick={cancelEditing} title="Cancel">
@@
-                        <button className="msg-action-btn copy-btn" onClick={saveEdit} title="Save">
+                        <button className="msg-action-btn copy-btn" onClick={saveEdit} title="Save" disabled={locked}>
@@
-                    <button
+                    <button
                         className="msg-action-btn repeat-btn"
                         onClick={handleRepeat}
                         title="Repeat (deletes this and all following messages)"
+                        disabled={locked}
                     >
@@
-                    <button className="msg-action-btn edit-btn" onClick={startEditing} title="Edit">
+                    <button className="msg-action-btn edit-btn" onClick={startEditing} title="Edit" disabled={locked}>

Also applies to: 515-547, 562-580

🤖 Prompt for AI Agents
In frontend/app/view/waveai/waveai.tsx around lines 413-418 (and also apply the
same change to 515-547 and 562-580), the edit/repeat actions are allowed even
while a request/stream is in-flight which causes truncation races and
inconsistent history; add a guard using the existing in-flight/streaming state
(e.g. isRequestInFlight, isStreaming, or locked flag used elsewhere) so these
handlers return early when a request is active, and ensure UI controls are also
disabled while in-flight; update startEditing (and the analogous repeat/edit
handlers in the other ranges) to check the flag before calling
setEditing/setEditText (or performing repeat) so edits/repeats are only possible
when no request is outstanding.

Comment on lines +424 to +441
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);
}
});
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Editing/Repeat may target the wrong message when duplicates exist.

Both flows locate the message by role+content using findIndex, which can hit an earlier duplicate. Prefer the most recent match.

Apply this diff to search from the end in both places:

-            const msgIndex = history.findIndex((msg) => msg.role === user && msg.content === text);
+            const msgIndex = (() => {
+                for (let i = history.length - 1; i >= 0; i--) {
+                    if (history[i].role === user && history[i].content === text) return i;
+                }
+                return -1;
+            })();

Also applies to: 443-457

🤖 Prompt for AI Agents
In frontend/app/view/waveai/waveai.tsx around lines 424-441 (and similarly for
443-457), the code finds the target message using history.findIndex(msg =>
msg.role === user && msg.content === text) which picks the first match and can
target an earlier duplicate; change the lookup to find the most recent match by
using a reverse search (e.g., history.findLastIndex with the same predicate or
iterate from the end) so you get the last occurrence, then slice/update based on
that index in both edit and repeat flows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants