Skip to content

Commit 990c3f9

Browse files
committed
Entry Gallerey operations and info for AI Assistant
1 parent c05d07a commit 990c3f9

4 files changed

Lines changed: 155 additions & 4 deletions

File tree

_api_app/tests/Feature/AiChatControllerTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,91 @@
247247
->toContain('Domains')
248248
->toContain('SSL Certificates and HTTPS');
249249
})->skip(! $pluginInstalled, 'AiAssistant plugin not installed');
250+
251+
it('parses gallery_changes from ai response', function () {
252+
AssistantAgent::fake([
253+
'{"reply": "Updated gallery type.", "is_undo": false, "design_changes": [], "settings_changes": [], "section_changes": [], "entry_changes": [], "gallery_changes": [{"operation": "update_setting", "section": "portfolio", "entry_id": "3", "setting": "type", "value": "row"}]}',
254+
]);
255+
256+
$agent = new AssistantAgent('system prompt');
257+
$result = $agent->chat([['role' => 'user', 'content' => 'change gallery to row']]);
258+
259+
expect($result['gallery_changes'])->toHaveCount(1)
260+
->and($result['gallery_changes'][0]['operation'])->toBe('update_setting')
261+
->and($result['gallery_changes'][0]['section'])->toBe('portfolio')
262+
->and($result['gallery_changes'][0]['entry_id'])->toBe('3')
263+
->and($result['gallery_changes'][0]['setting'])->toBe('type')
264+
->and($result['gallery_changes'][0]['value'])->toBe('row');
265+
})->skip(! $pluginInstalled, 'AiAssistant plugin not installed');
266+
267+
it('returns empty gallery_changes when not in ai response', function () {
268+
AssistantAgent::fake([
269+
'{"reply": "Done.", "design_changes": [], "settings_changes": [], "section_changes": []}',
270+
]);
271+
272+
$agent = new AssistantAgent('system prompt');
273+
$result = $agent->chat([['role' => 'user', 'content' => 'hello']]);
274+
275+
expect($result['gallery_changes'])->toBeArray()->toBeEmpty();
276+
})->skip(! $pluginInstalled, 'AiAssistant plugin not installed');
277+
278+
it('includes gallery changes in change history section', function () {
279+
$controller = new AiChatController;
280+
$method = new ReflectionMethod($controller, 'buildChangeHistorySection');
281+
282+
$changeHistory = [
283+
[
284+
'user_message' => 'change gallery to row',
285+
'design_changes' => [],
286+
'settings_changes' => [],
287+
'section_changes' => [],
288+
'entry_changes' => [],
289+
'gallery_changes' => [
290+
['operation' => 'update_setting', 'section' => 'portfolio', 'entry_id' => '3', 'setting' => 'type', 'value' => 'row', 'previous_value' => 'slideshow'],
291+
],
292+
],
293+
[
294+
'user_message' => 'update image caption',
295+
'design_changes' => [],
296+
'settings_changes' => [],
297+
'section_changes' => [],
298+
'entry_changes' => [],
299+
'gallery_changes' => [
300+
['operation' => 'update_caption', 'section' => 'portfolio', 'entry_id' => '3', 'file_index' => 0, 'value' => '<p>New caption</p>', 'previous_value' => '<p>Old caption</p>'],
301+
],
302+
],
303+
];
304+
305+
$result = $method->invoke($controller, $changeHistory);
306+
307+
expect($result)
308+
->toContain('gallery: update_setting entry #3 in "portfolio"')
309+
->toContain('"slideshow" → "row"')
310+
->toContain('gallery: update_caption entry #3 in "portfolio" file[0]')
311+
->toContain('"Old caption" → "New caption"');
312+
})->skip(! $pluginInstalled, 'AiAssistant plugin not installed');
313+
314+
it('restricts change_history gallery_changes operation to update_setting and update_caption', function () {
315+
$request = new AiChatRequest;
316+
$rules = $request->rules();
317+
318+
expect($rules['change_history.*.gallery_changes.*.operation'])
319+
->toContain('in:update_setting,update_caption');
320+
})->skip(! $pluginInstalled, 'AiAssistant plugin not installed');
321+
322+
it('includes galleries section in system prompt', function () {
323+
$controller = new AiChatController;
324+
$method = new ReflectionMethod($controller, 'buildGalleriesSection');
325+
326+
$result = $method->invoke($controller);
327+
328+
expect($result)
329+
->toContain('Galleries')
330+
->toContain('get_entry_gallery')
331+
->toContain('update_setting')
332+
->toContain('update_caption')
333+
->toContain('slideshow')
334+
->toContain('fullscreen')
335+
->toContain('file_index')
336+
->toContain('manual');
337+
})->skip(! $pluginInstalled, 'AiAssistant plugin not installed');

