Skip to content

eat: Add LLM-powered script import and item reordering to Run of Show… - #103

Merged
cj-vana merged 3 commits into
betafrom
new-pr
Sep 29, 2025
Merged

cj-vana merged 3 commits into
betafrom
new-pr

Conversation

@cj-vana

@cj-vana cj-vana commented Sep 29, 2025 •

Copy link
Copy Markdown
Collaborator

User description

… editor

Add LLM-powered script import and item reordering to Run of Show editor

LLM Script Import

  • New ImportShowFlowModal with comprehensive conversion instructions for LLMs
  • Enhanced prompt template that guides LLMs to embellish basic scripts with:
    • Realistic lighting, audio, and video cues
    • Production notes and crew coordination
    • Strategic color coding and timing estimates
  • Two-tab interface: copy instructions, paste converted JSON
  • Full JSON validation with error handling
  • Accessible via "Import" button in editor

Item Reordering

  • Add up/down arrow controls to reorder items in both desktop and mobile views
  • Automatically renumber items when moved
  • Support for both regular items and section headers
  • Buttons disabled at list boundaries

PR Type

Enhancement


Description

  • Add LLM-powered script import with comprehensive conversion instructions

  • Implement item reordering with up/down arrow controls

  • Support both regular items and section headers in reordering

  • Include JSON validation and error handling for imports


Diagram Walkthrough

flowchart LR
  A["Script/Show Flow"] --> B["LLM Conversion"]
  B --> C["ImportShowFlowModal"]
  C --> D["JSON Validation"]
  D --> E["Run of Show Items"]
  F["Existing Items"] --> G["Reorder Controls"]
  G --> H["Updated Item Order"]
Loading

File Walkthrough

Relevant files
Enhancement
ImportShowFlowModal.tsx
New LLM import modal component                                                     

apps/web/src/components/ImportShowFlowModal.tsx

  • Create new modal component for LLM-powered script import
  • Include comprehensive prompt template with production details
  • Add two-tab interface for instructions and JSON import
  • Implement JSON validation with error handling
+380/-0 
RunOfShowEditor.tsx
Import modal and reordering functionality                               

apps/web/src/pages/RunOfShowEditor.tsx

  • Add import modal integration with ImportShowFlowModal component
  • Implement item reordering with handleMoveItem function
  • Add up/down arrow controls for both desktop and mobile views
  • Include automatic item renumbering after reordering
+164/-14

… editor

Add LLM-powered script import and item reordering to Run of Show editor

  ## LLM Script Import
  - New ImportShowFlowModal with comprehensive conversion instructions for LLMs
  - Enhanced prompt template that guides LLMs to embellish basic scripts with:
    * Realistic lighting, audio, and video cues
    * Production notes and crew coordination
    * Strategic color coding and timing estimates
  - Two-tab interface: copy instructions, paste converted JSON
  - Full JSON validation with error handling
  - Accessible via "Import" button in editor

  ## Item Reordering
  - Add up/down arrow controls to reorder items in both desktop and mobile views
  - Automatically renumber items when moved
  - Support for both regular items and section headers
  - Buttons disabled at list boundaries
@netlify

netlify Bot commented Sep 29, 2025 •

Copy link
Copy Markdown

✅ Deploy Preview for sounddocsbeta ready!

Name Link
🔨 Latest commit c7e1d30
🔍 Latest deploy log https://app.netlify.com/projects/sounddocsbeta/deploys/68dacf785dedd500081a4e5e
😎 Deploy Preview https://deploy-preview-103--sounddocsbeta.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@cj-vana cj-vana changed the title eat: Add LLM-powered script import and item reordering to Run of Show… feat: Add LLM-powered script import and item reordering to Run of Show… Sep 29, 2025
@qodo-code-review qodo-code-review Bot changed the title feat: Add LLM-powered script import and item reordering to Run of Show… eat: Add LLM-powered script import and item reordering to Run of Show… Sep 29, 2025
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Validation Gaps

The JSON validation only checks presence of 'name', items array, and minimal per-item fields. It does not validate required fields like 'itemNumber' for items vs headers, durations/time formats, or unknown properties. This could allow malformed data into state and downstream persistence.

