everydaytok commited on
Commit
01a73c9
·
verified ·
1 Parent(s): 724df9f

Update aiEngine.js

Browse files
Files changed (1) hide show
  1. aiEngine.js +109 -311
aiEngine.js CHANGED
@@ -6,113 +6,174 @@ dotenv.config();
6
 
7
  const REMOTE_SERVER_URL = process.env.REMOTE_AI_URL || "http://localhost:7860";
8
 
 
9
  let prompts = {};
10
  try {
11
  const promptsPath = path.resolve('./prompts.json');
12
- if (fs.existsSync(promptsPath)) prompts = JSON.parse(fs.readFileSync(promptsPath, 'utf8'));
13
- } catch (e) { console.error("Prompt Load Error:", e); }
 
 
 
 
14
 
 
15
  const flattenHistory = (history, currentInput, systemPrompt, limit = 10, gdd = null) => {
 
16
  const recentHistory = history.slice(-limit);
 
17
  let context = recentHistory.map(m => {
18
  const roleName = m.role === 'model' ? 'Assistant' : 'User';
19
  return `${roleName}: ${m.parts[0].text}`;
20
  }).join('\n');
21
- const projectAnchor = gdd ? `[GDD REFERENCE]:\n${gdd}\n\n` : "";
 
 
 
22
  return `System: ${systemPrompt}\n\n${projectAnchor}${context}\nUser: ${currentInput}\nAssistant:`;
23
  };
24
 
 
25
  const handleStreamResponse = async (response, onThink, onOutput) => {
26
  if (!response.ok) throw new Error(`Stream Error: ${response.statusText}`);
 
27
  const reader = response.body.getReader();
28
  const decoder = new TextDecoder("utf-8");
 
29
  let fullStreamData = "";
30
 
31
  while (true) {
32
  const { done, value } = await reader.read();
33
  if (done) break;
 
34
  const chunk = decoder.decode(value, { stream: true });
35
  fullStreamData += chunk;
36
 
37
- if (chunk.includes("__THINK__")) {
38
- const parts = chunk.split("__THINK__");
39
- if (parts[0]) onOutput?.(parts[0]);
40
- if (parts[1]) onThink?.(parts[1]);
41
- } else if (!chunk.includes("__USAGE__")) {
42
- onOutput?.(chunk);
 
 
 
43
  }
44
  }
45
 
46
- let usage = { inputTokens: 0, outputTokens: 0, totalTokenCount: 0 };
47
- let finalOutput = fullStreamData;
 
48
 
49
  if (fullStreamData.includes("__USAGE__")) {
50
  const parts = fullStreamData.split("__USAGE__");
51
- finalOutput = parts[0];
 
 
52
  try {
53
- const rawUsage = JSON.parse(parts[1]);
54
- // Standardize usage object
55
- usage.totalTokenCount = rawUsage.totalTokenCount || 0;
56
- // If the remote server provides a breakdown, use it; otherwise estimate 80/20 split
57
- usage.inputTokens = rawUsage.inputTokens || Math.floor(usage.totalTokenCount * 0.8);
58
- usage.outputTokens = rawUsage.outputTokens || Math.ceil(usage.totalTokenCount * 0.2);
59
- } catch (e) { console.warn("Usage Parse Failed"); }
60
  }
61
 
62
- const cleanText = finalOutput.split("__THINK__")[0].trim();
63
- return { text: cleanText, usage };
 
 
64
  };
65
 
