@waterwx/dsh-novel-forge 1.7.2 → 1.8.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/README.md +1 -1
- package/lib/client.js +3788 -600
- package/lib/client.js.map +1 -1
- package/lib/index.js +1001 -83
- package/lib/index.js.map +1 -1
- package/lib/types/author-assets.d.ts +25 -0
- package/lib/types/client/api.d.ts +18 -0
- package/lib/types/client/panel/AdaptModeView.d.ts +4 -0
- package/lib/types/client/panel/AuthorAssetsView.d.ts +4 -0
- package/lib/types/client/panel/AuthorHome.d.ts +20 -0
- package/lib/types/client/panel/ProgressHomeView.d.ts +5 -0
- package/lib/types/client/panel/SettingsView.d.ts +14 -0
- package/lib/types/client/text.d.ts +9 -0
- package/lib/types/engine.d.ts +24 -1
- package/lib/types/index.d.ts +8 -0
- package/lib/types/protocol.d.ts +206 -10
- package/lib/types/shot-language.d.ts +46 -0
- package/lib/types/story-beat-language.d.ts +35 -0
- package/package.json +4 -2
- package/skills/chapter-batch/SKILL.md +81 -0
- package/src/author-assets.ts +166 -0
- package/src/client/api.ts +41 -0
- package/src/client/mount.tsx._new +97 -0
- package/src/client/mount.tsx._nf +97 -0
- package/src/client/mount.tsx._tmp +97 -0
- package/src/client/panel/AdaptModeView.tsx +204 -0
- package/src/client/panel/AuthorAssetsView.tsx +206 -0
- package/src/client/panel/AuthorHome.tsx +93 -0
- package/src/client/panel/CreateBookView.tsx +4 -0
- package/src/client/panel/ImportModal.tsx +13 -3
- package/src/client/panel/NovelPanel.tsx +48 -16
- package/src/client/panel/ProgressHomeView.tsx +87 -0
- package/src/client/panel/RunPanel.tsx +1 -1
- package/src/client/panel/SceneLibrary.tsx +1 -1
- package/src/client/panel/SettingsView.tsx +285 -0
- package/src/client/panel/ShelfView.tsx +2 -2
- package/src/client/panel/StoryboardTab.tsx +42 -10
- package/src/client/panel/panel.module.css +325 -0
- package/src/client/sidebar-entry.ts +1 -1
- package/src/client/text.ts +28 -0
- package/src/engine.ts +256 -14
- package/src/index.ts +21 -0
- package/src/protocol.ts +218 -10
- package/src/routes.ts +204 -7
- package/src/run.ts +5 -4
- package/src/shot-language.ts +181 -0
- package/src/story-beat-language.ts +130 -0
- package/lib/types/client/panel/RoleVisualPanel.d.ts +0 -15
|
@@ -5,8 +5,25 @@
|
|
|
5
5
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
6
6
|
import type { NovelApi } from '../api.ts'
|
|
7
7
|
import type { ChapterPlan, ProjectState, StoryboardPrompt, StoryboardSkeleton, StoryboardTable } from '../../protocol.ts'
|
|
8
|
+
import {
|
|
9
|
+
sizeZh, cameraZh, compoZh, lightZh,
|
|
10
|
+
normalizeShotSize, normalizeCameras, normalizeComposition, normalizeLightings,
|
|
11
|
+
} from '../../shot-language.ts'
|
|
12
|
+
import { functionZh, emotionZh, normalizeStoryFunction, normalizeEmotions } from '../../story-beat-language.ts'
|
|
13
|
+
import type { StoryboardShot } from '../../protocol.ts'
|
|
8
14
|
import css from './panel.module.css'
|
|
9
15
|
|
|
16
|
+
/** 归一化旧分镜数据:把自由文本 shot/camera/light/composition 转成词库枚举。 */
|
|
17
|
+
function normalizeStoryboardShot(s: StoryboardShot): StoryboardShot {
|
|
18
|
+
return {
|
|
19
|
+
...s,
|
|
20
|
+
shot: normalizeShotSize(typeof s.shot === 'string' ? s.shot : undefined),
|
|
21
|
+
camera: normalizeCameras(typeof s.camera === 'string' ? s.camera : undefined),
|
|
22
|
+
composition: typeof s.composition === 'string' ? normalizeComposition(s.composition) : s.composition,
|
|
23
|
+
light: normalizeLightings(typeof s.light === 'string' ? s.light : undefined),
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
10
27
|
export function StoryboardTab({
|
|
11
28
|
api,
|
|
12
29
|
project,
|
|
@@ -76,8 +93,23 @@ export function StoryboardTab({
|
|
|
76
93
|
if (chapterNo === null) return
|
|
77
94
|
if (suppressRestoreRef.current === true) return
|
|
78
95
|
const entry = (project?.storyboards ?? []).find(e => e.chapterNo === chapterNo)
|
|
79
|
-
setSkeleton(prev =>
|
|
80
|
-
|
|
96
|
+
setSkeleton(prev => {
|
|
97
|
+
if (prev !== null) return prev
|
|
98
|
+
if (entry?.skeleton === undefined) return null
|
|
99
|
+
return {
|
|
100
|
+
...entry.skeleton,
|
|
101
|
+
beats: (entry.skeleton.beats ?? []).map(b => ({
|
|
102
|
+
...b,
|
|
103
|
+
function: normalizeStoryFunction(typeof b.function === 'string' ? b.function : undefined),
|
|
104
|
+
emotion: normalizeEmotions(typeof b.emotion === 'string' ? b.emotion : b.emotion?.join('→')),
|
|
105
|
+
})),
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
setTable(prev => {
|
|
109
|
+
if (prev !== null) return prev
|
|
110
|
+
if (entry?.table === undefined) return null
|
|
111
|
+
return { ...entry.table, shots: (entry.table.shots ?? []).map(normalizeStoryboardShot) }
|
|
112
|
+
})
|
|
81
113
|
setPrompts(prev => prev ?? entry?.prompts ?? null)
|
|
82
114
|
}, [chapterNo, project?.storyboards])
|
|
83
115
|
|
|
@@ -268,8 +300,8 @@ export function StoryboardTab({
|
|
|
268
300
|
<div key={b.id} style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-4)', border: '1px solid var(--nf-border)', borderRadius: 'var(--nf-radius-10)', padding: 'var(--nf-space-8) var(--nf-space-10)' }}>
|
|
269
301
|
<div className={css.row} style={{ flexWrap: 'wrap' }}>
|
|
270
302
|
<b>节拍 {i + 1}</b>
|
|
271
|
-
<span className={`${css.badge} ${b.function === '
|
|
272
|
-
<span className={css.meta}
|
|
303
|
+
<span className={`${css.badge} ${b.function === 'climax' ? css.badgeDone : b.function === 'turn' ? css.badgeWritten : css.badgePending}`}>{functionZh(b.function)}</span>
|
|
304
|
+
<span className={css.meta}>💭 {emotionZh(b.emotion)}</span>
|
|
273
305
|
</div>
|
|
274
306
|
<span>{b.event}</span>
|
|
275
307
|
{b.cause !== undefined && <span className={css.meta}>承接:{b.cause}</span>}
|
|
@@ -280,7 +312,7 @@ export function StoryboardTab({
|
|
|
280
312
|
type="button"
|
|
281
313
|
className={`${css.button} ${css.buttonSmall}`}
|
|
282
314
|
onClick={() => {
|
|
283
|
-
const text = `第${skeleton.chapterNo}章 弧线:${skeleton.arc}\n出场角色:${skeleton.characters !== undefined && skeleton.characters.length > 0 ? skeleton.characters.join('、') : '(未标注)'}\n\n` + skeleton.beats.map((b, i) => `${i + 1}. [${b.function}] ${b.event}(情绪:${b.emotion})${b.cause !== undefined ? `[承接:${b.cause}]` : ''}`).join('\n')
|
|
315
|
+
const text = `第${skeleton.chapterNo}章 弧线:${skeleton.arc}\n出场角色:${skeleton.characters !== undefined && skeleton.characters.length > 0 ? skeleton.characters.join('、') : '(未标注)'}\n\n` + skeleton.beats.map((b, i) => `${i + 1}. [${functionZh(b.function)}] ${b.event}(情绪:${emotionZh(b.emotion)})${b.cause !== undefined ? `[承接:${b.cause}]` : ''}`).join('\n')
|
|
284
316
|
void navigator.clipboard?.writeText(text)
|
|
285
317
|
}}
|
|
286
318
|
>
|
|
@@ -318,7 +350,7 @@ export function StoryboardTab({
|
|
|
318
350
|
onClick={() => {
|
|
319
351
|
const text = table.shots.map(s => {
|
|
320
352
|
const beat = skeleton?.beats.find(b => b.id === s.beatId)
|
|
321
|
-
return `镜头 ${s.id}(节拍 ${beat?.id ?? s.beatId} · ${s.shot} · ${s.camera} · ${s.duration}s)\n出场:${s.characters !== undefined && s.characters.length > 0 ? s.characters.join('、') : '(未标注)'}\n定妆:${s.mangaRoleIds !== undefined && s.mangaRoleIds.length > 0 ? bindingNames(s.mangaRoleIds) : '(未绑定漫剧卡)'}\n画面:${s.visual}\n台词:${s.line !== '' ? s.line : '(无)'}\n音效:${s.sound !== '' ? s.sound : '(无)'}\n光效:${s.light !== '' ? s.light : '(无)'}\n承接:${s.prevState} → ${s.nextState}`
|
|
353
|
+
return `镜头 ${s.id}(节拍 ${beat?.id ?? s.beatId} · ${sizeZh(s.shot)} · ${cameraZh(s.camera)} · ${s.duration}s)\n出场:${s.characters !== undefined && s.characters.length > 0 ? s.characters.join('、') : '(未标注)'}\n定妆:${s.mangaRoleIds !== undefined && s.mangaRoleIds.length > 0 ? bindingNames(s.mangaRoleIds) : '(未绑定漫剧卡)'}\n画面:${s.visual}\n台词:${s.line !== '' ? s.line : '(无)'}\n音效:${s.sound !== '' ? s.sound : '(无)'}\n光效:${lightZh(s.light) !== '' ? lightZh(s.light) : '(无)'}\n承接:${s.prevState} → ${s.nextState}`
|
|
322
354
|
}).join('\n\n')
|
|
323
355
|
void navigator.clipboard?.writeText(`第${table.chapterNo}章分镜表\n\n` + text)
|
|
324
356
|
}}
|
|
@@ -357,9 +389,9 @@ export function StoryboardTab({
|
|
|
357
389
|
<div key={s.id} style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-4)', border: '1px solid var(--nf-border)', borderRadius: 'var(--nf-radius-10)', padding: 'var(--nf-space-8) var(--nf-space-10)' }}>
|
|
358
390
|
<div className={css.row} style={{ flexWrap: 'wrap' }}>
|
|
359
391
|
<b>镜头 {s.id}</b>
|
|
360
|
-
<span className={css.badge}>{s.shot}</span>
|
|
361
|
-
<span className={css.meta}>{s.camera} · {s.duration}s</span>
|
|
362
|
-
{beat !== undefined && <span className={css.meta}>节拍 {beat.id}「{beat.function}」</span>}
|
|
392
|
+
<span className={css.badge}>{sizeZh(s.shot)}</span>
|
|
393
|
+
<span className={css.meta}>{cameraZh(s.camera)}{compoZh(s.composition) !== '' ? ' · ' + compoZh(s.composition) : ''} · {s.duration}s</span>
|
|
394
|
+
{beat !== undefined && <span className={css.meta}>节拍 {beat.id}「{functionZh(beat.function)}」</span>}
|
|
363
395
|
{s.characters !== undefined && s.characters.length > 0 && (
|
|
364
396
|
<span className={css.meta}>👥 {s.characters.join('、')}</span>
|
|
365
397
|
)}
|
|
@@ -371,7 +403,7 @@ export function StoryboardTab({
|
|
|
371
403
|
<div className={css.row} style={{ flexWrap: 'wrap' }}>
|
|
372
404
|
<span className={css.meta}>💬 {s.line !== '' ? s.line : '(无台词)'}</span>
|
|
373
405
|
<span className={css.meta}>🔊 {s.sound !== '' ? s.sound : '(无音效)'}</span>
|
|
374
|
-
<span className={css.meta}>💡 {s.light !== '' ? s.light : '(无光效)'}</span>
|
|
406
|
+
<span className={css.meta}>💡 {lightZh(s.light) !== '' ? lightZh(s.light) : '(无光效)'}</span>
|
|
375
407
|
</div>
|
|
376
408
|
<span className={css.meta}>承接:{s.prevState} → {s.nextState}</span>
|
|
377
409
|
</div>
|
|
@@ -41,6 +41,8 @@ body[data-ds-dark-theme] .entry {
|
|
|
41
41
|
align-items: center;
|
|
42
42
|
justify-content: center;
|
|
43
43
|
flex-shrink: 0;
|
|
44
|
+
width: 20px;
|
|
45
|
+
height: 20px;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
.entryLabel {
|
|
@@ -658,6 +660,7 @@ body[data-ds-dark-theme] .panel[data-nf-theme='neumorph'] .buttonPrimary {
|
|
|
658
660
|
z-index: 0;
|
|
659
661
|
pointer-events: none;
|
|
660
662
|
background:
|
|
663
|
+
linear-gradient(rgba(0, 0, 0, calc(var(--nf-glass-bg-dim, 0) / 100)), rgba(0, 0, 0, calc(var(--nf-glass-bg-dim, 0) / 100))),
|
|
661
664
|
var(--nf-glass-bg-image, none) center / cover no-repeat,
|
|
662
665
|
radial-gradient(46% 36% at 18% 12%, var(--nf-glass-orb-a), transparent 70%),
|
|
663
666
|
radial-gradient(52% 42% at 88% 22%, var(--nf-glass-orb-b), transparent 72%),
|
|
@@ -981,6 +984,10 @@ body[data-ds-dark-theme] .panel[data-nf-theme='neumorph'] .buttonPrimary {
|
|
|
981
984
|
.navTabIcon {
|
|
982
985
|
flex: none;
|
|
983
986
|
width: 22px;
|
|
987
|
+
height: 22px;
|
|
988
|
+
display: inline-flex;
|
|
989
|
+
align-items: center;
|
|
990
|
+
justify-content: center;
|
|
984
991
|
text-align: center;
|
|
985
992
|
font-size: var(--nf-fs-16, 17px);
|
|
986
993
|
line-height: 1;
|
|
@@ -2079,6 +2086,28 @@ select.input {
|
|
|
2079
2086
|
gap: var(--nf-space-10, 10px);
|
|
2080
2087
|
}
|
|
2081
2088
|
|
|
2089
|
+
/* 按分类分组的资产列表 */
|
|
2090
|
+
.assetGroups {
|
|
2091
|
+
display: flex;
|
|
2092
|
+
flex-direction: column;
|
|
2093
|
+
gap: var(--nf-space-14, 14px);
|
|
2094
|
+
}
|
|
2095
|
+
.assetGroup {
|
|
2096
|
+
display: flex;
|
|
2097
|
+
flex-direction: column;
|
|
2098
|
+
gap: var(--nf-space-10, 10px);
|
|
2099
|
+
}
|
|
2100
|
+
.assetGroupHeader {
|
|
2101
|
+
font-size: 14px;
|
|
2102
|
+
font-weight: 600;
|
|
2103
|
+
color: var(--nf-accent);
|
|
2104
|
+
padding-bottom: 2px;
|
|
2105
|
+
border-bottom: 1px solid var(--nf-glass-border);
|
|
2106
|
+
display: flex;
|
|
2107
|
+
align-items: center;
|
|
2108
|
+
gap: 8px;
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2082
2111
|
.assetStat {
|
|
2083
2112
|
border: 1px solid var(--nf-glass-border);
|
|
2084
2113
|
border-radius: var(--nf-radius-14, 14px);
|
|
@@ -2498,6 +2527,279 @@ select.input {
|
|
|
2498
2527
|
gap: var(--nf-container-gap, 14px);
|
|
2499
2528
|
}
|
|
2500
2529
|
|
|
2530
|
+
/* ---- 作者级首页(书架左侧导航 + 内容区) ---- */
|
|
2531
|
+
.authorHome {
|
|
2532
|
+
display: flex;
|
|
2533
|
+
flex: 1;
|
|
2534
|
+
min-height: 0;
|
|
2535
|
+
overflow: hidden;
|
|
2536
|
+
position: relative;
|
|
2537
|
+
z-index: 1;
|
|
2538
|
+
}
|
|
2539
|
+
.authorRail {
|
|
2540
|
+
width: 180px;
|
|
2541
|
+
flex-shrink: 0;
|
|
2542
|
+
display: flex;
|
|
2543
|
+
flex-direction: column;
|
|
2544
|
+
gap: 4px;
|
|
2545
|
+
padding: 14px 10px;
|
|
2546
|
+
border-right: 1px solid var(--nf-glass-border);
|
|
2547
|
+
background: var(--nf-glass-face);
|
|
2548
|
+
}
|
|
2549
|
+
.authorRailTitle {
|
|
2550
|
+
display: flex;
|
|
2551
|
+
align-items: center;
|
|
2552
|
+
gap: 8px;
|
|
2553
|
+
font-weight: 600;
|
|
2554
|
+
padding: 4px 8px 12px;
|
|
2555
|
+
color: var(--nf-accent);
|
|
2556
|
+
}
|
|
2557
|
+
.authorRailBtn {
|
|
2558
|
+
display: flex;
|
|
2559
|
+
align-items: center;
|
|
2560
|
+
gap: 10px;
|
|
2561
|
+
padding: 8px 12px;
|
|
2562
|
+
border-radius: 10px;
|
|
2563
|
+
background: transparent;
|
|
2564
|
+
border: 1px solid transparent;
|
|
2565
|
+
color: var(--nf-text);
|
|
2566
|
+
cursor: pointer;
|
|
2567
|
+
text-align: left;
|
|
2568
|
+
width: 100%;
|
|
2569
|
+
font-size: 14px;
|
|
2570
|
+
}
|
|
2571
|
+
.authorRailBtn:hover { background: color-mix(in srgb, var(--nf-accent) 12%, transparent); color: var(--nf-text); }
|
|
2572
|
+
.authorRailBtnActive {
|
|
2573
|
+
background: color-mix(in srgb, var(--nf-accent) 16%, var(--nf-glass-face));
|
|
2574
|
+
border-color: var(--nf-accent);
|
|
2575
|
+
color: var(--nf-accent);
|
|
2576
|
+
font-weight: 600;
|
|
2577
|
+
box-shadow: inset 0 0 0 1px var(--nf-accent);
|
|
2578
|
+
}
|
|
2579
|
+
.authorRailAbout { padding: 8px 12px; }
|
|
2580
|
+
.authorContent { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
2581
|
+
|
|
2582
|
+
/* ---- 作者级页面通用 ---- */
|
|
2583
|
+
.authorPageBody {
|
|
2584
|
+
display: flex;
|
|
2585
|
+
flex-direction: column;
|
|
2586
|
+
gap: 14px;
|
|
2587
|
+
padding: var(--nf-container-pad, 20px 22px);
|
|
2588
|
+
overflow: auto;
|
|
2589
|
+
flex: 1;
|
|
2590
|
+
min-height: 0;
|
|
2591
|
+
}
|
|
2592
|
+
.authorPageHeader {
|
|
2593
|
+
display: flex;
|
|
2594
|
+
align-items: flex-end;
|
|
2595
|
+
justify-content: space-between;
|
|
2596
|
+
gap: 14px;
|
|
2597
|
+
flex-wrap: wrap;
|
|
2598
|
+
}
|
|
2599
|
+
.authorPageHero {
|
|
2600
|
+
display: flex;
|
|
2601
|
+
flex-direction: column;
|
|
2602
|
+
gap: var(--nf-space-12, 12px);
|
|
2603
|
+
padding: var(--nf-card-pad, 20px 22px);
|
|
2604
|
+
border: 1px solid var(--nf-glass-border);
|
|
2605
|
+
border-radius: var(--nf-radius-22, 22px);
|
|
2606
|
+
background: var(--nf-glass-face);
|
|
2607
|
+
box-shadow: var(--nf-shadow), inset 0 1.5px 0 var(--nf-glass-sheen);
|
|
2608
|
+
-webkit-backdrop-filter: blur(26px) saturate(175%);
|
|
2609
|
+
backdrop-filter: blur(26px) saturate(175%);
|
|
2610
|
+
}
|
|
2611
|
+
.noticeError {
|
|
2612
|
+
padding: 10px 14px;
|
|
2613
|
+
border-radius: 10px;
|
|
2614
|
+
background: color-mix(in srgb, var(--nf-error) 14%, transparent);
|
|
2615
|
+
color: var(--nf-error);
|
|
2616
|
+
border: 1px solid var(--nf-error);
|
|
2617
|
+
}
|
|
2618
|
+
.rowEnd { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
2619
|
+
|
|
2620
|
+
/* ---- 作者资产库 ---- */
|
|
2621
|
+
.assetFilterBar { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
2622
|
+
.assetFilterChip {
|
|
2623
|
+
padding: 4px 10px;
|
|
2624
|
+
border-radius: 999px;
|
|
2625
|
+
border: 1px solid var(--nf-glass-border);
|
|
2626
|
+
background: transparent;
|
|
2627
|
+
color: var(--nf-text);
|
|
2628
|
+
cursor: pointer;
|
|
2629
|
+
}
|
|
2630
|
+
.assetFilterChipActive { background: var(--nf-accent); color: #fff; border-color: transparent; }
|
|
2631
|
+
.assetEditor {
|
|
2632
|
+
display: flex;
|
|
2633
|
+
flex-direction: column;
|
|
2634
|
+
gap: 12px;
|
|
2635
|
+
padding: 16px;
|
|
2636
|
+
border: 1px solid var(--nf-glass-border);
|
|
2637
|
+
border-radius: 14px;
|
|
2638
|
+
background: var(--nf-glass-face);
|
|
2639
|
+
}
|
|
2640
|
+
.assetEditorRow { display: flex; gap: 12px; flex-wrap: wrap; }
|
|
2641
|
+
.assetLabel {
|
|
2642
|
+
display: flex;
|
|
2643
|
+
flex-direction: column;
|
|
2644
|
+
gap: 4px;
|
|
2645
|
+
font-size: 12px;
|
|
2646
|
+
color: var(--nf-muted);
|
|
2647
|
+
flex: 1;
|
|
2648
|
+
min-width: 180px;
|
|
2649
|
+
}
|
|
2650
|
+
.assetTextarea { min-height: 160px; resize: vertical; font-family: inherit; }
|
|
2651
|
+
.assetGrid {
|
|
2652
|
+
display: grid;
|
|
2653
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
2654
|
+
gap: 14px;
|
|
2655
|
+
}
|
|
2656
|
+
.assetCard {
|
|
2657
|
+
display: flex;
|
|
2658
|
+
flex-direction: column;
|
|
2659
|
+
gap: 8px;
|
|
2660
|
+
padding: 14px;
|
|
2661
|
+
border: 1px solid var(--nf-glass-border);
|
|
2662
|
+
border-radius: 14px;
|
|
2663
|
+
background: var(--nf-glass-face);
|
|
2664
|
+
box-shadow: var(--nf-shadow), inset 0 1px 0 var(--nf-glass-sheen);
|
|
2665
|
+
}
|
|
2666
|
+
.assetCardTop { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
2667
|
+
.assetKind { font-size: 12px; font-weight: 600; }
|
|
2668
|
+
.assetCardName { font-weight: 600; }
|
|
2669
|
+
.assetCardContent { white-space: pre-wrap; font-size: 13px; color: var(--nf-text); max-height: 150px; overflow: auto; }
|
|
2670
|
+
.assetCardFooter { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
|
|
2671
|
+
.iconBtn {
|
|
2672
|
+
display: inline-flex;
|
|
2673
|
+
align-items: center;
|
|
2674
|
+
justify-content: center;
|
|
2675
|
+
padding: 4px;
|
|
2676
|
+
border: 1px solid transparent;
|
|
2677
|
+
background: transparent;
|
|
2678
|
+
border-radius: 8px;
|
|
2679
|
+
color: var(--nf-text);
|
|
2680
|
+
cursor: pointer;
|
|
2681
|
+
}
|
|
2682
|
+
.iconBtn:hover { background: rgba(0,0,0,0.06); }
|
|
2683
|
+
|
|
2684
|
+
/* ---- 改编模式 ---- */
|
|
2685
|
+
.adaptInputCard {
|
|
2686
|
+
display: flex;
|
|
2687
|
+
flex-direction: column;
|
|
2688
|
+
gap: 12px;
|
|
2689
|
+
padding: 16px;
|
|
2690
|
+
border: 1px solid var(--nf-glass-border);
|
|
2691
|
+
border-radius: 14px;
|
|
2692
|
+
background: var(--nf-glass-face);
|
|
2693
|
+
}
|
|
2694
|
+
.adaptTextarea { min-height: 220px; resize: vertical; }
|
|
2695
|
+
.adaptResult { display: flex; flex-direction: column; gap: 14px; }
|
|
2696
|
+
.adaptOutline {
|
|
2697
|
+
padding: 10px 14px;
|
|
2698
|
+
border-radius: 12px;
|
|
2699
|
+
background: var(--nf-glass-face);
|
|
2700
|
+
border: 1px solid var(--nf-glass-border);
|
|
2701
|
+
}
|
|
2702
|
+
.adaptMatrix {
|
|
2703
|
+
display: grid;
|
|
2704
|
+
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
2705
|
+
gap: 12px;
|
|
2706
|
+
}
|
|
2707
|
+
.dimCard {
|
|
2708
|
+
display: flex;
|
|
2709
|
+
flex-direction: column;
|
|
2710
|
+
gap: 8px;
|
|
2711
|
+
padding: 14px;
|
|
2712
|
+
border: 1px solid var(--nf-glass-border);
|
|
2713
|
+
border-radius: 14px;
|
|
2714
|
+
background: var(--nf-glass-face);
|
|
2715
|
+
}
|
|
2716
|
+
.dimCardHead { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
2717
|
+
.dimTitle { font-weight: 600; }
|
|
2718
|
+
.dimCurrent { font-size: 13px; white-space: pre-wrap; }
|
|
2719
|
+
.dimCardSelected { border-color: var(--nf-accent); box-shadow: 0 0 0 1px var(--nf-accent); }
|
|
2720
|
+
.dimEdit { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
2721
|
+
.adaptOutlinePre { white-space: pre-wrap; font-size: 13px; max-height: 260px; overflow: auto; margin: 0; font-family: inherit; }
|
|
2722
|
+
.adaptProposal {
|
|
2723
|
+
display: flex;
|
|
2724
|
+
flex-direction: column;
|
|
2725
|
+
gap: 8px;
|
|
2726
|
+
padding: 14px;
|
|
2727
|
+
border: 1px solid var(--nf-glass-border);
|
|
2728
|
+
border-radius: 14px;
|
|
2729
|
+
background: var(--nf-glass-face);
|
|
2730
|
+
}
|
|
2731
|
+
.adaptTable { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
2732
|
+
.adaptTable th, .adaptTable td { border: 1px solid var(--nf-glass-border); padding: 6px 8px; text-align: left; }
|
|
2733
|
+
.adaptImpacts { display: flex; flex-direction: column; gap: 4px; }
|
|
2734
|
+
.adaptImpact { font-size: 13px; }
|
|
2735
|
+
|
|
2736
|
+
/* ---- 首页 AI 进度 ---- */
|
|
2737
|
+
.progressOverview {
|
|
2738
|
+
display: grid;
|
|
2739
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
2740
|
+
gap: 12px;
|
|
2741
|
+
}
|
|
2742
|
+
.progressOverviewCard {
|
|
2743
|
+
display: flex;
|
|
2744
|
+
flex-direction: column;
|
|
2745
|
+
gap: 4px;
|
|
2746
|
+
padding: 14px;
|
|
2747
|
+
border: 1px solid var(--nf-glass-border);
|
|
2748
|
+
border-radius: 14px;
|
|
2749
|
+
background: var(--nf-glass-face);
|
|
2750
|
+
box-shadow: var(--nf-shadow), inset 0 1px 0 var(--nf-glass-sheen);
|
|
2751
|
+
}
|
|
2752
|
+
.progressOverviewNum {
|
|
2753
|
+
font-size: 24px;
|
|
2754
|
+
font-weight: 700;
|
|
2755
|
+
color: var(--nf-accent);
|
|
2756
|
+
}
|
|
2757
|
+
.progressTotalBar {
|
|
2758
|
+
position: relative;
|
|
2759
|
+
height: 14px;
|
|
2760
|
+
border-radius: 999px;
|
|
2761
|
+
background: var(--nf-glass-face);
|
|
2762
|
+
border: 1px solid var(--nf-glass-border);
|
|
2763
|
+
overflow: hidden;
|
|
2764
|
+
}
|
|
2765
|
+
.progressTotalFill {
|
|
2766
|
+
position: absolute;
|
|
2767
|
+
inset: 0 auto 0 0;
|
|
2768
|
+
height: 100%;
|
|
2769
|
+
background: linear-gradient(90deg, var(--nf-accent), var(--nf-info));
|
|
2770
|
+
border-radius: 999px;
|
|
2771
|
+
}
|
|
2772
|
+
.progressTotalBar .meta {
|
|
2773
|
+
position: absolute;
|
|
2774
|
+
inset: 0;
|
|
2775
|
+
display: flex;
|
|
2776
|
+
align-items: center;
|
|
2777
|
+
justify-content: center;
|
|
2778
|
+
font-size: 11px;
|
|
2779
|
+
font-weight: 600;
|
|
2780
|
+
color: var(--nf-text);
|
|
2781
|
+
}
|
|
2782
|
+
.progressBookList {
|
|
2783
|
+
display: flex;
|
|
2784
|
+
flex-direction: column;
|
|
2785
|
+
gap: 12px;
|
|
2786
|
+
}
|
|
2787
|
+
.progressBook {
|
|
2788
|
+
display: flex;
|
|
2789
|
+
flex-direction: column;
|
|
2790
|
+
gap: 8px;
|
|
2791
|
+
padding: 14px;
|
|
2792
|
+
border: 1px solid var(--nf-glass-border);
|
|
2793
|
+
border-radius: 14px;
|
|
2794
|
+
background: var(--nf-glass-face);
|
|
2795
|
+
}
|
|
2796
|
+
.progressBookActive { border-color: var(--nf-accent); box-shadow: 0 0 0 1px var(--nf-accent); }
|
|
2797
|
+
.progressBookHead { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
2798
|
+
.progressBookName { font-weight: 600; }
|
|
2799
|
+
.progressLive { color: var(--nf-accent); font-weight: 600; }
|
|
2800
|
+
.progressBookBar { height: 8px; border-radius: 999px; background: var(--nf-glass-face-hover, rgba(0,0,0,0.05)); overflow: hidden; }
|
|
2801
|
+
.progressBookFill { height: 100%; background: var(--nf-accent); border-radius: 999px; }
|
|
2802
|
+
|
|
2501
2803
|
.shelfHeader {
|
|
2502
2804
|
display: flex;
|
|
2503
2805
|
flex-direction: column;
|
|
@@ -3847,3 +4149,26 @@ select.input {
|
|
|
3847
4149
|
.switchOn .switchKnob {
|
|
3848
4150
|
transform: translateX(18px);
|
|
3849
4151
|
}
|
|
4152
|
+
|
|
4153
|
+
/* ---- 玻璃透明度滑块:实色主题(新拟态/黏土)调低时变透,透出背景图 ---- */
|
|
4154
|
+
/* 默认 --nf-glass-opacity=100%,color-mix 透明 0% → 与主题原值一致,不改动现有效果。 */
|
|
4155
|
+
.panel[data-nf-theme='neumorph']:not([data-nf-mode='dark']),
|
|
4156
|
+
body:not([data-ds-dark-theme]) .panel[data-nf-theme='neumorph'][data-nf-mode='light'] {
|
|
4157
|
+
--nf-glass-face: color-mix(in srgb, #e4e9f2, transparent calc(100% - var(--nf-glass-opacity, 100%)));
|
|
4158
|
+
--nf-bg-raise: color-mix(in srgb, #e4e9f2, transparent calc(100% - var(--nf-glass-opacity, 100%)));
|
|
4159
|
+
}
|
|
4160
|
+
.panel[data-nf-mode='dark'][data-nf-theme='neumorph'],
|
|
4161
|
+
body[data-ds-dark-theme] .panel[data-nf-theme='neumorph']:not([data-nf-mode]) {
|
|
4162
|
+
--nf-glass-face: color-mix(in srgb, #2b2f36, transparent calc(100% - var(--nf-glass-opacity, 100%)));
|
|
4163
|
+
--nf-bg-raise: color-mix(in srgb, #2b2f36, transparent calc(100% - var(--nf-glass-opacity, 100%)));
|
|
4164
|
+
}
|
|
4165
|
+
.panel[data-nf-theme='clay']:not([data-nf-mode='dark']),
|
|
4166
|
+
body:not([data-ds-dark-theme]) .panel[data-nf-theme='clay'][data-nf-mode='light'] {
|
|
4167
|
+
--nf-glass-face: color-mix(in srgb, #efe4d6, transparent calc(100% - var(--nf-glass-opacity, 100%)));
|
|
4168
|
+
--nf-bg-raise: color-mix(in srgb, #efe4d6, transparent calc(100% - var(--nf-glass-opacity, 100%)));
|
|
4169
|
+
}
|
|
4170
|
+
.panel[data-nf-mode='dark'][data-nf-theme='clay'],
|
|
4171
|
+
body[data-ds-dark-theme] .panel[data-nf-theme='clay']:not([data-nf-mode]) {
|
|
4172
|
+
--nf-glass-face: color-mix(in srgb, #37302b, transparent calc(100% - var(--nf-glass-opacity, 100%)));
|
|
4173
|
+
--nf-bg-raise: color-mix(in srgb, #37302b, transparent calc(100% - var(--nf-glass-opacity, 100%)));
|
|
4174
|
+
}
|
|
@@ -11,7 +11,7 @@ import css from './panel/panel.module.css'
|
|
|
11
11
|
export const ENTRY_SELECTOR = '[data-dsh-novelforge-entry]'
|
|
12
12
|
|
|
13
13
|
/** Inline icon: an open book / writing glyph. */
|
|
14
|
-
const ICON = '<svg viewBox="0 0
|
|
14
|
+
const ICON = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12.67 19a2 2 0 0 0 1.416-.588l6.154-6.172a6 6 0 0 0-8.49-8.49L5.586 9.914A2 2 0 0 0 5 11.328V18a1 1 0 0 0 1 1z"/><path d="M16 8 2 22"/><path d="M17.5 15H9"/></svg>'
|
|
15
15
|
|
|
16
16
|
/** Find the sidebar shell root element. */
|
|
17
17
|
function sidebarRoot(): HTMLElement | undefined {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 客户端文件文本智能解码:兼容 UTF-8 BOM / UTF-16 / UTF-8 / GB18030(GBK)。
|
|
3
|
+
* 浏览器 File.text() 固定按 UTF-8 解码,GBK 中文网文 txt 会乱码,这里兜底。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 读取一个 File 的文本,按编码探测兜底解码。
|
|
8
|
+
* @returns 解码后的文本。
|
|
9
|
+
*/
|
|
10
|
+
export async function readFileTextSmart(file: File): Promise<string> {
|
|
11
|
+
const buf = await file.arrayBuffer()
|
|
12
|
+
const u8 = new Uint8Array(buf)
|
|
13
|
+
// UTF-8 BOM
|
|
14
|
+
if (u8.length >= 3 && u8[0] === 0xEF && u8[1] === 0xBB && u8[2] === 0xBF) {
|
|
15
|
+
return new TextDecoder('utf-8').decode(u8.subarray(3))
|
|
16
|
+
}
|
|
17
|
+
// UTF-16 LE / BE
|
|
18
|
+
if (u8.length >= 2 && u8[0] === 0xFF && u8[1] === 0xFE) return new TextDecoder('utf-16le').decode(u8.subarray(2))
|
|
19
|
+
if (u8.length >= 2 && u8[0] === 0xFE && u8[1] === 0xFF) return new TextDecoder('utf-16be').decode(u8.subarray(2))
|
|
20
|
+
// UTF-8(宽松),含替换符则回退 GB18030
|
|
21
|
+
const utf8 = new TextDecoder('utf-8', { fatal: false }).decode(u8)
|
|
22
|
+
if (!utf8.includes('\uFFFD')) return utf8
|
|
23
|
+
try {
|
|
24
|
+
return new TextDecoder('gb18030').decode(u8)
|
|
25
|
+
} catch {
|
|
26
|
+
return utf8
|
|
27
|
+
}
|
|
28
|
+
}
|