diff --git a/src/TelegramPanel.Core/Services/BatchTaskManagementService.cs b/src/TelegramPanel.Core/Services/BatchTaskManagementService.cs index 0f398c1..5d78d49 100644 --- a/src/TelegramPanel.Core/Services/BatchTaskManagementService.cs +++ b/src/TelegramPanel.Core/Services/BatchTaskManagementService.cs @@ -68,6 +68,18 @@ public class BatchTaskManagementService } } + public async Task UpdateTaskDraftAsync(int taskId, int total, string? config) + { + var task = await _batchTaskRepository.GetByIdAsync(taskId); + if (task != null) + { + if (total < 0) total = 0; + task.Total = total; + task.Config = config; + await _batchTaskRepository.UpdateAsync(task); + } + } + public async Task StartTaskAsync(int taskId) { var task = await _batchTaskRepository.GetByIdAsync(taskId); diff --git a/src/TelegramPanel.Web/Components/Dialogs/EditUserChatActiveTaskDialog.razor b/src/TelegramPanel.Web/Components/Dialogs/EditUserChatActiveTaskDialog.razor new file mode 100644 index 0000000..0248740 --- /dev/null +++ b/src/TelegramPanel.Web/Components/Dialogs/EditUserChatActiveTaskDialog.razor @@ -0,0 +1,133 @@ +@namespace TelegramPanel.Web.Components.Dialogs +@inject BatchTaskManagementService TaskManagement +@inject ISnackbar Snackbar +@inject IDialogService DialogService +@using TelegramPanel.Core.BatchTasks +@using TelegramPanel.Data.Entities +@using TelegramPanel.Modules +@using Microsoft.AspNetCore.Components + + + + @if (loading) + { + + + 正在加载任务配置... + + } + else if (task == null) + { + + 任务不存在或已被删除。 + + } + else if (!string.Equals(task.TaskType, BatchTaskTypes.UserChatActive, StringComparison.OrdinalIgnoreCase)) + { + + 仅支持编辑“账号持续活跃”任务。 + + } + else if (string.Equals(task.Status, "running", StringComparison.OrdinalIgnoreCase)) + { + + 该任务正在执行中,请先取消后再编辑。 + + } + else + { + + 编辑后会更新当前任务配置。建议编辑完成后使用“重新运行”创建新任务执行。 + + + + + @if (!string.IsNullOrWhiteSpace(draft.ValidationError)) + { + @draft.ValidationError + } + } + + + 关闭 + + @if (saving) + { + + 保存中... + } + else + { + 保存配置 + } + + + + +@code { + [CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!; + [Parameter] public int TaskId { get; set; } + + private BatchTask? task; + private bool loading = true; + private bool saving; + private ModuleTaskDraft draft = new(0, null, false, null); + + private bool CanSave => + task != null + && string.Equals(task.TaskType, BatchTaskTypes.UserChatActive, StringComparison.OrdinalIgnoreCase) + && !string.Equals(task.Status, "running", StringComparison.OrdinalIgnoreCase) + && draft.CanSubmit + && !string.IsNullOrWhiteSpace(draft.Config); + + protected override async Task OnInitializedAsync() + { + try + { + if (TaskId > 0) + task = await TaskManagement.GetTaskAsync(TaskId); + } + finally + { + loading = false; + } + } + + private void OnDraftChanged(ModuleTaskDraft value) + { + draft = value; + } + + private async Task Save() + { + if (!CanSave || task == null || saving) + return; + + bool? confirm = await DialogService.ShowMessageBox( + "确认保存", + $"将更新任务 #{task.Id} 的配置,是否继续?", + yesText: "保存", cancelText: "取消"); + + if (confirm != true) + return; + + saving = true; + try + { + var config = draft.Config?.Trim(); + await TaskManagement.UpdateTaskDraftAsync(task.Id, draft.Total < 0 ? 0 : draft.Total, config); + Snackbar.Add($"任务 #{task.Id} 配置已更新", Severity.Success); + MudDialog.Close(DialogResult.Ok(task.Id)); + } + catch (Exception ex) + { + Snackbar.Add($"保存失败:{ex.Message}", Severity.Error); + } + finally + { + saving = false; + } + } + + private void Cancel() => MudDialog.Close(); +} diff --git a/src/TelegramPanel.Web/Components/Dialogs/UserChatActiveTaskEditor.razor b/src/TelegramPanel.Web/Components/Dialogs/UserChatActiveTaskEditor.razor index 0fc7502..a775373 100644 --- a/src/TelegramPanel.Web/Components/Dialogs/UserChatActiveTaskEditor.razor +++ b/src/TelegramPanel.Web/Components/Dialogs/UserChatActiveTaskEditor.razor @@ -85,9 +85,11 @@ @code { [Parameter] public ModuleTaskDraft Draft { get; set; } [Parameter] public EventCallback DraftChanged { get; set; } + [Parameter] public string? InitialConfigJson { get; set; } private List categories = new(); private bool loading = true; + private bool initializedFromConfig; private int selectedCategoryId; private string targetsText = ""; @@ -102,6 +104,8 @@ protected override async Task OnInitializedAsync() { + ApplyInitialConfigIfNeeded(); + try { categories = (await CategoryManagement.GetAllCategoriesAsync()) @@ -119,6 +123,41 @@ } } + private void ApplyInitialConfigIfNeeded() + { + if (initializedFromConfig) + return; + + initializedFromConfig = true; + + var raw = (InitialConfigJson ?? string.Empty).Trim(); + if (raw.Length == 0) + return; + + try + { + var cfg = JsonSerializer.Deserialize(raw); + if (cfg == null) + return; + + if (cfg.CategoryId > 0) + selectedCategoryId = cfg.CategoryId; + + targetsText = string.Join(Environment.NewLine, cfg.Targets ?? new List()); + dictionaryText = string.Join(Environment.NewLine, cfg.Dictionary ?? new List()); + delayMinMs = cfg.DelayMinMs; + delayMaxMs = cfg.DelayMaxMs; + maxMessages = cfg.MaxMessages; + accountMode = NormalizeMode(cfg.AccountMode); + targetMode = NormalizeMode(cfg.TargetMode); + messageMode = NormalizeMode(cfg.MessageMode); + } + catch + { + // 忽略旧配置解析失败,回退到默认值。 + } + } + private async Task OnInputsChanged() { await PushDraftAsync(); @@ -209,6 +248,13 @@ || string.Equals(value, UserChatActiveTaskModes.Queue, StringComparison.OrdinalIgnoreCase); } + private static string NormalizeMode(string? value) + { + return string.Equals(value, UserChatActiveTaskModes.Queue, StringComparison.OrdinalIgnoreCase) + ? UserChatActiveTaskModes.Queue + : UserChatActiveTaskModes.Random; + } + private static List ParseTargetLines(string? text) { if (string.IsNullOrWhiteSpace(text)) diff --git a/src/TelegramPanel.Web/Components/Pages/Tasks.razor b/src/TelegramPanel.Web/Components/Pages/Tasks.razor index f81c9aa..517ea0e 100644 --- a/src/TelegramPanel.Web/Components/Pages/Tasks.razor +++ b/src/TelegramPanel.Web/Components/Pages/Tasks.razor @@ -90,6 +90,11 @@ + @if (CanEditUserChatActiveTask(context)) + { + + } @if (IsActiveStatus(GetDisplayStatus(context))) { + @if (CanEditUserChatActiveTask(context)) + { + + } + @if (CanRerunUserChatActiveTask(context)) + { + + } @@ -398,6 +413,18 @@ private static bool IsUserChatActiveTask(string taskType) => taskType is BatchTaskTypes.UserChatActive; + private static bool CanEditUserChatActiveTask(BatchTask task) + { + return IsUserChatActiveTask(task.TaskType) + && !string.Equals(task.Status, "running", StringComparison.OrdinalIgnoreCase); + } + + private static bool CanRerunUserChatActiveTask(BatchTask task) + { + return IsUserChatActiveTask(task.TaskType) + && !string.Equals(task.Status, "running", StringComparison.OrdinalIgnoreCase); + } + private static List ExtractBotAdminFailureLines(BatchTask task) { var lines = new List(); @@ -632,4 +659,125 @@ if (result is { Canceled: false }) await LoadTasks(); } + + private async Task OpenEditUserChatActiveTask(BatchTask task) + { + if (!CanEditUserChatActiveTask(task)) + { + Snackbar.Add("该任务当前不可编辑", Severity.Warning); + return; + } + + var parameters = new DialogParameters + { + ["TaskId"] = task.Id + }; + var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Large, FullWidth = true }; + var dialog = DialogService.Show($"编辑持续任务 #{task.Id}", parameters, options)!; + var result = await dialog.Result; + if (result is { Canceled: false }) + await LoadTasks(); + } + + private async Task RerunUserChatActiveTask(BatchTask task) + { + if (!CanRerunUserChatActiveTask(task)) + { + Snackbar.Add("该任务当前不可重新运行", Severity.Warning); + return; + } + + bool? confirm = await DialogService.ShowMessageBox( + "确认重新运行", + $"将基于任务 #{task.Id} 的配置创建一个新任务,是否继续?", + yesText: "继续", cancelText: "取消"); + + if (confirm != true) + return; + + try + { + var rerunConfig = BuildRerunConfig(task.Config); + var total = rerunConfig.MaxMessages > 0 ? rerunConfig.MaxMessages : 0; + var configJson = JsonSerializer.Serialize(rerunConfig, new JsonSerializerOptions { WriteIndented = true }); + + var created = await TaskManagement.CreateTaskAsync(new BatchTask + { + TaskType = BatchTaskTypes.UserChatActive, + Total = total, + Completed = 0, + Failed = 0, + Config = configJson + }); + + Snackbar.Add($"已创建重跑任务 #{created.Id}", Severity.Success); + await LoadTasks(); + } + catch (Exception ex) + { + Snackbar.Add($"重新运行失败:{ex.Message}", Severity.Error); + } + } + + private static UserChatActiveTaskConfig BuildRerunConfig(string? rawConfig) + { + var raw = (rawConfig ?? string.Empty).Trim(); + if (raw.Length == 0) + throw new InvalidOperationException("任务配置为空,无法重新运行"); + + UserChatActiveTaskConfig cfg; + try + { + cfg = JsonSerializer.Deserialize(raw) + ?? throw new InvalidOperationException("任务配置解析结果为空"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"任务配置 JSON 无效:{ex.Message}"); + } + + if (cfg.CategoryId <= 0) + throw new InvalidOperationException("任务缺少账号分类,无法重新运行"); + + cfg.Targets = (cfg.Targets ?? new List()) + .Select(x => (x ?? string.Empty).Trim()) + .Where(x => x.Length > 0) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + cfg.Dictionary = (cfg.Dictionary ?? new List()) + .Select(x => (x ?? string.Empty).Trim()) + .Where(x => x.Length > 0) + .ToList(); + + if (cfg.Targets.Count == 0) + throw new InvalidOperationException("任务缺少目标群组/频道,无法重新运行"); + + if (cfg.Dictionary.Count == 0) + throw new InvalidOperationException("任务缺少词典消息,无法重新运行"); + + if (cfg.DelayMinMs < 0) cfg.DelayMinMs = 0; + if (cfg.DelayMaxMs < 0) cfg.DelayMaxMs = 0; + if (cfg.DelayMinMs > 600000) cfg.DelayMinMs = 600000; + if (cfg.DelayMaxMs > 600000) cfg.DelayMaxMs = 600000; + if (cfg.DelayMaxMs < cfg.DelayMinMs) cfg.DelayMaxMs = cfg.DelayMinMs; + if (cfg.MaxMessages < 0) cfg.MaxMessages = 0; + + cfg.AccountMode = NormalizeModeValue(cfg.AccountMode); + cfg.TargetMode = NormalizeModeValue(cfg.TargetMode); + cfg.MessageMode = NormalizeModeValue(cfg.MessageMode); + + cfg.Canceled = false; + cfg.Error = null; + cfg.RecentFailures = new List(); + + return cfg; + } + + private static string NormalizeModeValue(string? mode) + { + return string.Equals((mode ?? string.Empty).Trim(), UserChatActiveTaskModes.Queue, StringComparison.OrdinalIgnoreCase) + ? UserChatActiveTaskModes.Queue + : UserChatActiveTaskModes.Random; + } }