fix: resolve bracket hallucination and improve tag masking in AI Assist

- Improved parseStreamingContent to be cực kỳ robust against malformed/bracket-heavy tags
- Added history transformation to send context in standard Markdown blocks instead of internal tags
- This prevents the AI from becoming 'tag-confused' and improves surgical edit reliability
- Refined partial tag hiding for smoother streaming UX
This commit is contained in:
Gemini AI
2025-12-29 02:31:28 +04:00
Unverified
parent 695bd74e73
commit 654dce29e8

View File

@@ -174,16 +174,18 @@ function parseStreamingContent(text: string, currentAgent: string) {
return fenced ? fenced[1].trim() : value.trim();
};
const agentMatch = text.match(/\[AGENT:([\w-]+)\]/);
if (agentMatch) agent = agentMatch[1];
// 1. Detect Agent (be flexible with brackets and keywords like APP/WEB/SEO)
const agentMatch = text.match(/\[+(?:AGENT|content|seo|smm|pm|code|design|web|app):([\w-]+)\]+/i);
if (agentMatch) agent = agentMatch[1].toLowerCase();
const previewMatch = text.match(/\[PREVIEW:([\w-]+):?([\w-]+)?\]([\s\S]*?)(?:\[\/PREVIEW\]|$)/);
// 2. Detect Preview (flexible brackets)
const previewMatch = text.match(/\[+PREVIEW:([\w-]+):?([\w-]+)?\]+([\s\S]*?)(?:\[\/(?:PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT)\]+|$)/i);
if (previewMatch) {
preview = {
type: previewMatch[1],
language: previewMatch[2] || "text",
data: previewMatch[3].trim(),
isStreaming: !text.includes("[/PREVIEW]")
isStreaming: !/\[\/(?:PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT)\]+/i.test(text)
};
if (preview.isStreaming) {
const isUpdate = text.toLowerCase().includes("update") || text.toLowerCase().includes("fix") || text.toLowerCase().includes("change");
@@ -191,11 +193,16 @@ function parseStreamingContent(text: string, currentAgent: string) {
}
}
// Hide tags and partial tags from display
// 3. Clean display text - hide all tag-like sequences and their partials
chatDisplay = text
.replace(/\[AGENT:[\w-]+\]/g, "")
.replace(/\[PREVIEW:[\w-]+:?[\w-]+?\][\s\S]*?(?:\[\/PREVIEW\]|$)/g, "")
.replace(/\[(AGENT|PREVIEW)?(?::[\w-]*)?$/g, "") // Hide partial tags at the end
// Hide complete tags (flexible brackets)
.replace(/\[+(?:AGENT|content|seo|smm|pm|code|design|web|app|PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT|PREV):?[\w-]*:?[\w-]*\]+/gi, "")
// Hide content inside preview block (cleanly)
.replace(/\[+PREVIEW:[\w-]+:?[\w-]+?\]+[\s\S]*?(?:\[\/(?:PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT)\]+|$)/gi, "")
// Hide closing tags
.replace(/\[\/(?:PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT)\]+/gi, "")
// Hide ANY partial tag sequence at the very end (greedy)
.replace(/\[+[^\]]*$/g, "")
.trim();
if (!preview) {
@@ -222,7 +229,7 @@ function parseStreamingContent(text: string, currentAgent: string) {
}
}
if (!preview) {
if (!preview && !text.includes("[PREVIEW")) {
const htmlDoc = text.match(/<!doctype\s+html[\s\S]*$/i) || text.match(/<html[\s\S]*$/i);
if (htmlDoc) {
preview = {
@@ -346,9 +353,22 @@ export default function AIAssist() {
let accumulated = "";
let lastParsedPreview: PreviewData | null = null;
// Format history to remove internal tags and provide clear context for surgical edits
const formattedHistory = aiAssistHistory.map(m => {
if (m.role === "assistant") {
const { chatDisplay, preview } = parseStreamingContent(m.content, m.agent || "general");
let contextContent = chatDisplay;
if (preview && preview.data) {
contextContent += `\n\n--- CURRENT ARTIFACT (Surgical Context) ---\nType: ${preview.type}\nLanguage: ${preview.language}\n\`\`\`${preview.language || preview.type}\n${preview.data}\n\`\`\``;
}
return { ...m, content: contextContent };
}
return m;
});
const response = await modelAdapter.generateAIAssistStream(
{
messages: [...aiAssistHistory, { role: "user" as const, content: finalInput, timestamp: new Date() }],
messages: [...formattedHistory, { role: "user" as const, content: finalInput, timestamp: new Date() }],
currentAgent,
onChunk: (chunk) => {
accumulated += chunk;