@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/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.20";
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 prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
1325
- if (prompt === "") {
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
- if (prompt.length > 2e3) {
1577
+ try {
1334
1578
  writeJson(res, 200, {
1335
- ok: false,
1336
- code: "bad-request",
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.20",
4
+ "version": "1.1.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {