@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.
- package/README.md +363 -196
- package/docs/images/ecommerce-mode.png +0 -0
- package/docs/images/image-generation-studio-three-column.png +0 -0
- package/docs/images/imagegen-overview.png +0 -0
- package/docs/images/multi-model-comparison.png +0 -0
- package/docs/videos/agent-chat-edit.gif +0 -0
- package/docs/videos/agent-chat-edit.mp4 +0 -0
- package/lib/client.js +2589 -884
- package/lib/client.js.map +1 -1
- package/lib/index.js +585 -240
- package/package.json +5 -2
- package/src/agent-image-tools.ts +131 -102
- package/src/client/ImageGenPanel.tsx +2679 -1594
- package/src/client/SettingsCard.tsx +6 -27
- package/src/client/api.ts +11 -1
- package/src/client/conversation-sync.ts +14 -0
- package/src/client/image-toolview.tsx +176 -165
- package/src/client/index.ts +25 -15
- package/src/client/locales.ts +746 -602
- package/src/client/mount.tsx +213 -124
- package/src/client/panel.module.css +2619 -1563
- package/src/client/sidebar-entry.ts +190 -144
- package/src/edit-image-command.ts +110 -0
- package/src/engine.ts +47 -5
- package/src/gallery-store.ts +20 -0
- package/src/generation-runtime.ts +11 -2
- package/src/history-store.ts +26 -0
- package/src/image-models.ts +1 -1
- package/src/index.ts +31 -12
- package/src/model-catalog.ts +19 -2
- package/src/presets.ts +11 -3
- package/src/prompt-enhancer.ts +63 -5
- package/src/protocol.ts +59 -5
- package/src/routes.ts +62 -4
- package/src/task-queue.ts +42 -32
- package/docs/images/agent-chat-edit.png +0 -0
- package/docs/images/agent-chat-generate.png +0 -0
- package/docs/images/agent-chat-poster-workflow.png +0 -0
package/src/client/mount.tsx
CHANGED
|
@@ -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 {
|
|
19
|
-
import type
|
|
20
|
-
import type {
|
|
21
|
-
import {
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
+
}
|