mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-22 16:35:20 +08:00
Merge pull request #2737 from langbot-app/agent/sg-claude/07b0571b414f
feat(web): rename credits label, shorten workspace switcher, add upgrade button
This commit is contained in:
@@ -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,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'),
|
||||
);
|
||||
import NewVersionDialog from '@/app/home/components/new-version-dialog/NewVersionDialog';
|
||||
import SettingsDialog, {
|
||||
SettingsSection,
|
||||
@@ -1893,6 +1896,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<string>('');
|
||||
const [starCount, setStarCount] = useState<number | null>(null);
|
||||
const [userMenuOpen, setUserMenuOpen] = useState(false);
|
||||
@@ -2163,8 +2167,26 @@ export default function HomeSidebar({
|
||||
</SidebarHeader>
|
||||
|
||||
{showWorkspaceSwitcher && (
|
||||
<div className="px-2 group-data-[collapsible=icon]:px-0">
|
||||
<WorkspaceSwitcher className="w-full group-data-[collapsible=icon]:min-w-0 group-data-[collapsible=icon]:px-2" />
|
||||
<div className="flex items-center gap-1 px-2 group-data-[collapsible=icon]:px-0">
|
||||
<WorkspaceSwitcher className="min-w-0 flex-1 group-data-[collapsible=icon]:min-w-0 group-data-[collapsible=icon]:px-2" />
|
||||
{currentWorkspace?.workspace.source === 'cloud_projection' && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-9 shrink-0 text-muted-foreground hover:text-foreground group-data-[collapsible=icon]:hidden"
|
||||
aria-label={t('workspace.upgradePlan')}
|
||||
onClick={() => setCloudPlansOpen(true)}
|
||||
>
|
||||
<ArrowUp className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
{t('workspace.upgradePlan')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2473,6 +2495,16 @@ export default function HomeSidebar({
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{currentWorkspace && (
|
||||
<Suspense fallback={null}>
|
||||
<CloudPlansDialog
|
||||
open={cloudPlansOpen}
|
||||
onOpenChange={setCloudPlansOpen}
|
||||
workspace={currentWorkspace}
|
||||
spaceURL={systemInfo.cloud_service_url}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
<SettingsDialog
|
||||
open={settingsOpen}
|
||||
onOpenChange={handleSettingsOpenChange}
|
||||
|
||||
@@ -40,11 +40,13 @@ export default function WorkspaceSwitcher({
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className={cn('h-9 min-w-0 justify-start px-2.5 text-sm', className)}
|
||||
className={cn('h-9 min-w-0 gap-1.5 px-2.5 text-sm', className)}
|
||||
aria-label={t('workspace.switchWorkspace')}
|
||||
>
|
||||
<Building2 className="size-4 shrink-0" />
|
||||
<span className="truncate">{currentWorkspace.workspace.name}</span>
|
||||
<span className="max-w-[6rem] truncate">
|
||||
{currentWorkspace.workspace.name}
|
||||
</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-64 p-1.5">
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -102,8 +102,9 @@ const jaJP = {
|
||||
retentionLabel: 'メッセージ履歴の保存期間',
|
||||
retentionHelp: '保存期間を過ぎた履歴は自動的に削除されます。',
|
||||
credits: '毎月 {{count}} クレジット',
|
||||
creditsLabel: '毎月のクレジット',
|
||||
creditsHelp: '年払いでも、契約の各月にクレジットが付与されます。',
|
||||
creditsLabel: 'モデルクレジット',
|
||||
creditsHelp:
|
||||
'年払いでも、契約の各月にクレジットが付与されます。クレジットが不足しても、独自に設定したモデルプロバイダーを引き続き利用できます。',
|
||||
platforms: '主要プラットフォームに対応',
|
||||
excludedPlatforms: '対象外:{{platforms}}',
|
||||
allPlatforms: '現在利用可能なすべてのプラットフォーム',
|
||||
|
||||
@@ -98,8 +98,9 @@ const zhHans = {
|
||||
retentionLabel: '消息记录保留时长',
|
||||
retentionHelp: '超过保留时长的消息记录会自动清理。',
|
||||
credits: '每月赠送 {{count}} 积分',
|
||||
creditsLabel: '每月赠送积分',
|
||||
creditsHelp: '每个订阅月自动发放,年付计划同样按月发放。',
|
||||
creditsLabel: '模型积分',
|
||||
creditsHelp:
|
||||
'每个订阅月自动发放,年付计划同样按月发放。积分用尽后,仍可接入自行配置的模型供应商继续使用。',
|
||||
platforms: '支持主流平台',
|
||||
excludedPlatforms: '不支持:{{platforms}}',
|
||||
allPlatforms: '支持当前所有可用平台',
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user