Skip to main content

Generation Workflow UI

The generation interface allows users to create or update work items using AI. It consists of a server page that determines the current state, a multi-section form for configuring generation parameters, provider selectors, dynamic plugin fields, and a progress display for active generation runs.

Architecture Overview

Generator Page (server)
|-- checks work permissions via canGenerate()
|-- checks generateStatus to determine view
|
|-- GenerationProgress (if GENERATING)
| |-- animated progress bar
| |-- step text and item count
|
|-- GeneratorForm (otherwise)
|-- RequiredFields (name, prompt)
|-- UpdateItemsFields (for existing works)
|-- ProviderSelectionSection
|-- DynamicPluginFields

Generator Page

Route: /works/[id]/generator File: src/app/[locale]/(dashboard)/works/[id]/generator/page.tsx

The server component fetches work data and configuration in parallel, then determines which view to render.

const [workRes, configRes] = await Promise.all([
workAPI.get(id),
workAPI.getConfig(id).catch(() => ({ config: undefined }))
]);

View Selection:

ConditionView
canGenerate(work.userRole) is falsenotFound() (404)
generateStatus.status === GENERATINGGenerationProgress
OtherwiseGeneratorForm

The canGenerate function performs a server-side role check, ensuring only editors and above can access the generator.

GeneratorForm

File: src/components/works/detail/generator/GeneratorForm.tsx

The main form component that handles both new work generation and updates to existing works.

Props:

PropTypeDescription
workIdstringWork identifier
workWorkFull work object
configWorkConfig?Work configuration (may be undefined for new works)

Form State

The form manages three layers of state:

StateTypeDescription
coreDataObjectName, prompt, generation method, PR toggle, website creation method
pluginConfigRecord<string, unknown>Dynamic key-value pairs from pipeline plugin fields
providersVia useProviderSelectionSelected providers for each category (AI, search, screenshot, etc.)

Core Data Fields:

{
name: string; // Work name (from work.name)
prompt: string; // Generation prompt
generation_method?: GenerationMethod; // CREATE_UPDATE or RECREATE
update_with_pull_request?: boolean; // Whether to create PRs for changes
website_repository_creation_method?: WebsiteRepositoryCreationMethod;
}

Form Schema Loading

The form dynamically loads its schema from the backend based on the selected pipeline provider.

useEffect(() => {
const pipelineId = providers.pipeline || undefined;
if (pipelineId === lastFetchedPipelineRef.current && formSchema) return;

async function loadFormSchema() {
const result = await getFormSchema(workId, pipelineId);
if (result.success && result.data) {
setFormSchema(result.data);
syncResolvedPipeline(result.data);
}
}
loadFormSchema();
}, [workId, providers.pipeline, syncResolvedPipeline]);

Stale Response Protection: A fetchVersionRef counter ensures that responses from outdated requests (due to rapid pipeline changes) are discarded.

Seed Data: The previous generation's pluginConfig is stored in a ref and used to populate defaults when the pipeline matches the last run.

View Modes

ContextFields Shown
New work (never generated)RequiredFields + ProviderSelectionSection + DynamicPluginFields
Existing work (simple update)UpdateItemsFields only
Existing work (advanced toggle on)UpdateItemsFields + RequiredFields + providers + plugin fields

The advanced options toggle lets users of existing works access the full generation form without being overwhelmed by options for a simple update.

Submission Flow

The form handles two distinct submission paths:

Simple Update (existing work, no advanced options):

const updateData: UpdateItemsGeneratorDto = {
generation_method: coreData.generation_method,
update_with_pull_request: coreData.update_with_pull_request
};
result = await updateItems(workId, updateData);

Full Generation (new work or advanced mode):

const generateData: CreateItemsGeneratorDto = {
name: coreData.name,
prompt: coreData.prompt,
generation_method: coreData.generation_method,
update_with_pull_request: coreData.update_with_pull_request,
website_repository_creation_method: coreData.website_repository_creation_method,
providers: buildSelectedProviders(formSchema),
pluginConfig: Object.keys(pluginConfig).length > 0 ? pluginConfig : undefined
};
result = await generateItems(workId, generateData);

Recreate Confirmation: When the generation method is RECREATE and the work has existing data, a confirmation dialog warns that all items will be deleted and regenerated. The dialog uses the Dialog component with a danger-styled confirm button.

