@dickpy/dsh-imagegen 1.0.20 → 1.1.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 +19 -8
- package/docs/images/multi-model-comparison.png +0 -0
- package/lib/client.js +1268 -461
- package/lib/client.js.map +1 -1
- package/lib/index.js +376 -56
- package/package.json +1 -1
- package/src/client/ImageGenPanel.tsx +306 -27
- package/src/client/SettingsCard.tsx +79 -0
- package/src/client/api.ts +37 -1
- package/src/client/locales.ts +110 -2
- package/src/client/panel.module.css +129 -1
- package/src/client/settings-card.module.css +42 -0
- package/src/client/settings-scope.ts +22 -1
- package/src/engine.ts +4 -3
- package/src/gallery-store.ts +15 -1
- package/src/index.ts +24 -1
- package/src/prompt-enhancer.ts +67 -0
- package/src/protocol.ts +32 -2
- package/src/routes.ts +133 -39
- package/src/task-queue.ts +70 -0
package/lib/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { spawn } from "node:child_process";
|
|
|
15
15
|
/** Settings namespace this plugin owns (host settings seam + bridge). */
|
|
16
16
|
const IMAGEGEN_SETTINGS_NAMESPACE = "dsh-imagegen";
|
|
17
17
|
/** Published package version shared by the host updater and the client UI. */
|
|
18
|
-
const PLUGIN_VERSION = "1.0
|
|
18
|
+
const PLUGIN_VERSION = "1.1.0";
|
|
19
19
|
/** Same-origin route family (loopback-only, mirroring the dsh-ssh fence). */
|
|
20
20
|
const SETTINGS_API = {
|
|
21
21
|
describe: "/api/dsh-imagegen/settings/describe",
|
|
@@ -23,6 +23,18 @@ const SETTINGS_API = {
|
|
|
23
23
|
};
|
|
24
24
|
/** The image-generation proxy route. */
|
|
25
25
|
const GENERATE_API = "/api/dsh-imagegen/generate";
|
|
26
|
+
/** Host-mediated OpenAI-compatible prompt enhancement endpoints. */
|
|
27
|
+
const PROMPT_ENHANCE_API = {
|
|
28
|
+
models: "/api/dsh-imagegen/prompt-enhance/models",
|
|
29
|
+
enhance: "/api/dsh-imagegen/prompt-enhance"
|
|
30
|
+
};
|
|
31
|
+
/** Host-resident generation queue endpoints. */
|
|
32
|
+
const TASK_API = {
|
|
33
|
+
submit: "/api/dsh-imagegen/tasks/submit",
|
|
34
|
+
list: "/api/dsh-imagegen/tasks/list",
|
|
35
|
+
cancel: "/api/dsh-imagegen/tasks/cancel",
|
|
36
|
+
retry: "/api/dsh-imagegen/tasks/retry"
|
|
37
|
+
};
|
|
26
38
|
/** Host-mediated GitHub Release update routes. */
|
|
27
39
|
const UPDATE_API = {
|
|
28
40
|
check: "/api/dsh-imagegen/update/check",
|
|
@@ -51,6 +63,7 @@ const GALLERY_API = {
|
|
|
51
63
|
append: "/api/dsh-imagegen/gallery/append",
|
|
52
64
|
remove: "/api/dsh-imagegen/gallery/remove",
|
|
53
65
|
clear: "/api/dsh-imagegen/gallery/clear",
|
|
66
|
+
tags: "/api/dsh-imagegen/gallery/tags",
|
|
54
67
|
image: "/api/dsh-imagegen/gallery/image"
|
|
55
68
|
};
|
|
56
69
|
/**
|
|
@@ -217,7 +230,7 @@ async function normalizeItem(item, upstream) {
|
|
|
217
230
|
* Issue one single-image request (never sends `n`). The response is kept as a
|
|
218
231
|
* list so a gateway that happens to return several images per call still works.
|
|
219
232
|
*/
|
|
220
|
-
async function requestOneImage(baseUrl, upstream, request, params) {
|
|
233
|
+
async function requestOneImage(baseUrl, upstream, request, params, signal) {
|
|
221
234
|
const headers = { authorization: `Bearer ${upstream.apiKey.trim()}` };
|
|
222
235
|
let body;
|
|
223
236
|
if (request.mode === "edit") {
|
|
@@ -266,7 +279,7 @@ async function requestOneImage(baseUrl, upstream, request, params) {
|
|
|
266
279
|
method: "POST",
|
|
267
280
|
headers,
|
|
268
281
|
body,
|
|
269
|
-
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS)
|
|
282
|
+
signal: signal === void 0 ? AbortSignal.timeout(UPSTREAM_TIMEOUT_MS) : AbortSignal.any([signal, AbortSignal.timeout(UPSTREAM_TIMEOUT_MS)])
|
|
270
283
|
});
|
|
271
284
|
} catch (error) {
|
|
272
285
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -295,13 +308,13 @@ async function requestOneImage(baseUrl, upstream, request, params) {
|
|
|
295
308
|
* parameter is never sent, because Responses-API-based gateways reject it as
|
|
296
309
|
* `tools[0].n`), then the results are flattened in order.
|
|
297
310
|
*/
|
|
298
|
-
async function generateImage(upstream, request) {
|
|
311
|
+
async function generateImage(upstream, request, options = {}) {
|
|
299
312
|
const baseUrl = upstream.apiUrl.trim().replace(/\/+$/, "");
|
|
300
313
|
if (baseUrl === "") throw new ImageGenError("api_url 未配置:请先在「设置 → 插件 → 可配置」中填写", "config-missing");
|
|
301
314
|
if (upstream.apiKey.trim() === "") throw new ImageGenError("api_key 未配置:请先在「设置 → 插件 → 可配置」中填写", "config-missing");
|
|
302
315
|
const params = effectiveParams(request);
|
|
303
316
|
const count = effectiveCount(request);
|
|
304
|
-
return { images: (await Promise.all(Array.from({ length: count }, () => requestOneImage(baseUrl, upstream, request, params)))).flat() };
|
|
317
|
+
return { images: (await Promise.all(Array.from({ length: count }, () => requestOneImage(baseUrl, upstream, request, params, options.signal)))).flat() };
|
|
305
318
|
}
|
|
306
319
|
/** Human-readable failure message from an upstream error payload. */
|
|
307
320
|
function upstreamMessage(payload, status) {
|
|
@@ -327,6 +340,128 @@ function extensionOf$2(mime) {
|
|
|
327
340
|
}
|
|
328
341
|
}
|
|
329
342
|
//#endregion
|
|
343
|
+
//#region src/prompt-enhancer.ts
|
|
344
|
+
function endpoint(base, suffix) {
|
|
345
|
+
return `${base.replace(/\/+$/, "")}${suffix}`;
|
|
346
|
+
}
|
|
347
|
+
function headers(apiKey) {
|
|
348
|
+
return {
|
|
349
|
+
"content-type": "application/json",
|
|
350
|
+
...apiKey.trim() === "" ? {} : { authorization: `Bearer ${apiKey.trim()}` }
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
async function responseJson(response) {
|
|
354
|
+
const body = await response.json().catch(() => void 0);
|
|
355
|
+
if (!response.ok || body === void 0 || body === null || typeof body !== "object") {
|
|
356
|
+
const message = body !== null && typeof body === "object" && typeof body.error?.message === "string" ? body.error.message : `HTTP ${response.status}`;
|
|
357
|
+
throw new Error(message);
|
|
358
|
+
}
|
|
359
|
+
return body;
|
|
360
|
+
}
|
|
361
|
+
/** List chat models exposed by an OpenAI-compatible endpoint. */
|
|
362
|
+
async function listPromptModels(config) {
|
|
363
|
+
if (config.apiUrl.trim() === "") throw new Error("prompt enhancement API URL is required");
|
|
364
|
+
const body = await responseJson(await fetch(endpoint(config.apiUrl, "/models"), { headers: headers(config.apiKey) }));
|
|
365
|
+
return (Array.isArray(body.data) ? body.data : []).flatMap((item) => item !== null && typeof item === "object" && typeof item.id === "string" ? [item.id] : []).sort((a, b) => a.localeCompare(b));
|
|
366
|
+
}
|
|
367
|
+
/** Expand a concise image request into a production-ready image prompt. */
|
|
368
|
+
async function enhancePrompt(config, prompt) {
|
|
369
|
+
if (config.apiUrl.trim() === "" || config.model.trim() === "") throw new Error("prompt enhancement model is not configured");
|
|
370
|
+
const body = await responseJson(await fetch(endpoint(config.apiUrl, "/chat/completions"), {
|
|
371
|
+
method: "POST",
|
|
372
|
+
headers: headers(config.apiKey),
|
|
373
|
+
body: JSON.stringify({
|
|
374
|
+
model: config.model.trim(),
|
|
375
|
+
temperature: .7,
|
|
376
|
+
messages: [{
|
|
377
|
+
role: "system",
|
|
378
|
+
content: "You are an expert image-prompt editor. Expand the user request into one vivid, specific image-generation prompt. Preserve intent and language. Add only useful visual detail: subject, composition, lighting, materials, color, camera/style and quality. Return only the finished prompt, with no preface or markdown."
|
|
379
|
+
}, {
|
|
380
|
+
role: "user",
|
|
381
|
+
content: prompt
|
|
382
|
+
}]
|
|
383
|
+
})
|
|
384
|
+
}));
|
|
385
|
+
const choices = Array.isArray(body.choices) ? body.choices : [];
|
|
386
|
+
const content = choices[0] !== null && typeof choices[0] === "object" ? choices[0].message?.content : void 0;
|
|
387
|
+
if (typeof content !== "string" || content.trim() === "") throw new Error("chat model returned an empty prompt");
|
|
388
|
+
return content.trim();
|
|
389
|
+
}
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/task-queue.ts
|
|
392
|
+
/** In-memory, host-resident image generation queue. */
|
|
393
|
+
var GenerationTaskQueue = class {
|
|
394
|
+
run;
|
|
395
|
+
tasks = [];
|
|
396
|
+
controllers = /* @__PURE__ */ new Map();
|
|
397
|
+
running = false;
|
|
398
|
+
constructor(run) {
|
|
399
|
+
this.run = run;
|
|
400
|
+
}
|
|
401
|
+
list() {
|
|
402
|
+
return this.tasks.map((task) => ({
|
|
403
|
+
...task,
|
|
404
|
+
request: { ...task.request },
|
|
405
|
+
...task.result === void 0 ? {} : { result: task.result }
|
|
406
|
+
}));
|
|
407
|
+
}
|
|
408
|
+
submit(request) {
|
|
409
|
+
const task = {
|
|
410
|
+
id: randomUUID(),
|
|
411
|
+
request: { ...request },
|
|
412
|
+
status: "queued",
|
|
413
|
+
createdAt: Date.now()
|
|
414
|
+
};
|
|
415
|
+
this.tasks.unshift(task);
|
|
416
|
+
this.drain();
|
|
417
|
+
return task;
|
|
418
|
+
}
|
|
419
|
+
cancel(id) {
|
|
420
|
+
const task = this.tasks.find((item) => item.id === id);
|
|
421
|
+
if (task === void 0 || task.status === "completed" || task.status === "failed" || task.status === "cancelled") return task;
|
|
422
|
+
task.status = "cancelled";
|
|
423
|
+
task.finishedAt = Date.now();
|
|
424
|
+
this.controllers.get(id)?.abort();
|
|
425
|
+
return task;
|
|
426
|
+
}
|
|
427
|
+
retry(id) {
|
|
428
|
+
const previous = this.tasks.find((item) => item.id === id);
|
|
429
|
+
return previous === void 0 ? void 0 : this.submit(previous.request);
|
|
430
|
+
}
|
|
431
|
+
async drain() {
|
|
432
|
+
if (this.running) return;
|
|
433
|
+
this.running = true;
|
|
434
|
+
try {
|
|
435
|
+
for (;;) {
|
|
436
|
+
const task = this.tasks.find((item) => item.status === "queued");
|
|
437
|
+
if (task === void 0) return;
|
|
438
|
+
task.status = "running";
|
|
439
|
+
task.startedAt = Date.now();
|
|
440
|
+
const controller = new AbortController();
|
|
441
|
+
this.controllers.set(task.id, controller);
|
|
442
|
+
try {
|
|
443
|
+
const result = await this.run(task.request, controller.signal);
|
|
444
|
+
if (this.tasks.find((item) => item.id === task.id)?.status !== "cancelled") {
|
|
445
|
+
task.status = "completed";
|
|
446
|
+
task.result = result;
|
|
447
|
+
task.finishedAt = Date.now();
|
|
448
|
+
}
|
|
449
|
+
} catch (error) {
|
|
450
|
+
if (this.tasks.find((item) => item.id === task.id)?.status !== "cancelled") {
|
|
451
|
+
task.status = "failed";
|
|
452
|
+
task.error = error instanceof Error ? error.message : String(error);
|
|
453
|
+
task.finishedAt = Date.now();
|
|
454
|
+
}
|
|
455
|
+
} finally {
|
|
456
|
+
this.controllers.delete(task.id);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
} finally {
|
|
460
|
+
this.running = false;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
//#endregion
|
|
330
465
|
//#region src/history-store.ts
|
|
331
466
|
/**
|
|
332
467
|
* Host-persisted generation history: images are stored as individual files
|
|
@@ -616,7 +751,8 @@ function toWire(entry) {
|
|
|
616
751
|
mime: image.mime,
|
|
617
752
|
...image.revisedPrompt === void 0 ? {} : { revisedPrompt: image.revisedPrompt }
|
|
618
753
|
})),
|
|
619
|
-
...entry.refName === void 0 ? {} : { refName: entry.refName }
|
|
754
|
+
...entry.refName === void 0 ? {} : { refName: entry.refName },
|
|
755
|
+
...entry.tags === void 0 ? {} : { tags: entry.tags }
|
|
620
756
|
};
|
|
621
757
|
}
|
|
622
758
|
/** List the persisted gallery, newest first, as wire entries. */
|
|
@@ -686,6 +822,17 @@ async function removeGallery(id) {
|
|
|
686
822
|
return kept.map(toWire);
|
|
687
823
|
});
|
|
688
824
|
}
|
|
825
|
+
/** Replace the user-managed labels for one gallery entry. */
|
|
826
|
+
async function updateGalleryTags(id, tags) {
|
|
827
|
+
return mutateGallery(async () => {
|
|
828
|
+
const normalized = [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))].slice(0, 20);
|
|
829
|
+
const entries = await readIndex();
|
|
830
|
+
const target = entries.find((entry) => entry.id === id);
|
|
831
|
+
if (target !== void 0) target.tags = normalized;
|
|
832
|
+
await writeIndex(entries);
|
|
833
|
+
return entries.map(toWire);
|
|
834
|
+
});
|
|
835
|
+
}
|
|
689
836
|
/** Remove every entry (and all image files). */
|
|
690
837
|
async function clearGallery() {
|
|
691
838
|
return mutateGallery(async () => {
|
|
@@ -1134,6 +1281,21 @@ async function readJsonBody(req, maxBytes = MAX_JSON_BODY_BYTES) {
|
|
|
1134
1281
|
function messageOf(error) {
|
|
1135
1282
|
return error instanceof Error ? error.message : String(error);
|
|
1136
1283
|
}
|
|
1284
|
+
function parseGenerateRequest(body) {
|
|
1285
|
+
const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
|
|
1286
|
+
if (prompt === "") return void 0;
|
|
1287
|
+
return {
|
|
1288
|
+
mode: body.mode === "edit" ? "edit" : "text",
|
|
1289
|
+
model: typeof body.model === "string" ? body.model : "gpt-image-2",
|
|
1290
|
+
prompt,
|
|
1291
|
+
size: typeof body.size === "string" ? body.size : "auto",
|
|
1292
|
+
quality: typeof body.quality === "string" ? body.quality : "auto",
|
|
1293
|
+
n: typeof body.n === "number" ? body.n : 1,
|
|
1294
|
+
detail: typeof body.detail === "string" ? body.detail : "",
|
|
1295
|
+
...typeof body.image === "string" && body.image !== "" ? { image: body.image } : {},
|
|
1296
|
+
...typeof body.refName === "string" && body.refName !== "" ? { refName: body.refName } : {}
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1137
1299
|
/** Validate a submitted history entry (images carry base64). */
|
|
1138
1300
|
function parseHistoryEntryInput(body) {
|
|
1139
1301
|
const raw = body.entry;
|
|
@@ -1228,6 +1390,7 @@ function makeRoutes(deps) {
|
|
|
1228
1390
|
append: appendGallery,
|
|
1229
1391
|
remove: removeGallery,
|
|
1230
1392
|
clear: clearGallery,
|
|
1393
|
+
updateTags: updateGalleryTags,
|
|
1231
1394
|
readImage: readGalleryImage
|
|
1232
1395
|
};
|
|
1233
1396
|
const templates = deps.templates ?? {
|
|
@@ -1235,6 +1398,39 @@ function makeRoutes(deps) {
|
|
|
1235
1398
|
refresh: refreshTemplates,
|
|
1236
1399
|
readImage: readTemplateImage
|
|
1237
1400
|
};
|
|
1401
|
+
const resolvePrompt = deps.resolvePrompt ?? (() => ({
|
|
1402
|
+
apiUrl: "",
|
|
1403
|
+
apiKey: "",
|
|
1404
|
+
model: ""
|
|
1405
|
+
}));
|
|
1406
|
+
const runGeneration = async (request, signal) => {
|
|
1407
|
+
const result = await generateImage(deps.resolve(), request, { signal });
|
|
1408
|
+
try {
|
|
1409
|
+
const entries = await history.append({
|
|
1410
|
+
id: randomUUID(),
|
|
1411
|
+
createdAt: Date.now(),
|
|
1412
|
+
mode: request.mode,
|
|
1413
|
+
model: request.model,
|
|
1414
|
+
prompt: request.prompt,
|
|
1415
|
+
size: request.size,
|
|
1416
|
+
quality: request.quality,
|
|
1417
|
+
detail: request.detail,
|
|
1418
|
+
n: request.n,
|
|
1419
|
+
images: result.images,
|
|
1420
|
+
...request.refName === void 0 ? {} : { refName: request.refName }
|
|
1421
|
+
});
|
|
1422
|
+
return {
|
|
1423
|
+
...result,
|
|
1424
|
+
history: entries
|
|
1425
|
+
};
|
|
1426
|
+
} catch (error) {
|
|
1427
|
+
return {
|
|
1428
|
+
...result,
|
|
1429
|
+
historyError: messageOf(error)
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
const taskQueue = new GenerationTaskQueue((request, signal) => runGeneration(request, signal));
|
|
1238
1434
|
const guard = (req, res, method) => {
|
|
1239
1435
|
if (!isLoopbackRequest(req)) {
|
|
1240
1436
|
writeJson(res, 403, { error: "forbidden: loopback-only" });
|
|
@@ -1247,6 +1443,54 @@ function makeRoutes(deps) {
|
|
|
1247
1443
|
return true;
|
|
1248
1444
|
};
|
|
1249
1445
|
return [
|
|
1446
|
+
{
|
|
1447
|
+
kind: "exact",
|
|
1448
|
+
path: PROMPT_ENHANCE_API.models,
|
|
1449
|
+
handler: async (req, res) => {
|
|
1450
|
+
if (!guard(req, res, "POST")) return;
|
|
1451
|
+
try {
|
|
1452
|
+
writeJson(res, 200, {
|
|
1453
|
+
ok: true,
|
|
1454
|
+
models: await listPromptModels(resolvePrompt())
|
|
1455
|
+
});
|
|
1456
|
+
} catch (error) {
|
|
1457
|
+
writeJson(res, 200, {
|
|
1458
|
+
ok: false,
|
|
1459
|
+
code: "prompt-models-failed",
|
|
1460
|
+
message: messageOf(error)
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
},
|
|
1465
|
+
{
|
|
1466
|
+
kind: "exact",
|
|
1467
|
+
path: PROMPT_ENHANCE_API.enhance,
|
|
1468
|
+
handler: async (req, res) => {
|
|
1469
|
+
if (!guard(req, res, "POST")) return;
|
|
1470
|
+
const body = await readJsonBody(req);
|
|
1471
|
+
const prompt = typeof body?.prompt === "string" ? body.prompt.trim() : "";
|
|
1472
|
+
if (prompt === "") {
|
|
1473
|
+
writeJson(res, 200, {
|
|
1474
|
+
ok: false,
|
|
1475
|
+
code: "bad-request",
|
|
1476
|
+
message: "prompt is required"
|
|
1477
|
+
});
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
try {
|
|
1481
|
+
writeJson(res, 200, {
|
|
1482
|
+
ok: true,
|
|
1483
|
+
prompt: await enhancePrompt(resolvePrompt(), prompt)
|
|
1484
|
+
});
|
|
1485
|
+
} catch (error) {
|
|
1486
|
+
writeJson(res, 200, {
|
|
1487
|
+
ok: false,
|
|
1488
|
+
code: "prompt-enhance-failed",
|
|
1489
|
+
message: messageOf(error)
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
},
|
|
1250
1494
|
{
|
|
1251
1495
|
kind: "exact",
|
|
1252
1496
|
path: SETTINGS_API.describe,
|
|
@@ -1321,8 +1565,8 @@ function makeRoutes(deps) {
|
|
|
1321
1565
|
});
|
|
1322
1566
|
return;
|
|
1323
1567
|
}
|
|
1324
|
-
const
|
|
1325
|
-
if (
|
|
1568
|
+
const request = parseGenerateRequest(body);
|
|
1569
|
+
if (request === void 0) {
|
|
1326
1570
|
writeJson(res, 200, {
|
|
1327
1571
|
ok: false,
|
|
1328
1572
|
code: "bad-request",
|
|
@@ -1330,53 +1574,11 @@ function makeRoutes(deps) {
|
|
|
1330
1574
|
});
|
|
1331
1575
|
return;
|
|
1332
1576
|
}
|
|
1333
|
-
|
|
1577
|
+
try {
|
|
1334
1578
|
writeJson(res, 200, {
|
|
1335
|
-
ok:
|
|
1336
|
-
|
|
1337
|
-
message: "prompt exceeds 2000 characters"
|
|
1579
|
+
ok: true,
|
|
1580
|
+
...await runGeneration(request)
|
|
1338
1581
|
});
|
|
1339
|
-
return;
|
|
1340
|
-
}
|
|
1341
|
-
const request = {
|
|
1342
|
-
mode: body.mode === "edit" ? "edit" : "text",
|
|
1343
|
-
model: typeof body.model === "string" ? body.model : "gpt-image-2",
|
|
1344
|
-
prompt,
|
|
1345
|
-
size: typeof body.size === "string" ? body.size : "auto",
|
|
1346
|
-
quality: typeof body.quality === "string" ? body.quality : "auto",
|
|
1347
|
-
n: typeof body.n === "number" ? body.n : 1,
|
|
1348
|
-
detail: typeof body.detail === "string" ? body.detail : "",
|
|
1349
|
-
...typeof body.image === "string" && body.image !== "" ? { image: body.image } : {},
|
|
1350
|
-
...typeof body.refName === "string" && body.refName !== "" ? { refName: body.refName } : {}
|
|
1351
|
-
};
|
|
1352
|
-
try {
|
|
1353
|
-
const result = await generateImage(deps.resolve(), request);
|
|
1354
|
-
try {
|
|
1355
|
-
const entries = await history.append({
|
|
1356
|
-
id: randomUUID(),
|
|
1357
|
-
createdAt: Date.now(),
|
|
1358
|
-
mode: request.mode,
|
|
1359
|
-
model: request.model,
|
|
1360
|
-
prompt: request.prompt,
|
|
1361
|
-
size: request.size,
|
|
1362
|
-
quality: request.quality,
|
|
1363
|
-
detail: request.detail,
|
|
1364
|
-
n: request.n,
|
|
1365
|
-
images: result.images,
|
|
1366
|
-
...request.refName === void 0 ? {} : { refName: request.refName }
|
|
1367
|
-
});
|
|
1368
|
-
writeJson(res, 200, {
|
|
1369
|
-
ok: true,
|
|
1370
|
-
...result,
|
|
1371
|
-
history: entries
|
|
1372
|
-
});
|
|
1373
|
-
} catch (error) {
|
|
1374
|
-
writeJson(res, 200, {
|
|
1375
|
-
ok: true,
|
|
1376
|
-
...result,
|
|
1377
|
-
historyError: messageOf(error)
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
1582
|
} catch (error) {
|
|
1381
1583
|
const message = error instanceof Error ? error.message : String(error);
|
|
1382
1584
|
writeJson(res, 200, {
|
|
@@ -1387,6 +1589,80 @@ function makeRoutes(deps) {
|
|
|
1387
1589
|
}
|
|
1388
1590
|
}
|
|
1389
1591
|
},
|
|
1592
|
+
{
|
|
1593
|
+
kind: "exact",
|
|
1594
|
+
path: TASK_API.submit,
|
|
1595
|
+
handler: async (req, res) => {
|
|
1596
|
+
if (!guard(req, res, "POST")) return;
|
|
1597
|
+
const body = await readJsonBody(req);
|
|
1598
|
+
const request = body === void 0 ? void 0 : parseGenerateRequest(body);
|
|
1599
|
+
if (request === void 0) {
|
|
1600
|
+
writeJson(res, 200, {
|
|
1601
|
+
ok: false,
|
|
1602
|
+
code: "bad-request",
|
|
1603
|
+
message: "prompt is required"
|
|
1604
|
+
});
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
writeJson(res, 200, {
|
|
1608
|
+
ok: true,
|
|
1609
|
+
task: taskQueue.submit(request)
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
kind: "exact",
|
|
1615
|
+
path: TASK_API.list,
|
|
1616
|
+
handler: async (req, res) => {
|
|
1617
|
+
if (!guard(req, res, "POST")) return;
|
|
1618
|
+
writeJson(res, 200, {
|
|
1619
|
+
ok: true,
|
|
1620
|
+
tasks: taskQueue.list()
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
},
|
|
1624
|
+
{
|
|
1625
|
+
kind: "exact",
|
|
1626
|
+
path: TASK_API.cancel,
|
|
1627
|
+
handler: async (req, res) => {
|
|
1628
|
+
if (!guard(req, res, "POST")) return;
|
|
1629
|
+
const body = await readJsonBody(req);
|
|
1630
|
+
const task = typeof body?.id === "string" ? taskQueue.cancel(body.id) : void 0;
|
|
1631
|
+
if (task === void 0) {
|
|
1632
|
+
writeJson(res, 200, {
|
|
1633
|
+
ok: false,
|
|
1634
|
+
code: "not-found",
|
|
1635
|
+
message: "task not found"
|
|
1636
|
+
});
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
writeJson(res, 200, {
|
|
1640
|
+
ok: true,
|
|
1641
|
+
task
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
},
|
|
1645
|
+
{
|
|
1646
|
+
kind: "exact",
|
|
1647
|
+
path: TASK_API.retry,
|
|
1648
|
+
handler: async (req, res) => {
|
|
1649
|
+
if (!guard(req, res, "POST")) return;
|
|
1650
|
+
const body = await readJsonBody(req);
|
|
1651
|
+
const task = typeof body?.id === "string" ? taskQueue.retry(body.id) : void 0;
|
|
1652
|
+
if (task === void 0) {
|
|
1653
|
+
writeJson(res, 200, {
|
|
1654
|
+
ok: false,
|
|
1655
|
+
code: "not-found",
|
|
1656
|
+
message: "task not found"
|
|
1657
|
+
});
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
writeJson(res, 200, {
|
|
1661
|
+
ok: true,
|
|
1662
|
+
task
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
},
|
|
1390
1666
|
{
|
|
1391
1667
|
kind: "exact",
|
|
1392
1668
|
path: UPDATE_API.check,
|
|
@@ -1671,6 +1947,36 @@ function makeRoutes(deps) {
|
|
|
1671
1947
|
}
|
|
1672
1948
|
}
|
|
1673
1949
|
},
|
|
1950
|
+
{
|
|
1951
|
+
kind: "exact",
|
|
1952
|
+
path: GALLERY_API.tags,
|
|
1953
|
+
handler: async (req, res) => {
|
|
1954
|
+
if (!guard(req, res, "POST")) return;
|
|
1955
|
+
const body = await readJsonBody(req);
|
|
1956
|
+
const id = typeof body?.id === "string" ? body.id : "";
|
|
1957
|
+
const tags = Array.isArray(body?.tags) ? body.tags.filter((tag) => typeof tag === "string") : void 0;
|
|
1958
|
+
if (id === "" || tags === void 0 || gallery.updateTags === void 0) {
|
|
1959
|
+
writeJson(res, 200, {
|
|
1960
|
+
ok: false,
|
|
1961
|
+
code: "bad-request",
|
|
1962
|
+
message: "gallery id and tags are required"
|
|
1963
|
+
});
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
try {
|
|
1967
|
+
writeJson(res, 200, {
|
|
1968
|
+
ok: true,
|
|
1969
|
+
entries: await gallery.updateTags(id, tags)
|
|
1970
|
+
});
|
|
1971
|
+
} catch (error) {
|
|
1972
|
+
writeJson(res, 200, {
|
|
1973
|
+
ok: false,
|
|
1974
|
+
code: "gallery-failed",
|
|
1975
|
+
message: messageOf(error)
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
},
|
|
1674
1980
|
{
|
|
1675
1981
|
kind: "exact",
|
|
1676
1982
|
path: GALLERY_API.clear,
|
|
@@ -1802,7 +2108,10 @@ const Config = z.object({
|
|
|
1802
2108
|
enabled: z.boolean().default(true),
|
|
1803
2109
|
announceToAgent: z.boolean().default(true),
|
|
1804
2110
|
apiUrl: z.string().default(""),
|
|
1805
|
-
apiKey: z.string().role("secret").default("")
|
|
2111
|
+
apiKey: z.string().role("secret").default(""),
|
|
2112
|
+
promptApiUrl: z.string().default(""),
|
|
2113
|
+
promptApiKey: z.string().role("secret").default(""),
|
|
2114
|
+
promptModel: z.string().default("")
|
|
1806
2115
|
});
|
|
1807
2116
|
/** Schema defaults, re-read for hand-built contexts (the loader applies them normally). */
|
|
1808
2117
|
const DEFAULT_ENABLED = true;
|
|
@@ -1824,7 +2133,10 @@ function apply(ctx, config) {
|
|
|
1824
2133
|
enabled: value.enabled ?? DEFAULT_ENABLED,
|
|
1825
2134
|
announceToAgent: value.announceToAgent ?? DEFAULT_ANNOUNCE,
|
|
1826
2135
|
apiUrl: value.apiUrl ?? "",
|
|
1827
|
-
apiKey: value.apiKey ?? ""
|
|
2136
|
+
apiKey: value.apiKey ?? "",
|
|
2137
|
+
promptApiUrl: value.promptApiUrl ?? "",
|
|
2138
|
+
promptApiKey: value.promptApiKey ?? "",
|
|
2139
|
+
promptModel: value.promptModel ?? ""
|
|
1828
2140
|
};
|
|
1829
2141
|
};
|
|
1830
2142
|
ctx.inject(["settings"], (sctx) => {
|
|
@@ -1838,6 +2150,14 @@ function apply(ctx, config) {
|
|
|
1838
2150
|
apiUrl: value.apiUrl,
|
|
1839
2151
|
apiKey: value.apiKey
|
|
1840
2152
|
};
|
|
2153
|
+
},
|
|
2154
|
+
resolvePrompt: () => {
|
|
2155
|
+
const value = resolve();
|
|
2156
|
+
return {
|
|
2157
|
+
apiUrl: value.promptApiUrl.trim() || value.apiUrl,
|
|
2158
|
+
apiKey: value.promptApiKey.trim() || value.apiKey,
|
|
2159
|
+
model: value.promptModel
|
|
2160
|
+
};
|
|
1841
2161
|
}
|
|
1842
2162
|
}).map((route) => ctx.webServer.register(route));
|
|
1843
2163
|
return () => {
|
|
@@ -1869,4 +2189,4 @@ function apply(ctx, config) {
|
|
|
1869
2189
|
sync();
|
|
1870
2190
|
}
|
|
1871
2191
|
//#endregion
|
|
1872
|
-
export { CURRENT_VERSION, Config, IMAGEGEN_GUIDANCE, ImageGenError, ImageGenSettingsNamespace, appendGallery, apply, checkForUpdate, clearGallery, clearTemplateMemo, clearUpdateCache, compareVersions, generateImage, inject, installUpdate, listGallery, listTemplates, makeRoutes, name, profileFromProcess, readGalleryImage, readTemplateImage, refreshTemplates, removeGallery };
|
|
2192
|
+
export { CURRENT_VERSION, Config, IMAGEGEN_GUIDANCE, ImageGenError, ImageGenSettingsNamespace, appendGallery, apply, checkForUpdate, clearGallery, clearTemplateMemo, clearUpdateCache, compareVersions, generateImage, inject, installUpdate, listGallery, listTemplates, makeRoutes, name, profileFromProcess, readGalleryImage, readTemplateImage, refreshTemplates, removeGallery, updateGalleryTags };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dickpy/dsh-imagegen",
|
|
3
3
|
"description": "AI 鐢熷浘 (image generation) plugin for the dsh web GUI: text-to-image and image-to-image through a configurable OpenAI-compatible endpoint (gpt-image-2 / grok-imagine-image / dall-e-3, with native xAI Grok Imagine request shaping), with a settings card for api_url / api_key and a sidebar entry opening a split-pane generation studio.",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|