UNPKG

aura-ai

Version:

AI-powered marketing strategist CLI tool for developers

189 lines (163 loc) 5.74 kB
import fs from 'fs/promises'; import path from 'path'; import { aiParser } from './aiQuestionParser.js'; /** * Convert markdown questions to structured JSON format */ export class MarkdownToJsonConverter { /** * Parse markdown content and extract questions * @param {string} content - Markdown content * @param {boolean} useAI - Whether to use AI for option extraction * @returns {Object} Structured questions object */ async parseMarkdown(content, useAI = true) { const lines = content.split('\n'); const questions = []; let questionIndex = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Check if line is a question (starts with -) if (line.trim().startsWith('-') && !line.trim().startsWith('- explainer:')) { const questionText = line.trim().substring(1).trim(); // Check if question has options in parentheses const parenMatch = questionText.match(/\(([^)]+)\)/); let type = 'text'; let options = []; if (parenMatch) { const optionsText = parenMatch[1]; options = optionsText.split(',').map(opt => ({ label: opt.trim(), value: opt.trim().toLowerCase().replace(/\s+/g, '_') })); type = 'select'; } // Remove parentheses from question text for display const displayQuestion = questionText.replace(/\s*\([^)]+\)\s*/, '').trim(); // Look for explainer on the next line let explainer = ''; if (i + 1 < lines.length) { const nextLine = lines[i + 1].trim(); if (nextLine.startsWith('- explainer:')) { explainer = nextLine.substring('- explainer:'.length).trim(); // Check for multi-line explainer let j = i + 2; while (j < lines.length && lines[j].startsWith(' ')) { explainer += ' ' + lines[j].trim(); j++; } } } questions.push({ id: `q_${questionIndex + 1}`, type: type, question: displayQuestion, originalQuestion: questionText, required: true, options: options, explainer: explainer, metadata: { originalIndex: questionIndex, category: this.inferCategory(displayQuestion) } }); questionIndex++; } } // Enhance with AI if enabled and API key is available if (useAI && process.env.OPENAI_API_KEY) { try { const enhancedQuestions = await aiParser.processQuestions(questions); return { version: '1.0.0', generatedAt: new Date().toISOString(), totalQuestions: enhancedQuestions.length, questions: enhancedQuestions, aiEnhanced: true }; } catch (error) { // If AI enhancement fails, continue with basic parsing } } return { version: '1.0.0', generatedAt: new Date().toISOString(), totalQuestions: questions.length, questions: questions, aiEnhanced: false }; } /** * Infer category from question text * @param {string} questionText - The question text * @returns {string} Category name */ inferCategory(questionText) { const text = questionText.toLowerCase(); if (text.includes('customer') || text.includes('audience') || text.includes('user')) { return 'audience'; } if (text.includes('competitor') || text.includes('alternative')) { return 'competition'; } if (text.includes('channel') || text.includes('reach')) { return 'marketing'; } if (text.includes('problem') || text.includes('solution') || text.includes('feature')) { return 'product'; } return 'general'; } /** * Convert markdown file to JSON * @param {string} inputPath - Path to markdown file * @param {string} outputPath - Path to output JSON file * @param {boolean} useAI - Whether to use AI for option extraction */ async convertFile(inputPath, outputPath, useAI = false) { try { // Read markdown file const content = await fs.readFile(inputPath, 'utf-8'); // Parse to JSON (now async) const jsonData = await this.parseMarkdown(content, useAI); // Add source file info jsonData.source = { file: path.basename(inputPath), path: inputPath, lastModified: (await fs.stat(inputPath)).mtime.toISOString() }; // Write JSON file await fs.writeFile( outputPath, JSON.stringify(jsonData, null, 2), 'utf-8' ); // Conversion successful - no console output to avoid breaking the UI return jsonData; } catch (error) { // Error occurred but don't log to avoid breaking the UI throw error; } } /** * Watch markdown file for changes and auto-convert * @param {string} inputPath - Path to markdown file * @param {string} outputPath - Path to output JSON file */ async watchAndConvert(inputPath, outputPath) { // Initial conversion await this.convertFile(inputPath, outputPath); // Watch for changes const { watch } = await import('fs'); const watcher = watch(inputPath, async (eventType) => { if (eventType === 'change') { // File changed, regenerating JSON silently await this.convertFile(inputPath, outputPath); } }); // Watching for changes silently return watcher; } } // Export a singleton instance export const converter = new MarkdownToJsonConverter();