Files
teamai-test/.teamai/skills/common/web-video-presentation/scripts/scaffold.sh
T

246 lines
9.3 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────
# scaffold.sh —— 一键脚手架,创建一个 video-presentation 项目。
#
# 用法:
# bash scripts/scaffold.sh <target-dir> [--theme=<id>]
# bash scripts/scaffold.sh --list-themes
#
# 例子:
# bash <path-to-web-video-presentation>/scripts/scaffold.sh ./presentation
# bash <path-to-web-video-presentation>/scripts/scaffold.sh ./talk --theme=paper-press
# bash <path-to-web-video-presentation>/scripts/scaffold.sh --list-themes
#
# 跑完后,看 SKILL.md "Phase 2.4 实现单章" + references/CHAPTER-CRAFT.md
# 了解每章怎么写。卡壳时翻 references/EXAMPLES/ 找完整章节 anchor。
#
# 之后切换主题,覆盖一个文件即可:
# cp <path-to-web-video-presentation>/themes/<id>/tokens.css \
# <project>/src/styles/tokens.css
# ─────────────────────────────────────────────────────────────
set -euo pipefail
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TEMPLATES="$SKILL_DIR/templates"
THEMES_DIR="$SKILL_DIR/themes"
DEFAULT_THEME="midnight-press"
list_themes() {
echo "可用主题(来自 ${THEMES_DIR}):"
echo
for dir in "$THEMES_DIR"/*/; do
[[ -d "$dir" ]] || continue
local meta="$dir/theme.json"
[[ -f "$meta" ]] || continue
# 没有 jq,简单 grep + sed 提字段
local id name desc
id=$(grep -E '"id"' "$meta" | head -n1 | sed -E 's/.*"id":[[:space:]]*"([^"]+)".*/\1/')
name=$(grep -E '"nameZh"' "$meta" | head -n1 | sed -E 's/.*"nameZh":[[:space:]]*"([^"]+)".*/\1/')
desc=$(grep -E '"descriptionZh"' "$meta" | head -n1 | sed -E 's/.*"descriptionZh":[[:space:]]*"([^"]+)".*/\1/')
printf " • %-18s %s\n %s\n\n" "$id" "$name" "$desc"
done
echo "用 --theme=<id> 选定一个。默认:${DEFAULT_THEME}。"
}
# ── 解析参数 ──
TARGET=""
THEME="$DEFAULT_THEME"
for arg in "$@"; do
case "$arg" in
--list-themes)
list_themes
exit 0
;;
--theme=*)
THEME="${arg#--theme=}"
;;
--*)
echo "✗ 未知参数: $arg" >&2
exit 1
;;
*)
if [[ -z "$TARGET" ]]; then TARGET="$arg"; fi
;;
esac
done
TARGET="${TARGET:-presentation}"
THEME_DIR="$THEMES_DIR/$THEME"
THEME_TOKENS="$THEME_DIR/tokens.css"
if [[ ! -d "$THEME_DIR" || ! -f "$THEME_TOKENS" ]]; then
echo "✗ 找不到主题 '${THEME}'。可用主题:" >&2
echo >&2
for dir in "$THEMES_DIR"/*/; do
[[ -d "$dir" ]] || continue
echo " • $(basename "$dir")" >&2
done
exit 1
fi
if [[ -d "$TARGET" && -n "$(ls -A "$TARGET" 2>/dev/null || true)" ]]; then
echo "✗ 目标目录 '${TARGET}' 已存在且非空,已中止。" >&2
exit 1
fi
if ! command -v npm >/dev/null; then
echo "✗ 需要 npm,但在 PATH 里没找到。" >&2
exit 1
fi
echo "▸ 在 $TARGET 创建 Vite + React + TS 项目"
echo "▸ 使用主题:$THEME"
npm create vite@latest "$TARGET" -- --template react-ts >/dev/null
cd "$TARGET"
echo "▸ 安装依赖(可能要等一会)..."
npm install >/dev/null 2>&1
echo "▸ 安装 tsx(用于 extract-narrations 脚本)..."
npm install --save-dev tsx >/dev/null 2>&1
echo "▸ 用演示骨架替换默认 boilerplate"
# 干掉我们不要的 Vite 默认 boilerplate
rm -f \
src/App.tsx src/App.css \
src/main.tsx src/index.css \
src/assets/react.svg \
public/vite.svg \
README.md
rmdir src/assets 2>/dev/null || true
# 把脚手架文件拷到项目根
mkdir -p \
src/styles src/hooks src/components src/registry \
src/chapters/01-example \
public scripts
cp "$TEMPLATES/vite.config.ts" .
cp "$TEMPLATES/index.html" .
cp "$TEMPLATES/src/main.tsx" src/main.tsx
cp "$TEMPLATES/src/App.tsx" src/App.tsx
# tokens.css 来自所选主题
cp "$THEME_TOKENS" src/styles/tokens.css
cp "$TEMPLATES/src/styles/base.css" src/styles/base.css
cp "$TEMPLATES/src/styles/animations.css" src/styles/animations.css
cp "$TEMPLATES/src/styles/fonts.css" src/styles/fonts.css
cp "$TEMPLATES/src/hooks/useStageScale.ts" src/hooks/useStageScale.ts
cp "$TEMPLATES/src/hooks/useStepper.ts" src/hooks/useStepper.ts
cp "$TEMPLATES/src/hooks/useAudioPlayer.ts" src/hooks/useAudioPlayer.ts
cp "$TEMPLATES/src/hooks/useAutoMode.ts" src/hooks/useAutoMode.ts
cp "$TEMPLATES/src/components/Stage.tsx" src/components/Stage.tsx
cp "$TEMPLATES/src/components/MaskReveal.tsx" src/components/MaskReveal.tsx
cp "$TEMPLATES/src/components/ProgressBar.tsx" src/components/ProgressBar.tsx
cp "$TEMPLATES/src/components/ProgressBar.css" src/components/ProgressBar.css
cp "$TEMPLATES/src/components/AutoStartGate.tsx" src/components/AutoStartGate.tsx
cp "$TEMPLATES/src/components/AutoStartGate.css" src/components/AutoStartGate.css
cp "$TEMPLATES/src/components/AutoToggle.tsx" src/components/AutoToggle.tsx
cp "$TEMPLATES/src/components/AutoToggle.css" src/components/AutoToggle.css
cp "$TEMPLATES/src/registry/types.ts" src/registry/types.ts
cp "$TEMPLATES/src/registry/chapters.ts" src/registry/chapters.ts
cp "$TEMPLATES/src/chapters/01-example/Example.tsx" src/chapters/01-example/Example.tsx
cp "$TEMPLATES/src/chapters/01-example/Example.css" src/chapters/01-example/Example.css
cp "$TEMPLATES/src/chapters/01-example/narrations.ts" src/chapters/01-example/narrations.ts
# Audio pipeline scripts (extract-narrations + synthesize-audio runner +
# pluggable TTS providers under tts-providers/).
cp "$TEMPLATES/scripts/extract-narrations.ts" scripts/extract-narrations.ts
cp "$TEMPLATES/scripts/synthesize-audio.sh" scripts/synthesize-audio.sh
chmod +x scripts/synthesize-audio.sh
mkdir -p scripts/tts-providers
cp "$TEMPLATES/scripts/tts-providers/README.md" scripts/tts-providers/README.md
cp "$TEMPLATES/scripts/tts-providers/minimax.sh" scripts/tts-providers/minimax.sh
cp "$TEMPLATES/scripts/tts-providers/openai.sh" scripts/tts-providers/openai.sh
# Wire the audio scripts into npm so contributors don't have to remember
# the exact command. Uses node to merge into the existing package.json.
node -e '
const fs = require("fs");
const p = JSON.parse(fs.readFileSync("package.json", "utf8"));
p.scripts = Object.assign({}, p.scripts, {
"extract-narrations": "tsx scripts/extract-narrations.ts",
"synthesize-audio": "bash scripts/synthesize-audio.sh",
});
fs.writeFileSync("package.json", JSON.stringify(p, null, 2) + "\n");
'
# 留个标记,以后能查这个项目从哪个主题起步的
{
echo "$THEME"
} > .theme
# 跑一次 typecheck 确认接线 OK
echo "▸ 跑 typecheck ..."
if npx tsc --noEmit; then
echo "✓ typecheck 通过"
else
echo "✗ typecheck 失败 —— 请看上面的错误" >&2
exit 1
fi
cat <<EOF
✓ 完成。下一步:
1. cd $TARGET
2. npm run dev # 默认 http://localhost:5174(被占会自动换端口)
当前主题:${THEME}(见 .theme)
然后:
• 点舞台任意位置推进全局 step 计数器。
• 鼠标移到底部边缘可显出进度条;鼠标移到右上角可显出播放模式切换。
• 把 src/chapters/01-example/ 替换成你自己的章节
(流程见 SKILL.md "Phase 2.4 实现单章" —— 每章一次到位完整版本,
不分骨架 / 精修两步;动画选型由 chapter agent 按 CHAPTER-CRAFT.md
Part 0 原则 7 + Part 1 五问决定)。
• 在 src/registry/chapters.ts 注册每个新章节。
• **每章必须有 narrations.ts**(与 Example.tsx 同目录),
数组长度 = step 数,是音频合成 + Auto 模式的唯一真相源。
• 章节改了就 bump src/hooks/useStepper.ts 的 STORAGE_KEY 末尾版本号。
录制:
• 手动模式:直接打开 http://localhost:5174(点击 / 方向键推进)
• 半自动:URL 加 ?audio=1 — 音频跟 step 切,但你手动推进
• 全自动录屏:URL 加 ?auto=1 — 按一次 SPACE 启动,整片自动播 + 推进
按 M 键随时切换三种模式。
音频合成(可选,录制前做):
npm run extract-narrations # 扫所有章节 narrations.ts → audio-segments.json
npm run synthesize-audio # 默认 minimax provider 合成 → public/audio/<id>/<step>.mp3
# 换 provider:PRESENTATION_TTS=<name> npm run synthesize-audio
# 自定义 / 没装 mmx 见 scripts/tts-providers/README.md
写章节时必读(单一入口,路径在 SKILL 仓库内):
• $SKILL_DIR/references/CHAPTER-CRAFT.md
Part 0 十条原则 / Part 1 开工 5 问 / Part 2 关系→动作决策树 /
Part 3 视觉工具箱 / Part 4 时长 / Part 5 反 AI 味反模式 /
Part 6 代码硬规则 / Part 7 完工自检 / Part 8 反馈速查
• $SKILL_DIR/themes/$THEME/theme.json
看 descriptionZh / mood / bestFor —— 参考主题气质
(动画 / 时长 / 字号 / emoji 由 chapter agent 在每章自由决定)
卡壳时可翻:
• $SKILL_DIR/references/EXAMPLES/
完整章节 anchor(钩子型 / 列举型)—— 看"形",不要照搬
要换一个主题,覆盖 tokens.css 即可:
cp $SKILL_DIR/themes/<id>/tokens.css src/styles/tokens.css
想自创主题,看 $SKILL_DIR/references/THEMES.md。
EOF