editor/src/app/ai-assistant/ai-assistant.actions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export class AiMessageReceivedAction {
1616
public sectionChanges: { operation: string; name?: string; title?: string; property?: string; value?: string; previous_value?: string | null; order?: number }[],
1717
public isUndo: boolean = false,
1818
public entryChanges: { operation: 'create' | 'update' | 'delete'; section?: string; entry_id?: string; value?: string; previous_value?: string | null; description?: string }[] = [],
19+
public galleryChanges: { operation: 'update_setting' | 'update_caption'; section?: string; entry_id?: string; setting?: string; file_index?: number; value?: string; previous_value?: string | null }[] = [],
1920
) {}
2021
}
2122

editor/src/app/ai-assistant/ai-assistant.service.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,24 @@ export interface AiEntryChangeItem {
2929
description?: string;
3030
}
3131

32+
export interface AiGalleryChangeItem {
33+
operation: 'update_setting' | 'update_caption';
34+
section?: string;
35+
entry_id?: string;
36+
setting?: string;
37+
file_index?: number;
38+
value?: string;
39+
previous_value?: string | null;
40+
}
41+
3242
export interface AiChatResponse {
3343
reply: string;
3444
is_undo: boolean;
3545
design_changes: AiChangeItem[];
3646
settings_changes: AiChangeItem[];
3747
section_changes: AiSectionChangeItem[];
3848
entry_changes: AiEntryChangeItem[];
49+
gallery_changes: AiGalleryChangeItem[];
3950
}
4051

4152
@Injectable({
@@ -49,7 +60,7 @@ export class AiAssistantService {
4960
history: { role: string; content: string }[],
5061
site: string,
5162
template: string,
52-
changeHistory: { user_message: string; design_changes: AiChangeItem[]; settings_changes: AiChangeItem[]; section_changes: AiSectionChangeItem[]; entry_changes: AiEntryChangeItem[] }[] = [],
63+
changeHistory: { user_message: string; design_changes: AiChangeItem[]; settings_changes: AiChangeItem[]; section_changes: AiSectionChangeItem[]; entry_changes: AiEntryChangeItem[]; gallery_changes: AiGalleryChangeItem[] }[] = [],
5364
): Observable<AiChatResponse> {
5465
return this.appStateService
5566
.sync('aiChat', { message, history, site, template, change_history: changeHistory }, 'POST')

editor/src/app/ai-assistant/ai-assistant.state.ts

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
DeleteSectionEntryFromSyncAction,
2323
} from '../sites/sections/entries/entries-state/section-entries.actions';
2424
import { SectionEntriesState } from '../sites/sections/entries/entries-state/section-entries.state';
25-
import { AiAssistantService, AiEntryChangeItem as AiEntryChangeResponseItem, AiSectionChangeItem as AiSectionChangeResponseItem } from './ai-assistant.service';
25+
import { AiAssistantService, AiEntryChangeItem as AiEntryChangeResponseItem, AiGalleryChangeItem as AiGalleryChangeResponseItem, AiSectionChangeItem as AiSectionChangeResponseItem } from './ai-assistant.service';
2626
import {
2727
ToggleAiAssistantAction,
2828
SendAiMessageAction,
@@ -61,12 +61,23 @@ export interface AiEntryChangeItem {
6161
description?: string;
6262
}
6363

64+
export interface AiGalleryChangeItem {
65+
operation: string;
66+
section?: string;
67+
entryId?: string;
68+
setting?: string;
69+
fileIndex?: number;
70+
value?: string;
71+
previousValue?: string | null;
72+
}
73+
6474
export interface AiChangeHistoryItem {
6575
userMessage: string;
6676
designChanges: AiChangeItem[];
6777
settingsChanges: AiChangeItem[];
6878
sectionChanges: AiSectionChangeItem[];
6979
entryChanges: AiEntryChangeItem[];
80+
galleryChanges: AiGalleryChangeItem[];
7081
}
7182

7283
export interface AiAssistantStateModel {
@@ -165,14 +176,23 @@ export class AiAssistantState {
165176
value: c.value,
166177
previous_value: c.previousValue,
167178
})),
179+
gallery_changes: entry.galleryChanges.map((c) => ({
180+
operation: c.operation as AiGalleryChangeResponseItem['operation'],
181+
section: c.section,
182+
entry_id: c.entryId,
183+
setting: c.setting,
184+
file_index: c.fileIndex,
185+
value: c.value,
186+
previous_value: c.previousValue,
187+
})),
168188
}));
169189