66
  export const AIEngine = {
 
 
67
  callPMStream: async (history, input, onThink, onOutput, gdd = null) => {
68
- const prompt = flattenHistory(history, input, prompts.pm_system_prompt, 15, gdd); // Reduced to 15 for cost
 
 
 
69
  const response = await fetch(`${REMOTE_SERVER_URL}/api/stream`, {
70
  method: 'POST',
71
  headers: { 'Content-Type': 'application/json' },
72
- body: JSON.stringify({ model: "claude", prompt: prompt, system_prompt: prompts.pm_system_prompt })
 
 
 
 
73
  });
74
  return await handleStreamResponse(response, onThink, onOutput);
75
  },
76
 
77
  callWorkerStream: async (history, input, onThink, onOutput, images = []) => {
78
- const prompt = flattenHistory(history, input, prompts.worker_system_prompt, 8, null); // Reduced to 8 for cost
 
 
 
79
  const response = await fetch(`${REMOTE_SERVER_URL}/api/stream`, {
80
  method: 'POST',
81
  headers: { 'Content-Type': 'application/json' },
82
- body: JSON.stringify({ model: "gpt", prompt: prompt, system_prompt: prompts.worker_system_prompt, images })
 
 
 
 
 
83
  });
84
  return await handleStreamResponse(response, onThink, onOutput);
85
  },
86
 
 
 
87
  callPM: async (history, input, gdd = null) => {
88
- const prompt = flattenHistory(history, input, prompts.pm_system_prompt, 15, gdd);
 
 
89
  const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
90
  method: 'POST',
91
  headers: { 'Content-Type': 'application/json' },
92
- body: JSON.stringify({ model: "claude", prompt: prompt, system_prompt: prompts.pm_system_prompt })
 
 
 
 
93
  });
94
  const result = await response.json();
95
  return { text: result.data, usage: result.usage || { totalTokenCount: 0 } };
96
  },
97
 
98
  callWorker: async (history, input) => {
99
- const prompt = flattenHistory(history, input, prompts.worker_system_prompt, 8, null);
 
 
100
  const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
101
  method: 'POST',
102
  headers: { 'Content-Type': 'application/json' },
103
- body: JSON.stringify({ model: "gpt", prompt: prompt, system_prompt: prompts.worker_system_prompt })
 
 
 
 
104
  });
105
  const result = await response.json();
106
  return { text: result.data, usage: result.usage || { totalTokenCount: 0 } };
107
  },
108
 
 
 
109
  generateEntryQuestions: async (desc) => {
110
  const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
111
  method: 'POST',
112
  headers: { 'Content-Type': 'application/json' },
113
- body: JSON.stringify({ model: "gpt", prompt: `Analyze: ${desc}`, system_prompt: prompts.analyst_system_prompt })
 
 
 
 
114
  });
115
  const result = await response.json();
 
116
  return { ...JSON.parse(result.data), usage: result.usage };
117
  },
118
 
@@ -120,293 +181,30 @@ export const AIEngine = {
120
  const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
121
  method: 'POST',
122
  headers: { 'Content-Type': 'application/json' },
123
- body: JSON.stringify({ model: "gpt", prompt: `Grade: ${desc} ${JSON.stringify(ans)}`, system_prompt: prompts.analyst_system_prompt })
 
 
 
 
124
  });
125
  const result = await response.json();
126
  const parsed = JSON.parse(result.data);
127
- parsed.usage = result.usage;
128
  return parsed;
