Guest User

kobold translate

a guest
Aug 8th, 2025
56
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.27 KB | None | 0 0
  1. import tkinter as tk
  2. import threading
  3. import time
  4. import keyboard
  5. from PIL import Image
  6. import mss
  7. import pytesseract
  8. import sys
  9. import shutil
  10. import os
  11. import queue
  12. import ctypes
  13. import requests
  14.  
  15. # ========================================================================================
  16. # USER CONFIGURATION
  17. # ========================================================================================
  18. # --- Tesseract Configuration ---
  19. # IMPORTANT: Please verify this path is correct for your Tesseract installation.
  20. pytesseract.pytesseract.tesseract_cmd = r'[YOUR TESSERACT.EXE PATH GOES HERE]'
  21.  
  22. # --- Kobold AI API Configuration ---
  23. # This should be the address of your running Kobold AI API server.
  24. # For KoboldCpp, the default is usually http://127.0.0.1:5001
  25. # For the original Kobold AI client, it might be http://127.0.0.1:5000
  26. KOBOLD_API_URL = "http://127.0.0.1:5001"
  27.  
  28.  
  29. # --- Prompting Configuration ---
  30. # These are the "system" prompts that define the AI's role for each task.
  31. # NOTE: Kobold's simple API doesn't have a dedicated "system" prompt.
  32. # This text will be prepended to the user's prompt.
  33. SYSTEM_PROMPT_TRANSLATE = """As a professional translator specializing in Japanese to English, your task is to provide an accurate translation that sounds natural in English without changing the meaning or context of the original text. Take care to preserve the tone, nuance, and cultural context of the original text. Provide ONLY the final translation. Do not add any notes, additions, or context."""
  34. SYSTEM_PROMPT_ALT = """You are a creative linguistic expert. Your task is to provide an accurate alternative translation for the following Japanese text. The goal is to offer a plausible alternative interpretation of the original text, while remaining true to the original meaning. Take care to preserve the tone, nuance, and cultural context of the original text. Provide a single, alternative English translation. Do not repeat the original or add any commentary."""
  35. SYSTEM_PROMPT_EXPLAIN = """As a Japanese language expert, your task is to provide a brief explanation for the following translation. Focus on any interesting grammar, vocabulary, cultural nuances, or reasons why a particular phrasing was chosen in the translation. Provide a concise explanation. Do not repeat the original text or the translation in your response."""
  36.  
  37.  
  38. # --- Hotkey Configuration ---
  39. RESELECT_HOTKEY = "alt+r"
  40. ALT_TRANSLATE_HOTKEY = "ctrl+right arrow"
  41. EXPLAIN_TRANSLATION_HOTKEY = "ctrl+down arrow"
  42. # ========================================================================================
  43.  
  44. def make_dpi_aware():
  45.     """Makes the application DPI-aware on Windows to fix screen scaling issues."""
  46.     if sys.platform == "win32":
  47.         try:
  48.             ctypes.windll.shcore.SetProcessDpiAwareness(2)
  49.         except (AttributeError, OSError):
  50.             try:
  51.                 ctypes.windll.user32.SetProcessDPIAware()
  52.             except Exception:
  53.                 pass
  54.  
  55. def check_prerequisites():
  56.     """Checks for Tesseract and the Kobold AI server."""
  57.     print("--- Checking Prerequisites ---")
  58.    
  59.     # Check for Tesseract
  60.     if not os.path.isfile(pytesseract.pytesseract.tesseract_cmd):
  61.         print("\n[ERROR] Tesseract OCR not found.")
  62.         print("Please update the path for 'pytesseract.pytesseract.tesseract_cmd' at the top of this script.")
  63.         sys.exit(1)
  64.     print(f"✅ Tesseract OCR found at manual path: {pytesseract.pytesseract.tesseract_cmd}")
  65.  
  66.     # Check for Kobold AI server connection
  67.     try:
  68.         # Pinging the /api/v1/model endpoint is a reliable way to check for a Kobold API
  69.         response = requests.get(f"{KOBOLD_API_URL}/api/v1/model", timeout=3)
  70.         response.raise_for_status()
  71.         print(f"✅ Connected to Kobold AI server at: {KOBOLD_API_URL}")
  72.         print(f"   Model: {response.json().get('result', 'Unknown')}")
  73.     except requests.exceptions.RequestException:
  74.         print(f"\n[ERROR] Could not connect to Kobold AI server at {KOBOLD_API_URL}")
  75.         print("Please ensure your Kobold AI or KoboldCpp server is running and the API is enabled.")
  76.         sys.exit(1)
  77.  
  78.     print("----------------------------\n")
  79.     return True
  80.  
  81. class ScreenRegionSelector:
  82.     """A tool to let the user select a rectangular region on the screen."""
  83.     def __init__(self, parent):
  84.         self.parent = parent
  85.         self.parent.attributes("-alpha", 0.3)
  86.         self.parent.attributes("-topmost", True)
  87.         self.parent.configure(bg='grey')
  88.         self.canvas = tk.Canvas(self.parent, cursor="cross", bg='grey')
  89.         self.canvas.pack(fill=tk.BOTH, expand=True)
  90.         self.start_x, self.start_y = 0, 0
  91.         self.rect, self.region = None, None
  92.         self.canvas.bind("<ButtonPress-1>", self.on_button_press)
  93.         self.canvas.bind("<B1-Motion>", self.on_mouse_drag)
  94.         self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
  95.  
  96.     def on_button_press(self, event):
  97.         self.start_x, self.start_y = self.canvas.winfo_pointerx(), self.canvas.winfo_pointery()
  98.         if self.rect: self.canvas.delete(self.rect)
  99.         self.rect = self.canvas.create_rectangle(self.start_x, self.start_y, self.start_x, self.start_y, outline='red', width=2)
  100.  
  101.     def on_mouse_drag(self, event):
  102.         cur_x, cur_y = self.canvas.winfo_pointerx(), self.canvas.winfo_pointery()
  103.         self.canvas.coords(self.rect, self.start_x, self.start_y, cur_x, cur_y)
  104.  
  105.     def on_button_release(self, event):
  106.         end_x, end_y = self.canvas.winfo_pointerx(), self.canvas.winfo_pointery()
  107.         x1, y1 = min(self.start_x, end_x), min(self.start_y, end_y)
  108.         x2, y2 = max(self.start_x, end_x), max(self.start_y, end_y)
  109.         if abs(x2 - x1) < 10 or abs(y2 - y1) < 10:
  110.             print("Region is too small."); self.region = None
  111.         else:
  112.             self.region = {"top": y1, "left": x1, "width": x2 - x1, "height": y2 - y1}
  113.         self.parent.destroy()
  114.  
  115. class TranslationApp:
  116.     """Main application orchestrating OCR and translation in the console."""
  117.     def __init__(self):
  118.         self.capture_region = None
  119.         self.last_ocr_text = ""
  120.         self.last_translation = ""
  121.         self.running = True
  122.         self.root = None
  123.         self.message_queue = queue.Queue()
  124.  
  125.     def select_region(self):
  126.         """Creates a Toplevel window for region selection and returns the region."""
  127.         print("\nPlease click and drag to select the screen region...")
  128.         selector_window = tk.Toplevel(self.root)
  129.         selector_window.overrideredirect(True)
  130.         width, height = selector_window.winfo_screenwidth(), selector_window.winfo_screenheight()
  131.         selector_window.geometry(f'{width}x{height}+0+0')
  132.         selector_logic = ScreenRegionSelector(selector_window)
  133.         self.root.wait_window(selector_window)
  134.         return selector_logic.region
  135.  
  136.     def capture_and_ocr(self, region):
  137.         """Captures the screen region and performs OCR."""
  138.         if not region: return None
  139.         try:
  140.             with mss.mss() as sct:
  141.                 screenshot = sct.grab(region)
  142.                 img = Image.frombytes("RGB", screenshot.size, screenshot.bgra, "raw", "BGRX")
  143.                 custom_config = r'--oem 3 --psm 5 -l jpn+jpn_vert'
  144.                 return " ".join(pytesseract.image_to_string(img, config=custom_config).split()).strip()
  145.         except Exception as e:
  146.             print(f"An error occurred during OCR: {e}"); return None
  147.  
  148.     def get_llm_response(self, system_prompt, user_prompt):
  149.         """Gets a response from the Kobold AI server."""
  150.         if not user_prompt: return "No text provided."
  151.        
  152.         headers = {"Content-Type": "application/json"}
  153.         api_url = f"{KOBOLD_API_URL}/api/v1/generate"
  154.        
  155.         # Combine system and user prompts for Kobold's single prompt format
  156.         full_prompt = f"{system_prompt}\n\n{user_prompt}"
  157.  
  158.         payload = {
  159.             "prompt": full_prompt,
  160.             "temperature": 0.7,
  161.             "max_length": 512  # Kobold uses 'max_length' instead of 'max_tokens'
  162.         }
  163.        
  164.         try:
  165.             response = requests.post(api_url, headers=headers, json=payload, timeout=None)
  166.             response.raise_for_status()
  167.             # The response format for Kobold is different
  168.             return response.json()['results'][0]['text'].strip()
  169.         except requests.exceptions.RequestException as e:
  170.             print(f"Error connecting to Kobold AI server: {e}")
  171.             return "Error: Could not connect to server."
  172.         except (KeyError, IndexError) as e:
  173.             print(f"Error parsing Kobold AI response: {e}")
  174.             print(f"Received data: {response.text}")
  175.             return "Error: Unexpected response format from server."
  176.         except Exception as e:
  177.             print(f"An error occurred during model inference: {e}")
  178.             return "Error: Model inference failed."
  179.  
  180.     def setup_hotkey(self):
  181.         """Sets up the global hotkeys to put messages in the queue."""
  182.         keyboard.add_hotkey(RESELECT_HOTKEY, lambda: self.message_queue.put(("RESELECT_REQUESTED", None)))
  183.         keyboard.add_hotkey(ALT_TRANSLATE_HOTKEY, lambda: self.message_queue.put(("ALT_TRANSLATE_REQUESTED", None)))
  184.         keyboard.add_hotkey(EXPLAIN_TRANSLATION_HOTKEY, lambda: self.message_queue.put(("EXPLAIN_REQUESTED", None)))
  185.        
  186.         print(f"\n>>> Press '{RESELECT_HOTKEY}' to select a region and translate.")
  187.         print(f">>> Press '{ALT_TRANSLATE_HOTKEY}' for an alternative translation.")
  188.         print(f">>> Press '{EXPLAIN_TRANSLATION_HOTKEY}' for an explanation.")
  189.         print(">>> Press 'Ctrl+C' in this console to quit the application.\n")
  190.  
  191.  
  192.     def process_queue(self):
  193.         """Processes messages from the hotkey callbacks in the main thread."""
  194.         try:
  195.             message = self.message_queue.get_nowait()
  196.             if message is None:
  197.                 self.running = False; self.root.destroy(); return
  198.            
  199.             msg_type, msg_data = message
  200.            
  201.             if msg_type == "RESELECT_REQUESTED":
  202.                 print("\n--- Hotkey pressed: Select Region ---")
  203.                 new_region = self.select_region()
  204.                 if new_region:
  205.                     self.capture_region = new_region
  206.                     print(f"Region successfully selected. Performing OCR...")
  207.                     ocr_text = self.capture_and_ocr(self.capture_region)
  208.                     if ocr_text:
  209.                         self.last_ocr_text = ocr_text
  210.                         print(f"Detected Text: \"{ocr_text}\"")
  211.                         print("Translating...")
  212.                         user_prompt = f"Japanese Text:\n\"{ocr_text}\"\n\nEnglish Translation:"
  213.                         translation = self.get_llm_response(SYSTEM_PROMPT_TRANSLATE, user_prompt)
  214.                         self.last_translation = translation
  215.                         print("\n" + "="*20 + " TRANSLATION " + "="*20); print(f"{translation}"); print("="*53 + "\n")
  216.                     else:
  217.                         print("No text found in the selected region.")
  218.                 else:
  219.                     print("Region selection cancelled or invalid.")
  220.  
  221.             elif msg_type == "ALT_TRANSLATE_REQUESTED":
  222.                 if not self.last_ocr_text: print("No text has been translated yet.")
  223.                 else:
  224.                     print(f"\n--- Requesting alternative for: {self.last_ocr_text} ---")
  225.                     user_prompt = f"Japanese Text:\n\"{self.last_ocr_text}\"\n\nAlternative English Translation:"
  226.                     alt_translation = self.get_llm_response(SYSTEM_PROMPT_ALT, user_prompt)
  227.                     print("\n" + "="*15 + " ALTERNATIVE TRANSLATION " + "="*15); print(f"{alt_translation}"); print("="*57 + "\n")
  228.            
  229.             elif msg_type == "EXPLAIN_REQUESTED":
  230.                 if not self.last_ocr_text: print("No text has been translated yet.")
  231.                 else:
  232.                     print("\n--- Requesting explanation ---")
  233.                     user_prompt = f"Original Japanese Text:\n\"{self.last_ocr_text}\"\n\nEnglish Translation:\n\"{self.last_translation}\"\n\nExplanation:"
  234.                     explanation = self.get_llm_response(SYSTEM_PROMPT_EXPLAIN, user_prompt)
  235.                     print("\n" + "="*20 + " EXPLANATION " + "="*20); print(f"{explanation}"); print("="*53 + "\n")
  236.  
  237.         except queue.Empty: pass
  238.         finally:
  239.             if self.running: self.root.after(100, self.process_queue)
  240.             else:
  241.                 if self.root.winfo_exists(): self.root.destroy()
  242.  
  243.     def run(self):
  244.         """Starts the main application loop."""
  245.         self.root = tk.Tk()
  246.         self.root.withdraw()
  247.        
  248.         self.setup_hotkey()
  249.        
  250.         print("Script started. Press 'alt+r' to select your first region.")
  251.  
  252.         self.process_queue()
  253.         self.root.mainloop()
  254.  
  255.         self.running = False
  256.         keyboard.remove_all_hotkeys()
  257.         print("\nApplication shutting down.")
  258.  
  259. if __name__ == "__main__":
  260.     try:
  261.         make_dpi_aware()
  262.         check_prerequisites()
  263.         app = TranslationApp()
  264.         app.run()
  265.     except KeyboardInterrupt: print("\nCtrl+C detected. Shutting down.")
  266.     except SystemExit as e: print(f"Exiting. Exit code: {e.code if e.code is not None else 1}")
  267.     except Exception as e: print(f"An unexpected error occurred: {e}")
Advertisement
Add Comment
Please, Sign In to add comment