const handleImport = () => {
  try {
    setError("");

    if (!jsonInput.trim()) {
      setError("Please paste JSON data");
      return;
    }

    const data = JSON.parse(jsonInput);

    // Validate structure
    if (!data.name || typeof data.name !== "string") {
      setError("JSON must include a 'name' field");
      return;
    }

    if (!Array.isArray(data.items)) {
      setError("JSON must include an 'items' array");
      return;
    }

    // Validate each item has required fields
    for (let i = 0; i < data.items.length; i++) {
      const item = data.items[i];
      if (!item.id) {
        setError(`Item at index ${i} is missing 'id' field`);
        return;
      }
      if (!item.type || !["item", "header"].includes(item.type)) {
        setError(`Item at index ${i} has invalid type. Must be 'item' or 'header'`);
        return;
      }
    }

    const customColumns = data.custom_column_definitions || [];

    // Import the data
    onImport(data.name, data.items, customColumns);
In-place Mutation

During reordering, items are shallow-copied but individual item objects are mutated when renumbering. This may cause subtle state update issues if other parts rely on referential equality. Consider mapping to new objects when updating 'itemNumber'.

// Update item numbers for regular items
let itemCount = 1;
items.forEach((item) => {
  if (item.type === "item") {
    item.itemNumber = itemCount.toString();
    itemCount++;
  }
});

setRunOfShow({ ...runOfShow, items });
Boundary Logic

Move Up/Down disable logic uses the global first/last indices; headers and items share the same list. Confirm this UX is intended, and consider skipping over headers when moving items or providing section-aware boundaries.

<button
  onClick={() => handleMoveItem(item.id, "up")}
  disabled={runOfShow.items.findIndex((i) => i.id === item.id) === 0}
  className="text-gray-400 hover:text-indigo-400 disabled:opacity-30 disabled:cursor-not-allowed p-1"
  title="Move Up"
>
  <ArrowUp className="h-4 w-4" />
</button>
<button
  onClick={() => handleMoveItem(item.id, "down")}
  disabled={
    runOfShow.items.findIndex((i) => i.id === item.id) ===
    runOfShow.items.length - 1
  }
  className="text-gray-400 hover:text-indigo-400 disabled:opacity-30 disabled:cursor-not-allowed p-1"
  title="Move Down"
>
  <ArrowDown className="h-4 w-4" />
</button>

@qodo-code-review

qodo-code-review Bot commented Sep 29, 2025 •

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Reordering items breaks the show's timeline

The handleMoveItem function should be updated to recalculate the startTime for
all items after a reorder. Currently, it only re-numbers items, which breaks the
show's timeline.

Examples:

apps/web/src/pages/RunOfShowEditor.tsx [496-526]
  const handleMoveItem = (itemId: string, direction: "up" | "down") => {
    if (!runOfShow) return;

    const items = [...runOfShow.items];
    const currentIndex = items.findIndex((item) => item.id === itemId);

    if (currentIndex === -1) return;

    let targetIndex: number;
    if (direction === "up") {

 ... (clipped 21 lines)

Solution Walkthrough:

Before:

function handleMoveItem(itemId, direction) {
  // ... find indices and swap items in the array ...
  [items[currentIndex], items[targetIndex]] = [items[targetIndex], items[currentIndex]];

  // Only update item numbers
  let itemCount = 1;
  items.forEach(item => {
    if (item.type === "item") {
      item.itemNumber = itemCount.toString();
      itemCount++;
    }
  });

  setRunOfShow({ ...runOfShow, items });
}

After:

function handleMoveItem(itemId, direction) {
  // ... find indices and swap items in the array ...
  [items[currentIndex], items[targetIndex]] = [items[targetIndex], items[currentIndex]];

  // Create a new function to recalculate everything
  const recalculatedItems = recalculateNumbersAndTimes(items);

  setRunOfShow({ ...runOfShow, items: recalculatedItems });
}

function recalculateNumbersAndTimes(items) {
  let cumulativeTime = 0;
  let itemCount = 1;
  return items.map(item => {
    if (item.type === "item") {
      item.startTime = formatSecondsToTime(cumulativeTime);
      cumulativeTime += parseDurationToSeconds(item.duration);
      item.itemNumber = (itemCount++).toString();
    }
    return item;
  });
}
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical bug in the handleMoveItem function where startTime is not recalculated, which breaks the core functionality of the show's timeline.

High
The LLM import workflow is brittle

The LLM import feature should be refactored to use a backend API instead of a
hardcoded frontend prompt and manual copy-paste workflow. This would improve
maintainability and user experience.

Examples:

apps/web/src/components/ImportShowFlowModal.tsx [11-190]
const LLM_PROMPT = `# Convert Script/Show Flow to SoundDocs JSON Format

Please convert the following script or show flow into a JSON format compatible with SoundDocs' Run of Show system.

## IMPORTANT INSTRUCTIONS:

1. **For Basic Scripts**: If the input is just dialogue or a simple script without technical details, PLEASE EMBELLISH by:
   - Adding realistic lighting cues (e.g., "Stage wash at 75%", "Spotlight on center", "Fade to black")
   - Including audio requirements (e.g., "Wireless mic 1", "Background music -10dB", "SFX: Thunder")
   - Suggesting video/projection needs where appropriate

 ... (clipped 170 lines)
apps/web/src/components/ImportShowFlowModal.tsx [198-206]
  const handleCopyInstructions = async () => {
    try {
      await navigator.clipboard.writeText(LLM_PROMPT);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch (err) {
      console.error("Failed to copy:", err);
    }
  };

Solution Walkthrough:

Before:

// Frontend Component
const LLM_PROMPT = "A very long, hardcoded prompt string...";

function ImportModal() {
  const handleCopy = () => {
    navigator.clipboard.writeText(LLM_PROMPT);
  };

  return (
    <button onClick={handleCopy}>Copy Instructions for LLM</button>
    // ... textarea for user to paste JSON back ...
  );
}

After:

// Frontend Component
function ImportModal({ userScript }) {
  const { generateShow, isLoading } = useGenerateShowFromScript();

  const handleGenerate = () => {
    generateShow(userScript); // Calls backend API
  };

  return (
    <button onClick={handleGenerate} disabled={isLoading}>
      {isLoading ? "Generating..." : "Generate with AI"}
    </button>
  );
}

// Backend API Endpoint
async function POST(request) {
  const { userScript } = await request.json();
  const prompt = getPromptFromDatabaseOrConfig(); // Prompt is managed on the backend
  const json = await llm.generate(prompt, userScript);
  return Response.json(json);
}
Suggestion importance[1-10]: 7

__

Why: This is a valid architectural suggestion that correctly identifies a significant design weakness, as the hardcoded prompt and manual workflow are brittle and not scalable.

Medium
Possible issue
✅ Confirm destructive import action with user
Suggestion Impact:The commit added a confirmation dialog in handleImportShowFlow that warns users and aborts if not confirmed, and it replaces existing content upon confirmation, aligning with the suggestion’s intent.

code diff:

@@ -780,7 +806,21 @@
     customColumns: CustomColumnDefinition[],
   ) => {
     if (runOfShow) {
-      // Add imported items to existing show
+      // Check if there are existing items that would be replaced
+      const hasExistingContent = runOfShow.items.length > 0;
+
+      if (hasExistingContent) {
+        const confirmMessage =
+          "Are you sure you want to import this show flow?\n\n" +
+          "This will replace all current items in your run of show. " +
+          "This action cannot be undone.";
+
+        if (!window.confirm(confirmMessage)) {
+          return;
+        }
+      }
+
+      // Replace the existing show content
       setRunOfShow({
         ...runOfShow,
         name: name || runOfShow.name,

Add a window.confirm() dialog in handleImportShowFlow to warn users that
importing will overwrite the current show content, preventing accidental data
loss.

apps/web/src/pages/RunOfShowEditor.tsx [777-792]

   const handleImportShowFlow = (
     name: string,
     items: RunOfShowItem[],
     customColumns: CustomColumnDefinition[],
   ) => {
     if (runOfShow) {
-      // Add imported items to existing show
+      const isConfirmed = window.confirm(
+        "Are you sure you want to import? This will replace the current show content.",
+      );
+
+      if (!isConfirmed) {
+        return;
+      }
+
+      // Replace the existing show content
       setRunOfShow({
         ...runOfShow,
         name: name || runOfShow.name,
         items: items,
         custom_column_definitions: customColumns,
       });
       setShowImportModal(false);
     }
   };

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a destructive action that overwrites user data without confirmation, which could lead to significant data loss, and proposes a simple and effective safeguard.

Medium
Add stricter validation for imported data

Add stricter validation in handleImport to ensure 'header' items have a
headerTitle and 'item' items have a preset, preventing malformed data import.

apps/web/src/components/ImportShowFlowModal.tsx [230-241]

       // Validate each item has required fields
       for (let i = 0; i < data.items.length; i++) {
         const item = data.items[i];
         if (!item.id) {
           setError(`Item at index ${i} is missing 'id' field`);
           return;
         }
         if (!item.type || !["item", "header"].includes(item.type)) {
           setError(`Item at index ${i} has invalid type. Must be 'item' or 'header'`);
           return;
         }
+
+        if (item.type === "header" && (!item.headerTitle || typeof item.headerTitle !== "string")) {
+          setError(`Header at index ${i} is missing or has an invalid 'headerTitle'`);
+          return;
+        }
+
+        if (item.type === "item" && (!item.preset || typeof item.preset !== "string")) {
+          setError(`Item at index ${i} is missing or has an invalid 'preset'`);
+          return;
+        }
       }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the JSON validation is too lenient and proposes adding checks for required fields based on the item type, which improves data integrity.

Medium
  • Update

@cj-vana
cj-vana merged commit fb77f99 into beta Sep 29, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant