@waterwx/dsh-novel-forge 1.7.0 → 1.7.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 +2537 -1858
- package/lib/client.js.map +1 -1
- package/lib/index.js +657 -45
- package/lib/index.js.map +1 -1
- package/lib/types/client/api.d.ts +2 -0
- package/lib/types/client/locales.d.ts +0 -1
- package/lib/types/client/panel/FlowGuide.d.ts +21 -0
- package/lib/types/client/panel/MangaRoleLibrary.d.ts +18 -0
- package/lib/types/client/panel/MangaWorkspace.d.ts +2 -2
- package/lib/types/client/panel/StoryboardTab.d.ts +5 -1
- package/lib/types/engine.d.ts +15 -8
- package/lib/types/protocol.d.ts +124 -0
- package/package.json +2 -1
- package/src/client/api.ts +5 -0
- package/src/client/locales.ts +0 -1
- package/src/client/panel/AssetsTab.tsx +22 -22
- package/src/client/panel/AssistantTab.tsx +3 -3
- package/src/client/panel/BookshelfBar.tsx +1 -1
- package/src/client/panel/CreateBookView.tsx +10 -10
- package/src/client/panel/FlowGuide.tsx +83 -0
- package/src/client/panel/MangaRoleLibrary.tsx +810 -0
- package/src/client/panel/MangaWorkspace.tsx +359 -297
- package/src/client/panel/NovelPanel.tsx +107 -107
- package/src/client/panel/ReaderView.tsx +4 -4
- package/src/client/panel/RunPanel.tsx +8 -8
- package/src/client/panel/SceneLibrary.tsx +68 -67
- package/src/client/panel/StoryboardTab.tsx +105 -41
- package/src/client/panel/StyleCard.tsx +13 -13
- package/src/client/panel/StyleGallery.tsx +2 -2
- package/src/client/panel/WorldTab.tsx +5 -5
- package/src/client/panel/panel.module.css +861 -510
- package/src/client/panel/views.tsx +24 -24
- package/src/engine.ts +482 -51
- package/src/protocol.ts +125 -0
- package/src/routes.ts +203 -0
- package/src/client/panel/RoleVisualPanel.tsx +0 -485
|
@@ -18,7 +18,6 @@ import { RunPanel } from './RunPanel.tsx'
|
|
|
18
18
|
import { CreateBookView } from './CreateBookView.tsx'
|
|
19
19
|
import { ImportModal } from './ImportModal.tsx'
|
|
20
20
|
import { WorldTab } from './WorldTab.tsx'
|
|
21
|
-
import { StoryboardTab } from './StoryboardTab.tsx'
|
|
22
21
|
import { MangaWorkspace } from './MangaWorkspace.tsx'
|
|
23
22
|
import { AuditIssueRow, PlotlineCard, PlotlineHealthPanel, PlotlinePlanPanel, PlotlineSuggestionPanel, RoleCandidateRow, RoleCard, StatCell, TodoRow } from './views.tsx'
|
|
24
23
|
import { extractDocxTextFromBuffer } from '../docx.ts'
|
|
@@ -87,7 +86,7 @@ const NAV_GROUPS: ReadonlyArray<{ id: string; label: string; items: ReadonlyArra
|
|
|
87
86
|
label: '工具',
|
|
88
87
|
items: [
|
|
89
88
|
{ id: 'assistant', label: tt('tab.assistant'), icon: <MessageSquare size={16} /> },
|
|
90
|
-
{ id: 'progress', label: 'AI进度', icon: <BarChart3 size={16} /> },
|
|
89
|
+
{ id: 'progress', label: 'AI 进度', icon: <BarChart3 size={16} /> },
|
|
91
90
|
{ id: 'breakdown', label: '拆书分析', icon: <Search size={16} /> },
|
|
92
91
|
{ id: 'manhua', label: '漫剧工作台', icon: <Clapperboard size={16} /> },
|
|
93
92
|
{ id: 'run', label: '生产单', icon: <Factory size={16} /> },
|
|
@@ -226,7 +225,7 @@ function paragraphDiff(oldText: string, newText: string): DiffRow[] {
|
|
|
226
225
|
function renderBeats(beats: string): ReactElement {
|
|
227
226
|
const lines = beats.split('\n')
|
|
228
227
|
return (
|
|
229
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
228
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-2)' }}>
|
|
230
229
|
{lines.map((line, i) => {
|
|
231
230
|
const trimmed = line.trim()
|
|
232
231
|
const match = /^([^::]{2,14})[::]/.exec(trimmed)
|
|
@@ -382,14 +381,14 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
382
381
|
setEditorFontSize(v)
|
|
383
382
|
try { window.localStorage.setItem('dsh-novel-forge.editor.fontSize', String(v)) } catch { /* ignore */ }
|
|
384
383
|
}
|
|
385
|
-
/** 面板主题(localStorage 记忆):'liquid'=iOS 液态玻璃(绿) / '
|
|
386
|
-
const [panelTheme, setPanelTheme] = useState<'liquid' | '
|
|
384
|
+
/** 面板主题(localStorage 记忆):'liquid'=iOS 液态玻璃(绿) / 'neumorph'=新拟物(浅色) / 'macos'=macOS 玻璃(蓝,随外观自动浅深) / 'clay'=粘土拟态。 */
|
|
385
|
+
const [panelTheme, setPanelTheme] = useState<'liquid' | 'neumorph' | 'macos' | 'clay'>(() => {
|
|
387
386
|
try {
|
|
388
387
|
const v = window.localStorage.getItem('dsh-novel-forge.theme')
|
|
389
|
-
return v === '
|
|
388
|
+
return v === 'neumorph' || v === 'macos' || v === 'clay' ? v : 'liquid'
|
|
390
389
|
} catch { return 'liquid' }
|
|
391
390
|
})
|
|
392
|
-
const changePanelTheme = (next: 'liquid' | '
|
|
391
|
+
const changePanelTheme = (next: 'liquid' | 'neumorph' | 'macos' | 'clay'): void => {
|
|
393
392
|
setPanelTheme(next)
|
|
394
393
|
try { window.localStorage.setItem('dsh-novel-forge.theme', next) } catch { /* ignore */ }
|
|
395
394
|
}
|
|
@@ -2575,7 +2574,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
2575
2574
|
<div className={css.card} style={{ borderColor: 'var(--nf-info)' }}>
|
|
2576
2575
|
<div className={css.busyRow}>
|
|
2577
2576
|
<span style={{ color: 'var(--nf-info)' }}>第 {draftNo} 章有未采纳的润色/修订草稿(原稿未被改动)</span>
|
|
2578
|
-
<span style={{ display: 'flex', gap: 8 }}>
|
|
2577
|
+
<span style={{ display: 'flex', gap: 'var(--nf-space-8)' }}>
|
|
2579
2578
|
<button type="button" className={`${css.button} ${css.buttonSmall} ${css.buttonPrimary}`} disabled={busy} onClick={() => { void openWorkspace(draftNo) }}>
|
|
2580
2579
|
打开工作区
|
|
2581
2580
|
</button>
|
|
@@ -2602,7 +2601,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
2602
2601
|
)}
|
|
2603
2602
|
<span className={css.cardTitle}>第 {workspace.no} 章《{workspace.title}》</span>
|
|
2604
2603
|
<span className={css.meta}>{workspace.original.length} 字</span>
|
|
2605
|
-
<span style={{ display: 'flex', gap: 4, alignItems: 'center', marginLeft: 'auto' }}>
|
|
2604
|
+
<span style={{ display: 'flex', gap: 'var(--nf-space-4)', alignItems: 'center', marginLeft: 'auto' }}>
|
|
2606
2605
|
<button type="button" className={css.iconButton} title="减小字号" aria-label="减小字号" onClick={() => { changeEditorFontSize(editorFontSize - 1) }}>A−</button>
|
|
2607
2606
|
<span className={css.meta}>{editorFontSize}px</span>
|
|
2608
2607
|
<button type="button" className={css.iconButton} title="增大字号" aria-label="增大字号" onClick={() => { changeEditorFontSize(editorFontSize + 1) }}>A+</button>
|
|
@@ -2717,12 +2716,12 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
2717
2716
|
</div>
|
|
2718
2717
|
<div className={css.meta}><b>总评:</b>{wsCheckReport.verdict}</div>
|
|
2719
2718
|
{wsCheckReport.issues.length > 0 && (
|
|
2720
|
-
<ul style={{ margin: 0, paddingLeft: 16, display: 'flex', flexDirection: 'column', gap:
|
|
2719
|
+
<ul style={{ margin: 0, paddingLeft: 'var(--nf-space-16)', display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-4)', fontSize: editorFontSize - 1, maxHeight: '45vh', overflowY: 'auto' }}>
|
|
2721
2720
|
{wsCheckReport.issues.map((issue, i) => (
|
|
2722
|
-
<li key={i} style={{ color: severityColor(issue.severity), display: 'flex', gap: 6, alignItems: 'flex-start' }}>
|
|
2721
|
+
<li key={i} style={{ color: severityColor(issue.severity), display: 'flex', gap: 'var(--nf-space-6)', alignItems: 'flex-start' }}>
|
|
2723
2722
|
<input
|
|
2724
2723
|
type="checkbox"
|
|
2725
|
-
style={{ marginTop:
|
|
2724
|
+
style={{ marginTop: 'var(--nf-space-2)' }}
|
|
2726
2725
|
checked={wsChecked.includes(i)}
|
|
2727
2726
|
onChange={e => {
|
|
2728
2727
|
setWsChecked(prev => e.target.checked ? [...prev, i] : prev.filter(x => x !== i))
|
|
@@ -2748,7 +2747,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
2748
2747
|
<div className={css.wsPreview} style={{ flex: 1, minHeight: 0 }}>
|
|
2749
2748
|
<div className={css.busyRow}>
|
|
2750
2749
|
<span className={css.meta}>优化预览({workspace.draft.length} 字)</span>
|
|
2751
|
-
<span style={{ display: 'flex', gap: 8 }}>
|
|
2750
|
+
<span style={{ display: 'flex', gap: 'var(--nf-space-8)' }}>
|
|
2752
2751
|
<button type="button" className={`${css.button} ${css.buttonSmall}`} onClick={() => { setWsShowDiff(v => !v) }}>
|
|
2753
2752
|
{wsShowDiff ? '显示文本' : '查看对比'}
|
|
2754
2753
|
</button>
|
|
@@ -2871,17 +2870,17 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
2871
2870
|
</button>
|
|
2872
2871
|
</div>
|
|
2873
2872
|
{auditStatus?.status === 'running' && (
|
|
2874
|
-
<div className={css.meta} style={{ marginTop: 8 }}>
|
|
2873
|
+
<div className={css.meta} style={{ marginTop: 'var(--nf-space-8)' }}>
|
|
2875
2874
|
🔍 全书质检中:{auditStatus.completedBatches}/{auditStatus.totalBatches > 0 ? auditStatus.totalBatches : '…'} 批
|
|
2876
2875
|
{auditStatus.totalBatches > 0 && (
|
|
2877
|
-
<div className={css.dashJourneyBar} style={{ marginTop: 4 }}>
|
|
2876
|
+
<div className={css.dashJourneyBar} style={{ marginTop: 'var(--nf-space-4)' }}>
|
|
2878
2877
|
<div className={css.dashJourneyFill} style={{ width: `${Math.round((auditStatus.completedBatches / auditStatus.totalBatches) * 100)}%` }} />
|
|
2879
2878
|
</div>
|
|
2880
2879
|
)}
|
|
2881
2880
|
</div>
|
|
2882
2881
|
)}
|
|
2883
2882
|
{auditStatus?.status === 'error' && auditStatus.error !== undefined && (
|
|
2884
|
-
<div className={css.meta} style={{ color: 'var(--nf-error)', marginTop: 8 }}>全书质检失败:{auditStatus.error}</div>
|
|
2883
|
+
<div className={css.meta} style={{ color: 'var(--nf-error)', marginTop: 'var(--nf-space-8)' }}>全书质检失败:{auditStatus.error}</div>
|
|
2885
2884
|
)}
|
|
2886
2885
|
<div className={css.assetGrid}>
|
|
2887
2886
|
<StatCell
|
|
@@ -2913,7 +2912,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
2913
2912
|
</div>
|
|
2914
2913
|
|
|
2915
2914
|
{/* 剧情线进度 | 作者复盘趋势:精简摘要卡,并排(点击跳转详情页) */}
|
|
2916
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 14, alignItems: 'stretch' }}>
|
|
2915
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 'var(--nf-space-14)', alignItems: 'stretch' }}>
|
|
2917
2916
|
<div className={css.card}>
|
|
2918
2917
|
<div className={css.row} style={{ justifyContent: 'space-between', flexWrap: 'wrap' }}>
|
|
2919
2918
|
<span className={css.cardTitle}>🧵 {tt('plotlines.workflowTitle')}({(project?.plotlines ?? []).length} 条)</span>
|
|
@@ -2992,7 +2991,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
2992
2991
|
</button>
|
|
2993
2992
|
</div>
|
|
2994
2993
|
{auditIssues.length > 0 && (
|
|
2995
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
2994
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-6)' }}>
|
|
2996
2995
|
{auditIssues.map((issue, i) => (
|
|
2997
2996
|
<AuditIssueRow
|
|
2998
2997
|
key={i}
|
|
@@ -3124,7 +3123,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3124
3123
|
) : (
|
|
3125
3124
|
/* 只读展示:大纲是开书时的出生证明 */
|
|
3126
3125
|
<>
|
|
3127
|
-
<div className={css.meta} style={{ marginBottom: 6 }}>
|
|
3126
|
+
<div className={css.meta} style={{ marginBottom: 'var(--nf-space-6)' }}>
|
|
3128
3127
|
大纲是本书的「出生证明」;已写章节可点右上角「🔄 反推大纲」从正文反推,或「更新大纲」手动修订。
|
|
3129
3128
|
</div>
|
|
3130
3129
|
<pre className={css.outlineReadonly}>{project.outline}</pre>
|
|
@@ -3187,7 +3186,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3187
3186
|
</button>
|
|
3188
3187
|
</div>
|
|
3189
3188
|
{/* 封面 */}
|
|
3190
|
-
<div className={css.row} style={{ alignItems: 'flex-start', gap: 14 }}>
|
|
3189
|
+
<div className={css.row} style={{ alignItems: 'flex-start', gap: 'var(--nf-space-14)' }}>
|
|
3191
3190
|
<div className={css.coverPreview}>
|
|
3192
3191
|
{coverDataUrl !== null ? (
|
|
3193
3192
|
<img src={coverDataUrl} alt="封面" />
|
|
@@ -3195,7 +3194,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3195
3194
|
<span className={css.coverPlaceholder}>暂无封面</span>
|
|
3196
3195
|
)}
|
|
3197
3196
|
</div>
|
|
3198
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
3197
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-8)' }}>
|
|
3199
3198
|
<div className={css.row}>
|
|
3200
3199
|
<button type="button" className={`${css.button} ${css.buttonSmall}`} disabled={busy || project === null} onClick={() => { coverFileRef.current?.click() }}>
|
|
3201
3200
|
📤 上传封面
|
|
@@ -3286,9 +3285,9 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3286
3285
|
</div>
|
|
3287
3286
|
<span className={css.meta}>{tt('sensitive.hint')}</span>
|
|
3288
3287
|
{sensHits.length > 0 && (
|
|
3289
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, maxHeight: 300, overflowY: 'auto', fontSize: 12 }}>
|
|
3288
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-4)', maxHeight: 300, overflowY: 'auto', fontSize: 'var(--nf-fs-12)' }}>
|
|
3290
3289
|
{sensHits.map((hit, i) => (
|
|
3291
|
-
<div key={i} style={{ display: 'flex', gap: 8, alignItems: 'flex-start', border: '1px solid var(--nf-border)', borderRadius: 6, padding: '
|
|
3290
|
+
<div key={i} style={{ display: 'flex', gap: 'var(--nf-space-8)', alignItems: 'flex-start', border: '1px solid var(--nf-border)', borderRadius: 'var(--nf-radius-6)', padding: 'var(--nf-space-4) var(--nf-space-8)' }}>
|
|
3292
3291
|
<span className={css.badge} style={{ borderColor: 'var(--nf-warn)', color: 'var(--nf-warn)', flex: 'none' }}>
|
|
3293
3292
|
{hit.chapterNo > 0 ? `第${hit.chapterNo}章` : '文本'}
|
|
3294
3293
|
</span>
|
|
@@ -3315,7 +3314,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3315
3314
|
{chapters.length > 0 && (
|
|
3316
3315
|
<div className={css.card}>
|
|
3317
3316
|
<div className={css.row} style={{ justifyContent: 'space-between', flexWrap: 'wrap' }}>
|
|
3318
|
-
<div className={css.row} style={{ flexWrap: 'wrap', gap: 6 }}>
|
|
3317
|
+
<div className={css.row} style={{ flexWrap: 'wrap', gap: 'var(--nf-space-6)' }}>
|
|
3319
3318
|
{(volumes !== undefined && volumes.length > 0) && (
|
|
3320
3319
|
<>
|
|
3321
3320
|
<button
|
|
@@ -3372,7 +3371,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3372
3371
|
<span className={css.chapterNum}>{chapter.no}</span>
|
|
3373
3372
|
<div className={css.chapterMain}>
|
|
3374
3373
|
<div className={css.chapterTitle}>
|
|
3375
|
-
<button type="button" className={`${css.button} ${css.buttonSmall}`} style={{ padding: '
|
|
3374
|
+
<button type="button" className={`${css.button} ${css.buttonSmall}`} style={{ padding: 'var(--nf-space-2) var(--nf-space-6)' }} onClick={() => { void handleToggleChapter(chapter.no) }}>
|
|
3376
3375
|
{expanded ? '−' : '+'}
|
|
3377
3376
|
</button>
|
|
3378
3377
|
<span>{chapter.title}</span>
|
|
@@ -3383,7 +3382,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3383
3382
|
</div>
|
|
3384
3383
|
{!expanded && <div className={css.chapterBeats} title={chapter.beats}>{chapter.beats}</div>}
|
|
3385
3384
|
{expanded && (
|
|
3386
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
3385
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-6)' }}>
|
|
3387
3386
|
<div className={css.meta}><b>{tt('plan.beats')}:</b></div>
|
|
3388
3387
|
<div className={css.meta}>{renderBeats(chapter.beats)}</div>
|
|
3389
3388
|
{chapter.summary !== undefined && chapter.summary !== '' && (
|
|
@@ -3400,7 +3399,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3400
3399
|
</div>
|
|
3401
3400
|
<div className={css.meta}><b>{tt('plan.reviewVerdict')}:</b> {review.verdict}</div>
|
|
3402
3401
|
{review.issues.length > 0 && (
|
|
3403
|
-
<ul style={{ margin: 0, paddingLeft: 18, fontSize: 12 }}>
|
|
3402
|
+
<ul style={{ margin: 0, paddingLeft: 'var(--nf-space-18)', fontSize: 'var(--nf-fs-12)' }}>
|
|
3404
3403
|
{review.issues.map((issue, i) => (
|
|
3405
3404
|
<li key={i} style={{ color: severityColor(issue.severity) }}>
|
|
3406
3405
|
[{issue.severity}] {issue.item} → {issue.suggestion}
|
|
@@ -3446,7 +3445,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3446
3445
|
</div>
|
|
3447
3446
|
)}
|
|
3448
3447
|
{(chapter.status === 'rejected' || chapter.status === 'written' || chapter.status === 'approved') && (
|
|
3449
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
3448
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-6)' }}>
|
|
3450
3449
|
<div className={css.meta} style={{ fontWeight: 600 }}>
|
|
3451
3450
|
润色 / 修订 — 在右上角打开工作区:左栏原文可直接选中文字做局部修订,右栏输入指令后预览,确认后再应用(未应用不改动原稿)
|
|
3452
3451
|
</div>
|
|
@@ -3527,16 +3526,16 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3527
3526
|
)}
|
|
3528
3527
|
|
|
3529
3528
|
{activeTab === 'book' && (
|
|
3530
|
-
<div className={css.card} style={{ gap: 12 }}>
|
|
3529
|
+
<div className={css.card} style={{ gap: 'var(--nf-space-12)' }}>
|
|
3531
3530
|
<div className={css.row} style={{ justifyContent: 'space-between', flexWrap: 'wrap' }}>
|
|
3532
|
-
<span className={css.cardTitle} style={{ fontSize:
|
|
3531
|
+
<span className={css.cardTitle} style={{ fontSize: 'var(--nf-fs-16)', fontWeight: 700 }}>📚 本书设定</span>
|
|
3533
3532
|
<span className={css.meta}>当前书的知识与资料(参考 AI-Novel-Writing-Assistant:世界观/角色准备收进书内)</span>
|
|
3534
3533
|
</div>
|
|
3535
|
-
<div className={css.row} style={{ flexWrap: 'wrap', gap: 6 }}>
|
|
3534
|
+
<div className={css.row} style={{ flexWrap: 'wrap', gap: 'var(--nf-space-6)' }}>
|
|
3536
3535
|
<button
|
|
3537
3536
|
type="button"
|
|
3538
3537
|
className={`${css.button} ${bookTab === 'bible' ? css.buttonPrimary : ''}`}
|
|
3539
|
-
style={{ fontSize: 14, flex: 1 }}
|
|
3538
|
+
style={{ fontSize: 'var(--nf-fs-14)', flex: 1 }}
|
|
3540
3539
|
onClick={() => { changeBookTab('bible') }}
|
|
3541
3540
|
title="设定库:题材 / 世界观规则 / 人物摘要 / 红线 / 文风"
|
|
3542
3541
|
>
|
|
@@ -3545,7 +3544,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3545
3544
|
<button
|
|
3546
3545
|
type="button"
|
|
3547
3546
|
className={`${css.button} ${bookTab === 'world' ? css.buttonPrimary : ''}`}
|
|
3548
|
-
style={{ fontSize: 14, flex: 1 }}
|
|
3547
|
+
style={{ fontSize: 'var(--nf-fs-14)', flex: 1 }}
|
|
3549
3548
|
onClick={() => { changeBookTab('world') }}
|
|
3550
3549
|
title="大世界:境界体系 / 地理区域 / 势力分布(注入生成与审稿提示词)"
|
|
3551
3550
|
>
|
|
@@ -3554,7 +3553,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3554
3553
|
<button
|
|
3555
3554
|
type="button"
|
|
3556
3555
|
className={`${css.button} ${bookTab === 'roles' ? css.buttonPrimary : ''}`}
|
|
3557
|
-
style={{ fontSize: 14, flex: 1 }}
|
|
3556
|
+
style={{ fontSize: 'var(--nf-fs-14)', flex: 1 }}
|
|
3558
3557
|
onClick={() => { changeBookTab('roles') }}
|
|
3559
3558
|
title="角色库:全书角色主表(定位 / 关系网 / 成长线 / 知情度)"
|
|
3560
3559
|
>
|
|
@@ -3563,7 +3562,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3563
3562
|
<button
|
|
3564
3563
|
type="button"
|
|
3565
3564
|
className={`${css.button} ${bookTab === 'facts' ? css.buttonPrimary : ''}`}
|
|
3566
|
-
style={{ fontSize: 14, flex: 1 }}
|
|
3565
|
+
style={{ fontSize: 'var(--nf-fs-14)', flex: 1 }}
|
|
3567
3566
|
onClick={() => { changeBookTab('facts') }}
|
|
3568
3567
|
title="编年录与复盘记录"
|
|
3569
3568
|
>
|
|
@@ -3584,13 +3583,13 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3584
3583
|
{bible === undefined ? (
|
|
3585
3584
|
<span className={css.meta}>{tt('bible.none')}</span>
|
|
3586
3585
|
) : (
|
|
3587
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
|
3586
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-10)' }}>
|
|
3588
3587
|
{bible.genre !== '' && (
|
|
3589
3588
|
<div><b>{tt('bible.genre')}:</b> <span className={css.meta}>{bible.genre}</span></div>
|
|
3590
3589
|
)}
|
|
3591
3590
|
{/* 世界观规则独立卡片(可编辑,每行一条) */}
|
|
3592
3591
|
{bible.worldRules.length > 0 && (
|
|
3593
|
-
<div style={{ border: '1px solid var(--nf-border)', borderRadius: 10, padding: '
|
|
3592
|
+
<div style={{ border: '1px solid var(--nf-border)', borderRadius: 'var(--nf-radius-10)', padding: 'var(--nf-space-8) var(--nf-space-10)' }}>
|
|
3594
3593
|
<div className={css.row} style={{ justifyContent: 'space-between' }}>
|
|
3595
3594
|
<b>{tt('bible.worldRules')}({bible.worldRules.length})</b>
|
|
3596
3595
|
<button type="button" className={`${css.button} ${css.buttonSmall}`} onClick={() => { setWorldRulesDraft(bible.worldRules.join('\n')) }}>
|
|
@@ -3598,7 +3597,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3598
3597
|
</button>
|
|
3599
3598
|
</div>
|
|
3600
3599
|
{worldRulesDraft !== '' && (
|
|
3601
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 6 }}>
|
|
3600
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-6)', marginTop: 'var(--nf-space-6)' }}>
|
|
3602
3601
|
<textarea
|
|
3603
3602
|
className={css.textarea}
|
|
3604
3603
|
style={{ minHeight: 120 }}
|
|
@@ -3617,7 +3616,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3617
3616
|
</div>
|
|
3618
3617
|
)}
|
|
3619
3618
|
{worldRulesDraft === '' && (
|
|
3620
|
-
<ul style={{ margin: '4px 0 0', paddingLeft: 18, fontSize: 12 }}>{bible.worldRules.map((r, i) => <li key={i}>{r}</li>)}</ul>
|
|
3619
|
+
<ul style={{ margin: '4px 0 0', paddingLeft: 'var(--nf-space-18)', fontSize: 'var(--nf-fs-12)' }}>{bible.worldRules.map((r, i) => <li key={i}>{r}</li>)}</ul>
|
|
3621
3620
|
)}
|
|
3622
3621
|
</div>
|
|
3623
3622
|
)}
|
|
@@ -3625,7 +3624,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3625
3624
|
<div>
|
|
3626
3625
|
<b>{tt('bible.characters')}({bible.characters.length})</b>
|
|
3627
3626
|
{bible.characters.map(card => (
|
|
3628
|
-
<div key={card.name} style={{ marginTop: 4, fontSize: 12 }}>
|
|
3627
|
+
<div key={card.name} style={{ marginTop: 'var(--nf-space-4)', fontSize: 'var(--nf-fs-12)' }}>
|
|
3629
3628
|
<b>{card.name}</b> <span className={css.meta}>[{card.role}] {card.traits.join('、')}</span>
|
|
3630
3629
|
{card.goals !== '' && <div className={css.meta}>目标:{card.goals}</div>}
|
|
3631
3630
|
{card.relations !== '' && <div className={css.meta}>关系:{card.relations}</div>}
|
|
@@ -3636,13 +3635,13 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3636
3635
|
{bible.redLines.length > 0 && (
|
|
3637
3636
|
<div>
|
|
3638
3637
|
<b>{tt('bible.redLines')}({bible.redLines.length})</b>
|
|
3639
|
-
<ul style={{ margin: 0, paddingLeft: 18, fontSize: 12, color: 'var(--nf-error)' }}>{bible.redLines.map((r, i) => <li key={i}>{r}</li>)}</ul>
|
|
3638
|
+
<ul style={{ margin: 0, paddingLeft: 'var(--nf-space-18)', fontSize: 'var(--nf-fs-12)', color: 'var(--nf-error)' }}>{bible.redLines.map((r, i) => <li key={i}>{r}</li>)}</ul>
|
|
3640
3639
|
</div>
|
|
3641
3640
|
)}
|
|
3642
3641
|
{bible.style.length > 0 && (
|
|
3643
3642
|
<div>
|
|
3644
3643
|
<b>{tt('bible.style')}({bible.style.length})</b>
|
|
3645
|
-
<ul style={{ margin: 0, paddingLeft: 18, fontSize: 12 }}>{bible.style.map((r, i) => <li key={i}>{r}</li>)}</ul>
|
|
3644
|
+
<ul style={{ margin: 0, paddingLeft: 'var(--nf-space-18)', fontSize: 'var(--nf-fs-12)' }}>{bible.style.map((r, i) => <li key={i}>{r}</li>)}</ul>
|
|
3646
3645
|
</div>
|
|
3647
3646
|
)}
|
|
3648
3647
|
</div>
|
|
@@ -3692,7 +3691,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3692
3691
|
<span className={css.meta}>角色库是全书角色主表:定位(女主/女配/配角/反派)、身份、关系网、成长线、知情度——生成与审稿都会按定位规格刻画互动。点「从编年录刷新状态」可在每张卡上显示角色当前状态。</span>
|
|
3693
3692
|
|
|
3694
3693
|
{roleCandidates !== null && (
|
|
3695
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, border: '1px solid var(--nf-info)', borderRadius: 12, padding: 10 }}>
|
|
3694
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-6)', border: '1px solid var(--nf-info)', borderRadius: 'var(--nf-radius-12)', padding: 'var(--nf-space-10)' }}>
|
|
3696
3695
|
<div className={css.row} style={{ justifyContent: 'space-between', flexWrap: 'wrap' }}>
|
|
3697
3696
|
<b>✨ AI 提炼候选({roleCandidates.length})</b>
|
|
3698
3697
|
<button type="button" className={`${css.button} ${css.buttonSmall}`} onClick={() => { setRoleCandidates(null) }}>收起</button>
|
|
@@ -3711,7 +3710,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3711
3710
|
)}
|
|
3712
3711
|
|
|
3713
3712
|
{roleDraft !== null && (
|
|
3714
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, border: '1px solid var(--nf-accent)', borderRadius: 12, padding: 10 }}>
|
|
3713
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-8)', border: '1px solid var(--nf-accent)', borderRadius: 'var(--nf-radius-12)', padding: 'var(--nf-space-10)' }}>
|
|
3715
3714
|
<div className={css.row} style={{ flexWrap: 'wrap' }}>
|
|
3716
3715
|
<div className={css.field} style={{ flex: 1, minWidth: 140 }}>
|
|
3717
3716
|
<label className={css.fieldLabel}>角色名</label>
|
|
@@ -3761,7 +3760,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3761
3760
|
{(project?.roles ?? []).length === 0 ? (
|
|
3762
3761
|
<span className={css.meta}>角色库为空——点「✨ 从全书提炼角色」自动建立,或手动新增。</span>
|
|
3763
3762
|
) : (
|
|
3764
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
3763
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-6)' }}>
|
|
3765
3764
|
{(project?.roles ?? []).map(r => (
|
|
3766
3765
|
<RoleCard
|
|
3767
3766
|
key={r.name}
|
|
@@ -3783,18 +3782,18 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3783
3782
|
{activeTab === 'settings' && configDraft !== null && (
|
|
3784
3783
|
<>
|
|
3785
3784
|
{/* 设置页内子导航(大容器) */}
|
|
3786
|
-
<div className={`${css.card} ${css.settingsCard}`}
|
|
3787
|
-
<div className={css.row} style={{ justifyContent: 'space-between', flexWrap: 'wrap', alignItems: 'center', gap: 8 }}>
|
|
3788
|
-
<span className={css.cardTitle} style={{ fontSize: 20, fontWeight: 700 }}>⚙️ 设置</span>
|
|
3785
|
+
<div className={`${css.card} ${css.settingsCard}`}>
|
|
3786
|
+
<div className={css.row} style={{ justifyContent: 'space-between', flexWrap: 'wrap', alignItems: 'center', gap: 'var(--nf-space-8)' }}>
|
|
3787
|
+
<span className={css.cardTitle} style={{ fontSize: 'var(--nf-fs-20)', fontWeight: 700 }}>⚙️ 设置</span>
|
|
3789
3788
|
<span className={css.meta}>当前:{config?.provider} / {config?.model} · {config?.outputDir}</span>
|
|
3790
3789
|
</div>
|
|
3791
|
-
<div className={css.row} style={{ gap: 8, flexWrap: 'wrap' }}>
|
|
3790
|
+
<div className={css.row} style={{ gap: 'var(--nf-space-8)', flexWrap: 'wrap' }}>
|
|
3792
3791
|
{SETTINGS_SECTIONS.map(s => (
|
|
3793
3792
|
<button
|
|
3794
3793
|
key={s.id}
|
|
3795
3794
|
type="button"
|
|
3796
3795
|
className={`${css.button} ${settingsTab === s.id ? css.buttonPrimary : ''}`}
|
|
3797
|
-
style={{ fontSize: 14, padding: '
|
|
3796
|
+
style={{ fontSize: 'var(--nf-fs-14)', padding: 'var(--nf-space-8) var(--nf-space-12)', flex: 1, minWidth: 104, justifyContent: 'center' }}
|
|
3798
3797
|
onClick={() => { changeSettingsTab(s.id) }}
|
|
3799
3798
|
>
|
|
3800
3799
|
{s.icon} {s.label}
|
|
@@ -3804,9 +3803,9 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3804
3803
|
</div>
|
|
3805
3804
|
|
|
3806
3805
|
{settingsTab === 'model' && (
|
|
3807
|
-
<div className={`${css.card} ${css.settingsCard}`} style={{ gap:
|
|
3806
|
+
<div className={`${css.card} ${css.settingsCard}`} style={{ gap: 'var(--nf-space-24)' }}>
|
|
3808
3807
|
<span className={css.cardTitle}><Brain size={18} style={{ verticalAlign: -3 }} /> 模型与推理</span>
|
|
3809
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap:
|
|
3808
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-24)' }}>
|
|
3810
3809
|
<div className={css.field}>
|
|
3811
3810
|
<label className={css.fieldLabel}>{tt('settings.provider')}</label>
|
|
3812
3811
|
<input className={css.input} value={configDraft.provider} onChange={e => { setConfigDraft({ ...configDraft, provider: e.target.value }) }} />
|
|
@@ -3832,7 +3831,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3832
3831
|
{modelCustomMode && (
|
|
3833
3832
|
<input
|
|
3834
3833
|
className={css.input}
|
|
3835
|
-
style={{ marginTop: 6 }}
|
|
3834
|
+
style={{ marginTop: 'var(--nf-space-6)' }}
|
|
3836
3835
|
value={configDraft.model}
|
|
3837
3836
|
placeholder={tt('settings.modelCustomPlaceholder')}
|
|
3838
3837
|
onChange={e => { setConfigDraft({ ...configDraft, model: e.target.value }) }}
|
|
@@ -3866,9 +3865,9 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3866
3865
|
)}
|
|
3867
3866
|
|
|
3868
3867
|
{settingsTab === 'writing' && (
|
|
3869
|
-
<div className={`${css.card} ${css.settingsCard}`} style={{ gap:
|
|
3868
|
+
<div className={`${css.card} ${css.settingsCard}`} style={{ gap: 'var(--nf-space-24)' }}>
|
|
3870
3869
|
<span className={css.cardTitle}><PenLine size={18} style={{ verticalAlign: -3 }} /> 写作与审稿</span>
|
|
3871
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap:
|
|
3870
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-24)' }}>
|
|
3872
3871
|
<div className={css.field}>
|
|
3873
3872
|
<label className={css.fieldLabel}>{tt('settings.chapterChars')}</label>
|
|
3874
3873
|
<input className={css.input} type="number" min={1000} max={20000} value={configDraft.chapterChars} onChange={e => { setConfigDraft({ ...configDraft, chapterChars: Number(e.target.value) }) }} />
|
|
@@ -3935,10 +3934,10 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3935
3934
|
)}
|
|
3936
3935
|
|
|
3937
3936
|
{settingsTab === 'image' && (
|
|
3938
|
-
<div className={`${css.card} ${css.settingsCard}`} style={{ gap: 24 }}>
|
|
3937
|
+
<div className={`${css.card} ${css.settingsCard}`} style={{ gap: 'var(--nf-space-24)' }}>
|
|
3939
3938
|
<div className={css.row} style={{ justifyContent: 'space-between', flexWrap: 'wrap', alignItems: 'center' }}>
|
|
3940
3939
|
<span className={css.cardTitle}><Palette size={18} style={{ verticalAlign: -3 }} /> 生图模型</span>
|
|
3941
|
-
<span className={css.row} style={{ gap: 8, alignItems: 'center' }}>
|
|
3940
|
+
<span className={css.row} style={{ gap: 'var(--nf-space-8)', alignItems: 'center' }}>
|
|
3942
3941
|
<span className={css.meta}>启用生图功能</span>
|
|
3943
3942
|
<button
|
|
3944
3943
|
type="button"
|
|
@@ -3952,9 +3951,9 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3952
3951
|
</span>
|
|
3953
3952
|
</div>
|
|
3954
3953
|
<span className={css.meta}>支持多套 OpenAI 兼容生图接口(豆包 / 即梦 / 其他平台);启用一条作为生成模型,保存后角色详情显示「AI 生图」按钮。</span>
|
|
3955
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
|
|
3954
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-20)' }}>
|
|
3956
3955
|
{(configDraft.imageModels ?? []).map(m => (
|
|
3957
|
-
<div key={m.id} style={{ border: m.enabled ? '1px solid var(--nf-accent)' : '1px solid var(--nf-border)', borderRadius: 12, padding: 14, display: 'flex', flexDirection: 'column', gap: 12, background: m.enabled ? 'var(--nf-accent-soft)' : 'transparent' }}>
|
|
3956
|
+
<div key={m.id} style={{ border: m.enabled ? '1px solid var(--nf-accent)' : '1px solid var(--nf-border)', borderRadius: 'var(--nf-radius-12)', padding: 'var(--nf-space-14)', display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-12)', background: m.enabled ? 'var(--nf-accent-soft)' : 'transparent' }}>
|
|
3958
3957
|
<div className={css.row} style={{ alignItems: 'flex-end', flexWrap: 'wrap' }}>
|
|
3959
3958
|
<div className={css.field} style={{ flex: 1, minWidth: 180 }}>
|
|
3960
3959
|
<label className={css.fieldLabel}>名称</label>
|
|
@@ -3973,7 +3972,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3973
3972
|
<label className={css.fieldLabel}>API Key</label>
|
|
3974
3973
|
<input className={css.input} type="password" placeholder="ark-..." value={m.apiKey} onChange={e => { updateImageModel(m.id, { apiKey: e.target.value }) }} />
|
|
3975
3974
|
</div>
|
|
3976
|
-
<div className={css.row} style={{ gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
|
|
3975
|
+
<div className={css.row} style={{ gap: 'var(--nf-space-8)', flexWrap: 'wrap', alignItems: 'center' }}>
|
|
3977
3976
|
<button type="button" className={`${css.button} ${css.buttonSmall} ${m.enabled ? css.buttonPrimary : ''}`} onClick={() => { enableImageModel(m.id) }}>
|
|
3978
3977
|
{m.enabled ? '✓ 已启用' : '设为启用'}
|
|
3979
3978
|
</button>
|
|
@@ -3986,8 +3985,8 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
3986
3985
|
const st = imageTestState[m.id]
|
|
3987
3986
|
if (st === undefined || st.testing) return null
|
|
3988
3987
|
return st.ok === true
|
|
3989
|
-
? <span style={{ color: 'var(--nf-success)', fontSize: 12, fontWeight: 600 }}>✓ 连通 · {st.ms}ms{st.modelFound === true ? '' : st.modelFound === false ? '(模型未在列表)' : ''}</span>
|
|
3990
|
-
: <span style={{ color: 'var(--nf-error)', fontSize: 12 }}>✗ {st.message ?? '失败'}</span>
|
|
3988
|
+
? <span style={{ color: 'var(--nf-success)', fontSize: 'var(--nf-fs-12)', fontWeight: 600 }}>✓ 连通 · {st.ms}ms{st.modelFound === true ? '' : st.modelFound === false ? '(模型未在列表)' : ''}</span>
|
|
3989
|
+
: <span style={{ color: 'var(--nf-error)', fontSize: 'var(--nf-fs-12)' }}>✗ {st.message ?? '失败'}</span>
|
|
3991
3990
|
})()}
|
|
3992
3991
|
</div>
|
|
3993
3992
|
</div>
|
|
@@ -4003,7 +4002,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4003
4002
|
)}
|
|
4004
4003
|
|
|
4005
4004
|
{settingsTab === 'files' && (
|
|
4006
|
-
<div className={`${css.card} ${css.settingsCard}`} style={{ gap: 24 }}>
|
|
4005
|
+
<div className={`${css.card} ${css.settingsCard}`} style={{ gap: 'var(--nf-space-24)' }}>
|
|
4007
4006
|
<span className={css.cardTitle}><Folder size={18} style={{ verticalAlign: -3 }} /> 路径与文件</span>
|
|
4008
4007
|
<div className={css.field}>
|
|
4009
4008
|
<label className={css.fieldLabel}>{tt('settings.outlinePath')}</label>
|
|
@@ -4019,7 +4018,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4019
4018
|
|
|
4020
4019
|
<div style={{ borderTop: '1px solid var(--nf-border)', margin: '14px 0 12px' }} />
|
|
4021
4020
|
|
|
4022
|
-
<span className={css.cardTitle} style={{ fontSize:
|
|
4021
|
+
<span className={css.cardTitle} style={{ fontSize: 'var(--nf-fs-14)' }}>💾 保存与导出</span>
|
|
4023
4022
|
<div className={css.row}>
|
|
4024
4023
|
<button type="button" className={`${css.button} ${css.buttonPrimary}`} disabled={busy} onClick={() => { void handleSaveConfig() }}>
|
|
4025
4024
|
{tt('settings.save')}
|
|
@@ -4040,10 +4039,10 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4040
4039
|
<div className={`${css.card} ${css.settingsCard}`}>
|
|
4041
4040
|
<span className={css.cardTitle}><Sparkles size={18} style={{ verticalAlign: -3 }} /> 外观与主题</span>
|
|
4042
4041
|
<span className={css.meta}>选择适合长时间创作的界面颜色和显示密度。主题只保存在当前设备,不影响小说内容和任务状态。</span>
|
|
4043
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap:
|
|
4042
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-24)' }}>
|
|
4044
4043
|
<div className={css.field}>
|
|
4045
|
-
<label className={css.fieldLabel} style={{ fontSize:
|
|
4046
|
-
<select className={css.input} style={{ padding: '
|
|
4044
|
+
<label className={css.fieldLabel} style={{ fontSize: 'var(--nf-fs-14)' }}>显示模式</label>
|
|
4045
|
+
<select className={css.input} style={{ padding: 'var(--nf-space-10) var(--nf-space-12)', fontSize: 'var(--nf-fs-14)' }} value={themeMode} onChange={e => { changeThemeMode(e.target.value as 'system' | 'light' | 'dark') }}>
|
|
4047
4046
|
<option value="system">跟随系统</option>
|
|
4048
4047
|
<option value="light">浅色</option>
|
|
4049
4048
|
<option value="dark">深色</option>
|
|
@@ -4051,17 +4050,18 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4051
4050
|
<span className={css.meta}>强制浅色/深色只作用于小说工坊面板</span>
|
|
4052
4051
|
</div>
|
|
4053
4052
|
<div className={css.field}>
|
|
4054
|
-
<label className={css.fieldLabel} style={{ fontSize:
|
|
4055
|
-
<select className={css.input} style={{ padding: '
|
|
4053
|
+
<label className={css.fieldLabel} style={{ fontSize: 'var(--nf-fs-14)' }}>主题风格</label>
|
|
4054
|
+
<select className={css.input} style={{ padding: 'var(--nf-space-10) var(--nf-space-12)', fontSize: 'var(--nf-fs-14)' }} value={panelTheme} onChange={e => { changePanelTheme(e.target.value as 'liquid' | 'neumorph' | 'macos' | 'clay') }}>
|
|
4056
4055
|
<option value="liquid">液态玻璃 · 清新绿(默认)</option>
|
|
4057
|
-
<option value="classic">经典毛玻璃 · 沉静蓝</option>
|
|
4058
4056
|
<option value="neumorph">新拟物 · 柔和浅色</option>
|
|
4057
|
+
<option value="macos">macOS · 玻璃(蓝,随外观浅/深)</option>
|
|
4058
|
+
<option value="clay">粘土拟态 · 柔和黏土</option>
|
|
4059
4059
|
</select>
|
|
4060
4060
|
<span className={css.meta}>{tt('settings.themeHint')}</span>
|
|
4061
4061
|
</div>
|
|
4062
4062
|
<div className={css.field}>
|
|
4063
|
-
<label className={css.fieldLabel} style={{ fontSize:
|
|
4064
|
-
<select className={css.input} style={{ padding: '
|
|
4063
|
+
<label className={css.fieldLabel} style={{ fontSize: 'var(--nf-fs-14)' }}>界面密度</label>
|
|
4064
|
+
<select className={css.input} style={{ padding: 'var(--nf-space-10) var(--nf-space-12)', fontSize: 'var(--nf-fs-14)' }} value={themeDensity} onChange={e => { changeThemeDensity(e.target.value as 'comfort' | 'compact' | 'spacious') }}>
|
|
4065
4065
|
<option value="comfort">舒适(默认)</option>
|
|
4066
4066
|
<option value="compact">紧凑</option>
|
|
4067
4067
|
<option value="spacious">宽松</option>
|
|
@@ -4070,7 +4070,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4070
4070
|
</div>
|
|
4071
4071
|
</div>
|
|
4072
4072
|
<div className={css.row} style={{ justifyContent: 'flex-end' }}>
|
|
4073
|
-
<button type="button" className={css.button} style={{ padding: '
|
|
4073
|
+
<button type="button" className={css.button} style={{ padding: 'var(--nf-space-10) var(--nf-space-18)', fontSize: 'var(--nf-fs-14)' }} onClick={resetTheme}>
|
|
4074
4074
|
<RotateCcw size={14} style={{ verticalAlign: -2 }} /> 恢复默认主题
|
|
4075
4075
|
</button>
|
|
4076
4076
|
</div>
|
|
@@ -4081,13 +4081,13 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4081
4081
|
{activeTab === 'book' && bookTab === 'facts' && (
|
|
4082
4082
|
<div className={css.card}>
|
|
4083
4083
|
<div className={css.row} style={{ justifyContent: 'space-between', flexWrap: 'wrap' }}>
|
|
4084
|
-
<span className={css.cardTitle} style={{ fontSize:
|
|
4084
|
+
<span className={css.cardTitle} style={{ fontSize: 'var(--nf-fs-16)', fontWeight: 700 }}>📚 编年 / 复盘</span>
|
|
4085
4085
|
</div>
|
|
4086
|
-
<div className={css.row} style={{ flexWrap: 'wrap', gap: 6 }}>
|
|
4086
|
+
<div className={css.row} style={{ flexWrap: 'wrap', gap: 'var(--nf-space-6)' }}>
|
|
4087
4087
|
<button
|
|
4088
4088
|
type="button"
|
|
4089
4089
|
className={`${css.button} ${archiveTab === 'facts' ? css.buttonPrimary : ''}`}
|
|
4090
|
-
style={{ fontSize: 14, flex: 1 }}
|
|
4090
|
+
style={{ fontSize: 'var(--nf-fs-14)', flex: 1 }}
|
|
4091
4091
|
onClick={() => { changeArchiveTab('facts') }}
|
|
4092
4092
|
title="编年录:客观事实流水(第 N 章 · 事件),生成/审稿查证用"
|
|
4093
4093
|
>
|
|
@@ -4096,7 +4096,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4096
4096
|
<button
|
|
4097
4097
|
type="button"
|
|
4098
4098
|
className={`${css.button} ${archiveTab === 'reviews' ? css.buttonPrimary : ''}`}
|
|
4099
|
-
style={{ fontSize: 14, flex: 1 }}
|
|
4099
|
+
style={{ fontSize: 'var(--nf-fs-14)', flex: 1 }}
|
|
4100
4100
|
onClick={() => { changeArchiveTab('reviews') }}
|
|
4101
4101
|
title="复盘记录:每章作者复盘(钩子 / 推进 / 衔接 / 趋势)"
|
|
4102
4102
|
>
|
|
@@ -4138,12 +4138,12 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4138
4138
|
</span>
|
|
4139
4139
|
)
|
|
4140
4140
|
})()}
|
|
4141
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, overflowY: 'auto', flex: 1, minHeight: 0 }}>
|
|
4141
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-10)', overflowY: 'auto', flex: 1, minHeight: 0 }}>
|
|
4142
4142
|
{(() => {
|
|
4143
4143
|
const groupsWithReviews = chapterGroups.filter(group => group.chapters.some(c => c.authorReview !== undefined))
|
|
4144
4144
|
if (groupsWithReviews.length === 0) return null
|
|
4145
4145
|
return (
|
|
4146
|
-
<div className={css.row} style={{ justifyContent: 'flex-end', gap: 6 }}>
|
|
4146
|
+
<div className={css.row} style={{ justifyContent: 'flex-end', gap: 'var(--nf-space-6)' }}>
|
|
4147
4147
|
<button
|
|
4148
4148
|
type="button"
|
|
4149
4149
|
className={`${css.button} ${css.buttonSmall}`}
|
|
@@ -4166,10 +4166,10 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4166
4166
|
if (groupReviewed.length === 0) return null
|
|
4167
4167
|
const volumeExpanded = expandedVolumes[group.no] === true
|
|
4168
4168
|
return (
|
|
4169
|
-
<div key={group.no} style={{ border: '1px solid var(--nf-border)', borderRadius: 10, overflow: 'hidden' }}>
|
|
4169
|
+
<div key={group.no} style={{ border: '1px solid var(--nf-border)', borderRadius: 'var(--nf-radius-10)', overflow: 'hidden' }}>
|
|
4170
4170
|
<button
|
|
4171
4171
|
type="button"
|
|
4172
|
-
style={{ width: '100%', textAlign: 'left', border: 'none', background: 'transparent', cursor: 'pointer', padding: '
|
|
4172
|
+
style={{ width: '100%', textAlign: 'left', border: 'none', background: 'transparent', cursor: 'pointer', padding: 'var(--nf-space-8) var(--nf-space-12)', fontSize: 'var(--nf-fs-14)', display: 'flex', alignItems: 'center', gap: 'var(--nf-space-8)', flexWrap: 'wrap' }}
|
|
4173
4173
|
onClick={() => { setExpandedVolumes(prev => ({ ...prev, [group.no]: !volumeExpanded })) }}
|
|
4174
4174
|
>
|
|
4175
4175
|
<span style={{ color: 'var(--nf-text-3)' }}>{volumeExpanded ? '▾' : '▸'}</span>
|
|
@@ -4177,25 +4177,25 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4177
4177
|
<span className={css.meta}>已复盘 {groupReviewed.length}/{group.chapters.length} 章</span>
|
|
4178
4178
|
</button>
|
|
4179
4179
|
{volumeExpanded && (
|
|
4180
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, padding: '
|
|
4180
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-4)', padding: 'var(--nf-space-4) var(--nf-space-8) var(--nf-space-8)', borderTop: '1px solid var(--nf-border)' }}>
|
|
4181
4181
|
{[...groupReviewed].reverse().map(c => {
|
|
4182
4182
|
const ar = c.authorReview!
|
|
4183
4183
|
const expanded = expandedReviewChapter === c.no
|
|
4184
4184
|
return (
|
|
4185
|
-
<div key={c.no} style={{ border: '1px solid var(--nf-border)', borderRadius: 8, overflow: 'hidden' }}>
|
|
4185
|
+
<div key={c.no} style={{ border: '1px solid var(--nf-border)', borderRadius: 'var(--nf-radius-8)', overflow: 'hidden' }}>
|
|
4186
4186
|
<button
|
|
4187
4187
|
type="button"
|
|
4188
|
-
style={{ width: '100%', textAlign: 'left', border: 'none', background: 'transparent', cursor: 'pointer', padding: '
|
|
4188
|
+
style={{ width: '100%', textAlign: 'left', border: 'none', background: 'transparent', cursor: 'pointer', padding: 'var(--nf-space-6) var(--nf-space-10)', fontSize: 'var(--nf-fs-12)', display: 'flex', alignItems: 'center', gap: 'var(--nf-space-6)', flexWrap: 'wrap' }}
|
|
4189
4189
|
onClick={() => { setExpandedReviewChapter(expanded ? null : c.no) }}
|
|
4190
4190
|
>
|
|
4191
4191
|
<span style={{ color: 'var(--nf-text-3)' }}>{expanded ? '▾' : '▸'}</span>
|
|
4192
4192
|
<b>第{c.no}章《{c.title}》</b>
|
|
4193
4193
|
<span style={{ color: ar.hookHonored ? 'var(--nf-success)' : 'var(--nf-warn)' }}>钩子{ar.hookHonored ? '✓' : '✗'}</span>
|
|
4194
4194
|
<span style={{ color: ar.endingHook >= 6 ? 'var(--nf-success)' : 'var(--nf-error)' }}>结尾钩子 {ar.endingHook}/10</span>
|
|
4195
|
-
{ar.plotlineProgress !== '' && <span className={css.meta} style={{ marginLeft: 4 }}>{ar.plotlineProgress.slice(0, 40)}{ar.plotlineProgress.length > 40 ? '…' : ''}</span>}
|
|
4195
|
+
{ar.plotlineProgress !== '' && <span className={css.meta} style={{ marginLeft: 'var(--nf-space-4)' }}>{ar.plotlineProgress.slice(0, 40)}{ar.plotlineProgress.length > 40 ? '…' : ''}</span>}
|
|
4196
4196
|
</button>
|
|
4197
4197
|
{expanded && (
|
|
4198
|
-
<div style={{ padding: '
|
|
4198
|
+
<div style={{ padding: 'var(--nf-space-4) var(--nf-space-10) var(--nf-space-8)', fontSize: 'var(--nf-fs-12)', display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-4)', borderTop: '1px solid var(--nf-border)' }}>
|
|
4199
4199
|
{ar.hookNote !== '' && <span className={css.meta}><b>钩子:</b>{ar.hookNote}</span>}
|
|
4200
4200
|
{ar.plotlineProgress !== '' && <span className={css.meta}><b>推进:</b>{ar.plotlineProgress}</span>}
|
|
4201
4201
|
{ar.continuity !== '' && <span className={css.meta}><b>衔接:</b>{ar.continuity}</span>}
|
|
@@ -4228,10 +4228,10 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4228
4228
|
</div>
|
|
4229
4229
|
<span className={css.meta}>{tt('facts.hint')}</span>
|
|
4230
4230
|
{(project?.facts ?? []).length > 0 ? (
|
|
4231
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, maxHeight: '60vh', overflowY: 'auto', fontSize: 12 }}>
|
|
4231
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-4)', maxHeight: '60vh', overflowY: 'auto', fontSize: 'var(--nf-fs-12)' }}>
|
|
4232
4232
|
{[...(project?.facts ?? [])].reverse().map((fact, i) => (
|
|
4233
|
-
<div key={i} style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
|
|
4234
|
-
<span className={css.badge} style={{ borderColor: 'var(--nf-text-3)', color: 'var(--nf-text-3)', flex: 'none', marginTop:
|
|
4233
|
+
<div key={i} style={{ display: 'flex', gap: 'var(--nf-space-8)', alignItems: 'flex-start' }}>
|
|
4234
|
+
<span className={css.badge} style={{ borderColor: 'var(--nf-text-3)', color: 'var(--nf-text-3)', flex: 'none', marginTop: 'var(--nf-space-2)' }}>
|
|
4235
4235
|
第 {fact.chapterNo} 章
|
|
4236
4236
|
</span>
|
|
4237
4237
|
<span className={css.meta}>{fact.text}</span>
|
|
@@ -4249,13 +4249,13 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4249
4249
|
{activeTab === 'plotlines' && (
|
|
4250
4250
|
<div className={css.card}>
|
|
4251
4251
|
<div className={css.row} style={{ justifyContent: 'space-between', flexWrap: 'wrap' }}>
|
|
4252
|
-
<span className={css.cardTitle} style={{ fontSize:
|
|
4252
|
+
<span className={css.cardTitle} style={{ fontSize: 'var(--nf-fs-16)', fontWeight: 700 }}>📜 长线管理</span>
|
|
4253
4253
|
</div>
|
|
4254
|
-
<div className={css.row} style={{ flexWrap: 'wrap', gap: 6 }}>
|
|
4254
|
+
<div className={css.row} style={{ flexWrap: 'wrap', gap: 'var(--nf-space-6)' }}>
|
|
4255
4255
|
<button
|
|
4256
4256
|
type="button"
|
|
4257
4257
|
className={`${css.button} ${longlineTab === 'plotlines' ? css.buttonPrimary : ''}`}
|
|
4258
|
-
style={{ fontSize: 14, flex: 1 }}
|
|
4258
|
+
style={{ fontSize: 'var(--nf-fs-14)', flex: 1 }}
|
|
4259
4259
|
onClick={() => { changeLonglineTab('plotlines') }}
|
|
4260
4260
|
title="剧情线:故事明线(主线 / 支线 / 人物 / 悬念)"
|
|
4261
4261
|
>
|
|
@@ -4264,7 +4264,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4264
4264
|
<button
|
|
4265
4265
|
type="button"
|
|
4266
4266
|
className={`${css.button} ${longlineTab === 'foreshadow' ? css.buttonPrimary : ''}`}
|
|
4267
|
-
style={{ fontSize: 14, flex: 1 }}
|
|
4267
|
+
style={{ fontSize: 'var(--nf-fs-14)', flex: 1 }}
|
|
4268
4268
|
onClick={() => { changeLonglineTab('foreshadow') }}
|
|
4269
4269
|
title="伏笔:道具 / 事件级暗线(埋设 → 回收)"
|
|
4270
4270
|
>
|
|
@@ -4300,7 +4300,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4300
4300
|
</div>
|
|
4301
4301
|
</div>
|
|
4302
4302
|
<span className={css.badge} style={{ borderColor: statusColor, color: statusColor }}>{statusLabel}</span>
|
|
4303
|
-
<div className={css.row} style={{ gap: 4 }}>
|
|
4303
|
+
<div className={css.row} style={{ gap: 'var(--nf-space-4)' }}>
|
|
4304
4304
|
{f.status === 'planned' && (
|
|
4305
4305
|
<button type="button" className={`${css.button} ${css.buttonSmall}`} disabled={busy} onClick={() => {
|
|
4306
4306
|
void api.foreshadow({ id: f.id, status: 'planted', plantedChapter: doneCount + 1 }).then(r => setProject(prev => prev === null ? prev : { ...prev, foreshadows: r.foreshadows }))
|
|
@@ -4395,7 +4395,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4395
4395
|
)}
|
|
4396
4396
|
|
|
4397
4397
|
{plotlineDraft !== null && (
|
|
4398
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, border: '1px solid var(--nf-accent)', borderRadius: 12, padding: 10 }}>
|
|
4398
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-8)', border: '1px solid var(--nf-accent)', borderRadius: 'var(--nf-radius-12)', padding: 'var(--nf-space-10)' }}>
|
|
4399
4399
|
<div className={css.row} style={{ flexWrap: 'wrap' }}>
|
|
4400
4400
|
<div className={css.field} style={{ flex: 2, minWidth: 160 }}>
|
|
4401
4401
|
<label className={css.fieldLabel}>{tt('plotlines.name')}</label>
|
|
@@ -4442,7 +4442,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4442
4442
|
{(project?.plotlines ?? []).length === 0 ? (
|
|
4443
4443
|
<span className={css.meta}>{tt('plotlines.empty')}</span>
|
|
4444
4444
|
) : (
|
|
4445
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, overflowY: 'auto', flex: 1, minHeight: 0 }}>
|
|
4445
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-8)', overflowY: 'auto', flex: 1, minHeight: 0 }}>
|
|
4446
4446
|
{(project?.plotlines ?? []).map(line => (
|
|
4447
4447
|
<PlotlineCard
|
|
4448
4448
|
key={line.id}
|
|
@@ -4510,7 +4510,7 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4510
4510
|
<span className={css.meta}>选择范围与档位,点「开始拆书」对已写章节做整卷体检</span>
|
|
4511
4511
|
</div>
|
|
4512
4512
|
) : (
|
|
4513
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
|
4513
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-10)' }}>
|
|
4514
4514
|
<span className={css.meta}>
|
|
4515
4515
|
分析 {breakdownResult.chaptersScanned} 章 · {breakdownResult.sections.length} 个小节 · 约 {breakdownResult.usedTokens} token
|
|
4516
4516
|
</span>
|
|
@@ -4520,14 +4520,14 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4520
4520
|
<b>{sec.title}</b>
|
|
4521
4521
|
</div>
|
|
4522
4522
|
{Object.keys(sec.structured).length > 0 && (
|
|
4523
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 2, fontSize: 12, marginBottom: 6 }}>
|
|
4523
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-2)', fontSize: 'var(--nf-fs-12)', marginBottom: 'var(--nf-space-6)' }}>
|
|
4524
4524
|
{Object.entries(sec.structured).map(([k, v]) => {
|
|
4525
4525
|
const label = (typeof v === 'string') ? v : Array.isArray(v) ? (v as string[]).join('、') : JSON.stringify(v)
|
|
4526
4526
|
return <div key={k} className={css.meta}><b>{k}:</b>{label}</div>
|
|
4527
4527
|
})}
|
|
4528
4528
|
</div>
|
|
4529
4529
|
)}
|
|
4530
|
-
<div className={css.meta} style={{ whiteSpace: 'pre-wrap', fontSize: 12 }}>{sec.markdown}</div>
|
|
4530
|
+
<div className={css.meta} style={{ whiteSpace: 'pre-wrap', fontSize: 'var(--nf-fs-12)' }}>{sec.markdown}</div>
|
|
4531
4531
|
</div>
|
|
4532
4532
|
))}
|
|
4533
4533
|
</div>
|
|
@@ -4585,11 +4585,11 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4585
4585
|
}}
|
|
4586
4586
|
>
|
|
4587
4587
|
<span>
|
|
4588
|
-
📊 AI进度
|
|
4588
|
+
📊 AI 进度
|
|
4589
4589
|
{busy && <span style={{ color: 'var(--nf-accent)' }}> 🟢 任务进行中</span>}
|
|
4590
4590
|
<span className={css.meta}>(拖动标题栏移动 · 右下角拉大小)</span>
|
|
4591
4591
|
</span>
|
|
4592
|
-
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
4592
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 'var(--nf-space-4)' }}>
|
|
4593
4593
|
{progress.length > 0 && (
|
|
4594
4594
|
<button type="button" className={`${css.button} ${css.buttonSmall}`} onClick={() => { setProgress([]) }} title="清空活动记录">
|
|
4595
4595
|
清空
|
|
@@ -4598,11 +4598,11 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4598
4598
|
<button type="button" className={css.iconButton} title="关闭" aria-label="关闭AI进度" onClick={() => { setProgressOpen(false) }}>×</button>
|
|
4599
4599
|
</span>
|
|
4600
4600
|
</div>
|
|
4601
|
-
<div className={css.assistantFloatBody} style={{ padding: 10, display: 'flex', flexDirection: 'column', gap: 8, overflow: 'hidden' }}>
|
|
4601
|
+
<div className={css.assistantFloatBody} style={{ padding: 'var(--nf-space-10)', display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-8)', overflow: 'hidden' }}>
|
|
4602
4602
|
{/* 当前任务大进度条:进行中显示 */}
|
|
4603
4603
|
{(busy && (busyLabel !== '' || liveBar !== null)) && (
|
|
4604
|
-
<div style={{ border: '1px solid var(--nf-accent)', borderRadius: 10, padding: '
|
|
4605
|
-
<span style={{ fontSize: 12, fontWeight: 600, color: 'var(--nf-accent)' }}>
|
|
4604
|
+
<div style={{ border: '1px solid var(--nf-accent)', borderRadius: 'var(--nf-radius-10)', padding: 'var(--nf-space-8) var(--nf-space-12)', display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-6)', background: 'color-mix(in srgb, var(--nf-accent) 6%, transparent)' }}>
|
|
4605
|
+
<span style={{ fontSize: 'var(--nf-fs-12)', fontWeight: 600, color: 'var(--nf-accent)' }}>
|
|
4606
4606
|
✍ {busyLabel !== '' ? busyLabel : (liveBar?.text ?? '任务进行中')}…
|
|
4607
4607
|
</span>
|
|
4608
4608
|
{liveBar?.ratio !== undefined && (
|
|
@@ -4614,10 +4614,10 @@ export function NovelPanel({ controller, api }: NovelPanelProps) {
|
|
|
4614
4614
|
</div>
|
|
4615
4615
|
)}
|
|
4616
4616
|
{/* 活动记录列表(标题行已并入窗口标题栏,清空按钮在右上角) */}
|
|
4617
|
-
<div className={css.progress} style={{ flex: 1, minHeight: 0, overflowY: 'auto', border: '1px solid var(--nf-border)', borderRadius: 10, background: 'var(--nf-bg-inset)', padding: 8, display: 'flex', flexDirection: 'column' }}>
|
|
4617
|
+
<div className={css.progress} style={{ flex: 1, minHeight: 0, overflowY: 'auto', border: '1px solid var(--nf-border)', borderRadius: 'var(--nf-radius-10)', background: 'var(--nf-bg-inset)', padding: 'var(--nf-space-8)', display: 'flex', flexDirection: 'column' }}>
|
|
4618
4618
|
{progress.length === 0 ? (
|
|
4619
|
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 4, color: 'var(--nf-text-3)', fontSize: 12, minHeight: 120 }}>
|
|
4620
|
-
<span style={{ fontSize: 22 }}>📭</span>
|
|
4619
|
+
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 'var(--nf-space-4)', color: 'var(--nf-text-3)', fontSize: 'var(--nf-fs-12)', minHeight: 120 }}>
|
|
4620
|
+
<span style={{ fontSize: 'var(--nf-fs-22)' }}>📭</span>
|
|
4621
4621
|
<span>暂无活动记录</span>
|
|
4622
4622
|
<span>生成、审稿等操作会显示在这里</span>
|
|
4623
4623
|
</div>
|