@dickpy/dsh-imagegen 1.4.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 (53) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +270 -124
  3. package/cordis.patch.yml +8 -8
  4. package/docs/images/ecommerce-mode.png +0 -0
  5. package/docs/images/image-generation-studio-three-column.png +0 -0
  6. package/docs/images/imagegen-overview.png +0 -0
  7. package/docs/videos/agent-chat-edit.gif +0 -0
  8. package/docs/videos/agent-chat-edit.mp4 +0 -0
  9. package/lib/client.js +1859 -420
  10. package/lib/client.js.map +1 -1
  11. package/lib/index.js +327 -112
  12. package/package.json +69 -68
  13. package/src/agent-image-tools.ts +447 -418
  14. package/src/client/ImageGenPanel.tsx +1242 -348
  15. package/src/client/SettingsCard.tsx +936 -936
  16. package/src/client/TemplateLibrary.tsx +336 -336
  17. package/src/client/api.ts +203 -193
  18. package/src/client/channels-form.ts +263 -263
  19. package/src/client/controller.ts +46 -46
  20. package/src/client/conversation-sync.ts +14 -14
  21. package/src/client/css-modules.d.ts +5 -5
  22. package/src/client/helpers.ts +33 -33
  23. package/src/client/image-toolview.module.css +73 -73
  24. package/src/client/image-toolview.tsx +18 -18
  25. package/src/client/index.ts +22 -22
  26. package/src/client/locales.ts +156 -28
  27. package/src/client/mount.tsx +117 -117
  28. package/src/client/panel.module.css +1243 -455
  29. package/src/client/settings-card.module.css +1023 -1023
  30. package/src/client/settings-form.ts +336 -336
  31. package/src/client/settings-scope.ts +298 -298
  32. package/src/client/sidebar-entry.ts +190 -190
  33. package/src/client/templates.module.css +453 -453
  34. package/src/edit-image-command.ts +110 -0
  35. package/src/engine.ts +520 -520
  36. package/src/gallery-store.ts +306 -286
  37. package/src/generation-runtime.ts +84 -79
  38. package/src/history-store.ts +270 -250
  39. package/src/image-format.ts +11 -11
  40. package/src/image-models.ts +19 -19
  41. package/src/index.ts +337 -318
  42. package/src/model-catalog.ts +115 -115
  43. package/src/presets.ts +71 -71
  44. package/src/prompt-enhancer.ts +137 -137
  45. package/src/protocol.ts +380 -338
  46. package/src/routes.ts +964 -916
  47. package/src/task-queue.ts +113 -113
  48. package/src/templates/cases.json +10196 -10196
  49. package/src/templates-store.ts +278 -278
  50. package/src/updater.ts +117 -117
  51. package/docs/images/agent-chat-edit.png +0 -0
  52. package/docs/images/agent-chat-generate.png +0 -0
  53. package/docs/images/agent-chat-poster-workflow.png +0 -0
