mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 23:56:42 +08:00
feat: add batch AI reorganization support to Transfer History view
This commit is contained in:
@@ -460,7 +460,8 @@ export default {
|
|||||||
botSecret: 'Bot Secret',
|
botSecret: 'Bot Secret',
|
||||||
botSecretHint: 'WebSocket secret of the WeChat Work AI bot',
|
botSecretHint: 'WebSocket secret of the WeChat Work AI bot',
|
||||||
botChatId: 'Default Target',
|
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',
|
botChatIdPlaceholder: 'userid or group:chatid',
|
||||||
botWsUrl: 'WebSocket URL',
|
botWsUrl: 'WebSocket URL',
|
||||||
botWsUrlHint: 'WebSocket endpoint for the WeChat Work AI bot, usually the default value',
|
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',
|
fanartEnableHint: 'Use image data from fanart.tv',
|
||||||
fanartLang: 'Fanart Language',
|
fanartLang: 'Fanart Language',
|
||||||
fanartLangHint: 'Set language preference for Fanart images, ordered by priority when multiple selected',
|
fanartLangHint: 'Set language preference for Fanart images, ordered by priority when multiple selected',
|
||||||
recognizePluginFirst: "Prioritize Plugin Recognition",
|
recognizePluginFirst: 'Prioritize Plugin Recognition',
|
||||||
recognizePluginFirstHint: "Prioritize calling plugins for media recognition. If a plugin matches, native recognition will be skipped",
|
recognizePluginFirstHint:
|
||||||
|
'Prioritize calling plugins for media recognition. If a plugin matches, native recognition will be skipped',
|
||||||
githubProxy: 'Github Acceleration Proxy',
|
githubProxy: 'Github Acceleration Proxy',
|
||||||
githubProxyPlaceholder: 'Leave empty for no proxy',
|
githubProxyPlaceholder: 'Leave empty for no proxy',
|
||||||
githubProxyHint: 'Use proxy to accelerate Github access speed',
|
githubProxyHint: 'Use proxy to accelerate Github access speed',
|
||||||
@@ -1598,7 +1600,7 @@ export default {
|
|||||||
skipDesc: 'Skip scraping, this file will not be generated',
|
skipDesc: 'Skip scraping, this file will not be generated',
|
||||||
missingOnlyDesc: 'Scrape only if missing, existing file remains unchanged',
|
missingOnlyDesc: 'Scrape only if missing, existing file remains unchanged',
|
||||||
overwriteDesc: 'Always scrape, existing file will be overwritten',
|
overwriteDesc: 'Always scrape, existing file will be overwritten',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
site: {
|
site: {
|
||||||
siteSync: 'Site Synchronization',
|
siteSync: 'Site Synchronization',
|
||||||
@@ -2842,6 +2844,7 @@ export default {
|
|||||||
actions: {
|
actions: {
|
||||||
aiRedo: 'Assistant Organize',
|
aiRedo: 'Assistant Organize',
|
||||||
aiRedoPending: 'Assistant Organizing...',
|
aiRedoPending: 'Assistant Organizing...',
|
||||||
|
batchAiRedo: 'Assistant Batch Organize',
|
||||||
redo: 'Reorganize',
|
redo: 'Reorganize',
|
||||||
delete: 'Delete',
|
delete: 'Delete',
|
||||||
batchRedo: 'Batch Reorganize',
|
batchRedo: 'Batch Reorganize',
|
||||||
@@ -3265,7 +3268,8 @@ export default {
|
|||||||
infoDesc:
|
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.',
|
'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',
|
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',
|
siteConfigNotExist: 'Authentication site configuration does not exist',
|
||||||
fieldRequired: 'Please enter {name}',
|
fieldRequired: 'Please enter {name}',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2799,6 +2799,7 @@ export default {
|
|||||||
actions: {
|
actions: {
|
||||||
aiRedo: '智能助手整理',
|
aiRedo: '智能助手整理',
|
||||||
aiRedoPending: '智能助手整理中...',
|
aiRedoPending: '智能助手整理中...',
|
||||||
|
batchAiRedo: '智能助手批量整理',
|
||||||
redo: '重新整理',
|
redo: '重新整理',
|
||||||
delete: '删除',
|
delete: '删除',
|
||||||
batchRedo: '批量重新整理',
|
batchRedo: '批量重新整理',
|
||||||
|
|||||||
@@ -2801,6 +2801,7 @@ export default {
|
|||||||
actions: {
|
actions: {
|
||||||
aiRedo: '智能助手整理',
|
aiRedo: '智能助手整理',
|
||||||
aiRedoPending: '智能助手整理中...',
|
aiRedoPending: '智能助手整理中...',
|
||||||
|
batchAiRedo: '智能助手批量整理',
|
||||||
redo: '重新整理',
|
redo: '重新整理',
|
||||||
delete: '刪除',
|
delete: '刪除',
|
||||||
batchRedo: '批量重新整理',
|
batchRedo: '批量重新整理',
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const aiRedoProgressDialog = ref(false)
|
|||||||
const aiRedoProgressActive = ref(false)
|
const aiRedoProgressActive = ref(false)
|
||||||
const aiRedoProgressText = ref(t('transferHistory.actions.aiRedoPending'))
|
const aiRedoProgressText = ref(t('transferHistory.actions.aiRedoPending'))
|
||||||
const aiRedoProgressSSE = ref<any>(null)
|
const aiRedoProgressSSE = ref<any>(null)
|
||||||
const aiRedoProgressHistoryId = ref<number>()
|
const aiRedoProgressHistoryIds = ref<number[]>([])
|
||||||
|
|
||||||
// 重新整理IDS
|
// 重新整理IDS
|
||||||
const redoIds = ref<number[]>([])
|
const redoIds = ref<number[]>([])
|
||||||
@@ -374,6 +374,7 @@ async function removeSingle(deleteSrc: boolean, deleteDest: boolean) {
|
|||||||
|
|
||||||
// 批量删除记录
|
// 批量删除记录
|
||||||
async function removeBatch(deleteSrc: boolean, deleteDest: boolean) {
|
async function removeBatch(deleteSrc: boolean, deleteDest: boolean) {
|
||||||
|
if (hasRunningAiRedo.value) return
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
deleteConfirmDialog.value = false
|
deleteConfirmDialog.value = false
|
||||||
// 总条数
|
// 总条数
|
||||||
@@ -409,6 +410,7 @@ async function deleteConfirmHandler(deleteSrc: boolean, deleteDest: boolean) {
|
|||||||
|
|
||||||
// 批量删除历史记录
|
// 批量删除历史记录
|
||||||
async function removeHistoryBatch() {
|
async function removeHistoryBatch() {
|
||||||
|
if (hasRunningAiRedo.value) return
|
||||||
if (selected.value.length === 0) return
|
if (selected.value.length === 0) return
|
||||||
|
|
||||||
// 清空当前操作记录
|
// 清空当前操作记录
|
||||||
@@ -421,6 +423,7 @@ async function removeHistoryBatch() {
|
|||||||
}
|
}
|
||||||
// 批量重新整理
|
// 批量重新整理
|
||||||
async function retransferBatch() {
|
async function retransferBatch() {
|
||||||
|
if (hasRunningAiRedo.value) return
|
||||||
if (selected.value.length === 0) return
|
if (selected.value.length === 0) return
|
||||||
|
|
||||||
// 清空当前操作记录
|
// 清空当前操作记录
|
||||||
@@ -462,15 +465,14 @@ function stopAiRedoProgress() {
|
|||||||
|
|
||||||
// AI整理完成
|
// AI整理完成
|
||||||
async function finishAiRedo(success: boolean, errorMessage?: string) {
|
async function finishAiRedo(success: boolean, errorMessage?: string) {
|
||||||
const historyId = aiRedoProgressHistoryId.value
|
const historyIds = [...aiRedoProgressHistoryIds.value]
|
||||||
|
const historyIdSet = new Set(historyIds)
|
||||||
|
|
||||||
stopAiRedoProgress()
|
stopAiRedoProgress()
|
||||||
aiRedoProgressDialog.value = false
|
aiRedoProgressDialog.value = false
|
||||||
aiRedoProgressHistoryId.value = undefined
|
aiRedoProgressHistoryIds.value = []
|
||||||
|
aiRedoIds.value = aiRedoIds.value.filter(id => !historyIdSet.has(id))
|
||||||
if (historyId !== undefined) {
|
selected.value = selected.value.filter(item => !historyIdSet.has(item.id))
|
||||||
aiRedoIds.value = aiRedoIds.value.filter(id => id !== historyId)
|
|
||||||
}
|
|
||||||
|
|
||||||
await fetchData()
|
await fetchData()
|
||||||
|
|
||||||
@@ -493,9 +495,14 @@ async function handleAiRedoProgressMessage(event: MessageEvent) {
|
|||||||
|
|
||||||
// 开始监听整理进度
|
// 开始监听整理进度
|
||||||
function startAiRedoProgress(historyId: number, progressKey: string) {
|
function startAiRedoProgress(historyId: number, progressKey: string) {
|
||||||
|
startAiRedoProgressBatch([historyId], progressKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始监听批量整理进度
|
||||||
|
function startAiRedoProgressBatch(historyIds: number[], progressKey: string) {
|
||||||
stopAiRedoProgress()
|
stopAiRedoProgress()
|
||||||
|
|
||||||
aiRedoProgressHistoryId.value = historyId
|
aiRedoProgressHistoryIds.value = historyIds
|
||||||
aiRedoProgressDialog.value = true
|
aiRedoProgressDialog.value = true
|
||||||
aiRedoProgressActive.value = true
|
aiRedoProgressActive.value = true
|
||||||
aiRedoProgressText.value = t('transferHistory.actions.aiRedoPending')
|
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) {
|
function getDropdownItems(item: TransferHistory) {
|
||||||
return [
|
return [
|
||||||
@@ -645,7 +690,7 @@ const historyDynamicIcon = computed(() => (selected.value.length > 0 ? 'mdi-chev
|
|||||||
const historyDynamicMenuItems = computed(() => {
|
const historyDynamicMenuItems = computed(() => {
|
||||||
if (selected.value.length === 0) return undefined
|
if (selected.value.length === 0) return undefined
|
||||||
|
|
||||||
return [
|
const items: Array<{ titleKey: string; icon: string; action: () => void; color?: string }> = [
|
||||||
{
|
{
|
||||||
titleKey: 'dialog.transferQueue.title',
|
titleKey: 'dialog.transferQueue.title',
|
||||||
icon: 'mdi-timer-sand-paused',
|
icon: 'mdi-timer-sand-paused',
|
||||||
@@ -653,22 +698,36 @@ const historyDynamicMenuItems = computed(() => {
|
|||||||
transferQueueDialog.value = true
|
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({
|
useDynamicButton({
|
||||||
@@ -980,7 +1039,7 @@ onUnmounted(() => {
|
|||||||
<Teleport to="body" v-if="!appMode && route.path === '/history'">
|
<Teleport to="body" v-if="!appMode && route.path === '/history'">
|
||||||
<div v-if="isRefreshed" class="compact-fab-stack compact-fab-stack--history">
|
<div v-if="isRefreshed" class="compact-fab-stack compact-fab-stack--history">
|
||||||
<VFab
|
<VFab
|
||||||
v-if="selected.length > 0"
|
v-if="selected.length > 0 && !hasRunningAiRedo"
|
||||||
icon="mdi-trash-can-outline"
|
icon="mdi-trash-can-outline"
|
||||||
color="warning"
|
color="warning"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
@@ -989,7 +1048,7 @@ onUnmounted(() => {
|
|||||||
@click="removeHistoryBatch"
|
@click="removeHistoryBatch"
|
||||||
/>
|
/>
|
||||||
<VFab
|
<VFab
|
||||||
v-if="selected.length > 0"
|
v-if="selected.length > 0 && !hasRunningAiRedo"
|
||||||
icon="mdi-redo-variant"
|
icon="mdi-redo-variant"
|
||||||
color="success"
|
color="success"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
@@ -997,6 +1056,15 @@ onUnmounted(() => {
|
|||||||
class="compact-fab compact-fab--secondary"
|
class="compact-fab compact-fab--secondary"
|
||||||
@click="retransferBatch"
|
@click="retransferBatch"
|
||||||
/>
|
/>
|
||||||
|
<VFab
|
||||||
|
v-if="selected.length > 0 && !hasRunningAiRedo"
|
||||||
|
icon="mdi-robot-outline"
|
||||||
|
color="info"
|
||||||
|
variant="tonal"
|
||||||
|
appear
|
||||||
|
class="compact-fab compact-fab--secondary"
|
||||||
|
@click="triggerBatchAiRedo"
|
||||||
|
/>
|
||||||
<VFab
|
<VFab
|
||||||
icon="mdi-timer-sand-paused"
|
icon="mdi-timer-sand-paused"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|||||||
Reference in New Issue
Block a user