Skip to content

Fix: #33 Refactor AIChatPanel to Separate UI and Tool Logic#501

Open
Vishal-Prajapati17 wants to merge 2 commits into
piyushdotcomm:mainfrom
Vishal-Prajapati17:refactor/ai-chat-panel-separation
Open

Fix: #33 Refactor AIChatPanel to Separate UI and Tool Logic#501
Vishal-Prajapati17 wants to merge 2 commits into
piyushdotcomm:mainfrom
Vishal-Prajapati17:refactor/ai-chat-panel-separation

Conversation

@Vishal-Prajapati17

@Vishal-Prajapati17 Vishal-Prajapati17 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Closes #33

Summary

This PR refactors the AI chat module by separating tool execution logic from UI rendering responsibilities.

Previously, modules/playground/components/ai-chat-panel.tsx contained both chat interface rendering and AI tool execution logic in a single large component. This made the file difficult to maintain, extend, and reason about as functionality continued to grow.

This refactor introduces dedicated abstractions for tool execution and message rendering while preserving existing behavior and functionality.

Changes Made

Extracted AI Tool Execution Logic

Created:

modules/playground/hooks/useAITools.ts

Moved AI tool execution responsibilities into a dedicated custom hook.

Responsibilities moved include:

  • Processing AI tool calls
  • Executing supported file operations
  • Managing tool execution lifecycle
  • Preventing duplicate tool execution
  • Handling tool-related side effects
  • Returning tool results back to the chat flow

This separates business logic from presentation logic and makes future tool additions easier to maintain.


Extracted Chat Message Rendering

Created:

modules/playground/components/chat-message.tsx

Moved message rendering into a dedicated reusable component.

Responsibilities include:

  • Rendering user messages
  • Rendering assistant messages
  • Markdown rendering
  • Tool result display
  • Message bubble presentation
  • Role-based styling

This keeps rendering concerns isolated from application logic.


Simplified AIChatPanel

Refactored:

modules/playground/components/ai-chat-panel.tsx

The component now primarily acts as the chat layout wrapper and orchestration layer.

Current responsibilities include:

  • Chat layout structure
  • Input handling
  • Hook integration
  • Rendering message components
  • Managing chat UI state

Most execution and rendering complexity has been delegated to dedicated modules.

Benefits

  • Improved separation of concerns
  • Reduced component complexity
  • Better maintainability
  • Easier debugging
  • Improved code readability
  • Better scalability for future AI tool integrations
  • Reusable message rendering component

Verification Performed

Build Verification

Executed:

npm run build

Result:

  • Build completed successfully
  • No build issues introduced by this refactor

Lint Verification

Executed:

npm run lint

Result:

  • No new lint warnings or errors introduced by this PR
  • Existing repository warnings remain unchanged

Note:

The repository currently contains several pre-existing ESLint/TypeScript warnings in unrelated modules. These warnings were present before this refactor and are outside the scope of this issue.

This PR does not modify the affected files and does not introduce any additional warnings.

Test Verification

Executed:

npx vitest run

Result:

Test Files: 16 passed
Tests: 112 passed

All existing tests pass successfully after the refactor.

Notes

This PR focuses exclusively on architectural refactoring and responsibility separation.

No user-facing functionality was intentionally changed.

The objective was to improve maintainability and code organization while preserving the existing behavior of the AI chat system.

Screenshots

Application Running Successfully

Screenshot (23)

Vitest Results

Screenshot (22)

Summary by CodeRabbit

  • New Features
    • AI chat panel now displays AI tool execution status with visual completion indicators
    • AI-assisted file operations support (read, edit, and delete functionality)
    • Added streaming indicator for AI thinking during responses
    • Enhanced message rendering with tool operation badges

Signed-off-by: Vishal-Prajapati17 <vishalprajapa15@gmail.com>
Signed-off-by: Vishal-Prajapati17 <vishalprajapa15@gmail.com>
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@github-actions github-actions Bot added the help wanted Extra attention is needed label Jun 19, 2026
@github-actions

Copy link
Copy Markdown

👋 Thanks for opening a PR, @Vishal-Prajapati17!

Your PR has entered the 🚦 PR Review Pipeline.

Standard PR detected — your PR will follow the standard review pipeline.


What happens next

Stage Reviewer Checks
Stage 1 — Automated Validation 🤖 Bot DCO · Format · AI/Slop · Duplicate
Stage 2 — Human Review 👥 Maintainer Code + Quality Review
Stage 3 — PA / Maintainer Review 🔑 Project Admin Final Merge Decision

