@agent-native/core 0.92.5 → 0.92.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +19 -0
  3. package/corpus/core/docs/content/template-assets.mdx +139 -0
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/agent/production-agent.ts +96 -6
  6. package/corpus/core/src/cli/sync-builder-starter-manifest.ts +1 -1
  7. package/corpus/core/src/client/AssistantChat.tsx +127 -15
  8. package/corpus/core/src/client/chat/tool-call-display.tsx +9 -3
  9. package/corpus/core/src/client/org/TeamPage.tsx +274 -177
  10. package/corpus/core/src/client/settings/DemoModeSection.tsx +6 -6
  11. package/corpus/core/src/client/settings/SettingsPanel.tsx +1 -1
  12. package/corpus/core/src/client/settings/SettingsTabsPage.tsx +49 -1
  13. package/corpus/core/src/demo/actions/toggle-demo-mode.ts +2 -2
  14. package/corpus/core/src/demo/config.ts +3 -3
  15. package/corpus/core/src/demo/redact.ts +720 -0
  16. package/corpus/core/src/extensions/url-safety.ts +13 -1
  17. package/corpus/core/src/localization/default-messages.ts +7 -0
  18. package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +7 -0
  19. package/corpus/core/src/templates/default/app/i18n/de-DE.ts +7 -0
  20. package/corpus/core/src/templates/default/app/i18n/en-US.ts +7 -0
  21. package/corpus/core/src/templates/default/app/i18n/es-ES.ts +7 -0
  22. package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +7 -0
  23. package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +7 -0
  24. package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +7 -0
  25. package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +7 -0
  26. package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +7 -0
  27. package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +7 -0
  28. package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +7 -0
  29. package/corpus/templates/analytics/AGENTS.md +6 -3
  30. package/corpus/templates/analytics/changelog/2026-07-09-uptime-monitors-use-scheduled-workers-in-production-serverless.md +6 -0
  31. package/corpus/templates/analytics/docs/uptime-monitoring.md +5 -0
  32. package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +14 -2
  33. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +35 -0
  34. package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +19 -0
  35. package/corpus/templates/assets/AGENTS.md +13 -0
  36. package/corpus/templates/assets/actions/_generation-preset-settings.ts +73 -0
  37. package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +63 -0
  38. package/corpus/templates/assets/actions/create-generation-preset.ts +22 -8
  39. package/corpus/templates/assets/actions/duplicate-library.ts +49 -1
  40. package/corpus/templates/assets/actions/generate-image-batch.ts +14 -1
  41. package/corpus/templates/assets/actions/generate-image.ts +195 -26
  42. package/corpus/templates/assets/actions/import-asset-from-url.ts +323 -0
  43. package/corpus/templates/assets/actions/list-generation-presets.ts +1 -1
  44. package/corpus/templates/assets/actions/rerun-generation-run.ts +37 -0
  45. package/corpus/templates/assets/actions/update-generation-preset.ts +19 -1
  46. package/corpus/templates/assets/app/i18n/ar-SA.ts +1 -0
  47. package/corpus/templates/assets/app/i18n/de-DE.ts +1 -0
  48. package/corpus/templates/assets/app/i18n/es-ES.ts +1 -0
  49. package/corpus/templates/assets/app/i18n/fr-FR.ts +1 -0
  50. package/corpus/templates/assets/app/i18n/hi-IN.ts +1 -0
  51. package/corpus/templates/assets/app/i18n/ja-JP.ts +1 -0
  52. package/corpus/templates/assets/app/i18n/ko-KR.ts +1 -0
  53. package/corpus/templates/assets/app/i18n/pt-BR.ts +1 -0
  54. package/corpus/templates/assets/app/i18n/zh-CN.ts +1 -0
  55. package/corpus/templates/assets/app/i18n/zh-TW.ts +41 -0
  56. package/corpus/templates/assets/app/i18n-data.ts +93 -0
  57. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +66 -11
  58. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +959 -24
  59. package/corpus/templates/assets/changelog/2026-07-07-preset-skeleton-controls-fit-correctly-on-narrower-edit-page.md +6 -0
  60. package/corpus/templates/assets/changelog/2026-07-09-import-reference-images-from-url.md +6 -0
  61. package/corpus/templates/assets/changelog/2026-07-09-preset-reference-board.md +6 -0
  62. package/corpus/templates/assets/server/handlers/assets.ts +7 -27
  63. package/corpus/templates/assets/server/lib/generation.ts +83 -2
  64. package/corpus/templates/assets/server/lib/preset-chat-context.ts +17 -4
  65. package/corpus/templates/assets/server/lib/preset-references.ts +182 -0
  66. package/corpus/templates/assets/server/lib/upload-validation.ts +38 -0
  67. package/corpus/templates/assets/shared/api.ts +20 -0
  68. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +6 -0
  69. package/corpus/templates/calendar/actions/create-event.ts +8 -2
  70. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +22 -7
  71. package/corpus/templates/calendar/changelog/2026-07-09-calendar-events-with-guests-now-automatically-get-a-google-m.md +6 -0
  72. package/corpus/templates/calendar/changelog/2026-07-09-new-events-now-mark-the-creator-rsvp-as-yes.md +6 -0
  73. package/corpus/templates/calendar/server/handlers/events.ts +9 -2
  74. package/corpus/templates/calendar/server/lib/event-video-conferencing.ts +58 -0
  75. package/corpus/templates/calendar/server/lib/google-calendar.ts +1 -0
  76. package/corpus/templates/clips/changelog/2026-07-09-meeting-reminder-popovers-can-now-be-dismissed-with-a-top-le.md +6 -0
  77. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +3 -1
  78. package/corpus/templates/clips/desktop/src/styles.css +13 -9
  79. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +5 -2
  80. package/dist/agent/production-agent.d.ts +34 -1
  81. package/dist/agent/production-agent.d.ts.map +1 -1
  82. package/dist/agent/production-agent.js +58 -5
  83. package/dist/agent/production-agent.js.map +1 -1
  84. package/dist/cli/sync-builder-starter-manifest.js +1 -1
  85. package/dist/cli/sync-builder-starter-manifest.js.map +1 -1
  86. package/dist/client/AssistantChat.d.ts +3 -1
  87. package/dist/client/AssistantChat.d.ts.map +1 -1
  88. package/dist/client/AssistantChat.js +96 -15
  89. package/dist/client/AssistantChat.js.map +1 -1
  90. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  91. package/dist/client/chat/tool-call-display.js +5 -1
  92. package/dist/client/chat/tool-call-display.js.map +1 -1
  93. package/dist/client/org/TeamPage.d.ts.map +1 -1
  94. package/dist/client/org/TeamPage.js +32 -13
  95. package/dist/client/org/TeamPage.js.map +1 -1
  96. package/dist/client/settings/DemoModeSection.d.ts +3 -3
  97. package/dist/client/settings/DemoModeSection.js +4 -4
  98. package/dist/client/settings/DemoModeSection.js.map +1 -1
  99. package/dist/client/settings/SettingsPanel.js +1 -1
  100. package/dist/client/settings/SettingsPanel.js.map +1 -1
  101. package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
  102. package/dist/client/settings/SettingsTabsPage.js +38 -2
  103. package/dist/client/settings/SettingsTabsPage.js.map +1 -1
  104. package/dist/collab/routes.d.ts +1 -1
  105. package/dist/collab/struct-routes.d.ts +1 -1
  106. package/dist/demo/actions/toggle-demo-mode.js +2 -2
  107. package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
  108. package/dist/demo/config.js +3 -3
  109. package/dist/demo/config.js.map +1 -1
  110. package/dist/demo/redact.d.ts +1 -1
  111. package/dist/demo/redact.d.ts.map +1 -1
  112. package/dist/demo/redact.js +0 -0
  113. package/dist/demo/redact.js.map +1 -1
  114. package/dist/extensions/url-safety.d.ts +5 -0
  115. package/dist/extensions/url-safety.d.ts.map +1 -1
  116. package/dist/extensions/url-safety.js +10 -0
  117. package/dist/extensions/url-safety.js.map +1 -1
  118. package/dist/localization/default-messages.d.ts +7 -0
  119. package/dist/localization/default-messages.d.ts.map +1 -1
  120. package/dist/localization/default-messages.js +7 -0
  121. package/dist/localization/default-messages.js.map +1 -1
  122. package/dist/notifications/routes.d.ts +2 -2
  123. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  124. package/dist/resources/handlers.d.ts +2 -2
  125. package/dist/templates/default/app/i18n/ar-SA.ts +7 -0
  126. package/dist/templates/default/app/i18n/de-DE.ts +7 -0
  127. package/dist/templates/default/app/i18n/en-US.ts +7 -0
  128. package/dist/templates/default/app/i18n/es-ES.ts +7 -0
  129. package/dist/templates/default/app/i18n/fr-FR.ts +7 -0
  130. package/dist/templates/default/app/i18n/hi-IN.ts +7 -0
  131. package/dist/templates/default/app/i18n/ja-JP.ts +7 -0
  132. package/dist/templates/default/app/i18n/ko-KR.ts +7 -0
  133. package/dist/templates/default/app/i18n/pt-BR.ts +7 -0
  134. package/dist/templates/default/app/i18n/zh-CN.ts +7 -0
  135. package/dist/templates/default/app/i18n/zh-TW.ts +7 -0
  136. package/docs/content/template-assets.mdx +139 -0
  137. package/package.json +1 -1
  138. package/src/templates/default/app/i18n/ar-SA.ts +7 -0
  139. package/src/templates/default/app/i18n/de-DE.ts +7 -0
  140. package/src/templates/default/app/i18n/en-US.ts +7 -0
  141. package/src/templates/default/app/i18n/es-ES.ts +7 -0
  142. package/src/templates/default/app/i18n/fr-FR.ts +7 -0
  143. package/src/templates/default/app/i18n/hi-IN.ts +7 -0
  144. package/src/templates/default/app/i18n/ja-JP.ts +7 -0
  145. package/src/templates/default/app/i18n/ko-KR.ts +7 -0
  146. package/src/templates/default/app/i18n/pt-BR.ts +7 -0
  147. package/src/templates/default/app/i18n/zh-CN.ts +7 -0
  148. package/src/templates/default/app/i18n/zh-TW.ts +7 -0
