mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-13 14:10:59 +00:00
feat(oss): enforce invitation account and owner billing flows
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
import { CustomApiError } from '@/app/infra/entities/common';
|
||||
import { PanelBody } from '../settings-dialog/panel-layout';
|
||||
import { useCurrentWorkspace } from '@/app/infra/http';
|
||||
import type { WorkspaceSpaceBilling } from '@/app/infra/entities/workspace';
|
||||
|
||||
interface ModelsPanelProps {
|
||||
// True when this panel is the active section and the dialog is open.
|
||||
@@ -89,8 +90,8 @@ export default function ModelsPanel({
|
||||
currentWorkspace?.permissions.includes('provider_secret.manage') ?? false;
|
||||
|
||||
const [providers, setProviders] = useState<ModelProvider[]>([]);
|
||||
const [accountType, setAccountType] = useState<'local' | 'space'>('local');
|
||||
const [spaceCredits, setSpaceCredits] = useState<number | null>(null);
|
||||
const [spaceBilling, setSpaceBilling] =
|
||||
useState<WorkspaceSpaceBilling | null>(null);
|
||||
|
||||
// Expanded providers and their models
|
||||
const [expandedProviders, setExpandedProviders] = useState<Set<string>>(
|
||||
@@ -144,7 +145,7 @@ export default function ModelsPanel({
|
||||
|
||||
useEffect(() => {
|
||||
if (active) {
|
||||
loadUserInfo();
|
||||
loadWorkspaceBilling();
|
||||
loadProviders();
|
||||
loadRequesterSupportTypes();
|
||||
}
|
||||
@@ -167,16 +168,11 @@ export default function ModelsPanel({
|
||||
}
|
||||
}, [providersLoaded, providers]);
|
||||
|
||||
async function loadUserInfo() {
|
||||
async function loadWorkspaceBilling() {
|
||||
try {
|
||||
const userInfo = await httpClient.getUserInfo();
|
||||
setAccountType(userInfo.account_type);
|
||||
if (userInfo.account_type === 'space') {
|
||||
const creditsInfo = await httpClient.getSpaceCredits();
|
||||
setSpaceCredits(creditsInfo.credits);
|
||||
}
|
||||
setSpaceBilling(await httpClient.getWorkspaceSpaceBilling());
|
||||
} catch {
|
||||
setAccountType('local');
|
||||
setSpaceBilling(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,8 +542,9 @@ export default function ModelsPanel({
|
||||
isExpanded={expandedProviders.has(provider.uuid)}
|
||||
isLoading={loadingProviders.has(provider.uuid)}
|
||||
models={providerModels[provider.uuid]}
|
||||
accountType={accountType}
|
||||
spaceCredits={spaceCredits}
|
||||
isWorkspaceOwner={currentWorkspace?.membership.role === 'owner'}
|
||||
ownerSpaceBound={spaceBilling?.owner_space_bound ?? false}
|
||||
spaceCredits={spaceBilling?.credits ?? null}
|
||||
addModelPopoverOpen={addModelPopoverOpen}
|
||||
editModelPopoverOpen={editModelPopoverOpen}
|
||||
deleteConfirmOpen={deleteConfirmOpen}
|
||||
|
||||
@@ -44,7 +44,8 @@ interface ProviderCardProps {
|
||||
isExpanded: boolean;
|
||||
isLoading: boolean;
|
||||
models?: ProviderModels;
|
||||
accountType: 'local' | 'space';
|
||||
isWorkspaceOwner: boolean;
|
||||
ownerSpaceBound: boolean;
|
||||
spaceCredits: number | null;
|
||||
// Popover states
|
||||
addModelPopoverOpen: string | null;
|
||||
@@ -108,7 +109,8 @@ export default function ProviderCard({
|
||||
isExpanded,
|
||||
isLoading,
|
||||
models,
|
||||
accountType,
|
||||
isWorkspaceOwner,
|
||||
ownerSpaceBound,
|
||||
spaceCredits,
|
||||
addModelPopoverOpen,
|
||||
editModelPopoverOpen,
|
||||
@@ -198,7 +200,7 @@ export default function ProviderCard({
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 ml-2 shrink-0">
|
||||
{canManage && isLangBotModels && accountType !== 'space' && (
|
||||
{isLangBotModels && isWorkspaceOwner && !ownerSpaceBound && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -208,32 +210,40 @@ export default function ProviderCard({
|
||||
}}
|
||||
>
|
||||
<LogIn className="h-4 w-4 mr-1" />
|
||||
{t('models.loginWithSpace')}
|
||||
{t('models.ownerMustBindSpace')}
|
||||
</Button>
|
||||
)}
|
||||
{isLangBotModels &&
|
||||
accountType === 'space' &&
|
||||
spaceCredits !== null && (
|
||||
<div className="flex items-center gap-1 border rounded-md px-2 h-8 text-sm mr-2">
|
||||
<span>
|
||||
{(spaceCredits / 5000).toFixed(2)} {t('models.credits')}
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-5 w-5"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
window.open(
|
||||
`${systemInfo.cloud_service_url}/profile?tab=billing`,
|
||||
'_blank',
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{isLangBotModels && ownerSpaceBound && spaceCredits !== null && (
|
||||
<div className="flex items-center gap-1 border rounded-md px-2 h-8 text-sm mr-2">
|
||||
<span>
|
||||
{(spaceCredits / 5000).toFixed(2)} {t('models.credits')}
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-5 w-5"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
window.open(
|
||||
`${systemInfo.cloud_service_url}/profile?tab=billing`,
|
||||
'_blank',
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{isLangBotModels && !isWorkspaceOwner && ownerSpaceBound && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('models.usesOwnerSpaceBilling')}
|
||||
</span>
|
||||
)}
|
||||
{isLangBotModels && !isWorkspaceOwner && !ownerSpaceBound && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('models.ownerMustBindSpace')}
|
||||
</span>
|
||||
)}
|
||||
{canManage && !isLangBotModels && (
|
||||
<>
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user