@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.cjs CHANGED
@@ -152,7 +152,7 @@ var blockStyles = {
152
152
  }
153
153
  };
154
154
  pdfjs__namespace.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs__namespace.version}/build/pdf.worker.min.mjs`;
155
- function PdfImporter({ onScriptImported, children }) {
155
+ function PdfImporter({ onScriptImported, disabled, children }) {
156
156
  const [isProcessing, setIsProcessing] = react.useState(false);
157
157
  const [error, setError] = react.useState(null);
158
158
  const fileInputRef = react.useRef(null);
@@ -285,7 +285,7 @@ function PdfImporter({ onScriptImported, children }) {
285
285
  type: "file",
286
286
  accept: ".pdf,application/pdf",
287
287
  onChange: handleFileChange,
288
- disabled: isProcessing,
288
+ disabled: isProcessing || disabled,
289
289
  className: "hidden",
290
290
  id: "pdf-importer-input"
291
291
  }
@@ -294,8 +294,8 @@ function PdfImporter({ onScriptImported, children }) {
294
294
  "button",
295
295
  {
296
296
  onClick: handleClick,
297
- disabled: isProcessing,
298
- 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",
297
+ disabled: isProcessing || disabled,
298
+ 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",
299
299
  "aria-label": "Import Script",
300
300
  children: isProcessing ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: "Processing..." }) : children
301
301
  }
@@ -318,6 +318,8 @@ function ScreenplayEditorView({
318
318
  showPdfImport = false,
319
319
  showSaveButton = false,
320
320
  showSyncButton = false,
321
+ isLocked = false,
322
+ onToggleLock,
321
323
  handleBlockTextChange,
322
324
  handleSceneTypeChange,
323
325
  handleTimeOfDayChange,
@@ -391,7 +393,8 @@ function ScreenplayEditorView({
391
393
  "button",
392
394
  {
393
395
  type: "button",
394
- 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"}`,
396
+ disabled: isLocked,
397
+ 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" : ""}`,
395
398
  onClick: () => handleBlockTypeChange(type),
396
399
  children: [
397
400
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -430,8 +433,18 @@ function ScreenplayEditorView({
430
433
  }) }),
