Skip to content

Make Interactive Tasks look better#1422

Merged
aliasaria merged 8 commits intomainfrom
add/improve-interactive-task-UI
Mar 2, 2026
Merged

Make Interactive Tasks look better#1422
aliasaria merged 8 commits intomainfrom
add/improve-interactive-task-UI

Conversation

@aliasaria
Copy link
Copy Markdown
Member

@aliasaria aliasaria commented Mar 2, 2026

  • Put the interactive Modal into one screen (output and instructions)
  • Make it look cleaner

Summary by CodeRabbit

  • New Features

    • Added tabbed interface for viewing task output and provider logs in interactive modals
    • Enhanced modal sizing and layout for improved readability
    • Streamlined job card display for interactive tasks with integrated output viewing
  • UI Improvements

    • Expanded interactive task modals to utilize more screen space for better visibility

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 2, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c76038 and 61e782b.

📒 Files selected for processing (10)
  • src/renderer/components/Experiment/Interactive/Interactive.tsx
  • src/renderer/components/Experiment/Interactive/InteractiveJobCard.tsx
  • src/renderer/components/Experiment/Tasks/EmbeddableStreamingOutput.tsx
  • src/renderer/components/Experiment/Tasks/InteractiveJupyterModal.tsx
  • src/renderer/components/Experiment/Tasks/InteractiveOllamaModal.tsx
  • src/renderer/components/Experiment/Tasks/InteractiveSshModal.tsx
  • src/renderer/components/Experiment/Tasks/InteractiveVSCodeModal.tsx
  • src/renderer/components/Experiment/Tasks/InteractiveVllmModal.tsx
  • src/renderer/components/Experiment/Tasks/Tasks.tsx
  • src/renderer/components/Experiment/Tasks/ViewOutputModalStreaming.tsx

📝 Walkthrough

Walkthrough

This refactoring simplifies the main Interactive component by extracting job card rendering into a dedicated InteractiveJobCard component and introducing a new EmbeddableStreamingOutput component for streaming task output. Interactive modal components (Jupyter, VLLM, SSH, Ollama, VSCode) are updated to accept an embeddedOutput prop instead of onOpenOutput callbacks, enabling flexible embedded layouts.

Changes

Cohort / File(s) Summary
Interactive Component Simplification
src/renderer/components/Experiment/Interactive/Interactive.tsx
Removes inline job card rendering logic, modal orchestration, and helper functions; delegates to InteractiveJobCard component. Simplifies state management by removing viewOutputFromJob and interactiveJobForModal fields. Reduces control-flow branches for different interactive types.
New Interactive Job Card
src/renderer/components/Experiment/Interactive/InteractiveJobCard.tsx
New component that renders a stylized job card with icon, title, colored chip label, delete button, and JobProgress. Includes Connect button that opens type-specific modals using INTERACTIVE_MODALS mapping. Handles interactive_type extraction from job_data with robust fallback logic.
New Embeddable Streaming Output
src/renderer/components/Experiment/Tasks/EmbeddableStreamingOutput.tsx
New component providing tabbed interface for task output and provider logs. Integrates ProviderLogsTerminal using xterm.js for provider log visualization. Manages SWR-based provider logs fetching with live update toggle and responsive Resizing via ResizeObserver.
Interactive Modal Refactoring
src/renderer/components/Experiment/Tasks/InteractiveJupyterModal.tsx, InteractiveOllamaModal.tsx, InteractiveSshModal.tsx, InteractiveVSCodeModal.tsx, InteractiveVllmModal.tsx
All five modals updated to accept embeddedOutput prop (replacing onOpenOutput callback). Conditional rendering: when embeddedOutput is provided, renders two-column layout with embedded content and status panel; otherwise preserves original standalone UI. Modal sizing enlarged to 95vw/85vh. SSH modal adds dynamic SSH key injection logic.
Streaming Output Modal Refactoring
src/renderer/components/Experiment/Tasks/ViewOutputModalStreaming.tsx
Delegates all rendering to EmbeddableStreamingOutput component. Removes direct terminal setup, SWR fetching, and tab management logic. Simplifies interface by making tabs optional with default ['output', 'provider']. Removes activeTab and viewLiveProviderLogs state.
Tasks Component Cleanup
src/renderer/components/Experiment/Tasks/Tasks.tsx
Removes onOpenOutput callbacks from interactive modal prop assignments. Eliminates console.log debug statement in SSH modal branch.

Sequence Diagram(s)

