added upload folder location for all document creation nodes
This commit is contained in:
parent
eeb9a4a161
commit
6e3da0d0d8
8 changed files with 110 additions and 12 deletions
|
|
@ -30,9 +30,6 @@ AI_NODES = [
|
|||
{"name": "context", "type": "Any", "required": False, "frontendType": "contextBuilder",
|
||||
"description": t("Daten aus vorherigen Schritten"), "default": "",
|
||||
"graphInherit": {"port": 0, "kind": "primaryTextRef"}},
|
||||
{"name": "documentTheme", "type": "str", "required": False, "frontendType": "select",
|
||||
"frontendOptions": {"options": ["general", "finance", "legal", "technical", "hr"]},
|
||||
"description": t("Dokument-Thema (Style-Hinweis fuer den Renderer)"), "default": "general"},
|
||||
{"name": "simpleMode", "type": "bool", "required": False, "frontendType": "checkbox",
|
||||
"description": t("Einfacher Modus"), "default": True},
|
||||
] + _AI_COMMON_PARAMS,
|
||||
|
|
@ -80,9 +77,15 @@ AI_NODES = [
|
|||
{"name": "documentList", "type": "DocumentList", "required": True, "frontendType": "dataRef",
|
||||
"description": t("Dokumente aus vorherigen Schritten"),
|
||||
"graphInherit": {"port": 0, "kind": "documentListWire"}},
|
||||
{"name": "resultType", "type": "str", "required": False, "frontendType": "select",
|
||||
"frontendOptions": {"options": ["txt", "json", "md", "csv", "xml", "html", "pdf", "docx", "xlsx", "pptx", "png", "jpg"]},
|
||||
"description": t("Ausgabeformat"), "default": "txt"},
|
||||
{"name": "summaryLength", "type": "str", "required": False, "frontendType": "select",
|
||||
"frontendOptions": {"options": ["brief", "medium", "detailed"]},
|
||||
"description": t("Kurz, mittel oder ausführlich"), "default": "medium"},
|
||||
{"name": "folderId", "type": "str", "required": False, "frontendType": "userFileFolder",
|
||||
"description": t("Zielordner in Meine Dateien"),
|
||||
"default": ""},
|
||||
] + _AI_COMMON_PARAMS,
|
||||
"inputs": 1,
|
||||
"outputs": 1,
|
||||
|
|
@ -101,8 +104,14 @@ AI_NODES = [
|
|||
{"name": "documentList", "type": "DocumentList", "required": True, "frontendType": "dataRef",
|
||||
"description": t("Dokumente aus vorherigen Schritten"),
|
||||
"graphInherit": {"port": 0, "kind": "documentListWire"}},
|
||||
{"name": "resultType", "type": "str", "required": False, "frontendType": "select",
|
||||
"frontendOptions": {"options": ["txt", "json", "md", "csv", "xml", "html", "pdf", "docx", "xlsx", "pptx", "png", "jpg"]},
|
||||
"description": t("Ausgabeformat"), "default": "txt"},
|
||||
{"name": "targetLanguage", "type": "str", "required": True, "frontendType": "text",
|
||||
"description": t("Zielsprache (z.B. de, en, French)")},
|
||||
{"name": "folderId", "type": "str", "required": False, "frontendType": "userFileFolder",
|
||||
"description": t("Zielordner in Meine Dateien"),
|
||||
"default": ""},
|
||||
] + _AI_COMMON_PARAMS,
|
||||
"inputs": 1,
|
||||
"outputs": 1,
|
||||
|
|
@ -124,6 +133,9 @@ AI_NODES = [
|
|||
{"name": "targetFormat", "type": "str", "required": True, "frontendType": "select",
|
||||
"frontendOptions": {"options": ["docx", "pdf", "xlsx", "csv", "txt", "html", "json", "md"]},
|
||||
"description": t("Zielformat")},
|
||||
{"name": "folderId", "type": "str", "required": False, "frontendType": "userFileFolder",
|
||||
"description": t("Zielordner in Meine Dateien"),
|
||||
"default": ""},
|
||||
] + _AI_COMMON_PARAMS,
|
||||
"inputs": 1,
|
||||
"outputs": 1,
|
||||
|
|
@ -149,6 +161,9 @@ AI_NODES = [
|
|||
{"name": "documentType", "type": "str", "required": False, "frontendType": "select",
|
||||
"frontendOptions": {"options": ["letter", "memo", "proposal", "contract", "report", "email"]},
|
||||
"description": t("Dokumentart (Inhaltshinweis fuer die KI)"), "default": "proposal"},
|
||||
{"name": "folderId", "type": "str", "required": False, "frontendType": "userFileFolder",
|
||||
"description": t("Zielordner in Meine Dateien"),
|
||||
"default": ""},
|
||||
{"name": "context", "type": "Any", "required": False, "frontendType": "contextBuilder",
|
||||
"description": t("Daten aus vorherigen Schritten"), "default": "",
|
||||
"graphInherit": {"port": 0, "kind": "primaryTextRef"}},
|
||||
|
|
@ -177,6 +192,9 @@ AI_NODES = [
|
|||
{"name": "resultType", "type": "str", "required": False, "frontendType": "select",
|
||||
"frontendOptions": {"options": ["py", "js", "ts", "html", "java", "cpp", "txt", "json", "csv", "xml"]},
|
||||
"description": t("Datei-Endung der erzeugten Code-Datei"), "default": "py"},
|
||||
{"name": "folderId", "type": "str", "required": False, "frontendType": "userFileFolder",
|
||||
"description": t("Zielordner in Meine Dateien"),
|
||||
"default": ""},
|
||||
{"name": "context", "type": "Any", "required": False, "frontendType": "contextBuilder",
|
||||
"description": t("Daten aus vorherigen Schritten"), "default": "",
|
||||
"graphInherit": {"port": 0, "kind": "primaryTextRef"}},
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ FILE_NODES = [
|
|||
"description": t("Ausgabeformat"), "default": "docx"},
|
||||
{"name": "title", "type": "str", "required": False, "frontendType": "text",
|
||||
"description": t("Dokumenttitel")},
|
||||
{"name": "folderId", "type": "str", "required": False, "frontendType": "userFileFolder",
|
||||
"description": t("Zielordner in Meine Dateien"),
|
||||
"default": ""},
|
||||
{"name": "context", "type": "Any", "required": False, "frontendType": "contextBuilder",
|
||||
"description": t("Daten aus vorherigen Schritten"), "default": "",
|
||||
"graphInherit": {"port": 0, "kind": "primaryTextRef"}},
|
||||
|
|
|
|||
|
|
@ -1345,16 +1345,34 @@ class ComponentObjects:
|
|||
return newfileName
|
||||
counter += 1
|
||||
|
||||
def createFile(self, name: str, mimeType: str, content: bytes) -> FileItem:
|
||||
def createFile(
|
||||
self,
|
||||
name: str,
|
||||
mimeType: str,
|
||||
content: bytes,
|
||||
folderId: Optional[str] = None,
|
||||
) -> FileItem:
|
||||
"""Creates a new file entry if user has permission. Computes fileHash and fileSize from content.
|
||||
|
||||
Duplicate check: if a file with the same user + fileHash + fileName already exists,
|
||||
the existing file is returned instead of creating a new one.
|
||||
Same hash with different name is allowed (intentional copy by user).
|
||||
|
||||
When ``folderId`` is set, the folder must exist and the user must be allowed to modify it.
|
||||
"""
|
||||
if not self.checkRbacPermission(FileItem, "create"):
|
||||
raise PermissionError("No permission to create files")
|
||||
|
||||
resolved_folder_id: Optional[str] = None
|
||||
if folderId is not None:
|
||||
raw = str(folderId).strip()
|
||||
if raw:
|
||||
folder = self.getFolder(raw)
|
||||
if not folder:
|
||||
raise FileNotFoundError(f"Folder {raw} not found")
|
||||
self._requireFolderWriteAccess(folder, raw, "update")
|
||||
resolved_folder_id = raw
|
||||
|
||||
# Compute file size and hash
|
||||
fileSize = len(content)
|
||||
fileHash = hashlib.sha256(content).hexdigest()
|
||||
|
|
@ -1386,6 +1404,7 @@ class ComponentObjects:
|
|||
mimeType=mimeType,
|
||||
fileSize=fileSize,
|
||||
fileHash=fileHash,
|
||||
folderId=resolved_folder_id,
|
||||
)
|
||||
# Ensure audit user is always stored: workflow/singleton contexts sometimes leave
|
||||
# the connector without _current_user_id, so _saveRecord skips sysCreatedBy →
|
||||
|
|
|
|||
|
|
@ -88,6 +88,9 @@ class FrontendType(str, Enum):
|
|||
FILTER_EXPRESSION = "filterExpression"
|
||||
"""Filter expression builder for data.filter"""
|
||||
|
||||
USER_FILE_FOLDER = "userFileFolder"
|
||||
"""User file storage folder (graph editor): browse My Files tree or create folders."""
|
||||
|
||||
|
||||
# Mapping of custom types to their API endpoint for dynamic options
|
||||
CUSTOM_TYPE_OPTIONS_API: Dict[FrontendType, str] = {
|
||||
|
|
|
|||
|
|
@ -393,6 +393,13 @@ class ActionNodeExecutor:
|
|||
return _normalizeError(e, outputSchema)
|
||||
|
||||
# 9. Persist generated documents as files and build JSON-safe output
|
||||
_raw_folder_id = resolvedParams.get("folderId")
|
||||
persist_folder_id: Optional[str] = None
|
||||
if _raw_folder_id is not None:
|
||||
_s = str(_raw_folder_id).strip()
|
||||
if _s:
|
||||
persist_folder_id = _s
|
||||
|
||||
docsList = []
|
||||
for d in (result.documents or []):
|
||||
dumped = d.model_dump() if hasattr(d, "model_dump") else dict(d) if isinstance(d, dict) else d
|
||||
|
|
@ -432,7 +439,7 @@ class ActionNodeExecutor:
|
|||
_mgmt = _getMgmtInterface(_owner, mandateId=_mandateId, featureInstanceId=_instanceId)
|
||||
_docName = dumped.get("documentName") or f"workflow-result-{nodeId}.bin"
|
||||
_mimeType = dumped.get("mimeType") or "application/octet-stream"
|
||||
_fileItem = _mgmt.createFile(_docName, _mimeType, rawBytes)
|
||||
_fileItem = _mgmt.createFile(_docName, _mimeType, rawBytes, folderId=persist_folder_id)
|
||||
_mgmt.createFileData(_fileItem.id, rawBytes)
|
||||
dumped["fileId"] = _fileItem.id
|
||||
dumped["id"] = _fileItem.id
|
||||
|
|
|
|||
|
|
@ -194,7 +194,14 @@ class MethodAi(MethodBase):
|
|||
required=False,
|
||||
default="txt",
|
||||
description="Output file extension"
|
||||
)
|
||||
),
|
||||
"folderId": WorkflowActionParameter(
|
||||
name="folderId",
|
||||
type="str",
|
||||
frontendType=FrontendType.USER_FILE_FOLDER,
|
||||
required=False,
|
||||
description="Target folder in My Files when persisting workflow output",
|
||||
),
|
||||
},
|
||||
execute=summarizeDocument.__get__(self, self.__class__)
|
||||
),
|
||||
|
|
@ -239,7 +246,14 @@ class MethodAi(MethodBase):
|
|||
frontendType=FrontendType.TEXT,
|
||||
required=False,
|
||||
description="Output file extension. If not specified, uses same format as input"
|
||||
)
|
||||
),
|
||||
"folderId": WorkflowActionParameter(
|
||||
name="folderId",
|
||||
type="str",
|
||||
frontendType=FrontendType.USER_FILE_FOLDER,
|
||||
required=False,
|
||||
description="Target folder in My Files when persisting workflow output",
|
||||
),
|
||||
},
|
||||
execute=translateDocument.__get__(self, self.__class__)
|
||||
),
|
||||
|
|
@ -271,7 +285,14 @@ class MethodAi(MethodBase):
|
|||
required=False,
|
||||
default=True,
|
||||
description="Whether to preserve document structure (headings, tables, etc.)"
|
||||
)
|
||||
),
|
||||
"folderId": WorkflowActionParameter(
|
||||
name="folderId",
|
||||
type="str",
|
||||
frontendType=FrontendType.USER_FILE_FOLDER,
|
||||
required=False,
|
||||
description="Target folder in My Files when persisting workflow output",
|
||||
),
|
||||
},
|
||||
execute=convertDocument.__get__(self, self.__class__)
|
||||
),
|
||||
|
|
@ -335,6 +356,13 @@ class MethodAi(MethodBase):
|
|||
required=False,
|
||||
description="Legacy/API output format extension (e.g. txt, docx). Ignored when outputFormat is set."
|
||||
),
|
||||
"folderId": WorkflowActionParameter(
|
||||
name="folderId",
|
||||
type="str",
|
||||
frontendType=FrontendType.USER_FILE_FOLDER,
|
||||
required=False,
|
||||
description="Target folder in My Files when persisting workflow output",
|
||||
),
|
||||
},
|
||||
execute=generateDocument.__get__(self, self.__class__)
|
||||
),
|
||||
|
|
@ -366,7 +394,14 @@ class MethodAi(MethodBase):
|
|||
frontendOptions=["py", "js", "ts", "html", "java", "cpp", "txt", "json", "csv", "xml"],
|
||||
required=False,
|
||||
description="Output format (html, js, py, json, csv, xml, etc.). Optional: if omitted, formats are determined from prompt by AI. This action can return MULTIPLE files in a single call when the prompt requests multiple files. With per-document format determination, AI can determine different formats for different files based on prompt. When multiple files are requested, the action will return multiple documents (one per file)."
|
||||
)
|
||||
),
|
||||
"folderId": WorkflowActionParameter(
|
||||
name="folderId",
|
||||
type="str",
|
||||
frontendType=FrontendType.USER_FILE_FOLDER,
|
||||
required=False,
|
||||
description="Target folder in My Files when persisting workflow output",
|
||||
),
|
||||
},
|
||||
execute=generateCode.__get__(self, self.__class__)
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2025 Patrick Motsch
|
||||
# All rights reserved.
|
||||
|
||||
from typing import Dict, Any
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
|
|
@ -17,6 +17,7 @@ logger = logging.getLogger(__name__)
|
|||
def _persistDocumentsToUserFiles(
|
||||
action_documents: list,
|
||||
services,
|
||||
folder_id: Optional[str] = None,
|
||||
) -> 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."""
|
||||
|
|
@ -70,7 +71,7 @@ def _persistDocumentsToUserFiles(
|
|||
doc_name,
|
||||
len(content),
|
||||
)
|
||||
file_item = mgmt.createFile(doc_name, mime, content)
|
||||
file_item = mgmt.createFile(doc_name, mime, content, folderId=folder_id)
|
||||
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)
|
||||
|
|
@ -111,6 +112,11 @@ async def create(self, parameters: Dict[str, Any]) -> ActionResult:
|
|||
"de",
|
||||
)
|
||||
|
||||
folder_id: Optional[str] = None
|
||||
raw_folder = parameters.get("folderId")
|
||||
if raw_folder is not None and str(raw_folder).strip():
|
||||
folder_id = str(raw_folder).strip()
|
||||
|
||||
try:
|
||||
structured_content = markdownToDocumentJson(context, title, language)
|
||||
if templateName:
|
||||
|
|
@ -164,7 +170,7 @@ async def create(self, parameters: Dict[str, Any]) -> ActionResult:
|
|||
},
|
||||
))
|
||||
|
||||
_persistDocumentsToUserFiles(action_documents, self.services)
|
||||
_persistDocumentsToUserFiles(action_documents, self.services, folder_id=folder_id)
|
||||
return ActionResult.isSuccess(documents=action_documents)
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -73,6 +73,13 @@ class MethodFile(MethodBase):
|
|||
default="de",
|
||||
description="Language code",
|
||||
),
|
||||
"folderId": WorkflowActionParameter(
|
||||
name="folderId",
|
||||
type="str",
|
||||
frontendType=FrontendType.USER_FILE_FOLDER,
|
||||
required=False,
|
||||
description="Optional My Files folder to store created documents",
|
||||
),
|
||||
},
|
||||
execute=create.__get__(self, self.__class__),
|
||||
),
|
||||
|
|
|
|||
Loading…
Reference in a new issue