@vishu1301/script-writing 1.2.2 → 1.2.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
@@ -1753,6 +1753,7 @@ var CATEGORIES = [
1753
1753
  { id: "SET_PROP", label: "Set Prop", color: "#00C853", hex: "#69F0AE" },
1754
1754
  { id: "EXTRA", label: "Extra", color: "#00B8D4", hex: "#62EFFF" },
1755
1755
  { id: "LOCATION", label: "Location", color: "#FFB300", hex: "#FFE082" },
1756
+ { id: "SUBLOCATION", label: "Sublocation", color: "#004CFF", hex: "#004CFF" },
1756
1757
  { id: "OTHER", label: "Other", color: "#9E9E9E", hex: "#E0E0E0" }
1757
1758
  ];
1758
1759
  var PopcornIcon = ({ isSummarizing }) => /* @__PURE__ */ jsxRuntime.jsxs(
@@ -1940,31 +1941,17 @@ function ScriptBreakdownSceneView({
1940
1941
  selectionMenu,
1941
1942
  handleMouseUp,
1942
1943
  addTag,
1944
+ updateTag,
1943
1945
  removeTag,
1944
1946
  clearSelection,
1945
1947
  menuPlacement,
1946
1948
  menuRef,
1947
- subLocations,
1948
- addSubLocation,
1949
- removeSubLocation,
1950
1949
  sceneBrief,
1951
1950
  setSceneBrief,
1952
1951
  onSummarize,
1953
1952
  isSummarizing
1954
1953
  }) {
1955
1954
  const COURIER_STACK = "'Courier Prime', 'Courier', monospace";
1956
- const [isSubLocOpen, setIsSubLocOpen] = react.useState(false);
1957
- const [subLocInput, setSubLocInput] = react.useState("");
1958
- const subLocPopoverRef = react.useRef(null);
1959
- react.useEffect(() => {
1960
- const handleClickOutside = (e) => {
1961
- if (isSubLocOpen && subLocPopoverRef.current && !subLocPopoverRef.current.contains(e.target)) {
1962
- setIsSubLocOpen(false);
1963
- }
1964
- };
1965
- document.addEventListener("mousedown", handleClickOutside);
1966
- return () => document.removeEventListener("mousedown", handleClickOutside);
1967
- }, [isSubLocOpen]);
1968
1955
  react.useEffect(() => {
1969
1956
  const fontId = "google-font-courier-prime";
1970
1957
  const styleId = "screenplay-editor-force-v4";
@@ -2108,11 +2095,20 @@ function ScriptBreakdownSceneView({
2108
2095
  ] }),
2109
2096
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative z-10 flex flex-col gap-1", children: [
2110
2097
  CATEGORIES.filter(
2111
- (cat) => !(cat.id === "LOCATION" && hasLocationTag)
2098
+ (cat) => !(cat.id === "LOCATION" && hasLocationTag) && !(cat.id === "SUBLOCATION" && !hasLocationTag)
2112
2099
  ).map((cat) => /* @__PURE__ */ jsxRuntime.jsxs(
2113
2100
  "button",
2114
2101
  {
2115
- onClick: () => addTag(cat.id),
2102
+ onClick: () => {
2103
+ const existingTag = tags.find(
2104
+ (t) => t.block_id === block.id && t.start_index === selectionMenu.startIndex && t.end_index === selectionMenu.endIndex
2105
+ );
2106
+ if (existingTag && existingTag.id) {
2107
+ updateTag == null ? void 0 : updateTag(existingTag.id, cat.id);
2108
+ } else {
2109
+ addTag(cat.id);
2110
+ }
2111
+ },
2116
2112
  className: "group w-full text-[12px] font-bold px-3 py-2 rounded-xl transition-all duration-300 text-left flex items-center justify-between hover:bg-white/80 hover:shadow-[0_2px_10px_rgb(0,0,0,0.02)] active:scale-[0.98]",
2117
2113
  style: { color: cat.color },
2118
2114
  children: [
@@ -2199,6 +2195,7 @@ function ScriptBreakdownSceneView({
2199
2195
  /* @__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 })
2200
2196
  ] }),
2201
2197
  tags.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-8", children: CATEGORIES.map((cat) => {
2198
+ if (cat.id === "SUBLOCATION" && !hasLocationTag) return null;
2202
2199
  const catTags = Array.from(
2203
2200
  new Map(
2204
2201
  tags.filter((t) => t.category_id === cat.id).map((tag) => [tag.name.toLowerCase(), tag])
@@ -2216,88 +2213,15 @@ function ScriptBreakdownSceneView({
2216
2213
  ),
2217
2214
  cat.label
2218
2215
  ] }),
2219
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
2220
- catTags.map((tag, index) => /* @__PURE__ */ jsxRuntime.jsx(
2221
- "span",
2222
- {
2223
- 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",
2224
- style: { color: cat.color },
2225
- children: tag.name
2226
- },
2227
- index
2228
- )),
2229
- cat.id === "LOCATION" && /* @__PURE__ */ jsxRuntime.jsxs(
2230
- "div",
2231
- {
2232
- className: "relative flex items-center",
2233
- ref: subLocPopoverRef,
2234
- children: [
2235
- /* @__PURE__ */ jsxRuntime.jsx(
2236
- "button",
2237
- {
2238
- onClick: () => setIsSubLocOpen(!isSubLocOpen),
2239
- className: "flex items-center justify-center w-5 h-5 rounded-full hover:bg-slate-200 transition-colors",
2240
- title: "Add Sub Location",
2241
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "w-3 h-3 text-slate-500" })
2242
- }
2243
- ),
2244
- 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: [
2245
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[9px] font-extrabold tracking-[0.2em] text-slate-400 uppercase mb-2 px-1", children: "Add Sub Location" }),
2246
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 items-center", children: [
2247
- /* @__PURE__ */ jsxRuntime.jsx(
2248
- "input",
2249
- {
2250
- type: "text",
2251
- value: subLocInput,
2252
- onChange: (e) => setSubLocInput(e.target.value),
2253
- onKeyDown: (e) => {
2254
- if (e.key === "Enter") {
2255
- addSubLocation(subLocInput);
2256
- setSubLocInput("");
2257
- setIsSubLocOpen(false);
2258
- }
2259
- },
2260
- 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",
2261
- placeholder: "Sub location...",
2262
- autoFocus: true
2263
- }
2264
- ),
2265
- /* @__PURE__ */ jsxRuntime.jsx(
2266
- "button",
2267
- {
2268
- onClick: () => {
2269
- addSubLocation(subLocInput);
2270
- setSubLocInput("");
2271
- setIsSubLocOpen(false);
2272
- },
2273
- 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",
2274
- children: "Add"
2275
- }
2276
- )
2277
- ] })
2278
- ] })
2279
- ]
2280
- }
2281
- ),
2282
- cat.id === "LOCATION" && subLocations.map((subLoc) => /* @__PURE__ */ jsxRuntime.jsxs(
2283
- "span",
2284
- {
2285
- 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",
2286
- children: [
2287
- subLoc,
2288
- /* @__PURE__ */ jsxRuntime.jsx(
2289
- "button",
2290
- {
2291
- onClick: () => removeSubLocation(subLoc),
2292
- 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",
2293
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-2.5 h-2.5" })
2294
- }
2295
- )
2296
- ]
2297
- },
2298
- `sub-${subLoc}`
2299
- ))
2300
- ] })
2216
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: catTags.map((tag, index) => /* @__PURE__ */ jsxRuntime.jsx(
2217
+ "span",
2218
+ {
2219
+ 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",
2220
+ style: { color: cat.color },
2221
+ children: tag.name
2222
+ },
2223
+ index
2224
+ )) })
2301
2225
  ] }, cat.id);
