@waterwx/dsh-novel-forge 1.3.0 → 1.3.2
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/lib/client.js +248 -298
- package/lib/client.js.map +1 -1
- package/lib/index.js +4 -4
- package/lib/index.js.map +1 -1
- package/package.json +2 -1
- package/scripts/assistant-frames.txt +8 -0
- package/scripts/diagnose-adapter.mts +81 -0
- package/scripts/diagnose-assistant.mjs +58 -0
- package/scripts/diagnose-bible.mjs +72 -0
- package/scripts/diagnose-harness.mts +75 -0
- package/scripts/novel-forge-restart.log +10 -0
- package/scripts/novel-forge-web.stderr.log +2 -0
- package/scripts/novel-forge-web.stdout.log +1 -0
- package/scripts/outline-sample.txt +449 -0
- package/scripts/patch-schemastery.mjs +47 -0
- package/scripts/probe.mjs +6 -0
- package/scripts/restart-web.ps1 +88 -0
- package/scripts/smoke.mts +72 -0
- package/scripts/upload-github.ps1 +51 -0
- package/src/client/panel/NovelPanel.tsx +6 -35
- package/src/engine.ts +2 -2
- package/src/routes.ts +2 -2
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Upload the dsh-novel-forge project to GitHub via the Contents API.
|
|
2
|
+
# Uses the current token (passed as $env:GH_TOKEN) — one PUT per file,
|
|
3
|
+
# directories are created implicitly. Retries transient failures once.
|
|
4
|
+
param(
|
|
5
|
+
[string]$Owner = "watersxya",
|
|
6
|
+
[string]$Repo = "dsh-novel-forge",
|
|
7
|
+
[string]$SourceDir = "C:\Users\Ryan\Desktop\ai xiaoshuo",
|
|
8
|
+
[string]$Branch = "main"
|
|
9
|
+
)
|
|
10
|
+
$ErrorActionPreference = 'Stop'
|
|
11
|
+
$token = $env:GH_TOKEN
|
|
12
|
+
if ([string]::IsNullOrEmpty($token)) { Write-Host "GH_TOKEN env var required"; exit 1 }
|
|
13
|
+
$headers = @{
|
|
14
|
+
Authorization = "Bearer $token"
|
|
15
|
+
'User-Agent' = 'dsh-novel-forge-uploader'
|
|
16
|
+
'X-GitHub-Api-Version' = '2022-11-28'
|
|
17
|
+
}
|
|
18
|
+
$base = "https://api.github.com/repos/$Owner/$Repo/contents"
|
|
19
|
+
$files = Get-ChildItem $SourceDir -Recurse -File
|
|
20
|
+
$log = Join-Path $SourceDir 'upload-log.txt'
|
|
21
|
+
"=== upload started $(Get-Date) ===" | Set-Content $log -Encoding UTF8
|
|
22
|
+
$ok = 0; $failed = 0
|
|
23
|
+
foreach ($file in $files) {
|
|
24
|
+
$rel = $file.FullName.Substring($SourceDir.Length + 1).Replace('\', '/')
|
|
25
|
+
$bytes = [System.IO.File]::ReadAllBytes($file.FullName)
|
|
26
|
+
$contentB64 = [System.Convert]::ToBase64String($bytes)
|
|
27
|
+
$body = @{ message = "Add $rel"; content = $contentB64; branch = $Branch } | ConvertTo-Json
|
|
28
|
+
$done = $false
|
|
29
|
+
for ($attempt = 1; $attempt -le 2 -and -not $done; $attempt++) {
|
|
30
|
+
try {
|
|
31
|
+
$null = Invoke-RestMethod -Uri "$base/$rel" -Headers $headers -Method PUT -Body $body -ContentType "application/json" -TimeoutSec 60
|
|
32
|
+
$done = $true
|
|
33
|
+
$ok++
|
|
34
|
+
"[OK] $rel" | Add-Content $log -Encoding UTF8
|
|
35
|
+
} catch {
|
|
36
|
+
$resp = $_.Exception.Response
|
|
37
|
+
$status = if ($resp) { [int]$resp.StatusCode } else { 0 }
|
|
38
|
+
if ($attempt -eq 2 -or $status -ne 502 -and $status -ne 503) {
|
|
39
|
+
$msg = if ($resp) { (New-Object System.IO.StreamReader($resp.GetResponseStream())).ReadToEnd() } else { $_.Exception.Message }
|
|
40
|
+
$failed++
|
|
41
|
+
"[FAIL] $rel -> HTTP $status $msg" | Add-Content $log -Encoding UTF8
|
|
42
|
+
Write-Host "[FAIL] $rel -> HTTP $status"
|
|
43
|
+
break
|
|
44
|
+
}
|
|
45
|
+
Start-Sleep -Seconds 3
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if ($done) { Write-Host "[OK] $rel" }
|
|
49
|
+
}
|
|
50
|
+
"=== done: ok=$ok failed=$failed ===" | Add-Content $log -Encoding UTF8
|
|
51
|
+
Write-Host "=== done: ok=$ok failed=$failed ==="
|
|
@@ -1204,24 +1204,6 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
1204
1204
|
}
|
|
1205
1205
|
}
|
|
1206
1206
|
|
|
1207
|
-
/** 更新角色漫画重要性(main/support/extra)。 */
|
|
1208
|
-
const handleRoleImportance = async (role: RoleRecord, importance: RoleRecord['importance']): Promise<void> => {
|
|
1209
|
-
setBusy(true)
|
|
1210
|
-
setBusyLabel(`更新「${role.name}」重要性…`)
|
|
1211
|
-
setError('')
|
|
1212
|
-
try {
|
|
1213
|
-
await api.roles({ op: 'update', role: { ...role, importance } })
|
|
1214
|
-
await refresh(false)
|
|
1215
|
-
pushProgress(`已更新「${role.name}」重要性`, 'done')
|
|
1216
|
-
} catch (err) {
|
|
1217
|
-
setError((err as Error).message)
|
|
1218
|
-
pushProgress(`更新「${role.name}」重要性失败:${(err as Error).message}`, 'error')
|
|
1219
|
-
} finally {
|
|
1220
|
-
setBusy(false)
|
|
1221
|
-
setBusyLabel('')
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
1207
|
/** 作者复盘补跑:全书缺失章节(流式)。 */
|
|
1226
1208
|
const handleAuthorBackfillAll = async (): Promise<void> => {
|
|
1227
1209
|
const missing = chapters.filter(c => c.status !== 'pending' && c.status !== 'generating' && c.status !== 'error' && c.authorReview === undefined).length
|
|
@@ -3818,7 +3800,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3818
3800
|
<option value="0">关(默认)</option>
|
|
3819
3801
|
<option value="1">开</option>
|
|
3820
3802
|
</select>
|
|
3821
|
-
<span className={css.meta}
|
|
3803
|
+
<span className={css.meta}>开启后角色详情显示「豆包生成」参考图按钮</span>
|
|
3822
3804
|
</div>
|
|
3823
3805
|
</div>
|
|
3824
3806
|
<div className={css.row}>
|
|
@@ -4396,7 +4378,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4396
4378
|
<div className={css.card} style={{ flex: 1, minHeight: 0 }}>
|
|
4397
4379
|
<div className={css.row} style={{ justifyContent: 'space-between', flexWrap: 'wrap' }}>
|
|
4398
4380
|
<span className={css.cardTitle} style={{ fontSize: 17, fontWeight: 700 }}>🖼️ 角色形象</span>
|
|
4399
|
-
<span className={css.meta}>角色形象锚点 /
|
|
4381
|
+
<span className={css.meta}>角色形象锚点 / 生图提示词包 / 参考图集——复制提示词即可在即梦、豆包等工具出图</span>
|
|
4400
4382
|
</div>
|
|
4401
4383
|
|
|
4402
4384
|
<input
|
|
@@ -4663,10 +4645,10 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4663
4645
|
for (const e of kit.expressions) blocks.push({ key: 'exp-' + e.name, title: `表情·${e.name}`, zh: e.zh, en: e.en })
|
|
4664
4646
|
blocks.push({ key: 'details', title: '细节', zh: kit.details.zh, en: kit.details.en })
|
|
4665
4647
|
} else {
|
|
4666
|
-
blocks.push({ key: 'portrait', title: '立绘', zh: anchor.zh + '
|
|
4667
|
-
blocks.push({ key: 'sheet', title: '四视图', zh: anchor.zh + '
|
|
4668
|
-
for (const n of expressions) blocks.push({ key: 'exp-' + n, title: `表情·${expName(n)}`, zh: anchor.zh + `。表情:${expName(n)}
|
|
4669
|
-
blocks.push({ key: 'details', title: '细节', zh:
|
|
4648
|
+
blocks.push({ key: 'portrait', title: '立绘', zh: '正面站立全身人像,3D动漫,超精细建模,纯白纯色背景,' + anchor.zh + '。角色设计稿,服装磨损、标志物细节完整展示,无多余杂物,全身完整无裁切。' + rulesZh, en: anchor.en + ', full body, front view, plain white background, 3d render, character design sheet' })
|
|
4649
|
+
blocks.push({ key: 'sheet', title: '四视图', zh: '角色设定四视图,3D动漫,超精细建模,纯白纯色背景,' + anchor.zh + '。同一角色正面/左侧面/右侧面/背面,四个视角分别描述,全身完整,服装与标志物细节完整展示,无多余杂物。' + rulesZh, en: anchor.en + ', character sheet, front view, left side view, right side view, back view, full body, plain white background, 3d render' })
|
|
4650
|
+
for (const n of expressions) blocks.push({ key: 'exp-' + n, title: `表情·${expName(n)}`, zh: '脸部特写(头部到锁骨),3D动漫,超精细建模,纯白纯色背景,' + anchor.zh + `。表情:${expName(n)},五官与角色定稿完全一致,皮肤纹理细节完整,无多余杂物。` + rulesZh, en: anchor.en + `, facial close-up, head to collarbone, expression: ${expName(n)}, plain white background, 3d render, consistent with character design, detailed skin texture, no extra objects` })
|
|
4651
|
+
blocks.push({ key: 'details', title: '细节', zh: `多组局部细节集合参考图,3D动漫,超精细建模,纯白纯色背景:${anchor.tags.map(t => t + '特写').join(';')}。细节清晰锐利,角色细节参考稿,无多余杂物。` + rulesZh, en: anchor.tags.join(', ') + ', multi-panel detail reference sheet, plain white background, 3d render, macro close-up, sharp details, character detail sheet, no extra objects' })
|
|
4670
4652
|
}
|
|
4671
4653
|
return (
|
|
4672
4654
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 8 }}>
|
|
@@ -4718,17 +4700,6 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4718
4700
|
onChange={e => { setDetailUploadLabel(e.target.value) }}
|
|
4719
4701
|
/>
|
|
4720
4702
|
<button type="button" className={`${css.button} ${css.buttonSmall}`} disabled={busy} onClick={() => { uploadInDetail(detailRole) }}>📤 上传图</button>
|
|
4721
|
-
<select
|
|
4722
|
-
className={css.input}
|
|
4723
|
-
style={{ fontSize: 12, padding: '3px 4px', width: 90 }}
|
|
4724
|
-
value={detailRole.importance ?? 'support'}
|
|
4725
|
-
onChange={e => { void handleRoleImportance(detailRole, e.target.value as RoleRecord['importance']) }}
|
|
4726
|
-
title="漫剧重要性"
|
|
4727
|
-
>
|
|
4728
|
-
<option value="main">重要</option>
|
|
4729
|
-
<option value="support">配角</option>
|
|
4730
|
-
<option value="extra">路人</option>
|
|
4731
|
-
</select>
|
|
4732
4703
|
</div>
|
|
4733
4704
|
</div>
|
|
4734
4705
|
</div>
|
package/src/engine.ts
CHANGED
|
@@ -1333,7 +1333,7 @@ export async function extractRoleVisual(
|
|
|
1333
1333
|
'- source:说明依据(如"第1章/第8章外貌描写;瞳色未明确")。',
|
|
1334
1334
|
'- expressions:该角色在本书剧情中需要的情绪表情清单数组,6-12 个(如 ["疲惫","麻木","压抑悲伤","皱眉","紧绷","放空"]),依据正文情绪描写与角色处境推断。',
|
|
1335
1335
|
'- promptKit:四类精修提示词(字段化+出图约束,参考示例结构):',
|
|
1336
|
-
' · portrait
|
|
1336
|
+
' · portrait 立绘:严格按字段顺序写(参考示例结构):开头构图定位(正面站立全身人像)→ 风格(3D动漫,超精细建模)→ 背景(纯白纯色背景)→ 身份(男子,{角色名},外表{年龄},男性)→ 发型发色 → 胡茬 → 眼眸 → 面部 → 气质 → 上身服装分件(颜色款式+磨损细节)→ 下身 → 鞋 → 标志物(标签/印记,含细节如洇暗翘边)→ 收尾(角色设计稿,细节完整展示,无多余杂物,全身完整无裁切)。',
|
|
1337
1337
|
' · sheet 四视图:同一角色的 正面/左侧面/右侧面/背面 四视图设定表(character sheet),纯白背景,全身完整,四个视角分别描述,服装与标志物细节完整展示。',
|
|
1338
1338
|
' · expressions 表情:每个表情一段,脸部特写(头部到锁骨),纯白背景,五官与角色定稿完全一致,只换情绪表达(眼神/嘴角/眉),皮肤纹理细节完整,无多余杂物。',
|
|
1339
1339
|
' · details 细节:多组局部细节集合参考图(一张图多个局部框),纯白背景;把该角色全部标志物逐项列出(如标签/印记/工牌/袖口磨损/鞋),每项一句特写描述;细节清晰锐利,角色细节参考稿,无多余杂物。',
|
|
@@ -1407,7 +1407,7 @@ export async function generateRolePromptKit(
|
|
|
1407
1407
|
const system = [
|
|
1408
1408
|
'你是一位 AI 绘图提示词工程师。基于给定角色的形象锚点、表情清单与本书视觉规则,输出四类生图提示词(每类 zh+en 各一段)。',
|
|
1409
1409
|
'四类:',
|
|
1410
|
-
'1. portrait
|
|
1410
|
+
'1. portrait 立绘:正面站立全身人像,3D动漫超精细建模,纯白纯色背景;按字段流组织:身份(男子,角色名,外表年龄)→ 发型发色 → 胡茬 → 眼眸 → 面部 → 气质 → 上身服装分件(含磨损)→ 下身 → 鞋 → 标志物(含细节)→ 收尾(角色设计稿,细节完整展示,无多余杂物,全身完整无裁切)。',
|
|
1411
1411
|
'2. sheet 四视图:同一角色的 正面/左侧面/右侧面/背面 四视图设定表(character sheet),纯白背景,四个视角分别描述。',
|
|
1412
1412
|
'3. expressions 表情:每个表情一段,脸部特写(头部到锁骨),纯白背景,五官与角色定稿完全一致,只换情绪表达(眼神/嘴角/眉),皮肤纹理细节完整,无多余杂物。',
|
|
1413
1413
|
'4. details 细节:多组局部细节集合参考图(一张图多个局部框),纯白背景;把该角色全部标志物逐项列出,每项一句特写描述;细节清晰锐利,角色细节参考稿,无多余杂物。',
|
package/src/routes.ts
CHANGED
|
@@ -1699,7 +1699,7 @@ export function makeRoutes(deps: NovelRoutesDeps): WebRoute[] {
|
|
|
1699
1699
|
}
|
|
1700
1700
|
const role = project.roles.find(r => r.name === name)
|
|
1701
1701
|
if (role === undefined) {
|
|
1702
|
-
writeJson(res, 404, { error:
|
|
1702
|
+
writeJson(res, 404, { error: `当前激活《${project.bookName}》,角色 ${name} 不存在——若期望的书不对,请先在书架切换该书并刷新` })
|
|
1703
1703
|
return
|
|
1704
1704
|
}
|
|
1705
1705
|
const label = body?.label?.trim() ?? ''
|
|
@@ -2079,7 +2079,7 @@ export function makeRoutes(deps: NovelRoutesDeps): WebRoute[] {
|
|
|
2079
2079
|
}
|
|
2080
2080
|
const scene = project.scenes.find(x => x.name === name)
|
|
2081
2081
|
if (scene === undefined) {
|
|
2082
|
-
writeJson(res, 404, { error:
|
|
2082
|
+
writeJson(res, 404, { error: `当前激活《${project.bookName}》,场景 ${name} 不存在——若期望的书不对,请先在书架切换该书并刷新` })
|
|
2083
2083
|
return
|
|
2084
2084
|
}
|
|
2085
2085
|
const label = body?.label?.trim() ?? '全景'
|