diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 6c4199be..c7617185 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -460,7 +460,8 @@ export default { botSecret: 'Bot Secret', botSecretHint: 'WebSocket secret of the WeChat Work AI bot', botChatId: 'Default Target', - botChatIdHint: 'Use user userid; for proactive group messages use group:chatid. Leave empty to notify known interacted users', + botChatIdHint: + 'Use user userid; for proactive group messages use group:chatid. Leave empty to notify known interacted users', botChatIdPlaceholder: 'userid or group:chatid', botWsUrl: 'WebSocket URL', botWsUrlHint: 'WebSocket endpoint for the WeChat Work AI bot, usually the default value', @@ -1475,8 +1476,9 @@ export default { fanartEnableHint: 'Use image data from fanart.tv', fanartLang: 'Fanart Language', fanartLangHint: 'Set language preference for Fanart images, ordered by priority when multiple selected', - recognizePluginFirst: "Prioritize Plugin Recognition", - recognizePluginFirstHint: "Prioritize calling plugins for media recognition. If a plugin matches, native recognition will be skipped", + recognizePluginFirst: 'Prioritize Plugin Recognition', + recognizePluginFirstHint: + 'Prioritize calling plugins for media recognition. If a plugin matches, native recognition will be skipped', githubProxy: 'Github Acceleration Proxy', githubProxyPlaceholder: 'Leave empty for no proxy', githubProxyHint: 'Use proxy to accelerate Github access speed', @@ -1598,7 +1600,7 @@ export default { skipDesc: 'Skip scraping, this file will not be generated', missingOnlyDesc: 'Scrape only if missing, existing file remains unchanged', overwriteDesc: 'Always scrape, existing file will be overwritten', - } + }, }, site: { siteSync: 'Site Synchronization', @@ -2842,6 +2844,7 @@ export default { actions: { aiRedo: 'Assistant Organize', aiRedoPending: 'Assistant Organizing...', + batchAiRedo: 'Assistant Batch Organize', redo: 'Reorganize', delete: 'Delete', batchRedo: 'Batch Reorganize', @@ -3265,7 +3268,8 @@ export default { infoDesc: 'Completing site authentication unlocks site capabilities and some plugin permissions. This step is optional and can also be configured later from the user menu.', selectSiteHint: 'Choose a supported auth site and fill in the required credentials for that site', - submitHint: 'When you click Next, the wizard will immediately validate against the selected auth site and save the current parameters on success.', + submitHint: + 'When you click Next, the wizard will immediately validate against the selected auth site and save the current parameters on success.', siteConfigNotExist: 'Authentication site configuration does not exist', fieldRequired: 'Please enter {name}', }, diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 8dac47fd..921d3c33 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -2799,6 +2799,7 @@ export default { actions: { aiRedo: '智能助手整理', aiRedoPending: '智能助手整理中...', + batchAiRedo: '智能助手批量整理', redo: '重新整理', delete: '删除', batchRedo: '批量重新整理', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 4af1fbab..a1cb3e0f 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -2801,6 +2801,7 @@ export default { actions: { aiRedo: '智能助手整理', aiRedoPending: '智能助手整理中...', + batchAiRedo: '智能助手批量整理', redo: '重新整理', delete: '刪除', batchRedo: '批量重新整理', diff --git a/src/views/reorganize/TransferHistoryView.vue b/src/views/reorganize/TransferHistoryView.vue index a973d9ff..3bb55d7d 100644 --- a/src/views/reorganize/TransferHistoryView.vue +++ b/src/views/reorganize/TransferHistoryView.vue @@ -59,7 +59,7 @@ const aiRedoProgressDialog = ref(false) const aiRedoProgressActive = ref(false) const aiRedoProgressText = ref(t('transferHistory.actions.aiRedoPending')) const aiRedoProgressSSE = ref(null) -const aiRedoProgressHistoryId = ref() +const aiRedoProgressHistoryIds = ref([]) // 重新整理IDS const redoIds = ref([]) @@ -374,6 +374,7 @@ async function removeSingle(deleteSrc: boolean, deleteDest: boolean) { // 批量删除记录 async function removeBatch(deleteSrc: boolean, deleteDest: boolean) { + if (hasRunningAiRedo.value) return // 关闭弹窗 deleteConfirmDialog.value = false // 总条数 @@ -409,6 +410,7 @@ async function deleteConfirmHandler(deleteSrc: boolean, deleteDest: boolean) { // 批量删除历史记录 async function removeHistoryBatch() { + if (hasRunningAiRedo.value) return if (selected.value.length === 0) return // 清空当前操作记录 @@ -421,6 +423,7 @@ async function removeHistoryBatch() { } // 批量重新整理 async function retransferBatch() { + if (hasRunningAiRedo.value) return if (selected.value.length === 0) return // 清空当前操作记录 @@ -462,15 +465,14 @@ function stopAiRedoProgress() { // AI整理完成 async function finishAiRedo(success: boolean, errorMessage?: string) { - const historyId = aiRedoProgressHistoryId.value + const historyIds = [...aiRedoProgressHistoryIds.value] + const historyIdSet = new Set(historyIds) stopAiRedoProgress() aiRedoProgressDialog.value = false - aiRedoProgressHistoryId.value = undefined - - if (historyId !== undefined) { - aiRedoIds.value = aiRedoIds.value.filter(id => id !== historyId) - } + aiRedoProgressHistoryIds.value = [] + aiRedoIds.value = aiRedoIds.value.filter(id => !historyIdSet.has(id)) + selected.value = selected.value.filter(item => !historyIdSet.has(item.id)) await fetchData() @@ -493,9 +495,14 @@ async function handleAiRedoProgressMessage(event: MessageEvent) { // 开始监听整理进度 function startAiRedoProgress(historyId: number, progressKey: string) { + startAiRedoProgressBatch([historyId], progressKey) +} + +// 开始监听批量整理进度 +function startAiRedoProgressBatch(historyIds: number[], progressKey: string) { stopAiRedoProgress() - aiRedoProgressHistoryId.value = historyId + aiRedoProgressHistoryIds.value = historyIds aiRedoProgressDialog.value = true aiRedoProgressActive.value = true aiRedoProgressText.value = t('transferHistory.actions.aiRedoPending') @@ -543,6 +550,44 @@ async function triggerAiRedo(item: TransferHistory) { } } +// 批量触发AI整理 +async function triggerBatchAiRedo() { + if (!aiAgentEnabled.value) { + $toast.error(t('transferHistory.aiRedoDisabled')) + return + } + if (hasRunningAiRedo.value) return + + const historyIds = [...new Set(selected.value.map(item => item.id))] + if (historyIds.length === 0) return + + aiRedoIds.value = [...new Set([...aiRedoIds.value, ...historyIds])] + let progressStarted = false + try { + const result: { [key: string]: any } = await api.post('history/transfer/ai-redo', { + history_ids: historyIds, + }) + + const progressKey = result.data?.progress_key + const acceptedIds = (result.data?.history_ids as number[] | undefined) ?? historyIds + + if (!result.success || !progressKey) { + $toast.error(result.message || t('transferHistory.aiRedoFailed')) + return + } + startAiRedoProgressBatch(acceptedIds, progressKey) + selected.value = selected.value.filter(item => !acceptedIds.includes(item.id)) + progressStarted = true + } catch (error) { + console.error(error) + $toast.error(t('transferHistory.aiRedoFailed')) + } finally { + if (!progressStarted) { + aiRedoIds.value = aiRedoIds.value.filter(id => !historyIds.includes(id)) + } + } +} + // 计算下拉菜单 function getDropdownItems(item: TransferHistory) { return [ @@ -645,7 +690,7 @@ const historyDynamicIcon = computed(() => (selected.value.length > 0 ? 'mdi-chev const historyDynamicMenuItems = computed(() => { if (selected.value.length === 0) return undefined - return [ + const items: Array<{ titleKey: string; icon: string; action: () => void; color?: string }> = [ { titleKey: 'dialog.transferQueue.title', icon: 'mdi-timer-sand-paused', @@ -653,22 +698,36 @@ const historyDynamicMenuItems = computed(() => { transferQueueDialog.value = true }, }, - { - titleKey: 'transferHistory.actions.batchRedo', - icon: 'mdi-redo-variant', - action: () => { - retransferBatch() - }, - }, - { - titleKey: 'transferHistory.actions.batchDelete', - icon: 'mdi-trash-can-outline', - color: 'error', - action: () => { - removeHistoryBatch() - }, - }, ] + + if (!hasRunningAiRedo.value) { + items.push( + { + titleKey: 'transferHistory.actions.batchAiRedo', + icon: 'mdi-robot-outline', + action: () => { + triggerBatchAiRedo() + }, + }, + { + titleKey: 'transferHistory.actions.batchRedo', + icon: 'mdi-redo-variant', + action: () => { + retransferBatch() + }, + }, + { + titleKey: 'transferHistory.actions.batchDelete', + icon: 'mdi-trash-can-outline', + color: 'error', + action: () => { + removeHistoryBatch() + }, + }, + ) + } + + return items }) useDynamicButton({ @@ -980,7 +1039,7 @@ onUnmounted(() => {
{ @click="removeHistoryBatch" /> { class="compact-fab compact-fab--secondary" @click="retransferBatch" /> +