mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
fix(settings): stack the AI usage estimate on narrow screens and read readiness from the server
This commit is contained in:
1 parent
86113848b5
commit
b3dcd428f6
2 files changed
+68
-25
No files matched your search
@@ -100,6 +100,39 @@ describe("AiUsageCard", () => {
|
||||
expect(html).not.toContain('data-slot="ai-usage-use-default"');
|
||||
});
|
||||
|
||||
it("follows the server's estimates, not a day threshold of its own", () => {
|
||||
// Two measured days, but the server already published estimates: the
|
||||
// card shows the measured line because the server said it may.
|
||||
const ready = render({ ...CAPPED, measuredDays: 2 });
|
||||
expect(ready).toContain(
|
||||
"Measured: 100,000 tokens a day over the last 2 days",
|
||||
);
|
||||
// Seven days, but the server withheld every estimate: no measured line.
|
||||
const withheld = render({
|
||||
...CAPPED,
|
||||
options: CAPPED.options.map((o) => ({
|
||||
...o,
|
||||
estimatedTokensPerDay: null,
|
||||
})),
|
||||
});
|
||||
expect(withheld).toContain(
|
||||
"An estimate per level appears after three days",
|
||||
);
|
||||
});
|
||||
|
||||
it("stacks the estimate under the level on a narrow screen", () => {
|
||||
const html = render(CAPPED);
|
||||
expect(html).toMatch(
|
||||
/class="[^"]*flex-col[^"]*sm:flex-row[^"]*"[^>]*>\s*<span class="min-w-0 flex-1">/,
|
||||
);
|
||||
expect(html).toMatch(
|
||||
/data-slot="ai-usage-estimate" class="[^"]*sm:shrink-0/,
|
||||
);
|
||||
expect(html).not.toMatch(
|
||||
/data-slot="ai-usage-estimate" class="(?:(?!sm:)[^"])*\bshrink-0/,
|
||||
);
|
||||
});
|
||||
|
||||
it("offers the way back to the default when a choice is stored", () => {
|
||||
const html = render({
|
||||
...CAPPED,
|
||||
|
||||
@@ -70,6 +70,12 @@ export function AiUsageCard() {
|
||||
const editable = provider.canConfigure && !save.isPending;
|
||||
const tokens = (value: number) => new Intl.NumberFormat(locale).format(value);
|
||||
const levelName = (level: AiUsageLevel) => t(LEVEL_KEYS[level].label);
|
||||
// Whether the history suffices for an estimate is the server's call: it
|
||||
// publishes the per-level estimates only once it does. The card reads that
|
||||
// decision instead of repeating its day threshold.
|
||||
const estimatesReady = usage.options.some(
|
||||
(option) => option.estimatedTokensPerDay !== null,
|
||||
);
|
||||
const allowedLevels = usage.options
|
||||
.filter((option) => option.allowed)
|
||||
.map((option) => option.level);
|
||||
@@ -144,31 +150,35 @@ export function AiUsageCard() {
|
||||
<span className="bg-primary size-2 rounded-full" />
|
||||
) : null}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-sm font-medium">
|
||||
{levelName(option.level)}
|
||||
</span>
|
||||
<span className="text-muted-foreground block text-xs">
|
||||
{t(LEVEL_KEYS[option.level].hint)}
|
||||
</span>
|
||||
{!option.allowed ? (
|
||||
<span
|
||||
data-slot="ai-usage-limited"
|
||||
className="text-muted-foreground block text-xs"
|
||||
>
|
||||
{t("settings.ai.usage.limited")}
|
||||
{/* Stacked under 640 px so the estimate never squeezes the
|
||||
level's name and hint into a sliver; side by side above. */}
|
||||
<span className="flex min-w-0 flex-1 flex-col gap-0.5 sm:flex-row sm:items-start sm:gap-3">
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-sm font-medium">
|
||||
{levelName(option.level)}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
<span
|
||||
data-slot="ai-usage-estimate"
|
||||
className="text-muted-foreground shrink-0 text-right text-xs tabular-nums"
|
||||
>
|
||||
{option.estimatedTokensPerDay === null
|
||||
? null
|
||||
: t("settings.ai.usage.estimate", {
|
||||
tokens: tokens(option.estimatedTokensPerDay),
|
||||
})}
|
||||
<span className="text-muted-foreground block text-xs">
|
||||
{t(LEVEL_KEYS[option.level].hint)}
|
||||
</span>
|
||||
{!option.allowed ? (
|
||||
<span
|
||||
data-slot="ai-usage-limited"
|
||||
className="text-muted-foreground block text-xs"
|
||||
>
|
||||
{t("settings.ai.usage.limited")}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
<span
|
||||
data-slot="ai-usage-estimate"
|
||||
className="text-muted-foreground text-xs tabular-nums sm:max-w-[45%] sm:shrink-0 sm:text-right"
|
||||
>
|
||||
{option.estimatedTokensPerDay === null
|
||||
? null
|
||||
: t("settings.ai.usage.estimate", {
|
||||
tokens: tokens(option.estimatedTokensPerDay),
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
@@ -179,7 +189,7 @@ export function AiUsageCard() {
|
||||
data-slot="ai-usage-measured"
|
||||
className="text-muted-foreground text-xs"
|
||||
>
|
||||
{usage.measuredTokensPerDay === null || usage.measuredDays < 3
|
||||
{usage.measuredTokensPerDay === null || !estimatesReady
|
||||
? t("settings.ai.usage.noEstimate")
|
||||
: t("settings.ai.usage.measured", {
|
||||
tokens: tokens(usage.measuredTokensPerDay),
|
||||
|
||||
Reference in new issue
Block a user