@@ -5,12 +5,14 @@ import {
5
5
  getRequestUserEmail,
6
6
  } from "@agent-native/core/server/request-context";
7
7
  import { assertAccess } from "@agent-native/core/sharing";
8
- import { and, eq, inArray, ne } from "drizzle-orm";
8
+ import { and, eq, inArray, ne, notInArray } from "drizzle-orm";
9
9
  import { nanoid } from "nanoid";
10
10
  import { z } from "zod";
11
11
 
12
12
  import { getDb, schema } from "../server/db/index.js";
13
13
  import { nowIso, parseJson, stringifyJson } from "../server/lib/json.js";
14
+ import { normalizePresetReferences } from "../server/lib/preset-references.js";
15
+ import { normalizePresetSkeletonSpec } from "../server/lib/preset-skeleton.js";
14
16
  import {
15
17
  serializeAsset,
16
18
  serializeGenerationPreset,
@@ -57,6 +59,35 @@ function remapJsonText(
57
59
  return stringifyJson(remapJsonValue(parseJson(text, {}), ids));
58
60
  }
59
61
 
62
+ // Assets pinned by preset settings (reference board entries, skeleton plate/
63
+ // mask/foreground) must be copied even when the general asset filter would
64
+ // skip them — e.g. board subject photos upload as role "subject_reference".
65
+ // Otherwise the duplicated preset keeps source-library asset ids and
66
+ // generation in the copy fails its library-membership check.
67
+ function pinnedPresetAssetIds(
68
+ presets: Array<{ settings: string | null }>,
69
+ ): Set<string> {
70
+ const ids = new Set<string>();
71
+ for (const preset of presets) {
72
+ const settings = parseJson<{
73
+ skeletonSpec?: unknown;
74
+ presetReferences?: unknown;
75
+ }>(preset.settings, {});
76
+ for (const entry of normalizePresetReferences(settings.presetReferences)) {
77
+ for (const assetId of entry.assetIds) ids.add(assetId);
78
+ }
79
+ const skeleton = normalizePresetSkeletonSpec(settings.skeletonSpec);
80
+ if (skeleton) {
81
+ ids.add(skeleton.background.assetId);
82
+ if (skeleton.mask) ids.add(skeleton.mask.assetId);
83
+ for (const layer of skeleton.foreground ?? []) {
84
+ if (typeof layer.source === "object") ids.add(layer.source.assetId);
85
+ }
86
+ }
87
+ }
88
+ return ids;
89
+ }
90
+
60
91
  export default defineAction({
61
92
  description:
62
93
  "Duplicate a Brand Kit into a new private copy owned by the current user. Copies the kit metadata, collections, folders, generation presets, and curated reference/saved assets, but not generation history, handoff sessions, shares, or visibility.",
@@ -127,6 +158,23 @@ export default defineAction({
127
158
  ),
128
159
  ]);
129
160
 
161
+ const missingPinnedAssetIds = [...pinnedPresetAssetIds(presets)].filter(
162
+ (assetId) => !assets.some((asset) => asset.id === assetId),
163
+ );
164
+ if (missingPinnedAssetIds.length) {
165
+ const pinnedAssets = await db
166
+ .select()
167
+ .from(schema.assets)
168
+ .where(
169
+ and(
170
+ eq(schema.assets.libraryId, id),
171
+ inArray(schema.assets.id, missingPinnedAssetIds),
172
+ notInArray(schema.assets.status, ["archived", "failed"]),
173
+ ),
174
+ );
175
+ assets.push(...pinnedAssets);
176
+ }
177
+
130
178
  const collectionIds = new Map(
131
179
  collections.map((collection) => [collection.id, nanoid()]),
132
180
  );
@@ -23,9 +23,14 @@ import { upsertVariantSlot } from "./variant-slots.js";
23
23
 
24
24
  const IMAGE_GENERATION_TOOL_TIMEOUT_MS = 12 * 60_000;
25
25
 
26
+ const presetReferenceFillSchema = z.object({
27
+ referenceId: z.string().min(1),
28
+ assetIds: z.array(z.string().min(1)).min(1).max(4),
29
+ });
30
+
26
31
  export default defineAction({
27
32
  description:
28
- "Generate several brand-consistent images in parallel from one brand kit/library. Use @brand-kit mentions as libraryId and @preset mentions as presetId when present. This is synchronous for images: one call waits for every slot and returns final image artifacts. Use this for slide decks, landing pages, and multi-slot design work. Do not call get-generation-run or refresh-generation-run after a normal image batch result.",
33
+ "Generate several brand-consistent images in parallel from one brand kit/library. Use @brand-kit mentions as libraryId and @preset mentions as presetId when present. If no preset is tagged, call list-generation-presets first and use a matching preset's presetId; the user may not know presets exist. Generate presetless only when no preset matches the request. This is synchronous for images: one call waits for every slot and returns final image artifacts. Use this for slide decks, landing pages, and multi-slot design work. Do not call get-generation-run or refresh-generation-run after a normal image batch result.",
29
34
  schema: z.object({
30
35
  libraryId: z
31
36
  .string()
@@ -40,6 +45,13 @@ export default defineAction({
40
45
  .describe(
41
46
  "Generation preset ID (from a @preset mention or list-generation-presets). The preset already defines aspectRatio, imageSize, model, tier, and category. When you set presetId, OMIT each slot's aspectRatio/imageSize and the top-level model/tier so the preset's values are used; only pass one when the user explicitly asks for a value that differs from the preset.",
42
47
  ),
48
+ presetReferenceFills: z
49
+ .array(presetReferenceFillSchema)
50
+ .max(6)
51
+ .optional()
52
+ .describe(
53
+ 'Per-run images for the preset\'s reference board (see the tagged preset brief or list-generation-presets settings.presetReferences). Each fill REPLACES the images of one variable entry by id, e.g. [{ referenceId: "guest-speaker", assetIds: ["..."] }]. Required entries without pinned images MUST be filled or the call fails. Only valid when presetId is set. Applies to every slot in the batch.',
54
+ ),
43
55
  sessionId: z.string().optional(),
44
56
  slots: z
45
57
  .array(
@@ -150,6 +162,7 @@ export default defineAction({
150
162
  libraryId: base.libraryId,
151
163
  collectionId: base.collectionId,
152
164
  presetId: base.presetId,
165
+ presetReferenceFills: base.presetReferenceFills,
153
166
  sessionId: base.sessionId,
154
167
  prompt: slot.prompt,
155
168
  embeddedText: slot.embeddedText,
@@ -9,7 +9,7 @@ import {
9
9
  getRequestOrgId,
10
10
  } from "@agent-native/core/server/request-context";
11
11
  import { assertAccess } from "@agent-native/core/sharing";
12
- import { eq } from "drizzle-orm";
12
+ import { eq, inArray } from "drizzle-orm";
13
13
  import { nanoid } from "nanoid";
14
14
  import { z } from "zod";
15
15
 
@@ -21,6 +21,7 @@ import {
21
21
  DEFAULT_GENERATION_REFERENCE_LIMIT,
22
22
  generateWithManagedImageProvider,
23
23
  isImageGenerationSetupError,
24
+ loadReferenceData,
24
25
  resolveImageModelForRequest,
25
26
  selectReferences,
26
27
  type ReferenceForGeneration,
@@ -33,6 +34,11 @@ import {
33
34
  prepareGptImage2SkeletonInpaintImages,
34
35
  } from "../server/lib/image-processing.js";
35
36
  import { nowIso, parseJson, stringifyJson } from "../server/lib/json.js";
37
+ import {
38
+ normalizePresetReferences,
39
+ PRESET_REFERENCE_ROLE_MAP,
40
+ resolvePresetReferenceFills,
41
+ } from "../server/lib/preset-references.js";
36
42
  import {
37
43
  aspectRatioValue,
38
44
  clampCutoutAspectRatio,
@@ -65,9 +71,14 @@ import { upsertVariantSlot, wasVariantSlotDismissed } from "./variant-slots.js";
65
71
 
66
72
  const IMAGE_GENERATION_TOOL_TIMEOUT_MS = 12 * 60_000;
67
73
 
74
+ const presetReferenceFillSchema = z.object({
75
+ referenceId: z.string().min(1),
76
+ assetIds: z.array(z.string().min(1)).min(1).max(4),
77
+ });
78
+
68
79
  export default defineAction({
69
80
  description:
70
- "Generate one brand-consistent image from a brand kit/library. This is synchronous for images and returns the final asset with preview/download/embed URLs. Use @brand-kit mentions as libraryId and @preset mentions as presetId when present. Use generate-image-batch for multiple independent slots; do not poll image runs after this action returns.",
81
+ "Generate one brand-consistent image from a brand kit/library. This is synchronous for images and returns the final asset with preview/download/embed URLs. Use @brand-kit mentions as libraryId and @preset mentions as presetId when present. If no preset is tagged, call list-generation-presets first and use a matching preset's presetId; the user may not know presets exist. Generate presetless only when no preset matches the request. Use generate-image-batch for multiple independent slots; do not poll image runs after this action returns.",
71
82
  schema: z.object({
72
83
  libraryId: z
73
84
  .string()
@@ -124,6 +135,13 @@ export default defineAction({
124
135
  .describe(
125
136
  "Exact reference assets to use. When omitted, the server deterministically chooses a small relevant subset from the latest library references.",
126
137
  ),
138
+ presetReferenceFills: z
139
+ .array(presetReferenceFillSchema)
140
+ .max(6)
141
+ .optional()
142
+ .describe(
143
+ 'Per-run images for the preset\'s reference board (see the tagged preset brief or list-generation-presets settings.presetReferences). Each fill REPLACES the images of one variable entry by id, e.g. [{ referenceId: "guest-speaker", assetIds: ["..."] }]. Required entries without pinned images MUST be filled or the call fails. Only valid when presetId is set.',
144
+ ),
127
145
  includeLogo: z.coerce
128
146
  .boolean()
129
147
  .optional()
@@ -281,10 +299,27 @@ export default defineAction({
281
299
  tier?: ImageQualityTier;
282
300
  includeLogo?: boolean;
283
301
  skeletonSpec?: unknown;
302
+ presetReferences?: unknown;
284
303
  }>(preset?.settings, {});
285
304
  const skeletonSpec = normalizePresetSkeletonSpec(
286
305
  presetSettings.skeletonSpec,
287
306
  );
307
+ const presetReferences = normalizePresetReferences(
308
+ presetSettings.presetReferences,
309
+ );
310
+ if (args.presetReferenceFills?.length && !preset) {
311
+ throw new Error(
312
+ "presetReferenceFills requires a presetId whose preset declares a reference board.",
313
+ );
314
+ }
315
+ const resolvedPresetReferences = resolvePresetReferenceFills({
316
+ entries: presetReferences,
317
+ fills: args.presetReferenceFills,
318
+ presetTitle: preset?.title ?? "",
319
+ });
320
+ const boardAssignments = Object.fromEntries(
321
+ resolvedPresetReferences.map((item) => [item.entry.id, item.assetIds]),
322
+ );
288
323
  const isSkeletonCutout = skeletonSpec?.contentMode === "cutout";
289
324
  const outputAspectRatio = (args.aspectRatio ??
290
325
  preset?.aspectRatio ??
@@ -333,11 +368,32 @@ export default defineAction({
333
368
  model: resolvedModel,
334
369
  });
335
370
  }
371
+ // Mirror the create/update-generation-preset guard for per-run model
372
+ // overrides: subject board photos on a model without character
373
+ // consistency would silently ignore the people they exist to preserve.
374
+ if (
375
+ resolvedModel === "gemini-2.5-flash-image" &&
376
+ resolvedPresetReferences.some((item) => item.entry.role === "subject")
377
+ ) {
378
+ throw new Error(
379
+ "Subject reference entries need a model with character consistency. Use gemini-3.1-flash-image, gemini-3-pro-image, gpt-image-1, or gpt-image-2.",
380
+ );
381
+ }
336
382
  const resolvedCategories =
337
383
  args.categories ??
338
384
  (preset?.category ? ([preset.category] as any) : undefined);
339
385
  const skeletonReferenceExclusionIds =
340
386
  skeletonCompositeAssetIds(skeletonSpec);
387
+ const boardRefs = await loadPresetReferenceBoardRefs({
388
+ db,
389
+ libraryId: args.libraryId,
390
+ resolved: resolvedPresetReferences,
391
+ });
392
+ const boardAssetIdSet = new Set(boardRefs.map((ref) => ref.id));
393
+ const referenceExclusionIds = [
394
+ ...(skeletonReferenceExclusionIds ?? []),
395
+ ...boardAssetIdSet,
396
+ ];
341
397
  const skeletonAssets = skeletonSpec
342
398
  ? await loadSkeletonAssets({
343
399
  spec: skeletonSpec,
@@ -395,35 +451,67 @@ export default defineAction({
395
451
  .join("\n")
396
452
  : "";
397
453
  let references: ReferenceForGeneration[];
454
+ const baseReferenceLimit =
455
+ args.intent !== "restyle" &&
456
+ preset?.referencePolicy === "explicit" &&
457
+ !args.referenceAssetIds?.length
458
+ ? 0
459
+ : DEFAULT_GENERATION_REFERENCE_LIMIT;
398
460
  if (useSkeletonInpaint) {
399
- references = skeletonInpaint?.references ?? [];
461
+ const guidanceReferenceLimit = Math.max(
462
+ 0,
463
+ baseReferenceLimit - boardRefs.length,
464
+ );
465
+ const guidanceReferences =
466
+ baseReferenceLimit > 0 || args.referenceAssetIds?.length
467
+ ? await selectReferences({
468
+ libraryId: args.libraryId,
469
+ collectionId: resolvedCollectionId,
470
+ categories: resolvedCategories,
471
+ referenceAssetIds: args.referenceAssetIds,
472
+ excludeAssetIds: referenceExclusionIds,
473
+ sourceAssetId: args.sourceAssetId,
474
+ subjectAssetId: args.subjectAssetId,
475
+ intent: args.intent,
476
+ limit: guidanceReferenceLimit,
477
+ })
478
+ : [];
479
+ const inpaintReferences = skeletonInpaint?.references ?? [];
480
+ const [plateReference, ...maskReferences] = inpaintReferences;
481
+ references = [
482
+ ...(plateReference ? [plateReference] : []),
483
+ ...boardRefs.map(referenceForSkeletonInpaintGuidance),
484
+ ...guidanceReferences.map(referenceForSkeletonInpaintGuidance),
485
+ ...maskReferences,
486
+ ];
400
487
  } else {
401
488
  const backgroundPlateReference =
402
489
  backgroundPlateReferenceForSkeleton(skeletonAssets);
403
- const baseReferenceLimit =
404
- args.intent !== "restyle" &&
405
- preset?.referencePolicy === "explicit" &&
406
- !args.referenceAssetIds?.length
407
- ? 0
408
- : DEFAULT_GENERATION_REFERENCE_LIMIT;
409
- const referenceLimit =
410
- backgroundPlateReference && baseReferenceLimit > 0
490
+ const referenceLimit = Math.max(
491
+ 0,
492
+ (backgroundPlateReference && baseReferenceLimit > 0
411
493
  ? baseReferenceLimit + 1
412
- : baseReferenceLimit;
413
- references = await selectReferences({
414
- libraryId: args.libraryId,
415
- collectionId: resolvedCollectionId,
416
- categories: resolvedCategories,
417
- referenceAssetIds: args.referenceAssetIds,
418
- excludeAssetIds: skeletonReferenceExclusionIds,
419
- sourceAssetId: args.sourceAssetId,
420
- subjectAssetId: args.subjectAssetId,
421
- intent: args.intent,
422
- limit: referenceLimit,
423
- });
424
- if (backgroundPlateReference) {
425
- references.unshift(backgroundPlateReference);
426
- }
494
+ : baseReferenceLimit) - boardRefs.length,
495
+ );
496
+ const autoReferences =
497
+ referenceLimit > 0 || args.referenceAssetIds?.length
498
+ ? await selectReferences({
499
+ libraryId: args.libraryId,
500
+ collectionId: resolvedCollectionId,
501
+ categories: resolvedCategories,
502
+ referenceAssetIds: args.referenceAssetIds,
503
+ excludeAssetIds: referenceExclusionIds,
504
+ sourceAssetId: args.sourceAssetId,
505
+ subjectAssetId: args.subjectAssetId,
506
+ intent: args.intent,
507
+ limit: referenceLimit,
508
+ })
509
+ : [];
510
+ references = [
511
+ ...(backgroundPlateReference ? [backgroundPlateReference] : []),
512
+ ...boardRefs,
513
+ ...autoReferences,
514
+ ];
427
515
  }
428
516
  const compiledPrompt = compilePrompt({
429
517
  libraryTitle: library.title,
@@ -448,6 +536,12 @@ export default defineAction({
448
536
  category,
449
537
  intent: args.intent,
450
538
  styleStrength: args.styleStrength,
539
+ referenceBoard: resolvedPresetReferences.map((item) => ({
540
+ label: item.entry.label,
541
+ role: item.entry.role,
542
+ count: item.assetIds.length,
543
+ description: item.entry.description,
544
+ })),
451
545
  });
452
546
  const runId = nanoid();
453
547
  const now = nowIso();
@@ -476,6 +570,7 @@ export default defineAction({
476
570
  selectionReasons: Object.fromEntries(
477
571
  references.map((ref) => [ref.id, ref.selectionReason ?? "scored"]),
478
572
  ),
573
+ boardAssignments,
479
574
  };
480
575
  const settingsUsed = {
481
576
  model: resolvedModel,
@@ -497,6 +592,7 @@ export default defineAction({
497
592
  embeddedText: args.embeddedText ?? null,
498
593
  textPlacement: args.textPlacement ?? null,
499
594
  customInstructions: library.customInstructions ?? "",
595
+ boardAssignments,
500
596
  };
501
597
  const dismissibleSlot = args.dismissible !== false && Boolean(slotId);
502
598
  const baseMetadata = {
@@ -593,6 +689,7 @@ export default defineAction({
593
689
  collectionId: resolvedCollectionId ?? null,
594
690
  source: args.source,
595
691
  callerAppId: args.callerAppId,
692
+ hasBoardReferences: boardRefs.length > 0,
596
693
  }),
597
694
  );
598
695
  await deleteAppState("image-generation-setup").catch(() => {});
@@ -847,6 +944,62 @@ function backgroundPlateReferenceForSkeleton(
847
944
  };
848
945
  }
849
946
 
947
+ async function loadPresetReferenceBoardRefs(input: {
948
+ db: any;
949
+ libraryId: string;
950
+ resolved: Array<{
951
+ entry: {
952
+ id: string;
953
+ role: keyof typeof PRESET_REFERENCE_ROLE_MAP;
954
+ };
955
+ assetIds: string[];
956
+ }>;
957
+ }): Promise<ReferenceForGeneration[]> {
958
+ const assetIds = [
959
+ ...new Set(input.resolved.flatMap((item) => item.assetIds)),
960
+ ];
961
+ if (!assetIds.length) return [];
962
+
963
+ const rows = await input.db
964
+ .select()
965
+ .from(schema.assets)
966
+ .where(inArray(schema.assets.id, assetIds));
967
+ const byId = new Map<string, any>(
968
+ (rows as any[]).map((asset) => [asset.id, asset]),
969
+ );
970
+ const valid = assetIds.every((assetId) => {
971
+ const asset = byId.get(assetId);
972
+ return (
973
+ asset &&
974
+ asset.libraryId === input.libraryId &&
975
+ typeof asset.mimeType === "string" &&
976
+ asset.mimeType.startsWith("image/") &&
977
+ asset.status !== "archived" &&
978
+ asset.status !== "failed"
979
+ );
980
+ });
981
+ if (!valid) {
982
+ throw new Error(
983
+ "Reference board images must be images in this asset library.",
984
+ );
985
+ }
986
+
987
+ const refs: ReferenceForGeneration[] = [];
988
+ for (const item of input.resolved) {
989
+ const assets = item.assetIds
990
+ .map((assetId) => byId.get(assetId))
991
+ .filter((asset): asset is any => asset != null);
992
+ refs.push(
993
+ ...(await loadReferenceData(
994
+ assets,
995
+ () => PRESET_REFERENCE_ROLE_MAP[item.entry.role],
996
+ () => `preset-ref:${item.entry.id}`,
997
+ )),
998
+ );
999
+ }
1000
+ return refs;
1001
+ }
1002
+
850
1003
  async function inpaintReferencesForSkeleton(
851
1004
  skeletonAssets: LoadedSkeletonAssets | null,
852
1005
  ): Promise<{
@@ -897,6 +1050,22 @@ async function inpaintReferencesForSkeleton(
897
1050
  };
898
1051
  }
899
1052
 
1053
+ function referenceForSkeletonInpaintGuidance(
1054
+ ref: ReferenceForGeneration,
1055
+ ): ReferenceForGeneration {
1056
+ if (
1057
+ ref.role !== "subject_reference" &&
1058
+ ref.role !== "generated" &&
1059
+ ref.role !== "edit_target"
1060
+ ) {
1061
+ return ref;
1062
+ }
1063
+ return {
1064
+ ...ref,
1065
+ role: "product_reference",
1066
+ };
1067
+ }
1068
+
900
1069
  function closestSupportedAspectRatioForSize(
901
1070
  size: { width: number; height: number },
902
1071
  supported: readonly AspectRatio[],