MingruiZhang commited on
Commit
ba9285c
·
unverified ·
1 Parent(s): 0bb36de

feat: conversation restructure, assistant message should be just code (#89)

Browse files

![image](https://github.com/landing-ai/vision-agent-ui/assets/5669963/94e99862-a986-40d5-abb5-aab367618e67)


![image](https://github.com/landing-ai/vision-agent-ui/assets/5669963/a185e979-b0fa-4763-a3ac-d3da6a20230c)


Also fix backwards compatibly from
https://github.com/landing-ai/vision-agent-ui/pull/86

app/api/vision-agent/route.ts CHANGED
@@ -1,11 +1,9 @@
1
  import { JSONValue, StreamingTextResponse, experimental_StreamData } from 'ai';
2
 
3
  // import { auth } from '@/auth';
4
- import { MessageUI, ResultPayload, SignedPayload } from '@/lib/types';
5
 
6
  import { logger, withLogging } from '@/lib/logger';
7
- import { CLEANED_SEPARATOR } from '@/lib/constants';
8
- import { cleanAnswerMessage, cleanInputMessage } from '@/lib/utils/content';
9
  import { getPresignedUrl } from '@/lib/aws';
10
 
11
  // export const runtime = 'edge';
@@ -68,7 +66,7 @@ const modifyCodePayload = async (
68
  ) {
69
  return msg;
70
  }
71
- const result = JSON.parse(msg.payload.result) as ResultPayload;
72
  if (msg.type === 'code') {
73
  if (result && result.results) {
74
  msg.payload.result = {
@@ -106,45 +104,18 @@ export const POST = withLogging(
106
  async (
107
  session,
108
  json: {
109
- messages: MessageUI[];
110
  id: string;
111
  mediaUrl: string;
112
  },
113
  request,
114
  ) => {
115
- const { messages, mediaUrl } = json;
 
116
  const user = session?.user?.email ?? 'anonymous';
117
 
118
- // const session = await auth();
119
- // if (!session?.user?.email) {
120
- // return new Response('Unauthorized', {
121
- // status: 401,
122
- // });
123
- // }
124
-
125
  const formData = new FormData();
126
- formData.append(
127
- 'input',
128
- JSON.stringify(
129
- messages.map(message => {
130
- if (message.role !== 'assistant') {
131
- return {
132
- ...message,
133
- content: cleanInputMessage(message.content),
134
- };
135
- } else {
136
- const splitedContent = message.content.split(CLEANED_SEPARATOR);
137
- return {
138
- ...message,
139
- content:
140
- splitedContent.length > 1
141
- ? cleanAnswerMessage(splitedContent[1])
142
- : message.content,
143
- };
144
- }
145
- }),
146
- ),
147
- );
148
  formData.append('image', mediaUrl);
149
 
150
  const agentHost = process.env.LND_TIER
@@ -213,14 +184,14 @@ export const POST = withLogging(
213
  .filter(line => line.trim().length > 0);
214
  if (lines.length === 0) {
215
  if (Date.now() - time > TIMEOUT_MILI_SECONDS) {
216
- logger.info(
217
- session,
218
- {
219
- message: 'Agent timed out',
220
- },
221
- request,
222
- '__Agent_timeout__',
223
- );
224
  controller.enqueue(
225
  encoder.encode(JSON.stringify(FINAL_TIMEOUT_ERROR) + '\n'),
226
  );
 
1
  import { JSONValue, StreamingTextResponse, experimental_StreamData } from 'ai';
2
 
3
  // import { auth } from '@/auth';
4
+ import { MessageUI, SignedPayload } from '@/lib/types';
5
 
6
  import { logger, withLogging } from '@/lib/logger';
 
 
7
  import { getPresignedUrl } from '@/lib/aws';
8
 
9
  // export const runtime = 'edge';
 
66
  ) {
67
  return msg;
68
  }
69
+ const result = JSON.parse(msg.payload.result) as PrismaJson.StructuredResult;
70
  if (msg.type === 'code') {
71
  if (result && result.results) {
72
  msg.payload.result = {
 
104
  async (
105
  session,
106
  json: {
107
+ apiMessages: string;
108
  id: string;
109
  mediaUrl: string;
110
  },
111
  request,
112
  ) => {
113
+ const { apiMessages, mediaUrl } = json;
114
+ const messages: MessageUI[] = JSON.parse(apiMessages);
115
  const user = session?.user?.email ?? 'anonymous';
116
 
 
 
 
 
 
 
 
117
  const formData = new FormData();
118
+ formData.append('input', JSON.stringify(messages));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  formData.append('image', mediaUrl);
120
 
121
  const agentHost = process.env.LND_TIER
 
184
  .filter(line => line.trim().length > 0);
185
  if (lines.length === 0) {
186
  if (Date.now() - time > TIMEOUT_MILI_SECONDS) {
187
+ // logger.info(
188
+ // session,
189
+ // {
190
+ // message: 'Agent timed out',
191
+ // },
192
+ // request,
193
+ // '__Agent_timeout__',
194
+ // );
195
  controller.enqueue(
196
  encoder.encode(JSON.stringify(FINAL_TIMEOUT_ERROR) + '\n'),
197
  );
app/page.tsx CHANGED
@@ -15,7 +15,7 @@ const EXAMPLES = [
15
  mediaUrl:
16
  'https://vision-agent-dev.s3.us-east-2.amazonaws.com/examples/flower.png',
17
  prompt:
18
- 'Draw box and output the image, return the number of flowers as output.',
19
  },
20
  {
21
  title: 'Detecting sharks in video',
 
15
  mediaUrl:
16
  'https://vision-agent-dev.s3.us-east-2.amazonaws.com/examples/flower.png',
17
  prompt:
18
+ 'Detect flowers in this image, draw boxes and output the image, also return total number of flowers',
19
  },
20
  {
21
  title: 'Detecting sharks in video',
components/CodeResultDisplay.tsx CHANGED
@@ -1,6 +1,5 @@
1
  import React from 'react';
2
 
3
- import { ChunkBody, CodeResult, formatStreamLogs } from '@/lib/utils/content';
4
  import { CodeBlock } from './ui/CodeBlock';
5
  import {
6
  Dialog,
@@ -12,7 +11,6 @@ import {
12
  import { Button } from './ui/Button';
13
  import { IconLog, IconTerminalWindow } from './ui/Icons';
14
  import { Separator } from './ui/Separator';
15
- import { ResultPayload } from '@/lib/types';
16
  import Img from './ui/Img';
17
  import {
18
  Carousel,
@@ -25,13 +23,18 @@ import {
25
  export interface CodeResultDisplayProps {}
26
 
27
  const CodeResultDisplay: React.FC<{
28
- codeResult: CodeResult;
29
  }> = ({ codeResult }) => {
30
  const { code, test, result } = codeResult;
31
  const getDetail = () => {
32
  if (!result) return {};
33
  try {
34
- const detail = JSON.parse(result) as ResultPayload;
 
 
 
 
 
35
  return {
36
  results: detail.results,
37
  stderr: detail.logs.stderr,
@@ -108,7 +111,9 @@ const CodeResultDisplay: React.FC<{
108
  <p>image output</p>
109
  <Dialog>
110
  <DialogTrigger asChild>
111
- <Button variant="ghost">View all</Button>
 
 
112
  </DialogTrigger>
113
  <DialogContent className="max-w-5xl flex justify-center items-center">
114
  <Carousel className="w-3/4">
@@ -138,7 +143,7 @@ const CodeResultDisplay: React.FC<{
138
  </div>
139
  )}
140
  {!!videoResults.length && (
141
- <div className="p-4 text-xs lowercase bg-zinc-900 space-y-4">
142
  <p>video output</p>
143
  <div className="flex flex-row space-x-4 overflow-auto">
144
  {videoResults.map((mp4, index) => (
 
1
  import React from 'react';
2
 
 
3
  import { CodeBlock } from './ui/CodeBlock';
4
  import {
5
  Dialog,
 
11
  import { Button } from './ui/Button';
12
  import { IconLog, IconTerminalWindow } from './ui/Icons';
13
  import { Separator } from './ui/Separator';
 
14
  import Img from './ui/Img';
15
  import {
16
  Carousel,
 
23
  export interface CodeResultDisplayProps {}
24
 
25
  const CodeResultDisplay: React.FC<{
26
+ codeResult: PrismaJson.FinalChatResult['payload'];
27
  }> = ({ codeResult }) => {
28
  const { code, test, result } = codeResult;
29
  const getDetail = () => {
30
  if (!result) return {};
31
  try {
32
+ // IMPORTANT: This is for backwards compatibility with old chat that save result as JSON string
33
+ // updated in https://github.com/landing-ai/vision-agent-ui/pull/86
34
+ const detail =
35
+ typeof result === 'object'
36
+ ? result
37
+ : (JSON.parse(result) as PrismaJson.StructuredResult);
38
  return {
39
  results: detail.results,
40
  stderr: detail.logs.stderr,
 
111
  <p>image output</p>
112
  <Dialog>
113
  <DialogTrigger asChild>
114
+ <Button variant="outline" size="sm">
115
+ View all
116
+ </Button>
117
  </DialogTrigger>
118
  <DialogContent className="max-w-5xl flex justify-center items-center">
119
  <Carousel className="w-3/4">
 
143
  </div>
144
  )}
145
  {!!videoResults.length && (
146
+ <div className="p-4 text-xs lowercase bg-zinc-900 space-y-4 border-t border-muted">
147
  <p>video output</p>
148
  <div className="flex flex-row space-x-4 overflow-auto">
149
  {videoResults.map((mp4, index) => (
components/chat/ChatList.tsx CHANGED
@@ -30,7 +30,11 @@ const ChatList: React.FC<ChatListProps> = ({ chat, userId }) => {
30
  // Only login and chat owner can compose
31
  const canCompose = !chatUserId || userId === chatUserId;
32
 
33
- const lastMessage = messages[messages.length - 1];
 
 
 
 
34
  const lastDbMessage = dbMessages[dbMessages.length - 1];
35
  const setMessageId = useSetAtom(selectedMessageId);
36
 
@@ -67,8 +71,8 @@ const ChatList: React.FC<ChatListProps> = ({ chat, userId }) => {
67
  message={message}
68
  loading={isLastMessage && isLoading}
69
  wipAssistantMessage={
70
- lastMessage.role === 'assistant' && isLastMessage
71
- ? lastMessage
72
  : undefined
73
  }
74
  />
 
30
  // Only login and chat owner can compose
31
  const canCompose = !chatUserId || userId === chatUserId;
32
 
33
+ const lastAssistantMessage = messages.length
34
+ ? messages[messages.length - 1]?.role === 'assistant'
35
+ ? messages[messages.length - 1]?.content
36
+ : undefined
37
+ : undefined;
38
  const lastDbMessage = dbMessages[dbMessages.length - 1];
39
  const setMessageId = useSetAtom(selectedMessageId);
40
 
 
71
  message={message}
72
  loading={isLastMessage && isLoading}
73
  wipAssistantMessage={
74
+ lastAssistantMessage && isLastMessage
75
+ ? lastAssistantMessage
76
  : undefined
77
  }
78
  />
components/chat/ChatMessage.tsx CHANGED
@@ -11,11 +11,7 @@ import {
11
  IconGlowingDot,
12
  } from '@/components/ui/Icons';
13
  import { MessageUI } from '@/lib/types';
14
- import {
15
- WIPChunkBodyGroup,
16
- CodeResult,
17
- formatStreamLogs,
18
- } from '@/lib/utils/content';
19
  import {
20
  Table,
21
  TableBody,
@@ -37,7 +33,7 @@ import { cn } from '@/lib/utils';
37
  export interface ChatMessageProps {
38
  message: Message;
39
  loading?: boolean;
40
- wipAssistantMessage?: MessageUI;
41
  }
42
 
43
  export const ChatMessage: React.FC<ChatMessageProps> = ({
@@ -48,8 +44,8 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({
48
  const [messageId, setMessageId] = useAtom(selectedMessageId);
49
  const { id, mediaUrl, prompt, response, result } = message;
50
  const [formattedSections, finalResult, finalError] = useMemo(
51
- () => formatStreamLogs(response ?? wipAssistantMessage?.content),
52
- [response, wipAssistantMessage?.content],
53
  );
54
  // prioritize the result from the message over the WIP message
55
  const codeResult = result?.payload ?? finalResult;
@@ -100,10 +96,10 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({
100
  <div className="flex-1 px-1 space-y-4 ml-4 overflow-hidden">
101
  <Table className="w-[400px]">
102
  <TableBody>
103
- {formattedSections.map(section => (
104
  <TableRow
105
  className="border-primary/50 h-[56px]"
106
- key={section.type}
107
  >
108
  <TableCell className="text-center text-webkit-center">
109
  {ChunkStatusToIconDict[section.status]}
@@ -131,13 +127,11 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({
131
  )}
132
  {!codeResult && finalError && (
133
  <>
134
- <p>❌ Coding failed </p>
135
  <div>
136
  <CodeBlock
137
  language="error"
138
  value={
139
- finalError.name +
140
- '\n' +
141
  finalError.value +
142
  '\n' +
143
  finalError.traceback_raw.join('\n')
@@ -181,7 +175,7 @@ const ChunkTypeToText: React.FC<{
181
  const isExecuting = !['completed', 'failed'].includes(status);
182
 
183
  useEffect(() => {
184
- if (isExecuting && timestamp && !useTimer) {
185
  const timerId = setInterval(() => {
186
  setMSeconds(Date.now() - Date.parse(timestamp));
187
  }, 200);
@@ -272,7 +266,7 @@ const ChunkPayloadAction: React.FC<{
272
  </DialogContent>
273
  </Dialog>
274
  );
275
- } else {
276
  return (
277
  <Dialog>
278
  <DialogTrigger asChild>
@@ -281,11 +275,14 @@ const ChunkPayloadAction: React.FC<{
281
  </Button>
282
  </DialogTrigger>
283
  <DialogContent className="max-w-5xl">
284
- <CodeResultDisplay codeResult={payload as CodeResult} />
 
 
285
  </DialogContent>
286
  </Dialog>
287
  );
288
  }
 
289
  };
290
 
291
  export default ChatMessage;
 
11
  IconGlowingDot,
12
  } from '@/components/ui/Icons';
13
  import { MessageUI } from '@/lib/types';
14
+ import { WIPChunkBodyGroup, formatStreamLogs } from '@/lib/utils/content';
 
 
 
 
15
  import {
16
  Table,
17
  TableBody,
 
33
  export interface ChatMessageProps {
34
  message: Message;
35
  loading?: boolean;
36
+ wipAssistantMessage?: string;
37
  }
38
 
39
  export const ChatMessage: React.FC<ChatMessageProps> = ({
 
44
  const [messageId, setMessageId] = useAtom(selectedMessageId);
45
  const { id, mediaUrl, prompt, response, result } = message;
46
  const [formattedSections, finalResult, finalError] = useMemo(
47
+ () => formatStreamLogs(response ?? wipAssistantMessage),
48
+ [response, wipAssistantMessage],
49
  );
50
  // prioritize the result from the message over the WIP message
51
  const codeResult = result?.payload ?? finalResult;
 
96
  <div className="flex-1 px-1 space-y-4 ml-4 overflow-hidden">
97
  <Table className="w-[400px]">
98
  <TableBody>
99
+ {formattedSections.map((section, index) => (
100
  <TableRow
101
  className="border-primary/50 h-[56px]"
102
+ key={index}
103
  >
104
  <TableCell className="text-center text-webkit-center">
105
  {ChunkStatusToIconDict[section.status]}
 
127
  )}
128
  {!codeResult && finalError && (
129
  <>
130
+ <p>❌ {finalError.name}</p>
131
  <div>
132
  <CodeBlock
133
  language="error"
134
  value={
 
 
135
  finalError.value +
136
  '\n' +
137
  finalError.traceback_raw.join('\n')
 
175
  const isExecuting = !['completed', 'failed'].includes(status);
176
 
177
  useEffect(() => {
178
+ if (isExecuting && timestamp && useTimer) {
179
  const timerId = setInterval(() => {
180
  setMSeconds(Date.now() - Date.parse(timestamp));
181
  }, 200);
 
266
  </DialogContent>
267
  </Dialog>
268
  );
269
+ } else if ((payload as PrismaJson.FinalChatResult['payload']).code) {
270
  return (
271
  <Dialog>
272
  <DialogTrigger asChild>
 
275
  </Button>
276
  </DialogTrigger>
277
  <DialogContent className="max-w-5xl">
278
+ <CodeResultDisplay
279
+ codeResult={payload as PrismaJson.FinalChatResult['payload']}
280
+ />
281
  </DialogContent>
282
  </Dialog>
283
  );
284
  }
285
+ return null;
286
  };
287
 
288
  export default ChatMessage;
components/chat/TopPrompt.tsx CHANGED
@@ -13,7 +13,6 @@ export interface TopPrompt {
13
 
14
  export default async function TopPrompt({ chat, userId }: TopPrompt) {
15
  const authorId = chat.userId;
16
- console.log('[Ming] ~ TopPrompt ~ authorId:', authorId);
17
  // 1. Viewer logged in, Viewer = Author
18
  if (userId && authorId === userId) {
19
  return null;
@@ -26,15 +25,11 @@ export default async function TopPrompt({ chat, userId }: TopPrompt) {
26
  if (authorId && authorId !== userId) {
27
  const chatAuthor = authorId ? await dbGetUser(authorId) : null;
28
  return (
29
- <Card className="group py-2 px-4 flex items-center">
30
- <div className="bg-background flex size-8 shrink-0 select-none items-center justify-center rounded-md">
 
31
  <Avatar name={chatAuthor?.name} avatar={chatAuthor?.avatar} />
32
- </div>
33
- <div className="flex-1 px-1 ml-2 overflow-hidden">
34
- <p className="leading-normal">
35
- Code author:{' '}
36
- <span className="font-medium">{chatAuthor?.name ?? 'Unknown'}</span>
37
- </p>
38
  </div>
39
  </Card>
40
  );
 
13
 
14
  export default async function TopPrompt({ chat, userId }: TopPrompt) {
15
  const authorId = chat.userId;
 
16
  // 1. Viewer logged in, Viewer = Author
17
  if (userId && authorId === userId) {
18
  return null;
 
25
  if (authorId && authorId !== userId) {
26
  const chatAuthor = authorId ? await dbGetUser(authorId) : null;
27
  return (
28
+ <Card className="group py-2 px-4 flex flex-row items-center">
29
+ <p className="leading-normal text-sm">Authored by</p>
30
+ <div className="flex-1 px-1 ml-2 flex flex-row items-center space-x-2">
31
  <Avatar name={chatAuthor?.name} avatar={chatAuthor?.avatar} />
32
+ <p className="font-medium">{chatAuthor?.name ?? 'Unknown'}</p>
 
 
 
 
 
33
  </div>
34
  </Card>
35
  );
components/ui/CodeBlock.tsx CHANGED
@@ -47,7 +47,7 @@ export const programmingLanguages: languageMap = {
47
  css: '.css',
48
  // custom titles
49
  print: '.txt',
50
- error: 'vim',
51
  // add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
52
  };
53
 
 
47
  css: '.css',
48
  // custom titles
49
  print: '.txt',
50
+ error: '.txt',
51
  // add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
52
  };
53
 
components/ui/Icons.tsx CHANGED
@@ -222,7 +222,7 @@ function IconCrossCircle({ className, ...props }: React.ComponentProps<'svg'>) {
222
  return (
223
  <svg
224
  height="16"
225
- stroke-linejoin="round"
226
  viewBox="0 0 16 16"
227
  width="16"
228
  className={cn('size-4', className)}
 
222
  return (
223
  <svg
224
  height="16"
225
+ strokeLinejoin="round"
226
  viewBox="0 0 16 16"
227
  width="16"
228
  className={cn('size-4', className)}
lib/db/prisma.ts CHANGED
@@ -4,24 +4,35 @@ 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: string; // TODO To be fixed to JSON below
14
- // result: {
15
- // logs: {
16
- // stderr: string[];
17
- // stdout: string[];
18
- // };
19
- // results: Array<{
20
- // png?: string;
21
- // text: string;
22
- // is_main_result: boolean;
23
- // }>;
24
- // };
25
  };
26
  };
27
  }
 
4
  var prisma: PrismaClient | undefined;
5
  namespace PrismaJson {
6
  // you can use classes, interfaces, types, etc.
7
+
8
+ type StructuredResult = {
9
+ logs: {
10
+ stderr: string[];
11
+ stdout: string[];
12
+ };
13
+ results: Array<{
14
+ png?: string;
15
+ mp4?: string;
16
+ text: string;
17
+ is_main_result: boolean;
18
+ }>;
19
+ error: {
20
+ name: string;
21
+ value: string;
22
+ traceback_raw: string[];
23
+ };
24
+ };
25
+
26
  type FinalChatResult = {
27
  type: 'final_code';
28
  status: 'completed' | 'failed';
29
  payload: {
30
  code: string;
31
  test: string;
32
+ // Change introduces https://github.com/landing-ai/vision-agent-ui/pull/86
33
+ // 1. Backward compatibility, it could be stringified StructuredResult
34
+ // 2. result not modified in stream server, could still be stringified StructuredResult
35
+ result: string | StructuredResult;
 
 
 
 
 
 
 
 
36
  };
37
  };
38
  }
lib/hooks/useVisionAgent.ts CHANGED
@@ -2,13 +2,10 @@ import { useChat } from 'ai/react';
2
  import { toast } from 'react-hot-toast';
3
  import { useEffect, useRef, useState } from 'react';
4
  import { ChatWithMessages, MessageUI, MessageUserInput } from '../types';
5
- import {
6
- dbPostCreateMessage,
7
- dbPostUpdateMessageResponse,
8
- } from '../db/functions';
9
  import {
10
  convertAssistantUIMessageToDBMessageResponse,
11
- convertDBMessageToUIMessage,
12
  } from '../utils/message';
13
  import { useSetAtom } from 'jotai';
14
  import { selectedMessageId } from '@/state/chat';
@@ -38,10 +35,11 @@ const useVisionAgent = (chat: ChatWithMessages) => {
38
  );
39
  setMessageId(currMessageId.current);
40
  },
41
- initialMessages: convertDBMessageToUIMessage(dbMessages),
42
  body: {
43
  mediaUrl: currMediaUrl.current,
44
  id,
 
 
45
  },
46
  onError: err => {
47
  err && toast.error(err.message);
 
2
  import { toast } from 'react-hot-toast';
3
  import { useEffect, useRef, useState } from 'react';
4
  import { ChatWithMessages, MessageUI, MessageUserInput } from '../types';
5
+ import { dbPostUpdateMessageResponse } from '../db/functions';
 
 
 
6
  import {
7
  convertAssistantUIMessageToDBMessageResponse,
8
+ convertDBMessageToAPIMessage,
9
  } from '../utils/message';
10
  import { useSetAtom } from 'jotai';
11
  import { selectedMessageId } from '@/state/chat';
 
35
  );
36
  setMessageId(currMessageId.current);
37
  },
 
38
  body: {
39
  mediaUrl: currMediaUrl.current,
40
  id,
41
+ // for some reason, the messages has to be stringified to be sent to the API
42
+ apiMessages: JSON.stringify(convertDBMessageToAPIMessage(dbMessages)),
43
  },
44
  onError: err => {
45
  err && toast.error(err.message);
lib/types.ts CHANGED
@@ -16,21 +16,3 @@ export interface SignedPayload {
16
  signedUrl: string;
17
  fields: Record<string, string>;
18
  }
19
-
20
- export type ResultPayload = {
21
- logs: {
22
- stderr: string[];
23
- stdout: string[];
24
- };
25
- results: Array<{
26
- png?: string;
27
- mp4?: string;
28
- text: string;
29
- is_main_result: boolean;
30
- }>;
31
- error: {
32
- name: string;
33
- value: string;
34
- traceback_raw: string[];
35
- };
36
- };
 
16
  signedUrl: string;
17
  fields: Record<string, string>;
18
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/utils/content.ts CHANGED
@@ -1,26 +1,4 @@
1
  import toast from 'react-hot-toast';
2
- import { ResultPayload } from '../types';
3
- const ANSWERS_PREFIX = 'answers';
4
-
5
- export const generateAnswersImageMarkdown = (index: number, url: string) => {
6
- return `![${ANSWERS_PREFIX}-${index}](${url})`;
7
- };
8
-
9
- export const cleanInputMessage = (content: string) => {
10
- return content
11
- .replace(/!\[input-.*?\)/g, '')
12
- .replace(/<video[^>]*>.*?<\/video>/g, '');
13
- };
14
-
15
- export const cleanAnswerMessage = (content: string) => {
16
- return content.replace(/!\[answers.*?\.png\)/g, '');
17
- };
18
-
19
- export type CodeResult = {
20
- code: string;
21
- test: string;
22
- result: string;
23
- };
24
 
25
  const WIPLogTypes = ['plans', 'tools', 'code'];
26
  const AllLogTypes = [
@@ -37,8 +15,8 @@ export type ChunkBody = {
37
  timestamp?: string;
38
  payload:
39
  | Array<Record<string, string>> // PlansBody | ToolsBody
40
- | CodeResult // CodeBody
41
- | ResultPayload['error']; // ErrorBody
42
  };
43
 
44
  export type WIPChunkBodyGroup = ChunkBody & {
@@ -53,7 +31,11 @@ export type WIPChunkBodyGroup = ChunkBody & {
53
  */
54
  export const formatStreamLogs = (
55
  content: string | null | undefined,
56
- ): [WIPChunkBodyGroup[], CodeResult?, ResultPayload['error']?] => {
 
 
 
 
57
  if (!content) return [[], undefined];
58
  const streamLogs = content.split('\n').filter(log => !!log);
59
 
@@ -104,8 +86,8 @@ export const formatStreamLogs = (
104
  return [
105
  groupedSections.filter(section => WIPLogTypes.includes(section.type)),
106
  groupedSections.find(section => section.type === 'final_code')
107
- ?.payload as CodeResult,
108
  groupedSections.find(section => section.type === 'final_error')
109
- ?.payload as ResultPayload['error'],
110
  ];
111
  };
 
1
  import toast from 'react-hot-toast';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  const WIPLogTypes = ['plans', 'tools', 'code'];
4
  const AllLogTypes = [
 
15
  timestamp?: string;
16
  payload:
17
  | Array<Record<string, string>> // PlansBody | ToolsBody
18
+ | PrismaJson.FinalChatResult['payload'] // CodeBody & FinalCodeBody
19
+ | PrismaJson.StructuredResult['error']; // ErrorBody
20
  };
21
 
22
  export type WIPChunkBodyGroup = ChunkBody & {
 
31
  */
32
  export const formatStreamLogs = (
33
  content: string | null | undefined,
34
+ ): [
35
+ WIPChunkBodyGroup[],
36
+ PrismaJson.FinalChatResult['payload']?,
37
+ PrismaJson.StructuredResult['error']?,
38
+ ] => {
39
  if (!content) return [[], undefined];
40
  const streamLogs = content.split('\n').filter(log => !!log);
41
 
 
86
  return [
87
  groupedSections.filter(section => WIPLogTypes.includes(section.type)),
88
  groupedSections.find(section => section.type === 'final_code')
89
+ ?.payload as PrismaJson.FinalChatResult['payload'],
90
  groupedSections.find(section => section.type === 'final_error')
91
+ ?.payload as PrismaJson.StructuredResult['error'],
92
  ];
93
  };
lib/utils/message.ts CHANGED
@@ -4,13 +4,13 @@ import { ChunkBody } from './content';
4
 
5
  /**
6
  * The Message we saved to database consists of a prompt and a response
7
- * for the UI to use, we need to break them to 2 messages, User and Assistant(if responded)
8
  */
9
- export const convertDBMessageToUIMessage = (
10
  messages: Message[],
11
  ): MessageUI[] => {
12
  return messages.reduce((acc, message) => {
13
- const { id, mediaUrl, prompt, response, result } = message;
14
  if (mediaUrl && prompt) {
15
  acc.push({
16
  id: id + '-user',
@@ -18,11 +18,11 @@ export const convertDBMessageToUIMessage = (
18
  content: prompt,
19
  });
20
  }
21
- if (response) {
22
  acc.push({
23
  id: id + '-assistant',
24
  role: 'assistant',
25
- content: response,
26
  });
27
  }
28
  return acc;
 
4
 
5
  /**
6
  * The Message we saved to database consists of a prompt and a response
7
+ * for the API to use, we need to break them to 2 messages, User and Assistant(if responded)
8
  */
9
+ export const convertDBMessageToAPIMessage = (
10
  messages: Message[],
11
  ): MessageUI[] => {
12
  return messages.reduce((acc, message) => {
13
+ const { id, mediaUrl, prompt, result } = message;
14
  if (mediaUrl && prompt) {
15
  acc.push({
16
  id: id + '-user',
 
18
  content: prompt,
19
  });
20
  }
21
+ if (result) {
22
  acc.push({
23
  id: id + '-assistant',
24
  role: 'assistant',
25
+ content: result?.payload.code,
26
  });
27
  }
28
  return acc;