@dickpy/dsh-imagegen 1.4.0 → 1.5.1
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/LICENSE +201 -201
- package/README.md +270 -124
- package/cordis.patch.yml +8 -8
- package/docs/images/ecommerce-mode.png +0 -0
- package/docs/images/image-generation-studio-three-column.png +0 -0
- package/docs/images/imagegen-overview.png +0 -0
- package/docs/videos/agent-chat-edit.gif +0 -0
- package/docs/videos/agent-chat-edit.mp4 +0 -0
- package/lib/client.js +1873 -431
- package/lib/client.js.map +1 -1
- package/lib/index.js +355 -116
- package/package.json +77 -70
- package/src/agent-image-tools.ts +447 -418
- package/src/client/ImageGenPanel.tsx +1243 -348
- package/src/client/SettingsCard.tsx +936 -936
- package/src/client/TemplateLibrary.tsx +336 -336
- package/src/client/api.ts +203 -193
- package/src/client/channels-form.ts +263 -263
- package/src/client/controller.ts +46 -46
- package/src/client/conversation-sync.ts +14 -14
- package/src/client/css-modules.d.ts +5 -5
- package/src/client/helpers.ts +33 -33
- package/src/client/image-toolview.module.css +73 -73
- package/src/client/image-toolview.tsx +34 -28
- package/src/client/index.ts +25 -24
- package/src/client/locales.ts +156 -28
- package/src/client/mount.tsx +117 -117
- package/src/client/panel.module.css +1243 -455
- package/src/client/settings-card.module.css +1023 -1023
- package/src/client/settings-form.ts +337 -336
- package/src/client/settings-scope.ts +302 -298
- package/src/client/sidebar-entry.ts +190 -190
- package/src/client/templates.module.css +453 -453
- package/src/edit-image-command.ts +110 -0
- package/src/engine.ts +520 -520
- package/src/gallery-store.ts +306 -286
- package/src/generation-runtime.ts +84 -79
- package/src/history-store.ts +270 -250
- package/src/image-format.ts +11 -11
- package/src/image-models.ts +19 -19
- package/src/index.ts +337 -318
- package/src/model-catalog.ts +115 -115
- package/src/presets.ts +71 -71
- package/src/prompt-enhancer.ts +137 -137
- package/src/protocol.ts +380 -338
- package/src/routes.ts +966 -916
- package/src/settings-compat.ts +60 -0
- package/src/task-queue.ts +113 -113
- package/src/templates/cases.json +10196 -10196
- package/src/templates-store.ts +278 -278
- package/src/updater.ts +117 -117
- package/docs/images/agent-chat-edit.png +0 -0
- package/docs/images/agent-chat-generate.png +0 -0
- package/docs/images/agent-chat-poster-workflow.png +0 -0
package/lib/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as settingsModule from "@deepseek-ai/dsh-settings";
|
|
2
|
+
import { SettingsConflictError } from "@deepseek-ai/dsh-settings";
|
|
2
3
|
import z from "schemastery";
|
|
3
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
5
|
import { promises } from "node:fs";
|
|
@@ -7,6 +8,29 @@ import path from "node:path";
|
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
import { spawn } from "node:child_process";
|
|
9
10
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
11
|
+
//#region src/settings-compat.ts
|
|
12
|
+
const compatModule = settingsModule;
|
|
13
|
+
/** Brand namespaces where the installed settings package still exposes it. */
|
|
14
|
+
function settingsNamespaceCompat(value) {
|
|
15
|
+
return compatModule.settingsNamespace?.(value) ?? value;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Register an optional settings section across the rc.7 and alpha.2 APIs.
|
|
19
|
+
* rc.7 exposes a module helper; alpha.2 moves the helper onto the provider.
|
|
20
|
+
*/
|
|
21
|
+
function installSettingsSectionCompat(ctx, ns, schema, entry, hooks) {
|
|
22
|
+
const legacyInstaller = compatModule.installSettingsSection;
|
|
23
|
+
if (legacyInstaller !== void 0) {
|
|
24
|
+
legacyInstaller(ctx, ns, schema, entry, hooks);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
ctx.inject(["settings"], (sctx) => {
|
|
28
|
+
const provider = sctx.get("settings");
|
|
29
|
+
if (provider.installSection === void 0) throw new TypeError("dsh-settings does not expose installSection");
|
|
30
|
+
provider.installSection(ctx, ns, schema, entry, hooks);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
10
34
|
//#region src/protocol.ts
|
|
11
35
|
/**
|
|
12
36
|
* Wire contract shared by the host and client halves of dsh-imagegen: the
|
|
@@ -16,7 +40,7 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
|
16
40
|
/** Settings namespace this plugin owns (host settings seam + bridge). */
|
|
17
41
|
const IMAGEGEN_SETTINGS_NAMESPACE = "dsh-imagegen";
|
|
18
42
|
/** Published package version shared by the host updater and the client UI. */
|
|
19
|
-
const PLUGIN_VERSION = "1.
|
|
43
|
+
const PLUGIN_VERSION = "1.5.1";
|
|
20
44
|
/** Same-origin route family (loopback-only, mirroring the dsh-ssh fence). */
|
|
21
45
|
const SETTINGS_API = {
|
|
22
46
|
describe: "/api/dsh-imagegen/settings/describe",
|
|
@@ -35,6 +59,8 @@ const IMAGE_MODEL_API = { models: "/api/dsh-imagegen/image-models" };
|
|
|
35
59
|
const PRESETS_API = "/api/dsh-imagegen/presets";
|
|
36
60
|
/** Loopback-only image reader for Agent tool-result previews. */
|
|
37
61
|
const AGENT_IMAGE_API = "/api/dsh-imagegen/agent-image";
|
|
62
|
+
/** Store the current composer image for the direct edit_image command. */
|
|
63
|
+
const CONVERSATION_IMAGE_API = "/api/dsh-imagegen/conversation-image";
|
|
38
64
|
/**
|
|
39
65
|
* Host-computed per-channel usage counters (generation-count badges in the
|
|
40
66
|
* settings card): entries are tallied from the persisted history and gallery
|
|
@@ -811,7 +837,7 @@ async function writeIndex$1(entries) {
|
|
|
811
837
|
function isStoredEntry$1(value) {
|
|
812
838
|
if (value === null || typeof value !== "object") return false;
|
|
813
839
|
const entry = value;
|
|
814
|
-
return typeof entry.id === "string" && typeof entry.createdAt === "number" && (entry.mode === "text" || entry.mode === "edit") && typeof entry.prompt === "string" && Array.isArray(entry.images) && entry.images.every((image) => {
|
|
840
|
+
return typeof entry.id === "string" && typeof entry.createdAt === "number" && (entry.mode === "text" || entry.mode === "edit") && (entry.workflow === void 0 || entry.workflow === "ecommerce") && (entry.projectId === void 0 || typeof entry.projectId === "string") && (entry.projectName === void 0 || typeof entry.projectName === "string") && (entry.slotKey === void 0 || typeof entry.slotKey === "string") && (entry.slotLabel === void 0 || typeof entry.slotLabel === "string") && typeof entry.prompt === "string" && Array.isArray(entry.images) && entry.images.every((image) => {
|
|
815
841
|
if (image === null || typeof image !== "object") return false;
|
|
816
842
|
const record = image;
|
|
817
843
|
return typeof record.file === "string" && typeof record.mime === "string";
|
|
@@ -844,7 +870,12 @@ function toWire$1(entry) {
|
|
|
844
870
|
...entry.channel === void 0 ? {} : { channel: entry.channel },
|
|
845
871
|
...entry.channelId === void 0 ? {} : { channelId: entry.channelId },
|
|
846
872
|
...entry.comparisonId === void 0 ? {} : { comparisonId: entry.comparisonId },
|
|
847
|
-
...entry.comparisonModels === void 0 ? {} : { comparisonModels: entry.comparisonModels }
|
|
873
|
+
...entry.comparisonModels === void 0 ? {} : { comparisonModels: entry.comparisonModels },
|
|
874
|
+
...entry.workflow === void 0 ? {} : { workflow: entry.workflow },
|
|
875
|
+
...entry.projectId === void 0 ? {} : { projectId: entry.projectId },
|
|
876
|
+
...entry.projectName === void 0 ? {} : { projectName: entry.projectName },
|
|
877
|
+
...entry.slotKey === void 0 ? {} : { slotKey: entry.slotKey },
|
|
878
|
+
...entry.slotLabel === void 0 ? {} : { slotLabel: entry.slotLabel }
|
|
848
879
|
};
|
|
849
880
|
}
|
|
850
881
|
/** List the persisted history, newest first, as wire entries. */
|
|
@@ -887,7 +918,12 @@ async function appendHistory(input) {
|
|
|
887
918
|
...input.channelId === void 0 ? {} : { channelId: input.channelId },
|
|
888
919
|
...input.channel === void 0 ? {} : { channel: input.channel },
|
|
889
920
|
...input.comparisonId === void 0 ? {} : { comparisonId: input.comparisonId },
|
|
890
|
-
...input.comparisonModels === void 0 ? {} : { comparisonModels: input.comparisonModels }
|
|
921
|
+
...input.comparisonModels === void 0 ? {} : { comparisonModels: input.comparisonModels },
|
|
922
|
+
...input.workflow === void 0 ? {} : { workflow: input.workflow },
|
|
923
|
+
...input.projectId === void 0 ? {} : { projectId: input.projectId },
|
|
924
|
+
...input.projectName === void 0 ? {} : { projectName: input.projectName },
|
|
925
|
+
...input.slotKey === void 0 ? {} : { slotKey: input.slotKey },
|
|
926
|
+
...input.slotLabel === void 0 ? {} : { slotLabel: input.slotLabel }
|
|
891
927
|
}, ...await readIndex$1()];
|
|
892
928
|
const kept = merged.slice(0, 50);
|
|
893
929
|
for (const dropped of merged.slice(50)) await removeEntryFiles$1(dropped);
|
|
@@ -1075,7 +1111,12 @@ var ImageGenerationRuntime = class {
|
|
|
1075
1111
|
...request.channelId === void 0 ? {} : { channelId: request.channelId },
|
|
1076
1112
|
...request.channel === void 0 ? {} : { channel: request.channel },
|
|
1077
1113
|
...request.comparisonId === void 0 ? {} : { comparisonId: request.comparisonId },
|
|
1078
|
-
...request.comparisonModels === void 0 ? {} : { comparisonModels: request.comparisonModels }
|
|
1114
|
+
...request.comparisonModels === void 0 ? {} : { comparisonModels: request.comparisonModels },
|
|
1115
|
+
...request.workflow === void 0 ? {} : { workflow: request.workflow },
|
|
1116
|
+
...request.projectId === void 0 ? {} : { projectId: request.projectId },
|
|
1117
|
+
...request.projectName === void 0 ? {} : { projectName: request.projectName },
|
|
1118
|
+
...request.slotKey === void 0 ? {} : { slotKey: request.slotKey },
|
|
1119
|
+
...request.slotLabel === void 0 ? {} : { slotLabel: request.slotLabel }
|
|
1079
1120
|
});
|
|
1080
1121
|
return {
|
|
1081
1122
|
...result,
|
|
@@ -1169,7 +1210,7 @@ async function writeIndex(entries) {
|
|
|
1169
1210
|
function isStoredEntry(value) {
|
|
1170
1211
|
if (value === null || typeof value !== "object") return false;
|
|
1171
1212
|
const entry = value;
|
|
1172
|
-
return typeof entry.id === "string" && typeof entry.createdAt === "number" && (entry.mode === "text" || entry.mode === "edit") && typeof entry.prompt === "string" && Array.isArray(entry.images) && entry.images.every((image) => {
|
|
1213
|
+
return typeof entry.id === "string" && typeof entry.createdAt === "number" && (entry.mode === "text" || entry.mode === "edit") && (entry.workflow === void 0 || entry.workflow === "ecommerce") && (entry.projectId === void 0 || typeof entry.projectId === "string") && (entry.projectName === void 0 || typeof entry.projectName === "string") && (entry.slotKey === void 0 || typeof entry.slotKey === "string") && (entry.slotLabel === void 0 || typeof entry.slotLabel === "string") && typeof entry.prompt === "string" && Array.isArray(entry.images) && entry.images.every((image) => {
|
|
1173
1214
|
if (image === null || typeof image !== "object") return false;
|
|
1174
1215
|
const record = image;
|
|
1175
1216
|
return typeof record.file === "string" && typeof record.mime === "string";
|
|
@@ -1201,7 +1242,12 @@ function toWire(entry) {
|
|
|
1201
1242
|
...entry.refName === void 0 ? {} : { refName: entry.refName },
|
|
1202
1243
|
...entry.tags === void 0 ? {} : { tags: entry.tags },
|
|
1203
1244
|
...entry.channel === void 0 ? {} : { channel: entry.channel },
|
|
1204
|
-
...entry.channelId === void 0 ? {} : { channelId: entry.channelId }
|
|
1245
|
+
...entry.channelId === void 0 ? {} : { channelId: entry.channelId },
|
|
1246
|
+
...entry.workflow === void 0 ? {} : { workflow: entry.workflow },
|
|
1247
|
+
...entry.projectId === void 0 ? {} : { projectId: entry.projectId },
|
|
1248
|
+
...entry.projectName === void 0 ? {} : { projectName: entry.projectName },
|
|
1249
|
+
...entry.slotKey === void 0 ? {} : { slotKey: entry.slotKey },
|
|
1250
|
+
...entry.slotLabel === void 0 ? {} : { slotLabel: entry.slotLabel }
|
|
1205
1251
|
};
|
|
1206
1252
|
}
|
|
1207
1253
|
/** List the persisted gallery, newest first, as wire entries. */
|
|
@@ -1253,7 +1299,12 @@ async function appendGallery(input) {
|
|
|
1253
1299
|
...hash === void 0 ? {} : { hash },
|
|
1254
1300
|
...input.refName === void 0 ? {} : { refName: input.refName },
|
|
1255
1301
|
...input.channelId === void 0 ? {} : { channelId: input.channelId },
|
|
1256
|
-
...input.channel === void 0 ? {} : { channel: input.channel }
|
|
1302
|
+
...input.channel === void 0 ? {} : { channel: input.channel },
|
|
1303
|
+
...input.workflow === void 0 ? {} : { workflow: input.workflow },
|
|
1304
|
+
...input.projectId === void 0 ? {} : { projectId: input.projectId },
|
|
1305
|
+
...input.projectName === void 0 ? {} : { projectName: input.projectName },
|
|
1306
|
+
...input.slotKey === void 0 ? {} : { slotKey: input.slotKey },
|
|
1307
|
+
...input.slotLabel === void 0 ? {} : { slotLabel: input.slotLabel }
|
|
1257
1308
|
}, ...await readIndex()];
|
|
1258
1309
|
await writeIndex(merged);
|
|
1259
1310
|
return {
|
|
@@ -1806,7 +1857,12 @@ function parseGenerateRequest(body) {
|
|
|
1806
1857
|
...typeof body.refName === "string" && body.refName !== "" ? { refName: body.refName } : {},
|
|
1807
1858
|
...typeof body.channelId === "string" && body.channelId !== "" ? { channelId: body.channelId } : {},
|
|
1808
1859
|
...typeof body.comparisonId === "string" && body.comparisonId !== "" ? { comparisonId: body.comparisonId } : {},
|
|
1809
|
-
...comparisonModels.length > 1 ? { comparisonModels } : {}
|
|
1860
|
+
...comparisonModels.length > 1 ? { comparisonModels } : {},
|
|
1861
|
+
...body.workflow === "ecommerce" ? { workflow: "ecommerce" } : {},
|
|
1862
|
+
...typeof body.projectId === "string" && body.projectId !== "" ? { projectId: body.projectId } : {},
|
|
1863
|
+
...typeof body.projectName === "string" && body.projectName !== "" ? { projectName: body.projectName } : {},
|
|
1864
|
+
...typeof body.slotKey === "string" && body.slotKey !== "" ? { slotKey: body.slotKey } : {},
|
|
1865
|
+
...typeof body.slotLabel === "string" && body.slotLabel !== "" ? { slotLabel: body.slotLabel } : {}
|
|
1810
1866
|
};
|
|
1811
1867
|
}
|
|
1812
1868
|
/** Validate a submitted history entry (images carry base64). */
|
|
@@ -1847,7 +1903,12 @@ function parseHistoryEntryInput(body) {
|
|
|
1847
1903
|
...typeof entry.channelId === "string" ? { channelId: entry.channelId } : {},
|
|
1848
1904
|
...typeof entry.channel === "string" ? { channel: entry.channel } : {},
|
|
1849
1905
|
...typeof entry.comparisonId === "string" ? { comparisonId: entry.comparisonId } : {},
|
|
1850
|
-
...comparisonModels.length > 1 ? { comparisonModels } : {}
|
|
1906
|
+
...comparisonModels.length > 1 ? { comparisonModels } : {},
|
|
1907
|
+
...entry.workflow === "ecommerce" ? { workflow: "ecommerce" } : {},
|
|
1908
|
+
...typeof entry.projectId === "string" ? { projectId: entry.projectId } : {},
|
|
1909
|
+
...typeof entry.projectName === "string" ? { projectName: entry.projectName } : {},
|
|
1910
|
+
...typeof entry.slotKey === "string" ? { slotKey: entry.slotKey } : {},
|
|
1911
|
+
...typeof entry.slotLabel === "string" ? { slotLabel: entry.slotLabel } : {}
|
|
1851
1912
|
};
|
|
1852
1913
|
}
|
|
1853
1914
|
/** Extract the image file name from a history-image request URL. */
|
|
@@ -1889,6 +1950,15 @@ function agentImageRefFrom(rawUrl) {
|
|
|
1889
1950
|
function isImageMediaType(value) {
|
|
1890
1951
|
return value === "image/png" || value === "image/jpeg" || value === "image/webp" || value === "image/gif";
|
|
1891
1952
|
}
|
|
1953
|
+
function imageDataUrl$1(value) {
|
|
1954
|
+
const match = /^data:(image\/(?:png|jpeg|webp|gif));base64,(.*)$/su.exec(value.trim());
|
|
1955
|
+
if (match === null || match[1] === void 0 || match[2] === void 0) return void 0;
|
|
1956
|
+
const data = Buffer.from(match[2], "base64");
|
|
1957
|
+
return data.byteLength === 0 ? void 0 : {
|
|
1958
|
+
mediaType: match[1],
|
|
1959
|
+
data
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1892
1962
|
/** Project one settings descriptor onto the bridge wire view. */
|
|
1893
1963
|
function toView(descriptor) {
|
|
1894
1964
|
return {
|
|
@@ -2040,6 +2110,39 @@ function makeRoutes(deps) {
|
|
|
2040
2110
|
return true;
|
|
2041
2111
|
};
|
|
2042
2112
|
return [
|
|
2113
|
+
...deps.attachments?.saveImage === void 0 || deps.pendingConversationImages === void 0 ? [] : [{
|
|
2114
|
+
kind: "exact",
|
|
2115
|
+
path: CONVERSATION_IMAGE_API,
|
|
2116
|
+
handler: async (req, res) => {
|
|
2117
|
+
if (!guard(req, res, "POST")) return;
|
|
2118
|
+
const body = await readJsonBody(req, MAX_JSON_BODY_BYTES);
|
|
2119
|
+
const sessionId = typeof body?.sessionId === "string" ? body.sessionId.trim() : "";
|
|
2120
|
+
const dataUrl = typeof body?.dataUrl === "string" ? imageDataUrl$1(body.dataUrl) : void 0;
|
|
2121
|
+
if (sessionId === "" || dataUrl === void 0) {
|
|
2122
|
+
writeJson(res, 200, {
|
|
2123
|
+
ok: false,
|
|
2124
|
+
code: "bad-request",
|
|
2125
|
+
message: "sessionId and image data are required"
|
|
2126
|
+
});
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
try {
|
|
2130
|
+
const ref = await deps.attachments.saveImage({
|
|
2131
|
+
data: dataUrl.data,
|
|
2132
|
+
mediaType: dataUrl.mediaType,
|
|
2133
|
+
...typeof body?.name === "string" && body.name.trim() !== "" ? { name: body.name.trim() } : {}
|
|
2134
|
+
});
|
|
2135
|
+
deps.pendingConversationImages.set(sessionId, ref);
|
|
2136
|
+
writeJson(res, 200, { ok: true });
|
|
2137
|
+
} catch (error) {
|
|
2138
|
+
writeJson(res, 200, {
|
|
2139
|
+
ok: false,
|
|
2140
|
+
code: "image-save-failed",
|
|
2141
|
+
message: messageOf(error)
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
}],
|
|
2043
2146
|
...deps.attachments === void 0 ? [] : [{
|
|
2044
2147
|
kind: "prefix",
|
|
2045
2148
|
path: AGENT_IMAGE_API,
|
|
@@ -2233,7 +2336,7 @@ function makeRoutes(deps) {
|
|
|
2233
2336
|
}
|
|
2234
2337
|
const expectedRevision = typeof body.expectedRevision === "number" ? body.expectedRevision : void 0;
|
|
2235
2338
|
try {
|
|
2236
|
-
await deps.settings.mutate(
|
|
2339
|
+
await deps.settings.mutate(ns, body.ops, expectedRevision);
|
|
2237
2340
|
} catch (error) {
|
|
2238
2341
|
writeJson(res, 200, failureOf(error));
|
|
2239
2342
|
return;
|
|
@@ -2863,6 +2966,113 @@ const taskResultSchema = {
|
|
|
2863
2966
|
};
|
|
2864
2967
|
/** Agent calls stay pending until the provider and history write settle. */
|
|
2865
2968
|
const AGENT_GENERATION_TIMEOUT_MS = 3e5;
|
|
2969
|
+
function ensureAgentImageConfigured(config) {
|
|
2970
|
+
if (!config.enabled) throw new ImageGenError("AI image generation is disabled. Open Settings > Plugins > AI Image and enable it.", "plugin-disabled");
|
|
2971
|
+
if (!config.allowAgentImageGeneration) throw new ImageGenError("Agent image generation is disabled in Settings > Plugins > AI Image.", "agent-generation-disabled");
|
|
2972
|
+
if (!config.channels.some((channel) => channel.apiUrl.trim() !== "" && channel.apiKey.trim() !== "")) throw new ImageGenError("Image API credentials are not configured. Open Settings > Plugins > AI Image, add a channel and fill in its API URL and API key.", "image-api-not-configured");
|
|
2973
|
+
}
|
|
2974
|
+
/** Resolve a configured image alias and its owning channel. */
|
|
2975
|
+
function resolveAgentImageModel(config, requested) {
|
|
2976
|
+
const entries = config.channels.flatMap((channel) => channel.models.map((model) => ({
|
|
2977
|
+
channel,
|
|
2978
|
+
alias: model.alias,
|
|
2979
|
+
upstream: model.id
|
|
2980
|
+
})));
|
|
2981
|
+
if (entries.length === 0) throw new ImageGenError("No image models are configured. Open Settings > Plugins > AI Image and add a channel with at least one model.", "no-models-configured");
|
|
2982
|
+
const wanted = typeof requested === "string" && requested.trim() !== "" ? requested.trim() : "";
|
|
2983
|
+
if (wanted === "") {
|
|
2984
|
+
if (entries.length === 1) return entries[0];
|
|
2985
|
+
throw new ImageGenError(`Multiple image models are available — ask the user which channel and model to use, then call this tool again with that exact model name. Options: ${config.channels.flatMap((channel) => channel.models.map((model) => `"${channel.name} · ${model.alias}"`)).join(", ")}.`, "model-choice-required");
|
|
2986
|
+
}
|
|
2987
|
+
const hosting = entries.filter((entry) => entry.alias === wanted);
|
|
2988
|
+
if (hosting.length === 0) throw new ImageGenError(`Image model "${wanted}" is not configured in any channel. Choose one of: ${[...new Set(entries.map((entry) => entry.alias))].join(", ")}.`, "image-model-not-configured");
|
|
2989
|
+
return hosting.find((entry) => entry.channel.id === config.defaultChannelId) ?? hosting[0];
|
|
2990
|
+
}
|
|
2991
|
+
function findAgentImageTask(runtime, id) {
|
|
2992
|
+
const task = runtime.queue.list().find((candidate) => candidate.id === id);
|
|
2993
|
+
if (task === void 0) throw new ImageGenError(`Image generation task ${id} was not found.`, "task-not-found");
|
|
2994
|
+
return task;
|
|
2995
|
+
}
|
|
2996
|
+
/** Wait for a queue task without sending anything through the chat model. */
|
|
2997
|
+
function waitForAgentImageTask(runtime, id, signal) {
|
|
2998
|
+
return new Promise((resolveTask, rejectTask) => {
|
|
2999
|
+
let settled = false;
|
|
3000
|
+
let dispose = () => {};
|
|
3001
|
+
let timer;
|
|
3002
|
+
let abort = () => {};
|
|
3003
|
+
const cleanup = () => {
|
|
3004
|
+
dispose();
|
|
3005
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
3006
|
+
signal?.removeEventListener("abort", abort);
|
|
3007
|
+
};
|
|
3008
|
+
const resolve = (task) => {
|
|
3009
|
+
if (settled) return;
|
|
3010
|
+
settled = true;
|
|
3011
|
+
cleanup();
|
|
3012
|
+
resolveTask(task);
|
|
3013
|
+
};
|
|
3014
|
+
const reject = (error) => {
|
|
3015
|
+
if (settled) return;
|
|
3016
|
+
settled = true;
|
|
3017
|
+
cleanup();
|
|
3018
|
+
rejectTask(error);
|
|
3019
|
+
};
|
|
3020
|
+
abort = () => {
|
|
3021
|
+
if (settled) return;
|
|
3022
|
+
const reason = signal?.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("Image generation was cancelled.");
|
|
3023
|
+
settled = true;
|
|
3024
|
+
cleanup();
|
|
3025
|
+
runtime.queue.cancel(id);
|
|
3026
|
+
rejectTask(reason);
|
|
3027
|
+
};
|
|
3028
|
+
const onChange = (updated) => {
|
|
3029
|
+
if (updated.id === id && isFinalTask(updated)) resolve(updated);
|
|
3030
|
+
};
|
|
3031
|
+
if (signal?.aborted === true) {
|
|
3032
|
+
abort();
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3035
|
+
dispose = runtime.queue.subscribe(onChange);
|
|
3036
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
3037
|
+
timer = setTimeout(() => {
|
|
3038
|
+
if (settled) return;
|
|
3039
|
+
const timeout = new ImageGenError(`Image generation task ${id} timed out after ${AGENT_GENERATION_TIMEOUT_MS / 1e3} seconds.`, "generation-timeout");
|
|
3040
|
+
settled = true;
|
|
3041
|
+
cleanup();
|
|
3042
|
+
runtime.queue.cancel(id);
|
|
3043
|
+
rejectTask(timeout);
|
|
3044
|
+
}, AGENT_GENERATION_TIMEOUT_MS);
|
|
3045
|
+
let current;
|
|
3046
|
+
try {
|
|
3047
|
+
current = findAgentImageTask(runtime, id);
|
|
3048
|
+
} catch (error) {
|
|
3049
|
+
reject(error);
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
if (isFinalTask(current)) resolve(current);
|
|
3053
|
+
});
|
|
3054
|
+
}
|
|
3055
|
+
/** Submit the same image-edit request used by the Agent tool. */
|
|
3056
|
+
async function submitAgentImageEdit(attachments, runtime, resolve, input) {
|
|
3057
|
+
const config = resolve();
|
|
3058
|
+
ensureAgentImageConfigured(config);
|
|
3059
|
+
const reference = await attachments.readImage(input.sourceImage, input.signal);
|
|
3060
|
+
const picked = resolveAgentImageModel(config, (config.channels.find((channel) => channel.id === config.defaultChannelId) ?? config.channels[0])?.models[0]?.alias ?? config.channels.flatMap((channel) => channel.models)[0]?.alias);
|
|
3061
|
+
return waitForAgentImageTask(runtime, runtime.queue.submit({
|
|
3062
|
+
mode: "edit",
|
|
3063
|
+
model: picked.alias,
|
|
3064
|
+
upstream: picked.upstream,
|
|
3065
|
+
channelId: picked.channel.id,
|
|
3066
|
+
channel: picked.channel.name,
|
|
3067
|
+
prompt: input.prompt.trim(),
|
|
3068
|
+
size: "auto",
|
|
3069
|
+
quality: "auto",
|
|
3070
|
+
n: 1,
|
|
3071
|
+
detail: "",
|
|
3072
|
+
image: imageDataUrl(reference),
|
|
3073
|
+
...reference.ref.name === void 0 ? {} : { refName: reference.ref.name }
|
|
3074
|
+
}).id, input.signal);
|
|
3075
|
+
}
|
|
2866
3076
|
function acceptedMediaType(value) {
|
|
2867
3077
|
return value === "image/png" || value === "image/jpeg" || value === "image/webp" || value === "image/gif";
|
|
2868
3078
|
}
|
|
@@ -2948,36 +3158,9 @@ function presentImageResult(_args, result) {
|
|
|
2948
3158
|
function registerAgentImageTools(ctx, runtime, resolve) {
|
|
2949
3159
|
const attachmentRefs = /* @__PURE__ */ new Map();
|
|
2950
3160
|
const ensureConfigured = () => {
|
|
2951
|
-
|
|
2952
|
-
if (!config.enabled) throw new ImageGenError("AI image generation is disabled. Open Settings > Plugins > AI Image and enable it.", "plugin-disabled");
|
|
2953
|
-
if (!config.allowAgentImageGeneration) throw new ImageGenError("Agent image generation is disabled in Settings > Plugins > AI Image.", "agent-generation-disabled");
|
|
2954
|
-
if (!config.channels.some((channel) => channel.apiUrl.trim() !== "" && channel.apiKey.trim() !== "")) throw new ImageGenError("Image API credentials are not configured. Open Settings > Plugins > AI Image, add a channel and fill in its API URL and API key.", "image-api-not-configured");
|
|
2955
|
-
};
|
|
2956
|
-
/**
|
|
2957
|
-
* Resolve the requested model alias onto a channel. Rules:
|
|
2958
|
-
* - a named alias must exist in some channel's catalog (several channels
|
|
2959
|
-
* may host it; the default channel wins);
|
|
2960
|
-
* - with no alias, a single configured model is used directly, while
|
|
2961
|
-
* multiple models require the Agent to ask the user first.
|
|
2962
|
-
* @returns the channel plus the alias and its upstream id.
|
|
2963
|
-
*/
|
|
2964
|
-
const resolveModel = (requested) => {
|
|
2965
|
-
const config = resolve();
|
|
2966
|
-
const entries = config.channels.flatMap((channel) => channel.models.map((model) => ({
|
|
2967
|
-
channel,
|
|
2968
|
-
alias: model.alias,
|
|
2969
|
-
upstream: model.id
|
|
2970
|
-
})));
|
|
2971
|
-
if (entries.length === 0) throw new ImageGenError("No image models are configured. Open Settings > Plugins > AI Image and add a channel with at least one model.", "no-models-configured");
|
|
2972
|
-
const wanted = typeof requested === "string" && requested.trim() !== "" ? requested.trim() : "";
|
|
2973
|
-
if (wanted === "") {
|
|
2974
|
-
if (entries.length === 1) return entries[0];
|
|
2975
|
-
throw new ImageGenError(`Multiple image models are available — ask the user which channel and model to use, then call this tool again with that exact model name. Options: ${config.channels.flatMap((channel) => channel.models.map((model) => `"${channel.name} · ${model.alias}"`)).join(", ")}.`, "model-choice-required");
|
|
2976
|
-
}
|
|
2977
|
-
const hosting = entries.filter((entry) => entry.alias === wanted);
|
|
2978
|
-
if (hosting.length === 0) throw new ImageGenError(`Image model "${wanted}" is not configured in any channel. Choose one of: ${[...new Set(entries.map((entry) => entry.alias))].join(", ")}.`, "image-model-not-configured");
|
|
2979
|
-
return hosting.find((entry) => entry.channel.id === config.defaultChannelId) ?? hosting[0];
|
|
3161
|
+
ensureAgentImageConfigured(resolve());
|
|
2980
3162
|
};
|
|
3163
|
+
const resolveModel = (requested) => resolveAgentImageModel(resolve(), requested);
|
|
2981
3164
|
const materializeTaskImages = (task) => {
|
|
2982
3165
|
if (task.status !== "completed") return Promise.resolve([]);
|
|
2983
3166
|
const existing = attachmentRefs.get(task.id);
|
|
@@ -2999,67 +3182,8 @@ function registerAgentImageTools(ctx, runtime, resolve) {
|
|
|
2999
3182
|
images
|
|
3000
3183
|
};
|
|
3001
3184
|
};
|
|
3002
|
-
const findTask = (id) =>
|
|
3003
|
-
|
|
3004
|
-
if (task === void 0) throw new ImageGenError(`Image generation task ${id} was not found.`, "task-not-found");
|
|
3005
|
-
return task;
|
|
3006
|
-
};
|
|
3007
|
-
const waitForTask = (id, signal) => new Promise((resolveTask, rejectTask) => {
|
|
3008
|
-
let settled = false;
|
|
3009
|
-
let dispose = () => {};
|
|
3010
|
-
let timer;
|
|
3011
|
-
let abort = () => {};
|
|
3012
|
-
const cleanup = () => {
|
|
3013
|
-
dispose();
|
|
3014
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
3015
|
-
signal?.removeEventListener("abort", abort);
|
|
3016
|
-
};
|
|
3017
|
-
const resolve = (task) => {
|
|
3018
|
-
if (settled) return;
|
|
3019
|
-
settled = true;
|
|
3020
|
-
cleanup();
|
|
3021
|
-
resolveTask(task);
|
|
3022
|
-
};
|
|
3023
|
-
const reject = (error) => {
|
|
3024
|
-
if (settled) return;
|
|
3025
|
-
settled = true;
|
|
3026
|
-
cleanup();
|
|
3027
|
-
rejectTask(error);
|
|
3028
|
-
};
|
|
3029
|
-
abort = () => {
|
|
3030
|
-
if (settled) return;
|
|
3031
|
-
const reason = signal?.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("Image generation was cancelled.");
|
|
3032
|
-
settled = true;
|
|
3033
|
-
cleanup();
|
|
3034
|
-
runtime.queue.cancel(id);
|
|
3035
|
-
rejectTask(reason);
|
|
3036
|
-
};
|
|
3037
|
-
const onChange = (updated) => {
|
|
3038
|
-
if (updated.id === id && isFinalTask(updated)) resolve(updated);
|
|
3039
|
-
};
|
|
3040
|
-
if (signal?.aborted === true) {
|
|
3041
|
-
abort();
|
|
3042
|
-
return;
|
|
3043
|
-
}
|
|
3044
|
-
dispose = runtime.queue.subscribe(onChange);
|
|
3045
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
3046
|
-
timer = setTimeout(() => {
|
|
3047
|
-
if (settled) return;
|
|
3048
|
-
const timeout = new ImageGenError(`Image generation task ${id} timed out after ${AGENT_GENERATION_TIMEOUT_MS / 1e3} seconds.`, "generation-timeout");
|
|
3049
|
-
settled = true;
|
|
3050
|
-
cleanup();
|
|
3051
|
-
runtime.queue.cancel(id);
|
|
3052
|
-
rejectTask(timeout);
|
|
3053
|
-
}, AGENT_GENERATION_TIMEOUT_MS);
|
|
3054
|
-
let current;
|
|
3055
|
-
try {
|
|
3056
|
-
current = findTask(id);
|
|
3057
|
-
} catch (error) {
|
|
3058
|
-
reject(error);
|
|
3059
|
-
return;
|
|
3060
|
-
}
|
|
3061
|
-
if (isFinalTask(current)) resolve(current);
|
|
3062
|
-
});
|
|
3185
|
+
const findTask = (id) => findAgentImageTask(runtime, id);
|
|
3186
|
+
const waitForTask = (id, signal) => waitForAgentImageTask(runtime, id, signal);
|
|
3063
3187
|
const disposers = [
|
|
3064
3188
|
ctx.tools.register(defineTool({
|
|
3065
3189
|
name: "generate_image",
|
|
@@ -3244,13 +3368,109 @@ function toSaveImage(image, taskId, index) {
|
|
|
3244
3368
|
};
|
|
3245
3369
|
}
|
|
3246
3370
|
//#endregion
|
|
3371
|
+
//#region src/edit-image-command.ts
|
|
3372
|
+
function isImageReference(value) {
|
|
3373
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
3374
|
+
const ref = value;
|
|
3375
|
+
return typeof ref.attachmentId === "string" && (ref.mediaType === "image/png" || ref.mediaType === "image/jpeg" || ref.mediaType === "image/webp" || ref.mediaType === "image/gif") && Number.isInteger(ref.bytes) && ref.bytes > 0 && Number.isInteger(ref.width) && ref.width > 0 && Number.isInteger(ref.height) && ref.height > 0;
|
|
3376
|
+
}
|
|
3377
|
+
function imageInContent(value) {
|
|
3378
|
+
if (!Array.isArray(value)) return void 0;
|
|
3379
|
+
for (let index = value.length - 1; index >= 0; index -= 1) {
|
|
3380
|
+
const block = value[index];
|
|
3381
|
+
if (typeof block !== "object" || block === null || Array.isArray(block)) continue;
|
|
3382
|
+
const raw = block;
|
|
3383
|
+
if (raw.type === "image" && isImageReference(raw.attachment)) return raw.attachment;
|
|
3384
|
+
if (raw.type === "tool-result") {
|
|
3385
|
+
const nested = imageInContent(raw.content);
|
|
3386
|
+
if (nested !== void 0) return nested;
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
/** Pick the newest image explicitly attached to this command invocation. */
|
|
3391
|
+
function imageInInvocation(value) {
|
|
3392
|
+
if (!Array.isArray(value)) return void 0;
|
|
3393
|
+
for (let index = value.length - 1; index >= 0; index -= 1) {
|
|
3394
|
+
const block = value[index];
|
|
3395
|
+
if (typeof block !== "object" || block === null || Array.isArray(block)) continue;
|
|
3396
|
+
const raw = block;
|
|
3397
|
+
if (raw.type === "image" && isImageReference(raw.attachment)) return raw.attachment;
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
/** Find the newest durable image reference, including nested tool results. */
|
|
3401
|
+
function latestSessionImage(messages) {
|
|
3402
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
3403
|
+
const message = messages[index];
|
|
3404
|
+
if (typeof message !== "object" || message === null || Array.isArray(message)) continue;
|
|
3405
|
+
const image = imageInContent(message.content);
|
|
3406
|
+
if (image !== void 0) return image;
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
function commandError(error) {
|
|
3410
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
3411
|
+
return {
|
|
3412
|
+
kind: "error",
|
|
3413
|
+
text: text.trim() === "" ? "图片编辑失败。" : text
|
|
3414
|
+
};
|
|
3415
|
+
}
|
|
3416
|
+
/** Register the host-side command; it never sends the command line to a chat model. */
|
|
3417
|
+
function registerEditImageCommand(ctx, runtime, resolve, pendingImages) {
|
|
3418
|
+
return ctx.commands.register({
|
|
3419
|
+
name: "edit_image",
|
|
3420
|
+
description: "Edit the latest image in this conversation with the plugin image model",
|
|
3421
|
+
input: {
|
|
3422
|
+
hint: "Describe how to modify the latest image",
|
|
3423
|
+
images: true
|
|
3424
|
+
},
|
|
3425
|
+
async handler(invocation) {
|
|
3426
|
+
const prompt = invocation.rawInput.trim();
|
|
3427
|
+
if (prompt === "") return {
|
|
3428
|
+
kind: "error",
|
|
3429
|
+
text: "请提供图片修改描述,例如:/edit_image 把背景改成夜景"
|
|
3430
|
+
};
|
|
3431
|
+
const invocationImage = imageInInvocation(invocation.attachments);
|
|
3432
|
+
const pendingImage = pendingImages?.get(String(invocation.agent.id));
|
|
3433
|
+
const durableImage = latestSessionImage(invocation.agent.session.deriveMessages());
|
|
3434
|
+
const sourceImage = invocationImage ?? pendingImage ?? durableImage;
|
|
3435
|
+
if (sourceImage === void 0) return {
|
|
3436
|
+
kind: "error",
|
|
3437
|
+
text: "当前对话没有可用图片,请先上传图片或把画廊图片加入对话。"
|
|
3438
|
+
};
|
|
3439
|
+
try {
|
|
3440
|
+
const task = await submitAgentImageEdit(ctx.attachments, runtime, resolve, {
|
|
3441
|
+
prompt,
|
|
3442
|
+
sourceImage,
|
|
3443
|
+
signal: invocation.signal
|
|
3444
|
+
});
|
|
3445
|
+
if (task.status === "completed") {
|
|
3446
|
+
if (pendingImage !== void 0) pendingImages?.consume(String(invocation.agent.id), pendingImage);
|
|
3447
|
+
return {
|
|
3448
|
+
kind: "success",
|
|
3449
|
+
text: "图片编辑已完成,可在 AI 生图面板查看结果。"
|
|
3450
|
+
};
|
|
3451
|
+
}
|
|
3452
|
+
return {
|
|
3453
|
+
kind: "error",
|
|
3454
|
+
text: task.error ?? `图片编辑${task.status === "cancelled" ? "已取消" : "失败"}。`
|
|
3455
|
+
};
|
|
3456
|
+
} catch (error) {
|
|
3457
|
+
return commandError(error);
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
});
|
|
3461
|
+
}
|
|
3462
|
+
//#endregion
|
|
3247
3463
|
//#region src/index.ts
|
|
3248
3464
|
/** Stable cordis plugin name. */
|
|
3249
3465
|
const name = "imagegen";
|
|
3250
3466
|
/** Services required before the surfaces can mount. */
|
|
3251
|
-
const inject = [
|
|
3467
|
+
const inject = [
|
|
3468
|
+
"webServer",
|
|
3469
|
+
"systemPrompt",
|
|
3470
|
+
"commands"
|
|
3471
|
+
];
|
|
3252
3472
|
/** The branded settings namespace of this plugin (the card edits it). */
|
|
3253
|
-
const ImageGenSettingsNamespace =
|
|
3473
|
+
const ImageGenSettingsNamespace = settingsNamespaceCompat(IMAGEGEN_SETTINGS_NAMESPACE);
|
|
3254
3474
|
const Config = z.object({
|
|
3255
3475
|
enabled: z.boolean().default(true),
|
|
3256
3476
|
announceToAgent: z.boolean().default(true),
|
|
@@ -3281,7 +3501,7 @@ const DEFAULT_ALLOW_AGENT_IMAGE_GENERATION = true;
|
|
|
3281
3501
|
/** Order of the announcement section within the tool-guidance band. */
|
|
3282
3502
|
const SECTION_ORDER = 150;
|
|
3283
3503
|
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
|
3284
|
-
const IMAGEGEN_GUIDANCE = "本机已安装 dsh-imagegen 插件(DSH AI 生图):侧边栏「AI 生图」入口。能力:通过「渠道」对接 OpenAI 兼容图像生成 API(每个渠道 = 一个 API 端点 + 各自的模型目录),支持文生图(/images/generations)与图生图(/images/edits,上传参考图,grok-imagine 模型按官方 JSON image_url 协议发送,nanobanana 系列按 aspect_ratio / image_size 参数协议发送;seedream 系列统一走 /images/generations,参考图以 JSON image 数组发送;智谱 `glm-image` 使用官方 `/api/paas/v4/images/generations`,当前仅支持文生图)。API 地址与密钥在 GUI 设置中按渠道配置,密钥仅存于本机设置文档;生成请求由本地宿主代理转发,结果以 base64 返回面板,可预览与下载。模型只能使用用户在各渠道配置目录中的模型;检测模型时会过滤聊天、Embedding 等非图片模型,但模型出现在 /models 中仍不等于其网关原生支持生图协议,遇到 Qwen、Gemini 等非 OpenAI 生图协议时应如实说明上游兼容性。可一键把满意的图片加入「画廊」。内置「提示词模板库」(面板提示词框左下角「模板库」按钮):打包 awesome-gpt-image-2 的数百条提示词案例,可搜索、筛选与复用。Agent 可直接调用 `generate_image` 提交文生图,也可用 `edit_image`
|
|
3504
|
+
const IMAGEGEN_GUIDANCE = "本机已安装 dsh-imagegen 插件(DSH AI 生图):侧边栏「AI 生图」入口。能力:通过「渠道」对接 OpenAI 兼容图像生成 API(每个渠道 = 一个 API 端点 + 各自的模型目录),支持文生图(/images/generations)与图生图(/images/edits,上传参考图,grok-imagine 模型按官方 JSON image_url 协议发送,nanobanana 系列按 aspect_ratio / image_size 参数协议发送;seedream 系列统一走 /images/generations,参考图以 JSON image 数组发送;智谱 `glm-image` 使用官方 `/api/paas/v4/images/generations`,当前仅支持文生图)。API 地址与密钥在 GUI 设置中按渠道配置,密钥仅存于本机设置文档;生成请求由本地宿主代理转发,结果以 base64 返回面板,可预览与下载。模型只能使用用户在各渠道配置目录中的模型;检测模型时会过滤聊天、Embedding 等非图片模型,但模型出现在 /models 中仍不等于其网关原生支持生图协议,遇到 Qwen、Gemini 等非 OpenAI 生图协议时应如实说明上游兼容性。可一键把满意的图片加入「画廊」。内置「提示词模板库」(面板提示词框左下角「模板库」按钮):打包 awesome-gpt-image-2 的数百条提示词案例,可搜索、筛选与复用。Agent 可直接调用 `generate_image` 提交文生图,也可用 `edit_image` 图生图;默认保持工具调用等待直到任务完成,完成图片显示在工具调用对应的左侧结果区域,模型收到状态和附件引用,不会额外伪造用户消息。用户也可以使用 `/edit_image <修改描述>`,命令会直接读取当前对话最近图片并调用插件图片模型,不经过对话模型的图片能力检查。若明确需要后台执行,可传 `wait_for_completion: false`,之后再用 `get_image_generation_task` 查询;不要反复轮询。限制:生成消耗上游 API 额度;图片内容由上游模型生成,可能不符合预期或包含不适宜内容;api_key 以明文存储在设置文档中;参考图会发送至所配置的 API 服务;模板库在线刷新与参考图首次加载需要访问 vibeui.top。用户提到「生图 / 绘画 / 生成图片 / 文生图 / 图生图 / 画廊 / 提示词模板」时即指本插件,请据此协作。";
|
|
3285
3505
|
/** Append the live channel × model table so an Agent can honor user choices. */
|
|
3286
3506
|
function guidanceFor(channels, defaultChannelId) {
|
|
3287
3507
|
if (channels.length === 0) return `${IMAGEGEN_GUIDANCE} 尚未配置任何渠道:请先在「设置 → 插件 → AI 生图」添加渠道并填写 API 地址与密钥。`;
|
|
@@ -3381,6 +3601,7 @@ function apply(ctx, config) {
|
|
|
3381
3601
|
};
|
|
3382
3602
|
};
|
|
3383
3603
|
const runtime = new ImageGenerationRuntime(channelsView);
|
|
3604
|
+
const pendingConversationImages = /* @__PURE__ */ new Map();
|
|
3384
3605
|
ctx.inject(["settings", "attachments"], (sctx) => {
|
|
3385
3606
|
const seam = sctx.get("settings");
|
|
3386
3607
|
sctx.effect(() => {
|
|
@@ -3409,6 +3630,7 @@ function apply(ctx, config) {
|
|
|
3409
3630
|
return [...new Set(value.channels.flatMap((channel) => channel.models.map((model) => model.alias)))];
|
|
3410
3631
|
},
|
|
3411
3632
|
attachments: sctx.attachments,
|
|
3633
|
+
pendingConversationImages,
|
|
3412
3634
|
runtime
|
|
3413
3635
|
}).map((route) => ctx.webServer.register(route));
|
|
3414
3636
|
return () => {
|
|
@@ -3416,16 +3638,33 @@ function apply(ctx, config) {
|
|
|
3416
3638
|
};
|
|
3417
3639
|
}, "dsh-imagegen: routes");
|
|
3418
3640
|
});
|
|
3419
|
-
ctx.inject([
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3641
|
+
ctx.inject([
|
|
3642
|
+
"tools",
|
|
3643
|
+
"attachments",
|
|
3644
|
+
"commands"
|
|
3645
|
+
], (tctx) => {
|
|
3646
|
+
tctx.effect(() => {
|
|
3647
|
+
const resolveAgentConfig = () => {
|
|
3648
|
+
const value = resolve();
|
|
3649
|
+
return {
|
|
3650
|
+
enabled: value.enabled,
|
|
3651
|
+
allowAgentImageGeneration: value.allowAgentImageGeneration,
|
|
3652
|
+
channels: value.channels,
|
|
3653
|
+
defaultChannelId: value.defaultChannelId
|
|
3654
|
+
};
|
|
3655
|
+
};
|
|
3656
|
+
const disposeTools = registerAgentImageTools(tctx, runtime, resolveAgentConfig);
|
|
3657
|
+
const disposeCommand = registerEditImageCommand(tctx, runtime, resolveAgentConfig, {
|
|
3658
|
+
get: (sessionId) => pendingConversationImages.get(sessionId),
|
|
3659
|
+
consume: (sessionId, ref) => {
|
|
3660
|
+
if (pendingConversationImages.get(sessionId)?.attachmentId === ref.attachmentId) pendingConversationImages.delete(sessionId);
|
|
3661
|
+
}
|
|
3662
|
+
});
|
|
3663
|
+
return () => {
|
|
3664
|
+
disposeCommand();
|
|
3665
|
+
disposeTools();
|
|
3427
3666
|
};
|
|
3428
|
-
}
|
|
3667
|
+
}, "dsh-imagegen: agent image tools and commands");
|
|
3429
3668
|
});
|
|
3430
3669
|
let disposeSection;
|
|
3431
3670
|
const sync = () => {
|
|
@@ -3441,7 +3680,7 @@ function apply(ctx, config) {
|
|
|
3441
3680
|
text: guidanceFor(value.channels, value.defaultChannelId)
|
|
3442
3681
|
});
|
|
3443
3682
|
};
|
|
3444
|
-
|
|
3683
|
+
installSettingsSectionCompat(ctx, ImageGenSettingsNamespace, Config, config ?? {}, {
|
|
3445
3684
|
setSource: (source) => {
|
|
3446
3685
|
current = source;
|
|
3447
3686
|
sync();
|
|
@@ -3451,4 +3690,4 @@ function apply(ctx, config) {
|
|
|
3451
3690
|
sync();
|
|
3452
3691
|
}
|
|
3453
3692
|
//#endregion
|
|
3454
|
-
export { CURRENT_VERSION, Config, IMAGEGEN_GUIDANCE, ImageGenError, ImageGenSettingsNamespace, ImageGenerationRuntime, appendGallery, apply, checkForUpdate, clearGallery, clearTemplateMemo, clearUpdateCache, compareVersions, generateImage, inject, installUpdate, listGallery, listTemplates, makeRoutes, name, profileFromProcess, readGalleryImage, readTemplateImage, refreshTemplates, registerAgentImageTools, removeGallery, updateGalleryTags };
|
|
3693
|
+
export { CURRENT_VERSION, Config, IMAGEGEN_GUIDANCE, ImageGenError, ImageGenSettingsNamespace, ImageGenerationRuntime, appendGallery, apply, checkForUpdate, clearGallery, clearTemplateMemo, clearUpdateCache, compareVersions, generateImage, inject, installUpdate, latestSessionImage, listGallery, listTemplates, makeRoutes, name, profileFromProcess, readGalleryImage, readTemplateImage, refreshTemplates, registerAgentImageTools, registerEditImageCommand, removeGallery, updateGalleryTags };
|