mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-28 19:47:49 +08:00
feat: add AI-powered reorganization option to transfer history records
This commit is contained in:
@@ -2787,7 +2787,12 @@ export default {
|
|||||||
loading: 'Loading...',
|
loading: 'Loading...',
|
||||||
pageSize: 'Items Per Page',
|
pageSize: 'Items Per Page',
|
||||||
pageInfo: '{begin} - {end} / {total}',
|
pageInfo: '{begin} - {end} / {total}',
|
||||||
|
aiRedoDisabled: 'Please enable the AI assistant in system settings first',
|
||||||
|
aiRedoQueued: 'Assistant organize task submitted: {title}',
|
||||||
|
aiRedoFailed: 'Failed to submit assistant organize task',
|
||||||
actions: {
|
actions: {
|
||||||
|
aiRedo: 'Assistant Organize',
|
||||||
|
aiRedoPending: 'Assistant Organizing...',
|
||||||
redo: 'Reorganize',
|
redo: 'Reorganize',
|
||||||
delete: 'Delete',
|
delete: 'Delete',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2749,7 +2749,12 @@ export default {
|
|||||||
loading: '加载中...',
|
loading: '加载中...',
|
||||||
pageSize: '每页条数',
|
pageSize: '每页条数',
|
||||||
pageInfo: '{begin} - {end} / {total}',
|
pageInfo: '{begin} - {end} / {total}',
|
||||||
|
aiRedoDisabled: '请先在系统设置中启用 AI 智能助手',
|
||||||
|
aiRedoQueued: '已提交智能助手整理任务:{title}',
|
||||||
|
aiRedoFailed: '提交智能助手整理任务失败',
|
||||||
actions: {
|
actions: {
|
||||||
|
aiRedo: '智能助手整理',
|
||||||
|
aiRedoPending: '智能助手整理中...',
|
||||||
redo: '重新整理',
|
redo: '重新整理',
|
||||||
delete: '删除',
|
delete: '删除',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2750,7 +2750,12 @@ export default {
|
|||||||
loading: '加載中...',
|
loading: '加載中...',
|
||||||
pageSize: '每頁條數',
|
pageSize: '每頁條數',
|
||||||
pageInfo: '{begin} - {end} / {total}',
|
pageInfo: '{begin} - {end} / {total}',
|
||||||
|
aiRedoDisabled: '請先在系統設置中啟用 AI 智能助手',
|
||||||
|
aiRedoQueued: '已提交智能助手整理任務:{title}',
|
||||||
|
aiRedoFailed: '提交智能助手整理任務失敗',
|
||||||
actions: {
|
actions: {
|
||||||
|
aiRedo: '智能助手整理',
|
||||||
|
aiRedoPending: '智能助手整理中...',
|
||||||
redo: '重新整理',
|
redo: '重新整理',
|
||||||
delete: '刪除',
|
delete: '刪除',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,10 +13,14 @@ import { formatFileSize } from '@/@core/utils/formatters'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { usePWA } from '@/composables/usePWA'
|
import { usePWA } from '@/composables/usePWA'
|
||||||
import { useAvailableHeight } from '@/composables/useAvailableHeight'
|
import { useAvailableHeight } from '@/composables/useAvailableHeight'
|
||||||
|
import { useGlobalSettingsStore } from '@/stores'
|
||||||
|
|
||||||
// i18n
|
// i18n
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// 全局设置
|
||||||
|
const globalSettingsStore = useGlobalSettingsStore()
|
||||||
|
|
||||||
// APP
|
// APP
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
// PWA模式检测
|
// PWA模式检测
|
||||||
@@ -44,6 +48,9 @@ const transferQueueDialog = ref(false)
|
|||||||
// 当前操作记录
|
// 当前操作记录
|
||||||
const currentHistory = ref<TransferHistory>()
|
const currentHistory = ref<TransferHistory>()
|
||||||
|
|
||||||
|
// AI整理中的记录
|
||||||
|
const aiRedoIds = ref<number[]>([])
|
||||||
|
|
||||||
// 重新整理IDS
|
// 重新整理IDS
|
||||||
const redoIds = ref<number[]>([])
|
const redoIds = ref<number[]>([])
|
||||||
const redoTargetStorage = ref<string>()
|
const redoTargetStorage = ref<string>()
|
||||||
@@ -425,32 +432,122 @@ function transferDone() {
|
|||||||
fetchData()
|
fetchData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 弹出菜单
|
// AI助手是否启用
|
||||||
const dropdownItems = ref([
|
const aiAgentEnabled = computed(() => Boolean(globalSettingsStore.globalSettings.AI_AGENT_ENABLE))
|
||||||
{
|
|
||||||
title: t('transferHistory.actions.redo'),
|
function isAiRedoing(historyId: number) {
|
||||||
value: 1,
|
return aiRedoIds.value.includes(historyId)
|
||||||
props: {
|
}
|
||||||
prependIcon: 'mdi-redo-variant',
|
|
||||||
click: (item: TransferHistory) => {
|
function buildAiRedoPrompt(item: TransferHistory) {
|
||||||
redoIds.value = [item.id]
|
const sourcePath = item.src_fileitem?.path || item.src || ''
|
||||||
redoTargetStorage.value = item.dest_storage
|
|
||||||
redoDialog.value = true
|
return [
|
||||||
|
'/ai',
|
||||||
|
'[系统任务 - 手动 AI 整理]',
|
||||||
|
'这是用户从媒体整理记录页面手动触发的一次 AI 整理请求。',
|
||||||
|
'当前目标是处理一条已经整理过或整理失败的历史记录,尤其用于修正识别错误、综艺命名不规范等自动接管不会触发的场景。',
|
||||||
|
'',
|
||||||
|
'请优先基于下面这条历史记录独立完成判断,不要依赖之前会话中的上下文。',
|
||||||
|
'',
|
||||||
|
'历史记录信息:',
|
||||||
|
`- 历史记录 ID: ${item.id}`,
|
||||||
|
`- 当前状态: ${item.status ? '成功' : '失败'}`,
|
||||||
|
`- 当前识别标题: ${item.title || '未知'}`,
|
||||||
|
`- 媒体类型: ${item.type || '未知'}`,
|
||||||
|
`- 分类: ${item.category || '未知'}`,
|
||||||
|
`- 年份: ${item.year || '未知'}`,
|
||||||
|
`- 季/集: ${item.seasons || ''}${item.episodes || ''}`.trim() || '- 季/集: 未知',
|
||||||
|
`- 源路径: ${sourcePath || '未知'}`,
|
||||||
|
`- 目标路径: ${item.dest || '未知'}`,
|
||||||
|
`- 转移方式: ${item.mode || '未知'}`,
|
||||||
|
`- 当前 TMDB ID: ${item.tmdbid || '无'}`,
|
||||||
|
`- 当前 豆瓣 ID: ${item.doubanid || '无'}`,
|
||||||
|
`- 失败原因: ${item.errmsg || '无'}`,
|
||||||
|
'',
|
||||||
|
'处理要求:',
|
||||||
|
'1. 先判断这条记录当前识别是否可信;如果当前识别明显错误,请重新识别正确媒体。',
|
||||||
|
'2. 如有需要,可使用 recognize_media、search_media、query_transfer_history 等工具辅助判断。',
|
||||||
|
'3. 一旦确认了正确的媒体 ID 和类型,请优先使用 run_slash_command 执行 /redo 命令完成重新整理。',
|
||||||
|
`4. /redo 命令格式必须是:/redo ${item.id} <tmdbid或doubanid>|电影 或 /redo ${item.id} <tmdbid或doubanid>|电视剧`,
|
||||||
|
'5. 只有在你能可靠确认媒体信息时才执行整理;如果无法确认,请停止并说明原因,不要盲目整理。',
|
||||||
|
'6. 最终只需要用中文简洁反馈处理结果。',
|
||||||
|
].join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function triggerAiRedo(item: TransferHistory) {
|
||||||
|
if (!aiAgentEnabled.value) {
|
||||||
|
$toast.error(t('transferHistory.aiRedoDisabled'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isAiRedoing(item.id)) return
|
||||||
|
|
||||||
|
aiRedoIds.value = [...aiRedoIds.value, item.id]
|
||||||
|
try {
|
||||||
|
const result: { [key: string]: any } = await api.post('message/web', {
|
||||||
|
text: buildAiRedoPrompt(item),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
$toast.error(result.message || t('transferHistory.aiRedoFailed'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$toast.success(
|
||||||
|
t('transferHistory.aiRedoQueued', {
|
||||||
|
title: item.title || sourcePathDisplay(item),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
$toast.error(t('transferHistory.aiRedoFailed'))
|
||||||
|
} finally {
|
||||||
|
aiRedoIds.value = aiRedoIds.value.filter(id => id !== item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sourcePathDisplay(item: TransferHistory) {
|
||||||
|
return item.src_fileitem?.name || item.src || `#${item.id}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDropdownItems(item: TransferHistory) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: isAiRedoing(item.id) ? t('transferHistory.actions.aiRedoPending') : t('transferHistory.actions.aiRedo'),
|
||||||
|
value: 0,
|
||||||
|
props: {
|
||||||
|
prependIcon: 'mdi-robot-outline',
|
||||||
|
disabled: !aiAgentEnabled.value || isAiRedoing(item.id),
|
||||||
|
click: () => {
|
||||||
|
triggerAiRedo(item)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
title: t('transferHistory.actions.redo'),
|
||||||
title: t('transferHistory.actions.delete'),
|
value: 1,
|
||||||
value: 2,
|
props: {
|
||||||
props: {
|
prependIcon: 'mdi-redo-variant',
|
||||||
prependIcon: 'mdi-trash-can-outline',
|
click: () => {
|
||||||
color: 'error',
|
redoIds.value = [item.id]
|
||||||
click: (item: TransferHistory) => {
|
redoTargetStorage.value = item.dest_storage
|
||||||
removeHistory(item)
|
redoDialog.value = true
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
])
|
title: t('transferHistory.actions.delete'),
|
||||||
|
value: 2,
|
||||||
|
props: {
|
||||||
|
prependIcon: 'mdi-trash-can-outline',
|
||||||
|
color: 'error',
|
||||||
|
click: () => {
|
||||||
|
removeHistory(item)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// 添加url参数
|
// 添加url参数
|
||||||
function addUrlQuery(url: string, name: string, value: any) {
|
function addUrlQuery(url: string, name: string, value: any) {
|
||||||
@@ -642,10 +739,11 @@ onMounted(() => {
|
|||||||
<VMenu activator="parent" close-on-content-click>
|
<VMenu activator="parent" close-on-content-click>
|
||||||
<VList>
|
<VList>
|
||||||
<VListItem
|
<VListItem
|
||||||
v-for="(menu, i) in dropdownItems"
|
v-for="(menu, i) in getDropdownItems(item)"
|
||||||
:key="i"
|
:key="i"
|
||||||
:base-color="menu.props.color"
|
:base-color="menu.props.color"
|
||||||
@click="menu.props.click(item)"
|
:disabled="menu.props.disabled"
|
||||||
|
@click="menu.props.click()"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon :icon="menu.props.prependIcon" />
|
<VIcon :icon="menu.props.prependIcon" />
|
||||||
@@ -728,10 +826,11 @@ onMounted(() => {
|
|||||||
<VMenu activator="parent" close-on-content-click>
|
<VMenu activator="parent" close-on-content-click>
|
||||||
<VList>
|
<VList>
|
||||||
<VListItem
|
<VListItem
|
||||||
v-for="(menu, i) in dropdownItems"
|
v-for="(menu, i) in getDropdownItems(item)"
|
||||||
:key="i"
|
:key="i"
|
||||||
:base-color="menu.props.color"
|
:base-color="menu.props.color"
|
||||||
@click="menu.props.click(item)"
|
:disabled="menu.props.disabled"
|
||||||
|
@click="menu.props.click()"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon :icon="menu.props.prependIcon" />
|
<VIcon :icon="menu.props.prependIcon" />
|
||||||
|
|||||||
Reference in New Issue
Block a user