@waterwx/dsh-novel-forge 1.2.0 → 1.2.1-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 (125) hide show
  1. package/README.md +237 -349
  2. package/lib/client.js +54447 -10560
  3. package/lib/index.js +12008 -5071
  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 +18 -3
  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 +147 -9
  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 +33 -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 +8 -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/ReasoningSection.d.ts +10 -0
  36. package/lib/types/client/panel/SettingsView.d.ts +19 -0
  37. package/lib/types/client/panel/ShelfView.d.ts +12 -1
  38. package/lib/types/client/panel/SubPage.d.ts +27 -0
  39. package/lib/types/client/panel/toast.d.ts +2 -0
  40. package/lib/types/client/panel/views.d.ts +25 -7
  41. package/lib/types/client/run-status.d.ts +19 -0
  42. package/lib/types/client/text.d.ts +9 -0
  43. package/lib/types/docx-core.d.ts +16 -0
  44. package/lib/types/docx.d.ts +2 -3
  45. package/lib/types/engine.d.ts +146 -40
  46. package/lib/types/global-assets.d.ts +6 -0
  47. package/lib/types/index.d.ts +18 -2
  48. package/lib/types/llm-live.d.ts +44 -0
  49. package/lib/types/market-radar-scan.d.ts +19 -0
  50. package/lib/types/market-radar-sources.d.ts +31 -0
  51. package/lib/types/migrate-output.d.ts +25 -0
  52. package/lib/types/novel-context.d.ts +80 -0
  53. package/lib/types/prompt-utils.d.ts +35 -0
  54. package/lib/types/prompting.d.ts +10 -0
  55. package/lib/types/protocol.d.ts +1029 -123
  56. package/lib/types/review-policy.d.ts +97 -0
  57. package/lib/types/routes.d.ts +4 -0
  58. package/lib/types/run.d.ts +3 -1
  59. package/lib/types/stage-contract.d.ts +60 -0
  60. package/package.json +51 -27
  61. package/scripts/patch-schemastery.mjs +47 -0
  62. package/src/action-guard.ts +127 -0
  63. package/src/ai-scan.ts +179 -0
  64. package/src/assets.ts +1774 -520
  65. package/src/assistant.ts +1082 -770
  66. package/src/author-assets.ts +149 -0
  67. package/src/bookshelf.ts +187 -151
  68. package/src/client/api.ts +684 -410
  69. package/src/client/docx.ts +37 -69
  70. package/src/client/index.ts +3 -1
  71. package/src/client/llmLive.ts +101 -0
  72. package/src/client/locales.ts +418 -383
  73. package/src/client/mount.tsx +1 -1
  74. package/src/client/panel/AdaptModeView.tsx +535 -0
  75. package/src/client/panel/AiTag.tsx +7 -0
  76. package/src/client/panel/AssetsTab.tsx +448 -458
  77. package/src/client/panel/AssistantTab.tsx +355 -301
  78. package/src/client/panel/AuthorAssetsView.tsx +206 -0
  79. package/src/client/panel/AuthorHome.tsx +309 -0
  80. package/src/client/panel/BookAnalysisView.tsx +63 -0
  81. package/src/client/panel/CmdPalette.tsx +102 -0
  82. package/src/client/panel/CreateBookView.tsx +366 -348
  83. package/src/client/panel/DirectorView.tsx +200 -0
  84. package/src/client/panel/GlobalAssetLibraryView.tsx +168 -0
  85. package/src/client/panel/IdeaInspirationView.tsx +76 -0
  86. package/src/client/panel/ImportModal.tsx +303 -0
  87. package/src/client/panel/KnowledgeBaseView.tsx +125 -0
  88. package/src/client/panel/LiveFeedLog.tsx +67 -0
  89. package/src/client/panel/MarketRadarView.tsx +377 -0
  90. package/src/client/panel/ModelManager.tsx +205 -0
  91. package/src/client/panel/NovelPanel.tsx +5403 -4597
  92. package/src/client/panel/PageHeader.tsx +37 -0
  93. package/src/client/panel/ProgressConsole.tsx +63 -0
  94. package/src/client/panel/ReaderView.tsx +306 -306
  95. package/src/client/panel/ReasoningSection.tsx +31 -0
  96. package/src/client/panel/RunPanel.tsx +223 -204
  97. package/src/client/panel/SettingsView.tsx +305 -0
  98. package/src/client/panel/ShelfView.tsx +266 -236
  99. package/src/client/panel/SubPage.tsx +42 -0
  100. package/src/client/panel/WorldTab.tsx +202 -202
  101. package/src/client/panel/panel.module.css +5764 -2941
  102. package/src/client/panel/toast.ts +51 -0
  103. package/src/client/panel/views.tsx +401 -314
  104. package/src/client/run-status.ts +143 -0
  105. package/src/client/sidebar-entry.ts +1 -1
  106. package/src/client/text.ts +28 -0
  107. package/src/docx-core.ts +53 -0
  108. package/src/docx.ts +44 -83
  109. package/src/engine.ts +4231 -2787
  110. package/src/global-assets.ts +59 -0
  111. package/src/index.ts +311 -252
  112. package/src/llm-live.ts +68 -0
  113. package/src/market-radar-scan.ts +41 -0
  114. package/src/market-radar-sources.ts +173 -0
  115. package/src/migrate-output.ts +76 -0
  116. package/src/novel-context.ts +286 -0
  117. package/src/prompting.ts +26 -0
  118. package/src/protocol.ts +2072 -1182
  119. package/src/review-policy.ts +161 -0
  120. package/src/routes.ts +3405 -2151
  121. package/src/run.ts +342 -330
  122. package/src/stage-contract.ts +189 -0
  123. package/lib/client.js.map +0 -1
  124. package/lib/index.js.map +0 -1
  125. 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,101 @@
