@lalalic/markcut 2.8.0 → 3.0.0

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.
Files changed (41) hide show
  1. package/B] +2 -0
  2. package/README.md +29 -0
  3. package/package.json +1 -1
  4. package/skills/markcut/SKILL.md +12 -45
  5. package/skills/markcut/docs/components.md +89 -0
  6. package/skills/markcut/docs/markdown-descriptive.md +17 -2
  7. package/skills/markcut/docs/sound-effects.md +45 -0
  8. package/src/components/Markdown.tsx +138 -24
  9. package/src/components/Mermaid.tsx +223 -22
  10. package/src/config.mjs +2 -2
  11. package/src/context/EventContext.tsx +3 -0
  12. package/src/descriptive/compiler.ts +68 -29
  13. package/src/descriptive/markdown.ts +20 -0
  14. package/src/player/browser.tsx +95 -5
  15. package/src/player/bundle/player.js +1078 -629
  16. package/src/player/components/EditControls.tsx +6 -3
  17. package/src/player/components/EditMessagePanel.tsx +96 -0
  18. package/src/player/components/HeaderBar.tsx +9 -11
  19. package/src/player/components/index.ts +1 -0
  20. package/src/player/pipeline.mjs +72 -21
  21. package/src/player/server-shared.mjs +4 -1
  22. package/src/player/server.mjs +202 -42
  23. package/src/render/cli.mjs +1 -1
  24. package/src/schema/index.ts +4 -1
  25. package/src/types/Component.tsx +27 -1
  26. package/src/types/Effect.tsx +13 -6
  27. package/src/types/Folder.tsx +1 -1
  28. package/src/types/Map.tsx +51 -1
  29. package/src/utils/index.ts +14 -2
  30. package/tests/fixtures/md/animate-diagrams.md +40 -0
  31. package/tests/fixtures/md/electricity-grow.md +130 -0
  32. package/tests/tmp/vision-1785081637127-video/videos/.normalized/segments/test-clip_0to3_seg_1100to3000.mp4 +0 -0
  33. package/tests/tmp/vision-1785081637127-video/videos/.normalized/test-clip_0to3.mp4 +0 -0
  34. package/tests/tmp/vision-1785081637127-video/videos/.normalized/test-clip_audio.mp3 +0 -0
  35. package/tests/tmp/vision-1785081637127-video/videos/metadata.json +9 -0
  36. package/tests/tmp/vision-1785081637127-video/videos/test-clip.mp4 +0 -0
  37. package/tests/tmp/vision-1785081637127-video/videos/test-clip.vtt +5 -0
  38. package/tests/tmp/vision-1784830584961/images/.normalized/test-photo_384.jpg +0 -0
  39. package/tests/tmp/vision-1784830584961/images/metadata.json +0 -8
  40. package/tests/tmp/vision-1784830584961/images/test-photo.png +0 -0
  41. package/tmp/frontmatter-test.ts +0 -21
@@ -14,7 +14,8 @@ import * as ReactDOM from "react-dom";
14
14
  import * as Remotion from "remotion";
15
15
  import { Player } from "@remotion/player";
16
16
  import { MarkCut, getDurationInSeconds } from "../entry";
17
- import { HeaderBar, EditControls, LabelControls, SceneThumbnails, VariantBar } from "./components/index";
17
+ import { HeaderBar, EditControls, EditMessagePanel, LabelControls, SceneThumbnails, VariantBar } from "./components/index";
18
+ import type { EditEntry } from "./components/EditMessagePanel";
18
19
  import * as MarkcutComponents from "../components/index";
19
20
 
