mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 02:11:28 +00:00
fix(dashboard): refine api profile quick start state
- prevent the providers page from introducing nested overflow - show a select-profile state when profiles already exist - localize the new quick-start and AI Providers copy
This commit is contained in:
@@ -1,32 +1,92 @@
|
||||
/**
|
||||
* OpenRouter Quick Start Card
|
||||
* Prominent CTA for new users to create OpenRouter profile
|
||||
*/
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { useOpenRouterReady } from '@/hooks/use-openrouter-models';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
Sparkles,
|
||||
ExternalLink,
|
||||
ArrowRight,
|
||||
Zap,
|
||||
CloudCog,
|
||||
ExternalLink,
|
||||
KeyRound,
|
||||
SlidersHorizontal,
|
||||
Sparkles,
|
||||
Zap,
|
||||
} from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface OpenRouterQuickStartProps {
|
||||
hasProfiles: boolean;
|
||||
profileCount: number;
|
||||
onOpenRouterClick: () => void;
|
||||
onAlibabaCodingPlanClick: () => void;
|
||||
onCliproxyClick: () => void;
|
||||
onCustomClick: () => void;
|
||||
}
|
||||
|
||||
interface QuickStartCardProps {
|
||||
badge: string;
|
||||
badgeClassName?: string;
|
||||
className?: string;
|
||||
title: string;
|
||||
description: string;
|
||||
visual: ReactNode;
|
||||
highlights: Array<{ icon: ReactNode; label: string }>;
|
||||
actionLabel: string;
|
||||
actionClassName: string;
|
||||
onAction: () => void;
|
||||
footer?: ReactNode;
|
||||
}
|
||||
|
||||
function QuickStartCard({
|
||||
badge,
|
||||
badgeClassName,
|
||||
className,
|
||||
title,
|
||||
description,
|
||||
visual,
|
||||
highlights,
|
||||
actionLabel,
|
||||
actionClassName,
|
||||
onAction,
|
||||
footer,
|
||||
}: QuickStartCardProps) {
|
||||
return (
|
||||
<Card className={cn('flex h-full flex-col border shadow-sm', className)}>
|
||||
<CardHeader className="space-y-3 pb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
{visual}
|
||||
<Badge variant="secondary" className={badgeClassName}>
|
||||
{badge}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<CardTitle className="text-base">{title}</CardTitle>
|
||||
<CardDescription className="text-sm leading-6">{description}</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="mt-auto flex flex-1 flex-col gap-4 pt-0">
|
||||
<div className="space-y-2 text-xs text-muted-foreground">
|
||||
{highlights.map((item) => (
|
||||
<div key={item.label} className="flex items-center gap-2">
|
||||
{item.icon}
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Button onClick={onAction} className={actionClassName}>
|
||||
{actionLabel}
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
{footer ? <div className="text-xs text-muted-foreground">{footer}</div> : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function OpenRouterQuickStart({
|
||||
hasProfiles,
|
||||
profileCount,
|
||||
onOpenRouterClick,
|
||||
onAlibabaCodingPlanClick,
|
||||
onCliproxyClick,
|
||||
@@ -34,185 +94,151 @@ export function OpenRouterQuickStart({
|
||||
}: OpenRouterQuickStartProps) {
|
||||
const { t } = useTranslation();
|
||||
const { modelCount, isLoading } = useOpenRouterReady();
|
||||
const modelCountLabel = isLoading ? '300+' : `${modelCount}+`;
|
||||
const profileSummaryLabel = hasProfiles
|
||||
? t('openrouterQuickStart.profileCount', { count: profileCount })
|
||||
: t('openrouterQuickStart.recommended');
|
||||
const summaryTitle = hasProfiles
|
||||
? t('openrouterQuickStart.selectProfileTitle')
|
||||
: t('apiProfiles.noProfilesYet');
|
||||
const summaryDescription = hasProfiles
|
||||
? t('openrouterQuickStart.summaryDescriptionWithProfiles', { count: profileCount })
|
||||
: t('openrouterQuickStart.summaryDescriptionNoProfiles');
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center bg-muted/20 p-8">
|
||||
<div className="max-w-lg w-full space-y-6">
|
||||
{/* Main OpenRouter Card */}
|
||||
<Card className="border-accent/30 dark:border-accent/40 bg-gradient-to-br from-accent/5 to-background dark:from-accent/10">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="p-2 rounded-lg bg-accent/10 dark:bg-accent/20">
|
||||
<img src="/icons/openrouter.svg" alt="OpenRouter" className="w-6 h-6" />
|
||||
<div className="flex h-full min-h-0 flex-col overflow-auto bg-muted/20 p-4 sm:p-6">
|
||||
<div className="mx-auto flex w-full max-w-7xl flex-col gap-4">
|
||||
<Card className="border-dashed bg-background/90 shadow-sm">
|
||||
<CardContent className="flex flex-col gap-4 p-5 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div className="space-y-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge variant="secondary">{profileSummaryLabel}</Badge>
|
||||
<Badge variant="outline">
|
||||
{t('openrouterQuickStart.openrouterModelsBadge', { modelCountLabel })}
|
||||
</Badge>
|
||||
</div>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="bg-accent/10 text-accent dark:bg-accent/20 dark:text-accent-foreground"
|
||||
>
|
||||
{t('openrouterQuickStart.recommended')}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className="text-xl">{t('openrouterQuickStart.title')}</CardTitle>
|
||||
<CardDescription className="text-base">
|
||||
{t('openrouterQuickStart.description', {
|
||||
modelCountLabel: isLoading ? '300+' : `${modelCount}+`,
|
||||
})}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Key Features */}
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Zap className="w-4 h-4 text-accent" />
|
||||
<span>{t('openrouterQuickStart.featureOneApi')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Sparkles className="w-4 h-4 text-accent" />
|
||||
<span>{t('openrouterQuickStart.featureTierMapping')}</span>
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-xl font-semibold">{summaryTitle}</h2>
|
||||
<p className="max-w-3xl text-sm leading-6 text-muted-foreground">
|
||||
{summaryDescription}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={onOpenRouterClick}
|
||||
className="w-full bg-accent hover:bg-accent/90 text-white"
|
||||
size="lg"
|
||||
>
|
||||
{t('openrouterQuickStart.createOpenRouterProfile')}
|
||||
<ArrowRight className="w-4 h-4 ml-2" />
|
||||
<Button variant="outline" onClick={onCustomClick} className="shrink-0">
|
||||
{t('openrouterQuickStart.createCustomProfile')}
|
||||
</Button>
|
||||
|
||||
<p className="text-xs text-center text-muted-foreground">
|
||||
{t('openrouterQuickStart.getApiKeyAt')}{' '}
|
||||
<a
|
||||
href="https://openrouter.ai/keys"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-accent hover:underline inline-flex items-center gap-1"
|
||||
>
|
||||
openrouter.ai/keys
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</a>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Alibaba Coding Plan Card */}
|
||||
<Card className="border-orange-500/30 dark:border-orange-500/40 bg-gradient-to-br from-orange-500/5 to-background dark:from-orange-500/10">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="p-2 rounded-lg bg-orange-500/10 dark:bg-orange-500/20">
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
<QuickStartCard
|
||||
badge={t('openrouterQuickStart.recommended')}
|
||||
title={t('openrouterQuickStart.title')}
|
||||
description={t('openrouterQuickStart.description', { modelCountLabel })}
|
||||
visual={
|
||||
<div className="rounded-lg bg-accent/10 p-2">
|
||||
<img src="/icons/openrouter.svg" alt="OpenRouter" className="h-5 w-5" />
|
||||
</div>
|
||||
}
|
||||
highlights={[
|
||||
{
|
||||
icon: <Zap className="h-3.5 w-3.5 text-accent" />,
|
||||
label: t('openrouterQuickStart.featureOneApi'),
|
||||
},
|
||||
{
|
||||
icon: <Sparkles className="h-3.5 w-3.5 text-accent" />,
|
||||
label: t('openrouterQuickStart.featureTierMapping'),
|
||||
},
|
||||
]}
|
||||
actionLabel={t('openrouterQuickStart.createOpenRouterProfile')}
|
||||
actionClassName="w-full bg-accent text-white hover:bg-accent/90"
|
||||
onAction={onOpenRouterClick}
|
||||
footer={
|
||||
<>
|
||||
{t('openrouterQuickStart.getApiKeyAt')}{' '}
|
||||
<a
|
||||
href="https://openrouter.ai/keys"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 text-accent hover:underline"
|
||||
>
|
||||
openrouter.ai/keys
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<QuickStartCard
|
||||
badge={t('openrouterQuickStart.runtimeProviderBadge')}
|
||||
badgeClassName="bg-emerald-500/10 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-200"
|
||||
title={t('openrouterQuickStart.runtimeProviderTitle')}
|
||||
description={t('openrouterQuickStart.runtimeProviderDescription')}
|
||||
visual={
|
||||
<div className="rounded-lg bg-emerald-500/10 p-2">
|
||||
<SlidersHorizontal className="h-5 w-5 text-emerald-700 dark:text-emerald-300" />
|
||||
</div>
|
||||
}
|
||||
highlights={[
|
||||
{
|
||||
icon: <SlidersHorizontal className="h-3.5 w-3.5 text-emerald-600" />,
|
||||
label: t('openrouterQuickStart.runtimeProviderFeatureConnectors'),
|
||||
},
|
||||
{
|
||||
icon: <KeyRound className="h-3.5 w-3.5 text-emerald-600" />,
|
||||
label: t('openrouterQuickStart.runtimeProviderFeatureSecrets'),
|
||||
},
|
||||
]}
|
||||
actionLabel={t('openrouterQuickStart.runtimeProviderTitle')}
|
||||
actionClassName="w-full bg-emerald-600 text-white hover:bg-emerald-600/90"
|
||||
onAction={onCliproxyClick}
|
||||
footer={<span>{t('openrouterQuickStart.runtimeProviderFooter')}</span>}
|
||||
/>
|
||||
|
||||
<QuickStartCard
|
||||
badge={t('alibabaCodingPlanQuickStart.recommended')}
|
||||
badgeClassName="bg-orange-500/10 text-orange-700 dark:bg-orange-500/20 dark:text-orange-200"
|
||||
className="lg:col-span-2"
|
||||
title={t('alibabaCodingPlanQuickStart.title')}
|
||||
description={t('alibabaCodingPlanQuickStart.description')}
|
||||
visual={
|
||||
<div className="rounded-lg bg-orange-500/10 p-2">
|
||||
<img
|
||||
src="/assets/providers/alibabacloud-color.svg"
|
||||
alt="Alibaba Coding Plan"
|
||||
className="w-6 h-6"
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
</div>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="bg-orange-500/10 text-orange-700 dark:bg-orange-500/20 dark:text-orange-200"
|
||||
>
|
||||
{t('alibabaCodingPlanQuickStart.recommended')}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className="text-xl">{t('alibabaCodingPlanQuickStart.title')}</CardTitle>
|
||||
<CardDescription className="text-base">
|
||||
{t('alibabaCodingPlanQuickStart.description')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<CloudCog className="w-4 h-4 text-orange-600" />
|
||||
<span>{t('alibabaCodingPlanQuickStart.featureEndpoint')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<KeyRound className="w-4 h-4 text-orange-600" />
|
||||
<span>{t('alibabaCodingPlanQuickStart.featureKeyFormat')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={onAlibabaCodingPlanClick}
|
||||
className="w-full bg-orange-600 hover:bg-orange-600/90 text-white"
|
||||
size="lg"
|
||||
>
|
||||
{t('alibabaCodingPlanQuickStart.createAlibabaProfile')}
|
||||
<ArrowRight className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
|
||||
<p className="text-xs text-center text-muted-foreground">
|
||||
{t('alibabaCodingPlanQuickStart.readGuideAt')}{' '}
|
||||
<a
|
||||
href="https://www.alibabacloud.com/help/en/model-studio/coding-plan"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-orange-700 dark:text-orange-400 hover:underline inline-flex items-center gap-1"
|
||||
>
|
||||
Alibaba Cloud Model Studio
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</a>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-emerald-500/30 dark:border-emerald-500/40 bg-gradient-to-br from-emerald-500/5 to-background dark:from-emerald-500/10">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="p-2 rounded-lg bg-emerald-500/10 dark:bg-emerald-500/20">
|
||||
<SlidersHorizontal className="w-6 h-6 text-emerald-700 dark:text-emerald-300" />
|
||||
</div>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="bg-emerald-500/10 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-200"
|
||||
>
|
||||
Configure in AI Providers
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className="text-xl">Manage CLIProxy AI providers</CardTitle>
|
||||
<CardDescription className="text-base">
|
||||
Configure Gemini, Codex, Claude, Vertex, and OpenAI-compatible connectors directly in
|
||||
the dedicated CLIProxy AI Providers page.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<SlidersHorizontal className="w-4 h-4 text-emerald-600" />
|
||||
<span>Dedicated /cliproxy/ai-providers workspace</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<KeyRound className="w-4 h-4 text-emerald-600" />
|
||||
<span>Manage provider secrets outside API Profiles</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={onCliproxyClick}
|
||||
className="w-full bg-emerald-600 hover:bg-emerald-600/90 text-white"
|
||||
size="lg"
|
||||
>
|
||||
Open AI Providers
|
||||
<ArrowRight className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
|
||||
<p className="text-xs text-center text-muted-foreground">
|
||||
Keep runtime provider configuration in CLIProxy, then create API Profiles only when
|
||||
you need standalone Anthropic-compatible endpoints.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Separator className="flex-1" />
|
||||
<span className="text-xs text-muted-foreground">{t('openrouterQuickStart.or')}</span>
|
||||
<Separator className="flex-1" />
|
||||
}
|
||||
highlights={[
|
||||
{
|
||||
icon: <CloudCog className="h-3.5 w-3.5 text-orange-600" />,
|
||||
label: t('alibabaCodingPlanQuickStart.featureEndpoint'),
|
||||
},
|
||||
{
|
||||
icon: <KeyRound className="h-3.5 w-3.5 text-orange-600" />,
|
||||
label: t('alibabaCodingPlanQuickStart.featureKeyFormat'),
|
||||
},
|
||||
]}
|
||||
actionLabel={t('alibabaCodingPlanQuickStart.createAlibabaProfile')}
|
||||
actionClassName="w-full bg-orange-600 text-white hover:bg-orange-600/90"
|
||||
onAction={onAlibabaCodingPlanClick}
|
||||
footer={
|
||||
<>
|
||||
{t('alibabaCodingPlanQuickStart.readGuideAt')}{' '}
|
||||
<a
|
||||
href="https://www.alibabacloud.com/help/en/model-studio/coding-plan"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 text-orange-700 hover:underline dark:text-orange-400"
|
||||
>
|
||||
Alibaba Cloud Model Studio
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Custom Option */}
|
||||
<Button variant="outline" onClick={onCustomClick} className="w-full">
|
||||
{t('openrouterQuickStart.createCustomProfile')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -241,6 +241,25 @@ const resources = {
|
||||
'Access {{modelCountLabel}} models from OpenAI, Anthropic, Google, Meta and more - all through one API.',
|
||||
featureOneApi: 'One API, all providers',
|
||||
featureTierMapping: 'Model tier mapping',
|
||||
profileCount: '{{count}} profile',
|
||||
profileCount_other: '{{count}} profiles',
|
||||
selectProfileTitle: 'Select an API profile',
|
||||
summaryDescriptionWithProfiles:
|
||||
'You already have {{count}} profile in this workspace. Select one from the left rail to edit it, or create another profile from here.',
|
||||
summaryDescriptionWithProfiles_other:
|
||||
'You already have {{count}} profiles in this workspace. Select one from the left rail to edit it, or create another profile from here.',
|
||||
summaryDescriptionNoProfiles:
|
||||
'Use API Profiles for Anthropic-compatible endpoints. Use AI Providers for Gemini, Codex, Claude, Vertex, and OpenAI-compatible connectors.',
|
||||
openrouterModelsBadge: '{{modelCountLabel}} OpenRouter models',
|
||||
runtimeProviderBadge: 'Runtime provider setup',
|
||||
runtimeProviderTitle: 'Open AI Providers',
|
||||
runtimeProviderDescription:
|
||||
'Manage shared provider keys and runtime connectors before creating standalone API Profiles.',
|
||||
runtimeProviderFeatureConnectors:
|
||||
'Gemini, Codex, Claude, Vertex, and OpenAI-compatible connectors',
|
||||
runtimeProviderFeatureSecrets: 'Shared secrets stay outside individual API profiles',
|
||||
runtimeProviderFooter:
|
||||
'Best when multiple profiles should share one provider configuration.',
|
||||
createOpenRouterProfile: 'Create OpenRouter Profile',
|
||||
getApiKeyAt: 'Get your API key at',
|
||||
or: 'or',
|
||||
@@ -1420,6 +1439,24 @@ const resources = {
|
||||
'通过一个 API 即可访问来自 OpenAI、Anthropic、Google、Meta 等的 {{modelCountLabel}} 个模型。',
|
||||
featureOneApi: '一个 API,接入全部提供商',
|
||||
featureTierMapping: '模型档位映射',
|
||||
profileCount: '{{count}} 个 API 配置',
|
||||
profileCount_other: '{{count}} 个 API 配置',
|
||||
selectProfileTitle: '选择一个 API 配置',
|
||||
summaryDescriptionWithProfiles:
|
||||
'此工作区中已存在 {{count}} 个 API 配置。请从左侧栏选择一个进行编辑,或在此继续创建新的配置。',
|
||||
summaryDescriptionWithProfiles_other:
|
||||
'此工作区中已存在 {{count}} 个 API 配置。请从左侧栏选择一个进行编辑,或在此继续创建新的配置。',
|
||||
summaryDescriptionNoProfiles:
|
||||
'API 配置适用于 Anthropic 兼容端点。AI Providers 适用于 Gemini、Codex、Claude、Vertex 以及 OpenAI 兼容连接器。',
|
||||
openrouterModelsBadge: '{{modelCountLabel}} 个 OpenRouter 模型',
|
||||
runtimeProviderBadge: '运行时提供商设置',
|
||||
runtimeProviderTitle: '打开 AI Providers',
|
||||
runtimeProviderDescription:
|
||||
'先管理共享的提供商密钥和运行时连接器,再创建独立的 API 配置。',
|
||||
runtimeProviderFeatureConnectors:
|
||||
'Gemini、Codex、Claude、Vertex 以及 OpenAI 兼容连接器',
|
||||
runtimeProviderFeatureSecrets: '共享密钥与单个 API 配置分离存放',
|
||||
runtimeProviderFooter: '当多个配置需要共享同一提供商设置时最适合使用。',
|
||||
createOpenRouterProfile: '创建 OpenRouter 配置',
|
||||
getApiKeyAt: '在此获取 API Key:',
|
||||
or: '或',
|
||||
@@ -2566,6 +2603,25 @@ const resources = {
|
||||
'Truy cập các mô hình {{modelCountLabel}} từ OpenAI, Anthropic, Google, Meta, v.v. - tất cả đều thông qua một API.',
|
||||
featureOneApi: 'Một API cho mọi nhà cung cấp',
|
||||
featureTierMapping: 'Ánh xạ tầng mô hình',
|
||||
profileCount: '{{count}} hồ sơ',
|
||||
profileCount_other: '{{count}} hồ sơ',
|
||||
selectProfileTitle: 'Chọn một hồ sơ API',
|
||||
summaryDescriptionWithProfiles:
|
||||
'Bạn đã có {{count}} hồ sơ trong workspace này. Chọn một hồ sơ ở thanh bên trái để chỉnh sửa, hoặc tạo thêm hồ sơ mới tại đây.',
|
||||
summaryDescriptionWithProfiles_other:
|
||||
'Bạn đã có {{count}} hồ sơ trong workspace này. Chọn một hồ sơ ở thanh bên trái để chỉnh sửa, hoặc tạo thêm hồ sơ mới tại đây.',
|
||||
summaryDescriptionNoProfiles:
|
||||
'Dùng API Profiles cho các endpoint tương thích Anthropic. Dùng AI Providers cho Gemini, Codex, Claude, Vertex và các connector tương thích OpenAI.',
|
||||
openrouterModelsBadge: '{{modelCountLabel}} mô hình OpenRouter',
|
||||
runtimeProviderBadge: 'Thiết lập nhà cung cấp runtime',
|
||||
runtimeProviderTitle: 'Mở AI Providers',
|
||||
runtimeProviderDescription:
|
||||
'Quản lý khóa nhà cung cấp dùng chung và các connector runtime trước khi tạo API Profiles độc lập.',
|
||||
runtimeProviderFeatureConnectors:
|
||||
'Gemini, Codex, Claude, Vertex và các connector tương thích OpenAI',
|
||||
runtimeProviderFeatureSecrets: 'Khóa dùng chung tách biệt khỏi từng API Profile',
|
||||
runtimeProviderFooter:
|
||||
'Phù hợp nhất khi nhiều hồ sơ cần dùng chung một cấu hình nhà cung cấp.',
|
||||
createOpenRouterProfile: 'Tạo hồ sơ OpenRouter',
|
||||
getApiKeyAt: 'Nhận khóa API của bạn tại',
|
||||
or: 'hoặc',
|
||||
@@ -3767,6 +3823,26 @@ const resources = {
|
||||
'OpenAI、Anthropic、Google、Meta などの {{modelCountLabel}} モデルを、1 つの API で利用できます。',
|
||||
featureOneApi: '1 つの API で全プロバイダー',
|
||||
featureTierMapping: 'モデルティアマッピング',
|
||||
profileCount: '{{count}} 個の API プロファイル',
|
||||
profileCount_other: '{{count}} 個の API プロファイル',
|
||||
selectProfileTitle: 'API プロファイルを選択',
|
||||
summaryDescriptionWithProfiles:
|
||||
'このワークスペースには既に {{count}} 個の API プロファイルがあります。左側レールから 1 つ選んで編集するか、ここから新しいプロファイルを作成してください。',
|
||||
summaryDescriptionWithProfiles_other:
|
||||
'このワークスペースには既に {{count}} 個の API プロファイルがあります。左側レールから 1 つ選んで編集するか、ここから新しいプロファイルを作成してください。',
|
||||
summaryDescriptionNoProfiles:
|
||||
'API Profiles は Anthropic 互換エンドポイント向けです。AI Providers は Gemini、Codex、Claude、Vertex、OpenAI 互換コネクタ向けです。',
|
||||
openrouterModelsBadge: '{{modelCountLabel}} 個の OpenRouter モデル',
|
||||
runtimeProviderBadge: 'ランタイムプロバイダー設定',
|
||||
runtimeProviderTitle: 'AI Providers を開く',
|
||||
runtimeProviderDescription:
|
||||
'個別の API プロファイルを作成する前に、共有プロバイダーキーとランタイムコネクタを管理します。',
|
||||
runtimeProviderFeatureConnectors:
|
||||
'Gemini、Codex、Claude、Vertex、OpenAI 互換コネクタ',
|
||||
runtimeProviderFeatureSecrets:
|
||||
'共有シークレットを個別の API プロファイルから切り離して管理',
|
||||
runtimeProviderFooter:
|
||||
'複数のプロファイルで 1 つのプロバイダー設定を共有したい場合に最適です。',
|
||||
createOpenRouterProfile: 'OpenRouter プロファイルを作成',
|
||||
getApiKeyAt: 'API キー取得:',
|
||||
or: 'または',
|
||||
|
||||
@@ -206,9 +206,9 @@ export function ApiPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex h-full flex-col overflow-hidden">
|
||||
<OpenRouterBanner onCreateClick={() => setCreateDialogOpen(true)} />
|
||||
<div className="flex-1 flex min-h-0">
|
||||
<div className="flex-1 flex min-h-0 overflow-hidden">
|
||||
<div className="w-80 border-r flex flex-col bg-muted/30">
|
||||
<div className="p-4 border-b bg-background">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
@@ -262,7 +262,7 @@ export function ApiPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="flex-1">
|
||||
<ScrollArea className="flex-1 min-h-0">
|
||||
{isLoading ? (
|
||||
<div className="p-4 text-sm text-muted-foreground">
|
||||
{t('apiProfiles.loadingProfiles')}
|
||||
@@ -354,7 +354,7 @@ export function ApiPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col min-w-0">
|
||||
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
{selectedProfileData ? (
|
||||
<>
|
||||
<div className="px-4 py-2 border-b bg-background flex items-center justify-end gap-2">
|
||||
@@ -387,6 +387,8 @@ export function ApiPage() {
|
||||
</>
|
||||
) : (
|
||||
<OpenRouterQuickStart
|
||||
hasProfiles={profiles.length > 0}
|
||||
profileCount={profiles.length}
|
||||
onCliproxyClick={() => {
|
||||
navigate('/cliproxy/ai-providers');
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user