@vishu1301/script-writing 1.1.2 → 1.1.4

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);
@@ -256,7 +256,7 @@ function PdfImporter({ onScriptImported, children }) {
256
256
  title = await processPage(1);
257
257
  }
258
258
  let scriptContent = "";
259
- for (let i = 2; i <= pdf.numPages; i++) {
259
+ for (let i = 1; i <= pdf.numPages; i++) {
260
260
  scriptContent += await processPage(i) + "\n\n";
261
261
  }
262
262
  onScriptImported(title.trim(), scriptContent);
@@ -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,
@@ -472,7 +485,7 @@ function ScreenplayEditorView({
472
485
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Cog, { className: "w-[18px] h-[18px]" })
473
486
  }
474
487
  ),
475
- isRulesOpen && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-full mt-2 right-0 bg-white/95 backdrop-blur-3xl rounded-[1.5rem] shadow-[0_20px_60px_-15px_rgba(0,0,0,0.1)] ring-1 ring-zinc-900/5 p-5 text-sm text-zinc-700 select-none font-sans overflow-hidden transition-all duration-300 w-72 origin-top-right animate-in fade-in zoom-in-95 z-50", children: [
488
+ isRulesOpen && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-full mt-2 right-0 bg-white/95 backdrop-blur-3xl rounded-[1.5rem] shadow-[0_20px_60px_-15px_rgba(0,0,0,0.1)] p-5 text-sm text-zinc-700 select-none font-sans overflow-hidden transition-all duration-300 w-72 origin-top-right animate-in fade-in zoom-in-95 z-50", children: [
476
489
  /* @__PURE__ */ jsxRuntime.jsxs("h4", { className: "font-bold text-zinc-900 mb-4 text-sm flex items-center gap-2", children: [
477
490
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Cog, { className: "w-4 h-4 text-zinc-400" }),
478
491
  "Settings & Shortcuts"
@@ -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",
@@ -1728,13 +1744,14 @@ var handleSyncWithCloud = (blocks, sceneNumbers, onSaveAsSbx, project_name) => {
1728
1744
 
1729
1745
  // app/types/script-breakdown.types.tsx
1730
1746
  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" }
1747
+ { id: "CAST", label: "Cast", color: "#7C3AED", hex: "#A78BFA" },
1748
+ { id: "PROP", label: "Prop", color: "#FF6A00", hex: "#FFB86B" },
1749
+ { id: "COSTUME", label: "Costume", color: "#FF2E93", hex: "#FF8AC2" },
1750
+ { id: "VEHICLE", label: "Vehicle", color: "#00A6FF", hex: "#6ED6FF" },
1751
+ { id: "SET_PROP", label: "Set Prop", color: "#00C853", hex: "#69F0AE" },
1752
+ { id: "EXTRA", label: "Extra", color: "#00B8D4", hex: "#62EFFF" },
1753
+ { id: "LOCATION", label: "Location", color: "#FFB300", hex: "#FFE082" },
1754
+ { id: "OTHER", label: "Other", color: "#9E9E9E", hex: "#E0E0E0" }
1738
1755
  ];
1739
1756
  function ScriptBreakdownSceneView({
1740
1757
  blocks,
@@ -1753,7 +1770,9 @@ function ScriptBreakdownSceneView({
1753
1770
  addSubLocation,
1754
1771
  removeSubLocation,
1755
1772
  sceneBrief,
1756
- setSceneBrief
1773
+ setSceneBrief,
1774
+ onSummarize,
1775
+ isSummarizing
1757
1776
  }) {
1758
1777
  const COURIER_STACK = "'Courier Prime', 'Courier', monospace";
1759
1778
  const [isSubLocOpen, setIsSubLocOpen] = react.useState(false);
@@ -1800,47 +1819,54 @@ function ScriptBreakdownSceneView({
1800
1819
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-zinc-500 animate-pulse", children: "Loading scene details..." })
1801
1820
  ] });
1802
1821
  }
1803
- const hasLocationTag = tags.some((t) => t.categoryId === "LOCATION");
1822
+ const hasLocationTag = tags.some((t) => t.category_id === "LOCATION");
1804
1823
  const renderBlockText = (block) => {
1805
- const blockTags = tags.filter((t) => t.blockId === block.id).sort((a, b) => a.startIndex - b.startIndex);
1824
+ const blockTags = tags.filter((t) => t.block_id === block.id).sort((a, b) => a.start_index - b.start_index);
1806
1825
  if (blockTags.length === 0) return block.text;
1807
1826
  const nodes = [];
1808
1827
  let currentIndex = 0;
1809
1828
  blockTags.forEach((tag) => {
1810
- if (tag.startIndex > currentIndex) {
1829
+ const actualStart = Math.max(tag.start_index, currentIndex);
1830
+ if (actualStart > currentIndex) {
1811
1831
  nodes.push(
1812
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: block.text.slice(currentIndex, tag.startIndex) }, `text-${currentIndex}`)
1832
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: block.text.slice(currentIndex, actualStart) }, `text-${currentIndex}`)
1813
1833
  );
1814
1834
  }
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);
1835
+ const category = CATEGORIES.find((c) => c.id === tag.category_id);
1836
+ if (actualStart < tag.end_index) {
1837
+ nodes.push(
1838
+ /* @__PURE__ */ jsxRuntime.jsx(
1839
+ "span",
1840
+ {
1841
+ title: `${category == null ? void 0 : category.label} (Click to edit)`,
1842
+ onClick: (e) => {
1843
+ e.stopPropagation();
1844
+ const selection = window.getSelection();
1845
+ if (!selection) return;
1846
+ const range = document.createRange();
1847
+ const textNode = e.currentTarget.firstChild;
1848
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
1849
+ range.selectNodeContents(textNode);
1850
+ } else {
1851
+ range.selectNodeContents(e.currentTarget);
1852
+ }
1853
+ selection.removeAllRanges();
1854
+ selection.addRange(range);
1855
+ setTimeout(() => handleMouseUp(), 0);
1856
+ },
1857
+ className: "cursor-pointer font-bold transition-all hover:opacity-80 rounded-[3px]",
1858
+ style: {
1859
+ color: category == null ? void 0 : category.color,
1860
+ padding: "0.125rem 0.25rem",
1861
+ margin: "0 -0.125rem"
1862
+ },
1863
+ children: block.text.slice(actualStart, tag.end_index)
1835
1864
  },
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;
1865
+ tag.id || `tag-${actualStart}-${tag.end_index}`
1866
+ )
1867
+ );
1868
+ }
1869
+ currentIndex = Math.max(currentIndex, tag.end_index);
1844
1870
  });
