""" 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 (2–4 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("", ok) root.bind("", 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()