431
434
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 shrink-0 relative px-1", children: [
432
435
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-[1px] h-6 bg-zinc-200/80 mx-2 hidden sm:block rounded-full" }),
433
- showPdfImport && /* @__PURE__ */ jsxRuntime.jsx(PdfImporter, { onScriptImported: handleScriptImport, children: /* @__PURE__ */ jsxRuntime.jsx("div", { title: "Import Script", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: "w-[18px] h-[18px]" }) }) }),
434
- onSave && showSaveButton && /* @__PURE__ */ jsxRuntime.jsx(
436
+ showPdfImport && /* @__PURE__ */ jsxRuntime.jsx(PdfImporter, { disabled: isLocked, onScriptImported: handleScriptImport, children: /* @__PURE__ */ jsxRuntime.jsx("div", { title: "Import Script", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: "w-[18px] h-[18px]" }) }) }),
437
+ onToggleLock && /* @__PURE__ */ jsxRuntime.jsx(
438
+ "button",
439
+ {
440
+ onClick: onToggleLock,
441
+ 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"}`,
442
+ title: isLocked ? "Unlock Script" : "Lock Script",
443
+ "aria-label": isLocked ? "Unlock Script" : "Lock Script",
444
+ children: isLocked ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Lock, { className: "w-[18px] h-[18px]" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Unlock, { className: "w-[18px] h-[18px]" })
445
+ }
446
+ ),
447
+ onSave && showSaveButton && !isLocked && /* @__PURE__ */ jsxRuntime.jsx(
435
448
  "button",
436
449
  {
437
450
  onClick: onSave,
@@ -451,7 +464,7 @@ function ScreenplayEditorView({
451
464
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileDown, { className: "w-[18px] h-[18px]" })
452
465
  }
453
466
  ),
454
- onSyncWithCloud && showSyncButton && /* @__PURE__ */ jsxRuntime.jsx(
467
+ onSyncWithCloud && showSyncButton && !isLocked && /* @__PURE__ */ jsxRuntime.jsx(
455
468
  "button",
456
469
  {
457
470
  onClick: onSyncWithCloud,
@@ -528,6 +541,7 @@ function ScreenplayEditorView({
528
541
  {
529
542
  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",
530
543
  spellCheck: false,
544
+ disabled: isLocked,
531
545
  value: block.sceneNumber || "",
532
546
  onChange: (e) => handleSceneNumberChange(
533
547
  block.id,
@@ -548,6 +562,7 @@ function ScreenplayEditorView({
548
562
  {
549
563
  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",
550
564
  "aria-label": "Scene Type",
565
+ disabled: isLocked,
551
566
  value: (_a = block.sceneType) != null ? _a : "INT.",
552
567
  onChange: (e) => handleSceneTypeChange(block.id, e.target.value),
553
568
  style: {
@@ -567,7 +582,7 @@ function ScreenplayEditorView({
567
582
  if (!el) return;
568
583
  refs.current[block.id] = el;
569
584
  },
570
- contentEditable: true,
585
+ contentEditable: !isLocked,
571
586
  suppressContentEditableWarning: true,
572
587
  "aria-label": `Scene Heading: ${block.text}`,
573
588
  "aria-haspopup": "listbox",
@@ -592,6 +607,7 @@ function ScreenplayEditorView({
592
607
  {
593
608
  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",
594
609
  "aria-label": "Time of Day",
610
+ disabled: isLocked,
595
611
  value: (_b = block.timeOfDay) != null ? _b : "DAY",
596
612
  style: {
597
613
  appearance: "none"
@@ -647,7 +663,7 @@ function ScreenplayEditorView({
647
663
  if (!el) return;
648
664
  refs.current[block.id] = el;
649
665
  },
650
- contentEditable: true,
666
+ contentEditable: !isLocked,
651
667
  suppressContentEditableWarning: true,
652
668
  "aria-label": `${blockStyles[block.type].label} text`,
653
669
  "aria-multiline": block.type === "ACTION" || block.type === "DIALOGUE",
@@ -1374,7 +1390,7 @@ function useScreenplayEditor(options) {
1374
1390
  [blocks, handleBlockTextChange]
1375
1391
  );
1376
1392
  const handleScriptImport = react.useCallback(
1377
- (title, content, preParsedBlocks) => {
1393
+ (title, content, preParsedBlocks, isInitialLoad) => {
1378
1394
  let parsedBlocks = [];
1379
1395
  if (preParsedBlocks && preParsedBlocks.length > 0) {
1380
1396
  parsedBlocks = preParsedBlocks.map((b) => ({
@@ -1430,8 +1446,10 @@ function useScreenplayEditor(options) {
1430
1446
  }).join("");
1431
1447
  if (sbxData !== lastSavedContent.current) {
1432
1448
  lastSavedContent.current = sbxData;
1433
- const blob = new Blob([sbxData], { type: "text/plain" });
1434
- onSaveRef.current(blob);
1449
+ if (!isInitialLoad) {
1450
+ const blob = new Blob([sbxData], { type: "text/plain" });
1451
+ onSaveRef.current(blob);
1452
+ }
1435
1453
  }
1436
1454
  }
1437
1455
  setTimeout(() => {
@@ -1479,7 +1497,7 @@ function useScreenplayEditor(options) {
1479
1497
  }, 200);
1480
1498
  }, []);
1481
1499
  const loadFromUrl = react.useCallback(
1482
- async (url, fetchOptions = {}) => {
1500
+ async (url, fetchOptions = {}, isInitialLoad) => {
1483
1501
  var _a;
1484
1502
  try {
1485
1503
  const response = await fetch(url, fetchOptions);
@@ -1551,7 +1569,7 @@ function useScreenplayEditor(options) {
1551
1569
  }
1552
1570
  }
1553
1571
  const filename = ((_a = url.split("/").pop()) == null ? void 0 : _a.replace(/\.sbx$/i, "")) || "Imported from URL";
1554
- handleScriptImport(filename, scriptContent, preParsedBlocks);
1572
+ handleScriptImport(filename, scriptContent, preParsedBlocks, isInitialLoad);
1555
1573
  } catch (error) {
1556
1574
  console.error(
1557
1575
  "[useScreenplayEditor] Error loading script from URL:",
@@ -1565,7 +1583,7 @@ function useScreenplayEditor(options) {
1565
1583
  react.useEffect(() => {
1566
1584
  if ((options == null ? void 0 : options.initialUrl) && options.initialUrl !== loadedUrlRef.current) {
1567
1585
  loadedUrlRef.current = options.initialUrl;
1568
- loadFromUrl(options.initialUrl, options.fetchOptions);
1586
+ loadFromUrl(options.initialUrl, options.fetchOptions, true);
1569
1587
  }
1570
1588
  }, [options == null ? void 0 : options.initialUrl, options == null ? void 0 : options.fetchOptions, loadFromUrl]);
1571
1589
  return {
@@ -1728,13 +1746,14 @@ var handleSyncWithCloud = (blocks, sceneNumbers, onSaveAsSbx, project_name) => {
1728
1746
 
1729
1747
  // app/types/script-breakdown.types.tsx
1730
1748
  var CATEGORIES = [
1731
- { id: "CAST", label: "Cast", color: "#7c3aed", hex: "#8b5cf6" },
1732
- { id: "PROP", label: "Prop", color: "#ea580c", hex: "#f97316" },
1733
- { id: "COSTUME", label: "Costume", color: "#db2777", hex: "#ec4899" },
1734
- { id: "VEHICLE", label: "Vehicle", color: "#2563eb", hex: "#3b82f6" },
1735
- { id: "SET_PROP", label: "Set Prop", color: "#16a34a", hex: "#22c55e" },
1736
- { id: "EXTRA", label: "Extra", color: "#0d9488", hex: "#14b8a6" },
1737
- { id: "LOCATION", label: "Location", color: "#ca8a04", hex: "#eab308" }
1749
+ { id: "CAST", label: "Cast", color: "#7C3AED", hex: "#A78BFA" },
1750
+ { id: "PROP", label: "Prop", color: "#FF6A00", hex: "#FFB86B" },
1751
+ { id: "COSTUME", label: "Costume", color: "#FF2E93", hex: "#FF8AC2" },
1752
+ { id: "VEHICLE", label: "Vehicle", color: "#00A6FF", hex: "#6ED6FF" },
1753
+ { id: "SET_PROP", label: "Set Prop", color: "#00C853", hex: "#69F0AE" },
1754
+ { id: "EXTRA", label: "Extra", color: "#00B8D4", hex: "#62EFFF" },
1755
+ { id: "LOCATION", label: "Location", color: "#FFB300", hex: "#FFE082" },
1756
+ { id: "OTHER", label: "Other", color: "#9E9E9E", hex: "#E0E0E0" }
1738
1757
  ];
1739
1758
  function ScriptBreakdownSceneView({
1740
1759
  blocks,
@@ -1753,7 +1772,9 @@ function ScriptBreakdownSceneView({
1753
1772
  addSubLocation,
1754
1773
  removeSubLocation,
1755
1774
  sceneBrief,
1756
- setSceneBrief
1775
+ setSceneBrief,
1776
+ onSummarize,
1777
+ isSummarizing
1757
1778
  }) {
1758
1779
  const COURIER_STACK = "'Courier Prime', 'Courier', monospace";
1759
1780
  const [isSubLocOpen, setIsSubLocOpen] = react.useState(false);
@@ -1800,47 +1821,54 @@ function ScriptBreakdownSceneView({
1800
1821
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-zinc-500 animate-pulse", children: "Loading scene details..." })
1801
1822
  ] });
1802
1823
  }
1803
- const hasLocationTag = tags.some((t) => t.categoryId === "LOCATION");
1824
+ const hasLocationTag = tags.some((t) => t.category_id === "LOCATION");
1804
1825
  const renderBlockText = (block) => {
1805
- const blockTags = tags.filter((t) => t.blockId === block.id).sort((a, b) => a.startIndex - b.startIndex);
1826
+ const blockTags = tags.filter((t) => t.block_id === block.id).sort((a, b) => a.start_index - b.start_index);
1806
1827
  if (blockTags.length === 0) return block.text;
1807
1828
  const nodes = [];
1808
1829
  let currentIndex = 0;
1809
1830
  blockTags.forEach((tag) => {
1810
- if (tag.startIndex > currentIndex) {
1831
+ const actualStart = Math.max(tag.start_index, currentIndex);
1832
+ if (actualStart > currentIndex) {
1811
1833
  nodes.push(
1812
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: block.text.slice(currentIndex, tag.startIndex) }, `text-${currentIndex}`)
1834
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: block.text.slice(currentIndex, actualStart) }, `text-${currentIndex}`)
1813
1835
  );
1814
1836
  }
1815
- const category = CATEGORIES.find((c) => c.id === tag.categoryId);
1816
- nodes.push(
1817
- /* @__PURE__ */ jsxRuntime.jsx(
1818
- "span",
1819
- {
1820
- title: `${category == null ? void 0 : category.label} (Click to edit)`,
1821
- onClick: (e) => {
1822
- e.stopPropagation();
1823
- const selection = window.getSelection();
1824
- if (!selection) return;
1825
- const range = document.createRange();
1826
- const textNode = e.currentTarget.firstChild;
1827
- if (textNode && textNode.nodeType === Node.TEXT_NODE) {
1828
- range.selectNodeContents(textNode);
1829
- } else {
1830
- range.selectNodeContents(e.currentTarget);
1831
- }
1832
- selection.removeAllRanges();
1833
- selection.addRange(range);
1834
- setTimeout(() => handleMouseUp(), 0);
1837
+ const category = CATEGORIES.find((c) => c.id === tag.category_id);
1838
+ if (actualStart < tag.end_index) {
1839
+ nodes.push(
1840
+ /* @__PURE__ */ jsxRuntime.jsx(
1841
+ "span",
1842
+ {
1843
+ title: `${category == null ? void 0 : category.label} (Click to edit)`,
1844
+ onClick: (e) => {
1845
+ e.stopPropagation();
1846
+ const selection = window.getSelection();
1847
+ if (!selection) return;
1848
+ const range = document.createRange();
1849
+ const textNode = e.currentTarget.firstChild;
1850
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
1851
+ range.selectNodeContents(textNode);
1852
+ } else {
1853
+ range.selectNodeContents(e.currentTarget);
1854
+ }
1855
+ selection.removeAllRanges();
1856
+ selection.addRange(range);
1857
+ setTimeout(() => handleMouseUp(), 0);
1858
+ },
1859
+ className: "cursor-pointer font-bold transition-all hover:opacity-80 rounded-[3px]",
1860
+ style: {
1861
+ color: category == null ? void 0 : category.color,
1862
+ padding: "0.125rem 0.25rem",
1863
+ margin: "0 -0.125rem"
1864
+ },
1865
+ children: block.text.slice(actualStart, tag.end_index)
1835
1866
  },
1836
- className: "cursor-pointer font-bold transition-opacity hover:opacity-70",
1837
- style: { color: category == null ? void 0 : category.color },
1838
- children: block.text.slice(tag.startIndex, tag.endIndex)
1839
- },
1840
- tag.id
1841
- )
1842
- );
1843
- currentIndex = tag.endIndex;
1867
+ tag.id || `tag-${actualStart}-${tag.end_index}`
1868
+ )
1869
+ );
1870
+ }
1871
+ currentIndex = Math.max(currentIndex, tag.end_index);
1844
1872
  });
1845
1873
  if (currentIndex < block.text.length) {
1846
1874
  nodes.push(
@@ -1928,16 +1956,16 @@ function ScriptBreakdownSceneView({
1928
1956
  cat.id
1929
1957
  )),
1930
1958
  tags.some(
1931
- (t) => t.blockId === block.id && t.startIndex === selectionMenu.startIndex && t.endIndex === selectionMenu.endIndex
1959
+ (t) => t.block_id === block.id && t.start_index === selectionMenu.startIndex && t.end_index === selectionMenu.endIndex
1932
1960
  ) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 pt-1 border-t border-white/60", children: /* @__PURE__ */ jsxRuntime.jsxs(
1933
1961
  "button",
1934
1962
  {
1935
1963
  onClick: (e) => {
1936
1964
  const tagToRemove = tags.find(
1937
- (t) => t.blockId === block.id && t.startIndex === selectionMenu.startIndex && t.endIndex === selectionMenu.endIndex
1965
+ (t) => t.block_id === block.id && t.start_index === selectionMenu.startIndex && t.end_index === selectionMenu.endIndex
1938
1966
  );
1939
- if (tagToRemove) {
1940
- removeTag(e, tagToRemove.id);
1967
+ if (tagToRemove && tagToRemove.id) {
1968
+ removeTag(e, tagToRemove == null ? void 0 : tagToRemove.id);
1941
1969
  clearSelection();
1942
1970
  }
1943
1971
  },
@@ -1980,115 +2008,136 @@ function ScriptBreakdownSceneView({
1980
2008
  ) })
1981
2009
  ] })
1982
2010
  ] }),
1983
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full lg:w-80 flex-shrink-0 flex flex-col gap-6 sticky top-6", children: /* @__PURE__ */ jsxRuntime.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: [
1984
- /* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "text-xs font-extrabold text-slate-800 uppercase tracking-[0.25em] mb-8 flex items-center gap-3", children: [
1985
- /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(lucideReact.Tags, { className: "w-3.5 h-3.5 text-slate-500" }) }),
1986
- "Tags",
1987
- /* @__PURE__ */ jsxRuntime.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 })
1988
- ] }),
1989
- tags.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-8", children: CATEGORIES.map((cat) => {
1990
- const catTags = Array.from(
1991
- new Map(
1992
- tags.filter((t) => t.categoryId === cat.id).map((tag) => [tag.text.toLowerCase(), tag])
1993
- ).values()
1994
- );
1995
- if (catTags.length === 0) return null;
1996
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
1997
- /* @__PURE__ */ jsxRuntime.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: [
1998
- /* @__PURE__ */ jsxRuntime.jsx(
1999
- "div",
2000
- {
2001
- className: "w-2 h-2 rounded-full shadow-sm",
2002
- style: { backgroundColor: cat.hex }
2003
- }
2004
- ),
2005
- cat.label,
2006
- cat.id === "LOCATION" && /* @__PURE__ */ jsxRuntime.jsxs(
2007
- "div",
2008
- {
2009
- className: "ml-auto relative",
2010
- ref: subLocPopoverRef,
2011
- children: [
2012
- /* @__PURE__ */ jsxRuntime.jsx(
2013
- "button",
2014
- {
2015
- onClick: () => setIsSubLocOpen(!isSubLocOpen),
2016
- className: "flex items-center justify-center w-5 h-5 rounded-full hover:bg-slate-200 transition-colors",
2017
- title: "Add Sub Location",
2018
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "w-3 h-3 text-slate-500" })
2019
- }
2020
- ),
2021
- isSubLocOpen && /* @__PURE__ */ jsxRuntime.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: [
2022
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[9px] font-extrabold tracking-[0.2em] text-slate-400 uppercase mb-2 px-1", children: "Add Sub Location" }),
2023
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 items-center", children: [
2024
- /* @__PURE__ */ jsxRuntime.jsx(
2025
- "input",
2026
- {
2027
- type: "text",
2028
- value: subLocInput,
2029
- onChange: (e) => setSubLocInput(e.target.value),
2030
- onKeyDown: (e) => {
2031
- if (e.key === "Enter") {
2011
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full lg:w-80 flex-shrink-0 flex flex-col gap-6 sticky top-6", children: [
2012
+ /* @__PURE__ */ jsxRuntime.jsxs(
2013
+ "button",
2014
+ {
2015
+ onClick: onSummarize,
2016
+ disabled: isSummarizing,
2017
+ 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]"}`,
2018
+ children: [
2019
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 opacity-0 group-hover:opacity-100 transition duration-700", children: /* @__PURE__ */ jsxRuntime.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]" }) }),
2020
+ /* @__PURE__ */ jsxRuntime.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%)]" }),
2021
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-[1px] rounded-[inherit] bg-gradient-to-b from-white/90 to-transparent pointer-events-none" }),
2022
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.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]" }) }),
2023
+ /* @__PURE__ */ jsxRuntime.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" }),
2024
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center gap-3", children: [
2025
+ isSummarizing ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "w-4 h-4 text-violet-500 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "w-4 h-4 text-violet-500 transition-all duration-500 group-hover:-rotate-12 group-hover:scale-125" }),
2026
+ /* @__PURE__ */ jsxRuntime.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" })
2027
+ ] })
2028
+ ]
2029
+ }
2030
+ ),
2031
+ /* @__PURE__ */ jsxRuntime.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: [
2032
+ /* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "text-xs font-extrabold text-slate-800 uppercase tracking-[0.25em] mb-8 flex items-center gap-3", children: [
2033
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(lucideReact.Tags, { className: "w-3.5 h-3.5 text-slate-500" }) }),
2034
+ "Tags",
2035
+ /* @__PURE__ */ jsxRuntime.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 })
2036
+ ] }),
2037
+ tags.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-8", children: CATEGORIES.map((cat) => {
2038
+ const catTags = Array.from(
2039
+ new Map(
2040
+ tags.filter((t) => t.category_id === cat.id).map((tag) => [tag.name.toLowerCase(), tag])
2041
+ ).values()
2042
+ );
2043
+ if (catTags.length === 0) return null;
2044
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
2045
+ /* @__PURE__ */ jsxRuntime.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: [
2046
+ /* @__PURE__ */ jsxRuntime.jsx(
2047
+ "div",
2048
+ {
2049
+ className: "w-2 h-2 rounded-full shadow-sm",
2050
+ style: { backgroundColor: cat.hex }
2051
+ }
2052
+ ),
2053
+ cat.label
2054
+ ] }),
2055
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
2056
+ catTags.map((tag, index) => /* @__PURE__ */ jsxRuntime.jsx(
2057
+ "span",
2058
+ {
2059
+ 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",
2060
+ style: { color: cat.color },
2061
+ children: tag.name
2062
+ },
2063
+ index
2064
+ )),
2065
+ cat.id === "LOCATION" && /* @__PURE__ */ jsxRuntime.jsxs(
2066
+ "div",
2067
+ {
2068
+ className: "relative flex items-center",
2069
+ ref: subLocPopoverRef,
2070
+ children: [
2071
+ /* @__PURE__ */ jsxRuntime.jsx(
2072
+ "button",
2073
+ {
2074
+ onClick: () => setIsSubLocOpen(!isSubLocOpen),
2075
+ className: "flex items-center justify-center w-5 h-5 rounded-full hover:bg-slate-200 transition-colors",
2076
+ title: "Add Sub Location",
2077
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "w-3 h-3 text-slate-500" })
2078
+ }
2079
+ ),
2080
+ isSubLocOpen && /* @__PURE__ */ jsxRuntime.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: [
2081
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[9px] font-extrabold tracking-[0.2em] text-slate-400 uppercase mb-2 px-1", children: "Add Sub Location" }),
2082
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 items-center", children: [
2083
+ /* @__PURE__ */ jsxRuntime.jsx(
2084
+ "input",
2085
+ {
2086
+ type: "text",
2087
+ value: subLocInput,
2088
+ onChange: (e) => setSubLocInput(e.target.value),
2089
+ onKeyDown: (e) => {
2090
+ if (e.key === "Enter") {
2091
+ addSubLocation(subLocInput);
2092
+ setSubLocInput("");
2093
+ setIsSubLocOpen(false);
2094
+ }
2095
+ },
2096
+ 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",
2097
+ placeholder: "Sub location...",
2098
+ autoFocus: true
2099
+ }
2100
+ ),
2101
+ /* @__PURE__ */ jsxRuntime.jsx(
2102
+ "button",
2103
+ {
2104
+ onClick: () => {
2032
2105
  addSubLocation(subLocInput);
2033
2106
  setSubLocInput("");
2034
2107
  setIsSubLocOpen(false);
2035
- }
2036
- },
2037
- 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",
2038
- placeholder: "Sub location...",
2039
- autoFocus: true
2040
- }
2041
- ),
2042
- /* @__PURE__ */ jsxRuntime.jsx(
2043
- "button",
2044
- {
2045
- onClick: () => {
2046
- addSubLocation(subLocInput);
2047
- setSubLocInput("");
2048
- setIsSubLocOpen(false);
2049
- },
2050
- 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",
2051
- children: "Add"
2052
- }
2053
- )
2108
+ },
2109
+ 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",
2110
+ children: "Add"
2111
+ }
2112
+ )
2113
+ ] })
2054
2114
  ] })
2055
- ] })
2056
- ]
2057
- }
2058
- )
2059
- ] }),
2060
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
2061
- catTags.map((tag) => /* @__PURE__ */ jsxRuntime.jsx(
2062
- "span",
2063
- {
2064
- 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",
2065
- style: { color: cat.color },
2066
- children: tag.text
2067
- },
2068
- tag.id
2069
- )),
2070
- cat.id === "LOCATION" && subLocations.map((subLoc) => /* @__PURE__ */ jsxRuntime.jsxs(
2071
- "span",
2072
- {
2073
- 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",
2074
- children: [
2075
- subLoc,
2076
- /* @__PURE__ */ jsxRuntime.jsx(
2077
- "button",
2078
- {
2079
- onClick: () => removeSubLocation(subLoc),
2080
- 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",
2081
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-2.5 h-2.5" })
2082
- }
2083
- )
2084
- ]
2085
- },
2086
- `sub-${subLoc}`
2087
- ))
2088
- ] })
2089
- ] }, cat.id);
2090
- }) }) : /* @__PURE__ */ jsxRuntime.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." })
2091
- ] }) })
2115
+ ]
2116
+ }
2117
+ ),
2118
+ cat.id === "LOCATION" && subLocations.map((subLoc) => /* @__PURE__ */ jsxRuntime.jsxs(
2119
+ "span",
2120
+ {
2121
+ 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",
2122
+ children: [
2123
+ subLoc,
2124
+ /* @__PURE__ */ jsxRuntime.jsx(
2125
+ "button",
2126
+ {
2127
+ onClick: () => removeSubLocation(subLoc),
2128
+ 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",
2129
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-2.5 h-2.5" })
2130
+ }
2131
+ )
2132
+ ]
2133
+ },
2134
+ `sub-${subLoc}`
2135
+ ))
2136
+ ] })
2137
+ ] }, cat.id);
2138
+ }) }) : /* @__PURE__ */ jsxRuntime.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." })
2139
+ ] })
2140
+ ] })
2092
2141
  ] }) });
