@dickpy/dsh-imagegen 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +203 -182
  3. package/cordis.patch.yml +8 -8
  4. package/docs/images/multi-model-comparison.png +0 -0
  5. package/lib/client.js +1103 -837
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +265 -135
  8. package/package.json +70 -68
  9. package/src/agent-image-tools.ts +418 -418
  10. package/src/client/ImageGenPanel.tsx +1699 -1508
  11. package/src/client/SettingsCard.tsx +936 -957
  12. package/src/client/TemplateLibrary.tsx +336 -336
  13. package/src/client/api.ts +193 -193
  14. package/src/client/channels-form.ts +263 -263
  15. package/src/client/controller.ts +46 -46
  16. package/src/client/conversation-sync.ts +14 -0
  17. package/src/client/css-modules.d.ts +5 -5
  18. package/src/client/helpers.ts +33 -33
  19. package/src/client/image-toolview.module.css +73 -73
  20. package/src/client/image-toolview.tsx +169 -158
  21. package/src/client/index.ts +32 -22
  22. package/src/client/locales.ts +610 -594
  23. package/src/client/mount.tsx +185 -96
  24. package/src/client/panel.module.css +1713 -1445
  25. package/src/client/settings-card.module.css +1023 -1023
  26. package/src/client/settings-form.ts +336 -336
  27. package/src/client/settings-scope.ts +298 -298
  28. package/src/client/sidebar-entry.ts +148 -102
  29. package/src/client/templates.module.css +453 -453
  30. package/src/engine.ts +520 -478
  31. package/src/gallery-store.ts +286 -286
  32. package/src/generation-runtime.ts +79 -75
  33. package/src/history-store.ts +250 -244
  34. package/src/image-format.ts +11 -11
  35. package/src/image-models.ts +19 -19
  36. package/src/index.ts +318 -318
  37. package/src/model-catalog.ts +115 -98
  38. package/src/presets.ts +71 -63
  39. package/src/prompt-enhancer.ts +137 -79
  40. package/src/protocol.ts +338 -326
  41. package/src/routes.ts +916 -906
  42. package/src/task-queue.ts +113 -103
  43. package/src/templates/cases.json +10196 -10196
  44. package/src/templates-store.ts +278 -278
  45. package/src/updater.ts +117 -117
@@ -1,124 +1,213 @@
1
- /**
2
- * Panel view mounting.
3
- *
4
- * The `conversation` slot is single-occupant (ui-conversation) and external
5
- * plugins cannot declare slots, so the panel takes over the center column at
6
- * the DOM level: a container is appended inside the conversation grid item
7
- * (an extra trailing child React never manages), and a stylesheet rule hides
8
- * the conversation content while the panel is active. Toggling is a data
9
- * attribute on <html> — no React involvement, so the conversation subtree
10
- * underneath stays mounted and stateful.
11
- *
12
- * Shell compatibility: the center column is `[data-pane="conversation"]` on
13
- * legacy shells and `[class*="centerCol"]` on the rc.6+ AppFrame layout (the
14
- * same dual selector the dsh-ssh / task-board panels use); both are queried
15
- * and both get the `position: relative` base in panel.module.css.
16
- */
17
-
1
+ /**
2
+ * Panel view mounting.
3
+ *
4
+ * The `conversation` slot is single-occupant (ui-conversation) and external
5
+ * plugins cannot declare slots, so the panel takes over the center column at
6
+ * the DOM level: a container is appended inside the conversation grid item
7
+ * (an extra trailing child React never manages), and a stylesheet rule hides
8
+ * the conversation content while the panel is active. Toggling is a data
9
+ * attribute on <html> — no React involvement, so the conversation subtree
10
+ * underneath stays mounted and stateful.
11
+ *
12
+ * Shell compatibility: the center column is `[data-pane="conversation"]` on
13
+ * legacy shells and `[class*="centerCol"]` on the rc.6+ AppFrame layout (the
14
+ * same dual selector the dsh-ssh / task-board panels use); both are queried
15
+ * and both get the `position: relative` base in panel.module.css.
16
+ */
17
+
18
+ import type { ISessions } from '@deepseek-ai/dsh-client-runtime/client'
18
19
  import { createRoot, type Root } from 'react-dom/client'
