Feat/onboarding wizard (#2086)

* feat(web): add onboarding wizard for guided bot creation

Implement a full-screen 4-step wizard at /wizard that guides users
through selecting a platform, configuring a bot, choosing an AI engine,
and completing setup. The wizard uses DynamicFormComponent for adapter
and pipeline configuration, embeds BotLogListComponent for real-time
debugging, persists state to localStorage, and integrates with Space
OAuth flow. Also fixes a prompt-editor crash in DynamicFormComponent
when value is undefined.

* feat(wizard): redesign step 0/1 flow, add skip dialog, auto-expand log images

- Step 0: Remove bot name/description fields; auto-derive name from adapter
  label; create disabled bot on confirm; advance to Step 1 automatically
- Step 1: Replace 'Create Bot' with 'Save & Enable Bot'; update adapter
  config and enable bot; disable form fields after saving
- Add skip confirmation AlertDialog with i18n message
- Add LanguageSelector to wizard header
- Move wizard sidebar entry to last position to prevent fallback redirect loop
- Add defaultExpanded prop to BotLogCard; auto-expand entries with images
  in wizard via autoExpandImages prop on BotLogListComponent
- Remove automatic default pipeline creation (write_default_pipeline) from
  backend persistence manager since the wizard now handles pipeline creation
- Update all 4 locale files (en-US, zh-Hans, zh-Hant, ja-JP)

* fix(wizard): hide detailed logs link in wizard, allow re-editing bot config after save

- Add hideDetailedLogsLink prop to BotLogListComponent; pass it in wizard
- Remove isEditing on DynamicFormComponent so form stays editable after save
- Always show save button; label changes to 'Re-save' after first save
- Add resaveBot i18n key to all 4 locale files

* style(wizard): move save button into config card header

* fix(wizard): initialize userInfo/systemInfo so model selector works

The wizard runs outside /home layout, so userInfo was null. This caused
the model-fallback-selector to filter out all Space models, showing an
empty dropdown. Fix by calling initializeUserInfo() and
initializeSystemInfo() before fetching wizard data.

Also:
- Hide log toolbar in wizard via hideToolbar prop on BotLogListComponent
- Add empty state message for bot logs (noLogs i18n key, all 4 locales)

* feat(wizard): redesign AI Engine step with left-right split layout

Before selecting a runner: centered grid of runner cards.
After selecting: left panel shows compact runner list for switching,
right panel shows runner config form with slide-in animations.

Also fix prompt field default: add default value to prompt-editor field
in ai.yaml metadata so the prompt is pre-populated with
'You are a helpful assistant.' instead of being empty.

* feat(pipeline): add default values to ai.yaml runner configs and show_if for n8n auth fields

- Sync default values from default-pipeline-config.json to all runner
  config fields in ai.yaml so wizard forms are pre-populated
- Add show_if conditions to n8n-service-api auth fields so only the
  relevant credentials appear based on selected auth-type
- Fix prompt-editor crash in DynamicFormItemComponent when field.value
  is undefined (Array.isArray guard + fallback)
- Improve wizard Step 2 split layout with fixed column widths,
  independent scroll, ring clipping fix, and mobile responsiveness
- Use key={selected} on DynamicFormComponent to force remount on
  runner switch
- Improve pipeline creation flow: create → fetch defaults → merge AI
  section → update (preserves trigger/safety/output defaults)

* feat(dynamic-form): add systemContext prop with __system.* namespace for show_if conditions

- Add systemContext prop to DynamicFormComponent for injecting external
  variables accessible via __system.* prefix in show_if conditions
- Extract resolveShowIfValue() helper for cleaner field resolution
- Pass { is_wizard: true } from wizard to hide knowledge-bases field
- Remove bot config save toast in wizard (keep inline indicator)

* feat(sidebar): render wizard as standalone item before Home group with fallback redirect fix

* fix(wizard): remove unused setBotDescription to fix lint error
This commit is contained in:
Junyan Chin
2026-03-28 00:46:22 +08:00
committed by GitHub
parent 6570f276d2
commit c111bf1714
15 changed files with 1601 additions and 115 deletions

View File

@@ -2,18 +2,16 @@ from __future__ import annotations
import datetime
import typing
import json
import uuid
import sqlalchemy.ext.asyncio as sqlalchemy_asyncio
import sqlalchemy
from . import database, migration
from ..entity.persistence import base, pipeline, metadata, model as persistence_model
from ..entity.persistence import base, metadata, model as persistence_model
from ..entity import persistence
from ..core import app
from ..utils import constants, importutil
from ..api.http.service import pipeline as pipeline_service
from . import databases, migrations
importutil.import_modules_in_pkg(databases)
@@ -78,7 +76,6 @@ class PersistenceManager:
self.ap.logger.info(f'Successfully upgraded database to version {last_migration_number}.')
await self.write_default_pipeline()
await self.write_space_model_providers()
async def create_tables(self):
@@ -101,29 +98,6 @@ class PersistenceManager:
if row is None:
await self.execute_async(sqlalchemy.insert(metadata.Metadata).values(item))
async def write_default_pipeline(self):
# write default pipeline
result = await self.execute_async(sqlalchemy.select(pipeline.LegacyPipeline))
default_pipeline_uuid = None
if result.first() is None:
self.ap.logger.info('Creating default pipeline...')
pipeline_config = json.loads(importutil.read_resource_file('templates/default-pipeline-config.json'))
default_pipeline_uuid = str(uuid.uuid4())
pipeline_data = {
'uuid': default_pipeline_uuid,
'for_version': self.ap.ver_mgr.get_current_version(),
'stages': pipeline_service.default_stage_order,
'is_default': True,
'name': 'ChatPipeline',
'description': 'Default pipeline, new bots will be bound to this pipeline | 默认提供的流水线,您配置的机器人将自动绑定到此流水线',
'config': pipeline_config,
'extensions_preferences': {},
}
await self.execute_async(sqlalchemy.insert(pipeline.LegacyPipeline).values(pipeline_data))
async def write_space_model_providers(self):
space_models_gateway_api_url = self.ap.instance_config.data.get('space', {}).get(
'models_gateway_api_url', 'https://api.langbot.cloud/v1'

View File

@@ -74,6 +74,10 @@ stages:
type: integer
required: true
default: 10
show_if:
field: __system.is_wizard
operator: neq
value: true
- name: prompt
label:
en_US: Prompt
@@ -83,6 +87,9 @@ stages:
zh_Hans: 除非您了解消息结构,否则请只使用 system 单提示词
type: prompt-editor
required: true
default:
- role: system
content: "You are a helpful assistant."
- name: knowledge-bases
label:
en_US: Knowledge Bases
@@ -93,6 +100,10 @@ stages:
type: knowledge-base-multi-selector
required: false
default: []
show_if:
field: __system.is_wizard
operator: neq
value: true
- name: tbox-app-api
label:
en_US: Tbox App API
@@ -107,12 +118,14 @@ stages:
zh_Hans: API 密钥
type: string
required: true
default: ''
- name: app-id
label:
en_US: App ID
zh_Hans: 应用 ID
type: string
required: true
default: ''
- name: dify-service-api
label:
en_US: Dify Service API
@@ -127,6 +140,7 @@ stages:
zh_Hans: 基础 URL
type: string
required: true
default: 'https://api.dify.ai/v1'
- name: base-prompt
label:
en_US: Base PROMPT
@@ -163,6 +177,7 @@ stages:
zh_Hans: API 密钥
type: string
required: true
default: 'your-api-key'
- name: dashscope-app-api
label:
en_US: Aliyun Dashscope App API
@@ -193,12 +208,14 @@ stages:
zh_Hans: API 密钥
type: string
required: true
default: 'your-api-key'
- name: app-id
label:
en_US: App ID
zh_Hans: 应用 ID
type: string
required: true
default: 'your-app-id'
- name: references_quote
label:
en_US: References Quote
@@ -226,6 +243,7 @@ stages:
zh_Hans: n8n 工作流的 webhook URL
type: string
required: true
default: 'http://your-n8n-webhook-url'
- name: auth-type
label:
en_US: Authentication Type
@@ -263,6 +281,10 @@ stages:
type: string
required: false
default: ''
show_if:
field: auth-type
operator: eq
value: 'basic'
- name: basic-password
label:
en_US: Password
@@ -273,6 +295,10 @@ stages:
type: string
required: false
default: ''
show_if:
field: auth-type
operator: eq
value: 'basic'
- name: jwt-secret
label:
en_US: Secret
@@ -283,6 +309,10 @@ stages:
type: string
required: false
default: ''
show_if:
field: auth-type
operator: eq
value: 'jwt'
- name: jwt-algorithm
label:
en_US: Algorithm
@@ -293,6 +323,10 @@ stages:
type: string
required: false
default: 'HS256'
show_if:
field: auth-type
operator: eq
value: 'jwt'
- name: header-name
label:
en_US: Header Name
@@ -303,6 +337,10 @@ stages:
type: string
required: false
default: ''
show_if:
field: auth-type
operator: eq
value: 'header'
- name: header-value
label:
en_US: Header Value
@@ -313,6 +351,10 @@ stages:
type: string
required: false
default: ''
show_if:
field: auth-type
operator: eq
value: 'header'
- name: timeout
label:
en_US: Timeout
@@ -350,6 +392,7 @@ stages:
zh_Hans: Langflow 服务器的基础 URL
type: string
required: true
default: 'http://localhost:7860'
- name: api-key
label:
en_US: API Key
@@ -359,6 +402,7 @@ stages:
zh_Hans: Langflow 服务器的 API 密钥
type: string
required: true
default: 'your-api-key'
- name: flow-id
label:
en_US: Flow ID
@@ -368,6 +412,7 @@ stages:
zh_Hans: 要运行的流程 ID
type: string
required: true
default: 'your-flow-id'
- name: input-type
label:
en_US: Input Type
@@ -415,6 +460,7 @@ stages:
zh_Hans: Coze服务器的 API 密钥
type: string
required: true
default: ''
- name: bot-id
label:
en_US: Bot ID
@@ -424,6 +470,7 @@ stages:
zh_Hans: 要运行的机器人 ID
type: string
required: true
default: ''
- name: api-base
label:
en_US: API Base URL

View File

@@ -46,8 +46,12 @@ function SpaceOAuthCallbackContent() {
}
setStatus('success');
toast.success(t('common.spaceLoginSuccess'));
// If wizard state exists, redirect back to wizard instead of home
const wizardState = localStorage.getItem('langbot_wizard_state');
const redirectTo = wizardState ? '/wizard' : '/home';
setTimeout(() => {
router.push('/home');
router.push(redirectTo);
}, 1000);
} catch (err) {
setStatus('error');

View File

@@ -19,11 +19,17 @@ const LEVEL_STYLES: Record<string, string> = {
const SHORT_TEXT_LIMIT = 120;
export function BotLogCard({ botLog }: { botLog: BotLog }) {
export function BotLogCard({
botLog,
defaultExpanded = false,
}: {
botLog: BotLog;
defaultExpanded?: boolean;
}) {
const { t } = useTranslation();
const baseURL = httpClient.getBaseUrl();
const [copied, setCopied] = useState(false);
const [expanded, setExpanded] = useState(false);
const [expanded, setExpanded] = useState(defaultExpanded);
function copySessionId() {
const text = botLog.message_session_id;

View File

@@ -17,7 +17,20 @@ import { debounce } from 'lodash';
import { useTranslation } from 'react-i18next';
import { useRouter } from 'next/navigation';
export function BotLogListComponent({ botId }: { botId: string }) {
export function BotLogListComponent({
botId,
autoExpandImages = false,
hideDetailedLogsLink = false,
hideToolbar = false,
}: {
botId: string;
/** When true, log entries with images are rendered expanded by default */
autoExpandImages?: boolean;
/** When true, hides the "View Detailed Logs" navigation button */
hideDetailedLogsLink?: boolean;
/** When true, hides the entire toolbar (auto-refresh, level filter, detailed logs link) */
hideToolbar?: boolean;
}) {
const { t } = useTranslation();
const router = useRouter();
const manager = useRef(new BotLogManager(botId)).current;
@@ -158,77 +171,91 @@ export function BotLogListComponent({ botId }: { botId: string }) {
ref={listContainerRef}
>
{/* Toolbar */}
<div className="flex items-center gap-3 pb-3 shrink-0 flex-wrap">
{/* Auto-refresh toggle */}
<div className="flex items-center gap-1.5">
<span className="text-sm text-muted-foreground">
{t('bots.enableAutoRefresh')}
</span>
<Switch
checked={autoFlush}
onCheckedChange={(v) => setAutoFlush(v)}
/>
</div>
{!hideToolbar && (
<div className="flex items-center gap-3 pb-3 shrink-0 flex-wrap">
{/* Auto-refresh toggle */}
<div className="flex items-center gap-1.5">
<span className="text-sm text-muted-foreground">
{t('bots.enableAutoRefresh')}
</span>
<Switch
checked={autoFlush}
onCheckedChange={(v) => setAutoFlush(v)}
/>
</div>
{/* Level filter */}
<div className="flex items-center gap-1.5">
<span className="text-sm text-muted-foreground">
{t('bots.logLevel')}
</span>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
className="w-[160px] justify-between"
>
<span className="text-sm truncate">{getDisplayText()}</span>
<ChevronDownIcon className="size-3.5 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[160px] p-2">
<div className="flex flex-col gap-2">
{logLevels.map((level) => (
<div
key={level.value}
className="flex items-center space-x-2"
>
<Checkbox
id={level.value}
checked={selectedLevels.includes(level.value)}
onCheckedChange={() => handleLevelToggle(level.value)}
/>
<label
htmlFor={level.value}
className="text-sm font-medium leading-none cursor-pointer"
{/* Level filter */}
<div className="flex items-center gap-1.5">
<span className="text-sm text-muted-foreground">
{t('bots.logLevel')}
</span>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
className="w-[160px] justify-between"
>
<span className="text-sm truncate">{getDisplayText()}</span>
<ChevronDownIcon className="size-3.5 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[160px] p-2">
<div className="flex flex-col gap-2">
{logLevels.map((level) => (
<div
key={level.value}
className="flex items-center space-x-2"
>
{level.label}
</label>
</div>
))}
</div>
</PopoverContent>
</Popover>
</div>
<Checkbox
id={level.value}
checked={selectedLevels.includes(level.value)}
onCheckedChange={() => handleLevelToggle(level.value)}
/>
<label
htmlFor={level.value}
className="text-sm font-medium leading-none cursor-pointer"
>
{level.label}
</label>
</div>
))}
</div>
</PopoverContent>
</Popover>
</div>
{/* Link to detailed logs */}
<Button
variant="outline"
size="sm"
className="gap-1"
onClick={() => router.push(`/home/monitoring?botId=${botId}`)}
>
<ExternalLink className="size-3.5" />
<span className="text-sm">{t('bots.viewDetailedLogs')}</span>
</Button>
</div>
{/* Link to detailed logs */}
{!hideDetailedLogsLink && (
<Button
variant="outline"
size="sm"
className="gap-1"
onClick={() => router.push(`/home/monitoring?botId=${botId}`)}
>
<ExternalLink className="size-3.5" />
<span className="text-sm">{t('bots.viewDetailedLogs')}</span>
</Button>
)}
</div>
)}
{/* Log cards */}
<div className="flex flex-col gap-2">
{filteredLogs.map((botLog) => (
<BotLogCard botLog={botLog} key={botLog.seq_id} />
))}
</div>
{filteredLogs.length === 0 ? (
<div className="flex-1 flex items-center justify-center py-12">
<p className="text-sm text-muted-foreground">{t('bots.noLogs')}</p>
</div>
) : (
<div className="flex flex-col gap-2">
{filteredLogs.map((botLog) => (
<BotLogCard
botLog={botLog}
key={botLog.seq_id}
defaultExpanded={autoExpandImages && botLog.images.length > 0}
/>
))}
</div>
)}
</div>
);
}

View File

@@ -16,6 +16,30 @@ import { extractI18nObject } from '@/i18n/I18nProvider';
import { useTranslation } from 'react-i18next';
import { cn } from '@/lib/utils';
/**
* Resolve the value referenced by a `show_if.field` string.
*
* Fields prefixed with `__system.` are looked up in the caller-supplied
* `systemContext` dictionary (e.g. `__system.is_wizard` → `systemContext.is_wizard`).
* All other field names are resolved from the live form values first, then
* fall back to `externalDependentValues`.
*/
function resolveShowIfValue(
field: string,
watchedValues: Record<string, unknown>,
externalDependentValues?: Record<string, unknown>,
systemContext?: Record<string, unknown>,
): unknown {
if (field.startsWith('__system.')) {
const key = field.slice('__system.'.length);
return systemContext?.[key];
}
if (watchedValues[field] !== undefined) {
return watchedValues[field];
}
return externalDependentValues?.[field];
}
export default function DynamicFormComponent({
itemConfigList,
onSubmit,
@@ -23,6 +47,7 @@ export default function DynamicFormComponent({
onFileUploaded,
isEditing,
externalDependentValues,
systemContext,
}: {
itemConfigList: IDynamicFormItemSchema[];
onSubmit?: (val: object) => unknown;
@@ -30,6 +55,9 @@ export default function DynamicFormComponent({
onFileUploaded?: (fileKey: string) => void;
isEditing?: boolean;
externalDependentValues?: Record<string, unknown>;
/** Extra variables accessible via the `__system.*` namespace in show_if conditions.
* e.g. `{ is_wizard: true }` makes `show_if: { field: "__system.is_wizard", ... }` work. */
systemContext?: Record<string, unknown>;
}) {
const isInitialMount = useRef(true);
const previousInitialValues = useRef(initialValues);
@@ -61,6 +89,13 @@ export default function DynamicFormComponent({
fallbacks: [],
};
}
if (item.type === 'prompt-editor') {
if (Array.isArray(value)) {
return value;
}
// Default to a single empty system prompt entry
return [{ role: 'system', content: '' }];
}
return value;
};
@@ -241,14 +276,12 @@ export default function DynamicFormComponent({
<div className="space-y-4">
{itemConfigList.map((config) => {
if (config.show_if) {
const dependValue =
watchedValues[
config.show_if.field as keyof typeof watchedValues
] !== undefined
? watchedValues[
config.show_if.field as keyof typeof watchedValues
]
: externalDependentValues?.[config.show_if.field];
const dependValue = resolveShowIfValue(
config.show_if.field,
watchedValues as Record<string, unknown>,
externalDependentValues,
systemContext,
);
if (
config.show_if.operator === 'eq' &&

View File

@@ -775,10 +775,18 @@ export default function DynamicFormItemComponent({
</Select>
);
case DynamicFormItemType.PROMPT_EDITOR:
case DynamicFormItemType.PROMPT_EDITOR: {
// Guard: field.value may be undefined when the form resets or
// initialValues haven't propagated yet. Fall back to a default
// single system-prompt entry to prevent the .map() crash.
const promptItems: { role: string; content: string }[] = Array.isArray(
field.value,
)
? field.value
: [{ role: 'system', content: '' }];
return (
<div className="space-y-2">
{field.value.map(
{promptItems.map(
(item: { role: string; content: string }, index: number) => (
<div key={index} className="flex gap-2 items-center">
{/* 角色选择 */}
@@ -790,7 +798,7 @@ export default function DynamicFormItemComponent({
<Select
value={item.role}
onValueChange={(value) => {
const newValue = [...field.value];
const newValue = [...(field.value ?? promptItems)];
newValue[index] = { ...newValue[index], role: value };
field.onChange(newValue);
}}
@@ -811,7 +819,7 @@ export default function DynamicFormItemComponent({
className="w-[300px]"
value={item.content}
onChange={(e) => {
const newValue = [...field.value];
const newValue = [...(field.value ?? promptItems)];
newValue[index] = {
...newValue[index],
content: e.target.value,
@@ -825,7 +833,7 @@ export default function DynamicFormItemComponent({
type="button"
className="p-2 hover:bg-gray-100 rounded"
onClick={() => {
const newValue = field.value.filter(
const newValue = (field.value ?? promptItems).filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(_: any, i: number) => i !== index,
);
@@ -849,13 +857,17 @@ export default function DynamicFormItemComponent({
type="button"
variant="outline"
onClick={() => {
field.onChange([...field.value, { role: 'user', content: '' }]);
field.onChange([
...(field.value ?? promptItems),
{ role: 'user', content: '' },
]);
}}
>
{t('common.addRound')}
</Button>
</div>
);
}
case DynamicFormItemType.FILE:
return (

View File

@@ -1181,8 +1181,11 @@ export default function HomeSidebar({
// Route already matches — just select without navigating (preserves ?id= query params)
selectChild(matchedChild);
} else {
// No match — redirect to default route
handleChildClick(sidebarConfigList[0]);
// No match — redirect to the first route under /home
const defaultChild =
sidebarConfigList.find((c) => c.route.startsWith('/home')) ??
sidebarConfigList[0];
handleChildClick(defaultChild);
}
}
@@ -1249,6 +1252,28 @@ export default function HomeSidebar({
{/* Navigation items grouped by section */}
<SidebarContent>
{/* Standalone items (e.g. Quick Start) — rendered before section groups */}
{sidebarConfigList
.filter((c) => c.section === 'standalone')
.map((config) => (
<SidebarGroup key={config.id}>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton
isActive={selectedChild?.id === config.id}
onClick={() => handleChildClick(config)}
tooltip={config.name}
>
{config.icon}
<span>{config.name}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
))}
<SidebarGroup>
<SidebarGroupLabel>{t('sidebar.home')}</SidebarGroupLabel>
<SidebarGroupContent>

View File

@@ -1,6 +1,6 @@
import { I18nObject } from '@/app/infra/entities/common';
export type SidebarSection = 'home' | 'extensions';
export type SidebarSection = 'home' | 'extensions' | 'standalone';
export interface ISidebarChildVO {
id: string;

View File

@@ -6,6 +6,28 @@ const t = (key: string) => {
};
export const sidebarConfigList = [
// ── Quick Start ──
new SidebarChildVO({
id: 'wizard',
name: t('sidebar.quickStart'),
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M13 9H21L11 24V15H4L13 0V9ZM11 11V7.22063L7.53238 13H13V17.3944L17.263 11H11Z"></path>
</svg>
),
route: '/wizard',
description: t('wizard.sidebarDescription'),
helpLink: {
en_US: '',
zh_Hans: '',
},
section: 'standalone',
}),
// ── Home section ──
new SidebarChildVO({
id: 'monitoring',

1086
web/src/app/wizard/page.tsx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@ const enUS = {
installedPlugins: 'Installed Plugins',
pluginMarket: 'Marketplace',
mcpServers: 'MCP Servers',
quickStart: 'Quick Start',
},
common: {
login: 'Login',
@@ -321,6 +322,7 @@ const enUS = {
viewDetails: 'Details',
collapse: 'Collapse',
imagesAttached: 'image(s) attached',
noLogs: 'No logs yet',
sessionMonitor: {
title: 'Sessions',
sessions: 'Sessions',
@@ -1109,6 +1111,68 @@ const enUS = {
maxExtensionsReached:
'Maximum number of extensions ({{max}}) reached. Please remove an existing MCP server or plugin before adding a new one.',
},
wizard: {
sidebarDescription: 'Create a bot with guided steps',
loading: 'Loading wizard...',
loadError: 'Failed to load wizard data',
skip: 'Skip',
skipConfirmMessage:
'You can re-enter the Quick Start wizard from the sidebar later, or create a bot manually.',
skipConfirmOk: 'OK',
prev: 'Previous',
next: 'Next',
finish: 'Create & Deploy',
confirmCreateBot: 'Confirm, Create Bot',
createSuccess: 'Pipeline created and linked to bot successfully!',
botCreateSuccess: 'Bot created successfully!',
botSaveSuccess: 'Bot configuration saved and enabled!',
createError: 'Failed to create resources',
spaceAuthError: 'Failed to initiate Space authorization',
step: {
platform: 'Platform',
botConfig: 'Bot Setup',
aiEngine: 'AI Engine',
done: 'Done',
},
platform: {
title: 'Select a Platform',
description: 'Choose the messaging platform your bot will connect to.',
},
botConfig: {
title: 'Configure Your Bot',
description: 'Set up your bot and verify it works before continuing.',
saveBot: 'Save & Enable Bot',
resaveBot: 'Re-save Configuration',
botSaved:
'Bot configuration saved and enabled. Check the logs to verify the connection.',
logsTitle: 'Bot Logs',
logsDescription:
'Monitor bot activity to verify the platform connection is working.',
},
aiEngine: {
title: 'Select an AI Engine',
description:
"Choose the AI engine that will power your bot's intelligence.",
},
spaceBanner: {
message:
'Connect to LangBot Space for free trial model credits and zero-config instant setup!',
action: 'Authorize with Space',
},
config: {
botInfo: 'Bot Information',
botNamePlaceholder: 'Enter bot name',
botDescPlaceholder: 'Enter bot description (optional)',
platformConfig: '{{platform}} Configuration',
aiConfig: '{{engine}} Configuration',
},
done: {
title: 'All Set!',
description:
'Your bot has been created and connected to the AI pipeline. You can now manage it from the workbench.',
backToWorkbench: 'Back to Workbench',
},
},
};
export default enUS;

View File

@@ -5,6 +5,7 @@
installedPlugins: 'インストール済みプラグイン',
pluginMarket: 'プラグインマーケット',
mcpServers: 'MCPサーバー',
quickStart: 'クイックスタート',
},
common: {
login: 'ログイン',
@@ -322,6 +323,7 @@
allLevels: 'すべてのレベル',
selectLevel: 'レベルを選択',
levelsSelected: 'レベル選択済み',
noLogs: 'ログはまだありません',
sessionMonitor: {
title: 'セッション監視',
sessions: 'セッション一覧',
@@ -1083,6 +1085,70 @@
maxExtensionsReached:
'拡張機能数が上限({{max}}個)に達しました。新しい MCP サーバーやプラグインを追加するには、既存のものを削除してください。',
},
wizard: {
sidebarDescription: 'ガイド付きステップでボットを作成',
loading: 'ウィザードを読み込み中...',
loadError: 'ウィザードデータの読み込みに失敗しました',
skip: 'スキップ',
skipConfirmMessage:
'サイドバーからクイックスタートウィザードに再度アクセスするか、手動でボットを作成できます。',
skipConfirmOk: 'OK',
prev: '前へ',
next: '次へ',
finish: '作成&デプロイ',
confirmCreateBot: '確定、ボットを作成',
createSuccess: 'パイプラインが作成され、ボットにリンクされました!',
botCreateSuccess: 'ボットが正常に作成されました!',
botSaveSuccess: 'ボット設定が保存され、有効になりました!',
createError: 'リソースの作成に失敗しました',
spaceAuthError: 'Space 認証の開始に失敗しました',
step: {
platform: 'プラットフォーム',
botConfig: 'ボット設定',
aiEngine: 'AIエンジン',
done: '完了',
},
platform: {
title: 'プラットフォームを選択',
description:
'ボットが接続するメッセージングプラットフォームを選択してください。',
},
botConfig: {
title: 'ボットを設定',
description:
'ボットをセットアップし、正常に動作することを確認してから続行してください。',
saveBot: '保存して有効化',
resaveBot: '設定を再保存',
botSaved:
'ボット設定が保存され、有効になりました。ログを確認して接続を検証してください。',
logsTitle: 'ボットログ',
logsDescription:
'ボットの活動を監視して、プラットフォーム接続が正常に動作していることを確認します。',
},
aiEngine: {
title: 'AIエンジンを選択',
description:
'ボットのインテリジェンスを駆動するAIエンジンを選択してください。',
},
spaceBanner: {
message:
'LangBot Spaceに接続して、無料トライアルモデルクレジットとゼロ設定の即時セットアップを入手',
action: 'Spaceで認証',
},
config: {
botInfo: 'ボット情報',
botNamePlaceholder: 'ボット名を入力',
botDescPlaceholder: 'ボットの説明を入力(任意)',
platformConfig: '{{platform}} 設定',
aiConfig: '{{engine}} 設定',
},
done: {
title: '完了しました!',
description:
'ボットが作成され、AIパイプラインに接続されました。ワークベンチから管理できます。',
backToWorkbench: 'ワークベンチに戻る',
},
},
};
export default jaJP;

View File

@@ -5,6 +5,7 @@ const zhHans = {
installedPlugins: '已安装插件',
pluginMarket: '插件市场',
mcpServers: 'MCP 服务器',
quickStart: '快速开始',
},
common: {
login: '登录',
@@ -306,6 +307,7 @@ const zhHans = {
viewDetails: '详情',
collapse: '收起',
imagesAttached: '张图片',
noLogs: '暂无日志',
sessionMonitor: {
title: '会话监控',
sessions: '会话列表',
@@ -1056,6 +1058,64 @@ const zhHans = {
maxExtensionsReached:
'已达到扩展数量上限({{max}}个)。请先删除已有的 MCP 服务器或插件后再添加新的。',
},
wizard: {
sidebarDescription: '通过引导步骤创建机器人',
loading: '正在加载向导...',
loadError: '加载向导数据失败',
skip: '跳过',
skipConfirmMessage:
'您之后可以在侧边栏重新进入快速开始向导,或手动创建机器人。',
skipConfirmOk: '确定',
prev: '上一步',
next: '下一步',
finish: '创建并部署',
confirmCreateBot: '确定,创建机器人',
createSuccess: '流水线已创建并关联到机器人!',
botCreateSuccess: '机器人创建成功!',
botSaveSuccess: '机器人配置已保存并启用!',
createError: '创建资源失败',
spaceAuthError: '无法发起 Space 授权',
step: {
platform: '平台接入',
botConfig: '机器人配置',
aiEngine: 'AI 引擎',
done: '完成',
},
platform: {
title: '选择平台',
description: '选择机器人要接入的消息平台。',
},
botConfig: {
title: '配置机器人',
description: '配置好机器人并确认其正常运行后再继续。',
saveBot: '保存并启用',
resaveBot: '重新保存配置',
botSaved: '机器人配置已保存并启用,请查看日志确认连接正常。',
logsTitle: '机器人日志',
logsDescription: '监控机器人活动,确认平台连接是否正常工作。',
},
aiEngine: {
title: '选择 AI 引擎',
description: '选择驱动机器人智能的 AI 引擎。',
},
spaceBanner: {
message: '接入 LangBot Space获取免费试用模型额度零配置极速开箱',
action: '前往授权登录',
},
config: {
botInfo: '机器人信息',
botNamePlaceholder: '请输入机器人名称',
botDescPlaceholder: '请输入机器人描述(可选)',
platformConfig: '{{platform}} 配置',
aiConfig: '{{engine}} 配置',
},
done: {
title: '一切就绪!',
description:
'机器人已创建并连接到 AI 流水线。你现在可以在工作台中管理它。',
backToWorkbench: '返回工作台',
},
},
};
export default zhHans;

View File

@@ -5,6 +5,7 @@ const zhHant = {
installedPlugins: '已安裝外掛',
pluginMarket: '外掛市場',
mcpServers: 'MCP 伺服器',
quickStart: '快速開始',
},
common: {
login: '登入',
@@ -301,6 +302,7 @@ const zhHant = {
allLevels: '全部級別',
selectLevel: '選擇級別',
levelsSelected: '個級別已選',
noLogs: '暫無日誌',
sessionMonitor: {
title: '會話監控',
sessions: '會話列表',
@@ -1023,6 +1025,64 @@ const zhHant = {
maxExtensionsReached:
'已達到擴充功能數量上限({{max}}個)。請先刪除已有的 MCP 伺服器或外掛後再新增。',
},
wizard: {
sidebarDescription: '透過引導步驟建立機器人',
loading: '正在載入嚮導...',
loadError: '載入嚮導資料失敗',
skip: '跳過',
skipConfirmMessage:
'您之後可以在側邊欄重新進入快速開始嚮導,或手動建立機器人。',
skipConfirmOk: '確定',
prev: '上一步',
next: '下一步',
finish: '建立並部署',
confirmCreateBot: '確定,建立機器人',
createSuccess: '流水線已建立並關聯到機器人!',
botCreateSuccess: '機器人建立成功!',
botSaveSuccess: '機器人配置已儲存並啟用!',
createError: '建立資源失敗',
spaceAuthError: '無法發起 Space 授權',
step: {
platform: '平台接入',
botConfig: '機器人配置',
aiEngine: 'AI 引擎',
done: '完成',
},
platform: {
title: '選擇平台',
description: '選擇機器人要接入的訊息平台。',
},
botConfig: {
title: '配置機器人',
description: '配置好機器人並確認其正常運作後再繼續。',
saveBot: '儲存並啟用',
resaveBot: '重新儲存配置',
botSaved: '機器人配置已儲存並啟用,請查看日誌確認連接正常。',
logsTitle: '機器人日誌',
logsDescription: '監控機器人活動,確認平台連接是否正常運作。',
},
aiEngine: {
title: '選擇 AI 引擎',
description: '選擇驅動機器人智慧的 AI 引擎。',
},
spaceBanner: {
message: '接入 LangBot Space取得免費試用模型額度零配置極速開箱',
action: '前往授權登入',
},
config: {
botInfo: '機器人資訊',
botNamePlaceholder: '請輸入機器人名稱',
botDescPlaceholder: '請輸入機器人描述(可選)',
platformConfig: '{{platform}} 配置',
aiConfig: '{{engine}} 配置',
},
done: {
title: '一切就緒!',
description:
'機器人已建立並連接到 AI 流水線。你現在可以在工作台中管理它。',
backToWorkbench: '返回工作台',
},
},
};
export default zhHant;