schema([ Section::make('Style Basics') ->schema([ TextInput::make('key') ->required() ->maxLength(120) ->unique(ignoreRecord: true), TextInput::make('name') ->required() ->maxLength(120), TextInput::make('version') ->numeric() ->default(1) ->disabled() ->dehydrated(false) ->helperText('Auto-increments when core style configuration changes.'), TextInput::make('category') ->maxLength(50), TextInput::make('sort') ->numeric() ->default(0) ->required(), Toggle::make('is_active') ->default(true), Toggle::make('is_premium') ->default(false), Toggle::make('requires_source_image') ->default(true), ]) ->columns(3), Section::make('Provider Binding') ->schema([ Select::make('provider') ->options([ 'runware' => 'runware.ai', ]) ->required() ->default('runware'), Select::make('provider_model') ->label('Runware model (AIR)') ->searchable() ->getSearchResultsUsing(static fn (string $search): array => app(RunwareModelSearchService::class)->searchOptions($search)) ->getOptionLabelUsing(static fn (mixed $value): ?string => app(RunwareModelSearchService::class)->labelForModel($value)) ->helperText('Start typing to search models from runware.ai.') ->native(false) ->live() ->afterStateUpdated(static function (Set $set, ?string $state): void { self::applySelectedRunwareModel($set, $state); }), ]) ->columns(2), Section::make('Runware Generation') ->schema([ TextInput::make('metadata.runware.generation.width') ->label('Width') ->numeric() ->minValue(64) ->maxValue(4096) ->step(64) ->helperText(static fn (Get $get): ?string => self::dimensionConstraintHint($get, 'width')), TextInput::make('metadata.runware.generation.height') ->label('Height') ->numeric() ->minValue(64) ->maxValue(4096) ->step(64) ->helperText(static fn (Get $get): ?string => self::dimensionConstraintHint($get, 'height')), TextInput::make('metadata.runware.generation.steps') ->label('Steps') ->numeric() ->minValue(1) ->maxValue(150) ->helperText(static fn (Get $get): ?string => self::rangeConstraintHint($get, 'steps')), TextInput::make('metadata.runware.generation.cfg_scale') ->label('CFG Scale') ->numeric() ->minValue(0) ->maxValue(30) ->step(0.1) ->helperText(static fn (Get $get): ?string => self::rangeConstraintHint($get, 'cfg_scale')), TextInput::make('metadata.runware.generation.strength') ->label('Strength') ->numeric() ->minValue(0) ->maxValue(1) ->step(0.01) ->helperText(static fn (Get $get): ?string => self::rangeConstraintHint($get, 'strength')), Select::make('metadata.runware.generation.output_format') ->label('Output format') ->options([ 'JPG' => 'JPG', 'PNG' => 'PNG', 'WEBP' => 'WEBP', ]) ->default('JPG') ->native(false), Select::make('metadata.runware.generation.delivery_method') ->label('Delivery method') ->options([ 'async' => 'async (queue + poll)', 'sync' => 'sync', ]) ->default('async') ->native(false), ]) ->columns(3), Section::make('Prompts') ->schema([ Textarea::make('description') ->rows(2), Textarea::make('prompt_template') ->rows(5), Textarea::make('negative_prompt_template') ->rows(4), ]), Section::make('Metadata') ->schema([ KeyValue::make('metadata') ->nullable(), ]), ]); } public static function table(Table $table): Table { return $table ->defaultSort('sort') ->columns([ Tables\Columns\TextColumn::make('key') ->searchable() ->copyable(), Tables\Columns\TextColumn::make('name') ->searchable(), Tables\Columns\TextColumn::make('version') ->sortable() ->toggleable(), Tables\Columns\TextColumn::make('provider') ->badge(), Tables\Columns\TextColumn::make('provider_model') ->toggleable(), Tables\Columns\IconColumn::make('is_active') ->boolean(), Tables\Columns\IconColumn::make('is_premium') ->boolean(), Tables\Columns\TextColumn::make('sort') ->sortable(), Tables\Columns\TextColumn::make('updated_at') ->since() ->toggleable(), ]) ->filters([ Tables\Filters\TernaryFilter::make('is_active'), Tables\Filters\TernaryFilter::make('is_premium'), ]) ->actions([ Actions\EditAction::make() ->after(fn (array $data, AiStyle $record) => app(SuperAdminAuditLogger::class)->recordModelMutation( 'updated', $record, SuperAdminAuditLogger::fieldsMetadata(array_keys($data)), static::class )), Actions\DeleteAction::make() ->after(fn (AiStyle $record) => app(SuperAdminAuditLogger::class)->recordModelMutation( 'deleted', $record, source: static::class )), ]) ->bulkActions([ Actions\DeleteBulkAction::make(), ]); } public static function getPages(): array { return [ 'index' => ManageAiStyles::route('/'), ]; } private static function applySelectedRunwareModel(Set $set, ?string $air): void { if (! is_string($air) || trim($air) === '') { return; } $model = app(RunwareModelSearchService::class)->findByAir($air); if (! is_array($model)) { return; } $set('metadata.runware.model.air', $model['air']); $set('metadata.runware.model.name', $model['name']); $set('metadata.runware.model.architecture', $model['architecture']); $set('metadata.runware.model.category', $model['category']); foreach ((array) ($model['constraints'] ?? []) as $key => $value) { $set("metadata.runware.constraints.{$key}", $value); } self::setIfNumeric($set, 'metadata.runware.generation.width', $model['defaults']['width'] ?? null); self::setIfNumeric($set, 'metadata.runware.generation.height', $model['defaults']['height'] ?? null); self::setIfNumeric($set, 'metadata.runware.generation.steps', $model['defaults']['steps'] ?? null); self::setIfNumeric($set, 'metadata.runware.generation.cfg_scale', $model['defaults']['cfg_scale'] ?? null); } private static function setIfNumeric(Set $set, string $path, mixed $value): void { if (is_numeric($value)) { $set($path, $value); } } private static function dimensionConstraintHint(Get $get, string $dimension): ?string { $min = $get("metadata.runware.constraints.min_{$dimension}"); $max = $get("metadata.runware.constraints.max_{$dimension}"); $step = $get("metadata.runware.constraints.{$dimension}_step"); if (! is_numeric($min) && ! is_numeric($max) && ! is_numeric($step)) { return null; } $parts = []; if (is_numeric($min) || is_numeric($max)) { $parts[] = sprintf( 'Model range: %s - %s', is_numeric($min) ? (string) (int) $min : '?', is_numeric($max) ? (string) (int) $max : '?' ); } if (is_numeric($step) && (int) $step > 0) { $parts[] = sprintf('Step: %d', (int) $step); } return $parts !== [] ? implode(' | ', $parts) : null; } private static function rangeConstraintHint(Get $get, string $field): ?string { $min = $get("metadata.runware.constraints.min_{$field}"); $max = $get("metadata.runware.constraints.max_{$field}"); if (! is_numeric($min) && ! is_numeric($max)) { return null; } return sprintf( 'Model range: %s - %s', is_numeric($min) ? trim((string) $min) : '?', is_numeric($max) ? trim((string) $max) : '?' ); } }