From fa2913a9868f40fd921bab3aa8e5f17bf6ee77aa Mon Sep 17 00:00:00 2001 From: Chin Rock Date: Wed, 21 Oct 2026 07:45:56 +0000 Subject: [PATCH 1/2] feat(web): rename credits label to model credits, shorten workspace switcher, add upgrade button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - creditsLabel: 每月赠送积分 → 模型积分 (zh), Monthly credits → Model credits (en), 毎月のクレジット → モデルクレジット (ja) - creditsHelp: append note that own model providers remain usable after credits run out, all locales - WorkspaceSwitcher trigger: cap workspace name to 6rem, remove justify-start - HomeSidebar: add ArrowUp upgrade icon button next to workspace switcher (cloud_projection only), opens CloudPlansDialog lazy-loaded Co-Authored-By: Claude Opus 5.5 Co-authored-by: multica-agent --- .../components/home-sidebar/HomeSidebar.tsx | 36 +++++++++++++++++-- .../workspace-settings/WorkspaceSwitcher.tsx | 4 +-- web/src/i18n/locales/en-US.ts | 4 +-- web/src/i18n/locales/ja-JP.ts | 4 +-- web/src/i18n/locales/zh-Hans.ts | 4 +-- 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index 55d3326d6..2ecfd11ce 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, lazy, Suspense } from 'react'; import { SidebarChildVO } from '@/app/home/components/home-sidebar/HomeSidebarChild'; import { useNavigate, useLocation, useSearchParams } from 'react-router-dom'; import { sidebarConfigList } from '@/app/home/components/home-sidebar/sidbarConfigList'; @@ -70,6 +70,7 @@ import { Button } from '@/components/ui/button'; import WorkspaceSwitcher, { OPEN_WORKSPACE_SETTINGS_EVENT, } from '@/app/home/components/workspace-settings/WorkspaceSwitcher'; +const CloudPlansDialog = lazy(() => import('@/app/home/components/workspace-settings/CloudPlansDialog')); import NewVersionDialog from '@/app/home/components/new-version-dialog/NewVersionDialog'; import SettingsDialog, { SettingsSection, @@ -1893,6 +1894,7 @@ export default function HomeSidebar({ const [hasNewVersion, setHasNewVersion] = useState(false); const [versionDialogOpen, setVersionDialogOpen] = useState(false); const [feedbackOpen, setFeedbackOpen] = useState(false); + const [cloudPlansOpen, setCloudPlansOpen] = useState(false); const [userEmail, setUserEmail] = useState(''); const [starCount, setStarCount] = useState(null); const [userMenuOpen, setUserMenuOpen] = useState(false); @@ -2163,8 +2165,26 @@ export default function HomeSidebar({ {showWorkspaceSwitcher && ( -
- +
+ + {currentWorkspace?.workspace.source === 'cloud_projection' && ( + + + + + + {t('workspace.upgradePlan')} + + + )}
)} @@ -2473,6 +2493,16 @@ export default function HomeSidebar({ + {currentWorkspace && ( + + + + )} - {currentWorkspace.workspace.name} + {currentWorkspace.workspace.name} diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index d28ef48da..809259056 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -103,9 +103,9 @@ const enUS = { retentionHelp: 'Message history is automatically removed after this period.', credits: '{{count}} credits every month', - creditsLabel: 'Monthly credits', + creditsLabel: 'Model credits', creditsHelp: - 'Credits are granted each subscription month, including on yearly plans.', + 'Credits are granted each subscription month, including on yearly plans. When credits run out, you can still use your own configured model providers.', platforms: 'Mainstream platforms supported', excludedPlatforms: 'Excluded: {{platforms}}', allPlatforms: 'All currently available platforms', diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index 52893bfee..5b5fb193b 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -102,8 +102,8 @@ const jaJP = { retentionLabel: 'メッセージ履歴の保存期間', retentionHelp: '保存期間を過ぎた履歴は自動的に削除されます。', credits: '毎月 {{count}} クレジット', - creditsLabel: '毎月のクレジット', - creditsHelp: '年払いでも、契約の各月にクレジットが付与されます。', + creditsLabel: 'モデルクレジット', + creditsHelp: '年払いでも、契約の各月にクレジットが付与されます。クレジットが不足しても、独自に設定したモデルプロバイダーを引き続き利用できます。', platforms: '主要プラットフォームに対応', excludedPlatforms: '対象外:{{platforms}}', allPlatforms: '現在利用可能なすべてのプラットフォーム', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index 78cc13831..9e79a514c 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -98,8 +98,8 @@ const zhHans = { retentionLabel: '消息记录保留时长', retentionHelp: '超过保留时长的消息记录会自动清理。', credits: '每月赠送 {{count}} 积分', - creditsLabel: '每月赠送积分', - creditsHelp: '每个订阅月自动发放,年付计划同样按月发放。', + creditsLabel: '模型积分', + creditsHelp: '每个订阅月自动发放,年付计划同样按月发放。积分用尽后,仍可接入自行配置的模型供应商继续使用。', platforms: '支持主流平台', excludedPlatforms: '不支持:{{platforms}}', allPlatforms: '支持当前所有可用平台', From 61c59687c2d6d2c67bdbfeef336b9f568d4a1e6a Mon Sep 17 00:00:00 2001 From: Chin Rock Date: Wed, 21 Oct 2026 08:06:42 +0000 Subject: [PATCH 2/2] fix(web): satisfy prettier and align the switcher layout spec after the upgrade control Co-authored-by: multica-agent --- .../components/home-sidebar/HomeSidebar.tsx | 4 ++- .../workspace-settings/WorkspaceSwitcher.tsx | 4 ++- web/src/i18n/locales/ja-JP.ts | 3 +- web/src/i18n/locales/zh-Hans.ts | 3 +- web/tests/e2e/workspace-layout.spec.ts | 29 ++++++++++++++----- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index 2ecfd11ce..134a7deb9 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -70,7 +70,9 @@ import { Button } from '@/components/ui/button'; import WorkspaceSwitcher, { OPEN_WORKSPACE_SETTINGS_EVENT, } from '@/app/home/components/workspace-settings/WorkspaceSwitcher'; -const CloudPlansDialog = lazy(() => import('@/app/home/components/workspace-settings/CloudPlansDialog')); +const CloudPlansDialog = lazy( + () => import('@/app/home/components/workspace-settings/CloudPlansDialog'), +); import NewVersionDialog from '@/app/home/components/new-version-dialog/NewVersionDialog'; import SettingsDialog, { SettingsSection, diff --git a/web/src/app/home/components/workspace-settings/WorkspaceSwitcher.tsx b/web/src/app/home/components/workspace-settings/WorkspaceSwitcher.tsx index 6281d25e6..7660f8a35 100644 --- a/web/src/app/home/components/workspace-settings/WorkspaceSwitcher.tsx +++ b/web/src/app/home/components/workspace-settings/WorkspaceSwitcher.tsx @@ -44,7 +44,9 @@ export default function WorkspaceSwitcher({ aria-label={t('workspace.switchWorkspace')} > - {currentWorkspace.workspace.name} + + {currentWorkspace.workspace.name} + diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index 5b5fb193b..5a7342e8a 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -103,7 +103,8 @@ const jaJP = { retentionHelp: '保存期間を過ぎた履歴は自動的に削除されます。', credits: '毎月 {{count}} クレジット', creditsLabel: 'モデルクレジット', - creditsHelp: '年払いでも、契約の各月にクレジットが付与されます。クレジットが不足しても、独自に設定したモデルプロバイダーを引き続き利用できます。', + creditsHelp: + '年払いでも、契約の各月にクレジットが付与されます。クレジットが不足しても、独自に設定したモデルプロバイダーを引き続き利用できます。', platforms: '主要プラットフォームに対応', excludedPlatforms: '対象外:{{platforms}}', allPlatforms: '現在利用可能なすべてのプラットフォーム', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index 9e79a514c..146a27439 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -99,7 +99,8 @@ const zhHans = { retentionHelp: '超过保留时长的消息记录会自动清理。', credits: '每月赠送 {{count}} 积分', creditsLabel: '模型积分', - creditsHelp: '每个订阅月自动发放,年付计划同样按月发放。积分用尽后,仍可接入自行配置的模型供应商继续使用。', + creditsHelp: + '每个订阅月自动发放,年付计划同样按月发放。积分用尽后,仍可接入自行配置的模型供应商继续使用。', platforms: '支持主流平台', excludedPlatforms: '不支持:{{platforms}}', allPlatforms: '支持当前所有可用平台', diff --git a/web/tests/e2e/workspace-layout.spec.ts b/web/tests/e2e/workspace-layout.spec.ts index dae717afd..e22f01700 100644 --- a/web/tests/e2e/workspace-layout.spec.ts +++ b/web/tests/e2e/workspace-layout.spec.ts @@ -55,6 +55,11 @@ for (const scenario of [ }); await expect(trigger).toHaveCount(scenario === 'singleton-local' ? 0 : 1); if (scenario === 'singleton-local') return; + const upgrade = page.getByRole('button', { + name: 'Change or upgrade plan', + exact: true, + }); + await expect(upgrade).toHaveCount(1); const sidebar = page.locator('[data-slot="sidebar-inner"]'); const header = sidebar.locator('[data-slot="sidebar-header"]'); const home = sidebar.getByText('Home', { exact: true }); @@ -62,20 +67,30 @@ for (const scenario of [ name: 'Models', exact: true, }); - const [headerBox, triggerBox, homeBox, navBox] = await Promise.all([ - header.boundingBox(), - trigger.boundingBox(), - home.boundingBox(), - navigation.boundingBox(), - ]); + const [headerBox, triggerBox, homeBox, navBox, upgradeBox] = + await Promise.all([ + header.boundingBox(), + trigger.boundingBox(), + home.boundingBox(), + navigation.boundingBox(), + upgrade.boundingBox(), + ]); expect(triggerBox!.y).toBeGreaterThanOrEqual( headerBox!.y + headerBox!.height, ); expect(triggerBox!.y + triggerBox!.height).toBeLessThanOrEqual(homeBox!.y); expect(Math.abs(triggerBox!.x - navBox!.x)).toBeLessThanOrEqual(1); + // The upgrade control shares the switcher row and owns its trailing edge, + // so the row stays flush with the navigation column while the switcher + // itself ends before the control. + expect(Math.abs(triggerBox!.y - upgradeBox!.y)).toBeLessThanOrEqual(1); + expect(upgradeBox!.x).toBeGreaterThanOrEqual( + triggerBox!.x + triggerBox!.width, + ); expect( - Math.abs(triggerBox!.x + triggerBox!.width - navBox!.x - navBox!.width), + Math.abs(upgradeBox!.x + upgradeBox!.width - navBox!.x - navBox!.width), ).toBeLessThanOrEqual(1); + await expect(upgrade).toBeVisible(); await trigger.click(); const row = page .getByRole('menuitem')