@@ -1,33 +1,33 @@
1
- /**
2
- * Shared panel helpers: the active-dictionary pick (document-language based,
3
- * dsh-ssh precedent) bound to the dsh-imagegen interpolator, plus a small
4
- * error-message extractor. All copy stays in the locale dictionaries.
5
- */
6
-
7
- import { en, zh, type ImageGenKey } from './locales.ts'
8
-
9
- /** Template values accepted by the interpolator. */
10
- export type TranslateValues = Record<string, string | number>
11
-
12
- /** Active dictionary, picked by the document language at call time. */
13
- export function dictionary(): Record<string, string> {
14
- const lang = typeof document !== 'undefined' ? document.documentElement.lang : 'zh'
15
- return lang.toLowerCase().startsWith('en') ? { ...en } : { ...zh }
16
- }
17
-
18
- /** Translate a key with optional {name} template params (current language). */
19
- export function tt(key: ImageGenKey, values?: TranslateValues): string {
20
- const text = dictionary()[key] ?? key
21
- if (values === undefined) return text
22
- let rendered = text
23
- for (const [name, value] of Object.entries(values)) {
24
- rendered = rendered.replaceAll(`{${name}}`, String(value))
25
- }
26
- return rendered
27
- }
28
-
29
- /** Human-readable error text from an unknown thrown value. */
30
- export function errorMessage(error: unknown): string {
31
- if (error instanceof Error) return error.message
32
- return String(error)
33
- }
1
+ /**
2
+ * Shared panel helpers: the active-dictionary pick (document-language based,
3
+ * dsh-ssh precedent) bound to the dsh-imagegen interpolator, plus a small
4
+ * error-message extractor. All copy stays in the locale dictionaries.
5
+ */
6
+
7
+ import { en, zh, type ImageGenKey } from './locales.ts'
8
+
9
+ /** Template values accepted by the interpolator. */
10
+ export type TranslateValues = Record<string, string | number>
11
+
12
+ /** Active dictionary, picked by the document language at call time. */
13
+ export function dictionary(): Record<string, string> {
14
+ const lang = typeof document !== 'undefined' ? document.documentElement.lang : 'zh'
15
+ return lang.toLowerCase().startsWith('en') ? { ...en } : { ...zh }
16
+ }
17
+
18
+ /** Translate a key with optional {name} template params (current language). */
19
+ export function tt(key: ImageGenKey, values?: TranslateValues): string {
20
+ const text = dictionary()[key] ?? key
21
+ if (values === undefined) return text
22
+ let rendered = text
23
+ for (const [name, value] of Object.entries(values)) {
24
+ rendered = rendered.replaceAll(`{${name}}`, String(value))
25
+ }
26
+ return rendered
27
+ }
28
+
29
+ /** Human-readable error text from an unknown thrown value. */
30
+ export function errorMessage(error: unknown): string {
31
+ if (error instanceof Error) return error.message
32
+ return String(error)
33
+ }
@@ -1,73 +1,73 @@
1
- .root {
2
- display: flex;
3
- flex-direction: column;
4
- gap: 7px;
5
- margin: 4px 0;
6
- padding: 8px 10px 10px;
7
- border: 1px solid var(--dsw-alias-border-l1);
8
- border-radius: 8px;
9
- background: var(--dsw-alias-bg-layer-1);
10
- }
11
-
12
- .header {
13
- display: flex;
14
- align-items: center;
15
- gap: 7px;
16
- min-height: 20px;
17
- color: var(--dsw-alias-label-secondary);
18
- font-size: 12px;
19
- }
20
-
21
- .icon {
22
- color: var(--dsw-alias-brand-primary);
23
- font-size: 15px;
24
- line-height: 1;
25
- }
26
-
27
- .status {
28
- margin-left: auto;
29
- color: var(--dsw-alias-label-tertiary);
30
- font-size: 11px;
31
- }
32
-
33
- .message,
34
- .loading,
35
- .error {
36
- margin: 0;
37
- color: var(--dsw-alias-label-tertiary);
38
- font-size: 12px;
39
- line-height: 1.5;
40
- overflow-wrap: anywhere;
41
- }
42
-
43
- .images {
44
- display: flex;
45
- flex-wrap: wrap;
46
- gap: 8px;
47
- }
48
-
49
- .imageLink {
50
- display: block;
51
- max-width: min(280px, 100%);
52
- overflow: hidden;
53
- border: 1px solid var(--dsw-alias-border-l1);
54
- border-radius: 6px;
55
- background: var(--dsw-alias-bg-base);
56
- }
57
-
58
- .imageLink:hover {
59
- border-color: var(--dsw-alias-brand-primary);
60
- }
61
-
62
- .image {
63
- display: block;
64
- width: auto;
65
- max-width: 280px;
66
- height: auto;
67
- max-height: 220px;
68
- object-fit: contain;
69
- }
70
-
71
- .error {
72
- color: var(--dsw-alias-state-error-primary);
73
- }
1
+ .root {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 7px;
5
+ margin: 4px 0;
6
+ padding: 8px 10px 10px;
7
+ border: 1px solid var(--dsw-alias-border-l1);
8
+ border-radius: 8px;
9
+ background: var(--dsw-alias-bg-layer-1);
10
+ }
11
+
12
+ .header {
13
+ display: flex;
14
+ align-items: center;
15
+ gap: 7px;
16
+ min-height: 20px;
17
+ color: var(--dsw-alias-label-secondary);
18
+ font-size: 12px;
19
+ }
20
+
21
+ .icon {
22
+ color: var(--dsw-alias-brand-primary);
23
+ font-size: 15px;
24
+ line-height: 1;
25
+ }
26
+
27
+ .status {
28
+ margin-left: auto;
29
+ color: var(--dsw-alias-label-tertiary);
30
+ font-size: 11px;
31
+ }
32
+
33
+ .message,
34
+ .loading,
35
+ .error {
36
+ margin: 0;
37
+ color: var(--dsw-alias-label-tertiary);
38
+ font-size: 12px;
39
+ line-height: 1.5;
40
+ overflow-wrap: anywhere;
41
+ }
42
+
43
+ .images {
44
+ display: flex;
45
+ flex-wrap: wrap;
46
+ gap: 8px;
47
+ }
48
+
49
+ .imageLink {
50
+ display: block;
51
+ max-width: min(280px, 100%);
52
+ overflow: hidden;
53
+ border: 1px solid var(--dsw-alias-border-l1);
54
+ border-radius: 6px;
55
+ background: var(--dsw-alias-bg-base);
56
+ }
57
+
58
+ .imageLink:hover {
59
+ border-color: var(--dsw-alias-brand-primary);
60
+ }
61
+
62
+ .image {
63
+ display: block;
64
+ width: auto;
65
+ max-width: 280px;
66
+ height: auto;
67
+ max-height: 220px;
68
+ object-fit: contain;
69
+ }
70
+
71
+ .error {
72
+ color: var(--dsw-alias-state-error-primary);
73
+ }
@@ -2,9 +2,9 @@
2
2
 
