Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds per-run cost data to EvalRunCard with a formatted USD total and an interactive tooltip; introduces cost-related TypeScript types and a utility to format USD amounts. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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: 3
🤖 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/`(main)/evaluations/[id]/page.tsx:
- Around line 755-757: Reformat the inline JSX conditional that renders token
counts to satisfy Prettier: break the expression onto multiple lines and align
parentheses/spacing around the conditional expression so it matches surrounding
JSX formatting (the block using job.cost.response.input_tokens and
job.cost.response.output_tokens and calling formatTokenCount). Update the
conditional rendering around that span (the JSX that checks
job.cost.response.input_tokens != null && job.cost.response.output_tokens !=
null) to follow Prettier’s preferred line breaks and indentation.
- Around line 698-726: The cost card currently only checks job.cost before
calling formatCostUSD(job.cost.total_cost_usd), which can throw if
total_cost_usd is missing; update the render guard in the Cost Breakdown block
(the JSX that renders Total Cost and calls formatCostUSD) to explicitly verify
total_cost_usd exists and is a valid number (e.g., job.cost?.total_cost_usd !=
null and Number.isFinite(job.cost.total_cost_usd)) before calling formatCostUSD,
and render a fallback (e.g., "—" or "N/A") when it's absent; adjust the
condition that wraps the Total Cost div (where formatCostUSD is invoked) so
formatCostUSD is only called when the value is present and valid.
In `@app/components/utils.ts`:
- Around line 75-80: The function formatCostUSD currently calls toFixed on
possibly invalid inputs and can produce "$NaN" or throw; modify formatCostUSD to
first validate the input (e.g., Number.isFinite(cost)) and if it's not a finite
number return a safe fallback like "$0.00"; only perform the cost < 0.01 branch
and call cost.toFixed(4) / toFixed(2) after the finite check so toFixed is never
invoked on bad values.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: b9b1507a-6bd0-4b38-a642-0a1ec4400c2e
📒 Files selected for processing (4)
app/(main)/evaluations/[id]/page.tsxapp/components/evaluations/EvalRunCard.tsxapp/components/types.tsapp/components/utils.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
app/components/evaluations/EvalRunCard.tsx (1)
149-175: Unify tooltip width/position constants to avoid edge clipping drift.Line 149 uses
tooltipWidth = 280while Line 171 renderswidth: "260px". This can mis-clamp near the viewport edge. Consider sharing one constant and clamping top as well.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/components/evaluations/EvalRunCard.tsx` around lines 149 - 175, The tooltip width/position math is inconsistent: the onMouseEnter handler declares tooltipWidth = 280 but the rendered style uses width: "260px", which can cause clamping drift and edge clipping; update the component to use a single shared constant for tooltip width (replace the local tooltipWidth and the hard-coded "260px" with a single constant such as TOOLTIP_WIDTH) and use that same value when computing clampedLeft and when setting the style, and also clamp/set the tooltip top (using rect.top/rect.height and window.scrollY or Math.max/Math.min) before calling setCostTooltipPos so costTooltipPos, setCostTooltipPos, isCostTooltipOpen and the tooltip style remain consistent.
🤖 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/components/evaluations/EvalRunCard.tsx`:
- Around line 165-195: The tooltip can end up empty because it only renders
job.cost.response and job.cost.embedding; update the EvalRunCard tooltip
rendering to include a fallback row when neither response nor embedding exist:
inside the isCostTooltipOpen block (where costTooltipPos and formatCostUSD are
used) add a default line that displays a descriptive label like "Total cost" and
formats job.cost.total (or job.cost.cost_usd / computed total) so the tooltip
always shows at least the aggregate cost when EvalCost lacks response/embedding
entries.
- Around line 141-197: The tooltip trigger is a plain div with only mouse
handlers; make it keyboard and touch accessible by replacing or enhancing the
trigger element (the current inline div that references isCostTooltipOpen,
setIsCostTooltipOpen, costTooltipPos, setCostTooltipPos) so it is focusable and
operable: add role="button" or use a real <button>, include tabIndex,
aria-label/aria-describedby (pointing to the tooltip container) and
aria-expanded tied to isCostTooltipOpen, add onFocus/onBlur handlers that mirror
onMouseEnter/onMouseLeave to set costTooltipPos and open/close, implement
onKeyDown to open on Enter/Space and close on Escape, and add onClick to support
touch toggling; ensure the tooltip container is referenced by id for
accessibility and retains pointer-events: none only if you still need it
visually but allow screen readers to access its contents.
---
Nitpick comments:
In `@app/components/evaluations/EvalRunCard.tsx`:
- Around line 149-175: The tooltip width/position math is inconsistent: the
onMouseEnter handler declares tooltipWidth = 280 but the rendered style uses
width: "260px", which can cause clamping drift and edge clipping; update the
component to use a single shared constant for tooltip width (replace the local
tooltipWidth and the hard-coded "260px" with a single constant such as
TOOLTIP_WIDTH) and use that same value when computing clampedLeft and when
setting the style, and also clamp/set the tooltip top (using
rect.top/rect.height and window.scrollY or Math.max/Math.min) before calling
setCostTooltipPos so costTooltipPos, setCostTooltipPos, isCostTooltipOpen and
the tooltip style remain consistent.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: b81330e1-c480-433c-b122-4a853d4798ca
📒 Files selected for processing (2)
app/components/evaluations/EvalRunCard.tsxapp/components/utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- app/components/utils.ts
| <div | ||
| className="inline-flex items-center justify-center w-4 h-4 rounded-full text-xs font-normal cursor-help" | ||
| style={{ | ||
| backgroundColor: isCostTooltipOpen ? "#171717" : "#fafafa", | ||
| color: isCostTooltipOpen ? "#ffffff" : "#737373", | ||
| }} | ||
| onMouseEnter={(e) => { | ||
| const rect = e.currentTarget.getBoundingClientRect(); | ||
| const tooltipWidth = 280; | ||
| const centerX = rect.left + rect.width / 2; | ||
| const clampedLeft = Math.min( | ||
| Math.max(centerX - tooltipWidth / 2, 8), | ||
| window.innerWidth - tooltipWidth - 8, | ||
| ); | ||
| setCostTooltipPos({ | ||
| top: rect.top - 8, | ||
| left: clampedLeft, | ||
| }); | ||
| setIsCostTooltipOpen(true); | ||
| }} | ||
| onMouseLeave={() => setIsCostTooltipOpen(false)} | ||
| > | ||
| i | ||
| </div> | ||
| {isCostTooltipOpen && ( | ||
| <div | ||
| className="fixed z-50 px-3 py-2 rounded-md text-xs whitespace-normal pointer-events-none space-y-1" | ||
| style={{ | ||
| backgroundColor: "#171717", | ||
| color: "#ffffff", | ||
| width: "260px", | ||
| boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)", | ||
| top: costTooltipPos.top, | ||
| left: costTooltipPos.left, | ||
| transform: "translateY(-100%)", | ||
| }} | ||
| > | ||
| {job.cost.response && ( | ||
| <div className="flex justify-between gap-3"> | ||
| <span style={{ color: "#a3a3a3" }}> | ||
| Response generation | ||
| </span> | ||
| <span>{formatCostUSD(job.cost.response.cost_usd)}</span> | ||
| </div> | ||
| )} | ||
| {job.cost.embedding && ( | ||
| <div className="flex justify-between gap-3"> | ||
| <span style={{ color: "#a3a3a3" }}> | ||
| Cosine similarity calculation | ||
| </span> | ||
| <span> | ||
| {formatCostUSD(job.cost.embedding.cost_usd)} | ||
| </span> | ||
| </div> | ||
| )} | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
Cost tooltip is mouse-only and not keyboard/touch accessible.
On Lines 147-162, the trigger is a div with hover handlers only. Keyboard and touch users can’t reliably open this tooltip.
Proposed accessibility fix
- <div
+ <button
+ type="button"
+ aria-label="Show cost breakdown"
+ aria-expanded={isCostTooltipOpen}
+ aria-describedby={`cost-tooltip-${job.id}`}
className="inline-flex items-center justify-center w-4 h-4 rounded-full text-xs font-normal cursor-help"
style={{
backgroundColor: isCostTooltipOpen ? "#171717" : "#fafafa",
color: isCostTooltipOpen ? "#ffffff" : "#737373",
}}
onMouseEnter={(e) => {
const rect = e.currentTarget.getBoundingClientRect();
const tooltipWidth = 280;
const centerX = rect.left + rect.width / 2;
const clampedLeft = Math.min(
Math.max(centerX - tooltipWidth / 2, 8),
window.innerWidth - tooltipWidth - 8,
);
setCostTooltipPos({
top: rect.top - 8,
left: clampedLeft,
});
setIsCostTooltipOpen(true);
}}
onMouseLeave={() => setIsCostTooltipOpen(false)}
+ onFocus={(e) => {
+ const rect = e.currentTarget.getBoundingClientRect();
+ const tooltipWidth = 280;
+ const centerX = rect.left + rect.width / 2;
+ const clampedLeft = Math.min(
+ Math.max(centerX - tooltipWidth / 2, 8),
+ window.innerWidth - tooltipWidth - 8,
+ );
+ setCostTooltipPos({ top: rect.top - 8, left: clampedLeft });
+ setIsCostTooltipOpen(true);
+ }}
+ onBlur={() => setIsCostTooltipOpen(false)}
+ onClick={() => setIsCostTooltipOpen((prev) => !prev)}
>
i
- </div>
+ </button>
{isCostTooltipOpen && (
<div
+ id={`cost-tooltip-${job.id}`}
+ role="tooltip"
className="fixed z-50 px-3 py-2 rounded-md text-xs whitespace-normal pointer-events-none space-y-1"📝 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.
| <div | |
| className="inline-flex items-center justify-center w-4 h-4 rounded-full text-xs font-normal cursor-help" | |
| style={{ | |
| backgroundColor: isCostTooltipOpen ? "#171717" : "#fafafa", | |
| color: isCostTooltipOpen ? "#ffffff" : "#737373", | |
| }} | |
| onMouseEnter={(e) => { | |
| const rect = e.currentTarget.getBoundingClientRect(); | |
| const tooltipWidth = 280; | |
| const centerX = rect.left + rect.width / 2; | |
| const clampedLeft = Math.min( | |
| Math.max(centerX - tooltipWidth / 2, 8), | |
| window.innerWidth - tooltipWidth - 8, | |
| ); | |
| setCostTooltipPos({ | |
| top: rect.top - 8, | |
| left: clampedLeft, | |
| }); | |
| setIsCostTooltipOpen(true); | |
| }} | |
| onMouseLeave={() => setIsCostTooltipOpen(false)} | |
| > | |
| i | |
| </div> | |
| {isCostTooltipOpen && ( | |
| <div | |
| className="fixed z-50 px-3 py-2 rounded-md text-xs whitespace-normal pointer-events-none space-y-1" | |
| style={{ | |
| backgroundColor: "#171717", | |
| color: "#ffffff", | |
| width: "260px", | |
| boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)", | |
| top: costTooltipPos.top, | |
| left: costTooltipPos.left, | |
| transform: "translateY(-100%)", | |
| }} | |
| > | |
| {job.cost.response && ( | |
| <div className="flex justify-between gap-3"> | |
| <span style={{ color: "#a3a3a3" }}> | |
| Response generation | |
| </span> | |
| <span>{formatCostUSD(job.cost.response.cost_usd)}</span> | |
| </div> | |
| )} | |
| {job.cost.embedding && ( | |
| <div className="flex justify-between gap-3"> | |
| <span style={{ color: "#a3a3a3" }}> | |
| Cosine similarity calculation | |
| </span> | |
| <span> | |
| {formatCostUSD(job.cost.embedding.cost_usd)} | |
| </span> | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| <button | |
| type="button" | |
| aria-label="Show cost breakdown" | |
| aria-expanded={isCostTooltipOpen} | |
| aria-describedby={`cost-tooltip-${job.id}`} | |
| className="inline-flex items-center justify-center w-4 h-4 rounded-full text-xs font-normal cursor-help" | |
| style={{ | |
| backgroundColor: isCostTooltipOpen ? "#171717" : "#fafafa", | |
| color: isCostTooltipOpen ? "#ffffff" : "#737373", | |
| }} | |
| onMouseEnter={(e) => { | |
| const rect = e.currentTarget.getBoundingClientRect(); | |
| const tooltipWidth = 280; | |
| const centerX = rect.left + rect.width / 2; | |
| const clampedLeft = Math.min( | |
| Math.max(centerX - tooltipWidth / 2, 8), | |
| window.innerWidth - tooltipWidth - 8, | |
| ); | |
| setCostTooltipPos({ | |
| top: rect.top - 8, | |
| left: clampedLeft, | |
| }); | |
| setIsCostTooltipOpen(true); | |
| }} | |
| onMouseLeave={() => setIsCostTooltipOpen(false)} | |
| onFocus={(e) => { | |
| const rect = e.currentTarget.getBoundingClientRect(); | |
| const tooltipWidth = 280; | |
| const centerX = rect.left + rect.width / 2; | |
| const clampedLeft = Math.min( | |
| Math.max(centerX - tooltipWidth / 2, 8), | |
| window.innerWidth - tooltipWidth - 8, | |
| ); | |
| setCostTooltipPos({ top: rect.top - 8, left: clampedLeft }); | |
| setIsCostTooltipOpen(true); | |
| }} | |
| onBlur={() => setIsCostTooltipOpen(false)} | |
| onClick={() => setIsCostTooltipOpen((prev) => !prev)} | |
| > | |
| i | |
| </button> | |
| {isCostTooltipOpen && ( | |
| <div | |
| id={`cost-tooltip-${job.id}`} | |
| role="tooltip" | |
| className="fixed z-50 px-3 py-2 rounded-md text-xs whitespace-normal pointer-events-none space-y-1" | |
| style={{ | |
| backgroundColor: "#171717", | |
| color: "#ffffff", | |
| width: "260px", | |
| boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)", | |
| top: costTooltipPos.top, | |
| left: costTooltipPos.left, | |
| transform: "translateY(-100%)", | |
| }} | |
| > | |
| {job.cost.response && ( | |
| <div className="flex justify-between gap-3"> | |
| <span style={{ color: "#a3a3a3" }}> | |
| Response generation | |
| </span> | |
| <span>{formatCostUSD(job.cost.response.cost_usd)}</span> | |
| </div> | |
| )} | |
| {job.cost.embedding && ( | |
| <div className="flex justify-between gap-3"> | |
| <span style={{ color: "#a3a3a3" }}> | |
| Cosine similarity calculation | |
| </span> | |
| <span> | |
| {formatCostUSD(job.cost.embedding.cost_usd)} | |
| </span> | |
| </div> | |
| )} | |
| </div> | |
| )} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/components/evaluations/EvalRunCard.tsx` around lines 141 - 197, The
tooltip trigger is a plain div with only mouse handlers; make it keyboard and
touch accessible by replacing or enhancing the trigger element (the current
inline div that references isCostTooltipOpen, setIsCostTooltipOpen,
costTooltipPos, setCostTooltipPos) so it is focusable and operable: add
role="button" or use a real <button>, include tabIndex,
aria-label/aria-describedby (pointing to the tooltip container) and
aria-expanded tied to isCostTooltipOpen, add onFocus/onBlur handlers that mirror
onMouseEnter/onMouseLeave to set costTooltipPos and open/close, implement
onKeyDown to open on Enter/Space and close on Escape, and add onClick to support
touch toggling; ensure the tooltip container is referenced by id for
accessibility and retains pointer-events: none only if you still need it
visually but allow screen readers to access its contents.
| {isCostTooltipOpen && ( | ||
| <div | ||
| className="fixed z-50 px-3 py-2 rounded-md text-xs whitespace-normal pointer-events-none space-y-1" | ||
| style={{ | ||
| backgroundColor: "#171717", | ||
| color: "#ffffff", | ||
| width: "260px", | ||
| boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)", | ||
| top: costTooltipPos.top, | ||
| left: costTooltipPos.left, | ||
| transform: "translateY(-100%)", | ||
| }} | ||
| > | ||
| {job.cost.response && ( | ||
| <div className="flex justify-between gap-3"> | ||
| <span style={{ color: "#a3a3a3" }}> | ||
| Response generation | ||
| </span> | ||
| <span>{formatCostUSD(job.cost.response.cost_usd)}</span> | ||
| </div> | ||
| )} | ||
| {job.cost.embedding && ( | ||
| <div className="flex justify-between gap-3"> | ||
| <span style={{ color: "#a3a3a3" }}> | ||
| Cosine similarity calculation | ||
| </span> | ||
| <span> | ||
| {formatCostUSD(job.cost.embedding.cost_usd)} | ||
| </span> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
Tooltip can render blank when no breakdown entries are present.
Lines 178-195 only render response/embedding. Since these fields are optional in EvalCost, users can get an empty tooltip even when total cost is shown.
Proposed fallback row
{isCostTooltipOpen && (
<div
className="fixed z-50 px-3 py-2 rounded-md text-xs whitespace-normal pointer-events-none space-y-1"
style={{
backgroundColor: "#171717",
color: "#ffffff",
width: "260px",
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
top: costTooltipPos.top,
left: costTooltipPos.left,
transform: "translateY(-100%)",
}}
>
+ {!job.cost.response && !job.cost.embedding && (
+ <div className="flex justify-between gap-3">
+ <span style={{ color: "#a3a3a3" }}>Total</span>
+ <span>{formatCostUSD(job.cost.total_cost_usd)}</span>
+ </div>
+ )}
{job.cost.response && (📝 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.
| {isCostTooltipOpen && ( | |
| <div | |
| className="fixed z-50 px-3 py-2 rounded-md text-xs whitespace-normal pointer-events-none space-y-1" | |
| style={{ | |
| backgroundColor: "#171717", | |
| color: "#ffffff", | |
| width: "260px", | |
| boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)", | |
| top: costTooltipPos.top, | |
| left: costTooltipPos.left, | |
| transform: "translateY(-100%)", | |
| }} | |
| > | |
| {job.cost.response && ( | |
| <div className="flex justify-between gap-3"> | |
| <span style={{ color: "#a3a3a3" }}> | |
| Response generation | |
| </span> | |
| <span>{formatCostUSD(job.cost.response.cost_usd)}</span> | |
| </div> | |
| )} | |
| {job.cost.embedding && ( | |
| <div className="flex justify-between gap-3"> | |
| <span style={{ color: "#a3a3a3" }}> | |
| Cosine similarity calculation | |
| </span> | |
| <span> | |
| {formatCostUSD(job.cost.embedding.cost_usd)} | |
| </span> | |
| </div> | |
| )} | |
| {isCostTooltipOpen && ( | |
| <div | |
| className="fixed z-50 px-3 py-2 rounded-md text-xs whitespace-normal pointer-events-none space-y-1" | |
| style={{ | |
| backgroundColor: "#171717", | |
| color: "#ffffff", | |
| width: "260px", | |
| boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)", | |
| top: costTooltipPos.top, | |
| left: costTooltipPos.left, | |
| transform: "translateY(-100%)", | |
| }} | |
| > | |
| {!job.cost.response && !job.cost.embedding && ( | |
| <div className="flex justify-between gap-3"> | |
| <span style={{ color: "#a3a3a3" }}>Total</span> | |
| <span>{formatCostUSD(job.cost.total_cost_usd)}</span> | |
| </div> | |
| )} | |
| {job.cost.response && ( | |
| <div className="flex justify-between gap-3"> | |
| <span style={{ color: "#a3a3a3" }}> | |
| Response generation | |
| </span> | |
| <span>{formatCostUSD(job.cost.response.cost_usd)}</span> | |
| </div> | |
| )} | |
| {job.cost.embedding && ( | |
| <div className="flex justify-between gap-3"> | |
| <span style={{ color: "#a3a3a3" }}> | |
| Cosine similarity calculation | |
| </span> | |
| <span> | |
| {formatCostUSD(job.cost.embedding.cost_usd)} | |
| </span> | |
| </div> | |
| )} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/components/evaluations/EvalRunCard.tsx` around lines 165 - 195, The
tooltip can end up empty because it only renders job.cost.response and
job.cost.embedding; update the EvalRunCard tooltip rendering to include a
fallback row when neither response nor embedding exist: inside the
isCostTooltipOpen block (where costTooltipPos and formatCostUSD are used) add a
default line that displays a descriptive label like "Total cost" and formats
job.cost.total (or job.cost.cost_usd / computed total) so the tooltip always
shows at least the aggregate cost when EvalCost lacks response/embedding
entries.
Summary
Target issue is #39
New Features
Summary by CodeRabbit