@waterwx/dsh-novel-forge 1.2.0 → 1.3.0-alpha
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 +236 -349
- package/lib/client.js +56064 -10565
- package/lib/index.js +13922 -5218
- package/lib/types/action-guard.d.ts +61 -0
- package/lib/types/ai-scan.d.ts +36 -0
- package/lib/types/assets.d.ts +24 -9
- package/lib/types/assistant.d.ts +29 -0
- package/lib/types/author-assets.d.ts +25 -0
- package/lib/types/bookshelf.d.ts +11 -1
- package/lib/types/client/api.d.ts +178 -10
- package/lib/types/client/docx.d.ts +2 -0
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/llmLive.d.ts +31 -0
- package/lib/types/client/locales.d.ts +28 -11
- package/lib/types/client/panel/AdaptModeView.d.ts +5 -0
- package/lib/types/client/panel/AiTag.d.ts +1 -0
- package/lib/types/client/panel/AssistantTab.d.ts +4 -1
- package/lib/types/client/panel/AuthorAssetsView.d.ts +4 -0
- package/lib/types/client/panel/AuthorHome.d.ts +34 -0
- package/lib/types/client/panel/BookAnalysisView.d.ts +4 -0
- package/lib/types/client/panel/CmdPalette.d.ts +12 -0
- package/lib/types/client/panel/CreateBookView.d.ts +5 -1
- package/lib/types/client/panel/DirectorView.d.ts +7 -0
- package/lib/types/client/panel/GlobalAssetLibraryView.d.ts +4 -0
- package/lib/types/client/panel/IdeaInspirationView.d.ts +6 -0
- package/lib/types/client/panel/ImportModal.d.ts +7 -0
- package/lib/types/client/panel/KnowledgeBaseView.d.ts +4 -0
- package/lib/types/client/panel/LiveFeedLog.d.ts +13 -0
- package/lib/types/client/panel/MarketRadarView.d.ts +5 -0
- package/lib/types/client/panel/ModelManager.d.ts +14 -0
- package/lib/types/client/panel/NovelPanel.d.ts +6 -1
- package/lib/types/client/panel/PageHeader.d.ts +18 -0
- package/lib/types/client/panel/ProgressConsole.d.ts +17 -0
- package/lib/types/client/panel/ProgressHomeView.d.ts +5 -0
- package/lib/types/client/panel/PromptSlotsView.d.ts +11 -0
- package/lib/types/client/panel/ReasoningSection.d.ts +10 -0
- package/lib/types/client/panel/SettingsView.d.ts +19 -0
- package/lib/types/client/panel/ShelfView.d.ts +12 -1
- package/lib/types/client/panel/SubPage.d.ts +27 -0
- package/lib/types/client/panel/TensionView.d.ts +14 -0
- package/lib/types/client/panel/TimelineView.d.ts +14 -0
- package/lib/types/client/panel/toast.d.ts +2 -0
- package/lib/types/client/panel/views.d.ts +25 -7
- package/lib/types/client/run-status.d.ts +19 -0
- package/lib/types/client/text.d.ts +9 -0
- package/lib/types/docx-core.d.ts +16 -0
- package/lib/types/docx.d.ts +2 -3
- package/lib/types/engine.d.ts +185 -41
- package/lib/types/global-assets.d.ts +6 -0
- package/lib/types/index.d.ts +22 -2
- package/lib/types/llm-live.d.ts +75 -0
- package/lib/types/llm-retry.d.ts +38 -0
- package/lib/types/market-radar-scan.d.ts +19 -0
- package/lib/types/market-radar-sources.d.ts +31 -0
- package/lib/types/migrate-output.d.ts +25 -0
- package/lib/types/novel-context.d.ts +80 -0
- package/lib/types/prompt-slots.d.ts +57 -0
- package/lib/types/prompt-utils.d.ts +35 -0
- package/lib/types/prompting.d.ts +10 -0
- package/lib/types/protocol.d.ts +1311 -125
- package/lib/types/review-policy.d.ts +97 -0
- package/lib/types/routes.d.ts +4 -0
- package/lib/types/run.d.ts +3 -1
- package/lib/types/snapshots.d.ts +46 -0
- package/lib/types/stage-contract.d.ts +60 -0
- package/lib/types/tension.d.ts +65 -0
- package/lib/types/timeline.d.ts +37 -0
- package/package.json +52 -27
- package/scripts/patch-schemastery.mjs +47 -0
- package/src/action-guard.ts +127 -0
- package/src/ai-scan.ts +179 -0
- package/src/assets.ts +1806 -520
- package/src/assistant.ts +1098 -770
- package/src/author-assets.ts +149 -0
- package/src/bookshelf.ts +187 -151
- package/src/client/api.ts +758 -410
- package/src/client/docx.ts +37 -69
- package/src/client/index.ts +3 -1
- package/src/client/llmLive.ts +111 -0
- package/src/client/locales.ts +418 -383
- package/src/client/mount.tsx +1 -1
- package/src/client/panel/AdaptModeView.tsx +535 -0
- package/src/client/panel/AiTag.tsx +7 -0
- package/src/client/panel/AssetsTab.tsx +448 -458
- package/src/client/panel/AssistantTab.tsx +355 -301
- package/src/client/panel/AuthorAssetsView.tsx +206 -0
- package/src/client/panel/AuthorHome.tsx +309 -0
- package/src/client/panel/BookAnalysisView.tsx +63 -0
- package/src/client/panel/CmdPalette.tsx +102 -0
- package/src/client/panel/CreateBookView.tsx +366 -348
- package/src/client/panel/DirectorView.tsx +200 -0
- package/src/client/panel/GlobalAssetLibraryView.tsx +168 -0
- package/src/client/panel/IdeaInspirationView.tsx +76 -0
- package/src/client/panel/ImportModal.tsx +303 -0
- package/src/client/panel/KnowledgeBaseView.tsx +125 -0
- package/src/client/panel/LiveFeedLog.tsx +204 -0
- package/src/client/panel/MarketRadarView.tsx +377 -0
- package/src/client/panel/ModelManager.tsx +205 -0
- package/src/client/panel/NovelPanel.tsx +5517 -4597
- package/src/client/panel/PageHeader.tsx +37 -0
- package/src/client/panel/ProgressConsole.tsx +63 -0
- package/src/client/panel/PromptSlotsView.tsx +97 -0
- package/src/client/panel/ReaderView.tsx +306 -306
- package/src/client/panel/ReasoningSection.tsx +31 -0
- package/src/client/panel/RunPanel.tsx +223 -204
- package/src/client/panel/SettingsView.tsx +340 -0
- package/src/client/panel/ShelfView.tsx +266 -236
- package/src/client/panel/SubPage.tsx +42 -0
- package/src/client/panel/TensionView.tsx +196 -0
- package/src/client/panel/TimelineView.tsx +192 -0
- package/src/client/panel/WorldTab.tsx +202 -202
- package/src/client/panel/panel.module.css +5764 -2941
- package/src/client/panel/toast.ts +51 -0
- package/src/client/panel/views.tsx +401 -314
- package/src/client/run-status.ts +143 -0
- package/src/client/sidebar-entry.ts +1 -1
- package/src/client/text.ts +28 -0
- package/src/docx-core.ts +53 -0
- package/src/docx.ts +44 -83
- package/src/engine.ts +4672 -2787
- package/src/global-assets.ts +59 -0
- package/src/index.ts +323 -252
- package/src/llm-live.ts +260 -0
- package/src/llm-retry.ts +77 -0
- package/src/market-radar-scan.ts +41 -0
- package/src/market-radar-sources.ts +173 -0
- package/src/migrate-output.ts +76 -0
- package/src/novel-context.ts +286 -0
- package/src/prompt-slots.ts +125 -0
- package/src/prompting.ts +26 -0
- package/src/protocol.ts +2383 -1182
- package/src/review-policy.ts +161 -0
- package/src/routes.ts +3724 -2151
- package/src/run.ts +346 -330
- package/src/snapshots.ts +174 -0
- package/src/stage-contract.ts +189 -0
- package/src/tension.ts +240 -0
- package/src/timeline.ts +159 -0
- package/lib/client.js.map +0 -1
- package/lib/index.js.map +0 -1
- package/src/client/panel/BookshelfBar.tsx +0 -170
package/src/client/docx.ts
CHANGED
|
@@ -1,69 +1,37 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-side docx outline extraction: a .docx is a zip whose
|
|
3
|
-
* word/document.xml holds the body text in <w:t> runs inside <w:p> paragraphs.
|
|
4
|
-
* Uses fflate (inlined into the client bundle) so the user can pick or drag a
|
|
5
|
-
* docx without any server upload.
|
|
6
|
-
*
|
|
7
|
-
* Import from 'fflate/browser' (not 'fflate'): the default entry resolves to
|
|
8
|
-
* the Node build (esm/index.mjs), which calls module.createRequire() for the
|
|
9
|
-
* optional worker_threads path — inlining that into the browser bundle leaves
|
|
10
|
-
* a bare require("module") the client-modules table cannot answer.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
const xml = strFromU8(document)
|
|
40
|
-
|
|
41
|
-
const paragraphs: string[] = []
|
|
42
|
-
const parts = xml.split(/<w:p\b[^>]*>/)
|
|
43
|
-
for (let i = 1; i < parts.length; i++) {
|
|
44
|
-
const segment = parts[i]!
|
|
45
|
-
const runs: string[] = []
|
|
46
|
-
const runRe = /<w:t(?:\s[^>]*)?>([\s\S]*?)<\/w:t>|<w:tab\b[^>]*\/>|<w:br\b[^>]*\/>/g
|
|
47
|
-
for (const match of segment.matchAll(runRe)) {
|
|
48
|
-
if (match[0].startsWith('<w:tab')) {
|
|
49
|
-
runs.push('\t')
|
|
50
|
-
} else if (match[0].startsWith('<w:br')) {
|
|
51
|
-
runs.push('\n')
|
|
52
|
-
} else {
|
|
53
|
-
runs.push(decodeEntities(match[1] ?? ''))
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
paragraphs.push(runs.join('').replace(/\u00a0/g, ' ').trimEnd())
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const text = paragraphs.join('\n').replace(/\n{3,}/g, '\n\n').trim()
|
|
60
|
-
if (text.length === 0) {
|
|
61
|
-
throw new Error('docx 中没有可提取的文本')
|
|
62
|
-
}
|
|
63
|
-
return text
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Read a File as ArrayBuffer. */
|
|
67
|
-
export function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
|
|
68
|
-
return file.arrayBuffer()
|
|
69
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side docx outline extraction: a .docx is a zip whose
|
|
3
|
+
* word/document.xml holds the body text in <w:t> runs inside <w:p> paragraphs.
|
|
4
|
+
* Uses fflate (inlined into the client bundle) so the user can pick or drag a
|
|
5
|
+
* docx without any server upload.
|
|
6
|
+
*
|
|
7
|
+
* Import from 'fflate/browser' (not 'fflate'): the default entry resolves to
|
|
8
|
+
* the Node build (esm/index.mjs), which calls module.createRequire() for the
|
|
9
|
+
* optional worker_threads path — inlining that into the browser bundle leaves
|
|
10
|
+
* a bare require("module") the client-modules table cannot answer.
|
|
11
|
+
*
|
|
12
|
+
* XML 解析共享核心在 ../docx-core.ts(与服务端同源)。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { unzipSync, strFromU8 } from 'fflate/browser'
|
|
16
|
+
import { extractDocxTextFromXml } from '../docx-core.ts'
|
|
17
|
+
|
|
18
|
+
/** Extract plain text from a docx buffer: one line per <w:p> paragraph. */
|
|
19
|
+
export function extractDocxTextFromBuffer(buffer: ArrayBuffer | Uint8Array): string {
|
|
20
|
+
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)
|
|
21
|
+
let files: ReturnType<typeof unzipSync>
|
|
22
|
+
try {
|
|
23
|
+
files = unzipSync(bytes)
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new Error(`不是有效的 docx(zip 解压失败):${(error as Error).message}`)
|
|
26
|
+
}
|
|
27
|
+
const document = files['word/document.xml']
|
|
28
|
+
if (document === undefined) {
|
|
29
|
+
throw new Error('不是有效的 docx(缺少 word/document.xml)')
|
|
30
|
+
}
|
|
31
|
+
return extractDocxTextFromXml(strFromU8(document))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Read a File as ArrayBuffer. */
|
|
35
|
+
export function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
|
|
36
|
+
return file.arrayBuffer()
|
|
37
|
+
}
|
package/src/client/index.ts
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
* problems are logged, never thrown — the web shell fails the whole boot when
|
|
5
5
|
* a plugin apply throws.
|
|
6
6
|
*/
|
|
7
|
-
import type { ClientContext } from '@deepseek-ai/
|
|
7
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis'
|
|
8
8
|
import { NovelApi } from './api.ts'
|
|
9
9
|
import { mountPanel } from './mount.tsx'
|
|
10
10
|
import { PanelController } from './panel/controller.ts'
|
|
11
11
|
import { mountSidebarEntry } from './sidebar-entry.ts'
|
|
12
|
+
import { mountRunStatus } from './run-status.ts'
|
|
12
13
|
|
|
13
14
|
/** Required services (fiber inject waiting). */
|
|
14
15
|
export const inject = ['slots', 'locale']
|
|
@@ -24,6 +25,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
24
25
|
try {
|
|
25
26
|
disposers.push(mountSidebarEntry(controller))
|
|
26
27
|
disposers.push(mountPanel(controller, api))
|
|
28
|
+
disposers.push(mountRunStatus(controller, api))
|
|
27
29
|
} catch (error) {
|
|
28
30
|
// DOM failures degrade the panel, never the GUI.
|
|
29
31
|
console.warn('[dsh-novel-forge] mount failed:', error)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器端「AI 创作实况」钩子:连接 /llm-live/stream (SSE),按 LLM 调用分组会话。
|
|
3
|
+
* 轻量实现,不持久化缓存(对齐上游 useLlmLiveFeed 的核心行为)。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
7
|
+
import { NOVEL_API, type LlmLiveFrame, type LlmTokenUsage } from '../protocol.ts'
|
|
8
|
+
|
|
9
|
+
// 帧结构定义在 protocol.ts(宿主与浏览器共用同一份线上形状),此处只再导出。
|
|
10
|
+
export type { LlmLiveFrame }
|
|
11
|
+
|
|
12
|
+
export interface LlmLiveSession {
|
|
13
|
+
sessionId: string
|
|
14
|
+
label: string
|
|
15
|
+
model?: string
|
|
16
|
+
phase: 'requesting' | 'streaming' | 'completed' | 'failed'
|
|
17
|
+
phaseMessage: string
|
|
18
|
+
preview: string
|
|
19
|
+
totalChars: number
|
|
20
|
+
startedAt: string
|
|
21
|
+
updatedAt: string
|
|
22
|
+
/** 本次调用的 token 用量(供应商未上报时缺省)。 */
|
|
23
|
+
usage?: LlmTokenUsage
|
|
24
|
+
/** 本次调用耗时(毫秒)。 */
|
|
25
|
+
elapsedMs?: number
|
|
26
|
+
/** 首字耗时(毫秒)。 */
|
|
27
|
+
firstTokenMs?: number
|
|
28
|
+
/** 是否保存了可查看的 Prompt。 */
|
|
29
|
+
hasPrompt?: boolean
|
|
30
|
+
/** Prompt 字符数(system + user)。 */
|
|
31
|
+
promptChars?: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const MAX_PREVIEW_CHARS = 20_000
|
|
35
|
+
|
|
36
|
+
function applyFrame(current: Record<string, LlmLiveSession>, frame: LlmLiveFrame): Record<string, LlmLiveSession> {
|
|
37
|
+
const next = { ...current }
|
|
38
|
+
const id = frame.sessionId
|
|
39
|
+
let session = next[id]
|
|
40
|
+
if (frame.type === 'session_started') {
|
|
41
|
+
session = {
|
|
42
|
+
sessionId: id,
|
|
43
|
+
label: frame.label ?? 'LLM 调用',
|
|
44
|
+
model: frame.model,
|
|
45
|
+
phase: 'requesting',
|
|
46
|
+
phaseMessage: '正在连接模型',
|
|
47
|
+
preview: '',
|
|
48
|
+
totalChars: 0,
|
|
49
|
+
startedAt: frame.at,
|
|
50
|
+
updatedAt: frame.at,
|
|
51
|
+
hasPrompt: frame.hasPrompt,
|
|
52
|
+
promptChars: frame.promptChars,
|
|
53
|
+
}
|
|
54
|
+
next[id] = session
|
|
55
|
+
return next
|
|
56
|
+
}
|
|
57
|
+
if (session === undefined) return next
|
|
58
|
+
if (frame.type === 'output_delta') {
|
|
59
|
+
const preview = (session.preview + (frame.content ?? '')).slice(-MAX_PREVIEW_CHARS)
|
|
60
|
+
session = { ...session, phase: session.phase === 'requesting' ? 'streaming' : session.phase, phaseMessage: session.phase === 'requesting' ? '模型正在返回内容' : session.phaseMessage, preview, totalChars: frame.totalChars ?? session.totalChars + (frame.content?.length ?? 0), updatedAt: frame.at }
|
|
61
|
+
next[id] = session
|
|
62
|
+
} else if (frame.type === 'reasoning_delta') {
|
|
63
|
+
session = { ...session, phase: session.phase === 'requesting' ? 'streaming' : session.phase, updatedAt: frame.at }
|
|
64
|
+
next[id] = session
|
|
65
|
+
} else if (frame.type === 'phase_changed') {
|
|
66
|
+
session = { ...session, phase: frame.phase ?? session.phase, phaseMessage: frame.phaseMessage ?? session.phaseMessage, updatedAt: frame.at }
|
|
67
|
+
next[id] = session
|
|
68
|
+
} else if (frame.type === 'session_completed') {
|
|
69
|
+
session = {
|
|
70
|
+
...session,
|
|
71
|
+
phase: frame.phase ?? 'completed',
|
|
72
|
+
phaseMessage: frame.phase === 'failed' ? (frame.error ?? '调用失败') : '模型结果已准备完成',
|
|
73
|
+
totalChars: frame.totalChars ?? session.totalChars,
|
|
74
|
+
preview: session.preview || (frame.preview ?? ''),
|
|
75
|
+
updatedAt: frame.at,
|
|
76
|
+
usage: frame.usage ?? session.usage,
|
|
77
|
+
elapsedMs: frame.elapsedMs ?? session.elapsedMs,
|
|
78
|
+
firstTokenMs: frame.firstTokenMs ?? session.firstTokenMs,
|
|
79
|
+
}
|
|
80
|
+
next[id] = session
|
|
81
|
+
}
|
|
82
|
+
return next
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function useLlmLiveFeed(enabled = true): { connected: boolean; sessions: LlmLiveSession[] } {
|
|
86
|
+
const [sessions, setSessions] = useState<Record<string, LlmLiveSession>>({})
|
|
87
|
+
const [connected, setConnected] = useState(false)
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!enabled) {
|
|
90
|
+
setSessions({})
|
|
91
|
+
setConnected(false)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
const es = new EventSource(NOVEL_API.llmLive)
|
|
95
|
+
es.onopen = () => setConnected(true)
|
|
96
|
+
es.onmessage = (ev) => {
|
|
97
|
+
try {
|
|
98
|
+
const frame = JSON.parse(ev.data as string) as LlmLiveFrame
|
|
99
|
+
setSessions(prev => applyFrame(prev, frame))
|
|
100
|
+
} catch { /* ignore malformed frame */ }
|
|
101
|
+
}
|
|
102
|
+
// EventSource 断线会自动重连;这里只在断线瞬间标记未连接,重连成功后会再次 onopen。
|
|
103
|
+
es.onerror = () => setConnected(false)
|
|
104
|
+
return () => {
|
|
105
|
+
es.close()
|
|
106
|
+
setConnected(false)
|
|
107
|
+
}
|
|
108
|
+
}, [enabled])
|
|
109
|
+
const list = useMemo(() => Object.values(sessions).sort((a, b) => a.startedAt.localeCompare(b.startedAt)), [sessions])
|
|
110
|
+
return { connected, sessions: list }
|
|
111
|
+
}
|