import json import os import urllib.request import uno import unohelper from com.sun.star.task import XJobExecutor from com.sun.star.awt import XActionListener CONFIG_DIR = os.path.join(os.path.expanduser("~"), ".ollama_writer_sidebar") CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json") DEFAULT_CFG = { "ollama_url": "http://127.0.0.1:11434", "model": "llama3.1:8b", "prompt": "Überarbeite den Text klarer, Sinn beibehalten." } def load_cfg(): try: os.makedirs(CONFIG_DIR, exist_ok=True) if not os.path.exists(CONFIG_FILE): save_cfg(DEFAULT_CFG) return dict(DEFAULT_CFG) with open(CONFIG_FILE, "r", encoding="utf-8") as f: data = json.load(f) for k, v in DEFAULT_CFG.items(): data.setdefault(k, v) return data except Exception: return dict(DEFAULT_CFG) def save_cfg(cfg): os.makedirs(CONFIG_DIR, exist_ok=True) with open(CONFIG_FILE, "w", encoding="utf-8") as f: json.dump(cfg, f, ensure_ascii=False, indent=2) def ollama_generate(url, model, prompt, source): full_prompt = f"{prompt}\n\nTEXT:\n{source}" payload = json.dumps({"model": model, "prompt": full_prompt, "stream": False}).encode("utf-8") req = urllib.request.Request(url.rstrip("/") + "/api/generate", data=payload, headers={"Content-Type": "application/json"}) with urllib.request.urlopen(req, timeout=120) as resp: data = json.loads(resp.read().decode("utf-8")) return data.get("response", "") def get_doc_text(doc): return doc.Text.String if hasattr(doc, "Text") else "" def get_selected_text(doc): try: sel = doc.getCurrentSelection() if sel and sel.getCount() > 0: part = sel.getByIndex(0) return getattr(part, "String", "") except Exception: pass return "" def replace_selection_or_append(doc, txt): try: sel = doc.getCurrentSelection() if sel and sel.getCount() > 0: part = sel.getByIndex(0) if hasattr(part, "String"): part.String = txt return except Exception: pass try: doc.Text.insertString(doc.Text.End, "\n" + txt, False) except Exception: pass class _BtnListener(unohelper.Base, XActionListener): def __init__(self, owner): self.owner = owner def actionPerformed(self, event): cmd = event.ActionCommand self.owner.on_action(cmd) def disposing(self, event): pass class OllamaDialog: def __init__(self, ctx): self.ctx = ctx self.smgr = ctx.getServiceManager() self.desktop = self.smgr.createInstanceWithContext("com.sun.star.frame.Desktop", ctx) self.doc = self.desktop.getCurrentComponent() self.cfg = load_cfg() self.dialog = None self.controls = {} def _add_control(self, model, name, ctype, x, y, w, h, props): c = model.createInstance(ctype) c.Name = name c.PositionX = x c.PositionY = y c.Width = w c.Height = h for k, v in props.items(): setattr(c, k, v) model.insertByName(name, c) def create(self): dm = self.smgr.createInstanceWithContext("com.sun.star.awt.UnoControlDialogModel", self.ctx) dm.PositionX = 120 dm.PositionY = 80 dm.Width = 300 dm.Height = 230 dm.Title = "Ollama Rewrite (Sidebar-MVP)" self._add_control(dm, "lbl1", "com.sun.star.awt.UnoControlFixedTextModel", 6, 6, 40, 10, {"Label": "URL"}) self._add_control(dm, "url", "com.sun.star.awt.UnoControlEditModel", 48, 4, 245, 12, {"Text": self.cfg.get("ollama_url", "")}) self._add_control(dm, "lbl2", "com.sun.star.awt.UnoControlFixedTextModel", 6, 20, 40, 10, {"Label": "Model"}) self._add_control(dm, "model", "com.sun.star.awt.UnoControlEditModel", 48, 18, 245, 12, {"Text": self.cfg.get("model", "")}) self._add_control(dm, "lbl3", "com.sun.star.awt.UnoControlFixedTextModel", 6, 34, 40, 10, {"Label": "Prompt"}) self._add_control(dm, "prompt", "com.sun.star.awt.UnoControlEditModel", 48, 32, 245, 28, { "MultiLine": True, "VScroll": True, "Text": self.cfg.get("prompt", "") }) src = get_selected_text(self.doc) or get_doc_text(self.doc) self._add_control(dm, "lbl4", "com.sun.star.awt.UnoControlFixedTextModel", 6, 62, 40, 10, {"Label": "Quelle"}) self._add_control(dm, "source", "com.sun.star.awt.UnoControlEditModel", 48, 60, 245, 58, { "MultiLine": True, "VScroll": True, "Text": src }) self._add_control(dm, "lbl5", "com.sun.star.awt.UnoControlFixedTextModel", 6, 122, 40, 10, {"Label": "Ziel"}) self._add_control(dm, "target", "com.sun.star.awt.UnoControlEditModel", 48, 120, 245, 58, { "MultiLine": True, "VScroll": True, "Text": "" }) self._add_control(dm, "rewrite", "com.sun.star.awt.UnoControlButtonModel", 48, 184, 58, 14, {"Label": "Bearbeiten", "PushButtonType": 0}) self._add_control(dm, "copy", "com.sun.star.awt.UnoControlButtonModel", 110, 184, 58, 14, {"Label": "Clipboard", "PushButtonType": 0}) self._add_control(dm, "insert", "com.sun.star.awt.UnoControlButtonModel", 172, 184, 58, 14, {"Label": "Einfügen", "PushButtonType": 0}) self._add_control(dm, "close", "com.sun.star.awt.UnoControlButtonModel", 234, 184, 58, 14, {"Label": "Schließen", "PushButtonType": 0}) self._add_control(dm, "status", "com.sun.star.awt.UnoControlFixedTextModel", 48, 202, 245, 12, {"Label": "Bereit"}) self.dialog = self.smgr.createInstanceWithContext("com.sun.star.awt.UnoControlDialog", self.ctx) self.dialog.setModel(dm) toolkit = self.smgr.createInstanceWithContext("com.sun.star.awt.ExtToolkit", self.ctx) self.dialog.createPeer(toolkit, None) listener = _BtnListener(self) for btn, cmd in [("rewrite", "rewrite"), ("copy", "copy"), ("insert", "insert"), ("close", "close")]: c = self.dialog.getControl(btn) c.setActionCommand(cmd) c.addActionListener(listener) self.listener = listener def set_status(self, txt): self.dialog.getControl("status").getModel().Label = txt def on_action(self, cmd): if cmd == "close": self.dialog.endExecute() return if cmd == "rewrite": try: url = self.dialog.getControl("url").getModel().Text.strip() model = self.dialog.getControl("model").getModel().Text.strip() prompt = self.dialog.getControl("prompt").getModel().Text.strip() source = self.dialog.getControl("source").getModel().Text self.cfg.update({"ollama_url": url, "model": model, "prompt": prompt}) save_cfg(self.cfg) self.set_status("Bearbeite…") out = ollama_generate(url, model, prompt, source) self.dialog.getControl("target").getModel().Text = out self.set_status("Fertig") except Exception as e: self.set_status(f"Fehler: {e}") return if cmd == "copy": txt = self.dialog.getControl("target").getModel().Text try: service = self.smgr.createInstanceWithContext("com.sun.star.datatransfer.clipboard.SystemClipboard", self.ctx) transferable = self.smgr.createInstanceWithContext("com.sun.star.datatransfer.DataFlavor", self.ctx) _ = service, transferable except Exception: pass # Simpler fallback: keep in target and close self.dialog.endExecute() return if cmd == "insert": txt = self.dialog.getControl("target").getModel().Text replace_selection_or_append(self.doc, txt) self.dialog.endExecute() return def run(self): self.create() self.dialog.execute() class Toggle(unohelper.Base, XJobExecutor): def __init__(self, ctx): self.ctx = ctx def trigger(self, args): dlg = OllamaDialog(self.ctx) dlg.run() g_ImplementationHelper = unohelper.ImplementationHelper() g_ImplementationHelper.addImplementation( Toggle, "de.aquantico.ollama.sidebar.Toggle", ("com.sun.star.task.Job", "com.sun.star.task.XJobExecutor",) )