feat(wizard): restructure AI engine selection flow

Restructure the wizard's Step 2 (AI Engine) into a two-layer choice:
- Orchestrated Agent apps (Dify, n8n, Coze, etc.)
- Direct LLM usage (Space service or custom provider)

For direct LLM mode, users choose between LangBot Space (OAuth)
or adding their own model provider via a dedicated ProviderForm page.

Support intra-step back navigation across all layers.
Add Vite dev server proxy config for API requests.
Add i18n translations for all 8 locales.
This commit is contained in:
langbot-dev
2026-08-12 01:29:07 +08:00
parent 90f3d880e5
commit 1f3aad6baa
10 changed files with 606 additions and 87 deletions
+321 -87
View File
@@ -12,6 +12,8 @@ import {
Loader2,
X,
ExternalLink,
Rocket,
Wrench,
} from 'lucide-react';
import { httpClient } from '@/app/infra/http/HttpClient';
@@ -38,6 +40,7 @@ import {
parseDynamicFormItemType,
} from '@/app/home/components/dynamic-form/DynamicFormItemConfig';
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
import ProviderForm from '@/app/home/components/models-dialog/component/provider-form/ProviderForm';
import { BotLogListComponent } from '@/app/home/bots/components/bot-log/view/BotLogListComponent';
import { extractI18nObject } from '@/i18n/I18nProvider';
import {
@@ -67,6 +70,7 @@ import {
DialogTitle,
} from '@/components/ui/dialog';
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
@@ -92,6 +96,13 @@ export default function WizardPage() {
{},
);
const [runnerConfig, setRunnerConfig] = useState<Record<string, unknown>>({});
const [aiEngineMode, setAiEngineMode] = useState<
'orchestrated' | 'llm' | null
>(null);
const [modelSource, setModelSource] = useState<'space' | 'custom' | null>(
null,
);
const [providerCreated, setProviderCreated] = useState(false);
const [createdBotUuid, setCreatedBotUuid] = useState<string | null>(null);
const [webhookUrl, setWebhookUrl] = useState<string>('');
const [extraWebhookUrl, setExtraWebhookUrl] = useState<string>('');
@@ -278,6 +289,16 @@ export default function WizardPage() {
[saveProgress],
);
const handleAiEngineModeSelect = useCallback(
(mode: 'orchestrated' | 'llm') => {
setAiEngineMode(mode);
setSelectedRunner(null);
setModelSource(null);
setProviderCreated(false);
},
[],
);
// ---- Navigation helpers ----
const canProceed = useCallback((): boolean => {
@@ -287,11 +308,29 @@ export default function WizardPage() {
case 1:
return createdBotUuid !== null && botSaved;
case 2:
return selectedRunner !== null;
if (aiEngineMode === 'orchestrated') {
return selectedRunner !== null;
}
if (aiEngineMode === 'llm') {
return (
modelSource === 'space' ||
(modelSource === 'custom' && providerCreated)
);
}
return false;
default:
return false;
}
}, [currentStep, selectedAdapter, createdBotUuid, botSaved, selectedRunner]);
}, [
currentStep,
selectedAdapter,
createdBotUuid,
botSaved,
aiEngineMode,
selectedRunner,
modelSource,
providerCreated,
]);
const goNext = useCallback(() => {
if (currentStep < TOTAL_STEPS - 1 && canProceed()) {
@@ -302,12 +341,35 @@ export default function WizardPage() {
}, [currentStep, canProceed, saveProgress]);
const goPrev = useCallback(() => {
if (currentStep === 2) {
// Intra-step back navigation for AI Engine step
if (selectedRunner && aiEngineMode === 'orchestrated') {
setSelectedRunner(null);
return;
}
if (
aiEngineMode === 'llm' &&
modelSource === 'custom' &&
!providerCreated
) {
setModelSource(null);
return;
}
if (modelSource) {
setModelSource(null);
return;
}
if (aiEngineMode) {
setAiEngineMode(null);
return;
}
}
if (currentStep > 0) {
const prevStep = currentStep - 1;
setCurrentStep(prevStep);
saveProgress({ step: prevStep });
}
}, [currentStep, saveProgress]);
}, [currentStep, saveProgress, aiEngineMode, selectedRunner, modelSource, providerCreated]);
// ---- Create Bot (Step 0) ----
// Creates a disabled bot using the adapter label as name.
@@ -503,6 +565,16 @@ export default function WizardPage() {
}
}, [t]);
const handleModelSourceSelect = useCallback(
(source: 'space' | 'custom') => {
setModelSource(source);
if (source === 'space') {
handleSpaceAuth();
}
},
[handleSpaceAuth],
);
// ---- Check if local account ----
// Re-evaluated after remote data fetch (when userInfo is populated)
const isLocalAccount =
@@ -660,10 +732,17 @@ export default function WizardPage() {
{currentStep === 2 && (
<StepAIEngine
runnerOptions={runnerOptions}
selected={selectedRunner}
onSelect={handleSelectRunner}
aiEngineMode={aiEngineMode}
onAiEngineModeSelect={handleAiEngineModeSelect}
selectedRunner={selectedRunner}
onSelectRunner={handleSelectRunner}
isLocalAccount={isLocalAccount}
onSpaceAuth={handleSpaceAuth}
modelSource={modelSource}
onModelSourceSelect={handleModelSourceSelect}
providerCreated={providerCreated}
onProviderCreated={() => setProviderCreated(true)}
onResetModelSource={() => setModelSource(null)}
runnerConfigItems={selectedRunnerConfigItems}
runnerConfigValues={runnerConfig}
onRunnerConfigChange={setRunnerConfig}
@@ -1004,19 +1083,33 @@ function StepBotConfig({
function StepAIEngine({
runnerOptions,
selected,
onSelect,
aiEngineMode,
onAiEngineModeSelect,
selectedRunner,
onSelectRunner,
isLocalAccount,
onSpaceAuth,
modelSource,
onModelSourceSelect,
providerCreated,
onProviderCreated,
onResetModelSource,
runnerConfigItems,
runnerConfigValues,
onRunnerConfigChange,
}: {
runnerOptions: { name: string; label: { en_US: string; zh_Hans: string } }[];
selected: string | null;
onSelect: (name: string) => void;
aiEngineMode: 'orchestrated' | 'llm' | null;
onAiEngineModeSelect: (mode: 'orchestrated' | 'llm') => void;
selectedRunner: string | null;
onSelectRunner: (name: string) => void;
isLocalAccount: boolean;
onSpaceAuth: () => void;
modelSource: 'space' | 'custom' | null;
onModelSourceSelect: (source: 'space' | 'custom') => void;
providerCreated: boolean;
onProviderCreated: () => void;
onResetModelSource: () => void;
runnerConfigItems: IDynamicFormItemSchema[];
runnerConfigValues: Record<string, unknown>;
onRunnerConfigChange: (v: Record<string, unknown>) => void;
@@ -1032,14 +1125,19 @@ function StepAIEngine({
);
const runnerLabel = useMemo(() => {
const r = runnerOptions.find((o) => o.name === selected);
return r ? extractI18nObject(r.label) : (selected ?? '');
}, [runnerOptions, selected]);
const r = runnerOptions.find((o) => o.name === selectedRunner);
return r ? extractI18nObject(r.label) : (selectedRunner ?? '');
}, [runnerOptions, selectedRunner]);
// Before any runner is selected: centered grid layout
if (!selected) {
// Orchestrated runners: everything except local-agent
const orchestratedRunners = runnerOptions.filter(
(o) => o.name !== 'local-agent',
);
// ---- Layer 0: Main choice ----
if (!aiEngineMode) {
return (
<div className="space-y-6 max-w-4xl mx-auto">
<div className="space-y-6 max-w-3xl mx-auto">
<div className="text-center">
<h2 className="text-xl font-semibold">
{t('wizard.aiEngine.title')}
@@ -1048,12 +1146,75 @@ function StepAIEngine({
{t('wizard.aiEngine.description')}
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
{/* Orchestrated Agent Apps */}
<div
className="relative rounded-xl border-2 border-border cursor-pointer transition-all hover:shadow-lg hover:border-primary/50 hover:scale-[1.02]"
onClick={() => onAiEngineModeSelect('orchestrated')}
>
<div className="p-6 flex flex-col items-center gap-4 text-center h-full">
<div className="w-14 h-14 rounded-full bg-muted flex items-center justify-center">
<Wrench className="w-7 h-7 text-muted-foreground" />
</div>
<div>
<h3 className="text-lg font-semibold">
{t('wizard.aiEngine.orchestrated.title')}
</h3>
<p className="text-sm text-muted-foreground mt-1">
{t('wizard.aiEngine.orchestrated.description')}
</p>
</div>
<Button variant="outline" size="lg" className="w-full">
{t('wizard.aiEngine.orchestrated.action')}
</Button>
</div>
</div>
{/* Direct LLM */}
<div
className="relative rounded-xl border-2 border-border cursor-pointer transition-all hover:shadow-lg hover:border-primary/50 hover:scale-[1.02]"
onClick={() => onAiEngineModeSelect('llm')}
>
<div className="p-6 flex flex-col items-center gap-4 text-center h-full">
<div className="w-14 h-14 rounded-full bg-muted flex items-center justify-center">
<Rocket className="w-7 h-7 text-muted-foreground" />
</div>
<div>
<h3 className="text-lg font-semibold">
{t('wizard.aiEngine.llm.title')}
</h3>
<p className="text-sm text-muted-foreground mt-1">
{t('wizard.aiEngine.llm.description')}
</p>
</div>
<Button variant="outline" size="lg" className="w-full">
{t('wizard.aiEngine.llm.action')}
</Button>
</div>
</div>
</div>
</div>
);
}
// ---- Layer 1a: Orchestrated runners ----
if (aiEngineMode === 'orchestrated' && !selectedRunner) {
return (
<div className="space-y-6 max-w-4xl mx-auto">
<div className="text-center">
<h2 className="text-xl font-semibold">
{t('wizard.aiEngine.orchestrated.selectTitle')}
</h2>
<p className="text-sm text-muted-foreground mt-1">
{t('wizard.aiEngine.orchestrated.selectDescription')}
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{runnerOptions.map((opt) => (
{orchestratedRunners.map((opt) => (
<Card
key={opt.name}
className="cursor-pointer transition-all hover:shadow-md hover:border-primary/50"
onClick={() => onSelect(opt.name)}
onClick={() => onSelectRunner(opt.name)}
>
<CardHeader className="flex flex-row items-center gap-3">
<div className="min-w-0 flex-1">
@@ -1072,9 +1233,96 @@ function StepAIEngine({
);
}
// After a runner is selected: left-right split layout
// On mobile (< lg): single column, normal scroll from parent
// On desktop (>= lg): side-by-side with independent scroll per column
// ---- Layer 1b: LLM model source selection ----
if (aiEngineMode === 'llm' && !modelSource) {
return (
<div className="space-y-6 max-w-3xl mx-auto">
<div className="text-center">
<h2 className="text-xl font-semibold">
{t('wizard.modelSource.title')}
</h2>
<p className="text-sm text-muted-foreground mt-1">
{t('wizard.modelSource.description')}
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
{/* LangBot Service */}
<div
className="relative rounded-xl border-2 border-border cursor-pointer transition-all hover:shadow-lg hover:border-primary/50 hover:scale-[1.02]"
onClick={() => onModelSourceSelect('space')}
>
<div className="p-6 flex flex-col items-center gap-4 text-center h-full">
<div className="w-14 h-14 rounded-full bg-muted flex items-center justify-center">
<Rocket className="w-7 h-7 text-muted-foreground" />
</div>
<div>
<h3 className="text-lg font-semibold">
{t('wizard.modelSource.space.title')}
</h3>
<p className="text-sm text-muted-foreground mt-1">
{t('wizard.modelSource.space.description')}
</p>
</div>
<Button variant="outline" size="lg" className="w-full">
{t('wizard.modelSource.space.action')}
</Button>
</div>
</div>
{/* Custom provider */}
<div
className="relative rounded-xl border-2 border-border cursor-pointer transition-all hover:shadow-lg hover:border-primary/50 hover:scale-[1.02]"
onClick={() => onModelSourceSelect('custom')}
>
<div className="p-6 flex flex-col items-center gap-4 text-center h-full">
<div className="w-14 h-14 rounded-full bg-muted flex items-center justify-center">
<Wrench className="w-7 h-7 text-muted-foreground" />
</div>
<div>
<h3 className="text-lg font-semibold">
{t('wizard.modelSource.custom.title')}
</h3>
<p className="text-sm text-muted-foreground mt-1">
{t('wizard.modelSource.custom.description')}
</p>
</div>
<Button variant="outline" size="lg" className="w-full">
{t('wizard.modelSource.custom.action')}
</Button>
</div>
</div>
</div>
</div>
);
}
// ---- Layer 2: Custom provider creation form ----
if (
aiEngineMode === 'llm' &&
modelSource === 'custom' &&
!providerCreated
) {
return (
<div className="max-w-2xl mx-auto w-full animate-in fade-in slide-in-from-bottom-2 duration-300">
<div className="text-center mb-6">
<h2 className="text-xl font-semibold">
{t('wizard.provider.title')}
</h2>
<p className="text-sm text-muted-foreground mt-1">
{t('wizard.provider.description')}
</p>
</div>
<div className="border rounded-lg p-6 bg-card">
<ProviderForm
onFormSubmit={onProviderCreated}
onFormCancel={onResetModelSource}
/>
</div>
</div>
);
}
// ---- Layer 3: Runner configuration form ----
return (
<div className="flex flex-col lg:flex-1 lg:min-h-0 max-w-6xl mx-auto w-full">
<div className="text-center shrink-0 mb-4">
@@ -1085,75 +1333,61 @@ function StepAIEngine({
</div>
<div className="flex flex-col lg:flex-row lg:justify-center gap-6 lg:flex-1 lg:min-h-0 animate-in fade-in slide-in-from-bottom-2 duration-300">
{/* Left: runner list */}
<div className="w-full lg:w-[280px] shrink-0 lg:overflow-y-auto lg:pr-3">
{/* p-1 provides space for ring-2 (4px) to render without clipping */}
<div className="space-y-3 p-1">
{runnerOptions.map((opt) => {
const isSelected = selected === opt.name;
return (
<Card
key={opt.name}
className={cn(
'cursor-pointer transition-all',
isSelected
? 'ring-2 ring-primary shadow-md'
: 'opacity-50 hover:opacity-80 hover:border-primary/50',
)}
onClick={() => onSelect(opt.name)}
>
<CardHeader className="flex flex-row items-center gap-3 py-3 px-4">
<div className="min-w-0 flex-1">
<CardTitle
className={cn(
'text-sm',
!isSelected && 'text-muted-foreground',
)}
>
{extractI18nObject(opt.label)}
</CardTitle>
<CardDescription className="text-xs font-mono text-muted-foreground">
{opt.name}
</CardDescription>
</div>
{isSelected && (
<div className="shrink-0">
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center">
<Check className="w-3 h-3 text-primary-foreground" />
</div>
</div>
{/* Left: runner list (only for orchestrated mode) */}
{aiEngineMode === 'orchestrated' && (
<div className="w-full lg:w-[280px] shrink-0 lg:overflow-y-auto lg:pr-3">
<div className="space-y-3 p-1">
{orchestratedRunners.map((opt) => {
const isSelected = selectedRunner === opt.name;
return (
<Card
key={opt.name}
className={cn(
'cursor-pointer transition-all',
isSelected
? 'ring-2 ring-primary shadow-md'
: 'opacity-50 hover:opacity-80 hover:border-primary/50',
)}
</CardHeader>
</Card>
);
})}
{/* Space promotion banner */}
{selected === 'local-agent' && isLocalAccount && (
<div className="animate-in fade-in slide-in-from-left-2 duration-300">
<div className="relative rounded-lg p-[2px] bg-gradient-to-r from-purple-500 via-pink-500 to-orange-500">
<div className="rounded-[calc(0.5rem-2px)] bg-background p-3 flex flex-col items-center gap-2 text-center">
<Sparkles className="w-6 h-6 text-purple-500 shrink-0" />
<p className="text-xs font-medium">
{t('wizard.spaceBanner.message')}
</p>
<Button
variant="outline"
size="sm"
onClick={onSpaceAuth}
className="w-full"
>
{t('wizard.spaceBanner.action')}
</Button>
</div>
</div>
</div>
)}
onClick={() => onSelectRunner(opt.name)}
>
<CardHeader className="flex flex-row items-center gap-3 py-3 px-4">
<div className="min-w-0 flex-1">
<CardTitle
className={cn(
'text-sm',
!isSelected && 'text-muted-foreground',
)}
>
{extractI18nObject(opt.label)}
</CardTitle>
<CardDescription className="text-xs font-mono text-muted-foreground">
{opt.name}
</CardDescription>
</div>
{isSelected && (
<div className="shrink-0">
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center">
<Check className="w-3 h-3 text-primary-foreground" />
</div>
</div>
)}
</CardHeader>
</Card>
);
})}
</div>
</div>
</div>
)}
{/* Right: runner configuration — fixed width on desktop */}
<div className="w-full lg:w-[560px] shrink-0 lg:overflow-y-auto lg:pr-3 animate-in fade-in slide-in-from-right-2 duration-300">
{/* Right: runner configuration */}
<div
className={cn(
'shrink-0 lg:overflow-y-auto lg:pr-3 animate-in fade-in slide-in-from-right-2 duration-300',
aiEngineMode === 'orchestrated'
? 'w-full lg:w-[560px]'
: 'w-full max-w-2xl mx-auto',
)}
>
<div className="p-1">
{runnerConfigItems.length > 0 && (
<Card>
@@ -1164,7 +1398,7 @@ function StepAIEngine({
</CardHeader>
<CardContent>
<DynamicFormComponent
key={selected}
key={selectedRunner ?? 'llm'}
itemConfigList={runnerConfigItems}
initialValues={runnerConfigValues as Record<string, object>}
onSubmit={stableRunnerConfigCb}
+35
View File
@@ -1835,12 +1835,47 @@ const enUS = {
title: 'Select an AI Engine',
description:
"Choose the AI engine that will power your bot's intelligence.",
orchestrated: {
title: 'Use Orchestrated Agent Apps',
description:
'Use pre-built Agent apps from Dify, n8n, Coze, and more.',
action: 'Select Agent App',
selectTitle: 'Select Agent App',
selectDescription: 'Choose the Agent app platform you want to use.',
},
llm: {
title: 'Use LLM Directly',
description:
'Configure a model provider and use LLM to drive your bot directly.',
action: 'Configure LLM',
},
},
spaceBanner: {
message:
'Connect to LangBot Space for free trial model credits and zero-config instant setup!',
action: 'Authorize with Space',
},
provider: {
title: 'Add Model Provider',
description:
'Add your own model provider. Configure the API key to get started.',
},
modelSource: {
title: 'Choose Model Source',
description: 'Select how to provide model capabilities for your AI engine.',
space: {
title: 'LangBot Service',
description:
'Zero-config ready with free trial model credits. No API key needed — just plug and play.',
action: 'Use LangBot Service',
},
custom: {
title: 'Custom Model',
description:
'Bring your own model API key. Supports OpenAI, Claude, Gemini, and more.',
action: 'Add My Own Model',
},
},
config: {
botInfo: 'Bot Information',
botNamePlaceholder: 'Enter bot name',
+37
View File
@@ -1691,12 +1691,49 @@ const esES = {
title: 'Selecciona un motor de IA',
description:
'Elige el motor de IA que impulsará la inteligencia de tu Bot.',
orchestrated: {
title: 'Usar aplicaciones Agent orquestadas',
description:
'Usar aplicaciones Agent predefinidas de Dify, n8n, Coze y más.',
action: 'Seleccionar aplicación Agent',
selectTitle: 'Seleccionar aplicación Agent',
selectDescription:
'Elige la plataforma de aplicación Agent que deseas usar.',
},
llm: {
title: 'Usar modelo directamente',
description:
'Configurar un proveedor de modelos y usar el modelo para impulsar tu Bot.',
action: 'Configurar modelo',
},
},
spaceBanner: {
message:
'¡Conéctate a LangBot Space para obtener créditos de prueba gratuitos y configuración instantánea sin esfuerzo!',
action: 'Autorizar con Space',
},
provider: {
title: 'Agregar proveedor de modelos',
description:
'Agrega tu propio proveedor de modelos. Configura la API key para comenzar.',
},
modelSource: {
title: 'Elegir fuente del modelo',
description:
'Selecciona cómo proporcionar capacidades de modelo para tu motor de IA.',
space: {
title: 'Servicio LangBot',
description:
'Listo para usar sin configuración, con créditos de prueba gratuitos. No se necesita API key.',
action: 'Usar servicio LangBot',
},
custom: {
title: 'Modelo personalizado',
description:
'Usa tu propia API key. Compatible con OpenAI, Claude, Gemini y más.',
action: 'Agregar mi propio modelo',
},
},
config: {
botInfo: 'Información del Bot',
botNamePlaceholder: 'Introduce el nombre del Bot',
+36
View File
@@ -1752,12 +1752,48 @@ const jaJP = {
title: 'AIエンジンを選択',
description:
'ボットのインテリジェンスを駆動するAIエンジンを選択してください。',
orchestrated: {
title: 'オーケストレーション済みAgentアプリを使用',
description:
'Dify、n8n、Cozeなどのプラットフォームで構築済みのAgentアプリを使用。',
action: 'Agentアプリを選択',
selectTitle: 'Agentアプリを選択',
selectDescription:
'使用するAgentアプリプラットフォームを選択してください。',
},
llm: {
title: '大規模モデルを直接使用',
description:
'モデルプロバイダーを設定し、大規模モデルでボットを直接駆動。',
action: '大規模モデルを設定',
},
},
spaceBanner: {
message:
'LangBot Spaceに接続して、無料トライアルモデルクレジットとゼロ設定の即時セットアップを入手!',
action: 'Spaceで認証',
},
provider: {
title: 'モデルプロバイダーを追加',
description:
'独自のモデルプロバイダーを追加します。APIキーを設定すると使用可能になります。',
},
modelSource: {
title: 'モデルソースを選択',
description: 'AIエンジンにモデル機能を提供する方法を選択してください。',
space: {
title: 'LangBot サービス',
description:
'設定不要で利用可能。無料トライアルモデルクレジット付き。APIキー不要ですぐに使えます。',
action: 'LangBot サービスを使用',
},
custom: {
title: 'カスタムモデル',
description:
'お手持ちのモデルAPIキーを使用。OpenAI、Claude、Geminiなど主要モデルに対応。',
action: '独自のモデルを追加',
},
},
config: {
botInfo: 'ボット情報',
botNamePlaceholder: 'ボット名を入力',
+37
View File
@@ -1661,12 +1661,49 @@ const ruRU = {
title: 'Выберите ИИ-движок',
description:
'Выберите ИИ-движок, который будет управлять интеллектом вашего бота.',
orchestrated: {
title: 'Использовать оркестрированные Agent-приложения',
description:
'Использовать готовые Agent-приложения из Dify, n8n, Coze и других.',
action: 'Выбрать Agent-приложение',
selectTitle: 'Выбрать Agent-приложение',
selectDescription:
'Выберите платформу Agent-приложения для использования.',
},
llm: {
title: 'Использовать LLM напрямую',
description:
'Настроить провайдера моделей и использовать LLM для управления ботом.',
action: 'Настроить LLM',
},
},
spaceBanner: {
message:
'Подключитесь к LangBot Space для бесплатных пробных кредитов и мгновенной настройки!',
action: 'Авторизация через Space',
},
provider: {
title: 'Добавить провайдера моделей',
description:
'Добавьте своего провайдера моделей. Настройте API-ключ для начала работы.',
},
modelSource: {
title: 'Выберите источник модели',
description:
'Выберите способ предоставления возможностей модели для вашего AI-движка.',
space: {
title: 'Сервис LangBot',
description:
'Готов к использованию без настройки, с бесплатными пробными кредитами. API-ключ не требуется.',
action: 'Использовать сервис LangBot',
},
custom: {
title: 'Пользовательская модель',
description:
'Используйте свой собственный API-ключ. Поддержка OpenAI, Claude, Gemini и других.',
action: 'Добавить свою модель',
},
},
config: {
botInfo: 'Информация о боте',
botNamePlaceholder: 'Введите имя бота',
+35
View File
@@ -1626,12 +1626,47 @@ const thTH = {
aiEngine: {
title: 'เลือกเครื่องมือ AI',
description: 'เลือกเครื่องมือ AI ที่จะขับเคลื่อนความฉลาดของ Bot',
orchestrated: {
title: 'ใช้แอป Agent ที่จัดเตรียมไว้',
description:
'ใช้แอป Agent ที่สร้างไว้จาก Dify, n8n, Coze และอื่นๆ',
action: 'เลือกแอป Agent',
selectTitle: 'เลือกแอป Agent',
selectDescription: 'เลือกแพลตฟอร์มแอป Agent ที่ต้องการใช้',
},
llm: {
title: 'ใช้โมเดลโดยตรง',
description:
'กำหนดค่าผู้ให้บริการโมเดลและใช้โมเดลขับเคลื่อน Bot โดยตรง',
action: 'กำหนดค่าโมเดล',
},
},
spaceBanner: {
message:
'เชื่อมต่อกับ LangBot Space เพื่อรับเครดิตทดลองใช้โมเดลฟรีและตั้งค่าทันทีโดยไม่ต้องกำหนดค่า!',
action: 'ยืนยันสิทธิ์กับ Space',
},
provider: {
title: 'เพิ่มผู้ให้บริการโมเดล',
description:
'เพิ่มผู้ให้บริการโมเดลของคุณเอง กำหนดค่า API key เพื่อเริ่มต้น',
},
modelSource: {
title: 'เลือกแหล่งโมเดล',
description: 'เลือกวิธีการให้ความสามารถของโมเดลสำหรับ AI engine ของคุณ',
space: {
title: 'บริการ LangBot',
description:
'พร้อมใช้งานทันทีไม่ต้องตั้งค่า พร้อมเครดิตทดลองใช้โมเดลฟรี ไม่ต้องใช้ API key',
action: 'ใช้บริการ LangBot',
},
custom: {
title: 'โมเดลกำหนดเอง',
description:
'ใช้ API key โมเดลของคุณเอง รองรับ OpenAI, Claude, Gemini และอื่นๆ',
action: 'เพิ่มโมเดลของฉัน',
},
},
config: {
botInfo: 'ข้อมูล Bot',
botNamePlaceholder: 'กรอกชื่อ Bot',
+35
View File
@@ -1652,12 +1652,47 @@ const viVN = {
aiEngine: {
title: 'Chọn công cụ AI',
description: 'Chọn công cụ AI sẽ cung cấp trí tuệ cho Bot của bạn.',
orchestrated: {
title: 'Sử dụng ứng dụng Agent đã được phối hợp',
description:
'Sử dụng các ứng dụng Agent đã được xây dựng từ Dify, n8n, Coze.',
action: 'Chọn ứng dụng Agent',
selectTitle: 'Chọn ứng dụng Agent',
selectDescription: 'Chọn nền tảng ứng dụng Agent bạn muốn sử dụng.',
},
llm: {
title: 'Sử dụng mô hình trực tiếp',
description:
'Cấu hình nhà cung cấp mô hình và sử dụng mô hình lớn để điều khiển Bot.',
action: 'Cấu hình mô hình',
},
},
spaceBanner: {
message:
'Kết nối với LangBot Space để nhận tín dụng dùng thử mô hình miễn phí và thiết lập tức thì không cần cấu hình!',
action: 'Ủy quyền với Space',
},
provider: {
title: 'Thêm nhà cung cấp mô hình',
description:
'Thêm nhà cung cấp mô hình của bạn. Cấu hình API key để bắt đầu.',
},
modelSource: {
title: 'Chọn nguồn mô hình',
description: 'Chọn cách cung cấp khả năng mô hình cho AI engine của bạn.',
space: {
title: 'Dịch vụ LangBot',
description:
'Sẵn sàng sử dụng không cần cấu hình, tặng tín dụng mô hình thử nghiệm miễn phí. Không cần API key.',
action: 'Sử dụng dịch vụ LangBot',
},
custom: {
title: 'Mô hình tùy chỉnh',
description:
'Sử dụng API key mô hình của riêng bạn. Hỗ trợ OpenAI, Claude, Gemini và nhiều hơn nữa.',
action: 'Thêm mô hình của tôi',
},
},
config: {
botInfo: 'Thông tin Bot',
botNamePlaceholder: 'Nhập tên Bot',
+32
View File
@@ -1755,11 +1755,43 @@ const zhHans = {
aiEngine: {
title: '选择 AI 引擎',
description: '选择驱动机器人智能的 AI 引擎。',
orchestrated: {
title: '使用编排好的 Agent 应用',
description: '使用 Dify、n8n、Coze 等平台编排好的 Agent 应用。',
action: '选择 Agent 应用',
selectTitle: '选择 Agent 应用',
selectDescription: '选择你要使用的 Agent 应用平台。',
},
llm: {
title: '直接使用大模型',
description: '配置大模型供应商,直接使用大模型驱动机器人。',
action: '配置大模型',
},
},
spaceBanner: {
message: '接入 LangBot Space,获取免费试用模型额度,零配置极速开箱!',
action: '前往授权登录',
},
provider: {
title: '添加模型供应商',
description: '添加你自己的模型供应商,配置 API Key 后即可使用。',
},
modelSource: {
title: '选择模型来源',
description: '选择如何为你的 AI 引擎提供模型能力。',
space: {
title: 'LangBot 服务',
description:
'零配置即可使用,提供免费试用模型额度,开箱即用,无需自备 API Key。',
action: '使用 LangBot 服务',
},
custom: {
title: '自定义模型',
description:
'使用你自己的模型 API Key,支持 OpenAI、Claude、Gemini 等主流模型。',
action: '添加我自己的模型',
},
},
config: {
botInfo: '机器人信息',
botNamePlaceholder: '请输入机器人名称',
+32
View File
@@ -1578,11 +1578,43 @@ const zhHant = {
aiEngine: {
title: '選擇 AI 引擎',
description: '選擇驅動機器人智慧的 AI 引擎。',
orchestrated: {
title: '使用編排好的 Agent 應用',
description: '使用 Dify、n8n、Coze 等平台編排好的 Agent 應用。',
action: '選擇 Agent 應用',
selectTitle: '選擇 Agent 應用',
selectDescription: '選擇你要使用的 Agent 應用平台。',
},
llm: {
title: '直接使用大模型',
description: '配置大模型供應商,直接使用大模型驅動機器人。',
action: '配置大模型',
},
},
spaceBanner: {
message: '接入 LangBot Space,取得免費試用模型額度,零配置極速開箱!',
action: '前往授權登入',
},
provider: {
title: '新增模型供應商',
description: '新增你自己的模型供應商,配置 API Key 後即可使用。',
},
modelSource: {
title: '選擇模型來源',
description: '選擇如何為你的 AI 引擎提供模型能力。',
space: {
title: 'LangBot 服務',
description:
'零配置即可使用,提供免費試用模型額度,開箱即用,無需自備 API Key。',
action: '使用 LangBot 服務',
},
custom: {
title: '自訂模型',
description:
'使用你自己的模型 API Key,支援 OpenAI、Claude、Gemini 等主流模型。',
action: '新增我自己的模型',
},
},
config: {
botInfo: '機器人資訊',
botNamePlaceholder: '請輸入機器人名稱',
+6
View File
@@ -11,6 +11,12 @@ export default defineConfig({
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://127.0.0.1:5300',
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',