170190
return this.aiAssistantService
171191
.chat(action.message, history, site, template, changeHistoryPayload)
172192
.pipe(
173193
tap((response) => {
174194
dispatch(
175-
new AiMessageReceivedAction(response.reply, response.design_changes, response.settings_changes, response.section_changes ?? [], response.is_undo, response.entry_changes ?? []),
195+
new AiMessageReceivedAction(response.reply, response.design_changes, response.settings_changes, response.section_changes ?? [], response.is_undo, response.entry_changes ?? [], response.gallery_changes ?? []),
176196
);
177197
}),
178198
catchError((error) => {
@@ -245,12 +265,22 @@ export class AiAssistantState {
245265
previousValue: c.previous_value ?? null,
246266
description: c.description,
247267
})),
268+
galleryChanges: action.galleryChanges.map((c) => ({
269+
operation: c.operation,
270+
section: c.section,
271+
entryId: c.entry_id,
272+
setting: c.setting,
273+
fileIndex: c.file_index,
274+
value: c.value,
275+
previousValue: c.previous_value ?? null,
276+
})),
248277
};
249278
const hasChanges =
250279
newItem.designChanges.length > 0 ||
251280
newItem.settingsChanges.length > 0 ||
252281
newItem.sectionChanges.length > 0 ||
253-
newItem.entryChanges.length > 0;
282+
newItem.entryChanges.length > 0 ||
283+
newItem.galleryChanges.length > 0;
254284
changeHistory = hasChanges ? [...state.changeHistory, newItem] : state.changeHistory;
255285
}
256286

@@ -476,6 +506,27 @@ export class AiAssistantState {
476506
}
477507
}
478508

509+
// Gallery: update gallery settings and file captions
510+
for (const change of action.galleryChanges) {
511+
if (!change.entry_id || !change.section) continue;
512+
513+
if (change.operation === 'update_setting' && change.setting) {
514+
dispatch(
515+
new UpdateSectionEntryFromSyncAction(
516+
`${site}/entry/${change.section}/${change.entry_id}/mediaCacheData/@attributes/${change.setting}`,
517+
change.value ?? '',
518+
),
519+
);
520+
} else if (change.operation === 'update_caption' && change.file_index != null) {
521+
dispatch(
522+
new UpdateSectionEntryFromSyncAction(
523+
`${site}/entry/${change.section}/${change.entry_id}/mediaCacheData/file/${change.file_index}/@value`,
524+
change.value ?? '',
525+
),
526+
);
527+
}
528+
}
529+
479530
// Entry: create dispatches — sequential, capturing server-assigned entry ID for undo
480531
const createEntryChanges = action.entryChanges.filter((c) => c.operation === 'create');
481532
const createEntryChain = from(createEntryChanges).pipe(

0 commit comments

Comments
 (0)