@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.
- package/B] +2 -0
- package/README.md +29 -0
- package/package.json +1 -1
- package/skills/markcut/SKILL.md +12 -45
- package/skills/markcut/docs/components.md +89 -0
- package/skills/markcut/docs/markdown-descriptive.md +17 -2
- package/skills/markcut/docs/sound-effects.md +45 -0
- package/src/components/Markdown.tsx +138 -24
- package/src/components/Mermaid.tsx +223 -22
- package/src/config.mjs +2 -2
- package/src/context/EventContext.tsx +3 -0
- package/src/descriptive/compiler.ts +68 -29
- package/src/descriptive/markdown.ts +20 -0
- package/src/player/browser.tsx +95 -5
- package/src/player/bundle/player.js +1078 -629
- package/src/player/components/EditControls.tsx +6 -3
- package/src/player/components/EditMessagePanel.tsx +96 -0
- package/src/player/components/HeaderBar.tsx +9 -11
- package/src/player/components/index.ts +1 -0
- package/src/player/pipeline.mjs +72 -21
- package/src/player/server-shared.mjs +4 -1
- package/src/player/server.mjs +202 -42
- package/src/render/cli.mjs +1 -1
- package/src/schema/index.ts +4 -1
- package/src/types/Component.tsx +27 -1
- package/src/types/Effect.tsx +13 -6
- package/src/types/Folder.tsx +1 -1
- package/src/types/Map.tsx +51 -1
- package/src/utils/index.ts +14 -2
- package/tests/fixtures/md/animate-diagrams.md +40 -0
- package/tests/fixtures/md/electricity-grow.md +130 -0
- package/tests/tmp/vision-1785081637127-video/videos/.normalized/segments/test-clip_0to3_seg_1100to3000.mp4 +0 -0
- package/tests/tmp/vision-1785081637127-video/videos/.normalized/test-clip_0to3.mp4 +0 -0
- package/tests/tmp/vision-1785081637127-video/videos/.normalized/test-clip_audio.mp3 +0 -0
- package/tests/tmp/vision-1785081637127-video/videos/metadata.json +9 -0
- package/tests/tmp/vision-1785081637127-video/videos/test-clip.mp4 +0 -0
- package/tests/tmp/vision-1785081637127-video/videos/test-clip.vtt +5 -0
- package/tests/tmp/vision-1784830584961/images/.normalized/test-photo_384.jpg +0 -0
- package/tests/tmp/vision-1784830584961/images/metadata.json +0 -8
- package/tests/tmp/vision-1784830584961/images/test-photo.png +0 -0
- package/tmp/frontmatter-test.ts +0 -21
package/src/player/browser.tsx
CHANGED
|
@@ -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" && (
|