From 654dce29e8204f1600fc1e343588302f65f7d755 Mon Sep 17 00:00:00 2001 From: Gemini AI Date: Mon, 29 Dec 2025 02:31:28 +0400 Subject: [PATCH] fix: resolve bracket hallucination and improve tag masking in AI Assist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- components/AIAssist.tsx | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/components/AIAssist.tsx b/components/AIAssist.tsx index 338339b..e572250 100644 --- a/components/AIAssist.tsx +++ b/components/AIAssist.tsx @@ -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(/ { + 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;