File size: 3,072 Bytes
c232e44
3c8b24f
0d6f04b
 
 
 
 
 
 
f665131
 
 
0d6f04b
 
 
 
 
 
c232e44
 
 
 
 
 
3c8b24f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c232e44
 
 
 
 
 
 
 
4af6326
3c8b24f
4af6326
c232e44
 
11e3c5e
 
c232e44
11e3c5e
 
 
c232e44
 
 
 
 
11e3c5e
 
 
 
 
 
 
 
 
c232e44
 
3c8b24f
ae074fc
 
3c8b24f
ae074fc
 
3c8b24f
 
 
 
 
 
 
 
 
 
c232e44
 
 
 
3c8b24f
c232e44
 
3c8b24f
c232e44
 
3c8b24f
 
c232e44
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import toast from 'react-hot-toast';
import { ResultPayload } from '../types';
const ANSWERS_PREFIX = 'answers';

export const generateAnswersImageMarkdown = (index: number, url: string) => {
  return `![${ANSWERS_PREFIX}-${index}](${url})`;
};

export const cleanInputMessage = (content: string) => {
  return content
    .replace(/!\[input-.*?\)/g, '')
    .replace(/<video[^>]*>.*?<\/video>/g, '');
};

export const cleanAnswerMessage = (content: string) => {
  return content.replace(/!\[answers.*?\.png\)/g, '');
};

export type CodeResult = {
  code: string;
  test: string;
  result: string;
};

const WIPLogTypes = ['plans', 'tools', 'code'];
const AllLogTypes = [
  'plans',
  'tools',
  'code',
  'final_code',
  'final_error',
] as const;

export type ChunkBody = {
  type: (typeof AllLogTypes)[number];
  status: 'started' | 'completed' | 'failed' | 'running';
  timestamp?: string;
  payload:
    | Array<Record<string, string>> // PlansBody | ToolsBody
    | CodeResult // CodeBody
    | ResultPayload['error']; // ErrorBody
};

export type WIPChunkBodyGroup = ChunkBody & {
  duration?: number;
};

/**
 * Formats the stream logs and returns an array of grouped sections.
 *
 * @param content - The content of the stream logs.
 * @returns An array of grouped sections and an optional final code result.
 */
export const formatStreamLogs = (
  content: string | null | undefined,
): [WIPChunkBodyGroup[], CodeResult?, ResultPayload['error']?] => {
  if (!content) return [[], undefined];
  const streamLogs = content.split('\n').filter(log => !!log);

  const buffer = streamLogs.pop();
  const parsedStreamLogs: ChunkBody[] = [];
  try {
    streamLogs.forEach(streamLog =>
      parsedStreamLogs.push(JSON.parse(streamLog)),
    );
  } catch {
    toast.error('Error parsing stream logs');
    return [[], undefined];
  }

  if (buffer) {
    try {
      const lastLog = JSON.parse(buffer);
      parsedStreamLogs.push(lastLog);
    } catch {
      console.log(buffer);
    }
  }

  // Merge consecutive logs of the same type to the latest status
  const groupedSections = parsedStreamLogs.reduce((acc, curr) => {
    const lastGroup = acc[acc.length - 1];
    if (
      acc.length > 0 &&
      lastGroup.type === curr.type &&
      curr.status !== 'started'
    ) {
      acc[acc.length - 1] = {
        ...curr,
        // always use the timestamp of the first log
        timestamp: lastGroup?.timestamp,
        // duration is the difference between the last log and the first log
        duration:
          lastGroup?.timestamp && curr.timestamp
            ? Date.parse(curr.timestamp) - Date.parse(lastGroup.timestamp)
            : undefined,
      };
    } else {
      acc.push(curr);
    }
    return acc;
  }, [] as WIPChunkBodyGroup[]);

  return [
    groupedSections.filter(section => WIPLogTypes.includes(section.type)),
    groupedSections.find(section => section.type === 'final_code')
      ?.payload as CodeResult,
    groupedSections.find(section => section.type === 'final_error')
      ?.payload as ResultPayload['error'],
  ];
};