gateway/modules/workflows/methods/methodFile/actions/create.py

167 lines
6.8 KiB
Python

# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
import base64
import logging
from typing import Dict, Any
from modules.datamodels.datamodelChat import ActionResult, ActionDocument
from modules.serviceCenter.services.serviceGeneration.subDocumentUtility import markdownToDocumentJson
from modules.shared.i18nRegistry import normalizePrimaryLanguageTag
logger = logging.getLogger(__name__)
def _persistDocumentsToUserFiles(
action_documents: list,
services,
) -> None:
"""Persist file.create output documents to user's file storage (like upload).
Adds fileId to each document's validationMetadata for download links in UI."""
mgmt = getattr(services, "interfaceDbComponent", None)
if not mgmt:
try:
import modules.interfaces.interfaceDbManagement as iface
user = getattr(services, "user", None)
if not user:
return
mgmt = iface.getInterface(
user,
mandateId=getattr(services, "mandateId", None) or "",
featureInstanceId=getattr(services, "featureInstanceId", None) or "",
)
except Exception as e:
logger.warning("file.create: could not get management interface for persistence: %s", e)
return
if not mgmt:
return
logger.info(
"file.create persist: mgmt=%s id(mgmt)=%s has_createFileData=%s",
type(mgmt).__name__,
id(mgmt),
hasattr(mgmt, "createFileData"),
)
for doc in action_documents:
try:
doc_data = doc.documentData if hasattr(doc, "documentData") else doc.get("documentData")
if not doc_data:
continue
if isinstance(doc_data, str):
content = base64.b64decode(doc_data)
else:
content = doc_data
doc_name = (
getattr(doc, "documentName", None)
or doc.get("documentName")
or "output.pdf"
)
mime = (
getattr(doc, "mimeType", None)
or doc.get("mimeType")
or "application/octet-stream"
)
logger.info(
"file.create persist: calling createFile name=%s bytes=%s",
doc_name,
len(content),
)
file_item = mgmt.createFile(doc_name, mime, content)
logger.info("file.create persist: createFile returned id=%s", file_item.id)
ok = mgmt.createFileData(file_item.id, content)
logger.info("file.create persist: createFileData returned %s for id=%s", ok, file_item.id)
meta = getattr(doc, "validationMetadata", None) or doc.get("validationMetadata") or {}
if isinstance(meta, dict):
meta["fileId"] = file_item.id
if hasattr(doc, "validationMetadata"):
doc.validationMetadata = meta
elif isinstance(doc, dict):
doc["validationMetadata"] = meta
logger.info("file.create: persisted %s to user files (id=%s)", doc_name, file_item.id)
except Exception as e:
dname = getattr(doc, "documentName", None) or doc.get("documentName", "?")
logger.warning("file.create: failed to persist document %s: %s", dname, e)
async def create(self, parameters: Dict[str, Any]) -> ActionResult:
"""
Create a file from context (text/markdown from upstream AI node).
Uses GenerationService.renderReport to produce docx, pdf, txt, md, html, xlsx, etc.
"""
from modules.workflows.methods.methodAi._common import serialize_context
raw_context = parameters.get("context", "") or parameters.get("text", "") or ""
context = serialize_context(raw_context)
if not context:
logger.warning(
"file.create: context empty after resolve — check DataRefs (e.g. Antworttext / "
"documents[0].documentData from the AI step)."
)
return ActionResult.isFailure(error="context is required (connect an AI node or provide text)")
outputFormat = (parameters.get("outputFormat") or "docx").strip().lower().lstrip(".")
title = (parameters.get("title") or "Document").strip()
templateName = parameters.get("templateName")
language = normalizePrimaryLanguageTag(
str(parameters.get("language") or "de"),
"de",
)
try:
structured_content = markdownToDocumentJson(context, title, language)
if templateName:
structured_content.setdefault("metadata", {})["templateName"] = templateName
generation = getattr(self.services, "generation", None)
if not generation:
return ActionResult.isFailure(error="Generation service not available")
ai_service = getattr(self.services, "ai", None)
rendered_docs = await generation.renderReport(
extractedContent=structured_content,
outputFormat=outputFormat,
language=language,
title=title,
userPrompt=None,
aiService=ai_service,
parentOperationId=parameters.get("parentOperationId"),
)
if not rendered_docs:
return ActionResult.isFailure(error="Rendering produced no output")
action_documents = []
mime_map = {
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"pdf": "application/pdf",
"txt": "text/plain",
"md": "text/markdown",
"html": "text/html",
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"csv": "text/csv",
"json": "application/json",
}
for rd in rendered_docs:
doc_data = rd.documentData if hasattr(rd, "documentData") else getattr(rd, "document_data", None)
doc_name = getattr(rd, "filename", None) or getattr(rd, "documentName", None) or getattr(rd, "document_name", f"output.{outputFormat}")
mime = getattr(rd, "mimeType", None) or getattr(rd, "mime_type", None) or mime_map.get(outputFormat, "application/octet-stream")
if isinstance(doc_data, bytes):
doc_data = base64.b64encode(doc_data).decode("ascii")
action_documents.append(ActionDocument(
documentName=doc_name,
documentData=doc_data,
mimeType=mime,
validationMetadata={
"actionType": "file.create",
"outputFormat": outputFormat,
"templateName": templateName,
},
))
_persistDocumentsToUserFiles(action_documents, self.services)
return ActionResult.isSuccess(documents=action_documents)
except Exception as e:
logger.error(f"file.create failed: {e}", exc_info=True)
return ActionResult.isFailure(error=str(e))