Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0fee507
chore: sync main to dev after v0.4.25
kittors Aug 12, 2026
b7dc33e
Merge pull request #920 from kittors/chore/sync-main-to-dev-v0425
kittors Aug 12, 2026
9ba2357
fix(ui): stop repeating model names as tooltips and alias-only "real …
kittors Aug 17, 2026
c275059
Merge pull request #921 from kittors/fix/model-alias-tooltip-noise
kittors Aug 17, 2026
765aeeb
feat(video): add the video models page and share the highlighted code…
kittors Aug 17, 2026
ce8f26a
test(video): cover the video page in a real browser
kittors Aug 17, 2026
e31405f
Merge pull request #922 from kittors/feat/video-generation-page
kittors Aug 17, 2026
26a37bf
fix(video): grey out generation without a credential, and give the me…
kittors Aug 17, 2026
223297c
Merge pull request #923 from kittors/fix/video-model-availability
kittors Aug 17, 2026
18e3d69
feat(providers): add the Command Code channel with cookie-free plan u…
kittors Aug 18, 2026
e838768
Merge pull request #924 from kittors/feat/commandcode-channel
kittors Aug 18, 2026
126ae84
fix(antigravity): render the upstream's own quota buckets
kittors Aug 19, 2026
0e808ec
Merge pull request #927 from kittors/fix/antigravity-quota-summary
kittors Aug 19, 2026
1b755a3
feat(providers): sort credentials by remaining quota
kittors Aug 19, 2026
4f96059
Merge pull request #928 from kittors/feat/provider-credential-quota-sort
kittors Aug 19, 2026
f1a7940
chore(bundle): align the AuthFilesPage baseline with dev
kittors Aug 19, 2026
d02454e
Merge pull request #930 from kittors/chore/refresh-bundle-baseline
kittors Aug 19, 2026
6307841
feat(ai-accounts): sort accounts by remaining quota
kittors Aug 19, 2026
430535d
refactor(ai-accounts): use the toolbar's Select for the sort control
kittors Aug 19, 2026
9a14ae7
Merge pull request #929 from kittors/feat/ai-accounts-quota-sort
kittors Aug 19, 2026
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
4 changes: 2 additions & 2 deletions docs/internal-review/bundle-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ bun run build
| `index` | 396.29 kB | 121.04 kB | 多账号切换(AuthProvider vault + AppShell 账号菜单)进入 shell 入口;后续可再拆 account menu |
| `index.css` | 430.02 kB | 86.34 kB | 入口样式表;含 114 条自托管字体 @font-face 分片声明(Inter / Noto Sans SC / JetBrains Mono),字体文件本身按 unicode-range 懒加载不计入此行 |
| `ConfigPage` | 119.60 kB | 33.35 kB | 页面 chunk 低于预算 |
| `AuthFilesPage` | 216.81 kB | 58.90 kB | 身份多档案与出站策略交互加入后仍低于页面 gzip 预算 |
| `AuthFilesPage` | 234.90 kB | 64.36 kB | 与 dev 现状对齐(原记 58.90 kB 已落后约 5.5 kB);仍低于 `< 80 kB gzip` 页面预算,余量约 15 kB |
| `ProvidersPage` | 121.98 kB | 30.85 kB | 已低于 `< 80 kB gzip` 页面预算 |
| `MonitorPage` | 24.33 kB | 6.80 kB | 已拆为 toolbar / state hook / dashboard sections |
| `LogsPage` | 22.15 kB | 6.51 kB | 已拆为 live logs / error logs / helpers |
Expand All @@ -37,7 +37,7 @@ bun run build

| 页面/模块 | 当前体积 | 预算状态 | 最近治理结果 |
| ----------------- | ------------------------: | -------------- | --------------------------------------------------------------------------------------------------------------- |
| `AuthFilesPage` | 216.81 kB / 58.90 kB gzip | 通过 | 新增 Codex 身份多档案与出站选择后仍低于页面 gzip 预算,后续继续关注 chunk 治理 |
| `AuthFilesPage` | 234.90 kB / 64.36 kB gzip | 通过 | 基线与 dev 现状对齐;仍低于页面 gzip 预算,余量约 15 kB,chunk 治理继续关注 |
| `ConfigPage` | 119.60 kB / 33.35 kB gzip | 通过 | 已拆出 runtime panel / visual payload editors,并复用 feature 侧 visual config hook |
| `ProvidersPage` | 121.98 kB / 30.85 kB gzip | 通过且低于预算 | OpenAI tab、usage summary、provider editor hooks 已完成拆分 |
| `MonitorPage` | 24.33 kB / 6.80 kB gzip | 通过且低于预算 | 拆出 `MonitorToolbarSection`、`MonitorDashboardSections`、`useMonitorDashboardState` |
Expand Down
134 changes: 134 additions & 0 deletions e2e/video-generation-page.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { expect, test, type Page } from "@playwright/test";

/**
* Renders the video models page against mocked management APIs.
*
* Not marked @critical: the page's behaviour is covered by unit tests, and this
* spec exists so the layout — highlighted curl block, endpoint switch, spec
* tables — is exercised in a real browser rather than only in jsdom.
*/

const VIDEO_MODEL = {
id: "grok-imagine-video-1.5",
provider: "xai",
display_name: "Grok Imagine Video",
description: "Grok Imagine text-to-video and image-to-video generation.",
supports_image_to_video: true,
max_duration_seconds: 15,
};

const seedAuth = async (page: Page) => {
await page.addInitScript(() => {
sessionStorage.setItem(
"code-proxy-admin-auth",
JSON.stringify({
apiBase: "http://127.0.0.1:8317",
managementKey: "cps_test",
rememberPassword: false,
expiresAt: Date.now() + 60_000,
}),
);
localStorage.setItem(
"cli-proxy-language",
JSON.stringify({ language: "zh-CN", state: { language: "zh-CN" } }),
);
});
};