2093
2142
  }
2094
2143
  function useScriptBreakdown({
@@ -2170,6 +2219,14 @@ var use_script_breakdown_default = useScriptBreakdown;
2170
2219
  // app/hook/use-script-breakdown-scene.ts
2171
2220
  function useScriptBreakdownScene(sceneNumber) {
2172
2221
  const { scenes, isLoading, error } = use_script_breakdown_default({ scenes: [] });
2222
+ const [tags, setTags] = react.useState([]);
2223
+ const [selectionMenu, setSelectionMenu] = react.useState(null);
2224
+ const autoTaggedSceneRef = react.useRef(null);
2225
+ const [menuPlacement, setMenuPlacement] = react.useState("top");
2226
+ const [subLocations, setSubLocations] = react.useState([]);
2227
+ const [sceneBrief, setSceneBrief] = react.useState("");
2228
+ const [isSummarizing, setIsSummarizing] = react.useState(false);
2229
+ const menuRef = react.useRef(null);
2173
2230
  const scene = react.useMemo(() => {
2174
2231
  return scenes.find((s) => s.scene_number === sceneNumber);
2175
2232
  }, [scenes, sceneNumber]);
@@ -2198,7 +2255,9 @@ function useScriptBreakdownScene(sceneNumber) {
2198
2255
  break;
2199
2256
  }
2200
2257
  }
2201
- parsedBlocks.push({ id: uuid(), type, text: divText });
2258
+ const idAttr = div.getAttribute("id");
2259
+ const blockId = idAttr && idAttr.startsWith("par") ? idAttr.substring(3) : idAttr || uuid();
2260
+ parsedBlocks.push({ id: blockId, type, text: divText });
2202
2261
  });