1845
1871
  if (currentIndex < block.text.length) {
1846
1872
  nodes.push(
@@ -1928,16 +1954,16 @@ function ScriptBreakdownSceneView({
1928
1954
  cat.id
1929
1955
  )),
1930
1956
  tags.some(
1931
- (t) => t.blockId === block.id && t.startIndex === selectionMenu.startIndex && t.endIndex === selectionMenu.endIndex
1957
+ (t) => t.block_id === block.id && t.start_index === selectionMenu.startIndex && t.end_index === selectionMenu.endIndex
1932
1958
  ) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 pt-1 border-t border-white/60", children: /* @__PURE__ */ jsxRuntime.jsxs(
1933
1959
  "button",
1934
1960
  {
1935
1961
  onClick: (e) => {
1936
1962
  const tagToRemove = tags.find(
1937
- (t) => t.blockId === block.id && t.startIndex === selectionMenu.startIndex && t.endIndex === selectionMenu.endIndex
1963
+ (t) => t.block_id === block.id && t.start_index === selectionMenu.startIndex && t.end_index === selectionMenu.endIndex
1938
1964
  );
1939
- if (tagToRemove) {
1940
- removeTag(e, tagToRemove.id);
1965
+ if (tagToRemove && tagToRemove.id) {
1966
+ removeTag(e, tagToRemove == null ? void 0 : tagToRemove.id);
1941
1967
  clearSelection();
1942
1968
  }
1943
1969
  },
@@ -1980,115 +2006,136 @@ function ScriptBreakdownSceneView({
1980
2006
  ) })
1981
2007
  ] })
