@vishu1301/script-writing 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
2
- import { ArrowRightLeft, MessageCircle, Brackets, UserRound, Sparkles, Clapperboard, Upload, Save, FileDown, RefreshCcw, Cog, ArrowRight, User, ChevronRight, Loader2, AlignLeft, Tags, Plus, X } from 'lucide-react';
2
+ import { ArrowRightLeft, MessageCircle, Brackets, UserRound, Sparkles, Clapperboard, Upload, Lock, Unlock, Save, FileDown, RefreshCcw, Cog, ArrowRight, User, ChevronRight, Loader2, AlignLeft, Tags, Plus, X } from 'lucide-react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import * as pdfjs from 'pdfjs-dist';
5
5
  import jsPDF from 'jspdf';
@@ -127,7 +127,7 @@ var blockStyles = {
127
127
  }
128
128
  };
129
129
  pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
130
- function PdfImporter({ onScriptImported, children }) {
130
+ function PdfImporter({ onScriptImported, disabled, children }) {
131
131
  const [isProcessing, setIsProcessing] = useState(false);
132
132
  const [error, setError] = useState(null);
133
133
  const fileInputRef = useRef(null);
@@ -260,7 +260,7 @@ function PdfImporter({ onScriptImported, children }) {
260
260
  type: "file",
261
261
  accept: ".pdf,application/pdf",
262
262
  onChange: handleFileChange,
263
- disabled: isProcessing,
263
+ disabled: isProcessing || disabled,
264
264
  className: "hidden",
265
265
  id: "pdf-importer-input"
266
266
  }
@@ -269,8 +269,8 @@ function PdfImporter({ onScriptImported, children }) {
269
269
  "button",
270
270
  {
271
271
  onClick: handleClick,
272
- disabled: isProcessing,
273
- className: "flex items-center justify-center gap-2 w-auto px-4 h-12 rounded-full bg-zinc-950 text-white shadow-xl shadow-zinc-900/20 border border-white/10 hover:bg-zinc-800 hover:scale-105 active:scale-95 transition-all duration-300",
272
+ disabled: isProcessing || disabled,
273
+ className: "flex items-center justify-center gap-2 w-auto px-4 h-12 rounded-full bg-zinc-950 text-white shadow-xl shadow-zinc-900/20 border border-white/10 hover:bg-zinc-800 hover:scale-105 active:scale-95 transition-all duration-300 disabled:cursor-not-allowed disabled:bg-zinc-700 disabled:shadow-none disabled:border-zinc-600/50",
274
274
  "aria-label": "Import Script",
275
275
  children: isProcessing ? /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Processing..." }) : children
276
276
  }
@@ -293,6 +293,8 @@ function ScreenplayEditorView({
293
293
  showPdfImport = false,
294
294
  showSaveButton = false,
295
295
  showSyncButton = false,
296
+ isLocked = false,
297
+ onToggleLock,
296
298
  handleBlockTextChange,
297
299
  handleSceneTypeChange,
298
300
  handleTimeOfDayChange,
@@ -366,7 +368,8 @@ function ScreenplayEditorView({
366
368
  "button",
367
369
  {
368
370
  type: "button",
369
- className: `group flex items-center gap-2.5 px-4 py-2 rounded-full font-semibold text-sm transition-all duration-300 ease-out active:scale-95 ${selected ? "bg-zinc-900 text-white shadow-md shadow-zinc-900/20" : "text-zinc-500 hover:bg-zinc-100 hover:text-zinc-900"}`,
371
+ disabled: isLocked,
372
+ className: `group flex items-center gap-2.5 px-4 py-2 rounded-full font-semibold text-sm transition-all duration-300 ease-out active:scale-95 ${selected ? "bg-zinc-900 text-white shadow-md shadow-zinc-900/20" : "text-zinc-500 hover:bg-zinc-100 hover:text-zinc-900"} ${isLocked ? "opacity-50 cursor-not-allowed" : ""}`,
370
373
  onClick: () => handleBlockTypeChange(type),
371
374
  children: [
372
375
  /* @__PURE__ */ jsx(
@@ -405,8 +408,18 @@ function ScreenplayEditorView({
405
408
  }) }),
406
409
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0 relative px-1", children: [
407
410
  /* @__PURE__ */ jsx("div", { className: "w-[1px] h-6 bg-zinc-200/80 mx-2 hidden sm:block rounded-full" }),
408
- showPdfImport && /* @__PURE__ */ jsx(PdfImporter, { onScriptImported: handleScriptImport, children: /* @__PURE__ */ jsx("div", { title: "Import Script", children: /* @__PURE__ */ jsx(Upload, { className: "w-[18px] h-[18px]" }) }) }),
409
- onSave && showSaveButton && /* @__PURE__ */ jsx(
411
+ showPdfImport && /* @__PURE__ */ jsx(PdfImporter, { disabled: isLocked, onScriptImported: handleScriptImport, children: /* @__PURE__ */ jsx("div", { title: "Import Script", children: /* @__PURE__ */ jsx(Upload, { className: "w-[18px] h-[18px]" }) }) }),
412
+ onToggleLock && /* @__PURE__ */ jsx(
413
+ "button",
414
+ {
415
+ onClick: onToggleLock,
416
+ className: `flex items-center justify-center w-10 h-10 rounded-full transition-all duration-200 active:scale-95 ${isLocked ? "text-rose-500 hover:bg-rose-50" : "text-zinc-500 hover:bg-zinc-100 hover:text-zinc-900"}`,
417
+ title: isLocked ? "Unlock Script" : "Lock Script",
418
+ "aria-label": isLocked ? "Unlock Script" : "Lock Script",
419
+ children: isLocked ? /* @__PURE__ */ jsx(Lock, { className: "w-[18px] h-[18px]" }) : /* @__PURE__ */ jsx(Unlock, { className: "w-[18px] h-[18px]" })
420
+ }
421
+ ),
422
+ onSave && showSaveButton && !isLocked && /* @__PURE__ */ jsx(
410
423
  "button",
411
424
  {
412
425
  onClick: onSave,
@@ -426,7 +439,7 @@ function ScreenplayEditorView({
426
439
  children: /* @__PURE__ */ jsx(FileDown, { className: "w-[18px] h-[18px]" })
427
440
  }
428
441
  ),
429
- onSyncWithCloud && showSyncButton && /* @__PURE__ */ jsx(
442
+ onSyncWithCloud && showSyncButton && !isLocked && /* @__PURE__ */ jsx(
430
443
  "button",
431
444
  {
432
445
  onClick: onSyncWithCloud,
@@ -503,6 +516,7 @@ function ScreenplayEditorView({
503
516
  {
504
517
  className: "absolute -left-16 top-2 w-12 text-right text-zinc-400 font-semibold select-none bg-transparent outline-none focus:ring-1 focus:ring-blue-400 rounded-sm",
505
518
  spellCheck: false,
519
+ disabled: isLocked,
506
520
  value: block.sceneNumber || "",
507
521
  onChange: (e) => handleSceneNumberChange(
508
522
  block.id,
@@ -523,6 +537,7 @@ function ScreenplayEditorView({
523
537
  {
524
538
  className: "rounded-md text-zinc-800 font-bold px-1.5 py-1 appearance-none bg-transparent hover:bg-zinc-200/50 outline-none cursor-pointer w-fit transition-colors",
525
539
  "aria-label": "Scene Type",
540
+ disabled: isLocked,
526
541
  value: (_a = block.sceneType) != null ? _a : "INT.",
527
542
  onChange: (e) => handleSceneTypeChange(block.id, e.target.value),
528
543
  style: {
@@ -542,7 +557,7 @@ function ScreenplayEditorView({
542
557
  if (!el) return;
543
558
  refs.current[block.id] = el;
544
559
  },
545
- contentEditable: true,
560
+ contentEditable: !isLocked,
546
561
  suppressContentEditableWarning: true,
547
562
  "aria-label": `Scene Heading: ${block.text}`,
548
563
  "aria-haspopup": "listbox",
@@ -567,6 +582,7 @@ function ScreenplayEditorView({
567
582
  {
568
583
  className: "rounded-md text-zinc-800 font-bold px-1.5 py-1 appearance-none bg-transparent hover:bg-zinc-200/50 outline-none cursor-pointer transition-colors",
569
584
  "aria-label": "Time of Day",
585
+ disabled: isLocked,
570
586
  value: (_b = block.timeOfDay) != null ? _b : "DAY",
571
587
  style: {
572
588
  appearance: "none"
@@ -622,7 +638,7 @@ function ScreenplayEditorView({
622
638
  if (!el) return;
623
639
  refs.current[block.id] = el;
624
640
  },
625
- contentEditable: true,
641
+ contentEditable: !isLocked,
626
642
  suppressContentEditableWarning: true,
627
643
  "aria-label": `${blockStyles[block.type].label} text`,
628
644
  "aria-multiline": block.type === "ACTION" || block.type === "DIALOGUE",
@@ -1349,7 +1365,7 @@ function useScreenplayEditor(options) {
1349
1365
  [blocks, handleBlockTextChange]
1350
1366
  );
1351
1367
  const handleScriptImport = useCallback(
1352
- (title, content, preParsedBlocks) => {
1368
+ (title, content, preParsedBlocks, isInitialLoad) => {
1353
1369
  let parsedBlocks = [];
1354
1370
  if (preParsedBlocks && preParsedBlocks.length > 0) {
1355
1371
  parsedBlocks = preParsedBlocks.map((b) => ({
@@ -1405,8 +1421,10 @@ function useScreenplayEditor(options) {
1405
1421
  }).join("");
1406
1422
  if (sbxData !== lastSavedContent.current) {
1407
1423
  lastSavedContent.current = sbxData;
1408
- const blob = new Blob([sbxData], { type: "text/plain" });
1409
- onSaveRef.current(blob);
1424
+ if (!isInitialLoad) {
1425
+ const blob = new Blob([sbxData], { type: "text/plain" });
1426
+ onSaveRef.current(blob);
1427
+ }
1410
1428
  }
1411
1429
  }
1412
1430
  setTimeout(() => {
@@ -1454,7 +1472,7 @@ function useScreenplayEditor(options) {
1454
1472
  }, 200);
1455
1473
  }, []);
1456
1474
  const loadFromUrl = useCallback(
1457
- async (url, fetchOptions = {}) => {
1475
+ async (url, fetchOptions = {}, isInitialLoad) => {
1458
1476
  var _a;
1459
1477
  try {
1460
1478
  const response = await fetch(url, fetchOptions);
@@ -1526,7 +1544,7 @@ function useScreenplayEditor(options) {
1526
1544
  }
1527
1545
  }
1528
1546
  const filename = ((_a = url.split("/").pop()) == null ? void 0 : _a.replace(/\.sbx$/i, "")) || "Imported from URL";
1529
- handleScriptImport(filename, scriptContent, preParsedBlocks);
1547
+ handleScriptImport(filename, scriptContent, preParsedBlocks, isInitialLoad);
1530
1548
  } catch (error) {
1531
1549
  console.error(
1532
1550
  "[useScreenplayEditor] Error loading script from URL:",
@@ -1540,7 +1558,7 @@ function useScreenplayEditor(options) {
1540
1558
  useEffect(() => {
1541
1559
  if ((options == null ? void 0 : options.initialUrl) && options.initialUrl !== loadedUrlRef.current) {
1542
1560
  loadedUrlRef.current = options.initialUrl;
1543
- loadFromUrl(options.initialUrl, options.fetchOptions);
1561
+ loadFromUrl(options.initialUrl, options.fetchOptions, true);
1544
1562
  }
1545
1563
  }, [options == null ? void 0 : options.initialUrl, options == null ? void 0 : options.fetchOptions, loadFromUrl]);
1546
1564
  return {
@@ -1703,13 +1721,14 @@ var handleSyncWithCloud = (blocks, sceneNumbers, onSaveAsSbx, project_name) => {
1703
1721
 
1704
1722
  // app/types/script-breakdown.types.tsx
1705
1723
  var CATEGORIES = [
1706
- { id: "CAST", label: "Cast", color: "#7c3aed", hex: "#8b5cf6" },
1707
- { id: "PROP", label: "Prop", color: "#ea580c", hex: "#f97316" },
1708
- { id: "COSTUME", label: "Costume", color: "#db2777", hex: "#ec4899" },
1709
- { id: "VEHICLE", label: "Vehicle", color: "#2563eb", hex: "#3b82f6" },
1710
- { id: "SET_PROP", label: "Set Prop", color: "#16a34a", hex: "#22c55e" },
1711
- { id: "EXTRA", label: "Extra", color: "#0d9488", hex: "#14b8a6" },
1712
- { id: "LOCATION", label: "Location", color: "#ca8a04", hex: "#eab308" }
1724
+ { id: "CAST", label: "Cast", color: "#7C3AED", hex: "#A78BFA" },
1725
+ { id: "PROP", label: "Prop", color: "#FF6A00", hex: "#FFB86B" },
1726
+ { id: "COSTUME", label: "Costume", color: "#FF2E93", hex: "#FF8AC2" },
1727
+ { id: "VEHICLE", label: "Vehicle", color: "#00A6FF", hex: "#6ED6FF" },
1728
+ { id: "SET_PROP", label: "Set Prop", color: "#00C853", hex: "#69F0AE" },
1729
+ { id: "EXTRA", label: "Extra", color: "#00B8D4", hex: "#62EFFF" },
1730
+ { id: "LOCATION", label: "Location", color: "#FFB300", hex: "#FFE082" },
1731
+ { id: "OTHER", label: "Other", color: "#9E9E9E", hex: "#E0E0E0" }
1713
1732
  ];
1714
1733
  function ScriptBreakdownSceneView({
1715
1734
  blocks,
@@ -1728,7 +1747,9 @@ function ScriptBreakdownSceneView({
1728
1747
  addSubLocation,
1729
1748
  removeSubLocation,
1730
1749
  sceneBrief,
1731
- setSceneBrief
1750
+ setSceneBrief,
1751
+ onSummarize,
1752
+ isSummarizing
1732
1753
  }) {
1733
1754
  const COURIER_STACK = "'Courier Prime', 'Courier', monospace";
1734
1755
  const [isSubLocOpen, setIsSubLocOpen] = useState(false);
@@ -1775,47 +1796,54 @@ function ScriptBreakdownSceneView({
1775
1796
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-zinc-500 animate-pulse", children: "Loading scene details..." })
1776
1797
  ] });
1777
1798
  }
1778
- const hasLocationTag = tags.some((t) => t.categoryId === "LOCATION");
1799
+ const hasLocationTag = tags.some((t) => t.category_id === "LOCATION");
1779
1800
  const renderBlockText = (block) => {
1780
- const blockTags = tags.filter((t) => t.blockId === block.id).sort((a, b) => a.startIndex - b.startIndex);
1801
+ const blockTags = tags.filter((t) => t.block_id === block.id).sort((a, b) => a.start_index - b.start_index);
1781
1802
  if (blockTags.length === 0) return block.text;
1782
1803
  const nodes = [];
1783
1804
  let currentIndex = 0;
1784
1805
  blockTags.forEach((tag) => {
1785
- if (tag.startIndex > currentIndex) {
1806
+ const actualStart = Math.max(tag.start_index, currentIndex);
1807
+ if (actualStart > currentIndex) {
1786
1808
  nodes.push(
1787
- /* @__PURE__ */ jsx("span", { children: block.text.slice(currentIndex, tag.startIndex) }, `text-${currentIndex}`)
1809
+ /* @__PURE__ */ jsx("span", { children: block.text.slice(currentIndex, actualStart) }, `text-${currentIndex}`)
1788
1810
  );
1789
1811
  }
1790
- const category = CATEGORIES.find((c) => c.id === tag.categoryId);
1791
- nodes.push(
1792
- /* @__PURE__ */ jsx(
1793
- "span",
1794
- {
1795
- title: `${category == null ? void 0 : category.label} (Click to edit)`,
1796
- onClick: (e) => {
1797
- e.stopPropagation();
1798
- const selection = window.getSelection();
1799
- if (!selection) return;
1800
- const range = document.createRange();
1801
- const textNode = e.currentTarget.firstChild;
1802
- if (textNode && textNode.nodeType === Node.TEXT_NODE) {
1803
- range.selectNodeContents(textNode);
1804
- } else {
1805
- range.selectNodeContents(e.currentTarget);
1806
- }
1807
- selection.removeAllRanges();
1808
- selection.addRange(range);
1809
- setTimeout(() => handleMouseUp(), 0);
1812
+ const category = CATEGORIES.find((c) => c.id === tag.category_id);
1813
+ if (actualStart < tag.end_index) {
1814
+ nodes.push(
1815
+ /* @__PURE__ */ jsx(
1816
+ "span",
1817
+ {
1818
+ title: `${category == null ? void 0 : category.label} (Click to edit)`,
1819
+ onClick: (e) => {
1820
+ e.stopPropagation();
1821
+ const selection = window.getSelection();
1822
+ if (!selection) return;
1823
+ const range = document.createRange();
1824
+ const textNode = e.currentTarget.firstChild;
1825
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
1826
+ range.selectNodeContents(textNode);
1827
+ } else {
1828
+ range.selectNodeContents(e.currentTarget);
1829
+ }
1830
+ selection.removeAllRanges();
1831
+ selection.addRange(range);
1832
+ setTimeout(() => handleMouseUp(), 0);
1833
+ },
1834
+ className: "cursor-pointer font-bold transition-all hover:opacity-80 rounded-[3px]",
1835
+ style: {
1836
+ color: category == null ? void 0 : category.color,
1837
+ padding: "0.125rem 0.25rem",
1838
+ margin: "0 -0.125rem"
1839
+ },
1840
+ children: block.text.slice(actualStart, tag.end_index)
1810
1841
  },
1811
- className: "cursor-pointer font-bold transition-opacity hover:opacity-70",
1812
- style: { color: category == null ? void 0 : category.color },
1813
- children: block.text.slice(tag.startIndex, tag.endIndex)
1814
- },
1815
- tag.id
1816
- )
1817
- );
1818
- currentIndex = tag.endIndex;
1842
+ tag.id || `tag-${actualStart}-${tag.end_index}`
1843
+ )
1844
+ );
1845
+ }
1846
+ currentIndex = Math.max(currentIndex, tag.end_index);
1819
1847
  });
1820
1848
  if (currentIndex < block.text.length) {
1821
1849
  nodes.push(
@@ -1903,16 +1931,16 @@ function ScriptBreakdownSceneView({
1903
1931
  cat.id
1904
1932
  )),
1905
1933
  tags.some(
1906
- (t) => t.blockId === block.id && t.startIndex === selectionMenu.startIndex && t.endIndex === selectionMenu.endIndex
1934
+ (t) => t.block_id === block.id && t.start_index === selectionMenu.startIndex && t.end_index === selectionMenu.endIndex
1907
1935
  ) && /* @__PURE__ */ jsx("div", { className: "mt-1 pt-1 border-t border-white/60", children: /* @__PURE__ */ jsxs(
1908
1936
  "button",
1909
1937
  {
1910
1938
  onClick: (e) => {
1911
1939
  const tagToRemove = tags.find(
1912
- (t) => t.blockId === block.id && t.startIndex === selectionMenu.startIndex && t.endIndex === selectionMenu.endIndex
1940
+ (t) => t.block_id === block.id && t.start_index === selectionMenu.startIndex && t.end_index === selectionMenu.endIndex
1913
1941
  );
1914
- if (tagToRemove) {
1915
- removeTag(e, tagToRemove.id);
1942
+ if (tagToRemove && tagToRemove.id) {
1943
+ removeTag(e, tagToRemove == null ? void 0 : tagToRemove.id);
1916
1944
  clearSelection();
1917
1945
  }
1918
1946
  },
@@ -1955,115 +1983,136 @@ function ScriptBreakdownSceneView({
1955
1983
  ) })
1956
1984
  ] })
1957
1985
  ] }),
1958
- /* @__PURE__ */ jsx("div", { className: "w-full lg:w-80 flex-shrink-0 flex flex-col gap-6 sticky top-6", children: /* @__PURE__ */ jsxs("div", { className: "bg-white/50 backdrop-blur-2xl border border-white shadow-[0_8px_30px_rgb(0,0,0,0.04)] rounded-[2.5rem] p-8", children: [
1959
- /* @__PURE__ */ jsxs("h3", { className: "text-xs font-extrabold text-slate-800 uppercase tracking-[0.25em] mb-8 flex items-center gap-3", children: [
1960
- /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center w-8 h-8 rounded-full bg-white/80 shadow-[0_4px_15px_rgb(0,0,0,0.04)] border border-white", children: /* @__PURE__ */ jsx(Tags, { className: "w-3.5 h-3.5 text-slate-500" }) }),
1961
- "Tags",
1962
- /* @__PURE__ */ jsx("span", { className: "ml-auto bg-slate-100/80 text-slate-500 px-2.5 py-1 rounded-lg text-[10px] font-bold tracking-widest border border-slate-200/50 shadow-inner", children: tags.length })
1963
- ] }),
1964
- tags.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-8", children: CATEGORIES.map((cat) => {
1965
- const catTags = Array.from(
1966
- new Map(
1967
- tags.filter((t) => t.categoryId === cat.id).map((tag) => [tag.text.toLowerCase(), tag])
1968
- ).values()
1969
- );
1970
- if (catTags.length === 0) return null;
1971
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
1972
- /* @__PURE__ */ jsxs("h4", { className: "text-[10px] font-bold text-slate-400 uppercase tracking-[0.2em] flex items-center gap-2 border-b border-white/60 pb-2", children: [
1973
- /* @__PURE__ */ jsx(
1974
- "div",
1975
- {
1976
- className: "w-2 h-2 rounded-full shadow-sm",
1977
- style: { backgroundColor: cat.hex }
1978
- }
1979
- ),
1980
- cat.label,
1981
- cat.id === "LOCATION" && /* @__PURE__ */ jsxs(
1982
- "div",
1983
- {
1984
- className: "ml-auto relative",
1985
- ref: subLocPopoverRef,
1986
- children: [
1987
- /* @__PURE__ */ jsx(
1988
- "button",
1989
- {
1990
- onClick: () => setIsSubLocOpen(!isSubLocOpen),
1991
- className: "flex items-center justify-center w-5 h-5 rounded-full hover:bg-slate-200 transition-colors",
1992
- title: "Add Sub Location",
1993
- children: /* @__PURE__ */ jsx(Plus, { className: "w-3 h-3 text-slate-500" })
1994
- }
1995
- ),
1996
- isSubLocOpen && /* @__PURE__ */ jsxs("div", { className: "absolute right-0 top-full mt-2 w-56 bg-white backdrop-blur-2xl shadow-[0_10px_40px_rgb(0,0,0,0.06)] border border-white rounded-[1.5rem] p-3 z-50 animate-in fade-in zoom-in-95", children: [
1997
- /* @__PURE__ */ jsx("p", { className: "text-[9px] font-extrabold tracking-[0.2em] text-slate-400 uppercase mb-2 px-1", children: "Add Sub Location" }),
1998
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
1999
- /* @__PURE__ */ jsx(
2000
- "input",
2001
- {
2002
- type: "text",
2003
- value: subLocInput,
2004
- onChange: (e) => setSubLocInput(e.target.value),
2005
- onKeyDown: (e) => {
2006
- if (e.key === "Enter") {
1986
+ /* @__PURE__ */ jsxs("div", { className: "w-full lg:w-80 flex-shrink-0 flex flex-col gap-6 sticky top-6", children: [
1987
+ /* @__PURE__ */ jsxs(
1988
+ "button",
1989
+ {
1990
+ onClick: onSummarize,
1991
+ disabled: isSummarizing,
1992
+ className: `group relative w-full py-4 px-6 rounded-[3rem] overflow-hidden bg-gradient-to-b from-white via-zinc-50 to-zinc-100 border border-white/70 shadow-[0_10px_30px_-10px_rgba(0,0,0,0.15),inset_0_1px_0_rgba(255,255,255,0.9)] transition-all duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] ${isSummarizing ? "opacity-70 cursor-wait" : "hover:shadow-[0_20px_50px_-15px_rgba(139,92,246,0.35)] hover:-translate-y-[2px] active:scale-[0.97]"}`,
1993
+ children: [
1994
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-0 opacity-0 group-hover:opacity-100 transition duration-700", children: /* @__PURE__ */ jsx("span", { className: "absolute inset-0 bg-[conic-gradient(from_180deg_at_50%_50%,#8b5cf6,transparent_40%,#d946ef,transparent_70%)] blur-2xl opacity-30 animate-[spin_6s_linear_infinite]" }) }),
1995
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-0 opacity-0 group-hover:opacity-100 transition duration-500 bg-[radial-gradient(circle_at_50%_40%,rgba(139,92,246,0.18),transparent_70%)]" }),
1996
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-[1px] rounded-[inherit] bg-gradient-to-b from-white/90 to-transparent pointer-events-none" }),
1997
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-0 overflow-hidden", children: /* @__PURE__ */ jsx("span", { className: "absolute -left-[100%] top-0 h-full w-[60%] bg-gradient-to-r from-transparent via-white/60 to-transparent skew-x-[-20deg] group-hover:left-[120%] transition-all duration-[1200ms]" }) }),
1998
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-[2px] rounded-[inherit] opacity-0 group-hover:opacity-100 bg-gradient-to-r from-violet-200/40 via-white/40 to-fuchsia-200/40 blur-md transition duration-500" }),
1999
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center gap-3", children: [
2000
+ isSummarizing ? /* @__PURE__ */ jsx(Loader2, { className: "w-4 h-4 text-violet-500 animate-spin" }) : /* @__PURE__ */ jsx(Sparkles, { className: "w-4 h-4 text-violet-500 transition-all duration-500 group-hover:-rotate-12 group-hover:scale-125" }),
2001
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold tracking-[0.32em] uppercase text-zinc-800 group-hover:text-zinc-950 transition-all duration-300", children: isSummarizing ? "Summarizing..." : "Summarize Scene" })
2002
+ ] })
2003
+ ]
2004
+ }
2005
+ ),
2006
+ /* @__PURE__ */ jsxs("div", { className: "bg-white/50 backdrop-blur-2xl border border-white shadow-[0_8px_30px_rgb(0,0,0,0.04)] rounded-[2.5rem] p-8", children: [
2007
+ /* @__PURE__ */ jsxs("h3", { className: "text-xs font-extrabold text-slate-800 uppercase tracking-[0.25em] mb-8 flex items-center gap-3", children: [
2008
+ /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center w-8 h-8 rounded-full bg-white/80 shadow-[0_4px_15px_rgb(0,0,0,0.04)] border border-white", children: /* @__PURE__ */ jsx(Tags, { className: "w-3.5 h-3.5 text-slate-500" }) }),
2009
+ "Tags",
2010
+ /* @__PURE__ */ jsx("span", { className: "ml-auto bg-slate-100/80 text-slate-500 px-2.5 py-1 rounded-lg text-[10px] font-bold tracking-widest border border-slate-200/50 shadow-inner", children: tags.length })
2011
+ ] }),
2012
+ tags.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-8", children: CATEGORIES.map((cat) => {
2013
+ const catTags = Array.from(
2014
+ new Map(
2015
+ tags.filter((t) => t.category_id === cat.id).map((tag) => [tag.name.toLowerCase(), tag])
2016
+ ).values()
2017
+ );
2018
+ if (catTags.length === 0) return null;
2019
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
2020
+ /* @__PURE__ */ jsxs("h4", { className: "text-[10px] font-bold text-slate-400 uppercase tracking-[0.2em] flex items-center gap-2 border-b border-white/60 pb-2", children: [
2021
+ /* @__PURE__ */ jsx(
2022
+ "div",
2023
+ {
2024
+ className: "w-2 h-2 rounded-full shadow-sm",
2025
+ style: { backgroundColor: cat.hex }
2026
+ }
2027
+ ),
2028
+ cat.label
2029
+ ] }),
2030
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
2031
+ catTags.map((tag, index) => /* @__PURE__ */ jsx(
2032
+ "span",
2033
+ {
2034
+ className: "text-[11px] font-bold px-3 py-1.5 rounded-xl bg-white/80 backdrop-blur-md border border-white shadow-[0_4px_15px_rgb(0,0,0,0.03)] hover:shadow-[0_4px_20px_rgb(0,0,0,0.06)] hover:-translate-y-0.5 transition-all duration-300 cursor-default",
2035
+ style: { color: cat.color },
2036
+ children: tag.name
2037
+ },
2038
+ index
2039
+ )),
2040
+ cat.id === "LOCATION" && /* @__PURE__ */ jsxs(
2041
+ "div",
2042
+ {
2043
+ className: "relative flex items-center",
2044
+ ref: subLocPopoverRef,
2045
+ children: [
2046
+ /* @__PURE__ */ jsx(
2047
+ "button",
2048
+ {
2049
+ onClick: () => setIsSubLocOpen(!isSubLocOpen),
2050
+ className: "flex items-center justify-center w-5 h-5 rounded-full hover:bg-slate-200 transition-colors",
2051
+ title: "Add Sub Location",
2052
+ children: /* @__PURE__ */ jsx(Plus, { className: "w-3 h-3 text-slate-500" })
2053
+ }
2054
+ ),
2055
+ isSubLocOpen && /* @__PURE__ */ jsxs("div", { className: "absolute left-0 top-full mt-2 w-56 bg-white backdrop-blur-2xl shadow-[0_10px_40px_rgb(0,0,0,0.06)] border border-white rounded-[1.5rem] p-3 z-50 animate-in fade-in zoom-in-95", children: [
2056
+ /* @__PURE__ */ jsx("p", { className: "text-[9px] font-extrabold tracking-[0.2em] text-slate-400 uppercase mb-2 px-1", children: "Add Sub Location" }),
2057
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
2058
+ /* @__PURE__ */ jsx(
2059
+ "input",
2060
+ {
2061
+ type: "text",
2062
+ value: subLocInput,
2063
+ onChange: (e) => setSubLocInput(e.target.value),
2064
+ onKeyDown: (e) => {
2065
+ if (e.key === "Enter") {
2066
+ addSubLocation(subLocInput);
2067
+ setSubLocInput("");
2068
+ setIsSubLocOpen(false);
2069
+ }
2070
+ },
2071
+ className: "w-full text-xs px-3 py-2 bg-white/50 border border-white/60 rounded-xl outline-none focus:bg-white/80 focus:border-white transition-all text-slate-700 font-bold shadow-[0_2px_10px_rgb(0,0,0,0.02)] placeholder:font-medium placeholder:text-slate-400",
2072
+ placeholder: "Sub location...",
2073
+ autoFocus: true
2074
+ }
2075
+ ),
2076
+ /* @__PURE__ */ jsx(
2077
+ "button",
2078
+ {
2079
+ onClick: () => {
2007
2080
  addSubLocation(subLocInput);
2008
2081
  setSubLocInput("");
2009
2082
  setIsSubLocOpen(false);
2010
- }
2011
- },
2012
- className: "w-full text-xs px-3 py-2 bg-white/50 border border-white/60 rounded-xl outline-none focus:bg-white/80 focus:border-white transition-all text-slate-700 font-bold shadow-[0_2px_10px_rgb(0,0,0,0.02)] placeholder:font-medium placeholder:text-slate-400",
2013
- placeholder: "Sub location...",
2014
- autoFocus: true
2015
- }
2016
- ),
2017
- /* @__PURE__ */ jsx(
2018
- "button",
2019
- {
2020
- onClick: () => {
2021
- addSubLocation(subLocInput);
2022
- setSubLocInput("");
2023
- setIsSubLocOpen(false);
2024
- },
2025
- className: "flex items-center justify-center shrink-0 bg-slate-800 text-white px-3.5 py-2 rounded-xl text-[11px] font-bold hover:bg-slate-700 hover:shadow-md transition-all active:scale-95",
2026
- children: "Add"
2027
- }
2028
- )
2083
+ },
2084
+ className: "flex items-center justify-center shrink-0 bg-slate-800 text-white px-3.5 py-2 rounded-xl text-[11px] font-bold hover:bg-slate-700 hover:shadow-md transition-all active:scale-95",
2085
+ children: "Add"
2086
+ }
2087
+ )
2088
+ ] })
2029
2089
  ] })
2030
- ] })
2031
- ]
2032
- }
2033
- )
2034
- ] }),
2035
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
2036
- catTags.map((tag) => /* @__PURE__ */ jsx(
2037
- "span",
2038
- {
2039
- className: "text-[11px] font-bold px-3 py-1.5 rounded-xl bg-white/80 backdrop-blur-md border border-white shadow-[0_4px_15px_rgb(0,0,0,0.03)] hover:shadow-[0_4px_20px_rgb(0,0,0,0.06)] hover:-translate-y-0.5 transition-all duration-300 cursor-default",
2040
- style: { color: cat.color },
2041
- children: tag.text
2042
- },
2043
- tag.id
2044
- )),
2045
- cat.id === "LOCATION" && subLocations.map((subLoc) => /* @__PURE__ */ jsxs(
2046
- "span",
2047
- {
2048
- className: "group flex items-center gap-1.5 text-[11px] font-bold px-3 py-1.5 rounded-xl bg-white backdrop-blur-md border border-slate-200/50 shadow-[0_4px_15px_rgb(0,0,0,0.03)] transition-all duration-300 cursor-default text-slate-500",
2049
- children: [
2050
- subLoc,
2051
- /* @__PURE__ */ jsx(
2052
- "button",
2053
- {
2054
- onClick: () => removeSubLocation(subLoc),
2055
- className: "w-3.5 h-3.5 rounded-full hover:bg-slate-300/50 flex items-center justify-center transition-colors opacity-0 group-hover:opacity-100",
2056
- children: /* @__PURE__ */ jsx(X, { className: "w-2.5 h-2.5" })
2057
- }
2058
- )
2059
- ]
2060
- },
2061
- `sub-${subLoc}`
2062
- ))
2063
- ] })
2064
- ] }, cat.id);
2065
- }) }) : /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-slate-400 italic bg-white/40 p-6 rounded-[2rem] border border-white border-dashed text-center shadow-[0_4px_20px_rgb(0,0,0,0.02)]", children: "Highlight text to tag elements." })
2066
- ] }) })
2090
+ ]
2091
+ }
2092
+ ),
2093
+ cat.id === "LOCATION" && subLocations.map((subLoc) => /* @__PURE__ */ jsxs(
2094
+ "span",
2095
+ {
2096
+ className: "group flex items-center gap-1.5 text-[11px] font-bold px-3 py-1.5 rounded-xl bg-white backdrop-blur-md border border-slate-200/50 shadow-[0_4px_15px_rgb(0,0,0,0.03)] transition-all duration-300 cursor-default text-slate-500",
2097
+ children: [
2098
+ subLoc,
2099
+ /* @__PURE__ */ jsx(
2100
+ "button",
2101
+ {
2102
+ onClick: () => removeSubLocation(subLoc),
2103
+ className: "w-3.5 h-3.5 rounded-full hover:bg-slate-300/50 flex items-center justify-center transition-colors opacity-0 group-hover:opacity-100",
2104
+ children: /* @__PURE__ */ jsx(X, { className: "w-2.5 h-2.5" })
2105
+ }
2106
+ )
2107
+ ]
2108
+ },
2109
+ `sub-${subLoc}`
2110
+ ))
2111
+ ] })
2112
+ ] }, cat.id);
2113
+ }) }) : /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-slate-400 italic bg-white/40 p-6 rounded-[2rem] border border-white border-dashed text-center shadow-[0_4px_20px_rgb(0,0,0,0.02)]", children: "Highlight text to tag elements." })
2114
+ ] })
2115
+ ] })
2067
2116
  ] }) });
2068
2117
  }
2069
2118
  function useScriptBreakdown({
@@ -2145,6 +2194,14 @@ var use_script_breakdown_default = useScriptBreakdown;
2145
2194
  // app/hook/use-script-breakdown-scene.ts
2146
2195
  function useScriptBreakdownScene(sceneNumber) {
2147
2196
  const { scenes, isLoading, error } = use_script_breakdown_default({ scenes: [] });
2197
+ const [tags, setTags] = useState([]);
2198
+ const [selectionMenu, setSelectionMenu] = useState(null);
2199
+ const autoTaggedSceneRef = useRef(null);
2200
+ const [menuPlacement, setMenuPlacement] = useState("top");
2201
+ const [subLocations, setSubLocations] = useState([]);
2202
+ const [sceneBrief, setSceneBrief] = useState("");
2203
+ const [isSummarizing, setIsSummarizing] = useState(false);
2204
+ const menuRef = useRef(null);
2148
2205
  const scene = useMemo(() => {
2149
2206
  return scenes.find((s) => s.scene_number === sceneNumber);
2150
2207
  }, [scenes, sceneNumber]);
@@ -2173,7 +2230,9 @@ function useScriptBreakdownScene(sceneNumber) {
2173
2230
  break;
2174
2231
  }
2175
2232
  }
2176
- parsedBlocks.push({ id: uuid(), type, text: divText });
2233
+ const idAttr = div.getAttribute("id");
2234
+ const blockId = idAttr && idAttr.startsWith("par") ? idAttr.substring(3) : idAttr || uuid();
2235
+ parsedBlocks.push({ id: blockId, type, text: divText });
2177
2236
  });
2178
2237
  return parsedBlocks;
2179
2238
  }, [scene]);
@@ -2185,12 +2244,57 @@ function useScriptBreakdownScene(sceneNumber) {
2185
2244
  }).filter(Boolean);
2186
2245
  return [...new Set(chars)];
2187
2246
  }, [blocks]);
2188
- const [tags, setTags] = useState([]);
2189
- const [selectionMenu, setSelectionMenu] = useState(null);
2190
- const autoTaggedSceneRef = useRef(null);
2191
- const [menuPlacement, setMenuPlacement] = useState("top");
2192
- const menuRef = useRef(null);
2193
- const [subLocations, setSubLocations] = useState([]);
2247
+ const handleAISummarize = async () => {
2248
+ setIsSummarizing(true);
2249
+ const res = await fetch(
2250
+ "https://nonfibrous-extrafloral-verlene.ngrok-free.dev/extract",
2251
+ {
2252
+ method: "POST",
2253
+ headers: {
2254
+ "Content-Type": "application/json",
2255
+ "ngrok-skip-browser-warning": "true"
2256
+ },
2257
+ body: JSON.stringify({
2258
+ script: (scene == null ? void 0 : scene.content) || ""
2259
+ })
2260
+ }
2261
+ );
2262
+ if (res.ok) {
2263
+ const data = await res.json();
2264
+ setIsSummarizing(false);
2265
+ const parsedData = JSON.parse(data.data) || [];
2266
+ console.log("AI Tags:", parsedData);
2267
+ const newTags = [];
2268
+ parsedData.forEach((aiTag) => {
2269
+ if (!aiTag.block_id || !aiTag.category_id || typeof aiTag.start_index !== "number" || typeof aiTag.end_index !== "number") {
2270
+ return;
2271
+ }
2272
+ newTags.push({
2273
+ block_id: String(aiTag.block_id).startsWith("par") ? String(aiTag.block_id).substring(3) : String(aiTag.block_id),
2274
+ category_id: aiTag.category_id,
2275
+ name: aiTag.name,
2276
+ start_index: aiTag.start_index,
2277
+ end_index: aiTag.end_index
2278
+ });
2279
+ });
2280
+ setTags((prev) => {
2281
+ const merged = [...prev];
2282
+ newTags.forEach((newTag) => {
2283
+ const isOverlapping = merged.some(
2284
+ (t) => t.block_id === newTag.block_id && newTag.end_index > t.start_index && newTag.start_index < t.end_index
2285
+ );
2286
+ if (!isOverlapping) {
2287
+ merged.push(newTag);
2288
+ }
2289
+ });
2290
+ return merged;
2291
+ });
2292
+ return data;
2293
+ } else {
2294
+ setIsSummarizing(false);
2295
+ console.error("Failed to summarize scene:", res);
2296
+ }
2297
+ };
2194
2298
  const addSubLocation = useCallback(
2195
2299
  (subLocation) => {
2196
2300
  const trimmed = subLocation.trim();
@@ -2203,7 +2307,6 @@ function useScriptBreakdownScene(sceneNumber) {
2203
2307
  const removeSubLocation = useCallback((subLocation) => {
2204
2308
  setSubLocations((prev) => prev.filter((loc) => loc !== subLocation));
2205
2309
  }, []);
2206
- const [sceneBrief, setSceneBrief] = useState("");
2207
2310
  useEffect(() => {
2208
2311
  setTags([]);
2209
2312
  setSubLocations([]);
@@ -2222,19 +2325,19 @@ function useScriptBreakdownScene(sceneNumber) {
2222
2325
  let match;
2223
2326
  while ((match = regex.exec(block.text)) !== null) {
2224
2327
  const isOverlapping = autoTags.some(
2225
- (t) => t.blockId === block.id && match.index + char.length > t.startIndex && match.index < t.endIndex
2328
+ (t) => t.block_id === block.id && match.index + char.length > t.start_index && match.index < t.end_index
2226
2329
  );
2227
2330
  if (!isOverlapping) {
2228
2331
  autoTags.push({
2229
2332
  id: uuid(),
2230
- blockId: block.id,
2231
- categoryId: "CAST",
2232
- text: block.text.substring(
2333
+ block_id: block.id,
2334
+ category_id: "CAST",
2335
+ name: block.text.substring(
2233
2336
  match.index,
2234
2337
  match.index + char.length
2235
2338
  ),
2236
- startIndex: match.index,
2237
- endIndex: match.index + char.length
2339
+ start_index: match.index,
2340
+ end_index: match.index + char.length
2238
2341
  });
2239
2342
  }
2240
2343
  }
@@ -2334,15 +2437,15 @@ function useScriptBreakdownScene(sceneNumber) {
2334
2437
  if (!selectionMenu) return;
2335
2438
  const newTag = {
2336
2439
  id: uuid(),
2337
- blockId: selectionMenu.blockId,
2338
- categoryId,
2339
- text: selectionMenu.text,
2340
- startIndex: selectionMenu.startIndex,
2341
- endIndex: selectionMenu.endIndex
2440
+ block_id: selectionMenu.blockId,
2441
+ category_id: categoryId,
2442
+ name: selectionMenu.text,
2443
+ start_index: selectionMenu.startIndex,
2444
+ end_index: selectionMenu.endIndex
2342
2445
  };
2343
2446
  setTags((prev) => {
2344
2447
  const filtered = prev.filter(
2345
- (t) => t.blockId !== newTag.blockId || !(newTag.endIndex > t.startIndex && newTag.startIndex < t.endIndex)
2448
+ (t) => t.block_id !== newTag.block_id || !(newTag.end_index > t.start_index && newTag.start_index < t.end_index)
2346
2449
  );
2347
2450
  return [...filtered, newTag];
2348
2451
  });
@@ -2372,7 +2475,9 @@ function useScriptBreakdownScene(sceneNumber) {
2372
2475
  addSubLocation,
2373
2476
  removeSubLocation,
2374
2477
  sceneBrief,
2375
- setSceneBrief
2478
+ setSceneBrief,
2479
+ handleAISummarize,
2480
+ isSummarizing
2376
2481
  };
2377
2482
  }
2378
2483