19
- import type { ImageGenApi } from './api.ts'
20
- import type { ImageGenController } from './controller.ts'
21
- import { ImageGenPanel } from './ImageGenPanel.tsx'
20
+ import type { ImageGenApi } from './api.ts'
21
+ import type { ImageGenController } from './controller.ts'
22
+ import { ImageGenPanel } from './ImageGenPanel.tsx'
22
23
  import type { ImageGenScope } from './settings-scope.ts'
23
- import css from './panel.module.css'
24
-
25
- /** The injected panel container (kept in the DOM, hidden when inactive). */
26
- export const PANEL_VIEW_SELECTOR = '[data-dsh-imagegen-view]'
27
-
24
+ import type { ConversationService } from './conversation-sync.ts'
25
+ import css from './panel.module.css'
26
+
27
+ /** The injected panel container (kept in the DOM, hidden when inactive). */
28
+ export const PANEL_VIEW_SELECTOR = '[data-dsh-imagegen-view]'
29
+
28
30
  const CONVERSATION_COLUMN_SELECTOR = '[data-pane="conversation"], [class*="centerCol"]'
29
31
  const ACTIVE_ATTR = 'data-dsh-imagegen-active'
30
- /** Sibling panels' activation attributes, removed when this panel opens. */
31
- const OTHER_ACTIVE_ATTRS = ['data-dsh-taskboard-active', 'data-dsh-ssh-active']
32
- /** Cross-plugin activation event; detail is the activating panel name. */
33
- const ACTIVATE_EVENT = 'dsh-panel-activate'
34
- const PANEL_NAME = 'imagegen'
35
-
36
- /** Find the center column, or undefined while the frame is not mounted. */
32
+ const RESIZER_SELECTOR = '[data-dsh-imagegen-chat-resizer]'
33
+ const CHAT_WIDTH_STORAGE_KEY = 'dsh-imagegen:chat-width'
34
+ const CHAT_MIN_WIDTH = 320
35
+ /** Sibling panels' activation attributes, removed when this panel opens. */
36
+ const OTHER_ACTIVE_ATTRS = ['data-dsh-taskboard-active', 'data-dsh-ssh-active']
37
+ /** Cross-plugin activation event; detail is the activating panel name. */
38
+ const ACTIVATE_EVENT = 'dsh-panel-activate'
39
+ const PANEL_NAME = 'imagegen'
40
+
41
+ /** Find the center column, or undefined while the frame is not mounted. */
37
42
  function conversationColumn(): HTMLElement | undefined {
38
43
  return document.querySelector<HTMLElement>(CONVERSATION_COLUMN_SELECTOR) ?? undefined
39
44
  }
40
45
 
