feat: simplify resource setup and detail guides

This commit is contained in:
fdc310
2026-09-19 02:39:35 +08:00
parent 960e322c79
commit cc8a0ec847
27 changed files with 1237 additions and 840 deletions
@@ -115,7 +115,6 @@ export default function AgentDetailContent({ id }: { id: string }) {
if (isCreateMode) {
return (
<AgentCreateContent
guideEnabled={canManage}
onCreated={(newAgentId) => {
refreshPipelines();
navigate(`/home/agents?id=${encodeURIComponent(newAgentId)}`);
@@ -27,16 +27,11 @@ import {
import { Input } from '@/components/ui/input';
import EmojiPicker from '@/components/ui/emoji-picker';
import ProcessorTypeDiagram from './ProcessorTypeDiagram';
import GuidedTour, {
GuidedTourStep,
} from '@/app/home/components/guided-tour/GuidedTour';
export default function AgentCreateContent({
onCreated,
guideEnabled = true,
}: {
onCreated: (agentId: string) => void;
guideEnabled?: boolean;
}) {
const { t } = useTranslation();
const [kind, setKind] = useState<AgentKind>('agent');
@@ -114,29 +109,6 @@ export default function AgentCreateContent({
description: t('agents.eventProcessor.description'),
},
];
const guideSteps: GuidedTourStep[] = [
{
id: 'type',
target: '[data-guide="processor-type"]',
title: t('guidedTour.processorCreate.type.title'),
description: t('guidedTour.processorCreate.type.description'),
},
{
id: 'basic',
target: '[data-guide="processor-basic"]',
title: t('guidedTour.processorCreate.basic.title'),
description: t('guidedTour.processorCreate.basic.description'),
complete: Boolean(form.watch('name')?.trim()),
requirement: t('guidedTour.processorCreate.basic.requirement'),
},
{
id: 'submit',
target: '[data-guide="processor-submit"]',
title: t('guidedTour.processorCreate.submit.title'),
description: t('guidedTour.processorCreate.submit.description'),
},
];
return (
<div className="flex h-full flex-col">
<div className="flex items-center justify-between pb-4 shrink-0">
@@ -147,7 +119,6 @@ export default function AgentCreateContent({
type="submit"
form="agent-create-form"
disabled={form.formState.isSubmitting}
data-guide="processor-submit"
>
{t('common.submit')}
</Button>
@@ -160,7 +131,6 @@ export default function AgentCreateContent({
<section
aria-labelledby="processor-kind-heading"
className="space-y-3"
data-guide="processor-type"
>
<div>
<h2
@@ -211,7 +181,7 @@ export default function AgentCreateContent({
</ToggleGroup>
</section>
<Card data-guide="processor-basic">
<Card>
<CardHeader>
<CardTitle>{t('agents.basicInfo')}</CardTitle>
<CardDescription>
@@ -287,12 +257,6 @@ export default function AgentCreateContent({
</div>
</div>
</div>
<GuidedTour
enabled={guideEnabled}
storageKey="langbot_processor_create_guide_v1"
steps={guideSteps}
testId="processor-create-guide"
/>
</div>
);
}
@@ -48,7 +48,6 @@ import RunnerSelect from './RunnerSelect';
import GuidedTour, {
GuidedTourStep,
} from '@/app/home/components/guided-tour/GuidedTour';
import { areRequiredDynamicFieldsComplete } from '@/app/home/components/guided-tour/dynamic-form-progress';
import AgentApiToolPicker from './AgentApiToolPicker';
const OTHER_TOOL_SCOPES = [
@@ -482,8 +481,6 @@ function AgentFormComponent(
target: '[data-guide="runner-selector"]',
title: t('guidedTour.runner.select.title'),
description: t('guidedTour.runner.select.description'),
complete: Boolean(currentRunner && selectedRunnerOption),
requirement: t('guidedTour.runner.select.requirement'),
action: {
href: 'https://space.langbot.app/market?type=plugin&component=Runner&runner_usage=agent',
label: t('guidedTour.runner.select.action'),
@@ -497,11 +494,6 @@ function AgentFormComponent(
target: '[data-guide="runner-parameters"]',
title: t('guidedTour.runner.parameters.title'),
description: t('guidedTour.runner.parameters.description'),
complete: areRequiredDynamicFieldsComplete(
activeRunnerStage.config,
activeRunnerValues,
),
requirement: t('guidedTour.runner.parameters.requirement'),
});
}
@@ -510,18 +502,9 @@ function AgentFormComponent(
target: '[data-guide="agent-sections"]',
title: t('guidedTour.runner.events.title'),
description: t('guidedTour.runner.events.description'),
complete: activeSection === 'events_and_tools',
requirement: t('guidedTour.runner.events.requirement'),
});
return steps;
}, [
activeRunnerStage,
activeRunnerValues,
activeSection,
currentRunner,
selectedRunnerOption,
t,
]);
}, [activeRunnerStage, t]);
useEffect(() => {
onRunnerStatusChange?.(runnerStatus);
@@ -348,7 +348,7 @@ export default function RunnerSelect({
<SelectValue placeholder={t('common.select')} />
)}
</SelectTrigger>
<SelectContent className="max-h-72 w-[var(--radix-select-trigger-width)] max-w-[calc(100vw-2rem)]">
<SelectContent className="z-[70] max-h-72 w-[var(--radix-select-trigger-width)] max-w-[calc(100vw-2rem)]">
<SelectGroup>
<SelectLabel className="px-2 py-1 text-[11px] font-medium">
<span className="inline-flex items-center gap-1.5">
+5 -2
View File
@@ -162,7 +162,7 @@ export default function BotDetailContent({ id }: { id: string }) {
{/* Header */}
<div className="flex items-center justify-between pb-4 shrink-0">
<h1 className="text-xl font-semibold">{t('bots.createBot')}</h1>
{canManage && (
{canManage && adapterLabel && (
<Button type="submit" form="bot-form" data-guide="bot-submit">
{t('common.submit')}
</Button>
@@ -171,13 +171,14 @@ export default function BotDetailContent({ id }: { id: string }) {
{/* Content */}
<div className="min-h-0 min-w-0 flex-1 overflow-y-auto overflow-x-hidden">
<div className="mx-auto w-full min-w-0 max-w-3xl pb-8">
<div className="mx-auto w-full min-w-0 max-w-7xl pb-8">
<fieldset className="contents" disabled={!canManage}>
<BotForm
initBotId={undefined}
onFormSubmit={handleFormSubmit}
onNewBotCreated={handleNewBotCreated}
guideEnabled={canManage}
onAdapterLabelChange={setAdapterLabel}
/>
</fieldset>
</div>
@@ -236,6 +237,7 @@ export default function BotDetailContent({ id }: { id: string }) {
form="bot-form"
disabled={!formDirty}
className={activeTab !== 'config' ? 'invisible' : ''}
data-guide="bot-config-save"
>
{t('common.save')}
</Button>
@@ -318,6 +320,7 @@ export default function BotDetailContent({ id }: { id: string }) {
onNewBotCreated={handleNewBotCreated}
onDirtyChange={setFormDirty}
onAdapterLabelChange={setAdapterLabel}
guideEnabled={canManage}
/>
</fieldset>
</div>
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
import { createPortal } from 'react-dom';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Check, ChevronRight, ExternalLink, LockKeyhole } from 'lucide-react';
import { Check, ChevronRight, ExternalLink, X } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button';
@@ -24,9 +24,6 @@ export interface GuidedTourStep {
target: string;
title: string;
description: string;
complete?: boolean;
advanceOnComplete?: boolean;
requirement?: string;
action?: {
href: string;
label: string;
@@ -78,14 +75,9 @@ export default function GuidedTour({
useState<PopoverPosition | null>(null);
const popoverRef = useRef<HTMLDivElement | null>(null);
const previousStorageKeyRef = useRef(storageKey);
const previousCompletionRef = useRef<{
stepId: string;
complete: boolean;
} | null>(null);
const activeIndex = steps.findIndex((step) => step.id === activeStepId);
const activeStep = activeIndex >= 0 ? steps[activeIndex] : undefined;
const isComplete = activeStep?.complete !== false;
const isPopoverPositioned = popoverPosition !== null;
useEffect(() => {
@@ -105,15 +97,7 @@ export default function GuidedTour({
}
const currentIndex = steps.findIndex((step) => step.id === activeStepId);
const firstIncompleteIndex = steps.findIndex(
(step) => step.complete === false,
);
const nextIndex =
currentIndex < 0
? 0
: firstIncompleteIndex >= 0 && firstIncompleteIndex < currentIndex
? firstIncompleteIndex
: currentIndex;
const nextIndex = currentIndex < 0 ? 0 : currentIndex;
if (nextIndex !== currentIndex) {
const nextStep = steps[nextIndex];
@@ -208,27 +192,33 @@ export default function GuidedTour({
if (isPopoverPositioned) measure();
}, [activeStep?.id, isPopoverPositioned, measure]);
const finishTour = useCallback(() => {
setTargetRect(null);
setPopoverPosition(null);
storeProgress(storageKey, 'completed');
setFinished(true);
setActiveStepId(null);
}, [storageKey]);
const handleNext = useCallback(() => {
if (!activeStep || !isComplete) return;
if (!activeStep) return;
const nextStep = steps[activeIndex + 1];
setTargetRect(null);
setPopoverPosition(null);
if (!nextStep) {
storeProgress(storageKey, 'completed');
setFinished(true);
setActiveStepId(null);
finishTour();
return;
}
storeProgress(storageKey, nextStep.id);
setActiveStepId(nextStep.id);
}, [activeIndex, activeStep, isComplete, steps, storageKey]);
}, [activeIndex, activeStep, finishTour, steps, storageKey]);
useEffect(() => {
const handleNativeClick = (event: MouseEvent) => {
const target = event.target;
if (!(target instanceof Element)) return;
const button = target.closest<HTMLButtonElement>(
'[data-guided-tour-action="next"]',
'[data-guided-tour-action]',
);
if (
!button ||
@@ -237,33 +227,18 @@ export default function GuidedTour({
) {
return;
}
handleNext();
if (button.dataset.guidedTourAction === 'skip') {
finishTour();
} else if (button.dataset.guidedTourAction === 'next') {
handleNext();
}
};
// Translation extensions can rewrite nodes inside the popover and detach
// React's delegated handler. Capture the command by its stable data marker.
document.addEventListener('click', handleNativeClick, true);
return () => document.removeEventListener('click', handleNativeClick, true);
}, [handleNext, testId]);
useEffect(() => {
if (!activeStep) return;
const previous = previousCompletionRef.current;
previousCompletionRef.current = {
stepId: activeStep.id,
complete: isComplete,
};
if (
!activeStep.advanceOnComplete ||
previous?.stepId !== activeStep.id ||
previous.complete ||
!isComplete
) {
return;
}
window.setTimeout(handleNext, 0);
}, [activeStep, handleNext, isComplete]);
}, [finishTour, handleNext, testId]);
if (
!enabled ||
@@ -282,7 +257,6 @@ export default function GuidedTour({
<div
data-testid={testId}
data-active-step={activeStep.id}
data-step-complete={String(isComplete)}
translate="no"
className="notranslate pointer-events-none"
>
@@ -301,19 +275,30 @@ export default function GuidedTour({
ref={popoverRef}
role="dialog"
aria-labelledby={titleId}
className="pointer-events-auto fixed z-[62] rounded-lg border bg-popover p-4 text-popover-foreground shadow-xl"
className="pointer-events-auto fixed z-[80] rounded-lg border bg-popover p-4 text-popover-foreground shadow-xl"
style={popoverPosition}
>
<div className="mb-3 flex items-center justify-between gap-3">
<div className="mb-3 flex items-center gap-2">
<span className="text-xs font-medium text-blue-600 dark:text-blue-400">
{t('guidedTour.label')}
</span>
<span className="text-xs tabular-nums text-muted-foreground">
<span className="ml-auto text-xs tabular-nums text-muted-foreground">
{t('guidedTour.progress', {
current: activeIndex + 1,
total: steps.length,
})}
</span>
<Button
type="button"
variant="ghost"
size="sm"
data-guided-tour-action="skip"
data-guided-tour-id={testId}
className="-my-2 -mr-2 h-7 gap-1 px-2 text-xs text-muted-foreground"
>
<X className="size-3.5" />
{t('guidedTour.skip')}
</Button>
</div>
<h2 id={titleId} className="text-base font-semibold">
{activeStep.title}
@@ -332,18 +317,11 @@ export default function GuidedTour({
<ExternalLink className="size-3.5" />
</a>
)}
{!isComplete && activeStep.requirement && (
<div className="mt-3 flex items-start gap-2 rounded-md bg-muted px-3 py-2 text-xs leading-5 text-muted-foreground">
<LockKeyhole className="mt-0.5 size-3.5 shrink-0" />
<span>{activeStep.requirement}</span>
</div>
)}
<Button
type="button"
data-guided-tour-action="next"
data-guided-tour-id={testId}
className="mt-4 w-full"
disabled={!isComplete}
>
{isLastStep ? (
<Check className="size-4" />
@@ -1,46 +0,0 @@
import {
IDynamicFormItemSchema,
SYSTEM_FIELD_PREFIX,
} from '@/app/infra/entities/form/dynamic';
function isVisible(
item: IDynamicFormItemSchema,
values: Record<string, unknown>,
externalValues: Record<string, unknown> = {},
) {
if (!item.show_if || item.show_if.field.startsWith(SYSTEM_FIELD_PREFIX)) {
return true;
}
const actual =
values[item.show_if.field] ?? externalValues[item.show_if.field];
if (item.show_if.operator === 'eq') return actual === item.show_if.value;
if (item.show_if.operator === 'neq') return actual !== item.show_if.value;
return (
Array.isArray(item.show_if.value) && item.show_if.value.includes(actual)
);
}
function hasValue(value: unknown) {
if (value === null || value === undefined) return false;
if (typeof value === 'string') return value.trim().length > 0;
if (Array.isArray(value)) return value.length > 0;
if (typeof value === 'object' && 'primary' in value) {
return String((value as { primary?: unknown }).primary ?? '').trim() !== '';
}
return true;
}
export function areRequiredDynamicFieldsComplete(
items: IDynamicFormItemSchema[],
values: Record<string, unknown>,
externalValues?: Record<string, unknown>,
) {
return items
.filter(
(item) =>
item.required &&
!item.name.startsWith(SYSTEM_FIELD_PREFIX) &&
isVisible(item, values, externalValues),
)
.every((item) => hasValue(values[item.name]));
}
+22 -13
View File
@@ -164,7 +164,7 @@ export default function KBDetailContent({ id }: { id: string }) {
</h1>
{canManage && (
<Button type="submit" form="kb-form" data-guide="knowledge-submit">
{t('common.submit')}
{t('common.save')}
</Button>
)}
</div>
@@ -215,6 +215,7 @@ export default function KBDetailContent({ id }: { id: string }) {
form="kb-form"
disabled={!formDirty}
className={activeTab !== 'metadata' ? 'invisible' : ''}
data-guide="knowledge-config-save"
>
{t('common.save')}
</Button>
@@ -233,16 +234,18 @@ export default function KBDetailContent({ id }: { id: string }) {
<FileText className="size-3.5" />
{t('knowledge.metadata')}
</TabsTrigger>
{hasDocumentCapability() && (
{kbInfo.initialized !== false && hasDocumentCapability() && (
<TabsTrigger value="documents" className="gap-1.5">
<FolderOpen className="size-3.5" />
{t('knowledge.documents')}
</TabsTrigger>
)}
<TabsTrigger value="retrieve" className="gap-1.5">
<Search className="size-3.5" />
{t('knowledge.retrieve')}
</TabsTrigger>
{kbInfo.initialized !== false && (
<TabsTrigger value="retrieve" className="gap-1.5">
<Search className="size-3.5" />
{t('knowledge.retrieve')}
</TabsTrigger>
)}
</TabsList>
{/* Tab: Metadata */}
@@ -258,6 +261,7 @@ export default function KBDetailContent({ id }: { id: string }) {
onNewKbCreated={handleNewKbCreated}
onKbUpdated={handleKbUpdated}
onDirtyChange={setFormDirty}
guideEnabled={canManage}
/>
</fieldset>
@@ -299,7 +303,7 @@ export default function KBDetailContent({ id }: { id: string }) {
</TabsContent>
{/* Tab: Documents */}
{hasDocumentCapability() && (
{kbInfo.initialized !== false && hasDocumentCapability() && (
<TabsContent
value="documents"
className="flex-1 min-h-0 overflow-y-auto mt-4"
@@ -315,12 +319,17 @@ export default function KBDetailContent({ id }: { id: string }) {
)}
{/* Tab: Retrieve */}
<TabsContent
value="retrieve"
className="flex-1 min-h-0 overflow-y-auto mt-4"
>
<KBRetrieveGeneric kbId={id} retrieveFunction={retrieveFunction} />
</TabsContent>
{kbInfo.initialized !== false && (
<TabsContent
value="retrieve"
className="flex-1 min-h-0 overflow-y-auto mt-4"
>
<KBRetrieveGeneric
kbId={id}
retrieveFunction={retrieveFunction}
/>
</TabsContent>
)}
</Tabs>
</div>
@@ -40,7 +40,6 @@ import KnowledgeEngineSelect from './KnowledgeEngineSelect';
import GuidedTour, {
GuidedTourStep,
} from '@/app/home/components/guided-tour/GuidedTour';
import { areRequiredDynamicFieldsComplete } from '@/app/home/components/guided-tour/dynamic-form-progress';
const KNOWLEDGE_ENGINE_MARKETPLACE_URL =
'https://space.langbot.app/market?type=plugin&component=KnowledgeEngine';
@@ -104,6 +103,7 @@ export default function KBForm({
Record<string, unknown>
>({});
const [isEditing, setIsEditing] = useState(Boolean(initKbId));
const [engineInitialized, setEngineInitialized] = useState(true);
const [loadFailed, setLoadFailed] = useState(false);
const [loadAttempt, setLoadAttempt] = useState(0);
const [initialDataLoaded, setInitialDataLoaded] = useState(false);
@@ -220,11 +220,13 @@ export default function KBForm({
setConfigSettings(kb.creation_settings || {});
setRetrievalSettings(kb.retrieval_settings || {});
setEngineInitialized(kb.initialized !== false);
// Capture snapshot after a tick so dynamic forms have emitted initial values
setTimeout(() => {
captureSnapshot();
isInitializing.current = false;
onDirtyChange?.(kb.initialized === false);
}, 500);
} catch (err) {
isInitializing.current = false;
@@ -269,8 +271,8 @@ export default function KBForm({
}, []);
const onSubmit = async (data: z.infer<typeof formSchema>) => {
// Validate dynamic forms before submission
if (configValidateRef.current) {
// Engine parameters are configured only after the draft has been created.
if (initKbId && configValidateRef.current) {
const configValid = await configValidateRef.current();
if (!configValid) {
toast.error(t('knowledge.engineSettingsInvalid'));
@@ -278,7 +280,7 @@ export default function KBForm({
}
}
if (retrievalValidateRef.current) {
if (initKbId && retrievalValidateRef.current) {
const retrievalValid = await retrievalValidateRef.current();
if (!retrievalValid) {
toast.error(t('knowledge.retrievalSettingsInvalid'));
@@ -293,12 +295,16 @@ export default function KBForm({
knowledge_engine_plugin_id: selectedEngineId,
creation_settings: configSettings,
retrieval_settings: retrievalSettings,
...(initKbId
? { initialize_engine: !engineInitialized }
: { defer_initialization: true }),
};
if (initKbId) {
httpClient
.updateKnowledgeBase(initKbId, kbData)
.then((res) => {
setEngineInitialized(true);
captureSnapshot();
onDirtyChange?.(false);
onKbUpdated(res.uuid);
@@ -339,21 +345,11 @@ export default function KBForm({
const guideSteps = useMemo<GuidedTourStep[]>(() => {
const steps: GuidedTourStep[] = [
{
id: 'basic',
target: '[data-guide="knowledge-basic"]',
title: t('guidedTour.knowledge.basic.title'),
description: t('guidedTour.knowledge.basic.description'),
complete: Boolean(watchedFormValues.name?.trim()),
requirement: t('guidedTour.knowledge.basic.requirement'),
},
{
id: 'engine',
target: '[data-guide="knowledge-engine"]',
title: t('guidedTour.knowledge.engine.title'),
description: t('guidedTour.knowledge.engine.description'),
complete: Boolean(selectedEngineId),
requirement: t('guidedTour.knowledge.engine.requirement'),
action: {
href: KNOWLEDGE_ENGINE_MARKETPLACE_URL,
label: t('guidedTour.knowledge.engine.action'),
@@ -367,12 +363,6 @@ export default function KBForm({
target: '[data-guide="knowledge-engine-parameters"]',
title: t('guidedTour.knowledge.parameters.title'),
description: t('guidedTour.knowledge.parameters.description'),
complete: areRequiredDynamicFieldsComplete(
configFormItems,
configSettings,
retrievalSettings,
),
requirement: t('guidedTour.knowledge.parameters.requirement'),
});
}
@@ -382,31 +372,17 @@ export default function KBForm({
target: '[data-guide="knowledge-retrieval"]',
title: t('guidedTour.knowledge.retrieval.title'),
description: t('guidedTour.knowledge.retrieval.description'),
complete: areRequiredDynamicFieldsComplete(
retrievalFormItems,
retrievalSettings,
configSettings,
),
requirement: t('guidedTour.knowledge.retrieval.requirement'),
});
}
steps.push({
id: 'submit',
target: '[data-guide="knowledge-submit"]',
title: t('guidedTour.knowledge.submit.title'),
description: t('guidedTour.knowledge.submit.description'),
id: 'save',
target: '[data-guide="knowledge-config-save"]',
title: t('guidedTour.knowledge.save.title'),
description: t('guidedTour.knowledge.save.description'),
});
return steps;
}, [
configFormItems,
configSettings,
retrievalFormItems,
retrievalSettings,
selectedEngineId,
t,
watchedFormValues.name,
]);
}, [configFormItems, retrievalFormItems, t]);
if (loadFailed)
return (
@@ -529,7 +505,7 @@ export default function KBForm({
)}
/>
{configFormItems.length > 0 && (
{isEditing && configFormItems.length > 0 && (
<div
data-guide="knowledge-engine-parameters"
className="space-y-6"
@@ -541,7 +517,7 @@ export default function KBForm({
onSubmit={(val) =>
setConfigSettings(val as Record<string, unknown>)
}
isEditing={isEditing}
isEditing={engineInitialized}
externalDependentValues={retrievalSettings}
onValidate={(validateFn) =>
(configValidateRef.current = validateFn)
@@ -553,7 +529,7 @@ export default function KBForm({
</Card>
{/* Retrieval Settings (dynamic form from retrieval_schema) */}
{retrievalFormItems.length > 0 && (
{isEditing && retrievalFormItems.length > 0 && (
<Card data-guide="knowledge-retrieval">
<CardHeader>
<CardTitle>{t('knowledge.retrievalSettings')}</CardTitle>
@@ -578,10 +554,10 @@ export default function KBForm({
)}
</form>
<GuidedTour
enabled={!isEditing && guideEnabled}
storageKey="langbot_knowledge_create_guide_v1"
enabled={isEditing && guideEnabled}
storageKey="langbot_knowledge_detail_guide_v1"
steps={guideSteps}
testId="knowledge-create-guide"
testId="knowledge-detail-guide"
/>
</Form>
);
+3
View File
@@ -431,6 +431,9 @@ export interface KnowledgeBase {
knowledge_engine_plugin_id?: string;
creation_settings?: Record<string, unknown>;
retrieval_settings?: Record<string, unknown>;
initialized?: boolean;
defer_initialization?: boolean;
initialize_engine?: boolean;
knowledge_engine?: KnowledgeEngineInfo;
}
+17 -6
View File
@@ -52,6 +52,7 @@ const enUS = {
progress: '{{current}} of {{total}}',
next: 'Next',
finish: 'Finish',
skip: 'Skip',
bot: {
connection: {
title: 'Choose a connection method',
@@ -68,7 +69,7 @@ const enUS = {
adapter: {
title: 'Choose a platform adapter',
description:
'Choose the platform first. LangBot will then show whether this adapter supports Webhook, persistent connection, or both.',
'Choose the platform adapter for this bot. Connection methods and platform parameters are configured after creation.',
requirement: 'Select an adapter to continue.',
},
parameters: {
@@ -81,12 +82,17 @@ const enUS = {
routing: {
title: 'Route incoming events',
description:
'Choose which processor handles each event. You can add more routes after the bot is created.',
'Choose which processor handles each event received by this bot.',
},
save: {
title: 'Save the bot configuration',
description:
'Save after checking the connection parameters and event routing. You can then enable the bot when it is ready.',
},
submit: {
title: 'Create the bot',
description:
'Create the bot to apply the connection. Webhook URLs generated by LangBot are shown in the saved bot configuration.',
'Create a disabled bot, then continue with its connection method and platform parameters on the bot page.',
},
},
processorCreate: {
@@ -136,9 +142,9 @@ const enUS = {
requirement: 'Enter a knowledge base name to continue.',
},
engine: {
title: 'Choose or install an engine',
title: 'Review the knowledge engine',
description:
'Select an installed knowledge engine, or install one from the Marketplace section in this selector.',
'Confirm the engine used by this knowledge base. Its parameters and retrieval settings are configured below.',
requirement: 'Select a knowledge engine to continue.',
action: 'Browse Knowledge Engine Marketplace',
},
@@ -154,6 +160,11 @@ const enUS = {
'Set how this engine searches and returns relevant content to processors.',
requirement: 'Complete every visible required retrieval parameter.',
},
save: {
title: 'Save the knowledge base configuration',
description:
'Save after checking the engine parameters and retrieval settings.',
},
submit: {
title: 'Create the knowledge base',
description:
@@ -570,7 +581,7 @@ const enUS = {
getBotConfigError: 'Failed to get bot configuration: ',
saveSuccess: 'Saved successfully',
saveError: 'Save failed: ',
createSuccess: 'Created successfully. Please configure event routing',
createSuccess: 'Created successfully. Continue configuring the bot',
createError: 'Creation failed: ',
deleteSuccess: 'Deleted successfully',
deleteError: 'Delete failed: ',
+15 -8
View File
@@ -48,6 +48,7 @@ const zhHans = {
progress: '第 {{current}} 步,共 {{total}} 步',
next: '下一步',
finish: '完成引导',
skip: '跳过',
bot: {
connection: {
title: '选择接入方式',
@@ -63,7 +64,7 @@ const zhHans = {
adapter: {
title: '选择平台适配器',
description:
'先选择平台,LangBot 会再判断该适配器支持 Webhook、长连接或两者都支持。',
'选择机器人使用的平台适配器;接入方式和平台参数将在创建后配置。',
requirement: '请选择一个适配器。',
},
parameters: {
@@ -75,13 +76,16 @@ const zhHans = {
},
routing: {
title: '设置事件路由',
description:
'选择各类事件交给哪个处理器;机器人创建后仍可继续添加路由。',
description: '选择该机器人收到的各类事件交给哪个处理器。',
},
save: {
title: '保存机器人配置',
description: '确认接入参数和事件路由后保存;准备完成后即可启用机器人。',
},
submit: {
title: '创建机器人',
description:
'创建后连接配置才会生效;LangBot 生成的 Webhook 地址会显示在已保存机器人的配置中。',
'先创建未启用的机器人,再到机器人页面配置接入方式和平台参数。',
},
},
processorCreate: {
@@ -130,9 +134,8 @@ const zhHans = {
requirement: '请先填写知识库名称。',
},
engine: {
title: '选择或安装知识引擎',
description:
'选择已安装的知识引擎,也可以在此选择器的市场区域直接安装。',
title: '确认知识引擎',
description: '确认该知识库使用的引擎,并在下方配置引擎参数和检索方式。',
requirement: '请选择一个知识引擎。',
action: '浏览知识引擎市场',
},
@@ -146,6 +149,10 @@ const zhHans = {
description: '设置引擎如何搜索内容,以及如何把相关结果返回给处理器。',
requirement: '请填写当前可见的全部必填检索参数。',
},
save: {
title: '保存知识库配置',
description: '确认引擎参数和检索设置后保存配置。',
},
submit: {
title: '创建知识库',
description: '创建后即可添加文档,或连接所选引擎支持的外部知识源。',
@@ -537,7 +544,7 @@ const zhHans = {
getBotConfigError: '获取机器人配置失败:',
saveSuccess: '保存成功',
saveError: '保存失败:',
createSuccess: '创建成功,请配置事件路由',
createSuccess: '创建成功,请继续配置机器人',
createError: '创建失败:',
deleteSuccess: '删除成功',
deleteError: '删除失败:',