Files
rootandClaude Opus 5.5 fa344a7184 feat(web): token 数以 K 输入显示,已存凭证显示掩码占位(FIX-8)
- 新增 UnitInput 与 kind=tokens:上下文预算、单次 token 上限、自定义模型上下文窗口以 K 输入(允许小数),存储仍为原始 token 数;校验与提示用 K 表述;列表/详情 token 数统一 K/M 简写(formatTokens)
- 新增 SecretInput 与 kind=secret:已保存时显示「已设置 ••••••••」(有服务端掩码时带末 4 位),聚焦直接输入替换,留空保持,「清除」确认后保存生效;替换模型服务、TMDB、网页搜索、下载器、媒体服务器、浏览器服务、站点账号、Passkey、本机 Token 各处
- 统一只写凭证服务端语义:省略或空串 = 不变,null = 清除(contracts secretInput);下载器/媒体服务器新增清除;站点凭证同类更新可留空保持密码/Cookie/API Key/TOTP,TOTP 可 null 清除,不再误删 TOTP 与 UA
- contextBudget 最小值 4096→4000、contextWindow 1024→1000,便于按 K 填写
- 测试:换算与校验、SecretInput 渲染与状态、各接口留空不变/显式清除

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-30 00:10:46 +08:00

59 lines
2.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// token 数在界面上以 K(千)为单位输入与显示;存储与 API 仍是原始 token 数,只在前端换算。
// 1K = 1000 token(模型目录的上下文窗口也是按 1000 计,如 128K = 128000)。
export const TOKENS_PER_K = 1000;
/** 去掉小数末尾的 0:1.50 → "1.5",2.00 → "2" */
const trim = (n: number, digits: number) => String(Number(n.toFixed(digits)));
/** 列表 / 详情里的 token 数:<1000 原样,千级 "128K",百万级 "1.5M" */
export function formatTokens(n: number | null | undefined): string {
if (n === null || n === undefined || !Number.isFinite(n)) return "—";
const abs = Math.abs(n);
if (abs < TOKENS_PER_K) return String(Math.round(n));
if (abs < 1_000_000) {
const k = n / TOKENS_PER_K;
// 999.95K 以上四舍五入会变成 1000K,直接进位到 M
if (Math.abs(Number(k.toFixed(1))) < 1000) return `${trim(k, Math.abs(k) < 10 ? 2 : 1)}K`;
}
return `${trim(n / 1_000_000, 2)}M`;
}
/** 原始 token 数 → 输入框里的 K 值字符串(null → 空串);最多 3 位小数,正好换回原值 */
export function tokensToK(n: number | null | undefined): string {
if (n === null || n === undefined || !Number.isFinite(n)) return "";
return trim(n / TOKENS_PER_K, 3);
}
/**
* 输入框里的 K 值 → 原始 token 数。
* 空串 → undefined;不是数字 → NaN(交给校验报错);其余四舍五入到整数 token。
*/
export function kToTokens(input: string): number | undefined {
const s = input.trim().replace(/[kK]$/, "").trim();
if (s === "") return undefined;
if (!/^\d+(\.\d+)?$|^\.\d+$/.test(s)) return Number.NaN;
return Math.round(Number(s) * TOKENS_PER_K);
}
export type TokenRange = { min?: number; max?: number; required?: boolean };
/** 范围说明:{min:4000,max:2000000} → "4K–2000K" */
export function tokenRangeText(r: TokenRange): string | null {
if (r.min !== undefined && r.max !== undefined) return `${tokensToK(r.min)}K–${tokensToK(r.max)}K`;
if (r.min !== undefined) return `${tokensToK(r.min)}K 以上`;
if (r.max !== undefined) return `不超过 ${tokensToK(r.max)}K`;
return null;
}
/** 校验 K 值输入;通过返回 null,否则返回一句错误 */
export function tokenInputError(input: string, r: TokenRange = {}): string | null {
const t = kToTokens(input);
if (t === undefined) return r.required ? "填一个数,单位 K" : null;
const range = tokenRangeText(r);
if (Number.isNaN(t)) return "只填数字,单位 K,如 128 或 1.5";
if (t <= 0 || (r.min !== undefined && t < r.min) || (r.max !== undefined && t > r.max))
return range ? `填 ${range}` : "填大于 0 的数";
return null;
}