215 lines
12 KiB
Python
215 lines
12 KiB
Python
"""Workspace-Übersicht, Anlegen, Detailseite und Mitglieder."""
|
|
|
|
from typing import Annotated
|
|
from urllib.parse import urlencode
|
|
|
|
from fastapi import APIRouter, Form, Request
|
|
from fastapi.responses import HTMLResponse, Response
|
|
|
|
from app import web
|
|
from app.ai import service as ai
|
|
from app.chat import service as chats
|
|
from app.core.errors import Forbidden, Invalid
|
|
from app.data_sources import service as sources
|
|
from app.documents import service as documents
|
|
from app.hugin import routes as hugin_routes
|
|
from app.hugin import service as hugin
|
|
from app.hugin import tools as hugin_tools
|
|
from app.identity.models import Team
|
|
from app.mcp import databases
|
|
from app.mcp import service as mcp
|
|
from app.tools import service as tools
|
|
from app.uploads import service as uploads
|
|
from app.workspaces import removal
|
|
from app.workspaces import service as workspaces
|
|
|
|
# Reiter der Workspace-Einstellungen
|
|
SETTINGS_TABS = {"general": "Allgemein", "members": "Mitglieder", "tools": "Tools und Regeln", "mcp": "MCP-Server",
|
|
"hugin": "Hugin"}
|
|
|
|
|
|
def router() -> APIRouter:
|
|
r = APIRouter()
|
|
|
|
@r.get("/")
|
|
def home(principal: web.User) -> Response:
|
|
return web.redirect("/workspaces")
|
|
|
|
@r.get("/workspaces")
|
|
def overview(request: Request, principal: web.User, deleted: str = "", memory: str = "",
|
|
trashed: str = "") -> HTMLResponse:
|
|
with web.db(request).session() as session:
|
|
memberships = workspaces.list_for(session, principal)
|
|
teams = {t.id: t.name for t in workspaces.teams_for(session, principal)}
|
|
context = {"memberships": memberships, "teams": teams, "can_create": bool(principal.team_ids),
|
|
"stats": workspaces.stats(session, [m.workspace.id for m in memberships]),
|
|
"trash": removal.trashed(session, principal, web.settings(request)),
|
|
"trash_days": web.settings(request).workspace_trash_days,
|
|
"deleted": deleted[:200], "memory": memory[:500], "trashed": trashed[:200]}
|
|
return web.render(request, "workspaces.html", context)
|
|
|
|
@r.post("/workspaces/{workspace_id}/delete")
|
|
def delete(request: Request, principal: web.Checked, workspace_id: int,
|
|
confirmation: Annotated[str, Form()] = "") -> Response:
|
|
with web.db(request).session() as session:
|
|
workspace = removal.trash(session, principal, workspace_id, confirmation)
|
|
return web.redirect(f"/workspaces?{urlencode({'trashed': workspace.name})}#trash")
|
|
|
|
@r.post("/workspaces/{workspace_id}/restore")
|
|
def restore(request: Request, principal: web.Checked, workspace_id: int) -> Response:
|
|
with web.db(request).session() as session:
|
|
removal.restore(session, principal, workspace_id)
|
|
return web.redirect(f"/workspaces/{workspace_id}")
|
|
|
|
@r.post("/workspaces/{workspace_id}/purge")
|
|
def purge(request: Request, principal: web.Checked, workspace_id: int) -> Response:
|
|
with web.db(request).session() as session:
|
|
removed = removal.purge(session, principal, web.settings(request), workspace_id)
|
|
query = {"deleted": removed.name}
|
|
if removed.memory_servers:
|
|
query["memory"] = ", ".join(removed.memory_servers)
|
|
return web.redirect(f"/workspaces?{urlencode(query)}")
|
|
|
|
@r.get("/workspaces/new")
|
|
def new(request: Request, principal: web.User) -> HTMLResponse:
|
|
with web.db(request).session() as session:
|
|
teams = workspaces.teams_for(session, principal)
|
|
return web.render(request, "workspace_new.html", {"teams": teams, "form": {}})
|
|
|
|
@r.post("/workspaces")
|
|
def create(request: Request, principal: web.Checked, team_id: Annotated[int, Form()],
|
|
name: Annotated[str, Form()], description: Annotated[str, Form()] = "") -> Response:
|
|
try:
|
|
with web.db(request).session() as session:
|
|
workspace = workspaces.create(session, principal, team_id, name, description)
|
|
except Invalid as e:
|
|
with web.db(request).session() as session:
|
|
teams = workspaces.teams_for(session, principal)
|
|
form = {"team_id": team_id, "name": name, "description": description}
|
|
return web.render(request, "workspace_new.html", {"teams": teams, "form": form, "error": str(e)}, 400)
|
|
return web.redirect(f"/workspaces/{workspace.id}")
|
|
|
|
def base(session, principal, workspace_id: int) -> dict:
|
|
membership = workspaces.get(session, principal, workspace_id)
|
|
return {"membership": membership, "workspace": membership.workspace,
|
|
"current_workspace": membership.workspace, "team": session.get(Team, membership.workspace.team_id)}
|
|
|
|
@r.get("/workspaces/{workspace_id}")
|
|
def detail(request: Request, principal: web.User, workspace_id: int, q: str = "") -> HTMLResponse:
|
|
with web.db(request).session() as session:
|
|
context = base(session, principal, workspace_id)
|
|
context.update({
|
|
"sources": sources.list_for(session, principal, workspace_id),
|
|
"chats": chats.overview(session, principal, workspace_id, q),
|
|
"q": q,
|
|
"ready": sources.chat_ready(session, workspace_id),
|
|
"uploads": uploads.list_for(session, principal, workspace_id),
|
|
"effective_model": ai.effective_model(session, context["workspace"]),
|
|
"examples": workspaces.examples(context["workspace"]),
|
|
"folders": documents.folders(session, principal, workspace_id),
|
|
})
|
|
return web.render(request, "workspace.html", context)
|
|
|
|
@r.get("/workspaces/{workspace_id}/data")
|
|
def data(request: Request, principal: web.User, workspace_id: int) -> HTMLResponse:
|
|
with web.db(request).session() as session:
|
|
context = base(session, principal, workspace_id)
|
|
context.update({
|
|
"sources": sources.list_for(session, principal, workspace_id),
|
|
"uploads": uploads.list_for(session, principal, workspace_id),
|
|
"upload_max_mb": web.settings(request).upload_max_mb,
|
|
"storage": uploads.storage(session, web.settings(request), context["workspace"]),
|
|
"ready": sources.chat_ready(session, workspace_id),
|
|
})
|
|
folders = documents.folders(session, principal, workspace_id)
|
|
context.update({"folders": folders, "folder_summaries": documents.summaries(session, [f.id for f in folders]),
|
|
"scopes": documents.SCOPES})
|
|
return web.render(request, "workspace_data.html", context)
|
|
|
|
@r.get("/workspaces/{workspace_id}/settings")
|
|
def settings_page(request: Request, principal: web.User, workspace_id: int, tab: str = "general",
|
|
tool: str = "", saved: str = "") -> HTMLResponse:
|
|
with web.db(request).session() as session:
|
|
context = base(session, principal, workspace_id)
|
|
if not context["membership"].is_admin:
|
|
raise Forbidden
|
|
source_list = sources.list_for(session, principal, workspace_id)
|
|
upload_list = uploads.list_for(session, principal, workspace_id)
|
|
kinds = ({s.kind for s in source_list if s.status == "ok"} | mcp.kinds(session, workspace_id)
|
|
| ({"uploads"} if any(u.status == "ok" for u in upload_list) else set())
|
|
| hugin_tools.kinds(hugin.load(session, workspace_id)))
|
|
context.update({
|
|
"sources": source_list,
|
|
"members": workspaces.members(session, principal, workspace_id),
|
|
"models": ai.models(session, active_only=True),
|
|
"effective_model": ai.effective_model(session, context["workspace"]),
|
|
"tool_rounds": workspaces.tool_rounds(context["workspace"], web.settings(request)),
|
|
"rounds_default": web.settings(request).max_tool_rounds,
|
|
"rounds_limit": web.settings(request).max_tool_rounds_limit,
|
|
"tools": tools.overview(session, context["workspace"], kinds),
|
|
"mcp_servers": mcp.workspace_servers(session, workspace_id),
|
|
"connections": mcp.connections(session, workspace_id),
|
|
"db_drivers": databases.drivers(session),
|
|
"source_rules": tools.source_policies(session, workspace_id),
|
|
"memory_enabled": any(s.role == "memory" for s, _ in mcp.enabled(session, workspace_id)),
|
|
"trash_days": web.settings(request).workspace_trash_days,
|
|
"saved": bool(saved),
|
|
"tab": tab if tab in SETTINGS_TABS else "general",
|
|
"tabs": SETTINGS_TABS,
|
|
"hugin": hugin_routes.tab_context(session, web.settings(request), context["workspace"]),
|
|
})
|
|
# Nach dem Setzen einer Vorgabe: was jetzt für das Tool gilt
|
|
context["saved_tool"] = next((t for t in context["tools"] if t.definition.name == tool), None)
|
|
return web.render(request, "workspace_settings.html", context)
|
|
|
|
@r.post("/workspaces/{workspace_id}/settings")
|
|
def update(request: Request, principal: web.Checked, workspace_id: int, name: Annotated[str, Form()],
|
|
description: Annotated[str, Form()] = "") -> Response:
|
|
with web.db(request).session() as session:
|
|
workspaces.update(session, principal, workspace_id, name, description)
|
|
return web.redirect(f"/workspaces/{workspace_id}/settings?tab=general&saved=1")
|
|
|
|
@r.post("/workspaces/{workspace_id}/examples")
|
|
def set_examples(request: Request, principal: web.Checked, workspace_id: int,
|
|
examples: Annotated[str, Form()] = "") -> Response:
|
|
with web.db(request).session() as session:
|
|
workspaces.set_examples(session, principal, workspace_id, examples)
|
|
return web.redirect(f"/workspaces/{workspace_id}/settings?tab=general&saved=1#examples")
|
|
|
|
@r.post("/workspaces/{workspace_id}/tool-rounds")
|
|
def tool_rounds(request: Request, principal: web.Checked, workspace_id: int,
|
|
value: Annotated[str, Form()] = "") -> Response:
|
|
with web.db(request).session() as session:
|
|
workspaces.set_tool_rounds(session, principal, web.settings(request), workspace_id, value)
|
|
return web.redirect(f"/workspaces/{workspace_id}/settings?tab=general&saved=1#answers")
|
|
|
|
@r.post("/workspaces/{workspace_id}/approval-mode")
|
|
def approval_mode(request: Request, principal: web.Checked, workspace_id: int,
|
|
mode: Annotated[str, Form()]) -> Response:
|
|
with web.db(request).session() as session:
|
|
workspaces.set_approval_mode(session, principal, workspace_id, mode)
|
|
return web.redirect(f"/workspaces/{workspace_id}/settings?tab=general&saved=1#approvals")
|
|
|
|
@r.post("/workspaces/{workspace_id}/members")
|
|
def set_member(request: Request, principal: web.Checked, workspace_id: int,
|
|
username: Annotated[str, Form()], role: Annotated[str, Form()]) -> Response:
|
|
with web.db(request).session() as session:
|
|
workspaces.set_member(session, principal, workspace_id, username, role)
|
|
if web.is_htmx(request):
|
|
return Response(headers={"HX-Refresh": "true"})
|
|
return web.redirect(f"/workspaces/{workspace_id}/settings?tab=members&saved=1")
|
|
|
|
@r.post("/workspaces/{workspace_id}/members/{user_id}/role")
|
|
def set_role(request: Request, principal: web.Checked, workspace_id: int, user_id: int,
|
|
role: Annotated[str, Form()]) -> Response:
|
|
with web.db(request).session() as session:
|
|
workspaces.set_role(session, principal, workspace_id, user_id, role)
|
|
return web.redirect(f"/workspaces/{workspace_id}/settings?tab=members&saved=1")
|
|
|
|
@r.post("/workspaces/{workspace_id}/members/{user_id}/remove")
|
|
def remove_member(request: Request, principal: web.Checked, workspace_id: int, user_id: int) -> Response:
|
|
with web.db(request).session() as session:
|
|
workspaces.remove_member(session, principal, workspace_id, user_id)
|
|
return web.redirect(f"/workspaces/{workspace_id}/settings?tab=members&saved=1")
|
|
|
|
return r
|