Skip to content

Commit 6249fd2

Browse files
committed
use base64 to encode answer
1 parent 12c2d1c commit 6249fd2

File tree

4 files changed

+84
-20
lines changed

4 files changed

+84
-20
lines changed

components/answer-display.tsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
88
import {github} from "react-syntax-highlighter/dist/esm/styles/hljs"
99
import { Badge } from "@/components/ui/badge"
1010
import { Button } from "@/components/ui/button"
11-
import { formatCodeBlock, preprocessCodeInAnswer } from "@/lib/utils"
11+
import {decodeProcessedAnswer, formatCodeBlock, preprocessCodeInAnswer} from "@/lib/utils"
12+
import { AIAnswer } from "@/lib/types";
1213

1314
interface AnswerDisplayProps {
1415
answer: string
@@ -20,9 +21,26 @@ interface AnswerDisplayProps {
2021
onRetry?: () => void
2122
}
2223

23-
export default function AnswerDisplay({
24-
answer,
25-
language,
24+
25+
// Decode any encoded code blocks
26+
const processContent = (content: string) => {
27+
return decodeProcessedAnswer(content);
28+
}
29+
30+
// Process the answer to decode Base64 encoded blocks
31+
const getProcessedContent = (displayAnswer: string | AIAnswer, language: string) => {
32+
if (typeof displayAnswer === "string") {
33+
return processContent(displayAnswer);
34+
} else if (displayAnswer && displayAnswer.answer) {
35+
const content = displayAnswer.answer[language] || displayAnswer.answer.en || "";
36+
return processContent(content);
37+
}
38+
return "";
39+
}
40+
41+
export default function AnswerDisplay({
42+
answer,
43+
language,
2644
isStreaming = false,
2745
parsedAnswer,
2846
onClose,
@@ -51,8 +69,11 @@ export default function AnswerDisplay({
5169
)
5270
}
5371

72+
73+
5474
// Use parsed answer if available, otherwise fall back to the original answer
55-
const displayAnswer = parsedAnswer ? preprocessCodeInAnswer(parsedAnswer) : answer
75+
let displayAnswer = parsedAnswer ? preprocessCodeInAnswer(parsedAnswer) : answer
76+
5677

5778
// If we're getting string data but couldn't parse it
5879
if (typeof displayAnswer === "string") {
@@ -93,13 +114,13 @@ export default function AnswerDisplay({
93114
code({node, className, children, ...props}: any) {
94115
const match = /language-(\w+)/.exec(className || '')
95116
const inline = !match
96-
117+
97118
if (!inline) {
98-
// Format code using our utility function
119+
// Format code using our utility function
99120
const { code, language: detectedLanguage } = formatCodeBlock(String(children));
100121
// Use either the detected language or the one from className
101122
const langToUse = match ? match[1] : detectedLanguage;
102-
123+
103124
return (
104125
<SyntaxHighlighter
105126
style={github}
@@ -130,8 +151,7 @@ export default function AnswerDisplay({
130151
)
131152
}
132153

133-
const answerContent = typeof displayAnswer === "string" ?
134-
displayAnswer : displayAnswer.answer?.[language] || displayAnswer.answer?.en || displayAnswer
154+
const answerContent = getProcessedContent(displayAnswer, language);
135155

136156
return (
137157
<Card className="mb-6 shadow-sm border border-gray-200 dark:border-gray-800 bg-white dark:bg-[#2c2c2e] rounded-xl">
@@ -166,13 +186,13 @@ export default function AnswerDisplay({
166186
code({node, className, children, ...props}: any) {
167187
const match = /language-(\w+)/.exec(className || '')
168188
const inline = !match
169-
189+
170190
if (!inline) {
171-
// Format code using our utility function
191+
// Format code using our utility function
172192
const { code, language: detectedLanguage } = formatCodeBlock(String(children));
173193
// Use either the detected language or the one from className
174194
const langToUse = match ? match[1] : detectedLanguage;
175-
195+
176196
return (
177197
<SyntaxHighlighter
178198
style={github}

lib/api.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client"
22

3-
import { cleanupJsonResponse } from "./utils"
3+
import {cleanupJsonResponse, encodeCodeBlocks, processAnswerWithRegexImproved} from "./utils"
44

55
// Default server-side settings (will be loaded from environment variables on the server)
66
const DEFAULT_OPENAI_MODEL = "gpt-4"
@@ -226,21 +226,21 @@ export async function getModelAnswer(question: any, language: string, onStream?:
226226
// If we have a streaming handler, use it
227227
if (onStream) {
228228
finalResult = await callOpenAI(prompt, systemPrompt, (chunk) => {
229-
// 清理流式数据中的 markdown 代码块
230-
const cleanedChunk = cleanupJsonResponse(chunk);
231-
onStream(cleanedChunk);
229+
onStream(chunk);
232230
}, "modelAnswer")
233231
} else {
234232
finalResult = await callOpenAI(prompt, systemPrompt, undefined, "modelAnswer")
233+
// Encode code blocks in the complete response
234+
finalResult = processAnswerWithRegexImproved(finalResult);
235235
}
236236

237-
238-
const cleanedResult = cleanupJsonResponse(finalResult);
237+
let cleanedResult;
239238
try {
239+
cleanedResult = processAnswerWithRegexImproved(finalResult)
240240
return JSON.parse(cleanedResult);
241241
} catch (error) {
242242
console.warn("Could not parse model answer as JSON, returning raw text");
243-
return { answer: { en: cleanedResult, zh: cleanedResult } };
243+
return { answer: { en: finalResult, zh: finalResult } };
244244
}
245245
} catch (error) {
246246
console.error("Error getting model answer:", error)

lib/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ export interface UserAnswer {
5151
content: string
5252
}
5353

54+
export interface AIAnswer{
55+
answer: {
56+
[key: string]: string
57+
}
58+
}
59+
5460
export interface QuestionHistory {
5561
id: string
5662
title: string

lib/utils.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,44 @@ export function cn(...inputs: ClassValue[]) {
55
return twMerge(clsx(inputs))
66
}
77

8+
9+
// 改进版本,处理转义字符
10+
export function processAnswerWithRegexImproved(jsonData: string): string {
11+
const regex = /("en"|"zh"):\s*"((?:[^"\\]|\\.)*)"/g;
12+
13+
return jsonData.replace(regex, (match, key, value) => {
14+
const unescapedValue = value.replace(/\\(.)/g, "$1");
15+
// 使用 encodeURIComponent 和 btoa 进行 Base64 编码
16+
const encodedValue = btoa(encodeURIComponent(unescapedValue));
17+
18+
// 3. 重新添加转义字符(如果需要)- 通常不需要,因为 Base64 编码后的字符串通常不需要转义
19+
// const reescapedValue = encodedValue.replace(/([\\"])/g, '\\$1'); // 如果确实需要
20+
return `${key}: "${encodedValue}"`; // 通常返回这个就足够了
21+
});
22+
}
23+
24+
export function decodeProcessedAnswer(jsonData: string): string {
25+
const regex = /("en"|"zh"):\s*"((?:[^"\\]|\\.)*)"/g;
26+
27+
return jsonData.replace(regex, (match, key, value) => {
28+
try {
29+
// 使用 atob 和 decodeURIComponent 进行 Base64 解码
30+
const decodedValue = decodeURIComponent(atob(value));
31+
return `${key}: "${decodedValue}"`;
32+
33+
} catch (error) {
34+
console.error("Base64 decoding failed:", error);
35+
return match;
36+
}
37+
});
38+
}
39+
40+
41+
// Encode code blocks to Base64
42+
export function encodeCodeBlocks(text: string): string {
43+
return processAnswerWithRegexImproved(text)
44+
}
45+
846
export function cleanupJsonResponse(text: string): string {
947
// Step 1: Remove any outer code blocks like ```json, ```js, etc.
1048
const cleaned = text.replace(/^```(?:json|javascript|js)?\s*|\s*```$/gm, '').trim();

0 commit comments

Comments
 (0)