236 lines
8.4 KiB
Python
236 lines
8.4 KiB
Python
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",)
|
|
)
|