Skip to content

Commit f9d26b8

Browse files
fix(core): Refresh MediaPicker3 thumbnails after AI-driven value changes
The MediaPicker3 thumbnail subcomponent fetches the rendered media on mount and doesn't re-fetch when its mediaKey prop changes within an existing picker entry. So when an LLM swaps an image via set_value, the underlying value is correct (right mediaKey, right entry shape) but the visible thumbnail keeps showing the previous image until the user saves and reloads. Lit re-mounts a subcomponent only when its surrounding entry key changes, so to force a fresh fetch we re-mint the entry's `key` (distinct from `mediaKey`) when its content actually changed. The prior hand-rolled `[0].key = uuidv4()` only fired on string-parsed input and only touched the first entry; the new path: - Compares each incoming entry to the same-keyed entry in the current staged value. - Regenerates the key only when the entry exists in the old value AND its non-key content differs. This forces re-mount for replaced entries. - Leaves keys alone when content is identical (no churn for entries the LLM didn't touch — focal points, crops, etc. stay stable). - Leaves keys alone for entries whose key isn't in the old value at all — those are genuinely new (e.g. minted by `add_item`'s handler) and the LLM may reference them via `remove_item({ blockKey })` later, so we must not change them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 297cbe4 commit f9d26b8

1 file changed

Lines changed: 39 additions & 3 deletions

File tree

Umbraco.AI/src/Umbraco.AI.Web.StaticAssets/Client/src/entity-adapter/adapters/value-preparation.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,44 @@ export function prepareValueForEditor(value: unknown, editorAlias?: string, curr
4545
// Try to parse JSON values
4646
try {
4747
valueToSet = JSON.parse(valueToSet as string);
48-
if (editorAlias === "Umbraco.MediaPicker3") {
49-
(valueToSet as Array<{ key: string }>)[0].key = crypto.randomUUID();
50-
}
5148
} catch {
5249
// Not JSON, use as-is
5350
}
5451

52+
// MediaPicker3: re-mint the entry's `key` when its content changed against the staged value.
53+
// The picker's thumbnail subcomponent fetches the resolved media on mount and doesn't re-fetch
54+
// when only `mediaKey` changes within the same entry; replacing the entry's `key` forces lit to
55+
// unmount and re-mount, which re-fetches the thumbnail. We only regen when the entry's
56+
// non-key content actually changed — that way add_item's handler-minted fresh keys stay stable
57+
// (so the LLM's subsequent remove_item({blockKey}) still resolves), and unchanged entries
58+
// don't churn their per-entry settings (focal point, crops, …).
59+
if (editorAlias === "Umbraco.MediaPicker3" && Array.isArray(valueToSet)) {
60+
const oldByKey = new Map<string, Record<string, unknown>>();
61+
if (Array.isArray(currentValue)) {
62+
for (const entry of currentValue as Array<Record<string, unknown>>) {
63+
const k = entry?.key;
64+
if (typeof k === "string") {
65+
oldByKey.set(k, entry);
66+
}
67+
}
68+
}
69+
70+
valueToSet = (valueToSet as Array<Record<string, unknown>>).map((entry) => {
71+
const key = typeof entry?.key === "string" ? entry.key : undefined;
72+
if (!key) {
73+
return { ...entry, key: crypto.randomUUID() };
74+
}
75+
const old = oldByKey.get(key);
76+
if (!old) {
77+
return entry;
78+
}
79+
if (JSON.stringify(omitKey(old)) === JSON.stringify(omitKey(entry))) {
80+
return entry;
81+
}
82+
return { ...entry, key: crypto.randomUUID() };
83+
});
84+
}
85+
5586
// Wrap in TipTap's expected format for RichText properties
5687
if (editorAlias === "Umbraco.RichText") {
5788
const current = currentValue as { markup?: string; blocks?: object } | undefined;
@@ -63,3 +94,8 @@ export function prepareValueForEditor(value: unknown, editorAlias?: string, curr
6394

6495
return valueToSet;
6596
}
97+
98+
function omitKey(entry: Record<string, unknown>): Record<string, unknown> {
99+
const { key: _key, ...rest } = entry;
100+
return rest;
101+
}

0 commit comments

Comments
 (0)