20
21
  /**
@@ -161,6 +162,13 @@ function PlayerApp() {
161
162
  const urlParams = new URLSearchParams(typeof window !== "undefined" ? window.location.search : "");
162
163
  const autoPlay = urlParams.get("autoplay") === "true";
163
164
  const startAt = parseFloat(urlParams.get("start") || urlParams.get("t") || "0") || 0;
165
+ const locale: "en" | "zh" = (() => {
166
+ const raw = (urlParams.get("lang") || "").toLowerCase();
167
+ if (raw === "zh" || raw.startsWith("zh-")) return "zh";
168
+ if (raw === "en" || raw.startsWith("en-")) return "en";
169
+ if (typeof navigator !== "undefined" && navigator.language.toLowerCase().startsWith("zh")) return "zh";
170
+ return "en";
171
+ })();
164
172
 
165
173
  // Derive player config from data early so effects can reference them safely.
166
174
  const fps = data?.fps ?? 30;
@@ -389,10 +397,30 @@ function PlayerApp() {
389
397
  (typeof window !== "undefined" ? (window as any).MODE : null) || "preview";
390
398
 
391
399
  // Shared state for header info
392
- const [editStatus, setEditStatus] = React.useState("");
393
400
  const [sseConnected, setSseConnected] = React.useState(false);
394
401
  const [labelSceneInfo, setLabelSceneInfo] = React.useState("");
395
402
 
403
+ // ── Edit message panel state ─────────────────────────────────────────
404
+ const [editEntries, setEditEntries] = React.useState<EditEntry[]>([]);
405
+ const [showEditOverlay, setShowEditOverlay] = React.useState(false);
406
+ const nextEditIdRef = React.useRef(1);
407
+ const hideEditOverlayTimerRef = React.useRef<number | null>(null);
408
+
409
+ const clearHideOverlayTimer = React.useCallback(() => {
410
+ if (hideEditOverlayTimerRef.current !== null) {
411
+ window.clearTimeout(hideEditOverlayTimerRef.current);
412
+ hideEditOverlayTimerRef.current = null;
413
+ }
414
+ }, []);
415
+
416
+ const scheduleHideEditOverlay = React.useCallback(() => {
417
+ clearHideOverlayTimer();
418
+ hideEditOverlayTimerRef.current = window.setTimeout(() => {
419
+ setShowEditOverlay(false);
420
+ hideEditOverlayTimerRef.current = null;
421
+ }, 5000);
422
+ }, [clearHideOverlayTimer]);
423
+
396
424
  // SSE connection — shared across all modes as a server-liveness monitor
397
425
  // Edit mode also listens for "reload" messages (auto-refresh on file change)
398
426
  const suppressReloadRef = React.useRef(false);
@@ -404,8 +432,57 @@ function PlayerApp() {
404
432
  evtSource.onmessage = (e: MessageEvent) => {
405
433
  try {
406
434
  const msg = JSON.parse(e.data);
435
+
436
+ // Reload event (file changed on disk or agent finished editing)
407
437
  if (msg.type === "reload" && !suppressReloadRef.current) {
408
438
  window.dispatchEvent(new Event("refresh-player"));
439
+ return;
440
+ }
441
+
442
+ // Edit progress events (live from agent RPC)
443
+ if (msg.type === "edit:start") {
444
+ clearHideOverlayTimer();
445
+ setShowEditOverlay(true);
446
+ const id = nextEditIdRef.current++;
447
+ setEditEntries((prev) => [
448
+ ...prev,
449
+ { id, request: msg.request || "", progress: "", status: "thinking" },
450
+ ]);
451
+ return;
452
+ }
453
+ if (msg.type === "edit:progress") {
454
+ setEditEntries((prev) => {
455
+ const last = prev[prev.length - 1];
456
+ if (!last || last.status !== "thinking") return prev;
457
+ return prev.map((e) =>
458
+ e.id === last.id ? { ...e, progress: msg.text || "" } : e
459
+ );
460
+ });
461
+ return;
462
+ }
463
+ if (msg.type === "edit:done") {
464
+ setEditEntries((prev) => {
465
+ const last = prev[prev.length - 1];
466
+ if (!last) return prev;
467
+ return prev.map((e) =>
468
+ e.id === last.id
469
+ ? { ...e, status: "done", progress: e.progress || msg.summary || "" }
470
+ : e
471
+ );
472
+ });
473
+ scheduleHideEditOverlay();
474
+ return;
475
+ }
476
+ if (msg.type === "edit:error") {
477
+ setEditEntries((prev) => {
478
+ const last = prev[prev.length - 1];
479
+ if (!last) return prev;
480
+ return prev.map((e) =>
481
+ e.id === last.id ? { ...e, status: "error", error: msg.error } : e
482
+ );
483
+ });
484
+ scheduleHideEditOverlay();
485
+ return;
409
486
  }
410
487
  } catch {}
411
488
  };
@@ -414,10 +491,11 @@ function PlayerApp() {
414
491
  setSseConnected(false);
415
492
  }
416
493
  return () => {
494
+ clearHideOverlayTimer();
417
495
  evtSource?.close();
418
496
  setSseConnected(false);
419
497
  };
420
- }, []);
498
+ }, [clearHideOverlayTimer, scheduleHideEditOverlay]);
421
499
 
422
500
  if (error) {
423
501
  return <div style={{ color: "red", padding: 40, fontFamily: "sans-serif" }}>Error: {error}</div>;
@@ -440,9 +518,9 @@ function PlayerApp() {
440
518
  {/* ── Header (close button + mode info) ── */}
441
519
  <HeaderBar
442
520
  mode={mode}
443
- editStatus={editStatus}
444
521
  sseConnected={sseConnected}
445
522
  sceneInfo={mode === "label" ? labelSceneInfo : undefined}
523
+ locale={locale}
446
524
  />
447
525
 
448
526
  {/* ── Variant switcher ── */}
@@ -466,6 +544,18 @@ function PlayerApp() {
466
544
  doubleClickToFullscreen={true}
467
545
  autoPlay={autoPlay}
468
546
  />
547
+
548
+ {/* ── Edit message overlay (shown while waiting, then auto-hides) ── */}
549
+ {mode === "edit" && showEditOverlay && (
550
+ <div id="edit-message-overlay">
551
+ <EditMessagePanel
552
+ entries={editEntries}
553
+ minimized={false}
554
+ locale={locale}
555
+ onToggleMinimize={() => setShowEditOverlay(false)}
556
+ />
557
+ </div>
558
+ )}
469
559
  </div>
470
560
 
471
561
  {/* ── Scene thumbnails (shared across all modes) ── */}
@@ -483,10 +573,10 @@ function PlayerApp() {
483
573
  {/* ── Mode-specific controls ── */}
484
574
  {mode === "edit" && (
485
575
  <EditControls
486
- onStatusChange={setEditStatus}
487
576
  suppressReloadRef={suppressReloadRef}
488
577
  currentTime={currentTime}
489
578
  activeScene={activeScene}
579
+ locale={locale}
490
580
  />
491
581
  )}
492
582
  {mode === "label" && (