wuyiqunLu commited on
Commit
ca7a659
1 Parent(s): cfb938a

feat: change db message format and parser (#66)

Browse files

<img width="767" alt="image"
src="https://github.com/landing-ai/vision-agent-ui/assets/132986242/070987c0-994b-4223-a030-a05d352c8b4f">

app/api/vision-agent/route.ts CHANGED
@@ -1,15 +1,54 @@
1
  import { StreamingTextResponse } from 'ai';
2
 
3
  // import { auth } from '@/auth';
4
- import { MessageBase } from '../../../lib/types';
5
  import { logger, withLogging } from '@/lib/logger';
6
  import { CLEANED_SEPARATOR } from '@/lib/constants';
7
  import { cleanAnswerMessage, cleanInputMessage } from '@/lib/messageUtils';
 
8
 
9
  // export const runtime = 'edge';
10
  export const dynamic = 'force-dynamic';
11
  export const maxDuration = 300; // This function can run for a maximum of 5 minutes
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  export const POST = withLogging(
14
  async (
15
  session,
@@ -110,7 +149,6 @@ export const POST = withLogging(
110
  if (fetchResponse.body) {
111
  const encoder = new TextEncoder();
112
  const decoder = new TextDecoder('utf-8');
113
- let buffer = '';
114
  let maxChunkSize = 0;
115
  const stream = fetchResponse.body.pipeThrough(
116
  new TransformStream({
@@ -128,7 +166,50 @@ export const POST = withLogging(
128
  transform: async (chunk, controller) => {
129
  const data = decoder.decode(chunk, { stream: true });
130
  maxChunkSize = Math.max(data.length, maxChunkSize);
131
- controller.enqueue(encoder.encode(data));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  },
133
  }),
134
  );
 
1
  import { StreamingTextResponse } from 'ai';
2
 
3
  // import { auth } from '@/auth';
4
+ import { MessageBase, SignedPayload } from '../../../lib/types';
5
  import { logger, withLogging } from '@/lib/logger';
6
  import { CLEANED_SEPARATOR } from '@/lib/constants';
7
  import { cleanAnswerMessage, cleanInputMessage } from '@/lib/messageUtils';
8
+ import { fetcher } from '@/lib/utils';
9
 
10
  // export const runtime = 'edge';
11
  export const dynamic = 'force-dynamic';
12
  export const maxDuration = 300; // This function can run for a maximum of 5 minutes
13
 
14
+ const uploadBase64 = async (
15
+ base64: string,
16
+ messageId: string,
17
+ chatId: string,
18
+ index: number,
19
+ ) => {
20
+ const res = await fetch(
21
+ 'data:image/png;base64,' + base64.replace('base:64', ''),
22
+ );
23
+ const blob = await res.blob();
24
+ const { signedUrl, publicUrl, fields } = await fetcher<SignedPayload>(
25
+ '/api/sign',
26
+ {
27
+ method: 'POST',
28
+ body: JSON.stringify({
29
+ id: `${chatId}/${messageId}`,
30
+ fileType: blob.type,
31
+ fileName: `answer-${index}.${blob.type.split('/')[1]}`,
32
+ }),
33
+ },
34
+ );
35
+ const formData = new FormData();
36
+ Object.entries(fields).forEach(([key, value]) => {
37
+ formData.append(key, value as string);
38
+ });
39
+ formData.append('file', blob);
40
+
41
+ const uploadResponse = await fetch(signedUrl, {
42
+ method: 'POST',
43
+ body: formData,
44
+ });
45
+ if (uploadResponse.ok) {
46
+ return publicUrl;
47
+ } else {
48
+ throw new Error('Upload failed');
49
+ }
50
+ };
51
+
52
  export const POST = withLogging(
53
  async (
54
  session,
 
149
  if (fetchResponse.body) {
150
  const encoder = new TextEncoder();
151
  const decoder = new TextDecoder('utf-8');
 
152
  let maxChunkSize = 0;
153
  const stream = fetchResponse.body.pipeThrough(
154
  new TransformStream({
 
166
  transform: async (chunk, controller) => {
167
  const data = decoder.decode(chunk, { stream: true });
168
  maxChunkSize = Math.max(data.length, maxChunkSize);
169
+ const lines = data.split('\n');
170
+ const results = [];
171
+ for (let line of lines) {
172
+ if (!line.trim()) {
173
+ continue;
174
+ }
175
+ try {
176
+ const msg = JSON.parse(line.trim());
177
+ if (msg.type !== 'final_code') {
178
+ results.push(line);
179
+ continue;
180
+ }
181
+ const result = JSON.parse(
182
+ msg.payload.result,
183
+ ) as PrismaJson.FinalChatResult['payload']['result'];
184
+ for (let index = 0; index < result.results.length; index++) {
185
+ const png = result.results[index].png;
186
+ if (!png) continue;
187
+ const resp = await uploadBase64(
188
+ png,
189
+ messages[messages.length - 1].id,
190
+ json.id,
191
+ index,
192
+ );
193
+ result.results[index].png = resp;
194
+ }
195
+ msg.payload.result = JSON.stringify(result);
196
+ results.push(JSON.stringify(msg));
197
+ } catch (e) {
198
+ console.error(e);
199
+ logger.error(
200
+ session,
201
+ {
202
+ message: (e as Error).message,
203
+ },
204
+ request,
205
+ );
206
+ }
207
+ }
208
+ controller.enqueue(
209
+ encoder.encode(
210
+ results.length === 0 ? '' : results.join('\n') + '\n',
211
+ ),
212
+ );
213
  },
214
  }),
215
  );
app/page.tsx CHANGED
@@ -56,6 +56,7 @@ export default function Page() {
56
  (mediaUrl
57
  ? '\n\n' + generateInputImageMarkdown(mediaUrl)
58
  : ''),
 
59
  },
60
  ],
61
  });
 
56
  (mediaUrl
57
  ? '\n\n' + generateInputImageMarkdown(mediaUrl)
58
  : ''),
59
+ result: null,
60
  },
61
  ],
62
  });
components/chat/ChatMessage.tsx CHANGED
@@ -10,9 +10,9 @@ import { cn } from '@/lib/utils';
10
  import { CodeBlock } from '@/components/ui/CodeBlock';
11
  import { MemoizedReactMarkdown } from '@/components/chat/MemoizedReactMarkdown';
12
  import { IconLandingAI, IconUser } from '@/components/ui/Icons';
13
- import { MessageBase } from '../../lib/types';
14
  import Img from '../ui/Img';
15
- import { getCleanedUpMessages } from '@/lib/messageUtils';
16
  import Loading from '../ui/Loading';
17
  import {
18
  Table,
@@ -23,9 +23,11 @@ import {
23
  } from '../ui/Table';
24
  import { Button } from '../ui/Button';
25
  import { Dialog, DialogContent } from '../ui/Dialog';
 
 
26
 
27
  export interface ChatMessageProps {
28
- message: MessageBase;
29
  isLoading: boolean;
30
  }
31
 
@@ -140,9 +142,9 @@ const Markdown: React.FC<{
140
 
141
  export function ChatMessage({ message, isLoading }: ChatMessageProps) {
142
  const { content } = useMemo(() => {
143
- return getCleanedUpMessages({
144
  content: message.content,
145
- role: message.role,
146
  });
147
  }, [message.content, message.role]);
148
  const [details, setDetails] = useState<string>('');
 
10
  import { CodeBlock } from '@/components/ui/CodeBlock';
11
  import { MemoizedReactMarkdown } from '@/components/chat/MemoizedReactMarkdown';
12
  import { IconLandingAI, IconUser } from '@/components/ui/Icons';
13
+ import type { ChatMessage } from '@/lib/db/types';
14
  import Img from '../ui/Img';
15
+ import { getFormattedMessage } from '@/lib/messageUtils';
16
  import Loading from '../ui/Loading';
17
  import {
18
  Table,
 
23
  } from '../ui/Table';
24
  import { Button } from '../ui/Button';
25
  import { Dialog, DialogContent } from '../ui/Dialog';
26
+ import { Message } from 'ai';
27
+ import { MessageRole } from '@prisma/client';
28
 
29
  export interface ChatMessageProps {
30
+ message: Message;
31
  isLoading: boolean;
32
  }
33
 
 
142
 
143
  export function ChatMessage({ message, isLoading }: ChatMessageProps) {
144
  const { content } = useMemo(() => {
145
+ return getFormattedMessage({
146
  content: message.content,
147
+ role: message.role as MessageRole,
148
  });
149
  }, [message.content, message.role]);
150
  const [details, setDetails] = useState<string>('');
lib/db/functions.ts CHANGED
@@ -119,6 +119,7 @@ export async function dbPostCreateChat({
119
  create: initMessages.map(message => ({
120
  ...message,
121
  ...userConnect,
 
122
  })),
123
  },
124
  },
 
119
  create: initMessages.map(message => ({
120
  ...message,
121
  ...userConnect,
122
+ result: undefined,
123
  })),
124
  },
125
  },
lib/db/prisma.ts CHANGED
@@ -2,6 +2,28 @@ import { PrismaClient } from '@prisma/client';
2
 
3
  declare global {
4
  var prisma: PrismaClient | undefined;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  }
6
 
7
  const db = globalThis.prisma || new PrismaClient();
 
2
 
3
  declare global {
4
  var prisma: PrismaClient | undefined;
5
+ namespace PrismaJson {
6
+ // you can use classes, interfaces, types, etc.
7
+ type FinalChatResult = {
8
+ type: 'final_code';
9
+ status: 'completed' | 'failed';
10
+ payload: {
11
+ code: string;
12
+ test: string;
13
+ result: {
14
+ logs: {
15
+ stderr: string[];
16
+ stdout: string[];
17
+ };
18
+ results: Array<{
19
+ png?: string;
20
+ text: string;
21
+ is_main_result: boolean;
22
+ }>;
23
+ };
24
+ };
25
+ };
26
+ }
27
  }
28
 
29
  const db = globalThis.prisma || new PrismaClient();
lib/db/types.ts CHANGED
@@ -1,8 +1,10 @@
1
  import { Chat, Message } from '@prisma/client';
2
 
3
  export type ChatWithMessages = Chat & { messages: Message[] };
 
4
 
5
  export type MessageRaw = {
6
  role: Message['role'];
 
7
  content: Message['content'];
8
  };
 
1
  import { Chat, Message } from '@prisma/client';
2
 
3
  export type ChatWithMessages = Chat & { messages: Message[] };
4
+ export type ChatMessage = Message;
5
 
6
  export type MessageRaw = {
7
  role: Message['role'];
8
+ result: Message['result'];
9
  content: Message['content'];
10
  };
lib/hooks/useVisionAgent.ts CHANGED
@@ -1,55 +1,13 @@
1
- import { useChat, type Message, UseChatHelpers } from 'ai/react';
2
  import { toast } from 'react-hot-toast';
3
- import { useEffect, useState } from 'react';
4
- import { MessageBase, SignedPayload } from '../types';
5
- import { fetcher, nanoid } from '../utils';
6
- import {
7
- getCleanedUpMessages,
8
- generateAnswersImageMarkdown,
9
- generateInputImageMarkdown,
10
- } from '../messageUtils';
11
- import { CLEANED_SEPARATOR } from '../constants';
12
  import { useSearchParams } from 'next/navigation';
13
- import { ChatWithMessages, MessageRaw } from '../db/types';
14
  import { dbPostCreateMessage } from '../db/functions';
15
-
16
- const uploadBase64 = async (
17
- base64: string,
18
- messageId: string,
19
- chatId: string,
20
- index: number,
21
- ) => {
22
- const res = await fetch(
23
- 'data:image/png;base64,' + base64.replace('base:64', ''),
24
- );
25
- const blob = await res.blob();
26
- const { signedUrl, publicUrl, fields } = await fetcher<SignedPayload>(
27
- '/api/sign',
28
- {
29
- method: 'POST',
30
- body: JSON.stringify({
31
- id: `${chatId}/${messageId}`,
32
- fileType: blob.type,
33
- fileName: `answer-${index}.${blob.type.split('/')[1]}`,
34
- }),
35
- },
36
- );
37
- const formData = new FormData();
38
- Object.entries(fields).forEach(([key, value]) => {
39
- formData.append(key, value as string);
40
- });
41
- formData.append('file', blob);
42
-
43
- const uploadResponse = await fetch(signedUrl, {
44
- method: 'POST',
45
- body: formData,
46
- });
47
- if (uploadResponse.ok) {
48
- return publicUrl;
49
- } else {
50
- throw new Error('Upload failed');
51
- }
52
- };
53
 
54
  const useVisionAgent = (chat: ChatWithMessages) => {
55
  const { messages: initialMessages, id, mediaUrl } = chat;
@@ -69,16 +27,16 @@ const useVisionAgent = (chat: ChatWithMessages) => {
69
  }
70
  },
71
  onFinish: async message => {
72
- await dbPostCreateMessage(id, {
73
- role: message.role as 'user' | 'assistant',
74
- content: message.content,
75
- });
76
  },
77
- initialMessages: initialMessages,
78
  body: {
79
  mediaUrl,
80
  id,
81
  },
 
 
 
82
  });
83
 
84
  /**
@@ -94,12 +52,13 @@ const useVisionAgent = (chat: ChatWithMessages) => {
94
  dbPostCreateMessage(id, {
95
  role: message.role as 'user' | 'assistant',
96
  content: message.content,
 
97
  });
98
  return appendRaw(message);
99
  };
100
 
101
  return {
102
- messages: messages as MessageBase[],
103
  append,
104
  reload,
105
  isLoading,
 
1
+ import { useChat, UseChatHelpers } from 'ai/react';
2
  import { toast } from 'react-hot-toast';
3
+ import { useEffect } from 'react';
 
 
 
 
 
 
 
 
4
  import { useSearchParams } from 'next/navigation';
5
+ import { ChatWithMessages } from '../db/types';
6
  import { dbPostCreateMessage } from '../db/functions';
7
+ import {
8
+ convertDbMessageToMessage,
9
+ convertMessageToDbMessage,
10
+ } from '../messageUtils';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  const useVisionAgent = (chat: ChatWithMessages) => {
13
  const { messages: initialMessages, id, mediaUrl } = chat;
 
27
  }
28
  },
29
  onFinish: async message => {
30
+ await dbPostCreateMessage(id, convertMessageToDbMessage(message));
 
 
 
31
  },
32
+ initialMessages: initialMessages.map(convertDbMessageToMessage),
33
  body: {
34
  mediaUrl,
35
  id,
36
  },
37
+ onError: err => {
38
+ err && toast.error(err.message);
39
+ },
40
  });
41
 
42
  /**
 
52
  dbPostCreateMessage(id, {
53
  role: message.role as 'user' | 'assistant',
54
  content: message.content,
55
+ result: null,
56
  });
57
  return appendRaw(message);
58
  };
59
 
60
  return {
61
+ messages,
62
  append,
63
  reload,
64
  isLoading,
lib/messageUtils.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { MessageBase } from './types';
2
- import { CLEANED_SEPARATOR } from './constants';
3
 
4
  const PAIRS: Record<string, string> = {
5
  '┍': '┑',
@@ -89,31 +89,30 @@ const generateCodeExecutionMarkdown = (
89
  };
90
 
91
  const generateFinalCodeMarkdown = (
92
- message: string,
93
- payload: FinalCodeBody['payload'],
 
94
  ) => {
95
- message += 'Final Code: \n';
96
- message += `\`\`\`python\n${payload.code}\n\`\`\`\n`;
97
  message += 'Final test: \n';
98
- message += `\`\`\`python\n${payload.test}\n\`\`\`\n`;
99
  message += `Final result: \n`;
100
- message += `\`\`\`\n${payload.result}\n\`\`\`\n`;
101
- return message;
102
- };
103
-
104
- const generateFinalResultMarkdown = (
105
- message: string,
106
- payload: {
107
- success: boolean;
108
- reach_max_retries: boolean;
109
- },
110
- ) => {
111
- if (payload.reach_max_retries) {
112
- message += 'Reach max debug retries!\n';
113
- } else if (payload.success) {
114
- message += 'Success!';
115
  }
116
- message += '\n';
117
  return message;
118
  };
119
 
@@ -162,26 +161,6 @@ type CodeBody =
162
  };
163
  };
164
 
165
- type FinalCodeBody = {
166
- type: 'final_code';
167
- status: 'completed' | 'failed';
168
- payload: {
169
- code: string;
170
- test: string;
171
- result: {
172
- logs: {
173
- stderr: string[];
174
- stdout: string[];
175
- };
176
- results: Array<{
177
- png: string;
178
- text: string;
179
- is_main_result: boolean;
180
- }>;
181
- };
182
- };
183
- };
184
-
185
  // this will return if self_reflection flag is true
186
  type ReflectionBody =
187
  | {
@@ -199,7 +178,7 @@ type MessageBody =
199
  | ToolsBody
200
  | CodeBody
201
  | ReflectionBody
202
- | FinalCodeBody;
203
 
204
  const getMessageTitle = (json: MessageBody) => {
205
  switch (json.type) {
@@ -258,16 +237,16 @@ const parseLine = (json: MessageBody) => {
258
  case 'self_reflection':
259
  return generateJSONArrayMarkdown(title, [json.payload]);
260
  case 'final_code':
261
- return generateFinalCodeMarkdown(title, json.payload);
262
  default:
263
  throw 'Not supported type';
264
  }
265
  };
266
 
267
- export const getCleanedUpMessages = ({
268
  content,
269
  role,
270
- }: Pick<MessageBase, 'role' | 'content'>) => {
271
  if (role === 'user') {
272
  return {
273
  content,
@@ -275,14 +254,25 @@ export const getCleanedUpMessages = ({
275
  }
276
  const lines = content.split('\n');
277
  let formattedLogs = '';
 
278
  const jsons: MessageBody[] = [];
279
  for (let line of lines) {
 
280
  if (!line.trim()) {
281
  continue;
282
  }
283
  try {
284
  const json = JSON.parse(line) as MessageBody;
285
- if (
 
 
 
 
 
 
 
 
 
286
  jsons.length > 0 &&
287
  json.type === jsons[jsons.length - 1].type &&
288
  json.status !== 'started'
@@ -298,6 +288,40 @@ export const getCleanedUpMessages = ({
298
  }
299
  jsons.forEach(json => (formattedLogs += parseLine(json)));
300
  return {
301
- content: formattedLogs,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  };
303
  };
 
1
+ import type { ChatMessage } from '@/lib/db/types';
2
+ import { Message } from 'ai';
3
 
4
  const PAIRS: Record<string, string> = {
5
  '┍': '┑',
 
89
  };
90
 
91
  const generateFinalCodeMarkdown = (
92
+ code: string,
93
+ test: string,
94
+ result: PrismaJson.FinalChatResult['payload']['result'],
95
  ) => {
96
+ let message = 'Final Code: \n';
97
+ message += `\`\`\`python\n${code}\n\`\`\`\n`;
98
  message += 'Final test: \n';
99
+ message += `\`\`\`python\n${test}\n\`\`\`\n`;
100
  message += `Final result: \n`;
101
+ const images = result.results.map(result => result.png).filter(png => !!png);
102
+ if (images.length > 0) {
103
+ message += `Visualization output:\n`;
104
+ images.forEach((image, index) => {
105
+ message += generateAnswersImageMarkdown(index, image!);
106
+ });
107
+ }
108
+ if (result.logs.stderr.length > 0) {
109
+ message += `Error output:\n`;
110
+ message += `\`\`\`\n${result.logs.stderr.join('\n')}\n\`\`\`\n`;
111
+ }
112
+ if (result.logs.stdout.length > 0) {
113
+ message += `Output:\n`;
114
+ message += `\`\`\`\n${result.logs.stdout.join('\n')}\n\`\`\`\n`;
 
115
  }
 
116
  return message;
117
  };
118
 
 
161
  };
162
  };
163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  // this will return if self_reflection flag is true
165
  type ReflectionBody =
166
  | {
 
178
  | ToolsBody
179
  | CodeBody
180
  | ReflectionBody
181
+ | PrismaJson.FinalChatResult;
182
 
183
  const getMessageTitle = (json: MessageBody) => {
184
  switch (json.type) {
 
237
  case 'self_reflection':
238
  return generateJSONArrayMarkdown(title, [json.payload]);
239
  case 'final_code':
240
+ return '';
241
  default:
242
  throw 'Not supported type';
243
  }
244
  };
245
 
246
+ export const getFormattedMessage = ({
247
  content,
248
  role,
249
+ }: Pick<ChatMessage, 'role' | 'content'>) => {
250
  if (role === 'user') {
251
  return {
252
  content,
 
254
  }
255
  const lines = content.split('\n');
256
  let formattedLogs = '';
257
+ let finalResult = null;
258
  const jsons: MessageBody[] = [];
259
  for (let line of lines) {
260
+ console.log(line);
261
  if (!line.trim()) {
262
  continue;
263
  }
264
  try {
265
  const json = JSON.parse(line) as MessageBody;
266
+ if (json.type === 'final_code') {
267
+ const result = JSON.parse(
268
+ json.payload.result as unknown as string,
269
+ ) as PrismaJson.FinalChatResult['payload']['result'];
270
+ finalResult = generateFinalCodeMarkdown(
271
+ json.payload.code,
272
+ json.payload.test,
273
+ result,
274
+ );
275
+ } else if (
276
  jsons.length > 0 &&
277
  json.type === jsons[jsons.length - 1].type &&
278
  json.status !== 'started'
 
288
  }
289
  jsons.forEach(json => (formattedLogs += parseLine(json)));
290
  return {
291
+ content: formattedLogs + (finalResult ?? ''),
292
+ };
293
+ };
294
+
295
+ export const convertDbMessageToMessage = (message: ChatMessage): Message => {
296
+ return {
297
+ id: message.id,
298
+ role: message.role,
299
+ createdAt: message.createdAt,
300
+ content: message.result
301
+ ? message.content + '\n' + JSON.stringify(message.result)
302
+ : message.content,
303
+ };
304
+ };
305
+
306
+ export const convertMessageToDbMessage = (
307
+ message: Message,
308
+ ): Pick<ChatMessage, 'content' | 'result' | 'role'> => {
309
+ let result = null;
310
+ const lines = message.content.split('\n');
311
+ for (let line of lines) {
312
+ try {
313
+ const json = JSON.parse(line) as MessageBody;
314
+ if (json.type == 'final_code') {
315
+ result = json as PrismaJson.FinalChatResult;
316
+ break;
317
+ }
318
+ } catch (e) {
319
+ console.error((e as Error).message);
320
+ }
321
+ }
322
+ return {
323
+ role: message.role as ChatMessage['role'],
324
+ content: message.content,
325
+ result,
326
  };
327
  };
package.json CHANGED
@@ -46,6 +46,7 @@
46
  "pino": "^9.0.0",
47
  "pino-loki": "^2.2.1",
48
  "prisma": "^5.14.0",
 
49
  "react": "^18.2.0",
50
  "react-dom": "^18.2.0",
51
  "react-dropzone": "^14.2.3",
 
46
  "pino": "^9.0.0",
47
  "pino-loki": "^2.2.1",
48
  "prisma": "^5.14.0",
49
+ "prisma-json-types-generator": "^3.0.4",
50
  "react": "^18.2.0",
51
  "react-dom": "^18.2.0",
52
  "react-dropzone": "^14.2.3",
pnpm-lock.yaml CHANGED
@@ -104,6 +104,9 @@ importers:
104
  prisma:
105
  specifier: ^5.14.0
106
  version: 5.14.0
 
 
 
107
  react:
108
  specifier: ^18.2.0
109
  version: 18.2.0
@@ -736,6 +739,9 @@ packages:
736
  '@prisma/debug@5.14.0':
737
  resolution: {integrity: sha512-iq56qBZuFfX3fCxoxT8gBX33lQzomBU0qIUaEj1RebsKVz1ob/BVH1XSBwwwvRVtZEV1b7Fxx2eVu34Ge/mg3w==}
738
 
 
 
 
739
  '@prisma/engines-version@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48':
740
  resolution: {integrity: sha512-ip6pNkRo1UxWv+6toxNcYvItNYaqQjXdFNGJ+Nuk2eYtRoEdoF13wxo7/jsClJFFenMPVNVqXQDV0oveXnR1cA==}
741
 
@@ -745,6 +751,9 @@ packages:
745
  '@prisma/fetch-engine@5.14.0':
746
  resolution: {integrity: sha512-VrheA9y9DMURK5vu8OJoOgQpxOhas3qF0IBHJ8G/0X44k82kc8E0w98HCn2nhnbOOMwbWsJWXfLC2/F8n5u0gQ==}
747
 
 
 
 
748
  '@prisma/get-platform@5.14.0':
749
  resolution: {integrity: sha512-/yAyBvcEjRv41ynZrhdrPtHgk47xLRRq/o5eWGcUpBJ1YrUZTYB8EoPiopnP7iQrMATK8stXQdPOoVlrzuTQZw==}
750
 
@@ -3153,6 +3162,14 @@ packages:
3153
  pretty-format@3.8.0:
3154
  resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==}
3155
 
 
 
 
 
 
 
 
 
3156
  prisma@5.14.0:
3157
  resolution: {integrity: sha512-gCNZco7y5XtjrnQYeDJTiVZmT/ncqCr5RY1/Cf8X2wgLRmyh9ayPAGBNziI4qEE4S6SxCH5omQLVo9lmURaJ/Q==}
3158
  engines: {node: '>=16.13'}
@@ -4764,6 +4781,8 @@ snapshots:
4764
 
4765
  '@prisma/debug@5.14.0': {}
4766
 
 
 
4767
  '@prisma/engines-version@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48': {}
4768
 
4769
  '@prisma/engines@5.14.0':
@@ -4779,6 +4798,10 @@ snapshots:
4779
  '@prisma/engines-version': 5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48
4780
  '@prisma/get-platform': 5.14.0
4781
 
 
 
 
 
4782
  '@prisma/get-platform@5.14.0':
4783
  dependencies:
4784
  '@prisma/debug': 5.14.0
@@ -7724,6 +7747,13 @@ snapshots:
7724
 
7725
  pretty-format@3.8.0: {}
7726
 
 
 
 
 
 
 
 
7727
  prisma@5.14.0:
7728
  dependencies:
7729
  '@prisma/engines': 5.14.0
 
104
  prisma:
105
  specifier: ^5.14.0
106
  version: 5.14.0
107
+ prisma-json-types-generator:
108
+ specifier: ^3.0.4
109
+ version: 3.0.4(prisma@5.14.0)(typescript@5.4.5)
110
  react:
111
  specifier: ^18.2.0
112
  version: 18.2.0
 
739
  '@prisma/debug@5.14.0':
740
  resolution: {integrity: sha512-iq56qBZuFfX3fCxoxT8gBX33lQzomBU0qIUaEj1RebsKVz1ob/BVH1XSBwwwvRVtZEV1b7Fxx2eVu34Ge/mg3w==}
741
 
742
+ '@prisma/debug@5.9.1':
743
+ resolution: {integrity: sha512-yAHFSFCg8KVoL0oRUno3m60GAjsUKYUDkQ+9BA2X2JfVR3kRVSJFc/GpQ2fSORi4pSHZR9orfM4UC9OVXIFFTA==}
744
+
745
  '@prisma/engines-version@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48':
746
  resolution: {integrity: sha512-ip6pNkRo1UxWv+6toxNcYvItNYaqQjXdFNGJ+Nuk2eYtRoEdoF13wxo7/jsClJFFenMPVNVqXQDV0oveXnR1cA==}
747
 
 
751
  '@prisma/fetch-engine@5.14.0':
752
  resolution: {integrity: sha512-VrheA9y9DMURK5vu8OJoOgQpxOhas3qF0IBHJ8G/0X44k82kc8E0w98HCn2nhnbOOMwbWsJWXfLC2/F8n5u0gQ==}
753
 
754
+ '@prisma/generator-helper@5.9.1':
755
+ resolution: {integrity: sha512-WMdEUPpPYxUGruRQM6e6IVTWXFjt1hHdF/m2TO7pWxhPo7/ZeoTOF9fH8JsvVSV78DYLOQkx9osjFLXZu447Kw==}
756
+
757
  '@prisma/get-platform@5.14.0':
758
  resolution: {integrity: sha512-/yAyBvcEjRv41ynZrhdrPtHgk47xLRRq/o5eWGcUpBJ1YrUZTYB8EoPiopnP7iQrMATK8stXQdPOoVlrzuTQZw==}
759
 
 
3162
  pretty-format@3.8.0:
3163
  resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==}
3164
 
3165
+ prisma-json-types-generator@3.0.4:
3166
+ resolution: {integrity: sha512-W53OpjBdGZxCsYv7MlUX69d7TPA9lEsQbDf9ddF0J93FX5EvaIRDMexdFPe0KTxiuquGvZTDJgeNXb3gIqEhJw==}
3167
+ engines: {node: '>=14.0'}
3168
+ hasBin: true
3169
+ peerDependencies:
3170
+ prisma: ^5.1
3171
+ typescript: ^5.1
3172
+
3173
  prisma@5.14.0:
3174
  resolution: {integrity: sha512-gCNZco7y5XtjrnQYeDJTiVZmT/ncqCr5RY1/Cf8X2wgLRmyh9ayPAGBNziI4qEE4S6SxCH5omQLVo9lmURaJ/Q==}
3175
  engines: {node: '>=16.13'}
 
4781
 
4782
  '@prisma/debug@5.14.0': {}
4783
 
4784
+ '@prisma/debug@5.9.1': {}
4785
+
4786
  '@prisma/engines-version@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48': {}
4787
 
4788
  '@prisma/engines@5.14.0':
 
4798
  '@prisma/engines-version': 5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48
4799
  '@prisma/get-platform': 5.14.0
4800
 
4801
+ '@prisma/generator-helper@5.9.1':
4802
+ dependencies:
4803
+ '@prisma/debug': 5.9.1
4804
+
4805
  '@prisma/get-platform@5.14.0':
4806
  dependencies:
4807
  '@prisma/debug': 5.14.0
 
7747
 
7748
  pretty-format@3.8.0: {}
7749
 
7750
+ prisma-json-types-generator@3.0.4(prisma@5.14.0)(typescript@5.4.5):
7751
+ dependencies:
7752
+ '@prisma/generator-helper': 5.9.1
7753
+ prisma: 5.14.0
7754
+ tslib: 2.6.2
7755
+ typescript: 5.4.5
7756
+
7757
  prisma@5.14.0:
7758
  dependencies:
7759
  '@prisma/engines': 5.14.0
prisma/schema.prisma CHANGED
@@ -2,6 +2,10 @@ generator client {
2
  provider = "prisma-client-js"
3
  }
4
 
 
 
 
 
5
  datasource db {
6
  provider = "postgresql"
7
  url = env("POSTGRES_PRISMA_URL")
@@ -40,6 +44,8 @@ model Message {
40
  userId String?
41
  chatId String
42
  content String
 
 
43
  role MessageRole
44
  chat Chat @relation(fields: [chatId], references: [id], onDelete: Cascade)
45
  user User? @relation(fields: [userId], references: [id])
 
2
  provider = "prisma-client-js"
3
  }
4
 
5
+ generator json {
6
+ provider = "prisma-json-types-generator"
7
+ }
8
+
9
  datasource db {
10
  provider = "postgresql"
11
  url = env("POSTGRES_PRISMA_URL")
 
44
  userId String?
45
  chatId String
46
  content String
47
+ /// [FinalChatResult]
48
+ result Json?
49
  role MessageRole
50
  chat Chat @relation(fields: [chatId], references: [id], onDelete: Cascade)
51
  user User? @relation(fields: [userId], references: [id])