diff --git a/PRIVACY.md b/PRIVACY.md index b49c887..55f3dba 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,6 +1,6 @@ # Privacy Policy -Last updated: August 23, 2026 +Last updated: August 26, 2026 AI Limits is a locally running Chrome extension by TiantianFlow. This policy describes version 0.4.3. @@ -162,7 +162,7 @@ metrics; raw provider responses are not persisted. ## Local storage and retention AI Limits stores one application-state record in `chrome.storage.local`. It -contains display and automatic-refresh preferences, per-instance nonsecret +contains display, language, and automatic-refresh preferences, per-instance nonsecret configuration such as a normalized configurable-provider base URL and label, provider permission state, the normalized usage fields listed above, History, and sanitized refresh-attempt metadata. It does not contain provider cookies, diff --git a/README.md b/README.md index c627e14..31df610 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ consumption with elapsed time to show a pace signal. Cursor also shows weekly Grok Bot usage next to its monthly Cursor-model and Other-model limits, plus a per-model included-usage breakdown. It starts empty and reads usage only after you connect an individual provider and approve that provider's optional access. +The side panel supports English and Simplified Chinese. Follow Chrome uses those +languages when Chrome is set to one of them, and falls back to English for every +other Chrome language. Settings can pin English or Simplified Chinese for this +panel. AI Limits is an independent project by TiantianFlow. It is not affiliated with, endorsed by, or authorized by any supported provider, its parent company, or diff --git a/README.zh-CN.md b/README.zh-CN.md index 07b17cf..30ab6ce 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -18,13 +18,14 @@ AI Limits 支持二十个服务:ChatGPT、Claude、Kimi、Cursor、Grok、Mist 服务商提供完整的重置周期信息时,还会将配额消耗与已过时间比较,显示用量节奏。 Cursor 还会在 Cursor 模型与其他模型的月度限额旁显示 Grok Bot 每周用量,并提供 按模型划分的包含用量明细。扩展初始不读取任何数据;只有当你主动连接某个服务 -并批准该服务的可选访问权限后,才会读取其用量。 +并批准该服务的可选访问权限后,才会读取其用量。侧边栏仅支持英文和简体中文;跟随 +Chrome 时,仅在 Chrome 为英文或简体中文时使用对应语言,其他 Chrome 语言会回退到英文。 +也可在 Settings 中为侧边栏固定英文或简体中文。 AI Limits 是 TiantianFlow 开发的独立项目,与任何受支持的服务商、其母公司或 关联方不存在隶属、背书或授权关系。 -> 当前扩展界面为英文。下图的中文文字是项目介绍文案;嵌入的实际扩展界面 -> 仍为英文。后续完成扩展本身的简体中文支持后,我们会重新生成截图。 +> 下图外圈介绍文案为中文;图中嵌入的侧边栏截图目前仍是英文界面。 ![AI Limits Chrome 侧边栏展示示例已用视图、订阅用量、重置时间、用量节奏和服务导航](store-assets/chrome-web-store/zh_CN/screenshot-overview-1280x800.png) diff --git a/entrypoints/sidepanel/App.test.tsx b/entrypoints/sidepanel/App.test.tsx index 65fc7ae..a7ebf49 100644 --- a/entrypoints/sidepanel/App.test.tsx +++ b/entrypoints/sidepanel/App.test.tsx @@ -1255,6 +1255,11 @@ describe("side-panel App", () => { name: "Disconnect relay.example · 22222222", }), ); + fireEvent.click( + screen.getByRole("button", { + name: "Confirm disconnect relay.example · 22222222", + }), + ); expect(await screen.findByRole("status")).toHaveTextContent( "Couldn’t disconnect relay.example · 22222222.", ); @@ -1555,6 +1560,9 @@ describe("side-panel App", () => { await screen.findByText("ChatGPT"); fireEvent.click(screen.getByRole("button", { name: "Settings" })); fireEvent.click(screen.getByRole("button", { name: "Disconnect ChatGPT" })); + fireEvent.click( + screen.getByRole("button", { name: "Confirm disconnect ChatGPT" }), + ); expect(await screen.findByRole("status")).toHaveTextContent( "Disconnected ChatGPT and deleted its stored usage.", @@ -1584,6 +1592,9 @@ describe("side-panel App", () => { await screen.findByText("ChatGPT"); fireEvent.click(screen.getByRole("button", { name: "Settings" })); fireEvent.click(screen.getByRole("button", { name: "Disconnect ChatGPT" })); + fireEvent.click( + screen.getByRole("button", { name: "Confirm disconnect ChatGPT" }), + ); expect(await screen.findByRole("status")).toHaveTextContent( "Deleted ChatGPT’s local usage. Browser access could not be removed.", @@ -1615,6 +1626,9 @@ describe("side-panel App", () => { fireEvent.click(screen.getByRole("button", { name: "Settings" })); fireEvent.click(screen.getByRole("switch", { name: "Automatic refresh" })); fireEvent.click(screen.getByRole("button", { name: "Disconnect ChatGPT" })); + fireEvent.click( + screen.getByRole("button", { name: "Confirm disconnect ChatGPT" }), + ); fireEvent.click(screen.getByRole("button", { name: "Delete all local data" })); fireEvent.click(screen.getByRole("button", { name: "Confirm delete all local data" })); diff --git a/entrypoints/sidepanel/Cockpit.test.tsx b/entrypoints/sidepanel/Cockpit.test.tsx index 776e32e..e83c370 100644 --- a/entrypoints/sidepanel/Cockpit.test.tsx +++ b/entrypoints/sidepanel/Cockpit.test.tsx @@ -483,6 +483,10 @@ describe("Cockpit", () => { ); fireEvent.click(screen.getByRole("button", { name: "Settings" })); fireEvent.click(screen.getByRole("button", { name: "Disconnect Work relay" })); + expect(onDisconnectInstance).not.toHaveBeenCalled(); + fireEvent.click( + screen.getByRole("button", { name: "Confirm disconnect Work relay" }), + ); expect(onDisconnectInstance).toHaveBeenCalledWith(WORK_NEW_API_ID); expect(onDisconnectInstance).not.toHaveBeenCalledWith(PERSONAL_NEW_API_ID); }); @@ -1488,6 +1492,10 @@ describe("Cockpit", () => { ); fireEvent.click(screen.getByRole("button", { name: "Settings for ChatGPT" })); fireEvent.click(screen.getByRole("button", { name: "Disconnect ChatGPT" })); + expect(onDisconnectInstance).not.toHaveBeenCalled(); + fireEvent.click( + screen.getByRole("button", { name: "Confirm disconnect ChatGPT" }), + ); expect(onDisconnectInstance).toHaveBeenCalledWith("chatgpt:default"); expect( @@ -1505,6 +1513,35 @@ describe("Cockpit", () => { ).not.toBeInTheDocument(); }); + it("keeps Settings open when disconnect is cancelled", () => { + const onDisconnectInstance = vi.fn(); + render( + , + ); + + fireEvent.click(screen.getByRole("button", { name: "Settings" })); + fireEvent.click(screen.getByRole("button", { name: "Disconnect ChatGPT" })); + fireEvent.click( + screen.getByRole("button", { name: "Cancel disconnect ChatGPT" }), + ); + + expect(onDisconnectInstance).not.toHaveBeenCalled(); + expect( + screen.getByRole("region", { name: "Provider settings" }), + ).toBeVisible(); + expect( + screen.queryByRole("group", { name: "Confirm disconnect" }), + ).not.toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Disconnect ChatGPT" })).toHaveFocus(); + }); + it("shows a truthful unavailable screen if the active provider disconnects", () => { const state = createFixtureState(NOW); const view = render( @@ -2908,6 +2945,10 @@ describe("Cockpit", () => { expect(action.parentElement).toBe(providerActions); } fireEvent.click(screen.getByRole("button", { name: "Disconnect Claude" })); + expect(onDisconnectProvider).not.toHaveBeenCalled(); + fireEvent.click( + screen.getByRole("button", { name: "Confirm disconnect Claude" }), + ); expect(onDisconnectProvider).toHaveBeenCalledWith("claude"); }); diff --git a/entrypoints/sidepanel/styles.css b/entrypoints/sidepanel/styles.css index d0af819..1aa0cd7 100644 --- a/entrypoints/sidepanel/styles.css +++ b/entrypoints/sidepanel/styles.css @@ -2396,8 +2396,8 @@ section[aria-labelledby="overview-title"] .provider-list { align-items: flex-start; } -.settings-provider-row--renaming .settings-provider-actions { - justify-content: flex-start; +.settings-provider-row--disconnecting .settings-disconnect-confirm { + min-width: 0; } .settings-provider-actions > button { @@ -2421,7 +2421,7 @@ section[aria-labelledby="overview-title"] .provider-list { background: color-mix(in srgb, var(--surface-muted) 55%, var(--surface)); } -.settings-provider-actions > button:last-child { +.settings-provider-actions > button.settings-provider-actions__disconnect { color: #be123c; } diff --git a/entrypoints/sidepanel/views/SettingsView.tsx b/entrypoints/sidepanel/views/SettingsView.tsx index 8ade6e7..af0e440 100644 --- a/entrypoints/sidepanel/views/SettingsView.tsx +++ b/entrypoints/sidepanel/views/SettingsView.tsx @@ -105,6 +105,14 @@ export function SettingsView({ const restoreRenameFocus = useRef(undefined); const renameInputRef = useRef(null); const restoreRenameInputFocus = useRef(false); + const [confirmDisconnectId, setConfirmDisconnectId] = + useState(); + const disconnectTriggerRefs = useRef< + Partial> + >({}); + const restoreDisconnectFocus = useRef( + undefined, + ); const labelsByInstance = instanceLabels(instances); useEffect(() => { @@ -129,6 +137,23 @@ export function SettingsView({ } }, [renameError, renamePending]); + useEffect(() => { + if ( + confirmDisconnectId && + !instances.some((instance) => instance.id === confirmDisconnectId) + ) { + setConfirmDisconnectId(undefined); + } + }, [confirmDisconnectId, instances]); + + useEffect(() => { + if (!confirmDisconnectId && restoreDisconnectFocus.current) { + const instanceId = restoreDisconnectFocus.current; + restoreDisconnectFocus.current = undefined; + disconnectTriggerRefs.current[instanceId]?.focus(); + } + }, [confirmDisconnectId]); + return (
@@ -365,6 +397,7 @@ export function SettingsView({ restoreRenameInputFocus.current = false; setRenameError(""); setLabelDraft(instance.userLabel ?? ""); + setConfirmDisconnectId(undefined); setRenamingInstanceId(instance.id); }} > @@ -398,15 +431,61 @@ export function SettingsView({ ) : null} - + {confirmingDisconnect ? null : ( + + )} + {confirmingDisconnect ? ( +
+

{l10n.t("providerDetail.disconnectConfirmWarning")}

+
+ + +
+
+ ) : null} ); diff --git a/locales/en.yml b/locales/en.yml index b2b9413..a0599fa 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -198,7 +198,7 @@ settings: automaticOn: On · about every 15 minutes automaticOff: Off · refresh manually from the header language: Language - languageDescription: Applies to this side panel. Chrome’s own menus and the extension name stay in Chrome’s language. + languageDescription: English and Simplified Chinese only. Other Chrome languages fall back to English. Chrome’s own menus and the extension name stay in Chrome’s language. languageFollowChrome: Follow Chrome connectedProviders: Connected providers instanceSettings: "{label} settings" @@ -206,7 +206,7 @@ settings: freshnessMethod: "{freshness} · {method} · {readOnly}" apiKeySaved: API key saved noProviders: No providers connected. - disconnectExplanation: Disconnecting removes the provider from Overview and deletes its stored local history. + disconnectExplanation: Disconnecting permanently deletes this provider's credentials, configuration, usage, and history. Permission cleanup can be delayed or shared with another instance. renameGroup: "Rename {label}" instanceLabel: Instance label saveLabelNamed: "Save label for {label}" diff --git a/locales/zh_CN.yml b/locales/zh_CN.yml index fec3603..eb3c841 100644 --- a/locales/zh_CN.yml +++ b/locales/zh_CN.yml @@ -198,7 +198,7 @@ settings: automaticOn: 开 · 大约每 15 分钟 automaticOff: 关 · 请从顶部手动刷新 language: 语言 - languageDescription: 仅作用于本侧边栏。Chrome 自身的菜单和扩展名称仍跟随 Chrome 的语言。 + languageDescription: 仅支持英文和简体中文。其他 Chrome 语言会回退到英文。Chrome 自身的菜单和扩展名称仍跟随 Chrome 的语言。 languageFollowChrome: 跟随 Chrome connectedProviders: 已连接的服务 instanceSettings: "{label} 设置" @@ -206,7 +206,7 @@ settings: freshnessMethod: "{freshness} · {method} · {readOnly}" apiKeySaved: 已保存 API 密钥 noProviders: 尚未连接服务。 - disconnectExplanation: 断开连接会将该服务从总览中移除,并删除其本地历史。 + disconnectExplanation: 断开连接会永久删除该服务的凭据、配置、用量和历史。权限清理可能延迟,或与另一实例共享。 renameGroup: "重命名 {label}" instanceLabel: 实例标签 saveLabelNamed: "保存 {label} 的标签" diff --git a/scripts/publication-contract.mjs b/scripts/publication-contract.mjs index 7d7f5f9..e056c0f 100644 --- a/scripts/publication-contract.mjs +++ b/scripts/publication-contract.mjs @@ -181,9 +181,9 @@ const requiredVisibleElevenLabsStatements = [ { key: "listing", statement: - "confirm the side panel opens with twenty permission-required cards.", + "confirm First Run opens with twenty opt-in provider rows and no prefilled usage.", error: - "Store listing reviewer flow must expect twenty permission-required cards.", + "Store listing reviewer flow must expect twenty First Run provider rows.", }, ]; const requiredVisibleV030Statements = [ diff --git a/scripts/publication-contract.test.js b/scripts/publication-contract.test.js index a3588b0..a6c5870 100644 --- a/scripts/publication-contract.test.js +++ b/scripts/publication-contract.test.js @@ -169,9 +169,9 @@ const elevenLabsPublicationStatements = [ { key: "listing", statement: - "confirm the side panel opens with twenty permission-required cards.", + "confirm First Run opens with twenty opt-in provider rows and no prefilled usage.", error: - "Store listing reviewer flow must expect twenty permission-required cards.", + "Store listing reviewer flow must expect twenty First Run provider rows.", }, ]; const v030PublicationStatements = [ diff --git a/store-assets/README.md b/store-assets/README.md index f48749f..7884222 100644 --- a/store-assets/README.md +++ b/store-assets/README.md @@ -44,14 +44,15 @@ The complete Chrome Web Store submission packs also live in this directory: These four images are for the Simplified Chinese GitHub README and social media. Their dimensions and content are suitable for future localized Chrome -Web Store screenshots, but they are not part of the current store submission. -Chrome Web Store promotional tiles are not localized, so there is no separate -Chinese promotional tile. +Web Store screenshots, but they are not part of the current English store +submission. Chrome Web Store promotional tiles are not localized, so there is +no separate Chinese promotional tile. -The surrounding project copy in these four Chinese images is Simplified -Chinese, while the embedded production extension UI remains in English. After -runtime localization is implemented, regenerate them so both the surrounding -copy and embedded UI are Chinese. +The side panel now supports English and Simplified Chinese. The current Chinese +GitHub images still embed the English production UI; only the surrounding +project copy is Chinese. Regenerate them so the embedded UI uses the Simplified +Chinese catalog. The capture preview currently still leaves the production +Cockpit in English even when `locale=zh_CN`. ### GitHub repository artwork @@ -80,7 +81,7 @@ AI_LIMITS_CHROME_PATH=/absolute/path/to/chrome pnpm assets:store The preview uses the fixed instant `2026-08-09T14:00:00.000Z`, forces light mode, `en-US`, `America/Toronto`, and a device scale factor of 1, and creates a fresh browser context without a user profile. The `locale=zh_CN` preview option -localizes only the surrounding project copy; it intentionally leaves the +localizes only the surrounding project copy; it currently still leaves the production extension UI in English. The pacing image aligns the complete Kimi and Cursor cards below the real sticky side-panel header at production scale. The History image follows the real ChatGPT quota action into the dedicated diff --git a/store-assets/chrome-web-store/listing-en.md b/store-assets/chrome-web-store/listing-en.md index 9a922dd..3223e6e 100644 --- a/store-assets/chrome-web-store/listing-en.md +++ b/store-assets/chrome-web-store/listing-en.md @@ -89,6 +89,11 @@ reported balance, and **Fireworks** shows rated spend over the last 30 days. **GroqCloud** shows five-minute usage rates, and **OpenRouter** shows credits and an optional key budget. +The side panel supports English and Simplified Chinese. It follows Chrome when +Chrome is set to one of those languages, and falls back to English for other +Chrome languages. Settings can pin English or Simplified Chinese for this panel. +Chrome's own menus and the extension name stay in Chrome's language. + AI Limits keeps everything local: your usage history stays on your device and is never sent anywhere else. No ads, no AI Limits account to create, no analytics or telemetry. @@ -101,7 +106,7 @@ parent companies, or their affiliates. ### Required permissions -- `storage`: saves display and refresh preferences, provider access state, +- `storage`: saves display, language, and refresh preferences, provider access state, normalized quota/counter/balance snapshots, up to 30 days of typed per-instance History, and sanitized refresh status in `chrome.storage.local`. It also holds temporary Kimi and Cursor tab-cleanup @@ -265,7 +270,7 @@ The full policy is in [PRIVACY.md](../../PRIVACY.md). 1. Install the submitted build, or extract the ZIP and load its root as an unpacked extension. Pin AI Limits if desired, select its toolbar action, and - confirm the side panel opens with twenty permission-required cards. + confirm First Run opens with twenty opt-in provider rows and no prefilled usage. 2. **ChatGPT:** sign in at `chatgpt.com`, click **Connect** on ChatGPT, approve only the ChatGPT origin, and confirm a plan plus available quota windows or credits appears. Use the card's **Refresh** action once. @@ -362,7 +367,11 @@ The full policy is in [PRIVACY.md](../../PRIVACY.md). 22. Use the header refresh and confirm connected providers retain their previous visible data while refreshing. Open a quota window's **History** action and confirm the dedicated screen appears, then confirm a successful refresh adds - quota history while counter/spend and balance samples remain ungraphed. In Settings, turn automatic - refresh off and on, disconnect one provider, then use **Delete all local - data** and confirm the cards return to the permission-required state. For API-key providers, inspect extension storage after Disconnect and after + quota history while counter/spend and balance samples remain ungraphed. From a + provider detail screen, Disconnect asks for confirmation before deleting + credentials, configuration, usage, and history. In Settings, turn automatic + refresh off and on, disconnect one provider after confirming the same + deletion, then use **Delete all local data** and confirm the cards return to + the permission-required state. For + API-key providers, inspect extension storage after Disconnect and after Delete all to confirm the credential record no longer contains a key. diff --git a/store-assets/chrome-web-store/listing-zh_CN.md b/store-assets/chrome-web-store/listing-zh_CN.md index 9f854ba..c54c709 100644 --- a/store-assets/chrome-web-store/listing-zh_CN.md +++ b/store-assets/chrome-web-store/listing-zh_CN.md @@ -44,6 +44,8 @@ AI Limits 把你的 AI 订阅用量集中显示在同一个 Chrome 侧边栏中 **ChatGPT** 和 **Claude** 直接从你已登录的账号显示套餐、用量周期、重置时间和可用额度。**Kimi** 直接读取你的用量和订阅状态;如果会话需要刷新,会有一个简短、可见的恢复步骤。**Cursor** 会显示 Cursor 模型与其他模型的月度限额以及 Grok Bot 每周用量,并按 Cursor Models 和 Other Models 分组列出各模型的包含用量(输入、输出、费用和服务商合计)。该明细视图是通用的分服务界面;目前只有 Cursor 会填充它。**Grok** 读取你已登录的消费者版套餐和每周或每月用量池,不显示按模式返回的短窗。**Mistral** 显示本月至今支出和可用额度,**Perplexity** 显示周期性、已购买和促销额度池。**ElevenLabs** 通过你自己创建的、权限受限的只读 API 密钥连接。**New API** 用你提供的密钥连接自建或第三方 API 兼容实例。AI Limits 支持多个相互独立的 New API 实例,包括同一来源下多个分别标注的密钥。**LiteLLM**、**ClawRouter**、**sub2api** 和 **LLM Proxy** 使用你提供的密钥连接可配置实例。**DeepSeek** 和 **Moonshot** 显示开发平台余额,**DeepInfra** 显示当月支出相对上限的配额或服务商报告的余额,**Fireworks** 显示最近 30 天已计价支出。**OpenAI** 读取平台组织/开发者账单(组织支出和补全用量,并在需要时回退到额度接口),**GroqCloud** 显示五分钟用量速率,**OpenRouter** 显示额度和可选密钥预算。 +侧边栏仅支持英文和简体中文。跟随 Chrome 时,仅在 Chrome 为英文或简体中文时使用对应语言,其他 Chrome 语言会回退到英文。也可以在 Settings 中为侧边栏固定英文或简体中文。Chrome 自身的菜单和扩展名称仍跟随 Chrome 的语言。 + AI Limits 的一切数据都保存在本地:你的用量历史只留在你自己的设备上,不会发送到任何别处。没有广告,无需注册 AI Limits 账号,也没有任何分析或遥测。 AI Limits 是 TiantianFlow 独立开发的项目,与任何已连接的服务提供商及其母公司或关联公司均无关联,也未获得它们的认可或授权。 @@ -52,7 +54,7 @@ AI Limits 是 TiantianFlow 独立开发的项目,与任何已连接的服务 ### 必需权限 -- `storage`:在 `chrome.storage.local` 中保存显示和刷新偏好、服务访问状态、标准化后的配额/计数/余额快照、最多 30 天的按类型分实例 History,以及经过清理的刷新状态。它还会在 `chrome.storage.session` 中保存临时的 Kimi 和 Cursor 标签页清理租约元数据。浏览器会话 cookie 和访问凭据不会被持久化。验证通过后,用户创建的服务商 API 密钥会保存在单独的 `chrome.storage.local` 凭据记录中,其 Chrome 存储访问级别仅限于受信任的扩展上下文。该记录不受操作系统钥匙串加密保护,因此从已解锁的 Chrome 配置文件、扩展 DevTools 或配置文件文件中仍可能被检查到。 +- `storage`:在 `chrome.storage.local` 中保存显示、语言和刷新偏好、服务访问状态、标准化后的配额/计数/余额快照、最多 30 天的按类型分实例 History,以及经过清理的刷新状态。它还会在 `chrome.storage.session` 中保存临时的 Kimi 和 Cursor 标签页清理租约元数据。浏览器会话 cookie 和访问凭据不会被持久化。验证通过后,用户创建的服务商 API 密钥会保存在单独的 `chrome.storage.local` 凭据记录中,其 Chrome 存储访问级别仅限于受信任的扩展上下文。该记录不受操作系统钥匙串加密保护,因此从已解锁的 Chrome 配置文件、扩展 DevTools 或配置文件文件中仍可能被检查到。 - `alarms`:仅在启用自动刷新且至少有一个服务已连接时,安排大约每 15 分钟一次的刷新周期。 - `sidePanel`:在 Chrome 侧边栏中显示本扩展的唯一应用界面。 @@ -105,7 +107,7 @@ AI Limits 是 TiantianFlow 独立开发的项目,与任何已连接的服务 ## 二十个服务的测试流程 -1. 安装提交的构建,或解压 ZIP 并将其根目录作为未打包扩展加载。如需要可固定 AI Limits,选择其工具栏操作,并确认侧边栏打开后显示二十张需要权限的卡片。 +1. 安装提交的构建,或解压 ZIP 并将其根目录作为未打包扩展加载。如需要可固定 AI Limits,选择其工具栏操作,并确认首次运行界面列出二十个可选服务行,且没有预填用量。 2. **ChatGPT:** 在 `chatgpt.com` 登录,在 ChatGPT 上点击 **连接**,只批准 ChatGPT 来源,并确认出现套餐以及可用的配额窗口或额度。使用该卡片的 **刷新** 操作一次。 3. **Claude:** 在 `claude.ai` 登录,在 Claude 上点击 **连接**,只批准 Claude 来源,并确认出现组织套餐以及可用的用量窗口或额外用量额度。使用该卡片的 **刷新** 操作一次。 4. **Kimi:** 在 `www.kimi.com` 登录,在 Kimi 上点击 **连接**,批准 Kimi 来源以及 `cookies` 和 `scripting`,并确认出现可用用量和订阅数据。如果在交互式刷新期间需要会话恢复,可能会出现一个不活动的 Kimi 标签页。确认恢复流程在 10 秒后停止等待凭据并尝试尽力清理;不要把关闭过程或浏览器 API 错误期间的延迟清理当作保证被违反。 @@ -126,4 +128,4 @@ AI Limits 是 TiantianFlow 独立开发的项目,与任何已连接的服务 19. **OpenAI:** 连接临时密钥,只批准 `https://api.openai.com/*`,并确认显示组织支出/补全用量或旧版可用额度回退。记录所需组织或项目范围。 20. **GroqCloud:** 连接临时密钥,只批准 `https://api.groq.com/*`。具备所需范围时确认显示 Prometheus 速率;标准密钥返回 404 时应显示需要更高范围。 21. **OpenRouter:** 连接临时密钥,只批准 `https://openrouter.ai/*`,并确认显示额度余额,以及服务报告时的密钥预算。 -22. 使用顶部刷新,并确认已连接的服务在刷新期间保留之前的可见数据。打开某个配额窗口的 **历史** 操作,确认出现独立界面,然后确认一次成功刷新会新增配额历史,而计数/支出和余额样本仍不会被绘图。在**设置**中关闭再打开自动刷新,断开一个服务,然后使用 **删除全部本地数据**,并确认卡片回到需要权限的状态。对于 API 密钥服务,在断开连接之后以及删除全部之后检查扩展存储,确认其凭据记录中不再包含密钥。 +22. 使用顶部刷新,并确认已连接的服务在刷新期间保留之前的可见数据。打开某个配额窗口的 **历史** 操作,确认出现独立界面,然后确认一次成功刷新会新增配额历史,而计数/支出和余额样本仍不会被绘图。在服务详情页,断开连接会先要求确认,然后再删除凭据、配置、用量和历史。在**设置**中关闭再打开自动刷新,断开一个服务时同样需要确认该删除,然后使用 **删除全部本地数据**,并确认卡片回到需要权限的状态。对于 API 密钥服务,在断开连接之后以及删除全部之后检查扩展存储,确认其凭据记录中不再包含密钥。