129
- }
130
- };
131
-
132
- /*import dotenv from 'dotenv';
133
- import fs from 'fs';
134
- import path from 'path';
135
-
136
- dotenv.config();
137
-
138
- const REMOTE_SERVER_URL = process.env.REMOTE_AI_URL || "http://localhost:7860";
139
-
140
- let prompts = {};
141
- try {
142
- const promptsPath = path.resolve('./prompts.json');
143
- if (fs.existsSync(promptsPath)) prompts = JSON.parse(fs.readFileSync(promptsPath, 'utf8'));
144
- } catch (e) { console.error("Prompt Load Error:", e); }
145
-
146
- const flattenHistory = (history, currentInput, systemPrompt) => {
147
- const context = history.map(m => {
148
- const roleName = m.role === 'model' ? 'Assistant' : 'User';
149
- return `${roleName}: ${m.parts[0].text}`;
150
- }).join('\n');
151
- return `System: ${systemPrompt}\n\n${context}\nUser: ${currentInput}\nAssistant:`;
152
- };
153
-
154
- /*
155
- // HELPER: STREAM SPLITTER & USAGE PARSER
156
- const handleStreamResponse = async (response, onThink, onOutput) => {
157
- if (!response.ok) throw new Error(`Stream Error: ${response.statusText}`);
158
-
159
- const reader = response.body.getReader();
160
- const decoder = new TextDecoder("utf-8");
161
-
162
- let fullText = "";
163
- let usage = { totalTokenCount: 0 }; // Default
164
-
165
- while (true) {
166
- const { done, value } = await reader.read();
167
- if (done) break;
168
-
169
- let chunk = decoder.decode(value, { stream: true });
170
-
171
- // CHECK FOR USAGE FOOTER
172
- if (chunk.includes("__USAGE__")) {
173
- const parts = chunk.split("__USAGE__");
174
- chunk = parts[0]; // The text part
175
- try {
176
- if (parts[1]) {
177
- usage = JSON.parse(parts[1]);
178
- }
179
- } catch (e) {
180
- console.warn("Failed to parse usage footer:", e);
181
- }
182
- }
183
-
184
- // STANDARD CHUNK PROCESSING
185
- if (chunk.startsWith("__THINK__")) {
186
- const thoughtContent = chunk.replace("__THINK__", "");
187
- if (onThink) onThink(thoughtContent);
188
- }
189
- else if (chunk.includes("__THINK__")) {
190
- const parts = chunk.split("__THINK__");
191
- if (parts[0]) {
192
- if (onOutput) onOutput(parts[0]);
193
- fullText += parts[0];
194
- }
195
- if (parts[1] && onThink) {
196
- onThink(parts[1]);
197
- }
198
- }
199
- else {
200
- if (onOutput) onOutput(chunk);
201
- fullText += chunk;
202
- }
203
- }
204
-
205
- return {
206
- text: fullText,
207
- usage: usage
208
- };
209
- };
210
- */
211
- /*
212
- const handleStreamResponse = async (response, onThink, onOutput) => {
213
- if (!response.ok) throw new Error(`Stream Error: ${response.statusText}`);
214
-
215
- const reader = response.body.getReader();
216
- const decoder = new TextDecoder("utf-8");
217
-
218
- let fullText = "";
219
- let usage = { totalTokenCount: 0 }; // Initialize usage object
220
-
221
- while (true) {
222
- const { done, value } = await reader.read();
223
- if (done) break;
224
-
225
- let chunk = decoder.decode(value, { stream: true });
226
-
227
- // --- CREDIT LOGIC: Parse Usage Tag ---
228
- if (chunk.includes("__USAGE__")) {
229
- const parts = chunk.split("__USAGE__");
230
- chunk = parts[0]; // Process the text before the tag
231
- try {
232
- if (parts[1]) {
233
- usage = JSON.parse(parts[1]); // Capture the usage from the remote server
234
- }
235
- } catch (e) {
236
- console.warn("Failed to parse usage footer");
237
- }
238
- }
239
-
240
- // --- THOUGHT/OUTPUT SPLITTING (Your existing logic) ---
241
- if (chunk.startsWith("__THINK__")) {
242
- const thoughtContent = chunk.replace("__THINK__", "");
243
- if (onThink) onThink(thoughtContent);
244
- } else if (chunk.includes("__THINK__")) {
245
- const parts = chunk.split("__THINK__");
246
- if (parts[0]) {
247
- if (onOutput) onOutput(parts[0]);
248
- fullText += parts[0];
249
- }
250
- if (parts[1] && onThink) onThink(parts[1]);
251
- } else {
252
- if (onOutput) onOutput(chunk);
253
- fullText += chunk;
254
- }
255
- }
256
-
257
- // Return both the text and the usage data
258
- return { text: fullText, usage: usage };
259
- };
260
- export const AIEngine = {
261
- // --- PM STREAMING ---
262
- callPMStream: async (history, input, onThink, onOutput) => {
263
- const systemPrompt = prompts.pm_system_prompt || "You are a pro manager.";
264
- const fullPrompt = flattenHistory(history, input, "");
265
-
266
- try {
267
- const response = await fetch(`${REMOTE_SERVER_URL}/api/stream`, {
268
- method: 'POST',
269
- headers: { 'Content-Type': 'application/json' },
270
- body: JSON.stringify({
271
- model: "claude",
272
- prompt: fullPrompt,
273
- system_prompt: systemPrompt
274
- })
275
- });
276
-
277
- return await handleStreamResponse(response, onThink, onOutput);
278
-
279
- } catch (error) {
280
- console.log("PM Stream error: ", error);
281
- throw error;
282
- }
283
  },
284
-
285
- // --- WORKER STREAMING (WITH IMAGES) ---
286
- callWorkerStream: async (history, input, onThink, onOutput, images = []) => {
287
- const systemPrompt = prompts.worker_system_prompt || "You are a worker.";
288
- const fullPrompt = flattenHistory(history, input, "");
289
-
290
- try {
291
- const response = await fetch(`${REMOTE_SERVER_URL}/api/stream`, {
292
  method: 'POST',
293
  headers: { 'Content-Type': 'application/json' },
294
- body: JSON.stringify({
295
- model: "gpt",
296
- prompt: fullPrompt,
297
- system_prompt: systemPrompt,
298
- images: images
299
- })
300
  });
301
-
302
- return await handleStreamResponse(response, onThink, onOutput);
303
-
304
- } catch (error) {
305
- console.log("Worker Stream error: ", error);
306
- throw error;
307
- }
308
- },
309
-
310
- // --- LEGACY BLOCKING CALLS ---
311
- callPM: async (history, input) => {
312
- const systemPrompt = prompts.pm_system_prompt || "You are a pro manager.";
313
- const fullPrompt = flattenHistory(history, input, "" );
314
-
315
- try {
316
- const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
317
- method: 'POST',
318
- headers: { 'Content-Type': 'application/json' },
319
- body: JSON.stringify({
320
- model: "claude",
321
- prompt: fullPrompt,
322
- system_prompt: systemPrompt
323
- })
324
- });
325
-
326
- const result = await response.json();
327
- if (!result.success) throw new Error(result.error);
328
-
329
- return {
330
- text: result.data,
331
- usage: result.usage || { totalTokenCount: 0 }
332
- };
333
- } catch (error) {
334
- console.log("PM error: ",error);
335
- return { text: "", error };
336
- }
337
- },
338
-
339
- callWorker: async (history, input, images = []) => {
340
- const systemPrompt = prompts.worker_system_prompt || "You are a worker.";
341
- const fullPrompt = flattenHistory(history, input, "" );
342
-
343
- try {
344
- const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
345
- method: 'POST',
346
- headers: { 'Content-Type': 'application/json' },
347
- body: JSON.stringify({
348
- model: "gpt",
349
- prompt: fullPrompt,
350
- system_prompt: systemPrompt
351
- })
352
- });
353
-
354
- const result = await response.json();
355
- if (!result.success) throw new Error(result.error);
356
-
357
- return {
358
- text: result.data,
359
- usage: result.usage || { totalTokenCount: 0 }
360
- };
361
- } catch (error) {
362
- console.log("Worker error: ",error);
363
- return { text: "", error };
364
- }
365
- },
366
-
367
- generateEntryQuestions: async (desc) => {
368
- try {
369
- const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
370
- method: 'POST',
371
- headers: { 'Content-Type': 'application/json' },
372
- body: JSON.stringify({
373
- model: "gpt",
374
- prompt: `[OUTPUT ONLY JSON]\n Generate entry questions for this idea: ${desc}`,
375
- system_prompt: `Goal: ${prompts.analyst_system_prompt}`
376
- })
377
- });
378
-
379
- const result = await response.json();
380
-
381
- return {
382
- ...JSON.parse(result.data),
383
- usage: result.usage || { totalTokenCount: 0 }
384
- };
385
-
386
- } catch (error) {
387
- console.log("GenerateQ error: ",error)
388
- }
389
- },
390
-
391
- gradeProject: async (desc, ans) => {
392
- try {
393
- const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
394
- method: 'POST',
395
- headers: { 'Content-Type': 'application/json' },
396
- body: JSON.stringify({
397
- model: "gpt",
398
- prompt: `[OUTPUT ONLY JSON]\n. Grade this project. Desc: ${desc}\nAnswers: ${JSON.stringify(ans)}`,
399
- system_prompt: prompts.analyst_system_prompt
400
- })
401
- });
402
-
403
- const result = await response.json();
404
- const parsed = JSON.parse(result.data);
405
- parsed.usage = result.usage || { totalTokenCount: 0 };
406
- return parsed;
407
-
408
- } catch (error) {
409
- console.log("GenerateQ error: ",error)
410
- }
411
  }
412
- }; */
 
