feat: initial LibreOffice Writer Ollama sidebar extension scaffold
This commit is contained in:
23
Addons.xcu
Normal file
23
Addons.xcu
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry"
|
||||||
|
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||||
|
oor:name="Addons"
|
||||||
|
oor:package="org.openoffice.Office">
|
||||||
|
<node oor:name="AddonUI">
|
||||||
|
<node oor:name="OfficeMenuBarMerging">
|
||||||
|
<node oor:name="de.aquantico.ollama.menu" oor:op="replace">
|
||||||
|
<prop oor:name="MergePoint"><value>.uno:ToolsMenu</value></prop>
|
||||||
|
<prop oor:name="MergeCommand"><value>AddAfter</value></prop>
|
||||||
|
<prop oor:name="MergeFallback"><value>AddPath</value></prop>
|
||||||
|
<node oor:name="MenuItems">
|
||||||
|
<node oor:name="m1" oor:op="replace">
|
||||||
|
<prop oor:name="URL"><value>service:de.aquantico.ollama.sidebar.Toggle</value></prop>
|
||||||
|
<prop oor:name="Title"><value>Ollama Sidebar öffnen</value></prop>
|
||||||
|
<prop oor:name="Target"><value>_self</value></prop>
|
||||||
|
<prop oor:name="Context"><value>com.sun.star.text.TextDocument</value></prop>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</oor:component-data>
|
||||||
7
META-INF/manifest.xml
Normal file
7
META-INF/manifest.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<manifest:manifest xmlns:manifest="http://openoffice.org/2001/manifest">
|
||||||
|
<manifest:file-entry manifest:media-type="application/vnd.sun.star.configuration-data" manifest:full-path="Addons.xcu"/>
|
||||||
|
<manifest:file-entry manifest:media-type="application/vnd.sun.star.configuration-data" manifest:full-path="Sidebar.xcu"/>
|
||||||
|
<manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-components" manifest:full-path="OllamaSidebar.components"/>
|
||||||
|
<manifest:file-entry manifest:media-type="application/binary" manifest:full-path="src/ollama_sidebar.py"/>
|
||||||
|
</manifest:manifest>
|
||||||
8
OllamaSidebar.components
Normal file
8
OllamaSidebar.components
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<components xmlns="http://openoffice.org/2010/uno-components">
|
||||||
|
<component loader="com.sun.star.loader.Python" uri="vnd.openoffice.pymodule:ollama_sidebar">
|
||||||
|
<implementation name="de.aquantico.ollama.sidebar.Toggle">
|
||||||
|
<service name="com.sun.star.task.Job"/>
|
||||||
|
</implementation>
|
||||||
|
</component>
|
||||||
|
</components>
|
||||||
42
README.md
Normal file
42
README.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# LibreOffice Writer Ollama Sidebar
|
||||||
|
|
||||||
|
LibreOffice Writer Extension (`.oxt`) mit Sidebar-Panel:
|
||||||
|
- Prompt direkt in der Seitenleiste
|
||||||
|
- Auswahl/Absatz an Ollama senden
|
||||||
|
- Ergebnis zurück in Writer einfügen oder Auswahl ersetzen
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
MVP-Gerüst mit Sidebar-Panel + Python UNO-Komponente.
|
||||||
|
|
||||||
|
## Struktur
|
||||||
|
|
||||||
|
- `description.xml`
|
||||||
|
- `Addons.xcu`
|
||||||
|
- `Sidebar.xcu`
|
||||||
|
- `src/ollama_sidebar.py`
|
||||||
|
- `META-INF/manifest.xml`
|
||||||
|
|
||||||
|
## Build (.oxt)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd libreoffice-writer-ollama-sidebar
|
||||||
|
zip -r ollama-sidebar.oxt description.xml Addons.xcu Sidebar.xcu META-INF src resources
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
- LibreOffice -> Extras -> Erweiterungsmanager -> Hinzufügen -> `ollama-sidebar.oxt`
|
||||||
|
- Writer öffnen
|
||||||
|
- Seitenleiste -> Deck/Panel `Ollama Rewrite`
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
|
||||||
|
In der Sidebar:
|
||||||
|
- Ollama URL (z. B. `http://127.0.0.1:11434`)
|
||||||
|
- Modell (z. B. `llama3.1:8b`)
|
||||||
|
- Prompt
|
||||||
|
|
||||||
|
## Hinweis
|
||||||
|
|
||||||
|
LibreOffice UNO-Sidebar APIs variieren je Version. Dieses Repo enthält ein solides Startgerüst; bei Bedarf passe ich es auf deine konkrete LO-Version nach.
|
||||||
22
Sidebar.xcu
Normal file
22
Sidebar.xcu
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry"
|
||||||
|
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||||
|
oor:name="Sidebar"
|
||||||
|
oor:package="org.openoffice.Office.UI">
|
||||||
|
<node oor:name="DeckList">
|
||||||
|
<node oor:name="de.aquantico.ollama.deck" oor:op="replace">
|
||||||
|
<prop oor:name="Title"><value>Ollama</value></prop>
|
||||||
|
<prop oor:name="Id"><value>de.aquantico.ollama.deck</value></prop>
|
||||||
|
<prop oor:name="IconURL"><value></value></prop>
|
||||||
|
<prop oor:name="ContextList"><value>com.sun.star.text.TextDocument</value></prop>
|
||||||
|
<node oor:name="PanelList">
|
||||||
|
<node oor:name="de.aquantico.ollama.panel" oor:op="replace">
|
||||||
|
<prop oor:name="Title"><value>Ollama Rewrite</value></prop>
|
||||||
|
<prop oor:name="Id"><value>de.aquantico.ollama.panel</value></prop>
|
||||||
|
<prop oor:name="ImplementationURL"><value>vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages/ollama-sidebar.oxt/src/ollama_sidebar.py</value></prop>
|
||||||
|
<prop oor:name="ContextList"><value>com.sun.star.text.TextDocument</value></prop>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</oor:component-data>
|
||||||
16
description.xml
Normal file
16
description.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<description xmlns="http://openoffice.org/extensions/description/2006"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<identifier value="de.aquantico.libreoffice.ollama.sidebar"/>
|
||||||
|
<version value="0.1.0"/>
|
||||||
|
<display-name>
|
||||||
|
<name lang="en">Ollama Writer Sidebar</name>
|
||||||
|
<name lang="de">Ollama Writer Seitenleiste</name>
|
||||||
|
</display-name>
|
||||||
|
<publisher>
|
||||||
|
<name xlink:href="https://aquantico.lan">Aquantico</name>
|
||||||
|
</publisher>
|
||||||
|
<dependencies>
|
||||||
|
<OpenOffice.org-minimal-version value="7.0"/>
|
||||||
|
</dependencies>
|
||||||
|
</description>
|
||||||
235
ollama_sidebar.py
Normal file
235
ollama_sidebar.py
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
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",)
|
||||||
|
)
|
||||||
235
src/ollama_sidebar.py
Normal file
235
src/ollama_sidebar.py
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
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",)
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user