Skip to content

Commit a712a28

Browse files
committed
feat: convert generate image modal to the new UI
1 parent 613349b commit a712a28

File tree

10 files changed

+436
-389
lines changed

10 files changed

+436
-389
lines changed

_build/js/src/chatHistory.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1+
export type MessageType = 'text' | 'image';
2+
3+
export type UpdatableHTMLElement = HTMLElement & {
4+
update?: (msg: Message) => void
5+
};
6+
17
export type Message = {
28
content: string;
3-
id?: string;
9+
id: string;
410
hidden: boolean;
511
role: string;
6-
el?: HTMLDivElement;
12+
type: MessageType;
13+
el?: UpdatableHTMLElement;
14+
ctx: Record<string, unknown>;
715
};
816

917
type Namespace = {
1018
history: Message[];
1119
idRef: Record<string, Message>;
12-
onAddMessage: (msg: Message) => HTMLDivElement | undefined;
13-
onUpdateMessage: (message: Message) => void;
20+
onAddMessage: (msg: Message) => UpdatableHTMLElement | undefined;
1421
};
1522

1623
const _namespace: Record<string, Namespace> = {};
@@ -20,35 +27,39 @@ const ROLES = {
2027
'assistant': 'assistant',
2128
};
2229

23-
const addMessage = (key: string, content: string, role: string, id?: string, hidden: boolean = false) => {
30+
const addMessage = (key: string, content: string, role: string, id: string, hidden: boolean = false, type: MessageType = 'text') => {
2431
const namespace = _namespace[key];
2532
if (!namespace) {
2633
return;
2734
}
2835

29-
const msgObject: Message = {content, role, id, hidden};
30-
31-
msgObject.el = namespace.onAddMessage(msgObject);
36+
const msgObject: Message = {content, role, id, hidden, type, ctx: {}};
3237

3338
const index = namespace.history.push(msgObject) - 1;
3439
if (id) {
3540
namespace.idRef[id] = namespace.history[index];
3641
}
42+
43+
msgObject.el = namespace.onAddMessage(msgObject);
3744
}
3845

39-
const updateMessage = (key: string, id: string, content: string) => {
46+
const updateMessage = (key: string, id: string, content: string, type: MessageType = 'text') => {
4047
const namespace = _namespace[key];
4148
if (!namespace) {
4249
return;
4350
}
4451

4552
if (!namespace.idRef[id]) {
46-
addMessage(key, content, ROLES.assistant, id);
53+
addMessage(key, content, ROLES.assistant, id, false, type);
4754
return;
4855
}
4956

50-
namespace.idRef[id].content = content;
51-
namespace.onUpdateMessage(namespace.idRef[id]);
57+
const msg = namespace.idRef[id];
58+
msg.content = content;
59+
60+
if (msg.el && msg.el.update) {
61+
msg.el.update(msg);
62+
}
5263
}
5364

5465
const getMessage = (key: string, id: string) => {
@@ -61,28 +72,26 @@ const getMessage = (key: string, id: string) => {
6172
}
6273

6374
export const chatHistory = {
64-
init: (key: string, onAddMessage: (msg: Message) => HTMLDivElement | undefined, onUpdateMessage: (msg: Message) => void) => {
75+
init: (key: string, onAddMessage: Namespace['onAddMessage']) => {
6576
if (!_namespace[key]) {
6677
_namespace[key] = {
6778
history: [],
6879
idRef: {},
6980
onAddMessage,
70-
onUpdateMessage,
7181
};
7282
}
7383

7484
_namespace[key].onAddMessage = onAddMessage;
75-
_namespace[key].onUpdateMessage = onUpdateMessage;
7685

7786
return {
78-
addUserMessage: (content: string, hidden?: boolean) => {
79-
addMessage(key, content, ROLES.user, undefined, hidden);
87+
addUserMessage: (content: string, id: string, hidden?: boolean, type: MessageType = 'text') => {
88+
addMessage(key, content, ROLES.user, id, hidden, type);
8089
},
81-
addAssistantMessage: (content: string, id: string) => {
82-
addMessage(key, content, ROLES.assistant, id);
90+
addAssistantMessage: (content: string, id: string, type: MessageType = 'text') => {
91+
addMessage(key, content, ROLES.assistant, id, false, type);
8392
},
84-
updateAssistantMessage: (id: string, content: string) => {
85-
updateMessage(key, id, content);
93+
updateAssistantMessage: (id: string, content: string, type: MessageType = 'text') => {
94+
updateMessage(key, id, content, type);
8695
},
8796
getAssistantMessage: (id: string) => {
8897
return getMessage(key, id);

_build/js/src/executor.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,24 +69,22 @@ type ImageParams = {
6969
namespace?: string;
7070
}
7171

72-
type DownloadImageParams = ({
72+
type DownloadImageParams = {
7373
url: string
74-
} | { image: string }) & {
7574
field?: string;
7675
namespace?: string;
7776
resource?: string | number;
7877
mediaSource?: string | number;
79-
}
78+
};
8079

8180
export type TextData = {
8281
id: string;
8382
content: string;
8483
}
8584

8685
export type ImageData = {
86+
id: string;
8787
url: string;
88-
} | {
89-
base64: string;
9088
}
9189

9290
type ChunkStream<D = unknown> = (data: D) => void;
@@ -116,6 +114,7 @@ const services: ServiceHandlers = {
116114
}
117115

118116
return {
117+
id: `chatgpt-${Date.now()}-${Math.round(Math.random()*1000)}`,
119118
url
120119
}
121120
}
@@ -157,7 +156,8 @@ const services: ServiceHandlers = {
157156
}
158157

159158
return {
160-
base64: `data:image/png;base64,${base64}`
159+
id: `gemini-${Date.now()}-${Math.round(Math.random()*1000)}`,
160+
url: `data:image/png;base64,${base64}`
161161
}
162162
}
163163
}
@@ -384,7 +384,7 @@ const serviceExecutor = async <D extends ServiceResponse>(details: ExecutorData,
384384
return services['buffered'][executorDetails.service as ServiceType][executorDetails.parser as keyof ServiceHandlers['buffered'][ServiceType]](data) as D;
385385
}
386386

387-
const modxFetch = async (action: string, params: Record<string, unknown>) => {
387+
const modxFetch = async <R>(action: string, params: Record<string, unknown>) => {
388388
const res = await fetch(`${modAI.apiURL}?action=${action}`, {
389389
method: 'POST',
390390
body: JSON.stringify(params),
@@ -402,7 +402,7 @@ const modxFetch = async (action: string, params: Record<string, unknown>) => {
402402
throw new Error(data.detail);
403403
}
404404

405-
return res.json();
405+
return await res.json() as R;
406406
}
407407

408408
const aiFetch = async <D extends ServiceResponse>(action: string, params: Record<string, unknown>, onChunkStream?: ChunkStream<D>, controller?: AbortController): Promise<D> => {
@@ -463,7 +463,7 @@ export const executor = {
463463
mgr: {
464464
download: {
465465
image: async (params: DownloadImageParams) => {
466-
return await modxFetch('Download\\Image', params);
466+
return await modxFetch<{url: string; fullUrl: string}>('Download\\Image', params);
467467
}
468468
},
469469
prompt: {
@@ -477,7 +477,7 @@ export const executor = {
477477
return aiFetch('Prompt\\Vision', params, onChunkStream, controller);
478478
},
479479
image: async (params: ImageParams, controller?: AbortController) => {
480-
return aiFetch('Prompt\\Image', params, undefined, controller);
480+
return aiFetch<ImageData>('Prompt\\Image', params, undefined, controller);
481481
}
482482
}
483483
}

_build/js/src/resource.ts

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import {executor, ImageData} from "./executor";
1+
import {executor} from "./executor";
22
import {DataOutput, history} from './history';
33
import {ui} from "./ui";
44
import {createLoadingOverlay} from "./ui/overlay";
5+
import {Message} from "./chatHistory";
56

67
type DataContext = {els: { field: any, wrapper: HistoryElement }[]};
78
type HistoryButton = HTMLButtonElement & {
@@ -127,6 +128,9 @@ const createFreeTextPrompt = (fieldName: string) => {
127128
ui.freePrompt({
128129
key: fieldName,
129130
field: fieldName,
131+
type: 'text',
132+
// @ts-ignore
133+
resource: MODx.request.id,
130134
});
131135
});
132136

@@ -177,30 +181,26 @@ const createForcedTextPrompt = (field: any, fieldName: string) => {
177181
return aiWrapper;
178182
}
179183

180-
const createImagePrompt = (defaultPrompt: string, mediaSource: string, fieldName: string, onSuccess: (res: ImageData) => void) => {
184+
const createImagePrompt = (mediaSource: string, fieldName: string, onSuccess: (msg: Message) => void) => {
181185
const imageWand = createWandEl();
182186
imageWand.addEventListener('click', () => {
183-
//@ts-expect-error Ext
184-
const createColumn = MODx.load({
185-
xtype: 'modai-window-image_prompt',
186-
title: 'Image',
187-
cacheKey: fieldName,
188-
record: {
189-
//@ts-expect-error Ext
190-
resource: MODx.request.id,
191-
prompt: defaultPrompt,
192-
mediaSource,
193-
field: fieldName,
187+
ui.freePrompt({
188+
key: fieldName,
189+
field: fieldName,
190+
type: 'image',
191+
// @ts-ignore
192+
resource: MODx.request.id,
193+
image: {
194+
mediaSource: parseInt(mediaSource) || undefined,
194195
},
195-
listeners: {
196-
success: {
197-
fn: onSuccess,
198-
scope:this
196+
imageActions: {
197+
copy: false,
198+
insert: (msg, modal) => {
199+
onSuccess(msg);
200+
modal.api.closeModal();
199201
}
200202
}
201203
});
202-
203-
createColumn.show();
204204
});
205205

206206
return imageWand;
@@ -261,14 +261,11 @@ const attachImagePlus = (imgPlusPanel: Element, fieldName: string) => {
261261
const imagePlus = Ext.getCmp(imgPlusPanel.firstElementChild?.id);
262262

263263
const imageWand = createImagePrompt(
264-
'',
265264
imagePlus.imageBrowser.source,
266265
fieldName,
267-
function(res) {
268-
if ('url' in res) {
269-
imagePlus.imageBrowser.setValue(res.url);
270-
imagePlus.onImageChange(res.url)
271-
}
266+
function(msg) {
267+
imagePlus.imageBrowser.setValue(msg.ctx.url);
268+
imagePlus.onImageChange(msg.ctx.url)
272269
}
273270
);
274271

@@ -364,19 +361,16 @@ const attachTVs = () => {
364361

365362
if (field.xtype === 'modx-panel-tv-image') {
366363
const imageWand = createImagePrompt(
367-
'',
368364
field.source,
369365
fieldName,
370-
function(res) {
371-
if ('url' in res) {
366+
function(msg) {
372367
const eventData = {
373-
relativeUrl: res.url,
374-
url: res.url
368+
relativeUrl: msg.ctx.url,
369+
url: msg.ctx.url
375370
};
376371

377372
field.items.items[1].fireEvent('select', eventData)
378373
field.fireEvent('select', eventData);
379-
}
380374
}
381375
);
382376

0 commit comments

Comments
 (0)