gitea_connector.py aktualisiert

This commit is contained in:
2026-03-06 14:22:23 +00:00
parent f778138381
commit 494fd21a99

View File

@@ -1,7 +1,7 @@
""" """
title: Gitea Connector title: Gitea Connector
author: H5N3RG author: H5N3RG
version: 0.2 version: 0.3
license: MIT license: MIT
description: Allows Open WebUI LLMs to read and edit Gitea repositories. description: Allows Open WebUI LLMs to read and edit Gitea repositories.
""" """
@@ -9,7 +9,7 @@ description: Allows Open WebUI LLMs to read and edit Gitea repositories.
import requests import requests
import base64 import base64
from typing import Optional from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel, Field
class GiteaAPI: class GiteaAPI:
@@ -39,163 +39,280 @@ class GiteaAPI:
class Tools: class Tools:
class Valves(BaseModel): class Valves(BaseModel):
GITEA_URL: str = Field(
default="", description="Gitea API URL, z.B. https://git.example.com/api/v1"
)
API_TOKEN: str = Field(default="", description="Gitea API Token")
GITEA_URL: str = "https://git.example.com/api/v1" DEFAULT_OWNER: str = Field(
API_TOKEN: str = "" default="", description="Standard-Repository-Owner/Organisation"
)
DEFAULT_REPO: str = Field(default="", description="Standard-Repository-Name")
DEFAULT_BRANCH: str = Field(default="main", description="Standard-Branch")
DEFAULT_OWNER: str = "user" ENABLE_READ: bool = Field(default=True, description="Lesen erlauben")
DEFAULT_REPO: str = "repo" ENABLE_WRITE: bool = Field(default=False, description="Schreiben erlauben")
DEFAULT_BRANCH: str = "main" ENABLE_BRANCH: bool = Field(
default=True, description="Branch-Erstellung erlauben"
)
ENABLE_PR: bool = Field(
default=True, description="Pull-Request-Erstellung erlauben"
)
ENABLE_READ: bool = True ALLOWED_PATH_PREFIX: str = Field(
ENABLE_WRITE: bool = False default="",
ENABLE_BRANCH: bool = True description="Erlaubtes Pfad-Präfix für Schreibzugriff (leer = alle Pfade erlaubt)",
ENABLE_PR: bool = True )
MAX_FILE_SIZE_KB: int = Field(
ALLOWED_PATH_PREFIX: str = "docs/" default=200, description="Maximale Dateigröße in KB"
)
MAX_FILE_SIZE_KB: int = 200
def __init__(self): def __init__(self):
self.valves = self.Valves() self.valves = self.Valves()
# FIX: GiteaAPI wird NICHT mehr hier instanziiert.
# Stattdessen wird self.api als Property definiert,
# damit die Valve-Werte immer aktuell sind wenn OWUI sie injiziert.
@property def _get_valves(self, __user__: dict) -> Valves:
def api(self) -> GiteaAPI: """
"""Lazy property: baut GiteaAPI immer frisch mit den aktuellen Valve-Werten.""" Holt die Valves aus __user__ falls von OWUI injiziert,
return GiteaAPI(self.valves.GITEA_URL, self.valves.API_TOKEN) sonst Fallback auf self.valves.
Dies ist der Workaround für den OWUI-Bug bei dem Valves
nicht korrekt in self.valves landen.
"""
user_valves = __user__.get("valves") if __user__ else None
if user_valves:
try:
return (
self.Valves(**user_valves)
if isinstance(user_valves, dict)
else user_valves
)
except Exception:
pass
return self.valves
def _get_api(self, valves: Valves) -> GiteaAPI:
return GiteaAPI(valves.GITEA_URL, valves.API_TOKEN)
def _check_config(self, valves: Valves) -> Optional[str]:
if not valves.GITEA_URL:
return "❌ Bitte GITEA_URL in den Valve-Einstellungen eintragen."
if not valves.API_TOKEN:
return "❌ Bitte API_TOKEN in den Valve-Einstellungen eintragen."
return None
# -------------------------------------------------- # --------------------------------------------------
# LIST FILES # LIST FILES
# -------------------------------------------------- # --------------------------------------------------
def list_repository_tree(self, path: str = ""): def list_repository_tree(self, path: str = "", __user__: dict = {}):
""" """
List files in the repository. List files and directories in the Gitea repository.
:param path: Optional subdirectory path to list. Leave empty for root.
:return: List of files and directories with name, path and type.
""" """
valves = self._get_valves(__user__)
if not self.valves.ENABLE_READ: if not valves.ENABLE_READ:
return "Reading repository disabled." return "❌ Lesen ist deaktiviert."
owner = self.valves.DEFAULT_OWNER err = self._check_config(valves)
repo = self.valves.DEFAULT_REPO if err:
branch = self.valves.DEFAULT_BRANCH return err
owner = valves.DEFAULT_OWNER
repo = valves.DEFAULT_REPO
branch = valves.DEFAULT_BRANCH
endpoint = f"/repos/{owner}/{repo}/contents/{path}?ref={branch}" endpoint = f"/repos/{owner}/{repo}/contents/{path}?ref={branch}"
data = self.api.get(endpoint) try:
data = self._get_api(valves).get(endpoint)
return [{"name": f["name"], "path": f["path"], "type": f["type"]} for f in data] return [
{"name": f["name"], "path": f["path"], "type": f["type"]} for f in data
]
except requests.HTTPError as e:
return f"❌ API-Fehler: HTTP {e.response.status_code} {e.response.text[:300]}"
except Exception as e:
return f"❌ Fehler: {type(e).__name__}: {e}"
# -------------------------------------------------- # --------------------------------------------------
# READ FILE # READ FILE
# -------------------------------------------------- # --------------------------------------------------
def read_file(self, path: str): def read_file(self, path: str, __user__: dict = {}):
""" """
Read a file from the repository. Read the content of a file from the Gitea repository.
:param path: Path to the file within the repository.
:return: The decoded file content as a string.
""" """
valves = self._get_valves(__user__)
if not self.valves.ENABLE_READ: if not valves.ENABLE_READ:
return "Reading disabled." return "❌ Lesen ist deaktiviert."
owner = self.valves.DEFAULT_OWNER err = self._check_config(valves)
repo = self.valves.DEFAULT_REPO if err:
branch = self.valves.DEFAULT_BRANCH return err
owner = valves.DEFAULT_OWNER
repo = valves.DEFAULT_REPO
branch = valves.DEFAULT_BRANCH
endpoint = f"/repos/{owner}/{repo}/contents/{path}?ref={branch}" endpoint = f"/repos/{owner}/{repo}/contents/{path}?ref={branch}"
data = self.api.get(endpoint) try:
data = self._get_api(valves).get(endpoint)
content = base64.b64decode(data["content"]).decode("utf-8") return base64.b64decode(data["content"]).decode("utf-8")
except requests.HTTPError as e:
return content return f"❌ API-Fehler: HTTP {e.response.status_code} {e.response.text[:300]}"
except Exception as e:
return f"❌ Fehler: {type(e).__name__}: {e}"
# -------------------------------------------------- # --------------------------------------------------
# CREATE OR UPDATE FILE # CREATE OR UPDATE FILE
# -------------------------------------------------- # --------------------------------------------------
def create_or_update_file( def create_or_update_file(
self, path: str, content: str, commit_message: str, branch: Optional[str] = None self,
path: str,
content: str,
commit_message: str,
branch: Optional[str] = None,
__user__: dict = {},
): ):
""" """
Create or update a file in the repository. Create or update a file in the Gitea repository.
:param path: Path of the file to create or update.
:param content: The new file content as a plain string.
:param commit_message: Git commit message.
:param branch: Target branch. Uses DEFAULT_BRANCH if omitted.
:return: Confirmation of the operation.
""" """
valves = self._get_valves(__user__)
if not self.valves.ENABLE_WRITE: if not valves.ENABLE_WRITE:
return "Write access disabled." return "❌ Schreiben ist deaktiviert."
if not path.startswith(self.valves.ALLOWED_PATH_PREFIX): if valves.ALLOWED_PATH_PREFIX and not path.startswith(
return "Path not allowed." valves.ALLOWED_PATH_PREFIX
):
return f"❌ Pfad nicht erlaubt. Erlaubtes Präfix: '{valves.ALLOWED_PATH_PREFIX}'"
if len(content) > self.valves.MAX_FILE_SIZE_KB * 1024: if len(content) > valves.MAX_FILE_SIZE_KB * 1024:
return "File too large." return f"❌ Datei zu groß (max. {valves.MAX_FILE_SIZE_KB} KB)."
owner = self.valves.DEFAULT_OWNER err = self._check_config(valves)
repo = self.valves.DEFAULT_REPO if err:
branch = branch or self.valves.DEFAULT_BRANCH return err
owner = valves.DEFAULT_OWNER
repo = valves.DEFAULT_REPO
target_branch = branch or valves.DEFAULT_BRANCH
endpoint = f"/repos/{owner}/{repo}/contents/{path}" endpoint = f"/repos/{owner}/{repo}/contents/{path}"
api = self._get_api(valves)
# check if file exists try:
sha = None sha = None
try: try:
existing = self.api.get(f"{endpoint}?ref={branch}") existing = api.get(f"{endpoint}?ref={target_branch}")
sha = existing["sha"] sha = existing["sha"]
except: except Exception:
pass pass
encoded = base64.b64encode(content.encode()).decode() encoded = base64.b64encode(content.encode()).decode()
payload = {
payload = {"message": commit_message, "content": encoded, "branch": branch} "message": commit_message,
"content": encoded,
"branch": target_branch,
}
if sha: if sha:
payload["sha"] = sha payload["sha"] = sha
return self.api.put(endpoint, payload) api.put(endpoint, payload)
action = "aktualisiert" if sha else "erstellt"
return f"✅ Datei '{path}' erfolgreich {action} (Branch: {target_branch})."
except requests.HTTPError as e:
return f"❌ API-Fehler: HTTP {e.response.status_code} {e.response.text[:300]}"
except Exception as e:
return f"❌ Fehler: {type(e).__name__}: {e}"
# -------------------------------------------------- # --------------------------------------------------
# CREATE BRANCH # CREATE BRANCH
# -------------------------------------------------- # --------------------------------------------------
def create_branch(self, new_branch: str, from_branch: Optional[str] = None): def create_branch(
self, new_branch: str, from_branch: Optional[str] = None, __user__: dict = {}
):
""" """
Create a new branch. Create a new branch in the Gitea repository.
:param new_branch: Name of the new branch to create.
:param from_branch: Source branch. Uses DEFAULT_BRANCH if omitted.
:return: Confirmation of the operation.
""" """
valves = self._get_valves(__user__)
if not self.valves.ENABLE_BRANCH: if not valves.ENABLE_BRANCH:
return "Branch creation disabled." return "Branch-Erstellung ist deaktiviert."
owner = self.valves.DEFAULT_OWNER err = self._check_config(valves)
repo = self.valves.DEFAULT_REPO if err:
return err
from_branch = from_branch or self.valves.DEFAULT_BRANCH owner = valves.DEFAULT_OWNER
repo = valves.DEFAULT_REPO
source = from_branch or valves.DEFAULT_BRANCH
endpoint = f"/repos/{owner}/{repo}/branches" endpoint = f"/repos/{owner}/{repo}/branches"
payload = {"new_branch_name": new_branch, "old_branch_name": source}
payload = {"new_branch_name": new_branch, "old_branch_name": from_branch} try:
self._get_api(valves).post(endpoint, payload)
return self.api.post(endpoint, payload) return f"✅ Branch '{new_branch}' erfolgreich aus '{source}' erstellt."
except requests.HTTPError as e:
return f"❌ API-Fehler: HTTP {e.response.status_code} {e.response.text[:300]}"
except Exception as e:
return f"❌ Fehler: {type(e).__name__}: {e}"
# -------------------------------------------------- # --------------------------------------------------
# CREATE PR # CREATE PR
# -------------------------------------------------- # --------------------------------------------------
def create_pull_request(self, head: str, base: str, title: str, body: str = ""): def create_pull_request(
self, head: str, base: str, title: str, body: str = "", __user__: dict = {}
):
""" """
Create a pull request. Create a pull request in the Gitea repository.
:param head: Source branch (the branch with your changes).
:param base: Target branch (e.g. 'main').
:param title: Title of the pull request.
:param body: Optional description for the pull request.
:return: Confirmation of the operation.
""" """
valves = self._get_valves(__user__)
if not self.valves.ENABLE_PR: if not valves.ENABLE_PR:
return "PR creation disabled." return "❌ Pull-Request-Erstellung ist deaktiviert."
owner = self.valves.DEFAULT_OWNER err = self._check_config(valves)
repo = self.valves.DEFAULT_REPO if err:
return err
owner = valves.DEFAULT_OWNER
repo = valves.DEFAULT_REPO
endpoint = f"/repos/{owner}/{repo}/pulls" endpoint = f"/repos/{owner}/{repo}/pulls"
payload = {"title": title, "head": head, "base": base, "body": body} payload = {"title": title, "head": head, "base": base, "body": body}
return self.api.post(endpoint, payload) try:
result = self._get_api(valves).post(endpoint, payload)
pr_id = result.get("number", "?")
return f"✅ Pull Request #{pr_id} '{title}' erfolgreich erstellt ({head}{base})."
except requests.HTTPError as e:
return f"❌ API-Fehler: HTTP {e.response.status_code} {e.response.text[:300]}"
except Exception as e:
return f"❌ Fehler: {type(e).__name__}: {e}"