@messenger-box/platform-server 10.0.3-alpha.72 → 10.0.3-alpha.75
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.
- package/lib/config/env-config.d.ts +7 -0
- package/lib/config/env-config.js +20 -0
- package/lib/config/env-config.js.map +1 -1
- package/lib/containers/containers.js +6 -1
- package/lib/containers/containers.js.map +1 -1
- package/lib/containers/context-services-from-container.js +4 -2
- package/lib/containers/context-services-from-container.js.map +1 -1
- package/lib/graphql/resolvers/ai-fragment.d.ts +3 -0
- package/lib/graphql/resolvers/ai-fragment.js +276 -0
- package/lib/graphql/resolvers/ai-fragment.js.map +1 -0
- package/lib/graphql/resolvers/channel.js +29 -0
- package/lib/graphql/resolvers/channel.js.map +1 -1
- package/lib/graphql/resolvers/index.js +1 -1
- package/lib/graphql/resolvers/index.js.map +1 -1
- package/lib/graphql/resolvers/post.js +187 -14
- package/lib/graphql/resolvers/post.js.map +1 -1
- package/lib/graphql/schema/ai-fragment.graphql +311 -0
- package/lib/graphql/schema/ai-fragment.graphql.js +1 -0
- package/lib/graphql/schema/ai-fragment.graphql.js.map +1 -0
- package/lib/graphql/schema/channel.graphql +19 -0
- package/lib/graphql/schema/channel.graphql.js +1 -1
- package/lib/graphql/schema/index.js +2 -2
- package/lib/graphql/schema/index.js.map +1 -1
- package/lib/graphql/schema/post.graphql +76 -0
- package/lib/graphql/schema/post.graphql.js +1 -1
- package/lib/graphql/schema/services.graphql +19 -0
- package/lib/index.js.map +1 -1
- package/lib/inngest/factory.d.ts +20 -0
- package/lib/inngest/factory.js +4 -0
- package/lib/inngest/factory.js.map +1 -0
- package/lib/inngest/functions.d.ts +235 -0
- package/lib/inngest/functions.js +1385 -0
- package/lib/inngest/functions.js.map +1 -0
- package/lib/inngest/index.d.ts +3 -0
- package/lib/inngest/prompt.d.ts +6 -0
- package/lib/inngest/prompt.js +871 -0
- package/lib/inngest/prompt.js.map +1 -0
- package/lib/inngest/utils.d.ts +5 -0
- package/lib/inngest/utils.js +32 -0
- package/lib/inngest/utils.js.map +1 -0
- package/lib/module.js +10 -3
- package/lib/module.js.map +1 -1
- package/lib/plugins/ai-fragment-moleculer-service.d.ts +29 -0
- package/lib/plugins/ai-fragment-moleculer-service.js +516 -0
- package/lib/plugins/ai-fragment-moleculer-service.js.map +1 -0
- package/lib/plugins/channel-moleculer-service.js +9 -0
- package/lib/plugins/channel-moleculer-service.js.map +1 -1
- package/lib/plugins/index.d.ts +1 -0
- package/lib/plugins/post-moleculer-service.js +116 -0
- package/lib/plugins/post-moleculer-service.js.map +1 -1
- package/lib/services/ai-fragment-service.d.ts +195 -0
- package/lib/services/ai-fragment-service.js +631 -0
- package/lib/services/ai-fragment-service.js.map +1 -0
- package/lib/services/channel-service.d.ts +5 -2
- package/lib/services/channel-service.js +24 -2
- package/lib/services/channel-service.js.map +1 -1
- package/lib/services/index.d.ts +2 -0
- package/lib/services/post-service.d.ts +9 -2
- package/lib/services/post-service.js +225 -5
- package/lib/services/post-service.js.map +1 -1
- package/lib/services/proxy-services/ai-fragment-microservice.d.ts +23 -0
- package/lib/services/proxy-services/ai-fragment-microservice.js +78 -0
- package/lib/services/proxy-services/ai-fragment-microservice.js.map +1 -0
- package/lib/services/proxy-services/channel-microservice.d.ts +2 -1
- package/lib/services/proxy-services/channel-microservice.js +6 -0
- package/lib/services/proxy-services/channel-microservice.js.map +1 -1
- package/lib/services/proxy-services/index.d.ts +1 -0
- package/lib/services/proxy-services/post-microservice.d.ts +22 -1
- package/lib/services/proxy-services/post-microservice.js +80 -0
- package/lib/services/proxy-services/post-microservice.js.map +1 -1
- package/lib/services/sandbox-error-service.d.ts +23 -0
- package/lib/services/sandbox-error-service.js +422 -0
- package/lib/services/sandbox-error-service.js.map +1 -0
- package/lib/store/models/ai-fragment.d.ts +4 -0
- package/lib/store/models/ai-fragment.js +125 -0
- package/lib/store/models/ai-fragment.js.map +1 -0
- package/lib/store/models/channel.js +5 -0
- package/lib/store/models/channel.js.map +1 -1
- package/lib/store/models/index.d.ts +1 -0
- package/lib/store/repositories/ai-fragment-repository.d.ts +15 -0
- package/lib/store/repositories/ai-fragment-repository.js +69 -0
- package/lib/store/repositories/ai-fragment-repository.js.map +1 -0
- package/lib/store/repositories/channel-repository.js +1 -1
- package/lib/store/repositories/channel-repository.js.map +1 -1
- package/lib/store/repositories/index.d.ts +1 -0
- package/lib/store/repositories/post-repository.js +1 -1
- package/lib/store/repositories/post-repository.js.map +1 -1
- package/lib/store/repositories/post-thread-repository.js +1 -1
- package/lib/store/repositories/post-thread-repository.js.map +1 -1
- package/lib/store/repositories/reaction-repository.js +1 -1
- package/lib/store/repositories/reaction-repository.js.map +1 -1
- package/lib/templates/constants/SERVER_TYPES.ts.template +4 -1
- package/lib/templates/repositories/AiFragmentRepository.ts.template +4 -0
- package/lib/templates/services/AiFragmentService.ts.template +123 -0
- package/lib/templates/services/ChannelService.ts.template +11 -1
- package/lib/templates/services/PostService.ts.template +82 -1
- package/lib/templates/services/SandboxErrorService.ts.template +125 -0
- package/package.json +12 -6
|
@@ -0,0 +1,1385 @@
|
|
|
1
|
+
import {SERVER_TYPES,RoomType,PostTypeEnum,AiAgentMessageRole,AiAgentMessageType}from'common/server';import {TaggedType}from'@common-stack/core';import {createAgent,createTool,createState,createNetwork,gemini,anthropic,openai}from'@inngest/agent-kit';import {Sandbox}from'@e2b/code-interpreter';import {z}from'zod';import {lastAssistantTextMessageContent,getSandbox,parseAgentOutput}from'./utils.js';import {CODING_PROMPT,FRAGMENT_TITLE_PROMPT,RESPONSE_PROMPT,VUE_CODING_PROMPT,VITE_REACT_CODING_PROMPT}from'./prompt.js';import {config}from'../config/env-config.js';// E2B Configuration constants
|
|
2
|
+
const templateID = config.E2B_TEMPLATE_ID; // Next.js template (default)
|
|
3
|
+
const vueTemplateID = config.E2B_VUE_TEMPLATE_ID; // Vue template
|
|
4
|
+
const viteReactTemplateID = config.E2B_VITE_REACT_TEMPLATE_ID; // Vite+React template
|
|
5
|
+
const domain = config.E2B_DOMAIN;
|
|
6
|
+
const apiKey = config.E2B_API_KEY;
|
|
7
|
+
// Helper function to get the correct coding prompt based on template
|
|
8
|
+
const getCodingPrompt = modelConfig => {
|
|
9
|
+
if (modelConfig?.template === 'vue') {
|
|
10
|
+
return VUE_CODING_PROMPT;
|
|
11
|
+
}
|
|
12
|
+
if (modelConfig?.template === 'vite-react') {
|
|
13
|
+
return VITE_REACT_CODING_PROMPT;
|
|
14
|
+
}
|
|
15
|
+
return CODING_PROMPT; // Default Next.js prompt
|
|
16
|
+
};
|
|
17
|
+
// Helper function to get the correct template ID based on modal config
|
|
18
|
+
const getTemplateId = modelConfig => {
|
|
19
|
+
if (modelConfig?.template === 'vue' && vueTemplateID) {
|
|
20
|
+
return vueTemplateID;
|
|
21
|
+
}
|
|
22
|
+
if (modelConfig?.template === 'vite-react' && viteReactTemplateID) {
|
|
23
|
+
return viteReactTemplateID;
|
|
24
|
+
}
|
|
25
|
+
return templateID; // Default to Next.js template
|
|
26
|
+
};
|
|
27
|
+
// Helper function to normalize template names for database validation
|
|
28
|
+
const normalizeTemplateName = template => {
|
|
29
|
+
if (!template) return 'NEXTJS';
|
|
30
|
+
switch (template.toLowerCase()) {
|
|
31
|
+
case 'nextjs':
|
|
32
|
+
return 'NEXTJS';
|
|
33
|
+
case 'vue':
|
|
34
|
+
return 'VUE';
|
|
35
|
+
case 'vite-react':
|
|
36
|
+
case 'vitereact':
|
|
37
|
+
return 'VITE_REACT';
|
|
38
|
+
default:
|
|
39
|
+
return 'NEXTJS';
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
// Helper to create model instance from modelConfig (provider/model/apiKey)
|
|
43
|
+
const createModelInstance = modelConfig => {
|
|
44
|
+
if (!modelConfig || !modelConfig.apiKey) {
|
|
45
|
+
// Default to Gemini if nothing provided
|
|
46
|
+
return gemini({
|
|
47
|
+
model: 'gemini-2.5-flash'
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
switch (modelConfig.provider) {
|
|
51
|
+
case 'openai':
|
|
52
|
+
return openai({
|
|
53
|
+
model: modelConfig.model || 'gpt-4o',
|
|
54
|
+
apiKey: modelConfig.apiKey,
|
|
55
|
+
defaultParameters: {
|
|
56
|
+
temperature: 0.7
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
case 'anthropic':
|
|
60
|
+
return anthropic({
|
|
61
|
+
model: modelConfig.model || 'claude-3-5-sonnet-latest',
|
|
62
|
+
apiKey: modelConfig.apiKey,
|
|
63
|
+
defaultParameters: {
|
|
64
|
+
max_tokens: 4096,
|
|
65
|
+
temperature: 0.7
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
case 'gemini':
|
|
69
|
+
default:
|
|
70
|
+
return gemini({
|
|
71
|
+
model: modelConfig.model || 'gemini-2.5-flash',
|
|
72
|
+
apiKey: modelConfig.apiKey
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const createChannelWithProjectId = (inngest, container) => inngest.createFunction({
|
|
77
|
+
id: 'create-channel-with-projectid'
|
|
78
|
+
}, {
|
|
79
|
+
event: 'channel-with-project-id.create'
|
|
80
|
+
}, async ({
|
|
81
|
+
event,
|
|
82
|
+
step
|
|
83
|
+
}) => {
|
|
84
|
+
console.log('=== Project create FUNCTION STARTED ===');
|
|
85
|
+
console.log('Event data:', JSON.stringify(event.data, null, 2));
|
|
86
|
+
const {
|
|
87
|
+
projectId,
|
|
88
|
+
channelInput
|
|
89
|
+
} = event.data || {};
|
|
90
|
+
const {
|
|
91
|
+
content,
|
|
92
|
+
createdBy,
|
|
93
|
+
orgName,
|
|
94
|
+
organization,
|
|
95
|
+
team,
|
|
96
|
+
files,
|
|
97
|
+
notificationParams,
|
|
98
|
+
modelConfig,
|
|
99
|
+
accountId,
|
|
100
|
+
teamId,
|
|
101
|
+
orgId
|
|
102
|
+
} = channelInput;
|
|
103
|
+
// Validate that projectId is provided
|
|
104
|
+
if (!projectId) {
|
|
105
|
+
throw new Error('Project ID is required but was not provided');
|
|
106
|
+
}
|
|
107
|
+
// Create channel in database with template immutability logic
|
|
108
|
+
const channelData = await step.run('ensure-channel', async () => {
|
|
109
|
+
const channelService = container.getNamed(SERVER_TYPES.ChannelService, TaggedType.MICROSERVICE);
|
|
110
|
+
// If a channel already exists for this project, reuse it
|
|
111
|
+
const existingList = await channelService.getAll({
|
|
112
|
+
criteria: {
|
|
113
|
+
projectId,
|
|
114
|
+
type: RoomType.Aiassistant
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
const existing = Array.isArray(existingList) ? existingList[0] : null;
|
|
118
|
+
if (existing) {
|
|
119
|
+
return existing;
|
|
120
|
+
}
|
|
121
|
+
const newChannel = await channelService.saveChannel({
|
|
122
|
+
type: RoomType.Aiassistant,
|
|
123
|
+
orgName: orgId,
|
|
124
|
+
organization: orgId,
|
|
125
|
+
creator: createdBy || accountId,
|
|
126
|
+
title: 'AI Assistant',
|
|
127
|
+
displayName: 'AI Assistant',
|
|
128
|
+
topic: 'AI Assistant',
|
|
129
|
+
team: teamId,
|
|
130
|
+
description: 'AI Assistant',
|
|
131
|
+
projectId,
|
|
132
|
+
_id: undefined
|
|
133
|
+
});
|
|
134
|
+
return await newChannel;
|
|
135
|
+
});
|
|
136
|
+
if (channelData) {
|
|
137
|
+
const channelId = channelData?.id?.toString?.() || channelData?._id?.toString?.() || '';
|
|
138
|
+
if (!channelId) {
|
|
139
|
+
throw new Error('Failed to resolve channel id for post creation');
|
|
140
|
+
}
|
|
141
|
+
await step.sendEvent('emit-send-message', {
|
|
142
|
+
name: 'channel.message.send',
|
|
143
|
+
data: {
|
|
144
|
+
channelId,
|
|
145
|
+
content,
|
|
146
|
+
createdBy,
|
|
147
|
+
files,
|
|
148
|
+
notificationParams,
|
|
149
|
+
projectId,
|
|
150
|
+
modelConfig,
|
|
151
|
+
accountId,
|
|
152
|
+
teamId,
|
|
153
|
+
orgId
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return channelData;
|
|
158
|
+
});
|
|
159
|
+
const sendMessageHandler = (inngest, container) => inngest.createFunction({
|
|
160
|
+
id: 'channel-message-send'
|
|
161
|
+
}, {
|
|
162
|
+
event: 'channel.message.send'
|
|
163
|
+
}, async ({
|
|
164
|
+
event,
|
|
165
|
+
step
|
|
166
|
+
}) => {
|
|
167
|
+
console.log('=== Send Message Handler FUNCTION STARTED ===');
|
|
168
|
+
console.log('=== Send Message Handler Event data:', JSON.stringify(event.data, null, 2));
|
|
169
|
+
const {
|
|
170
|
+
projectId,
|
|
171
|
+
channelId,
|
|
172
|
+
content,
|
|
173
|
+
createdBy,
|
|
174
|
+
files,
|
|
175
|
+
notificationParams,
|
|
176
|
+
modelConfig,
|
|
177
|
+
accountId,
|
|
178
|
+
teamId,
|
|
179
|
+
orgId
|
|
180
|
+
} = event.data || {};
|
|
181
|
+
if (!channelId || !content || !createdBy || !accountId) {
|
|
182
|
+
throw new Error('channelId, content and createdBy are required');
|
|
183
|
+
}
|
|
184
|
+
const messageData = await step.run('persist-message', async () => {
|
|
185
|
+
const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
|
|
186
|
+
container.get('PubSub');
|
|
187
|
+
const postDoc = await postService.create({
|
|
188
|
+
channel: channelId,
|
|
189
|
+
message: content,
|
|
190
|
+
editedBy: createdBy || accountId,
|
|
191
|
+
author: createdBy || accountId,
|
|
192
|
+
// Convert props to propsConfiguration structure
|
|
193
|
+
props: {
|
|
194
|
+
notificationParams: notificationParams || {},
|
|
195
|
+
template: modelConfig?.template || 'vite-react',
|
|
196
|
+
projectId: projectId,
|
|
197
|
+
role: AiAgentMessageRole.USER,
|
|
198
|
+
fragment: {},
|
|
199
|
+
sendNotificationWithProjectId: false
|
|
200
|
+
},
|
|
201
|
+
type: PostTypeEnum.Simple,
|
|
202
|
+
files
|
|
203
|
+
});
|
|
204
|
+
// if (postDoc) {
|
|
205
|
+
// pubsub.publish(`POST_CREATED.${projectId}`, postDoc);
|
|
206
|
+
// }
|
|
207
|
+
return await postDoc;
|
|
208
|
+
});
|
|
209
|
+
await step.run('publish-subscription-update', async () => {
|
|
210
|
+
const pubsub = container.get('PubSub');
|
|
211
|
+
pubsub.publish(`POST_CREATED.${projectId}`, messageData);
|
|
212
|
+
});
|
|
213
|
+
// Prepare final model config with the template that was actually used
|
|
214
|
+
// const finalModelConfig = await step.run('prepare-final-model-config', async () => {
|
|
215
|
+
// let actualTemplate = modelConfig?.template || 'nextjs';
|
|
216
|
+
// if (modelConfig) {
|
|
217
|
+
// // Use provided modelConfig but ensure template is what was actually stored
|
|
218
|
+
// const finalConfig = {
|
|
219
|
+
// ...modelConfig,
|
|
220
|
+
// template: actualTemplate, // Always use the immutable template from DB
|
|
221
|
+
// };
|
|
222
|
+
// console.log('Final modelConfig for code generation:', finalConfig);
|
|
223
|
+
// return finalConfig;
|
|
224
|
+
// }
|
|
225
|
+
// return {
|
|
226
|
+
// template: actualTemplate,
|
|
227
|
+
// provider: 'gemini',
|
|
228
|
+
// model: 'gemini-1.5-flash',
|
|
229
|
+
// apiKey: '', // Will need to be provided via environment
|
|
230
|
+
// };
|
|
231
|
+
// });
|
|
232
|
+
// Trigger code generation
|
|
233
|
+
// await step.run('trigger-code-generation', async () => {
|
|
234
|
+
// await inngest.send({
|
|
235
|
+
// name: 'code-agent/run-code-creation',
|
|
236
|
+
// data: {
|
|
237
|
+
// value:messageData.message,
|
|
238
|
+
// projectId,
|
|
239
|
+
// messageId: messageData.id,
|
|
240
|
+
// owner: messageData?.author || createdBy || accountId,
|
|
241
|
+
// orgName: orgId,
|
|
242
|
+
// modelConfig: finalModelConfig,
|
|
243
|
+
// accountId,
|
|
244
|
+
// teamId,
|
|
245
|
+
// orgId,
|
|
246
|
+
// channelId
|
|
247
|
+
// },
|
|
248
|
+
// });
|
|
249
|
+
// });
|
|
250
|
+
// Send event after result is available
|
|
251
|
+
// await step.sendEvent('emit-ai-response-generate', {
|
|
252
|
+
// name: 'ai.response.generate',
|
|
253
|
+
// data: {
|
|
254
|
+
// projectId,
|
|
255
|
+
// post: result,
|
|
256
|
+
// modelConfig,
|
|
257
|
+
// },
|
|
258
|
+
// });
|
|
259
|
+
return messageData;
|
|
260
|
+
});
|
|
261
|
+
// Main code agent function - processes queries and generates code
|
|
262
|
+
const codeAgentFunction = (inngest, container) => inngest.createFunction({
|
|
263
|
+
id: 'code-agent'
|
|
264
|
+
}, {
|
|
265
|
+
event: 'code-agent/run-code-creation'
|
|
266
|
+
}, async ({
|
|
267
|
+
event,
|
|
268
|
+
step
|
|
269
|
+
}) => {
|
|
270
|
+
console.log('=== CODE AGENT FUNCTION STARTED ===');
|
|
271
|
+
console.log('Event data:', JSON.stringify(event.data, null, 2));
|
|
272
|
+
const {
|
|
273
|
+
sandboxId,
|
|
274
|
+
sandboxUrl
|
|
275
|
+
} = await step.run('create-sandbox-and-start-monitoring', async () => {
|
|
276
|
+
const selectedTemplateId = getTemplateId(event.data.modelConfig);
|
|
277
|
+
console.log(`Creating sandbox with template: ${selectedTemplateId} (requested: ${event.data.modelConfig?.template || 'nextjs'})`);
|
|
278
|
+
const sandbox = await Sandbox.create(selectedTemplateId, {
|
|
279
|
+
apiKey,
|
|
280
|
+
domain,
|
|
281
|
+
timeoutMs: 60_000 * 20
|
|
282
|
+
});
|
|
283
|
+
const sandboxUrl = `https://3000-${sandbox.sandboxId}.yarntra.ai`;
|
|
284
|
+
// Start error monitoring immediately after sandbox creation
|
|
285
|
+
// const container = await initializeContainer();
|
|
286
|
+
const sandboxErrorService = container.get(SERVER_TYPES.SandboxErrorService);
|
|
287
|
+
console.log(`Starting immediate error monitoring for sandbox ${sandbox.sandboxId}`);
|
|
288
|
+
await sandboxErrorService.startErrorMonitoring(event.data.projectId, sandbox.sandboxId, sandboxUrl);
|
|
289
|
+
return {
|
|
290
|
+
sandboxId: sandbox.sandboxId,
|
|
291
|
+
sandboxUrl
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
// Only fetch previous messages if we don't have active fragment files
|
|
295
|
+
const previousMessages = event.data.activeFragmentFiles ? [] // Skip fetching previous messages when we have active fragment files
|
|
296
|
+
: await step.run('get-previous-messages', async () => {
|
|
297
|
+
const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
|
|
298
|
+
const result = await postService.getPreviousMessagesByProjectId(event.data.projectId, 5);
|
|
299
|
+
// Handle error case - return empty array if getPreviousMessagesByProjectId returns an Error
|
|
300
|
+
if (result instanceof Error) {
|
|
301
|
+
console.warn('Failed to get previous messages:', result.message);
|
|
302
|
+
return [];
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
305
|
+
});
|
|
306
|
+
const state = createState({
|
|
307
|
+
summary: '',
|
|
308
|
+
files: event.data.activeFragmentFiles || {},
|
|
309
|
+
// Use active fragment files if provided
|
|
310
|
+
canvasLayers: []
|
|
311
|
+
}, {
|
|
312
|
+
messages: previousMessages
|
|
313
|
+
});
|
|
314
|
+
// Get model configuration from event data
|
|
315
|
+
const {
|
|
316
|
+
modelConfig
|
|
317
|
+
} = event.data;
|
|
318
|
+
console.log('Model configuration received:', JSON.stringify(modelConfig, null, 2));
|
|
319
|
+
// Create a new agent with the template-specific coding prompt
|
|
320
|
+
const selectedCodingPrompt = getCodingPrompt(event.data.modelConfig);
|
|
321
|
+
console.log(`Using coding prompt for template: ${event.data.modelConfig?.template || 'nextjs'}`);
|
|
322
|
+
const codeAgent = createAgent({
|
|
323
|
+
name: 'code-agent',
|
|
324
|
+
description: 'An expert coding agent for page creation',
|
|
325
|
+
system: selectedCodingPrompt,
|
|
326
|
+
model: createModelInstance(modelConfig),
|
|
327
|
+
tools: [createTool({
|
|
328
|
+
name: 'terminal',
|
|
329
|
+
description: 'Use the terminal to run commands',
|
|
330
|
+
parameters: z.object({
|
|
331
|
+
command: z.string()
|
|
332
|
+
}),
|
|
333
|
+
handler: async ({
|
|
334
|
+
command
|
|
335
|
+
}, {
|
|
336
|
+
step
|
|
337
|
+
}) => await step?.run('terminal', async () => {
|
|
338
|
+
try {
|
|
339
|
+
const sandbox = await getSandbox(sandboxId);
|
|
340
|
+
const result = await sandbox.commands.run(command);
|
|
341
|
+
return result.stdout;
|
|
342
|
+
} catch (e) {
|
|
343
|
+
return `Command failed: ${e}`;
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
}), createTool({
|
|
347
|
+
name: 'createOrUpdateFiles',
|
|
348
|
+
description: 'Create or update files in the sandbox',
|
|
349
|
+
parameters: z.object({
|
|
350
|
+
files: z.union([z.array(z.object({
|
|
351
|
+
path: z.string(),
|
|
352
|
+
content: z.string()
|
|
353
|
+
})), z.string() // Accept JSON string as fallback
|
|
354
|
+
])
|
|
355
|
+
}),
|
|
356
|
+
// handler: async ({ files }, { step, network }: Tool.Options<AgentState>) => {
|
|
357
|
+
handler: async ({
|
|
358
|
+
files
|
|
359
|
+
}, {
|
|
360
|
+
step,
|
|
361
|
+
network
|
|
362
|
+
}) => {
|
|
363
|
+
const newFiles = await step?.run('createOrUpdateFiles', async () => {
|
|
364
|
+
try {
|
|
365
|
+
console.log('createOrUpdateFiles - Raw files parameter type:', typeof files);
|
|
366
|
+
console.log('createOrUpdateFiles - Raw files parameter:', `${JSON.stringify(files).substring(0, 200)}...`);
|
|
367
|
+
// Parse files if it's a JSON string (fallback for AI model behavior)
|
|
368
|
+
let parsedFiles;
|
|
369
|
+
if (typeof files === 'string') {
|
|
370
|
+
try {
|
|
371
|
+
// First try standard JSON parsing
|
|
372
|
+
parsedFiles = JSON.parse(files);
|
|
373
|
+
console.log('createOrUpdateFiles - Successfully parsed files from JSON string');
|
|
374
|
+
} catch (parseError) {
|
|
375
|
+
console.log('createOrUpdateFiles - Standard JSON parse failed, trying template literal fix');
|
|
376
|
+
try {
|
|
377
|
+
// AI is using template literals in JSON - need more sophisticated parsing
|
|
378
|
+
console.log('createOrUpdateFiles - Attempting to fix template literals in JSON');
|
|
379
|
+
// Try to evaluate the string as JavaScript to handle template literals
|
|
380
|
+
// This is safe because we're in a sandboxed environment and only parsing our own AI's output
|
|
381
|
+
const evalString = `(${files})`;
|
|
382
|
+
console.log('createOrUpdateFiles - Evaluating as JavaScript array');
|
|
383
|
+
parsedFiles = eval(evalString);
|
|
384
|
+
console.log('createOrUpdateFiles - Successfully evaluated template literals');
|
|
385
|
+
} catch (secondError) {
|
|
386
|
+
console.error('createOrUpdateFiles - Both parsing attempts failed:');
|
|
387
|
+
console.error('Original error:', parseError.message);
|
|
388
|
+
console.error('Template literal fix error:', secondError.message);
|
|
389
|
+
console.error('Raw string (first 500 chars):', files.substring(0, 500));
|
|
390
|
+
return `Error: Failed to parse files JSON - ${parseError.message}`;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
} else if (Array.isArray(files)) {
|
|
394
|
+
parsedFiles = files;
|
|
395
|
+
console.log('createOrUpdateFiles - Using files array directly');
|
|
396
|
+
} else {
|
|
397
|
+
console.error('createOrUpdateFiles - Files parameter is neither string nor array:', typeof files);
|
|
398
|
+
return `Error: Files parameter must be an array or JSON string, got ${typeof files}`;
|
|
399
|
+
}
|
|
400
|
+
// Validate that parsedFiles is an array
|
|
401
|
+
if (!Array.isArray(parsedFiles)) {
|
|
402
|
+
console.error('createOrUpdateFiles - Parsed files is not an array:', parsedFiles);
|
|
403
|
+
return `Error: Files must be an array after parsing, got ${typeof parsedFiles}`;
|
|
404
|
+
}
|
|
405
|
+
console.log(`createOrUpdateFiles - Processing ${parsedFiles.length} files`);
|
|
406
|
+
const updatedFiles = network.state.data.files || {};
|
|
407
|
+
const sandbox = await getSandbox(sandboxId);
|
|
408
|
+
for (const file of parsedFiles) {
|
|
409
|
+
if (!file.path || file.content === undefined) {
|
|
410
|
+
console.error('createOrUpdateFiles - Invalid file object (missing path or content):', file);
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
console.log(`createOrUpdateFiles - Writing file: ${file.path}`);
|
|
414
|
+
await sandbox.files.write(file.path, file.content);
|
|
415
|
+
updatedFiles[file.path] = file.content;
|
|
416
|
+
}
|
|
417
|
+
console.log(`createOrUpdateFiles - Successfully updated ${parsedFiles.length} files`);
|
|
418
|
+
return updatedFiles;
|
|
419
|
+
} catch (e) {
|
|
420
|
+
console.error('createOrUpdateFiles - Error:', e);
|
|
421
|
+
console.error('createOrUpdateFiles - Error stack:', e?.stack);
|
|
422
|
+
return `Error: ${e}`;
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
if (typeof newFiles === 'object' && !Array.isArray(newFiles) && typeof newFiles !== 'string') {
|
|
426
|
+
network.state.data.files = newFiles;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}), createTool({
|
|
430
|
+
name: 'readFiles',
|
|
431
|
+
description: 'Read files from the sandbox',
|
|
432
|
+
parameters: z.object({
|
|
433
|
+
files: z.array(z.string())
|
|
434
|
+
}),
|
|
435
|
+
handler: async ({
|
|
436
|
+
files
|
|
437
|
+
}, {
|
|
438
|
+
step
|
|
439
|
+
}) => await step?.run('readFiles', async () => {
|
|
440
|
+
try {
|
|
441
|
+
const sandbox = await getSandbox(sandboxId);
|
|
442
|
+
const contents = [];
|
|
443
|
+
for (const file of files) {
|
|
444
|
+
const content = await sandbox.files.read(file);
|
|
445
|
+
contents.push({
|
|
446
|
+
path: file,
|
|
447
|
+
content
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
return JSON.stringify(contents);
|
|
451
|
+
} catch (e) {
|
|
452
|
+
return `Error: ${e}`;
|
|
453
|
+
}
|
|
454
|
+
})
|
|
455
|
+
})],
|
|
456
|
+
lifecycle: {
|
|
457
|
+
onResponse: async ({
|
|
458
|
+
result,
|
|
459
|
+
network
|
|
460
|
+
}) => {
|
|
461
|
+
const lastAssistantMessageText = lastAssistantTextMessageContent(result);
|
|
462
|
+
if (lastAssistantMessageText && network) {
|
|
463
|
+
if (lastAssistantMessageText.includes('<task_summary>')) {
|
|
464
|
+
network.state.data.summary = lastAssistantMessageText;
|
|
465
|
+
// Extract canvas layers if present
|
|
466
|
+
const canvasLayersMatch = lastAssistantMessageText.match(/<canvas_layers>([\s\S]*?)<\/canvas_layers>/);
|
|
467
|
+
if (canvasLayersMatch) {
|
|
468
|
+
try {
|
|
469
|
+
const layersJson = canvasLayersMatch[1].trim();
|
|
470
|
+
network.state.data.canvasLayers = JSON.parse(layersJson);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
console.error('Error parsing canvas layers:', error);
|
|
473
|
+
network.state.data.canvasLayers = null;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return result;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
const network = createNetwork({
|
|
483
|
+
name: 'coding-agent-network',
|
|
484
|
+
agents: [codeAgent],
|
|
485
|
+
maxIter: 15,
|
|
486
|
+
defaultState: state,
|
|
487
|
+
router: async ({
|
|
488
|
+
network
|
|
489
|
+
}) => {
|
|
490
|
+
const {
|
|
491
|
+
summary
|
|
492
|
+
} = network.state.data;
|
|
493
|
+
if (summary) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
return codeAgent;
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
// Prepare the enhanced input message that includes activeFragmentFiles context
|
|
500
|
+
let enhancedInput = event.data.value;
|
|
501
|
+
// If we have activeFragmentFiles, include them in the message to the AI
|
|
502
|
+
if (event.data.activeFragmentFiles && Object.keys(event.data.activeFragmentFiles).length > 0) {
|
|
503
|
+
console.log('📂 Including activeFragmentFiles context for AI agent:');
|
|
504
|
+
console.log('- File count:', Object.keys(event.data.activeFragmentFiles).length);
|
|
505
|
+
console.log('- Files:', Object.keys(event.data.activeFragmentFiles));
|
|
506
|
+
// Check if ViteVisualEditor exists and warn if it's missing for vite-react template
|
|
507
|
+
const hasViteVisualEditor = 'src/components/ViteVisualEditor.tsx' in event.data.activeFragmentFiles;
|
|
508
|
+
const isViteReact = event.data.modelConfig?.template === 'vite-react';
|
|
509
|
+
if (isViteReact && !hasViteVisualEditor) {
|
|
510
|
+
console.warn('⚠️ ViteVisualEditor.tsx missing from activeFragmentFiles for vite-react template');
|
|
511
|
+
console.warn('AI may create stub component instead of importing existing one');
|
|
512
|
+
}
|
|
513
|
+
enhancedInput = `${event.data.value}
|
|
514
|
+
|
|
515
|
+
ACTIVE FRAGMENT FILES CONTEXT:
|
|
516
|
+
The current sandbox contains the following files that you MUST include in your createOrUpdateFiles call:
|
|
517
|
+
|
|
518
|
+
${Object.entries(event.data.activeFragmentFiles).map(([path, content]) => `FILE: ${path}\n${typeof content === 'string' ? content.substring(0, 500) + (content.length > 500 ? '...' : '') : '[Non-string content]'}`).join('\n\n')}
|
|
519
|
+
|
|
520
|
+
CRITICAL INSTRUCTIONS FOR VISUAL CHANGES:
|
|
521
|
+
- You MUST include ALL ${Object.keys(event.data.activeFragmentFiles).length} files from the active fragment files in your createOrUpdateFiles call
|
|
522
|
+
- Apply the visual changes described above to the appropriate files
|
|
523
|
+
- Preserve ALL existing files and their content except for the specific changes requested
|
|
524
|
+
- Use the complete file context provided above as your current codebase state
|
|
525
|
+
- DO NOT output only the changed file - output ALL files to rebuild the complete sandbox${isViteReact && !hasViteVisualEditor ? '\n- IMPORTANT: For vite-react template, you MUST import ViteVisualEditor from "./components/ViteVisualEditor" (it exists in the template), do NOT create a new component' : ''}`;
|
|
526
|
+
}
|
|
527
|
+
console.log('🤖 Running AI agent with enhanced input length:', enhancedInput.length);
|
|
528
|
+
const result = await network.run(enhancedInput, {
|
|
529
|
+
state
|
|
530
|
+
});
|
|
531
|
+
// Enhanced file validation with logging
|
|
532
|
+
const hasFiles = result.state.data.files && Object.keys(result.state.data.files).length > 0;
|
|
533
|
+
const hasSummary = result.state.data.summary && result.state.data.summary.trim().length > 0;
|
|
534
|
+
const outputFileCount = Object.keys(result.state.data.files || {}).length;
|
|
535
|
+
const inputFileCount = event.data.activeFragmentFiles ? Object.keys(event.data.activeFragmentFiles).length : 0;
|
|
536
|
+
console.log('Agent result validation:', {
|
|
537
|
+
hasFiles,
|
|
538
|
+
outputFileCount,
|
|
539
|
+
inputFileCount,
|
|
540
|
+
fileCountMatch: inputFileCount > 0 ? outputFileCount === inputFileCount : true,
|
|
541
|
+
hasSummary,
|
|
542
|
+
summaryLength: result.state.data.summary?.length || 0,
|
|
543
|
+
outputFiles: Object.keys(result.state.data.files || {}),
|
|
544
|
+
inputFiles: event.data.activeFragmentFiles ? Object.keys(event.data.activeFragmentFiles) : []
|
|
545
|
+
});
|
|
546
|
+
// Warning if file counts don't match for visual changes
|
|
547
|
+
if (event.data.activeFragmentFiles && inputFileCount > 0 && outputFileCount !== inputFileCount) {
|
|
548
|
+
console.warn(`⚠️ File count mismatch! Expected ${inputFileCount} files, got ${outputFileCount} files`);
|
|
549
|
+
console.warn('Missing files may cause incomplete sandbox rebuild');
|
|
550
|
+
}
|
|
551
|
+
const fragmentTitleGenerator = createAgent({
|
|
552
|
+
name: 'fragment-title-generator',
|
|
553
|
+
description: 'A fragment title generator',
|
|
554
|
+
system: FRAGMENT_TITLE_PROMPT,
|
|
555
|
+
model: createModelInstance(modelConfig)
|
|
556
|
+
});
|
|
557
|
+
const responseGenerator = createAgent({
|
|
558
|
+
name: 'response-generator',
|
|
559
|
+
description: 'A response generator',
|
|
560
|
+
system: RESPONSE_PROMPT,
|
|
561
|
+
model: createModelInstance(modelConfig)
|
|
562
|
+
});
|
|
563
|
+
// Run generators in parallel outside of steps (like lovable-clone)
|
|
564
|
+
const {
|
|
565
|
+
output: fragmentTitleOutput
|
|
566
|
+
} = await fragmentTitleGenerator.run(result.state.data.summary || 'Generated Page');
|
|
567
|
+
const {
|
|
568
|
+
output: responseOutput
|
|
569
|
+
} = await responseGenerator.run(result.state.data.summary || 'Code generated successfully');
|
|
570
|
+
const agentError = !result.state.data.summary || Object.keys(result.state.data.files || {}).length === 0;
|
|
571
|
+
// sandboxUrl is already available from the previous step
|
|
572
|
+
// Save to database using handler
|
|
573
|
+
const messageData = await step.run('save-result', async () => {
|
|
574
|
+
const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
|
|
575
|
+
// Normalize template name for database validation
|
|
576
|
+
const normalizedModelConfig = {
|
|
577
|
+
...event.data.modelConfig,
|
|
578
|
+
template: normalizeTemplateName(event.data.modelConfig?.template)
|
|
579
|
+
};
|
|
580
|
+
return await postService.saveCodeAgentResult({
|
|
581
|
+
value: event.data.value,
|
|
582
|
+
projectId: event.data.projectId,
|
|
583
|
+
messageId: event.data.messageId,
|
|
584
|
+
owner: event.data.owner,
|
|
585
|
+
orgName: event.data.orgName,
|
|
586
|
+
modelConfig: normalizedModelConfig
|
|
587
|
+
}, sandboxUrl, parseAgentOutput(fragmentTitleOutput), parseAgentOutput(responseOutput), result.state.data.files || {}, result.state.data.summary || '', agentError, result.state.data.canvasLayers);
|
|
588
|
+
});
|
|
589
|
+
// Publish subscription update for real-time client updates
|
|
590
|
+
await step.run('publish-subscription-update', async () => {
|
|
591
|
+
// const responseData = {
|
|
592
|
+
// projectId: event.data.projectId,
|
|
593
|
+
// sandboxId: sandboxId,
|
|
594
|
+
// sandboxUrl: sandboxUrl,
|
|
595
|
+
// url: sandboxUrl, // Keep both for compatibility
|
|
596
|
+
// title: parseAgentOutput(fragmentTitleOutput) || 'Generated Page',
|
|
597
|
+
// files: result.state.data.files,
|
|
598
|
+
// summary: result.state.data.summary,
|
|
599
|
+
// isError: agentError,
|
|
600
|
+
// messageId: agentError ? null : (messageData as any).id,
|
|
601
|
+
// canvasLayers: result.state.data.canvasLayers,
|
|
602
|
+
// template: normalizeTemplateName(event.data.modelConfig?.template),
|
|
603
|
+
// timestamp: new Date().toISOString(),
|
|
604
|
+
// };
|
|
605
|
+
const pubsub = container.get('PubSub');
|
|
606
|
+
// const post = event.data.post;
|
|
607
|
+
// const postData = {
|
|
608
|
+
// ...messageData,
|
|
609
|
+
// props: {
|
|
610
|
+
// ...messageData.props,
|
|
611
|
+
// fragment: responseData,
|
|
612
|
+
// },
|
|
613
|
+
// };
|
|
614
|
+
console.log('=== Publish to subscription Post Data:', JSON.stringify(messageData, null, 2));
|
|
615
|
+
pubsub.publish(`POST_CREATED.${event.data.projectId}`, messageData);
|
|
616
|
+
});
|
|
617
|
+
// Error monitoring was already started immediately after sandbox creation
|
|
618
|
+
// No need to start it again here
|
|
619
|
+
return {
|
|
620
|
+
projectId: event.data.projectId,
|
|
621
|
+
sandboxId: sandboxId,
|
|
622
|
+
sandboxUrl: sandboxUrl,
|
|
623
|
+
url: sandboxUrl,
|
|
624
|
+
// Keep both for compatibility
|
|
625
|
+
title: parseAgentOutput(fragmentTitleOutput) || 'Generated Page',
|
|
626
|
+
files: result.state.data.files,
|
|
627
|
+
summary: result.state.data.summary,
|
|
628
|
+
isError: agentError,
|
|
629
|
+
canvasLayers: result.state.data.canvasLayers,
|
|
630
|
+
template: normalizeTemplateName(event.data.modelConfig?.template),
|
|
631
|
+
timestamp: new Date().toISOString()
|
|
632
|
+
};
|
|
633
|
+
});
|
|
634
|
+
const generateAIResponseWithSandbox = (inngest, container) => inngest.createFunction({
|
|
635
|
+
id: 'generate-ai-response-with-sandbox'
|
|
636
|
+
}, {
|
|
637
|
+
event: 'ai.response.generate'
|
|
638
|
+
}, async ({
|
|
639
|
+
event,
|
|
640
|
+
step
|
|
641
|
+
}) => {
|
|
642
|
+
console.log('=== AI Response Generation with Sandbox FUNCTION STARTED ===');
|
|
643
|
+
console.log('Event data:', JSON.stringify(event.data, null, 2));
|
|
644
|
+
const {
|
|
645
|
+
projectId,
|
|
646
|
+
post,
|
|
647
|
+
modelConfig
|
|
648
|
+
} = event.data || {};
|
|
649
|
+
const message = post.message;
|
|
650
|
+
// Validate required parameters
|
|
651
|
+
if (!projectId || !message) {
|
|
652
|
+
throw new Error('projectId and message are required');
|
|
653
|
+
}
|
|
654
|
+
// Create e2b sandbox
|
|
655
|
+
const sandbox = await step.run('create-sandbox', async () => {
|
|
656
|
+
try {
|
|
657
|
+
const selectedTemplateId = getTemplateId(modelConfig);
|
|
658
|
+
console.log(`Creating sandbox with template: ${selectedTemplateId} (requested: ${modelConfig?.template || 'vite-react'})`);
|
|
659
|
+
const sandbox = await Sandbox.create(selectedTemplateId, {
|
|
660
|
+
apiKey,
|
|
661
|
+
domain,
|
|
662
|
+
timeoutMs: 60_000 * 20 // 20 minutes timeout
|
|
663
|
+
});
|
|
664
|
+
return sandbox;
|
|
665
|
+
} catch (error) {
|
|
666
|
+
console.error('Failed to create sandbox:', error);
|
|
667
|
+
throw new Error(`Failed to create sandbox: ${error.message}`);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
// Create the appropriate coding prompt based on framework
|
|
671
|
+
let codingPrompt = CODING_PROMPT;
|
|
672
|
+
if (modelConfig?.template === 'vue') {
|
|
673
|
+
codingPrompt = VUE_CODING_PROMPT;
|
|
674
|
+
} else if (modelConfig?.template === 'vite-react') {
|
|
675
|
+
codingPrompt = VITE_REACT_CODING_PROMPT;
|
|
676
|
+
}
|
|
677
|
+
// Create agent with sandbox tools (use modelConfig model if provided)
|
|
678
|
+
const agent = createAgent({
|
|
679
|
+
name: 'CodeGenerator',
|
|
680
|
+
system: codingPrompt,
|
|
681
|
+
model: createModelInstance(modelConfig?.modelConfig || modelConfig),
|
|
682
|
+
tools: [createTool({
|
|
683
|
+
name: 'createOrUpdateFiles',
|
|
684
|
+
description: 'Create or update files in the sandbox',
|
|
685
|
+
parameters: z.object({
|
|
686
|
+
files: z.array(z.object({
|
|
687
|
+
path: z.string(),
|
|
688
|
+
content: z.string()
|
|
689
|
+
}))
|
|
690
|
+
}),
|
|
691
|
+
handler: async ({
|
|
692
|
+
files
|
|
693
|
+
}) => {
|
|
694
|
+
try {
|
|
695
|
+
await sandbox.files.write(files);
|
|
696
|
+
return {
|
|
697
|
+
success: true,
|
|
698
|
+
message: `Updated ${files.length} files`
|
|
699
|
+
};
|
|
700
|
+
} catch (error) {
|
|
701
|
+
return {
|
|
702
|
+
success: false,
|
|
703
|
+
error: error.message
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}), createTool({
|
|
708
|
+
name: 'readFiles',
|
|
709
|
+
description: 'Read files from the sandbox',
|
|
710
|
+
parameters: z.object({
|
|
711
|
+
paths: z.array(z.string())
|
|
712
|
+
}),
|
|
713
|
+
handler: async ({
|
|
714
|
+
paths
|
|
715
|
+
}) => {
|
|
716
|
+
try {
|
|
717
|
+
const files = await Promise.all(paths.map(async path => {
|
|
718
|
+
try {
|
|
719
|
+
const content = await sandbox.files.read(path);
|
|
720
|
+
return {
|
|
721
|
+
path,
|
|
722
|
+
content,
|
|
723
|
+
exists: true
|
|
724
|
+
};
|
|
725
|
+
} catch (error) {
|
|
726
|
+
return {
|
|
727
|
+
path,
|
|
728
|
+
content: '',
|
|
729
|
+
exists: false,
|
|
730
|
+
error: error.message
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}));
|
|
734
|
+
return {
|
|
735
|
+
success: true,
|
|
736
|
+
files
|
|
737
|
+
};
|
|
738
|
+
} catch (error) {
|
|
739
|
+
return {
|
|
740
|
+
success: false,
|
|
741
|
+
error: error.message
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}), createTool({
|
|
746
|
+
name: 'terminal',
|
|
747
|
+
description: 'Execute terminal commands in the sandbox',
|
|
748
|
+
parameters: z.object({
|
|
749
|
+
command: z.string()
|
|
750
|
+
}),
|
|
751
|
+
handler: async ({
|
|
752
|
+
command
|
|
753
|
+
}) => {
|
|
754
|
+
try {
|
|
755
|
+
const result = await sandbox.process.start({
|
|
756
|
+
cmd: command
|
|
757
|
+
});
|
|
758
|
+
const output = await result.finished;
|
|
759
|
+
return {
|
|
760
|
+
success: true,
|
|
761
|
+
output: output.stdout || output.stderr,
|
|
762
|
+
exitCode: output.exitCode
|
|
763
|
+
};
|
|
764
|
+
} catch (error) {
|
|
765
|
+
return {
|
|
766
|
+
success: false,
|
|
767
|
+
error: error.message
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
})]
|
|
772
|
+
});
|
|
773
|
+
// Generate AI response using agent-kit (completely outside of step.run to avoid nesting)
|
|
774
|
+
let aiResponse;
|
|
775
|
+
try {
|
|
776
|
+
console.log('=== Starting AI response generation ===');
|
|
777
|
+
console.log('Message:', message);
|
|
778
|
+
// Generate response
|
|
779
|
+
const result = await agent.run(message);
|
|
780
|
+
// Parse the response to extract task summary and canvas layers
|
|
781
|
+
const responseContent = lastAssistantTextMessageContent(result);
|
|
782
|
+
console.log('=== AI Response Content ===');
|
|
783
|
+
console.log('Response Content:', responseContent);
|
|
784
|
+
const taskSummary = responseContent?.match(/<task_summary>([\s\S]*?)<\/task_summary>/)?.[1]?.trim();
|
|
785
|
+
const canvasLayersMatch = responseContent?.match(/<canvas_layers>([\s\S]*?)<\/canvas_layers>/)?.[1];
|
|
786
|
+
let canvasLayers = null;
|
|
787
|
+
if (canvasLayersMatch) {
|
|
788
|
+
try {
|
|
789
|
+
canvasLayers = JSON.parse(canvasLayersMatch.trim());
|
|
790
|
+
} catch (error) {
|
|
791
|
+
console.error('Failed to parse canvas layers:', error);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
aiResponse = {
|
|
795
|
+
success: true,
|
|
796
|
+
response: responseContent,
|
|
797
|
+
taskSummary,
|
|
798
|
+
canvasLayers,
|
|
799
|
+
agentResult: result
|
|
800
|
+
};
|
|
801
|
+
console.log('=== AI Response Object ===');
|
|
802
|
+
console.log('AI Response:', JSON.stringify(aiResponse, null, 2));
|
|
803
|
+
} catch (error) {
|
|
804
|
+
console.error('AI response generation failed:', error);
|
|
805
|
+
aiResponse = {
|
|
806
|
+
success: false,
|
|
807
|
+
error: error.message,
|
|
808
|
+
response: null,
|
|
809
|
+
taskSummary: null,
|
|
810
|
+
canvasLayers: null
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
// Generate sandbox URL
|
|
814
|
+
const sandboxUrl = await step.run('generate-sandbox-url', async () => {
|
|
815
|
+
try {
|
|
816
|
+
// Your domain pattern: https://3000-<sandboxId>.yarntra.ai
|
|
817
|
+
const url = `https://3000-${sandbox.sandboxId}.yarntra.ai`;
|
|
818
|
+
console.log(`Generated sandbox URL: ${url}`);
|
|
819
|
+
return url;
|
|
820
|
+
} catch (error) {
|
|
821
|
+
console.error('Failed to generate sandbox URL:', error);
|
|
822
|
+
throw new Error(`Failed to generate sandbox URL: ${error.message}`);
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
console.log('sandboxUrl', sandboxUrl);
|
|
826
|
+
// Publish to subscription
|
|
827
|
+
await step.run('publish-to-subscription', async () => {
|
|
828
|
+
try {
|
|
829
|
+
const pubsub = container.get('PubSub');
|
|
830
|
+
const responseData = {
|
|
831
|
+
projectId,
|
|
832
|
+
sandboxId: sandbox.sandboxId,
|
|
833
|
+
sandboxUrl,
|
|
834
|
+
url: sandboxUrl,
|
|
835
|
+
// Keep both for compatibility
|
|
836
|
+
aiResponse: aiResponse.success ? aiResponse.response : null,
|
|
837
|
+
taskSummary: aiResponse.success ? aiResponse.taskSummary : null,
|
|
838
|
+
canvasLayers: aiResponse.success ? aiResponse.canvasLayers : null,
|
|
839
|
+
error: aiResponse.success ? null : aiResponse.error,
|
|
840
|
+
template: normalizeTemplateName(modelConfig?.template || 'vite-react'),
|
|
841
|
+
timestamp: new Date().toISOString()
|
|
842
|
+
};
|
|
843
|
+
console.log('=== Response Data ===');
|
|
844
|
+
console.log('Response Data:', JSON.stringify(responseData, null, 2));
|
|
845
|
+
console.log('AI Response Success:', aiResponse.success);
|
|
846
|
+
// console.log('AI Response Content:', aiResponse.response);
|
|
847
|
+
// pubsub.publish(`AI_RESPONSE_GENERATED.${projectId}`, responseData);
|
|
848
|
+
const postData = {
|
|
849
|
+
...post,
|
|
850
|
+
props: {
|
|
851
|
+
...post.props,
|
|
852
|
+
generatedResponse: responseData
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
console.log('=== Publish to subscription Post Data:', JSON.stringify(postData, null, 2));
|
|
856
|
+
pubsub.publish(`POST_CREATED.${projectId}`, postData);
|
|
857
|
+
return responseData;
|
|
858
|
+
} catch (error) {
|
|
859
|
+
console.error('Failed to publish to subscription:', error);
|
|
860
|
+
throw new Error(`Failed to publish to subscription: ${error.message}`);
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
return {
|
|
864
|
+
projectId,
|
|
865
|
+
sandboxId: sandbox.sandboxId,
|
|
866
|
+
sandboxUrl,
|
|
867
|
+
url: sandboxUrl,
|
|
868
|
+
// Keep both for compatibility
|
|
869
|
+
aiResponse: aiResponse.success ? aiResponse.response : null,
|
|
870
|
+
taskSummary: aiResponse.success ? aiResponse.taskSummary : null,
|
|
871
|
+
canvasLayers: aiResponse.success ? aiResponse.canvasLayers : null,
|
|
872
|
+
error: aiResponse.success ? null : aiResponse.error,
|
|
873
|
+
template: normalizeTemplateName(modelConfig?.template || 'vite-react'),
|
|
874
|
+
timestamp: new Date().toISOString()
|
|
875
|
+
};
|
|
876
|
+
});
|
|
877
|
+
const generateAiCodeFunction = (inngest, container) => inngest.createFunction({
|
|
878
|
+
id: 'ai-code-agent'
|
|
879
|
+
}, {
|
|
880
|
+
event: 'ai-code-agent/run-code-generation'
|
|
881
|
+
}, async ({
|
|
882
|
+
event,
|
|
883
|
+
step
|
|
884
|
+
}) => {
|
|
885
|
+
console.log('=== CODE AGENT FUNCTION STARTED ===');
|
|
886
|
+
console.log('Event data:', JSON.stringify(event.data, null, 2));
|
|
887
|
+
const {
|
|
888
|
+
messageId,
|
|
889
|
+
modelConfig
|
|
890
|
+
} = event.data || {};
|
|
891
|
+
const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
|
|
892
|
+
const post = await postService.get(messageId);
|
|
893
|
+
console.log('ai-event-post', JSON.stringify(post, null, 2));
|
|
894
|
+
if (!post) {
|
|
895
|
+
throw new Error('Post not found');
|
|
896
|
+
}
|
|
897
|
+
const {
|
|
898
|
+
sandboxId,
|
|
899
|
+
sandboxUrl
|
|
900
|
+
} = await step.run('create-sandbox-and-start-monitoring', async () => {
|
|
901
|
+
const selectedTemplateId = getTemplateId(modelConfig);
|
|
902
|
+
const sandbox = await Sandbox.create(selectedTemplateId, {
|
|
903
|
+
apiKey,
|
|
904
|
+
domain,
|
|
905
|
+
timeoutMs: 60_000 * 20
|
|
906
|
+
});
|
|
907
|
+
const sandboxUrl = `https://3000-${sandbox.sandboxId}.yarntra.ai`;
|
|
908
|
+
// Start error monitoring immediately after sandbox creation
|
|
909
|
+
// const container = await initializeContainer();
|
|
910
|
+
// const sandboxErrorService = container.get<ISandboxErrorService>(SERVER_TYPES.SandboxErrorService);
|
|
911
|
+
// console.log(`Starting immediate error monitoring for sandbox ${sandbox.sandboxId}`);
|
|
912
|
+
// await sandboxErrorService.startErrorMonitoring(post.props.projectId, sandbox.sandboxId, sandboxUrl,messageId);
|
|
913
|
+
return {
|
|
914
|
+
sandboxId: sandbox.sandboxId,
|
|
915
|
+
sandboxUrl
|
|
916
|
+
};
|
|
917
|
+
});
|
|
918
|
+
// Only fetch previous messages if we don't have active fragment files
|
|
919
|
+
const previousMessages = event.data.activeFragmentFiles ? [] // Skip fetching previous messages when we have active fragment files
|
|
920
|
+
: await step.run('get-previous-messages', async () => {
|
|
921
|
+
const result = await postService.getPreviousMessagesByProjectId(post.props.projectId, 5);
|
|
922
|
+
console.log('previousMessages', JSON.stringify(result, null, 2));
|
|
923
|
+
// Handle error case - return empty array if getPreviousMessagesByProjectId returns an Error
|
|
924
|
+
if (result instanceof Error) {
|
|
925
|
+
console.warn('Failed to get previous messages:', result.message);
|
|
926
|
+
return [];
|
|
927
|
+
}
|
|
928
|
+
return result;
|
|
929
|
+
});
|
|
930
|
+
const state = createState({
|
|
931
|
+
summary: '',
|
|
932
|
+
files: event.data.activeFragmentFiles || {},
|
|
933
|
+
// Use active fragment files if provided
|
|
934
|
+
canvasLayers: []
|
|
935
|
+
}, {
|
|
936
|
+
messages: previousMessages
|
|
937
|
+
});
|
|
938
|
+
// Create a new agent with the template-specific coding prompt
|
|
939
|
+
const selectedCodingPrompt = getCodingPrompt(modelConfig);
|
|
940
|
+
const codeAgent = createAgent({
|
|
941
|
+
name: 'code-agent',
|
|
942
|
+
description: 'An expert coding agent for page creation',
|
|
943
|
+
system: selectedCodingPrompt,
|
|
944
|
+
model: createModelInstance(modelConfig),
|
|
945
|
+
tools: [createTool({
|
|
946
|
+
name: 'terminal',
|
|
947
|
+
description: 'Use the terminal to run commands',
|
|
948
|
+
parameters: z.object({
|
|
949
|
+
command: z.string()
|
|
950
|
+
}),
|
|
951
|
+
handler: async ({
|
|
952
|
+
command
|
|
953
|
+
}, {
|
|
954
|
+
step
|
|
955
|
+
}) => await step?.run('terminal', async () => {
|
|
956
|
+
try {
|
|
957
|
+
const sandbox = await getSandbox(sandboxId);
|
|
958
|
+
const result = await sandbox.commands.run(command);
|
|
959
|
+
return result.stdout;
|
|
960
|
+
} catch (e) {
|
|
961
|
+
return `Command failed: ${e}`;
|
|
962
|
+
}
|
|
963
|
+
})
|
|
964
|
+
}), createTool({
|
|
965
|
+
name: 'createOrUpdateFiles',
|
|
966
|
+
description: 'Create or update files in the sandbox',
|
|
967
|
+
parameters: z.object({
|
|
968
|
+
files: z.union([z.array(z.object({
|
|
969
|
+
path: z.string(),
|
|
970
|
+
content: z.string()
|
|
971
|
+
})), z.string() // Accept JSON string as fallback
|
|
972
|
+
])
|
|
973
|
+
}),
|
|
974
|
+
// handler: async ({ files }, { step, network }: Tool.Options<AgentState>) => {
|
|
975
|
+
handler: async ({
|
|
976
|
+
files
|
|
977
|
+
}, {
|
|
978
|
+
step,
|
|
979
|
+
network
|
|
980
|
+
}) => {
|
|
981
|
+
const newFiles = await step?.run('createOrUpdateFiles', async () => {
|
|
982
|
+
try {
|
|
983
|
+
console.log('createOrUpdateFiles - Raw files parameter type:', typeof files);
|
|
984
|
+
console.log('createOrUpdateFiles - Raw files parameter:', `${JSON.stringify(files).substring(0, 200)}...`);
|
|
985
|
+
// Parse files if it's a JSON string (fallback for AI model behavior)
|
|
986
|
+
let parsedFiles;
|
|
987
|
+
if (typeof files === 'string') {
|
|
988
|
+
try {
|
|
989
|
+
// First try standard JSON parsing
|
|
990
|
+
parsedFiles = JSON.parse(files);
|
|
991
|
+
console.log('createOrUpdateFiles - Successfully parsed files from JSON string');
|
|
992
|
+
} catch (parseError) {
|
|
993
|
+
console.log('createOrUpdateFiles - Standard JSON parse failed, trying template literal fix');
|
|
994
|
+
try {
|
|
995
|
+
// AI is using template literals in JSON - need more sophisticated parsing
|
|
996
|
+
console.log('createOrUpdateFiles - Attempting to fix template literals in JSON');
|
|
997
|
+
// Try to evaluate the string as JavaScript to handle template literals
|
|
998
|
+
// This is safe because we're in a sandboxed environment and only parsing our own AI's output
|
|
999
|
+
const evalString = `(${files})`;
|
|
1000
|
+
console.log('createOrUpdateFiles - Evaluating as JavaScript array');
|
|
1001
|
+
parsedFiles = eval(evalString);
|
|
1002
|
+
console.log('createOrUpdateFiles - Successfully evaluated template literals');
|
|
1003
|
+
} catch (secondError) {
|
|
1004
|
+
console.error('createOrUpdateFiles - Both parsing attempts failed:');
|
|
1005
|
+
console.error('Original error:', parseError.message);
|
|
1006
|
+
console.error('Template literal fix error:', secondError.message);
|
|
1007
|
+
console.error('Raw string (first 500 chars):', files.substring(0, 500));
|
|
1008
|
+
return `Error: Failed to parse files JSON - ${parseError.message}`;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
} else if (Array.isArray(files)) {
|
|
1012
|
+
parsedFiles = files;
|
|
1013
|
+
console.log('createOrUpdateFiles - Using files array directly');
|
|
1014
|
+
} else {
|
|
1015
|
+
console.error('createOrUpdateFiles - Files parameter is neither string nor array:', typeof files);
|
|
1016
|
+
return `Error: Files parameter must be an array or JSON string, got ${typeof files}`;
|
|
1017
|
+
}
|
|
1018
|
+
// Validate that parsedFiles is an array
|
|
1019
|
+
if (!Array.isArray(parsedFiles)) {
|
|
1020
|
+
console.error('createOrUpdateFiles - Parsed files is not an array:', parsedFiles);
|
|
1021
|
+
return `Error: Files must be an array after parsing, got ${typeof parsedFiles}`;
|
|
1022
|
+
}
|
|
1023
|
+
console.log(`createOrUpdateFiles - Processing ${parsedFiles.length} files`);
|
|
1024
|
+
const updatedFiles = network.state.data.files || {};
|
|
1025
|
+
const sandbox = await getSandbox(sandboxId);
|
|
1026
|
+
for (const file of parsedFiles) {
|
|
1027
|
+
if (!file.path || file.content === undefined) {
|
|
1028
|
+
console.error('createOrUpdateFiles - Invalid file object (missing path or content):', file);
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
console.log(`createOrUpdateFiles - Writing file: ${file.path}`);
|
|
1032
|
+
await sandbox.files.write(file.path, file.content);
|
|
1033
|
+
updatedFiles[file.path] = file.content;
|
|
1034
|
+
}
|
|
1035
|
+
console.log(`createOrUpdateFiles - Successfully updated ${parsedFiles.length} files`);
|
|
1036
|
+
return updatedFiles;
|
|
1037
|
+
} catch (e) {
|
|
1038
|
+
console.error('createOrUpdateFiles - Error:', e);
|
|
1039
|
+
console.error('createOrUpdateFiles - Error stack:', e?.stack);
|
|
1040
|
+
return `Error: ${e}`;
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
if (typeof newFiles === 'object' && !Array.isArray(newFiles) && typeof newFiles !== 'string') {
|
|
1044
|
+
network.state.data.files = newFiles;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}), createTool({
|
|
1048
|
+
name: 'readFiles',
|
|
1049
|
+
description: 'Read files from the sandbox',
|
|
1050
|
+
parameters: z.object({
|
|
1051
|
+
files: z.array(z.string())
|
|
1052
|
+
}),
|
|
1053
|
+
handler: async ({
|
|
1054
|
+
files
|
|
1055
|
+
}, {
|
|
1056
|
+
step
|
|
1057
|
+
}) => await step?.run('readFiles', async () => {
|
|
1058
|
+
try {
|
|
1059
|
+
const sandbox = await getSandbox(sandboxId);
|
|
1060
|
+
const contents = [];
|
|
1061
|
+
for (const file of files) {
|
|
1062
|
+
const content = await sandbox.files.read(file);
|
|
1063
|
+
contents.push({
|
|
1064
|
+
path: file,
|
|
1065
|
+
content
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
return JSON.stringify(contents);
|
|
1069
|
+
} catch (e) {
|
|
1070
|
+
return `Error: ${e}`;
|
|
1071
|
+
}
|
|
1072
|
+
})
|
|
1073
|
+
})],
|
|
1074
|
+
lifecycle: {
|
|
1075
|
+
onResponse: async ({
|
|
1076
|
+
result,
|
|
1077
|
+
network
|
|
1078
|
+
}) => {
|
|
1079
|
+
const lastAssistantMessageText = lastAssistantTextMessageContent(result);
|
|
1080
|
+
if (lastAssistantMessageText && network) {
|
|
1081
|
+
if (lastAssistantMessageText.includes('<task_summary>')) {
|
|
1082
|
+
network.state.data.summary = lastAssistantMessageText;
|
|
1083
|
+
// Extract canvas layers if present
|
|
1084
|
+
const canvasLayersMatch = lastAssistantMessageText.match(/<canvas_layers>([\s\S]*?)<\/canvas_layers>/);
|
|
1085
|
+
if (canvasLayersMatch) {
|
|
1086
|
+
try {
|
|
1087
|
+
const layersJson = canvasLayersMatch[1].trim();
|
|
1088
|
+
network.state.data.canvasLayers = JSON.parse(layersJson);
|
|
1089
|
+
} catch (error) {
|
|
1090
|
+
console.error('Error parsing canvas layers:', error);
|
|
1091
|
+
network.state.data.canvasLayers = null;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return result;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
const network = createNetwork({
|
|
1101
|
+
name: 'coding-agent-network',
|
|
1102
|
+
agents: [codeAgent],
|
|
1103
|
+
maxIter: 15,
|
|
1104
|
+
defaultState: state,
|
|
1105
|
+
router: async ({
|
|
1106
|
+
network
|
|
1107
|
+
}) => {
|
|
1108
|
+
const {
|
|
1109
|
+
summary
|
|
1110
|
+
} = network.state.data;
|
|
1111
|
+
if (summary) {
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
return codeAgent;
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
// Prepare the enhanced input message that includes activeFragmentFiles context
|
|
1118
|
+
let enhancedInput = post.message;
|
|
1119
|
+
// If we have activeFragmentFiles, include them in the message to the AI
|
|
1120
|
+
if (event.data.activeFragmentFiles && Object.keys(event.data.activeFragmentFiles).length > 0) {
|
|
1121
|
+
// Check if ViteVisualEditor exists and warn if it's missing for vite-react template
|
|
1122
|
+
const hasViteVisualEditor = 'src/components/ViteVisualEditor.tsx' in event.data.activeFragmentFiles;
|
|
1123
|
+
const isViteReact = modelConfig?.template === 'vite-react';
|
|
1124
|
+
enhancedInput = `${post.message}
|
|
1125
|
+
ACTIVE FRAGMENT FILES CONTEXT:
|
|
1126
|
+
The current sandbox contains the following files that you MUST include in your createOrUpdateFiles call:
|
|
1127
|
+
|
|
1128
|
+
${Object.entries(event.data.activeFragmentFiles).map(([path, content]) => `FILE: ${path}\n${typeof content === 'string' ? content.substring(0, 500) + (content.length > 500 ? '...' : '') : '[Non-string content]'}`).join('\n\n')}
|
|
1129
|
+
|
|
1130
|
+
CRITICAL INSTRUCTIONS FOR VISUAL CHANGES:
|
|
1131
|
+
- You MUST include ALL ${Object.keys(event.data.activeFragmentFiles).length} files from the active fragment files in your createOrUpdateFiles call
|
|
1132
|
+
- Apply the visual changes described above to the appropriate files
|
|
1133
|
+
- Preserve ALL existing files and their content except for the specific changes requested
|
|
1134
|
+
- Use the complete file context provided above as your current codebase state
|
|
1135
|
+
- DO NOT output only the changed file - output ALL files to rebuild the complete sandbox${isViteReact && !hasViteVisualEditor ? '\n- IMPORTANT: For vite-react template, you MUST import ViteVisualEditor from "./components/ViteVisualEditor" (it exists in the template), do NOT create a new component' : ''}`;
|
|
1136
|
+
}
|
|
1137
|
+
const result = await network.run(enhancedInput, {
|
|
1138
|
+
state
|
|
1139
|
+
});
|
|
1140
|
+
// Enhanced file validation with logging
|
|
1141
|
+
const hasFiles = result.state.data.files && Object.keys(result.state.data.files).length > 0;
|
|
1142
|
+
const hasSummary = result.state.data.summary && result.state.data.summary.trim().length > 0;
|
|
1143
|
+
const outputFileCount = Object.keys(result.state.data.files || {}).length;
|
|
1144
|
+
const inputFileCount = event.data.activeFragmentFiles ? Object.keys(event.data.activeFragmentFiles).length : 0;
|
|
1145
|
+
console.log('Agent result validation:', {
|
|
1146
|
+
hasFiles,
|
|
1147
|
+
outputFileCount,
|
|
1148
|
+
inputFileCount,
|
|
1149
|
+
fileCountMatch: inputFileCount > 0 ? outputFileCount === inputFileCount : true,
|
|
1150
|
+
hasSummary,
|
|
1151
|
+
summaryLength: result.state.data.summary?.length || 0,
|
|
1152
|
+
outputFiles: Object.keys(result.state.data.files || {}),
|
|
1153
|
+
inputFiles: event.data.activeFragmentFiles ? Object.keys(event.data.activeFragmentFiles) : []
|
|
1154
|
+
});
|
|
1155
|
+
// Warning if file counts don't match for visual changes
|
|
1156
|
+
if (event.data.activeFragmentFiles && inputFileCount > 0 && outputFileCount !== inputFileCount) {
|
|
1157
|
+
console.warn(`⚠️ File count mismatch! Expected ${inputFileCount} files, got ${outputFileCount} files`);
|
|
1158
|
+
console.warn('Missing files may cause incomplete sandbox rebuild');
|
|
1159
|
+
}
|
|
1160
|
+
const fragmentTitleGenerator = createAgent({
|
|
1161
|
+
name: 'fragment-title-generator',
|
|
1162
|
+
description: 'A fragment title generator',
|
|
1163
|
+
system: FRAGMENT_TITLE_PROMPT,
|
|
1164
|
+
model: createModelInstance(modelConfig)
|
|
1165
|
+
});
|
|
1166
|
+
const responseGenerator = createAgent({
|
|
1167
|
+
name: 'response-generator',
|
|
1168
|
+
description: 'A response generator',
|
|
1169
|
+
system: RESPONSE_PROMPT,
|
|
1170
|
+
model: createModelInstance(modelConfig)
|
|
1171
|
+
});
|
|
1172
|
+
// Run generators in parallel outside of steps (like lovable-clone)
|
|
1173
|
+
const {
|
|
1174
|
+
output: fragmentTitleOutput
|
|
1175
|
+
} = await fragmentTitleGenerator.run(result.state.data.summary || 'Generated Page');
|
|
1176
|
+
const {
|
|
1177
|
+
output: responseOutput
|
|
1178
|
+
} = await responseGenerator.run(result.state.data.summary || 'Code generated successfully');
|
|
1179
|
+
const agentError = !result.state.data.summary || Object.keys(result.state.data.files || {}).length === 0;
|
|
1180
|
+
// Save to database using handler
|
|
1181
|
+
const messageData = await step.run('save-ai-code-result', async () => {
|
|
1182
|
+
if (agentError) {
|
|
1183
|
+
return await postService.create({
|
|
1184
|
+
channel: post.channel,
|
|
1185
|
+
message: 'Something went wrong. Please try again.',
|
|
1186
|
+
type: PostTypeEnum.Simple,
|
|
1187
|
+
editedBy: post.editedBy,
|
|
1188
|
+
author: post.editedBy,
|
|
1189
|
+
files: [],
|
|
1190
|
+
props: {
|
|
1191
|
+
role: AiAgentMessageRole.ASSISTANT,
|
|
1192
|
+
projectId: post.props.projectId,
|
|
1193
|
+
template: modelConfig?.template || 'vite-react',
|
|
1194
|
+
sendNotificationWithProjectId: true,
|
|
1195
|
+
fragment: {
|
|
1196
|
+
sandboxUrl: sandboxUrl,
|
|
1197
|
+
title: parseAgentOutput(fragmentTitleOutput) || 'Generated Page',
|
|
1198
|
+
files: result.state.data.files || [],
|
|
1199
|
+
summary: result.state.data.summary || '',
|
|
1200
|
+
canvasLayers: result.state.data.canvasLayers || [],
|
|
1201
|
+
type: AiAgentMessageType.ERROR,
|
|
1202
|
+
messageId: messageId,
|
|
1203
|
+
isError: agentError
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
return await postService.create({
|
|
1209
|
+
channel: post.channel,
|
|
1210
|
+
message: parseAgentOutput(responseOutput) || 'Code generated successfully',
|
|
1211
|
+
type: PostTypeEnum.Simple,
|
|
1212
|
+
editedBy: post.editedBy,
|
|
1213
|
+
author: post.editedBy,
|
|
1214
|
+
files: [],
|
|
1215
|
+
props: {
|
|
1216
|
+
role: AiAgentMessageRole.ASSISTANT,
|
|
1217
|
+
projectId: post.props.projectId,
|
|
1218
|
+
template: modelConfig?.template || 'vite-react',
|
|
1219
|
+
sendNotificationWithProjectId: true,
|
|
1220
|
+
fragment: {
|
|
1221
|
+
sandboxUrl: sandboxUrl,
|
|
1222
|
+
title: parseAgentOutput(fragmentTitleOutput) || 'Generated Page',
|
|
1223
|
+
files: result.state.data.files || [],
|
|
1224
|
+
summary: result.state.data.summary || '',
|
|
1225
|
+
canvasLayers: result.state.data.canvasLayers || [],
|
|
1226
|
+
type: AiAgentMessageType.RESULT,
|
|
1227
|
+
messageId: messageId,
|
|
1228
|
+
isError: agentError
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
});
|
|
1233
|
+
// Publish subscription update for real-time client updates
|
|
1234
|
+
// await step.run('publish-subscription-ai-code-result', async () => {
|
|
1235
|
+
// const pubsub = container.get<PubSub>('PubSub');
|
|
1236
|
+
// console.log('=== Publish to subscription Post Data:', JSON.stringify(messageData, null, 2));
|
|
1237
|
+
// pubsub.publish(`POST_CREATED.${post.props.projectId}`, messageData);
|
|
1238
|
+
// });
|
|
1239
|
+
// Error monitoring was already started immediately after sandbox creation
|
|
1240
|
+
// No need to start it again here
|
|
1241
|
+
return messageData;
|
|
1242
|
+
});
|
|
1243
|
+
// Sandbox recreation function - recreates sandbox with existing files from fragment
|
|
1244
|
+
const recreateSandboxUrlFunction = (inngest, container) => inngest.createFunction({
|
|
1245
|
+
id: 'recreate-sandbox-url-function'
|
|
1246
|
+
}, {
|
|
1247
|
+
event: 'recreate-sandbox-url'
|
|
1248
|
+
}, async ({
|
|
1249
|
+
event,
|
|
1250
|
+
step
|
|
1251
|
+
}) => {
|
|
1252
|
+
console.log('=== RECREATE SANDBOX FUNCTION STARTED ===');
|
|
1253
|
+
console.log('Event data:', JSON.stringify(event.data, null, 2));
|
|
1254
|
+
const {
|
|
1255
|
+
projectId,
|
|
1256
|
+
messageId
|
|
1257
|
+
} = event.data;
|
|
1258
|
+
// Fetch the fragment with existing files
|
|
1259
|
+
const messageData = await step.run('fetch-message', async () => {
|
|
1260
|
+
const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
|
|
1261
|
+
return await postService.get(messageId);
|
|
1262
|
+
});
|
|
1263
|
+
if (!messageData || !messageData?.props?.fragment?.files) {
|
|
1264
|
+
throw new Error(`Message ${messageId} not found or has no files`);
|
|
1265
|
+
}
|
|
1266
|
+
// Create new sandbox with existing files using the stored template
|
|
1267
|
+
const {
|
|
1268
|
+
sandboxId,
|
|
1269
|
+
sandboxUrl
|
|
1270
|
+
} = await step.run('create-new-sandbox', async () => {
|
|
1271
|
+
// Get the correct template ID based on the stored template
|
|
1272
|
+
const selectedTemplateId = getTemplateId({
|
|
1273
|
+
template: messageData.props.template
|
|
1274
|
+
});
|
|
1275
|
+
console.log(`Recreating sandbox with template: ${selectedTemplateId} (stored: ${messageData.props.template || 'react-vite'})`);
|
|
1276
|
+
const sandbox = await Sandbox.create(selectedTemplateId, {
|
|
1277
|
+
apiKey,
|
|
1278
|
+
domain
|
|
1279
|
+
});
|
|
1280
|
+
const sandboxUrl = `https://3000-${sandbox.sandboxId}.yarntra.ai`;
|
|
1281
|
+
console.log(`Created new sandbox ${sandbox.sandboxId} for project ${projectId}`);
|
|
1282
|
+
// Write all existing files to the new sandbox
|
|
1283
|
+
for (const [filePath, fileContent] of Object.entries(messageData.props.fragment.files)) {
|
|
1284
|
+
await sandbox.files.write(filePath, fileContent);
|
|
1285
|
+
console.log(`Wrote file ${filePath} to new sandbox`);
|
|
1286
|
+
}
|
|
1287
|
+
// Start error monitoring for the new sandbox
|
|
1288
|
+
const sandboxErrorService = container.get(SERVER_TYPES.SandboxErrorService);
|
|
1289
|
+
console.log(`Starting error monitoring for recreated sandbox ${sandbox.sandboxId}`);
|
|
1290
|
+
await sandboxErrorService.startErrorMonitoring(projectId, sandbox.sandboxId, sandboxUrl, messageId);
|
|
1291
|
+
return {
|
|
1292
|
+
sandboxId: sandbox.sandboxId,
|
|
1293
|
+
sandboxUrl
|
|
1294
|
+
};
|
|
1295
|
+
});
|
|
1296
|
+
// Update the fragment with new sandbox URL
|
|
1297
|
+
const updatedMessage = await step.run('update-message', async () => {
|
|
1298
|
+
const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
|
|
1299
|
+
return await postService.update(messageId, {
|
|
1300
|
+
props: {
|
|
1301
|
+
...messageData.props,
|
|
1302
|
+
fragment: {
|
|
1303
|
+
...messageData.props.fragment,
|
|
1304
|
+
sandboxUrl: sandboxUrl
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
});
|
|
1309
|
+
// Publish update to notify clients of the new sandbox URL for the same fragment
|
|
1310
|
+
await step.run('notify-sandbox-recreation', async () => {
|
|
1311
|
+
const pubsub = container.get('PubSub');
|
|
1312
|
+
// Publish a fragment update instead of a project update
|
|
1313
|
+
await pubsub.publish(`POST_UPDATED.${projectId}`, updatedMessage);
|
|
1314
|
+
});
|
|
1315
|
+
return {
|
|
1316
|
+
success: true,
|
|
1317
|
+
projectId,
|
|
1318
|
+
messageId,
|
|
1319
|
+
newSandboxUrl: sandboxUrl,
|
|
1320
|
+
newSandboxId: sandboxId
|
|
1321
|
+
};
|
|
1322
|
+
});
|
|
1323
|
+
const regenerateAiCodeFunction = (inngest, container) => inngest.createFunction({
|
|
1324
|
+
id: 'regenerate-ai-code-agent'
|
|
1325
|
+
}, {
|
|
1326
|
+
event: 'regenerate-ai-code'
|
|
1327
|
+
}, async ({
|
|
1328
|
+
event,
|
|
1329
|
+
step
|
|
1330
|
+
}) => {
|
|
1331
|
+
console.log('=== REGENERATE AI CODE AGENT FUNCTION STARTED ===');
|
|
1332
|
+
console.log('Event data:', JSON.stringify(event.data, null, 2));
|
|
1333
|
+
const {
|
|
1334
|
+
messageId,
|
|
1335
|
+
modelConfig
|
|
1336
|
+
} = event.data || {};
|
|
1337
|
+
const postService = container.getNamed(SERVER_TYPES.PostService, TaggedType.MICROSERVICE);
|
|
1338
|
+
const post = await postService.get(messageId);
|
|
1339
|
+
if (!post) {
|
|
1340
|
+
throw new Error('Post not found');
|
|
1341
|
+
}
|
|
1342
|
+
const {
|
|
1343
|
+
sandboxId,
|
|
1344
|
+
sandboxUrl
|
|
1345
|
+
} = await step.run('create-sandbox-and-start-monitoring', async () => {
|
|
1346
|
+
const selectedTemplateId = getTemplateId(modelConfig);
|
|
1347
|
+
const sandbox = await Sandbox.create(selectedTemplateId, {
|
|
1348
|
+
apiKey,
|
|
1349
|
+
domain,
|
|
1350
|
+
timeoutMs: 60_000 * 20
|
|
1351
|
+
});
|
|
1352
|
+
const sandboxUrl = `https://3000-${sandbox.sandboxId}.yarntra.ai`;
|
|
1353
|
+
// Start error monitoring immediately after sandbox creation
|
|
1354
|
+
// const container = await initializeContainer();
|
|
1355
|
+
// const sandboxErrorService = container.get<ISandboxErrorService>(SERVER_TYPES.SandboxErrorService);
|
|
1356
|
+
// console.log(`Starting immediate error monitoring for sandbox ${sandbox.sandboxId}`);
|
|
1357
|
+
// await sandboxErrorService.startErrorMonitoring(post.props.projectId, sandbox.sandboxId, sandboxUrl,messageId);
|
|
1358
|
+
return {
|
|
1359
|
+
sandboxId: sandbox.sandboxId,
|
|
1360
|
+
sandboxUrl
|
|
1361
|
+
};
|
|
1362
|
+
});
|
|
1363
|
+
// Save to database using handler
|
|
1364
|
+
const messageData = await step.run('save-ai-code-result', async () => {
|
|
1365
|
+
return await postService.update(messageId, {
|
|
1366
|
+
props: {
|
|
1367
|
+
...post.props,
|
|
1368
|
+
sendNotificationWithProjectId: false,
|
|
1369
|
+
fragment: {
|
|
1370
|
+
sandboxUrl: sandboxUrl
|
|
1371
|
+
// files: result.state.data.files || [],
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
});
|
|
1376
|
+
// Publish subscription update for real-time client updates
|
|
1377
|
+
await step.run('publish-subscription-ai-code-result', async () => {
|
|
1378
|
+
const pubsub = container.get('PubSub');
|
|
1379
|
+
console.log('=== Publish to subscription Post Data:', JSON.stringify(messageData, null, 2));
|
|
1380
|
+
pubsub.publish(`POST_CREATED.${post.props.projectId}`, messageData);
|
|
1381
|
+
});
|
|
1382
|
+
// Error monitoring was already started immediately after sandbox creation
|
|
1383
|
+
// No need to start it again here
|
|
1384
|
+
return messageData;
|
|
1385
|
+
});export{codeAgentFunction,createChannelWithProjectId,generateAIResponseWithSandbox,generateAiCodeFunction,recreateSandboxUrlFunction,regenerateAiCodeFunction,sendMessageHandler};//# sourceMappingURL=functions.js.map
|