3
3
  import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
4
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'
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
8
  import css from './image-toolview.module.css'
9
9
 
10
10
  /** Owner props supplied by the host's keyed tool-call slot. */
@@ -130,21 +130,21 @@ export function registerImageToolviews(ctx: ClientContext): void {
130
130
  }
131
131
 
132
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}>
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
148
  <header className={css.header}>
149
149
  <span className={css.icon} aria-hidden="true">▧</span>
150
150
  <strong>{props.toolName}</strong>
@@ -11,9 +11,9 @@
11
11
  * whole boot when a plugin apply throws, and an external plugin must not take
12
12
  * the GUI down.
13
13
  */
14
- import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
15
- import type { ISessions } from '@deepseek-ai/dsh-client-runtime/client'
16
- import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'
14
+ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
15
+ import type { ISessions } from '@deepseek-ai/dsh-client-runtime/client'
16
+ import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'
17
17
  // Type-only: pulls the locale plugin's Context merge (ctx.locale).
18
18
  import type {} from '@deepseek-ai/dsh-client-locale/client'
19
19
  // Type-only: pulls the LocaleNamespaceMap merge table.
@@ -26,8 +26,8 @@ import { mountPanel } from './mount.tsx'
26
26
  import { mountSidebarEntry } from './sidebar-entry.ts'
27
27
  import { ImageGenSettingsCard, ImageGenSettingsCardController } from './SettingsCard.tsx'
28
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
+ import { registerImageToolviews, type ImageToolViewOwnerProps } from './image-toolview.tsx'
30
+ import type { ConversationService } from './conversation-sync.ts'
31
31
 
32
32
  /** Locale namespace this plugin owns. */
33
33
  const NS = 'dsh-imagegen'
