diff --git a/src/api/network/generate.js b/src/api/network/generate.js index e292921..e9ef7c7 100644 --- a/src/api/network/generate.js +++ b/src/api/network/generate.js @@ -6,10 +6,9 @@ const { savePrompt, saveError } = require("../controller/generate"); /** * Injects file contents into message content when file tags are present * @param {string} content - The message content to process - * @returns {string} The processed content with file contents injected + * @returns {Promise} The processed content with file contents injected */ - -function injectFileContent(content) { +async function injectFileContent(content) { let processedContent = content; // First, handle block @@ -19,7 +18,7 @@ function injectFileContent(content) { if (selectedFilesMatch) { const fileListBlock = selectedFilesMatch[1]; - // Skip injection if it already contains blocks + // Skip injection if it already contains tags const containsFileTags = fileListBlock.includes(" line.replace(/^- /, "")); - const fileBlocks = filePaths - .map(async (filePath) => { + const fileBlocks = await Promise.all( + filePaths.map(async (filePath) => { try { const resolvedPath = path.resolve(process.cwd(), filePath); const fileContent = fs.readFileSync(resolvedPath, "utf-8"); @@ -39,26 +38,24 @@ function injectFileContent(content) { console.warn( `⚠️ Failed to read file: ${filePath} → ${error.message}`, ); - const errorDetails = { - message: error.message, - request: null, - response: error.response?.data, - stack: error.stack, - }; - await saveError({ error_message: error.message, - details: errorDetails, + details: { + message: error.message, + request: null, + response: error.response?.data, + stack: error.stack, + }, }); return `\n[Error: File not found or unreadable]\n`; } - }) - .join("\n\n"); + }), + ); - // Replace the original block with injected file tags + // Replace the block with the injected file tags processedContent = processedContent.replace( selectedFilesMatch[0], - fileBlocks, + fileBlocks.join("\n\n"), ); } else { console.log( @@ -67,34 +64,34 @@ function injectFileContent(content) { } } - // Then, safely resolve and inject content into any tags (without inner content) + // Inject content into empty tags const fileTagRegex = /\s*<\/file>/g; - processedContent = processedContent.replace( - fileTagRegex, - async (_, filePath, lang) => { - try { - const resolvedPath = path.resolve(process.cwd(), filePath); - const fileContent = fs.readFileSync(resolvedPath, "utf-8"); - return `\n${fileContent}\n`; - } catch (error) { - console.warn( - `⚠️ Failed to inject tag for ${filePath}: ${error.message}`, - ); - const errorDetails = { + const matches = [...processedContent.matchAll(fileTagRegex)]; + + for (const match of matches) { + const [fullTag, filePath, lang] = match; + try { + const resolvedPath = path.resolve(process.cwd(), filePath); + const fileContent = fs.readFileSync(resolvedPath, "utf-8"); + const replacement = `\n${fileContent}\n`; + processedContent = processedContent.replace(fullTag, replacement); + } catch (error) { + console.warn( + `⚠️ Failed to inject tag for ${filePath}: ${error.message}`, + ); + await saveError({ + error_message: error.message, + details: { message: error.message, request: null, response: error.response?.data, stack: error.stack, - }; - - await saveError({ - error_message: error.message, - details: errorDetails, - }); - return `\n[Error: File not found or unreadable]\n`; - } - }, - ); + }, + }); + const replacement = `\n[Error: File not found or unreadable]\n`; + processedContent = processedContent.replace(fullTag, replacement); + } + } return processedContent; } @@ -104,7 +101,6 @@ function injectFileContent(content) { * Handles both chat-style and prompt-style requests to Ollama */ async function handleGenerate(req, res) { - // Log the complete request for debugging try { const requestData = { method: req.method, @@ -119,9 +115,7 @@ async function handleGenerate(req, res) { }, }; - // Validate request body if (!requestData.body) { - // Save request data even if body is empty await savePrompt({ model: requestData.body?.model || "codellama:7b", prompt: null, @@ -131,9 +125,9 @@ async function handleGenerate(req, res) { throw new Error("Request body is required"); } - // Determine request type and validate required fields const isChatRequest = requestData.body.messages && Array.isArray(requestData.body.messages); + if ( isChatRequest && (!requestData.body.messages.length || @@ -147,45 +141,42 @@ async function handleGenerate(req, res) { throw new Error("Prompt request must include a prompt field"); } - // Set defaults and validate model const model = requestData.body.model || "codellama:7b"; const stream = requestData.body.stream !== undefined ? requestData.body.stream : true; - // Clean and prepare the prompt/messages const cleanedRequest = { model, stream, ...(isChatRequest ? { - messages: requestData.body.messages.map((msg) => ({ - role: msg.role || "user", - content: - msg.role === "user" && typeof msg.content === "string" - ? injectFileContent(msg.content.trim()) - : msg.content.trim(), - })), + messages: await Promise.all( + requestData.body.messages.map(async (msg) => ({ + role: msg.role || "user", + content: + msg.role === "user" && typeof msg.content === "string" + ? await injectFileContent(msg.content.trim()) + : msg.content.trim(), + })), + ), } : { - prompt: injectFileContent(requestData.body.prompt.trim()), + prompt: await injectFileContent(requestData.body.prompt.trim()), }), }; - // Save the complete request data to database await savePrompt({ model, prompt: isChatRequest ? null : cleanedRequest.prompt, messages: isChatRequest ? cleanedRequest.messages : null, - request_data: requestData, // Save all request data for debugging/tracking + request_data: requestData, }); - // Prepare Ollama endpoint and payload const ollamaUrl = isChatRequest ? "http://localhost:11434/api/chat" : "http://localhost:11434/api/generate"; if (stream) { - // Handle streaming response const ollamaResponse = await axios.post(ollamaUrl, cleanedRequest, { responseType: "stream", headers: { @@ -197,11 +188,9 @@ async function handleGenerate(req, res) { res.setHeader("Content-Type", "application/x-ndjson"); res.setHeader("Transfer-Encoding", "chunked"); - let responseContent = ""; ollamaResponse.data.on("data", (chunk) => { try { const data = JSON.parse(chunk.toString()); - responseContent += data.response || ""; res.write(JSON.stringify(data) + "\n"); } catch (err) { console.error("Error parsing chunk:", err); @@ -233,12 +222,10 @@ async function handleGenerate(req, res) { } }); } else { - // Handle non-streaming response const ollamaResponse = await axios.post(ollamaUrl, cleanedRequest); res.status(ollamaResponse.status).json(ollamaResponse.data); } } catch (error) { - // Enhanced error handling const errorDetails = { message: error.message, request: requestData,