v2 of report

This commit is contained in:
Ishaan Jaffer 2026-01-05 19:45:29 +05:30
parent 0e4448a731
commit 69aefb8a94
6 changed files with 326 additions and 337 deletions

View file

@ -1,8 +1,7 @@
import React, { useState, useCallback } from "react";
import { Button, Text } from "@tremor/react";
import { PlusOutlined } from "@ant-design/icons";
import { Table, Select, InputNumber, Button } from "antd";
import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
import { PricingCalculatorProps, ModelEntry } from "./types";
import ModelEntryRow from "./model_entry_row";
import MultiCostResults from "./multi_cost_results";
import { useMultiCostEstimate } from "./use_multi_cost_estimate";
@ -53,38 +52,135 @@ const PricingCalculator: React.FC<PricingCalculatorProps> = ({
[removeEntry]
);
// Note: Fetching is triggered by handleEntryChange when model is selected
const multiModelResult = getMultiModelResult(entries);
const columns = [
{
title: "Model",
dataIndex: "model",
key: "model",
width: "30%",
render: (_: string, record: ModelEntry) => (
<Select
showSearch
placeholder="Select a model"
value={record.model || undefined}
onChange={(value) => handleEntryChange(record.id, "model", value)}
optionFilterProp="label"
filterOption={(input, option) =>
String(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
}
options={models.map((model) => ({
value: model,
label: model,
}))}
style={{ width: "100%" }}
size="small"
/>
),
},
{
title: "Input Tokens",
dataIndex: "input_tokens",
key: "input_tokens",
width: "15%",
render: (_: number, record: ModelEntry) => (
<InputNumber
min={0}
value={record.input_tokens}
onChange={(value) => handleEntryChange(record.id, "input_tokens", value ?? 0)}
style={{ width: "100%" }}
size="small"
formatter={(value) => `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
/>
),
},
{
title: "Output Tokens",
dataIndex: "output_tokens",
key: "output_tokens",
width: "15%",
render: (_: number, record: ModelEntry) => (
<InputNumber
min={0}
value={record.output_tokens}
onChange={(value) => handleEntryChange(record.id, "output_tokens", value ?? 0)}
style={{ width: "100%" }}
size="small"
formatter={(value) => `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
/>
),
},
{
title: "Requests/Day",
dataIndex: "num_requests_per_day",
key: "num_requests_per_day",
width: "15%",
render: (_: number | undefined, record: ModelEntry) => (
<InputNumber
min={0}
value={record.num_requests_per_day}
onChange={(value) => handleEntryChange(record.id, "num_requests_per_day", value ?? undefined)}
style={{ width: "100%" }}
size="small"
placeholder="-"
formatter={(value) => value ? `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : ""}
/>
),
},
{
title: "Requests/Month",
dataIndex: "num_requests_per_month",
key: "num_requests_per_month",
width: "15%",
render: (_: number | undefined, record: ModelEntry) => (
<InputNumber
min={0}
value={record.num_requests_per_month}
onChange={(value) => handleEntryChange(record.id, "num_requests_per_month", value ?? undefined)}
style={{ width: "100%" }}
size="small"
placeholder="-"
formatter={(value) => value ? `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : ""}
/>
),
},
{
title: "",
key: "actions",
width: 50,
render: (_: unknown, record: ModelEntry) => (
<Button
type="text"
icon={<DeleteOutlined />}
onClick={() => handleRemoveEntry(record.id)}
disabled={entries.length === 1}
danger
size="small"
/>
),
},
];
return (
<div className="space-y-4">
<div className="flex items-center justify-between mb-2">
<Text className="text-sm text-gray-600">
Add models to estimate costs. Each model can have its own token counts and request volumes.
</Text>
<Button
size="xs"
variant="secondary"
icon={PlusOutlined}
onClick={handleAddEntry}
>
Add Model
</Button>
</div>
<div className="space-y-3">
{entries.map((entry) => (
<ModelEntryRow
key={entry.id}
entry={entry}
models={models}
onChange={handleEntryChange}
onRemove={handleRemoveEntry}
canRemove={entries.length > 1}
/>
))}
</div>
<Table
columns={columns}
dataSource={entries}
rowKey="id"
pagination={false}
size="small"
footer={() => (
<Button
type="dashed"
onClick={handleAddEntry}
icon={<PlusOutlined />}
className="w-full"
>
Add Another Model
</Button>
)}
/>
<MultiCostResults multiResult={multiModelResult} />
</div>

View file

@ -1,107 +0,0 @@
import React from "react";
import { Select, InputNumber, Button, Tooltip } from "antd";
import { DeleteOutlined } from "@ant-design/icons";
import { ModelEntry } from "./types";
interface ModelEntryRowProps {
entry: ModelEntry;
models: string[];
onChange: (id: string, field: keyof ModelEntry, value: string | number | undefined) => void;
onRemove: (id: string) => void;
canRemove: boolean;
}
const ModelEntryRow: React.FC<ModelEntryRowProps> = ({
entry,
models,
onChange,
onRemove,
canRemove,
}) => {
return (
<div className="flex items-start gap-3 p-4 bg-gray-50 rounded-lg border border-gray-200">
<div className="flex-1 grid grid-cols-5 gap-3">
<div>
<label className="text-xs text-gray-500 block mb-1">Model</label>
<Select
showSearch
placeholder="Select model"
value={entry.model || undefined}
onChange={(value) => onChange(entry.id, "model", value)}
optionFilterProp="label"
filterOption={(input, option) =>
String(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
}
options={models.map((model) => ({
value: model,
label: model,
}))}
style={{ width: "100%" }}
size="small"
/>
</div>
<div>
<label className="text-xs text-gray-500 block mb-1">Input Tokens</label>
<InputNumber
min={0}
value={entry.input_tokens}
onChange={(value) => onChange(entry.id, "input_tokens", value ?? 0)}
style={{ width: "100%" }}
size="small"
formatter={(value) => `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
/>
</div>
<div>
<label className="text-xs text-gray-500 block mb-1">Output Tokens</label>
<InputNumber
min={0}
value={entry.output_tokens}
onChange={(value) => onChange(entry.id, "output_tokens", value ?? 0)}
style={{ width: "100%" }}
size="small"
formatter={(value) => `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
/>
</div>
<div>
<label className="text-xs text-gray-500 block mb-1">Requests/Day</label>
<InputNumber
min={0}
value={entry.num_requests_per_day}
onChange={(value) => onChange(entry.id, "num_requests_per_day", value ?? undefined)}
style={{ width: "100%" }}
size="small"
placeholder="Optional"
formatter={(value) => value ? `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : ""}
/>
</div>
<div>
<label className="text-xs text-gray-500 block mb-1">Requests/Month</label>
<InputNumber
min={0}
value={entry.num_requests_per_month}
onChange={(value) => onChange(entry.id, "num_requests_per_month", value ?? undefined)}
style={{ width: "100%" }}
size="small"
placeholder="Optional"
formatter={(value) => value ? `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : ""}
/>
</div>
</div>
<div className="pt-5">
<Tooltip title={canRemove ? "Remove model" : "At least one model required"}>
<Button
type="text"
icon={<DeleteOutlined />}
onClick={() => onRemove(entry.id)}
disabled={!canRemove}
danger
size="small"
/>
</Tooltip>
</div>
</div>
);
};
export default ModelEntryRow;

View file

@ -1,7 +1,7 @@
import React, { useState } from "react";
import { Text, Button } from "@tremor/react";
import { Card, Statistic, Row, Col, Divider, Spin, Table, Tag } from "antd";
import { DollarOutlined, LoadingOutlined, DownOutlined, RightOutlined } from "@ant-design/icons";
import { LoadingOutlined, DownOutlined, RightOutlined } from "@ant-design/icons";
import { CostEstimateResponse } from "../types";
import { formatNumberWithCommas } from "@/utils/dataUtils";
import { MultiModelResult } from "./types";
@ -29,7 +29,7 @@ const SingleModelBreakdown: React.FC<{
loading: boolean;
}> = ({ result, loading }) => {
return (
<div className="space-y-3">
<div className="space-y-3 bg-gray-50 p-4 rounded-lg">
{loading && (
<div className="flex items-center gap-2 text-gray-500 text-sm">
<Spin indicator={<LoadingOutlined spin />} size="small" />
@ -37,136 +37,82 @@ const SingleModelBreakdown: React.FC<{
</div>
)}
<Card size="small" title="Per-Request Cost Breakdown">
<Row gutter={16}>
<Col span={6}>
<Statistic
title="Total Cost"
value={formatCost(result.cost_per_request)}
valueStyle={{ color: "#1890ff", fontSize: "16px" }}
prefix={<DollarOutlined />}
/>
</Col>
<Col span={6}>
<Statistic
title="Input Cost"
value={formatCost(result.input_cost_per_request)}
valueStyle={{ fontSize: "14px" }}
/>
</Col>
<Col span={6}>
<Statistic
title="Output Cost"
value={formatCost(result.output_cost_per_request)}
valueStyle={{ fontSize: "14px" }}
/>
</Col>
<Col span={6}>
<Statistic
title="Margin/Fee"
value={formatCost(result.margin_cost_per_request)}
valueStyle={{
fontSize: "14px",
color: result.margin_cost_per_request > 0 ? "#faad14" : undefined,
}}
/>
</Col>
</Row>
</Card>
<div className="grid grid-cols-4 gap-4">
<div>
<Text className="text-xs text-gray-500 block">Total/Request</Text>
<Text className="text-base font-semibold text-blue-600">{formatCost(result.cost_per_request)}</Text>
</div>
<div>
<Text className="text-xs text-gray-500 block">Input Cost</Text>
<Text className="text-sm">{formatCost(result.input_cost_per_request)}</Text>
</div>
<div>
<Text className="text-xs text-gray-500 block">Output Cost</Text>
<Text className="text-sm">{formatCost(result.output_cost_per_request)}</Text>
</div>
<div>
<Text className="text-xs text-gray-500 block">Margin/Fee</Text>
<Text className={`text-sm ${result.margin_cost_per_request > 0 ? "text-amber-600" : ""}`}>
{formatCost(result.margin_cost_per_request)}
</Text>
</div>
</div>
{result.daily_cost !== null && (
<Card
size="small"
title={`Daily Costs (${formatRequests(result.num_requests_per_day)} requests/day)`}
>
<Row gutter={16}>
<Col span={6}>
<Statistic
title="Total Daily"
value={formatCost(result.daily_cost)}
valueStyle={{ color: "#52c41a", fontSize: "16px" }}
prefix={<DollarOutlined />}
/>
</Col>
<Col span={6}>
<Statistic
title="Input Cost"
value={formatCost(result.daily_input_cost)}
valueStyle={{ fontSize: "14px" }}
/>
</Col>
<Col span={6}>
<Statistic
title="Output Cost"
value={formatCost(result.daily_output_cost)}
valueStyle={{ fontSize: "14px" }}
/>
</Col>
<Col span={6}>
<Statistic
title="Margin/Fee"
value={formatCost(result.daily_margin_cost)}
valueStyle={{
fontSize: "14px",
color: (result.daily_margin_cost ?? 0) > 0 ? "#faad14" : undefined,
}}
/>
</Col>
</Row>
</Card>
<div className="grid grid-cols-4 gap-4 pt-2 border-t border-gray-200">
<div>
<Text className="text-xs text-gray-500 block">Daily Total ({formatRequests(result.num_requests_per_day)} req)</Text>
<Text className="text-base font-semibold text-green-600">{formatCost(result.daily_cost)}</Text>
</div>
<div>
<Text className="text-xs text-gray-500 block">Daily Input</Text>
<Text className="text-sm">{formatCost(result.daily_input_cost)}</Text>
</div>
<div>
<Text className="text-xs text-gray-500 block">Daily Output</Text>
<Text className="text-sm">{formatCost(result.daily_output_cost)}</Text>
</div>
<div>
<Text className="text-xs text-gray-500 block">Daily Margin</Text>
<Text className={`text-sm ${(result.daily_margin_cost ?? 0) > 0 ? "text-amber-600" : ""}`}>
{formatCost(result.daily_margin_cost)}
</Text>
</div>
</div>
)}
{result.monthly_cost !== null && (
<Card
size="small"
title={`Monthly Costs (${formatRequests(result.num_requests_per_month)} requests/month)`}
>
<Row gutter={16}>
<Col span={6}>
<Statistic
title="Total Monthly"
value={formatCost(result.monthly_cost)}
valueStyle={{ color: "#722ed1", fontSize: "16px" }}
prefix={<DollarOutlined />}
/>
</Col>
<Col span={6}>
<Statistic
title="Input Cost"
value={formatCost(result.monthly_input_cost)}
valueStyle={{ fontSize: "14px" }}
/>
</Col>
<Col span={6}>
<Statistic
title="Output Cost"
value={formatCost(result.monthly_output_cost)}
valueStyle={{ fontSize: "14px" }}
/>
</Col>
<Col span={6}>
<Statistic
title="Margin/Fee"
value={formatCost(result.monthly_margin_cost)}
valueStyle={{
fontSize: "14px",
color: (result.monthly_margin_cost ?? 0) > 0 ? "#faad14" : undefined,
}}
/>
</Col>
</Row>
</Card>
<div className="grid grid-cols-4 gap-4 pt-2 border-t border-gray-200">
<div>
<Text className="text-xs text-gray-500 block">Monthly Total ({formatRequests(result.num_requests_per_month)} req)</Text>
<Text className="text-base font-semibold text-purple-600">{formatCost(result.monthly_cost)}</Text>
</div>
<div>
<Text className="text-xs text-gray-500 block">Monthly Input</Text>
<Text className="text-sm">{formatCost(result.monthly_input_cost)}</Text>
</div>
<div>
<Text className="text-xs text-gray-500 block">Monthly Output</Text>
<Text className="text-sm">{formatCost(result.monthly_output_cost)}</Text>
</div>
<div>
<Text className="text-xs text-gray-500 block">Monthly Margin</Text>
<Text className={`text-sm ${(result.monthly_margin_cost ?? 0) > 0 ? "text-amber-600" : ""}`}>
{formatCost(result.monthly_margin_cost)}
</Text>
</div>
</div>
)}
{(result.input_cost_per_token || result.output_cost_per_token) && (
<div className="text-xs text-gray-500">
<span className="font-medium">Token Pricing: </span>
<div className="text-xs text-gray-400 pt-2 border-t border-gray-200">
Token Pricing: {" "}
{result.input_cost_per_token && (
<span>Input: ${formatNumberWithCommas(result.input_cost_per_token * 1_000_000, 2)}/1M</span>
<span>Input ${formatNumberWithCommas(result.input_cost_per_token * 1_000_000, 2)}/1M</span>
)}
{result.input_cost_per_token && result.output_cost_per_token && " | "}
{result.output_cost_per_token && (
<span>Output: ${formatNumberWithCommas(result.output_cost_per_token * 1_000_000, 2)}/1M</span>
<span>Output ${formatNumberWithCommas(result.output_cost_per_token * 1_000_000, 2)}/1M</span>
)}
</div>
)}
@ -184,7 +130,7 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult }) => {
if (!hasAnyResult && !isAnyLoading) {
return (
<div className="py-8 text-center border border-dashed border-gray-300 rounded-lg">
<div className="py-6 text-center border border-dashed border-gray-300 rounded-lg bg-gray-50">
<Text className="text-gray-500">
Select models above to see cost estimates
</Text>
@ -194,7 +140,7 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult }) => {
if (!hasAnyResult && isAnyLoading) {
return (
<div className="py-8 text-center">
<div className="py-6 text-center">
<Spin indicator={<LoadingOutlined spin />} />
<Text className="text-gray-500 block mt-2">Calculating costs...</Text>
</div>
@ -213,16 +159,18 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult }) => {
});
};
const hasMargin = multiResult.totals.margin_per_request > 0;
const summaryColumns = [
{
title: "Model",
dataIndex: "model",
key: "model",
render: (text: string, record: { id: string; provider?: string | null }) => (
<div>
<span className="font-medium">{text}</span>
<div className="flex items-center gap-2">
<span className="font-medium text-sm">{text}</span>
{record.provider && (
<Tag color="blue" className="ml-2 text-xs">
<Tag color="blue" className="text-xs">
{record.provider}
</Tag>
)}
@ -233,29 +181,44 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult }) => {
title: "Per Request",
dataIndex: "cost_per_request",
key: "cost_per_request",
render: (value: number) => formatCost(value),
align: "right" as const,
render: (value: number) => <span className="font-mono text-sm">{formatCost(value)}</span>,
},
{
title: "Margin",
dataIndex: "margin_cost_per_request",
key: "margin_cost_per_request",
align: "right" as const,
render: (value: number) => (
<span className={`font-mono text-sm ${value > 0 ? "text-amber-600" : "text-gray-400"}`}>
{formatCost(value)}
</span>
),
},
{
title: "Daily",
dataIndex: "daily_cost",
key: "daily_cost",
render: (value: number | null) => formatCost(value),
align: "right" as const,
render: (value: number | null) => <span className="font-mono text-sm">{formatCost(value)}</span>,
},
{
title: "Monthly",
dataIndex: "monthly_cost",
key: "monthly_cost",
render: (value: number | null) => formatCost(value),
align: "right" as const,
render: (value: number | null) => <span className="font-mono text-sm">{formatCost(value)}</span>,
},
{
title: "",
key: "expand",
width: 50,
width: 40,
render: (_: unknown, record: { id: string }) => (
<Button
size="xs"
variant="light"
onClick={() => toggleExpanded(record.id)}
className="text-gray-400 hover:text-gray-600"
>
{expandedModels.has(record.id) ? <DownOutlined /> : <RightOutlined />}
</Button>
@ -269,88 +232,95 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult }) => {
model: e.result!.model,
provider: e.result!.provider,
cost_per_request: e.result!.cost_per_request,
margin_cost_per_request: e.result!.margin_cost_per_request,
daily_cost: e.result!.daily_cost,
monthly_cost: e.result!.monthly_cost,
}));
return (
<div className="space-y-4">
<Divider />
<Divider className="my-4" />
<div className="flex items-center justify-between">
<div>
<Text className="text-lg font-semibold text-gray-900">Cost Estimates</Text>
<Text className="text-sm text-gray-500 block mt-1">
{validEntries.length} model{validEntries.length !== 1 ? "s" : ""} configured
</Text>
</div>
<Text className="text-base font-semibold text-gray-900">Cost Estimates</Text>
<div className="flex items-center gap-2">
{isAnyLoading && <Spin indicator={<LoadingOutlined spin />} size="small" />}
<MultiExportDropdown multiResult={multiResult} />
</div>
</div>
{/* Totals Summary */}
{validEntries.length > 1 && (
<Card size="small" className="bg-gradient-to-r from-blue-50 to-purple-50 border-blue-200">
<div className="flex items-center justify-between">
<Text className="font-semibold text-gray-800">Combined Totals</Text>
</div>
<Row gutter={16} className="mt-3">
<Col span={8}>
<Statistic
title="Total Per Request"
value={formatCost(multiResult.totals.cost_per_request)}
valueStyle={{ color: "#1890ff", fontSize: "20px" }}
prefix={<DollarOutlined />}
/>
{/* Combined Totals - Always show when there are results */}
<Card size="small" className="bg-gradient-to-r from-slate-50 to-blue-50 border-slate-200">
<Row gutter={[16, 8]}>
<Col xs={24} sm={8}>
<Statistic
title={<span className="text-xs">Total Per Request</span>}
value={formatCost(multiResult.totals.cost_per_request)}
valueStyle={{ color: "#1890ff", fontSize: "18px", fontFamily: "monospace" }}
/>
</Col>
<Col xs={24} sm={8}>
<Statistic
title={<span className="text-xs">Total Daily</span>}
value={formatCost(multiResult.totals.daily_cost)}
valueStyle={{ color: "#52c41a", fontSize: "18px", fontFamily: "monospace" }}
/>
</Col>
<Col xs={24} sm={8}>
<Statistic
title={<span className="text-xs">Total Monthly</span>}
value={formatCost(multiResult.totals.monthly_cost)}
valueStyle={{ color: "#722ed1", fontSize: "18px", fontFamily: "monospace" }}
/>
</Col>
</Row>
{hasMargin && (
<Row gutter={[16, 8]} className="mt-3 pt-3 border-t border-slate-200">
<Col xs={24} sm={8}>
<div className="text-xs text-gray-500">Margin/Request</div>
<div className="text-sm font-mono text-amber-600">{formatCost(multiResult.totals.margin_per_request)}</div>
</Col>
<Col span={8}>
<Statistic
title="Total Daily"
value={formatCost(multiResult.totals.daily_cost)}
valueStyle={{ color: "#52c41a", fontSize: "20px" }}
prefix={<DollarOutlined />}
/>
<Col xs={24} sm={8}>
<div className="text-xs text-gray-500">Daily Margin</div>
<div className="text-sm font-mono text-amber-600">{formatCost(multiResult.totals.daily_margin)}</div>
</Col>
<Col span={8}>
<Statistic
title="Total Monthly"
value={formatCost(multiResult.totals.monthly_cost)}
valueStyle={{ color: "#722ed1", fontSize: "20px" }}
prefix={<DollarOutlined />}
/>
<Col xs={24} sm={8}>
<div className="text-xs text-gray-500">Monthly Margin</div>
<div className="text-sm font-mono text-amber-600">{formatCost(multiResult.totals.monthly_margin)}</div>
</Col>
</Row>
</Card>
)}
)}
</Card>
{/* Per-Model Summary Table */}
<Table
columns={summaryColumns}
dataSource={summaryData}
pagination={false}
size="small"
expandable={{
expandedRowKeys: Array.from(expandedModels),
expandedRowRender: (record) => {
const entry = validEntries.find((e) => e.entry.id === record.id);
if (!entry?.result) return null;
return (
<div className="py-4 px-2">
<SingleModelBreakdown result={entry.result} loading={entry.loading} />
</div>
);
},
showExpandColumn: false,
}}
/>
{/* Per-Model Table */}
{validEntries.length > 0 && (
<Table
columns={summaryColumns}
dataSource={summaryData}
pagination={false}
size="small"
className="border border-gray-200 rounded-lg"
expandable={{
expandedRowKeys: Array.from(expandedModels),
expandedRowRender: (record) => {
const entry = validEntries.find((e) => e.entry.id === record.id);
if (!entry?.result) return null;
return (
<div className="py-2">
<SingleModelBreakdown result={entry.result} loading={entry.loading} />
</div>
);
},
showExpandColumn: false,
}}
/>
)}
{/* Error Messages */}
{multiResult.entries
.filter((e) => e.error)
.map((e) => (
<div key={e.entry.id} className="text-sm text-red-500 bg-red-50 p-2 rounded">
<div key={e.entry.id} className="text-sm text-red-600 bg-red-50 p-3 rounded-lg border border-red-200">
<span className="font-medium">{e.entry.model || "Unknown model"}: </span>
{e.error}
</div>
@ -360,4 +330,3 @@ const MultiCostResults: React.FC<MultiCostResultsProps> = ({ multiResult }) => {
};
export default MultiCostResults;

View file

@ -196,7 +196,6 @@ export const exportMultiToPDF = (multiResult: MultiModelResult): void => {
<h1>LLM Cost Estimate Report</h1>
<p style="color: #666; margin-top: -20px; margin-bottom: 30px;">${modelCount} model${modelCount !== 1 ? "s" : ""} configured</p>
${modelCount > 1 ? `
<div class="summary-box">
<h2>Combined Totals</h2>
<div class="summary-grid">
@ -213,8 +212,23 @@ export const exportMultiToPDF = (multiResult: MultiModelResult): void => {
<div class="value purple">${formatCostForExport(multiResult.totals.monthly_cost)}</div>
</div>
</div>
${multiResult.totals.margin_per_request > 0 ? `
<div class="summary-grid" style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #ddd;">
<div class="summary-item">
<div class="label">Margin/Request</div>
<div class="value" style="color: #faad14;">${formatCostForExport(multiResult.totals.margin_per_request)}</div>
</div>
<div class="summary-item">
<div class="label">Daily Margin</div>
<div class="value" style="color: #faad14;">${formatCostForExport(multiResult.totals.daily_margin)}</div>
</div>
<div class="summary-item">
<div class="label">Monthly Margin</div>
<div class="value" style="color: #faad14;">${formatCostForExport(multiResult.totals.monthly_margin)}</div>
</div>
</div>
` : ""}
</div>
` : ""}
<h2>Model Breakdown</h2>
${validEntries.map((e) => generateModelSection(e.result!)).join("")}
@ -243,15 +257,16 @@ export const exportMultiToCSV = (multiResult: MultiModelResult): void => {
];
// Summary section
if (validEntries.length > 1) {
rows.push(
["COMBINED TOTALS"],
["Total Per Request", multiResult.totals.cost_per_request.toString()],
["Total Daily", multiResult.totals.daily_cost?.toString() || "-"],
["Total Monthly", multiResult.totals.monthly_cost?.toString() || "-"],
[""]
);
}
rows.push(
["COMBINED TOTALS"],
["Total Per Request", multiResult.totals.cost_per_request.toString()],
["Total Daily", multiResult.totals.daily_cost?.toString() || "-"],
["Total Monthly", multiResult.totals.monthly_cost?.toString() || "-"],
["Margin Per Request", multiResult.totals.margin_per_request.toString()],
["Daily Margin", multiResult.totals.daily_margin?.toString() || "-"],
["Monthly Margin", multiResult.totals.monthly_margin?.toString() || "-"],
[""]
);
// Summary table header
rows.push([

View file

@ -31,6 +31,9 @@ export interface MultiModelResult {
cost_per_request: number;
daily_cost: number | null;
monthly_cost: number | null;
margin_per_request: number;
daily_margin: number | null;
monthly_margin: number | null;
};
}

View file

@ -159,16 +159,26 @@ export function useMultiCostEstimate(accessToken: string | null) {
let totalCostPerRequest = 0;
let totalDailyCost: number | null = null;
let totalMonthlyCost: number | null = null;
let totalMarginPerRequest = 0;
let totalDailyMargin: number | null = null;
let totalMonthlyMargin: number | null = null;
for (const r of results) {
if (r.result) {
totalCostPerRequest += r.result.cost_per_request;
totalMarginPerRequest += r.result.margin_cost_per_request;
if (r.result.daily_cost !== null) {
totalDailyCost = (totalDailyCost ?? 0) + r.result.daily_cost;
}
if (r.result.daily_margin_cost !== null) {
totalDailyMargin = (totalDailyMargin ?? 0) + r.result.daily_margin_cost;
}
if (r.result.monthly_cost !== null) {
totalMonthlyCost = (totalMonthlyCost ?? 0) + r.result.monthly_cost;
}
if (r.result.monthly_margin_cost !== null) {
totalMonthlyMargin = (totalMonthlyMargin ?? 0) + r.result.monthly_margin_cost;
}
}
}
@ -178,6 +188,9 @@ export function useMultiCostEstimate(accessToken: string | null) {
cost_per_request: totalCostPerRequest,
daily_cost: totalDailyCost,
monthly_cost: totalMonthlyCost,
margin_per_request: totalMarginPerRequest,
daily_margin: totalDailyMargin,
monthly_margin: totalMonthlyMargin,
},
};
},