diff --git a/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/aidecompiler/AIDecompilationdWindow.java b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/aidecompiler/AIDecompilationdWindow.java index f0b1416..f2d8fea 100644 --- a/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/aidecompiler/AIDecompilationdWindow.java +++ b/src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/aidecompiler/AIDecompilationdWindow.java @@ -1,10 +1,11 @@ package ai.reveng.toolkit.ghidra.binarysimilarity.ui.aidecompiler; import ai.reveng.invoker.ApiException; -import ai.reveng.model.AiDecompilationTaskStatus; -import ai.reveng.model.GetAiDecompilationTask; +import ai.reveng.model.DecompilationData; +import ai.reveng.model.WorkflowProgress; import ai.reveng.toolkit.ghidra.core.services.api.GhidraRevengService; import ai.reveng.toolkit.ghidra.core.services.api.TypedApiInterface; +import ai.reveng.toolkit.ghidra.core.services.api.types.AIDecompilationStatus; import ai.reveng.toolkit.ghidra.core.services.logging.ReaiLoggingService; import ai.reveng.toolkit.ghidra.plugins.ReaiPluginPackage; import docking.ActionContext; @@ -40,7 +41,7 @@ public class AIDecompilationdWindow extends ComponentProviderAdapter { private JComponent component; private Function function; private TaskMonitorComponent taskMonitorComponent; - private final Map cache = new java.util.HashMap<>(); + private final Map cache = new java.util.HashMap<>(); public AIDecompilationdWindow(PluginTool tool, String owner) { @@ -172,6 +173,11 @@ private JComponent buildComponent() { return component; } + @Override + public JComponent getComponent() { + return component; + } + /** * Apply the predicted function name to the current function */ @@ -181,11 +187,11 @@ private void applyPredictedName() { } var cachedStatus = cache.get(function); - if (cachedStatus == null || cachedStatus.getPredictedFunctionName() == null) { + if (cachedStatus == null || cachedStatus.predictedFunctionName() == null) { return; } - String predictedName = cachedStatus.getPredictedFunctionName(); + String predictedName = cachedStatus.predictedFunctionName(); var program = function.getProgram(); int txId = program.startTransaction("Rename function to predicted name"); @@ -199,34 +205,70 @@ private void applyPredictedName() { } } - @Override - public JComponent getComponent() { - return component; - } - - - - public void setDisplayedValuesBasedOnStatus(Function function, GetAiDecompilationTask status) { + public void setDisplayedValuesBasedOnStatus(Function function, AIDecompilationStatus status) { this.function = function; - if (status.getStatus() == AiDecompilationTaskStatus.SUCCESS) { - setCode(status.getDecompilation()); - descriptionArea.setText("%s".formatted(status.getSummary())); - - // Show predicted name if available - String predictedName = status.getPredictedFunctionName(); - if (predictedName != null && !predictedName.isEmpty()) { - predictedNameLabel.setText("Predicted name: " + predictedName); - predictedNamePanel.setVisible(true); - } else { + switch (status.status()) { + case COMPLETED -> { + setCode(withInlineComments(status.decompilation(), status.inlineComments())); + descriptionArea.setText("%s".formatted(status.summary() == null ? "" : status.summary())); + + String predictedName = status.predictedFunctionName(); + if (predictedName != null && !predictedName.isEmpty()) { + predictedNameLabel.setText("Predicted name: " + predictedName); + predictedNamePanel.setVisible(true); + } else { + predictedNamePanel.setVisible(false); + } + } + case FAILED -> { + setCode(""); + descriptionArea.setText("Decompilation failed"); predictedNamePanel.setVisible(false); } - } else if (status.getStatus() == AiDecompilationTaskStatus.ERROR) { - setCode(""); - descriptionArea.setText("Decompilation failed"); - predictedNamePanel.setVisible(false); + case UNINITIALISED, PENDING, RUNNING -> { + setCode(""); + descriptionArea.setText("Decompiling %s ...".formatted(function.getName())); + predictedNamePanel.setVisible(false); + } + default -> { + // Unknown status — leave existing UI state untouched. + } } } + /** + * Splice inline comments into the decompilation as `// comment` lines above each + * targeted line (1-indexed). Preserves the leading indentation of the target line. + */ + private static String withInlineComments(String decompilation, java.util.List comments) { + if (decompilation == null || decompilation.isEmpty() || comments == null || comments.isEmpty()) { + return decompilation == null ? "" : decompilation; + } + var byLine = new java.util.HashMap(); + for (var entry : comments) { + byLine.put(entry.line(), entry.comment()); + } + String[] lines = decompilation.split("\n", -1); + var out = new StringBuilder(); + for (int i = 0; i < lines.length; i++) { + long lineNumber = i + 1L; + String comment = byLine.get(lineNumber); + if (comment != null) { + int indentEnd = 0; + while (indentEnd < lines[i].length() && Character.isWhitespace(lines[i].charAt(indentEnd))) { + indentEnd++; + } + String indent = lines[i].substring(0, indentEnd); + out.append(indent).append("// ").append(comment).append('\n'); + } + out.append(lines[i]); + if (i < lines.length - 1) { + out.append('\n'); + } + } + return out.toString(); + } + private void setCode(String code) { String text = code; textArea.setText(text); @@ -250,6 +292,11 @@ public void refresh(GhidraRevengService.FunctionWithID function) { // Only start decompilation if the window is visible and the status of the analysis is complete. if (this.isVisible()) { taskMonitorComponent.setVisible(true); + // Replace the initial "no function selected" placeholder before the first poll lands. + this.function = function.function(); + setCode(""); + descriptionArea.setText("Decompiling %s ...".formatted(function.function().getName())); + predictedNamePanel.setVisible(false); // Start a new background task to decompile the function var task = new AIDecompTask(tool, function); var builder = TaskBuilder.withTask(task); @@ -280,20 +327,20 @@ public void locationChanged(ProgramLocation loc) { } - void newStatusForFunction(Function function, GetAiDecompilationTask status) { + void newStatusForFunction(Function function, AIDecompilationStatus status) { cache.put(function, status); if (function == this.function) { SwingUtilities.invokeLater(() -> setDisplayedValuesBasedOnStatus(function, status) ); } - if (status.getStatus() == AiDecompilationTaskStatus.SUCCESS) { + if (status.status() == DecompilationData.StatusEnum.COMPLETED) { var logger = tool.getService(ReaiLoggingService.class); - logger.info("AI Decompilation finished for function %s: %s".formatted(function.getName(), status.getDecompilation())); + logger.info("AI Decompilation finished for function %s: %s".formatted(function.getName(), status.decompilation())); if (!hasPendingDecompilations()) { taskMonitorComponent.setVisible(false); } - } else if (status.getStatus() == AiDecompilationTaskStatus.ERROR) { + } else if (status.status() == DecompilationData.StatusEnum.FAILED) { if (!hasPendingDecompilations()) { taskMonitorComponent.setVisible(false); } @@ -301,7 +348,9 @@ void newStatusForFunction(Function function, GetAiDecompilationTask status) { } private boolean hasPendingDecompilations() { - return cache.values().stream().anyMatch(s -> s.getStatus() == AiDecompilationTaskStatus.PENDING); + return cache.values().stream().anyMatch(s -> + s.status() == DecompilationData.StatusEnum.PENDING + || s.status() == DecompilationData.StatusEnum.RUNNING); } class AIDecompTask extends Task { @@ -318,7 +367,7 @@ public AIDecompTask(PluginTool tool, GhidraRevengService.FunctionWithID function public void run(TaskMonitor monitor) throws CancelledException { var fID = functionWithID.functionID(); // Check if there is an existing process already, because the trigger API will fail with 400 if there is - if (service.getApi().pollAIDecompileStatus(fID).getStatus() == AiDecompilationTaskStatus.UNINITIALISED) { + if (service.getApi().pollAIDecompileStatus(fID).status() == DecompilationData.StatusEnum.UNINITIALISED) { // Trigger the decompilation service.getApi().triggerAIDecompilationForFunctionID(fID); } @@ -330,18 +379,22 @@ public void run(TaskMonitor monitor) throws CancelledException { private void waitForDecomp(TypedApiInterface.FunctionID id, TaskMonitor monitor) throws CancelledException { var logger = tool.getService(ReaiLoggingService.class); var api = service.getApi(); - GetAiDecompilationTask lastDecompStatus = null; + AIDecompilationStatus lastDecompStatus = null; + boolean inlineCommentsTriggered = false; while (true) { var newStatus = api.pollAIDecompileStatus(id); - if (lastDecompStatus == null || !Objects.equals(newStatus.getStatus(), lastDecompStatus.getStatus())) { + if (lastDecompStatus == null + || !Objects.equals(newStatus.status(), lastDecompStatus.status()) + || !Objects.equals(newStatus.inlineCommentsStatus(), lastDecompStatus.inlineCommentsStatus()) + || newStatus.inlineComments().size() != lastDecompStatus.inlineComments().size()) { lastDecompStatus = newStatus; - newStatusForFunction(functionWithID.function(), newStatus); } - monitor.setMessage("Waiting for AI Decompilation for %s ... Current status: %s".formatted(functionWithID.function().getName(), lastDecompStatus.getStatus())); + monitor.setMessage("Waiting for AI Decompilation for %s ... Current status: %s".formatted(functionWithID.function().getName(), lastDecompStatus.status())); monitor.checkCancelled(); - switch (newStatus.getStatus()) { + switch (newStatus.status()) { case PENDING: + case RUNNING: case UNINITIALISED: try { // Wait a second before polling again. We don't want to spam the API with requests too often @@ -350,14 +403,40 @@ private void waitForDecomp(TypedApiInterface.FunctionID id, TaskMonitor monitor) throw new RuntimeException(e); } break; - case SUCCESS: + case COMPLETED: monitor.setProgress(monitor.getMaximum()); - return; - case ERROR: - logger.error("Decompilation failed: %s".formatted(newStatus.getDecompilation())); + // Decompilation is done; now wait for inline comments to land before stopping. + var commentsStatus = newStatus.inlineCommentsStatus(); + if (commentsStatus == WorkflowProgress.StatusEnum.COMPLETED) { + return; + } + if (commentsStatus == WorkflowProgress.StatusEnum.FAILED) { + logger.error("Inline comments generation failed for function %s".formatted(functionWithID.function().getName())); + return; + } + if (!inlineCommentsTriggered) { + // Trigger on first entry to COMPLETED regardless of reported status: if comments + // haven't been requested (or the status endpoint was unreachable), POSTing kicks + // them off; if they're already running the server treats it as a regenerate. + inlineCommentsTriggered = true; + try { + api.triggerAIDecompilationInlineComments(id); + } catch (RuntimeException e) { + logger.error("Failed to trigger inline comments: %s".formatted(e.getMessage())); + return; + } + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + break; + case FAILED: + logger.error("Decompilation failed: %s".formatted(newStatus.decompilation())); return; default: - throw new RuntimeException("Unknown status: %s".formatted(newStatus.getStatus())); + throw new RuntimeException("Unknown status: %s".formatted(newStatus.status())); } } diff --git a/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/GhidraRevengService.java b/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/GhidraRevengService.java index 9966cf5..87003ce 100644 --- a/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/GhidraRevengService.java +++ b/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/GhidraRevengService.java @@ -645,13 +645,11 @@ public String decompileFunctionViaAI(FunctionWithID functionWithID, TaskMonitor // Check if there is an existing process already, because the trigger API will fail with 400 if there is var fID = functionWithID.functionID; var function = functionWithID.function; - if (api.pollAIDecompileStatus(fID).getStatus() == AiDecompilationTaskStatus.UNINITIALISED){ + if (api.pollAIDecompileStatus(fID).status() == DecompilationData.StatusEnum.UNINITIALISED){ // Trigger the decompilation api.triggerAIDecompilationForFunctionID(fID); } - String lastStatus; - while (true) { if (monitor.isCancelled()) { return "Decompilation cancelled"; @@ -659,28 +657,25 @@ public String decompileFunctionViaAI(FunctionWithID functionWithID, TaskMonitor var status = api.pollAIDecompileStatus(fID); window.setDisplayedValuesBasedOnStatus(function, status); - switch (status.getStatus()) { + switch (status.status()) { case PENDING: + case RUNNING: case UNINITIALISED: try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } -// monitor.incrementProgress(100); break; - case SUCCESS: + case COMPLETED: monitor.setProgress(monitor.getMaximum()); window.setDisplayedValuesBasedOnStatus(function, status); - return status.getDecompilation(); - case ERROR: - return "Decompilation failed: %s".formatted(status.getStatus()); + return status.decompilation(); + case FAILED: + return "Decompilation failed: %s".formatted(status.status()); default: - throw new RuntimeException("Unknown status: %s".formatted(status.getStatus())); + throw new RuntimeException("Unknown status: %s".formatted(status.status())); } - - - } } @@ -925,7 +920,7 @@ public BoxPlot getNameScoreForMatch(GhidraFunctionMatch functionMatch) { public void openFunctionInPortal(TypedApiInterface.FunctionID functionID) { var details = api.getFunctionDetails(functionID); - openPortal("analyses", String.format("%s?fn=%s", details.analysisId().id(), functionID.value())); + openPortal("analyses", String.format("%s?view=functions&fn=%s", details.analysisId().id(), functionID.value())); } public void openCollectionInPortal(Collection collection) { diff --git a/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/TypedApiImplementation.java b/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/TypedApiImplementation.java index 81fc899..9c9e842 100644 --- a/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/TypedApiImplementation.java +++ b/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/TypedApiImplementation.java @@ -443,29 +443,86 @@ public Optional getFunctionDataTypes(AnalysisID analysis @Override public boolean triggerAIDecompilationForFunctionID(FunctionID functionID) { - JSONObject params = new JSONObject().put("function_id", functionID.value()); - HttpRequest request = requestBuilderForEndpoint("ai-decompilation") - .POST(HttpRequest.BodyPublishers.ofString(params.toString())) - .header("Content-Type", "application/json" ) - .build(); - return sendVersion2Request(request).status(); + try { + // POST /v3/functions/{function_id}/ai-decompilation + var result = functionsAiDecompilationApi.createAiDecompilation(functionID.value(), true, null); + return Boolean.TRUE.equals(result.getStatus()); + } catch (ApiException e) { + throw new RuntimeException("Failed to trigger AI decompilation", e); + } } @Override - public GetAiDecompilationTask pollAIDecompileStatus(FunctionID functionID) { + public AIDecompilationStatus pollAIDecompileStatus(FunctionID functionID) { try { - var response = functionsAiDecompilationApi.getAiDecompilationTaskResult( - functionID.value(), // Long functionId - true, // summarise - true, // generateInlineComments - null // forceRegenerate - ); - return response.getData(); + // GET /v3/functions/{function_id}/ai-decompilation + DecompilationData data = functionsAiDecompilationApi.getAiDecompilation(functionID.value()); + String summary = null; + String predictedFunctionName = null; + WorkflowProgress.StatusEnum inlineCommentsStatus = null; + List inlineComments = List.of(); + if (data.getStatus() == DecompilationData.StatusEnum.COMPLETED) { + try { + // GET /v3/functions/{function_id}/ai-decompilation/summary + SummaryData summaryData = functionsAiDecompilationApi.getAiDecompilationSummary(functionID.value()); + summary = summaryData.getAiSummary() != null ? summaryData.getAiSummary() : summaryData.getSummary(); + } catch (ApiException e) { + Msg.info(this, "Decompilation completed but summary not yet available for function " + functionID.value()); + } + try { + // GET /v3/functions/{function_id}/ai-decompilation/tokenised — carries the predicted name + TokenisedData tokenised = functionsAiDecompilationApi.getAiDecompilationTokenised(functionID.value()); + predictedFunctionName = tokenised.getPredictedFunctionName(); + } catch (ApiException | RuntimeException e) { + // RuntimeException covers IllegalArgumentException thrown by the SDK's JSON validator + // when the live response omits fields the generated model marks required (e.g. name_map). + Msg.info(this, "Could not fetch predicted function name for function " + functionID.value() + ": " + e.getMessage()); + } + try { + // GET /v3/functions/{function_id}/ai-decompilation/inline-comments/status + WorkflowProgress commentsProgress = functionsAiDecompilationApi.getAiDecompilationInlineCommentsStatus(functionID.value()); + inlineCommentsStatus = commentsProgress.getStatus(); + } catch (ApiException | RuntimeException e) { + Msg.info(this, "Could not fetch inline comments status for function " + functionID.value() + ": " + e.getMessage()); + } + if (inlineCommentsStatus == WorkflowProgress.StatusEnum.COMPLETED) { + try { + // GET /v3/functions/{function_id}/ai-decompilation/inline-comments + CommentsData commentsData = functionsAiDecompilationApi.getAiDecompilationInlineComments(functionID.value()); + var rawComments = commentsData.getInlineComments(); + if (rawComments != null) { + inlineComments = rawComments.stream() + .filter(c -> c.getLine() != null && c.getComment() != null) + .map(c -> new AIDecompilationStatus.InlineCommentEntry(c.getLine(), c.getComment())) + .toList(); + } + } catch (ApiException | RuntimeException e) { + Msg.info(this, "Could not fetch inline comments for function " + functionID.value() + ": " + e.getMessage()); + } + } + } + return new AIDecompilationStatus( + data.getStatus(), + data.getDecompilation(), + summary, + predictedFunctionName, + inlineCommentsStatus, + inlineComments); } catch (ApiException e) { throw new RuntimeException("Failed to poll AI decompilation status", e); } } + @Override + public void triggerAIDecompilationInlineComments(FunctionID functionID) { + try { + // POST /v3/functions/{function_id}/ai-decompilation/inline-comments + functionsAiDecompilationApi.regenerateAiDecompilationInlineComments(functionID.value()); + } catch (ApiException e) { + throw new RuntimeException("Failed to trigger AI decompilation inline comments", e); + } + } + /** * https://api.reveng.ai/v2/docs#tag/Functions-overview/operation/rename_function_id_v2_functions_rename__function_id__post * diff --git a/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/TypedApiInterface.java b/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/TypedApiInterface.java index 26f7134..f3494cb 100644 --- a/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/TypedApiInterface.java +++ b/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/TypedApiInterface.java @@ -152,10 +152,14 @@ default boolean triggerAIDecompilationForFunctionID(FunctionID functionID) { throw new UnsupportedOperationException("triggerAIDecompilationForFunctionID not implemented yet"); } - default GetAiDecompilationTask pollAIDecompileStatus(FunctionID functionID) { + default AIDecompilationStatus pollAIDecompileStatus(FunctionID functionID) { throw new UnsupportedOperationException("pollAIDecompileStatus not implemented yet"); } + default void triggerAIDecompilationInlineComments(FunctionID functionID) { + throw new UnsupportedOperationException("triggerAIDecompilationInlineComments not implemented yet"); + } + void renameFunction(FunctionID id, String newName, String newNameMangled); default FunctionNameScore getNameScore(FunctionMatch match) { diff --git a/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/types/AIDecompilationStatus.java b/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/types/AIDecompilationStatus.java new file mode 100644 index 0000000..719873a --- /dev/null +++ b/src/main/java/ai/reveng/toolkit/ghidra/core/services/api/types/AIDecompilationStatus.java @@ -0,0 +1,29 @@ +package ai.reveng.toolkit.ghidra.core.services.api.types; + +import ai.reveng.model.DecompilationData; +import ai.reveng.model.WorkflowProgress; + +import javax.annotation.Nullable; +import java.util.List; + +/** + * Polled view of the v3 AI decompilation pipeline for a single function. + * + * The v2 task endpoint returned status, decompilation, summary, predicted + * name and inline comments in one payload. v3 splits those across separate + * endpoints; this record stitches them back together so callers can treat + * polling as a single operation. + * + * `summary`, `predictedFunctionName`, `inlineCommentsStatus` and + * `inlineComments` are only populated once `status` reaches `COMPLETED`. + */ +public record AIDecompilationStatus( + DecompilationData.StatusEnum status, + @Nullable String decompilation, + @Nullable String summary, + @Nullable String predictedFunctionName, + @Nullable WorkflowProgress.StatusEnum inlineCommentsStatus, + List inlineComments +) { + public record InlineCommentEntry(long line, String comment) {} +} diff --git a/src/test/java/ai/reveng/AIDecompilerComponentTest.java b/src/test/java/ai/reveng/AIDecompilerComponentTest.java index f8b1356..a6a4407 100644 --- a/src/test/java/ai/reveng/AIDecompilerComponentTest.java +++ b/src/test/java/ai/reveng/AIDecompilerComponentTest.java @@ -1,13 +1,13 @@ package ai.reveng; import ai.reveng.invoker.ApiException; -import ai.reveng.model.AiDecompilationTaskStatus; -import ai.reveng.model.GetAiDecompilationTask; +import ai.reveng.model.DecompilationData; import ai.reveng.toolkit.ghidra.binarysimilarity.ui.aidecompiler.AIDecompilationdWindow; import ai.reveng.toolkit.ghidra.core.services.api.AnalysisOptionsBuilder; import ai.reveng.toolkit.ghidra.core.services.api.mocks.UnimplementedAPI; import ai.reveng.toolkit.ghidra.core.services.api.TypedApiInterface.FunctionID; import ai.reveng.toolkit.ghidra.core.services.api.TypedApiInterface.AnalysisID; +import ai.reveng.toolkit.ghidra.core.services.api.types.AIDecompilationStatus; import ai.reveng.toolkit.ghidra.core.services.api.types.AnalysisStatus; import ai.reveng.toolkit.ghidra.core.services.api.types.FunctionInfo; import ai.reveng.toolkit.ghidra.plugins.BinarySimilarityPlugin; @@ -61,23 +61,23 @@ public List getFunctionInfo(AnalysisID analysisID) { } @Override - public GetAiDecompilationTask pollAIDecompileStatus(FunctionID functionID) { + public AIDecompilationStatus pollAIDecompileStatus(FunctionID functionID) { if (functionID.value() == 2) { - return new GetAiDecompilationTask() - .status(AiDecompilationTaskStatus.SUCCESS) - .decompilation("int func2(int a) { return a + 1; }") - .rawDecompilation("int func2(int a) { return a + 1; }") - .summary("Mocked Description Summary for func2") - .aiSummary("Mocked Description Summary for func2") - .rawAiSummary("Summary for func2"); + return new AIDecompilationStatus( + DecompilationData.StatusEnum.COMPLETED, + "int func2(int a) { return a + 1; }", + "Mocked Description Summary for func2", + null, + null, + java.util.List.of()); } else if (functionID.value() == 1) { - return new GetAiDecompilationTask() - .status(AiDecompilationTaskStatus.SUCCESS) - .decompilation("void func1() { return; }") - .rawDecompilation("void func1() { return; }") - .summary("Mocked Description Summary") - .aiSummary("Mocked Description Summary") - .rawAiSummary("Summary"); + return new AIDecompilationStatus( + DecompilationData.StatusEnum.COMPLETED, + "void func1() { return; }", + "Mocked Description Summary", + null, + null, + java.util.List.of()); } else { throw new RuntimeException("Unknown FunctionID"); } @@ -107,7 +107,7 @@ public boolean triggerAIDecompilationForFunctionID(FunctionID functionID) { // get AIDecompiledWindow, and some internal fields for testing var aiDecompComponent = getComponentProvider(AIDecompilationdWindow.class); - Map aiDecompCache = (Map) getInstanceField("cache", aiDecompComponent); + Map aiDecompCache = (Map) getInstanceField("cache", aiDecompComponent); RSyntaxTextArea textArea = (RSyntaxTextArea) getInstanceField("textArea", aiDecompComponent); // Make sure it's hidden to start with @@ -224,14 +224,14 @@ public List getFunctionInfo(AnalysisID analysisID) { } @Override - public GetAiDecompilationTask pollAIDecompileStatus(FunctionID functionID) { - return new GetAiDecompilationTask() - .status(AiDecompilationTaskStatus.SUCCESS) - .decompilation("void func1() { return; }") - .rawDecompilation("void func1() { return; }") - .summary("Mocked Description Summary") - .aiSummary("Mocked Description Summary") - .rawAiSummary("Summary"); + public AIDecompilationStatus pollAIDecompileStatus(FunctionID functionID) { + return new AIDecompilationStatus( + DecompilationData.StatusEnum.COMPLETED, + "void func1() { return; }", + "Mocked Description Summary", + null, + null, + java.util.List.of()); } @Override