Skip to content
Open
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
1 change: 1 addition & 0 deletions messages/en/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
"nonBilling": "Non-Billing",
"skipped": "Skipped",
"specialSettings": "Special",
"anthropicEffort": "Effort: {effort}",
"times": "times",
"loadedCount": "Loaded {count} records",
"loadingMore": "Loading more...",
Expand Down
3 changes: 2 additions & 1 deletion messages/en/myUsage.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
"next": "Next",
"noLogs": "No logs",
"unknownModel": "Unknown model",
"billingModel": "Billing: {model}"
"billingModel": "Billing: {model}",
"anthropicEffort": "Effort: {effort}"
},
"expiration": {
"title": "Expiration",
Expand Down
1 change: 1 addition & 0 deletions messages/ja/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
"nonBilling": "非課金",
"skipped": "スキップ",
"specialSettings": "特殊設定",
"anthropicEffort": "Effort: {effort}",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new i18n key anthropicEffort has been added, but its value is untranslated. For consistency with other translated keys like billingModel, please provide a Japanese translation for "Effort: {effort}". A common translation would be エフォート: {effort}.

Suggested change
"anthropicEffort": "Effort: {effort}",
"anthropicEffort": "エフォート: {effort}",

"times": "回",
"loadedCount": "{count} 件のレコードを読み込みました",
"loadingMore": "読み込み中...",
Expand Down
3 changes: 2 additions & 1 deletion messages/ja/myUsage.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
"next": "次へ",
"noLogs": "ログがありません",
"unknownModel": "不明なモデル",
"billingModel": "課金: {model}"
"billingModel": "課金: {model}",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

anthropicEffort label not translated

The billingModel label is properly translated in all non-English locales (e.g. "課金: {model}" in Japanese, "Биллинг: {model}" in Russian, "计费:{model}" in Chinese), but anthropicEffort is left as "Effort: {effort}" across all non-English locales. For consistency, this label should be translated.

This same issue applies to:

  • messages/ja/myUsage.json:61"Effort: {effort}" (should be e.g. "エフォート: {effort}")
  • messages/ru/myUsage.json:61"Effort: {effort}" (should be e.g. "Уровень сложности: {effort}")
  • messages/zh-CN/myUsage.json:61"Effort: {effort}" (should be e.g. "思考强度: {effort}")
  • messages/zh-TW/myUsage.json:61"Effort: {effort}" (should be e.g. "思考強度: {effort}")
  • messages/ja/dashboard.json — same pattern
  • messages/ru/dashboard.json — same pattern
  • messages/zh-CN/dashboard.json — same pattern
  • messages/zh-TW/dashboard.json — same pattern
Prompt To Fix With AI
This is a comment left during a code review.
Path: messages/ja/myUsage.json
Line: 61

Comment:
**`anthropicEffort` label not translated**

The `billingModel` label is properly translated in all non-English locales (e.g. `"課金: {model}"` in Japanese, `"Биллинг: {model}"` in Russian, `"计费:{model}"` in Chinese), but `anthropicEffort` is left as `"Effort: {effort}"` across all non-English locales. For consistency, this label should be translated.

This same issue applies to:
- `messages/ja/myUsage.json:61``"Effort: {effort}"` (should be e.g. `"エフォート: {effort}"`)
- `messages/ru/myUsage.json:61``"Effort: {effort}"` (should be e.g. `"Уровень сложности: {effort}"`)
- `messages/zh-CN/myUsage.json:61``"Effort: {effort}"` (should be e.g. `"思考强度: {effort}"`)
- `messages/zh-TW/myUsage.json:61``"Effort: {effort}"` (should be e.g. `"思考強度: {effort}"`)
- `messages/ja/dashboard.json` — same pattern
- `messages/ru/dashboard.json` — same pattern
- `messages/zh-CN/dashboard.json` — same pattern
- `messages/zh-TW/dashboard.json` — same pattern

How can I resolve this? If you propose a fix, please make it concise.

"anthropicEffort": "Effort: {effort}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new i18n key anthropicEffort has been added, but its value is untranslated. For consistency with other translated keys like billingModel, please provide a Japanese translation for "Effort: {effort}". A common translation would be エフォート: {effort}.

Suggested change
"anthropicEffort": "Effort: {effort}"
"anthropicEffort": "エフォート: {effort}"

},
"expiration": {
"title": "有効期限",
Expand Down
1 change: 1 addition & 0 deletions messages/ru/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
"nonBilling": "Не тарифицируется",
"skipped": "Пропущено",
"specialSettings": "Особые",
"anthropicEffort": "Effort: {effort}",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new i18n key anthropicEffort has been added, but its value is untranslated. For consistency with other translated keys like billingModel, please provide a Russian translation for "Effort: {effort}". A possible translation is Усилие: {effort}.

Suggested change
"anthropicEffort": "Effort: {effort}",
"anthropicEffort": "Усилие: {effort}",

"times": "раз",
"loadedCount": "Загружено {count} записей",
"loadingMore": "Загрузка...",
Expand Down
3 changes: 2 additions & 1 deletion messages/ru/myUsage.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
"next": "Вперед",
"noLogs": "Нет записей",
"unknownModel": "Неизвестная модель",
"billingModel": "Биллинг: {model}"
"billingModel": "Биллинг: {model}",
"anthropicEffort": "Effort: {effort}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new i18n key anthropicEffort has been added, but its value is untranslated. For consistency with other translated keys like billingModel, please provide a Russian translation for "Effort: {effort}". A possible translation is Усилие: {effort}.

Suggested change
"anthropicEffort": "Effort: {effort}"
"anthropicEffort": "Усилие: {effort}"

},
"expiration": {
"title": "Срок действия",
Expand Down
1 change: 1 addition & 0 deletions messages/zh-CN/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
"nonBilling": "非计费",
"skipped": "已跳过",
"specialSettings": "特殊设置",
"anthropicEffort": "Effort: {effort}",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

zh-CN 文案不要直接回退成英文。

这里会直接显示在中文界面里,Effort 没有本地化,会和周围已翻译字段不一致。请补上对应的中文文案。

As per coding guidelines "All user-facing strings must use i18n (5 languages supported: zh-CN, zh-TW, en, ja, ru). Never hardcode display text".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@messages/zh-CN/dashboard.json` at line 161, The zh-CN translation for the key
"anthropicEffort" contains an untranslated English label; update the value for
"anthropicEffort" in messages/zh-CN/dashboard.json to a proper Chinese string
(e.g., "努力程度:{effort}" or "努力:{effort}") so it matches surrounding translated
fields and follows i18n rules.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new i18n key anthropicEffort has been added, but its value is untranslated. For consistency with other translated keys like billingModel ("计费:{model}"), please provide a Chinese (Simplified) translation for "Effort: {effort}". A possible translation could be "算力: {effort}".

Suggested change
"anthropicEffort": "Effort: {effort}",
"anthropicEffort": "算力: {effort}",

"times": "次",
"loadedCount": "已加载 {count} 条记录",
"loadingMore": "加载更多中...",
Expand Down
3 changes: 2 additions & 1 deletion messages/zh-CN/myUsage.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
"next": "下一页",
"noLogs": "暂无日志",
"unknownModel": "未知模型",
"billingModel": "计费:{model}"
"billingModel": "计费:{model}",
"anthropicEffort": "Effort: {effort}"
Comment on lines +61 to +62
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

这里还没有真正完成本地化。

anthropicEffort 直接插入了 {effort},但当前新增文案没有提供 low / medium / high 的 locale 映射。这样中文界面大概率还是会显示英文等级。建议把 effort 等级拆成独立翻译键,再由渲染层按值映射后插入。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@messages/zh-CN/myUsage.json` around lines 61 - 62, The translation key
"anthropicEffort" currently inserts the raw {effort} value which leaves English
levels visible; add discrete locale keys for each level (e.g., "effort.low",
"effort.medium", "effort.high") in the messages/zh-CN/myUsage.json and change
the rendering code that uses the "anthropicEffort" key to first map the effort
value to its localized string (e.g. localizedEffort = t(`effort.${effort}`)) and
then pass that localizedEffort into t('anthropicEffort', { effort:
localizedEffort }) instead of injecting the raw 'low/medium/high'.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new i18n key anthropicEffort has been added, but its value is untranslated. For consistency with other translated keys like billingModel ("计费:{model}"), please provide a Chinese (Simplified) translation for "Effort: {effort}". A possible translation could be "算力: {effort}".

Suggested change
"anthropicEffort": "Effort: {effort}"
"anthropicEffort": "算力: {effort}"

},
"expiration": {
"title": "过期时间",
Expand Down
1 change: 1 addition & 0 deletions messages/zh-TW/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
"nonBilling": "非計費",
"skipped": "已跳過",
"specialSettings": "特殊設定",
"anthropicEffort": "Effort: {effort}",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new i18n key anthropicEffort has been added, but its value is untranslated. For consistency with other translated keys like billingModel ("計費:{model}"), please provide a Chinese (Traditional) translation for "Effort: {effort}". A possible translation could be "算力: {effort}".

Suggested change
"anthropicEffort": "Effort: {effort}",
"anthropicEffort": "算力: {effort}",

"times": "次數",
"loadedCount": "已載入 {count} 筆記錄",
"loadingMore": "載入更多中...",
Expand Down
3 changes: 2 additions & 1 deletion messages/zh-TW/myUsage.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
"next": "下一頁",
"noLogs": "暫無日誌",
"unknownModel": "未知的模型",
"billingModel": "計費:{model}"
"billingModel": "計費:{model}",
"anthropicEffort": "Effort: {effort}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new i18n key anthropicEffort has been added, but its value is untranslated. For consistency with other translated keys like billingModel ("計費:{model}"), please provide a Chinese (Traditional) translation for "Effort: {effort}". A possible translation could be "算力: {effort}".

Suggested change
"anthropicEffort": "Effort: {effort}"
"anthropicEffort": "算力: {effort}"

},
"expiration": {
"title": "到期時間",
Expand Down
2 changes: 2 additions & 0 deletions src/actions/my-usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export interface MyUsageLogEntry {
createdAt: Date | null;
model: string | null;
billingModel: string | null;
anthropicEffort?: string | null;
modelRedirect: string | null;
inputTokens: number;
outputTokens: number;
Expand Down Expand Up @@ -506,6 +507,7 @@ export async function getMyUsageLogs(
createdAt: log.createdAt,
model: log.model,
billingModel,
anthropicEffort: log.anthropicEffort ?? null,
modelRedirect,
inputTokens: log.inputTokens ?? 0,
outputTokens: log.outputTokens ?? 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import { ArrowRight } from "lucide-react";
import { useTranslations } from "next-intl";
import { useCallback } from "react";
import { type MouseEvent, useCallback } from "react";
import { toast } from "sonner";
import { AnthropicEffortBadge } from "@/components/customs/anthropic-effort-badge";
import { ModelVendorIcon } from "@/components/customs/model-vendor-icon";
import { Badge } from "@/components/ui/badge";
import { copyTextToClipboard } from "@/lib/utils/clipboard";
Expand All @@ -13,62 +14,80 @@ interface ModelDisplayWithRedirectProps {
originalModel: string | null;
currentModel: string | null;
billingModelSource: BillingModelSource;
anthropicEffort?: string | null;
onRedirectClick?: () => void;
}

export function ModelDisplayWithRedirect({
originalModel,
currentModel,
billingModelSource,
anthropicEffort,
onRedirectClick,
}: ModelDisplayWithRedirectProps) {
const t = useTranslations("common");

const tCommon = useTranslations("common");
const tDashboard = useTranslations("dashboard");
// 判断是否发生重定向
const isRedirected = originalModel && currentModel && originalModel !== currentModel;

// 根据计费模型来源配置决定显示哪个模型
const billingModel = billingModelSource === "original" ? originalModel : currentModel;

const handleCopyModel = useCallback(
(e: React.MouseEvent) => {
(e: MouseEvent) => {
e.stopPropagation();
if (!billingModel) return;
void copyTextToClipboard(billingModel).then((ok) => {
if (ok) toast.success(t("copySuccess"));
if (ok) toast.success(tCommon("copySuccess"));
});
},
[billingModel, t]
[billingModel, tCommon]
);

const effortBadge = anthropicEffort ? (
<AnthropicEffortBadge
effort={anthropicEffort}
label={tDashboard("logs.table.anthropicEffort", { effort: anthropicEffort })}
/>
) : null;

if (!isRedirected) {
return (
<div className="flex items-center gap-1.5 min-w-0">
{billingModel ? <ModelVendorIcon modelId={billingModel} /> : null}
<span className="truncate cursor-pointer hover:underline" onClick={handleCopyModel}>
{billingModel || "-"}
</span>
<div className="min-w-0 flex flex-col items-start gap-1">
<div className="flex items-center gap-1.5 min-w-0">
{billingModel ? <ModelVendorIcon modelId={billingModel} /> : null}
<span
className="truncate max-w-full cursor-pointer hover:underline"
onClick={handleCopyModel}
>
{billingModel || "-"}
</span>
</div>
{effortBadge}
</div>
);
}

// 计费模型 + 重定向标记(只显示图标)
return (
<div className="flex items-center gap-1.5 min-w-0">
{billingModel ? <ModelVendorIcon modelId={billingModel} /> : null}
<span className="truncate cursor-pointer hover:underline" onClick={handleCopyModel}>
{billingModel}
</span>
<Badge
variant="outline"
className="cursor-pointer text-xs border-blue-300 text-blue-700 dark:border-blue-700 dark:text-blue-300 px-1 shrink-0"
onClick={(e) => {
e.stopPropagation();
onRedirectClick?.();
}}
>
<ArrowRight className="h-3 w-3" />
</Badge>
<div className="min-w-0 flex flex-col items-start gap-1">
<div className="flex items-center gap-1.5 min-w-0">
{billingModel ? <ModelVendorIcon modelId={billingModel} /> : null}
<span className="truncate cursor-pointer hover:underline" onClick={handleCopyModel}>
{billingModel}
</span>
<Badge
variant="outline"
className="cursor-pointer text-xs border-blue-300 text-blue-700 dark:border-blue-700 dark:text-blue-300 px-1 shrink-0"
onClick={(e) => {
e.stopPropagation();
onRedirectClick?.();
}}
>
<ArrowRight className="h-3 w-3" />
</Badge>
</div>
{effortBadge}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,12 @@ export function UsageLogsTable({
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center gap-1 min-w-0 cursor-help">
<div className="min-w-0 cursor-help">
<ModelDisplayWithRedirect
originalModel={log.originalModel}
currentModel={log.model}
billingModelSource={billingModelSource}
anthropicEffort={log.anthropicEffort}
onRedirectClick={() =>
setDialogState({ logId: log.id, scrollToRedirect: true })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ export function VirtualizedLogsTable({
originalModel={log.originalModel}
currentModel={log.model}
billingModelSource={billingModelSource}
anthropicEffort={log.anthropicEffort}
onRedirectClick={() =>
setDialogState({ logId: log.id, scrollToRedirect: true })
}
Expand Down
7 changes: 7 additions & 0 deletions src/app/[locale]/my-usage/_components/usage-logs-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useTimeZone, useTranslations } from "next-intl";
import { useCallback } from "react";
import { toast } from "sonner";
import type { MyUsageLogEntry } from "@/actions/my-usage";
import { AnthropicEffortBadge } from "@/components/customs/anthropic-effort-badge";
import { ModelVendorIcon } from "@/components/customs/model-vendor-icon";
import { Badge } from "@/components/ui/badge";
import { Skeleton } from "@/components/ui/skeleton";
Expand Down Expand Up @@ -122,6 +123,12 @@ export function UsageLogsTable({
{t("billingModel", { model: log.billingModel })}
</div>
) : null}
{log.anthropicEffort ? (
<AnthropicEffortBadge
effort={log.anthropicEffort}
label={t("anthropicEffort", { effort: log.anthropicEffort })}
/>
) : null}
</TableCell>
<TableCell className="text-right text-xs font-mono tabular-nums">
<div className="flex flex-col items-end leading-tight">
Expand Down
19 changes: 19 additions & 0 deletions src/app/v1/_lib/proxy/message-service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { extractAnthropicEffortFromRequestBody } from "@/lib/utils/anthropic-effort";
import { createMessageRequest } from "@/repository/message";
import type { ProxySession } from "./session";

Expand Down Expand Up @@ -31,6 +32,24 @@ export class ProxyMessageService {
session.setOriginalModel(currentModel);
}

const isAnthropicProvider =
provider.providerType === "claude" || provider.providerType === "claude-auth";
const hasAnthropicEffortAudit = session
.getSpecialSettings()
?.some((setting) => setting.type === "anthropic_effort");

if (isAnthropicProvider && !hasAnthropicEffortAudit) {
const anthropicEffort = extractAnthropicEffortFromRequestBody(session.request.message);
if (anthropicEffort) {
session.addSpecialSetting({
type: "anthropic_effort",
scope: "request",
hit: true,
effort: anthropicEffort,
});
}
}

const messageRequest = await createMessageRequest({
provider_id: provider.id,
user_id: authState.user.id,
Expand Down
39 changes: 39 additions & 0 deletions src/components/customs/anthropic-effort-badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";

const ANTHROPIC_EFFORT_BADGE_STYLES: Record<string, string> = {
auto: "border-sky-300 bg-gradient-to-r from-cyan-50 via-sky-50 to-indigo-50 text-sky-800 dark:border-sky-700 dark:from-cyan-950/40 dark:via-sky-950/40 dark:to-indigo-950/40 dark:text-sky-200",
low: "border-slate-200 bg-slate-50 text-slate-700 dark:border-slate-700 dark:bg-slate-900/40 dark:text-slate-300",
medium:
"border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-300",
high: "border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-800 dark:bg-rose-950/30 dark:text-rose-300",
max: "border-red-300 bg-red-100 text-red-800 dark:border-red-700 dark:bg-red-950/40 dark:text-red-200",
};

const DEFAULT_BADGE_STYLE =
"border-muted-foreground/20 bg-muted/40 text-muted-foreground dark:border-muted-foreground/30 dark:bg-muted/20";

export function getAnthropicEffortBadgeClassName(effort: string): string {
return ANTHROPIC_EFFORT_BADGE_STYLES[effort.trim().toLowerCase()] ?? DEFAULT_BADGE_STYLE;
}

interface AnthropicEffortBadgeProps {
effort: string;
label: string;
className?: string;
}

export function AnthropicEffortBadge({ effort, label, className }: AnthropicEffortBadgeProps) {
return (
<Badge
variant="outline"
className={cn(
"w-fit px-1 text-[10px] leading-tight whitespace-nowrap",
getAnthropicEffortBadgeClassName(effort),
className
)}
>
{label}
</Badge>
);
}
44 changes: 44 additions & 0 deletions src/lib/utils/anthropic-effort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { SpecialSetting } from "@/types/special-settings";

function normalizeAnthropicEffort(value: unknown): string | null {
if (typeof value !== "string") {
return null;
}

const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : null;
}

export function extractAnthropicEffortFromRequestBody(requestBody: unknown): string | null {
if (!requestBody || typeof requestBody !== "object" || Array.isArray(requestBody)) {
return null;
}

const outputConfig = (requestBody as Record<string, unknown>).output_config;
if (!outputConfig || typeof outputConfig !== "object" || Array.isArray(outputConfig)) {
return null;
}

return normalizeAnthropicEffort((outputConfig as Record<string, unknown>).effort);
}

export function extractAnthropicEffortFromSpecialSettings(
specialSettings: SpecialSetting[] | null | undefined
): string | null {
if (!Array.isArray(specialSettings)) {
return null;
}

for (const setting of specialSettings) {
if (setting.type !== "anthropic_effort") {
continue;
}

const normalized = normalizeAnthropicEffort(setting.effort);
if (normalized) {
return normalized;
}
}

return null;
}
2 changes: 2 additions & 0 deletions src/lib/utils/special-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ function buildSettingKey(setting: SpecialSetting): string {
]);
case "guard_intercept":
return JSON.stringify([setting.type, setting.guard, setting.action, setting.statusCode]);
case "anthropic_effort":
return JSON.stringify([setting.type, setting.hit, setting.effort]);
case "anthropic_cache_ttl_header_override":
return JSON.stringify([setting.type, setting.ttl]);
case "anthropic_context_1m_header_override":
Expand Down
Loading
Loading