@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.
Files changed (141) hide show
  1. package/README.md +236 -349
  2. package/lib/client.js +56064 -10565
  3. package/lib/index.js +13922 -5218
  4. package/lib/types/action-guard.d.ts +61 -0
  5. package/lib/types/ai-scan.d.ts +36 -0
  6. package/lib/types/assets.d.ts +24 -9
  7. package/lib/types/assistant.d.ts +29 -0
  8. package/lib/types/author-assets.d.ts +25 -0
  9. package/lib/types/bookshelf.d.ts +11 -1
  10. package/lib/types/client/api.d.ts +178 -10
  11. package/lib/types/client/docx.d.ts +2 -0
  12. package/lib/types/client/index.d.ts +1 -1
  13. package/lib/types/client/llmLive.d.ts +31 -0
  14. package/lib/types/client/locales.d.ts +28 -11
  15. package/lib/types/client/panel/AdaptModeView.d.ts +5 -0
  16. package/lib/types/client/panel/AiTag.d.ts +1 -0
  17. package/lib/types/client/panel/AssistantTab.d.ts +4 -1
  18. package/lib/types/client/panel/AuthorAssetsView.d.ts +4 -0
  19. package/lib/types/client/panel/AuthorHome.d.ts +34 -0
  20. package/lib/types/client/panel/BookAnalysisView.d.ts +4 -0
  21. package/lib/types/client/panel/CmdPalette.d.ts +12 -0
  22. package/lib/types/client/panel/CreateBookView.d.ts +5 -1
  23. package/lib/types/client/panel/DirectorView.d.ts +7 -0
  24. package/lib/types/client/panel/GlobalAssetLibraryView.d.ts +4 -0
  25. package/lib/types/client/panel/IdeaInspirationView.d.ts +6 -0
  26. package/lib/types/client/panel/ImportModal.d.ts +7 -0
  27. package/lib/types/client/panel/KnowledgeBaseView.d.ts +4 -0
  28. package/lib/types/client/panel/LiveFeedLog.d.ts +13 -0
  29. package/lib/types/client/panel/MarketRadarView.d.ts +5 -0
  30. package/lib/types/client/panel/ModelManager.d.ts +14 -0
  31. package/lib/types/client/panel/NovelPanel.d.ts +6 -1
  32. package/lib/types/client/panel/PageHeader.d.ts +18 -0
  33. package/lib/types/client/panel/ProgressConsole.d.ts +17 -0
  34. package/lib/types/client/panel/ProgressHomeView.d.ts +5 -0
  35. package/lib/types/client/panel/PromptSlotsView.d.ts +11 -0
  36. package/lib/types/client/panel/ReasoningSection.d.ts +10 -0
  37. package/lib/types/client/panel/SettingsView.d.ts +19 -0
  38. package/lib/types/client/panel/ShelfView.d.ts +12 -1
  39. package/lib/types/client/panel/SubPage.d.ts +27 -0
  40. package/lib/types/client/panel/TensionView.d.ts +14 -0
  41. package/lib/types/client/panel/TimelineView.d.ts +14 -0
  42. package/lib/types/client/panel/toast.d.ts +2 -0
  43. package/lib/types/client/panel/views.d.ts +25 -7
  44. package/lib/types/client/run-status.d.ts +19 -0
  45. package/lib/types/client/text.d.ts +9 -0
  46. package/lib/types/docx-core.d.ts +16 -0
  47. package/lib/types/docx.d.ts +2 -3
  48. package/lib/types/engine.d.ts +185 -41
  49. package/lib/types/global-assets.d.ts +6 -0
  50. package/lib/types/index.d.ts +22 -2
  51. package/lib/types/llm-live.d.ts +75 -0
  52. package/lib/types/llm-retry.d.ts +38 -0
  53. package/lib/types/market-radar-scan.d.ts +19 -0
  54. package/lib/types/market-radar-sources.d.ts +31 -0
  55. package/lib/types/migrate-output.d.ts +25 -0
  56. package/lib/types/novel-context.d.ts +80 -0
  57. package/lib/types/prompt-slots.d.ts +57 -0
  58. package/lib/types/prompt-utils.d.ts +35 -0
  59. package/lib/types/prompting.d.ts +10 -0
  60. package/lib/types/protocol.d.ts +1311 -125
  61. package/lib/types/review-policy.d.ts +97 -0
  62. package/lib/types/routes.d.ts +4 -0
  63. package/lib/types/run.d.ts +3 -1
  64. package/lib/types/snapshots.d.ts +46 -0
  65. package/lib/types/stage-contract.d.ts +60 -0
  66. package/lib/types/tension.d.ts +65 -0
  67. package/lib/types/timeline.d.ts +37 -0
  68. package/package.json +52 -27
  69. package/scripts/patch-schemastery.mjs +47 -0
  70. package/src/action-guard.ts +127 -0
  71. package/src/ai-scan.ts +179 -0
  72. package/src/assets.ts +1806 -520
  73. package/src/assistant.ts +1098 -770
  74. package/src/author-assets.ts +149 -0
  75. package/src/bookshelf.ts +187 -151
  76. package/src/client/api.ts +758 -410
  77. package/src/client/docx.ts +37 -69
  78. package/src/client/index.ts +3 -1
  79. package/src/client/llmLive.ts +111 -0
  80. package/src/client/locales.ts +418 -383
  81. package/src/client/mount.tsx +1 -1
  82. package/src/client/panel/AdaptModeView.tsx +535 -0
  83. package/src/client/panel/AiTag.tsx +7 -0
  84. package/src/client/panel/AssetsTab.tsx +448 -458
  85. package/src/client/panel/AssistantTab.tsx +355 -301
  86. package/src/client/panel/AuthorAssetsView.tsx +206 -0
  87. package/src/client/panel/AuthorHome.tsx +309 -0
  88. package/src/client/panel/BookAnalysisView.tsx +63 -0
  89. package/src/client/panel/CmdPalette.tsx +102 -0
  90. package/src/client/panel/CreateBookView.tsx +366 -348
  91. package/src/client/panel/DirectorView.tsx +200 -0
  92. package/src/client/panel/GlobalAssetLibraryView.tsx +168 -0
  93. package/src/client/panel/IdeaInspirationView.tsx +76 -0
  94. package/src/client/panel/ImportModal.tsx +303 -0
  95. package/src/client/panel/KnowledgeBaseView.tsx +125 -0
  96. package/src/client/panel/LiveFeedLog.tsx +204 -0
  97. package/src/client/panel/MarketRadarView.tsx +377 -0
  98. package/src/client/panel/ModelManager.tsx +205 -0
  99. package/src/client/panel/NovelPanel.tsx +5517 -4597
  100. package/src/client/panel/PageHeader.tsx +37 -0
  101. package/src/client/panel/ProgressConsole.tsx +63 -0
  102. package/src/client/panel/PromptSlotsView.tsx +97 -0
  103. package/src/client/panel/ReaderView.tsx +306 -306
  104. package/src/client/panel/ReasoningSection.tsx +31 -0
  105. package/src/client/panel/RunPanel.tsx +223 -204
  106. package/src/client/panel/SettingsView.tsx +340 -0
  107. package/src/client/panel/ShelfView.tsx +266 -236
  108. package/src/client/panel/SubPage.tsx +42 -0
  109. package/src/client/panel/TensionView.tsx +196 -0
  110. package/src/client/panel/TimelineView.tsx +192 -0
  111. package/src/client/panel/WorldTab.tsx +202 -202
  112. package/src/client/panel/panel.module.css +5764 -2941
  113. package/src/client/panel/toast.ts +51 -0
  114. package/src/client/panel/views.tsx +401 -314
  115. package/src/client/run-status.ts +143 -0
  116. package/src/client/sidebar-entry.ts +1 -1
  117. package/src/client/text.ts +28 -0
  118. package/src/docx-core.ts +53 -0
  119. package/src/docx.ts +44 -83
  120. package/src/engine.ts +4672 -2787
  121. package/src/global-assets.ts +59 -0
  122. package/src/index.ts +323 -252
  123. package/src/llm-live.ts +260 -0
  124. package/src/llm-retry.ts +77 -0
  125. package/src/market-radar-scan.ts +41 -0
  126. package/src/market-radar-sources.ts +173 -0
  127. package/src/migrate-output.ts +76 -0
  128. package/src/novel-context.ts +286 -0
  129. package/src/prompt-slots.ts +125 -0
  130. package/src/prompting.ts +26 -0
  131. package/src/protocol.ts +2383 -1182
  132. package/src/review-policy.ts +161 -0
  133. package/src/routes.ts +3724 -2151
  134. package/src/run.ts +346 -330
  135. package/src/snapshots.ts +174 -0
  136. package/src/stage-contract.ts +189 -0
  137. package/src/tension.ts +240 -0
  138. package/src/timeline.ts +159 -0
  139. package/lib/client.js.map +0 -1
  140. package/lib/index.js.map +0 -1
  141. package/src/client/panel/BookshelfBar.tsx +0 -170
@@ -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
- import { unzipSync, strFromU8 } from 'fflate/browser'
14
-
15
- /** Decode the handful of XML entities docx bodies actually use. */
16
- function decodeEntities(text: string): string {
17
- return text
18
- .replace(/&lt;/g, '<')
19
- .replace(/&gt;/g, '>')
20
- .replace(/&quot;/g, '"')
21
- .replace(/&apos;/g, "'")
22
- .replace(/&amp;/g, '&')
23
- .replace(/&nbsp;/g, ' ')
24
- }
25
-
26
- /** Extract plain text from a docx buffer: one line per <w:p> paragraph. */
27
- export function extractDocxTextFromBuffer(buffer: ArrayBuffer | Uint8Array): string {
28
- const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)
29
- let files: ReturnType<typeof unzipSync>
30
- try {
31
- files = unzipSync(bytes)
32
- } catch (error) {
33
- throw new Error(`不是有效的 docx(zip 解压失败):${(error as Error).message}`)
34
- }
35
- const document = files['word/document.xml']
36
- if (document === undefined) {
37
- throw new Error('不是有效的 docx(缺少 word/document.xml)')
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
+ }
@@ -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/dsh-client-runtime/client'
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
+ }