add: AnySearch-Skill

This commit is contained in:
InfinityPacer
2026-06-21 07:39:39 +08:00
committed by jxxghp
parent e02cebe16c
commit 6b21abd547
18 changed files with 3023 additions and 116 deletions

View File

@@ -0,0 +1,87 @@
"""整理记录 AI 重新整理提示词构造。"""
from typing import Any
from app.agent.prompt import prompt_manager
def build_manual_redo_template_context(history: Any) -> dict[str, int | str]:
"""把整理历史对象映射成 System Tasks 需要的模板变量。"""
src_fileitem = history.src_fileitem or {}
dest_fileitem = history.dest_fileitem or {}
source_path = src_fileitem.get("path") if isinstance(src_fileitem, dict) else ""
source_storage = history.src_storage or "local"
if history.status and history.mode == "move":
dest_path = dest_fileitem.get("path") if isinstance(dest_fileitem, dict) else ""
if dest_path:
source_path = dest_path
source_storage = history.dest_storage or "local"
source_path = source_path or history.src or ""
season_episode = f"{history.seasons or ''}{history.episodes or ''}".strip()
return {
"history_id": history.id,
"current_status": "success" if history.status else "failed",
"recognized_title": history.title or "unknown",
"media_type": history.type or "unknown",
"category": history.category or "unknown",
"year": history.year or "unknown",
"season_episode": season_episode or "unknown",
"source_path": source_path or "unknown",
"source_storage": source_storage,
"destination_path": history.dest or "unknown",
"destination_storage": history.dest_storage or "unknown",
"transfer_mode": history.mode or "unknown",
"tmdbid": history.tmdbid or "none",
"doubanid": history.doubanid or "none",
"error_message": history.errmsg or "none",
}
def format_manual_redo_record_context(history: Any) -> str:
"""把单条整理记录格式化为批量任务可直接消费的上下文块。"""
context = build_manual_redo_template_context(history)
return "\n".join(
[
f"Record #{context['history_id']}:",
f"- Current status: {context['current_status']}",
f"- Current recognized title: {context['recognized_title']}",
f"- Media type: {context['media_type']}",
f"- Category: {context['category']}",
f"- Year: {context['year']}",
f"- Season/Episode: {context['season_episode']}",
f"- Source path: {context['source_path']}",
f"- Source storage: {context['source_storage']}",
f"- Destination path: {context['destination_path']}",
f"- Destination storage: {context['destination_storage']}",
f"- Transfer mode: {context['transfer_mode']}",
f"- Current TMDB ID: {context['tmdbid']}",
f"- Current Douban ID: {context['doubanid']}",
f"- Error message: {context['error_message']}",
]
)
def build_manual_redo_prompt(history: Any) -> str:
"""构建手动 AI 整理提示词。"""
return prompt_manager.render_system_task_message(
"manual_transfer_redo",
template_context=build_manual_redo_template_context(history),
)
def build_batch_manual_redo_template_context(histories: list[Any]) -> dict[str, int | str]:
"""把多条整理历史对象映射成批量 System Tasks 需要的模板变量。"""
return {
"history_ids_csv": ", ".join(str(history.id) for history in histories),
"history_count": len(histories),
"records_context": "\n\n".join(
format_manual_redo_record_context(history) for history in histories
),
}
def build_batch_manual_redo_prompt(histories: list[Any]) -> str:
"""构建批量手动 AI 整理提示词。"""
return prompt_manager.render_system_task_message(
"batch_manual_transfer_redo",
template_context=build_batch_manual_redo_template_context(histories),
)

View File

