Conversation
|
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:
📝 WalkthroughWalkthroughExported word-diff utilities from TranscriptionDiffViewer, added language-aware dataset handling and per-file language controls in the speech-to-text UI, integrated a side-by-side word-level diff and score info in Evaluations, and added a Next.js GET route to proxy backend languages. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser as Browser (Client)
participant NextAPI as Next.js /api/languages
participant Backend as Backend /api/v1/languages
Browser->>NextAPI: GET /api/languages
Note right of NextAPI: read NEXT_PUBLIC_BACKEND_URL\nforward X-API-KEY if present
NextAPI->>Backend: GET /api/v1/languages (forward headers)
Backend-->>NextAPI: 200 + JSON languages
NextAPI-->>Browser: 200 + JSON languages
Browser->>Browser: set languages state / set datasetLanguageId\nrender language selectors and word-diff panels (computeWordDiff)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 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: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/speech-to-text/page.tsx (2)
1793-1795:⚠️ Potential issue | 🟠 MajorNon-null assertion on potentially null
is_correctmay cause unintended API payload.Using
result.is_correct!whenis_correctcan benullwill sendnullto the API, potentially overwriting an existing value or causing unexpected backend behavior when the user only intended to update the comment.🐛 Proposed fix - only send comment on blur
onBlur={(e) => { - updateFeedback(result.id, result.is_correct!, e.target.value); + // Only update comment without changing is_correct + if (apiKeys.length === 0) return; + fetch(`/api/evaluations/stt/results/${result.id}`, { + method: 'PATCH', + headers: { + 'X-API-KEY': apiKeys[0].key, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ comment: e.target.value }), + }).catch(console.error); }}Or refactor
updateFeedbackto accept optional parameters and only include defined values in the payload.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` around lines 1793 - 1795, The onBlur handler currently uses a non-null assertion result.is_correct! which may send null/incorrect data to the API; change the handler to avoid forcing a value — either call updateFeedback(result.id, undefined, e.target.value) (or updateFeedback(result.id, { comment: e.target.value })) so only the comment is sent, or refactor updateFeedback to accept optional params (e.g., updateFeedback(id: string, is_correct?: boolean | null, comment?: string)) and ensure it only includes defined fields in the payload; update the onBlur to call updateFeedback with only the comment (or the new optional param shape) and remove the `!` on result.is_correct.
1326-1327:⚠️ Potential issue | 🟠 MajorModel value and display text mismatch.
The option's
valueis"gemini-2.0-flash-exp"but displays"gemini-2.5-pro". This will confuse users and may cause the dropdown to behave unexpectedly since the default state ('gemini-2.5-pro') doesn't match any option value.🐛 Proposed fix
- <option value="gemini-2.0-flash-exp">gemini-2.5-pro</option> + <option value="gemini-2.5-pro">gemini-2.5-pro</option>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` around lines 1326 - 1327, The select option has a mismatched value/display: the option element currently uses value "gemini-2.0-flash-exp" but displays "gemini-2.5-pro", while the component's default state is 'gemini-2.5-pro'; fix by making the option value match the displayed label (or vice versa) and ensure the component's default selection matches an option value—update the option element (value="gemini-2.5-pro") or change the displayed text to "gemini-2.0-flash-exp" and align the default state accordingly so the select's value and displayed text are consistent.
🧹 Nitpick comments (1)
app/speech-to-text/page.tsx (1)
1514-1576: Consider accessibility improvements for the score info popup.The info button and popup lack accessibility attributes:
- The "i" button has no
aria-labeloraria-expandedfor screen readers.- The popup has no
roleattribute (e.g.,role="dialog"orrole="tooltip").- No keyboard navigation support (Escape to close, focus trap).
Additionally, the
metricsarray at lines 1526-1530 is static data recreated on each render—consider extracting it to module scope.♿ Partial fix for accessibility
<span className="inline-flex items-center justify-center w-3.5 h-3.5 rounded-full text-[9px] font-bold cursor-pointer shrink-0" style={{ backgroundColor: colors.bg.primary, border: `1px solid ${colors.border}`, color: colors.text.secondary }} onClick={(e) => { e.stopPropagation(); setOpenScoreInfo(openScoreInfo ? null : 'accuracy'); }} + aria-label="Score metrics info" + aria-expanded={!!openScoreInfo} + role="button" + tabIndex={0} + onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setOpenScoreInfo(openScoreInfo ? null : 'accuracy'); }}} > i </span>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` around lines 1514 - 1576, The info popup lacks accessibility and recreates metrics each render; update the "i" toggle (the span with onClick that toggles openScoreInfo via setOpenScoreInfo) to be a proper button with aria-label and aria-expanded tied to openScoreInfo, move the metrics array out of the render into module-scope (exported const metrics) so it isn’t recreated on every render, add appropriate aria attributes and role on the popup container (e.g., role="dialog" or role="tooltip", aria-modal/aria-describedby as applicable), and implement keyboard support on the popup/component to close on Escape and manage focus (return focus to the button and use a simple focus trap while open) using the same openScoreInfo / setOpenScoreInfo handlers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/speech-to-text/page.tsx`:
- Around line 1724-1749: The UI renders percentages using result.score
properties but only checks result.score exists, so undefined values
(result.score.wip, result.score.wer, result.score.cer, result.score.lenient_wer)
can produce NaN%; update the rendering in the Accuracy block and the Errors
mapping to guard each metric: either filter the errors array before .map to
remove entries with value == null/undefined, or replace uses like
result.score.wip / value with a defensive expression (e.g., check value != null
and only then render (value * 100).toFixed(1) + '%' else render '-' ), and make
the color logic also handle undefined (use a default color when value is
missing) so the mapping for [{label, value}] and the Accuracy span both never
compute with undefined.
---
Outside diff comments:
In `@app/speech-to-text/page.tsx`:
- Around line 1793-1795: The onBlur handler currently uses a non-null assertion
result.is_correct! which may send null/incorrect data to the API; change the
handler to avoid forcing a value — either call updateFeedback(result.id,
undefined, e.target.value) (or updateFeedback(result.id, { comment:
e.target.value })) so only the comment is sent, or refactor updateFeedback to
accept optional params (e.g., updateFeedback(id: string, is_correct?: boolean |
null, comment?: string)) and ensure it only includes defined fields in the
payload; update the onBlur to call updateFeedback with only the comment (or the
new optional param shape) and remove the `!` on result.is_correct.
- Around line 1326-1327: The select option has a mismatched value/display: the
option element currently uses value "gemini-2.0-flash-exp" but displays
"gemini-2.5-pro", while the component's default state is 'gemini-2.5-pro'; fix
by making the option value match the displayed label (or vice versa) and ensure
the component's default selection matches an option value—update the option
element (value="gemini-2.5-pro") or change the displayed text to
"gemini-2.0-flash-exp" and align the default state accordingly so the select's
value and displayed text are consistent.
---
Nitpick comments:
In `@app/speech-to-text/page.tsx`:
- Around line 1514-1576: The info popup lacks accessibility and recreates
metrics each render; update the "i" toggle (the span with onClick that toggles
openScoreInfo via setOpenScoreInfo) to be a proper button with aria-label and
aria-expanded tied to openScoreInfo, move the metrics array out of the render
into module-scope (exported const metrics) so it isn’t recreated on every
render, add appropriate aria attributes and role on the popup container (e.g.,
role="dialog" or role="tooltip", aria-modal/aria-describedby as applicable), and
implement keyboard support on the popup/component to close on Escape and manage
focus (return focus to the button and use a simple focus trap while open) using
the same openScoreInfo / setOpenScoreInfo handlers.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c807447a-9808-47da-9abf-2a2bd10f37c3
📒 Files selected for processing (2)
app/components/speech-to-text/TranscriptionDiffViewer.tsxapp/speech-to-text/page.tsx
| <td className="px-4 py-3 text-xs align-top"> | ||
| {result.score ? ( | ||
| <div className="space-y-2"> | ||
| <div className="flex justify-between gap-2"> | ||
| <span style={{ color: colors.text.secondary }}>Accuracy</span> | ||
| <span className="font-mono font-medium" style={{ color: result.score.wip >= 0.9 ? colors.status.success : result.score.wip >= 0.7 ? '#ca8a04' : colors.status.error }}>{(result.score.wip * 100).toFixed(1)}%</span> | ||
| </div> | ||
| <div> | ||
| <div className="mb-1" style={{ color: colors.text.secondary, fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Errors</div> | ||
| <div className="space-y-1 pl-1" style={{ borderLeft: `2px solid ${colors.border}` }}> | ||
| {[ | ||
| { label: 'WER', value: result.score.wer }, | ||
| { label: 'CER', value: result.score.cer }, | ||
| { label: 'Lenient WER', value: result.score.lenient_wer }, | ||
| ].map(({ label, value }) => ( | ||
| <div key={label} className="flex justify-between gap-2 pl-1.5"> | ||
| <span style={{ color: colors.text.secondary }}>{label}</span> | ||
| <span className="font-mono font-medium" style={{ color: value >= 0.8 ? colors.status.error : value >= 0.4 ? '#ca8a04' : colors.status.success }}>{(value * 100).toFixed(1)}%</span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| {result.transcription && result.transcription.length > 80 && ( | ||
| <button | ||
| onClick={() => toggleTranscription(result.id)} | ||
| className="text-xs mt-1" | ||
| style={{ color: colors.accent.primary, cursor: 'pointer' }} | ||
| > | ||
| {expandedTranscriptions.has(result.id) ? 'Show less' : 'Read more'} | ||
| </button> | ||
| )} | ||
| </div> | ||
| ) : ( | ||
| <span style={{ color: colors.text.secondary }}>-</span> | ||
| )} |
There was a problem hiding this comment.
Missing null checks for individual score properties may display "NaN%".
The code checks if result.score exists, but individual properties (wip, wer, cer, lenient_wer) may still be undefined. Accessing undefined * 100 produces NaN, causing "NaN%" to appear in the UI.
🛡️ Proposed defensive fix
- <div className="flex justify-between gap-2">
- <span style={{ color: colors.text.secondary }}>Accuracy</span>
- <span className="font-mono font-medium" style={{ color: result.score.wip >= 0.9 ? colors.status.success : result.score.wip >= 0.7 ? '#ca8a04' : colors.status.error }}>{(result.score.wip * 100).toFixed(1)}%</span>
- </div>
+ {result.score.wip != null && (
+ <div className="flex justify-between gap-2">
+ <span style={{ color: colors.text.secondary }}>Accuracy</span>
+ <span className="font-mono font-medium" style={{ color: result.score.wip >= 0.9 ? colors.status.success : result.score.wip >= 0.7 ? '#ca8a04' : colors.status.error }}>{(result.score.wip * 100).toFixed(1)}%</span>
+ </div>
+ )}Apply similar guards for the error metrics, or filter the array before mapping:
{[
{ label: 'WER', value: result.score.wer },
{ label: 'CER', value: result.score.cer },
{ label: 'Lenient WER', value: result.score.lenient_wer },
- ].map(({ label, value }) => (
+ ].filter(({ value }) => value != null).map(({ label, value }) => (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/speech-to-text/page.tsx` around lines 1724 - 1749, The UI renders
percentages using result.score properties but only checks result.score exists,
so undefined values (result.score.wip, result.score.wer, result.score.cer,
result.score.lenient_wer) can produce NaN%; update the rendering in the Accuracy
block and the Errors mapping to guard each metric: either filter the errors
array before .map to remove entries with value == null/undefined, or replace
uses like result.score.wip / value with a defensive expression (e.g., check
value != null and only then render (value * 100).toFixed(1) + '%' else render
'-' ), and make the color logic also handle undefined (use a default color when
value is missing) so the mapping for [{label, value}] and the Accuracy span both
never compute with undefined.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
app/speech-to-text/page.tsx (1)
1817-1834:⚠️ Potential issue | 🟡 MinorGuard each score metric before formatting percentages.
Line 1821 and Line 1833 can render
NaN%when any metric is missing (undefined * 100). This issue was already raised previously and is still present.Suggested fix
- <div className="flex justify-between gap-2"> - <span style={{ color: colors.text.secondary }}>Accuracy</span> - <span className="font-mono font-medium" style={{ color: result.score.wip >= 0.9 ? colors.status.success : result.score.wip >= 0.7 ? '#ca8a04' : colors.status.error }}>{(result.score.wip * 100).toFixed(1)}%</span> - </div> + <div className="flex justify-between gap-2"> + <span style={{ color: colors.text.secondary }}>Accuracy</span> + <span + className="font-mono font-medium" + style={{ + color: + result.score.wip == null + ? colors.text.secondary + : result.score.wip >= 0.9 + ? colors.status.success + : result.score.wip >= 0.7 + ? '#ca8a04' + : colors.status.error + }} + > + {result.score.wip == null ? '-' : `${(result.score.wip * 100).toFixed(1)}%`} + </span> + </div> ... - ].map(({ label, value }) => ( + ].filter(({ value }) => value != null).map(({ label, value }) => (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` around lines 1817 - 1834, The rendering of percentage strings uses numeric multiplications on possibly undefined values (e.g., result.score.wip, result.score.wer, result.score.cer, result.score.lenient_wer) which can produce NaN%; update the JSX that displays these percentages (the Accuracy span using result.score.wip and each Errors row using value) to guard each metric before formatting: check for a finite number (Number.isFinite or a nullish check) and only render (value * 100).toFixed(1) + '%' when valid, otherwise render a safe placeholder like '-' or 'N/A'; make this change for the Accuracy display and for the mapped error metrics so missing values no longer produce NaN.
🧹 Nitpick comments (1)
app/speech-to-text/page.tsx (1)
1680-1682: Avoid recomputing word diff in the row render path.Lines 1680-1682 run
computeWordDiff(...)during render for every result row. Since this is a DP diff, rerenders (e.g., typing comments) can become expensive with larger result sets.Suggested refactor
-import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef, useMemo } from 'react';+ const diffByResultId = useMemo(() => { + const map = new Map<number, DiffSegment[]>(); + for (const r of results) { + if (r.groundTruth && r.transcription) { + map.set(r.id, computeWordDiff(r.groundTruth, r.transcription)); + } + } + return map; + }, [results]);- const segments = computeWordDiff(result.groundTruth, result.transcription); + const segments = diffByResultId.get(result.id) ?? [];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` around lines 1680 - 1682, You're calling computeWordDiff(result.groundTruth, result.transcription) inside the row render path which recomputes the DP diff on every render; instead memoize or precompute diffs keyed by result.id. For example, create a memoized map of id->segments at a higher level (using useMemo over the results array) or call useMemo inside the row component with [result.groundTruth, result.transcription] as deps, then replace direct computeWordDiff(...) in the JSX with the memoized value; reference computeWordDiff, expandedTranscriptions, result.id, result.groundTruth and result.transcription to locate and update the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/speech-to-text/page.tsx`:
- Around line 1598-1604: Replace the clickable span used as the score-info
trigger with a real button element: swap the <span> (the element that uses
onClick to toggle setOpenScoreInfo(openScoreInfo ? null : 'accuracy')) to a
<button type="button">, preserve the className and inline styles, keep the
existing onClick handler (calling setOpenScoreInfo), and remove any manual
role/keyboard handling since a button is keyboard-accessible by default; also
add an accessible label such as aria-label="Toggle accuracy info" (or
aria-expanded tied to openScoreInfo) so screen readers know the control's
purpose.
- Around line 1407-1408: The option element for the Gemini model has a
mismatched value and label which causes the form to submit the wrong model and
conflicts with the selectedModel state; update the <option> so its value matches
its visible label (e.g., set value="gemini-2.5-pro" to match "gemini-2.5-pro")
and ensure the component's selectedModel default/initial state uses the same
model string (selectedModel) so the UI and submitted value stay consistent with
the intended model.
---
Duplicate comments:
In `@app/speech-to-text/page.tsx`:
- Around line 1817-1834: The rendering of percentage strings uses numeric
multiplications on possibly undefined values (e.g., result.score.wip,
result.score.wer, result.score.cer, result.score.lenient_wer) which can produce
NaN%; update the JSX that displays these percentages (the Accuracy span using
result.score.wip and each Errors row using value) to guard each metric before
formatting: check for a finite number (Number.isFinite or a nullish check) and
only render (value * 100).toFixed(1) + '%' when valid, otherwise render a safe
placeholder like '-' or 'N/A'; make this change for the Accuracy display and for
the mapped error metrics so missing values no longer produce NaN.
---
Nitpick comments:
In `@app/speech-to-text/page.tsx`:
- Around line 1680-1682: You're calling computeWordDiff(result.groundTruth,
result.transcription) inside the row render path which recomputes the DP diff on
every render; instead memoize or precompute diffs keyed by result.id. For
example, create a memoized map of id->segments at a higher level (using useMemo
over the results array) or call useMemo inside the row component with
[result.groundTruth, result.transcription] as deps, then replace direct
computeWordDiff(...) in the JSX with the memoized value; reference
computeWordDiff, expandedTranscriptions, result.id, result.groundTruth and
result.transcription to locate and update the code.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0dc48404-06a3-4b8b-a470-a8ad34edddc2
📒 Files selected for processing (1)
app/speech-to-text/page.tsx
| <span | ||
| className="inline-flex items-center justify-center w-3.5 h-3.5 rounded-full text-[9px] font-bold cursor-pointer shrink-0" | ||
| style={{ backgroundColor: colors.bg.primary, border: `1px solid ${colors.border}`, color: colors.text.secondary }} | ||
| onClick={(e) => { e.stopPropagation(); setOpenScoreInfo(openScoreInfo ? null : 'accuracy'); }} | ||
| > | ||
| i | ||
| </span> |
There was a problem hiding this comment.
Use a real button for the score-info trigger.
Line 1598 uses a clickable span, which is not keyboard-accessible by default. Replace it with a button and add ARIA attributes.
Suggested fix
- <span
+ <button
+ type="button"
+ aria-label="Open score metric info"
+ aria-expanded={Boolean(openScoreInfo)}
className="inline-flex items-center justify-center w-3.5 h-3.5 rounded-full text-[9px] font-bold cursor-pointer shrink-0"
style={{ backgroundColor: colors.bg.primary, border: `1px solid ${colors.border}`, color: colors.text.secondary }}
onClick={(e) => { e.stopPropagation(); setOpenScoreInfo(openScoreInfo ? null : 'accuracy'); }}
>
i
- </span>
+ </button>📝 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.
| <span | |
| className="inline-flex items-center justify-center w-3.5 h-3.5 rounded-full text-[9px] font-bold cursor-pointer shrink-0" | |
| style={{ backgroundColor: colors.bg.primary, border: `1px solid ${colors.border}`, color: colors.text.secondary }} | |
| onClick={(e) => { e.stopPropagation(); setOpenScoreInfo(openScoreInfo ? null : 'accuracy'); }} | |
| > | |
| i | |
| </span> | |
| <button | |
| type="button" | |
| aria-label="Open score metric info" | |
| aria-expanded={Boolean(openScoreInfo)} | |
| className="inline-flex items-center justify-center w-3.5 h-3.5 rounded-full text-[9px] font-bold cursor-pointer shrink-0" | |
| style={{ backgroundColor: colors.bg.primary, border: `1px solid ${colors.border}`, color: colors.text.secondary }} | |
| onClick={(e) => { e.stopPropagation(); setOpenScoreInfo(openScoreInfo ? null : 'accuracy'); }} | |
| > | |
| i | |
| </button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/speech-to-text/page.tsx` around lines 1598 - 1604, Replace the clickable
span used as the score-info trigger with a real button element: swap the <span>
(the element that uses onClick to toggle setOpenScoreInfo(openScoreInfo ? null :
'accuracy')) to a <button type="button">, preserve the className and inline
styles, keep the existing onClick handler (calling setOpenScoreInfo), and remove
any manual role/keyboard handling since a button is keyboard-accessible by
default; also add an accessible label such as aria-label="Toggle accuracy info"
(or aria-expanded tied to openScoreInfo) so screen readers know the control's
purpose.
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)
app/speech-to-text/page.tsx (1)
1885-1887:⚠️ Potential issue | 🟡 MinorNon-null assertion on potentially null
is_correctmay cause unintended behavior.The
onBlurhandler callsupdateFeedback(result.id, result.is_correct!, e.target.value). If the user edits a comment without first setting the "Is Correct" dropdown,result.is_correctisnull. The!assertion doesn't change the runtime value—it will still passnullto the API, which may not be the intended behavior.🛡️ Proposed fix to guard against null
onBlur={(e) => { - updateFeedback(result.id, result.is_correct!, e.target.value); + if (result.is_correct !== null) { + updateFeedback(result.id, result.is_correct, e.target.value); + } }}Alternatively, if you want to allow comment updates without requiring
is_correctto be set, refactorupdateFeedbackto acceptisCorrect: boolean | nulland adjust the payload logic accordingly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` around lines 1885 - 1887, The onBlur handler passes result.is_correct with a non-null assertion to updateFeedback which can still be null at runtime; change the call site in the onBlur handler to guard against null (e.g., derive a boolean default or explicitly pass null) and update the updateFeedback signature/consumer accordingly: either pass a default boolean (true/false) instead of result.is_correct! or change updateFeedback to accept isCorrect: boolean | null and handle null in its payload logic. Locate the onBlur in the component and the updateFeedback function to implement the chosen approach (refer to onBlur, updateFeedback, and result.is_correct).
🧹 Nitpick comments (1)
app/speech-to-text/page.tsx (1)
1605-1656: Consider extracting metrics definitions to a constant.The metrics array (lines 1606-1611) with titles, descriptions, formulas, and examples is defined inline within the render function. Extracting this to a module-level constant would improve readability and avoid recreating the array on each render.
♻️ Example extraction
// At module level, outside the component const SCORE_METRICS = [ { key: 'accuracy', title: 'Accuracy (Word Information Preserved)', desc: 'Measures how much of the original information was correctly captured.', formula: 'WIP = (C / N) × (C / H)', formulaDesc: 'C = correct words\nN = total words in reference\nH = total words in hypothesis', example: `Reference: ...`, // truncated direction: 'Higher is better.', isHigherBetter: true, }, // ... other metrics ] as const;Then reference
SCORE_METRICSin the component.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` around lines 1605 - 1656, The inline metrics array is recreated on every render; extract it to a module-level constant (e.g., SCORE_METRICS) and use that constant in the component instead of the local metrics variable. Move the array currently defined where openScoreInfo is used into a top-level const SCORE_METRICS (preserving keys like 'accuracy','wer','cer','lenient_wer' and fields title, desc, formula, formulaDesc, example, direction, directionColor), then replace metrics with SCORE_METRICS and keep the consumer logic (currentIdx = SCORE_METRICS.findIndex(...), setOpenScoreInfo, rendering tabs/content) unchanged to avoid reallocation on each render.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@app/speech-to-text/page.tsx`:
- Around line 1885-1887: The onBlur handler passes result.is_correct with a
non-null assertion to updateFeedback which can still be null at runtime; change
the call site in the onBlur handler to guard against null (e.g., derive a
boolean default or explicitly pass null) and update the updateFeedback
signature/consumer accordingly: either pass a default boolean (true/false)
instead of result.is_correct! or change updateFeedback to accept isCorrect:
boolean | null and handle null in its payload logic. Locate the onBlur in the
component and the updateFeedback function to implement the chosen approach
(refer to onBlur, updateFeedback, and result.is_correct).
---
Nitpick comments:
In `@app/speech-to-text/page.tsx`:
- Around line 1605-1656: The inline metrics array is recreated on every render;
extract it to a module-level constant (e.g., SCORE_METRICS) and use that
constant in the component instead of the local metrics variable. Move the array
currently defined where openScoreInfo is used into a top-level const
SCORE_METRICS (preserving keys like 'accuracy','wer','cer','lenient_wer' and
fields title, desc, formula, formulaDesc, example, direction, directionColor),
then replace metrics with SCORE_METRICS and keep the consumer logic (currentIdx
= SCORE_METRICS.findIndex(...), setOpenScoreInfo, rendering tabs/content)
unchanged to avoid reallocation on each render.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3c79a772-b973-45c1-8716-6cadb928c1be
📒 Files selected for processing (1)
app/speech-to-text/page.tsx
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (2)
app/speech-to-text/page.tsx (2)
1526-1526:⚠️ Potential issue | 🔴 CriticalFix model option value/label mismatch (still unresolved).
Line 1526 displays
gemini-2.5-probut submitsgemini-2.0-flash-exp, so evaluations can run with the wrong model.Suggested fix
- <option value="gemini-2.0-flash-exp">gemini-2.5-pro</option> + <option value="gemini-2.5-pro">gemini-2.5-pro</option>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` at line 1526, The select option for the model has a mismatched value and label (the <option> element in page.tsx currently has value="gemini-2.0-flash-exp" but displays "gemini-2.5-pro"); update the <option> so the value matches the displayed label (e.g., set the value to "gemini-2.5-pro" or change the visible text to match the intended value) to ensure the selected model is submitted correctly.
1936-1953:⚠️ Potential issue | 🟡 MinorGuard per-metric score fields before percentage math (still unresolved).
Lines 1940/1952 compute percentages even when individual metrics are missing, which can render
NaN%.Suggested defensive pattern
- <div className="flex justify-between gap-2"> - <span style={{ color: colors.text.secondary }}>Accuracy</span> - <span className="font-mono font-medium" style={{ color: result.score.wip >= 0.9 ? colors.status.success : result.score.wip >= 0.7 ? '#ca8a04' : colors.status.error }}>{(result.score.wip * 100).toFixed(1)}%</span> - </div> + {result.score.wip != null && ( + <div className="flex justify-between gap-2"> + <span style={{ color: colors.text.secondary }}>Accuracy</span> + <span className="font-mono font-medium" style={{ color: result.score.wip >= 0.9 ? colors.status.success : result.score.wip >= 0.7 ? '#ca8a04' : colors.status.error }}>{(result.score.wip * 100).toFixed(1)}%</span> + </div> + )} ... - ].map(({ label, value }) => ( + ].filter(({ value }) => value != null).map(({ label, value }) => (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` around lines 1936 - 1953, The UI computes percentage strings directly from result.score and its metric fields (result.score.wip, result.score.wer, result.score.cer, result.score.lenient_wer) which can produce NaN% when a metric is undefined; add guards that default missing numeric scores to 0 (or render a placeholder like "—") before multiplying/formatting, e.g., normalize result.score into local variables (wip, wer, cer, lenient_wer) or use a safe helper (Number(...) || 0) inside the rendering for the Accuracy and each Errors map item so (value * 100).toFixed(1) never receives undefined/NaN and the color logic also handles missing values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/api/languages/route.ts`:
- Around line 14-19: The handler currently assumes every upstream response is
JSON (uses response.json()) and returns the raw error object in the catch, which
can be non-serializable; update the logic around response parsing and error
normalization: when receiving the upstream Response (the variable where
response.json() is called), inspect content-type (e.g.,
response.headers.get('content-type')) and if it contains 'application/json'
parse via response.json(), otherwise read via response.text() and wrap that as
bodyText or as a parsed message; for non-2xx statuses include the parsed body
(body or bodyText) and the response.status in the NextResponse.json payload; in
the catch block replace returning raw error with a normalized object like {
success: false, error: String(error) } or { message: (error?.message ??
String(error)) } so the JSON response is always serializable and consistent
(apply changes around the code referencing response.json(), data, and
NextResponse.json).
- Around line 8-12: The fetch to `${backendUrl}/api/v1/languages` is unbounded
and can hang; wrap the fetch call in an AbortController with a short timeout
(e.g., configurable default like 5s), pass controller.signal into fetch (the
call in route.ts where response is awaited), clear the timeout on success, and
handle the abort/timeout error path to return an appropriate error response
instead of letting the route hang. Ensure you reference the existing fetch call
and use the AbortController instance and timeout ID to manage cancellation and
cleanup.
In `@app/speech-to-text/page.tsx`:
- Around line 427-432: The language selection is being clobbered: when loading
languages you call setDatasetLanguageId unconditionally and elsewhere you
hardcode setDatasetLanguageId(1); update the logic so you only
setDatasetLanguageId to languagesList[0].id if there is no existing valid
datasetLanguageId (or if the current id is not found in languagesList), and
remove/replace any hardcoded setDatasetLanguageId(1) calls with logic that
preserves the user-selected id or falls back to a valid id from languagesList;
search for uses of setDatasetLanguageId, datasetLanguageId, and languagesList to
apply this conditional/defaulting behavior.
- Line 676: The console.log in page.tsx that prints "Create dataset payload:"
with JSON.stringify(payload...) exposes user transcription text (payload.samples
and payload.samples[].ground_truth); remove this production log or gate it
behind a non-production/debug check (e.g., process.env.NODE_ENV !==
'production') and if you need telemetry log only safe metadata (counts, sample
IDs, or redacted samples) instead of full ground_truth. Update the logging usage
where payload is assembled (reference payload, samples, and ground_truth) to
either redact the ground_truth fields or replace the console.log with a
sanitized summary before deploying.
---
Duplicate comments:
In `@app/speech-to-text/page.tsx`:
- Line 1526: The select option for the model has a mismatched value and label
(the <option> element in page.tsx currently has value="gemini-2.0-flash-exp" but
displays "gemini-2.5-pro"); update the <option> so the value matches the
displayed label (e.g., set the value to "gemini-2.5-pro" or change the visible
text to match the intended value) to ensure the selected model is submitted
correctly.
- Around line 1936-1953: The UI computes percentage strings directly from
result.score and its metric fields (result.score.wip, result.score.wer,
result.score.cer, result.score.lenient_wer) which can produce NaN% when a metric
is undefined; add guards that default missing numeric scores to 0 (or render a
placeholder like "—") before multiplying/formatting, e.g., normalize
result.score into local variables (wip, wer, cer, lenient_wer) or use a safe
helper (Number(...) || 0) inside the rendering for the Accuracy and each Errors
map item so (value * 100).toFixed(1) never receives undefined/NaN and the color
logic also handles missing values.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ca8fe354-dec7-455b-a774-490e24a694ac
📒 Files selected for processing (2)
app/api/languages/route.tsapp/speech-to-text/page.tsx
| const response = await fetch(`${backendUrl}/api/v1/languages`, { | ||
| headers: { | ||
| 'X-API-KEY': apiKey || '', | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Add a timeout/abort signal for the upstream fetch.
Line 8 performs an unbounded external call; if the backend hangs, this route can hang too and tie up server resources.
Suggested hardening
try {
- const response = await fetch(`${backendUrl}/api/v1/languages`, {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 10000);
+ const response = await fetch(`${backendUrl}/api/v1/languages`, {
headers: {
'X-API-KEY': apiKey || '',
},
+ signal: controller.signal,
});
+ clearTimeout(timeout);📝 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.
| const response = await fetch(`${backendUrl}/api/v1/languages`, { | |
| headers: { | |
| 'X-API-KEY': apiKey || '', | |
| }, | |
| }); | |
| const controller = new AbortController(); | |
| const timeout = setTimeout(() => controller.abort(), 10000); | |
| const response = await fetch(`${backendUrl}/api/v1/languages`, { | |
| headers: { | |
| 'X-API-KEY': apiKey || '', | |
| }, | |
| signal: controller.signal, | |
| }); | |
| clearTimeout(timeout); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/api/languages/route.ts` around lines 8 - 12, The fetch to
`${backendUrl}/api/v1/languages` is unbounded and can hang; wrap the fetch call
in an AbortController with a short timeout (e.g., configurable default like 5s),
pass controller.signal into fetch (the call in route.ts where response is
awaited), clear the timeout on success, and handle the abort/timeout error path
to return an appropriate error response instead of letting the route hang.
Ensure you reference the existing fetch call and use the AbortController
instance and timeout ID to manage cancellation and cleanup.
| const data = await response.json(); | ||
| return NextResponse.json(data, { status: response.status }); | ||
| } catch (error) { | ||
| return NextResponse.json( | ||
| { success: false, error: error, data: null }, | ||
| { status: 500 } |
There was a problem hiding this comment.
Handle non-JSON upstream bodies and normalize error output.
Line 14 assumes JSON for every backend response, and Line 18 returns raw error. Both make failures brittle and inconsistent.
Suggested fix
- const data = await response.json();
- return NextResponse.json(data, { status: response.status });
+ const contentType = response.headers.get('content-type') || '';
+ const data = contentType.includes('application/json')
+ ? await response.json()
+ : { success: false, error: await response.text(), data: null };
+ return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json(
- { success: false, error: error, data: null },
+ {
+ success: false,
+ error: error instanceof Error ? error.message : 'Failed to fetch languages',
+ data: null,
+ },
{ status: 500 }
);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/api/languages/route.ts` around lines 14 - 19, The handler currently
assumes every upstream response is JSON (uses response.json()) and returns the
raw error object in the catch, which can be non-serializable; update the logic
around response parsing and error normalization: when receiving the upstream
Response (the variable where response.json() is called), inspect content-type
(e.g., response.headers.get('content-type')) and if it contains
'application/json' parse via response.json(), otherwise read via response.text()
and wrap that as bodyText or as a parsed message; for non-2xx statuses include
the parsed body (body or bodyText) and the response.status in the
NextResponse.json payload; in the catch block replace returning raw error with a
normalized object like { success: false, error: String(error) } or { message:
(error?.message ?? String(error)) } so the JSON response is always serializable
and consistent (apply changes around the code referencing response.json(), data,
and NextResponse.json).
| if (languagesList.length > 0) { | ||
| setLanguages(languagesList); | ||
| // Default dataset language to first available if not already set | ||
| if (languagesList[0]?.id) { | ||
| setDatasetLanguageId(languagesList[0].id); | ||
| } |
There was a problem hiding this comment.
Language selection is being reset/clobbered unintentionally.
Line 430-431 always overwrites datasetLanguageId after load, and Lines 699/1325 hardcode reset to 1. This can reset user choice and break when ID 1 is not present.
Suggested fix
- if (languagesList.length > 0) {
- setLanguages(languagesList);
- // Default dataset language to first available if not already set
- if (languagesList[0]?.id) {
- setDatasetLanguageId(languagesList[0].id);
- }
+ if (languagesList.length > 0) {
+ setLanguages(languagesList);
+ setDatasetLanguageId(prev =>
+ languagesList.some(l => l.id === prev) ? prev : languagesList[0].id
+ );
} else {
setLanguages(DEFAULT_LANGUAGES);
}- setDatasetLanguageId(1);
+ setDatasetLanguageId(languages[0]?.id ?? DEFAULT_LANGUAGES[0].id);- setDatasetLanguageId(1);
+ setDatasetLanguageId(languages[0]?.id ?? DEFAULT_LANGUAGES[0].id);Also applies to: 509-514, 699-699, 1325-1325
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/speech-to-text/page.tsx` around lines 427 - 432, The language selection
is being clobbered: when loading languages you call setDatasetLanguageId
unconditionally and elsewhere you hardcode setDatasetLanguageId(1); update the
logic so you only setDatasetLanguageId to languagesList[0].id if there is no
existing valid datasetLanguageId (or if the current id is not found in
languagesList), and remove/replace any hardcoded setDatasetLanguageId(1) calls
with logic that preserves the user-selected id or falls back to a valid id from
languagesList; search for uses of setDatasetLanguageId, datasetLanguageId, and
languagesList to apply this conditional/defaulting behavior.
| language_id: datasetLanguageId, | ||
| samples: samples, | ||
| }; | ||
| console.log('Create dataset payload:', JSON.stringify(payload, null, 2)); |
There was a problem hiding this comment.
Avoid logging dataset payloads with transcription content in production paths.
Line 676 logs samples including ground_truth, which is user-provided text and should not be routinely exposed in console logs.
Suggested fix
- console.log('Create dataset payload:', JSON.stringify(payload, null, 2));
+ // Optional: keep only non-sensitive debug metadata if needed
+ // console.debug('Create dataset payload summary:', { name: payload.name, sampleCount: payload.samples.length, languageId: payload.language_id });📝 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.
| console.log('Create dataset payload:', JSON.stringify(payload, null, 2)); | |
| // Optional: keep only non-sensitive debug metadata if needed | |
| // console.debug('Create dataset payload summary:', { name: payload.name, sampleCount: payload.samples.length, languageId: payload.language_id }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/speech-to-text/page.tsx` at line 676, The console.log in page.tsx that
prints "Create dataset payload:" with JSON.stringify(payload...) exposes user
transcription text (payload.samples and payload.samples[].ground_truth); remove
this production log or gate it behind a non-production/debug check (e.g.,
process.env.NODE_ENV !== 'production') and if you need telemetry log only safe
metadata (counts, sample IDs, or redacted samples) instead of full ground_truth.
Update the logging usage where payload is assembled (reference payload, samples,
and ground_truth) to either redact the ground_truth fields or replace the
console.log with a sanitized summary before deploying.
There was a problem hiding this comment.
♻️ Duplicate comments (4)
app/speech-to-text/page.tsx (4)
427-432:⚠️ Potential issue | 🟠 MajorPreserve the selected language instead of overwriting it.
loadLanguages()still resetsdatasetLanguageIdto the first returned option on every refresh, and the create/cancel flows still hardcode it back to1. That clobbers the user's choice and breaks when ID1is not valid in the fetched language list.Suggested fix
if (languagesList.length > 0) { setLanguages(languagesList); - // Default dataset language to first available if not already set - if (languagesList[0]?.id) { - setDatasetLanguageId(languagesList[0].id); - } + setDatasetLanguageId(prev => + languagesList.some(lang => lang.id === prev) ? prev : languagesList[0].id + ); } else { setLanguages(DEFAULT_LANGUAGES); }- setDatasetLanguageId(1); + setDatasetLanguageId(languages[0]?.id ?? DEFAULT_LANGUAGES[0].id);- setDatasetLanguageId(1); + setDatasetLanguageId(languages[0]?.id ?? DEFAULT_LANGUAGES[0].id);Also applies to: 509-514, 699-699, 1325-1325
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` around lines 427 - 432, The loadLanguages flow currently unconditionally sets datasetLanguageId to languagesList[0].id and other flows hardcode id `1`, which overwrites a user's selection; update the logic in loadLanguages (where setLanguages and setDatasetLanguageId are used) to only call setDatasetLanguageId when no datasetLanguageId is already selected or when the current datasetLanguageId is not present in the fetched languagesList; also find and replace the hardcoded `1` assignments in the create/cancel handlers (the occurrences referenced around lines 509-514, 699, and 1325) to use the validated default from languagesList or leave the existing selection intact instead of forcing `1`. Ensure you reference setLanguages, setDatasetLanguageId and the create/cancel handlers when making the change.
1936-1954:⚠️ Potential issue | 🟡 MinorGuard each metric before formatting percentages.
result.scorebeing present does not guaranteewip,wer,cer, orlenient_werare defined. Missing fields here still renderNaN%.Suggested fix
- <div className="flex justify-between gap-2"> - <span style={{ color: colors.text.secondary }}>Accuracy</span> - <span className="font-mono font-medium" style={{ color: result.score.wip >= 0.9 ? colors.status.success : result.score.wip >= 0.7 ? '#ca8a04' : colors.status.error }}>{(result.score.wip * 100).toFixed(1)}%</span> - </div> + {result.score.wip != null && ( + <div className="flex justify-between gap-2"> + <span style={{ color: colors.text.secondary }}>Accuracy</span> + <span className="font-mono font-medium" style={{ color: result.score.wip >= 0.9 ? colors.status.success : result.score.wip >= 0.7 ? '#ca8a04' : colors.status.error }}>{(result.score.wip * 100).toFixed(1)}%</span> + </div> + )}{[ { label: 'WER', value: result.score.wer }, { label: 'CER', value: result.score.cer }, { label: 'Lenient WER', value: result.score.lenient_wer }, - ].map(({ label, value }) => ( + ].filter(({ value }) => value != null).map(({ label, value }) => (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` around lines 1936 - 1954, The Accuracy and Errors blocks assume numeric fields exist on result.score (wip, wer, cer, lenient_wer) and can produce "NaN%"; update the rendering in page.tsx to guard each metric: for example, check Number.isFinite(result.score?.wip) before computing (result.score.wip * 100).toFixed(1) and render a fallback like "-" (or 0.0%) when missing, and apply the same guard in the mapped metrics array (the entries for wer, cer, lenient_wer) so color selection and percentage formatting only run on valid numbers; adjust the Accuracy span and the .map callback that references value so they use the guarded value and fallback consistently.
676-676:⚠️ Potential issue | 🟠 MajorRemove the raw dataset payload log.
This logs
samples[].ground_truthduring a normal create flow. That is user transcription content and should not be emitted to the console outside a sanitized debug path.Suggested fix
- console.log('Create dataset payload:', JSON.stringify(payload, null, 2)); + if (process.env.NODE_ENV !== 'production') { + console.debug('Create dataset payload summary:', { + name: payload.name, + sampleCount: payload.samples.length, + languageId: payload.language_id, + }); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` at line 676, Remove the raw dataset payload console.log that prints JSON.stringify(payload...) which leaks samples[].ground_truth (user transcription content); in the function where that log appears (look for the create dataset / payload handling and the local variable payload), either delete the console.log entirely or replace it with a sanitized debug message that only reports non-sensitive metadata (e.g., sample count, dataset id, or keys) and guard it behind an explicit debug flag so transcriptions are never logged in production.
1717-1723:⚠️ Potential issue | 🟠 MajorUse a real button for the score-info trigger.
The clickable
spanis still not keyboard-accessible, so the metric help popover is unreachable for keyboard and assistive-tech users.Suggested fix
- <span + <button + type="button" + aria-label="Toggle score metric info" + aria-expanded={Boolean(openScoreInfo)} className="inline-flex items-center justify-center w-3.5 h-3.5 rounded-full text-[9px] font-bold cursor-pointer shrink-0" style={{ backgroundColor: colors.bg.primary, border: `1px solid ${colors.border}`, color: colors.text.secondary }} onClick={(e) => { e.stopPropagation(); setOpenScoreInfo(openScoreInfo ? null : 'accuracy'); }} > i - </span> + </button>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` around lines 1717 - 1723, Replace the non-interactive span used as the score-info trigger with a real <button> element (preserve className and inline styles) so it's keyboard-accessible; keep the existing onClick handler that calls setOpenScoreInfo(openScoreInfo ? null : 'accuracy') but call e.stopPropagation() there as before and add type="button". Also add appropriate accessibility attributes such as aria-expanded={openScoreInfo === 'accuracy'} and either aria-controls pointing to the popover ID or an aria-label (e.g., "Score info") so assistive tech can identify the control; ensure you remove any role="button" or custom onKeyDown hacks since a native button handles keyboard activation.
🧹 Nitpick comments (1)
app/speech-to-text/page.tsx (1)
1799-1801: Avoid recomputing diffs on every table rerender.
computeWordDiff()runs insideresults.map(...), so typing in one comment or toggling any control recomputes every row's diff. For larger runs, this will make the results table noticeably laggy. Precompute when results load, or extract a memoized row component and memoize the diff bygroundTruth/transcription.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/speech-to-text/page.tsx` around lines 1799 - 1801, The diff computation computeWordDiff(result.groundTruth, result.transcription) is being run inline inside results.map, causing a full recompute on every render; fix by moving diff calculation out of the render path — either precompute diffs when results load (e.g., map results to include a precomputed diff field) or create a memoized row component (e.g., ResultRow) that memoizes computeWordDiff by groundTruth/transcription (useMemo keyed on result.groundTruth and result.transcription or React.memo for the row) and keep expandedTranscriptions handling in the parent, using result.id to toggle expansion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@app/speech-to-text/page.tsx`:
- Around line 427-432: The loadLanguages flow currently unconditionally sets
datasetLanguageId to languagesList[0].id and other flows hardcode id `1`, which
overwrites a user's selection; update the logic in loadLanguages (where
setLanguages and setDatasetLanguageId are used) to only call
setDatasetLanguageId when no datasetLanguageId is already selected or when the
current datasetLanguageId is not present in the fetched languagesList; also find
and replace the hardcoded `1` assignments in the create/cancel handlers (the
occurrences referenced around lines 509-514, 699, and 1325) to use the validated
default from languagesList or leave the existing selection intact instead of
forcing `1`. Ensure you reference setLanguages, setDatasetLanguageId and the
create/cancel handlers when making the change.
- Around line 1936-1954: The Accuracy and Errors blocks assume numeric fields
exist on result.score (wip, wer, cer, lenient_wer) and can produce "NaN%";
update the rendering in page.tsx to guard each metric: for example, check
Number.isFinite(result.score?.wip) before computing (result.score.wip *
100).toFixed(1) and render a fallback like "-" (or 0.0%) when missing, and apply
the same guard in the mapped metrics array (the entries for wer, cer,
lenient_wer) so color selection and percentage formatting only run on valid
numbers; adjust the Accuracy span and the .map callback that references value so
they use the guarded value and fallback consistently.
- Line 676: Remove the raw dataset payload console.log that prints
JSON.stringify(payload...) which leaks samples[].ground_truth (user
transcription content); in the function where that log appears (look for the
create dataset / payload handling and the local variable payload), either delete
the console.log entirely or replace it with a sanitized debug message that only
reports non-sensitive metadata (e.g., sample count, dataset id, or keys) and
guard it behind an explicit debug flag so transcriptions are never logged in
production.
- Around line 1717-1723: Replace the non-interactive span used as the score-info
trigger with a real <button> element (preserve className and inline styles) so
it's keyboard-accessible; keep the existing onClick handler that calls
setOpenScoreInfo(openScoreInfo ? null : 'accuracy') but call e.stopPropagation()
there as before and add type="button". Also add appropriate accessibility
attributes such as aria-expanded={openScoreInfo === 'accuracy'} and either
aria-controls pointing to the popover ID or an aria-label (e.g., "Score info")
so assistive tech can identify the control; ensure you remove any role="button"
or custom onKeyDown hacks since a native button handles keyboard activation.
---
Nitpick comments:
In `@app/speech-to-text/page.tsx`:
- Around line 1799-1801: The diff computation
computeWordDiff(result.groundTruth, result.transcription) is being run inline
inside results.map, causing a full recompute on every render; fix by moving diff
calculation out of the render path — either precompute diffs when results load
(e.g., map results to include a precomputed diff field) or create a memoized row
component (e.g., ResultRow) that memoizes computeWordDiff by
groundTruth/transcription (useMemo keyed on result.groundTruth and
result.transcription or React.memo for the row) and keep expandedTranscriptions
handling in the parent, using result.id to toggle expansion.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5ba535a6-47d9-41ec-b80e-fa96fca41321
📒 Files selected for processing (1)
app/speech-to-text/page.tsx

Summary by CodeRabbit