sequenceDiagram
    participant Job as Job List
    participant Card as InteractiveJobCard
    participant Modal as Interactive*Modal
    participant Embedded as EmbeddableStreamingOutput
    participant Output as PollingOutputTerminal
    participant Logs as ProviderLogsTerminal

    Job->>Card: Render job with delete callback
    Card->>Card: Extract interactive_type from job_data
    Card->>Card: Display icon, chip, Connect button
    activate Card
    Card->>Modal: onClick Connect → Open modal with jobId
    activate Modal
    Modal->>Embedded: Pass jobId to embeddedOutput prop
    activate Embedded
    Embedded->>Output: Render output tab
    activate Output
    Output-->>Embedded: Stream task output
    deactivate Output
    Embedded->>Logs: Render provider logs tab (if selected)
    activate Logs
    Logs->>Logs: Initialize xterm terminal
    Logs-->>Embedded: Stream provider logs
    deactivate Logs
    deactivate Embedded
    Modal->>Modal: Display status/URL info alongside embedded output
    deactivate Modal
    Card->>Card: Show Connect button for interactive jobs
    deactivate Card
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Suggested labels

mode:multiuser

Suggested reviewers

  • deep1401
✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add/improve-interactive-task-UI

Comment @coderabbitai help to get the list of available commands and usage tips.

@aliasaria
Copy link
Copy Markdown
Member Author

Screen.Recording.2026-03-02.at.9.53.37.AM.mov

@aliasaria aliasaria merged commit edc61c9 into main Mar 2, 2026
3 checks passed
@paragon-review
Copy link
Copy Markdown

paragon-review bot commented Mar 2, 2026

Paragon Summary

This pull request review identified 8 issues across 3 categories in 10 files. The review analyzed code changes, potential bugs, security vulnerabilities, performance issues, and code quality concerns using automated analysis tools.

This PR improves the UI of interactive task modals by consolidating output and instructions into a single unified screen and enhancing the visual presentation for a cleaner user experience.

Key changes:

  • New components added: InteractiveJobCard.tsx and EmbeddableStreamingOutput.tsx
  • Consolidated modal layout: Output and instructions now displayed on single screen
  • UI cleanup: Multiple interactive modals refactored (Jupyter, Ollama, SSH, VSCode, Vllm)
  • Streaming output extraction: New EmbeddableStreamingOutput component for reusable output display

Confidence score: 3/5

  • This PR has moderate risk due to 1 high-priority issue that should be addressed
  • Score reflects significant bugs, performance issues, or architectural concerns
  • Review high-priority findings carefully before merging

10 files reviewed, 8 comments

Severity breakdown: High: 1, Medium: 3, Low: 4


Tip: @paragon-run <instructions> to chat with our agent or push fixes!

Dashboard

size="sm"
onClick={() => setConnectOpen(true)}
>
Connect
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: EmbeddableStreamingOutput always renders when modal is closed

EmbeddableStreamingOutput always renders when modal is closed. Terminal instances mount for every card. Gate rendering on connectOpen state.

View Details

Location: src/renderer/components/Experiment/Interactive/InteractiveJobCard.tsx (lines 206)

Analysis

EmbeddableStreamingOutput always renders when modal is closed

What fails EmbeddableStreamingOutput (with xterm Terminal and SWR polling) is always rendered as embeddedOutput prop even when the ConnectModal is closed (jobId=-1).
Result N terminal instances and N SWR polling loops run simultaneously for N job cards, even when no modal is open.
Expected EmbeddableStreamingOutput should only mount when the Connect modal is actually open, avoiding unnecessary terminals and network requests.
Impact Significant performance degradation with many interactive jobs — unnecessary DOM nodes, xterm instances, and API polling for every card.
How to reproduce
Open the Interactive page with multiple job cards. Inspect React DevTree or network tab — every card mounts an EmbeddableStreamingOutput with active SWR polling.
Patch Details
-        embeddedOutput={
-          <EmbeddableStreamingOutput jobId={jobIdNum} tabs={['provider']} />
-        }
+        embeddedOutput={
+          connectOpen ? <EmbeddableStreamingOutput jobId={jobIdNum} tabs={['provider']} /> : undefined
+        }
AI Fix Prompt
Fix this issue: EmbeddableStreamingOutput always renders when modal is closed. Terminal instances mount for every card. Gate rendering on connectOpen state.

Location: src/renderer/components/Experiment/Interactive/InteractiveJobCard.tsx (lines 206)
Problem: EmbeddableStreamingOutput (with xterm Terminal and SWR polling) is always rendered as embeddedOutput prop even when the ConnectModal is closed (jobId=-1).
Current behavior: N terminal instances and N SWR polling loops run simultaneously for N job cards, even when no modal is open.
Expected: EmbeddableStreamingOutput should only mount when the Connect modal is actually open, avoiding unnecessary terminals and network requests.
Steps to reproduce: Open the Interactive page with multiple job cards. Inspect React DevTree or network tab — every card mounts an EmbeddableStreamingOutput with active SWR polling.