2302
2226
  }) }) : /* @__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." })
2303
2227
  ] })
@@ -2310,7 +2234,6 @@ function useScriptBreakdownScene(options) {
2310
2234
  const autoTaggedSceneRef = react.useRef(null);
2311
2235
  const [scene, setScene] = react.useState(null);
2312
2236
  const [menuPlacement, setMenuPlacement] = react.useState("top");
2313
- const [subLocations, setSubLocations] = react.useState([]);
2314
2237
  const [sceneBrief, setSceneBrief] = react.useState("");
2315
2238
  const [isSummarizing, setIsSummarizing] = react.useState(false);
2316
2239
  const [isLoading, setIsLoading] = react.useState(true);
@@ -2410,9 +2333,122 @@ function useScriptBreakdownScene(options) {
2410
2333
  end_index: aiTag.end_index
2411
2334
  });
2412
2335
  });
2336
+ if (newTags.length > 0) {
2337
+ const originalTags = tags;
2338
+ setTags((prev) => {
2339
+ const merged = [...prev];
2340
+ newTags.forEach((newTag) => {
2341
+ const isOverlapping = merged.some(
2342
+ (t) => t.block_id === newTag.block_id && newTag.end_index > t.start_index && newTag.start_index < t.end_index
2343
+ );
2344
+ if (!isOverlapping) {
2345
+ merged.push(newTag);
2346
+ }
2347
+ });
2348
+ return merged;
2349
+ });
2350
+ try {
2351
+ if (options.onTagsBulkAdded) {
2352
+ await options.onTagsBulkAdded(newTags, parsedSummaryData.summarise);
2353
+ }
2354
+ } catch (error2) {
2355
+ console.error("Failed to bulk add AI-generated tags:", error2);
2356
+ setTags(originalTags);
2357
+ }
2358
+ }
2359
+ return data;
2360
+ } else {
2361
+ setIsSummarizing(false);
2362
+ console.error("Failed to summarize scene:", res);
2363
+ }
2364
+ };
2365
+ const bulkCreateTags = react.useCallback(async () => {
2366
+ if (blocks.length === 0) return;
2367
+ const newTags = [];
2368
+ const seenCharacters = /* @__PURE__ */ new Set();
2369
+ const timeOfDays = ["DAY", "NIGHT"];
2370
+ const isTimeOfDay = (str) => timeOfDays.includes(str.toUpperCase());
2371
+ blocks.forEach((block) => {
2372
+ if (block.type === "CHARACTER") {
2373
+ const text = block.text.trim();
2374
+ const parenIndex = text.indexOf("(");
2375
+ const charName = parenIndex > -1 ? text.substring(0, parenIndex).trim() : text;
2376
+ if (charName && !seenCharacters.has(charName.toUpperCase())) {
2377
+ seenCharacters.add(charName.toUpperCase());
2378
+ const startIndex = text.indexOf(charName);
2379
+ if (startIndex !== -1) {
2380
+ newTags.push({
2381
+ id: uuid(),
2382
+ block_id: block.id,
2383
+ category_id: "CAST",
2384
+ name: charName,
2385
+ start_index: startIndex,
2386
+ end_index: startIndex + charName.length
2387
+ });
2388
+ }
2389
+ }
2390
+ } else if (block.type === "SCENE_HEADING") {
2391
+ const text = block.text.trim();
2392
+ const typeMatch = text.match(
2393
+ /^(INT\/EXT|INT\.?\/EXT\.?|INT\.EXT\.?|INT|EXT|I\/E)\.?\s+/i
2394
+ );
2395
+ let remainingText = text;
2396
+ let offset = 0;
2397
+ if (typeMatch) {
2398
+ offset = typeMatch[0].length;
2399
+ remainingText = text.substring(offset);
2400
+ }
2401
+ const parts = remainingText.split(/\s+-\s+/);
2402
+ if (parts.length > 0) {
2403
+ const locationName = parts[0].trim();
2404
+ const locStart = text.indexOf(locationName, offset);
2405
+ if (locStart !== -1 && locationName) {
2406
+ newTags.push({
2407
+ id: uuid(),
2408
+ block_id: block.id,
2409
+ category_id: "LOCATION",
2410
+ name: locationName,
2411
+ start_index: locStart,
2412
+ end_index: locStart + locationName.length
2413
+ });
2414
+ }
2415
+ if (parts.length > 1) {
2416
+ const secondPart = parts[1].trim();
2417
+ const isLast = parts.length === 2;
2418
+ if (!isLast || !isTimeOfDay(secondPart)) {
2419
+ const subLocStart = text.indexOf(
2420
+ secondPart,
2421
+ locStart + locationName.length
2422
+ );
2423
+ if (subLocStart !== -1 && secondPart) {
2424
+ newTags.push({
2425
+ id: uuid(),
2426
+ block_id: block.id,
2427
+ category_id: "SUBLOCATION",
2428
+ name: secondPart,
2429
+ start_index: subLocStart,
2430
+ end_index: subLocStart + secondPart.length
2431
+ });
2432
+ }
2433
+ }
2434
+ }
2435
+ }
2436
+ }
2437
+ });
2438
+ if (newTags.length > 0) {
2439
+ const originalTags = tags;
2413
2440
  setTags((prev) => {
2414
2441
  const merged = [...prev];
2442
+ const existingChars = new Set(
2443
+ merged.filter(
2444
+ (t) => t.category_id === "CHARACTER" || t.category_id === "CAST"
2445
+ ).map((t) => t.name.toUpperCase())
2446
+ );
2415
2447
  newTags.forEach((newTag) => {
2448
+ if (newTag.category_id === "CHARACTER") {
2449
+ if (existingChars.has(newTag.name.toUpperCase())) return;
2450
+ existingChars.add(newTag.name.toUpperCase());
2451
+ }
2416
2452
  const isOverlapping = merged.some(
2417
2453
  (t) => t.block_id === newTag.block_id && newTag.end_index > t.start_index && newTag.start_index < t.end_index
2418
2454
  );
@@ -2422,26 +2458,17 @@ function useScriptBreakdownScene(options) {
2422
2458
  });
2423
2459
  return merged;
2424
2460
  });
2425
- return data;
2426
- } else {
2427
- setIsSummarizing(false);
2428
- console.error("Failed to summarize scene:", res);
2429
- }
2430
- };
2431
- const addSubLocation = react.useCallback(
2432
- (subLocation) => {
2433
- const trimmed = subLocation.trim();
2434
- if (trimmed && !subLocations.includes(trimmed)) {
2435
- setSubLocations((prev) => [...prev, trimmed]);
2461
+ try {
2462
+ if (options.onTagsBulkAdded) {
2463
+ await options.onTagsBulkAdded(newTags);
2464
+ }
2465
+ } catch (error2) {
2466
+ console.error("Failed to bulk create tags:", error2);
2467
+ setTags(originalTags);
2436
2468
  }
2437
- },
2438
- [subLocations]
2439
- );
2440
- const removeSubLocation = react.useCallback((subLocation) => {
2441
- setSubLocations((prev) => prev.filter((loc) => loc !== subLocation));
2442
- }, []);
2469
+ }
2470
+ }, [blocks, tags, options.onTagsBulkAdded]);
2443
2471
  react.useEffect(() => {
2444
- setSubLocations([]);
2445
2472
  setSceneBrief("");
2446
2473
  autoTaggedSceneRef.current = null;
2447
2474
  }, [options.scene_url]);
