wuyiqunLu commited on
Commit
11e3c5e
1 Parent(s): c232e44

fix: parse stream log and add result display (#69)

Browse files

<img width="896" alt="image"
src="https://github.com/landing-ai/vision-agent-ui/assets/132986242/4db20e09-adca-4c48-9452-35f6e1b8deca">

components/chat/ChatMessage.tsx CHANGED
@@ -18,6 +18,8 @@ import {
18
  IconListUnordered,
19
  IconTerminalWindow,
20
  IconUser,
 
 
21
  } from '@/components/ui/Icons';
22
  import { MessageBase } from '../../lib/types';
23
  import Img from '../ui/Img';
@@ -203,9 +205,28 @@ const ChunkPayloadAction: React.FC<{
203
  <TableBody>
204
  {payload.map((line, index) => (
205
  <TableRow className="border-primary/50" key={index}>
206
- {keyArray.map(header => (
207
- <TableCell key={header}>{line[header]}</TableCell>
208
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  </TableRow>
210
  ))}
211
  </TableBody>
@@ -233,27 +254,65 @@ const CodeResultDisplay: React.FC<{
233
  codeResult: CodeResult;
234
  }> = ({ codeResult }) => {
235
  const { code, test, result } = codeResult;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  return (
237
  <div className="rounded-lg overflow-hidden relative max-w-5xl">
238
  <CodeBlock language="python" value={code} />
239
  <div className="rounded-lg relative">
240
  <Separator />
241
- <Tooltip>
242
- <TooltipTrigger asChild>
243
- <Button
244
- variant="ghost"
245
- size="icon"
246
- className="size-8 absolute left-1/2 -translate-x-1/2 -top-4 z-10"
247
- >
248
- <IconTerminalWindow className="text-teal-500 size-4" />
249
- </Button>
250
- </TooltipTrigger>
251
- <TooltipContent>
252
- <CodeBlock language="python" value={test} />
253
- </TooltipContent>
254
- </Tooltip>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  </div>
256
- <CodeBlock language="output" value={result} />
257
  </div>
258
  );
259
  };
 
18
  IconListUnordered,
19
  IconTerminalWindow,
20
  IconUser,
21
+ IconOutput,
22
+ IconLog,
23
  } from '@/components/ui/Icons';
24
  import { MessageBase } from '../../lib/types';
25
  import Img from '../ui/Img';
 
205
  <TableBody>
206
  {payload.map((line, index) => (
207
  <TableRow className="border-primary/50" key={index}>
208
+ {keyArray.map(header =>
209
+ header === 'documentation' ? (
210
+ <TableCell key={header}>
211
+ <Tooltip>
212
+ <TooltipTrigger asChild>
213
+ <Button
214
+ variant="ghost"
215
+ size="icon"
216
+ className="size-8 ml-[40%]"
217
+ >
218
+ <IconTerminalWindow className="text-teal-500 size-4" />
219
+ </Button>
220
+ </TooltipTrigger>
221
+ <TooltipContent>
222
+ <CodeBlock language="md" value={line[header]} />
223
+ </TooltipContent>
224
+ </Tooltip>
225
+ </TableCell>
226
+ ) : (
227
+ <TableCell key={header}>{line[header]}</TableCell>
228
+ ),
229
+ )}
230
  </TableRow>
231
  ))}
232
  </TableBody>
 
254
  codeResult: CodeResult;
255
  }> = ({ codeResult }) => {
256
  const { code, test, result } = codeResult;
257
+ const getDetail = () => {
258
+ if (!result) return {};
259
+ try {
260
+ const detail = JSON.parse(result);
261
+ return {
262
+ results: detail.results,
263
+ stderr: detail.logs.stderr,
264
+ stdout: detail.logs.stdout,
265
+ };
266
+ } catch {
267
+ return {};
268
+ }
269
+ };
270
+
271
+ const { results, stderr, stdout } = getDetail();
272
+
273
  return (
274
  <div className="rounded-lg overflow-hidden relative max-w-5xl">
275
  <CodeBlock language="python" value={code} />
276
  <div className="rounded-lg relative">
277
  <Separator />
278
+ <div className="absolute left-1/2 -translate-x-1/2 -top-4 z-10">
279
+ <Tooltip>
280
+ <TooltipTrigger asChild>
281
+ <Button variant="ghost" size="icon" className="size-8">
282
+ <IconTerminalWindow className="text-teal-500 size-4" />
283
+ </Button>
284
+ </TooltipTrigger>
285
+ <TooltipContent>
286
+ <CodeBlock language="python" value={test} />
287
+ </TooltipContent>
288
+ </Tooltip>
289
+ {Array.isArray(stdout) && (
290
+ <Tooltip>
291
+ <TooltipTrigger asChild>
292
+ <Button variant="ghost" size="icon" className="size-8">
293
+ <IconOutput className="text-blue-500 size-4" />
294
+ </Button>
295
+ </TooltipTrigger>
296
+ <TooltipContent>
297
+ <CodeBlock language="vim" value={stdout.join('').trim()} />
298
+ </TooltipContent>
299
+ </Tooltip>
300
+ )}
301
+ {Array.isArray(stderr) && (
302
+ <Tooltip>
303
+ <TooltipTrigger asChild>
304
+ <Button variant="ghost" size="icon" className="size-8">
305
+ <IconLog className="text-gray-500 size-4" />
306
+ </Button>
307
+ </TooltipTrigger>
308
+ <TooltipContent>
309
+ <CodeBlock language="vim" value={stderr.join('').trim()} />
310
+ </TooltipContent>
311
+ </Tooltip>
312
+ )}
313
+ </div>
314
  </div>
315
+ <CodeBlock language="output" value={results} />
316
  </div>
317
  );
318
  };
components/ui/CodeBlock.tsx CHANGED
@@ -24,6 +24,7 @@ export const programmingLanguages: languageMap = {
24
  javascript: '.js',
25
  python: '.py',
26
  java: '.java',
 
27
  c: '.c',
28
  cpp: '.cpp',
29
  'c++': '.cpp',
 
24
  javascript: '.js',
25
  python: '.py',
26
  java: '.java',
27
+ vim: '.txt',
28
  c: '.c',
29
  cpp: '.cpp',
30
  'c++': '.cpp',
components/ui/Icons.tsx CHANGED
@@ -728,6 +728,47 @@ function IconListUnordered({
728
  </svg>
729
  );
730
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
731
  export {
732
  IconEdit,
733
  IconLandingAI,
@@ -768,4 +809,6 @@ export {
768
  IconTerminalWindow,
769
  IconCodeWrap,
770
  IconListUnordered,
 
 
771
  };
 
728
  </svg>
729
  );
730
  }
731
+
732
+ function IconLog({ className, ...props }: React.ComponentProps<'svg'>) {
733
+ return (
734
+ <svg
735
+ height="16"
736
+ strokeLinejoin="round"
737
+ viewBox="0 0 16 16"
738
+ width="16"
739
+ className={cn('size-4', className)}
740
+ {...props}
741
+ >
742
+ <path
743
+ fill-rule="evenodd"
744
+ clip-rule="evenodd"
745
+ d="M3 2.5C3 2.22386 3.22386 2 3.5 2H9.08579C9.21839 2 9.34557 2.05268 9.43934 2.14645L11.8536 4.56066C11.9473 4.65443 12 4.78161 12 4.91421V12.5C12 12.7761 11.7761 13 11.5 13H3.5C3.22386 13 3 12.7761 3 12.5V2.5ZM3.5 1C2.67157 1 2 1.67157 2 2.5V12.5C2 13.3284 2.67157 14 3.5 14H11.5C12.3284 14 13 13.3284 13 12.5V4.91421C13 4.51639 12.842 4.13486 12.5607 3.85355L10.1464 1.43934C9.86514 1.15804 9.48361 1 9.08579 1H3.5ZM4.5 4C4.22386 4 4 4.22386 4 4.5C4 4.77614 4.22386 5 4.5 5H7.5C7.77614 5 8 4.77614 8 4.5C8 4.22386 7.77614 4 7.5 4H4.5ZM4.5 7C4.22386 7 4 7.22386 4 7.5C4 7.77614 4.22386 8 4.5 8H10.5C10.7761 8 11 7.77614 11 7.5C11 7.22386 10.7761 7 10.5 7H4.5ZM4.5 10C4.22386 10 4 10.2239 4 10.5C4 10.7761 4.22386 11 4.5 11H10.5C10.7761 11 11 10.7761 11 10.5C11 10.2239 10.7761 10 10.5 10H4.5Z"
746
+ fill="currentColor"
747
+ />
748
+ </svg>
749
+ );
750
+ }
751
+
752
+ function IconOutput({ className, ...props }: React.ComponentProps<'svg'>) {
753
+ return (
754
+ <svg
755
+ height="16"
756
+ strokeLinejoin="round"
757
+ viewBox="0 0 16 16"
758
+ width="16"
759
+ className={cn('size-4', className)}
760
+ {...props}
761
+ >
762
+ <path
763
+ fill-rule="evenodd"
764
+ clip-rule="evenodd"
765
+ d="M5 2V1H10V2H5ZM4.75 0C4.33579 0 4 0.335786 4 0.75V1H3.5C2.67157 1 2 1.67157 2 2.5V12.5C2 13.3284 2.67157 14 3.5 14H11.5C12.3284 14 13 13.3284 13 12.5V2.5C13 1.67157 12.3284 1 11.5 1H11V0.75C11 0.335786 10.6642 0 10.25 0H4.75ZM11 2V2.25C11 2.66421 10.6642 3 10.25 3H4.75C4.33579 3 4 2.66421 4 2.25V2H3.5C3.22386 2 3 2.22386 3 2.5V12.5C3 12.7761 3.22386 13 3.5 13H11.5C11.7761 13 12 12.7761 12 12.5V2.5C12 2.22386 11.7761 2 11.5 2H11Z"
766
+ fill="currentColor"
767
+ />
768
+ </svg>
769
+ );
770
+ }
771
+
772
  export {
773
  IconEdit,
774
  IconLandingAI,
 
809
  IconTerminalWindow,
810
  IconCodeWrap,
811
  IconListUnordered,
812
+ IconLog,
813
+ IconOutput,
814
  };
lib/messageUtils.ts CHANGED
@@ -350,14 +350,26 @@ export const formatStreamLogs = (
350
  ): [ChunkBody[], CodeResult?] => {
351
  const streamLogs = content.split('\n').filter(log => !!log);
352
 
353
- let parsedStreamLogs: ChunkBody[] = [];
 
354
  try {
355
- parsedStreamLogs = streamLogs.map(streamLog => JSON.parse(streamLog));
 
 
356
  } catch {
357
  toast.error('Error parsing stream logs');
358
  return [[], undefined];
359
  }
360
 
 
 
 
 
 
 
 
 
 
361
  // Merge consecutive logs of the same type to the latest status
362
  const groupedSections = parsedStreamLogs.reduce((acc, curr) => {
363
  if (acc.length > 0 && acc[acc.length - 1].type === curr.type) {
 
350
  ): [ChunkBody[], CodeResult?] => {
351
  const streamLogs = content.split('\n').filter(log => !!log);
352
 
353
+ const buffer = streamLogs.pop();
354
+ const parsedStreamLogs: ChunkBody[] = [];
355
  try {
356
+ streamLogs.forEach(streamLog =>
357
+ parsedStreamLogs.push(JSON.parse(streamLog)),
358
+ );
359
  } catch {
360
  toast.error('Error parsing stream logs');
361
  return [[], undefined];
362
  }
363
 
364
+ if (buffer) {
365
+ try {
366
+ const lastLog = JSON.parse(buffer);
367
+ parsedStreamLogs.push(lastLog);
368
+ } catch {
369
+ console.log(buffer);
370
+ }
371
+ }
372
+
373
  // Merge consecutive logs of the same type to the latest status
374
  const groupedSections = parsedStreamLogs.reduce((acc, curr) => {
375
  if (acc.length > 0 && acc[acc.length - 1].type === curr.type) {