This commit is contained in:
2026-05-18 11:03:53 +02:00
parent 1f25bcbc14
commit 6498c4e444
4 changed files with 359 additions and 0 deletions
+205
View File
@@ -0,0 +1,205 @@
"""
navrh_pojmenovani.py — Claude navrhne jmeno souboru pro naskenovanou brozuru.
Vezme prvnich N stranek PDF, posle je Claudovi jako obrazky a navrhne nazev.
Dialog umozni opravit navrh. Historie dvojic (navrh Claudea, volba uzivatele)
se ulozi a priste prikladem uci Claudea pojmenovavat podle tvych preferencí.
Pouziti:
python navrh_pojmenovani.py vystup/brozura.pdf
python navrh_pojmenovani.py vystup/brozura.pdf vystup/muj_nazev.pdf
"""
import sys
import json
import base64
import io
import copy
import tkinter as tk
from pathlib import Path
from datetime import date
import fitz # pymupdf
import anthropic
HISTORY_FILE = Path(__file__).parent / "pojmenovani_historie.json"
N_PAGES = 5
DPI = 120 # dostatecne pro cteni obsahu, nizke naklady na tokeny
# ---------- pomocne funkce ----------
def load_history() -> list:
if HISTORY_FILE.exists():
return json.loads(HISTORY_FILE.read_text(encoding="utf-8"))
return []
def save_history(history: list):
HISTORY_FILE.write_text(
json.dumps(history, ensure_ascii=False, indent=2),
encoding="utf-8",
)
def pdf_pages_to_b64(pdf_path: str, n: int = N_PAGES) -> list[str]:
"""Vrati seznam base64 JPEG retezcu pro prvnich n stranek PDF."""
doc = fitz.open(pdf_path)
pages = min(n, len(doc))
result = []
mat = fitz.Matrix(DPI / 72, DPI / 72)
for i in range(pages):
pix = doc[i].get_pixmap(matrix=mat, colorspace=fitz.csRGB)
jpeg = pix.tobytes("jpeg", jpg_quality=75)
result.append(base64.standard_b64encode(jpeg).decode())
doc.close()
return result
def ask_claude(images_b64: list[str], history: list) -> str:
"""Posle stranky Claudovi a vrati navrzeny nazev souboru."""
client = anthropic.Anthropic()
history_text = ""
if history:
history_text = "\n\nPriklady z minulosti (muj navrh → uzivatel zvolil):\n"
for item in history[-15:]:
history_text += f" {item['claude']} -> {item['user']}\n"
content: list = []
for i, b64 in enumerate(images_b64):
content.append({
"type": "image",
"source": {"type": "base64", "media_type": "image/jpeg", "data": b64},
})
today = date.today().strftime("%Y-%m-%d")
content.append({
"type": "text",
"text": (
f"Toto je prvnich {len(images_b64)} stranek naskenované brozury. "
"Navrhni vhodny kratky nazev souboru (bez pripony). "
f"Zacni vzdy datem dnesniho dne ve formatu YYYY-MM-DD ({today}), "
"pak podtrzitko a popis obsahu. "
"Pouzij pouze ASCII znaky, cisla, podtrzitka nebo pomlcky — zadna diakritika ani mezery. "
"Popis ma vystihovat obsah dokumentu, byt strucny (24 slova)."
f"{history_text}\n\n"
"Odpovez pouze samotnym nazvem souboru, nic jineho. "
f"Priklad spravneho formatu: {today}_nazev_dokumentu"
),
})
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=64,
thinking={"type": "adaptive"},
messages=[{"role": "user", "content": content}],
)
for block in response.content:
if block.type == "text":
return block.text.strip().strip('"').strip("'")
return "brozura"
def dialog(claude_suggestion: str) -> dict:
"""Zobrazi dialog s navrzenim nazvem, uzivatel muze opravit."""
root = tk.Tk()
root.title("Navrh pojmenovani")
root.resizable(False, False)
root.attributes("-topmost", True)
result = {"name": None, "cancelled": False}
tk.Label(
root, text="Claude navrhuje:", font=("Segoe UI", 9, "italic"), fg="#555"
).pack(padx=24, pady=(16, 2), anchor="w")
tk.Label(
root, text=claude_suggestion, font=("Segoe UI", 12, "bold"), fg="#1a6aaa"
).pack(padx=24, anchor="w")
tk.Label(
root, text="Nazev souboru (uprav pokud chces):", font=("Segoe UI", 9)
).pack(padx=24, pady=(12, 2), anchor="w")
entry = tk.Entry(root, width=52, font=("Segoe UI", 10))
entry.insert(0, claude_suggestion)
entry.pack(padx=24)
entry.select_range(0, tk.END)
entry.focus_set()
def ok(event=None):
val = entry.get().strip()
if val:
result["name"] = val
root.destroy()
def cancel(event=None):
result["cancelled"] = True
root.destroy()
btn = tk.Frame(root)
btn.pack(pady=16)
tk.Button(btn, text="OK", width=12, command=ok).pack(side=tk.LEFT, padx=6)
tk.Button(btn, text="Zrusit", width=12, command=cancel).pack(side=tk.LEFT, padx=6)
root.bind("<Return>", ok)
root.bind("<Escape>", cancel)
root.mainloop()
return result
# ---------- hlavni logika ----------
def main():
if len(sys.argv) < 2:
print(__doc__)
sys.exit(1)
pdf_path = Path(sys.argv[1])
if not pdf_path.exists():
print(f"Soubor nenalezen: {pdf_path}")
sys.exit(1)
# vystupni cesta — volitelny druhy argument
out_path: Path | None = None
if len(sys.argv) > 2:
out_path = Path(sys.argv[2])
print(f"Nacitam stranky z: {pdf_path}")
images = pdf_pages_to_b64(str(pdf_path), N_PAGES)
print(f" {len(images)} stranek prevedeno na obrazky")
history = load_history()
print(f" {len(history)} polozek v historii pojmenovani")
print("Ptam se Claudea...")
claude_name = ask_claude(images, history)
print(f" Claude navrhuje: {claude_name}")
res = dialog(claude_name)
if res["cancelled"] or not res["name"]:
print("Zruseno — soubor nebyl prejmenovan.")
return
user_name = res["name"]
# uloz do historie
history.append({"claude": claude_name, "user": user_name})
save_history(history)
print(f" Historie ulozena ({len(history)} polozek)")
# urceni vystupniho souboru
if out_path is None:
out_path = pdf_path.parent / (user_name + pdf_path.suffix)
if out_path != pdf_path:
pdf_path.rename(out_path)
print(f"Prejmenovan: {out_path}")
else:
print(f"Nazev beze zmeny: {out_path}")
if __name__ == "__main__":
main()