1
+ /**
2
+ * 浏览器端「AI 创作实况」钩子:连接 /llm-live/stream (SSE),按 LLM 调用分组会话。
3
+ * 轻量实现,不持久化缓存(对齐上游 useLlmLiveFeed 的核心行为)。
4
+ */
5
+
6
+ import { useEffect, useMemo, useState } from 'react'
7
+ import { NOVEL_API } from '../protocol.ts'
8
+
9
+ export interface LlmLiveFrame {
10
+ type: 'session_started' | 'output_delta' | 'reasoning_delta' | 'phase_changed' | 'session_completed'
11
+ sessionId: string
12
+ label?: string
13
+ model?: string
14
+ at: string
15
+ content?: string
16
+ totalChars?: number
17
+ totalReasoningChars?: number
18
+ phase?: 'requesting' | 'streaming' | 'completed' | 'failed'
19
+ phaseMessage?: string
20
+ preview?: string
21
+ error?: string
22
+ }
23
+
24
+ export interface LlmLiveSession {
25
+ sessionId: string
26
+ label: string
27
+ model?: string
28
+ phase: 'requesting' | 'streaming' | 'completed' | 'failed'
29
+ phaseMessage: string
30
+ preview: string
31
+ totalChars: number
32
+ startedAt: string
33
+ updatedAt: string
34
+ }
35
+
36
+ const MAX_PREVIEW_CHARS = 20_000
37
+
38
+ function applyFrame(current: Record<string, LlmLiveSession>, frame: LlmLiveFrame): Record<string, LlmLiveSession> {
39
+ const next = { ...current }
40
+ const id = frame.sessionId
41
+ let session = next[id]
42
+ if (frame.type === 'session_started') {
43
+ session = {
44
+ sessionId: id,
45
+ label: frame.label ?? 'LLM 调用',
46
+ model: frame.model,
47
+ phase: 'requesting',
48
+ phaseMessage: '正在连接模型',
49
+ preview: '',
50
+ totalChars: 0,
51
+ startedAt: frame.at,
52
+ updatedAt: frame.at,
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 = { ...session, phase: frame.phase ?? 'completed', phaseMessage: frame.phase === 'failed' ? (frame.error ?? '调用失败') : '模型结果已准备完成', totalChars: frame.totalChars ?? session.totalChars, preview: session.preview || (frame.preview ?? ''), updatedAt: frame.at }
70
+ next[id] = session
71
+ }
72
+ return next
73
+ }
74
+
75
+ export function useLlmLiveFeed(enabled = true): { connected: boolean; sessions: LlmLiveSession[] } {
76
+ const [sessions, setSessions] = useState<Record<string, LlmLiveSession>>({})
77
+ const [connected, setConnected] = useState(false)
78
+ useEffect(() => {
79
+ if (!enabled) {
80
+ setSessions({})
81
+ setConnected(false)
82
+ return
83
+ }
84
+ const es = new EventSource(NOVEL_API.llmLive)
85
+ es.onopen = () => setConnected(true)
86
+ es.onmessage = (ev) => {
87
+ try {
88
+ const frame = JSON.parse(ev.data as string) as LlmLiveFrame
89
+ setSessions(prev => applyFrame(prev, frame))
90
+ } catch { /* ignore malformed frame */ }
91
+ }
92
+ // EventSource 断线会自动重连;这里只在断线瞬间标记未连接,重连成功后会再次 onopen。
93
+ es.onerror = () => setConnected(false)
94
+ return () => {
95
+ es.close()
96
+ setConnected(false)
97
+ }
98
+ }, [enabled])
99
+ const list = useMemo(() => Object.values(sessions).sort((a, b) => a.startedAt.localeCompare(b.startedAt)), [sessions])
100
+ return { connected, sessions: list }
101
+ }