From 1d26deadd04d1dcd400d79cc03ecdbeed1d17d6c Mon Sep 17 00:00:00 2001 From: Keoma Wright Date: Mon, 25 Aug 2025 11:41:53 +0000 Subject: [PATCH] fix: resolve code output to chat instead of files (#1797) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Comprehensive fix for AI models (Claude 3.7, DeepSeek) that output code to chat instead of creating workspace files. The enhanced parser automatically detects and wraps code blocks in proper artifact tags. ## Key Improvements ### 1. Enhanced Message Parser - Detects code blocks that should be files even without artifact tags - Six pattern detection strategies for different code output formats - Automatic file path extraction and normalization - Language detection from file extensions ### 2. Pattern Detection - File creation/modification mentions with code blocks - Code blocks with filename comments - File paths followed by code blocks - "In " context patterns - HTML/Component structure detection - Package.json and config file detection ### 3. Intelligent Processing - Prevents duplicate processing with block hashing - Validates file paths before wrapping - Preserves original content when invalid - Automatic language detection for syntax highlighting ## Technical Implementation The solution extends the existing StreamingMessageParser with enhanced detection: - Falls back to normal parsing when artifacts are properly tagged - Only applies enhanced detection when no artifacts found - Maintains backward compatibility with existing models ## Testing ✅ Tested with various code output formats ✅ Handles multiple files in single message ✅ Preserves formatting and indentation ✅ Works with all file types and languages ✅ No performance impact on properly formatted messages This fix ensures consistent file creation regardless of AI model variations. 🚀 Generated with human expertise Co-Authored-By: Keoma Wright --- app/lib/hooks/useMessageParser.ts | 4 +- app/lib/runtime/enhanced-message-parser.ts | 300 +++++++++++++++++++++ 2 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 app/lib/runtime/enhanced-message-parser.ts diff --git a/app/lib/hooks/useMessageParser.ts b/app/lib/hooks/useMessageParser.ts index e08e77e..92e3a96 100644 --- a/app/lib/hooks/useMessageParser.ts +++ b/app/lib/hooks/useMessageParser.ts @@ -1,12 +1,12 @@ import type { Message } from 'ai'; import { useCallback, useState } from 'react'; -import { StreamingMessageParser } from '~/lib/runtime/message-parser'; +import { EnhancedStreamingMessageParser } from '~/lib/runtime/enhanced-message-parser'; import { workbenchStore } from '~/lib/stores/workbench'; import { createScopedLogger } from '~/utils/logger'; const logger = createScopedLogger('useMessageParser'); -const messageParser = new StreamingMessageParser({ +const messageParser = new EnhancedStreamingMessageParser({ callbacks: { onArtifactOpen: (data) => { logger.trace('onArtifactOpen', data); diff --git a/app/lib/runtime/enhanced-message-parser.ts b/app/lib/runtime/enhanced-message-parser.ts new file mode 100644 index 0000000..9820302 --- /dev/null +++ b/app/lib/runtime/enhanced-message-parser.ts @@ -0,0 +1,300 @@ +import { createScopedLogger } from '~/utils/logger'; +import { StreamingMessageParser, type StreamingMessageParserOptions } from './message-parser'; + +const logger = createScopedLogger('EnhancedMessageParser'); + +/** + * Enhanced message parser that detects code blocks and file patterns + * even when AI models don't wrap them in proper artifact tags. + * Fixes issue #1797 where code outputs to chat instead of files. + */ +export class EnhancedStreamingMessageParser extends StreamingMessageParser { + private _processedCodeBlocks = new Map>(); + private _artifactCounter = 0; + + constructor(options: StreamingMessageParserOptions = {}) { + super(options); + } + + parse(messageId: string, input: string): string { + // First try the normal parsing + let output = super.parse(messageId, input); + + // If no artifacts were detected, check for code blocks that should be files + if (!this._hasDetectedArtifacts(input)) { + const enhancedInput = this._detectAndWrapCodeBlocks(messageId, input); + + if (enhancedInput !== input) { + // Reset and reparse with enhanced input + this.reset(); + output = super.parse(messageId, enhancedInput); + } + } + + return output; + } + + private _hasDetectedArtifacts(input: string): boolean { + return input.includes(''); + } + + private _detectAndWrapCodeBlocks(messageId: string, input: string): string { + // Initialize processed blocks for this message if not exists + if (!this._processedCodeBlocks.has(messageId)) { + this._processedCodeBlocks.set(messageId, new Set()); + } + + const processed = this._processedCodeBlocks.get(messageId)!; + + // Regex patterns for detecting code blocks with file indicators + const patterns = [ + // Pattern 1: Explicit file creation/modification mentions + /(?:create|update|modify|edit|write|add|generate|here'?s?|file:?)\s+(?:a\s+)?(?:new\s+)?(?:file\s+)?(?:called\s+)?[`'"]*([\/\w\-\.]+\.\w+)[`'"]*:?\s*\n+```(\w*)\n([\s\S]*?)```/gi, + + // Pattern 2: Code blocks with filename comments + /```(\w*)\n(?:\/\/|#|