2203
2262
  return parsedBlocks;
2204
2263
  }, [scene]);
@@ -2210,12 +2269,57 @@ function useScriptBreakdownScene(sceneNumber) {
2210
2269
  }).filter(Boolean);
2211
2270
  return [...new Set(chars)];
2212
2271
  }, [blocks]);
2213
- const [tags, setTags] = react.useState([]);
2214
- const [selectionMenu, setSelectionMenu] = react.useState(null);
2215
- const autoTaggedSceneRef = react.useRef(null);
2216
- const [menuPlacement, setMenuPlacement] = react.useState("top");
2217
- const menuRef = react.useRef(null);
2218
- const [subLocations, setSubLocations] = react.useState([]);
2272
+ const handleAISummarize = async () => {
2273
+ setIsSummarizing(true);
2274
+ const res = await fetch(
2275
+ "https://nonfibrous-extrafloral-verlene.ngrok-free.dev/extract",
2276
+ {
2277
+ method: "POST",
2278
+ headers: {
2279
+ "Content-Type": "application/json",
2280
+ "ngrok-skip-browser-warning": "true"
2281
+ },
2282
+ body: JSON.stringify({
2283
+ script: (scene == null ? void 0 : scene.content) || ""
2284
+ })
2285
+ }
2286
+ );
2287
+ if (res.ok) {
2288
+ const data = await res.json();
2289
+ setIsSummarizing(false);
2290
+ const parsedData = JSON.parse(data.data) || [];
2291
+ console.log("AI Tags:", parsedData);
2292
+ const newTags = [];
2293
+ parsedData.forEach((aiTag) => {
2294
+ if (!aiTag.block_id || !aiTag.category_id || typeof aiTag.start_index !== "number" || typeof aiTag.end_index !== "number") {
2295
+ return;
2296
+ }
2297
+ newTags.push({
2298
+ block_id: String(aiTag.block_id).startsWith("par") ? String(aiTag.block_id).substring(3) : String(aiTag.block_id),
2299
+ category_id: aiTag.category_id,
2300
+ name: aiTag.name,
2301
+ start_index: aiTag.start_index,
2302
+ end_index: aiTag.end_index
2303
+ });
2304
+ });
2305
+ setTags((prev) => {
2306
+ const merged = [...prev];
2307
+ newTags.forEach((newTag) => {
2308
+ const isOverlapping = merged.some(
2309
+ (t) => t.block_id === newTag.block_id && newTag.end_index > t.start_index && newTag.start_index < t.end_index
2310
+ );
2311
+ if (!isOverlapping) {
2312
+ merged.push(newTag);
2313
+ }
2314
+ });
2315
+ return merged;
2316
+ });
2317
+ return data;
2318
+ } else {
2319
+ setIsSummarizing(false);
2320
+ console.error("Failed to summarize scene:", res);
2321
+ }
2322
+ };
2219
2323
  const addSubLocation = react.useCallback(
2220
2324
  (subLocation) => {
2221
2325
  const trimmed = subLocation.trim();
@@ -2228,7 +2332,6 @@ function useScriptBreakdownScene(sceneNumber) {
2228
2332
  const removeSubLocation = react.useCallback((subLocation) => {
2229
2333
  setSubLocations((prev) => prev.filter((loc) => loc !== subLocation));
2230
2334
  }, []);
2231
- const [sceneBrief, setSceneBrief] = react.useState("");
2232
2335
  react.useEffect(() => {
2233
2336
  setTags([]);
2234
2337
  setSubLocations([]);
@@ -2247,19 +2350,19 @@ function useScriptBreakdownScene(sceneNumber) {
2247
2350
  let match;
2248
2351
  while ((match = regex.exec(block.text)) !== null) {
2249
2352
  const isOverlapping = autoTags.some(
2250
- (t) => t.blockId === block.id && match.index + char.length > t.startIndex && match.index < t.endIndex
2353
+ (t) => t.block_id === block.id && match.index + char.length > t.start_index && match.index < t.end_index
2251
2354
  );
2252
2355
  if (!isOverlapping) {
2253
2356
  autoTags.push({
2254
2357
  id: uuid(),
2255
- blockId: block.id,
2256
- categoryId: "CAST",
2257
- text: block.text.substring(
2358
+ block_id: block.id,
2359
+ category_id: "CAST",
2360
+ name: block.text.substring(
2258
2361
  match.index,
2259
2362
  match.index + char.length
2260
2363
  ),
2261
- startIndex: match.index,
2262
- endIndex: match.index + char.length
2364
+ start_index: match.index,
2365
+ end_index: match.index + char.length
2263
2366
  });
2264
2367
  }
2265
2368
  }
@@ -2359,15 +2462,15 @@ function useScriptBreakdownScene(sceneNumber) {
2359
2462
  if (!selectionMenu) return;
2360
2463
  const newTag = {
2361
2464
  id: uuid(),
2362
- blockId: selectionMenu.blockId,
2363
- categoryId,
2364
- text: selectionMenu.text,
2365
- startIndex: selectionMenu.startIndex,
2366
- endIndex: selectionMenu.endIndex
2465
+ block_id: selectionMenu.blockId,
2466
+ category_id: categoryId,
2467
+ name: selectionMenu.text,
2468
+ start_index: selectionMenu.startIndex,
2469
+ end_index: selectionMenu.endIndex
2367
2470
  };
2368
2471
  setTags((prev) => {
2369
2472
  const filtered = prev.filter(
2370
- (t) => t.blockId !== newTag.blockId || !(newTag.endIndex > t.startIndex && newTag.startIndex < t.endIndex)
2473
+ (t) => t.block_id !== newTag.block_id || !(newTag.end_index > t.start_index && newTag.start_index < t.end_index)
2371
2474
  );
2372
2475
  return [...filtered, newTag];
2373
2476
  });
@@ -2397,7 +2500,9 @@ function useScriptBreakdownScene(sceneNumber) {
2397
2500
  addSubLocation,
2398
2501
  removeSubLocation,
2399
2502
  sceneBrief,
2400
- setSceneBrief
2503
+ setSceneBrief,
2504
+ handleAISummarize,
2505
+ isSummarizing
2401
2506
  };
2402
2507
  }
2403
2508