@vishu1301/script-writing 1.1.3 → 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.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",
@@ -1703,13 +1719,14 @@ var handleSyncWithCloud = (blocks, sceneNumbers, onSaveAsSbx, project_name) => {
1703
1719
 
1704
1720
  // app/types/script-breakdown.types.tsx
1705
1721
  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" }
1722
+ { id: "CAST", label: "Cast", color: "#7C3AED", hex: "#A78BFA" },
1723
+ { id: "PROP", label: "Prop", color: "#FF6A00", hex: "#FFB86B" },
1724
+ { id: "COSTUME", label: "Costume", color: "#FF2E93", hex: "#FF8AC2" },
1725
+ { id: "VEHICLE", label: "Vehicle", color: "#00A6FF", hex: "#6ED6FF" },
1726
+ { id: "SET_PROP", label: "Set Prop", color: "#00C853", hex: "#69F0AE" },
1727
+ { id: "EXTRA", label: "Extra", color: "#00B8D4", hex: "#62EFFF" },
1728
+ { id: "LOCATION", label: "Location", color: "#FFB300", hex: "#FFE082" },
1729
+ { id: "OTHER", label: "Other", color: "#9E9E9E", hex: "#E0E0E0" }
1713
1730
  ];
1714
1731
  function ScriptBreakdownSceneView({
1715
1732
  blocks,
@@ -1728,7 +1745,9 @@ function ScriptBreakdownSceneView({
1728
1745
  addSubLocation,
1729
1746
  removeSubLocation,
1730
1747
  sceneBrief,
1731
- setSceneBrief
1748
+ setSceneBrief,
1749
+ onSummarize,
1750
+ isSummarizing
1732
1751
  }) {
1733
1752
  const COURIER_STACK = "'Courier Prime', 'Courier', monospace";
1734
1753
  const [isSubLocOpen, setIsSubLocOpen] = useState(false);
@@ -1775,47 +1794,54 @@ function ScriptBreakdownSceneView({
1775
1794
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-zinc-500 animate-pulse", children: "Loading scene details..." })
1776
1795
  ] });
1777
1796
  }
1778
- const hasLocationTag = tags.some((t) => t.categoryId === "LOCATION");
1797
+ const hasLocationTag = tags.some((t) => t.category_id === "LOCATION");
1779
1798
  const renderBlockText = (block) => {
1780
- const blockTags = tags.filter((t) => t.blockId === block.id).sort((a, b) => a.startIndex - b.startIndex);
1799
+ const blockTags = tags.filter((t) => t.block_id === block.id).sort((a, b) => a.start_index - b.start_index);
1781
1800
  if (blockTags.length === 0) return block.text;
1782
1801
  const nodes = [];
1783
1802
  let currentIndex = 0;
1784
1803
  blockTags.forEach((tag) => {
1785
- if (tag.startIndex > currentIndex) {
1804
+ const actualStart = Math.max(tag.start_index, currentIndex);
1805
+ if (actualStart > currentIndex) {
1786
1806
  nodes.push(
1787
- /* @__PURE__ */ jsx("span", { children: block.text.slice(currentIndex, tag.startIndex) }, `text-${currentIndex}`)
1807
+ /* @__PURE__ */ jsx("span", { children: block.text.slice(currentIndex, actualStart) }, `text-${currentIndex}`)
1788
1808
  );
1789
1809
  }
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);
1810
+ const category = CATEGORIES.find((c) => c.id === tag.category_id);
1811
+ if (actualStart < tag.end_index) {
1812
+ nodes.push(
1813
+ /* @__PURE__ */ jsx(
1814
+ "span",
1815
+ {
1816
+ title: `${category == null ? void 0 : category.label} (Click to edit)`,
1817
+ onClick: (e) => {
1818
+ e.stopPropagation();
1819
+ const selection = window.getSelection();
1820
+ if (!selection) return;
1821
+ const range = document.createRange();
1822
+ const textNode = e.currentTarget.firstChild;
1823
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
1824
+ range.selectNodeContents(textNode);
1825
+ } else {
1826
+ range.selectNodeContents(e.currentTarget);
1827
+ }
1828
+ selection.removeAllRanges();
1829
+ selection.addRange(range);
1830
+ setTimeout(() => handleMouseUp(), 0);
1831
+ },
1832
+ className: "cursor-pointer font-bold transition-all hover:opacity-80 rounded-[3px]",
1833
+ style: {
1834
+ color: category == null ? void 0 : category.color,
1835
+ padding: "0.125rem 0.25rem",
1836
+ margin: "0 -0.125rem"
1837
+ },
1838
+ children: block.text.slice(actualStart, tag.end_index)
1810
1839
  },
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;
1840
+ tag.id || `tag-${actualStart}-${tag.end_index}`
1841
+ )
1842
+ );
1843
+ }
1844
+ currentIndex = Math.max(currentIndex, tag.end_index);
1819
1845
  });
1820
1846
  if (currentIndex < block.text.length) {
1821
1847
  nodes.push(
@@ -1903,16 +1929,16 @@ function ScriptBreakdownSceneView({
1903
1929
  cat.id
1904
1930
  )),
1905
1931
  tags.some(
1906
- (t) => t.blockId === block.id && t.startIndex === selectionMenu.startIndex && t.endIndex === selectionMenu.endIndex
1932
+ (t) => t.block_id === block.id && t.start_index === selectionMenu.startIndex && t.end_index === selectionMenu.endIndex
1907
1933
  ) && /* @__PURE__ */ jsx("div", { className: "mt-1 pt-1 border-t border-white/60", children: /* @__PURE__ */ jsxs(
1908
1934
  "button",
1909
1935
  {
1910
1936
  onClick: (e) => {
1911
1937
  const tagToRemove = tags.find(
1912
- (t) => t.blockId === block.id && t.startIndex === selectionMenu.startIndex && t.endIndex === selectionMenu.endIndex
1938
+ (t) => t.block_id === block.id && t.start_index === selectionMenu.startIndex && t.end_index === selectionMenu.endIndex
1913
1939
  );
1914
- if (tagToRemove) {
1915
- removeTag(e, tagToRemove.id);
1940
+ if (tagToRemove && tagToRemove.id) {
1941
+ removeTag(e, tagToRemove == null ? void 0 : tagToRemove.id);
1916
1942
  clearSelection();
1917
1943
  }
1918
1944
  },
@@ -1955,115 +1981,136 @@ function ScriptBreakdownSceneView({
1955
1981
  ) })
1956
1982
  ] })
1957
1983
  ] }),
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") {
1984
+ /* @__PURE__ */ jsxs("div", { className: "w-full lg:w-80 flex-shrink-0 flex flex-col gap-6 sticky top-6", children: [
1985
+ /* @__PURE__ */ jsxs(
1986
+ "button",
1987
+ {
1988
+ onClick: onSummarize,
1989
+ disabled: isSummarizing,
1990
+ 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]"}`,
1991
+ children: [
1992
+ /* @__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]" }) }),
1993
+ /* @__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%)]" }),
1994
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-[1px] rounded-[inherit] bg-gradient-to-b from-white/90 to-transparent pointer-events-none" }),
1995
+ /* @__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]" }) }),
1996
+ /* @__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" }),
1997
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center gap-3", children: [
1998
+ 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" }),
1999
+ /* @__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" })
2000
+ ] })
2001
+ ]
2002
+ }
2003
+ ),
2004
+ /* @__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: [
2005
+ /* @__PURE__ */ jsxs("h3", { className: "text-xs font-extrabold text-slate-800 uppercase tracking-[0.25em] mb-8 flex items-center gap-3", children: [
2006
+ /* @__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" }) }),
2007
+ "Tags",
2008
+ /* @__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 })
2009
+ ] }),
2010
+ tags.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-8", children: CATEGORIES.map((cat) => {
2011
+ const catTags = Array.from(
2012
+ new Map(
2013
+ tags.filter((t) => t.category_id === cat.id).map((tag) => [tag.name.toLowerCase(), tag])
2014
+ ).values()
2015
+ );
2016
+ if (catTags.length === 0) return null;
2017
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
2018
+ /* @__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: [
2019
+ /* @__PURE__ */ jsx(
2020
+ "div",
2021
+ {
2022
+ className: "w-2 h-2 rounded-full shadow-sm",
2023
+ style: { backgroundColor: cat.hex }
2024
+ }
2025
+ ),
2026
+ cat.label
2027
+ ] }),
2028
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
2029
+ catTags.map((tag, index) => /* @__PURE__ */ jsx(
2030
+ "span",
2031
+ {
2032
+ 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",
2033
+ style: { color: cat.color },
2034
+ children: tag.name
2035
+ },
2036
+ index
2037
+ )),
2038
+ cat.id === "LOCATION" && /* @__PURE__ */ jsxs(
2039
+ "div",
2040
+ {
2041
+ className: "relative flex items-center",
2042
+ ref: subLocPopoverRef,
2043
+ children: [
2044
+ /* @__PURE__ */ jsx(
2045
+ "button",
2046
+ {
2047
+ onClick: () => setIsSubLocOpen(!isSubLocOpen),
2048
+ className: "flex items-center justify-center w-5 h-5 rounded-full hover:bg-slate-200 transition-colors",
2049
+ title: "Add Sub Location",
2050
+ children: /* @__PURE__ */ jsx(Plus, { className: "w-3 h-3 text-slate-500" })
2051
+ }
2052
+ ),
2053
+ 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: [
2054
+ /* @__PURE__ */ jsx("p", { className: "text-[9px] font-extrabold tracking-[0.2em] text-slate-400 uppercase mb-2 px-1", children: "Add Sub Location" }),
2055
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
2056
+ /* @__PURE__ */ jsx(
2057
+ "input",
2058
+ {
2059
+ type: "text",
2060
+ value: subLocInput,
2061
+ onChange: (e) => setSubLocInput(e.target.value),
2062
+ onKeyDown: (e) => {
2063
+ if (e.key === "Enter") {
2064
+ addSubLocation(subLocInput);
2065
+ setSubLocInput("");
2066
+ setIsSubLocOpen(false);
2067
+ }
2068
+ },
2069
+ 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",
2070
+ placeholder: "Sub location...",
2071
+ autoFocus: true
2072
+ }
2073
+ ),
2074
+ /* @__PURE__ */ jsx(
2075
+ "button",
2076
+ {
2077
+ onClick: () => {
2007
2078
  addSubLocation(subLocInput);
2008
2079
  setSubLocInput("");
2009
2080
  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
- )
2081
+ },
2082
+ 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",
2083
+ children: "Add"
2084
+ }
2085
+ )
2086
+ ] })
2029
2087
  ] })
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
- ] }) })
2088
+ ]
2089
+ }
2090
+ ),
2091
+ cat.id === "LOCATION" && subLocations.map((subLoc) => /* @__PURE__ */ jsxs(
2092
+ "span",
2093
+ {
2094
+ 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",
2095
+ children: [
2096
+ subLoc,
2097
+ /* @__PURE__ */ jsx(
2098
+ "button",
2099
+ {
2100
+ onClick: () => removeSubLocation(subLoc),
2101
+ 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",
2102
+ children: /* @__PURE__ */ jsx(X, { className: "w-2.5 h-2.5" })
2103
+ }
2104
+ )
2105
+ ]
2106
+ },
2107
+ `sub-${subLoc}`
2108
+ ))
2109
+ ] })
2110
+ ] }, cat.id);
2111
+ }) }) : /* @__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." })
2112
+ ] })
2113
+ ] })
2067
2114
  ] }) });
2068
2115
  }
2069
2116
  function useScriptBreakdown({
@@ -2145,6 +2192,14 @@ var use_script_breakdown_default = useScriptBreakdown;
2145
2192
  // app/hook/use-script-breakdown-scene.ts
2146
2193
  function useScriptBreakdownScene(sceneNumber) {
2147
2194
  const { scenes, isLoading, error } = use_script_breakdown_default({ scenes: [] });
2195
+ const [tags, setTags] = useState([]);
2196
+ const [selectionMenu, setSelectionMenu] = useState(null);
2197
+ const autoTaggedSceneRef = useRef(null);
2198
+ const [menuPlacement, setMenuPlacement] = useState("top");
2199
+ const [subLocations, setSubLocations] = useState([]);
2200
+ const [sceneBrief, setSceneBrief] = useState("");
2201
+ const [isSummarizing, setIsSummarizing] = useState(false);
2202
+ const menuRef = useRef(null);
2148
2203
  const scene = useMemo(() => {
2149
2204
  return scenes.find((s) => s.scene_number === sceneNumber);
2150
2205
  }, [scenes, sceneNumber]);
@@ -2173,7 +2228,9 @@ function useScriptBreakdownScene(sceneNumber) {
2173
2228
  break;
2174
2229
  }
2175
2230
  }
2176
- parsedBlocks.push({ id: uuid(), type, text: divText });
2231
+ const idAttr = div.getAttribute("id");
2232
+ const blockId = idAttr && idAttr.startsWith("par") ? idAttr.substring(3) : idAttr || uuid();
2233
+ parsedBlocks.push({ id: blockId, type, text: divText });
2177
2234
  });
2178
2235
  return parsedBlocks;
2179
2236
  }, [scene]);
@@ -2185,12 +2242,57 @@ function useScriptBreakdownScene(sceneNumber) {
2185
2242
  }).filter(Boolean);
2186
2243
  return [...new Set(chars)];
2187
2244
  }, [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([]);
2245
+ const handleAISummarize = async () => {
2246
+ setIsSummarizing(true);
2247
+ const res = await fetch(
2248
+ "https://nonfibrous-extrafloral-verlene.ngrok-free.dev/extract",
2249
+ {
2250
+ method: "POST",
2251
+ headers: {
2252
+ "Content-Type": "application/json",
2253
+ "ngrok-skip-browser-warning": "true"
2254
+ },
2255
+ body: JSON.stringify({
2256
+ script: (scene == null ? void 0 : scene.content) || ""
2257
+ })
2258
+ }
2259
+ );
2260
+ if (res.ok) {
2261
+ const data = await res.json();
2262
+ setIsSummarizing(false);
2263
+ const parsedData = JSON.parse(data.data) || [];
2264
+ console.log("AI Tags:", parsedData);
2265
+ const newTags = [];
2266
+ parsedData.forEach((aiTag) => {
2267
+ if (!aiTag.block_id || !aiTag.category_id || typeof aiTag.start_index !== "number" || typeof aiTag.end_index !== "number") {
2268
+ return;
2269
+ }
2270
+ newTags.push({
2271
+ block_id: String(aiTag.block_id).startsWith("par") ? String(aiTag.block_id).substring(3) : String(aiTag.block_id),
2272
+ category_id: aiTag.category_id,
2273
+ name: aiTag.name,
2274
+ start_index: aiTag.start_index,
2275
+ end_index: aiTag.end_index
2276
+ });
2277
+ });
2278
+ setTags((prev) => {
2279
+ const merged = [...prev];
2280
+ newTags.forEach((newTag) => {
2281
+ const isOverlapping = merged.some(
2282
+ (t) => t.block_id === newTag.block_id && newTag.end_index > t.start_index && newTag.start_index < t.end_index
2283
+ );
2284
+ if (!isOverlapping) {
2285
+ merged.push(newTag);
2286
+ }
2287
+ });
2288
+ return merged;
2289
+ });
2290
+ return data;
2291
+ } else {
2292
+ setIsSummarizing(false);
2293
+ console.error("Failed to summarize scene:", res);
2294
+ }
2295
+ };
2194
2296
  const addSubLocation = useCallback(
2195
2297
  (subLocation) => {
2196
2298
  const trimmed = subLocation.trim();
@@ -2203,7 +2305,6 @@ function useScriptBreakdownScene(sceneNumber) {
2203
2305
  const removeSubLocation = useCallback((subLocation) => {
2204
2306
  setSubLocations((prev) => prev.filter((loc) => loc !== subLocation));
2205
2307
  }, []);
2206
- const [sceneBrief, setSceneBrief] = useState("");
2207
2308
  useEffect(() => {
2208
2309
  setTags([]);
2209
2310
  setSubLocations([]);
@@ -2222,19 +2323,19 @@ function useScriptBreakdownScene(sceneNumber) {
2222
2323
  let match;
2223
2324
  while ((match = regex.exec(block.text)) !== null) {
2224
2325
  const isOverlapping = autoTags.some(
2225
- (t) => t.blockId === block.id && match.index + char.length > t.startIndex && match.index < t.endIndex
2326
+ (t) => t.block_id === block.id && match.index + char.length > t.start_index && match.index < t.end_index
2226
2327
  );
2227
2328
  if (!isOverlapping) {
2228
2329
  autoTags.push({
2229
2330
  id: uuid(),
2230
- blockId: block.id,
2231
- categoryId: "CAST",
2232
- text: block.text.substring(
2331
+ block_id: block.id,
2332
+ category_id: "CAST",
2333
+ name: block.text.substring(
2233
2334
  match.index,
2234
2335
  match.index + char.length
2235
2336
  ),
2236
- startIndex: match.index,
2237
- endIndex: match.index + char.length
2337
+ start_index: match.index,
2338
+ end_index: match.index + char.length
2238
2339
  });
2239
2340
  }
2240
2341
  }
@@ -2334,15 +2435,15 @@ function useScriptBreakdownScene(sceneNumber) {
2334
2435
  if (!selectionMenu) return;
2335
2436
  const newTag = {
2336
2437
  id: uuid(),
2337
- blockId: selectionMenu.blockId,
2338
- categoryId,
2339
- text: selectionMenu.text,
2340
- startIndex: selectionMenu.startIndex,
2341
- endIndex: selectionMenu.endIndex
2438
+ block_id: selectionMenu.blockId,
2439
+ category_id: categoryId,
2440
+ name: selectionMenu.text,
2441
+ start_index: selectionMenu.startIndex,
2442
+ end_index: selectionMenu.endIndex
2342
2443
  };
2343
2444
  setTags((prev) => {
2344
2445
  const filtered = prev.filter(
2345
- (t) => t.blockId !== newTag.blockId || !(newTag.endIndex > t.startIndex && newTag.startIndex < t.endIndex)
2446
+ (t) => t.block_id !== newTag.block_id || !(newTag.end_index > t.start_index && newTag.start_index < t.end_index)
2346
2447
  );
2347
2448
  return [...filtered, newTag];
2348
2449
  });
@@ -2372,7 +2473,9 @@ function useScriptBreakdownScene(sceneNumber) {
2372
2473
  addSubLocation,
2373
2474
  removeSubLocation,
2374
2475
  sceneBrief,
2375
- setSceneBrief
2476
+ setSceneBrief,
2477
+ handleAISummarize,
2478
+ isSummarizing
2376
2479
  };
2377
2480
  }
2378
2481