@@ -2455,6 +2482,18 @@ function useScriptBreakdownScene(options) {
2455
2482
  });
2456
2483
  }
2457
2484
  }, [options.preLoadedTags]);
2485
+ react.useEffect(() => {
2486
+ const doBulkCreate = async () => {
2487
+ if (blocks.length > 0 && !autoTaggedSceneRef.current) {
2488
+ const hasPreloadedTags = options.preLoadedTags && options.preLoadedTags.length > 0;
2489
+ if (!hasPreloadedTags) {
2490
+ autoTaggedSceneRef.current = options.scene_url;
2491
+ await bulkCreateTags();
2492
+ }
2493
+ }
2494
+ };
2495
+ doBulkCreate();
2496
+ }, [blocks, options.scene_url, options.preLoadedTags, bulkCreateTags]);
2458
2497
  const clearSelection = react.useCallback(() => {
2459
2498
  var _a;
2460
2499
  setSelectionMenu(null);
@@ -2581,6 +2620,25 @@ function useScriptBreakdownScene(options) {
2581
2620
  setTags((prev) => [...prev, tagToRemove]);
2582
2621
  }
2583
2622
  };
2623
+ const updateTag = async (id, categoryId) => {
2624
+ var _a;
2625
+ const tagToUpdate = tags.find((t) => t.id === id);
2626
+ if (!tagToUpdate) return;
2627
+ setTags(
2628
+ (prev) => prev.map((t) => t.id === id ? __spreadProps(__spreadValues({}, t), { category_id: categoryId }) : t)
2629
+ );
2630
+ clearSelection();
2631
+ try {
2632
+ await ((_a = options.onTagUpdated) == null ? void 0 : _a.call(options, id, categoryId));
2633
+ } catch (error2) {
2634
+ console.error("Failed to update tag:", error2);
2635
+ setTags(
2636
+ (prev) => prev.map(
2637
+ (t) => t.id === id ? __spreadProps(__spreadValues({}, t), { category_id: tagToUpdate.category_id }) : t
2638
+ )
2639
+ );
2640
+ }
2641
+ };
2584
2642
  return {
2585
2643
  scene,
2586
2644
  blocks,
@@ -2591,13 +2649,11 @@ function useScriptBreakdownScene(options) {
2591
2649
  selectionMenu,
2592
2650
  handleMouseUp,
2593
2651
  addTag,
2652
+ updateTag,
2594
2653
  removeTag,
2595
2654
  clearSelection,
2596
2655
  menuPlacement,
2597
2656
  menuRef,
2598
- subLocations,
2599
- addSubLocation,
2600
- removeSubLocation,
2601
2657
  sceneBrief,
2602
2658
  setSceneBrief,
2603
2659
  handleAISummarize,