fix(ai-chat): show reset date for future credits

This commit is contained in:
DIYgod 2026-04-09 13:35:42 +08:00
parent 6765523d6f
commit a9860eac10
2 changed files with 101 additions and 3 deletions

View File

@ -0,0 +1,78 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
import { computeRateLimitMessage } from "./rate-limit"
vi.mock("~/i18n", () => ({
getI18n: () => ({
language: "en-GB",
resolvedLanguage: "en-GB",
t: (key: string, options?: Record<string, unknown>) => {
switch (key) {
case "rate_limit.depleted": {
return "AI credits depleted"
}
case "rate_limit.minute": {
return "minute"
}
case "rate_limit.minutes": {
return "minutes"
}
case "rate_limit.resets_at": {
return `resets at ${options?.time as string}`
}
case "rate_limit.resets_in": {
return `resets in ${options?.value as string}`
}
case "rate_limit.credits_left": {
return `${options?.count as number} credits left`
}
case "rate_limit.upgrade_to_get_more": {
return "Start free trial to get more AI credits."
}
default: {
return key
}
}
},
}),
}))
describe("computeRateLimitMessage", () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
it("shows only the time when credits reset later today", () => {
vi.setSystemTime(new Date(2026, 3, 10, 8, 0))
const message = computeRateLimitMessage(undefined, {
usage: {
total: 5_000_000,
used: 5_000_000,
remaining: 0,
resetAt: new Date(2026, 3, 10, 20, 45),
},
})
expect(message).toBe("AI credits depleted · resets at 20:45")
})
it("includes the date when credits reset on a future day", () => {
vi.setSystemTime(new Date(2026, 3, 10, 8, 0))
const message = computeRateLimitMessage(undefined, {
usage: {
total: 5_000_000,
used: 5_000_000,
remaining: 0,
resetAt: new Date(2026, 3, 12, 20, 45),
},
})
expect(message).toBe("AI credits depleted · resets at 12 Apr, 20:45")
})
})

View File

@ -20,6 +20,7 @@ const formatResetTime = (windowResetTime: Date) => {
const now = new Date()
const diffMs = resetDate.getTime() - now.getTime()
const diffMinutes = Math.ceil(diffMs / (1000 * 60))
const locale = i18n.language || i18n.resolvedLanguage || undefined
if (diffMinutes < 60 && diffMinutes > 0) {
const unit =
@ -30,12 +31,31 @@ const formatResetTime = (windowResetTime: Date) => {
return t("rate_limit.resets_in", { ns: "ai", value })
}
const timeFormatter = new Intl.DateTimeFormat("en-GB", {
const isSameCalendarDay =
resetDate.getFullYear() === now.getFullYear() &&
resetDate.getMonth() === now.getMonth() &&
resetDate.getDate() === now.getDate()
const time = new Intl.DateTimeFormat(locale, {
hour: "2-digit",
minute: "2-digit",
hour12: false,
})
return t("rate_limit.resets_at", { ns: "ai", time: timeFormatter.format(resetDate) })
}).format(resetDate)
if (isSameCalendarDay) {
return t("rate_limit.resets_at", { ns: "ai", time })
}
const dateTime = new Intl.DateTimeFormat(locale, {
...(resetDate.getFullYear() !== now.getFullYear() ? { year: "numeric" as const } : {}),
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
}).format(resetDate)
return t("rate_limit.resets_at", { ns: "ai", time: dateTime })
}
export function computeIsRateLimited(