Provide a code fix.


Tip: Reply with @paragon-run to automatically fix this issue

const jobData = job.job_data || {};
const interactiveType =
jobData.interactive_type ||
(typeof jobData === 'string'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: JSON

JSON.parse in InteractiveJobCard has no try-catch. Malformed job_data string crashes the component. Wrap in try-catch with fallback to 'vscode'.

View Details

Location: src/renderer/components/Experiment/Interactive/InteractiveJobCard.tsx (lines 124)

Analysis

JSON.parse in InteractiveJobCard has no try-catch. Malformed job_data string crashes the component

What fails InteractiveJobCard crashes if job.job_data is a malformed JSON string, causing the entire card list to unmount.
Result React error boundary catches unhandled JSON.parse exception, entire card list disappears.
Expected Component should gracefully fall back to 'vscode' as the default interactive type.
Impact One corrupted job record can break the entire Interactive jobs view for all users.
How to reproduce
Set a job's job_data to an invalid JSON string like '{bad json'. Open the Interactive tab.
Patch Details
-    (typeof jobData === 'string'
-      ? JSON.parse(jobData || '{}')?.interactive_type
+    (typeof jobData === 'string'
+      ? (() => { try { return JSON.parse(jobData || '{}')?.interactive_type; } catch { return null; } })()
AI Fix Prompt
Fix this issue: JSON.parse in InteractiveJobCard has no try-catch. Malformed job_data string crashes the component. Wrap in try-catch with fallback to 'vscode'.

Location: src/renderer/components/Experiment/Interactive/InteractiveJobCard.tsx (lines 124)
Problem: InteractiveJobCard crashes if job.job_data is a malformed JSON string, causing the entire card list to unmount.
Current behavior: React error boundary catches unhandled JSON.parse exception, entire card list disappears.
Expected: Component should gracefully fall back to 'vscode' as the default interactive type.
Steps to reproduce: Set a job's job_data to an invalid JSON string like '{bad json'. Open the Interactive tab.

Provide a code fix.


Tip: Reply with @paragon-run to automatically fix this issue

onDeleteJob: (jobId: string) => void;
}

function VscodeIcon() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Icon images load from external URL lab

Icon images load from external URL lab.cloud. Offline/air-gapped users see broken images. Bundle icons locally or add fallback.

View Details

Location: src/renderer/components/Experiment/Interactive/InteractiveJobCard.tsx (lines 27)

Analysis

Icon images load from external URL lab.cloud. Offline/air-gapped users see broken images

What fails All five icon components (VscodeIcon, JupyterIcon, SshIcon, VllmIcon, OllamaIcon) fetch images from https://lab\.cloud/img/icons/ at runtime.
Result All interactive type icons show as broken image placeholders since the external URLs are unreachable.
Expected Icons should render correctly in offline/air-gapped environments, either bundled locally or with graceful fallback.
Impact Electron apps commonly run offline. Broken icons degrade the UI significantly for users without internet access.
How to reproduce
Disconnect from the internet. Open the Interactive tab with active jobs.
AI Fix Prompt
Fix this issue: Icon images load from external URL lab.cloud. Offline/air-gapped users see broken images. Bundle icons locally or add fallback.

Location: src/renderer/components/Experiment/Interactive/InteractiveJobCard.tsx (lines 27)
Problem: All five icon components (VscodeIcon, JupyterIcon, SshIcon, VllmIcon, OllamaIcon) fetch images from https://lab.cloud/img/icons/ at runtime.
Current behavior: All interactive type icons show as broken image placeholders since the external URLs are unreachable.
Expected: Icons should render correctly in offline/air-gapped environments, either bundled locally or with graceful fallback.
Steps to reproduce: Disconnect from the internet. Open the Interactive tab with active jobs.

Provide a code fix.


Tip: Reply with @paragon-run to automatically fix this issue

</Typography>
)}
</Stack>
{embeddedOutput ? (
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainability: Massive UI code duplication in modal components

Massive UI code duplication in modal components. Each has identical embeddedOutput/fallback branches. Extract shared layout into a reusable wrapper.

View Details

Location: src/renderer/components/Experiment/Tasks/InteractiveJupyterModal.tsx (lines 100)

Analysis

Massive UI code duplication in modal components. Each has identical embeddedOutput/fallback branches

What fails Every interactive modal (VSCode, Jupyter, vLLM, Ollama, SSH) duplicates the full embeddedOutput two-pane layout vs fallback pattern, roughly doubling each component's size.
Result 5 files each contain ~100-150 lines of duplicated layout code for the embeddedOutput vs fallback branching.
Expected A shared InteractiveModalLayout component should handle the two-pane/single-pane branching, with each modal providing only its unique content.
Impact Any layout bug must be fixed in 5 places. High maintenance burden and risk of divergence between modals.
How to reproduce
Compare the ternary {embeddedOutput ? (...) : (...)} blocks across all five modal files. The outer structure is identical.
AI Fix Prompt
Fix this issue: Massive UI code duplication in modal components. Each has identical embeddedOutput/fallback branches. Extract shared layout into a reusable wrapper.

Location: src/renderer/components/Experiment/Tasks/InteractiveJupyterModal.tsx (lines 100)
Problem: Every interactive modal (VSCode, Jupyter, vLLM, Ollama, SSH) duplicates the full embeddedOutput two-pane layout vs fallback pattern, roughly doubling each component's size.
Current behavior: 5 files each contain ~100-150 lines of duplicated layout code for the embeddedOutput vs fallback branching.
Expected: A shared InteractiveModalLayout component should handle the two-pane/single-pane branching, with each modal providing only its unique content.
Steps to reproduce: Compare the ternary {embeddedOutput ? (...) : (...)} blocks across all five modal files. The outer structure is identical.

Provide a code fix.


Tip: Reply with @paragon-run to automatically fix this issue


interface InteractiveJobCardProps {
job: any;
onDeleteJob: (jobId: string) => void;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Icon components receive a size prop but render plain img tags

Icon components receive a size prop but render plain img tags. This prop is silently ignored. Remove the size prop or use it for width/height.

View Details

Location: src/renderer/components/Experiment/Interactive/InteractiveJobCard.tsx (lines 24)

Analysis

Icon components receive a size prop but render plain img tags. This prop is silently ignored

What fails TypeIcon is invoked with but VscodeIcon, JupyterIcon, etc. don't accept a 'size' prop — they render hardcoded .
Result The size={20} prop is passed to the img-based component but silently ignored. Icon size is always hardcoded to 20x20.
Expected Either the icon components should accept and use a size prop, or the call site should not pass one.
Impact Misleading API — developers may believe they can resize icons but nothing changes. React also passes unknown props to the DOM.
How to reproduce
Inspect <TypeIcon size={20} /> in InteractiveJobCard. Change size to 40 and observe — icon stays 20px.
AI Fix Prompt
Fix this issue: Icon components receive a `size` prop but render plain img tags. This prop is silently ignored. Remove the size prop or use it for width/height.

Location: src/renderer/components/Experiment/Interactive/InteractiveJobCard.tsx (lines 24)
Problem: TypeIcon is invoked with <TypeIcon size={20} /> but VscodeIcon, JupyterIcon, etc. don't accept a 'size' prop — they render hardcoded <img width={20} height={20}>.
Current behavior: The size={20} prop is passed to the img-based component but silently ignored. Icon size is always hardcoded to 20x20.
Expected: Either the icon components should accept and use a size prop, or the call site should not pass one.
Steps to reproduce: Inspect <TypeIcon size={20} /> in InteractiveJobCard. Change size to 40 and observe — icon stays 20px.

Provide a code fix.


Tip: Reply with @paragon-run to automatically fix this issue

useEffect(() => {
if (!termRef.current) return;

const text = logsText || 'No provider log data yet.';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: ProviderLogsTerminal clears and rewrites all lines on every SWR refresh

ProviderLogsTerminal clears and rewrites all lines on every SWR refresh. Causes visual flicker. Use incremental write approach instead.

View Details

Location: src/renderer/components/Experiment/Tasks/EmbeddableStreamingOutput.tsx (lines 75)

Analysis

ProviderLogsTerminal clears and rewrites all lines on every SWR refresh. Causes visual flicker

What fails ProviderLogsTerminal clears the entire xterm screen (\x1b[2J\x1b[H) and rewrites all lines every time logsText changes from SWR polling.
Result Terminal flashes/flickers as entire content is cleared and rewritten every 2-5 seconds during active polling.
Expected Terminal should diff or append only new content, avoiding full clear-and-rewrite on each update.
Impact Poor user experience with visible flicker in the terminal output, especially during active log streaming.
How to reproduce
Open an interactive task with provider logs visible. Watch the terminal output — it flickers on every SWR poll interval.
AI Fix Prompt
Fix this issue: ProviderLogsTerminal clears and rewrites all lines on every SWR refresh. Causes visual flicker. Use incremental write approach instead.

Location: src/renderer/components/Experiment/Tasks/EmbeddableStreamingOutput.tsx (lines 75)
Problem: ProviderLogsTerminal clears the entire xterm screen (\x1b[2J\x1b[H) and rewrites all lines every time logsText changes from SWR polling.
Current behavior: Terminal flashes/flickers as entire content is cleared and rewritten every 2-5 seconds during active polling.
Expected: Terminal should diff or append only new content, avoiding full clear-and-rewrite on each update.
Steps to reproduce: Open an interactive task with provider logs visible. Watch the terminal output — it flickers on every SWR poll interval.

Provide a code fix.


Tip: Reply with @paragon-run to automatically fix this issue

);
}

EmbeddableStreamingOutput.defaultProps = {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: React

React.defaultProps is deprecated since React 18.3. Triggers console warnings. Use JS default params instead.

View Details

Location: src/renderer/components/Experiment/Tasks/EmbeddableStreamingOutput.tsx (lines 335)

Analysis

React.defaultProps is deprecated since React 18.3. Triggers console warnings

What fails EmbeddableStreamingOutput uses React.defaultProps which is deprecated since React 18.3 and will be removed in React 19.
Result Console logs 'EmbeddableStreamingOutput: Support for defaultProps will be removed' deprecation warning.
Expected No deprecation warnings. Default props should use JS destructuring defaults (which are already present on line 119).
Impact Will break on React 19 upgrade. The default is already set via destructuring, making this line redundant.
How to reproduce
Open browser console while rendering EmbeddableStreamingOutput. Look for deprecation warnings.
Patch Details
-EmbeddableStreamingOutput.defaultProps = {
-  tabs: ['output', 'provider'],
-};
AI Fix Prompt
Fix this issue: React.defaultProps is deprecated since React 18.3. Triggers console warnings. Use JS default params instead.

Location: src/renderer/components/Experiment/Tasks/EmbeddableStreamingOutput.tsx (lines 335)
Problem: EmbeddableStreamingOutput uses React.defaultProps which is deprecated since React 18.3 and will be removed in React 19.
Current behavior: Console logs 'EmbeddableStreamingOutput: Support for defaultProps will be removed' deprecation warning.
Expected: No deprecation warnings. Default props should use JS destructuring defaults (which are already present on line 119).
Steps to reproduce: Open browser console while rendering EmbeddableStreamingOutput. Look for deprecation warnings.

Provide a code fix.


Tip: Reply with @paragon-run to automatically fix this issue

import InteractiveOllamaModal from '../Tasks/InteractiveOllamaModal';
import EmbeddableStreamingOutput from '../Tasks/EmbeddableStreamingOutput';

interface InteractiveJobCardProps {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: InteractiveJobCard uses job: any prop type

InteractiveJobCard uses job: any prop type. Weakens TypeScript safety throughout the component. Define a proper Job interface.

View Details

Location: src/renderer/components/Experiment/Interactive/InteractiveJobCard.tsx (lines 22)

Analysis

InteractiveJobCard uses job: any prop type. Weakens TypeScript safety throughout the component

What fails The InteractiveJobCardProps interface types 'job' as 'any', defeating TypeScript's type checking for all job property accesses in the component.
Result No compile-time errors if job properties are misspelled or accessed incorrectly. Runtime errors possible.
Expected A proper Job interface should be defined with known fields (id, status, job_data, etc.) to enable type checking.
Impact Reduces TypeScript value; property access bugs won't be caught at compile time. AGENTS.md states: 'Avoid any. Define interfaces.'
How to reproduce
Review the InteractiveJobCard component. Note job.job_data, job.status, job.id are all accessed without type safety.
AI Fix Prompt
Fix this issue: InteractiveJobCard uses `job: any` prop type. Weakens TypeScript safety throughout the component. Define a proper Job interface.

Location: src/renderer/components/Experiment/Interactive/InteractiveJobCard.tsx (lines 22)
Problem: The InteractiveJobCardProps interface types 'job' as 'any', defeating TypeScript's type checking for all job property accesses in the component.
Current behavior: No compile-time errors if job properties are misspelled or accessed incorrectly. Runtime errors possible.
Expected: A proper Job interface should be defined with known fields (id, status, job_data, etc.) to enable type checking.
Steps to reproduce: Review the InteractiveJobCard component. Note job.job_data, job.status, job.id are all accessed without type safety.

Provide a code fix.


Tip: Reply with @paragon-run to automatically fix this issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants