@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 +291 -186
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -11
- package/dist/index.d.ts +20 -11
- package/dist/index.js +292 -187
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
1434
|
-
|
|
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: "#
|
|
1732
|
-
{ id: "PROP", label: "Prop", color: "#
|
|
1733
|
-
{ id: "COSTUME", label: "Costume", color: "#
|
|
1734
|
-
{ id: "VEHICLE", label: "Vehicle", color: "#
|
|
1735
|
-
{ id: "SET_PROP", label: "Set Prop", color: "#
|
|
1736
|
-
{ id: "EXTRA", label: "Extra", color: "#
|
|
1737
|
-
{ id: "LOCATION", label: "Location", color: "#
|
|
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.
|
|
1824
|
+
const hasLocationTag = tags.some((t) => t.category_id === "LOCATION");
|
|
1804
1825
|
const renderBlockText = (block) => {
|
|
1805
|
-
const blockTags = tags.filter((t) => t.
|
|
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
|
-
|
|
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,
|
|
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.
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
e
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
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
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1984
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
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
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
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
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
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
|
-
|
|
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
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
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.
|
|
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
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
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
|
-
|
|
2262
|
-
|
|
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
|
-
|
|
2363
|
-
categoryId,
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
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.
|
|
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
|
|