@dsh-plus/image-studio 0.1.35 → 0.1.36
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/lib/client.js +1383 -394
- package/lib/index.d.ts +2 -2
- package/lib/index.js +13 -8
- package/package.json +2 -2
- package/src/client/card.tsx +21 -25
- package/src/client/i18n.ts +27 -4
- package/src/client/preset-sections.tsx +140 -174
- package/src/client/styles.ts +0 -24
- package/src/credentials.ts +9 -5
- package/src/uploads/store.ts +6 -3
package/lib/index.d.ts
CHANGED
|
@@ -306,8 +306,8 @@ declare class ImageStudioService extends Service {
|
|
|
306
306
|
//#region src/credentials.d.ts
|
|
307
307
|
/**
|
|
308
308
|
* 凭据引用名派生与校验:提供商预设 id → IMAGE_STUDIO_PRESET_<ID>。
|
|
309
|
-
*
|
|
310
|
-
*
|
|
309
|
+
* 命名与归一走 `@dsh-plus/shared` 的公共助手,本模块只补预设 id 的形态校验。
|
|
310
|
+
* 纯函数,零依赖(浏览器半亦可安全 import)。
|
|
311
311
|
* @module image-studio/credentials
|
|
312
312
|
*/
|
|
313
313
|
/** 预设 id 是否合法(kebab-case,用于引用名派生)。 */
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Service } from "@deepseek-ai/cordis";
|
|
2
2
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
3
|
-
import { atomicWriteFile, commitTmpFile, pluginDataPath, unwrapVolatile } from "@dsh-plus/shared";
|
|
3
|
+
import { atomicWriteFile, commitTmpFile, credentialRefName, pluginCachePath, pluginDataPath, unwrapVolatile } from "@dsh-plus/shared";
|
|
4
4
|
import { request } from "node:http";
|
|
5
5
|
import { request as request$1 } from "node:https";
|
|
6
6
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
@@ -959,12 +959,14 @@ function registerImageStudioApi(ctx, service) {
|
|
|
959
959
|
//#region src/credentials.ts
|
|
960
960
|
/**
|
|
961
961
|
* 凭据引用名派生与校验:提供商预设 id → IMAGE_STUDIO_PRESET_<ID>。
|
|
962
|
-
*
|
|
963
|
-
*
|
|
962
|
+
* 命名与归一走 `@dsh-plus/shared` 的公共助手,本模块只补预设 id 的形态校验。
|
|
963
|
+
* 纯函数,零依赖(浏览器半亦可安全 import)。
|
|
964
964
|
* @module image-studio/credentials
|
|
965
965
|
*/
|
|
966
|
-
/**
|
|
967
|
-
const
|
|
966
|
+
/** 属主段(插件 id)。 */
|
|
967
|
+
const REF_OWNER = "image-studio";
|
|
968
|
+
/** 业务段前缀(与属主一起构成 IMAGE_STUDIO_PRESET_<ID>)。 */
|
|
969
|
+
const REF_KIND = "preset";
|
|
968
970
|
/** 预设 id 是否合法(kebab-case,用于引用名派生)。 */
|
|
969
971
|
function isValidPresetId(id) {
|
|
970
972
|
return /^[a-z][a-z0-9-]*$/.test(id) && id.length <= 64;
|
|
@@ -972,7 +974,7 @@ function isValidPresetId(id) {
|
|
|
972
974
|
/** 预设 id → 凭据引用名(kebab → SNAKE 大写)。 */
|
|
973
975
|
function credentialRefNameOf(presetId) {
|
|
974
976
|
if (!isValidPresetId(presetId)) throw new Error(`非法提供商预设 id:${JSON.stringify(presetId)}`);
|
|
975
|
-
return
|
|
977
|
+
return credentialRefName(REF_OWNER, REF_KIND, presetId);
|
|
976
978
|
}
|
|
977
979
|
//#endregion
|
|
978
980
|
//#region src/images/id.ts
|
|
@@ -1243,9 +1245,12 @@ function defaultIdCounter() {
|
|
|
1243
1245
|
* 落点 $DSH_HOME/dsh-plus/image-studio/uploads/{index.jsonl, blobs/}
|
|
1244
1246
|
* @module image-studio/uploads/store
|
|
1245
1247
|
*/
|
|
1246
|
-
/**
|
|
1248
|
+
/**
|
|
1249
|
+
* 存储层根路径(惰性解析:DSH_HOME 可能在模块加载后才被测试环境设置)。
|
|
1250
|
+
* 上传是带 TTL 的暂存派生物(`uploadTtlHours` 回收),故落缓存目录。
|
|
1251
|
+
*/
|
|
1247
1252
|
function uploadsRoot() {
|
|
1248
|
-
return
|
|
1253
|
+
return pluginCachePath("image-studio", "uploads");
|
|
1249
1254
|
}
|
|
1250
1255
|
function indexFile() {
|
|
1251
1256
|
return join(uploadsRoot(), "index.jsonl");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plus/image-studio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"description": "dsh-plus service+ui plugin: 图像工作室——OpenAI Images 协议文生图/图生图,全参数开关化,双预设体系,并发生图任务与可二次编辑画廊",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"https-proxy-agent": "^9.1.0",
|
|
23
|
-
"@dsh-plus/shared": "0.1.
|
|
23
|
+
"@dsh-plus/shared": "0.1.44"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@deepseek-ai/cordis": "^4.0.5",
|
package/src/client/card.tsx
CHANGED
|
@@ -10,13 +10,16 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import {
|
|
12
12
|
type CardAction,
|
|
13
|
-
CardChrome,
|
|
14
13
|
CardLoading,
|
|
15
14
|
type CardStatusState,
|
|
15
|
+
ConfigShell,
|
|
16
|
+
FieldGrid,
|
|
16
17
|
IDLE_STATUS,
|
|
17
18
|
type NamespaceSettingsApi,
|
|
19
|
+
NumberField,
|
|
18
20
|
type PluginConfigViewProps,
|
|
19
21
|
type Scope,
|
|
22
|
+
Section,
|
|
20
23
|
TextField,
|
|
21
24
|
} from '@dsh-plus/shared/client'
|
|
22
25
|
import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
@@ -91,29 +94,24 @@ function AdvancedFields(props: {
|
|
|
91
94
|
}): ReactElement {
|
|
92
95
|
const { t, draft, disabled, onPatch } = props
|
|
93
96
|
return (
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
<TextField
|
|
97
|
+
<Section prefix="imsc" title={t('card.advanced')} description={t('card.advancedHint')}>
|
|
98
|
+
<FieldGrid prefix="imsc" columns={2}>
|
|
99
|
+
<NumberField
|
|
98
100
|
prefix="imsc"
|
|
99
101
|
id="imsc-conc"
|
|
100
102
|
label={t('card.maxConcurrent')}
|
|
101
|
-
numeric
|
|
102
103
|
value={draft.maxConcurrent}
|
|
103
104
|
disabled={disabled}
|
|
104
105
|
onEdit={(v) => onPatch({ maxConcurrent: v })}
|
|
105
106
|
/>
|
|
106
|
-
<
|
|
107
|
+
<NumberField
|
|
107
108
|
prefix="imsc"
|
|
108
109
|
id="imsc-timeout"
|
|
109
110
|
label={t('card.requestTimeoutMs')}
|
|
110
|
-
numeric
|
|
111
111
|
value={draft.requestTimeoutMs}
|
|
112
112
|
disabled={disabled}
|
|
113
113
|
onEdit={(v) => onPatch({ requestTimeoutMs: v })}
|
|
114
114
|
/>
|
|
115
|
-
</div>
|
|
116
|
-
<div className="imsc-grid2">
|
|
117
115
|
<TextField
|
|
118
116
|
prefix="imsc"
|
|
119
117
|
id="imsc-proxy"
|
|
@@ -122,26 +120,24 @@ function AdvancedFields(props: {
|
|
|
122
120
|
disabled={disabled}
|
|
123
121
|
onEdit={(v) => onPatch({ proxy: v })}
|
|
124
122
|
/>
|
|
125
|
-
<
|
|
123
|
+
<NumberField
|
|
126
124
|
prefix="imsc"
|
|
127
125
|
id="imsc-gmax"
|
|
128
126
|
label={t('card.galleryMaxItems')}
|
|
129
|
-
numeric
|
|
130
127
|
value={draft.galleryMaxItems}
|
|
131
128
|
disabled={disabled}
|
|
132
129
|
onEdit={(v) => onPatch({ galleryMaxItems: v })}
|
|
133
130
|
/>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
</>
|
|
131
|
+
<NumberField
|
|
132
|
+
prefix="imsc"
|
|
133
|
+
id="imsc-upload-ttl"
|
|
134
|
+
label={t('card.uploadTtlHours')}
|
|
135
|
+
value={draft.uploadTtlHours}
|
|
136
|
+
disabled={disabled}
|
|
137
|
+
onEdit={(v) => onPatch({ uploadTtlHours: v })}
|
|
138
|
+
/>
|
|
139
|
+
</FieldGrid>
|
|
140
|
+
</Section>
|
|
145
141
|
)
|
|
146
142
|
}
|
|
147
143
|
|
|
@@ -226,7 +222,7 @@ export function StudioConfigCard(props: CardProps): ReactElement | string | null
|
|
|
226
222
|
}
|
|
227
223
|
|
|
228
224
|
return (
|
|
229
|
-
<
|
|
225
|
+
<ConfigShell
|
|
230
226
|
prefix="imsc"
|
|
231
227
|
title={t('card.title')}
|
|
232
228
|
description={t('card.description')}
|
|
@@ -259,6 +255,6 @@ export function StudioConfigCard(props: CardProps): ReactElement | string | null
|
|
|
259
255
|
<PromptPresetSection {...sectionProps} />
|
|
260
256
|
<ParamPresetSection {...sectionProps} />
|
|
261
257
|
<AdvancedFields t={t} draft={draft} disabled={disabled} onPatch={patch} />
|
|
262
|
-
</
|
|
258
|
+
</ConfigShell>
|
|
263
259
|
)
|
|
264
260
|
}
|
package/src/client/i18n.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* i18n 字典(zh 为键域事实源,en 全量对译);NS 与 settings 命名空间分离,
|
|
3
3
|
* 仅承载前端文案 key。面板 / 入口 / 配置卡片共用同一命名空间。
|
|
4
|
+
*
|
|
5
|
+
* 公共键(卡片套件的 save/unsaved/code* 等)来自 shared 的 common 字典并在此合并,
|
|
6
|
+
* 本文件只维护业务键。
|
|
4
7
|
* @module image-studio/client/i18n
|
|
5
8
|
*/
|
|
9
|
+
import { commonEn, commonZh, mergeDict } from '@dsh-plus/shared/client'
|
|
6
10
|
|
|
7
11
|
/** locale 命名空间。 */
|
|
8
12
|
export const NS = 'dsh-plus-image-studio'
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
const ownZh = {
|
|
11
15
|
// 入口与面板骨架
|
|
12
16
|
'entry.label': '图像',
|
|
13
17
|
'panel.title': '图像工作室',
|
|
@@ -98,9 +102,16 @@ export const zh = {
|
|
|
98
102
|
'card.description': '文生图/图生图:提供商与 API Key、参数/提示词预设',
|
|
99
103
|
'card.summary': '文生图/图生图:提供商与 API Key、参数/提示词预设。',
|
|
100
104
|
'card.providers': '提供商预设',
|
|
105
|
+
'card.providersHint':
|
|
106
|
+
'按序尝试的提供商预设:条目内 API Key 走官方 credentials(引用名由预设 ID 派生),保存后不回显。',
|
|
101
107
|
'card.prompts': '提示词预设',
|
|
108
|
+
'card.promptsHint': '提示词模板列表:生成/编辑面板按名称选用,{prompt} 等占位符由面板注入。',
|
|
102
109
|
'card.params': '参数预设',
|
|
110
|
+
'card.paramsHint':
|
|
111
|
+
'参数预设列表:适用端点决定出现在「文生图」还是「图生图」,参数表为 JSON(如 {"size": {"enabled": true, "value": "1024x1024"}})。',
|
|
103
112
|
'card.advanced': '高级设置',
|
|
113
|
+
'card.advancedHint':
|
|
114
|
+
'并发、超时、代理与本地画廊保留策略;留空/0 表示沿用内置默认(并发 2、超时 60000ms、画廊 200 张、上传保留 168 小时)。',
|
|
104
115
|
'card.add': '添加',
|
|
105
116
|
'card.field.id': 'ID',
|
|
106
117
|
'card.field.name': '名称',
|
|
@@ -134,12 +145,10 @@ export const zh = {
|
|
|
134
145
|
'card.badgeOff': '未配置',
|
|
135
146
|
}
|
|
136
147
|
|
|
137
|
-
export type DictKey = keyof typeof zh
|
|
138
|
-
|
|
139
148
|
/** 面板/卡片共用的翻译函数类型(slot 注入的 bind 结果按此消费)。 */
|
|
140
149
|
export type Translate = (key: DictKey) => string
|
|
141
150
|
|
|
142
|
-
|
|
151
|
+
const ownEn: Record<keyof typeof ownZh, string> = {
|
|
143
152
|
'entry.label': 'Image',
|
|
144
153
|
'panel.title': 'Image Studio',
|
|
145
154
|
'tab.generation': 'Text to Image',
|
|
@@ -222,9 +231,17 @@ export const en: Record<DictKey, string> = {
|
|
|
222
231
|
'card.description': 'Text/Image-to-image: providers & API keys, param/prompt presets',
|
|
223
232
|
'card.summary': 'Text/Image-to-image: providers & API keys, param/prompt presets.',
|
|
224
233
|
'card.providers': 'Provider presets',
|
|
234
|
+
'card.providersHint':
|
|
235
|
+
'Providers tried in order; each entry keeps its API key in official credentials (reference derived from the preset id) and never echoes it back.',
|
|
225
236
|
'card.prompts': 'Prompt presets',
|
|
237
|
+
'card.promptsHint':
|
|
238
|
+
'Prompt templates; the generation/edit panels pick them by name and fill placeholders such as {prompt}.',
|
|
226
239
|
'card.params': 'Parameter presets',
|
|
240
|
+
'card.paramsHint':
|
|
241
|
+
'Parameter presets; the endpoint decides whether it shows up in Text to Image or Image to Image, and the spec is JSON such as {"size": {"enabled": true, "value": "1024x1024"}}.',
|
|
227
242
|
'card.advanced': 'Advanced',
|
|
243
|
+
'card.advancedHint':
|
|
244
|
+
'Concurrency, timeout, proxy and local gallery retention; empty/0 keeps the built-in defaults (concurrency 2, timeout 60000ms, gallery 200, uploads 168h).',
|
|
228
245
|
'card.add': 'Add',
|
|
229
246
|
'card.field.id': 'ID',
|
|
230
247
|
'card.field.name': 'Name',
|
|
@@ -258,3 +275,9 @@ export const en: Record<DictKey, string> = {
|
|
|
258
275
|
'card.badgeOn': '{n} providers',
|
|
259
276
|
'card.badgeOff': 'Not configured',
|
|
260
277
|
}
|
|
278
|
+
|
|
279
|
+
/** 卡片公共文案 + 本插件文案(own 覆盖同键):配置卡片经共享套件渲染,公共键只维护一份。 */
|
|
280
|
+
export const zh = mergeDict(commonZh, ownZh)
|
|
281
|
+
export const en: Record<keyof typeof zh, string> = mergeDict(commonEn, ownEn)
|
|
282
|
+
|
|
283
|
+
export type DictKey = keyof typeof zh
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 配置卡片的三组预设编辑区:提供商(含逐条 API Key 管理)、提示词、参数。
|
|
3
3
|
* 从 card.tsx 拆出,避免单组件过长;草稿类型与纯转换在 ./draft.ts。
|
|
4
|
+
*
|
|
5
|
+
* 排版与控件一律走 @dsh-plus/shared/client 套件(Section / ListEditor / 字段控件),
|
|
6
|
+
* 与其它插件卡片保持一致:条目头提供折叠/上移/下移/删除,新增固定在列表底部。
|
|
4
7
|
* @module image-studio/client/preset-sections
|
|
5
8
|
*/
|
|
6
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
Button,
|
|
11
|
+
CodeField,
|
|
12
|
+
ListEditor,
|
|
13
|
+
type ListLabels,
|
|
14
|
+
Section,
|
|
15
|
+
SelectField,
|
|
16
|
+
TextField,
|
|
17
|
+
} from '@dsh-plus/shared/client'
|
|
7
18
|
import { type ReactElement, useEffect, useState } from 'react'
|
|
8
19
|
|
|
9
20
|
import { isValidPresetId } from '../credentials.ts'
|
|
@@ -68,22 +79,18 @@ function CredentialRow(props: { t: Translate; presetId: string; disabled: boolea
|
|
|
68
79
|
{t(configured ? 'card.credential.configured' : 'card.credential.unconfigured')}
|
|
69
80
|
</span>
|
|
70
81
|
) : null}
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
82
|
+
<Button
|
|
83
|
+
prefix="imsc"
|
|
84
|
+
label={t('card.credential.set')}
|
|
74
85
|
disabled={disabled || busy || value === '' || !isValidPresetId(presetId)}
|
|
75
86
|
onClick={() => act(() => setCredential(presetId, value))}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
type="button"
|
|
81
|
-
className="imsc-btn imsc-btnGhost"
|
|
87
|
+
/>
|
|
88
|
+
<Button
|
|
89
|
+
prefix="imsc"
|
|
90
|
+
label={t('card.credential.unset')}
|
|
82
91
|
disabled={disabled || busy || configured !== true || !isValidPresetId(presetId)}
|
|
83
92
|
onClick={() => act(() => unsetCredential(presetId))}
|
|
84
|
-
|
|
85
|
-
{t('card.credential.unset')}
|
|
86
|
-
</button>
|
|
93
|
+
/>
|
|
87
94
|
</div>
|
|
88
95
|
)
|
|
89
96
|
}
|
|
@@ -100,26 +107,34 @@ export interface PresetSectionsProps {
|
|
|
100
107
|
onPatchPrompt(index: number, patch: Partial<PromptDraft>): void
|
|
101
108
|
}
|
|
102
109
|
|
|
110
|
+
/** 列表编辑器文案(公共字典键,各插件一致)。 */
|
|
111
|
+
function listLabels(t: Translate): ListLabels {
|
|
112
|
+
return {
|
|
113
|
+
add: t('listAdd'),
|
|
114
|
+
remove: t('listRemove'),
|
|
115
|
+
up: t('listMoveUp'),
|
|
116
|
+
down: t('listMoveDown'),
|
|
117
|
+
duplicate: t('listDuplicate'),
|
|
118
|
+
collapse: t('collapse'),
|
|
119
|
+
expand: t('expand'),
|
|
120
|
+
empty: t('listEmpty'),
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
103
124
|
export function ProviderPresetSection(props: PresetSectionsProps): ReactElement {
|
|
104
125
|
const { t, draft, disabled, protocols, onPatch, onPatchProvider } = props
|
|
105
126
|
return (
|
|
106
|
-
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
onClick={() => onPatch({ providers: draft.providers.filter((_, i) => i !== index) })}
|
|
118
|
-
>
|
|
119
|
-
{t('common.delete')}
|
|
120
|
-
</button>
|
|
121
|
-
</div>
|
|
122
|
-
<div className="imsc-grid2">
|
|
127
|
+
<Section prefix="imsc" title={t('card.providers')} description={t('card.providersHint')}>
|
|
128
|
+
<ListEditor
|
|
129
|
+
prefix="imsc"
|
|
130
|
+
items={draft.providers}
|
|
131
|
+
labels={listLabels(t)}
|
|
132
|
+
disabled={disabled}
|
|
133
|
+
titleOf={(provider) => provider.name || provider.id || '—'}
|
|
134
|
+
create={() => emptyProviderDraft(protocols[0]?.id ?? 'openai-images')}
|
|
135
|
+
onChange={(providers) => onPatch({ providers })}
|
|
136
|
+
renderItem={(provider, index) => (
|
|
137
|
+
<>
|
|
123
138
|
<TextField
|
|
124
139
|
prefix="imsc"
|
|
125
140
|
id={`imsc-pid-${index}`}
|
|
@@ -139,31 +154,19 @@ export function ProviderPresetSection(props: PresetSectionsProps): ReactElement
|
|
|
139
154
|
disabled={disabled}
|
|
140
155
|
onEdit={(v) => onPatchProvider(index, { name: v })}
|
|
141
156
|
/>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
onChange={(event) => onPatchProvider(index, { protocol: event.target.value })}
|
|
156
|
-
>
|
|
157
|
-
{(protocols.length > 0
|
|
158
|
-
? protocols
|
|
159
|
-
: [{ id: provider.protocol, label: provider.protocol }]
|
|
160
|
-
).map((protocol) => (
|
|
161
|
-
<option key={protocol.id} value={protocol.id}>
|
|
162
|
-
{protocol.label}
|
|
163
|
-
</option>
|
|
164
|
-
))}
|
|
165
|
-
</select>
|
|
166
|
-
</div>
|
|
157
|
+
<SelectField
|
|
158
|
+
prefix="imsc"
|
|
159
|
+
id={`imsc-pproto-${index}`}
|
|
160
|
+
label={t('card.field.protocol')}
|
|
161
|
+
value={provider.protocol}
|
|
162
|
+
options={
|
|
163
|
+
protocols.length > 0
|
|
164
|
+
? protocols.map((protocol) => ({ value: protocol.id, label: protocol.label }))
|
|
165
|
+
: [{ value: provider.protocol, label: provider.protocol }]
|
|
166
|
+
}
|
|
167
|
+
disabled={disabled}
|
|
168
|
+
onEdit={(v) => onPatchProvider(index, { protocol: v })}
|
|
169
|
+
/>
|
|
167
170
|
<TextField
|
|
168
171
|
prefix="imsc"
|
|
169
172
|
id={`imsc-pmodel-${index}`}
|
|
@@ -172,150 +175,113 @@ export function ProviderPresetSection(props: PresetSectionsProps): ReactElement
|
|
|
172
175
|
disabled={disabled}
|
|
173
176
|
onEdit={(v) => onPatchProvider(index, { model: v })}
|
|
174
177
|
/>
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
<div className="imsc-head">
|
|
186
|
-
<label className="imsc-label" htmlFor={`imsc-pheaders-${index}`}>
|
|
187
|
-
{t('card.field.extraHeaders')}
|
|
188
|
-
</label>
|
|
189
|
-
</div>
|
|
190
|
-
<textarea
|
|
178
|
+
<TextField
|
|
179
|
+
prefix="imsc"
|
|
180
|
+
id={`imsc-purl-${index}`}
|
|
181
|
+
label={t('card.field.baseUrl')}
|
|
182
|
+
value={provider.baseUrl}
|
|
183
|
+
disabled={disabled}
|
|
184
|
+
onEdit={(v) => onPatchProvider(index, { baseUrl: v })}
|
|
185
|
+
/>
|
|
186
|
+
<CodeField
|
|
187
|
+
prefix="imsc"
|
|
191
188
|
id={`imsc-pheaders-${index}`}
|
|
192
|
-
|
|
189
|
+
label={t('card.field.extraHeaders')}
|
|
193
190
|
value={provider.extraHeadersText}
|
|
194
191
|
disabled={disabled}
|
|
195
|
-
|
|
192
|
+
onEdit={(v) => onPatchProvider(index, { extraHeadersText: v })}
|
|
196
193
|
/>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
type="button"
|
|
203
|
-
className="imsc-btn imsc-btnGhost"
|
|
204
|
-
disabled={disabled}
|
|
205
|
-
onClick={() =>
|
|
206
|
-
onPatch({
|
|
207
|
-
providers: [
|
|
208
|
-
...draft.providers,
|
|
209
|
-
emptyProviderDraft(protocols[0]?.id ?? 'openai-images'),
|
|
210
|
-
],
|
|
211
|
-
})
|
|
212
|
-
}
|
|
213
|
-
>
|
|
214
|
-
{t('card.add')} · {t('card.providers')}
|
|
215
|
-
</button>
|
|
216
|
-
</>
|
|
194
|
+
<CredentialRow t={t} presetId={provider.id} disabled={disabled} />
|
|
195
|
+
</>
|
|
196
|
+
)}
|
|
197
|
+
/>
|
|
198
|
+
</Section>
|
|
217
199
|
)
|
|
218
200
|
}
|
|
219
201
|
|
|
220
202
|
export function PromptPresetSection(props: PresetSectionsProps): ReactElement {
|
|
221
203
|
const { t, draft, disabled, onPatch, onPatchPrompt } = props
|
|
222
204
|
return (
|
|
223
|
-
|
|
224
|
-
<
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
205
|
+
<Section prefix="imsc" title={t('card.prompts')} description={t('card.promptsHint')}>
|
|
206
|
+
<ListEditor
|
|
207
|
+
prefix="imsc"
|
|
208
|
+
items={draft.prompts}
|
|
209
|
+
labels={listLabels(t)}
|
|
210
|
+
disabled={disabled}
|
|
211
|
+
titleOf={(prompt) => prompt.name || '—'}
|
|
212
|
+
create={() => emptyPromptDraft()}
|
|
213
|
+
onChange={(prompts) => onPatch({ prompts })}
|
|
214
|
+
renderItem={(prompt, index) => (
|
|
215
|
+
<>
|
|
216
|
+
<TextField
|
|
217
|
+
prefix="imsc"
|
|
218
|
+
id={`imsc-prname-${index}`}
|
|
219
|
+
label={t('card.field.name')}
|
|
232
220
|
value={prompt.name}
|
|
233
221
|
disabled={disabled}
|
|
234
|
-
|
|
222
|
+
onEdit={(v) => onPatchPrompt(index, { name: v })}
|
|
235
223
|
/>
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
224
|
+
<CodeField
|
|
225
|
+
prefix="imsc"
|
|
226
|
+
id={`imsc-prtext-${index}`}
|
|
227
|
+
label={t('card.field.text')}
|
|
228
|
+
value={prompt.text}
|
|
239
229
|
disabled={disabled}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
className="imsc-textarea"
|
|
247
|
-
aria-label={t('card.field.text')}
|
|
248
|
-
value={prompt.text}
|
|
249
|
-
disabled={disabled}
|
|
250
|
-
onChange={(event) => onPatchPrompt(index, { text: event.target.value })}
|
|
251
|
-
/>
|
|
252
|
-
</div>
|
|
253
|
-
))}
|
|
254
|
-
<button
|
|
255
|
-
type="button"
|
|
256
|
-
className="imsc-btn imsc-btnGhost"
|
|
257
|
-
disabled={disabled}
|
|
258
|
-
onClick={() => onPatch({ prompts: [...draft.prompts, emptyPromptDraft()] })}
|
|
259
|
-
>
|
|
260
|
-
{t('card.add')} · {t('card.prompts')}
|
|
261
|
-
</button>
|
|
262
|
-
</>
|
|
230
|
+
onEdit={(v) => onPatchPrompt(index, { text: v })}
|
|
231
|
+
/>
|
|
232
|
+
</>
|
|
233
|
+
)}
|
|
234
|
+
/>
|
|
235
|
+
</Section>
|
|
263
236
|
)
|
|
264
237
|
}
|
|
265
238
|
|
|
266
239
|
export function ParamPresetSection(props: PresetSectionsProps): ReactElement {
|
|
267
240
|
const { t, draft, disabled, onPatch, onPatchParam } = props
|
|
241
|
+
const endpoints = [
|
|
242
|
+
{ value: 'generation', label: t('tab.generation') },
|
|
243
|
+
{ value: 'edit', label: t('tab.edit') },
|
|
244
|
+
]
|
|
268
245
|
return (
|
|
269
|
-
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
246
|
+
<Section prefix="imsc" title={t('card.params')} description={t('card.paramsHint')}>
|
|
247
|
+
<ListEditor
|
|
248
|
+
prefix="imsc"
|
|
249
|
+
items={draft.params}
|
|
250
|
+
labels={listLabels(t)}
|
|
251
|
+
disabled={disabled}
|
|
252
|
+
titleOf={(param) => param.name || '—'}
|
|
253
|
+
create={() => emptyParamDraft()}
|
|
254
|
+
onChange={(params) => onPatch({ params })}
|
|
255
|
+
renderItem={(param, index) => (
|
|
256
|
+
<>
|
|
257
|
+
<TextField
|
|
258
|
+
prefix="imsc"
|
|
259
|
+
id={`imsc-paname-${index}`}
|
|
260
|
+
label={t('card.field.name')}
|
|
278
261
|
value={param.name}
|
|
279
262
|
disabled={disabled}
|
|
280
|
-
|
|
263
|
+
onEdit={(v) => onPatchParam(index, { name: v })}
|
|
281
264
|
/>
|
|
282
|
-
<
|
|
283
|
-
|
|
284
|
-
|
|
265
|
+
<SelectField
|
|
266
|
+
prefix="imsc"
|
|
267
|
+
id={`imsc-paendpoint-${index}`}
|
|
268
|
+
label={t('card.field.endpoint')}
|
|
285
269
|
value={param.endpoint}
|
|
270
|
+
options={endpoints}
|
|
286
271
|
disabled={disabled}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
className="imsc-btn imsc-btnGhost"
|
|
272
|
+
onEdit={(v) => onPatchParam(index, { endpoint: v })}
|
|
273
|
+
/>
|
|
274
|
+
<CodeField
|
|
275
|
+
prefix="imsc"
|
|
276
|
+
id={`imsc-paspecs-${index}`}
|
|
277
|
+
label={t('card.field.paramSpecs')}
|
|
278
|
+
value={param.specsText}
|
|
295
279
|
disabled={disabled}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
className="imsc-textarea"
|
|
303
|
-
aria-label={t('card.field.paramSpecs')}
|
|
304
|
-
placeholder='{ "size": { "enabled": true, "value": "1024x1024" } }'
|
|
305
|
-
value={param.specsText}
|
|
306
|
-
disabled={disabled}
|
|
307
|
-
onChange={(event) => onPatchParam(index, { specsText: event.target.value })}
|
|
308
|
-
/>
|
|
309
|
-
</div>
|
|
310
|
-
))}
|
|
311
|
-
<button
|
|
312
|
-
type="button"
|
|
313
|
-
className="imsc-btn imsc-btnGhost"
|
|
314
|
-
disabled={disabled}
|
|
315
|
-
onClick={() => onPatch({ params: [...draft.params, emptyParamDraft()] })}
|
|
316
|
-
>
|
|
317
|
-
{t('card.add')} · {t('card.params')}
|
|
318
|
-
</button>
|
|
319
|
-
</>
|
|
280
|
+
onEdit={(v) => onPatchParam(index, { specsText: v })}
|
|
281
|
+
/>
|
|
282
|
+
</>
|
|
283
|
+
)}
|
|
284
|
+
/>
|
|
285
|
+
</Section>
|
|
320
286
|
)
|
|
321
287
|
}
|