@@ -60,7 +60,7 @@ export interface ImageGenPluginItemOwnerProps {
60
60
  }
61
61
 
62
62
  /** Required services (fiber inject waiting — the runtime must be up first). */
63
- export const inject = ['slots', 'locale', 'connection', 'sessions', 'conversation']
63
+ export const inject = ['slots', 'locale', 'connection', 'sessions', 'conversation']
64
64
 
65
65
  /**
66
66
  * Mount the studio, its sidebar entry, and the settings card.
@@ -102,22 +102,22 @@ export function apply(ctx: ClientContext): void {
102
102
  // the scope is still loading, the composition default is unknown, so nothing
103
103
  // mounts yet. Only an unavailable scope falls back to the default (enabled).
104
104
  let uiDisposer: (() => void) | undefined
105
- const mountUi = (): void => {
106
- if (uiDisposer !== undefined) return
107
- const controller = new ImageGenController()
108
- const api = new ImageGenApi()
109
- const sessions = ctx.get('sessions') as ISessions | undefined
110
- const conversation = ctx.get('conversation') as ConversationService | undefined
111
- const disposers: Array<() => void> = []
112
- try {
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 }))
105
+ const mountUi = (): void => {
106
+ if (uiDisposer !== undefined) return
107
+ const controller = new ImageGenController()
108
+ const api = new ImageGenApi()
109
+ const sessions = ctx.get('sessions') as ISessions | undefined
110
+ const conversation = ctx.get('conversation') as ConversationService | undefined
111
+ const disposers: Array<() => void> = []
112
+ try {
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 }))
121
121
  } catch (error) {
122
122
  // DOM failures degrade the studio, never the GUI.
123
123
  console.warn('[dsh-imagegen] mount failed:', error)
@@ -2,23 +2,81 @@
2
2
  * dsh-imagegen surface copy: zh is the key source, en mirrors every key.
3
3
  */
4
4
 