Unconfigured Provider Check: Before full generation, getUnconfiguredProviders(formSchema) checks that all required providers are configured and shows a toast error if any are missing.

RequiredFields

File: src/components/works/detail/generator/RequiredFields.tsx

Renders the core generation inputs.

FieldComponentDescription
Work NameInput (disabled)Read-only, pre-filled from work data
Generation PromptTextareaMulti-line prompt for AI generation, with helper text

UpdateItemsFields

File: src/components/works/detail/generator/UpdateItemsFields.tsx

Renders update-specific controls for existing works.

FieldComponentDescription
Generation MethodSelectDropdown with CREATE_UPDATE and RECREATE options
Update with PRSwitchToggle for creating pull requests for changes
PR Update InfoPrUpdateInfoShows status of the last main and data PRs

When RECREATE is selected and the work has existing configuration, an inline warning banner appears.

ProviderSelector

File: src/components/works/detail/generator/ProviderSelector.tsx

Renders a horizontal row of provider buttons for a single category (AI, search, screenshot, etc.).

Props:

| Prop | Type | Description | | ----------- | ------------------ | ------------------------------------------------- | ----------------------------------------------------- | | label | string | Category label (displayed in a 128px-wide column) | | providers | ProviderOption[] | Available providers for this category | | value | string | null | Currently selected provider ID (null = use default) | | onChange | (id: string | null) => void | Selection handler |

Default Resolution: Uses resolveEffectiveDefault() from @ever-works/plugin to determine which provider is the effective default when no explicit selection has been made. The default provider cannot be deselected.

Unconfigured Providers: Shown with reduced opacity and a tooltip explaining the configuration requirement. They cannot be selected.

PipelineModeSelector

Also defined in the same file, this component renders pipeline options as radio buttons instead of toggle buttons. Each pipeline shows its name and optional description.

DynamicPluginFields

File: src/components/works/detail/generator/DynamicPluginFields.tsx

Renders form fields that are dynamically defined by the active pipeline plugin.

Props:

PropTypeDescription
fieldsFormFieldDefinition[]Field definitions from the form schema
groupsFormFieldGroup[]?Optional field grouping with titles and collapsibility
valuesRecord<string, unknown>Current field values
onChange(values: Record<string, unknown>) => voidValue change handler

Supported Field Types:

TypeRendered As
textText input
urlURL input
passwordPassword input
numberNumber input with min/max
textareaMulti-line textarea
booleanSwitch toggle
selectDropdown with options
tagsTag input (Enter/comma-delimited, removable badges)

Conditional Visibility: Fields support a showIf property that conditionally hides them based on other field values. Supported operators include eq, neq/ne, gt, gte, lt, lte, contains, not_contains, and in. Multiple conditions are ANDed together.

Field Grouping: When groups are provided, fields are rendered inside collapsible card sections ordered by the group's order property. Groups with collapsible: true start collapsed by default (unless collapsed: false). Ungrouped fields render first above any groups.

Field Deduplication: Both fields and groups are deduplicated by name (first occurrence wins) to handle cases where pipeline plugins might register duplicate fields.

GenerationProgress

File: src/components/works/detail/generator/GenerationProgress.tsx

Displays the real-time progress of an active generation run.

Visual Elements:

ElementDescription
SpinnerAnimated spinning SVG icon in a primary-colored circle
Title"Generating" with animated trailing dots (cycling every 500ms)
Step TextCurrent generation step description from getStepText()
Items ProcessedCount of processed items from getItemsProcessedText()
Progress BarHorizontal bar with percentage from getStepProgress()
Info NoteMessage explaining the user can close the page safely

Utility Functions: The progress display uses three helper functions from src/lib/utils/generator-steps.ts:

FunctionDescription
getStepProgress(status)Returns a percentage (0-100) based on the current generation step
getStepText(status, fallback)Returns a human-readable description of the current step
getItemsProcessedText(status)Returns text like "5 of 20 items processed"

Server Actions Used

ActionSourceDescription
generateItemsdashboard/generator.tsStarts full item generation
updateItemsdashboard/generator.tsStarts an update-only generation run
getFormSchemadashboard/generator-form.tsFetches dynamic form schema for a work + pipeline
getGlobalFormSchemadashboard/generator-form.tsFetches global form schema (used by AI chat)