Skip to content

TTS Evaluation: Metrics#66

Open
AkhileshNegi wants to merge 13 commits intomainfrom
enhancement/tts-evals-metric
Open

TTS Evaluation: Metrics#66
AkhileshNegi wants to merge 13 commits intomainfrom
enhancement/tts-evals-metric

Conversation

@AkhileshNegi
Copy link
Contributor

@AkhileshNegi AkhileshNegi commented Mar 12, 2026

Summary by CodeRabbit

  • New Features

    • Text-to-Speech evaluation feature now available, featuring dataset creation, evaluation runs, and result inspection with audio playback
    • Audio playback capability for TTS evaluation results
  • Design

    • Implemented centralized color system for consistent UI theming across pages
    • Refined table alignment and styling with improved visual hierarchy
    • Simplified tab navigation styling and layout
  • UI Improvements

    • Updated evaluations page with refined language, typography, and spacing adjustments

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

Adds comprehensive Text-to-Speech evaluation functionality including new API proxy route handlers, a complete interactive TTS evaluation page with dataset/run/result management, component theme system adoption, and UI refinements across existing evaluations interface.

Changes

Cohort / File(s) Summary
TTS API Route Handlers
app/api/evaluations/tts/datasets/route.ts, app/api/evaluations/tts/datasets/[dataset_id]/route.ts, app/api/evaluations/tts/runs/route.ts, app/api/evaluations/tts/runs/[run_id]/route.ts, app/api/evaluations/tts/results/[result_id]/route.ts
New Next.js route handlers implementing proxy endpoints to backend TTS API. GET/POST endpoints for dataset management with API key validation. GET/PATCH endpoints for run retrieval and result updates. All routes include X-API-KEY header propagation, error handling, and backend URL configuration via environment variable.
STT Route Enhancement
app/api/evaluations/stt/datasets/[dataset_id]/route.ts
Added authorization guard requiring X-API-KEY header (returns 401 if missing). Removed fallback empty string for API key; ensures header is always propagated to backend. Simplified URL construction with inline ternary.
Component Styling & UI
app/components/DetailedResultsTable.tsx, app/components/TabNavigation.tsx, app/components/Sidebar.tsx
Swapped "Answer" and "Ground Truth" column headers/data in DetailedResultsTable with alignment refinements. Replaced verbose tab styling in TabNavigation with centralized color token approach. Removed "Coming Soon" flag from Text-to-Speech item in Sidebar Evaluations menu.
Theme System Adoption
app/evaluations/page.tsx
Migrated hardcoded colors to centralized color tokens (colors.bg.primary/secondary, colors.border, colors.text.*). Updated layout, headers, cards, forms, and buttons to use themed colors. Refined typography and spacing. Renamed "jobs" to "runs" in UI labels.
New TTS Evaluation Page
app/text-to-speech/page.tsx
Complete Text-to-Speech evaluation interface with two-tab workflow. Datasets tab: create datasets with name, description, language selection, and dynamic text samples. Evaluations tab: configure runs, view run history, inspect results with audio playback and score editing. Includes language loading, dataset/run/result management, real-time polling, error modals, and audio player component.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • nishika26
  • Prajna1999

Poem

🐰 Hops into the speech machine,
Records and runs, a featured dream!
Colors theme the interface bright,
Results display with audio delight—
Datasets dance, evaluations sing! 🎵

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'TTS Evaluation: Metrics' is vague and doesn't clearly convey the actual scope of changes, which include new TTS evaluation endpoints, UI pages, table updates, and component styling improvements. Consider a more descriptive title that captures the main work, such as 'Add Text-to-Speech evaluation UI and API endpoints' or 'Implement TTS evaluation suite with datasets and results management'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch enhancement/tts-evals-metric
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (4)
app/components/TabNavigation.tsx (1)

23-39: Add tab semantics to match the visual treatment.

This now renders like a tablist, but assistive tech still sees a row of generic buttons. Adding role="tablist" on the container and role="tab" plus aria-selected on each button will make the active tab discoverable to screen readers.

