@dickpy/dsh-imagegen 1.3.0 → 1.5.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 (38) hide show
  1. package/README.md +363 -196
  2. package/docs/images/ecommerce-mode.png +0 -0
  3. package/docs/images/image-generation-studio-three-column.png +0 -0
  4. package/docs/images/imagegen-overview.png +0 -0
  5. package/docs/images/multi-model-comparison.png +0 -0
  6. package/docs/videos/agent-chat-edit.gif +0 -0
  7. package/docs/videos/agent-chat-edit.mp4 +0 -0
  8. package/lib/client.js +2589 -884
  9. package/lib/client.js.map +1 -1
  10. package/lib/index.js +585 -240
  11. package/package.json +5 -2
  12. package/src/agent-image-tools.ts +131 -102
  13. package/src/client/ImageGenPanel.tsx +2679 -1594
  14. package/src/client/SettingsCard.tsx +6 -27
  15. package/src/client/api.ts +11 -1
  16. package/src/client/conversation-sync.ts +14 -0
  17. package/src/client/image-toolview.tsx +176 -165
  18. package/src/client/index.ts +25 -15
  19. package/src/client/locales.ts +746 -602
  20. package/src/client/mount.tsx +213 -124
  21. package/src/client/panel.module.css +2619 -1563
  22. package/src/client/sidebar-entry.ts +190 -144
  23. package/src/edit-image-command.ts +110 -0
  24. package/src/engine.ts +47 -5
  25. package/src/gallery-store.ts +20 -0
  26. package/src/generation-runtime.ts +11 -2
  27. package/src/history-store.ts +26 -0
  28. package/src/image-models.ts +1 -1
  29. package/src/index.ts +31 -12
  30. package/src/model-catalog.ts +19 -2
  31. package/src/presets.ts +11 -3
  32. package/src/prompt-enhancer.ts +63 -5
  33. package/src/protocol.ts +59 -5
  34. package/src/routes.ts +62 -4
  35. package/src/task-queue.ts +42 -32
  36. package/docs/images/agent-chat-edit.png +0 -0
  37. package/docs/images/agent-chat-generate.png +0 -0
  38. package/docs/images/agent-chat-poster-workflow.png +0 -0
@@ -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
-
18
- 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'
22
- 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
-
28
- const CONVERSATION_COLUMN_SELECTOR = '[data-pane="conversation"], [class*="centerCol"]'
29
- 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. */
37
- function conversationColumn(): HTMLElement | undefined {
38
- return document.querySelector<HTMLElement>(CONVERSATION_COLUMN_SELECTOR) ?? undefined
39
- }
40
-
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
- */
49
- export function mountPanel(
50
- controller: ImageGenController,
51
- api: ImageGenApi,
52
- scope: ImageGenScope,
53
- ): () => void {
54
- let root: Root | undefined
55
- 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.
61
- root?.unmount()
62
- root = undefined
63
- container.remove()
64
- container = undefined
65
- }
66
- const column = conversationColumn()
67
- if (column === undefined) return
68
- container = document.createElement('div')
69
- container.dataset.dshImagegenView = ''
70
- container.className = css.view
71
- column.appendChild(container)
72
- 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)
119
- root?.unmount()
120
- root = undefined
121
- container?.remove()
122
- container = undefined
123
- }
124
- }
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'
19
+ import { createRoot, type Root } from 'react-dom/client'
20
+ import type { ImageGenApi } from './api.ts'
21
+ import type { ImageGenController } from './controller.ts'
22
+ import { ImageGenPanel } from './ImageGenPanel.tsx'
23
+ import type { ImageGenScope } from './settings-scope.ts'
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
+
30
+ const CONVERSATION_COLUMN_SELECTOR = '[data-pane="conversation"], [class*="centerCol"]'
31
+ const ACTIVE_ATTR = 'data-dsh-imagegen-active'
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. */
42
+ function conversationColumn(): HTMLElement | undefined {
43
+ return document.querySelector<HTMLElement>(CONVERSATION_COLUMN_SELECTOR) ?? undefined
44
+ }
45
+
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
+ */
127
+ export function mountPanel(
128
+ controller: ImageGenController,
129
+ api: ImageGenApi,
130
+ scope: ImageGenScope,
131
+ services: {
132
+ sessions?: ISessions
133
+ conversation?: ConversationService
134
+ } = {},
135
+ ): () => void {
136
+ let root: Root | undefined
137
+ let container: HTMLDivElement | undefined
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.
144
+ root?.unmount()
145
+ root = undefined
146
+ container.remove()
147
+ container = undefined
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 = ''
155
+ container.className = css.view
156
+ column.appendChild(container)
157
+ root = createRoot(container)
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)
206
+ root?.unmount()
207
+ root = undefined
208
+ container?.remove()
209
+ container = undefined
210
+ resizer?.remove()
211
+ resizer = undefined
212
+ }
213
+ }