AI_Formatter_V02 / src /utils /aiFormatter.js
Varun10000's picture
Upload 28 files
63bca44 verified
Raw
History Blame Contribute Delete
6.02 kB
import axios from 'axios';
/**
* Formats "messed up" text into a structured JSON format using AI.
* @param {string} text - The raw text to format.
* @param {string} apiKey - The LLM API key.
* @param {string} provider - The LLM provider (e.g., 'openai', 'anthropic').
* @returns {Promise<Array>} - A structured array of document elements.
*/
/**
* Splits text into chunks based on character count while trying to preserve paragraphs.
*/
/**
* Splits text into chunks based on character count while trying to preserve paragraphs.
*/
const chunkText = (text, maxLength = 5000) => {
const paragraphs = text.split(/\n\n+/);
const chunks = [];
let currentChunk = "";
for (const p of paragraphs) {
if ((currentChunk + p).length > maxLength && currentChunk.length > 0) {
chunks.push(currentChunk.trim());
currentChunk = p;
} else {
currentChunk += (currentChunk ? "\n\n" : "") + p;
}
}
if (currentChunk) chunks.push(currentChunk.trim());
return chunks;
};
/**
* Formats "messed up" text into a structured JSON format using AI.
*/
export const formatWithAI = async (text, apiKey, provider = 'openai', onProgress) => {
if (!apiKey) return mockFormat(text);
const chunks = chunkText(text);
let allElements = [];
for (let i = 0; i < chunks.length; i++) {
if (onProgress) onProgress(i + 1, chunks.length);
const chunk = chunks[i];
const prompt = `
You are an expert RFP document formatter. I will provide you with a segment of "messed up" text.
Your task is to reformat it into a clean, structured JSON format.
Segment ${i + 1} of ${chunks.length}.
CRITICAL INSTRUCTIONS (ZERO ADDITION POLICY):
- DO NOT ADD ANY INFORMATION THAT IS NOT IN THE SOURCE TEXT.
- DO NOT invent answers, do not provide filler text, and do not guess.
- DO NOT add greetings, introductions, or concluding remarks.
- DO NOT SUMMARIZE OR OMIT ANY TEXT. YOU MUST PROCESS THE ENTIRE SEGMENT.
- REPRODUCE EVERY SINGLE PIECE OF INFORMATION FROM THE INPUT.
- DO NOT SKIP ANY PARAGRAPHS OR CLAUSES.
- RFP RECOGNITION: If you see a question (e.g., text ending in "?" or text that looks like an RFP requirement), format it as an 'heading2' or 'heading3' so it stands out.
- If you see a question followed by an answer, the question should be a heading and the answer a paragraph.
- Main headers (like "MASTER CONTRACTOR AGREEMENT") must ALWAYS be typed as 'heading1'.
- If you see a list of key-value pairs (like Company Name: Value), format it as a 2-column TABLE.
- In lists, REMOVE any existing numbering or bullet prefixes (e.g., '(a)', '1.', '-', '•') from the start of each item.
- Identify List Style: For 'numberedList', add a 'listType' field. Use 'lowerAlpha' if the source uses (a), (b), (c) or a., b., c. Use 'decimal' if it uses 1, 2, 3.
- Identify Centered Titles: If a heading (like "MASTER CONTRACTOR AGREEMENT") should be centered, add an 'alignment' field set to 'center'. Default is 'left'.
Return a JSON object with an 'elements' key containing an array of objects.
Each object must have:
- 'type': one of 'heading1', 'heading2', 'heading3', 'paragraph', 'bulletList', 'numberedList', 'table'
- 'listType': (for 'numberedList' only) 'decimal' or 'lowerAlpha'
- 'alignment': 'left' or 'center' (default is 'left')
- 'content': (string for text, array of strings for lists, array of arrays for tables)
Text segment to format:
${chunk}
`;
let attempts = 0;
let success = false;
while (attempts < 3 && !success) {
attempts++;
try {
let response;
const config = {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
timeout: 90000 // 90s timeout for better reliability
};
if (provider === 'openai') {
response = await axios.post(
'https://api.openai.com/v1/chat/completions',
{
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' },
max_tokens: 4096,
},
config
);
} else if (provider === 'groq') {
response = await axios.post(
'https://api.groq.com/openai/v1/chat/completions',
{
model: 'llama-3.3-70b-versatile',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' },
max_tokens: 4096,
},
config
);
}
const cleanJson = response.data.choices[0].message.content.replace(/```json\n?|\n?```/g, '').trim();
const elements = JSON.parse(cleanJson).elements || [];
allElements = [...allElements, ...elements];
success = true;
} catch (error) {
console.error(`AI Formatting Error on chunk ${i + 1} (Attempt ${attempts}):`, error);
if (attempts === 3) {
throw new Error(`Failed to format document segment ${i + 1} after 3 attempts. Please try again or check your connection.`);
}
// Wait 1s before retry
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
return allElements;
};
const mockFormat = (text) => {
// Simple mock logic: split by double newlines and guess types
const lines = text.split('\n').filter(line => line.trim() !== '');
return lines.map((line, index) => {
if (index === 0) return { type: 'heading1', content: line };
if (line.startsWith('* ') || line.startsWith('- ')) {
return { type: 'bulletList', content: [line.substring(2)] };
}
return { type: 'paragraph', content: line };
});
};