Files
qzl 19e273a9e6 refactor: clean CLI taxonomy — canonical subcommands, merged memory.update, no aliases
- calendar: split write → create/read/update/delete/share
- contacts: rename lookup → read
- memory: merge write+forget → update (unified action field in operations)
- Remove all alias/normalization logic from adapter and handlers
- Update tool_postprocessor ui_hints builders to canonical keys
- Remove frontend legacy TOOL_CALL_START/ARGS/END events and ToolCallItem
- Update SKILL.md files and protocol docs
- Update tests and settings screens
2026-04-23 12:12:41 +08:00

966 lines
28 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:social_app/core/l10n/l10n.dart';
import '../../../../app/di/injection.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/app_button.dart';
import '../../../../shared/widgets/app_input.dart';
import '../../../../shared/widgets/app_loading_indicator.dart';
import '../../../../shared/widgets/app_pressable.dart';
import '../../../../shared/widgets/app_selection_sheet.dart';
import '../../../../shared/widgets/detail_header_action_menu.dart';
import '../../../../shared/widgets/destructive_action_sheet.dart';
import '../../../../shared/widgets/toast/toast.dart';
import '../../../../shared/widgets/toast/toast_type.dart';
import '../../../../core/utils/tool_name_localizer.dart';
import '../../data/models/automation_job_model.dart';
import '../../data/apis/automation_jobs_api.dart';
import '../../presentation/cubits/job_detail_cubit.dart';
import '../widgets/settings_page_scaffold.dart';
class JobDetailScreen extends StatefulWidget {
const JobDetailScreen({super.key, this.jobId});
final String? jobId;
@override
State<JobDetailScreen> createState() => _JobDetailScreenState();
}
enum _JobDetailHeaderAction { delete }
class _JobDetailScreenState extends State<JobDetailScreen> {
late final JobDetailCubit _cubit;
final TextEditingController _titleController = TextEditingController();
final TextEditingController _templateController = TextEditingController();
String _scheduleType = 'daily';
String _timezone = 'Asia/Shanghai';
TimeOfDay _runAt = const TimeOfDay(hour: 8, minute: 0);
final Set<int> _selectedWeekdays = <int>{1};
String _contextSource = 'latest_chat';
String _contextWindowMode = 'day';
int _contextWindowCount = 2;
final Set<String> _selectedTools = <String>{'memory.update'};
ColorScheme get _colorScheme => Theme.of(context).colorScheme;
@override
void initState() {
super.initState();
_cubit = JobDetailCubit(sl<AutomationJobsApi>());
if (widget.jobId != null) {
_cubit.loadJob(widget.jobId!);
}
}
@override
void dispose() {
_titleController.dispose();
_templateController.dispose();
_cubit.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: _cubit,
child: BlocConsumer<JobDetailCubit, JobDetailState>(
listener: (context, state) {
if (state.error != null) {
Toast.show(context, state.error!, type: ToastType.error);
}
},
builder: (context, state) {
final l10n = context.l10n;
if (state.isLoading) {
return SettingsPageScaffold(
title: l10n.commonLoading,
body: const Center(child: AppLoadingIndicator()),
);
}
final job = state.job;
final isEditMode = widget.jobId != null;
if (isEditMode && job == null && state.error != null) {
return SettingsPageScaffold(
title: l10n.settingsJobDetailTitle,
onBack: () => context.pop(),
body: _buildLoadFailedView(state.error!),
);
}
return SettingsPageScaffold(
title: job?.title ?? l10n.settingsJobCreatePageTitle,
onBack: () => context.pop(),
trailing: job != null && !job.isSystem
? _buildHeaderActions(job.id, state)
: null,
body: job == null
? _buildCreateForm(state)
: _buildDetailPage(job, state),
);
},
),
);
}
Widget _buildLoadFailedView(String error) {
final l10n = context.l10n;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle(l10n.settingsJobLoadFailed),
const SizedBox(height: AppSpacing.sm),
Text(
error,
style: TextStyle(
color: _colorScheme.error,
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: AppSpacing.md),
AppButton(
text: l10n.settingsJobRetry,
onPressed: () => _cubit.loadJob(widget.jobId!),
),
],
);
}
Widget _buildDetailPage(AutomationJobModel job, JobDetailState state) {
final l10n = context.l10n;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildOverviewCard(job),
const SizedBox(height: AppSpacing.lg),
_buildSectionTitle(l10n.settingsJobPlanConfig),
const SizedBox(height: AppSpacing.sm),
_buildInfoCard([
_buildInfoRow(
l10n.settingsJobCycle,
_scheduleLabel(job.config.schedule.type),
),
_buildInfoRow(
l10n.settingsJobRunAt,
_displayRunAt(job.config.schedule),
),
_buildInfoRow(l10n.settingsJobTimezone, job.timezone),
_buildInfoRow(
l10n.settingsJobStatusLabel,
job.isActive
? l10n.settingsJobStatusEnabled
: l10n.settingsJobStatusDisabled,
),
]),
const SizedBox(height: AppSpacing.lg),
_buildSectionTitle(l10n.settingsJobInputTemplate),
const SizedBox(height: AppSpacing.sm),
_buildTextBlock(job.config.inputTemplate),
const SizedBox(height: AppSpacing.lg),
_buildSectionTitle(l10n.settingsJobEnabledTools),
const SizedBox(height: AppSpacing.sm),
_buildToolWrap(job.config.enabledTools),
const SizedBox(height: AppSpacing.lg),
_buildSectionTitle(l10n.settingsJobContextMode),
const SizedBox(height: AppSpacing.sm),
_buildInfoCard([
_buildInfoRow(
l10n.settingsJobContextSource,
_contextSourceLabel(job.config.context.source),
),
_buildInfoRow(
l10n.settingsJobWindowMode,
_windowModeLabel(job.config.context.windowMode),
),
_buildInfoRow(
l10n.settingsJobWindowCount,
l10n.settingsJobWindowCountValue(job.config.context.windowCount),
),
]),
if (!job.isSystem && state.isSaving)
const Padding(
padding: EdgeInsets.only(top: AppSpacing.lg),
child: Center(child: AppLoadingIndicator(size: AppSpacing.lg)),
),
],
);
}
Widget _buildHeaderActions(String jobId, JobDetailState state) {
final l10n = context.l10n;
return DetailHeaderActionMenu<_JobDetailHeaderAction>(
items: [
DetailHeaderActionItem<_JobDetailHeaderAction>(
value: _JobDetailHeaderAction.delete,
label: l10n.commonDelete,
icon: Icons.delete_outline,
isDestructive: true,
),
],
onSelected: (action) {
if (state.isSaving) {
return;
}
if (action == _JobDetailHeaderAction.delete) {
unawaited(_confirmAndDelete(jobId));
}
},
);
}
Future<void> _confirmAndDelete(String jobId) async {
final l10n = context.l10n;
final confirmed = await showDestructiveActionSheet(
context,
title: l10n.settingsJobDeleteTitle,
message: l10n.settingsJobDeleteMessage,
confirmText: l10n.settingsJobDeleteConfirm,
);
if (!confirmed) {
return;
}
final success = await _cubit.deleteJob(jobId);
if (!mounted) {
return;
}
if (success) {
Toast.show(
context,
l10n.settingsJobDeleteSuccess,
type: ToastType.success,
);
context.pop();
}
}
Widget _buildOverviewCard(AutomationJobModel job) {
final l10n = context.l10n;
return Container(
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [_colorScheme.surface, _colorScheme.surfaceContainerLow],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: _colorScheme.outlineVariant),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
job.title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: _colorScheme.onSurface,
),
),
const SizedBox(height: AppSpacing.sm),
Wrap(
spacing: AppSpacing.sm,
runSpacing: AppSpacing.sm,
children: [
_buildBadge(
job.isSystem
? l10n.settingsJobSourceSystem
: l10n.settingsJobSourceCustom,
),
_buildBadge(
job.isActive
? l10n.settingsJobStatusEnabled
: l10n.settingsJobStatusDisabled,
),
_buildBadge(_scheduleLabel(job.config.schedule.type)),
],
),
],
),
);
}
Widget _buildBadge(String text) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.xs,
),
decoration: BoxDecoration(
color: _colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.full),
border: Border.all(color: _colorScheme.outlineVariant),
),
child: Text(
text,
style: TextStyle(
color: _colorScheme.onSurfaceVariant,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
);
}
Widget _buildCreateForm(JobDetailState state) {
final l10n = context.l10n;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildCreateBasicSection(),
const SizedBox(height: AppSpacing.lg),
_buildCreateRuleSection(),
const SizedBox(height: AppSpacing.lg),
_buildCreateToolSection(),
const SizedBox(height: AppSpacing.lg),
_buildCreateContextSection(),
const SizedBox(height: AppSpacing.xl),
AppButton(
text: l10n.settingsCreateJob,
isLoading: state.isSaving,
onPressed: state.isSaving ? null : _submitCreate,
),
],
);
}
Widget _buildCreateBasicSection() {
final l10n = context.l10n;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle(l10n.settingsJobBasicInfo),
const SizedBox(height: AppSpacing.sm),
AppInput(
label: l10n.settingsJobName,
hint: l10n.settingsJobNameHint,
controller: _titleController,
),
const SizedBox(height: AppSpacing.md),
AppInput(
label: l10n.settingsJobInputTemplate,
hint: l10n.settingsJobTemplateHint,
controller: _templateController,
maxLines: 4,
),
],
);
}
Widget _buildCreateRuleSection() {
final l10n = context.l10n;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle(l10n.settingsJobExecutionRules),
const SizedBox(height: AppSpacing.sm),
_buildPickerTile(
label: l10n.settingsJobCycle,
value: _scheduleLabel(_scheduleType),
onTap: _pickScheduleType,
),
if (_scheduleType == 'weekly') ...[
const SizedBox(height: AppSpacing.sm),
_buildWeekdaySelector(),
],
const SizedBox(height: AppSpacing.sm),
_buildPickerTile(
label: l10n.settingsJobRunAt,
value: _formatTime(_runAt),
onTap: _pickRunAt,
),
const SizedBox(height: AppSpacing.sm),
_buildPickerTile(
label: l10n.settingsJobTimezone,
value: _timezone,
onTap: _pickTimezone,
),
],
);
}
Widget _buildCreateToolSection() {
final l10n = context.l10n;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle(l10n.settingsJobToolSelection),
const SizedBox(height: AppSpacing.sm),
_buildToolSelector(),
],
);
}
Widget _buildCreateContextSection() {
final l10n = context.l10n;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle(l10n.settingsJobContextMode),
const SizedBox(height: AppSpacing.sm),
_buildPickerTile(
label: l10n.settingsJobContextSource,
value: _contextSourceLabel(_contextSource),
onTap: _pickContextSource,
),
const SizedBox(height: AppSpacing.sm),
_buildPickerTile(
label: l10n.settingsJobWindowMode,
value: _windowModeLabel(_contextWindowMode),
onTap: _pickWindowMode,
),
const SizedBox(height: AppSpacing.sm),
_buildCounterTile(
label: l10n.settingsJobWindowCount,
value: _contextWindowCount,
onMinus: _contextWindowCount > 1
? () {
setState(() {
_contextWindowCount -= 1;
});
}
: null,
onPlus: _contextWindowCount < 200
? () {
setState(() {
_contextWindowCount += 1;
});
}
: null,
),
],
);
}
Widget _buildPickerTile({
required String label,
required String value,
required VoidCallback onTap,
}) {
return AppPressable(
onTap: onTap,
borderRadius: BorderRadius.circular(AppRadius.lg),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.md,
),
decoration: BoxDecoration(
color: _colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: _colorScheme.outlineVariant),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
color: _colorScheme.onSurfaceVariant,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: AppSpacing.xs),
Text(
value,
style: TextStyle(
color: _colorScheme.onSurface,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
],
),
),
Icon(
Icons.keyboard_arrow_down,
color: _colorScheme.onSurfaceVariant,
),
],
),
),
);
}
Widget _buildCounterTile({
required String label,
required int value,
required VoidCallback? onMinus,
required VoidCallback? onPlus,
}) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.md,
),
decoration: BoxDecoration(
color: _colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: _colorScheme.outlineVariant),
),
child: Row(
children: [
Expanded(
child: Text(
context.l10n.settingsJobCounterValue(label, value),
style: TextStyle(
color: _colorScheme.onSurface,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
_buildCounterAction(icon: Icons.remove, onTap: onMinus),
const SizedBox(width: AppSpacing.sm),
_buildCounterAction(icon: Icons.add, onTap: onPlus),
],
),
);
}
Widget _buildCounterAction({
required IconData icon,
required VoidCallback? onTap,
}) {
return AppPressable(
onTap: onTap,
borderRadius: BorderRadius.circular(AppRadius.full),
child: Container(
width: AppSpacing.xxl + AppSpacing.md,
height: AppSpacing.xxl + AppSpacing.md,
decoration: BoxDecoration(
color: onTap == null
? _colorScheme.surfaceContainerHighest
: _colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(AppRadius.full),
border: Border.all(color: _colorScheme.outlineVariant),
),
child: Icon(
icon,
size: AppSpacing.lg,
color: onTap == null
? _colorScheme.onSurfaceVariant
: _colorScheme.primary,
),
),
);
}
Widget _buildToolSelector() {
return Wrap(
spacing: AppSpacing.sm,
runSpacing: AppSpacing.sm,
children: automationToolOptions.map((toolName) {
final selected = _selectedTools.contains(toolName);
return AppPressable(
onTap: () {
setState(() {
if (selected) {
_selectedTools.remove(toolName);
} else {
_selectedTools.add(toolName);
}
});
},
borderRadius: BorderRadius.circular(AppRadius.full),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: selected
? _colorScheme.primaryContainer
: _colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.full),
border: Border.all(
color: selected
? _colorScheme.primary
: _colorScheme.outlineVariant,
),
),
child: Text(
localizeToolName(toolName),
style: TextStyle(
color: selected
? _colorScheme.primary
: _colorScheme.onSurfaceVariant,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
);
}).toList(),
);
}
Widget _buildWeekdaySelector() {
final l10n = context.l10n;
final weekdayLabels = <int, String>{
1: l10n.settingsJobWeekdayMon,
2: l10n.settingsJobWeekdayTue,
3: l10n.settingsJobWeekdayWed,
4: l10n.settingsJobWeekdayThu,
5: l10n.settingsJobWeekdayFri,
6: l10n.settingsJobWeekdaySat,
7: l10n.settingsJobWeekdaySun,
};
return Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: _colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: _colorScheme.outlineVariant),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.settingsJobRunDays,
style: TextStyle(
color: _colorScheme.onSurfaceVariant,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: AppSpacing.sm),
Wrap(
spacing: AppSpacing.sm,
runSpacing: AppSpacing.sm,
children: weekdayLabels.entries.map((entry) {
final selected = _selectedWeekdays.contains(entry.key);
return AppPressable(
onTap: () {
setState(() {
if (selected) {
if (_selectedWeekdays.length > 1) {
_selectedWeekdays.remove(entry.key);
}
} else {
_selectedWeekdays.add(entry.key);
}
});
},
borderRadius: BorderRadius.circular(AppRadius.full),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: selected
? _colorScheme.primaryContainer
: _colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.full),
border: Border.all(
color: selected
? _colorScheme.primary
: _colorScheme.outlineVariant,
),
),
child: Text(
entry.value,
style: TextStyle(
color: selected
? _colorScheme.primary
: _colorScheme.onSurfaceVariant,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
);
}).toList(),
),
],
),
);
}
Widget _buildToolWrap(List<String> tools) {
if (tools.isEmpty) {
return _buildTextBlock(context.l10n.settingsJobNoToolsEnabled);
}
return Wrap(
spacing: AppSpacing.sm,
runSpacing: AppSpacing.sm,
children: tools
.map((tool) => _buildBadge(localizeToolName(tool)))
.toList(),
);
}
Widget _buildTextBlock(String text) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: _colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: _colorScheme.outlineVariant),
),
child: Text(
text,
style: TextStyle(
color: _colorScheme.onSurface,
fontSize: 13,
fontWeight: FontWeight.w500,
height: 1.5,
),
),
);
}
Widget _buildSectionTitle(String title) {
return Text(
title,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: _colorScheme.onSurfaceVariant,
),
);
}
Widget _buildInfoCard(List<Widget> children) {
return Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: _colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: _colorScheme.outlineVariant),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: AppSpacing.xs),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(
color: _colorScheme.onSurfaceVariant,
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Text(
value,
textAlign: TextAlign.right,
style: TextStyle(
color: _colorScheme.onSurface,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
Future<void> _pickScheduleType() async {
final l10n = context.l10n;
final picked = await showAppSelectionSheet<String>(
context,
title: l10n.settingsJobPickCycle,
selectedValue: _scheduleType,
items: [
AppSelectionItem(value: 'daily', label: l10n.settingsJobScheduleDaily),
AppSelectionItem(
value: 'weekly',
label: l10n.settingsJobScheduleWeekly,
),
],
);
if (picked != null) {
setState(() {
_scheduleType = picked;
if (_scheduleType == 'weekly' && _selectedWeekdays.isEmpty) {
_selectedWeekdays.add(1);
}
});
}
}
Future<void> _pickTimezone() async {
final picked = await showAppSelectionSheet<String>(
context,
title: context.l10n.settingsJobPickTimezone,
selectedValue: _timezone,
items: const [
AppSelectionItem(value: 'Asia/Shanghai', label: 'Asia/Shanghai'),
AppSelectionItem(value: 'UTC', label: 'UTC'),
],
);
if (picked != null) {
setState(() {
_timezone = picked;
});
}
}
Future<void> _pickContextSource() async {
final l10n = context.l10n;
final picked = await showAppSelectionSheet<String>(
context,
title: l10n.settingsJobPickContextSource,
selectedValue: _contextSource,
items: [
AppSelectionItem(
value: 'latest_chat',
label: l10n.settingsJobContextSourceLatestChat,
),
],
);
if (picked != null) {
setState(() {
_contextSource = picked;
});
}
}
Future<void> _pickWindowMode() async {
final l10n = context.l10n;
final picked = await showAppSelectionSheet<String>(
context,
title: l10n.settingsJobPickWindowMode,
selectedValue: _contextWindowMode,
items: [
AppSelectionItem(value: 'day', label: l10n.settingsJobWindowModeByDay),
AppSelectionItem(
value: 'number',
label: l10n.settingsJobWindowModeByNumber,
),
],
);
if (picked != null) {
setState(() {
_contextWindowMode = picked;
});
}
}
Future<void> _pickRunAt() async {
final picked = await showTimePicker(context: context, initialTime: _runAt);
if (picked != null) {
setState(() {
_runAt = picked;
});
}
}
String _formatTime(TimeOfDay time) {
final hour = time.hour.toString().padLeft(2, '0');
final minute = time.minute.toString().padLeft(2, '0');
return '$hour:$minute:00';
}
String _displayRunAt(ScheduleConfigModel schedule) {
final hour = schedule.runAt.hour.toString().padLeft(2, '0');
final minute = schedule.runAt.minute.toString().padLeft(2, '0');
return '$hour:$minute';
}
String _scheduleLabel(String scheduleType) {
final l10n = context.l10n;
final normalized = scheduleType.toLowerCase();
if (normalized == 'daily') {
return l10n.settingsJobScheduleDaily;
}
if (normalized == 'weekly') {
return l10n.settingsJobScheduleWeekly;
}
return scheduleType;
}
String _contextSourceLabel(String source) {
if (source == 'latest_chat') {
return context.l10n.settingsJobContextSourceLatestChat;
}
return source;
}
String _windowModeLabel(String mode) {
if (mode == 'day') {
return context.l10n.settingsJobWindowModeByDay;
}
if (mode == 'number') {
return context.l10n.settingsJobWindowModeByNumber;
}
return mode;
}
Future<void> _submitCreate() async {
final title = _titleController.text.trim();
final template = _templateController.text.trim();
if (title.isEmpty || template.isEmpty) {
Toast.show(
context,
context.l10n.settingsJobFillRequired,
type: ToastType.error,
);
return;
}
final request = AutomationJobCreateRequest(
title: title,
timezone: _timezone,
status: 'active',
config: AutomationJobConfigModel(
inputTemplate: template,
enabledTools: _selectedTools.toList(),
context: MessageContextConfigModel(
source: _contextSource,
windowMode: _contextWindowMode,
windowCount: _contextWindowCount,
),
schedule: ScheduleConfigModel(
type: _scheduleType,
runAt: ScheduleRunAtModel(hour: _runAt.hour, minute: _runAt.minute),
weekdays: _scheduleType == 'weekly'
? (_selectedWeekdays.toList()..sort())
: null,
),
),
);
final success = await _cubit.createJob(request);
if (!mounted) {
return;
}
if (success) {
Toast.show(
context,
context.l10n.settingsJobCreateSuccess,
type: ToastType.success,
);
context.pop(true);
}
}
}