const mockApis = async (page: Page) => {
const tenant = {
id: "t-system",
slug: "system",
name: "System Administration",
type: "system",
status: "active",
effective_status: "active",
expires_at: null,
description: "",
version: 1,
created_at: "",
updated_at: "",
};
await page.route("**/v0/auth/me", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
principal: {
kind: "user_session",
user: {
id: "u-admin",
tenant_id: "t-system",
username: "admin",
display_name: "Super Administrator",
status: "active",
must_change_password: false,
last_login_at: null,
role_ids: ["r-platform-admin"],
role_codes: ["platform_super_admin"],
version: 1,
created_at: "",
updated_at: "",
},
home_tenant: tenant,
effective_tenant: tenant,
roles: [],
permissions: ["system.config.read", "dashboard.read"],
platform_admin: true,
},
}),
}),
);
// Order matters: Playwright tries the most recently registered route first, so
// the catch-all has to be registered before the specific one it must not shadow.
await page.route("**/v0/management/**", (route) =>
route.fulfill({ status: 200, contentType: "application/json", body: "{}" }),
);
await page.route("**/v0/management/video-generation/models", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ models: [VIDEO_MODEL] }),
}),
);
};

test("renders the video call docs with a highlighted snippet", async ({ page }) => {
await seedAuth(page);
await mockApis(page);
await page.goto("/#/models/video-generation");

await expect(page.getByRole("heading", { name: "视频模型" })).toBeVisible();

const codeBlock = page.locator("[data-code-block]").first();
await expect(codeBlock).toContainText("curl http://127.0.0.1:8317/v1/videos/generations");
await expect(codeBlock).toContainText("/v1/videos/$REQUEST_ID");

// Highlighting must produce coloured token spans, not one flat text node.
const tokenColours = await codeBlock.evaluate((element) => {
const spans = [...element.querySelectorAll("span")];
return new Set(spans.map((span) => getComputedStyle(span).color)).size;
});
expect(tokenColours).toBeGreaterThan(2);

await page.getByRole("tab", { name: "图生视频" }).click();
await expect(page.locator("[data-code-block]").first()).toContainText('"image": { "url"');

await expect(page.getByText("请求参数")).toBeVisible();
await expect(page.getByText("返回结构")).toBeVisible();
});

test("enables the test panel from the served model catalog", async ({ page }) => {
await seedAuth(page);
await mockApis(page);
await page.goto("/#/models/video-generation");

// The button stays disabled until the catalog answers, which is what made the
// panel unreachable when the models call was shadowed by a catch-all mock.
const testButton = page.getByRole("button", { name: "测试生成" });
await expect(testButton).toBeEnabled();

await testButton.click();
await expect(page.getByText("测试视频生成")).toBeVisible();
await expect(page.getByText(VIDEO_MODEL.id, { exact: false }).first()).toBeVisible();
});
3 changes: 3 additions & 0 deletions features/api-key-restrictions/useApiKeyPermissionOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export function useApiKeyPermissionOptions() {
openCodeGoKeys,
clineKeys,
ollamaCloudKeys,
commandCodeKeys,
vertexKeys,
openaiProviders,
authFiles,
Expand All @@ -142,6 +143,7 @@ export function useApiKeyPermissionOptions() {
providersApi.getOpenCodeGoConfigs().catch(() => []),
providersApi.getClineConfigs().catch(() => []),
providersApi.getOllamaCloudConfigs().catch(() => []),
providersApi.getCommandCodeConfigs().catch(() => []),
providersApi.getVertexConfigs().catch(() => []),
providersApi.getOpenAIProviders().catch(() => []),
authFilesApi.list().catch(() => ({ files: [] })),
Expand Down Expand Up @@ -173,6 +175,7 @@ export function useApiKeyPermissionOptions() {
openCodeGoKeys.forEach((item) => push(item.name || "", "API", "opencode-go"));
clineKeys.forEach((item) => push(item.name || "", "API", "cline"));
ollamaCloudKeys.forEach((item) => push(item.name || "", "API", "ollama-cloud"));
commandCodeKeys.forEach((item) => push(item.name || "", "API", "commandcode"));
vertexKeys.forEach((item) => push(item.name || "", "API", "vertex"));
openaiProviders.forEach((item) => push(item.name || "", "API", "openai"));
(authFiles.files || []).forEach((file) => {
Expand Down
2 changes: 2 additions & 0 deletions features/model-availability/modelAvailability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,15 @@ const PROVIDER_CHANNELS = [
{ key: "cline", load: () => providersApi.getClineConfigs() },
{ key: "opencode-go", load: () => providersApi.getOpenCodeGoConfigs() },
{ key: "ollama-cloud", load: () => providersApi.getOllamaCloudConfigs() },
{ key: "commandcode", load: () => providersApi.getCommandCodeConfigs() },
{ key: "vertex", load: () => providersApi.getVertexConfigs() },
] as const;

const MODEL_ACCESS_PROVIDER_KEYS = new Set([
"cline",
"opencode-go",
"ollama-cloud",
"commandcode",
]);

const emptyAvailability = (
Expand Down
5 changes: 4 additions & 1 deletion features/model-tags/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,10 @@ export function ModelTag({

return (
<span
title={title ?? id}
// No implicit title: it repeated the visible model id as a second, native
// tooltip stacked on top of the managed one. Callers that truncate the tag
// wrap it in OverflowTooltip, which only opens when the text is cut off.
title={title}
className={cn(
"inline-flex max-w-full items-center border font-mono font-semibold leading-none",
sizeClasses.wrapper,
Expand Down
Loading
Loading