inkos-write-next-atomic.sh 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. #!/usr/bin/env bash
  2. # =============================================================================
  3. # inkos-write-next-atomic.sh
  4. #
  5. # 用原子命令组装 inkos write next 的完整管线,绝不直接调用 inkos write next。
  6. #
  7. # 使用的原子命令:
  8. # inkos plan chapter – 预检:生成章节意图(无 LLM 调用)
  9. # inkos compose chapter – 预检:编译上下文/规则栈/追踪产物(无 LLM 调用)
  10. # inkos draft – 撰写章节草稿(LLM)
  11. # inkos audit – 连续性审计(LLM)
  12. # inkos revise – 修复问题 / 润色文笔(LLM)
  13. #
  14. # 管线:
  15. # plan → compose → draft → [audit → revise]×N → polish → 完成
  16. # =============================================================================
  17. set -euo pipefail
  18. # ---------------------------------------------------------------------------
  19. # LLM 环境变量(确保 inkos 使用正确的 API 端点和模型)
  20. # ---------------------------------------------------------------------------
  21. export INKOS_LLM_MODEL="${INKOS_LLM_MODEL:-deepseek-v3.2-exp}"
  22. export INKOS_LLM_BASE_URL="${INKOS_LLM_BASE_URL:-https://platform.shuyanai.com/v1}"
  23. # ---------------------------------------------------------------------------
  24. # 用法说明
  25. # ---------------------------------------------------------------------------
  26. usage() {
  27. cat <<'EOF'
  28. 用法:inkos-write-next-atomic.sh [book-id] [创作引导] [选项]
  29. 用原子命令组装 inkos 写下一章的完整管线:
  30. plan → [确认意图] → compose → draft → [确认草稿] → audit → revise×N → polish
  31. 参数:
  32. book-id 作品 ID(可选,仅一个作品时自动检测)
  33. 创作引导 章节创作引导文本(可选,不提供则交互式输入)
  34. 选项:
  35. -h, --help 显示此帮助信息
  36. 交互式检查点:
  37. 第 1 步后 用 less 查看意图文件,可选择重新输入引导或继续
  38. 第 3 步后 确认草稿,可选择输入提示词用 rewrite 重写
  39. 环境变量:
  40. INKOS_LLM_MODEL 模型(默认 deepseek-v4-flash)
  41. INKOS_LLM_BASE_URL API 端点(默认 https://platform.shuyanai.com/v1)
  42. EOF
  43. exit 0
  44. }
  45. # 检查 --help / -h
  46. for arg in "$@"; do
  47. if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
  48. usage
  49. fi
  50. done
  51. # ---------------------------------------------------------------------------
  52. # 辅助函数
  53. # ---------------------------------------------------------------------------
  54. RED='\033[0;31m'
  55. GREEN='\033[0;32m'
  56. YELLOW='\033[0;33m'
  57. CYAN='\033[0;36m'
  58. BOLD='\033[1m'
  59. NC='\033[0m'
  60. banner() { echo -e "${CYAN}${BOLD}==>${NC} ${BOLD}$*${NC}"; }
  61. ok() { echo -e " ${GREEN}✓${NC} $*"; }
  62. warn() { echo -e " ${YELLOW}⚠${NC} $*"; }
  63. fail() { echo -e " ${RED}✗${NC} $*"; }
  64. stage() { echo -e "\n${CYAN}━━━ $* ━━━${NC}"; }
  65. info() { echo -e " $*"; }
  66. die() {
  67. echo -e "${RED}${BOLD}致命错误:${NC} $*" >&2
  68. exit 1
  69. }
  70. require_cmd() {
  71. command -v "$1" >/dev/null 2>&1 || die "缺少命令:$1,请先安装。"
  72. }
  73. # 解析 JSON 字段(取首个匹配值)
  74. json_field() {
  75. local json="$1" key="$2"
  76. echo "$json" | jq -r ".${key} // empty" 2>/dev/null
  77. }
  78. # spinner:在后台任务运行时显示旋转动画和耗时
  79. spinner() {
  80. local pid="$1" msg="$2" spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' i=0
  81. local start="${3:-$SECONDS}"
  82. while kill -0 "$pid" 2>/dev/null; do
  83. local elapsed=$((SECONDS - start))
  84. printf "\r ${spin:$i:1} %s (已耗时 %ds)..." "$msg" "$elapsed" >&2
  85. i=$(( (i + 1) % ${#spin} ))
  86. sleep 0.2
  87. done
  88. wait "$pid"
  89. }
  90. # run_with_spinner <描述> <命令> [参数...]
  91. # 在后台运行命令,显示 spinner,捕获 stdout 到临时文件,保持原始退出码
  92. run_with_spinner() {
  93. local msg="$1" start_ts="$SECONDS"
  94. shift
  95. local tmpfile
  96. tmpfile=$(mktemp)
  97. # 后台运行命令,stdout → tmpfile,stderr → /dev/null
  98. "$@" > "$tmpfile" 2>/dev/null &
  99. local pid=$!
  100. spinner "$pid" "$msg" "$start_ts"
  101. local rc=$?
  102. # 清掉 spinner 行,输出结果
  103. local elapsed=$((SECONDS - start_ts))
  104. printf "\r\033[K ${GREEN}✓${NC} %s (耗时 %ds)\n" "$msg" "$elapsed" >&2
  105. cat "$tmpfile"
  106. rm -f "$tmpfile"
  107. return $rc
  108. }
  109. # ---------------------------------------------------------------------------
  110. # 自动检测项目中的作品 ID
  111. # ---------------------------------------------------------------------------
  112. detect_book_id() {
  113. local books_dir
  114. books_dir="$(findProjectRoot)/books"
  115. if [[ ! -d "$books_dir" ]]; then
  116. die "未找到 books/ 目录。请在 inkos 项目内运行此脚本。"
  117. fi
  118. local books
  119. books=($(ls -1 "$books_dir" 2>/dev/null || true))
  120. if [[ ${#books[@]} -eq 0 ]]; then
  121. die "项目中还没有作品。请先用 inkos book create --title ... 创建。"
  122. fi
  123. if [[ ${#books[@]} -eq 1 ]]; then
  124. echo "${books[0]}"
  125. return
  126. fi
  127. # 多作品时让用户选择
  128. echo "" >&2
  129. info "检测到多个作品:" >&2
  130. for i in "${!books[@]}"; do
  131. info " [$((i+1))] ${books[$i]}" >&2
  132. done
  133. echo "" >&2
  134. read -r -p "请选择作品 [1-${#books[@]}]:" choice || true
  135. if [[ ! "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#books[@]} )); then
  136. die "选择无效。"
  137. fi
  138. echo "${books[$((choice-1))]}"
  139. }
  140. findProjectRoot() {
  141. local dir="$PWD"
  142. while [[ "$dir" != "/" ]]; do
  143. if [[ -f "$dir/inkos.json" ]]; then
  144. echo "$dir"
  145. return
  146. fi
  147. dir="$(dirname "$dir")"
  148. done
  149. die "当前目录及父目录均未找到 inkos.json。请在 inkos 项目内运行此脚本。"
  150. }
  151. # ---------------------------------------------------------------------------
  152. # 章节号提取
  153. # ---------------------------------------------------------------------------
  154. get_latest_chapter() {
  155. local book_id="$1" root
  156. root="$(findProjectRoot)"
  157. local index="$root/books/$book_id/chapters/index.json"
  158. if [[ ! -f "$index" ]]; then
  159. echo "1"
  160. return
  161. fi
  162. jq -r 'if length == 0 then 1 else (max_by(.number) | .number + 1) end' "$index" 2>/dev/null || echo "1"
  163. }
  164. get_last_written_chapter() {
  165. local book_id="$1" root
  166. root="$(findProjectRoot)"
  167. local index="$root/books/$book_id/chapters/index.json"
  168. if [[ ! -f "$index" ]]; then
  169. echo "0"
  170. return
  171. fi
  172. jq -r 'max_by(.number) | .number // 0' "$index" 2>/dev/null || echo "0"
  173. }
  174. # ---------------------------------------------------------------------------
  175. # 前置检查
  176. # ---------------------------------------------------------------------------
  177. require_cmd inkos
  178. require_cmd jq
  179. require_cmd less
  180. echo ""
  181. echo -e "${BOLD}${CYAN}╔══════════════════════════════════════╗${NC}"
  182. echo -e "${BOLD}${CYAN}║ InkOS 原子写下一章(原子管线) ║${NC}"
  183. echo -e "${BOLD}${CYAN}╚══════════════════════════════════════╝${NC}"
  184. echo ""
  185. ROOT="$(findProjectRoot)"
  186. BOOK_ID="${1:-$(detect_book_id)}"
  187. NEXT_CH="$(get_latest_chapter "$BOOK_ID")"
  188. LAST_CH="$(get_last_written_chapter "$BOOK_ID")"
  189. info "项目 :${ROOT}"
  190. info "作品 :${BOOK_ID}"
  191. info "下一章 :第 ${NEXT_CH} 章"
  192. if (( LAST_CH > 0 )); then
  193. info "最新章 :第 ${LAST_CH} 章"
  194. fi
  195. # 可选章节引导
  196. CONTEXT="${2:-}"
  197. if [[ -z "$CONTEXT" ]]; then
  198. echo ""
  199. read -r -p "章节创作引导(可选,回车跳过):" CONTEXT || true
  200. fi
  201. # 用数组安全传参(处理空格等特殊字符)
  202. CONTEXT_ARGS=()
  203. if [[ -n "$CONTEXT" ]]; then
  204. CONTEXT_ARGS=(--context "$CONTEXT")
  205. fi
  206. # ---------------------------------------------------------------------------
  207. # 第 1 步:plan chapter(循环,允许用户查看意图后重新输入引导)
  208. # ---------------------------------------------------------------------------
  209. while true; do
  210. stage "第 1/5 步:规划章节意图"
  211. PLAN_OUTPUT=""
  212. PLAN_OUTPUT=$(run_with_spinner "正在规划章节意图" inkos plan chapter "$BOOK_ID" "${CONTEXT_ARGS[@]}" --json) || {
  213. fail "章节意图规划失败"
  214. echo "$PLAN_OUTPUT"
  215. exit 1
  216. }
  217. if [[ -z "$(json_field "$PLAN_OUTPUT" chapterNumber)" ]]; then
  218. fail "章节意图 JSON 解析失败(可能 stdout/stderr 混合)"
  219. exit 1
  220. fi
  221. PLAN_GOAL="$(json_field "$PLAN_OUTPUT" goal)"
  222. PLAN_CH="$(json_field "$PLAN_OUTPUT" chapterNumber)"
  223. PLAN_INTENT_FILE="$(json_field "$PLAN_OUTPUT" intentPath)"
  224. ok "已生成第 ${PLAN_CH} 章意图"
  225. if [[ -n "$PLAN_GOAL" ]]; then
  226. info "章节目标:${PLAN_GOAL}"
  227. fi
  228. if [[ -n "$PLAN_INTENT_FILE" ]]; then
  229. info "意图文件:${PLAN_INTENT_FILE}"
  230. fi
  231. # ---- 检查点:查看意图文件 ----
  232. echo ""
  233. INTENT_FULL_PATH="$ROOT/books/$BOOK_ID/$PLAN_INTENT_FILE"
  234. if [[ -n "$PLAN_INTENT_FILE" && -f "$INTENT_FULL_PATH" ]]; then
  235. info "正在用 less 显示意图文件(按 q 退出,↑↓ 滚动,/ 搜索)……"
  236. echo ""
  237. less -R "$INTENT_FULL_PATH" || true
  238. else
  239. warn "意图文件不存在(${INTENT_FULL_PATH}),跳过预览。"
  240. fi
  241. echo ""
  242. info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  243. info " c / 回车 — 继续下一步(compile)"
  244. info " r — 重新输入创作引导,重新 plan"
  245. info " q — 退出"
  246. info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  247. read -r -p "请选择 [c/r/q](默认 c):" PLAN_CHOICE || true
  248. case "${PLAN_CHOICE:-c}" in
  249. c|C|"")
  250. break
  251. ;;
  252. r|R)
  253. echo ""
  254. read -r -p "请输入新的创作引导:" CONTEXT || true
  255. CONTEXT_ARGS=()
  256. if [[ -n "$CONTEXT" ]]; then
  257. CONTEXT_ARGS=(--context "$CONTEXT")
  258. fi
  259. ;;
  260. q|Q)
  261. info "用户退出。"
  262. exit 0
  263. ;;
  264. *)
  265. warn "无效输入,默认继续。"
  266. break
  267. ;;
  268. esac
  269. done
  270. # ---------------------------------------------------------------------------
  271. # 第 2 步:compose chapter(无 LLM 调用 — 编译上下文/规则栈/追踪产物)
  272. # ---------------------------------------------------------------------------
  273. stage "第 2/5 步:编译章节上下文"
  274. COMPOSE_OUTPUT=""
  275. COMPOSE_OUTPUT=$(inkos compose chapter "$BOOK_ID" "${CONTEXT_ARGS[@]}" --json 2>/dev/null) || {
  276. fail "章节上下文编译失败"
  277. echo "$COMPOSE_OUTPUT"
  278. exit 1
  279. }
  280. if [[ -z "$(json_field "$COMPOSE_OUTPUT" contextPath)" ]]; then
  281. fail "章节上下文 JSON 解析失败(可能 stdout/stderr 混合)"
  282. exit 1
  283. fi
  284. COMPOSE_CH="$(json_field "$COMPOSE_OUTPUT" chapterNumber)"
  285. COMPOSE_CTX="$(json_field "$COMPOSE_OUTPUT" contextPath)"
  286. COMPOSE_RULES="$(json_field "$COMPOSE_OUTPUT" ruleStackPath)"
  287. COMPOSE_TRACE="$(json_field "$COMPOSE_OUTPUT" tracePath)"
  288. ok "已编译第 ${COMPOSE_CH} 章上下文"
  289. info "上下文 :${COMPOSE_CTX}"
  290. info "规则栈 :${COMPOSE_RULES}"
  291. info "追踪文件 :${COMPOSE_TRACE}"
  292. # ---------------------------------------------------------------------------
  293. # 第 3 步:draft(LLM — 撰写章节草稿,然后循环确认 / rewrite 重写)
  294. # ---------------------------------------------------------------------------
  295. stage "第 3/5 步:撰写章节草稿"
  296. DRAFT_OUTPUT=""
  297. DRAFT_OUTPUT=$(run_with_spinner "正在生成草稿" inkos draft "$BOOK_ID" "${CONTEXT_ARGS[@]}" --json) || {
  298. fail "草稿撰写失败"
  299. echo "$DRAFT_OUTPUT"
  300. exit 1
  301. }
  302. DRAFT_CH="$(json_field "$DRAFT_OUTPUT" chapterNumber)"
  303. DRAFT_TITLE="$(json_field "$DRAFT_OUTPUT" title)"
  304. DRAFT_WORDS="$(json_field "$DRAFT_OUTPUT" wordCount)"
  305. DRAFT_FILE="$(json_field "$DRAFT_OUTPUT" filePath)"
  306. if [[ -z "$DRAFT_CH" || -z "$DRAFT_FILE" ]]; then
  307. fail "草稿 JSON 解析失败(缺少 chapterNumber 或 filePath)"
  308. exit 1
  309. fi
  310. # ---- 确认循环:less 预览 → [c/r/q] → rewrite 后回到预览 ----
  311. while true; do
  312. info "第 ${DRAFT_CH} 章:${DRAFT_TITLE}"
  313. info "字数:${DRAFT_WORDS}"
  314. info "文件:${DRAFT_FILE}"
  315. # 检查字数警告
  316. LEN_WARNINGS="$(json_field "$DRAFT_OUTPUT" lengthWarnings)"
  317. if [[ -n "$LEN_WARNINGS" && "$LEN_WARNINGS" != "null" ]]; then
  318. warn "字数警告:"
  319. echo "$DRAFT_OUTPUT" | jq -r '.lengthWarnings[]? | " - \(.)"' 2>/dev/null || true
  320. fi
  321. echo ""
  322. if [[ -n "$DRAFT_FILE" && -f "$DRAFT_FILE" ]]; then
  323. info "正在用 less 显示草稿(按 q 退出,↑↓ 滚动,/ 搜索)……"
  324. echo ""
  325. less -R "$DRAFT_FILE" || true
  326. else
  327. warn "草稿文件不存在(${DRAFT_FILE}),跳过预览。"
  328. fi
  329. echo ""
  330. info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  331. info " c / 回车 — 继续下一步(审计)"
  332. info " r — 输入提示词,用 rewrite 重写本章"
  333. info " q — 退出"
  334. info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  335. read -r -p "请选择 [c/r/q](默认 c):" DRAFT_CHOICE || true
  336. case "${DRAFT_CHOICE:-c}" in
  337. c|C|"")
  338. break
  339. ;;
  340. r|R)
  341. echo ""
  342. read -r -p "请输入重写提示词:" REWRITE_BRIEF || true
  343. if [[ -z "$REWRITE_BRIEF" ]]; then
  344. warn "未输入提示词,跳过重写。"
  345. continue
  346. fi
  347. REWRITE_OUTPUT=""
  348. REWRITE_OUTPUT=$(run_with_spinner "正在 rewrite 重写" inkos revise "$BOOK_ID" "$DRAFT_CH" --mode rewrite --brief "$REWRITE_BRIEF" --json) || {
  349. warn "重写失败,将使用当前草稿继续。"
  350. echo "$REWRITE_OUTPUT"
  351. continue
  352. }
  353. REWRITE_APPLIED="$(json_field "$REWRITE_OUTPUT" applied)"
  354. REWRITE_WORDS="$(json_field "$REWRITE_OUTPUT" wordCount)"
  355. if [[ "$REWRITE_APPLIED" == "true" ]]; then
  356. ok "重写已应用 —— ${REWRITE_WORDS} 字"
  357. DRAFT_TITLE="$(json_field "$REWRITE_OUTPUT" title)"
  358. DRAFT_WORDS="$REWRITE_WORDS"
  359. # 重写后回到预览,让用户再次确认
  360. else
  361. warn "重写未应用 —— $(json_field "$REWRITE_OUTPUT" skippedReason)"
  362. continue
  363. fi
  364. ;;
  365. q|Q)
  366. info "用户退出。"
  367. exit 0
  368. ;;
  369. *)
  370. warn "无效输入,默认继续。"
  371. break
  372. ;;
  373. esac
  374. done
  375. # ---------------------------------------------------------------------------
  376. # 第 4 步:审计 + 修订循环
  377. # ---------------------------------------------------------------------------
  378. MAX_REVISE_ROUNDS=3
  379. AUDIT_PASSED=false
  380. REVISED=false
  381. ROUND=0
  382. stage "第 4/5 步:审计 + 修订循环"
  383. while true; do
  384. ROUND=$((ROUND + 1))
  385. # ---- 审计 ----
  386. AUDIT_OUTPUT=""
  387. AUDIT_OUTPUT=$(run_with_spinner "正在审计(第 ${ROUND} 轮)" inkos audit "$BOOK_ID" "$DRAFT_CH" --json) || {
  388. fail "审计失败"
  389. echo "$AUDIT_OUTPUT"
  390. exit 1
  391. }
  392. AUDIT_PASSED_FLAG="$(json_field "$AUDIT_OUTPUT" passed)"
  393. AUDIT_SUMMARY="$(json_field "$AUDIT_OUTPUT" summary)"
  394. if [[ "$AUDIT_PASSED_FLAG" == "true" ]]; then
  395. ok "审计通过 —— ${AUDIT_SUMMARY}"
  396. AUDIT_PASSED=true
  397. break
  398. fi
  399. warn "审计未通过(第 ${ROUND} 轮)—— ${AUDIT_SUMMARY}"
  400. # 打印问题列表
  401. ISSUE_COUNT=$(echo "$AUDIT_OUTPUT" | jq '.issues | length' 2>/dev/null || echo "0")
  402. if (( ISSUE_COUNT > 0 )); then
  403. echo "$AUDIT_OUTPUT" | jq -r '.issues[] | " [\(.severity)] \(.category): \(.description)"' 2>/dev/null
  404. fi
  405. # 检查是否还有修订轮次
  406. if (( ROUND >= MAX_REVISE_ROUNDS )); then
  407. warn "已达最大修订轮次(${MAX_REVISE_ROUNDS})。章节将保留为草稿状态。"
  408. break
  409. fi
  410. echo ""
  411. read -r -p "是否现在修订?[Y/n]:" DO_REVISE || true
  412. if [[ "$DO_REVISE" =~ ^[Nn] ]]; then
  413. warn "跳过修订。章节将保留为草稿状态。"
  414. break
  415. fi
  416. # 选择修订模式
  417. echo ""
  418. info "修订模式:"
  419. info " 1) spot-fix – 定点修复,最小改动(默认)"
  420. info " 2) rewrite – 全章改写,保留剧情走向"
  421. info " 3) rework – 深层重构"
  422. info " 4) anti-detect – 去 AI 痕迹"
  423. read -r -p "请选择模式 [1-4](默认 1):" MODE_CHOICE || true
  424. case "${MODE_CHOICE:-1}" in
  425. 1) REVISE_MODE="spot-fix" ;;
  426. 2) REVISE_MODE="rewrite" ;;
  427. 3) REVISE_MODE="rework" ;;
  428. 4) REVISE_MODE="anti-detect" ;;
  429. *) REVISE_MODE="spot-fix" ;;
  430. esac
  431. # ---- 修订 ----
  432. REVISE_OUTPUT=""
  433. REVISE_OUTPUT=$(run_with_spinner "正在修订 [$REVISE_MODE]" inkos revise "$BOOK_ID" "$DRAFT_CH" --mode "$REVISE_MODE" --json) || {
  434. fail "修订失败"
  435. echo "$REVISE_OUTPUT"
  436. continue
  437. }
  438. REVISE_APPLIED="$(json_field "$REVISE_OUTPUT" applied)"
  439. REVISE_WORDS="$(json_field "$REVISE_OUTPUT" wordCount)"
  440. REVISE_STATUS="$(json_field "$REVISE_OUTPUT" status)"
  441. REVISE_SKIPPED="$(json_field "$REVISE_OUTPUT" skippedReason)"
  442. if [[ "$REVISE_APPLIED" == "true" ]]; then
  443. ok "修订已应用 —— ${REVISE_WORDS} 字,状态:${REVISE_STATUS}"
  444. REVISED=true
  445. echo "$REVISE_OUTPUT" | jq -r '.fixedIssues[]? | " 已修复:\(.)"' 2>/dev/null
  446. else
  447. warn "修订未应用 —— ${REVISE_SKIPPED:-无需修改}"
  448. fi
  449. done
  450. # ---------------------------------------------------------------------------
  451. # 第 5 步:润色(可选,仅审计通过后执行)
  452. # ---------------------------------------------------------------------------
  453. stage "第 5/5 步:文笔润色"
  454. if [[ "$AUDIT_PASSED" == "true" ]]; then
  455. echo ""
  456. read -r -p "是否进行文笔润色?[Y/n]:" DO_POLISH || true
  457. if [[ ! "$DO_POLISH" =~ ^[Nn] ]]; then
  458. POLISH_OUTPUT=""
  459. POLISH_OUTPUT=$(run_with_spinner "正在润色" inkos revise "$BOOK_ID" "$DRAFT_CH" --mode polish --json) || {
  460. warn "润色失败(非致命错误)"
  461. echo "$POLISH_OUTPUT"
  462. }
  463. POLISH_APPLIED="$(json_field "$POLISH_OUTPUT" applied)"
  464. POLISH_WORDS="$(json_field "$POLISH_OUTPUT" wordCount)"
  465. if [[ "$POLISH_APPLIED" == "true" ]]; then
  466. ok "润色已应用 —— ${POLISH_WORDS} 字"
  467. else
  468. info "润色跳过(无需修改)"
  469. fi
  470. else
  471. info "跳过润色。"
  472. fi
  473. else
  474. warn "审计未通过,跳过润色。"
  475. fi
  476. # ---------------------------------------------------------------------------
  477. # 完成
  478. # ---------------------------------------------------------------------------
  479. echo ""
  480. echo -e "${GREEN}${BOLD}╔══════════════════════════════════════╗${NC}"
  481. echo -e "${GREEN}${BOLD}║ 写入下一章完成 ║${NC}"
  482. echo -e "${GREEN}${BOLD}╚══════════════════════════════════════╝${NC}"
  483. echo ""
  484. info "作品 :${BOOK_ID}"
  485. info "章节 :第 ${DRAFT_CH} 章 — ${DRAFT_TITLE}"
  486. info "字数 :${DRAFT_WORDS}"
  487. info "文件 :${DRAFT_FILE}"
  488. info "审计 :$( [[ "$AUDIT_PASSED" == "true" ]] && echo "${GREEN}通过${NC}" || echo "${YELLOW}未通过${NC}" )"
  489. info "已修订 :$( [[ "$REVISED" == "true" ]] && echo "${GREEN}是${NC}" || echo "否" )"
  490. echo ""
  491. banner "后续操作:"
  492. info " inkos review approve ${BOOK_ID} ${DRAFT_CH}"
  493. info " inkos export ${BOOK_ID}"
  494. info " inkos status"
  495. echo ""