6
 
7
  const REMOTE_SERVER_URL = process.env.REMOTE_AI_URL || "http://localhost:7860";
8
 
9
+ // --- PROMPT LOADING ---
10
  let prompts = {};
11
  try {
12
  const promptsPath = path.resolve('./prompts.json');
13
+ if (fs.existsSync(promptsPath)) {
14
+ prompts = JSON.parse(fs.readFileSync(promptsPath, 'utf8'));
15
+ }
16
+ } catch (e) {
17
+ console.error("Prompt Load Error:", e);
18
+ }
19
 
20
+ // --- HISTORY FLATTENER (WITH ECONOMIC LIMITS) ---
21
  const flattenHistory = (history, currentInput, systemPrompt, limit = 10, gdd = null) => {
22
+ // ECONOMIC CAP: Slice history to the last 'limit' messages to save tokens
23
  const recentHistory = history.slice(-limit);
24
+
25
  let context = recentHistory.map(m => {
26
  const roleName = m.role === 'model' ? 'Assistant' : 'User';
27
  return `${roleName}: ${m.parts[0].text}`;
28
  }).join('\n');
29
+
30
+ // Inject GDD only if provided (usually for PM)
31
+ const projectAnchor = gdd ? `[PROJECT GDD REFERENCE]:\n${gdd}\n\n` : "";
32
+
33
  return `System: ${systemPrompt}\n\n${projectAnchor}${context}\nUser: ${currentInput}\nAssistant:`;
34
  };
