dsh-creation-mode 0.0.0-stage → 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/LICENSE +33 -0
- package/README.md +26 -2
- package/client.js +2650 -0
- package/cordis.patch.yml +106 -0
- package/data.js +354 -0
- package/flow.js +338 -0
- package/icon.svg +13 -0
- package/index.js +234 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +50 -4
- package/stills/A1.jpg +0 -0
- package/stills/A2.jpg +0 -0
- package/stills/A3.jpg +0 -0
- package/stills/A4.jpg +0 -0
- package/stills/A5.jpg +0 -0
- package/stills/B1.jpg +0 -0
- package/stills/B2.jpg +0 -0
- package/stills/B3.jpg +0 -0
- package/stills/B4.jpg +0 -0
- package/stills/B5.jpg +0 -0
- package/stills/C1.jpg +0 -0
- package/stills/C2.jpg +0 -0
- package/stills/C3.jpg +0 -0
- package/stills/C4.jpg +0 -0
- package/stills/D1.jpg +0 -0
- package/stills/D2.jpg +0 -0
- package/stills/D3.jpg +0 -0
- package/stills/D4.jpg +0 -0
- package/stills/E1.jpg +0 -0
- package/stills/E2.jpg +0 -0
- package/stills/E3.jpg +0 -0
- package/stills/F1.jpg +0 -0
- package/stills/F2.jpg +0 -0
- package/stills/F3.jpg +0 -0
- package/stills/G1.jpg +0 -0
- package/stills/G2.jpg +0 -0
- package/stills/H1.jpg +0 -0
- package/stills/H10.jpg +0 -0
- package/stills/H11.jpg +0 -0
- package/stills/H12.jpg +0 -0
- package/stills/H2.jpg +0 -0
- package/stills/H3.jpg +0 -0
- package/stills/H4.jpg +0 -0
- package/stills/H5.jpg +0 -0
- package/stills/H6.jpg +0 -0
- package/stills/H7.jpg +0 -0
- package/stills/H8.jpg +0 -0
- package/stills/H9.jpg +0 -0
- package/stills/K1.jpg +0 -0
- package/stills/K10.jpg +0 -0
- package/stills/K11.jpg +0 -0
- package/stills/K12.jpg +0 -0
- package/stills/K13.jpg +0 -0
- package/stills/K14.jpg +0 -0
- package/stills/K15.jpg +0 -0
- package/stills/K16.jpg +0 -0
- package/stills/K17.jpg +0 -0
- package/stills/K18.jpg +0 -0
- package/stills/K19.jpg +0 -0
- package/stills/K2.jpg +0 -0
- package/stills/K20.jpg +0 -0
- package/stills/K21.jpg +0 -0
- package/stills/K22.jpg +0 -0
- package/stills/K23.jpg +0 -0
- package/stills/K24.jpg +0 -0
- package/stills/K25.jpg +0 -0
- package/stills/K26.jpg +0 -0
- package/stills/K27.jpg +0 -0
- package/stills/K28.jpg +0 -0
- package/stills/K29.jpg +0 -0
- package/stills/K3.jpg +0 -0
- package/stills/K30.jpg +0 -0
- package/stills/K31.jpg +0 -0
- package/stills/K32.jpg +0 -0
- package/stills/K33.jpg +0 -0
- package/stills/K34.jpg +0 -0
- package/stills/K35.jpg +0 -0
- package/stills/K36.jpg +0 -0
- package/stills/K37.jpg +0 -0
- package/stills/K38.jpg +0 -0
- package/stills/K4.jpg +0 -0
- package/stills/K5.jpg +0 -0
- package/stills/K6.jpg +0 -0
- package/stills/K7.jpg +0 -0
- package/stills/K8.jpg +0 -0
- package/stills/K9.jpg +0 -0
- package/stills/L1.jpg +0 -0
- package/stills/L10.jpg +0 -0
- package/stills/L11.jpg +0 -0
- package/stills/L12.jpg +0 -0
- package/stills/L13.jpg +0 -0
- package/stills/L14.jpg +0 -0
- package/stills/L15.jpg +0 -0
- package/stills/L16.jpg +0 -0
- package/stills/L17.jpg +0 -0
- package/stills/L18.jpg +0 -0
- package/stills/L19.jpg +0 -0
- package/stills/L2.jpg +0 -0
- package/stills/L20.jpg +0 -0
- package/stills/L21.jpg +0 -0
- package/stills/L22.jpg +0 -0
- package/stills/L23.jpg +0 -0
- package/stills/L24.jpg +0 -0
- package/stills/L25.jpg +0 -0
- package/stills/L26.jpg +0 -0
- package/stills/L27.jpg +0 -0
- package/stills/L28.jpg +0 -0
- package/stills/L29.jpg +0 -0
- package/stills/L3.jpg +0 -0
- package/stills/L30.jpg +0 -0
- package/stills/L31.jpg +0 -0
- package/stills/L32.jpg +0 -0
- package/stills/L33.jpg +0 -0
- package/stills/L34.jpg +0 -0
- package/stills/L35.jpg +0 -0
- package/stills/L36.jpg +0 -0
- package/stills/L37.jpg +0 -0
- package/stills/L38.jpg +0 -0
- package/stills/L39.jpg +0 -0
- package/stills/L4.jpg +0 -0
- package/stills/L40.jpg +0 -0
- package/stills/L41.jpg +0 -0
- package/stills/L42.jpg +0 -0
- package/stills/L43.jpg +0 -0
- package/stills/L44.jpg +0 -0
- package/stills/L45.jpg +0 -0
- package/stills/L46.jpg +0 -0
- package/stills/L47.jpg +0 -0
- package/stills/L48.jpg +0 -0
- package/stills/L49.jpg +0 -0
- package/stills/L5.jpg +0 -0
- package/stills/L50.jpg +0 -0
- package/stills/L51.jpg +0 -0
- package/stills/L52.jpg +0 -0
- package/stills/L53.jpg +0 -0
- package/stills/L54.jpg +0 -0
- package/stills/L55.jpg +0 -0
- package/stills/L56.jpg +0 -0
- package/stills/L57.jpg +0 -0
- package/stills/L58.jpg +0 -0
- package/stills/L59.jpg +0 -0
- package/stills/L6.jpg +0 -0
- package/stills/L7.jpg +0 -0
- package/stills/L8.jpg +0 -0
- package/stills/L9.jpg +0 -0
- package/stills/M1.jpg +0 -0
- package/stills/M10.jpg +0 -0
- package/stills/M11.jpg +0 -0
- package/stills/M12.jpg +0 -0
- package/stills/M13.jpg +0 -0
- package/stills/M14.jpg +0 -0
- package/stills/M15.jpg +0 -0
- package/stills/M16.jpg +0 -0
- package/stills/M17.jpg +0 -0
- package/stills/M18.jpg +0 -0
- package/stills/M19.jpg +0 -0
- package/stills/M2.jpg +0 -0
- package/stills/M20.jpg +0 -0
- package/stills/M21.jpg +0 -0
- package/stills/M22.jpg +0 -0
- package/stills/M23.jpg +0 -0
- package/stills/M24.jpg +0 -0
- package/stills/M25.jpg +0 -0
- package/stills/M26.jpg +0 -0
- package/stills/M27.jpg +0 -0
- package/stills/M28.jpg +0 -0
- package/stills/M29.jpg +0 -0
- package/stills/M3.jpg +0 -0
- package/stills/M30.jpg +0 -0
- package/stills/M31.jpg +0 -0
- package/stills/M32.jpg +0 -0
- package/stills/M33.jpg +0 -0
- package/stills/M34.jpg +0 -0
- package/stills/M35.jpg +0 -0
- package/stills/M36.jpg +0 -0
- package/stills/M37.jpg +0 -0
- package/stills/M38.jpg +0 -0
- package/stills/M39.jpg +0 -0
- package/stills/M4.jpg +0 -0
- package/stills/M40.jpg +0 -0
- package/stills/M5.jpg +0 -0
- package/stills/M6.jpg +0 -0
- package/stills/M7.jpg +0 -0
- package/stills/M8.jpg +0 -0
- package/stills/M9.jpg +0 -0
- package/stills/O1.jpg +0 -0
- package/stills/O10.jpg +0 -0
- package/stills/O11.jpg +0 -0
- package/stills/O12.jpg +0 -0
- package/stills/O13.jpg +0 -0
- package/stills/O14.jpg +0 -0
- package/stills/O15.jpg +0 -0
- package/stills/O16.jpg +0 -0
- package/stills/O17.jpg +0 -0
- package/stills/O18.jpg +0 -0
- package/stills/O19.jpg +0 -0
- package/stills/O2.jpg +0 -0
- package/stills/O20.jpg +0 -0
- package/stills/O21.jpg +0 -0
- package/stills/O22.jpg +0 -0
- package/stills/O23.jpg +0 -0
- package/stills/O24.jpg +0 -0
- package/stills/O25.jpg +0 -0
- package/stills/O26.jpg +0 -0
- package/stills/O27.jpg +0 -0
- package/stills/O28.jpg +0 -0
- package/stills/O29.jpg +0 -0
- package/stills/O3.jpg +0 -0
- package/stills/O30.jpg +0 -0
- package/stills/O31.jpg +0 -0
- package/stills/O32.jpg +0 -0
- package/stills/O33.jpg +0 -0
- package/stills/O34.jpg +0 -0
- package/stills/O35.jpg +0 -0
- package/stills/O36.jpg +0 -0
- package/stills/O37.jpg +0 -0
- package/stills/O38.jpg +0 -0
- package/stills/O39.jpg +0 -0
- package/stills/O4.jpg +0 -0
- package/stills/O40.jpg +0 -0
- package/stills/O41.jpg +0 -0
- package/stills/O42.jpg +0 -0
- package/stills/O43.jpg +0 -0
- package/stills/O44.jpg +0 -0
- package/stills/O45.jpg +0 -0
- package/stills/O46.jpg +0 -0
- package/stills/O47.jpg +0 -0
- package/stills/O48.jpg +0 -0
- package/stills/O49.jpg +0 -0
- package/stills/O5.jpg +0 -0
- package/stills/O50.jpg +0 -0
- package/stills/O51.jpg +0 -0
- package/stills/O52.jpg +0 -0
- package/stills/O53.jpg +0 -0
- package/stills/O54.jpg +0 -0
- package/stills/O55.jpg +0 -0
- package/stills/O56.jpg +0 -0
- package/stills/O57.jpg +0 -0
- package/stills/O58.jpg +0 -0
- package/stills/O59.jpg +0 -0
- package/stills/O6.jpg +0 -0
- package/stills/O60.jpg +0 -0
- package/stills/O61.jpg +0 -0
- package/stills/O62.jpg +0 -0
- package/stills/O63.jpg +0 -0
- package/stills/O64.jpg +0 -0
- package/stills/O65.jpg +0 -0
- package/stills/O66.jpg +0 -0
- package/stills/O67.jpg +0 -0
- package/stills/O68.jpg +0 -0
- package/stills/O69.jpg +0 -0
- package/stills/O7.jpg +0 -0
- package/stills/O70.jpg +0 -0
- package/stills/O71.jpg +0 -0
- package/stills/O72.jpg +0 -0
- package/stills/O73.jpg +0 -0
- package/stills/O74.jpg +0 -0
- package/stills/O75.jpg +0 -0
- package/stills/O76.jpg +0 -0
- package/stills/O77.jpg +0 -0
- package/stills/O78.jpg +0 -0
- package/stills/O79.jpg +0 -0
- package/stills/O8.jpg +0 -0
- package/stills/O80.jpg +0 -0
- package/stills/O81.jpg +0 -0
- package/stills/O82.jpg +0 -0
- package/stills/O83.jpg +0 -0
- package/stills/O84.jpg +0 -0
- package/stills/O85.jpg +0 -0
- package/stills/O86.jpg +0 -0
- package/stills/O87.jpg +0 -0
- package/stills/O88.jpg +0 -0
- package/stills/O89.jpg +0 -0
- package/stills/O9.jpg +0 -0
- package/stills/O90.jpg +0 -0
- package/stills/O91.jpg +0 -0
- package/stills/O92.jpg +0 -0
- package/stills/O93.jpg +0 -0
- package/stills/O94.jpg +0 -0
- package/stills/O95.jpg +0 -0
- package/stills/O96.jpg +0 -0
- package/stills/O97.jpg +0 -0
- package/stills/O98.jpg +0 -0
- package/stills/R1.jpg +0 -0
- package/stills/R2.jpg +0 -0
- package/stills/R3.jpg +0 -0
- package/stills/R4.jpg +0 -0
- package/stills/R5.jpg +0 -0
- package/stills/R6.jpg +0 -0
- package/stills/R7.jpg +0 -0
- package/stills/V1.jpg +0 -0
- package/stills/V10.jpg +0 -0
- package/stills/V11.jpg +0 -0
- package/stills/V12.jpg +0 -0
- package/stills/V13.jpg +0 -0
- package/stills/V14.jpg +0 -0
- package/stills/V15.jpg +0 -0
- package/stills/V2.jpg +0 -0
- package/stills/V3.jpg +0 -0
- package/stills/V4.jpg +0 -0
- package/stills/V5.jpg +0 -0
- package/stills/V6.jpg +0 -0
- package/stills/V7.jpg +0 -0
- package/stills/V8.jpg +0 -0
- package/stills/V9.jpg +0 -0
- package/stills/X1.jpg +0 -0
- package/stills/X10.jpg +0 -0
- package/stills/X11.jpg +0 -0
- package/stills/X12.jpg +0 -0
- package/stills/X13.jpg +0 -0
- package/stills/X14.jpg +0 -0
- package/stills/X15.jpg +0 -0
- package/stills/X16.jpg +0 -0
- package/stills/X17.jpg +0 -0
- package/stills/X18.jpg +0 -0
- package/stills/X19.jpg +0 -0
- package/stills/X2.jpg +0 -0
- package/stills/X20.jpg +0 -0
- package/stills/X21.jpg +0 -0
- package/stills/X22.jpg +0 -0
- package/stills/X23.jpg +0 -0
- package/stills/X24.jpg +0 -0
- package/stills/X25.jpg +0 -0
- package/stills/X26.jpg +0 -0
- package/stills/X27.jpg +0 -0
- package/stills/X28.jpg +0 -0
- package/stills/X29.jpg +0 -0
- package/stills/X3.jpg +0 -0
- package/stills/X30.jpg +0 -0
- package/stills/X31.jpg +0 -0
- package/stills/X4.jpg +0 -0
- package/stills/X5.jpg +0 -0
- package/stills/X6.jpg +0 -0
- package/stills/X7.jpg +0 -0
- package/stills/X8.jpg +0 -0
- package/stills/X9.jpg +0 -0
- package/stills/Z1.jpg +0 -0
- package/stills/Z10.jpg +0 -0
- package/stills/Z11.jpg +0 -0
- package/stills/Z12.jpg +0 -0
- package/stills/Z13.jpg +0 -0
- package/stills/Z14.jpg +0 -0
- package/stills/Z15.jpg +0 -0
- package/stills/Z2.jpg +0 -0
- package/stills/Z3.jpg +0 -0
- package/stills/Z4.jpg +0 -0
- package/stills/Z5.jpg +0 -0
- package/stills/Z6.jpg +0 -0
- package/stills/Z7.jpg +0 -0
- package/stills/Z8.jpg +0 -0
- package/stills/Z9.jpg +0 -0
- package/stills/approved.json +198 -0
- package/stills/index.json +1 -0
- package/styles.json +5090 -0
- package/tools.js +788 -0
package/flow.js
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The creation workflow, injected as a system-prompt section.
|
|
3
|
+
*
|
|
4
|
+
* This is the real gate for the staged flow. Tools can only advise; the prompt is
|
|
5
|
+
* what keeps the Agent from jumping straight from "帮我做个视频" to writing code
|
|
6
|
+
* before the user has seen a reference still and signed off on a storyboard.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const FLOW_SECTION_NAME = 'code-animation-flow';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Policy band: the harness places plan policy at 500 and tool documentation from
|
|
13
|
+
* 1000 up, so a workflow policy belongs just before those.
|
|
14
|
+
*/
|
|
15
|
+
export const FLOW_SECTION_ORDER = 450;
|
|
16
|
+
|
|
17
|
+
export const FLOW_PROMPT = `## 创作流程
|
|
18
|
+
|
|
19
|
+
在这个模式下你是导演,不是代码生成器。用户来找你是为了做出一个片子,不是要一段代码。
|
|
20
|
+
按下面的顺序推进;每一步都要拿到用户明确的答复才能往下走。
|
|
21
|
+
|
|
22
|
+
### 第 1 步 · 问清要做什么
|
|
23
|
+
只要用户还没明确具体做什么,**必须立即调用 ask_user_question 工具**向用户提问,弹出交互选项卡片:
|
|
24
|
+
- 问题 1(必须):询问视频主题——"这个片子要表达什么主题?有没有核心文案或关键词?"
|
|
25
|
+
- 问题 2:画幅与平台(提供选项:横屏 16:9、竖屏 9:16、方形 1:1)
|
|
26
|
+
- 问题 3:时长(提供选项:3-5秒 极速冲击、8-10秒 标准短片、12秒以上 丰富叙事)
|
|
27
|
+
|
|
28
|
+
绝对不要只用普通聊天文字问用户,必须调用 ask_user_question 工具弹出输入和选项!
|
|
29
|
+
|
|
30
|
+
### 第 2 步 · 推荐风格,并把参考图画给用户看
|
|
31
|
+
按第 1 步的答案挑 2–3 条候选,先 list_styles 确认它们存在,然后**必须调用 show_stills** 把参考静帧贴进对话。
|
|
32
|
+
只用文字描述风格是不够的:用户要先看到画面才能判断。
|
|
33
|
+
|
|
34
|
+
每条候选都要说清:编号、名称、画面长什么样、为什么适合这个主题。
|
|
35
|
+
选风格时不要把多条约束机械叠加——主画风、运动方式、版式各选一条,各自负责一件事。
|
|
36
|
+
|
|
37
|
+
### 第 3 步 · 等用户拍板风格(硬门禁)
|
|
38
|
+
展示完 show_stills 之后,**必须立即调用 ask_user_question 工具**让用户点击选择风格!
|
|
39
|
+
在 ask_user_question 的 options 里列出展示的各款风格(例如 label: "C1 水墨晕染"、label: "L19 写意山水"、label: "R6 东方赛博"、以及 label: "换一批风格")。
|
|
40
|
+
**绝对禁止用普通文本让用户回复,必须调用 ask_user_question 工具生成点击按钮!**
|
|
41
|
+
|
|
42
|
+
- 用户点击选定了 → get_style 取完整规范,进入第 4 步。
|
|
43
|
+
- 都不满意 → 回第 2 步换一批候选,或问清他想要什么感觉再选。
|
|
44
|
+
- 用户说"你定" → 你选最匹配的一条,但仍要把选择说清楚并给出理由。
|
|
45
|
+
|
|
46
|
+
### 第 4 步 · 出分镜草稿与配乐卡点谱
|
|
47
|
+
读完风格规范后,把片子拆成 3–8 个镜头,调用 save_storyboard 提交草稿。每个镜头写清:
|
|
48
|
+
- 起止时间(秒)与时长
|
|
49
|
+
- 画面内容:谁 / 在哪 / 在做什么
|
|
50
|
+
- 画面上的文字:字幕与标题,写原文
|
|
51
|
+
- 运镜与转场
|
|
52
|
+
- **镜头内关键动作与卡点(cues)**:除了镜头切点外,镜头内部若有关键视觉动作(如「0.8s 粒子汇聚成核心标题」、「2.4s 数据狂飙冲顶」、「4.1s 画面反转炸开」),必须在 cues 数组中精确标注时间秒数与类型(impact 重击、drop 下潜重低音、hit 清脆打击、rise 聚能扫频、whoosh 呼啸)!
|
|
53
|
+
|
|
54
|
+
#### 文案标准(字幕与标题)
|
|
55
|
+
|
|
56
|
+
字幕不是文章。观众只有这条字幕停留的那一两秒,读不完也没有耐心读第二遍。
|
|
57
|
+
默认要**具体**,不是**抒情**,也不是**口语**——口语只是换一种空话。
|
|
58
|
+
|
|
59
|
+
**硬规则(save_storyboard 会校验)**
|
|
60
|
+
|
|
61
|
+
1. 单条字幕 ≤ 镜头时长 × 5.5 字。2 秒镜头约 11 字,5 秒镜头约 27 字。
|
|
62
|
+
超了就删内容,不要缩字号、不要压行、不要用「…」拖过去。
|
|
63
|
+
2. 一条字幕只说一件事。两件事就拆两场。
|
|
64
|
+
|
|
65
|
+
**写法**
|
|
66
|
+
|
|
67
|
+
3. 至少一个可核查的具体信息:数字、专名、时间、地点、动作、前后对比。
|
|
68
|
+
一个都没有,说明这条还没想清楚,回去想清楚再写。
|
|
69
|
+
4. 动词开头优先。能写「改到第七版」就不写「支持多次修改」。
|
|
70
|
+
5. 句长要有变化。整条都是四字格或六字格,读起来就像广告词。
|
|
71
|
+
6. **保留事实的强度。**「从 3.2 秒压到 0.8 秒」不许改写成「大幅提速」;
|
|
72
|
+
反过来,「大幅提速」也不许擅自补成具体数字——那是编数据。
|
|
73
|
+
|
|
74
|
+
**禁止**
|
|
75
|
+
|
|
76
|
+
7. 抽象名词堆叠:赋能、闭环、抓手、生态、颗粒度、维度、链路、矩阵。
|
|
77
|
+
8. 四字格排比:化繁为简、由浅入深、层层递进。
|
|
78
|
+
9. 空泛评价:极致、震撼、完美、惊艳、颠覆、前所未有。
|
|
79
|
+
10. 连接词开场:首先、其次、此外、综上所述、值得一提的是。
|
|
80
|
+
11. 万能句式:让我们一起…/这不仅…更是…/从…到…。
|
|
81
|
+
12. 语气词与网络流行语:啊呢吧啦、绝绝子、yyds、狠狠爱了。
|
|
82
|
+
|
|
83
|
+
**改写时只做减法。** 删掉不承载信息的包装语,不换同义词,不把几个词压成一个概括词。
|
|
84
|
+
用户没有明确说「写平实点」,就不要降低原有判断的强度。片名(title)可以留白、可以有态度,
|
|
85
|
+
但同样不许堆形容词。
|
|
86
|
+
|
|
87
|
+
**对照**(左边这种会被打回)
|
|
88
|
+
|
|
89
|
+
- 「让我们一起探索这款产品如何赋能创作流程」→「三秒一版分镜。改到第七版,它还记着第一版」
|
|
90
|
+
- 「以极致性能带来前所未有的体验」→「首屏从 3.2 秒压到 0.8 秒」
|
|
91
|
+
- 「化繁为简,让复杂工作变得轻松」→「以前开四个窗口对着抄,现在一屏」
|
|
92
|
+
|
|
93
|
+
#### 音乐风格提示词(music)的推导规则(无任何预设限制):
|
|
94
|
+
不要使用机械死板的固定预设!音乐必须像专业电影原声一样,根据【视频画风 + 剧情情绪起伏】量身定制自由的音乐提示词:
|
|
95
|
+
1. **调性对齐画风**:国风/水墨应是「古筝泛音悠扬、竹笛空灵铺底、兼有温润大堂鼓」;赛博霓虹应是「失真酸性贝斯、极速脉冲琶音、冷冽数字合成音」;扁平科普应是「轻快顿音拨弦、明朗马林巴、清脆鼓点」;史诗宏大应是「深邃弦乐群长音、雷霆重低音下潜与重击」。
|
|
96
|
+
2. **情绪动态曲线**:开头如何铺垫、高潮在第几秒爆发、收尾如何淡出。
|
|
97
|
+
3. 把这段自由定制的音乐描述填入 save_storyboard 与 save_animation 的 music 字段,引擎会实时合成专属音轨!
|
|
98
|
+
|
|
99
|
+
**每个镜头切点与内部 cues 都会成为配乐的绝对卡点!** 引擎会在该毫秒分毫不差地触发声学重音与转场冲击,画面动、声音同时炸裂。
|
|
100
|
+
|
|
101
|
+
### 第 5 步 · 等用户拍板分镜(硬门禁)
|
|
102
|
+
提交分镜后,**必须调用 ask_user_question 工具**让用户确认分镜,提供选项(如 label: "确认分镜,开始制作"、label: "需要调整分镜或文案")。
|
|
103
|
+
绝对禁止只用普通文本让用户回复!
|
|
104
|
+
用户确认之前不要进入第 6 步。提了修改意见就改分镜并重新提交。
|
|
105
|
+
|
|
106
|
+
### 第 6 步 · 写动画与配乐,然后交付
|
|
107
|
+
|
|
108
|
+
**两样东西都是你现写的代码,没有预设素材:**
|
|
109
|
+
1. 动画 HTML(画面)
|
|
110
|
+
2. 配乐合成代码(声音)
|
|
111
|
+
|
|
112
|
+
现在动手写 HTML + Canvas 动画,然后调用 save_animation。
|
|
113
|
+
|
|
114
|
+
画面必须满足:
|
|
115
|
+
- 页面 HTML 必须显式包含一个 <canvas> 元素,画面全部用 Canvas 2D 或 WebGL 绘制,绝对禁止写纯 DOM/CSS 动效(DOM 元素无法被浏览器 MediaRecorder 捕获导出为视频)。
|
|
116
|
+
- 必须实现 window.renderAt(t) 接口(t 为秒,同一时刻 t 渲染同一帧)。
|
|
117
|
+
|
|
118
|
+
#### 卡点:先把 beats 导出来,再写配乐
|
|
119
|
+
|
|
120
|
+
这是整条流程里最要紧的一步。**动画代码是你自己写的,所以每个画面动作发生在第几秒,你本来就知道。**
|
|
121
|
+
不要让它只存在于你的脑子里——写进 save_animation 的 beats 参数。
|
|
122
|
+
|
|
123
|
+
写动画时,把每一个「画面值得被听见」的时刻记下来:
|
|
124
|
+
|
|
125
|
+
- 文字出现 / 砸下 / 逐字显完
|
|
126
|
+
- 元素爆开、聚合、断裂
|
|
127
|
+
- 镜头切换、故障闪烁、闪白
|
|
128
|
+
- 数字跳到终点、进度条走满
|
|
129
|
+
- 最重要那一帧的定格
|
|
130
|
+
|
|
131
|
+
每条给 \`time\`(精确到 0.01 秒,如 2.87 而不是 3)、\`type\`、\`label\`、\`power\`(0–1)。
|
|
132
|
+
|
|
133
|
+
\`\`\`json
|
|
134
|
+
"beats": [
|
|
135
|
+
{ "time": 0.41, "type": "rise", "label": "砖墙从暗处浮现", "power": 0.5 },
|
|
136
|
+
{ "time": 1.24, "type": "hit", "label": "第一个字母通电", "power": 0.7 },
|
|
137
|
+
{ "time": 2.87, "type": "impact", "label": "故障闪烁撕裂", "power": 1.0 },
|
|
138
|
+
{ "time": 3.72, "type": "impact", "label": "中文标题砸下", "power": 0.95 },
|
|
139
|
+
{ "time": 4.55, "type": "hit", "label": "副标题浮现", "power": 0.6 }
|
|
140
|
+
]
|
|
141
|
+
\`\`\`
|
|
142
|
+
|
|
143
|
+
**beats 会原样传进 score_js 的 beats 形参,时间误差是 0 毫秒**(已实测)。
|
|
144
|
+
你的配乐只要写 \`for (const b of beats) 打击(b.time, b.power)\` 就是精确卡点,不需要猜。
|
|
145
|
+
|
|
146
|
+
一次完整动作最好是:**rise 在峰值前 0.3–0.5 秒起势,impact 正好落在峰值上,之后留一点余响。**
|
|
147
|
+
|
|
148
|
+
##### power 决定这是普通卡点还是高潮卡点
|
|
149
|
+
|
|
150
|
+
引擎按 power 自动分两档处理铺底,**你必须把 power 标对**,否则高潮会被当普通处理:
|
|
151
|
+
|
|
152
|
+
| power | 档位 | 引擎对铺底做什么 | 用在哪 |
|
|
153
|
+
|---|---|---|---|
|
|
154
|
+
| 0.0–0.71 | 普通卡点 | 重音前后轻微下压,**律动不停** | 文字出现、元素入场、一般转场 |
|
|
155
|
+
| 0.72–1.0 | **高潮卡点** | 铺底提前 200–320ms **渐出**到近乎无声,重音落下后回升 | 全片最重的那一下、故障炸裂、标题砸下 |
|
|
156
|
+
|
|
157
|
+
普通卡点的意思是「音乐里该有的一个重拍」,不该打断律动;
|
|
158
|
+
高潮卡点的意思是「此刻除了这一下什么都不该响」。
|
|
159
|
+
|
|
160
|
+
**一片里高潮卡点不要超过 2–3 个**,多了就不稀罕了。其余都属于普通卡点。
|
|
161
|
+
|
|
162
|
+
渐出不是硬切——引擎已经把铺底做成分段下降(快→慢→贴近静音),听感上是音乐让位而不是断带。
|
|
163
|
+
你不要在 score_js 里自己再做一遍这件事,只要把 power 标对即可。
|
|
164
|
+
|
|
165
|
+
#### 配乐:写 score_js,不要依赖内置引擎
|
|
166
|
+
|
|
167
|
+
save_animation 的 score_js 参数是一段 Web Audio 合成代码,直接决定这条片子听起来是什么声音。
|
|
168
|
+
**不要把它当装饰跳过。** 内置引擎只是兜底,音色是为通用场景准备的,配不上具体片子。
|
|
169
|
+
|
|
170
|
+
代码是一个**函数体**(不要写 \`function(...)\` 头,不要 return),可用形参:
|
|
171
|
+
- \`ctx\` — OfflineAudioContext
|
|
172
|
+
- \`dest\` — 输出节点:**重音、打击乐接到这里**,不受铺底渐出影响
|
|
173
|
+
- \`bed\` — 铺底总线:**持续的旋律、和弦、律动接到这里**,引擎会在卡点前后对它做渐出/下压
|
|
174
|
+
- \`duration\` — 片长(秒)
|
|
175
|
+
- \`beats\` — 你刚导出的精确节奏点,\`[{time, type, label, power}]\`,**卡点用这个**
|
|
176
|
+
- \`cuts\` — 分镜切点秒数数组(比 beats 粗,只在没有 beats 时用)
|
|
177
|
+
- \`cues\` — 分镜里的镜头重点
|
|
178
|
+
- \`rand()\` — 可复现随机
|
|
179
|
+
- \`hz(midi)\` — MIDI 转频率
|
|
180
|
+
|
|
181
|
+
**分线是卡点能不能听出来的关键**:铺底全接 \`bed\`,重音全接 \`dest\`。
|
|
182
|
+
如果两者都接到同一个总线,引擎就没法在卡点前让铺底让位,重音会被淹没。
|
|
183
|
+
|
|
184
|
+
写法要点:
|
|
185
|
+
- 用 ctx.createOscillator / createBufferSource / createBiquadFilter / createGain / createConvolver / createWaveShaper 现场搭音色。
|
|
186
|
+
- **每一个 beats 时间点都必须有明确的声音事件**,直接用 start(b.time) 落在那个秒数上。这是卡点的全部意义。
|
|
187
|
+
- 重击之前让铺底让位(那 0.1 秒内把伴奏增益压到 0.2 左右),重击才听得出来。
|
|
188
|
+
- 背景铺底要比卡点轻很多(大约 1/10),否则重击会被淹掉。
|
|
189
|
+
- 所有节点必须在 duration 内收尾,函数里不要 await。
|
|
190
|
+
|
|
191
|
+
#### 音色配方表(28 条,已逐个试听校准)
|
|
192
|
+
|
|
193
|
+
「听感」那栏是它应该听起来像什么。如果你写出来的东西和这栏不符,说明配方用错了,重写。
|
|
194
|
+
|
|
195
|
+
| 音色 | 听感 | 做法 |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| 底鼓 kick | 低频冲击,几乎无音高感,短促 | 正弦 160→44Hz 在 70ms 内扫完,指数衰减 0.16s,叠 6ms 噪声瞬态 |
|
|
198
|
+
| 军鼓 snare | 中频「沙」加一点鼓皮音 | 2200Hz 带通噪声衰 0.07s,叠 190Hz 与 330Hz 正弦短衰减 |
|
|
199
|
+
| 闭合踩镲 | 极短极亮,只有「呲」一声 | 7000Hz 高通噪声,衰减 0.012s |
|
|
200
|
+
| 开镲 open hat | 同样高频,但尾巴拖长 | 同上,衰减 0.18s |
|
|
201
|
+
| 通鼓 tom | 有明确音高的鼓,比军鼓低沉 | 正弦 170→105Hz,衰减 0.22s |
|
|
202
|
+
| 吊镲 crash | 宽频金属噪声,尾巴很长 | 4200Hz 高通噪声乘非谐波调制,衰减 0.42s |
|
|
203
|
+
| 古筝拨弦 | 明亮拨弦,「啪」的起音加金属余韵 | Karplus-Strong,循环反馈 0.998,见示例一 |
|
|
204
|
+
| 清音吉他 | 同一类拨弦但更闷、衰更快 | Karplus-Strong 反馈 0.982,输出再过 1800Hz 低通 |
|
|
205
|
+
| 失真主音 | 沙哑持续,有攻击性 | 两路失谐锯齿叠加,tanh 硬削波,再过 900Hz 带通 |
|
|
206
|
+
| 竹笛 | 柔和持续音,带明显气声和轻微颤动 | 正弦加 0.4% 颤音,混 2400Hz 带通噪声 0.35,60ms 渐入 |
|
|
207
|
+
| 弦乐铺底 | 慢起音、宽而厚、无节奏 | 三路失谐锯齿加一个八度,1400Hz 低通,350ms 渐入 |
|
|
208
|
+
| 低频贝斯 sub | 极低频,音高明确但更像震动 | 55Hz 正弦加 110Hz 二次谐波 0.25 |
|
|
209
|
+
| 钟铃 bell | 金属非谐波,不是乐器音 | FM:载波 660Hz,调制比 3.51,调制指数从 6 指数衰到 0 |
|
|
210
|
+
| 马林巴 | 木头味,短促,有余音共鸣 | 523Hz 正弦衰 0.22,叠 4 倍频衰 0.06 与 9.2 倍频衰 0.02 |
|
|
211
|
+
| 上行扫频 riser | 亮度和音高一起往上冲 | 噪声过 400→7400Hz 扫频低通,叠 200→1100Hz 正弦 |
|
|
212
|
+
| 落重音 impact | 比底鼓更重更长,用在切点 | 90→30Hz 正弦衰 0.45,叠 2600Hz 高通噪声衰 0.3 与 240→60Hz 短瞬态 |
|
|
213
|
+
| 808 底鼓 | 长拖尾,有音高,像「轰」 | 72→30Hz 正弦,550ms 扫频,衰减 0.62s,轻微饱和 |
|
|
214
|
+
| 酸性贝斯 303 | 锯齿过强共振,每个音「呜」一下 | 锯齿过 Q=11 状态变量滤波,截止 240→2840Hz 指数衰 0.075s,见示例二 |
|
|
215
|
+
| 超级锯齿 supersaw | 多层失谐,宽、亮、有合唱感 | 七路锯齿失谐 ±0.09 半音叠加,6000Hz 低通,见示例三 |
|
|
216
|
+
| 合成拨弦 pluck | 起音很亮然后瞬间变闷 | 锯齿过 Q=3 状态变量滤波,截止 400→5600Hz 衰 0.05s |
|
|
217
|
+
| 琶音 arp | 十六分音符快速跑动,每个音很短 | 方波每个十六分一个音,滤波截止瞬开瞬关 |
|
|
218
|
+
| FM 贝斯 | 低频但起音带金属「叮」 | 载波 55Hz,调制比 2,调制指数 420 指数衰 0.035s |
|
|
219
|
+
| 门限铺底 trance gate | 和弦被节奏切成一格一格 | 锯齿和弦过 2200Hz 低通,再乘八分音符硬门限 |
|
|
220
|
+
| 芯片方波 chip | 单薄、刺耳,红白机味 | 25% 占空比方波,短音符 |
|
|
221
|
+
| 抖动贝斯 wobble | 周期性「哇—哇—」 | 锯齿过 Q=9 状态变量滤波,截止被 4Hz LFO 在 200–2400Hz 扫动 |
|
|
222
|
+
| 下坠噪声 downlifter | 亮度和音高一起往下掉 | 噪声过 7000→500Hz 反向扫频低通,叠 700→100Hz 正弦 |
|
|
223
|
+
| 侧链泵动 sidechain | 铺底每拍被压下去再弹回 | 铺底增益按 0.18 + 0.82 × (拍内相位 / 0.55) 起伏 |
|
|
224
|
+
| 四踩律动 four-on-floor | 每拍一记底鼓,二四拍军鼓 | 底鼓每拍,二四拍噪声军鼓,八分音符踩镲 |
|
|
225
|
+
|
|
226
|
+
#### 完整示例一:拨弦(古筝 / 吉他)
|
|
227
|
+
|
|
228
|
+
~~~js
|
|
229
|
+
// Karplus-Strong:噪声填一个周期长度的循环缓冲,每绕一圈丢一点高频。
|
|
230
|
+
// 这个衰减过程就是「被拨动的弦」和「一个 beep」的区别。
|
|
231
|
+
function pluckBuffer(freq, seconds, feedback) {
|
|
232
|
+
const n = Math.floor(seconds * ctx.sampleRate);
|
|
233
|
+
const buf = ctx.createBuffer(1, n, ctx.sampleRate);
|
|
234
|
+
const d = buf.getChannelData(0);
|
|
235
|
+
const period = Math.max(2, Math.round(ctx.sampleRate / freq));
|
|
236
|
+
const line = new Float32Array(period);
|
|
237
|
+
for (let i = 0; i < period; i++) line[i] = rand() * 2 - 1;
|
|
238
|
+
let prev = 0;
|
|
239
|
+
let idx = 0;
|
|
240
|
+
for (let i = 0; i < n; i++) {
|
|
241
|
+
const cur = line[idx];
|
|
242
|
+
line[idx] = 0.5 * (cur + prev) * feedback;
|
|
243
|
+
prev = cur;
|
|
244
|
+
idx = (idx + 1) % period;
|
|
245
|
+
d[i] = cur;
|
|
246
|
+
}
|
|
247
|
+
return buf;
|
|
248
|
+
}
|
|
249
|
+
// 古筝:feedback 0.998,余音长且带金属感
|
|
250
|
+
// 清音吉他:feedback 0.982,再接一个 1800Hz 低通,更闷更短
|
|
251
|
+
~~~
|
|
252
|
+
|
|
253
|
+
#### 完整示例二:酸性贝斯(滤波器扫动)
|
|
254
|
+
|
|
255
|
+
~~~js
|
|
256
|
+
// 状态变量滤波器,截止频率可以逐样本变化。
|
|
257
|
+
// biquad 做不到平滑扫频(系数按 buffer 固定),而扫频正是 acid 和 wobble 的灵魂。
|
|
258
|
+
function svf(input, cutoffAt, q) {
|
|
259
|
+
const out = new Float32Array(input.length);
|
|
260
|
+
const maxFc = ctx.sampleRate / 8; // 再高会不稳定
|
|
261
|
+
let low = 0;
|
|
262
|
+
let band = 0;
|
|
263
|
+
for (let i = 0; i < input.length; i++) {
|
|
264
|
+
const fc = Math.max(20, Math.min(maxFc, cutoffAt(i / ctx.sampleRate)));
|
|
265
|
+
const f = 2 * Math.sin((Math.PI * fc) / ctx.sampleRate);
|
|
266
|
+
const high = input[i] - low - (1 / q) * band;
|
|
267
|
+
band += f * high;
|
|
268
|
+
low += f * band;
|
|
269
|
+
out[i] = low;
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function sawBuffer(freq, seconds) {
|
|
275
|
+
const n = Math.floor(seconds * ctx.sampleRate);
|
|
276
|
+
const buf = ctx.createBuffer(1, n, ctx.sampleRate);
|
|
277
|
+
const d = buf.getChannelData(0);
|
|
278
|
+
for (let i = 0; i < n; i++) d[i] = 2 * (((freq * i) / ctx.sampleRate) % 1) - 1;
|
|
279
|
+
return buf;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 303:锯齿过 Q=11 的滤波器,截止从 2640Hz 在 75ms 内指数关掉
|
|
283
|
+
const raw = sawBuffer(55, 0.25).getChannelData(0);
|
|
284
|
+
const acid = svf(raw, (t) => 240 + 2400 * Math.exp(-t / 0.075), 11);
|
|
285
|
+
~~~
|
|
286
|
+
|
|
287
|
+
#### 完整示例三:超级锯齿(宽亮主音)
|
|
288
|
+
|
|
289
|
+
~~~js
|
|
290
|
+
// 七路失谐锯齿叠加,这是 trance / EDM 那种「宽」的来源。
|
|
291
|
+
const detunes = [-0.09, -0.06, -0.03, 0, 0.03, 0.06, 0.09];
|
|
292
|
+
const mix = new Float32Array(Math.floor(duration * ctx.sampleRate));
|
|
293
|
+
for (const d of detunes) {
|
|
294
|
+
const v = sawBuffer(261.6 * Math.pow(2, d / 12), duration).getChannelData(0);
|
|
295
|
+
for (let i = 0; i < mix.length; i++) mix[i] += v[i] / detunes.length;
|
|
296
|
+
}
|
|
297
|
+
// 再接 6000Hz 低通削掉刺耳高频
|
|
298
|
+
~~~
|
|
299
|
+
|
|
300
|
+
上面的示例只演示关键部分:master、sawBuffer 这些辅助函数要在你自己的代码里完整定义。
|
|
301
|
+
|
|
302
|
+
写完用 renderAt 自检首帧、中段、尾帧:不是黑屏、文字不溢出、不同时间画面确实在动。
|
|
303
|
+
把"未走分镜确认"之类的提示当回事——那是流程被跳过的信号。
|
|
304
|
+
|
|
305
|
+
### 不要自己造轮子
|
|
306
|
+
|
|
307
|
+
出片这件事已经由插件接管,以下做法都是错的,会白费功夫:
|
|
308
|
+
|
|
309
|
+
- **不要写录制脚本**(录屏、逐帧截图、record.mjs 之类)——导出一键完成。
|
|
310
|
+
- **不要找 ffmpeg**,不要检查它是否安装,不要建议用户去装。本流程完全不需要它。
|
|
311
|
+
- **不要用 playwright / puppeteer** 打开动画去截图。
|
|
312
|
+
- **不要手工拼视频文件**,也不要写自定义的 muxer。
|
|
313
|
+
|
|
314
|
+
你要做的只有两件事:写 HTML、写配乐代码,然后调用 save_animation。预览、合成、导出都由对话末尾的卡片完成。
|
|
315
|
+
|
|
316
|
+
### 交付物在哪里
|
|
317
|
+
|
|
318
|
+
- 动画 HTML 由 save_animation 落盘,卡片内可直接预览。
|
|
319
|
+
- 导出视频时,浏览器会把成片**写入会话工作目录下的 code-animation/ 子目录**,卡片上会显示完整路径。
|
|
320
|
+
- 不要为了"让用户能找到文件"而额外搬运或复制文件;卡片已经显示了准确路径。
|
|
321
|
+
|
|
322
|
+
### 唯一可以跳步的情况
|
|
323
|
+
用户在一句话里已经把风格编号和内容都给全了("用 B3 做一个 3 秒的粒子聚形,内容是 XXX")。
|
|
324
|
+
这时可以跳过第 1–3 步,但**第 4、5 步的分镜确认不能跳过**。`;
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Register the workflow section.
|
|
328
|
+
* @param systemPrompt - the \`ctx.systemPrompt\` registry of the realm that should own it.
|
|
329
|
+
* @returns a disposer, or undefined when the registry is unavailable.
|
|
330
|
+
*/
|
|
331
|
+
export function registerFlowSection(systemPrompt) {
|
|
332
|
+
if (!systemPrompt) return undefined;
|
|
333
|
+
return systemPrompt.section({
|
|
334
|
+
name: FLOW_SECTION_NAME,
|
|
335
|
+
order: FLOW_SECTION_ORDER,
|
|
336
|
+
text: FLOW_PROMPT,
|
|
337
|
+
});
|
|
338
|
+
}
|
package/icon.svg
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Code Animation Studio">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="cas-g" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#ff8a3d"/>
|
|
5
|
+
<stop offset="1" stop-color="#e0457b"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect x="6" y="10" width="52" height="44" rx="9" fill="url(#cas-g)"/>
|
|
9
|
+
<path d="M6 24h52" stroke="#12121a" stroke-opacity=".28" stroke-width="2"/>
|
|
10
|
+
<path d="M17 10l7 12M30 10l7 12M43 10l7 12" stroke="#12121a" stroke-opacity=".28" stroke-width="2" stroke-linecap="round"/>
|
|
11
|
+
<path d="M26 34.5v11.5l9.5-5.75z" fill="#fff"/>
|
|
12
|
+
<circle cx="47" cy="43" r="3.2" fill="#fff" fill-opacity=".9"/>
|
|
13
|
+
</svg>
|
package/index.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host half of the Code Animation Studio bundle.
|
|
3
|
+
*
|
|
4
|
+
* The bundle is used twice, on purpose, with different configs:
|
|
5
|
+
* - once at the profile root (`tools: false`) to serve catalog assets and the
|
|
6
|
+
* animations the Agent wrote;
|
|
7
|
+
* - once inside the 创作模式 preset (`tools: true`) to mount the creation tools
|
|
8
|
+
* in the sessions created under that mode.
|
|
9
|
+
*
|
|
10
|
+
* Routes are profile-wide, so only the root row registers them; the preset row is
|
|
11
|
+
* mounted per preset and would otherwise collide on the same prefix.
|
|
12
|
+
*/
|
|
13
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
import { BUILD_REV, EXPORT_DIR_NAME, OUTPUT_DIR, STILLS_DIR, TOOL_NAMES, catalog, isApproved } from './data.js';
|
|
18
|
+
import { registerFlowSection } from './flow.js';
|
|
19
|
+
import { registerCreationTools } from './tools.js';
|
|
20
|
+
|
|
21
|
+
const PREFIX = '/code-animation-api';
|
|
22
|
+
const LOG_FILE = fileURLToPath(new URL('./api.log', import.meta.url));
|
|
23
|
+
/** Rendered clips reach the host as one body; 8s of 1080p is tens of megabytes. */
|
|
24
|
+
const EXPORT_MAX_BYTES = 512 * 1024 * 1024;
|
|
25
|
+
|
|
26
|
+
function trace(line) {
|
|
27
|
+
try {
|
|
28
|
+
appendFileSync(LOG_FILE, `${new Date().toISOString()} ${line}\n`);
|
|
29
|
+
} catch {
|
|
30
|
+
/* diagnostics must never break a request */
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function send(res, status, body, type) {
|
|
35
|
+
res.writeHead(status, {
|
|
36
|
+
'content-type': type || 'text/plain; charset=utf-8',
|
|
37
|
+
'cache-control': 'no-store',
|
|
38
|
+
'content-length': Buffer.byteLength(body),
|
|
39
|
+
});
|
|
40
|
+
res.end(body);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** One exact id, no traversal: the id grammar is the allowlist. */
|
|
44
|
+
function safeId(raw) {
|
|
45
|
+
const id = String(raw || '').split('?')[0];
|
|
46
|
+
return /^[A-Za-z0-9_-]{1,80}$/.test(id) ? id : '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Keep a client-supplied name to a single safe path segment. */
|
|
50
|
+
function safeFileName(raw, fallback) {
|
|
51
|
+
const base = String(raw || '').split(/[\\/]/).pop() || '';
|
|
52
|
+
const cleaned = base.replace(/[^\w.\-\u4e00-\u9fa5]+/g, '_').replace(/^[._]+/, '').slice(0, 120);
|
|
53
|
+
return cleaned || fallback;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve where an export lands.
|
|
58
|
+
*
|
|
59
|
+
* The session directory is used when the caller supplies one, so the clip appears
|
|
60
|
+
* beside the user's other files rather than inside the plugin package. A supplied
|
|
61
|
+
* path is accepted only when it is absolute, already a directory, and the final
|
|
62
|
+
* target stays inside it.
|
|
63
|
+
*/
|
|
64
|
+
function resolveExportDir(requested, fallbackRoot) {
|
|
65
|
+
// If user explicitly picked a directory, honor it directly
|
|
66
|
+
if (typeof requested === 'string' && requested.trim()) {
|
|
67
|
+
const chosen = resolve(requested.trim());
|
|
68
|
+
if (isAbsolute(chosen) && existsSync(chosen)) {
|
|
69
|
+
try {
|
|
70
|
+
if (statSync(chosen).isDirectory()) return chosen;
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Fallback to session working directory / code-animation
|
|
76
|
+
if (typeof fallbackRoot === 'string' && fallbackRoot.trim()) {
|
|
77
|
+
const root = resolve(fallbackRoot.trim());
|
|
78
|
+
if (isAbsolute(root) && existsSync(root)) {
|
|
79
|
+
try {
|
|
80
|
+
if (statSync(root).isDirectory()) return join(root, EXPORT_DIR_NAME);
|
|
81
|
+
} catch {}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return join(OUTPUT_DIR, 'exports');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function readBody(req, limit) {
|
|
89
|
+
return new Promise((resolvePromise, reject) => {
|
|
90
|
+
const chunks = [];
|
|
91
|
+
let size = 0;
|
|
92
|
+
req.on('data', (chunk) => {
|
|
93
|
+
size += chunk.length;
|
|
94
|
+
if (size > limit) {
|
|
95
|
+
reject(new Error(`请求体超过上限 ${Math.round(limit / 1048576)} MB`));
|
|
96
|
+
req.destroy();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
chunks.push(chunk);
|
|
100
|
+
});
|
|
101
|
+
req.on('end', () => resolvePromise(Buffer.concat(chunks)));
|
|
102
|
+
req.on('error', reject);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* No `inject` declaration: the same module is mounted twice with different configs,
|
|
108
|
+
* and each placement only owns one half. Declaring either service would leave the
|
|
109
|
+
* other placement waiting forever, so both are read lazily and each half no-ops
|
|
110
|
+
* when its service is absent.
|
|
111
|
+
*/
|
|
112
|
+
export function apply(ctx, config = {}) {
|
|
113
|
+
const withTools = config.tools !== false;
|
|
114
|
+
const withRoutes = config.routes !== false;
|
|
115
|
+
|
|
116
|
+
if (withTools) {
|
|
117
|
+
// `ctx.inject` runs the callback once the service is present, and again if it is
|
|
118
|
+
// replaced. Reading `ctx.get('tools')` directly races plugin activation order and
|
|
119
|
+
// silently skips registration when the service is not ready yet.
|
|
120
|
+
ctx.inject(['tools'], (scoped) => {
|
|
121
|
+
scoped.effect(() => registerCreationTools({
|
|
122
|
+
tools: scoped.tools,
|
|
123
|
+
getAttachments: () => scoped.get('attachments'),
|
|
124
|
+
}), 'code-animation-studio:tools');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// The staged flow is a policy, so it lives in the prompt rather than in a tool.
|
|
128
|
+
ctx.inject(['systemPrompt'], (scoped) => {
|
|
129
|
+
scoped.effect(() => registerFlowSection(scoped.systemPrompt), 'code-animation-studio:flow');
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (withRoutes) {
|
|
134
|
+
ctx.inject(['webServer'], (scoped) => {
|
|
135
|
+
const handler = async (req, res) => {
|
|
136
|
+
const url = req.url || '';
|
|
137
|
+
try {
|
|
138
|
+
if (url.startsWith(`${PREFIX}/health`) && req.method === 'GET') {
|
|
139
|
+
return send(res, 200, JSON.stringify({
|
|
140
|
+
ok: true,
|
|
141
|
+
rev: BUILD_REV,
|
|
142
|
+
tools: TOOL_NAMES,
|
|
143
|
+
styles: catalog().styles.length,
|
|
144
|
+
stills: catalog().styles.filter((s) => s.still).length,
|
|
145
|
+
}), 'application/json; charset=utf-8');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Rendered clips land where the user can find them, and the absolute path
|
|
149
|
+
// is echoed back so the card can show exactly where the file went.
|
|
150
|
+
if (url.startsWith(`${PREFIX}/export`) && req.method === 'POST') {
|
|
151
|
+
const query = new URL(url, 'http://localhost').searchParams;
|
|
152
|
+
const body = await readBody(req, EXPORT_MAX_BYTES);
|
|
153
|
+
if (!body.length) {
|
|
154
|
+
return send(res, 400, JSON.stringify({ error: '空文件' }), 'application/json; charset=utf-8');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const fallbackRoot = scoped.get('workingDirectory')?.defaultDirectory;
|
|
158
|
+
const dir = resolveExportDir(query.get('dir'), fallbackRoot);
|
|
159
|
+
const name = safeFileName(query.get('name'), `animation-${Date.now()}.mp4`);
|
|
160
|
+
mkdirSync(dir, { recursive: true });
|
|
161
|
+
const file = join(dir, name);
|
|
162
|
+
writeFileSync(file, body);
|
|
163
|
+
trace(`export ${name} ${body.length}B -> ${file}`);
|
|
164
|
+
return send(res, 200, JSON.stringify({
|
|
165
|
+
path: file,
|
|
166
|
+
dir,
|
|
167
|
+
bytes: body.length,
|
|
168
|
+
}), 'application/json; charset=utf-8');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (url.startsWith(`${PREFIX}/export-dir`) && req.method === 'GET') {
|
|
172
|
+
const query = new URL(url, 'http://localhost').searchParams;
|
|
173
|
+
const fallbackRoot = scoped.get('workingDirectory')?.defaultDirectory;
|
|
174
|
+
return send(res, 200, JSON.stringify({
|
|
175
|
+
dir: resolveExportDir(query.get('dir'), fallbackRoot),
|
|
176
|
+
}), 'application/json; charset=utf-8');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// The model-written score is served as script text; the client compiles it
|
|
180
|
+
// into a function and runs it against an OfflineAudioContext.
|
|
181
|
+
if (url.startsWith(`${PREFIX}/score/`)) {
|
|
182
|
+
const id = safeId(url.slice(`${PREFIX}/score/`.length));
|
|
183
|
+
const file = id ? join(OUTPUT_DIR, `${id}.score.js`) : '';
|
|
184
|
+
if (!file || !existsSync(file)) return send(res, 404, 'not found');
|
|
185
|
+
const body = readFileSync(file, 'utf8');
|
|
186
|
+
res.writeHead(200, {
|
|
187
|
+
'content-type': 'text/javascript; charset=utf-8',
|
|
188
|
+
'content-length': Buffer.byteLength(body),
|
|
189
|
+
'cache-control': 'no-store',
|
|
190
|
+
});
|
|
191
|
+
return res.end(body);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (url.startsWith(`${PREFIX}/still/`)) {
|
|
195
|
+
const id = safeId(url.slice(`${PREFIX}/still/`.length));
|
|
196
|
+
// Unpublished stills keep their file but are not served: a style the user
|
|
197
|
+
// has not cleared must not leak through a hand-typed URL either.
|
|
198
|
+
const file = id && isApproved({ id }) ? join(STILLS_DIR, `${id}.jpg`) : '';
|
|
199
|
+
if (!file || !existsSync(file)) return send(res, 404, 'not found');
|
|
200
|
+
const buf = readFileSync(file);
|
|
201
|
+
res.writeHead(200, {
|
|
202
|
+
'content-type': 'image/jpeg',
|
|
203
|
+
'content-length': buf.length,
|
|
204
|
+
'cache-control': 'public, max-age=86400, immutable',
|
|
205
|
+
});
|
|
206
|
+
return res.end(buf);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (url.startsWith(`${PREFIX}/animation/`)) {
|
|
210
|
+
const id = safeId(url.slice(`${PREFIX}/animation/`.length));
|
|
211
|
+
const file = id ? join(OUTPUT_DIR, `${id}.html`) : '';
|
|
212
|
+
if (!file || !existsSync(file)) return send(res, 404, 'not found');
|
|
213
|
+
const html = readFileSync(file, 'utf8');
|
|
214
|
+
res.writeHead(200, {
|
|
215
|
+
'content-type': 'text/html; charset=utf-8',
|
|
216
|
+
'content-length': Buffer.byteLength(html),
|
|
217
|
+
'cache-control': 'no-store',
|
|
218
|
+
});
|
|
219
|
+
return res.end(html);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return send(res, 404, 'not found');
|
|
223
|
+
} catch (error) {
|
|
224
|
+
trace(`error ${req.method} ${url} :: ${error?.message || error}`);
|
|
225
|
+
if (!res.headersSent) send(res, 500, error?.message || 'internal error');
|
|
226
|
+
else res.end();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
scoped.effect(() => scoped.webServer.register({ kind: 'prefix', path: PREFIX, handler }),
|
|
231
|
+
'code-animation-studio:routes');
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-creation-mode",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "创作模式 / Creation Mode:193 种代码动画风格,在 DeepSeek Harness 对话里直接出片。",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"dsh",
|
|
9
|
+
"dsh-plugin",
|
|
10
|
+
"deepseek-harness",
|
|
11
|
+
"code-animation",
|
|
12
|
+
"video",
|
|
13
|
+
"animation"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/wyf-yfw/dsh-creation-mode#readme",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/wyf-yfw/dsh-creation-mode.git",
|
|
19
|
+
"directory": "plugin"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"dsh": ">=0.2.1-0"
|
|
23
|
+
},
|
|
24
|
+
"exports": {
|
|
25
|
+
".": "./index.js",
|
|
26
|
+
"./client": "./client.js",
|
|
27
|
+
"./locale/*.json": "./locale/*.json",
|
|
28
|
+
"./icon": "./icon.svg"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"index.js",
|
|
32
|
+
"data.js",
|
|
33
|
+
"tools.js",
|
|
34
|
+
"flow.js",
|
|
35
|
+
"client.js",
|
|
36
|
+
"cordis.patch.yml",
|
|
37
|
+
"styles.json",
|
|
38
|
+
"stills",
|
|
39
|
+
"locale/*.json",
|
|
40
|
+
"icon.svg"
|
|
41
|
+
],
|
|
42
|
+
"dsh": {
|
|
43
|
+
"bundle": {
|
|
44
|
+
"patch": "./cordis.patch.yml"
|
|
45
|
+
},
|
|
46
|
+
"client": {
|
|
47
|
+
"platform": "web",
|
|
48
|
+
"immediately": true,
|
|
49
|
+
"inject": ["@deepseek-ai/dsh-client-ui-conversation"]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
package/stills/A1.jpg
ADDED
|
Binary file
|
package/stills/A2.jpg
ADDED
|
Binary file
|
package/stills/A3.jpg
ADDED
|
Binary file
|
package/stills/A4.jpg
ADDED
|
Binary file
|
package/stills/A5.jpg
ADDED
|
Binary file
|