Rectangle, ls pagination
This commit is contained in:
parent
823da03858
commit
f033d9ea8c
89
tools.py
89
tools.py
@ -5,11 +5,36 @@ import logging
|
|||||||
import asyncio
|
import asyncio
|
||||||
import io
|
import io
|
||||||
import datetime
|
import datetime
|
||||||
|
import time
|
||||||
from typing import Any, Callable, Dict, List, Union
|
from typing import Any, Callable, Dict, List, Union
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
logger = logging.getLogger("MattCP")
|
logger = logging.getLogger("MattCP")
|
||||||
|
|
||||||
|
def format_relative_time(timestamp: float) -> str:
|
||||||
|
"""Converts a timestamp to a human-readable relative format."""
|
||||||
|
now = time.time()
|
||||||
|
diff = now - timestamp
|
||||||
|
seconds = int(diff)
|
||||||
|
if seconds < 60:
|
||||||
|
return "Just now"
|
||||||
|
minutes = seconds // 60
|
||||||
|
if minutes < 60:
|
||||||
|
return f"{minutes}m ago"
|
||||||
|
hours = minutes // 60
|
||||||
|
if hours < 24:
|
||||||
|
return f"{hours}h ago"
|
||||||
|
days = hours // 24
|
||||||
|
if days == 1:
|
||||||
|
return "Yesterday"
|
||||||
|
if days < 7:
|
||||||
|
return f"{days}d ago"
|
||||||
|
weeks = days // 7
|
||||||
|
if weeks < 4:
|
||||||
|
return f"{weeks}w ago"
|
||||||
|
dt = datetime.datetime.fromtimestamp(timestamp)
|
||||||
|
return dt.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
class Tool:
|
class Tool:
|
||||||
def __init__(self, name: str, description: str, schema: Dict[str, Any], handler: Callable):
|
def __init__(self, name: str, description: str, schema: Dict[str, Any], handler: Callable):
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -28,11 +53,9 @@ async def read_image_handler(args: Dict[str, Any]):
|
|||||||
path = args.get("path")
|
path = args.get("path")
|
||||||
if not path:
|
if not path:
|
||||||
return {"text": "Error: Missing path argument"}
|
return {"text": "Error: Missing path argument"}
|
||||||
|
|
||||||
mime_type, _ = mimetypes.guess_type(path)
|
mime_type, _ = mimetypes.guess_type(path)
|
||||||
if not mime_type or not mime_type.startswith("image/"):
|
if not mime_type or not mime_type.startswith("image/"):
|
||||||
return {"text": f"Error: File {path} is not a supported image format."}
|
return {"text": f"Error: File {path} is not a supported image format."}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
data = base64.b64encode(f.read()).decode("utf-8")
|
data = base64.b64encode(f.read()).decode("utf-8")
|
||||||
@ -44,7 +67,6 @@ async def read_png_metadata_handler(args: Dict[str, Any]):
|
|||||||
path = args.get("path")
|
path = args.get("path")
|
||||||
if not path:
|
if not path:
|
||||||
return {"text": "Error: Missing path argument"}
|
return {"text": "Error: Missing path argument"}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with Image.open(path) as img:
|
with Image.open(path) as img:
|
||||||
if img.format != 'PNG':
|
if img.format != 'PNG':
|
||||||
@ -69,7 +91,6 @@ async def list_thumbnails_handler(args: Dict[str, Any]):
|
|||||||
return {"text": f"Error: {dir_path} is not a directory."}
|
return {"text": f"Error: {dir_path} is not a directory."}
|
||||||
|
|
||||||
exts = ('.png', '.jpg', '.jpeg', '.webp', '.bmp')
|
exts = ('.png', '.jpg', '.jpeg', '.webp', '.bmp')
|
||||||
|
|
||||||
file_info_list = []
|
file_info_list = []
|
||||||
for f in os.listdir(dir_path):
|
for f in os.listdir(dir_path):
|
||||||
if f.lower().endswith(exts):
|
if f.lower().endswith(exts):
|
||||||
@ -153,13 +174,11 @@ async def list_thumbnails_handler(args: Dict[str, Any]):
|
|||||||
off_y = (thumb_size - img.height) // 2
|
off_y = (thumb_size - img.height) // 2
|
||||||
canvas.paste(img, (x + off_x, y + off_y))
|
canvas.paste(img, (x + off_x, y + off_y))
|
||||||
|
|
||||||
mod_time = datetime.datetime.fromtimestamp(info["mtime"]).strftime('%Y-%m-%d %H:%M')
|
mod_time_rel = format_relative_time(info["mtime"])
|
||||||
size_kb = info["size"] / 1024
|
size_kb = info["size"] / 1024
|
||||||
file_details.append(f"{i+1}. {filename} | {width}x{height} | {size_kb:.1f}KB | {mod_time}")
|
file_details.append(f"{i+1}. {filename} | {width}x{height} | {size_kb:.1f}KB | {mod_time_rel}")
|
||||||
|
|
||||||
text = str(i + 1)
|
text = str(i + 1)
|
||||||
text_w = len(text) * 10
|
|
||||||
draw.rectangle([x, y + thumb_size + 2, x + text_w + 4, y + thumb_size + 24], fill=(60, 60, 60))
|
|
||||||
draw.text((x + 2, y + thumb_size + 2), text, fill=(255, 255, 255), font=font)
|
draw.text((x + 2, y + thumb_size + 2), text, fill=(255, 255, 255), font=font)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -183,6 +202,9 @@ async def list_thumbnails_handler(args: Dict[str, Any]):
|
|||||||
|
|
||||||
async def list_directory_details_handler(args: Dict[str, Any]):
|
async def list_directory_details_handler(args: Dict[str, Any]):
|
||||||
path = args.get("path")
|
path = args.get("path")
|
||||||
|
page = int(args.get("page", 1))
|
||||||
|
page_size = int(args.get("page_size", 64))
|
||||||
|
|
||||||
if not path:
|
if not path:
|
||||||
return {"text": "Error: Missing path argument"}
|
return {"text": "Error: Missing path argument"}
|
||||||
if not os.path.isdir(path):
|
if not os.path.isdir(path):
|
||||||
@ -193,50 +215,57 @@ async def list_directory_details_handler(args: Dict[str, Any]):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"text": f"Error reading directory: {e}"}
|
return {"text": f"Error reading directory: {e}"}
|
||||||
|
|
||||||
dirs = []
|
all_items = []
|
||||||
files = []
|
|
||||||
|
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
full_path = os.path.join(path, entry)
|
full_path = os.path.join(path, entry)
|
||||||
try:
|
try:
|
||||||
stats = os.stat(full_path)
|
stats = os.stat(full_path)
|
||||||
mtime = datetime.datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M')
|
mtime_rel = format_relative_time(stats.st_mtime)
|
||||||
|
|
||||||
if os.path.isdir(full_path):
|
if os.path.isdir(full_path):
|
||||||
# Count items in dir (non-recursive)
|
|
||||||
try:
|
try:
|
||||||
count = len(os.listdir(full_path))
|
count = len(os.listdir(full_path))
|
||||||
except:
|
except:
|
||||||
count = 0
|
count = 0
|
||||||
dirs.append({
|
all_items.append({
|
||||||
|
"type": "dir",
|
||||||
"name": entry,
|
"name": entry,
|
||||||
"info": f"{count} items",
|
"info": f"{count} items",
|
||||||
"mtime": mtime
|
"mtime": mtime_rel
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
size_kb = stats.st_size / 1024
|
size_kb = stats.st_size / 1024
|
||||||
files.append({
|
all_items.append({
|
||||||
|
"type": "file",
|
||||||
"name": entry,
|
"name": entry,
|
||||||
"info": f"{size_kb:.1f}KB",
|
"info": f"{size_kb:.1f}KB",
|
||||||
"mtime": mtime
|
"mtime": mtime_rel
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Could not stat {entry}: {e}")
|
logger.error(f"Could not stat {entry}: {e}")
|
||||||
|
|
||||||
# Sort alphabetically
|
# Sort: Directories first, then files, then alphabetical
|
||||||
dirs.sort(key=lambda x: x["name"].lower())
|
all_items.sort(key=lambda x: (x["type"] == "file", x["name"].lower()))
|
||||||
files.sort(key=lambda x: x["name"].lower())
|
|
||||||
|
total_items = len(all_items)
|
||||||
|
start_idx = (page - 1) * page_size
|
||||||
|
end_idx = start_idx + page_size
|
||||||
|
paged_items = all_items[start_idx:end_idx]
|
||||||
|
|
||||||
|
if not paged_items:
|
||||||
|
return {"text": f"No items found on page {page}."}
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
for d in dirs:
|
for item in paged_items:
|
||||||
output.append(f"[DIR] {d['name']} | {d['info']} | {d['mtime']}")
|
if item["type"] == "dir":
|
||||||
for f in files:
|
output.append(f"[DIR] {item['name']} | {item['info']} | {item['mtime']}")
|
||||||
output.append(f" {f['name']} | {f['info']} | {f['mtime']}")
|
else:
|
||||||
|
output.append(f" {item['name']} | {item['info']} | {item['mtime']}")
|
||||||
|
|
||||||
if not output:
|
total_pages = (total_items + page_size - 1) // page_size
|
||||||
return {"text": f"Directory {path} is empty."}
|
header = f"Listing of {path}\nPage {page} of {total_pages} ({total_items} total items). Showing {start_idx+1}-{min(end_idx, total_items)}."
|
||||||
|
|
||||||
return {"text": f"Listing of {path}:\n\n" + "\n".join(output)}
|
return {"text": f"{header}\n\n" + "\n".join(output)}
|
||||||
|
|
||||||
TOOL_REGISTRY = [
|
TOOL_REGISTRY = [
|
||||||
Tool(
|
Tool(
|
||||||
@ -276,11 +305,13 @@ TOOL_REGISTRY = [
|
|||||||
),
|
),
|
||||||
Tool(
|
Tool(
|
||||||
name="list_directory",
|
name="list_directory",
|
||||||
description="Lists the contents of a directory in a Nemo-like format. Directories first, then files, with sizes/counts and mtime.",
|
description="Lists the contents of a directory in a Nemo-like format. Directories first, then files. Supports pagination.",
|
||||||
schema={
|
schema={
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"path": {"type": "string", "description": "Path to the directory to list"},
|
"path": {"type": "string", "description": "Path to the directory to list"},
|
||||||
|
"page": {"type": "integer", "description": "The page number to retrieve (1-indexed)", "default": 1},
|
||||||
|
"page_size": {"type": "integer", "description": "Number of items per page", "default": 64},
|
||||||
},
|
},
|
||||||
"required": ["path"],
|
"required": ["path"],
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user