feat(playground): add ai-powered readme generator#268
Conversation
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
👋 Thanks for opening a PR, @AdityaM-IITH!Your PR has entered the 🚦 PR Review Pipeline.
What happens next
A pipeline status comment will appear below and update automatically as your PR progresses. While you wait
This comment is posted only once. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughImplements AI-powered README generation: a POST /api/readme streaming endpoint with Zod validation, auth and IP rate limiting, frontend ReadmeGeneratorDialog that streams generated Markdown, and editor wiring to save/open the produced README.md. ChangesREADME Generator Feature
Sequence DiagramsequenceDiagram
participant User
participant Toolbar as Header Button
participant Dialog as ReadmeGeneratorDialog
participant API as POST /api/readme
participant Modals as PlaygroundModals
participant Editor as File Editor
User->>Toolbar: Click "Generate README"
Toolbar->>Dialog: setIsReadmeDialogOpen(true)
Dialog->>Dialog: Extract fileTree + package.json
Dialog->>API: POST payload (fileTree, packageJson?, template, provider?, userApiKey?)
API->>API: validate, auth, rate limit
API-->>Dialog: stream README Markdown
Dialog->>Dialog: accumulate streamed content
Dialog->>Modals: onReadmeGenerated(content)
Modals->>Modals: addOrUpdateFile("README.md", content)
Modals->>Modals: saveTemplateData()
Modals->>Editor: open README.md
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@app/api/readme/route.ts`:
- Around line 67-113: Normalize userApiKey once at the top of the handler (e.g.,
const normalizedUserApiKey = userApiKey?.trim() || undefined) and use
normalizedUserApiKey everywhere instead of raw userApiKey; update the auth check
that currently does (!userApiKey || userApiKey.trim() === "") to use
normalizedUserApiKey, and replace each provider branch's apiKey assignment
(where code uses userApiKey || (isAuthenticated ? process.env.* : undefined))
with normalizedUserApiKey || (isAuthenticated ? process.env.* : undefined) so
whitespace-only keys won't block fallback to server-configured keys (affects
symbols: userApiKey, normalizedUserApiKey, provider branches that call
createGoogleGenerativeAI, createGroq, and model assignment).
In `@modules/playground/components/readme-generator-dialog.tsx`:
- Around line 21-26: The component treats onReadmeGenerated as synchronous but
callers perform async persistence; update ReadmeGeneratorDialogProps to have
onReadmeGenerated return a Promise (e.g. onReadmeGenerated: (content: string) =>
Promise<void>), then await onReadmeGenerated(...) where the generation flow
currently sets the success state/auto-closes (refer to onReadmeGenerated call
sites around lines 116-118) and only show success/close the dialog after the
awaited promise resolves; also catch and handle rejection to show an error state
instead of closing on failure.
🪄 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: 35b15124-b224-495f-b82f-a0e3164cf835
📒 Files selected for processing (5)
app/api/readme/route.tsmodules/playground/components/playground-header.tsxmodules/playground/components/playground-modals.tsxmodules/playground/components/readme-generator-dialog.tsxmodules/playground/hooks/usePlaygroundUI.ts
piyushdotcomm
left a comment
There was a problem hiding this comment.
add screenshot and recording of this feature
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
modules/playground/components/readme-generator-dialog.tsx (1)
120-124:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winClear the timeout on unmount or early close to prevent stale state updates.
If the user manually closes the dialog before the 1.5s delay, the timeout still fires and calls
setIsDone(false)/onOpenChange(false)on a potentially unmounted component, which can cause React warnings or unexpected behavior.🛡️ Proposed fix using a ref to track and clear the timeout
+import React, { useState, useCallback, useRef, useEffect } from "react"; ... export function ReadmeGeneratorDialog({ ... }: ReadmeGeneratorDialogProps) { const [template, setTemplate] = useState<ReadmeTemplate>("standard"); const [isGenerating, setIsGenerating] = useState(false); const [isDone, setIsDone] = useState(false); + const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); const { provider, getUserApiKey } = useAI(); + useEffect(() => { + return () => { + if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current); + }; + }, []); const handleGenerate = useCallback(async () => { ... // Auto-close after a short delay so the user sees the success state. - setTimeout(() => { + closeTimeoutRef.current = setTimeout(() => { onOpenChange(false); setIsDone(false); }, 1500); ... }, [...]);🤖 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/readme-generator-dialog.tsx` around lines 120 - 124, The setTimeout in the readme-generator-dialog component can fire after the dialog unmounts or is closed early, causing stale state updates from setIsDone(false) and onOpenChange(false); fix by storing the timeout id in a ref (e.g., timeoutRef), clearTimeout(timeoutRef.current) before starting a new timeout, and clear it in the component cleanup (useEffect return) and any early-close/onClose handler so the timeout never runs after unmount or manual close; update the setTimeout usage around onOpenChange and setIsDone to use this ref-managed timeout.
🧹 Nitpick comments (2)
modules/playground/components/readme-generator-dialog.tsx (1)
74-130: ⚡ Quick winAdd an
AbortControllerto cancel the fetch when the dialog closes or component unmounts.If the user closes the dialog while README generation is in progress, the fetch continues running in the background, wasting resources and potentially calling state setters on an unmounted component.
♻️ Proposed approach
export function ReadmeGeneratorDialog({ ... }: ReadmeGeneratorDialogProps) { ... + const abortControllerRef = useRef<AbortController | null>(null); + useEffect(() => { + return () => { + abortControllerRef.current?.abort(); + }; + }, []); const handleGenerate = useCallback(async () => { ... setIsGenerating(true); setIsDone(false); + abortControllerRef.current = new AbortController(); try { const userApiKey = getUserApiKey(provider as AIProvider); const res = await fetch("/api/readme", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ... }), + signal: abortControllerRef.current.signal, }); ... } catch (error: unknown) { + if (error instanceof Error && error.name === "AbortError") { + return; // User cancelled, no error toast needed + } console.error("README generation error:", error); toast.error(error instanceof Error ? error.message : "Failed to generate README."); } finally { setIsGenerating(false); } }, [...]);🤖 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/readme-generator-dialog.tsx` around lines 74 - 130, Wrap the fetch("/api/readme") call with an AbortController: create a controller (e.g., in a ref) and pass controller.signal to fetch, store/replace it when generation starts (where setIsGenerating(true) is called), and call controller.abort() when the dialog closes or the component unmounts (hook into onOpenChange(false) close handler and useEffect cleanup). Ensure the fetch/stream read loop (res.body.getReader()) will be canceled by the signal and handle the abort error in the catch block so you don't show a failure toast for user-initiated cancels (detect DOMException/name === "AbortError" or similar), and clear/reset the controller and UI state (setIsGenerating(false), setIsDone(false) as appropriate) after abort so onReadmeGenerated and success toast are not called for aborted requests.app/api/readme/route.ts (1)
124-130: ⚡ Quick winPass
AbortSignaltostreamTextso the LLM call cancels when the client disconnects.In
app/api/readme/route.ts, the POST handler already receivesrequest: NextRequest(sorequest.signalis available); wiring it intostreamTextprevents continued token generation if the connection drops.♻️ Proposed change
const resultStream = streamText({ model, system: README_SYSTEM_PROMPT, prompt: contextParts, + abortSignal: request.signal, });🤖 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 `@app/api/readme/route.ts` around lines 124 - 130, The LLM streaming call should be cancellable when the client disconnects: update the streamText invocation in the POST handler to pass the incoming request.signal (available from the NextRequest) as the AbortSignal (e.g., add a signal: request.signal property alongside model, system: README_SYSTEM_PROMPT, and prompt: contextParts), and ensure streamText (and any downstream code that performs fetch/LLM requests) accepts and forwards that signal so token generation stops on client disconnect before returning resultStream.toTextStreamResponse().
🤖 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.
Outside diff comments:
In `@modules/playground/components/readme-generator-dialog.tsx`:
- Around line 120-124: The setTimeout in the readme-generator-dialog component
can fire after the dialog unmounts or is closed early, causing stale state
updates from setIsDone(false) and onOpenChange(false); fix by storing the
timeout id in a ref (e.g., timeoutRef), clearTimeout(timeoutRef.current) before
starting a new timeout, and clear it in the component cleanup (useEffect return)
and any early-close/onClose handler so the timeout never runs after unmount or
manual close; update the setTimeout usage around onOpenChange and setIsDone to
use this ref-managed timeout.
---
Nitpick comments:
In `@app/api/readme/route.ts`:
- Around line 124-130: The LLM streaming call should be cancellable when the
client disconnects: update the streamText invocation in the POST handler to pass
the incoming request.signal (available from the NextRequest) as the AbortSignal
(e.g., add a signal: request.signal property alongside model, system:
README_SYSTEM_PROMPT, and prompt: contextParts), and ensure streamText (and any
downstream code that performs fetch/LLM requests) accepts and forwards that
signal so token generation stops on client disconnect before returning
resultStream.toTextStreamResponse().
In `@modules/playground/components/readme-generator-dialog.tsx`:
- Around line 74-130: Wrap the fetch("/api/readme") call with an
AbortController: create a controller (e.g., in a ref) and pass controller.signal
to fetch, store/replace it when generation starts (where setIsGenerating(true)
is called), and call controller.abort() when the dialog closes or the component
unmounts (hook into onOpenChange(false) close handler and useEffect cleanup).
Ensure the fetch/stream read loop (res.body.getReader()) will be canceled by the
signal and handle the abort error in the catch block so you don't show a failure
toast for user-initiated cancels (detect DOMException/name === "AbortError" or
similar), and clear/reset the controller and UI state (setIsGenerating(false),
setIsDone(false) as appropriate) after abort so onReadmeGenerated and success
toast are not called for aborted requests.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 9f6ad971-982d-4451-8a86-053e5c15c0eb
📒 Files selected for processing (2)
app/api/readme/route.tsmodules/playground/components/readme-generator-dialog.tsx
|
Here is a quick demo of the README Generator feature in action! 🚀 The feature analyzes the current workspace (file tree and Screen Recording: pr-268.mp4Let me know if you need any adjustments or if this looks good to merge. |
Maxd646
left a comment
There was a problem hiding this comment.
Great work on this feature! but the following need changed before the merge
-
Fix the Streaming UX: In readme-generator-dialog.tsx, the response body is being accumulated fully before updating the UI. This completely defeats the purpose of the server-side streaming; the user just sees a static loading spinner. We need to progressively feed chunks to onReadmeGenerated so it streams into the editor in real-time.
-
Timeout Memory Leak: The 1500ms auto-close setTimeout can trigger an unmounted state update if a user manually dismisses the dialog quickly. Wrap it with a useRef and clear it on unmount using a useEffect cleanup hook.
and for optimzation
- API Optimization: Move the authentication check above the rate limiter in the API route to prevent unauthenticated requests from exhausting the rate limit pool.
|
Thanks for the feedback! I've addressed all the requested changes:
Let me know if there's anything else you need. |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/api/readme/route.ts (1)
53-75:⚠️ Potential issue | 🟠 Major | ⚡ Quick winApply rate limiting before the unauthorized early return.
Line 53 returns 401 before Lines 60-75, so unauthenticated traffic is currently not rate-limited. That creates an easy abuse path against this endpoint.
🔧 Suggested fix
- if (!session?.user?.id && !normalizedUserApiKey) { - return NextResponse.json( - { success: false, error: "Unauthorized: Please log in or provide your own API key in settings." }, - { status: 401 } - ); - } - const ip = getClientIp(request); // Stricter limit than chat: README generation is more expensive. const { allowed, remaining } = await rateLimit(ip, 5, 60_000); if (!allowed) { return NextResponse.json( { success: false, error: "Rate limit exceeded. Please wait before generating again." }, { status: 429, headers: { "Retry-After": "60", "X-RateLimit-Remaining": String(remaining), }, } ); } + + if (!session?.user?.id && !normalizedUserApiKey) { + return NextResponse.json( + { success: false, error: "Unauthorized: Please log in or provide your own API key in settings." }, + { status: 401 } + ); + }🤖 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 `@app/api/readme/route.ts` around lines 53 - 75, Move the rate-limit check to run before the unauthorized early return: call getClientIp(request) and await rateLimit(ip, 5, 60_000) immediately after computing session/normalizedUserApiKey, then if !allowed return the 429 response (with "Retry-After" and "X-RateLimit-Remaining") before the existing unauthorized check that returns NextResponse.json(..., { status: 401 }); ensure you still compute and use remaining in the 429 response and keep the same authorization logic using session?.user?.id and normalizedUserApiKey.modules/playground/components/readme-generator-dialog.tsx (1)
113-126:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDon’t persist README on every stream chunk.
Line 117 calls
onReadmeGeneratedfor each chunk, which can trigger repeated persistence/open-file side effects and save partial content before final validation. Persist once afterfinalContentis validated.🔧 Suggested fix
while (true) { const { done, value } = await reader.read(); if (done) break; accumulated += decoder.decode(value, { stream: true }); - await onReadmeGenerated(accumulated); } const finalContent = accumulated.trim(); if (!finalContent) { throw new Error("Model returned an empty README. Try again."); } + + await onReadmeGenerated(finalContent); setIsDone(true); toast.success("README.md generated and opened in editor.");🤖 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/readme-generator-dialog.tsx` around lines 113 - 126, The code currently calls onReadmeGenerated for every stream chunk inside the reader.read loop, causing repeated persistence/open-file side effects and saving partial content; modify the loop that reads from reader.read so it only accumulates chunks into the accumulated string (using decoder.decode with {stream: true}) and remove the per-chunk call to onReadmeGenerated, then after the loop compute finalContent = accumulated.trim(), validate that finalContent is not empty, and only then call onReadmeGenerated(finalContent) (followed by setIsDone(true) and toast.success) to persist/open the README once; keep references to reader.read, accumulated, decoder.decode, onReadmeGenerated, finalContent, setIsDone, and toast.success when making the change.
🤖 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 `@middleware.ts`:
- Around line 40-43: The branch in middleware.ts that checks "if (!isLoggedIn &&
!isPublicRoute)" currently returns null and must instead redirect
unauthenticated users to the sign-in page; change the return to use
Response.redirect(new URL(DEFAULT_LOGIN_REDIRECT /* or "/auth/sign-in" */,
nextUrl)) (or the existing sign-in URL constant) so protected pages enforce
auth, keeping API passthrough logic intact; update the return in that branch
(referencing isLoggedIn, isPublicRoute, nextUrl, and DEFAULT_LOGIN_REDIRECT)
accordingly.
In `@modules/playground/actions/index.ts`:
- Around line 102-118: Replace the hardcoded stub in getPlaygroundById with the
original implementation that performs the authenticated database lookup and
returns the real playground and template data; remove the static test object and
reintroduce the DB call(s) and auth checks used elsewhere (e.g., the code path
used by SaveUpdatedCode) so getPlaygroundById validates the user, queries the
playground record by id, and returns the same { playgroundData, templateData }
shape expected by consumers. Ensure you call the same auth utilities and DB
methods used across this module (or restore the previous function body) so loads
match saves.
---
Outside diff comments:
In `@app/api/readme/route.ts`:
- Around line 53-75: Move the rate-limit check to run before the unauthorized
early return: call getClientIp(request) and await rateLimit(ip, 5, 60_000)
immediately after computing session/normalizedUserApiKey, then if !allowed
return the 429 response (with "Retry-After" and "X-RateLimit-Remaining") before
the existing unauthorized check that returns NextResponse.json(..., { status:
401 }); ensure you still compute and use remaining in the 429 response and keep
the same authorization logic using session?.user?.id and normalizedUserApiKey.
In `@modules/playground/components/readme-generator-dialog.tsx`:
- Around line 113-126: The code currently calls onReadmeGenerated for every
stream chunk inside the reader.read loop, causing repeated persistence/open-file
side effects and saving partial content; modify the loop that reads from
reader.read so it only accumulates chunks into the accumulated string (using
decoder.decode with {stream: true}) and remove the per-chunk call to
onReadmeGenerated, then after the loop compute finalContent =
accumulated.trim(), validate that finalContent is not empty, and only then call
onReadmeGenerated(finalContent) (followed by setIsDone(true) and toast.success)
to persist/open the README once; keep references to reader.read, accumulated,
decoder.decode, onReadmeGenerated, finalContent, setIsDone, and toast.success
when making the change.
🪄 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: 4831547c-7e08-45ba-a61e-402c043e6778
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (4)
app/api/readme/route.tsmiddleware.tsmodules/playground/actions/index.tsmodules/playground/components/readme-generator-dialog.tsx
There was a problem hiding this comment.
🧹 Nitpick comments (1)
modules/playground/components/readme-generator-dialog.tsx (1)
109-117: ⚡ Quick winFlush the TextDecoder after the stream ends.
When using
TextDecoderwith{ stream: true }, a finaldecode()call without arguments is needed to flush any buffered bytes from incomplete multi-byte sequences. Without this, the last character could be truncated if it spans chunk boundaries.Proposed fix
const reader = res.body.getReader(); const decoder = new TextDecoder(); let accumulated = ""; while (true) { const { done, value } = await reader.read(); if (done) break; accumulated += decoder.decode(value, { stream: true }); } + accumulated += decoder.decode(); // flush remaining bytes const finalContent = accumulated.trim();🤖 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/readme-generator-dialog.tsx` around lines 109 - 117, The stream read loop using reader = res.body.getReader() and decoder = new TextDecoder() uses decoder.decode(value, { stream: true }) but never flushes the decoder; after the while loop (once done is true) call decoder.decode() with no arguments and append its result to accumulated to flush any buffered bytes from incomplete multi-byte sequences (update the logic around reader/read and accumulated in readme-generator-dialog's streaming handler to append decoder.decode() after the loop).
🤖 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.
Nitpick comments:
In `@modules/playground/components/readme-generator-dialog.tsx`:
- Around line 109-117: The stream read loop using reader = res.body.getReader()
and decoder = new TextDecoder() uses decoder.decode(value, { stream: true }) but
never flushes the decoder; after the while loop (once done is true) call
decoder.decode() with no arguments and append its result to accumulated to flush
any buffered bytes from incomplete multi-byte sequences (update the logic around
reader/read and accumulated in readme-generator-dialog's streaming handler to
append decoder.decode() after the loop).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 3c5a87be-4c13-401a-a669-18f3866c3867
📒 Files selected for processing (2)
app/api/readme/route.tsmodules/playground/components/readme-generator-dialog.tsx
There was a problem hiding this comment.
Thanks for the quick response! However, looking through the diff, it seems a few of the requested changes didn't actually make it into the code updates.
- Streaming UX -> readme-generator-dialog.tsx
You've set up the reader.read() loop, but onReadmeGenerated is still being called after the loop completes on line 114. We need to call it inside the loop so the chunks update the editor in real-time.
Fix: Inside your while (true) loop, pass the accumulated text to the callback on every chunk:
like
while (true) {
const { done, value } = await reader.read();
if (done) break;
accumulated += decoder.decode(value, { stream: true });
// Feed the stream chunks into the editor in real-time
await onReadmeGenerated(accumulated);
}
-> You'll also want to make sure your loading spinner UI doesn't completely block the user from seeing the editor update once streaming begins).
- API Rate Limiting -> app/api/readme/route.ts
The rate limiter is still executing before the authentication check. Unauthenticated requests will still trigger hit counters on the rate limit pool.
-> Fix: Move the if (!session?.user?.id && !normalizedUserApiKey) block above the rateLimit(ip, 5, 60_000) block.
Let me know when you've pushed these updates and I'll take another look!
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
modules/playground/components/readme-generator-dialog.tsx (1)
116-121:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftAvoid persistence side-effects in the per-chunk stream loop.
Line 120 currently does
await onReadmeGenerated(accumulated)for every chunk. With the current caller (modules/playground/components/playground-modals.tsx, Lines 57-70), this causes repeatedsaveTemplateData(...)+openFile(...)calls during streaming, which can create heavy write churn and flaky UX on longer outputs.Proposed contract split (progress vs final)
interface ReadmeGeneratorDialogProps { open: boolean; onOpenChange: (open: boolean) => void; templateData: TemplateFolder | null; + onReadmeProgress?: (content: string) => void; onReadmeGenerated: (content: string) => Promise<void> | void; } @@ export function ReadmeGeneratorDialog({ open, onOpenChange, templateData, + onReadmeProgress, onReadmeGenerated, }: ReadmeGeneratorDialogProps) { @@ while (true) { const { done, value } = await reader.read(); if (done) break; accumulated += decoder.decode(value, { stream: true }); - await onReadmeGenerated(accumulated); + onReadmeProgress?.(accumulated); } @@ const finalContent = accumulated.trim(); if (!finalContent) { throw new Error("Model returned an empty README. Try again."); } + await onReadmeGenerated(finalContent);🤖 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/readme-generator-dialog.tsx` around lines 116 - 121, The per-chunk loop in readme-generator-dialog.tsx calls await onReadmeGenerated(accumulated) for every chunk (inside the while(true) reader.read() loop), which causes the caller's heavy side-effects (saveTemplateData/openFile) to run repeatedly; change the contract so the loop emits progress-only updates and call the final persistence once when streaming completes: add a new onReadmeProgress(chunkOrAccumulated) callback and invoke that inside the loop using decoder.decode(value, {stream:true})/accumulated for UI progress, then after the loop finishes call onReadmeGenerated(accumulated) exactly once; update the caller (playground-modals.tsx) to use onReadmeProgress for incremental UI and keep saveTemplateData/openFile only in the final onReadmeGenerated handler.
🤖 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.
Outside diff comments:
In `@modules/playground/components/readme-generator-dialog.tsx`:
- Around line 116-121: The per-chunk loop in readme-generator-dialog.tsx calls
await onReadmeGenerated(accumulated) for every chunk (inside the while(true)
reader.read() loop), which causes the caller's heavy side-effects
(saveTemplateData/openFile) to run repeatedly; change the contract so the loop
emits progress-only updates and call the final persistence once when streaming
completes: add a new onReadmeProgress(chunkOrAccumulated) callback and invoke
that inside the loop using decoder.decode(value, {stream:true})/accumulated for
UI progress, then after the loop finishes call onReadmeGenerated(accumulated)
exactly once; update the caller (playground-modals.tsx) to use onReadmeProgress
for incremental UI and keep saveTemplateData/openFile only in the final
onReadmeGenerated handler.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 652c41e1-b06b-4d5e-b1ae-afccee9c3530
📒 Files selected for processing (1)
modules/playground/components/readme-generator-dialog.tsx


Summary
package.jsonto stream a completeREADME.mddirectly into the editor via the existing AI provider (gemini / groq / mistral).Type of change
Related issue
Closes #128
Validation
npm run lintnpm testnpm run buildList any additional manual verification you performed:
npm run lint— 0 errors, 25 pre-existing warnings (none in changed files)npm test— 112/112 tests passed across 16 test filesnpm run build— fails withCannot find module '.prisma/client/default'due to Prisma client not being generated locally (requires a live DB +npx prisma generate). This failure exists onmainand is unrelated to this PRcollectFilePathsserializes nested folder structures correctlyaddOrUpdateFileupsertsREADME.mdat root without corrupting existing file treeScreenshots or recordings
UI diff is visible in the changed files. The button renders in the toolbar between the core actions group and Deploy. The dialog follows the same visual structure as
DeployDialog(sameDialogContent,Tabs,Buttoncomponents).Checklist
Summary by CodeRabbit
Release Notes