| 1 | /** |
| 2 | * Agent Store - Action 处理器模块 |
| 3 | * 使用策略模式处理不同的任务结果操作 |
| 4 | * |
| 5 | * 从 public/AgentGenerator/actionHandlers.ts 移植并增强 |
| 6 | */ |
| 7 | |
| 8 | import type { ActionType, IActionContext, IMediaItem, ITaskData } from '../agent.types' |
| 9 | import type { PluginPublishItem } from '@/store/plugin/store' |
| 10 | import { driver } from 'driver.js' |
| 11 | import { MediaType } from '@/api/ai/ai.constants' |
| 12 | import { apiCreateMaterial, apiGetMaterialGroupList } from '@/api/materials/material.api' |
| 13 | import { PubType } from '@/app/config/publishConfig' |
| 14 | import { useAccountStore } from '@/store/account' |
| 15 | import { usePluginStore } from '@/store/plugin' |
| 16 | import { PluginStatus } from '@/store/plugin/types/baseTypes' |
| 17 | import { confirm } from '@/utils/ui/confirm' |
| 18 | import { toast } from '@/utils/ui/toast' |
| 19 | |
| 20 | // ============ Action Handler 接口 ============ |
| 21 | |
| 22 | /** Action Handler 接口 */ |
| 23 | export interface IActionHandler { |
| 24 | /** Action 类型 */ |
| 25 | type: ActionType |
| 26 | /** 判断是否能处理该任务 */ |
| 27 | canHandle: (taskData: ITaskData) => boolean |
| 28 | /** 执行 Action */ |
| 29 | execute: (taskData: ITaskData, context: IActionContext) => Promise<void> |
| 30 | } |
| 31 | |
| 32 | // ============ 工具函数 ============ |
| 33 | |
| 34 | /** |
| 35 | * 构建发布 URL 参数 |
| 36 | */ |
| 37 | function buildPublishQueryParams(taskData: ITaskData): URLSearchParams { |
| 38 | const params = new URLSearchParams() |
| 39 | |
| 40 | params.set('action', 'publish') |
| 41 | params.set('aiGenerated', 'true') |
| 42 | |
| 43 | // 只添加非空值 |
| 44 | if (taskData.platform) |
| 45 | params.set('platform', taskData.platform) |
| 46 | if (taskData.accountId) |
| 47 | params.set('accountId', taskData.accountId) |
| 48 | if (taskData.taskId) |
| 49 | params.set('taskId', taskData.taskId) |
| 50 | if (taskData.title) |
| 51 | params.set('title', taskData.title) |
| 52 | if (taskData.description) |
| 53 | params.set('description', taskData.description) |
| 54 | if (taskData.tags && taskData.tags.length > 0) { |
| 55 | params.set('tags', JSON.stringify(taskData.tags)) |
| 56 | } |
| 57 | if (taskData.medias && taskData.medias.length > 0) { |
| 58 | params.set('medias', JSON.stringify(taskData.medias)) |
| 59 | } |
| 60 | |
| 61 | return params |
| 62 | } |
| 63 | |
| 64 | /** |
| 65 | * 构建插件发布项 |
| 66 | */ |
| 67 | export function buildPluginPublishItem(taskData: ITaskData, account: any): PluginPublishItem { |
| 68 | const medias = taskData.medias || [] |
| 69 | const hasVideo = medias.some((m: IMediaItem) => m.type === 'VIDEO') |
| 70 | const video = hasVideo ? medias.find((m: IMediaItem) => m.type === 'VIDEO') : null |
| 71 | |
| 72 | const images = medias |
| 73 | .filter((m: IMediaItem) => m.type === 'IMAGE') |
| 74 | .map((m: IMediaItem) => ({ |
| 75 | id: '', |
| 76 | imgPath: m.url, |
| 77 | ossUrl: m.url, |
| 78 | size: 0, |
| 79 | imgUrl: m.url, |
| 80 | filename: '', |
| 81 | width: 0, |
| 82 | height: 0, |
| 83 | })) as any[] |
| 84 | |
| 85 | return { |
| 86 | account, |
| 87 | params: { |
| 88 | title: taskData.title || '', |
| 89 | des: taskData.description || '', |
| 90 | topics: taskData.tags || [], |
| 91 | video: video |
| 92 | ? ({ |
| 93 | size: 0, |
| 94 | videoUrl: video.url, |
| 95 | ossUrl: video.url, |
| 96 | filename: '', |
| 97 | width: 0, |
| 98 | height: 0, |
| 99 | duration: 0, |
| 100 | cover: { |
| 101 | id: '', |
| 102 | imgPath: video.coverUrl || video.thumbUrl || '', |
| 103 | ossUrl: video.coverUrl || video.thumbUrl, |
| 104 | size: 0, |
| 105 | imgUrl: video.coverUrl || video.thumbUrl || '', |
| 106 | filename: '', |
| 107 | width: 0, |
| 108 | height: 0, |
| 109 | }, |
| 110 | } as any) |
| 111 | : undefined, |
| 112 | images: images.length > 0 ? images : undefined, |
| 113 | option: {}, |
| 114 | }, |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | /** |
| 119 | * 显示插件引导 |
| 120 | */ |
| 121 | function showPluginGuide(t: (key: string) => string) { |
| 122 | setTimeout(() => { |
| 123 | const pluginButton = document.querySelector( |
| 124 | '[data-driver-target="plugin-button"]', |
| 125 | ) as HTMLElement |
| 126 | if (!pluginButton) { |
| 127 | console.warn('[ActionHandler] Plugin button not found') |
| 128 | return |
| 129 | } |
| 130 | |
| 131 | const driverObj = driver({ |
| 132 | showProgress: false, |
| 133 | showButtons: ['next'], |
| 134 | nextBtnText: t('aiGeneration.gotIt' as any), |
| 135 | doneBtnText: t('aiGeneration.gotIt' as any), |
| 136 | popoverOffset: 10, |
| 137 | stagePadding: 4, |
| 138 | stageRadius: 12, |
| 139 | allowClose: true, |
| 140 | smoothScroll: true, |
| 141 | steps: [ |
| 142 | { |
| 143 | element: '[data-driver-target="plugin-button"]', |
| 144 | popover: { |
| 145 | title: t('plugin.authorizePluginTitle' as any), |
| 146 | description: t('plugin.authorizePluginDescription' as any), |
| 147 | side: 'bottom', |
| 148 | align: 'start', |
| 149 | onPopoverRender: () => { |
| 150 | setTimeout(() => { |
| 151 | const nextBtn = document.querySelector( |
| 152 | '.driver-popover-next-btn', |
| 153 | ) as HTMLButtonElement |
| 154 | const doneBtn = document.querySelector( |
| 155 | '.driver-popover-done-btn', |
| 156 | ) as HTMLButtonElement |
| 157 | const btn = nextBtn || doneBtn |
| 158 | if (btn) { |
| 159 | btn.textContent = t('aiGeneration.gotIt' as any) |
| 160 | const handleClick = (e: MouseEvent) => { |
| 161 | e.preventDefault() |
| 162 | e.stopPropagation() |
| 163 | driverObj.destroy() |
| 164 | btn.removeEventListener('click', handleClick) |
| 165 | } |
| 166 | btn.addEventListener('click', handleClick) |
| 167 | } |
| 168 | }, 50) |
| 169 | }, |
| 170 | }, |
| 171 | }, |
| 172 | ], |
| 173 | onNextClick: () => { |
| 174 | driverObj.destroy() |
| 175 | return false |
| 176 | }, |
| 177 | }) |
| 178 | |
| 179 | driverObj.drive() |
| 180 | }, 1500) |
| 181 | } |
| 182 | |
| 183 | // ============ Action Handlers 实现 ============ |
| 184 | |
| 185 | /** |
| 186 | * 导航到发布页面 - 处理插件平台(xhs, douyin) |
| 187 | */ |
| 188 | const navigateToPublishPluginHandler: IActionHandler = { |
| 189 | type: 'navigateToPublish', |
| 190 | |
| 191 | canHandle: (taskData) => { |
| 192 | return ( |
| 193 | taskData.type === 'fullContent' |
| 194 | && taskData.action === 'navigateToPublish' |
| 195 | && (taskData.platform === 'xhs' || taskData.platform === 'douyin') |
| 196 | ) |
| 197 | }, |
| 198 | |
| 199 | async execute(taskData, context) { |
| 200 | const { t } = context |
| 201 | const pluginStatus = usePluginStore.getState().status |
| 202 | const isPluginReady = pluginStatus === PluginStatus.READY |
| 203 | |
| 204 | if (!isPluginReady) { |
| 205 | toast.warning(t('plugin.platformNeedsPlugin' as any)) |
| 206 | showPluginGuide(t as (key: string) => string) |
| 207 | return |
| 208 | } |
| 209 | |
| 210 | // 插件已就绪,执行发布 |
| 211 | try { |
| 212 | const accountGroupList = useAccountStore.getState().accountGroupList |
| 213 | const allAccounts = accountGroupList.reduce<any[]>((acc, group) => { |
| 214 | return [...acc, ...group.children] |
| 215 | }, []) |
| 216 | |
| 217 | // 根据 accountId 或 platform 查找目标账号 |
| 218 | let targetAccounts: any[] = [] |
| 219 | if (taskData.accountId) { |
| 220 | const targetAccount = allAccounts.find(account => account.id === taskData.accountId) |
| 221 | if (targetAccount) { |
| 222 | targetAccounts = [targetAccount] |
| 223 | } |
| 224 | else { |
| 225 | console.warn(`[ActionHandler] Account not found: ${taskData.accountId}`) |
| 226 | } |
| 227 | } |
| 228 | else { |
| 229 | targetAccounts = allAccounts.filter(account => account.type === taskData.platform) |
| 230 | } |
| 231 | |
| 232 | if (targetAccounts.length === 0) { |
| 233 | console.warn(`[ActionHandler] No accounts found for platform: ${taskData.platform}`) |
| 234 | toast.warning(t('aiGeneration.noAccountFound' as any) || '未找到可发布的账号') |
| 235 | return |
| 236 | } |
| 237 | |
| 238 | // 构建发布项 |
| 239 | const allPluginPublishItems: PluginPublishItem[] = [] |
| 240 | const platformTaskIdMap = new Map<string, string>() |
| 241 | |
| 242 | targetAccounts.forEach((account) => { |
| 243 | const publishItem = buildPluginPublishItem(taskData, account) |
| 244 | // @ts-ignore |
| 245 | allPluginPublishItems.push(publishItem) |
| 246 | |
| 247 | const requestId = `req-${Date.now()}-${Math.random().toString(36).slice(2, 10)}` |
| 248 | platformTaskIdMap.set(account.id, requestId) |
| 249 | }) |
| 250 | |
| 251 | if (allPluginPublishItems.length > 0) { |
| 252 | usePluginStore.getState().executePluginPublish({ |
| 253 | items: allPluginPublishItems, |
| 254 | platformTaskIdMap, |
| 255 | onProgress: (event) => { |
| 256 | const { stage, progress, message: progressMessage, accountId, platform } = event |
| 257 | if (stage === 'error') { |
| 258 | toast.error(progressMessage) |
| 259 | } |
| 260 | }, |
| 261 | onComplete: () => { |
| 262 | toast.info(t('plugin.publishTaskSubmitted' as any)) |
| 263 | }, |
| 264 | }) |
| 265 | } |
| 266 | } |
| 267 | catch (error: any) { |
| 268 | console.error('[ActionHandler] Plugin publish error:', error) |
| 269 | toast.error( |
| 270 | `${t('plugin.publishFailed' as any)}: ${error.message || t('aiGeneration.unknownError' as any)}`, |
| 271 | ) |
| 272 | } |
| 273 | }, |
| 274 | } |
| 275 | |
| 276 | /** |
| 277 | * 导航到发布页面 - 处理其他平台(快手等) |
| 278 | */ |
| 279 | const navigateToPublishOtherHandler: IActionHandler = { |
| 280 | type: 'navigateToPublish', |
| 281 | |
| 282 | canHandle: (taskData) => { |
| 283 | return ( |
| 284 | taskData.type === 'fullContent' |
| 285 | && taskData.action === 'navigateToPublish' |
| 286 | && taskData.platform !== 'xhs' |
| 287 | && taskData.platform !== 'douyin' |
| 288 | ) |
| 289 | }, |
| 290 | |
| 291 | async execute(taskData, context) { |
| 292 | const { router, lng } = context |
| 293 | const queryParams = buildPublishQueryParams(taskData) |
| 294 | |
| 295 | setTimeout(() => { |
| 296 | router.push(`/accounts?${queryParams.toString()}`) |
| 297 | }, 1500) |
| 298 | }, |
| 299 | } |
| 300 | |
| 301 | /** |
| 302 | * 导航到草稿箱 |
| 303 | */ |
| 304 | const navigateToDraftHandler: IActionHandler = { |
| 305 | type: 'navigateToDraft', |
| 306 | |
| 307 | canHandle: (taskData) => { |
| 308 | return taskData.type === 'fullContent' && taskData.action === 'navigateToDraft' |
| 309 | }, |
| 310 | |
| 311 | async execute(_taskData, context) { |
| 312 | const { router, lng } = context |
| 313 | |
| 314 | setTimeout(() => { |
| 315 | router.push(`/cgmaterial`) |
| 316 | }, 1500) |
| 317 | }, |
| 318 | } |
| 319 | |
| 320 | /** |
| 321 | * 保存草稿 |
| 322 | */ |
| 323 | const saveDraftHandler: IActionHandler = { |
| 324 | type: 'saveDraft', |
| 325 | |
| 326 | canHandle: (taskData) => { |
| 327 | return taskData.type === 'fullContent' && taskData.action === 'saveDraft' |
| 328 | }, |
| 329 | |
| 330 | async execute(taskData, context) { |
| 331 | const { router, lng, t } = context |
| 332 | |
| 333 | try { |
| 334 | // 转换 medias 格式 |
| 335 | const medias = taskData.medias || [] |
| 336 | const materialMediaList = medias.map((media: IMediaItem) => { |
| 337 | const pubType = media.type === MediaType.Video ? PubType.VIDEO : PubType.ImageText |
| 338 | return { |
| 339 | url: media.url, |
| 340 | type: pubType === PubType.VIDEO ? 'video' as const : pubType === PubType.ImageText ? 'img' as const : 'video' as const, |
| 341 | content: media.coverUrl || undefined, |
| 342 | } |
| 343 | }) |
| 344 | |
| 345 | // 确定封面URL |
| 346 | const coverUrl |
| 347 | = medias.find((m: IMediaItem) => m.coverUrl)?.coverUrl |
| 348 | || medias.find((m: IMediaItem) => m.type === 'IMAGE')?.url |
| 349 | || undefined |
| 350 | |
| 351 | // 获取分组列表 |
| 352 | const groupListRes = await apiGetMaterialGroupList(1, 100) |
| 353 | const groups = groupListRes?.data?.list || [] |
| 354 | |
| 355 | if (groups.length === 0) { |
| 356 | toast.warning(t('aiGeneration.noDraftGroupFound' as any)) |
| 357 | return |
| 358 | } |
| 359 | |
| 360 | // 根据 medias 类型选择默认分组 |
| 361 | const hasVideo = medias.some((m: IMediaItem) => m.type === 'VIDEO') |
| 362 | const targetGroupType = hasVideo ? PubType.VIDEO : PubType.ImageText |
| 363 | const defaultGroup = groups.find((g: any) => g.type === targetGroupType) || groups[0] |
| 364 | const finalGroupId = defaultGroup.id |
| 365 | |
| 366 | if (!finalGroupId) { |
| 367 | toast.warning(t('aiGeneration.noDraftGroup' as any)) |
| 368 | return |
| 369 | } |
| 370 | |
| 371 | // 创建草稿 |
| 372 | const createResult = await apiCreateMaterial({ |
| 373 | groupId: finalGroupId, |
| 374 | coverUrl, |
| 375 | mediaList: materialMediaList, |
| 376 | title: taskData.title || '', |
| 377 | desc: taskData.description || '', |
| 378 | type: targetGroupType, |
| 379 | }) |
| 380 | |
| 381 | if (createResult) { |
| 382 | toast.success(t('aiGeneration.saveDraftSuccess' as any)) |
| 383 | setTimeout(() => { |
| 384 | router.push(`/cgmaterial`) |
| 385 | }, 1500) |
| 386 | } |
| 387 | else { |
| 388 | toast.error(t('aiGeneration.saveDraftFailed' as any)) |
| 389 | } |
| 390 | } |
| 391 | catch (error: any) { |
| 392 | console.error('[ActionHandler] Save draft error:', error) |
| 393 | toast.error( |
| 394 | `${t('aiGeneration.saveDraftFailed' as any)}: ${error.message || t('aiGeneration.unknownError' as any)}`, |
| 395 | ) |
| 396 | } |
| 397 | }, |
| 398 | } |
| 399 | |
| 400 | /** |
| 401 | * 更新频道授权 |
| 402 | */ |
| 403 | const updateChannelHandler: IActionHandler = { |
| 404 | type: 'updateChannel', |
| 405 | |
| 406 | canHandle: (taskData) => { |
| 407 | return taskData.type === 'fullContent' && taskData.action === 'updateChannel' |
| 408 | }, |
| 409 | |
| 410 | async execute(taskData, context) { |
| 411 | const { router, lng, t } = context |
| 412 | const platform = taskData.platform |
| 413 | |
| 414 | toast.warning(t('aiGeneration.channelAuthExpired' as any)) |
| 415 | |
| 416 | confirm({ |
| 417 | title: t('aiGeneration.channelAuthExpiredTitle' as any), |
| 418 | content: t('aiGeneration.channelAuthExpiredContent' as any), |
| 419 | okText: t('aiGeneration.reauthorize' as any), |
| 420 | cancelText: t('aiGeneration.cancel' as any), |
| 421 | onOk: () => { |
| 422 | router.push(`/accounts?updateChannel=${platform}`) |
| 423 | }, |
| 424 | }) |
| 425 | }, |
| 426 | } |
| 427 | |
| 428 | /** |
| 429 | * 登录频道 |
| 430 | */ |
| 431 | const loginChannelHandler: IActionHandler = { |
| 432 | type: 'loginChannel', |
| 433 | |
| 434 | canHandle: (taskData) => { |
| 435 | return taskData.type === 'fullContent' && taskData.action === 'loginChannel' |
| 436 | }, |
| 437 | |
| 438 | async execute(taskData, context) { |
| 439 | const { router, lng, t } = context |
| 440 | const platform = taskData.platform |
| 441 | |
| 442 | toast.info(t('aiGeneration.needLoginChannel' as any)) |
| 443 | |
| 444 | confirm({ |
| 445 | title: t('aiGeneration.needLogin' as any), |
| 446 | content: t('aiGeneration.pleaseLoginChannel' as any), |
| 447 | okText: t('aiGeneration.goLogin' as any), |
| 448 | cancelText: t('aiGeneration.cancel' as any), |
| 449 | onOk: () => { |
| 450 | router.push(`/accounts?loginChannel=${platform}`) |
| 451 | }, |
| 452 | }) |
| 453 | }, |
| 454 | } |
| 455 | |
| 456 | /** |
| 457 | * 创建频道(需要先绑定账号) |
| 458 | */ |
| 459 | const createChannelHandler: IActionHandler = { |
| 460 | type: 'createChannel', |
| 461 | |
| 462 | canHandle: (taskData) => { |
| 463 | return !!(taskData.type === 'fullContent' && taskData.action === 'createChannel') |
| 464 | }, |
| 465 | |
| 466 | async execute(taskData, context) { |
| 467 | const { router, lng, t } = context |
| 468 | const platform = taskData.platform |
| 469 | const platformName = platform |
| 470 | ? platform.charAt(0).toUpperCase() + platform.slice(1) |
| 471 | : 'Platform' |
| 472 | |
| 473 | toast.warning(t('aiGeneration.needBindChannel' as any) as string) |
| 474 | |
| 475 | confirm({ |
| 476 | title: t('aiGeneration.needBindChannelTitle' as any) as string, |
| 477 | content: t('aiGeneration.needBindChannelContent' as any, { |
| 478 | platform: platformName, |
| 479 | }) as string, |
| 480 | okText: t('aiGeneration.goBind' as any) as string, |
| 481 | cancelText: undefined, // 不显示取消按钮 |
| 482 | onOk: () => { |
| 483 | router.push(`/accounts?addChannel=${platform}`) |
| 484 | }, |
| 485 | }) |
| 486 | }, |
| 487 | } |
| 488 | |
| 489 | /** |
| 490 | * 默认发布处理(无 action 时) |
| 491 | */ |
| 492 | const defaultPublishHandler: IActionHandler = { |
| 493 | type: 'navigateToPublish', |
| 494 | |
| 495 | canHandle: (taskData) => { |
| 496 | return taskData.type === 'fullContent' && !taskData.action |
| 497 | }, |
| 498 | |
| 499 | async execute(taskData, context) { |
| 500 | const { router, lng } = context |
| 501 | const queryParams = buildPublishQueryParams(taskData) |
| 502 | |
| 503 | setTimeout(() => { |
| 504 | router.push(`/accounts?${queryParams.toString()}`) |
| 505 | }, 1500) |
| 506 | }, |
| 507 | } |
| 508 | |
| 509 | // ============ Action Registry ============ |
| 510 | |
| 511 | /** 所有注册的 Action Handlers */ |
| 512 | const actionHandlers: IActionHandler[] = [ |
| 513 | navigateToPublishPluginHandler, |
| 514 | navigateToPublishOtherHandler, |
| 515 | navigateToDraftHandler, |
| 516 | saveDraftHandler, |
| 517 | updateChannelHandler, |
| 518 | loginChannelHandler, |
| 519 | createChannelHandler, |
| 520 | defaultPublishHandler, |
| 521 | ] |
| 522 | |
| 523 | /** |
| 524 | * Action 注册表 |
| 525 | * 使用策略模式管理和执行不同的 Action |
| 526 | */ |
| 527 | export const ActionRegistry = { |
| 528 | /** |
| 529 | * 注册新的 Action Handler |
| 530 | * @param handler Action Handler |
| 531 | */ |
| 532 | register(handler: IActionHandler): void { |
| 533 | // 添加到列表开头,确保新注册的优先匹配 |
| 534 | actionHandlers.unshift(handler) |
| 535 | }, |
| 536 | |
| 537 | /** |
| 538 | * 执行 Action |
| 539 | * @param taskData 任务数据 |
| 540 | * @param context Action 上下文 |
| 541 | * @returns 是否成功执行 |
| 542 | */ |
| 543 | async execute(taskData: ITaskData, context: IActionContext): Promise<boolean> { |
| 544 | // 跳过纯媒体类型 |
| 545 | if ( |
| 546 | taskData.type === 'imageOnly' |
| 547 | || taskData.type === 'videoOnly' |
| 548 | || taskData.type === 'mediaOnly' |
| 549 | ) { |
| 550 | return false |
| 551 | } |
| 552 | |
| 553 | // 查找匹配的 Handler |
| 554 | const handler = actionHandlers.find(h => h.canHandle(taskData)) |
| 555 | |
| 556 | if (handler) { |
| 557 | await handler.execute(taskData, context) |
| 558 | return true |
| 559 | } |
| 560 | |
| 561 | console.warn('[ActionRegistry] No handler found for task:', taskData) |
| 562 | return false |
| 563 | }, |
| 564 | |
| 565 | /** |
| 566 | * 批量执行 Actions(处理多个任务结果) |
| 567 | * @param taskDataList 任务数据列表 |
| 568 | * @param context Action 上下文 |
| 569 | */ |
| 570 | async executeBatch(taskDataList: ITaskData[], context: IActionContext): Promise<void> { |
| 571 | // 分离插件平台任务和其他任务 |
| 572 | const pluginTasks: ITaskData[] = [] |
| 573 | const otherTasks: ITaskData[] = [] |
| 574 | |
| 575 | taskDataList.forEach((taskData) => { |
| 576 | // 跳过纯媒体类型 |
| 577 | if ( |
| 578 | taskData.type === 'imageOnly' |
| 579 | || taskData.type === 'videoOnly' |
| 580 | || taskData.type === 'mediaOnly' |
| 581 | ) { |
| 582 | return |
| 583 | } |
| 584 | |
| 585 | if (taskData.type === 'fullContent' && taskData.action === 'navigateToPublish') { |
| 586 | if (taskData.platform === 'xhs' || taskData.platform === 'douyin') { |
| 587 | pluginTasks.push(taskData) |
| 588 | } |
| 589 | else { |
| 590 | otherTasks.push(taskData) |
| 591 | } |
| 592 | } |
| 593 | else { |
| 594 | otherTasks.push(taskData) |
| 595 | } |
| 596 | }) |
| 597 | |
| 598 | // 批量处理插件平台任务 |
| 599 | if (pluginTasks.length > 0) { |
| 600 | await ActionRegistry.executePluginBatch(pluginTasks, context) |
| 601 | } |
| 602 | |
| 603 | // 逐个处理其他任务 |
| 604 | for (const taskData of otherTasks) { |
| 605 | await ActionRegistry.execute(taskData, context) |
| 606 | } |
| 607 | }, |
| 608 | |
| 609 | /** |
| 610 | * 批量执行插件平台发布 |
| 611 | * @param pluginTasks 插件平台任务列表 |
| 612 | * @param context Action 上下文 |
| 613 | */ |
| 614 | async executePluginBatch(pluginTasks: ITaskData[], context: IActionContext): Promise<void> { |
| 615 | const pluginStatus = usePluginStore.getState().status |
| 616 | const isPluginReady = pluginStatus === PluginStatus.READY |
| 617 | const { t } = context |
| 618 | |
| 619 | if (!isPluginReady) { |
| 620 | toast.warning(t('plugin.platformNeedsPlugin' as any)) |
| 621 | showPluginGuide(t as (key: string) => string) |
| 622 | return |
| 623 | } |
| 624 | |
| 625 | try { |
| 626 | const accountGroupList = useAccountStore.getState().accountGroupList |
| 627 | const allAccounts = accountGroupList.reduce<any[]>((acc, group) => { |
| 628 | return [...acc, ...group.children] |
| 629 | }, []) |
| 630 | |
| 631 | const allPluginPublishItems: PluginPublishItem[] = [] |
| 632 | const platformTaskIdMap = new Map<string, string>() |
| 633 | |
| 634 | pluginTasks.forEach((taskData) => { |
| 635 | let targetAccounts: any[] = [] |
| 636 | if (taskData.accountId) { |
| 637 | const targetAccount = allAccounts.find(account => account.id === taskData.accountId) |
| 638 | if (targetAccount) { |
| 639 | targetAccounts = [targetAccount] |
| 640 | } |
| 641 | } |
| 642 | else { |
| 643 | targetAccounts = allAccounts.filter(account => account.type === taskData.platform) |
| 644 | } |
| 645 | |
| 646 | if (targetAccounts.length === 0) { |
| 647 | console.warn(`[ActionRegistry] No accounts found for platform: ${taskData.platform}`) |
| 648 | return |
| 649 | } |
| 650 | |
| 651 | targetAccounts.forEach((account) => { |
| 652 | const publishItem = buildPluginPublishItem(taskData, account) |
| 653 | // @ts-ignore |
| 654 | allPluginPublishItems.push(publishItem) |
| 655 | |
| 656 | const requestId = `req-${Date.now()}-${Math.random().toString(36).slice(2, 10)}` |
| 657 | platformTaskIdMap.set(account.id, requestId) |
| 658 | }) |
| 659 | }) |
| 660 | |
| 661 | if (allPluginPublishItems.length > 0) { |
| 662 | usePluginStore.getState().executePluginPublish({ |
| 663 | items: allPluginPublishItems, |
| 664 | platformTaskIdMap, |
| 665 | onProgress: (event) => { |
| 666 | const { stage, progress, message: progressMessage, accountId, platform } = event |
| 667 | |
| 668 | if (stage === 'error') { |
| 669 | toast.error(progressMessage) |
| 670 | } |
| 671 | }, |
| 672 | onComplete: () => { |
| 673 | toast.info(t('plugin.publishTaskSubmitted' as any)) |
| 674 | }, |
| 675 | }) |
| 676 | } |
| 677 | else { |
| 678 | toast.warning(t('aiGeneration.noAccountFound' as any) || '未找到可发布的账号') |
| 679 | } |
| 680 | } |
| 681 | catch (error: any) { |
| 682 | console.error('[ActionRegistry] Plugin batch publish error:', error) |
| 683 | toast.error( |
| 684 | `${t('plugin.publishFailed' as any)}: ${error.message || t('aiGeneration.unknownError' as any)}`, |
| 685 | ) |
| 686 | } |
| 687 | }, |
| 688 | |
| 689 | /** |
| 690 | * 获取所有已注册的 Action 类型 |
| 691 | */ |
| 692 | getRegisteredTypes(): ActionType[] { |
| 693 | return [...new Set(actionHandlers.map(h => h.type))] |
| 694 | }, |
| 695 | } |
| 696 | |
| 697 | export default ActionRegistry |
| 698 |