Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions apps/desktop-tauri/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "./lib/tauri";
import { useSurfaceSnapshot } from "./hooks/useSurfaceSnapshot";
import { useTheme } from "./hooks/useTheme";
import { useLocale } from "./hooks/useLocale";
import TrayPanel from "./surfaces/TrayPanel";
import { FLOATBAR_WINDOW_LABEL } from "./floatbar/api";
import { LocaleProvider } from "./i18n/LocaleProvider";
Expand Down Expand Up @@ -54,6 +55,7 @@ export default function App() {
}

function AppInner() {
const { t } = useLocale();
const surface = useSurfaceSnapshot();
const [state, setState] = useState<BootstrapState | null>(null);
const [error, setError] = useState<string | null>(null);
Expand Down Expand Up @@ -146,7 +148,7 @@ function AppInner() {
return (
<main className="shell">
<section className="panel error">
<h2>Bootstrap failed</h2>
<h2>{t("BootstrapFailed")}</h2>
<p>{error}</p>
</section>
</main>
Expand All @@ -157,8 +159,8 @@ function AppInner() {
return (
<main className="shell">
<section className="panel">
<h2>Loading shell contract…</h2>
<p>Waiting for the Rust bridge to describe providers, surfaces, and settings.</p>
<h2>{t("LoadingShellContract")}</h2>
<p>{t("LoadingShellContractHint")}</p>
</section>
</main>
);
Expand Down
6 changes: 5 additions & 1 deletion apps/desktop-tauri/src/components/AgentSessions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ export default function AgentSessions() {
key={`${session.host}:${session.provider}:${session.id}`}
onClick={() => focus(session)}
>
<span>{session.provider === "codex" ? "Codex" : "Claude"}</span>
<span>
{session.provider === "codex"
? t("ProviderNameCodex")
: t("ProviderNameClaude")}
</span>
<span>{session.workspace.projectName ?? session.host}</span>
<span>{session.state}</span>
</button>
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop-tauri/src/components/MenuCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,12 +297,12 @@ describe("MenuCard", () => {
const toggle = await screen.findByRole("button", { name: /On-pace budget/ });
expect(screen.getByText("now 20%")).toBeInTheDocument();
expect(screen.getByText("1h 21%")).toBeInTheDocument();
expect(screen.queryByRole("img", { name: /usage pace/i })).not.toBeInTheDocument();
expect(screen.queryByRole("img", { name: /PaceChartAriaLabel/i })).not.toBeInTheDocument();

fireEvent.click(toggle);

expect(toggle).toHaveAttribute("aria-expanded", "true");
expect(screen.getByRole("img", { name: /usage pace/i })).toBeInTheDocument();
expect(screen.getByRole("img", { name: /PaceChartAriaLabel/i })).toBeInTheDocument();
await waitFor(() => {
expect(onLayoutChange).toHaveBeenCalled();
});
Expand Down Expand Up @@ -339,7 +339,7 @@ describe("MenuCard", () => {
expect(await screen.findByText("69% left")).toBeInTheDocument();
expect(screen.queryByText("On-pace budget")).not.toBeInTheDocument();
expect(
screen.queryByRole("img", { name: /usage pace/i }),
screen.queryByRole("img", { name: /PaceChartAriaLabel/i }),
).not.toBeInTheDocument();
});

Expand Down
14 changes: 10 additions & 4 deletions apps/desktop-tauri/src/components/MenuCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,13 @@ function WayfinderUsageBlock({
);
}

function displayPlanName(planName: string | null): string | null {
function displayPlanName(
planName: string | null,
t: (key: LocaleKey) => string,
): string | null {
if (!planName) return null;
const normalized = planName.trim().toLowerCase();
if (normalized === "default_claude_ai") return "Claude AI";
if (normalized === "default_claude_ai") return t("ProviderPlanClaudeAi");
return planName;
}

Expand Down Expand Up @@ -381,7 +384,7 @@ function MetricRow({
</span>
))}
</div>
{expanded && <PaceDetailsChart snap={snap} />}
{expanded && <PaceDetailsChart snap={snap} t={t} />}
</div>
)}
{paceView.kind === "reserve" && (
Expand Down Expand Up @@ -463,7 +466,7 @@ export default function MenuCard({
? maskEmail(provider.accountEmail)
: provider.accountEmail
: null;
const planName = !isWayfinder ? displayPlanName(provider.planName) : null;
const planName = !isWayfinder ? displayPlanName(provider.planName, t) : null;

const metrics: MetricEntry[] = [
...(isWayfinder
Expand Down Expand Up @@ -685,6 +688,7 @@ export default function MenuCard({
label={t("DetailChartCost")}
color="var(--accent)"
formatValue={(v) => `$${v.toFixed(2)}`}
t={t}
/>
)}
{hasCreditsHistory && (
Expand All @@ -693,13 +697,15 @@ export default function MenuCard({
label={t("DetailChartCredits")}
color="var(--provider-status-ok)"
formatValue={(v) => v.toFixed(1)}
t={t}
/>
)}
{hasUsageBreakdown && (
<StackedBarChart
points={chartData!.usageBreakdown}
label={t("DetailChartUsageBreakdown")}
height={56}
t={t}
/>
)}
</section>
Expand Down
33 changes: 19 additions & 14 deletions apps/desktop-tauri/src/components/MiniBarChart.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
/**
* MiniBarChart — lightweight SVG bar chart with no external dependencies.
* Used in ProviderDetail for cost history, credits history, and usage breakdown.
*/
/** MiniBarChart:不依赖外部库的轻量 SVG 柱状图。 */

import type { DailyCostPoint, DailyUsageBreakdown } from "../types/bridge";
import type { LocaleKey } from "../i18n/keys";

interface BarChartProps {
points: DailyCostPoint[];
color?: string;
height?: number;
label?: string;
formatValue?: (v: number) => string;
t: (key: LocaleKey) => string;
}

/** Simple bar chart for daily cost or credits history. */
/** 展示每日成本或积分历史的简单柱状图。 */
export function SimpleBarChart({
points,
color = "#5d87ff",
height = 48,
label,
formatValue,
t,
}: BarChartProps) {
const emptyMsg = t("DetailChartEmpty");
if (points.length === 0) {
return (
<div className="mini-chart mini-chart--empty">
{label && <span className="mini-chart__label">{label}</span>}
<span className="mini-chart__empty-msg">No data</span>
<span className="mini-chart__empty-msg">{emptyMsg}</span>
</div>
);
}
Expand All @@ -34,7 +35,7 @@ export function SimpleBarChart({
const BAR_GAP = 2;
const fmt = formatValue ?? ((v: number) => v.toFixed(2));

// Show at most 30 bars; abbreviate label to last 2 chars of date
// 最多显示 30 根柱,并将日期标签缩短为末尾两位
const visible = points.slice(-30);
const svgWidth = 280;
const barWidth = Math.max(
Expand All @@ -51,7 +52,7 @@ export function SimpleBarChart({
height={height}
viewBox={`0 0 ${actualWidth} ${height}`}
className="mini-chart__svg"
aria-label={label ?? "bar chart"}
aria-label={label ?? t("BarChartAriaLabel")}
>
{visible.map((p, i) => {
const barH = Math.max(1, (p.value / max) * (height - 4));
Expand Down Expand Up @@ -88,7 +89,7 @@ export function SimpleBarChart({
);
}

// Deterministic palette for service names
// 按服务名称稳定分配颜色
const SERVICE_COLORS = [
"#5d87ff",
"#06d6a0",
Expand All @@ -109,19 +110,22 @@ interface StackedBarChartProps {
points: DailyUsageBreakdown[];
height?: number;
label?: string;
t: (key: LocaleKey) => string;
}

/** Stacked bar chart for daily usage breakdown by service. */
/** 按服务展示每日用量明细的堆叠柱状图。 */
export function StackedBarChart({
points,
height = 64,
label,
t,
}: StackedBarChartProps) {
const emptyMsg = t("DetailChartEmpty");
if (points.length === 0) {
return (
<div className="mini-chart mini-chart--empty">
{label && <span className="mini-chart__label">{label}</span>}
<span className="mini-chart__empty-msg">No data</span>
<span className="mini-chart__empty-msg">{emptyMsg}</span>
</div>
);
}
Expand Down Expand Up @@ -150,12 +154,12 @@ export function StackedBarChart({
height={height}
viewBox={`0 0 ${actualWidth} ${height}`}
className="mini-chart__svg"
aria-label={label ?? "stacked bar chart"}
aria-label={label ?? t("StackedBarChartAriaLabel")}
>
{visible.map((p, i) => {
const x = i * (barWidth + BAR_GAP);
const totalH = Math.max(1, (p.totalCreditsUsed / max) * (height - 4));
// Sort services to stack predictably
// 固定服务排序,确保堆叠顺序可预测
const sorted = [...p.services].sort((a, b) =>
a.service.localeCompare(b.service),
);
Expand All @@ -176,7 +180,8 @@ export function StackedBarChart({
rx={1}
>
<title>
{p.day} {s.service}: {s.creditsUsed.toFixed(2)} credits
{p.day} {s.service}: {s.creditsUsed.toFixed(2)}{" "}
{t?.("CreditsLabel") ?? "credits"}
</title>
</rect>
);
Expand Down
13 changes: 8 additions & 5 deletions apps/desktop-tauri/src/components/PaceDetailsChart.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { RateWindowSnapshot } from "../types/bridge";
import { getPaceChartSnapshot } from "../lib/paceBudget";
import type { LocaleKey } from "../i18n/keys";

const CHART_WIDTH = 300;
const CHART_HEIGHT = 76;
Expand All @@ -11,13 +12,15 @@ function chartX(percent: number): number {

function chartY(percent: number): number {
return CHART_HEIGHT - CHART_PADDING
- (percent / 100) * (CHART_HEIGHT - 2 * CHART_PADDING);
- (percent / 100) * (CHART_HEIGHT - 2 * CHART_PADDING);
}

export default function PaceDetailsChart({
snap,
t,
}: {
snap: RateWindowSnapshot;
t: (key: LocaleKey) => string;
}) {
const chart = getPaceChartSnapshot(snap);
if (!chart) return null;
Expand All @@ -35,7 +38,7 @@ export default function PaceDetailsChart({
<svg
viewBox={`0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`}
role="img"
aria-label="Average usage pace and projection through the current rate window"
aria-label={t("PaceChartAriaLabel")}
>
<line
className="pace-details-chart__grid"
Expand Down Expand Up @@ -70,9 +73,9 @@ export default function PaceDetailsChart({
/>
</svg>
<div className="pace-details-chart__legend">
<span data-series="actual">Average so far</span>
<span data-series="ideal">Ideal pace</span>
<span data-series="projection">Projection</span>
<span data-series="actual">{t("PaceChartLegendAverageSoFar")}</span>
<span data-series="ideal">{t("PaceChartLegendIdealPace")}</span>
<span data-series="projection">{t("PaceChartLegendProjection")}</span>
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop-tauri/src/components/PopOutTitleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default function PopOutTitleBar() {
}}
>
<span className="popout-titlebar__title" data-tauri-drag-region>
CodexBar
{t("AppName")}
</span>
<div className="popout-titlebar__controls">
<button
Expand Down
Loading