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:
@@ -174,16 +174,18 @@ function parseStreamingContent(text: string, currentAgent: string) {
|
|||||||
return fenced ? fenced[1].trim() : value.trim();
|
return fenced ? fenced[1].trim() : value.trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
const agentMatch = text.match(/\[AGENT:([\w-]+)\]/);
|
// 1. Detect Agent (be flexible with brackets and keywords like APP/WEB/SEO)
|
||||||
if (agentMatch) agent = agentMatch[1];
|
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) {
|
if (previewMatch) {
|
||||||
preview = {
|
preview = {
|
||||||
type: previewMatch[1],
|
type: previewMatch[1],
|
||||||
language: previewMatch[2] || "text",
|
language: previewMatch[2] || "text",
|
||||||
data: previewMatch[3].trim(),
|
data: previewMatch[3].trim(),
|
||||||
isStreaming: !text.includes("[/PREVIEW]")
|
isStreaming: !/\[\/(?:PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT)\]+/i.test(text)
|
||||||
};
|
};
|
||||||
if (preview.isStreaming) {
|
if (preview.isStreaming) {
|
||||||
const isUpdate = text.toLowerCase().includes("update") || text.toLowerCase().includes("fix") || text.toLowerCase().includes("change");
|
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
|
chatDisplay = text
|
||||||
.replace(/\[AGENT:[\w-]+\]/g, "")
|
// Hide complete tags (flexible brackets)
|
||||||
.replace(/\[PREVIEW:[\w-]+:?[\w-]+?\][\s\S]*?(?:\[\/PREVIEW\]|$)/g, "")
|
.replace(/\[+(?:AGENT|content|seo|smm|pm|code|design|web|app|PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT|PREV):?[\w-]*:?[\w-]*\]+/gi, "")
|
||||||
.replace(/\[(AGENT|PREVIEW)?(?::[\w-]*)?$/g, "") // Hide partial tags at the end
|
// 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();
|
.trim();
|
||||||
|
|
||||||
if (!preview) {
|
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);
|
const htmlDoc = text.match(/<!doctype\s+html[\s\S]*$/i) || text.match(/<html[\s\S]*$/i);
|
||||||
if (htmlDoc) {
|
if (htmlDoc) {
|
||||||
preview = {
|
preview = {
|
||||||
@@ -346,9 +353,22 @@ export default function AIAssist() {
|
|||||||
let accumulated = "";
|
let accumulated = "";
|
||||||
let lastParsedPreview: PreviewData | null = null;
|
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(
|
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,
|
currentAgent,
|
||||||
onChunk: (chunk) => {
|
onChunk: (chunk) => {
|
||||||
accumulated += chunk;
|
accumulated += chunk;
|
||||||
|
|||||||
Reference in New Issue
Block a user