// 公开检测页(veridrop.roamai.cloud)的文案,中文默认、英文可选。 // // 文风同 roamai.cloud(site/src/i18n/dict/landing.ts 文件头):陈述句,说行为不说原理,不夸张、不拟人,不用破折号串从句。 // 检测项那 11 条的说法与站点 /evaluation(site/src/i18n/dict/evaluation.ts)一致。 export type Lang = "zh" | "en"; /** ?lang= 说了算;没有就看 Accept-Language 的第一个 zh / en;都没有用中文。 */ export function pickLang(url: URL, headers: Headers): Lang { const q = url.searchParams.get("lang"); if (q === "en" || q === "zh") return q; const al = (headers.get("accept-language") ?? "").toLowerCase(); for (const part of al.split(",")) { const tag = part.trim().split(";")[0] ?? ""; if (tag.startsWith("zh")) return "zh"; if (tag.startsWith("en")) return "en"; } return "zh"; } export const GROUP_ORDER = ["authenticity", "capability", "protocol"] as const; export type Group = (typeof GROUP_ORDER)[number]; /** 检测项:页面上的顺序、分组。long_context 不在常规检测里,报告里出现时排在最后。 */ export const ITEMS: readonly { name: string; group: Group }[] = [ { name: "identity", group: "authenticity" }, { name: "behavioral_signature", group: "authenticity" }, { name: "thinking_signature", group: "authenticity" }, { name: "consistency", group: "authenticity" }, { name: "knowledge", group: "authenticity" }, { name: "pdf", group: "capability" }, { name: "structured_output", group: "capability" }, { name: "protocol", group: "protocol" }, { name: "integrity", group: "protocol" }, { name: "message_id", group: "protocol" }, { name: "token_usage", group: "protocol" }, ]; type ItemCopy = { name: string; what: string }; const zh = { htmlLang: "zh-CN", siteTitle: "Claude API 检测", switchTo: { label: "English", lang: "en" as Lang }, lead: "填入兼容 Anthropic Messages API 的接口地址、API Key 和型号,检测会发一组探针请求,看接口背后是不是真的 Claude、PDF 和工具调用这些能力是否正常、返回的格式是否符合官方 API。", protocolNote: "只支持 Anthropic Messages 协议(POST /v1/messages)。OpenAI 格式的接口测不了。", form: { baseUrl: "接口地址", baseUrlHint: "接口的根地址,检测会向它的 /v1/messages 发请求。只收 https。", apiKey: "API Key", apiKeyHint: "只用于这一次检测,不保存、不写日志,报告里只显示打码后的 Key。检测请求按正常用量从这个 Key 扣费。", model: "型号", modelHint: "下拉里是常见的型号,也可以直接输入接口支持的型号名。", mode: "检测深度", modes: { quick: "快速:5 项,约 15 秒", standard: "标准:9 项,约 45 秒", full: "完整:11 项,约 1 分钟" }, submit: "开始检测", running: "检测中,已用 {s} 秒。完成后自动打开报告。", failed: "检测没有完成:{msg}", network: "连接中断,请重试。", }, checksTitle: "检测什么", groups: { authenticity: "真伪", capability: "能力", protocol: "协议" } satisfies Record, items: { identity: { name: "身份一致性", what: "问模型是谁,回答里要有 Claude 和 Anthropic。" }, behavioral_signature: { name: "行为签名", what: "三道题,看排版习惯和拒绝方式是否符合 Claude 的风格。" }, thinking_signature: { name: "thinking 签名", what: "检查 thinking 块上的签名。签名由 Anthropic 服务端生成,中间环节伪造不了。" }, consistency: { name: "模型一致性", what: "响应里的 model 字段,以及同一道题跑三次输出长度的波动。" }, knowledge: { name: "知识准确度", what: "五道关于 Anthropic 和 Claude 的问题。" }, pdf: { name: "PDF 识别", what: "发一份 PDF,看模型能否读出里面的暗号。" }, structured_output: { name: "结构化输出", what: "按 JSON schema 调用工具,检查工具调用的 ID 和字段。" }, protocol: { name: "协议规范性", what: "响应字段、内容块类型、流式事件的顺序。" }, integrity: { name: "响应完整性", what: "同一道题流式和非流式各问一次,比较两次的结果。" }, message_id: { name: "消息标识", what: "消息 ID 和工具调用 ID 的前缀(msg_、toolu_)。" }, token_usage: { name: "Token 用量", what: "用量字段是否齐全、数字是否对得上,包括和 count_tokens 的结果对比。" }, long_context: { name: "长上下文", what: "长上下文检测,这里不提供。" }, } satisfies Record, limitsNote: "每个 IP 每 10 分钟最多检测 3 次、每天最多 20 次,同一时间只能跑一个。", footer: { origin: "基于开源项目 Veridrop(canarybyte/veridrop,AGPL-3.0)修改,由 RoamAI 部署,与 Veridrop 官方无关。", source: "本站源代码", upstream: "Veridrop 原项目", home: "roamai.cloud", }, report: { title: "检测报告", score: "总分", model: "型号", host: "接口", key: "API Key", mode: "检测深度", time: "时间", requests: "请求数", ttft: "首字时间", duration: "总耗时", backoff: "限流后重试", item: "检测项", status: "结果", points: "得分", weight: "权重", statuses: { pass: "通过", fail: "未通过", skip: "跳过", error: "出错" } as Record, details: "明细", errorLabel: "错误", runError: "整轮检测作废:{msg}", again: "再测一次", json: "原始 JSON", note: "总分是各项得分按权重的加权平均,跳过的项不计入。85 分及以上为「优秀」,70 到 84 分为「通过」,50 到 69 分为「基本合格」。", summaries: {} as Record, seconds: "{s} 秒", ms: "{ms} 毫秒", modes: { quick: "快速", standard: "标准", full: "完整" } as Record, }, notFound: { title: "没有这份报告", body: "链接里的报告不存在,或者已经被删除。", back: "回到检测页" }, sourceTitle: "源代码", sourceLead: "这是本站正在运行的检测代码(按 AGPL-3.0 公开)。它移植自 Veridrop 的 Claude 检测,改动写在 src/index.ts 的文件头里。", errors: { badJson: "请求体不是合法的 JSON。", tooLarge: "请求体太大。", rateIp10m: "这个 IP 10 分钟内已经检测了 3 次,请 {s} 秒后再试。", rateIpDay: "这个 IP 今天已经检测了 20 次,请明天再试(UTC 时间零点重置)。", rateHost: "这个接口地址今天已经被检测了很多次,请明天再试(UTC 时间零点重置)。", inflight: "这个 IP 已经有一个检测在跑,请等它结束。", limiterDown: "检测暂时不可用,请稍后再试。", privateHost: "接口地址必须是公网地址。", }, }; export type Copy = typeof zh; const en: Copy = { htmlLang: "en", siteTitle: "Claude API checker", switchTo: { label: "中文", lang: "zh" }, lead: "Enter the base URL, API key and model of an API that speaks the Anthropic Messages API. The check sends a set of probe requests and looks at whether the model behind the API is really Claude, whether features such as PDF input and tool use work, and whether responses follow the official API format.", protocolNote: "Only the Anthropic Messages protocol (POST /v1/messages) is supported. OpenAI-format APIs can't be checked.", form: { baseUrl: "Base URL", baseUrlHint: "The root of the API; requests go to its /v1/messages. https only.", apiKey: "API key", apiKeyHint: "Used only for this check. It is not stored or logged, and the report shows it masked. The probe requests are billed to this key as normal usage.", model: "Model", modelHint: "The list has common models; you can also type any model name the API supports.", mode: "Depth", modes: { quick: "Quick: 5 checks, about 15 s", standard: "Standard: 9 checks, about 45 s", full: "Full: 11 checks, about 1 min" }, submit: "Run the check", running: "Checking, {s} s so far. The report opens when it is done.", failed: "The check did not finish: {msg}", network: "The connection dropped. Please try again.", }, checksTitle: "What is checked", groups: { authenticity: "Authenticity", capability: "Capability", protocol: "Protocol" }, items: { identity: { name: "Identity", what: "Asks the model who it is and checks that the answer names Claude and Anthropic." }, behavioral_signature: { name: "Behavioral signature", what: "Three prompts that check formatting and refusal style typical of Claude." }, thinking_signature: { name: "Thinking signature", what: "Checks the signature on the thinking block. Anthropic's servers create it and a relay cannot forge it.", }, consistency: { name: "Model consistency", what: "The model field in responses, and how much the output length varies over three runs." }, knowledge: { name: "Knowledge", what: "Five questions about Anthropic and Claude." }, pdf: { name: "PDF reading", what: "Sends a PDF and checks that the model reads a code word from it." }, structured_output: { name: "Structured output", what: "A tool call against a JSON schema, including tool call IDs and fields." }, protocol: { name: "Protocol", what: "Response fields, content block types and the order of streaming events." }, integrity: { name: "Response integrity", what: "Compares streaming and non-streaming responses to the same prompt." }, message_id: { name: "Message IDs", what: "The prefixes of message and tool call IDs (msg_, toolu_)." }, token_usage: { name: "Token usage", what: "Whether the usage fields are present and add up, including against count_tokens." }, long_context: { name: "Long context", what: "The long-context check is not offered here." }, }, limitsNote: "Each IP can run 3 checks per 10 minutes and 20 per day, one at a time.", footer: { origin: "Modified from the open-source project Veridrop (canarybyte/veridrop, AGPL-3.0) and run by RoamAI. Not affiliated with the Veridrop project.", source: "Source code of this site", upstream: "Veridrop project", home: "roamai.cloud", }, report: { title: "Check report", score: "Total", model: "Model", host: "API", key: "API key", mode: "Depth", time: "Time", requests: "Requests", ttft: "Time to first token", duration: "Total time", backoff: "Retries after rate limits", item: "Check", status: "Result", points: "Score", weight: "Weight", statuses: { pass: "Pass", fail: "Fail", skip: "Skipped", error: "Error" }, details: "Details", errorLabel: "Error", runError: "The whole run was voided: {msg}", again: "Run another check", json: "Raw JSON", note: "The total is the weighted average of the check scores; skipped checks are left out. 85 or more is Excellent, 70 to 84 Pass, 50 to 69 Marginal.", summaries: { 优秀: "Excellent", 通过: "Pass", 基本合格: "Marginal", 未达标: "Fail" }, seconds: "{s} s", ms: "{ms} ms", modes: { quick: "Quick", standard: "Standard", full: "Full" }, }, notFound: { title: "Report not found", body: "The report in this link does not exist or has been removed.", back: "Back to the checker" }, sourceTitle: "Source code", sourceLead: "This is the checking code running on this site, published under AGPL-3.0. It is a port of Veridrop's Claude checks; the changes are listed at the top of src/index.ts.", errors: { badJson: "The request body is not valid JSON.", tooLarge: "The request body is too large.", rateIp10m: "This IP has run 3 checks in the last 10 minutes. Try again in {s} s.", rateIpDay: "This IP has run 20 checks today. Try again tomorrow (the count resets at 00:00 UTC).", rateHost: "This API has been checked many times today. Try again tomorrow (the count resets at 00:00 UTC).", inflight: "A check from this IP is already running. Wait for it to finish.", limiterDown: "Checks are unavailable right now. Please try again later.", privateHost: "The base URL must be a public address.", }, }; export function copy(lang: Lang): Copy { return lang === "en" ? en : zh; } export function fill(template: string, values: Record): string { return template.replace(/\{(\w+)\}/g, (whole, k: string) => (k in values ? String(values[k]) : whole)); }