UNPKG

mcp-ai-agent-guidelines

Version:

A comprehensive Model Context Protocol server providing professional tools, resources, and prompts for implementing AI agent best practices

978 lines 37.9 kB
import { z } from "zod"; async function validateDiagram(code) { try { // dynamic import keeps tool lightweight if mermaid not installed const mermaid = await import("mermaid"); // mermaid.parse will throw on error // Some versions expose parse async; wrap in Promise.resolve // @ts-expect-error await Promise.resolve(mermaid.parse(code)); return { valid: true }; } catch (err) { const msg = err.message || String(err); // If mermaid is not installed/available, skip validation but allow diagram output if (/Cannot find module 'mermaid'|Cannot use import statement|module not found/i.test(msg)) { return { valid: true, skipped: true }; } return { valid: false, error: msg }; } } const MermaidDiagramSchema = z.object({ description: z.string(), diagramType: z.enum([ "flowchart", "sequence", "class", "state", "gantt", "pie", "er", "journey", "quadrant", "git-graph", "mindmap", "timeline", ]), theme: z.string().optional(), strict: z.boolean().optional().default(true), // if true, never emit invalid diagram; fallback if needed repair: z.boolean().optional().default(true), // attempt auto-repair on failure // Accessibility metadata (added as Mermaid comments to avoid requiring specific Mermaid versions) accTitle: z.string().optional(), accDescr: z.string().optional(), // Advanced customization options direction: z.enum(["TD", "TB", "BT", "LR", "RL"]).optional(), // flowchart direction customStyles: z.string().optional(), // custom CSS/styling directives advancedFeatures: z.record(z.unknown()).optional(), // type-specific advanced features }); export async function mermaidDiagramGenerator(args) { const normalized = (() => { if (args && typeof args === "object" && args !== null) { const obj = args; // Handle legacy diagram type names if (obj.diagramType === "erDiagram") { return { ...obj, diagramType: "er" }; } if (obj.diagramType === "graph") { return { ...obj, diagramType: "flowchart" }; } if (obj.diagramType === "userJourney") { return { ...obj, diagramType: "journey" }; } if (obj.diagramType === "gitgraph" || obj.diagramType === "gitGraph") { return { ...obj, diagramType: "git-graph" }; } } return args; })(); const input = MermaidDiagramSchema.parse(normalized); let diagram = generateMermaidDiagram(input); // Prepend accessibility comments if provided const accLines = []; if (input.accTitle) accLines.push(`%% AccTitle: ${input.accTitle} %%`); if (input.accDescr) accLines.push(`%% AccDescr: ${input.accDescr} %%`); if (accLines.length) { diagram = [accLines.join("\n"), diagram].join("\n"); } let validation = await validateDiagram(diagram); let repaired = false; if (!validation.valid && input.repair) { const attempt = repairDiagram(diagram); if (attempt !== diagram) { diagram = attempt; validation = await validateDiagram(diagram); repaired = validation.valid; } } if (!validation.valid && input.strict) { // Provide safe fallback minimal valid diagram diagram = fallbackDiagram(); validation = await validateDiagram(diagram); // should pass; if not, still return with flag } const validityNote = validation.valid ? validation.skipped ? `ℹ️ Validation skipped (mermaid not available). Diagram generated.` : `✅ Diagram validated successfully${repaired ? " (after auto-repair)" : ""}.` : `❌ Diagram invalid even after attempts: ${validation.error}`; const feedback = validation.valid ? "" : [ "### Feedback Loop", "- Try simplifying node labels (avoid punctuation that Mermaid may misparse)", "- Ensure a single diagram header (e.g., 'flowchart TD')", "- Replace complex punctuation with plain words", "- If describing a pipeline, try a simpler 5-step flow and add branches gradually", ].join("\n"); return { content: [ { type: "text", text: [ "## Generated Mermaid Diagram", "", "### Description", input.description, "", "### Diagram Code", "```mermaid", diagram, "```", "", "### Accessibility", input.accTitle || input.accDescr ? [ input.accTitle ? `- Title: ${input.accTitle}` : undefined, input.accDescr ? `- Description: ${input.accDescr}` : undefined, ] .filter(Boolean) .join("\n") : "- You can provide accTitle and accDescr to improve screen reader context.", "", "### Validation", validityNote, feedback, "", "### Generation Settings", `Type: ${input.diagramType}`, `Strict: ${input.strict}`, `Repair: ${input.repair}`, "", "### Usage Instructions", "1. Copy the Mermaid code above", "2. Paste it into any Mermaid-enabled Markdown renderer or the Live Editor", "3. Adjust styling, layout, or relationships as needed", "", "### Notes", "Repair heuristics: classDef style tokens normalized, ensures colon syntax, fallback to minimal diagram if unrecoverable.", ].join("\n"), }, ], }; } function repairDiagram(diagram) { let repaired = diagram; // Normalize classDef syntax (convert fill= to fill: etc.) repaired = repaired.replace(/classDef (\w+) ([^\n;]+);?/g, (_m, name, body) => { const fixed = body .split(/[, ]+/) .filter(Boolean) .map((pair) => pair.replace(/=/g, ":")) .join(","); return `classDef ${name} ${fixed};`; }); // Ensure flowchart header present if (!/^\s*flowchart /.test(repaired) && /\bflowchart\b/.test(repaired)) { repaired = `flowchart TD\n${repaired}`; } return repaired; } function fallbackDiagram() { return [ "flowchart TD", "A([Start]) --> B[Fallback Diagram]", "B --> C([End])", ].join("\n"); } function generateMermaidDiagram(input) { const { description, diagramType, theme, direction, customStyles: _customStyles, // Reserved for future use advancedFeatures, } = input; switch (diagramType) { case "flowchart": return generateFlowchart(description, theme, direction); case "sequence": return generateSequenceDiagram(description, theme, advancedFeatures); case "class": return generateClassDiagram(description, theme, advancedFeatures); case "state": return generateStateDiagram(description, theme, advancedFeatures); case "gantt": return generateGanttChart(description, theme, advancedFeatures); case "pie": return generatePieChart(description, theme); case "er": return generateERDiagram(description, theme); case "journey": return generateUserJourney(description, theme); case "quadrant": return generateQuadrantChart(description, theme); case "git-graph": return generateGitGraph(description, theme); case "mindmap": return generateMindmap(description, theme); case "timeline": return generateTimeline(description, theme); default: return generateFlowchart(description, theme, direction); } } function generateFlowchart(description, theme, direction) { const steps = extractSteps(description); const flowDirection = direction || "TD"; const lines = [`flowchart ${flowDirection}`]; if (theme) lines.unshift(`%%{init: {'theme':'${theme}'}}%%`); if (!steps.length) { // Provide a simple user->system->processor->output pipeline if description lacks clear steps lines.push("U([User]) --> R[Read users.json]", "R --> P[Filter active users with permissions]", "P --> A[Append to result]", "A --> O([Summary Output])"); return lines.join("\n"); } // Build main chain with optional branching for filter step let offset = 0; for (let i = 0; i < steps.length; i++) { const id = String.fromCharCode(65 + i + offset); const label = steps[i]; const isFilter = /filter .*active .*users/i.test(label); const decisionLike = /(decision|choose|\?)/i.test(label) || isFilter; if (isFilter) { // Create decision node with yes/no branches lines.push(`${id}{${label}?}`); const yesId = String.fromCharCode(65 + i + 1 + offset); const noId = String.fromCharCode(65 + i + 2 + offset); lines.push(`${id} -->|Yes| ${yesId}[Append to result]`); lines.push(`${id} -->|No| ${noId}[Skip]`); // Advance offset because we injected two extra nodes offset += 2; const nextId = String.fromCharCode(65 + i + 1 + offset); if (i < steps.length - 1) { lines.push(`${yesId} --> ${nextId}`); lines.push(`${noId} --> ${nextId}`); } } else { lines.push(decisionLike ? `${id}{${label}}` : `${id}[${label}]`); if (i < steps.length - 1) { const nextId = String.fromCharCode(65 + i + 1 + offset); lines.push(`${id} --> ${nextId}`); } } } const lastId = String.fromCharCode(65 + steps.length - 1 + offset); const endId = String.fromCharCode(65 + steps.length + offset); lines.push(`${lastId} --> ${endId}([End])`); // Enrich with risk / smell nodes based on keywords const riskNodes = []; let riskIndex = steps.length + 1 + offset; // starting after End const addRisk = (id, label, connectFrom) => { lines.push(`${id}[${label}]`); lines.push(`${connectFrom} -.-> ${id}`); riskNodes.push(id); }; const lower = description.toLowerCase(); const firstId = "A"; if (/api key|secret/.test(lower)) { const id = String.fromCharCode(65 + riskIndex++); addRisk(id, "Hardcoded Secret", firstId); } if (/sql/.test(lower)) { const id = String.fromCharCode(65 + riskIndex++); addRisk(id, "Raw SQL Query Risk", firstId); } if (/deprecated|old method|old\b/.test(lower)) { const id = String.fromCharCode(65 + riskIndex++); addRisk(id, "Deprecated Method", firstId); } if (riskNodes.length) { lines.push("classDef risk fill:#fee,stroke:#d33,stroke-width:1px;"); lines.push(`class ${riskNodes.join(",")} risk;`); } return lines.join("\n"); } function generateSequenceDiagram(description, theme, advancedFeatures) { const header = ["sequenceDiagram"]; if (theme) header.unshift(`%%{init: {'theme':'${theme}'}}%%`); // Parse description to extract participants and interactions const { participants, interactions } = parseSequenceDescription(description); // Add participants if (participants.length > 0) { for (const p of participants) { header.push(`participant ${p.id} as ${p.name}`); } } // Add interactions if (interactions.length > 0) { for (const interaction of interactions) { header.push(interaction); } } else { // Fallback to default template if parsing fails const body = [ "participant U as User", "participant S as System", "participant D as Database", "U->>S: Request", "S->>D: Query", "D-->>S: Data", "S-->>U: Response", ]; return [...header, ...body].join("\n"); } // Add advanced features like loops, alt blocks, etc. if (advancedFeatures?.autonumber === true) { header.splice(1, 0, "autonumber"); } return header.join("\n"); } function generateClassDiagram(description, theme, _advancedFeatures) { const lines = ["classDiagram"]; if (theme) lines.unshift(`%%{init: {'theme':'${theme}'}}%%`); // Parse description to extract classes and relationships const { classes, relationships } = parseClassDescription(description); if (classes.length > 0) { // Add parsed classes for (const cls of classes) { if (cls.properties.length > 0 || cls.methods.length > 0) { lines.push(`class ${cls.name} {`); for (const prop of cls.properties) { lines.push(` ${prop}`); } for (const method of cls.methods) { lines.push(` ${method}`); } lines.push("}"); } else { lines.push(`class ${cls.name}`); } } // Add relationships for (const rel of relationships) { lines.push(rel); } } else { // Fallback to default template lines.push("class User {", " +String name", " +String email", " +login()", " +logout()", "}", "class System {", " +processRequest()", " +validateUser()", "}", "User --> System : uses"); } return lines.join("\n"); } function generateStateDiagram(description, theme, _advancedFeatures) { const lines = ["stateDiagram-v2"]; if (theme) lines.unshift(`%%{init: {'theme':'${theme}'}}%%`); // Parse description to extract states and transitions const { states, transitions } = parseStateDescription(description); if (states.length > 0 && transitions.length > 0) { for (const transition of transitions) { lines.push(transition); } } else { // Fallback to default template lines.push("[*] --> Idle", "Idle --> Processing : start", "Processing --> Complete : finish", "Processing --> Error : fail", "Complete --> [*]", "Error --> Idle : retry"); } return lines.join("\n"); } function generateGanttChart(description, theme, _advancedFeatures) { const lines = ["gantt"]; if (theme) lines.unshift(`%%{init: {'theme':'${theme}'}}%%`); // Parse description to extract tasks and timeline const { title, tasks } = parseGanttDescription(description); lines.push(`title ${title || "Project Timeline"}`); lines.push("dateFormat YYYY-MM-DD"); if (tasks.length > 0) { // Use parsed tasks const sections = new Map(); for (const task of tasks) { if (!sections.has(task.section)) { sections.set(task.section, []); } sections.get(task.section)?.push(task.line); } for (const [section, taskLines] of sections) { lines.push(`section ${section}`); for (const taskLine of taskLines) { lines.push(taskLine); } } } else { // Fallback to default template with dynamic dates const today = new Date(); const formatDate = (date) => date.toISOString().split("T")[0]; const startDate = formatDate(today); const researchEnd = formatDate(new Date(today.getTime() + 4 * 24 * 60 * 60 * 1000)); const designStart = formatDate(new Date(today.getTime() + 5 * 24 * 60 * 60 * 1000)); const designEnd = formatDate(new Date(today.getTime() + 11 * 24 * 60 * 60 * 1000)); const implementationStart = formatDate(new Date(new Date(designEnd).getTime() + 1 * 24 * 60 * 60 * 1000)); const implementationEnd = formatDate(new Date(new Date(designEnd).getTime() + 11 * 24 * 60 * 60 * 1000)); const testingStart = formatDate(new Date(new Date(implementationEnd).getTime() + 1 * 24 * 60 * 60 * 1000)); const testingEnd = formatDate(new Date(new Date(implementationEnd).getTime() + 6 * 24 * 60 * 60 * 1000)); lines.push("section Planning", `Research :done, research, ${startDate}, ${researchEnd}`, `Design :active, design, ${designStart}, ${designEnd}`, "section Development", `Implementation :impl, ${implementationStart}, ${implementationEnd}`, `Testing :test, ${testingStart}, ${testingEnd}`); } return lines.join("\n"); } function generatePieChart(description, theme) { const lines = []; if (theme) lines.push(`%%{init: {'theme':'${theme}'}}%%`); // Parse description to extract categories and values const { title, data } = parsePieDescription(description); lines.push(`pie title ${title || "Sample Distribution"}`); if (data.length > 0) { for (const item of data) { lines.push(`"${item.label}" : ${item.value}`); } } else { // Fallback to default template lines.push('"Category A" : 45', '"Category B" : 30', '"Category C" : 15', '"Category D" : 10'); } return lines.join("\n"); } function extractSteps(description) { // Simple step extraction logic: split on sentence terminators & newlines const sentences = description .split(/[.!?\n]+/) .map((s) => s.trim()) .filter((s) => s.length > 0); // Allow up to 12 steps for better granularity while keeping diagram readable return sentences.slice(0, 12); } // Parsing functions for different diagram types function parseSequenceDescription(description) { const participants = []; const interactions = []; const participantMap = new Map(); // Extract participant names (nouns that appear frequently or are explicitly mentioned) const words = description.toLowerCase().split(/\s+/); const commonParticipants = [ "user", "system", "server", "client", "database", "api", "service", "admin", "customer", ]; let participantId = 65; // ASCII 'A' for (const word of words) { const clean = word.replace(/[^a-z]/g, ""); if (commonParticipants.includes(clean) && !participantMap.has(clean)) { const id = String.fromCharCode(participantId++); const name = clean.charAt(0).toUpperCase() + clean.slice(1); participantMap.set(clean, id); participants.push({ id, name }); } } // Extract interactions from sentences const sentences = description .split(/[.!?\n]+/) .map((s) => s.trim()) .filter((s) => s.length > 0); for (const sentence of sentences) { const lower = sentence.toLowerCase(); // Look for interaction patterns for (const [name1, id1] of participantMap) { for (const [name2, id2] of participantMap) { if (name1 !== name2) { // Check for various interaction patterns if (lower.includes(`${name1} sends`) || lower.includes(`${name1} to ${name2}`)) { const action = extractAction(sentence); interactions.push(`${id1}->>${id2}: ${action}`); } else if (lower.includes(`${name2} responds`) || lower.includes(`${name2} returns`)) { const action = extractAction(sentence); interactions.push(`${id2}-->>${id1}: ${action}`); } } } } } return { participants, interactions }; } function parseClassDescription(description) { const classes = []; const relationships = []; const classNames = new Set(); // Extract class names (capitalized words or explicit mentions) const words = description.split(/\s+/); for (const word of words) { const clean = word.replace(/[^a-zA-Z]/g, ""); if (clean.length > 2 && clean[0] === clean[0].toUpperCase()) { classNames.add(clean); } } // Look for common class-related keywords const commonClasses = [ "User", "Product", "Order", "Customer", "Account", "Item", "Service", "Manager", ]; for (const cls of commonClasses) { if (description.toLowerCase().includes(cls.toLowerCase())) { classNames.add(cls); } } // Create class definitions for (const name of classNames) { const properties = []; const methods = []; // Extract properties (look for "has", "contains", "with" patterns) const lower = description.toLowerCase(); if (lower.includes(`${name.toLowerCase()} has`) || lower.includes(`${name.toLowerCase()} contains`)) { properties.push("+String id"); properties.push("+String name"); } // Extract methods (look for action verbs) if (lower.includes(`${name.toLowerCase()} can`) || lower.includes(`${name.toLowerCase()} does`)) { methods.push("+process()"); } classes.push({ name, properties, methods }); } // Extract relationships const classArray = Array.from(classNames); for (let i = 0; i < classArray.length - 1; i++) { const lower = description.toLowerCase(); const cls1 = classArray[i].toLowerCase(); const cls2 = classArray[i + 1].toLowerCase(); if (lower.includes(`${cls1} has ${cls2}`) || lower.includes(`${cls1} contains ${cls2}`)) { relationships.push(`${classArray[i]} --> ${classArray[i + 1]} : has`); } else if (lower.includes(`${cls1} uses ${cls2}`) || lower.includes(`${cls1} depends on ${cls2}`)) { relationships.push(`${classArray[i]} --> ${classArray[i + 1]} : uses`); } } return { classes, relationships }; } function parseStateDescription(description) { const states = []; const transitions = []; const stateMap = new Map(); // Extract state names (look for status/state keywords) const commonStates = [ "idle", "active", "processing", "complete", "error", "pending", "ready", "waiting", "done", "failed", ]; const words = description.toLowerCase().split(/\s+/); for (const word of words) { const clean = word.replace(/[^a-z]/g, ""); if (commonStates.includes(clean) && !stateMap.has(clean)) { const stateName = clean.charAt(0).toUpperCase() + clean.slice(1); stateMap.set(clean, stateName); states.push(stateName); } } // Extract transitions from sentences const sentences = description .split(/[.!?\n]+/) .map((s) => s.trim()) .filter((s) => s.length > 0); const stateArray = Array.from(stateMap.entries()); if (stateArray.length > 0) { // Add initial state transitions.push(`[*] --> ${stateArray[0][1]}`); // Extract transitions between states for (const sentence of sentences) { const lower = sentence.toLowerCase(); for (let i = 0; i < stateArray.length; i++) { for (let j = 0; j < stateArray.length; j++) { if (i !== j) { const [state1Key, state1Name] = stateArray[i]; const [state2Key, state2Name] = stateArray[j]; if (lower.includes(`${state1Key} to ${state2Key}`) || lower.includes(`from ${state1Key} to ${state2Key}`)) { const trigger = extractTrigger(sentence); transitions.push(`${state1Name} --> ${state2Name} : ${trigger}`); } } } } } // Add final state if we have a complete or done state const finalStates = ["Complete", "Done", "Finished"]; for (const state of states) { if (finalStates.includes(state)) { transitions.push(`${state} --> [*]`); break; } } } return { states, transitions }; } function parseGanttDescription(description) { let title = "Project Timeline"; const tasks = []; // Extract title if mentioned if (description.toLowerCase().includes("project:")) { const match = description.match(/project:\s*([^.\n]+)/i); if (match) title = match[1].trim(); } // Extract tasks (look for action verbs and time references) const sentences = description .split(/[.!?\n]+/) .map((s) => s.trim()) .filter((s) => s.length > 0); const today = new Date(); const formatDate = (date) => date.toISOString().split("T")[0]; let currentDate = today; let section = "Tasks"; for (let i = 0; i < sentences.length; i++) { const sentence = sentences[i]; const lower = sentence.toLowerCase(); // Check for section keywords if (lower.includes("phase") || lower.includes("stage")) { section = sentence.split(/[:]/)[0].trim(); continue; } // Extract task const taskName = sentence.length > 50 ? `${sentence.substring(0, 47)}...` : sentence; const startDate = formatDate(currentDate); const duration = 3 + Math.floor(Math.random() * 5); // 3-7 days const endDate = formatDate(new Date(currentDate.getTime() + duration * 24 * 60 * 60 * 1000)); const status = i === 0 ? "done" : i === 1 ? "active" : ""; const statusPart = status ? `${status}, ` : ""; tasks.push({ section, line: `${taskName} :${statusPart}task${i}, ${startDate}, ${endDate}`, }); // Move current date forward currentDate = new Date(currentDate.getTime() + (duration + 1) * 24 * 60 * 60 * 1000); } return { title, tasks }; } function parsePieDescription(description) { let title = "Distribution"; const data = []; // Extract title if mentioned const titleMatch = description.match(/(?:chart|distribution|breakdown)(?:\s+of|\s+for)?\s*:\s*([^.\n]+)/i); if (titleMatch) title = titleMatch[1].trim(); // Extract percentages or numbers const percentMatches = description.matchAll(/(\w+[\w\s]*?)[\s:]+(\d+)%/gi); for (const match of percentMatches) { data.push({ label: match[1].trim(), value: parseInt(match[2], 10) }); } // Extract explicit counts if (data.length === 0) { const countMatches = description.matchAll(/(\d+)\s+(\w+[\w\s]*)/gi); const items = []; for (const match of countMatches) { items.push({ label: match[2].trim(), value: parseInt(match[1], 10) }); } // Convert to percentages if (items.length > 0) { const total = items.reduce((sum, item) => sum + item.value, 0); for (const item of items) { data.push({ label: item.label, value: Math.round((item.value / total) * 100), }); } } } return { title, data }; } // New diagram type generators function generateERDiagram(description, theme) { const lines = ["erDiagram"]; if (theme) lines.unshift(`%%{init: {'theme':'${theme}'}}%%`); // Extract entities and relationships const { entities: _entities, relationships } = parseERDescription(description); if (relationships.length > 0) { for (const rel of relationships) { lines.push(rel); } } else { // Fallback template lines.push("CUSTOMER ||--o{ ORDER : places", "ORDER ||--|{ LINE-ITEM : contains", 'PRODUCT ||--o{ LINE-ITEM : "ordered in"'); } return lines.join("\n"); } function generateUserJourney(description, theme) { const lines = ["journey"]; if (theme) lines.unshift(`%%{init: {'theme':'${theme}'}}%%`); const { title, steps } = parseJourneyDescription(description); lines.push(`title ${title}`); if (steps.length > 0) { for (const step of steps) { lines.push(step); } } else { // Fallback template lines.push("section Discover", "Find product: 5: User", "Read reviews: 3: User", "section Purchase", "Add to cart: 4: User", "Checkout: 2: User, System"); } return lines.join("\n"); } function generateQuadrantChart(description, theme) { const lines = ["quadrantChart"]; if (theme) lines.unshift(`%%{init: {'theme':'${theme}'}}%%`); const { title, xAxis, yAxis, quadrants, points } = parseQuadrantDescription(description); lines.push(`title ${title}`); lines.push(`x-axis ${xAxis[0]} --> ${xAxis[1]}`); lines.push(`y-axis ${yAxis[0]} --> ${yAxis[1]}`); if (quadrants.length === 4) { lines.push(`quadrant-1 ${quadrants[0]}`); lines.push(`quadrant-2 ${quadrants[1]}`); lines.push(`quadrant-3 ${quadrants[2]}`); lines.push(`quadrant-4 ${quadrants[3]}`); } if (points.length > 0) { for (const point of points) { lines.push(point); } } else { // Fallback template lines.push("Item A: [0.3, 0.6]"); lines.push("Item B: [0.7, 0.8]"); lines.push("Item C: [0.2, 0.3]"); } return lines.join("\n"); } function generateGitGraph(description, theme) { const lines = ["gitGraph"]; if (theme) lines.unshift(`%%{init: {'theme':'${theme}'}}%%`); // Extract branch and commit info from description const { commits, branches: _branches } = parseGitDescription(description); if (commits.length > 0) { for (const commit of commits) { lines.push(commit); } } else { // Fallback template lines.push('commit id: "Initial"', "branch develop", "checkout develop", 'commit id: "Feature"', "checkout main", "merge develop", 'commit id: "Release"'); } return lines.join("\n"); } function generateMindmap(description, theme) { const lines = ["mindmap"]; if (theme) lines.unshift(`%%{init: {'theme':'${theme}'}}%%`); const { root, children } = parseMindmapDescription(description); lines.push(` root((${root}))`); if (children.length > 0) { for (const child of children) { lines.push(child); } } else { // Fallback template lines.push(" Topic 1", " Subtopic 1.1", " Subtopic 1.2", " Topic 2", " Subtopic 2.1"); } return lines.join("\n"); } function generateTimeline(description, theme) { const lines = ["timeline"]; if (theme) lines.unshift(`%%{init: {'theme':'${theme}'}}%%`); const { title, events } = parseTimelineDescription(description); lines.push(`title ${title}`); if (events.length > 0) { for (const event of events) { lines.push(event); } } else { // Fallback template lines.push("section 2024", "Q1 : Planning : Research", "Q2 : Development : Testing", "section 2025", "Q1 : Launch : Marketing"); } return lines.join("\n"); } // Helper parsing functions for new diagram types function parseERDescription(description) { const entities = []; const relationships = []; const lower = description.toLowerCase(); // Extract entity names (capitalized words or explicit mentions) const words = description.split(/\s+/); const entitySet = new Set(); for (const word of words) { const clean = word.replace(/[^a-zA-Z]/g, ""); if (clean.length > 2 && clean[0] === clean[0].toUpperCase()) { entitySet.add(clean.toUpperCase()); } } entities.push(...entitySet); // Extract relationships const entArray = Array.from(entitySet); for (let i = 0; i < entArray.length - 1; i++) { const e1 = entArray[i].toLowerCase(); const e2 = entArray[i + 1].toLowerCase(); if (lower.includes(`${e1} has ${e2}`) || lower.includes(`${e1} contains ${e2}`)) { relationships.push(`${entArray[i]} ||--o{ ${entArray[i + 1]} : has`); } else if (lower.includes(`${e1} belongs to ${e2}`)) { relationships.push(`${entArray[i]} }o--|| ${entArray[i + 1]} : "belongs to"`); } else { relationships.push(`${entArray[i]} ||--o{ ${entArray[i + 1]} : relates`); } } return { entities, relationships }; } function parseJourneyDescription(description) { const title = description.split(/[.!?\n]/)[0] || "User Journey"; const steps = []; const sentences = description .split(/[.!?\n]+/) .map((s) => s.trim()) .filter((s) => s.length > 0) .slice(1); let section = "Journey"; for (const sentence of sentences) { if (sentence.toLowerCase().includes("section") || sentence.toLowerCase().includes("phase")) { section = sentence.replace(/section|phase/gi, "").trim(); steps.push(`section ${section}`); } else { const score = 3 + Math.floor(Math.random() * 3); // Random score 3-5 const step = sentence.length > 30 ? `${sentence.substring(0, 27)}...` : sentence; steps.push(`${step}: ${score}: User`); } } return { title, steps }; } function parseQuadrantDescription(description) { const title = "Priority Matrix"; const xAxis = ["Low", "High"]; const yAxis = ["Low", "High"]; const quadrants = ["Plan", "Do", "Delegate", "Delete"]; const points = []; // Extract items to plot const sentences = description .split(/[.!?\n]+/) .map((s) => s.trim()) .filter((s) => s.length > 0); for (let i = 0; i < Math.min(sentences.length, 8); i++) { const item = sentences[i].length > 20 ? `${sentences[i].substring(0, 17)}...` : sentences[i]; const x = (0.2 + Math.random() * 0.6).toFixed(1); const y = (0.2 + Math.random() * 0.6).toFixed(1); points.push(`${item}: [${x}, ${y}]`); } return { title, xAxis, yAxis, quadrants, points }; } function parseGitDescription(description) { const commits = []; const branches = []; const sentences = description .split(/[.!?\n]+/) .map((s) => s.trim()) .filter((s) => s.length > 0); commits.push('commit id: "Initial"'); for (let i = 0; i < Math.min(sentences.length, 5); i++) { const msg = sentences[i].length > 30 ? `${sentences[i].substring(0, 27)}...` : sentences[i]; commits.push(`commit id: "${msg}"`); } return { commits, branches }; } function parseMindmapDescription(description) { const sentences = description .split(/[.!?\n]+/) .map((s) => s.trim()) .filter((s) => s.length > 0); const root = sentences[0] || "Main Topic"; const children = []; for (let i = 1; i < Math.min(sentences.length, 6); i++) { const topic = sentences[i].length > 30 ? `${sentences[i].substring(0, 27)}...` : sentences[i]; children.push(` ${topic}`); } return { root, children }; } function parseTimelineDescription(description) { const title = description.split(/[.!?\n]/)[0] || "Timeline"; const events = []; const sentences = description .split(/[.!?\n]+/) .map((s) => s.trim()) .filter((s) => s.length > 0) .slice(1); let currentYear = new Date().getFullYear(); let section = `section ${currentYear}`; events.push(section); for (let i = 0; i < sentences.length; i++) { if (i % 3 === 0 && i > 0) { currentYear++; section = `section ${currentYear}`; events.push(section); } const event = sentences[i].length > 40 ? `${sentences[i].substring(0, 37)}...` : sentences[i]; events.push(event); } return { title, events }; } // Helper functions for text extraction function extractAction(sentence) { const words = sentence.split(/\s+/); // Find verb and object const verbs = [ "sends", "requests", "queries", "returns", "responds", "provides", "fetches", ]; for (const word of words) { if (verbs.includes(word.toLowerCase())) { const idx = words.indexOf(word); return words.slice(idx, idx + 3).join(" "); } } return "Request"; } function extractTrigger(sentence) { const triggers = [ "start", "finish", "complete", "fail", "error", "success", "retry", "cancel", ]; const words = sentence.toLowerCase().split(/\s+/); for (const word of words) { if (triggers.includes(word)) { return word; } } return "event"; } //# sourceMappingURL=mermaid-diagram-generator.js.map