vision-agent / lib /utils /content.ts
MingruiZhang's picture
feat: Final error display and step duration (#88)
3c8b24f unverified
raw
history blame
3.07 kB
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'],
];
};