♿ Suggested update
-    <div className="border-b flex gap-1 px-4" style={{ backgroundColor: colors.bg.primary, borderColor: colors.border }}>
+    <div
+      role="tablist"
+      className="border-b flex gap-1 px-4"
+      style={{ backgroundColor: colors.bg.primary, borderColor: colors.border }}
+    >
       {tabs.map((tab) => {
         const isActive = activeTab === tab.id;
         return (
           <button
             key={tab.id}
+            role="tab"
+            aria-selected={isActive}
+            tabIndex={isActive ? 0 : -1}
             onClick={() => onTabChange(tab.id)}
             className="px-4 py-2.5 text-sm font-medium border-b-2 transition-colors"
             style={{
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/TabNavigation.tsx` around lines 23 - 39, The tab UI in
TabNavigation renders as buttons but lacks ARIA semantics; update the container
(the div in TabNavigation where tabs.map is rendered) to include role="tablist",
and update each tab button (inside tabs.map where onClick={() =>
onTabChange(tab.id)} is defined) to include role="tab" and an aria-selected
attribute set to true when activeTab === tab.id (false otherwise); ensure the
active styling logic remains unchanged and keep the existing key, onClick, and
className props on the button.
app/text-to-speech/page.tsx (2)

405-411: Data loading effect dependencies.

The useEffect at lines 405-411 correctly triggers on apiKeys and activeTab changes. The load functions (loadLanguages, loadDatasets, loadRuns) are defined in the component body and capture the current apiKeys state, so they work correctly. However, ESLint's exhaustive-deps rule would flag this pattern.

Consider wrapping these functions with useCallback for consistency and to satisfy linting rules, though the current implementation is functionally correct.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/text-to-speech/page.tsx` around lines 405 - 411, The effect that calls
loadLanguages, loadDatasets, and loadRuns is fine functionally but will trigger
ESLint exhaustive-deps because those loaders are declared in the component
scope; wrap each loader (loadLanguages, loadDatasets, loadRuns) with useCallback
and include their stable identity in the dependency array used by the useEffect
(which currently depends on apiKeys and activeTab) so the linter is satisfied
while preserving the current behavior when activeTab or apiKeys change.

117-134: Potential performance issue with event listener re-attachment.

The onPlayToggle callback in the dependency array (line 134) will cause this effect to re-run whenever the parent component re-renders, since the callback at line 1424 is an inline arrow function that's recreated each time. This results in frequent listener detachment/reattachment.

Consider using a ref to store the callback to avoid unnecessary effect re-runs:

♻️ Stable callback pattern
 function AudioPlayerFromUrl({
   signedUrl,
   isPlaying,
   onPlayToggle,
   sampleLabel,
   durationSeconds,
   sizeBytes,
 }: {
   signedUrl: string;
   isPlaying: boolean;
   onPlayToggle: () => void;
   sampleLabel?: string;
   durationSeconds?: number | null;
   sizeBytes?: number | null;
 }) {
   const audioRef = useRef<HTMLAudioElement>(null);
+  const onPlayToggleRef = useRef(onPlayToggle);
+  onPlayToggleRef.current = onPlayToggle;
   const [duration, setDuration] = useState(0);
   const [currentTime, setCurrentTime] = useState(0);

   useEffect(() => {
     const audio = audioRef.current;
     if (!audio) return;

     const handleLoadedMetadata = () => setDuration(audio.duration);
     const handleTimeUpdate = () => setCurrentTime(audio.currentTime);
-    const handleEnded = () => onPlayToggle();
+    const handleEnded = () => onPlayToggleRef.current();

     audio.addEventListener('loadedmetadata', handleLoadedMetadata);
     audio.addEventListener('timeupdate', handleTimeUpdate);
     audio.addEventListener('ended', handleEnded);

     return () => {
       audio.removeEventListener('loadedmetadata', handleLoadedMetadata);
       audio.removeEventListener('timeupdate', handleTimeUpdate);
       audio.removeEventListener('ended', handleEnded);
     };
-  }, [onPlayToggle]);
+  }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/text-to-speech/page.tsx` around lines 117 - 134, The effect re-attaches
audio event listeners whenever the inline onPlayToggle prop changes; store the
callback in a stable ref (e.g., onPlayToggleRef) and update that ref inside a
separate effect so the audio event-effect can use onPlayToggleRef.current and
keep an empty dependency array (or only audioRef) to avoid re-registering
listeners; update the handleEnded closure to call onPlayToggleRef.current() and
ensure you set onPlayToggleRef.current = onPlayToggle when the prop changes.
app/evaluations/page.tsx (1)

428-447: Partial color system adoption in tooltip elements.

The tooltip button and content still use hardcoded hex values (e.g., #737373, #fafafa, #ffffff, #e5e5e5, #171717) instead of the centralized color tokens. While these values match the color system definitions, using the tokens directly would ensure consistency if the color scheme changes.

♻️ Example refactor for tooltip button
 <button
   onMouseEnter={() => setShowHowItWorksTooltip(true)}
   onMouseLeave={() => setShowHowItWorksTooltip(false)}
   className="p-1 rounded-full"
   style={{
-    color: '#737373',
-    backgroundColor: showHowItWorksTooltip ? '#fafafa' : 'transparent',
+    color: colors.text.secondary,
+    backgroundColor: showHowItWorksTooltip ? colors.bg.secondary : 'transparent',
     transition: 'all 0.15s ease'
   }}
 >

As per coding guidelines: "Use centralized colors from /app/lib/colors.ts for styling, not hardcoded hex values".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/evaluations/page.tsx` around lines 428 - 447, The tooltip button and
tooltip content are using hardcoded hex colors; replace these inline hex values
with the centralized color tokens exported from /app/lib/colors.ts (use the same
tokens that map to `#737373`, `#fafafa`, `#ffffff`, `#e5e5e5`, `#171717`) in the style
objects for the button and the tooltip div so the appearance of
showHowItWorksTooltip-driven styles (the button element that references
showHowItWorksTooltip and the tooltip container div with className "absolute
left-0 top-full...") use the shared color constants instead of literal hex
strings; update the imports and replace the style properties (color,
backgroundColor, borderColor) to use those token names.
🤖 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/evaluations/tts/datasets/route.ts`:
- Around line 3-15: The GET handler in this route proxies an empty X-API-KEY to
the backend instead of rejecting unauthenticated requests; update the GET
function to check request.headers.get('X-API-KEY') (same as POST and the other
GET in evaluations/tts/datasets/[dataset_id]/route.ts) and return a 401
NextResponse when apiKey is missing or falsy, avoiding the backend fetch; only
perform the fetch when apiKey is present and include the header value in the
proxied request.

In `@app/api/evaluations/tts/results/`[result_id]/route.ts:
- Around line 3-19: The GET handler in route.ts currently reads X-API-KEY into
the apiKey variable and forwards an empty string to the backend; update the GET
function to fail fast like PATCH by checking request.headers.get('X-API-KEY')
(the apiKey variable) and if missing return a 401 NextResponse with a JSON error
body immediately, avoiding forwarding empty keys to the backend and keeping GET
and PATCH response shapes consistent.

In `@app/api/evaluations/tts/runs/`[run_id]/route.ts:
- Around line 8-23: The handler currently reads apiKey via
request.headers.get('X-API-KEY') and forwards an empty string to backend;
instead, in the route handler (before building backendUrlWithParams and calling
fetch) check if apiKey is falsy and immediately return a 401 Unauthorized
response; update the logic around apiKey and the fetch call (symbols: apiKey,
request.headers.get, backendUrlWithParams, fetch) so unauthenticated requests
short-circuit locally rather than proxying to the backend.

In `@app/api/evaluations/tts/runs/route.ts`:
- Around line 3-15: The GET handler currently forwards an empty X-API-KEY header
instead of enforcing the same auth guard as the POST; update the GET function to
read apiKey via request.headers.get('X-API-KEY') and if it's missing/empty
return a 401 NextResponse (mirroring the POST behavior) before calling fetch;
only when apiKey is present should you call
fetch(`${backendUrl}/api/v1/evaluations/tts/runs`, { headers: { 'X-API-KEY':
apiKey } }) and return NextResponse.json with the upstream response.

In `@app/components/DetailedResultsTable.tsx`:
- Around line 120-124: The table header order in DetailedResultsTable.tsx was
changed to "Question → Ground Truth → Answer" but the CSV export in
app/evaluations/[id]/page.tsx still writes rows as "Question,Answer,Ground
Truth", causing a mismatch; update the CSV row generation in that page.tsx (the
code that builds/writes the CSV rows where you currently emit "Question,Answer,
Ground Truth") to emit columns in the same order as the UI: "Question,Ground
Truth,Answer" (or alternatively revert the DetailedResultsTable header to the
original order) so both the table display and the individual CSV export match
exactly.

In `@app/text-to-speech/page.tsx`:
- Around line 551-557: The evaluation-creation flow resets the form and reloads
runs but lacks user feedback; update the completion logic in the block that
calls await response.json(), setEvaluationName(''), setSelectedDatasetId(null),
and await loadRuns() to also call toast.success with a concise success message
(mirroring handleCreateDataset's toast.success usage) so users receive
confirmation after starting an evaluation run.

---

Nitpick comments:
In `@app/components/TabNavigation.tsx`:
- Around line 23-39: The tab UI in TabNavigation renders as buttons but lacks
ARIA semantics; update the container (the div in TabNavigation where tabs.map is
rendered) to include role="tablist", and update each tab button (inside tabs.map
where onClick={() => onTabChange(tab.id)} is defined) to include role="tab" and
an aria-selected attribute set to true when activeTab === tab.id (false
otherwise); ensure the active styling logic remains unchanged and keep the
existing key, onClick, and className props on the button.

In `@app/evaluations/page.tsx`:
- Around line 428-447: The tooltip button and tooltip content are using
hardcoded hex colors; replace these inline hex values with the centralized color
tokens exported from /app/lib/colors.ts (use the same tokens that map to
`#737373`, `#fafafa`, `#ffffff`, `#e5e5e5`, `#171717`) in the style objects for the button
and the tooltip div so the appearance of showHowItWorksTooltip-driven styles
(the button element that references showHowItWorksTooltip and the tooltip
container div with className "absolute left-0 top-full...") use the shared color
constants instead of literal hex strings; update the imports and replace the
style properties (color, backgroundColor, borderColor) to use those token names.

In `@app/text-to-speech/page.tsx`:
- Around line 405-411: The effect that calls loadLanguages, loadDatasets, and
loadRuns is fine functionally but will trigger ESLint exhaustive-deps because
those loaders are declared in the component scope; wrap each loader
(loadLanguages, loadDatasets, loadRuns) with useCallback and include their
stable identity in the dependency array used by the useEffect (which currently
depends on apiKeys and activeTab) so the linter is satisfied while preserving
the current behavior when activeTab or apiKeys change.
- Around line 117-134: The effect re-attaches audio event listeners whenever the
inline onPlayToggle prop changes; store the callback in a stable ref (e.g.,
onPlayToggleRef) and update that ref inside a separate effect so the audio
event-effect can use onPlayToggleRef.current and keep an empty dependency array
(or only audioRef) to avoid re-registering listeners; update the handleEnded
closure to call onPlayToggleRef.current() and ensure you set
onPlayToggleRef.current = onPlayToggle when the prop changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aeeaea44-f3fc-4af4-9144-fc3598f962bf

📥 Commits

Reviewing files that changed from the base of the PR and between 2f66f12 and ca4fab4.

📒 Files selected for processing (11)
  • app/api/evaluations/stt/datasets/[dataset_id]/route.ts
  • app/api/evaluations/tts/datasets/[dataset_id]/route.ts
  • app/api/evaluations/tts/datasets/route.ts
  • app/api/evaluations/tts/results/[result_id]/route.ts
  • app/api/evaluations/tts/runs/[run_id]/route.ts
  • app/api/evaluations/tts/runs/route.ts
  • app/components/DetailedResultsTable.tsx
  • app/components/Sidebar.tsx
  • app/components/TabNavigation.tsx
  • app/evaluations/page.tsx
  • app/text-to-speech/page.tsx

Comment on lines +3 to +15
export async function GET(request: Request) {
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
const apiKey = request.headers.get('X-API-KEY');

try {
const response = await fetch(`${backendUrl}/api/v1/evaluations/tts/datasets`, {
headers: {
'X-API-KEY': apiKey || '',
},
});

const data = await response.json();
return NextResponse.json(data, { status: response.status });
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject unauthenticated dataset-list GETs here too.

POST in this file and GET in app/api/evaluations/tts/datasets/[dataset_id]/route.ts already return 401 when X-API-KEY is missing, but this handler still proxies X-API-KEY: '' to the backend. That makes the auth contract inconsistent and turns a local 401 into an extra backend call whose behavior now depends on backend header parsing.

🔐 Suggested fix
 export async function GET(request: Request) {
   const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
   const apiKey = request.headers.get('X-API-KEY');
+
+  if (!apiKey) {
+    return NextResponse.json(
+      { success: false, error: 'Unauthorized: Missing API key', data: null },
+      { status: 401 }
+    );
+  }

   try {
     const response = await fetch(`${backendUrl}/api/v1/evaluations/tts/datasets`, {
       headers: {
-        'X-API-KEY': apiKey || '',
+        'X-API-KEY': apiKey,
       },
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/evaluations/tts/datasets/route.ts` around lines 3 - 15, The GET
handler in this route proxies an empty X-API-KEY to the backend instead of
rejecting unauthenticated requests; update the GET function to check
request.headers.get('X-API-KEY') (same as POST and the other GET in
evaluations/tts/datasets/[dataset_id]/route.ts) and return a 401 NextResponse
when apiKey is missing or falsy, avoiding the backend fetch; only perform the
fetch when apiKey is present and include the header value in the proxied
request.

Comment on lines +3 to +19
export async function GET(
request: Request,
{ params }: { params: Promise<{ result_id: string }> }
) {
const { result_id } = await params;
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
const apiKey = request.headers.get('X-API-KEY');

try {
const response = await fetch(`${backendUrl}/api/v1/evaluations/tts/results/${result_id}`, {
headers: {
'X-API-KEY': apiKey || '',
},
});

const data = await response.json();
return NextResponse.json(data, { status: response.status });
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fail fast on missing API keys for result GETs as well.

PATCH already rejects missing X-API-KEY, but GET still forwards an empty value to the backend. Keeping both methods consistent here avoids anonymous proxy calls and gives the client a stable 401 response shape.

🔐 Suggested fix
 export async function GET(
   request: Request,
   { params }: { params: Promise<{ result_id: string }> }
 ) {
   const { result_id } = await params;
   const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
   const apiKey = request.headers.get('X-API-KEY');
+
+  if (!apiKey) {
+    return NextResponse.json(
+      { success: false, error: 'Unauthorized: Missing API key', data: null },
+      { status: 401 }
+    );
+  }

   try {
     const response = await fetch(`${backendUrl}/api/v1/evaluations/tts/results/${result_id}`, {
       headers: {
-        'X-API-KEY': apiKey || '',
+        'X-API-KEY': apiKey,
       },
     });
📝 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
export async function GET(
request: Request,
{ params }: { params: Promise<{ result_id: string }> }
) {
const { result_id } = await params;
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
const apiKey = request.headers.get('X-API-KEY');
try {
const response = await fetch(`${backendUrl}/api/v1/evaluations/tts/results/${result_id}`, {
headers: {
'X-API-KEY': apiKey || '',
},
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
export async function GET(
request: Request,
{ params }: { params: Promise<{ result_id: string }> }
) {
const { result_id } = await params;
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
const apiKey = request.headers.get('X-API-KEY');
if (!apiKey) {
return NextResponse.json(
{ success: false, error: 'Unauthorized: Missing API key', data: null },
{ status: 401 }
);
}
try {
const response = await fetch(`${backendUrl}/api/v1/evaluations/tts/results/${result_id}`, {
headers: {
'X-API-KEY': apiKey,
},
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/evaluations/tts/results/`[result_id]/route.ts around lines 3 - 19,
The GET handler in route.ts currently reads X-API-KEY into the apiKey variable
and forwards an empty string to the backend; update the GET function to fail
fast like PATCH by checking request.headers.get('X-API-KEY') (the apiKey
variable) and if missing return a 401 NextResponse with a JSON error body
immediately, avoiding forwarding empty keys to the backend and keeping GET and
PATCH response shapes consistent.

Comment on lines +8 to +23
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
const apiKey = request.headers.get('X-API-KEY');

const { searchParams } = new URL(request.url);
const queryString = searchParams.toString();

try {
const backendUrlWithParams = queryString
? `${backendUrl}/api/v1/evaluations/tts/runs/${run_id}?${queryString}`
: `${backendUrl}/api/v1/evaluations/tts/runs/${run_id}`;

const response = await fetch(backendUrlWithParams, {
headers: {
'X-API-KEY': apiKey || '',
},
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Return 401 before proxying unauthenticated run lookups.

This handler currently forwards X-API-KEY: '' when the header is absent, while the dataset-detail proxy already fails fast locally. The mismatch makes GET auth behavior inconsistent across the TTS routes and adds needless backend traffic for anonymous requests.

🔐 Suggested fix
   const { run_id } = await params;
   const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
   const apiKey = request.headers.get('X-API-KEY');
+
+  if (!apiKey) {
+    return NextResponse.json(
+      { success: false, error: 'Unauthorized: Missing API key', data: null },
+      { status: 401 }
+    );
+  }

   const { searchParams } = new URL(request.url);
   const queryString = searchParams.toString();
@@
     const response = await fetch(backendUrlWithParams, {
       headers: {
-        'X-API-KEY': apiKey || '',
+        'X-API-KEY': apiKey,
       },
     });
📝 Committable suggestion

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

Suggested change
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
const apiKey = request.headers.get('X-API-KEY');
const { searchParams } = new URL(request.url);
const queryString = searchParams.toString();
try {
const backendUrlWithParams = queryString
? `${backendUrl}/api/v1/evaluations/tts/runs/${run_id}?${queryString}`
: `${backendUrl}/api/v1/evaluations/tts/runs/${run_id}`;
const response = await fetch(backendUrlWithParams, {
headers: {
'X-API-KEY': apiKey || '',
},
});
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
const apiKey = request.headers.get('X-API-KEY');
if (!apiKey) {
return NextResponse.json(
{ success: false, error: 'Unauthorized: Missing API key', data: null },
{ status: 401 }
);
}
const { searchParams } = new URL(request.url);
const queryString = searchParams.toString();
try {
const backendUrlWithParams = queryString
? `${backendUrl}/api/v1/evaluations/tts/runs/${run_id}?${queryString}`
: `${backendUrl}/api/v1/evaluations/tts/runs/${run_id}`;
const response = await fetch(backendUrlWithParams, {
headers: {
'X-API-KEY': apiKey,
},
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/evaluations/tts/runs/`[run_id]/route.ts around lines 8 - 23, The
handler currently reads apiKey via request.headers.get('X-API-KEY') and forwards
an empty string to backend; instead, in the route handler (before building
backendUrlWithParams and calling fetch) check if apiKey is falsy and immediately
return a 401 Unauthorized response; update the logic around apiKey and the fetch
call (symbols: apiKey, request.headers.get, backendUrlWithParams, fetch) so
unauthenticated requests short-circuit locally rather than proxying to the
backend.

Comment on lines +3 to +15
export async function GET(request: Request) {
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
const apiKey = request.headers.get('X-API-KEY');

try {
const response = await fetch(`${backendUrl}/api/v1/evaluations/tts/runs`, {
headers: {
'X-API-KEY': apiKey || '',
},
});

const data = await response.json();
return NextResponse.json(data, { status: response.status });
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Mirror the POST auth guard on the runs GET endpoint.

Right now the list GET proxies X-API-KEY: '' instead of returning 401 like POST does. That makes callers depend on backend behavior for a missing key and weakens the proxy’s own auth boundary.

🔐 Suggested fix
 export async function GET(request: Request) {
   const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
   const apiKey = request.headers.get('X-API-KEY');
+
+  if (!apiKey) {
+    return NextResponse.json(
+      { success: false, error: 'Unauthorized: Missing API key', data: null },
+      { status: 401 }
+    );
+  }

   try {
     const response = await fetch(`${backendUrl}/api/v1/evaluations/tts/runs`, {
       headers: {
-        'X-API-KEY': apiKey || '',
+        'X-API-KEY': apiKey,
       },
     });
📝 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
export async function GET(request: Request) {
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
const apiKey = request.headers.get('X-API-KEY');
try {
const response = await fetch(`${backendUrl}/api/v1/evaluations/tts/runs`, {
headers: {
'X-API-KEY': apiKey || '',
},
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
export async function GET(request: Request) {
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
const apiKey = request.headers.get('X-API-KEY');
if (!apiKey) {
return NextResponse.json(
{ success: false, error: 'Unauthorized: Missing API key', data: null },
{ status: 401 }
);
}
try {
const response = await fetch(`${backendUrl}/api/v1/evaluations/tts/runs`, {
headers: {
'X-API-KEY': apiKey,
},
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/evaluations/tts/runs/route.ts` around lines 3 - 15, The GET handler
currently forwards an empty X-API-KEY header instead of enforcing the same auth
guard as the POST; update the GET function to read apiKey via
request.headers.get('X-API-KEY') and if it's missing/empty return a 401
NextResponse (mirroring the POST behavior) before calling fetch; only when
apiKey is present should you call
fetch(`${backendUrl}/api/v1/evaluations/tts/runs`, { headers: { 'X-API-KEY':
apiKey } }) and return NextResponse.json with the upstream response.

Comment on lines 120 to +124
<th className="px-4 py-3 text-left text-xs font-semibold uppercase" style={{ color: '#171717', width: '25%' }}>
Answer
Ground Truth
</th>
<th className="px-4 py-3 text-left text-xs font-semibold uppercase" style={{ color: '#171717', width: '25%' }}>
Ground Truth
Answer
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Keep the individual table and CSV column order aligned.

This reorders the row view to Question → Ground Truth → Answer, but the individual export in app/evaluations/[id]/page.tsx, Lines 283-300, still writes Question,Answer,Ground Truth. That mismatch makes it easy to misread columns when users compare the UI with the downloaded CSV. Please update both surfaces together or keep the existing order here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/DetailedResultsTable.tsx` around lines 120 - 124, The table
header order in DetailedResultsTable.tsx was changed to "Question → Ground Truth
→ Answer" but the CSV export in app/evaluations/[id]/page.tsx still writes rows
as "Question,Answer,Ground Truth", causing a mismatch; update the CSV row
generation in that page.tsx (the code that builds/writes the CSV rows where you
currently emit "Question,Answer, Ground Truth") to emit columns in the same
order as the UI: "Question,Ground Truth,Answer" (or alternatively revert the
DetailedResultsTable header to the original order) so both the table display and
the individual CSV export match exactly.

Comment on lines +551 to +557

await response.json();

setEvaluationName('');
setSelectedDatasetId(null);

await loadRuns();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing success feedback after starting evaluation.

After successfully creating an evaluation run, the form is reset and runs are reloaded, but no success message is shown to the user. This differs from handleCreateDataset which shows toast.success at line 496.

💡 Proposed fix to add success feedback
       await response.json();

+      toast.success('Evaluation started successfully!');
       setEvaluationName('');
       setSelectedDatasetId(null);

       await loadRuns();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/text-to-speech/page.tsx` around lines 551 - 557, The evaluation-creation
flow resets the form and reloads runs but lacks user feedback; update the
completion logic in the block that calls await response.json(),
setEvaluationName(''), setSelectedDatasetId(null), and await loadRuns() to also
call toast.success with a concise success message (mirroring
handleCreateDataset's toast.success usage) so users receive confirmation after
starting an evaluation run.

@AkhileshNegi AkhileshNegi changed the title TTS Evaluation: Metrics TTS Evaluation Mar 13, 2026
@AkhileshNegi AkhileshNegi changed the title TTS Evaluation TTS Evaluation: Metrics Mar 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants