@messenger-box/tailwind-ui-inbox 10.0.3-alpha.104 → 10.0.3-alpha.106

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.
@@ -17,6 +17,7 @@ import {
17
17
  MessagesDocument,
18
18
  useMessagesQuery,
19
19
  OnChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
20
+ useCreateChannelWorkflowJobMutation,
20
21
  useGenerateAiCodeMutation,
21
22
  useOnChatMessageAddedSubscription,
22
23
  useSandboxErrorSubscription,
@@ -65,9 +66,10 @@ export const AIAgent: React.FC<AIAgentProps> = ({
65
66
  isLoading,
66
67
  }) => {
67
68
  const [state, send] = useMachine(aiAgentMachine);
68
- const apolloClient = useApolloClient();
69
+ // const apolloClient = useApolloClient();
69
70
  const { startUpload } = useUploadFiles();
70
71
  const [sendMsg] = useSendMessagesMutation();
72
+ const [createChannelWorkflowJob] = useCreateChannelWorkflowJobMutation();
71
73
  const auth: any = useSelector<Store.Auth, IUserState>(userSelector, shallowEqual);
72
74
  const { id: pathChannelId } = useParams();
73
75
  const navigate = useNavigate();
@@ -76,7 +78,7 @@ export const AIAgent: React.FC<AIAgentProps> = ({
76
78
  const [errorData, setError] = useState<string | null>(null);
77
79
  // Get channelId from props or path params
78
80
  const actualChannelId = channelId || pathChannelId;
79
- const [generateAiCode] = useGenerateAiCodeMutation();
81
+ // const [generateAiCode] = useGenerateAiCodeMutation();
80
82
  const [recreateSandbox] = useRecreateSandboxMutation();
81
83
  const { data: chatMessageAddedData } = useOnChatMessageAddedSubscription({
82
84
  variables: { channelId: actualChannelId?.toString() || '' },
@@ -161,12 +163,12 @@ export const AIAgent: React.FC<AIAgentProps> = ({
161
163
  }
162
164
  }, [chatMessageAddedData?.chatMessageAdded]);
163
165
 
164
- useEffect(() => {
165
- if (messagesData?.messages?.data?.length == 1) {
166
- handleGenerateAiCode(messagesData.messages.data[0].id);
167
- }
168
- // console.log('messagesData?.messages?.data',JSON.stringify(messagesData?.messages?.data,null,2))
169
- }, [messagesData?.messages?.data]);
166
+ // useEffect(() => {
167
+ // if (messagesData?.messages?.data?.length == 1) {
168
+ // handleGenerateAiCode(messagesData.messages.data[0].id);
169
+ // }
170
+ // // console.log('messagesData?.messages?.data',JSON.stringify(messagesData?.messages?.data,null,2))
171
+ // }, [messagesData?.messages?.data]);
170
172
 
171
173
  // Handle sandbox error subscription updates
172
174
  useEffect(() => {
@@ -217,43 +219,43 @@ export const AIAgent: React.FC<AIAgentProps> = ({
217
219
  }
218
220
  }, [regularMessages, send]);
219
221
 
220
- const handleGenerateAiCode = useCallback(
221
- (messageId: string) => {
222
- generateAiCode({
223
- variables: {
224
- messageId: messageId,
225
- modelConfig: modelConfig,
226
- },
227
- onCompleted: (res) => {
228
- if (res.generateAiCode.success) {
229
- console.log('generated_result', res);
230
- // Show AI is thinking loader on success
231
- if (successThinkingTimeoutRef.current) {
232
- clearTimeout(successThinkingTimeoutRef.current);
233
- successThinkingTimeoutRef.current = null;
234
- }
235
- setIsSuccessThinking(true);
236
- // Fallback auto-hide after 15s in case no subsequent event arrives
237
- successThinkingTimeoutRef.current = setTimeout(() => {
238
- setIsSuccessThinking(false);
239
- // setIsLoading(false);
240
- successThinkingTimeoutRef.current = null;
241
- }, 15000);
242
- }
243
- },
244
- onError: (err) => {
245
- console.log('err', JSON.stringify(err, null, 2));
246
- // Also ensure loader is hidden on error
247
- if (successThinkingTimeoutRef.current) {
248
- clearTimeout(successThinkingTimeoutRef.current);
249
- successThinkingTimeoutRef.current = null;
250
- }
251
- setIsSuccessThinking(false);
252
- },
253
- });
254
- },
255
- [generateAiCode, modelConfig],
256
- );
222
+ // const handleGenerateAiCode = useCallback(
223
+ // (messageId: string) => {
224
+ // generateAiCode({
225
+ // variables: {
226
+ // messageId: messageId,
227
+ // modelConfig: modelConfig,
228
+ // },
229
+ // onCompleted: (res) => {
230
+ // if (res.generateAiCode.success) {
231
+ // console.log('generated_result', res);
232
+ // // Show AI is thinking loader on success
233
+ // if (successThinkingTimeoutRef.current) {
234
+ // clearTimeout(successThinkingTimeoutRef.current);
235
+ // successThinkingTimeoutRef.current = null;
236
+ // }
237
+ // setIsSuccessThinking(true);
238
+ // // Fallback auto-hide after 15s in case no subsequent event arrives
239
+ // successThinkingTimeoutRef.current = setTimeout(() => {
240
+ // setIsSuccessThinking(false);
241
+ // // setIsLoading(false);
242
+ // successThinkingTimeoutRef.current = null;
243
+ // }, 15000);
244
+ // }
245
+ // },
246
+ // onError: (err) => {
247
+ // console.log('err', JSON.stringify(err, null, 2));
248
+ // // Also ensure loader is hidden on error
249
+ // if (successThinkingTimeoutRef.current) {
250
+ // clearTimeout(successThinkingTimeoutRef.current);
251
+ // successThinkingTimeoutRef.current = null;
252
+ // }
253
+ // setIsSuccessThinking(false);
254
+ // },
255
+ // });
256
+ // },
257
+ // [generateAiCode, modelConfig],
258
+ // );
257
259
 
258
260
  const onOpen = (element?: any) => {
259
261
  setSelectedElement(element);
@@ -313,6 +315,163 @@ export const AIAgent: React.FC<AIAgentProps> = ({
313
315
  );
314
316
 
315
317
  // Updated handleSend function from InboxWithAi.tsx
318
+ // const handleSend = useCallback(
319
+ // async (message: string, files: any[] = []) => {
320
+ // // Allow sending if there's either a message or files
321
+ // if ((!message || !message.trim()) && (!files || files.length === 0)) return;
322
+
323
+ // const validated = getValidatedConfig();
324
+ // if (!validated && !hasApiKey) {
325
+ // // No API key/config; do nothing (AiLandingInput would prompt config UI)
326
+ // return;
327
+ // }
328
+
329
+ // // Start the AI thinking loader immediately on send
330
+ // if (successThinkingTimeoutRef.current) {
331
+ // clearTimeout(successThinkingTimeoutRef.current);
332
+ // successThinkingTimeoutRef.current = null;
333
+ // }
334
+ // setIsSuccessThinking(true);
335
+ // setIsLoading(true);
336
+ // // Safety auto-stop in case no event arrives
337
+ // successThinkingTimeoutRef.current = setTimeout(() => {
338
+ // setIsSuccessThinking(false);
339
+ // successThinkingTimeoutRef.current = null;
340
+ // }, 15000);
341
+
342
+ // // If we already have a channel, send message (with optional file upload) similar to Inbox
343
+ // if (actualChannelId) {
344
+ // try {
345
+ // const postId = objectId();
346
+ // const channelId = objectId();
347
+ // const currentDate = new Date();
348
+
349
+ // const createOptimisticMessage = (uploadedFiles?: any[]) => ({
350
+ // __typename: 'Post' as const,
351
+ // id: postId,
352
+ // message,
353
+ // createdAt: currentDate.toISOString(),
354
+ // updatedAt: currentDate.toISOString(),
355
+ // author: {
356
+ // __typename: 'UserAccount' as const,
357
+ // id: auth?.id,
358
+ // givenName: auth?.profile?.given_name || '',
359
+ // familyName: auth?.profile?.family_name || '',
360
+ // email: auth?.profile?.email || '',
361
+ // username: auth?.profile?.nickname || '',
362
+ // fullName: auth?.profile?.name || '',
363
+ // picture: auth?.profile?.picture || '',
364
+ // alias: [auth?.authUserId ?? ''],
365
+ // tokens: [],
366
+ // },
367
+ // isDelivered: false,
368
+ // isRead: false,
369
+ // type: 'TEXT' as PostTypeEnum,
370
+ // parentId: null,
371
+ // fromServer: false,
372
+ // channel: {
373
+ // __typename: 'Channel' as const,
374
+ // id: actualChannelId,
375
+ // },
376
+ // propsConfiguration: {
377
+ // __typename: 'MachineConfiguration' as const,
378
+ // id: null,
379
+ // resource: '' as any,
380
+ // contents: null,
381
+ // keys: null,
382
+ // target: null,
383
+ // overrides: null,
384
+ // },
385
+ // props: {},
386
+ // files: {
387
+ // __typename: 'FilesInfo' as const,
388
+ // data: uploadedFiles || [],
389
+ // totalCount: uploadedFiles?.length || 0,
390
+ // },
391
+ // replies: {
392
+ // __typename: 'Messages' as const,
393
+ // data: [],
394
+ // totalCount: 0,
395
+ // },
396
+ // });
397
+
398
+ // const cacheMessagesQuery = {
399
+ // query: MessagesDocument,
400
+ // variables: {
401
+ // // Match variables used in useMessagesQuery above
402
+ // props: { projectId: actualChannelId.toString() },
403
+ // parentId: null,
404
+ // limit: MESSAGES_PER_PAGE,
405
+ // },
406
+ // } as const;
407
+
408
+ // const extraProps = {
409
+ // projectId: actualChannelId,
410
+ // template: modelConfig?.template || 'vite-react',
411
+ // role: 'USER',
412
+ // sendNotificationWithProjectId: true,
413
+ // } as any;
414
+
415
+ // if (files?.length > 0) {
416
+ // const uploadResponse = await startUpload({
417
+ // file: files,
418
+ // saveUploadedFile: { variables: { postId } },
419
+ // createUploadLink: { variables: { postId } },
420
+ // });
421
+
422
+ // const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
423
+ // const fileIds = (uploadedFiles || []).map((f: any) => f.id);
424
+
425
+ // await sendMsg({
426
+ // variables: {
427
+ // postId,
428
+ // channelId: actualChannelId,
429
+ // content: message,
430
+ // files: fileIds,
431
+ // extraProps,
432
+ // },
433
+ // optimisticResponse: {
434
+ // __typename: 'Mutation',
435
+ // sendMessage: createOptimisticMessage(uploadedFiles),
436
+ // },
437
+ // update: (cache, { data: mutationData }) => {
438
+ // if (!mutationData?.sendMessage) return;
439
+ // // if (mutationData?.sendMessage?.id) {
440
+ // // handleGenerateAiCode(mutationData.sendMessage.id);
441
+ // // }
442
+ // },
443
+ // });
444
+ // } else {
445
+ // await sendMsg({
446
+ // variables: { channelId: actualChannelId, content: message, extraProps },
447
+ // optimisticResponse: {
448
+ // __typename: 'Mutation',
449
+ // sendMessage: createOptimisticMessage(),
450
+ // },
451
+ // update: (cache, { data: mutationData }) => {
452
+ // if (!mutationData?.sendMessage) return;
453
+ // console.log('mutationData', JSON.stringify(mutationData, null, 2));
454
+ // // if (mutationData?.sendMessage?.id) {
455
+ // // handleGenerateAiCode(mutationData.sendMessage.id);
456
+ // // }
457
+ // },
458
+ // });
459
+ // }
460
+
461
+ // // Scroll to bottom after send
462
+ // scrollToBottom('smooth', 0);
463
+ // } catch (error) {
464
+ // console.error('Error sending message from AIAgent:', error);
465
+ // setIsLoading(false);
466
+ // }
467
+
468
+ // return;
469
+ // }
470
+ // // If no channelId, we skip sending here.
471
+ // },
472
+ // [actualChannelId, auth, startUpload, sendMsg, scrollToBottom, getValidatedConfig, hasApiKey, modelConfig],
473
+ // );
474
+
316
475
  const handleSend = useCallback(
317
476
  async (message: string, files: any[] = []) => {
318
477
  // Allow sending if there's either a message or files
@@ -336,138 +495,87 @@ export const AIAgent: React.FC<AIAgentProps> = ({
336
495
  setIsSuccessThinking(false);
337
496
  successThinkingTimeoutRef.current = null;
338
497
  }, 15000);
498
+ // If no channelId, mirror AiLandingInput: create channel+project, seed first post, then navigate
499
+ try {
500
+ const projectId = actualChannelId;
501
+ const postId = objectId();
502
+ const channelId = objectId();
339
503
 
340
- // If we already have a channel, send message (with optional file upload) similar to Inbox
341
- if (actualChannelId) {
342
- try {
343
- const postId = objectId();
344
- const channelId = objectId();
345
- const currentDate = new Date();
346
-
347
- const createOptimisticMessage = (uploadedFiles?: any[]) => ({
348
- __typename: 'Post' as const,
349
- id: postId,
350
- message,
351
- createdAt: currentDate.toISOString(),
352
- updatedAt: currentDate.toISOString(),
353
- author: {
354
- __typename: 'UserAccount' as const,
355
- id: auth?.id,
356
- givenName: auth?.profile?.given_name || '',
357
- familyName: auth?.profile?.family_name || '',
358
- email: auth?.profile?.email || '',
359
- username: auth?.profile?.nickname || '',
360
- fullName: auth?.profile?.name || '',
361
- picture: auth?.profile?.picture || '',
362
- alias: [auth?.authUserId ?? ''],
363
- tokens: [],
364
- },
365
- isDelivered: false,
366
- isRead: false,
367
- type: 'TEXT' as PostTypeEnum,
368
- parentId: null,
369
- fromServer: false,
370
- channel: {
371
- __typename: 'Channel' as const,
372
- id: actualChannelId,
373
- },
374
- propsConfiguration: {
375
- __typename: 'MachineConfiguration' as const,
376
- id: null,
377
- resource: '' as any,
378
- contents: null,
379
- keys: null,
380
- target: null,
381
- overrides: null,
382
- },
383
- props: {},
384
- files: {
385
- __typename: 'FilesInfo' as const,
386
- data: uploadedFiles || [],
387
- totalCount: uploadedFiles?.length || 0,
388
- },
389
- replies: {
390
- __typename: 'Messages' as const,
391
- data: [],
392
- totalCount: 0,
393
- },
394
- });
395
-
396
- const cacheMessagesQuery = {
397
- query: MessagesDocument,
398
- variables: {
399
- // Match variables used in useMessagesQuery above
400
- props: { projectId: actualChannelId.toString() },
401
- parentId: null,
402
- limit: MESSAGES_PER_PAGE,
403
- },
404
- } as const;
504
+ // Do not include extensionId inside modelConfig when sending additionalProperties
505
+ const { extensionId: _omitExtensionId, ...modelConfigWithoutExtensionId } =
506
+ (modelConfig as any) || ({} as any);
405
507
 
406
- const extraProps = {
407
- projectId: actualChannelId,
408
- template: modelConfig?.template || 'vite-react',
409
- role: 'USER',
410
- sendNotificationWithProjectId: true,
411
- } as any;
412
-
413
- if (files?.length > 0) {
414
- const uploadResponse = await startUpload({
415
- file: files,
416
- saveUploadedFile: { variables: { postId } },
417
- createUploadLink: { variables: { postId } },
418
- });
419
-
420
- const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
421
- const fileIds = (uploadedFiles || []).map((f: any) => f.id);
422
-
423
- await sendMsg({
424
- variables: {
425
- postId,
426
- channelId: actualChannelId,
508
+ await createChannelWorkflowJob({
509
+ variables: {
510
+ createChannelInput: {
511
+ channelId,
512
+ title: 'AI Assistant',
513
+ description: 'AI Assistant',
514
+ displayName: 'AI Assistant',
515
+ topic: 'AI Assistant',
516
+ projectId: projectId,
517
+ type: RoomType.Aiassistant,
518
+ postData: {
519
+ postId: postId,
520
+ type: PostTypeEnum.Aiassistant,
427
521
  content: message,
428
- files: fileIds,
429
- extraProps,
430
- },
431
- optimisticResponse: {
432
- __typename: 'Mutation',
433
- sendMessage: createOptimisticMessage(uploadedFiles),
434
- },
435
- update: (cache, { data: mutationData }) => {
436
- if (!mutationData?.sendMessage) return;
437
- if (mutationData?.sendMessage?.id) {
438
- handleGenerateAiCode(mutationData.sendMessage.id);
439
- }
440
- },
441
- });
442
- } else {
443
- await sendMsg({
444
- variables: { channelId: actualChannelId, content: message, extraProps },
445
- optimisticResponse: {
446
- __typename: 'Mutation',
447
- sendMessage: createOptimisticMessage(),
522
+ props: {
523
+ generateAiCode: true,
524
+ template: modelConfig?.template || 'vite-react',
525
+ projectId: projectId,
526
+ role: 'USER',
527
+ fragment: {},
528
+ sendNotificationWithProjectId: true,
529
+ },
448
530
  },
449
- update: (cache, { data: mutationData }) => {
450
- if (!mutationData?.sendMessage) return;
451
- console.log('mutationData', JSON.stringify(mutationData, null, 2));
452
- if (mutationData?.sendMessage?.id) {
453
- handleGenerateAiCode(mutationData.sendMessage.id);
454
- }
531
+ additionalProperties: {
532
+ modelConfig: modelConfigWithoutExtensionId,
455
533
  },
456
- });
457
- }
458
-
459
- // Scroll to bottom after send
460
- scrollToBottom('smooth', 0);
461
- } catch (error) {
462
- console.error('Error sending message from AIAgent:', error);
463
- setIsLoading(false);
464
- }
465
-
466
- return;
534
+ },
535
+ extensionInput: {
536
+ // prefer saved extensionId from modelConfig; otherwise env default
537
+ extensionId:
538
+ (modelConfig as any)?.extensionId || (config as any)?.MESSENGER_BOX_FORM_EXTENSION_ID,
539
+ stepName: 'extractCodeFromExtension',
540
+ formId: 'form-builder-proxy',
541
+ functionId: 'create-channel-function',
542
+ metadata: {},
543
+ source: 'final-submission',
544
+ },
545
+ },
546
+ onCompleted: (data: any) => {
547
+ // if (!data?.createChannelWorkflowJob) {
548
+ // setIsSuccessThinking(false);
549
+ // setIsLoading(false);
550
+ // return;
551
+ // }
552
+ // setIsSuccessThinking(false);
553
+ // setIsLoading(false);
554
+ },
555
+ onError: (error: any) => {
556
+ console.error('Error creating channel:', error);
557
+ setIsSuccessThinking(false);
558
+ setIsLoading(false);
559
+ },
560
+ });
561
+ } catch (err) {
562
+ console.error('Failed to create new AI project/channel from AIAgent:', err);
563
+ setIsSuccessThinking(false);
564
+ setIsLoading(false);
467
565
  }
468
- // If no channelId, we skip sending here.
469
566
  },
470
- [actualChannelId, auth, startUpload, sendMsg, scrollToBottom, getValidatedConfig, hasApiKey, modelConfig],
567
+ [
568
+ actualChannelId,
569
+ auth,
570
+ startUpload,
571
+ sendMsg,
572
+ scrollToBottom,
573
+ getValidatedConfig,
574
+ hasApiKey,
575
+ modelConfig,
576
+ navigate,
577
+ createChannelWorkflowJob,
578
+ ],
471
579
  );
472
580
 
473
581
  const fixError = useCallback(
@@ -637,7 +745,7 @@ export const AIAgent: React.FC<AIAgentProps> = ({
637
745
  }, []);
638
746
 
639
747
  const extractCleanContent = useCallback((summary: string): string => {
640
- console.log('summary...........', summary);
748
+ // console.log('summary...........', summary);
641
749
  if (!summary) return 'Project generated successfully!';
642
750
 
643
751
  const summeryData = summary
@@ -41,6 +41,13 @@ export const InputComponent = ({
41
41
  }
42
42
  }, []);
43
43
 
44
+ // Ensure default template is set to 'vite-react' if missing
45
+ useEffect(() => {
46
+ if (modelConfig && !modelConfig.template && onModelConfigChange) {
47
+ onModelConfigChange({ ...modelConfig, template: 'vite-react' as any });
48
+ }
49
+ }, [modelConfig, onModelConfigChange]);
50
+
44
51
  // Handle click outside for dropdowns
45
52
  useEffect(() => {
46
53
  const handleClickOutside = (event: MouseEvent) => {
@@ -425,6 +432,22 @@ export const InputComponent = ({
425
432
  />
426
433
  </div>
427
434
 
435
+ {/* Extension ID Input */}
436
+ <div>
437
+ <label className="block text-sm font-medium text-gray-700 mb-2">
438
+ Extension ID
439
+ </label>
440
+ <input
441
+ type="text"
442
+ value={modelConfig.extensionId || ''}
443
+ onChange={(e) =>
444
+ onModelConfigChange({ ...modelConfig, extensionId: e.target.value })
445
+ }
446
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
447
+ placeholder="Enter your extension ID"
448
+ />
449
+ </div>
450
+
428
451
  {/* Model Selection */}
429
452
  <div>
430
453
  <label className="block text-sm font-medium text-gray-700 mb-2">Model</label>
@@ -76,6 +76,9 @@ const AiLandingInput: React.FC = () => {
76
76
  const id = objectId();
77
77
  const postId = objectId();
78
78
  const channelId = objectId();
79
+ // Do not include extensionId inside modelConfig when sending additionalProperties
80
+ const { extensionId: _omitExtensionId, ...modelConfigWithoutExtensionId } =
81
+ modelConfig || ({} as any);
79
82
  createChannelWorkflowJob({
80
83
  variables: {
81
84
  createChannelInput: {
@@ -100,12 +103,12 @@ const AiLandingInput: React.FC = () => {
100
103
  },
101
104
  },
102
105
  additionalProperties: {
103
- modelConfig: modelConfig,
106
+ modelConfig: modelConfigWithoutExtensionId,
104
107
  },
105
108
  },
106
109
  extensionInput: {
107
- extensionId: config.MESSENGER_BOX_FORM_EXTENSION_ID,
108
- stepName: 'createChannel',
110
+ extensionId: modelConfig?.extensionId || config.MESSENGER_BOX_FORM_EXTENSION_ID,
111
+ stepName: 'extractCodeFromExtension',
109
112
  formId: 'form-builder-proxy',
110
113
  functionId: 'create-channel-function',
111
114
  metadata: {},
@@ -6,7 +6,8 @@ export interface ModelConfig {
6
6
  provider: ProviderId;
7
7
  model: string;
8
8
  apiKey: string;
9
- template: 'react-vite' | 'nextjs' | 'vue' | 'vite-react';
9
+ template: 'vite-react' | 'nextjs' | 'vue';
10
+ extensionId?: string;
10
11
  }
11
12
 
12
13
  const STORAGE_KEY = 'mbx:model-config';
@@ -36,7 +37,8 @@ const DEFAULTS: ModelConfig = {
36
37
  provider: 'anthropic',
37
38
  model: 'claude-3-5-sonnet-20241022',
38
39
  apiKey: '',
39
- template: 'react-vite',
40
+ template: 'vite-react',
41
+ extensionId: '',
40
42
  };
41
43
 
42
44
  export function usePersistentModelConfig() {