5
- export const zh = {
6
- 'entry.label': 'AI 生图',
7
- 'entry.tooltip': 'AI 生图面板(gpt-image-2 / glm-image / grok-imagine-image / nanobanana / seedream 系列)',
8
- 'entry.newSession': '新会话',
9
- 'entry.newSessionTooltip': '创建一个新的 DSH 会话',
10
- 'entry.image': '生图',
11
- 'panel.title': 'AI 生图',
12
- 'panel.githubTip': '觉得好用或有建议?欢迎来 GitHub 提 issues、点个 star 支持一下!',
13
- 'conversation.add': '加入对话',
14
- 'conversation.adding': '加入中…',
15
- 'conversation.added': '图片已加入当前对话',
16
- 'conversation.noSession': '请先打开一个会话,再加入图片',
17
- 'conversation.unavailable': '当前会话暂不可用',
18
- 'conversation.busy': '当前对话正在发送,请稍后再试',
5
+ export const zh = {
6
+ 'entry.label': 'AI 生图',
7
+ 'entry.tooltip': 'AI 生图面板(gpt-image-2 / glm-image / grok-imagine-image / nanobanana / seedream 系列)',
8
+ 'entry.newSession': '新会话',
9
+ 'entry.newSessionTooltip': '创建一个新的 DSH 会话',
10
+ 'entry.image': '生图',
11
+ 'panel.title': 'AI 生图',
12
+ 'panel.githubTip': '觉得好用或有建议?欢迎来 GitHub 提 issues、点个 star 支持一下!',
13
+ 'conversation.add': '加入对话',
14
+ 'conversation.adding': '加入中…',
15
+ 'conversation.added': '图片已加入当前对话',
16
+ 'conversation.addHint': '加入对话后可使用 /edit_image 修改;命令使用插件图片模型',
17
+ 'conversation.noSession': '请先打开一个会话,再加入图片',
18
+ 'conversation.unavailable': '当前会话暂不可用',
19
+ 'conversation.busy': '当前对话正在发送,请稍后再试',
19
20
  // mode
20
21
  'mode.text': '文生图',
21
22
  'mode.edit': '图生图',
23
+ 'workspace.label': '创作模式',
24
+ 'workspace.normal': '普通生图',
25
+ 'workspace.ecommerce': '电商模式',
26
+ 'chat.expand': '展开右侧对话',
27
+ 'chat.collapse': '收起右侧对话',
28
+ 'chat.toggle': '对话',
29
+ 'config.resizeHint': '拖拽调整参数栏宽度',
30
+ 'ecommerce.generation': '生成设置',
31
+ 'ecommerce.title': '电商模式',
32
+ 'ecommerce.badge': '预览',
33
+ 'ecommerce.short': '电商',
34
+ 'ecommerce.anchorPending': '已生成主图,其余图片正在自动参考主图生成,保证整套同款…',
35
+ 'ecommerce.anchorFailed': '主图生成失败,其余图片已暂停。请重试主图(或整组重新生成)后再继续。',
36
+ 'ecommerce.anchorNote': '锚定生成:先出主图,其余图片自动参考主图,保证整套同款',
37
+ 'ecommerce.noAssetWarn': '未上传商品图:主图将按文字描述生成并作为锚定基准,建议先上传商品主图获得更高一致性',
38
+ 'ecommerce.product': '商品信息',
39
+ 'ecommerce.productName': '商品名称(必填)',
40
+ 'ecommerce.projectName': '项目名称(可选)',
41
+ 'ecommerce.constraints': '卖点与约束',
42
+ 'ecommerce.sellingPoints': '商品卖点,例如:防水防污 / 大容量 / 便携',
43
+ 'ecommerce.protectedFeatures': '必须保留的特征,例如:颜色、Logo、包装文字、结构',
44
+ 'ecommerce.styleHint': '风格补充,例如:高级感 / 写实 / 自然光',
45
+ 'ecommerce.setStructure': '套图结构',
46
+ 'ecommerce.preview': '生成套图预览',
47
+ 'ecommerce.planTitle': '预计生成 {count} 张图片',
48
+ 'ecommerce.planSlot': '{count} 张 · {description}',
49
+ 'ecommerce.confirm': '确认生成整套图片',
50
+ 'ecommerce.uploadRef': '上传商品素材(可选,最多 4 张:主体 / 包装 / 细节 / 风格)',
51
+ 'ecommerce.uploadShort': '上传素材',
52
+ 'ecommerce.sellingTitle': '商品卖点',
53
+ 'ecommerce.advanced': '高级选项(项目 / 平台 / 约束)',
54
+ 'ecommerce.params': '参数选择',
55
+ 'ecommerce.platformLabel': '平台',
56
+ 'ecommerce.languageLabel': '文案语言',
57
+ 'ecommerce.ratioLabel': '比例',
58
+ 'ecommerce.categoryLabel': '类目',
59
+ 'ecommerce.multiSelect': '可多选',
60
+ 'ecommerce.countHint': '点击调整数量(1-4)',
61
+ 'ecommerce.refSettings': '参考图设置(可选)',
62
+ 'ecommerce.styleTitle': '风格 / 补充提示',
63
+ 'ecommerce.protectedLabel': '必须保留的特征(可选)',
64
+ 'ecommerce.assetsFull': '最多上传 4 张素材',
65
+ 'ecommerce.refSelect': '该用途使用的参考图',
66
+ 'ecommerce.refNone': '不使用参考图',
67
+ 'ecommerce.role.product': '商品主体',
68
+ 'ecommerce.role.packaging': '包装',
69
+ 'ecommerce.role.detail': '细节/角度',
70
+ 'ecommerce.role.style': '风格参考',
71
+ 'ecommerce.footerReady': '将生成 {count} 张套图',
72
+ 'ecommerce.footerEmpty': '请选择套图结构',
73
+ 'ecommerce.results.title': '套图结果',
74
+ 'ecommerce.results.progress': '{done}/{total} 已完成',
75
+ 'ecommerce.results.failed': '{count} 张失败',
76
+ 'ecommerce.results.empty': '确认生成后,每张图片的任务状态和结果会按用途显示在这里',
77
+ 'ecommerce.results.regenerate': '重新生成',
78
+ 'ecommerce.results.export': '导出清单',
79
+ 'ecommerce.results.newProduct': '新商品',
22
80
  // prompt
23
81
  'prompt.placeholder': '描述你想要的画面,例如:一只戴着宇航员头盔的橘猫,在月球上举起望远镜,水彩风格,柔和光线…',
24
82
  'prompt.required': '请输入提示词',
@@ -81,6 +139,8 @@ export const zh = {
81
139
  // canvas
82
140
  'canvas.emptyTitle': '开始你的创作',
83
141
  'canvas.emptyHint': '在左侧输入提示词并点击「开始生成」,结果将显示在这里',
142
+ 'canvas.new': '新建作图',
143
+ 'canvas.newHint': '开始新的作图并清空预览',
84
144
  'canvas.error': '生成失败:{error}',
85
145
  'canvas.submitting': '正在加入生成队列…',
86
146
  'canvas.queued': '已加入生成队列',
@@ -94,6 +154,7 @@ export const zh = {
94
154
  'history.title': '历史记录',
95
155
  'history.empty': '暂无历史记录,生成图片后会保存在这里',
96
156
  'history.clear': '清空',
157
+ 'history.clearConfirm': '确定要清空全部历史记录吗?此操作不可撤销。',
97
158
  'history.restore': '恢复',
98
159
  'history.delete': '删除',
99
160
  'history.images': '张',
@@ -138,6 +199,7 @@ export const zh = {
138
199
  'gallery.already': '已在画廊中',
139
200
  'gallery.delete': '移出画廊',
140
201
  'gallery.clear': '清空画廊',
202
+ 'gallery.clearConfirm': '确定要清空整个画廊吗?此操作不可撤销。',
141
203
  'gallery.empty': '画廊还是空的,把喜欢的图片加入进来吧',
142
204
  'gallery.viewing': '画廊 · {time}',
143
205
  // preview
@@ -167,6 +229,8 @@ export const zh = {
167
229
  'config.disabled': '插件已停用,请在设置中重新启用。',
168
230
  'connection.connected': '已连接',
169
231
  'connection.disconnected': '未连接',
232
+ 'panel.collapseConfig': '收起参数栏',
233
+ 'panel.expandConfig': '展开参数栏',
170
234
  // plugin update
171
235
  'update.available': '检测到新版本:{version}',
172
236
  'update.install': '在线更新',
@@ -314,22 +378,80 @@ export const zh = {
314
378
  'channels.presetLoadFailed': '加载提供方失败:{error}',
315
379
  } as const
316
380
 
317
- export const en: Record<keyof typeof zh, string> = {
318
- 'entry.label': 'AI Image',
319
- 'entry.tooltip': 'AI image generation studio (gpt-image-2 / glm-image / grok-imagine-image / nanobanana / seedream family)',
320
- 'entry.newSession': 'New session',
321
- 'entry.newSessionTooltip': 'Create a new DSH session',
322
- 'entry.image': 'Image',
323
- 'panel.title': 'AI Image',
324
- 'panel.githubTip': 'Like it or have suggestions? Head to GitHub to open issues and star us!',
325
- 'conversation.add': 'Add to chat',
326
- 'conversation.adding': 'Adding…',
327
- 'conversation.added': 'Image added to the current chat',
328
- 'conversation.noSession': 'Open a session before adding an image',
329
- 'conversation.unavailable': 'The current session is unavailable',
330
- 'conversation.busy': 'The chat is sending a message; try again shortly',
381
+ export const en: Record<keyof typeof zh, string> = {
382
+ 'entry.label': 'AI Image',
383
+ 'entry.tooltip': 'AI image generation studio (gpt-image-2 / glm-image / grok-imagine-image / nanobanana / seedream family)',
384
+ 'entry.newSession': 'New session',
385
+ 'entry.newSessionTooltip': 'Create a new DSH session',
386
+ 'entry.image': 'Image',
387
+ 'panel.title': 'AI Image',
388
+ 'panel.githubTip': 'Like it or have suggestions? Head to GitHub to open issues and star us!',
389
+ 'conversation.add': 'Add to chat',
390
+ 'conversation.adding': 'Adding…',
391
+ 'conversation.added': 'Image added to the current chat',
392
+ 'conversation.addHint': 'After adding to chat, use /edit_image to modify it; the command uses the plugin image model',
393
+ 'conversation.noSession': 'Open a session before adding an image',
394
+ 'conversation.unavailable': 'The current session is unavailable',
395
+ 'conversation.busy': 'The chat is sending a message; try again shortly',
331
396
  'mode.text': 'Text to Image',
332
397
  'mode.edit': 'Image to Image',
398
+ 'workspace.label': 'Workspace',
399
+ 'workspace.normal': 'Standard',
400
+ 'workspace.ecommerce': 'E-commerce',
401
+ 'chat.expand': 'Expand the conversation pane',
402
+ 'chat.collapse': 'Collapse the conversation pane',
403
+ 'chat.toggle': 'Chat',
404
+ 'config.resizeHint': 'Drag to resize the parameter panel',
405
+ 'ecommerce.generation': 'Generation settings',
406
+ 'ecommerce.title': 'E-commerce Mode',
407
+ 'ecommerce.badge': 'Beta',
408
+ 'ecommerce.short': 'Store',
409
+ 'ecommerce.anchorPending': 'Main image generated — remaining images are being generated with the main image as reference to keep the set consistent…',
410
+ 'ecommerce.anchorFailed': 'Main image failed; remaining images are paused. Retry the main image or regenerate the set.',
411
+ 'ecommerce.anchorNote': 'Anchored: main image first, remaining images reference it automatically for a consistent set',
412
+ 'ecommerce.noAssetWarn': 'No product image uploaded: the main image will be generated from text as the anchor. Upload one for higher consistency.',
413
+ 'ecommerce.product': 'Product details',
414
+ 'ecommerce.productName': 'Product name (required)',
415
+ 'ecommerce.projectName': 'Project name (optional)',
416
+ 'ecommerce.constraints': 'Selling points & constraints',
417
+ 'ecommerce.sellingPoints': 'Selling points, e.g. waterproof / large capacity / portable',
418
+ 'ecommerce.protectedFeatures': 'Features to preserve: color, logo, packaging text, structure',
419
+ 'ecommerce.styleHint': 'Style hint, e.g. premium / realistic / natural light',
420
+ 'ecommerce.setStructure': 'Image set structure',
421
+ 'ecommerce.preview': 'Preview product set',
422
+ 'ecommerce.planTitle': 'Plan to generate {count} images',
423
+ 'ecommerce.planSlot': '{count} images · {description}',
424
+ 'ecommerce.confirm': 'Confirm and generate set',
425
+ 'ecommerce.uploadRef': 'Upload product assets (optional, up to 4: product / packaging / detail / style)',
426
+ 'ecommerce.uploadShort': 'Upload',
427
+ 'ecommerce.sellingTitle': 'Selling points',
428
+ 'ecommerce.advanced': 'Advanced (project / platform / constraints)',
429
+ 'ecommerce.params': 'Parameters',
430
+ 'ecommerce.platformLabel': 'Platform',
431
+ 'ecommerce.languageLabel': 'Copy language',
432
+ 'ecommerce.ratioLabel': 'Aspect ratio',
433
+ 'ecommerce.categoryLabel': 'Category',
434
+ 'ecommerce.multiSelect': 'multi-select',
435
+ 'ecommerce.countHint': 'Click to adjust count (1-4)',
436
+ 'ecommerce.refSettings': 'Reference images (optional)',
437
+ 'ecommerce.styleTitle': 'Style / extra hints',
438
+ 'ecommerce.protectedLabel': 'Features to preserve (optional)',
439
+ 'ecommerce.assetsFull': 'Up to 4 assets',
440
+ 'ecommerce.refSelect': 'Reference image for this slot',
441
+ 'ecommerce.refNone': 'No reference',
442
+ 'ecommerce.role.product': 'Product',
443
+ 'ecommerce.role.packaging': 'Packaging',
444
+ 'ecommerce.role.detail': 'Detail / angle',
445
+ 'ecommerce.role.style': 'Style',
446
+ 'ecommerce.footerReady': '{count} images in this set',
447
+ 'ecommerce.footerEmpty': 'Select the image set structure',
448
+ 'ecommerce.results.title': 'Product set results',
449
+ 'ecommerce.results.progress': '{done}/{total} completed',
450
+ 'ecommerce.results.failed': '{count} failed',
451
+ 'ecommerce.results.empty': 'After confirming, each image task and result will show here grouped by purpose',
452
+ 'ecommerce.results.regenerate': 'Regenerate',
453
+ 'ecommerce.results.export': 'Export manifest',
454
+ 'ecommerce.results.newProduct': 'New product',
333
455
  'prompt.placeholder': 'Describe the picture you want, e.g. an orange cat in an astronaut helmet raising a telescope on the moon, watercolor style, soft light…',
334
456
  'prompt.required': 'Enter a prompt first',
335
457
  'prompt.count': '{count}',
@@ -387,6 +509,8 @@ export const en: Record<keyof typeof zh, string> = {
387
509
  'edit.required': 'Upload a reference image first',
388
510
  'canvas.emptyTitle': 'Start creating',
389
511
  'canvas.emptyHint': 'Enter a prompt on the left and click "Generate"; results appear here',
512
+ 'canvas.new': 'New creation',
513
+ 'canvas.newHint': 'Start a new creation and clear the preview',
390
514
  'canvas.error': 'Generation failed: {error}',
391
515
  'canvas.submitting': 'Adding to the generation queue…',
392
516
  'canvas.queued': 'Added to the generation queue',
@@ -399,6 +523,7 @@ export const en: Record<keyof typeof zh, string> = {
399
523
  'history.title': 'History',
400
524
  'history.empty': 'No history yet — generations will be saved here',
401
525
  'history.clear': 'Clear',
526
+ 'history.clearConfirm': 'Clear all history? This action cannot be undone.',
402
527
  'history.restore': 'Restore',
403
528
  'history.delete': 'Delete',
404
529
  'history.images': 'images',
@@ -442,6 +567,7 @@ export const en: Record<keyof typeof zh, string> = {
442
567
  'gallery.already': 'Already in gallery',
443
568
  'gallery.delete': 'Remove',
444
569
  'gallery.clear': 'Clear gallery',
570
+ 'gallery.clearConfirm': 'Clear the entire gallery? This action cannot be undone.',
445
571
  'gallery.empty': 'The gallery is empty — add images you like here',
446
572
  'gallery.viewing': 'Gallery · {time}',
447
573
  'preview.title': 'Image preview',
@@ -469,6 +595,8 @@ export const en: Record<keyof typeof zh, string> = {
469
595
  'config.disabled': 'The plugin is disabled — re-enable it in Settings.',
470
596
  'connection.connected': 'Connected',
471
597
  'connection.disconnected': 'Disconnected',
598
+ 'panel.collapseConfig': 'Collapse settings panel',
599
+ 'panel.expandConfig': 'Expand settings panel',
472
600
  'update.available': 'A new version is available: {version}',
473
601
  'update.install': 'Update online',
474
602
  'update.installing': 'Updating…',