@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
|
@@ -249,10 +249,11 @@ export function ImageGenSettingsCard(props: ImageGenSettingsCardProps) {
|
|
|
249
249
|
<button type="button" className={css.channelMain} disabled={disabled} onClick={() => { setEditingId(channel.id) }}>
|
|
250
250
|
<span className={css.channelName}>{isDefault ? `★ ${channel.name || t('channels.untitled')}` : (channel.name || t('channels.untitled'))}</span>
|
|
251
251
|
<span className={css.channelMeta}>
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
252
|
+
<span className={css.channelBadge} data-warn={!keyHeld || channel.models.length === 0 ? '' : undefined}>
|
|
253
|
+
{keyHeld ? t('channels.keySet') : t('channels.keyMissing')}
|
|
254
|
+
{' · '}
|
|
255
|
+
{channel.models.length > 0 ? t('channels.modelCount', { n: channel.models.length }) : t('channels.noModels')}
|
|
256
|
+
</span>
|
|
256
257
|
</span>
|
|
257
258
|
</button>
|
|
258
259
|
<button type="button" className={css.channelAction} onClick={() => { setEditingId(channel.id) }}>{t('channels.edit')}</button>
|
|
@@ -517,14 +518,6 @@ function replaceChannel(channels: ChannelDraft[], id: string, patch: Partial<Cha
|
|
|
517
518
|
form.setChannels(channels.map(channel => channel.id === id ? { ...channel, ...patch } : channel))
|
|
518
519
|
}
|
|
519
520
|
|
|
520
|
-
function hostOf(apiUrl: string): string {
|
|
521
|
-
try {
|
|
522
|
-
return new URL(apiUrl).hostname
|
|
523
|
-
} catch {
|
|
524
|
-
return apiUrl
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
521
|
function clientId(): string {
|
|
529
522
|
const random = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? crypto.randomUUID() : undefined
|
|
530
523
|
return random ?? `ch-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
|
|
@@ -561,8 +554,7 @@ function PresetPicker(props: {
|
|
|
561
554
|
{props.presets.map(preset => (
|
|
562
555
|
<button key={preset.id} type="button" className={css.presetRow} disabled={props.disabled} onClick={() => { props.onPick(preset) }}>
|
|
563
556
|
<span className={css.presetName}>{preset.name}</span>
|
|
564
|
-
<span className={css.presetMeta}>{preset.
|
|
565
|
-
<span className={css.presetHint}>{preset.hint}</span>
|
|
557
|
+
<span className={css.presetMeta}>{preset.models.map(model => model.alias).join(' · ')}</span>
|
|
566
558
|
</button>
|
|
567
559
|
))}
|
|
568
560
|
<button type="button" className={css.presetRow} data-custom disabled={props.disabled} onClick={props.onCustom}>
|
|
@@ -656,18 +648,6 @@ function ChannelEditor(props: {
|
|
|
656
648
|
setCopyFrom('')
|
|
657
649
|
}
|
|
658
650
|
|
|
659
|
-
const onlyKnown = (): void => {
|
|
660
|
-
const known = (candidates ?? []).filter(id => describeModel(id).known)
|
|
661
|
-
const merged = [...channel.models]
|
|
662
|
-
for (const id of known) {
|
|
663
|
-
const alias = id
|
|
664
|
-
if (!merged.some(model => model.alias === alias)) merged.push({ alias, id })
|
|
665
|
-
}
|
|
666
|
-
props.onSetModels(merged)
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
const knownCount = (candidates ?? []).filter(id => describeModel(id).known).length
|
|
670
|
-
|
|
671
651
|
return (
|
|
672
652
|
<div className={css.editorBackdrop} role="dialog" aria-modal="true" aria-label={`${t('channels.editorTitle')} · ${channel.name || t('channels.untitled')}`} onClick={props.onClose}>
|
|
673
653
|
<div className={css.editorPanel} onClick={event => { event.stopPropagation() }}>
|
|
@@ -776,7 +756,6 @@ function ChannelEditor(props: {
|
|
|
776
756
|
<div className={css.modelCandidateList}>
|
|
777
757
|
<span className={css.modelCandidateLabel}>
|
|
778
758
|
{t('channels.candidatesTitle')}
|
|
779
|
-
<button type="button" className={css.inlineDisclosure} disabled={!props.writable || knownCount === 0} onClick={onlyKnown}>{t('channels.candidatesQuick')}</button>
|
|
780
759
|
</span>
|
|
781
760
|
{candidates.map(candidate => {
|
|
782
761
|
const selected = channel.models.some(model => model.alias === candidate)
|
package/src/client/api.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* data access path the panel uses — plain fetch, same origin.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { GALLERY_API, GENERATE_API, HISTORY_API, PROMPT_ENHANCE_API, TASK_API, TEMPLATES_API, UPDATE_API, type GenerateRequest, type GenerateResult, type GenerationTask, type HistoryEntry, type HistoryEntryInput, type TemplateListResult, type TemplateRefreshResult, type UpdateInfo } from '../protocol.ts'
|
|
6
|
+
import { CONVERSATION_IMAGE_API, GALLERY_API, GENERATE_API, HISTORY_API, PROMPT_ENHANCE_API, TASK_API, TEMPLATES_API, UPDATE_API, type GenerateRequest, type GenerateResult, type GenerationTask, type HistoryEntry, type HistoryEntryInput, type TemplateListResult, type TemplateRefreshResult, type UpdateInfo } from '../protocol.ts'
|
|
7
7
|
|
|
8
8
|
/** Error carrying the route's JSON error message. */
|
|
9
9
|
export class ImageGenApiError extends Error {
|
|
@@ -84,6 +84,16 @@ export class ImageGenApi {
|
|
|
84
84
|
return body.prompt
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/** Stage a generated image as a durable reference for `/edit_image`. */
|
|
88
|
+
async attachConversationImage(sessionId: string, dataUrl: string, name: string): Promise<void> {
|
|
89
|
+
const response = await fetch(CONVERSATION_IMAGE_API, {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: { 'content-type': 'application/json' },
|
|
92
|
+
body: JSON.stringify({ sessionId, dataUrl, name }),
|
|
93
|
+
})
|
|
94
|
+
await readEnvelope<{ ok: true }>(response)
|
|
95
|
+
}
|
|
96
|
+
|
|
87
97
|
async taskSubmit(request: GenerateRequest): Promise<GenerationTask> {
|
|
88
98
|
const response = await fetch(TASK_API.submit, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(request) })
|
|
89
99
|
return (await readEnvelope<{ ok: true; task: GenerationTask }>(response)).task
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
|
2
|
+
import type { ConversationController, IConversation } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
3
|
+
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
|
|
4
|
+
|
|
5
|
+
/** Document event used to bridge chat tool results into the image workspace. */
|
|
6
|
+
export const CHAT_IMAGE_EVENT = 'dsh-imagegen:chat-images'
|
|
7
|
+
|
|
8
|
+
export interface ChatImageEventDetail {
|
|
9
|
+
sessionId: SessionId
|
|
10
|
+
refs: readonly ImageAttachmentRef[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Composer-facing extension of the narrower public conversation interface. */
|
|
14
|
+
export type ConversationService = IConversation & Pick<ConversationController, 'createDraftImages' | 'releaseDraftImages'>
|
|
@@ -1,165 +1,176 @@
|
|
|
1
|
-
/** Inline renderer for image-generation tool-result attachments. */
|
|
2
|
-
|
|
3
|
-
import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
|
4
|
-
import type { ClientContext, SessionId, ToolCallBlock } from '@deepseek-ai/dsh-client-runtime/client'
|
|
5
|
-
import { useEffect, useMemo, useState } from 'react'
|
|
6
|
-
import { AGENT_IMAGE_API } from '../protocol.ts'
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
.
|
|
45
|
-
.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (status === '
|
|
65
|
-
if (status === '
|
|
66
|
-
return '
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
const [
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
urls.
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
const {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
1
|
+
/** Inline renderer for image-generation tool-result attachments. */
|
|
2
|
+
|
|
3
|
+
import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
|
|
4
|
+
import type { ClientContext, SessionId, ToolCallBlock } from '@deepseek-ai/dsh-client-runtime/client'
|
|
5
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
6
|
+
import { AGENT_IMAGE_API } from '../protocol.ts'
|
|
7
|
+
import { CHAT_IMAGE_EVENT } from './conversation-sync.ts'
|
|
8
|
+
import css from './image-toolview.module.css'
|
|
9
|
+
|
|
10
|
+
/** Owner props supplied by the host's keyed tool-call slot. */
|
|
11
|
+
export interface ImageToolViewOwnerProps {
|
|
12
|
+
callId: string
|
|
13
|
+
toolName: string
|
|
14
|
+
block: ToolCallBlock
|
|
15
|
+
cwd?: string
|
|
16
|
+
home?: string
|
|
17
|
+
openFile: (path: string) => void
|
|
18
|
+
inspect?: () => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface LoadedImage {
|
|
22
|
+
ref: ImageAttachmentRef
|
|
23
|
+
src: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ImageToolViewProps extends ImageToolViewOwnerProps {
|
|
27
|
+
sessionId: SessionId
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isSettled(block: ToolCallBlock): block is Extract<ToolCallBlock, { kind: 'tool-result' }> {
|
|
31
|
+
return 'kind' in block
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function imageRefsOf(block: ToolCallBlock): ImageAttachmentRef[] {
|
|
35
|
+
if (!isSettled(block)) return []
|
|
36
|
+
const resultContent = block.resultView?.card === 'generic' ? block.resultView.content ?? [] : []
|
|
37
|
+
return [...block.content, ...resultContent]
|
|
38
|
+
.flatMap(content => content.type === 'image' ? [content.attachment] : [])
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function textOf(block: ToolCallBlock): string {
|
|
42
|
+
if (!isSettled(block)) return ''
|
|
43
|
+
return block.content
|
|
44
|
+
.filter(content => content.type === 'text')
|
|
45
|
+
.map(content => content.text)
|
|
46
|
+
.join('\n')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resultInfo(block: ToolCallBlock): { status: string; message: string } {
|
|
50
|
+
if (!isSettled(block)) return { status: 'running', message: '正在生成图片…' }
|
|
51
|
+
const text = textOf(block)
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(text) as { status?: unknown; message?: unknown }
|
|
54
|
+
return {
|
|
55
|
+
status: typeof parsed.status === 'string' ? parsed.status : block.isError ? 'failed' : 'completed',
|
|
56
|
+
message: typeof parsed.message === 'string' ? parsed.message : '',
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
return { status: block.isError ? 'failed' : 'completed', message: text }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function statusLabel(status: string): string {
|
|
64
|
+
if (status === 'running' || status === 'queued') return '生成中'
|
|
65
|
+
if (status === 'failed') return '生成失败'
|
|
66
|
+
if (status === 'cancelled') return '已取消'
|
|
67
|
+
return '图片结果'
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function useAttachmentImages(
|
|
71
|
+
sessionId: SessionId,
|
|
72
|
+
refs: ImageAttachmentRef[],
|
|
73
|
+
load: (sessionId: SessionId, ref: ImageAttachmentRef) => Promise<string>,
|
|
74
|
+
): { images: LoadedImage[]; error: string | null } {
|
|
75
|
+
const key = useMemo(() => refs.map(ref => String(ref.attachmentId)).join('|'), [refs])
|
|
76
|
+
const [images, setImages] = useState<LoadedImage[]>([])
|
|
77
|
+
const [error, setError] = useState<string | null>(null)
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
let disposed = false
|
|
81
|
+
const urls: string[] = []
|
|
82
|
+
const revoke = (): void => {
|
|
83
|
+
for (const url of urls) URL.revokeObjectURL(url)
|
|
84
|
+
urls.length = 0
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setImages([])
|
|
88
|
+
setError(null)
|
|
89
|
+
if (refs.length === 0) return () => { /* no attachments to clean up */ }
|
|
90
|
+
|
|
91
|
+
void Promise.all(refs.map(async ref => {
|
|
92
|
+
const src = await load(sessionId, ref)
|
|
93
|
+
urls.push(src)
|
|
94
|
+
return { ref, src }
|
|
95
|
+
}))
|
|
96
|
+
.then(next => {
|
|
97
|
+
if (!disposed) setImages(next)
|
|
98
|
+
})
|
|
99
|
+
.catch(errorValue => {
|
|
100
|
+
revoke()
|
|
101
|
+
if (!disposed) setError(errorValue instanceof Error ? errorValue.message : String(errorValue))
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
return () => {
|
|
105
|
+
disposed = true
|
|
106
|
+
revoke()
|
|
107
|
+
}
|
|
108
|
+
}, [key, load, refs, sessionId])
|
|
109
|
+
|
|
110
|
+
return { images, error }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Register the inline image result view for all image-generation result tools. */
|
|
114
|
+
export function registerImageToolviews(ctx: ClientContext): void {
|
|
115
|
+
const load = async (_sessionId: SessionId, ref: ImageAttachmentRef): Promise<string> => {
|
|
116
|
+
// Tool-result images intentionally do not occur in model-visible session
|
|
117
|
+
// content, so session.readAttachment() rejects them. The plugin route
|
|
118
|
+
// reads the same durable attachment after validating the complete ref.
|
|
119
|
+
const query = new URLSearchParams({
|
|
120
|
+
attachment_id: String(ref.attachmentId),
|
|
121
|
+
media_type: ref.mediaType,
|
|
122
|
+
bytes: String(ref.bytes),
|
|
123
|
+
width: String(ref.width),
|
|
124
|
+
height: String(ref.height),
|
|
125
|
+
})
|
|
126
|
+
const response = await fetch(`${AGENT_IMAGE_API}?${query.toString()}`)
|
|
127
|
+
if (!response.ok) throw new Error(`无法读取图片附件(HTTP ${response.status})。`)
|
|
128
|
+
const blob = await response.blob()
|
|
129
|
+
return URL.createObjectURL(blob)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const ImageToolView = (props: ImageToolViewProps): React.JSX.Element => {
|
|
133
|
+
const refs = useMemo(() => imageRefsOf(props.block), [props.block])
|
|
134
|
+
const { status, message } = resultInfo(props.block)
|
|
135
|
+
const { images, error } = useAttachmentImages(props.sessionId, refs, load)
|
|
136
|
+
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (images.length === 0) return
|
|
139
|
+
document.dispatchEvent(new CustomEvent(CHAT_IMAGE_EVENT, {
|
|
140
|
+
detail: {
|
|
141
|
+
sessionId: props.sessionId,
|
|
142
|
+
refs: images.map(image => image.ref),
|
|
143
|
+
},
|
|
144
|
+
}))
|
|
145
|
+
}, [images, props.sessionId])
|
|
146
|
+
|
|
147
|
+
return <section className={css.root} data-state={status} data-tool={props.toolName}>
|
|
148
|
+
<header className={css.header}>
|
|
149
|
+
<span className={css.icon} aria-hidden="true">▧</span>
|
|
150
|
+
<strong>{props.toolName}</strong>
|
|
151
|
+
<span className={css.status}>{statusLabel(status)}</span>
|
|
152
|
+
</header>
|
|
153
|
+
{message !== '' && <p className={css.message}>{message}</p>}
|
|
154
|
+
{images.length > 0 && <div className={css.images}>
|
|
155
|
+
{images.map(image => <a
|
|
156
|
+
className={css.imageLink}
|
|
157
|
+
href={image.src}
|
|
158
|
+
key={String(image.ref.attachmentId)}
|
|
159
|
+
rel="noreferrer"
|
|
160
|
+
target="_blank"
|
|
161
|
+
title="打开原图"
|
|
162
|
+
>
|
|
163
|
+
<img className={css.image} src={image.src} alt={image.ref.name ?? '生成图片'} />
|
|
164
|
+
</a>)}
|
|
165
|
+
</div>}
|
|
166
|
+
{refs.length > 0 && images.length === 0 && error === null && <p className={css.loading}>正在加载图片…</p>}
|
|
167
|
+
{error !== null && <p className={css.error}>{error}</p>}
|
|
168
|
+
</section>
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
ctx.slots.inject('tool.call.toolview', function* () {
|
|
172
|
+
for (const key of ['generate_image', 'edit_image', 'get_image_generation_task']) {
|
|
173
|
+
yield ctx.slots.register({ name: 'tool.call.toolview', key }, ImageToolView)
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
}
|
package/src/client/index.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* the GUI down.
|
|
13
13
|
*/
|
|
14
14
|
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
|
15
|
+
import type { ISessions } from '@deepseek-ai/dsh-client-runtime/client'
|
|
15
16
|
import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'
|
|
16
17
|
// Type-only: pulls the locale plugin's Context merge (ctx.locale).
|
|
17
18
|
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
@@ -23,9 +24,10 @@ import { tt } from './helpers.ts'
|
|
|
23
24
|
import { en, zh, type ImageGenKey } from './locales.ts'
|
|
24
25
|
import { mountPanel } from './mount.tsx'
|
|
25
26
|
import { mountSidebarEntry } from './sidebar-entry.ts'
|
|
26
|
-
import { ImageGenSettingsCard, ImageGenSettingsCardController } from './SettingsCard.tsx'
|
|
27
|
-
import { bindImageGenScope, type ImageGenScope } from './settings-scope.ts'
|
|
28
|
-
import { registerImageToolviews, type ImageToolViewOwnerProps } from './image-toolview.tsx'
|
|
27
|
+
import { ImageGenSettingsCard, ImageGenSettingsCardController } from './SettingsCard.tsx'
|
|
28
|
+
import { bindImageGenScope, type ImageGenScope } from './settings-scope.ts'
|
|
29
|
+
import { registerImageToolviews, type ImageToolViewOwnerProps } from './image-toolview.tsx'
|
|
30
|
+
import type { ConversationService } from './conversation-sync.ts'
|
|
29
31
|
|
|
30
32
|
/** Locale namespace this plugin owns. */
|
|
31
33
|
const NS = 'dsh-imagegen'
|
|
@@ -36,7 +38,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
36
38
|
'dsh-imagegen': ImageGenKey
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
interface SlotMap {
|
|
41
|
+
interface SlotMap {
|
|
40
42
|
/**
|
|
41
43
|
* The official plugin-configuration slot the Settings → Plugins →
|
|
42
44
|
* Configurable tab declares and renders. This card registers there as its
|
|
@@ -45,11 +47,11 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
45
47
|
* same shape so this package can register without depending on the
|
|
46
48
|
* sibling UI package.
|
|
47
49
|
*/
|
|
48
|
-
'settings.plugin.item': { kind: 'keyed'; scope: 'root'; owner: ImageGenPluginItemOwnerProps }
|
|
49
|
-
/** Image-generation results render their durable image blocks inline. */
|
|
50
|
-
'tool.call.toolview': { kind: 'keyed'; scope: 'session'; owner: ImageToolViewOwnerProps }
|
|
51
|
-
}
|
|
52
|
-
}
|
|
50
|
+
'settings.plugin.item': { kind: 'keyed'; scope: 'root'; owner: ImageGenPluginItemOwnerProps }
|
|
51
|
+
/** Image-generation results render their durable image blocks inline. */
|
|
52
|
+
'tool.call.toolview': { kind: 'keyed'; scope: 'session'; owner: ImageToolViewOwnerProps }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
53
55
|
|
|
54
56
|
/** Owner share of a plugin card (the section supplies nothing). */
|
|
55
57
|
export interface ImageGenPluginItemOwnerProps {
|
|
@@ -58,15 +60,15 @@ export interface ImageGenPluginItemOwnerProps {
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/** Required services (fiber inject waiting — the runtime must be up first). */
|
|
61
|
-
export const inject = ['slots', 'locale', 'connection', 'sessions']
|
|
63
|
+
export const inject = ['slots', 'locale', 'connection', 'sessions', 'conversation']
|
|
62
64
|
|
|
63
65
|
/**
|
|
64
66
|
* Mount the studio, its sidebar entry, and the settings card.
|
|
65
67
|
* @param ctx - client root context (services: slots, locale, connection).
|
|
66
68
|
*/
|
|
67
|
-
export function apply(ctx: ClientContext): void {
|
|
68
|
-
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-imagegen: dictionaries')
|
|
69
|
-
registerImageToolviews(ctx)
|
|
69
|
+
export function apply(ctx: ClientContext): void {
|
|
70
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-imagegen: dictionaries')
|
|
71
|
+
registerImageToolviews(ctx)
|
|
70
72
|
|
|
71
73
|
const connection = ctx.get('connection') as ConnectionHandle | undefined
|
|
72
74
|
const loopback = connection?.isLoopback === true
|
|
@@ -104,10 +106,18 @@ export function apply(ctx: ClientContext): void {
|
|
|
104
106
|
if (uiDisposer !== undefined) return
|
|
105
107
|
const controller = new ImageGenController()
|
|
106
108
|
const api = new ImageGenApi()
|
|
109
|
+
const sessions = ctx.get('sessions') as ISessions | undefined
|
|
110
|
+
const conversation = ctx.get('conversation') as ConversationService | undefined
|
|
107
111
|
const disposers: Array<() => void> = []
|
|
108
112
|
try {
|
|
109
|
-
disposers.push(mountSidebarEntry(
|
|
110
|
-
|
|
113
|
+
disposers.push(mountSidebarEntry(
|
|
114
|
+
controller,
|
|
115
|
+
tt('entry.newSession'),
|
|
116
|
+
tt('entry.newSessionTooltip'),
|
|
117
|
+
tt('entry.image'),
|
|
118
|
+
tt('entry.tooltip'),
|
|
119
|
+
))
|
|
120
|
+
disposers.push(mountPanel(controller, api, scope, { sessions, conversation }))
|
|
111
121
|
} catch (error) {
|
|
112
122
|
// DOM failures degrade the studio, never the GUI.
|
|
113
123
|
console.warn('[dsh-imagegen] mount failed:', error)
|