#!/bin/bash
# build-graph-html.sh — 生成共享 graph-engine 驱动的离线知识图谱 HTML
#
# 用法:
# bash scripts/build-graph-html.sh
#
# 前置:需要先运行 build-graph-data.sh 生成 wiki/graph-data.json
#
# 行为:
# 1. 读取 packages/graph-engine/dist/engine.iife.js
# 2. 内嵌 graph-data.json 与可选 .wiki-graph-layout.json 钉位
# 3. 注入离线启动脚本:创建 graph engine,持久化钉位到 localStorage
# 4. 生成单文件 knowledge-graph.html
#
# 退出码:0 成功;1 依赖/文件缺失/参数错误
set -eu
SCRIPT_DIR="${BASH_SOURCE[0]%/*}"
[ "$SCRIPT_DIR" = "${BASH_SOURCE[0]}" ] && SCRIPT_DIR="."
SCRIPT_DIR="$(cd "$SCRIPT_DIR" && pwd)"
# shellcheck disable=SC1091
source "$SCRIPT_DIR/shared-config.sh"
print_usage() {
cat <<'USAGE'
用法:
bash scripts/build-graph-html.sh
示例:
bash scripts/build-graph-html.sh /path/to/wiki-root
USAGE
}
die() {
echo "ERROR: $1" >&2
exit 1
}
ensure_file() {
local file="$1"
local label="${2:-文件}"
[ -f "$file" ] || {
echo "ERROR: 找不到${label} $file" >&2
echo " 请先运行 npm run build -w @llm-wiki/graph-engine,或重装 skill。" >&2
exit 1
}
}
json_for_script() {
perl -pe 's||<\\/script>|gi' "$1"
}
script_for_inline() {
perl -pe 's|//# sourceMappingURL=.*$||' "$1"
}
html_escape_text() {
printf '%s' "$1" | perl -pe 's/&/&/g; s/</g; s/>/>/g; s/"/"/g; s/'"'"'/'/g'
}
while [ "$#" -gt 0 ]; do
case "$1" in
-h|--help)
print_usage
exit 0
;;
--)
shift
break
;;
-*)
die "未知选项: $1"
;;
*)
break
;;
esac
done
[ "$#" -eq 1 ] || {
print_usage >&2
exit 1
}
WIKI_ROOT="$1"
command -v jq >/dev/null 2>&1 || {
echo "ERROR: jq is not installed. Install it via:" >&2
print_install_hint jq
exit 1
}
SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
DATA="$WIKI_ROOT/wiki/graph-data.json"
LAYOUT="$WIKI_ROOT/.wiki-graph-layout.json"
ENGINE="$SKILL_DIR/packages/graph-engine/dist/engine.iife.js"
MARKED="$SKILL_DIR/deps/marked.min.js"
PURIFY="$SKILL_DIR/deps/purify.min.js"
OUTPUT="$WIKI_ROOT/wiki/knowledge-graph.html"
[ -f "$DATA" ] || {
echo "ERROR: 未找到 $DATA" >&2
echo " 请先运行 build-graph-data.sh 生成图谱数据" >&2
exit 1
}
ensure_file "$ENGINE" "graph-engine IIFE 产物"
ensure_file "$MARKED" "marked vendor"
ensure_file "$PURIFY" "purify vendor"
WIKI_TITLE=$(jq -r '.meta.wiki_title // "知识库"' "$DATA")
NODE_COUNT=$(jq -r '.meta.total_nodes // 0' "$DATA")
EDGE_COUNT=$(jq -r '.meta.total_edges // 0' "$DATA")
BUILD_DATE=$(jq -r '.meta.build_date // ""' "$DATA")
BUILD_DATE_SHORT="${BUILD_DATE:0:10}"
[ -n "$BUILD_DATE_SHORT" ] || BUILD_DATE_SHORT="未知"
WIKI_TITLE_HTML=$(html_escape_text "$WIKI_TITLE")
NODE_COUNT_HTML=$(html_escape_text "$NODE_COUNT")
EDGE_COUNT_HTML=$(html_escape_text "$EDGE_COUNT")
BUILD_DATE_SHORT_HTML=$(html_escape_text "$BUILD_DATE_SHORT")
layout_json='{"version":2,"pins":{},"updatedAt":""}'
if [ -f "$LAYOUT" ]; then
if layout_json_candidate=$(jq -c '{version:(.version // 1), pins:(.pins // {}), updatedAt:(.updatedAt // "")}' "$LAYOUT" 2>/dev/null); then
layout_json="$layout_json_candidate"
else
echo "WARN: 忽略损坏的钉位文件:$LAYOUT" >&2
fi
fi
output_dir="$(dirname "$OUTPUT")"
mkdir -p "$output_dir"
output_tmp="$OUTPUT.partial"
output_next="$OUTPUT.next"
rm -f "$output_tmp" "$output_next"
cat > "$output_tmp" <
知识图谱 · ${WIKI_TITLE_HTML}
|<\/script>|gi' >> "$output_tmp"
cat >> "$output_tmp" <<'HTML_ENGINE'
HTML_BOOT
mv "$output_tmp" "$output_next"
mv "$output_next" "$OUTPUT"
rm -f \
"$output_dir/d3.min.js" \
"$output_dir/rough.min.js" \
"$output_dir/marked.min.js" \
"$output_dir/purify.min.js" \
"$output_dir/graph-wash.js" \
"$output_dir/graph-wash-helpers.js" \
"$output_dir/LICENSE-d3.txt" \
"$output_dir/LICENSE-roughjs.txt" \
"$output_dir/LICENSE-marked.txt" \
"$output_dir/LICENSE-purify.txt"
output_size=$(wc -c < "$OUTPUT" | tr -d ' ')
output_kb=$((output_size / 1024))
echo "交互式图谱已生成:"
echo " - $OUTPUT (${output_kb} KB)"
echo " 节点 $NODE_COUNT · 关联 $EDGE_COUNT"
echo ""
echo "查看方式:"
echo " 双击 $OUTPUT"