refactor: expand setup wizard input flow
This commit is contained in:
parent
70a228cdaa
commit
c6239bf253
@ -3,6 +3,7 @@ import { DEFAULT_ACCOUNT_ID } from "../../routing/session-key.js";
|
||||
import type { WizardPrompter } from "../../wizard/prompts.js";
|
||||
import type {
|
||||
ChannelOnboardingAdapter,
|
||||
ChannelOnboardingConfigureContext,
|
||||
ChannelOnboardingDmPolicy,
|
||||
ChannelOnboardingStatus,
|
||||
ChannelOnboardingStatusContext,
|
||||
@ -26,6 +27,18 @@ export type ChannelSetupWizardStatus = {
|
||||
configuredScore?: number;
|
||||
unconfiguredScore?: number;
|
||||
resolveConfigured: (params: { cfg: OpenClawConfig }) => boolean | Promise<boolean>;
|
||||
resolveStatusLines?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
configured: boolean;
|
||||
}) => string[] | Promise<string[]>;
|
||||
resolveSelectionHint?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
configured: boolean;
|
||||
}) => string | undefined | Promise<string | undefined>;
|
||||
resolveQuickstartScore?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
configured: boolean;
|
||||
}) => number | undefined | Promise<number | undefined>;
|
||||
};
|
||||
|
||||
export type ChannelSetupWizardCredentialState = {
|
||||
@ -84,6 +97,51 @@ export type ChannelSetupWizardCredential = {
|
||||
}) => OpenClawConfig | Promise<OpenClawConfig>;
|
||||
};
|
||||
|
||||
export type ChannelSetupWizardTextInput = {
|
||||
inputKey: keyof ChannelSetupInput;
|
||||
message: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
helpTitle?: string;
|
||||
helpLines?: string[];
|
||||
confirmCurrentValue?: boolean;
|
||||
keepPrompt?: string | ((value: string) => string);
|
||||
currentValue?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
credentialValues: ChannelSetupWizardCredentialValues;
|
||||
}) => string | undefined | Promise<string | undefined>;
|
||||
initialValue?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
credentialValues: ChannelSetupWizardCredentialValues;
|
||||
}) => string | undefined | Promise<string | undefined>;
|
||||
shouldPrompt?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
credentialValues: ChannelSetupWizardCredentialValues;
|
||||
currentValue?: string;
|
||||
}) => boolean | Promise<boolean>;
|
||||
applyCurrentValue?: boolean;
|
||||
validate?: (params: {
|
||||
value: string;
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
credentialValues: ChannelSetupWizardCredentialValues;
|
||||
}) => string | undefined;
|
||||
normalizeValue?: (params: {
|
||||
value: string;
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
credentialValues: ChannelSetupWizardCredentialValues;
|
||||
}) => string;
|
||||
applySet?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
value: string;
|
||||
}) => OpenClawConfig | Promise<OpenClawConfig>;
|
||||
};
|
||||
|
||||
export type ChannelSetupWizardAllowFromEntry = {
|
||||
input: string;
|
||||
resolved: boolean;
|
||||
@ -139,12 +197,33 @@ export type ChannelSetupWizardGroupAccess = {
|
||||
}) => OpenClawConfig;
|
||||
};
|
||||
|
||||
export type ChannelSetupWizardPrepare = (params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
credentialValues: ChannelSetupWizardCredentialValues;
|
||||
runtime: ChannelOnboardingConfigureContext["runtime"];
|
||||
prompter: WizardPrompter;
|
||||
options?: ChannelOnboardingConfigureContext["options"];
|
||||
}) =>
|
||||
| {
|
||||
cfg?: OpenClawConfig;
|
||||
credentialValues?: ChannelSetupWizardCredentialValues;
|
||||
}
|
||||
| void
|
||||
| Promise<{
|
||||
cfg?: OpenClawConfig;
|
||||
credentialValues?: ChannelSetupWizardCredentialValues;
|
||||
} | void>;
|
||||
|
||||
export type ChannelSetupWizard = {
|
||||
channel: string;
|
||||
status: ChannelSetupWizardStatus;
|
||||
introNote?: ChannelSetupWizardNote;
|
||||
envShortcut?: ChannelSetupWizardEnvShortcut;
|
||||
prepare?: ChannelSetupWizardPrepare;
|
||||
credentials: ChannelSetupWizardCredential[];
|
||||
textInputs?: ChannelSetupWizardTextInput[];
|
||||
completionNote?: ChannelSetupWizardNote;
|
||||
dmPolicy?: ChannelOnboardingDmPolicy;
|
||||
allowFrom?: ChannelSetupWizardAllowFrom;
|
||||
groupAccess?: ChannelSetupWizardGroupAccess;
|
||||
@ -160,14 +239,28 @@ async function buildStatus(
|
||||
ctx: ChannelOnboardingStatusContext,
|
||||
): Promise<ChannelOnboardingStatus> {
|
||||
const configured = await wizard.status.resolveConfigured({ cfg: ctx.cfg });
|
||||
const statusLines = (await wizard.status.resolveStatusLines?.({
|
||||
cfg: ctx.cfg,
|
||||
configured,
|
||||
})) ?? [
|
||||
`${plugin.meta.label}: ${configured ? wizard.status.configuredLabel : wizard.status.unconfiguredLabel}`,
|
||||
];
|
||||
const selectionHint =
|
||||
(await wizard.status.resolveSelectionHint?.({
|
||||
cfg: ctx.cfg,
|
||||
configured,
|
||||
})) ?? (configured ? wizard.status.configuredHint : wizard.status.unconfiguredHint);
|
||||
const quickstartScore =
|
||||
(await wizard.status.resolveQuickstartScore?.({
|
||||
cfg: ctx.cfg,
|
||||
configured,
|
||||
})) ?? (configured ? wizard.status.configuredScore : wizard.status.unconfiguredScore);
|
||||
return {
|
||||
channel: plugin.id,
|
||||
configured,
|
||||
statusLines: [
|
||||
`${plugin.meta.label}: ${configured ? wizard.status.configuredLabel : wizard.status.unconfiguredLabel}`,
|
||||
],
|
||||
selectionHint: configured ? wizard.status.configuredHint : wizard.status.unconfiguredHint,
|
||||
quickstartScore: configured ? wizard.status.configuredScore : wizard.status.unconfiguredScore,
|
||||
statusLines,
|
||||
selectionHint,
|
||||
quickstartScore,
|
||||
};
|
||||
}
|
||||
|
||||
@ -238,6 +331,29 @@ function collectCredentialValues(params: {
|
||||
return values;
|
||||
}
|
||||
|
||||
async function applyWizardTextInputValue(params: {
|
||||
plugin: ChannelSetupWizardPlugin;
|
||||
input: ChannelSetupWizardTextInput;
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
value: string;
|
||||
}) {
|
||||
return params.input.applySet
|
||||
? await params.input.applySet({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
value: params.value,
|
||||
})
|
||||
: applySetupInput({
|
||||
plugin: params.plugin,
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
input: {
|
||||
[params.input.inputKey]: params.value,
|
||||
},
|
||||
}).cfg;
|
||||
}
|
||||
|
||||
export function buildChannelOnboardingAdapterFromSetupWizard(params: {
|
||||
plugin: ChannelSetupWizardPlugin;
|
||||
wizard: ChannelSetupWizard;
|
||||
@ -248,6 +364,7 @@ export function buildChannelOnboardingAdapterFromSetupWizard(params: {
|
||||
getStatus: async (ctx) => buildStatus(plugin, wizard, ctx),
|
||||
configure: async ({
|
||||
cfg,
|
||||
runtime,
|
||||
prompter,
|
||||
options,
|
||||
accountOverrides,
|
||||
@ -305,6 +422,26 @@ export function buildChannelOnboardingAdapterFromSetupWizard(params: {
|
||||
await prompter.note(wizard.introNote.lines.join("\n"), wizard.introNote.title);
|
||||
}
|
||||
|
||||
if (wizard.prepare) {
|
||||
const prepared = await wizard.prepare({
|
||||
cfg: next,
|
||||
accountId,
|
||||
credentialValues,
|
||||
runtime,
|
||||
prompter,
|
||||
options,
|
||||
});
|
||||
if (prepared?.cfg) {
|
||||
next = prepared.cfg;
|
||||
}
|
||||
if (prepared?.credentialValues) {
|
||||
credentialValues = {
|
||||
...credentialValues,
|
||||
...prepared.credentialValues,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!usedEnvShortcut) {
|
||||
for (const credential of wizard.credentials) {
|
||||
let credentialState = credential.inspect({ cfg: next, accountId });
|
||||
@ -383,6 +520,129 @@ export function buildChannelOnboardingAdapterFromSetupWizard(params: {
|
||||
}
|
||||
}
|
||||
|
||||
for (const textInput of wizard.textInputs ?? []) {
|
||||
let currentValue = trimResolvedValue(
|
||||
typeof credentialValues[textInput.inputKey] === "string"
|
||||
? credentialValues[textInput.inputKey]
|
||||
: undefined,
|
||||
);
|
||||
if (!currentValue && textInput.currentValue) {
|
||||
currentValue = trimResolvedValue(
|
||||
await textInput.currentValue({
|
||||
cfg: next,
|
||||
accountId,
|
||||
credentialValues,
|
||||
}),
|
||||
);
|
||||
}
|
||||
const shouldPrompt = textInput.shouldPrompt
|
||||
? await textInput.shouldPrompt({
|
||||
cfg: next,
|
||||
accountId,
|
||||
credentialValues,
|
||||
currentValue,
|
||||
})
|
||||
: true;
|
||||
|
||||
if (!shouldPrompt) {
|
||||
if (currentValue) {
|
||||
credentialValues[textInput.inputKey] = currentValue;
|
||||
if (textInput.applyCurrentValue) {
|
||||
next = await applyWizardTextInputValue({
|
||||
plugin,
|
||||
input: textInput,
|
||||
cfg: next,
|
||||
accountId,
|
||||
value: currentValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (textInput.helpLines && textInput.helpLines.length > 0) {
|
||||
await prompter.note(
|
||||
textInput.helpLines.join("\n"),
|
||||
textInput.helpTitle ?? textInput.message,
|
||||
);
|
||||
}
|
||||
|
||||
if (currentValue && textInput.confirmCurrentValue !== false) {
|
||||
const keep = await prompter.confirm({
|
||||
message:
|
||||
typeof textInput.keepPrompt === "function"
|
||||
? textInput.keepPrompt(currentValue)
|
||||
: (textInput.keepPrompt ?? `${textInput.message} set (${currentValue}). Keep it?`),
|
||||
initialValue: true,
|
||||
});
|
||||
if (keep) {
|
||||
credentialValues[textInput.inputKey] = currentValue;
|
||||
if (textInput.applyCurrentValue) {
|
||||
next = await applyWizardTextInputValue({
|
||||
plugin,
|
||||
input: textInput,
|
||||
cfg: next,
|
||||
accountId,
|
||||
value: currentValue,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const initialValue = trimResolvedValue(
|
||||
(await textInput.initialValue?.({
|
||||
cfg: next,
|
||||
accountId,
|
||||
credentialValues,
|
||||
})) ?? currentValue,
|
||||
);
|
||||
const rawValue = String(
|
||||
await prompter.text({
|
||||
message: textInput.message,
|
||||
initialValue,
|
||||
placeholder: textInput.placeholder,
|
||||
validate: (value) => {
|
||||
const trimmed = String(value ?? "").trim();
|
||||
if (!trimmed && textInput.required !== false) {
|
||||
return "Required";
|
||||
}
|
||||
return textInput.validate?.({
|
||||
value: trimmed,
|
||||
cfg: next,
|
||||
accountId,
|
||||
credentialValues,
|
||||
});
|
||||
},
|
||||
}),
|
||||
);
|
||||
const trimmedValue = rawValue.trim();
|
||||
if (!trimmedValue && textInput.required === false) {
|
||||
delete credentialValues[textInput.inputKey];
|
||||
continue;
|
||||
}
|
||||
const normalizedValue = trimResolvedValue(
|
||||
textInput.normalizeValue?.({
|
||||
value: trimmedValue,
|
||||
cfg: next,
|
||||
accountId,
|
||||
credentialValues,
|
||||
}) ?? trimmedValue,
|
||||
);
|
||||
if (!normalizedValue) {
|
||||
delete credentialValues[textInput.inputKey];
|
||||
continue;
|
||||
}
|
||||
next = await applyWizardTextInputValue({
|
||||
plugin,
|
||||
input: textInput,
|
||||
cfg: next,
|
||||
accountId,
|
||||
value: normalizedValue,
|
||||
});
|
||||
credentialValues[textInput.inputKey] = normalizedValue;
|
||||
}
|
||||
|
||||
if (wizard.groupAccess) {
|
||||
const access = wizard.groupAccess;
|
||||
if (access.helpLines && access.helpLines.length > 0) {
|
||||
@ -460,6 +720,19 @@ export function buildChannelOnboardingAdapterFromSetupWizard(params: {
|
||||
});
|
||||
}
|
||||
|
||||
const shouldShowCompletionNote =
|
||||
wizard.completionNote &&
|
||||
(wizard.completionNote.shouldShow
|
||||
? await wizard.completionNote.shouldShow({
|
||||
cfg: next,
|
||||
accountId,
|
||||
credentialValues,
|
||||
})
|
||||
: true);
|
||||
if (shouldShowCompletionNote && wizard.completionNote) {
|
||||
await prompter.note(wizard.completionNote.lines.join("\n"), wizard.completionNote.title);
|
||||
}
|
||||
|
||||
return { cfg: next, accountId };
|
||||
},
|
||||
dmPolicy: wizard.dmPolicy,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user