35
 
36
+ // --- STREAM HANDLER & USAGE PARSER ---
37
  const handleStreamResponse = async (response, onThink, onOutput) => {
38
  if (!response.ok) throw new Error(`Stream Error: ${response.statusText}`);
39
+
40
  const reader = response.body.getReader();
41
  const decoder = new TextDecoder("utf-8");
42
+
43
  let fullStreamData = "";
44
 
45
  while (true) {
46
  const { done, value } = await reader.read();
47
  if (done) break;
48
+
49
  const chunk = decoder.decode(value, { stream: true });
50
  fullStreamData += chunk;
51
 
52
+ // Streaming Logic: Don't show Usage to frontend, parse thoughts
53
+ if (!chunk.includes("__USAGE__")) {
54
+ if (chunk.includes("__THINK__")) {
55
+ const parts = chunk.split("__THINK__");
56
+ if (parts[0] && onOutput) onOutput(parts[0]);
57
+ if (parts[1] && onThink) onThink(parts[1]);
58
+ } else {
59
+ if (onOutput) onOutput(chunk);
60
+ }
61
  }
62
  }
63
 
64
+ // --- USAGE EXTRACTION FOR BILLING ---
65
+ let usage = { totalTokenCount: 0, inputTokens: 0, outputTokens: 0 };
66
+ let finalCleanText = fullStreamData;
67
 
68
  if (fullStreamData.includes("__USAGE__")) {
69
  const parts = fullStreamData.split("__USAGE__");
70
+ finalCleanText = parts[0]; // The actual text content
71
+ const usageRaw = parts[1];
72
+
73
  try {
74
+ const parsedUsage = JSON.parse(usageRaw);
75
+ usage.totalTokenCount = parsedUsage.totalTokenCount || 0;
76
+ usage.inputTokens = parsedUsage.inputTokens || 0;
77
+ usage.outputTokens = parsedUsage.outputTokens || 0;
78
+ } catch (e) {
79
+ console.warn("Usage Parse Failed in Engine:", e);
80
+ }
81
  }
82
 
83
+ // Clean any remaining tags
84
+ finalCleanText = finalCleanText.split("__THINK__")[0].trim();
85
+
86
+ return { text: finalCleanText, usage };
87
  };
88
 