1982
2008
  ] }),
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") {
2009
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full lg:w-80 flex-shrink-0 flex flex-col gap-6 sticky top-6", children: [
2010
+ /* @__PURE__ */ jsxRuntime.jsxs(
2011
+ "button",
2012
+ {
2013
+ onClick: onSummarize,
2014
+ disabled: isSummarizing,
2015
+ 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]"}`,
2016
+ children: [
2017
+ /* @__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]" }) }),
2018
+ /* @__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%)]" }),
2019
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-[1px] rounded-[inherit] bg-gradient-to-b from-white/90 to-transparent pointer-events-none" }),
2020
+ /* @__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]" }) }),
2021
+ /* @__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" }),
2022
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center gap-3", children: [
2023
+ 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" }),
2024
+ /* @__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" })
2025
+ ] })
2026
+ ]
2027
+ }
2028
+ ),
2029
+ /* @__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: [
2030
+ /* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "text-xs font-extrabold text-slate-800 uppercase tracking-[0.25em] mb-8 flex items-center gap-3", children: [
2031
+ /* @__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" }) }),
2032
+ "Tags",
2033
+ /* @__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 })
2034
+ ] }),
2035
+ tags.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-8", children: CATEGORIES.map((cat) => {
2036
+ const catTags = Array.from(
2037
+ new Map(
2038
+ tags.filter((t) => t.category_id === cat.id).map((tag) => [tag.name.toLowerCase(), tag])
2039
+ ).values()
2040
+ );
2041
+ if (catTags.length === 0) return null;
2042
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
2043
+ /* @__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: [
2044
+ /* @__PURE__ */ jsxRuntime.jsx(
2045
+ "div",
2046
+ {
2047
+ className: "w-2 h-2 rounded-full shadow-sm",
2048
+ style: { backgroundColor: cat.hex }
2049
+ }
2050
+ ),
2051
+ cat.label
2052
+ ] }),
2053
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
2054
+ catTags.map((tag, index) => /* @__PURE__ */ jsxRuntime.jsx(
2055
+ "span",
2056
+ {
2057
+ 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",
2058
+ style: { color: cat.color },
2059
+ children: tag.name
2060
+ },
2061
+ index
2062
+ )),
2063
+ cat.id === "LOCATION" && /* @__PURE__ */ jsxRuntime.jsxs(
2064
+ "div",
2065
+ {
2066
+ className: "relative flex items-center",
2067
+ ref: subLocPopoverRef,
2068
+ children: [
2069
+ /* @__PURE__ */ jsxRuntime.jsx(
2070
+ "button",
2071
+ {
2072
+ onClick: () => setIsSubLocOpen(!isSubLocOpen),
2073
+ className: "flex items-center justify-center w-5 h-5 rounded-full hover:bg-slate-200 transition-colors",
2074
+ title: "Add Sub Location",
2075
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "w-3 h-3 text-slate-500" })
2076
+ }
2077
+ ),
2078
+ 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: [
2079
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[9px] font-extrabold tracking-[0.2em] text-slate-400 uppercase mb-2 px-1", children: "Add Sub Location" }),
2080
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 items-center", children: [
2081
+ /* @__PURE__ */ jsxRuntime.jsx(
2082
+ "input",
2083
+ {
2084
+ type: "text",
2085
+ value: subLocInput,
2086
+ onChange: (e) => setSubLocInput(e.target.value),
2087
+ onKeyDown: (e) => {
2088
+ if (e.key === "Enter") {
2089
+ addSubLocation(subLocInput);
2090
+ setSubLocInput("");
2091
+ setIsSubLocOpen(false);
2092
+ }
2093
+ },
2094
+ 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",
2095
+ placeholder: "Sub location...",
2096
+ autoFocus: true
2097
+ }
2098
+ ),
2099
+ /* @__PURE__ */ jsxRuntime.jsx(
2100
+ "button",
2101
+ {
2102
+ onClick: () => {
2032
2103
  addSubLocation(subLocInput);
2033
2104
  setSubLocInput("");
2034
2105
  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
- )
2106
+ },
2107
+ 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",
2108
+ children: "Add"
2109
+ }
2110
+ )
2111
+ ] })
2054
2112
  ] })
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
- ] }) })
2113
+ ]
2114
+ }
2115
+ ),
2116
+ cat.id === "LOCATION" && subLocations.map((subLoc) => /* @__PURE__ */ jsxRuntime.jsxs(
2117
+ "span",
2118
+ {
2119
+ 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",
2120
+ children: [
2121
+ subLoc,
2122
+ /* @__PURE__ */ jsxRuntime.jsx(
2123
+ "button",
2124
+ {
2125
+ onClick: () => removeSubLocation(subLoc),
2126
+ 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",
2127
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-2.5 h-2.5" })
2128
+ }
2129
+ )
2130
+ ]
2131
+ },
2132
+ `sub-${subLoc}`
2133
+ ))
2134
+ ] })
2135
+ ] }, cat.id);
2136
+ }) }) : /* @__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." })
2137
+ ] })
2138
+ ] })
2092
2139
  ] }) });
2093
2140
  }
2094
2141
  function useScriptBreakdown({
@@ -2170,6 +2217,14 @@ var use_script_breakdown_default = useScriptBreakdown;
2170
2217
  // app/hook/use-script-breakdown-scene.ts
2171
2218
  function useScriptBreakdownScene(sceneNumber) {
2172
2219
  const { scenes, isLoading, error } = use_script_breakdown_default({ scenes: [] });
2220
+ const [tags, setTags] = react.useState([]);
2221
+ const [selectionMenu, setSelectionMenu] = react.useState(null);
2222
+ const autoTaggedSceneRef = react.useRef(null);
2223
+ const [menuPlacement, setMenuPlacement] = react.useState("top");
2224
+ const [subLocations, setSubLocations] = react.useState([]);
2225
+ const [sceneBrief, setSceneBrief] = react.useState("");
2226
+ const [isSummarizing, setIsSummarizing] = react.useState(false);
2227
+ const menuRef = react.useRef(null);
2173
2228
  const scene = react.useMemo(() => {
2174
2229
  return scenes.find((s) => s.scene_number === sceneNumber);
2175
2230
  }, [scenes, sceneNumber]);
@@ -2198,7 +2253,9 @@ function useScriptBreakdownScene(sceneNumber) {
2198
2253
  break;
2199
2254
  }
2200
2255
  }
2201
- parsedBlocks.push({ id: uuid(), type, text: divText });
2256
+ const idAttr = div.getAttribute("id");
2257
+ const blockId = idAttr && idAttr.startsWith("par") ? idAttr.substring(3) : idAttr || uuid();
2258
+ parsedBlocks.push({ id: blockId, type, text: divText });
2202
2259
  });
2203
2260
  return parsedBlocks;
2204
2261
  }, [scene]);
@@ -2210,12 +2267,57 @@ function useScriptBreakdownScene(sceneNumber) {
2210
2267
  }).filter(Boolean);
2211
2268
  return [...new Set(chars)];
2212
2269
  }, [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([]);
2270
+ const handleAISummarize = async () => {
2271
+ setIsSummarizing(true);
2272
+ const res = await fetch(
2273
+ "https://nonfibrous-extrafloral-verlene.ngrok-free.dev/extract",
2274
+ {
2275
+ method: "POST",
2276
+ headers: {
2277
+ "Content-Type": "application/json",
2278
+ "ngrok-skip-browser-warning": "true"
2279
+ },
2280
+ body: JSON.stringify({
2281
+ script: (scene == null ? void 0 : scene.content) || ""
2282
+ })
2283
+ }
2284
+ );
2285
+ if (res.ok) {
2286
+ const data = await res.json();
2287
+ setIsSummarizing(false);
2288
+ const parsedData = JSON.parse(data.data) || [];
2289
+ console.log("AI Tags:", parsedData);
2290
+ const newTags = [];
2291
+ parsedData.forEach((aiTag) => {
2292
+ if (!aiTag.block_id || !aiTag.category_id || typeof aiTag.start_index !== "number" || typeof aiTag.end_index !== "number") {
2293
+ return;
2294
+ }
2295
+ newTags.push({
2296
+ block_id: String(aiTag.block_id).startsWith("par") ? String(aiTag.block_id).substring(3) : String(aiTag.block_id),
2297
+ category_id: aiTag.category_id,
2298
+ name: aiTag.name,
2299
+ start_index: aiTag.start_index,
2300
+ end_index: aiTag.end_index
2301
+ });
2302
+ });
2303
+ setTags((prev) => {
2304
+ const merged = [...prev];
2305
+ newTags.forEach((newTag) => {
2306
+ const isOverlapping = merged.some(
2307
+ (t) => t.block_id === newTag.block_id && newTag.end_index > t.start_index && newTag.start_index < t.end_index
2308
+ );
2309
+ if (!isOverlapping) {
2310
+ merged.push(newTag);
2311
+ }
2312
+ });
2313
+ return merged;
2314
+ });
2315
+ return data;
2316
+ } else {
2317
+ setIsSummarizing(false);
2318
+ console.error("Failed to summarize scene:", res);
2319
+ }
2320
+ };
2219
2321
  const addSubLocation = react.useCallback(
2220
2322
  (subLocation) => {
2221
2323
  const trimmed = subLocation.trim();
@@ -2228,7 +2330,6 @@ function useScriptBreakdownScene(sceneNumber) {
2228
2330
  const removeSubLocation = react.useCallback((subLocation) => {
2229
2331
  setSubLocations((prev) => prev.filter((loc) => loc !== subLocation));
2230
2332
  }, []);
2231
- const [sceneBrief, setSceneBrief] = react.useState("");
2232
2333
  react.useEffect(() => {
2233
2334
  setTags([]);
2234
2335
  setSubLocations([]);
@@ -2247,19 +2348,19 @@ function useScriptBreakdownScene(sceneNumber) {
2247
2348
  let match;
2248
2349
  while ((match = regex.exec(block.text)) !== null) {
2249
2350
  const isOverlapping = autoTags.some(
2250
- (t) => t.blockId === block.id && match.index + char.length > t.startIndex && match.index < t.endIndex
2351
+ (t) => t.block_id === block.id && match.index + char.length > t.start_index && match.index < t.end_index
2251
2352
  );
2252
2353
  if (!isOverlapping) {
2253
2354
  autoTags.push({
2254
2355
  id: uuid(),
2255
- blockId: block.id,
2256
- categoryId: "CAST",
2257
- text: block.text.substring(
2356
+ block_id: block.id,
2357
+ category_id: "CAST",
2358
+ name: block.text.substring(
2258
2359
  match.index,
2259
2360
  match.index + char.length
2260
2361
  ),
2261
- startIndex: match.index,
2262
- endIndex: match.index + char.length
2362
+ start_index: match.index,
2363
+ end_index: match.index + char.length
2263
2364
  });
2264
2365
  }
2265
2366
  }
@@ -2359,15 +2460,15 @@ function useScriptBreakdownScene(sceneNumber) {
2359
2460
  if (!selectionMenu) return;
2360
2461
  const newTag = {
2361
2462
  id: uuid(),
2362
- blockId: selectionMenu.blockId,
2363
- categoryId,
2364
- text: selectionMenu.text,
2365
- startIndex: selectionMenu.startIndex,
2366
- endIndex: selectionMenu.endIndex
2463
+ block_id: selectionMenu.blockId,
2464
+ category_id: categoryId,
2465
+ name: selectionMenu.text,
2466
+ start_index: selectionMenu.startIndex,
2467
+ end_index: selectionMenu.endIndex
2367
2468
  };
2368
2469
  setTags((prev) => {
2369
2470
  const filtered = prev.filter(
2370
- (t) => t.blockId !== newTag.blockId || !(newTag.endIndex > t.startIndex && newTag.startIndex < t.endIndex)
2471
+ (t) => t.block_id !== newTag.block_id || !(newTag.end_index > t.start_index && newTag.start_index < t.end_index)
2371
2472
  );
2372
2473
  return [...filtered, newTag];
2373
2474
  });
@@ -2397,7 +2498,9 @@ function useScriptBreakdownScene(sceneNumber) {
2397
2498
  addSubLocation,
2398
2499
  removeSubLocation,
2399
2500
  sceneBrief,
2400
- setSceneBrief
2501
+ setSceneBrief,
2502
+ handleAISummarize,
2503
+ isSummarizing
2401
2504
  };
2402
2505
  }
2403
2506