mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 01:31:08 +00:00
refactor: dedupe ui provider lowercase helpers
This commit is contained in:
@@ -193,7 +193,7 @@ function resolveAnthropic46ForwardCompatModel(params: {
|
||||
fallbackTemplateIds: readonly string[];
|
||||
}): ProviderRuntimeModel | undefined {
|
||||
const trimmedModelId = params.ctx.modelId.trim();
|
||||
const lower = trimmedModelId.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(trimmedModelId);
|
||||
const is46Model =
|
||||
lower === params.dashModelId ||
|
||||
lower === params.dotModelId ||
|
||||
@@ -247,6 +247,16 @@ function resolveAnthropicForwardCompatModel(
|
||||
);
|
||||
}
|
||||
|
||||
function shouldUseAnthropicAdaptiveThinkingDefault(modelId: string): boolean {
|
||||
const lowerModelId = normalizeLowercaseStringOrEmpty(modelId);
|
||||
return (
|
||||
lowerModelId.startsWith(ANTHROPIC_OPUS_46_MODEL_ID) ||
|
||||
lowerModelId.startsWith(ANTHROPIC_OPUS_46_DOT_MODEL_ID) ||
|
||||
lowerModelId.startsWith(ANTHROPIC_SONNET_46_MODEL_ID) ||
|
||||
lowerModelId.startsWith(ANTHROPIC_SONNET_46_DOT_MODEL_ID)
|
||||
);
|
||||
}
|
||||
|
||||
function matchesAnthropicModernModel(modelId: string): boolean {
|
||||
const lower = normalizeLowercaseStringOrEmpty(modelId);
|
||||
return ANTHROPIC_MODERN_MODEL_PREFIXES.some((prefix) => lower.startsWith(prefix));
|
||||
@@ -468,11 +478,7 @@ export function registerAnthropicPlugin(api: OpenClawPluginApi): void {
|
||||
resolveReasoningOutputMode: () => "native",
|
||||
wrapStreamFn: wrapAnthropicProviderStream,
|
||||
resolveDefaultThinkingLevel: ({ modelId }) =>
|
||||
matchesAnthropicModernModel(modelId) &&
|
||||
(modelId.toLowerCase().startsWith(ANTHROPIC_OPUS_46_MODEL_ID) ||
|
||||
modelId.toLowerCase().startsWith(ANTHROPIC_OPUS_46_DOT_MODEL_ID) ||
|
||||
modelId.toLowerCase().startsWith(ANTHROPIC_SONNET_46_MODEL_ID) ||
|
||||
modelId.toLowerCase().startsWith(ANTHROPIC_SONNET_46_DOT_MODEL_ID))
|
||||
matchesAnthropicModernModel(modelId) && shouldUseAnthropicAdaptiveThinkingDefault(modelId)
|
||||
? "adaptive"
|
||||
: undefined,
|
||||
resolveUsageAuth: async (ctx) => await ctx.resolveOAuthToken(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { NormalizedWebhookMessage } from "./monitor-normalize.js";
|
||||
import type { BlueBubblesCoreRuntime, WebhookTarget } from "./monitor-shared.js";
|
||||
import type { OpenClawConfig } from "./runtime-api.js";
|
||||
@@ -70,7 +71,7 @@ function combineDebounceEntries(entries: BlueBubblesDebounceEntry[]): Normalized
|
||||
continue;
|
||||
}
|
||||
// Skip duplicate text (URL might be in both text message and balloon)
|
||||
const normalizedText = text.toLowerCase();
|
||||
const normalizedText = normalizeLowercaseStringOrEmpty(text);
|
||||
if (seenTexts.has(normalizedText)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ function rememberPendingOutboundMessageId(entry: {
|
||||
chatId: typeof entry.chatId === "number" ? entry.chatId : undefined,
|
||||
snippetRaw,
|
||||
snippetNorm,
|
||||
isMediaSnippet: snippetRaw.toLowerCase().startsWith("<media:"),
|
||||
isMediaSnippet: normalizeLowercaseStringOrEmpty(snippetRaw).startsWith("<media:"),
|
||||
createdAt: Date.now(),
|
||||
});
|
||||
return pendingOutboundMessageIdCounter;
|
||||
@@ -396,7 +396,7 @@ function resolveBlueBubblesAckReaction(params: {
|
||||
normalizeBlueBubblesReactionInput(raw);
|
||||
return raw;
|
||||
} catch {
|
||||
const key = raw.toLowerCase();
|
||||
const key = normalizeLowercaseStringOrEmpty(raw);
|
||||
if (!invalidAckReactions.has(key)) {
|
||||
invalidAckReactions.add(key);
|
||||
logVerbose(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
const log = createSubsystemLogger("chutes-models");
|
||||
|
||||
@@ -564,12 +565,13 @@ export async function discoverChutesModels(accessToken?: string): Promise<ModelD
|
||||
}
|
||||
seen.add(id);
|
||||
|
||||
const lowerId = normalizeLowercaseStringOrEmpty(id);
|
||||
const isReasoning =
|
||||
entry.supported_features?.includes("reasoning") ||
|
||||
id.toLowerCase().includes("r1") ||
|
||||
id.toLowerCase().includes("thinking") ||
|
||||
id.toLowerCase().includes("reason") ||
|
||||
id.toLowerCase().includes("tee");
|
||||
lowerId.includes("r1") ||
|
||||
lowerId.includes("thinking") ||
|
||||
lowerId.includes("reason") ||
|
||||
lowerId.includes("tee");
|
||||
|
||||
const input: Array<"text" | "image"> = (entry.input_modalities || ["text"]).filter(
|
||||
(i): i is "text" | "image" => i === "text" || i === "image",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import net from "node:net";
|
||||
import tls from "node:tls";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
parseIrcLine,
|
||||
parseIrcPrefix,
|
||||
@@ -93,6 +94,10 @@ function buildFallbackNick(nick: string): string {
|
||||
return `${base}${suffix}`;
|
||||
}
|
||||
|
||||
function normalizeIrcNick(value: string): string {
|
||||
return normalizeLowercaseStringOrEmpty(value);
|
||||
}
|
||||
|
||||
export function buildIrcNickServCommands(options?: IrcNickServOptions): string[] {
|
||||
if (!options || options.enabled === false) {
|
||||
return [];
|
||||
@@ -187,7 +192,7 @@ export async function connectIrcClient(options: IrcClientOptions): Promise<IrcCl
|
||||
if (!fallbackNickAttempted) {
|
||||
fallbackNickAttempted = true;
|
||||
const fallbackNick = buildFallbackNick(desiredNick);
|
||||
if (fallbackNick.toLowerCase() !== currentNick.toLowerCase()) {
|
||||
if (normalizeIrcNick(fallbackNick) !== normalizeIrcNick(currentNick)) {
|
||||
try {
|
||||
sendRaw(`NICK ${fallbackNick}`);
|
||||
currentNick = fallbackNick;
|
||||
@@ -288,7 +293,7 @@ export async function connectIrcClient(options: IrcClientOptions): Promise<IrcCl
|
||||
|
||||
if (line.command === "NICK") {
|
||||
const prefix = parseIrcPrefix(line.prefix);
|
||||
if (prefix.nick && prefix.nick.toLowerCase() === currentNick.toLowerCase()) {
|
||||
if (prefix.nick && normalizeIrcNick(prefix.nick) === normalizeIrcNick(currentNick)) {
|
||||
const next =
|
||||
line.trailing != null
|
||||
? line.trailing
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { ResolvedIrcAccount } from "./accounts.js";
|
||||
import { normalizeIrcAllowlist, resolveIrcAllowlistMatch } from "./normalize.js";
|
||||
import {
|
||||
@@ -209,7 +212,7 @@ export async function handleIrcInbound(params: {
|
||||
if (!dmAllowed) {
|
||||
if (dmPolicy === "pairing") {
|
||||
await pairing.issueChallenge({
|
||||
senderId: senderDisplay.toLowerCase(),
|
||||
senderId: normalizeLowercaseStringOrEmpty(senderDisplay),
|
||||
senderIdLine: `Your IRC id: ${senderDisplay}`,
|
||||
meta: { name: message.senderNick || undefined },
|
||||
sendPairingReply: async (text) => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { resolveLoggerBackedRuntime } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveIrcAccount } from "./accounts.js";
|
||||
import { connectIrcClient, type IrcClient } from "./client.js";
|
||||
import { buildIrcConnectOptions } from "./connect-options.js";
|
||||
@@ -79,7 +80,10 @@ export async function monitorIrcProvider(opts: IrcMonitorOptions): Promise<{ sto
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
if (event.senderNick.toLowerCase() === client.nick.toLowerCase()) {
|
||||
if (
|
||||
normalizeLowercaseStringOrEmpty(event.senderNick) ===
|
||||
normalizeLowercaseStringOrEmpty(client.nick)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { normalizeIrcAllowlist, resolveIrcAllowlistMatch } from "./normalize.js";
|
||||
import type { IrcAccountConfig, IrcChannelConfig } from "./types.js";
|
||||
import type { IrcInboundMessage } from "./types.js";
|
||||
@@ -36,8 +37,10 @@ export function resolveIrcGroupMatch(params: {
|
||||
};
|
||||
}
|
||||
|
||||
const targetLower = params.target.toLowerCase();
|
||||
const directKey = Object.keys(groups).find((key) => key.toLowerCase() === targetLower);
|
||||
const targetLower = normalizeLowercaseStringOrEmpty(params.target);
|
||||
const directKey = Object.keys(groups).find(
|
||||
(key) => normalizeLowercaseStringOrEmpty(key) === targetLower,
|
||||
);
|
||||
if (directKey) {
|
||||
const matched = groups[directKey];
|
||||
if (matched) {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { resolveActiveTalkProviderConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type { SpeechVoiceOption } from "openclaw/plugin-sdk/speech";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { definePluginEntry, type OpenClawPluginApi } from "./api.js";
|
||||
|
||||
function mask(s: string, keep: number = 6): string {
|
||||
@@ -75,20 +78,16 @@ function findVoice(voices: SpeechVoiceOption[], query: string): SpeechVoiceOptio
|
||||
if (!q) {
|
||||
return null;
|
||||
}
|
||||
const lower = q.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(q);
|
||||
const byId = voices.find((v) => v.id === q);
|
||||
if (byId) {
|
||||
return byId;
|
||||
}
|
||||
const exactName = voices.find(
|
||||
(v) => (normalizeOptionalString(v.name)?.toLowerCase() ?? "") === lower,
|
||||
);
|
||||
const exactName = voices.find((v) => normalizeOptionalLowercaseString(v.name) === lower);
|
||||
if (exactName) {
|
||||
return exactName;
|
||||
}
|
||||
const partial = voices.find((v) =>
|
||||
(normalizeOptionalString(v.name)?.toLowerCase() ?? "").includes(lower),
|
||||
);
|
||||
const partial = voices.find((v) => normalizeLowercaseStringOrEmpty(v.name).includes(lower));
|
||||
return partial ?? null;
|
||||
}
|
||||
|
||||
@@ -133,7 +132,7 @@ export default definePluginEntry({
|
||||
const commandLabel = resolveCommandLabel(ctx.channel);
|
||||
const args = ctx.args?.trim() ?? "";
|
||||
const tokens = args.split(/\s+/).filter(Boolean);
|
||||
const action = (tokens[0] ?? "status").toLowerCase();
|
||||
const action = normalizeLowercaseStringOrEmpty(tokens[0] ?? "status");
|
||||
|
||||
const cfg = api.runtime.config.loadConfig();
|
||||
const active = resolveActiveTalkProviderConfig(cfg.talk);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { createSubsystemLogger, retryAsync } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
const log = createSubsystemLogger("venice-models");
|
||||
|
||||
@@ -504,7 +505,7 @@ function isRetryableVeniceDiscoveryError(err: unknown): boolean {
|
||||
if (err instanceof Error && err.name === "AbortError") {
|
||||
return true;
|
||||
}
|
||||
if (err instanceof TypeError && err.message.toLowerCase() === "fetch failed") {
|
||||
if (err instanceof TypeError && normalizeLowercaseStringOrEmpty(err.message) === "fetch failed") {
|
||||
return true;
|
||||
}
|
||||
return hasRetryableNetworkCode(err);
|
||||
@@ -609,11 +610,12 @@ export async function discoverVeniceModels(): Promise<ModelDefinitionConfig[]> {
|
||||
models.push(definition);
|
||||
} else {
|
||||
const apiSpec = apiModel.model_spec;
|
||||
const lowerModelId = normalizeLowercaseStringOrEmpty(apiModel.id);
|
||||
const isReasoning =
|
||||
apiSpec?.capabilities?.supportsReasoning ||
|
||||
apiModel.id.toLowerCase().includes("thinking") ||
|
||||
apiModel.id.toLowerCase().includes("reason") ||
|
||||
apiModel.id.toLowerCase().includes("r1");
|
||||
lowerModelId.includes("thinking") ||
|
||||
lowerModelId.includes("reason") ||
|
||||
lowerModelId.includes("r1");
|
||||
|
||||
const hasVision = apiSpec?.capabilities?.supportsVision === true;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
mergeUsageLatency,
|
||||
} from "../../../../src/shared/usage-aggregates.js";
|
||||
import { t } from "../../i18n/index.ts";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../string-coerce.ts";
|
||||
import { UsageSessionEntry, UsageTotals, UsageAggregates } from "./usageTypes.ts";
|
||||
|
||||
const CHARS_PER_TOKEN = 4;
|
||||
@@ -105,7 +106,7 @@ function buildPeakErrorHours(sessions: UsageSessionEntry[], timeZone: "local" |
|
||||
.map((entry) => ({
|
||||
label: formatHourLabel(entry.hour),
|
||||
value: `${(entry.rate * 100).toFixed(2)}%`,
|
||||
sub: `${Math.round(entry.errors)} ${t("usage.overview.errors").toLowerCase()} · ${Math.round(entry.msgs)} ${t("usage.overview.messagesAbbrev")}`,
|
||||
sub: `${Math.round(entry.errors)} ${normalizeLowercaseStringOrEmpty(t("usage.overview.errors"))} · ${Math.round(entry.msgs)} ${t("usage.overview.messagesAbbrev")}`,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -198,7 +199,7 @@ function renderUsageMosaic(
|
||||
<div class="usage-mosaic-sub">${t("usage.mosaic.subtitleEmpty")}</div>
|
||||
</div>
|
||||
<div class="usage-mosaic-total">
|
||||
${formatTokens(0)} ${t("usage.metrics.tokens").toLowerCase()}
|
||||
${formatTokens(0)} ${normalizeLowercaseStringOrEmpty(t("usage.metrics.tokens"))}
|
||||
</div>
|
||||
</div>
|
||||
<div class="usage-empty-block usage-empty-block--compact">
|
||||
@@ -226,7 +227,8 @@ function renderUsageMosaic(
|
||||
</div>
|
||||
</div>
|
||||
<div class="usage-mosaic-total">
|
||||
${formatTokens(stats.totalTokens)} ${t("usage.metrics.tokens").toLowerCase()}
|
||||
${formatTokens(stats.totalTokens)}
|
||||
${normalizeLowercaseStringOrEmpty(t("usage.metrics.tokens"))}
|
||||
</div>
|
||||
</div>
|
||||
<div class="usage-mosaic-grid">
|
||||
@@ -260,7 +262,9 @@ function renderUsageMosaic(
|
||||
value > 0
|
||||
? `color-mix(in srgb, var(--accent) ${(8 + intensity * 70).toFixed(1)}%, transparent)`
|
||||
: "transparent";
|
||||
const title = `${hour}:00 · ${formatTokens(value)} ${t("usage.metrics.tokens").toLowerCase()}`;
|
||||
const title = `${hour}:00 · ${formatTokens(value)} ${normalizeLowercaseStringOrEmpty(
|
||||
t("usage.metrics.tokens"),
|
||||
)}`;
|
||||
const border =
|
||||
intensity > 0.7
|
||||
? "color-mix(in srgb, var(--accent) 60%, transparent)"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { html, svg, nothing } from "lit";
|
||||
import { formatDurationCompact } from "../../../../src/infra/format-time/format-duration.ts";
|
||||
import { t } from "../../i18n/index.ts";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../string-coerce.ts";
|
||||
import { parseToolSummary } from "../usage-helpers.ts";
|
||||
import { charsToTokens, formatCost, formatTokens } from "./usage-metrics.ts";
|
||||
import { renderInsightList } from "./usage-render-overview.ts";
|
||||
@@ -124,8 +125,10 @@ function renderSessionSummary(
|
||||
<div class="session-summary-title">${t("usage.overview.messages")}</div>
|
||||
<div class="stat-value session-summary-value">${usage.messageCounts?.total ?? 0}</div>
|
||||
<div class="session-summary-meta">
|
||||
${usage.messageCounts?.user ?? 0} ${t("usage.overview.user").toLowerCase()} ·
|
||||
${usage.messageCounts?.assistant ?? 0} ${t("usage.overview.assistant").toLowerCase()}
|
||||
${usage.messageCounts?.user ?? 0}
|
||||
${normalizeLowercaseStringOrEmpty(t("usage.overview.user"))} ·
|
||||
${usage.messageCounts?.assistant ?? 0}
|
||||
${normalizeLowercaseStringOrEmpty(t("usage.overview.assistant"))}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat session-summary-card">
|
||||
@@ -280,9 +283,10 @@ function renderSessionDetailPanel(
|
||||
${usage
|
||||
? html`
|
||||
<span
|
||||
><strong>${formatTokens(headerStats.totalTokens)}</strong> ${t(
|
||||
"usage.metrics.tokens",
|
||||
).toLowerCase()}${cursorIndicator}</span
|
||||
><strong>${formatTokens(headerStats.totalTokens)}</strong>
|
||||
${normalizeLowercaseStringOrEmpty(
|
||||
t("usage.metrics.tokens"),
|
||||
)}${cursorIndicator}</span
|
||||
>
|
||||
<span><strong>${formatCost(headerStats.totalCost)}</strong>${cursorIndicator}</span>
|
||||
`
|
||||
@@ -582,7 +586,7 @@ function renderTimeSeriesCompact(
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}),
|
||||
`${formatTokens(val)} ${t("usage.metrics.tokens").toLowerCase()}`,
|
||||
`${formatTokens(val)} ${normalizeLowercaseStringOrEmpty(t("usage.metrics.tokens"))}`,
|
||||
];
|
||||
if (breakdownByType) {
|
||||
tooltipLines.push(`Out ${formatTokens(p.output)}`);
|
||||
@@ -1034,7 +1038,7 @@ function renderSessionLogsCompact(
|
||||
`;
|
||||
}
|
||||
|
||||
const normalizedQuery = filters.query.trim().toLowerCase();
|
||||
const normalizedQuery = normalizeLowercaseStringOrEmpty(filters.query);
|
||||
const entries = logs.map((log) => {
|
||||
const toolInfo = parseToolSummary(log.content);
|
||||
const cleanContent = toolInfo.cleanContent || log.content;
|
||||
@@ -1069,7 +1073,7 @@ function renderSessionLogsCompact(
|
||||
}
|
||||
}
|
||||
if (normalizedQuery) {
|
||||
const haystack = entry.cleanContent.toLowerCase();
|
||||
const haystack = normalizeLowercaseStringOrEmpty(entry.cleanContent);
|
||||
if (!haystack.includes(normalizedQuery)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1093,7 +1097,7 @@ function renderSessionLogsCompact(
|
||||
<span>
|
||||
${t("usage.details.conversation")}
|
||||
<span class="session-logs-header-count">
|
||||
(${displayedCount} ${t("usage.overview.messages").toLowerCase()})
|
||||
(${displayedCount} ${normalizeLowercaseStringOrEmpty(t("usage.overview.messages"))})
|
||||
</span>
|
||||
</span>
|
||||
<button class="btn btn--sm" @click=${onToggleExpandedAll}>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { html, nothing } from "lit";
|
||||
import { formatDurationCompact } from "../../../../src/infra/format-time/format-duration.ts";
|
||||
import { t } from "../../i18n/index.ts";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../string-coerce.ts";
|
||||
import {
|
||||
formatCost,
|
||||
formatDayLabel,
|
||||
@@ -283,7 +284,8 @@ function renderDailyChartCompact(
|
||||
<div class="${labelClass}">${shortLabel}</div>
|
||||
<div class="daily-bar-tooltip">
|
||||
<strong>${formatFullDate(d.date)}</strong><br />
|
||||
${formatTokens(d.totalTokens)} ${t("usage.metrics.tokens").toLowerCase()}<br />
|
||||
${formatTokens(d.totalTokens)}
|
||||
${normalizeLowercaseStringOrEmpty(t("usage.metrics.tokens"))}<br />
|
||||
${formatCost(d.totalCost)}
|
||||
${breakdownLines.length
|
||||
? html`${breakdownLines.map((line) => html`<div>${line}</div>`)}`
|
||||
@@ -527,7 +529,7 @@ function renderUsageInsights(
|
||||
return {
|
||||
label: formatDayLabel(day.date),
|
||||
value: `${(rate * 100).toFixed(2)}%`,
|
||||
sub: `${day.errors} ${t("usage.overview.errors").toLowerCase()} · ${day.messages} ${t("usage.overview.messagesAbbrev")} · ${formatTokens(day.tokens)}`,
|
||||
sub: `${day.errors} ${normalizeLowercaseStringOrEmpty(t("usage.overview.errors"))} · ${day.messages} ${t("usage.overview.messagesAbbrev")} · ${formatTokens(day.tokens)}`,
|
||||
rate,
|
||||
};
|
||||
})
|
||||
@@ -570,7 +572,7 @@ function renderUsageInsights(
|
||||
title: t("usage.overview.messages"),
|
||||
hint: t("usage.overview.messagesHint"),
|
||||
value: aggregates.messages.total,
|
||||
sub: `${aggregates.messages.user} ${t("usage.overview.user").toLowerCase()} · ${aggregates.messages.assistant} ${t("usage.overview.assistant").toLowerCase()}`,
|
||||
sub: `${aggregates.messages.user} ${normalizeLowercaseStringOrEmpty(t("usage.overview.user"))} · ${aggregates.messages.assistant} ${normalizeLowercaseStringOrEmpty(t("usage.overview.assistant"))}`,
|
||||
className: "usage-summary-card--hero",
|
||||
})}
|
||||
${renderSummaryStat({
|
||||
@@ -609,7 +611,7 @@ function renderUsageInsights(
|
||||
title: t("usage.overview.errorRate"),
|
||||
hint: errorHint,
|
||||
value: `${errorRatePct.toFixed(2)}%`,
|
||||
sub: `${aggregates.messages.errors} ${t("usage.overview.errors").toLowerCase()} · ${avgDurationLabel} ${t("usage.overview.avgSession")}`,
|
||||
sub: `${aggregates.messages.errors} ${normalizeLowercaseStringOrEmpty(t("usage.overview.errors"))} · ${avgDurationLabel} ${t("usage.overview.avgSession")}`,
|
||||
tone: errorRatePct > 5 ? "bad" : errorRatePct > 1 ? "warn" : "good",
|
||||
className: "usage-summary-card--medium",
|
||||
})}
|
||||
@@ -617,7 +619,7 @@ function renderUsageInsights(
|
||||
title: t("usage.overview.avgCost"),
|
||||
hint: costHint,
|
||||
value: formatCost(avgCost, 4),
|
||||
sub: `${formatCost(totals.totalCost)} ${t("usage.breakdown.total").toLowerCase()}`,
|
||||
sub: `${formatCost(totals.totalCost)} ${normalizeLowercaseStringOrEmpty(t("usage.breakdown.total"))}`,
|
||||
className: "usage-summary-card--compact",
|
||||
})}
|
||||
${renderSummaryStat({
|
||||
@@ -848,7 +850,7 @@ function renderSessionsCard(
|
||||
${isTokenMode ? formatTokens(avgValue) : formatCost(avgValue)}
|
||||
${t("usage.sessions.avg")}
|
||||
</span>
|
||||
<span>${totalErrors} ${t("usage.overview.errors").toLowerCase()}</span>
|
||||
<span>${totalErrors} ${normalizeLowercaseStringOrEmpty(t("usage.overview.errors"))}</span>
|
||||
</div>
|
||||
<div class="chart-toggle small">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user