89
  export const AIEngine = {
90
+ // --- STREAMING METHODS (Main Loop) ---
91
+
92
  callPMStream: async (history, input, onThink, onOutput, gdd = null) => {
93
+ const systemPrompt = prompts.pm_system_prompt || "You are a Project Manager.";
94
+ // ECONOMIC CAP: 15 messages max for PM to maintain context but control costs
95
+ const prompt = flattenHistory(history, input, systemPrompt, 15, gdd);
96
+
97
  const response = await fetch(`${REMOTE_SERVER_URL}/api/stream`, {
98
  method: 'POST',
99
  headers: { 'Content-Type': 'application/json' },
100
+ body: JSON.stringify({
101
+ model: "claude",
102
+ prompt: prompt,
103
+ system_prompt: systemPrompt
104
+ })
105
  });
106
  return await handleStreamResponse(response, onThink, onOutput);
107
  },
108
 
109
  callWorkerStream: async (history, input, onThink, onOutput, images = []) => {
110
+ const systemPrompt = prompts.worker_system_prompt || "You are a Senior Engineer.";
111
+ // ECONOMIC CAP: 8 messages max for Worker (they only need recent context)
112
+ const prompt = flattenHistory(history, input, systemPrompt, 8, null);
113
+
114
  const response = await fetch(`${REMOTE_SERVER_URL}/api/stream`, {
115
  method: 'POST',
116
  headers: { 'Content-Type': 'application/json' },
117
+ body: JSON.stringify({
118
+ model: "gpt",
119
+ prompt: prompt,
120
+ system_prompt: systemPrompt,
121
+ images: images
122
+ })
123
  });
124
  return await handleStreamResponse(response, onThink, onOutput);
125
  },
126
 
127
+ // --- BLOCKING CALLS (Background Initialization) ---
128
+
129
  callPM: async (history, input, gdd = null) => {
130
+ const systemPrompt = prompts.pm_system_prompt || "You are a Project Manager.";
131
+ const prompt = flattenHistory(history, input, systemPrompt, 15, gdd); // Limit 15
132
+
133
  const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
134
  method: 'POST',
135
  headers: { 'Content-Type': 'application/json' },
136
+ body: JSON.stringify({
137
+ model: "claude",
138
+ prompt: prompt,
139
+ system_prompt: systemPrompt
140
+ })
141
  });
142
  const result = await response.json();
143
  return { text: result.data, usage: result.usage || { totalTokenCount: 0 } };
144
  },
145
 
146
  callWorker: async (history, input) => {
147
+ const systemPrompt = prompts.worker_system_prompt || "You are a Senior Engineer.";
148
+ const prompt = flattenHistory(history, input, systemPrompt, 8, null); // Limit 8
149
+
150
  const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
151
  method: 'POST',
152
  headers: { 'Content-Type': 'application/json' },
153
+ body: JSON.stringify({
154
+ model: "gpt",
155
+ prompt: prompt,
156
+ system_prompt: systemPrompt
157
+ })
158
  });
159
  const result = await response.json();
160
  return { text: result.data, usage: result.usage || { totalTokenCount: 0 } };
161
  },
162
 
163
+ // --- UTILITIES (One-off calls) ---
164
+
165
  generateEntryQuestions: async (desc) => {
166
  const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
167
  method: 'POST',
168
  headers: { 'Content-Type': 'application/json' },
169
+ body: JSON.stringify({
170
+ model: "gpt",
171
+ prompt: `Analyze this project idea: ${desc}`,
172
+ system_prompt: prompts.analyst_system_prompt || "Output JSON only."
173
+ })
174
  });
175
  const result = await response.json();
176
+ // Return parsed data AND usage for billing
177
  return { ...JSON.parse(result.data), usage: result.usage };
178
  },
179
 
 
181
  const response = await fetch(`${REMOTE_SERVER_URL}/api/generate`, {
182
  method: 'POST',
183
  headers: { 'Content-Type': 'application/json' },
184
+ body: JSON.stringify({
185
+ model: "gpt",
186
+ prompt: `Grade this project. Description: ${desc} Answers: ${JSON.stringify(ans)}`,
187
+ system_prompt: prompts.analyst_system_prompt || "Output JSON only."
188
+ })
189
  });
190
  const result = await response.json();
191
  const parsed = JSON.parse(result.data);
192
+ parsed.usage = result.usage; // Attach usage for billing
193
  return parsed;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  },
195
+
196
+ generateImage: async (prompt) => {
197
+ try {
198
+ const response = await fetch(`${REMOTE_SERVER_URL}/api/image`, {
 
 
 
 
199
  method: 'POST',
200
  headers: { 'Content-Type': 'application/json' },
201
+ body: JSON.stringify({ prompt })
 
 
 
 
 
202
  });
203
+ const result = await response.json();
204
+ return result; // Expected { image: "base64..." }
205
+ } catch (e) {
206
+ console.error("Image Gen Error:", e);
207
+ return null;
208
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  }
210
+ };