Files
w 08e66ccc02 发布(0.7.0): 修复问题并增强技能与工具体验
1. 解决多个 issue 反馈问题,修复已知 Bug
2. 优化前端界面与交互体验
3. 优化 tool 调用链路,较此前版本节省约 70% token 用量
4. 增加模型思考控制开关,提升整体响应体感
5. 新增 find-skills,全面接入腾讯云 SkillsHub,支持通过对话进行 skills 管理
6. 新增 ima-knowledge-base、ima-notes,全面接入 IMA 知识库和笔记,支持知识库与笔记内容管理和写入
7. 优化 README,并补充 0.7.0 发布说明
8. 发布说明:https://654321.ai/docs/releases/v0.7.0
2026-03-29 22:37:11 +08:00

320 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""截图工具
提供跨平台截图功能:
- 桌面截图(使用 mss 库)
- 网页截图(使用 playwright 库)
"""
import asyncio
import base64
import os
import platform
from pathlib import Path
from typing import Any, Dict, Literal
from loguru import logger
from backend.modules.tools.base import Tool
class ScreenshotTool(Tool):
"""通用截图工具 - 支持桌面截图和网页截图,跨平台兼容"""
def __init__(
self,
workspace: Path,
default_output_dir: str = "screenshots",
max_width: int = 1920,
max_height: int = 1080,
):
"""
初始化截图工具
Args:
workspace: 工作空间根目录
default_output_dir: 默认输出目录(相对于工作空间)
max_width: 网页截图最大宽度
max_height: 网页截图最大高度
"""
self.workspace = workspace.resolve()
self.default_output_dir = default_output_dir
self.max_width = max_width
self.max_height = max_height
logger.debug(
f"ScreenshotTool initialized: workspace={self.workspace}, "
f"output_dir={default_output_dir}"
)
@property
def name(self) -> str:
return "screenshot"
@property
def description(self) -> str:
return "Capture a desktop or webpage screenshot."
@property
def parameters(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": ["desktop", "webpage"],
"description": "Capture mode.",
},
"url": {
"type": "string",
"description": "Web page URL.",
},
"output_path": {
"type": "string",
"description": "Output path.",
},
"monitor": {
"type": "integer",
"description": "Monitor index.",
"minimum": 0,
},
"full_page": {
"type": "boolean",
"description": "Capture full page.",
},
"viewport_width": {
"type": "integer",
"description": "Viewport width.",
"minimum": 320,
"maximum": 3840,
},
"viewport_height": {
"type": "integer",
"description": "Viewport height.",
"minimum": 240,
"maximum": 2160,
},
"wait_time": {
"type": "integer",
"description": "Wait ms.",
"minimum": 0,
"maximum": 30000,
},
"timeout": {
"type": "integer",
"description": "Timeout ms.",
"minimum": 5000,
"maximum": 120000,
},
},
"required": ["mode"],
}
async def execute(self, **kwargs: Any) -> str:
"""
执行截图
Args:
**kwargs: 截图参数
- mode (str): 截图模式(desktop 或 webpage)
- url (str): 网页 URL(webpage 模式必需)
- output_path (str): 输出文件路径(可选)
- monitor (int): 显示器编号(desktop 模式,可选)
- full_page (bool): 是否全页截图(webpage 模式,可选)
- viewport_width (int): 视口宽度(webpage 模式,可选)
- viewport_height (int): 视口高度(webpage 模式,可选)
- wait_time (int): 等待时间(webpage 模式,可选)
Returns:
str: 截图结果信息或错误信息
"""
mode = kwargs.get("mode", "")
if not mode:
return "Error: mode parameter is required (desktop or webpage)"
if mode == "desktop":
return await self._capture_desktop(**kwargs)
elif mode == "webpage":
return await self._capture_webpage(**kwargs)
else:
return f"Error: Invalid mode '{mode}'. Must be 'desktop' or 'webpage'"
async def _capture_desktop(self, **kwargs: Any) -> str:
"""
捕获桌面截图
使用 mss 库进行跨平台桌面截图。
Args:
**kwargs: 截图参数
Returns:
str: 截图结果信息或错误信息
"""
try:
import mss
import mss.tools
except ImportError:
return (
"Error: mss library not installed. "
"Install it with: pip install mss"
)
monitor_num = kwargs.get("monitor", 0)
output_path = kwargs.get("output_path")
try:
with mss.mss() as sct:
# 获取显示器信息
monitors = sct.monitors
if monitor_num < 0 or monitor_num >= len(monitors):
return (
f"Error: Invalid monitor number {monitor_num}. "
f"Available monitors: 0-{len(monitors) - 1}"
)
# 选择显示器(0 表示所有显示器)
monitor = monitors[monitor_num]
logger.info(
f"捕获桌面截图: monitor={monitor_num}, "
f"size={monitor['width']}x{monitor['height']}"
)
# 截图
screenshot = sct.grab(monitor)
# 生成输出路径
if not output_path:
from datetime import datetime
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = f"{self.default_output_dir}/desktop_{timestamp}.png"
# 确保输出路径在工作空间内
full_path = (self.workspace / output_path).resolve()
# 创建输出目录
full_path.parent.mkdir(parents=True, exist_ok=True)
# 保存截图
mss.tools.to_png(screenshot.rgb, screenshot.size, output=str(full_path))
# 获取文件大小
file_size = full_path.stat().st_size
logger.info(f"桌面截图已保存: {output_path} ({file_size} bytes)")
return (
f"Desktop screenshot captured successfully!\n"
f"Path: {output_path}\n"
f"Size: {screenshot.width}x{screenshot.height}\n"
f"File size: {file_size:,} bytes\n"
f"Monitor: {monitor_num}"
)
except Exception as e:
logger.error(f"桌面截图失败: {e}")
return f"Error capturing desktop screenshot: {str(e)}"
async def _capture_webpage(self, **kwargs: Any) -> str:
"""
捕获网页截图
使用 playwright 库进行网页截图。
Args:
**kwargs: 截图参数
Returns:
str: 截图结果信息或错误信息
"""
try:
from playwright.async_api import async_playwright
except ImportError:
return (
"Error: playwright library not installed. "
"Install it with: pip install playwright && playwright install chromium"
)
url = kwargs.get("url", "")
output_path = kwargs.get("output_path")
full_page = kwargs.get("full_page", False)
viewport_width = kwargs.get("viewport_width", 1280)
viewport_height = kwargs.get("viewport_height", 720)
wait_time = kwargs.get("wait_time", 1000)
timeout = kwargs.get("timeout", 30000)
if not url:
return "Error: url parameter is required for webpage mode"
# 验证 URL
if not url.startswith(("http://", "https://")):
return f"Error: Invalid URL '{url}'. Must start with http:// or https://"
try:
logger.info(f"捕获网页截图: url={url}, full_page={full_page}")
async with async_playwright() as p:
# 启动浏览器
browser = await p.chromium.launch(headless=True)
# 创建上下文和页面
context = await browser.new_context(
viewport={"width": viewport_width, "height": viewport_height}
)
page = await context.new_page()
# 访问 URL
await page.goto(url, wait_until="networkidle", timeout=timeout)
# 等待指定时间
if wait_time > 0:
await asyncio.sleep(wait_time / 1000)
# 生成输出路径
if not output_path:
from datetime import datetime
from urllib.parse import urlparse
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
domain = urlparse(url).netloc.replace(".", "_")
output_path = f"{self.default_output_dir}/webpage_{domain}_{timestamp}.png"
# 确保输出路径在工作空间内
full_path = (self.workspace / output_path).resolve()
# 创建输出目录
full_path.parent.mkdir(parents=True, exist_ok=True)
# 截图
await page.screenshot(
path=str(full_path),
full_page=full_page,
)
# 获取页面信息
page_title = await page.title()
# 关闭浏览器
await browser.close()
# 获取文件大小
file_size = full_path.stat().st_size
logger.info(f"网页截图已保存: {output_path} ({file_size} bytes)")
return (
f"Webpage screenshot captured successfully!\n"
f"URL: {url}\n"
f"Title: {page_title}\n"
f"Path: {output_path}\n"
f"Viewport: {viewport_width}x{viewport_height}\n"
f"Full page: {full_page}\n"
f"File size: {file_size:,} bytes"
)
except Exception as e:
logger.error(f"网页截图失败: {e}")
return f"Error capturing webpage screenshot: {str(e)}"