mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
Add edit text support to stirling engine (#6245)
# Description of Changes Hooks up the (alpha) PDF Editor backend to the AI engine Edit Agent via an intermediary API which is easier for the agent to call. It suffers from all the same issues that the PDF Editor does in actually editing the text, but should also benefit from any fixes to that. It also adds protection against the underlying tools misbehaving by hanging, and fixes a hanging bug in the PDF Editor. --------- Co-authored-by: EthanHealy01 <[email protected]>
This commit is contained in:
co-authored by
EthanHealy01
parent
294b616a63
commit
575684ee4b
+41
-4
@@ -30,6 +30,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.service.FileStorage;
|
||||
import stirling.software.common.service.InternalApiClient;
|
||||
import stirling.software.common.service.InternalApiTimeoutException;
|
||||
import stirling.software.common.service.ToolMetadataService;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
@@ -323,10 +324,12 @@ public class AiWorkflowService {
|
||||
result.files(),
|
||||
inputFileNames(filesById),
|
||||
result.report()));
|
||||
} catch (InternalApiTimeoutException e) {
|
||||
log.error("Tool {} timed out: {}", endpointPath, e.getMessage());
|
||||
return new WorkflowState.Terminal(cannotContinue(toolTimeoutMessage(endpointPath, e)));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to execute tool {}: {}", endpointPath, e.getMessage(), e);
|
||||
return new WorkflowState.Terminal(
|
||||
cannotContinue("Tool execution failed: " + e.getMessage()));
|
||||
return new WorkflowState.Terminal(cannotContinue(toolFailureMessage(endpointPath, e)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,6 +417,10 @@ public class AiWorkflowService {
|
||||
return new WorkflowState.Terminal(
|
||||
buildCompletedResponse(
|
||||
summary, currentFiles, inputFileNames(filesById), lastReport));
|
||||
} catch (InternalApiTimeoutException e) {
|
||||
log.error("Plan step on tool {} timed out: {}", e.getEndpointPath(), e.getMessage());
|
||||
return new WorkflowState.Terminal(
|
||||
cannotContinue(toolTimeoutMessage(e.getEndpointPath(), e)));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to execute plan: {}", e.getMessage(), e);
|
||||
return new WorkflowState.Terminal(
|
||||
@@ -425,6 +432,20 @@ public class AiWorkflowService {
|
||||
return filesById.values().stream().map(MultipartFile::getOriginalFilename).toList();
|
||||
}
|
||||
|
||||
private static String toolTimeoutMessage(String endpointPath, InternalApiTimeoutException e) {
|
||||
return String.format(
|
||||
"The %s tool did not respond within %d seconds and was aborted. The underlying"
|
||||
+ " operation may be hung; try again, run on a smaller file, or use a"
|
||||
+ " different approach.",
|
||||
endpointPath, e.getReadTimeout().toSeconds());
|
||||
}
|
||||
|
||||
private static String toolFailureMessage(String endpointPath, Throwable cause) {
|
||||
String reason =
|
||||
cause.getMessage() != null ? cause.getMessage() : cause.getClass().getSimpleName();
|
||||
return String.format("The %s tool failed: %s", endpointPath, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a single tool step. If the endpoint accepts multiple files, all files are sent in one
|
||||
* call. Otherwise, the endpoint is called once per file. ZIP responses are unpacked so each
|
||||
@@ -477,8 +498,15 @@ public class AiWorkflowService {
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : parameters.entrySet()) {
|
||||
if (entry.getValue() instanceof List<?> list) {
|
||||
for (Object item : list) {
|
||||
body.add(entry.getKey(), item);
|
||||
if (containsStructuredElements(list)) {
|
||||
// Endpoints binding lists of structured objects (e.g. /security/redact's
|
||||
// redactions, /general/edit-text's edits) parse a single JSON string field via
|
||||
// a property editor. Pre-serialize the whole list so binding succeeds.
|
||||
body.add(entry.getKey(), objectMapper.writeValueAsString(list));
|
||||
} else {
|
||||
for (Object item : list) {
|
||||
body.add(entry.getKey(), item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
body.add(entry.getKey(), entry.getValue());
|
||||
@@ -529,6 +557,15 @@ public class AiWorkflowService {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean containsStructuredElements(List<?> list) {
|
||||
for (Object item : list) {
|
||||
if (item instanceof Map<?, ?> || item instanceof List<?>) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<Resource> toResources(Map<String, MultipartFile> filesById) throws IOException {
|
||||
List<Resource> resources = new ArrayList<>();
|
||||
for (MultipartFile file : filesById.values()) {
|
||||
|
||||
Reference in New Issue
Block a user