aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/ChatBox/AnswerParser.ts
blob: 88511419542f7bc1364388ffc85a785cfd2c9be8 (plain)
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
113
114
115
116
117
118
119
120
121
122
123
124
125
import { ASSISTANT_ROLE, AssistantMessage, Citation, CHUNK_TYPE, TEXT_TYPE, getChunkType, ProcessingInfo } from './types';
import { v4 as uuid } from 'uuid';

export class AnswerParser {
    static parse(xml: string, processingInfo: ProcessingInfo[]): AssistantMessage {
        const answerRegex = /<answer>([\s\S]*?)<\/answer>/;
        const citationsRegex = /<citations>([\s\S]*?)<\/citations>/;
        const citationRegex = /<citation index="([^"]+)" chunk_id="([^"]+)" type="([^"]+)">([\s\S]*?)<\/citation>/g;
        const followUpQuestionsRegex = /<follow_up_questions>([\s\S]*?)<\/follow_up_questions>/;
        const questionRegex = /<question>(.*?)<\/question>/g;
        const groundedTextRegex = /<grounded_text citation_index="([^"]+)">([\s\S]*?)<\/grounded_text>/g;
        const normalTextRegex = /<normal_text>([\s\S]*?)<\/normal_text>/g;
        const loopSummaryRegex = /<loop_summary>([\s\S]*?)<\/loop_summary>/;

        const answerMatch = answerRegex.exec(xml);
        const citationsMatch = citationsRegex.exec(xml);
        const followUpQuestionsMatch = followUpQuestionsRegex.exec(xml);
        const loopSummaryMatch = loopSummaryRegex.exec(xml);

        if (!answerMatch) {
            throw new Error('Invalid XML: Missing <answer> tag.');
        }

        let rawTextContent = answerMatch[1].trim();
        let content: AssistantMessage['content'] = [];
        let citations: Citation[] = [];
        let contentIndex = 0;

        // Remove citations and follow-up questions from rawTextContent
        if (citationsMatch) {
            rawTextContent = rawTextContent.replace(citationsMatch[0], '').trim();
        }
        if (followUpQuestionsMatch) {
            rawTextContent = rawTextContent.replace(followUpQuestionsMatch[0], '').trim();
        }
        if (loopSummaryMatch) {
            rawTextContent = rawTextContent.replace(loopSummaryMatch[0], '').trim();
        }

        // Parse citations
        let citationMatch;
        const citationMap = new Map<string, string>();
        if (citationsMatch) {
            const citationsContent = citationsMatch[1];
            while ((citationMatch = citationRegex.exec(citationsContent)) !== null) {
                const [_, index, chunk_id, type, direct_text] = citationMatch;
                const citation_id = uuid();
                citationMap.set(index, citation_id);
                citations.push({
                    direct_text: direct_text.trim(),
                    type: getChunkType(type),
                    chunk_id,
                    citation_id,
                });
            }
        }

        rawTextContent = rawTextContent.replace(normalTextRegex, '$1');

        // Parse text content (normal and grounded)
        let lastIndex = 0;
        let match;

        while ((match = groundedTextRegex.exec(rawTextContent)) !== null) {
            const [fullMatch, citationIndex, groundedText] = match;

            // Add normal text that is before the grounded text
            if (match.index > lastIndex) {
                const normalText = rawTextContent.slice(lastIndex, match.index).trim();
                if (normalText) {
                    content.push({
                        index: contentIndex++,
                        type: TEXT_TYPE.NORMAL,
                        text: normalText,
                        citation_ids: null,
                    });
                }
            }

            // Add grounded text
            const citation_ids = citationIndex.split(',').map(index => citationMap.get(index) || '');
            content.push({
                index: contentIndex++,
                type: TEXT_TYPE.GROUNDED,
                text: groundedText.trim(),
                citation_ids,
            });

            lastIndex = match.index + fullMatch.length;
        }

        // Add any remaining normal text after the last grounded text
        if (lastIndex < rawTextContent.length) {
            const remainingText = rawTextContent.slice(lastIndex).trim();
            if (remainingText) {
                content.push({
                    index: contentIndex++,
                    type: TEXT_TYPE.NORMAL,
                    text: remainingText,
                    citation_ids: null,
                });
            }
        }

        let followUpQuestions: string[] = [];
        if (followUpQuestionsMatch) {
            const questionsText = followUpQuestionsMatch[1];
            let questionMatch;
            while ((questionMatch = questionRegex.exec(questionsText)) !== null) {
                followUpQuestions.push(questionMatch[1].trim());
            }
        }

        const assistantResponse: AssistantMessage = {
            role: ASSISTANT_ROLE.ASSISTANT,
            content,
            follow_up_questions: followUpQuestions,
            citations,
            processing_info: processingInfo,
            loop_summary: loopSummaryMatch ? loopSummaryMatch[1].trim() : undefined,
        };

        return assistantResponse;
    }
}