Copilot commented on code in PR #7392:
URL: https://github.com/apache/texera/pull/7392#discussion_r3740568617


##########
frontend/src/app/dashboard/component/user/user-workflow/user-workflow.component.ts:
##########
@@ -312,6 +318,85 @@ export class UserWorkflowComponent implements 
AfterViewInit {
       });
   }
 
+  public get pythonNotebookMigrationEnabled(): boolean {
+    return this.config.env.pythonNotebookMigrationEnabled;
+  }
+
+  /**
+   * Open the AI-generate import modal from the dashboard. This is the same 
modal the canvas
+   * toolbar uses (notebook upload and model selection); it hands the 
selection back through the
+   * requestImport callback. Unlike the canvas entry point there is no open 
workflow to overwrite,
+   * so the callback always creates and opens a new workflow.
+   */
+  public openAiGenerateModal(): void {
+    this.modalService.create<NotebookImportModalComponent, 
NotebookImportModalData>({
+      nzTitle: "AI Generate Workflow from Python Notebook",
+      nzContent: NotebookImportModalComponent,
+      nzWidth: 700,
+      nzFooter: null,
+      nzCentered: true,
+      nzData: {
+        requestImport: (file, model) => this.startAiGeneratedWorkflow(file, 
model),
+        // The dashboard always creates a new workflow, so there is nothing to 
overwrite.
+        showOverwriteWarning: false,
+      },
+    });
+  }
+
+  /**
+   * Create the new workflow the generation will fill, record the notebook 
file and model for the
+   * workspace to pick up, and navigate to the new workflow. The workspace 
menu consumes the
+   * handoff once the workflow loads and runs the shared generation pipeline 
(generate, auto
+   * layout, open the Jupyter panel). Resolves true so the modal closes, or 
false (leaving the
+   * modal open with the selection intact) when the file is not a notebook or 
creation fails.
+   */
+  private startAiGeneratedWorkflow(file: NzUploadFile, model: string): 
Promise<boolean> {
+    // Reject a non-notebook file before creating anything, so we never leave 
an empty workflow
+    // behind on a no-op import. The workspace pipeline validates the 
extension again downstream.
+    const fileExtension = file.name.split(".").pop()?.toLowerCase();
+    if (fileExtension !== "ipynb") {
+      this.notificationService.error("Please upload a valid Jupyter Notebook 
(.ipynb) file.");
+      return Promise.resolve(false);
+    }
+    const emptyWorkflowContent: WorkflowContent = {
+      operators: [],
+      commentBoxes: [],
+      links: [],
+      operatorPositions: {},
+      settings: {
+        dataTransferBatchSize: this.config.env.defaultDataTransferBatchSize,
+        executionMode: this.config.env.defaultExecutionMode,
+      },
+    };
+    const localPid = this.pid;
+    return firstValueFrom(
+      this.workflowPersistService.createWorkflow(emptyWorkflowContent, 
DEFAULT_WORKFLOW_NAME).pipe(
+        tap(createdWorkflow => {
+          if (!createdWorkflow.workflow.wid) {
+            throw new Error("Workflow creation failed.");
+          }
+        }),
+        mergeMap(createdWorkflow => {
+          const wid = createdWorkflow.workflow.wid!;
+          // Mirror the create-workflow path: add to the current project when 
inside one.
+          if (localPid) {
+            return this.userProjectService.addWorkflowToProject(localPid, 
wid).pipe(map(() => wid));
+          }
+          return of(wid);
+        }),
+        untilDestroyed(this)
+      )
+    )
+      .then(wid => {
+        this.notebookMigrationService.setPendingGeneration(file, model, wid);
+        return this.router.navigate([USER_WORKSPACE, wid]).then(() => true);
+      })
+      .catch(() => {
+        this.notificationService.error("Workflow creation failed");
+        return false;
+      });

Review Comment:
   `router.navigate` returns a Promise<boolean> indicating whether navigation 
succeeded, but this code discards that result and always resolves `true`. If 
navigation fails (or is cancelled), the import modal will close even though the 
user was not taken to the new workflow, and the pending-generation slot may 
remain set.



##########
frontend/src/app/workspace/component/menu/menu.component.ts:
##########
@@ -999,7 +999,22 @@ export class MenuComponent implements OnInit, OnDestroy {
     this.workflowActionService
       .getWorkflowModificationEnabledStream()
       .pipe(untilDestroyed(this))
-      .subscribe(modifiable => (this.isWorkflowModifiable = modifiable));
+      .subscribe(modifiable => {
+        this.isWorkflowModifiable = modifiable;
+        // A generation started from the workflow dashboard has no canvas to 
run on, so the
+        // dashboard defers it here: once this freshly created workflow is 
loaded and editable,
+        // pick up the handoff and run the same pipeline the toolbar button 
uses. consumePending-
+        // Generation guards by wid and clears on consume, so this fires once 
for its workflow.
+        if (modifiable && this.pythonNotebookMigrationEnabled) {
+          const wid = this.workflowActionService.getWorkflow().wid;

Review Comment:
   This handler only needs the workflow id, but `getWorkflow()` recomputes full 
workflow content (operators/links/positions). Using `getWorkflowMetadata()` 
avoids extra work whenever the modifiable stream emits.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to