A pipeline status comment will appear below and update automatically as your PR progresses.


While you wait

  • Sign all commits (git commit -s)
  • Link your issue (Closes #123)
  • Use a feature branch (not main)
  • Avoid unrelated changes

This comment is posted only once.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR refactors AIChatPanel by extracting AI tool execution (read/edit/delete file operations) into a new useAITools hook and individual message rendering into a new ChatMessage component. AIChatPanel is updated to delegate to both, removing ~257 lines of inline logic.

Changes

AIChatPanel Refactor

Layer / File(s) Summary
useAITools hook: contracts and tool execution logic
modules/playground/hooks/useAITools.ts
Defines UseAIToolsProps and internal argument shapes. Implements a ref-backed Set for deduplication, a hasUnresolvedTools callback that inspects the latest assistant message's tool parts, and a useEffect that dispatches read_file, edit_file, edit_multiple_files, and delete_file operations against templateData and openFiles, persists via saveTemplateData, and calls addToolResult.
ChatMessage component
modules/playground/components/chat-message.tsx
Introduces MessagePart, ExtendedMessage, and ChatMessageProps interfaces. Extracts text content from parts (falling back to message.content) and collects tool-* parts. Renders user bubbles, assistant avatar and text bubbles, tool-invocation badges with derived filename and done/loading indicators, and a streaming "Thinking…" indicator.
AIChatPanel integration
modules/playground/components/ai-chat-panel.tsx
Adds JSDoc header, updates imports, introduces FileItem interface, adds inputValue state and useChat onError handler. Converts openFiles to FileItem[] via useMemo, wires useAITools with state setters, blocks sendMessage when hasUnresolvedTools() is true, replaces inline message/tool rendering with mapped ChatMessage components, and adds Enter-to-send handling.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AIChatPanel
  participant useChat
  participant useAITools
  participant ChatMessage

  User->>AIChatPanel: types message, presses Enter
  AIChatPanel->>useAITools: hasUnresolvedTools()?
  useAITools-->>AIChatPanel: false → allow send
  AIChatPanel->>useChat: append(message)
  useChat-->>AIChatPanel: messages updated (assistant reply with tool-* parts)
  AIChatPanel->>useAITools: messages change triggers useEffect
  useAITools->>useAITools: execute read_file / edit_file / delete_file on templateData
  useAITools->>useChat: addToolResult(toolCallId, output)
  AIChatPanel->>ChatMessage: render each message with isLoading
  ChatMessage-->>User: displays text bubble, tool badges, Thinking... indicator
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 Hop hop, the panel grew too wide,
So I carved the tools to live inside
A hook that reads and edits with care,
While ChatMessage renders bubbles with flair.
The panel now stays slim and bright —
One warren, three burrows, everything right! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main refactoring objective: separating UI and tool logic in the AIChatPanel component.
Description check ✅ Passed The PR description is comprehensive and follows the template structure with clear sections on what changed, why, type of change (Refactor), verification steps, and test results.
Linked Issues check ✅ Passed All objectives from issue #33 are fully addressed: tool execution logic extracted to useAITools hook, message rendering extracted to ChatMessage component, and AIChatPanel simplified to a layout wrapper.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the refactoring objectives from issue #33 with no extraneous modifications beyond the stated scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (4)
modules/playground/components/ai-chat-panel.tsx (3)

54-73: 💤 Low value

FileItem interface duplicates the type in useAITools.ts.

This interface mirrors the inline type in UseAIToolsProps.openFiles. Consider exporting a shared type from useAITools.ts or a common types module.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/ai-chat-panel.tsx` around lines 54 - 73, The
FileItem interface defined in the ai-chat-panel component duplicates a type
already defined in useAITools.ts, creating maintenance issues. Export the
FileItem type definition from useAITools.ts (or create a shared types module if
appropriate) and remove the duplicate interface definition from the
AIChatPanelProps section in ai-chat-panel.tsx, then import and use the shared
type instead.

229-243: ⚡ Quick win

Type cast and duplicate loading indicator.

Line 232 uses as any due to type mismatch between useChat message type and ExtendedMessage. This is related to the duplicated interface definitions.

Lines 237-242 render a "Thinking..." indicator that may duplicate the one in ChatMessage (lines 103-108). If both render, users see duplicate spinners.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/ai-chat-panel.tsx` around lines 229 - 243,
Remove the `as any` type cast on the message prop in the ChatMessage component
and ensure the message type from useChat is properly aligned with the
ExtendedMessage interface by consolidating any duplicate interface definitions.
Additionally, the "Thinking..." loading indicator rendered in the conditional
block (checking isLoading and last message role) duplicates the spinner already
rendered inside the ChatMessage component itself, so remove this duplicate
conditional block and rely solely on the ChatMessage component to handle
displaying its own loading state.

129-142: ⚡ Quick win

Type assertion (as any) bypasses type safety.

Line 138 uses as any to bridge the type mismatch between useAITools output and useFileExplorer input. This suggests the interfaces aren't properly aligned. Aligning the shared FileItem/OpenFile types would eliminate this escape hatch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/ai-chat-panel.tsx` around lines 129 - 142, The
setOpenFiles callback in the useAITools hook uses a type assertion (as any) to
handle a type mismatch between the FileItem type used by useAITools and the
format expected by useFileExplorer. Instead of using the as any escape hatch,
align the FileItem and OpenFile type definitions between the useAITools and
useFileExplorer hooks so they share a common interface, then remove the type
assertion and replace it with proper type-safe conversion if needed.
modules/playground/hooks/useAITools.ts (1)

27-53: ⚡ Quick win

Extract shared types to a common module.

MessagePart and ExtendedMessage interfaces are duplicated identically in chat-message.tsx. Additionally, OpenFile (lines 46-53) is defined but never used—the hook uses an inline type in UseAIToolsProps instead.

Consider creating a shared types file (e.g., modules/playground/types/chat.ts) to reduce duplication and ensure consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/hooks/useAITools.ts` around lines 27 - 53, Create a new
shared types file at modules/playground/types/chat.ts and move the MessagePart
and ExtendedMessage interfaces from useAITools.ts to this new file. Since
OpenFile is defined in useAITools.ts but never used (the hook uses an inline
type in UseAIToolsProps instead), either move it to the shared file for
consistency or remove it entirely. Then update the imports in useAITools.ts and
chat-message.tsx to reference these types from the new shared module instead of
duplicating the interface definitions in both files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modules/playground/components/chat-message.tsx`:
- Around line 84-99: The toolParts.map function in the return statement uses
ti.toolCallId as the React key, but ti.toolCallId can be undefined according to
the interface definition, which causes React reconciliation issues. Modify the
map function to capture the array index as a parameter and update the key prop
on the div element to use a fallback value such that if ti.toolCallId is
undefined, it falls back to the array index instead.
- Around line 103-108: The ChatMessage component contains redundant
"Thinking..." indicator logic that duplicates what AIChatPanel already handles.
Remove the entire conditional block that checks `isLoading && message.role !==
"assistant"` and renders the Loader2 spinner with the "Thinking..." span, since
AIChatPanel already displays this indicator at the conversation list level.
Removing this will prevent duplicate loading indicators from appearing for all
user messages simultaneously.

In `@modules/playground/hooks/useAITools.ts`:
- Around line 186-214: Fix two issues in the edit_multiple_files block: First,
replace the loose endsWith path matching on line 201 with a strict path
comparison that properly normalizes both the change.path and the constructed
fullName to ensure exact matches and prevent updating unintended files. Second,
move the toast.success and result assignment inside the saveTemplateData promise
chain so they only execute after the save succeeds, rather than showing success
immediately and silently catching errors; use then and catch handlers to
properly handle both success and failure cases from saveTemplateData.
- Around line 215-236: The delete_file handler has two issues: the path matching
on line 229 uses loose endsWith comparison which could incorrectly close wrong
files, and the success toast on line 233 is shown before confirming the save
operation succeeds (saveTemplateData on line 234 could fail). Fix the path
matching logic in the openFiles filter to use exact path comparison instead of
endsWith, and move the toast.success call to execute only after saveTemplateData
successfully completes, or handle the save error appropriately before showing
the success message.
- Around line 168-184: The path matching using path.endsWith(fullName) is too
loose and will incorrectly match files in different directories with the same
filename. Replace this comparison with a more robust path matching approach that
compares normalized full paths to ensure exact matches. Additionally, the
success toast and result message are displayed unconditionally even if
saveTemplateData fails, hiding persistence errors from the user. Restructure the
code so that the toast.success call and result assignment only execute after
confirming that saveTemplateData has completed successfully - move these
statements to execute only when the promise resolves, not in the unconditional
code path after the catch handler.

---

Nitpick comments:
In `@modules/playground/components/ai-chat-panel.tsx`:
- Around line 54-73: The FileItem interface defined in the ai-chat-panel
component duplicates a type already defined in useAITools.ts, creating
maintenance issues. Export the FileItem type definition from useAITools.ts (or
create a shared types module if appropriate) and remove the duplicate interface
definition from the AIChatPanelProps section in ai-chat-panel.tsx, then import
and use the shared type instead.
- Around line 229-243: Remove the `as any` type cast on the message prop in the
ChatMessage component and ensure the message type from useChat is properly
aligned with the ExtendedMessage interface by consolidating any duplicate
interface definitions. Additionally, the "Thinking..." loading indicator
rendered in the conditional block (checking isLoading and last message role)
duplicates the spinner already rendered inside the ChatMessage component itself,
so remove this duplicate conditional block and rely solely on the ChatMessage
component to handle displaying its own loading state.
- Around line 129-142: The setOpenFiles callback in the useAITools hook uses a
type assertion (as any) to handle a type mismatch between the FileItem type used
by useAITools and the format expected by useFileExplorer. Instead of using the
as any escape hatch, align the FileItem and OpenFile type definitions between
the useAITools and useFileExplorer hooks so they share a common interface, then
remove the type assertion and replace it with proper type-safe conversion if
needed.

In `@modules/playground/hooks/useAITools.ts`:
- Around line 27-53: Create a new shared types file at
modules/playground/types/chat.ts and move the MessagePart and ExtendedMessage
interfaces from useAITools.ts to this new file. Since OpenFile is defined in
useAITools.ts but never used (the hook uses an inline type in UseAIToolsProps
instead), either move it to the shared file for consistency or remove it
entirely. Then update the imports in useAITools.ts and chat-message.tsx to
reference these types from the new shared module instead of duplicating the
interface definitions in both files.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 56980a58-8484-40c8-ad2a-a6b21de76f98

📥 Commits

Reviewing files that changed from the base of the PR and between 3fb728f and 605224c.

📒 Files selected for processing (3)
  • modules/playground/components/ai-chat-panel.tsx
  • modules/playground/components/chat-message.tsx
  • modules/playground/hooks/useAITools.ts

Comment on lines +84 to +99
{toolParts.map((ti) => {
const tiName = (ti.toolName as string | undefined) ??
(ti.type as string)?.split("-").slice(1).join("-") ?? "tool";
const tiPath = (ti.input as Record<string, unknown> | undefined)?.path as string | undefined;
const tiDone = ti.state === "output-available" || ti.state === "result";
return (
<div key={ti.toolCallId} className="flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground border rounded-xl bg-muted/30 shadow-sm max-w-[90%]">
<div className="h-5 w-5 rounded-full bg-background flex items-center justify-center shrink-0 border shadow-sm">
<Wrench className="h-2.5 w-2.5" />
</div>
<span className="font-mono truncate tracking-tight">
{tiName}({tiPath ? tiPath.split("/").pop() : ""}) {tiDone ? "✓" : <Loader2 className="h-3 w-3 inline animate-spin ml-1" />}
</span>
</div>
);
})}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Provide fallback key when toolCallId is undefined.

ti.toolCallId can be undefined per the interface definition. Using undefined as a React key causes React to treat the element as having no stable identity, which can lead to incorrect reconciliation.

Proposed fix
-                            <div key={ti.toolCallId} className="flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground border rounded-xl bg-muted/30 shadow-sm max-w-[90%]">
+                            <div key={ti.toolCallId ?? `tool-${tiName}-${Math.random()}`} className="flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground border rounded-xl bg-muted/30 shadow-sm max-w-[90%]">

Or better, use the array index as fallback:

-                        {toolParts.map((ti) => {
+                        {toolParts.map((ti, idx) => {
                             ...
-                            <div key={ti.toolCallId} ...>
+                            <div key={ti.toolCallId ?? `tool-${idx}`} ...>
📝 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
{toolParts.map((ti) => {
const tiName = (ti.toolName as string | undefined) ??
(ti.type as string)?.split("-").slice(1).join("-") ?? "tool";
const tiPath = (ti.input as Record<string, unknown> | undefined)?.path as string | undefined;
const tiDone = ti.state === "output-available" || ti.state === "result";
return (
<div key={ti.toolCallId} className="flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground border rounded-xl bg-muted/30 shadow-sm max-w-[90%]">
<div className="h-5 w-5 rounded-full bg-background flex items-center justify-center shrink-0 border shadow-sm">
<Wrench className="h-2.5 w-2.5" />
</div>
<span className="font-mono truncate tracking-tight">
{tiName}({tiPath ? tiPath.split("/").pop() : ""}) {tiDone ? "✓" : <Loader2 className="h-3 w-3 inline animate-spin ml-1" />}
</span>
</div>
);
})}
{toolParts.map((ti, idx) => {
const tiName = (ti.toolName as string | undefined) ??
(ti.type as string)?.split("-").slice(1).join("-") ?? "tool";
const tiPath = (ti.input as Record<string, unknown> | undefined)?.path as string | undefined;
const tiDone = ti.state === "output-available" || ti.state === "result";
return (
<div key={ti.toolCallId ?? `tool-${idx}`} className="flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground border rounded-xl bg-muted/30 shadow-sm max-w-[90%]">
<div className="h-5 w-5 rounded-full bg-background flex items-center justify-center shrink-0 border shadow-sm">
<Wrench className="h-2.5 w-2.5" />
</div>
<span className="font-mono truncate tracking-tight">
{tiName}({tiPath ? tiPath.split("/").pop() : ""}) {tiDone ? "✓" : <Loader2 className="h-3 w-3 inline animate-spin ml-1" />}
</span>
</div>
);
})}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/chat-message.tsx` around lines 84 - 99, The
toolParts.map function in the return statement uses ti.toolCallId as the React
key, but ti.toolCallId can be undefined according to the interface definition,
which causes React reconciliation issues. Modify the map function to capture the
array index as a parameter and update the key prop on the div element to use a
fallback value such that if ti.toolCallId is undefined, it falls back to the
array index instead.

Comment on lines +103 to +108
{isLoading && message.role !== "assistant" && (
<div className="flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground">
<Loader2 className="h-3 w-3 animate-spin" />
<span>Thinking...</span>
</div>
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Duplicate "Thinking..." indicator logic.

This component renders "Thinking..." when isLoading && message.role !== "assistant", but AIChatPanel (lines 237-242) also renders its own "Thinking..." indicator with nearly identical conditions. Additionally, isLoading is passed with the same value to every ChatMessage, so all user messages would show "Thinking..." simultaneously.

Consider removing this indicator from ChatMessage since AIChatPanel already handles it at the list level, or refactor to only show for the last message.

Proposed fix: remove from ChatMessage
-            {isLoading && message.role !== "assistant" && (
-                <div className="flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground">
-                    <Loader2 className="h-3 w-3 animate-spin" />
-                    <span>Thinking...</span>
-                </div>
-            )}
📝 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
{isLoading && message.role !== "assistant" && (
<div className="flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground">
<Loader2 className="h-3 w-3 animate-spin" />
<span>Thinking...</span>
</div>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/chat-message.tsx` around lines 103 - 108, The
ChatMessage component contains redundant "Thinking..." indicator logic that
duplicates what AIChatPanel already handles. Remove the entire conditional block
that checks `isLoading && message.role !== "assistant"` and renders the Loader2
spinner with the "Thinking..." span, since AIChatPanel already displays this
indicator at the conversation list level. Removing this will prevent duplicate
loading indicators from appearing for all user messages simultaneously.

Comment on lines +168 to +184
const updatedOpenFiles = openFiles.map((f) => {
const ext = f.fileExtension ? `.${f.fileExtension}` : "";
const fullName = `${f.filename}${ext}`;
if (path.endsWith(fullName)) {
return {
...f,
content: content as string,
hasUnsavedChanges: true
};
}
return f;
});

setOpenFiles(updatedOpenFiles);
saveTemplateData(updatedTemplate).catch(console.error);
toast.success(`AI updated ${path}`);
result = `Successfully updated ${path}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Path matching is too loose and save failures are not surfaced to the user.

Two issues here:

  1. Incorrect path matching (line 171): Using path.endsWith(fullName) matches any file with the same filename regardless of directory. If the user has src/utils/Button.tsx and lib/components/Button.tsx open, editing one path would update both.

  2. Silent save failures (line 182-183): saveTemplateData errors are caught and logged, but the success toast fires unconditionally. The user sees "AI updated..." even if persistence failed, risking data loss.

Proposed fix for error handling
-                        saveTemplateData(updatedTemplate).catch(console.error);
-                        toast.success(`AI updated ${path}`);
-                        result = `Successfully updated ${path}`;
+                        try {
+                            await saveTemplateData(updatedTemplate);
+                            toast.success(`AI updated ${path}`);
+                            result = `Successfully updated ${path}`;
+                        } catch (err) {
+                            console.error(err);
+                            toast.error(`Failed to save ${path}`);
+                            result = `Error: Failed to persist ${path}`;
+                        }

Note: This requires making the effect callback async or restructuring the tool execution flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/hooks/useAITools.ts` around lines 168 - 184, The path
matching using path.endsWith(fullName) is too loose and will incorrectly match
files in different directories with the same filename. Replace this comparison
with a more robust path matching approach that compares normalized full paths to
ensure exact matches. Additionally, the success toast and result message are
displayed unconditionally even if saveTemplateData fails, hiding persistence
errors from the user. Restructure the code so that the toast.success call and
result assignment only execute after confirming that saveTemplateData has
completed successfully - move these statements to execute only when the promise
resolves, not in the unconditional code path after the catch handler.

Comment on lines +186 to +214
} else if (toolName === "edit_multiple_files") {
const { changes } = args as { changes?: { path: string; content: string }[] };
if (!changes || !Array.isArray(changes) || changes.length === 0) {
result = `Error: edit_multiple_files requires a "changes" array with at least one {path, content} entry`;
} else if (!templateData) {
result = `Error: Template data not loaded`;
} else {
let currentItems = templateData.items;
let currentOpenFiles = [...openFiles];

for (const change of changes) {
currentItems = addOrUpdateFile(currentItems, change.path, change.content);
currentOpenFiles = currentOpenFiles.map((f) => {
const ext = f.fileExtension ? `.${f.fileExtension}` : "";
const fullName = `${f.filename}${ext}`;
if (change.path.endsWith(fullName)) {
return { ...f, content: change.content, hasUnsavedChanges: true };
}
return f;
});
}

const updatedTemplate = { ...templateData, items: currentItems };
setTemplateData(updatedTemplate);
setOpenFiles(currentOpenFiles);
saveTemplateData(updatedTemplate).catch(console.error);
toast.success(`AI scaffolded ${changes.length} files`);
result = `Successfully updated ${changes.length} files`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Same path matching and error handling issues apply here.

Lines 201 and 211 have the same problems flagged for edit_file: loose endsWith matching could update wrong files, and save failures are silently ignored while showing success toast.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/hooks/useAITools.ts` around lines 186 - 214, Fix two
issues in the edit_multiple_files block: First, replace the loose endsWith path
matching on line 201 with a strict path comparison that properly normalizes both
the change.path and the constructed fullName to ensure exact matches and prevent
updating unintended files. Second, move the toast.success and result assignment
inside the saveTemplateData promise chain so they only execute after the save
succeeds, rather than showing success immediately and silently catching errors;
use then and catch handlers to properly handle both success and failure cases
from saveTemplateData.

Comment on lines +215 to +236
} else if (toolName === "delete_file") {
const { path } = args as { path?: string };
if (!path || typeof path !== "string") {
result = `Error: delete_file requires a "path" argument`;
} else if (!templateData) {
result = `Error: Template data not loaded`;
} else {
const updatedItems = deleteFileByPath(templateData.items, path);
const updatedTemplate = { ...templateData, items: updatedItems };
setTemplateData(updatedTemplate);

const updatedOpenFiles = openFiles.filter((f) => {
const ext = f.fileExtension ? `.${f.fileExtension}` : "";
const fullName = `${f.filename}${ext}`;
return !path.endsWith(fullName);
});

setOpenFiles(updatedOpenFiles);
saveTemplateData(updatedTemplate).catch(console.error);
toast.success(`AI deleted ${path}`);
result = `Successfully deleted ${path}`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Same path matching and error handling issues apply to delete_file.

Lines 229 and 233 have the same problems: loose endsWith matching could close wrong files, and save failures show success toast.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/hooks/useAITools.ts` around lines 215 - 236, The
delete_file handler has two issues: the path matching on line 229 uses loose
endsWith comparison which could incorrectly close wrong files, and the success
toast on line 233 is shown before confirming the save operation succeeds
(saveTemplateData on line 234 could fail). Fix the path matching logic in the
openFiles filter to use exact path comparison instead of endsWith, and move the
toast.success call to execute only after saveTemplateData successfully
completes, or handle the save error appropriately before showing the success
message.

@Maxd646 Maxd646 self-requested a review June 22, 2026 08:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

help wanted Extra attention is needed mentor:Maxd646

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor AIChatPanel to Separate UI and Tool Logic

2 participants