41
- /**
42
- * Mount the panel React tree into the center column and bind its visibility
43
- * to the controller's panelOpen state.
44
- * @param controller - the panel controller driving the view.
45
- * @param api - the image-generation API client the panel operates through.
46
- * @param scope - the settings scope (config status banner).
47
- * @returns disposer unmounting the tree and restoring the column.
48
- */
46
+ function readChatWidth(): number | undefined {
47
+ try {
48
+ const raw = window.localStorage.getItem(CHAT_WIDTH_STORAGE_KEY)
49
+ if (raw === null) return undefined
50
+ const value = Number(raw)
51
+ return Number.isFinite(value) && value >= CHAT_MIN_WIDTH ? value : undefined
52
+ } catch {
53
+ return undefined
54
+ }
55
+ }
56
+
57
+ function writeChatWidth(value: number): void {
58
+ try {
59
+ window.localStorage.setItem(CHAT_WIDTH_STORAGE_KEY, String(Math.round(value)))
60
+ } catch {
61
+ // Private browsing and embedded shells may disable localStorage.
62
+ }
63
+ }
64
+
65
+ function applyChatWidth(column: HTMLElement, clientX: number): void {
66
+ const bounds = column.getBoundingClientRect()
67
+ const max = Math.max(CHAT_MIN_WIDTH, bounds.width * 0.7)
68
+ const width = Math.min(max, Math.max(CHAT_MIN_WIDTH, bounds.right - clientX))
69
+ column.style.setProperty('--dsh-imagegen-chat-width', `${Math.round(width)}px`)
70
+ writeChatWidth(width)
71
+ }
72
+
73
+ /** Create the visible handle that separates the image workspace and chat. */
74
+ function createChatResizer(column: HTMLElement): HTMLDivElement {
75
+ const resizer = document.createElement('div')
76
+ resizer.dataset.dshImagegenChatResizer = ''
77
+ resizer.className = css.chatResizer
78
+ resizer.setAttribute('role', 'separator')
79
+ resizer.setAttribute('aria-orientation', 'vertical')
80
+ resizer.setAttribute('aria-label', '调整对话区域宽度')
81
+ resizer.tabIndex = 0
82
+
83
+ const saved = readChatWidth()
84
+ if (saved !== undefined) column.style.setProperty('--dsh-imagegen-chat-width', `${saved}px`)
85
+
86
+ const onPointerDown = (event: PointerEvent): void => {
87
+ if (event.button !== 0) return
88
+ event.preventDefault()
89
+ resizer.setPointerCapture?.(event.pointerId)
90
+ const onMove = (move: PointerEvent): void => { applyChatWidth(column, move.clientX) }
91
+ const onUp = (): void => {
92
+ window.removeEventListener('pointermove', onMove)
93
+ window.removeEventListener('pointerup', onUp)
94
+ window.removeEventListener('pointercancel', onUp)
95
+ resizer.releasePointerCapture?.(event.pointerId)
96
+ document.documentElement.style.removeProperty('cursor')
97
+ document.documentElement.style.removeProperty('user-select')
98
+ }
99
+ window.addEventListener('pointermove', onMove)
100
+ window.addEventListener('pointerup', onUp)
101
+ window.addEventListener('pointercancel', onUp)
102
+ document.documentElement.style.setProperty('cursor', 'col-resize')
103
+ document.documentElement.style.setProperty('user-select', 'none')
104
+ }
105
+ const onKeyDown = (event: KeyboardEvent): void => {
106
+ if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
107
+ event.preventDefault()
108
+ const bounds = column.getBoundingClientRect()
109
+ const current = column.style.getPropertyValue('--dsh-imagegen-chat-width')
110
+ const currentWidth = Number.parseFloat(current) || bounds.width * 0.36
111
+ const delta = event.key === 'ArrowLeft' ? -24 : 24
112
+ applyChatWidth(column, bounds.right - currentWidth - delta)
113
+ }
114
+ resizer.addEventListener('pointerdown', onPointerDown)
115
+ resizer.addEventListener('keydown', onKeyDown)
116
+ return resizer
117
+ }
118
+
119
+ /**
120
+ * Mount the panel React tree into the center column and bind its visibility
121
+ * to the controller's panelOpen state.
122
+ * @param controller - the panel controller driving the view.
123
+ * @param api - the image-generation API client the panel operates through.
124
+ * @param scope - the settings scope (config status banner).
125
+ * @returns disposer unmounting the tree and restoring the column.
126
+ */
49
127
  export function mountPanel(
50
128
  controller: ImageGenController,
51
129
  api: ImageGenApi,
52
130
  scope: ImageGenScope,
131
+ services: {
132
+ sessions?: ISessions
133
+ conversation?: ConversationService
134
+ } = {},
53
135
  ): () => void {
54
136
  let root: Root | undefined
55
137
  let container: HTMLDivElement | undefined
56
-
57
- const ensure = (): void => {
58
- if (container !== undefined) {
59
- if (container.isConnected) return
60
- // The conversation pane was replaced; drop the stale tree and remount.
138
+ let resizer: HTMLDivElement | undefined
139
+
140
+ const ensure = (): void => {
141
+ if (container !== undefined) {
142
+ if (container.isConnected) return
143
+ // The conversation pane was replaced; drop the stale tree and remount.
61
144
  root?.unmount()
62
145
  root = undefined
63
146
  container.remove()
64
147
  container = undefined
65
- }
66
- const column = conversationColumn()
67
- if (column === undefined) return
68
- container = document.createElement('div')
69
- container.dataset.dshImagegenView = ''
148
+ resizer?.remove()
149
+ resizer = undefined
150
+ }
151
+ const column = conversationColumn()
152
+ if (column === undefined) return
153
+ container = document.createElement('div')
154
+ container.dataset.dshImagegenView = ''
70
155
  container.className = css.view
71
156
  column.appendChild(container)
72
157
  root = createRoot(container)
73
- root.render(<ImageGenPanel api={api} scope={scope} />)
74
- }
75
-
76
- // The frame mounts after boot settlement; watch for the column's arrival.
77
- const waitObserver = new MutationObserver(() => { ensure() })
78
- waitObserver.observe(document.body, { childList: true, subtree: true })
79
-
80
- const applyActive = (): void => {
81
- if (controller.getSnapshot().panelOpen) {
82
- // Single-occupant center column: opening this panel must evict sibling
83
- // panels (task board / ssh), both their html attributes and their
84
- // controller states, otherwise the visibility rules fight.
85
- for (const attr of OTHER_ACTIVE_ATTRS) document.documentElement.removeAttribute(attr)
86
- document.documentElement.setAttribute(ACTIVE_ATTR, '')
87
- document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }))
88
- } else {
89
- document.documentElement.removeAttribute(ACTIVE_ATTR)
90
- }
91
- }
92
- const onOtherActivate = (event: Event): void => {
93
- const detail = (event as CustomEvent).detail
94
- if ((detail === 'ssh' || detail === 'taskboard') && controller.getSnapshot().panelOpen) {
95
- controller.close()
96
- }
97
- }
98
- // Jump out on sidebar context clicks: clicking a session/workspace row
99
- // hands the center column back to the conversation. Capture phase.
100
- const SIDEBAR_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [class*="newSession"]'
101
- const onClickSidebarRow = (event: MouseEvent): void => {
102
- if (!controller.getSnapshot().panelOpen) return
103
- const target = event.target as HTMLElement | null
104
- if (target === null) return
105
- if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.close()
106
- }
107
- document.addEventListener('click', onClickSidebarRow, true)
108
- document.addEventListener(ACTIVATE_EVENT, onOtherActivate)
109
- const unsubscribe = controller.subscribe(applyActive)
110
- applyActive()
111
- ensure()
112
-
113
- return () => {
114
- document.removeEventListener('click', onClickSidebarRow, true)
115
- document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)
116
- waitObserver.disconnect()
117
- unsubscribe()
118
- document.documentElement.removeAttribute(ACTIVE_ATTR)
158
+ root.render(<ImageGenPanel api={api} scope={scope} {...services} />)
159
+ resizer = column.querySelector<HTMLDivElement>(RESIZER_SELECTOR) ?? createChatResizer(column)
160
+ if (resizer.parentElement !== column) column.appendChild(resizer)
161
+ }
162
+
163
+ // The frame mounts after boot settlement; watch for the column's arrival.
164
+ const waitObserver = new MutationObserver(() => { ensure() })
165
+ waitObserver.observe(document.body, { childList: true, subtree: true })
166
+
167
+ const applyActive = (): void => {
168
+ if (controller.getSnapshot().panelOpen) {
169
+ // Single-occupant center column: opening this panel must evict sibling
170
+ // panels (task board / ssh), both their html attributes and their
171
+ // controller states, otherwise the visibility rules fight.
172
+ for (const attr of OTHER_ACTIVE_ATTRS) document.documentElement.removeAttribute(attr)
173
+ document.documentElement.setAttribute(ACTIVE_ATTR, '')
174
+ document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }))
175
+ } else {
176
+ document.documentElement.removeAttribute(ACTIVE_ATTR)
177
+ }
178
+ }
179
+ const onOtherActivate = (event: Event): void => {
180
+ const detail = (event as CustomEvent).detail
181
+ if ((detail === 'ssh' || detail === 'taskboard') && controller.getSnapshot().panelOpen) {
182
+ controller.close()
183
+ }
184
+ }
185
+ // Jump out on sidebar context clicks: clicking a session/workspace row
186
+ // hands the center column back to the conversation. Capture phase.
187
+ const SIDEBAR_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [class*="newSession"]'
188
+ const onClickSidebarRow = (event: MouseEvent): void => {
189
+ if (!controller.getSnapshot().panelOpen) return
190
+ const target = event.target as HTMLElement | null
191
+ if (target === null) return
192
+ if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.close()
193
+ }
194
+ document.addEventListener('click', onClickSidebarRow, true)
195
+ document.addEventListener(ACTIVATE_EVENT, onOtherActivate)
196
+ const unsubscribe = controller.subscribe(applyActive)
197
+ applyActive()
198
+ ensure()
199
+
200
+ return () => {
201
+ document.removeEventListener('click', onClickSidebarRow, true)
202
+ document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)
203
+ waitObserver.disconnect()
204
+ unsubscribe()
205
+ document.documentElement.removeAttribute(ACTIVE_ATTR)
119
206
  root?.unmount()
120
207
  root = undefined
121
208
  container?.remove()
122
209
  container = undefined
210
+ resizer?.remove()
211
+ resizer = undefined
123
212
  }
124
213
  }