@@ -6,8 +6,10 @@ from pydantic import BaseModel, Field
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
from app.chain.storage import StorageChain
from app.db.transferhistory_oper import TransferHistoryOper
from app.log import logger
from app.schemas import FileItem
class DeleteTransferHistoryInput(BaseModel):
@@ -27,7 +29,11 @@ class DeleteTransferHistoryTool(MoviePilotTool):
ToolTag.Transfer,
ToolTag.Admin,
]
description: str = "Delete a specific transfer history record by its ID. This is useful when you need to remove a failed transfer record before retrying the transfer, as the system skips files that already have transfer history."
description: str = (
"Delete a specific transfer history record by its ID. For non-successful-move records with an old "
"destination file, the tool removes that media-library file before deleting the history record. This is "
"useful before retrying or re-organizing because the system skips files that already have transfer history."
)
args_schema: Type[BaseModel] = DeleteTransferHistoryInput
require_admin: bool = True
@@ -48,10 +54,21 @@ class DeleteTransferHistoryTool(MoviePilotTool):
title = history.title or "未知"
src = history.src or "未知"
status = "成功" if history.status else "失败"
deleted_dest = False
if history.dest_fileitem and not (history.status and history.mode == "move"):
dest_fileitem = FileItem(**history.dest_fileitem)
storage_chain = StorageChain()
if storage_chain.exists(dest_fileitem):
if not storage_chain.delete_media_file(dest_fileitem):
return f"错误:旧媒体库文件删除失败,路径={dest_fileitem.path}"
deleted_dest = True
await transferhis.async_delete(history_id)
return (
message = (
f"已删除整理历史记录ID={history_id},标题={title},源路径={src},状态={status}"
)
if deleted_dest:
message += ",已删除旧媒体库文件"
return message
except Exception as e:
logger.error(f"删除整理历史记录失败: {e}", exc_info=True)
return f"删除整理历史记录时发生错误: {str(e)}"

View File

@@ -8,7 +8,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
from app import schemas
from app.agent import ReplyMode, prompt_manager, agent_manager
from app.agent import ReplyMode, agent_manager
from app.agent.prompt.transfer_redo import (
build_batch_manual_redo_prompt,
build_manual_redo_prompt,
)
from app.chain.storage import StorageChain
from app.core.config import settings, global_vars
from app.core.event import eventmanager
@@ -37,86 +41,6 @@ def normalize_history_ids(history_ids: list[int]) -> list[int]:
return normalized_ids
def build_manual_redo_template_context(
history: TransferHistory,
) -> dict[str, int | str]:
"""仅负责把整理历史对象映射成 System Tasks 需要的模板变量。"""
src_fileitem = history.src_fileitem or {}
source_path = src_fileitem.get("path") if isinstance(src_fileitem, dict) else ""
source_path = source_path or history.src or ""
season_episode = f"{history.seasons or ''}{history.episodes or ''}".strip()
return {
"history_id": history.id,
"current_status": "success" if history.status else "failed",
"recognized_title": history.title or "unknown",
"media_type": history.type or "unknown",
"category": history.category or "unknown",
"year": history.year or "unknown",
"season_episode": season_episode or "unknown",
"source_path": source_path or "unknown",
"source_storage": history.src_storage or "local",
"destination_path": history.dest or "unknown",
"destination_storage": history.dest_storage or "unknown",
"transfer_mode": history.mode or "unknown",
"tmdbid": history.tmdbid or "none",
"doubanid": history.doubanid or "none",
"error_message": history.errmsg or "none",
}
def format_manual_redo_record_context(history: Any) -> str:
"""把单条整理记录格式化为批量任务可直接消费的上下文块。"""
context = build_manual_redo_template_context(history)
return "\n".join(
[
f"Record #{context['history_id']}:",
f"- Current status: {context['current_status']}",
f"- Current recognized title: {context['recognized_title']}",
f"- Media type: {context['media_type']}",
f"- Category: {context['category']}",
f"- Year: {context['year']}",
f"- Season/Episode: {context['season_episode']}",
f"- Source path: {context['source_path']}",
f"- Source storage: {context['source_storage']}",
f"- Destination path: {context['destination_path']}",
f"- Destination storage: {context['destination_storage']}",
f"- Transfer mode: {context['transfer_mode']}",
f"- Current TMDB ID: {context['tmdbid']}",
f"- Current Douban ID: {context['doubanid']}",
f"- Error message: {context['error_message']}",
]
)
def build_manual_redo_prompt(history: Any) -> str:
"""构建手动 AI 整理提示词。"""
return prompt_manager.render_system_task_message(
"manual_transfer_redo",
template_context=build_manual_redo_template_context(history),
)
def build_batch_manual_redo_template_context(
histories: list[Any],
) -> dict[str, int | str]:
"""仅负责把多条整理历史对象映射成批量 System Tasks 需要的模板变量。"""
return {
"history_ids_csv": ", ".join(str(history.id) for history in histories),
"history_count": len(histories),
"records_context": "\n\n".join(
format_manual_redo_record_context(history) for history in histories
),
}
def build_batch_manual_redo_prompt(histories: list[Any]) -> str:
"""构建批量手动 AI 整理提示词。"""
return prompt_manager.render_system_task_message(
"batch_manual_transfer_redo",
template_context=build_batch_manual_redo_template_context(histories),
)
def _start_ai_redo_task(history_id: int, prompt: str, progress_key: str):
"""在后台线程中启动单条 AI 重新整理任务,并通过 ProgressHelper 实时更新进度。"""
progress = ProgressHelper(progress_key)

View File

@@ -11,8 +11,9 @@ from pathlib import Path
from typing import Any, Optional, Dict, Union, List, Tuple
from urllib.parse import unquote, urlparse
from app.agent import ReplyMode, agent_manager, prompt_manager
from app.agent import ReplyMode, agent_manager
from app.agent.llm import AgentCapabilityManager, LLMHelper
from app.agent.prompt.transfer_redo import build_manual_redo_prompt
from app.chain import ChainBase
from app.chain.download import DownloadChain
from app.chain.media import MediaChain
@@ -872,36 +873,6 @@ class MessageChain(ChainBase):
由智能助手接管一条失败的整理记录。
"""
def __build_manual_redo_prompt(his: TransferHistory) -> str:
"""构建手动 AI 整理提示词。"""
src_fileitem = his.src_fileitem or {}
source_path = src_fileitem.get("path") if isinstance(src_fileitem, dict) else ""
source_path = source_path or his.src or ""
season_episode = f"{his.seasons or ''}{his.episodes or ''}".strip()
# 键名必须与 System Tasks.yaml 中 manual_transfer_redo 模板的占位符一致
template_context = {
"history_id": his.id,
"current_status": "success" if his.status else "failed",
"recognized_title": his.title or "unknown",
"media_type": his.type or "unknown",
"category": his.category or "unknown",
"year": his.year or "unknown",
"season_episode": season_episode or "unknown",
"source_path": source_path or "unknown",
"source_storage": his.src_storage or "local",
"destination_path": his.dest or "unknown",
"destination_storage": his.dest_storage or "unknown",
"transfer_mode": his.mode or "unknown",
"tmdbid": his.tmdbid or "none",
"doubanid": his.doubanid or "none",
"error_message": his.errmsg or "none",
}
return prompt_manager.render_system_task_message(
"manual_transfer_redo",
template_context=template_context,
)
if not settings.AI_AGENT_ENABLE:
self.post_message(
Notification(
@@ -931,7 +902,7 @@ class MessageChain(ChainBase):
)
return
redo_prompt = __build_manual_redo_prompt(history)
redo_prompt = build_manual_redo_prompt(history)
self.post_message(
Notification(