chore: restructure skills repo with new agents and skill bundles

- Add new skills: deep-dive, docs-rag, meta-creator, ppt-maker, sdlc
- Add agent configs: g-assistent, meta-creator, sdlc with prompt files
- Add reference docs for custom agents and skills specification
- Add utility scripts: install-agents.sh, orchestrate.py, puml2svg.sh
- Update README and commit-message skill config
- Remove deprecated skills: codereview, python, testing, typescript
- Add .gitignore
This commit is contained in:
Team
2026-04-18 13:07:46 +08:00
parent 72f16d26b8
commit c0d14c6ac1
74 changed files with 5726 additions and 324 deletions
+172
View File
@@ -0,0 +1,172 @@
{
"name": "scripts",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "scripts",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"pptxgenjs": "^4.0.1"
}
},
"node_modules/@types/node": {
"version": "22.19.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz",
"integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/https": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
"integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==",
"license": "ISC"
},
"node_modules/image-size": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
"integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
"license": "MIT",
"dependencies": {
"queue": "6.0.2"
},
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=16.x"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/pptxgenjs": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pptxgenjs/-/pptxgenjs-4.0.1.tgz",
"integrity": "sha512-TeJISr8wouAuXw4C1F/mC33xbZs/FuEG6nH9FG1Zj+nuPcGMP5YRHl6X+j3HSUnS1f3at6k75ZZXPMZlA5Lj9A==",
"license": "MIT",
"dependencies": {
"@types/node": "^22.8.1",
"https": "^1.0.0",
"image-size": "^1.2.1",
"jszip": "^3.10.1"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"license": "MIT",
"dependencies": {
"inherits": "~2.0.3"
}
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"name": "ppt-maker",
"version": "1.0.0",
"description": "Generate PPT slides automatically from user input",
"main": "ppt-maker.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": ["ppt", "pptx", "slides", "presentation", "ai", "generator", "automatic"],
"author": "北灵聊AI",
"license": "MIT",
"dependencies": {
"pptxgenjs": "^4.0.1"
}
}
+654
View File
@@ -0,0 +1,654 @@
/**
* PPT Maker - Markdown to PPTX Generator
* Supports auto charts (pie/bar/line), multiple themes, ending page detection.
*/
var PptxGenJS = require("pptxgenjs");
// ══════════════════════════════════════════════════════
// 1. Themes & Colors (no # prefix)
// ══════════════════════════════════════════════════════
var THEMES = {
sunset: { bg: 'FFF8F3', title: 'E85D04', text: '3D405B', accent: 'F48C06', secondary: 'FAA307', light: 'FFECD2', lighter: 'FFF5EB' },
ocean: { bg: 'F0F8FF', title: '0077B6', text: '2D3748', accent: '00B4D8', secondary: '90E0EF', light: 'CAF0F8', lighter: 'E8F8FF' },
purple: { bg: 'FAF5FF', title: '7C3AED', text: '4C1D95', accent: 'A78BFA', secondary: 'C4B5FD', light: 'EDE9FE', lighter: 'F5F3FF' },
luxury: { bg: '1C1917', title: 'F5F5F4', text: 'A8A29E', accent: 'D4AF37', secondary: 'F59E0B', light: '292524', lighter: '1C1917' },
midnight: { bg: '0F172A', title: 'F8FAFC', text: 'CBD5E1', accent: '38BDF8', secondary: '60A5FA', light: '1E293B', lighter: '0F172A' },
classic: { bg: 'FFFFFF', title: '1F2937', text: '4B5563', accent: '059669', secondary: '10B981', light: 'ECFDF5', lighter: 'F0FDF4' }
};
var CHART_COLORS = {
ocean: ['0077B6', '00B4D8', '90E0EF', '48CAE4', '023E8A', '0096C7', 'ADE8F4'],
sunset: ['E85D04', 'F48C06', 'FAA307', 'FFBA08', 'DC2F02', 'E36414', 'F77F00'],
purple: ['7C3AED', 'A78BFA', 'C4B5FD', '8B5CF6', '6D28D9', '5B21B6', 'DDD6FE'],
luxury: ['D4AF37', 'F59E0B', 'FBBF24', 'FFD700', 'B8860B', 'D97706', 'FCD34D'],
midnight: ['38BDF8', '60A5FA', '93C5FD', '2563EB', '1D4ED8', '3B82F6', 'BFDBFE'],
classic: ['059669', '10B981', '34D399', '6EE7B7', '047857', '065F46', 'A7F3D0']
};
var CHART_RULES = [
{ type: 'pie', keys: ['饼图', '饼状图', '占比', '比例', '份额', '构成', '组成', '百分比', '比重', 'pie', 'proportion', 'share'] },
{ type: 'line', keys: ['折线', '折线图', '趋势', '增长', '变化', '走势', '曲线', '时间', '月度', '季度', '年度', 'line', 'trend'] },
{ type: 'bar', keys: ['柱状', '柱状图', '柱形', '柱形图', '排名', 'top', '对比', '比较', '分布', '销售额', '金额', '数量', '业绩', '产量', '营收', '收入', 'bar', 'column', 'chart'] }
];
var ENDING_KEYWORDS = ['感谢', '谢谢', 'thank', 'thanks', 'q&a', 'q & a', '问答', '结束', 'the end', '再见', '联系方式', '联系我们'];
// ══════════════════════════════════════════════════════
// 2. Utility Functions
// ══════════════════════════════════════════════════════
function stripInlineMarkdown(text) {
if (!text) return '';
return text
.replace(/\*\*(.+?)\*\*/g, '$1')
.replace(/__(.+?)__/g, '$1')
.replace(/\*(.+?)\*/g, '$1')
.replace(/_(.+?)_/g, '$1')
.replace(/~~(.+?)~~/g, '$1')
.replace(/`(.+?)`/g, '$1')
.replace(/\[(.+?)\]\(.+?\)/g, '$1')
.trim();
}
function isTableSeparator(line) {
var inner = line.replace(/^\|/, '').replace(/\|$/, '');
var cells = inner.split('|');
return cells.length > 0 && cells.every(function(c) {
return /^\s*:?-{2,}:?\s*$/.test(c);
});
}
function isEndingSlide(title) {
if (!title) return false;
var lower = title.toLowerCase().trim();
return ENDING_KEYWORDS.some(function(k) { return lower.indexOf(k) !== -1; });
}
function parseNumericCell(raw) {
if (!raw) return NaN;
var cleaned = raw
.replace(/[,]/g, '')
.replace(/[%]/g, '')
.replace(/[¥¥$€£₩]/g, '')
.replace(/[元万亿千百十个份人次件台套组年月日号]/g, '')
.replace(/[()\s]/g, '')
.trim();
return parseFloat(cleaned);
}
function ensureColors(colors, needed) {
var result = [];
for (var i = 0; i < needed; i++) {
result.push(colors[i % colors.length]);
}
return result;
}
// ══════════════════════════════════════════════════════
// 3. Markdown Parser
// ══════════════════════════════════════════════════════
function parse(text) {
var slides = [];
var lines = text.split('\n');
var current = null;
var inCode = false;
var codeContent = [];
for (var li = 0; li < lines.length; li++) {
var line = lines[li];
var trimmed = line.trim();
if (trimmed.indexOf('```') === 0) {
if (inCode) {
if (current) {
current.content.push({ type: 'code', code: codeContent.join('\n') });
}
codeContent = [];
}
inCode = !inCode;
continue;
}
if (inCode) {
codeContent.push(line);
continue;
}
if (!trimmed) continue;
if (/^# (?!#)/.test(trimmed)) {
if (current) slides.push(current);
current = {
type: 'cover',
title: stripInlineMarkdown(trimmed.slice(2)),
subtitle: '',
content: []
};
}
else if (/^## (?!#)/.test(trimmed)) {
if (current) slides.push(current);
var title = stripInlineMarkdown(trimmed.slice(3));
current = {
type: isEndingSlide(title) ? 'ending' : 'content',
title: title,
content: []
};
}
else if (trimmed.indexOf('### ') === 0) {
if (!current) current = { type: 'content', title: '', content: [] };
current.content.push({ type: 'h3', text: stripInlineMarkdown(trimmed.slice(4)) });
}
else if (/^[-*]\s/.test(trimmed)) {
if (!current) current = { type: 'content', title: '', content: [] };
var itemText = stripInlineMarkdown(trimmed.replace(/^[-*]\s+/, ''));
var last = current.content[current.content.length - 1];
if (last && last.type === 'list') {
last.items.push(itemText);
} else {
current.content.push({ type: 'list', items: [itemText] });
}
}
else if (/^\d+\.\s/.test(trimmed)) {
if (!current) current = { type: 'content', title: '', content: [] };
var oItemText = stripInlineMarkdown(trimmed.replace(/^\d+\.\s+/, ''));
var oLast = current.content[current.content.length - 1];
if (oLast && oLast.type === 'olist') {
oLast.items.push(oItemText);
} else {
current.content.push({ type: 'olist', items: [oItemText] });
}
}
else if (trimmed.charAt(0) === '|') {
if (isTableSeparator(trimmed)) continue;
if (!current) current = { type: 'content', title: '', content: [] };
var inner = trimmed.replace(/^\|/, '').replace(/\|$/, '');
var cells = inner.split('|').map(function(c) { return c.trim(); });
if (cells.length === 0) continue;
var tLast = current.content[current.content.length - 1];
if (tLast && tLast.type === 'table') {
tLast.rows.push(cells);
} else {
current.content.push({ type: 'table', rows: [cells] });
}
}
else if (trimmed.charAt(0) === '>') {
if (!current) current = { type: 'content', title: '', content: [] };
var quoteText = stripInlineMarkdown(trimmed.replace(/^>\s*/, ''));
var qLast = current.content[current.content.length - 1];
if (qLast && qLast.type === 'quote') {
qLast.lines.push(quoteText);
} else {
current.content.push({ type: 'quote', lines: [quoteText] });
}
}
else {
if (!current) current = { type: 'content', title: '', content: [] };
var cleaned = stripInlineMarkdown(trimmed);
if (current.type === 'cover' && !current.subtitle) {
current.subtitle = cleaned;
} else {
current.content.push({ type: 'text', text: cleaned });
}
}
}
if (inCode && codeContent.length > 0 && current) {
current.content.push({ type: 'code', code: codeContent.join('\n') });
}
if (current) slides.push(current);
return slides;
}
// ══════════════════════════════════════════════════════
// 4. Chart Detection
// ══════════════════════════════════════════════════════
function extractSeries(table) {
if (!table.rows || table.rows.length < 2) return null;
var headers = table.rows[0];
var dataRows = table.rows.slice(1);
if (headers.length < 2 || dataRows.length === 0) return null;
var labels = dataRows.map(function(r) { return (r[0] || '').trim(); });
var series = [];
for (var col = 1; col < headers.length; col++) {
var values = dataRows.map(function(r) { return parseNumericCell(r[col]); });
if (values.every(function(v) { return !isNaN(v) && isFinite(v); })) {
series.push({
name: (headers[col] || '').trim() || ('Series' + col),
labels: labels,
values: values
});
}
}
return series.length > 0 ? series : null;
}
function detectChart(table, slide, tableIndex) {
var series = extractSeries(table);
if (!series) return null;
var labels = series[0].labels;
var hints = [];
if (slide.title) hints.push(slide.title.toLowerCase());
var hintIndex = -1;
for (var j = tableIndex - 1; j >= 0; j--) {
var item = slide.content[j];
if (item.type === 'h3') {
hints.push(item.text.toLowerCase());
hintIndex = j;
break;
}
if (item.type === 'text') {
hints.push(item.text.toLowerCase());
}
}
hints.push(table.rows[0].map(function(h) { return (h || '').toLowerCase(); }).join(' '));
var combined = hints.join(' ');
for (var ri = 0; ri < CHART_RULES.length; ri++) {
var rule = CHART_RULES[ri];
if (rule.keys.some(function(k) { return combined.indexOf(k) !== -1; })) {
return { type: rule.type, series: series, labels: labels, hintIndex: hintIndex };
}
}
var vals = series[0].values;
var sum = vals.reduce(function(a, b) { return a + b; }, 0);
var count = vals.length;
if (count >= 2 && count <= 12 && sum >= 80 && sum <= 120) {
return { type: 'pie', series: series, labels: labels, hintIndex: hintIndex };
}
if (count >= 2) {
return { type: 'bar', series: series, labels: labels, hintIndex: hintIndex };
}
return null;
}
// ══════════════════════════════════════════════════════
// 5. Chart Type Resolution (multi-version compat)
// ══════════════════════════════════════════════════════
function getChartType(pres, name) {
var MAP = { pie: 'PIE', line: 'LINE', bar: 'BAR' };
var key = MAP[name];
if (!key) return name;
if (pres.charts && pres.charts[key] !== undefined) return pres.charts[key];
if (pres.ChartType && pres.ChartType[key] !== undefined) return pres.ChartType[key];
if (pres.ChartType && pres.ChartType[name] !== undefined) return pres.ChartType[name];
return name;
}
// ══════════════════════════════════════════════════════
// 6. Chart Rendering
// ══════════════════════════════════════════════════════
function addChartToSlide(s, pres, chartData, colors, t, layout) {
var lx = (layout && layout.x) || 0.5;
var ly = (layout && layout.y) || 1.4;
var lw = (layout && layout.w) || 9;
var lh = (layout && layout.h) || 3.8;
var chartType = getChartType(pres, chartData.type);
var isPie = chartData.type === 'pie';
var isLine = chartData.type === 'line';
var data = isPie ? [chartData.series[0]] : chartData.series;
var needed = isPie ? data[0].values.length : Math.max(data[0].values.length, data.length);
var clrs = ensureColors(colors, needed);
var opts = {
x: lx, y: ly, w: lw, h: lh,
chartColors: clrs,
showLegend: true,
legendPos: isPie ? 'r' : 'b',
legendFontSize: 9,
legendColor: t.text,
showTitle: false
};
if (isPie) {
opts.showPercent = true;
opts.showValue = false;
opts.dataLabelColor = t.text;
opts.dataLabelFontSize = 10;
} else if (isLine) {
opts.lineSize = 2;
opts.showMarker = true;
opts.markerSize = 6;
opts.catAxisLabelColor = t.text;
opts.catAxisLabelFontSize = 9;
opts.valAxisLabelColor = t.text;
opts.valAxisLabelFontSize = 9;
opts.showValue = true;
opts.dataLabelColor = t.text;
opts.dataLabelFontSize = 8;
opts.dataLabelPosition = 'outEnd';
} else {
opts.barDir = 'col';
opts.barGapWidthPct = 80;
opts.catAxisLabelColor = t.text;
opts.catAxisLabelFontSize = 10;
opts.valAxisLabelColor = t.text;
opts.valAxisLabelFontSize = 9;
opts.showValue = true;
opts.dataLabelColor = t.text;
opts.dataLabelFontSize = 9;
opts.dataLabelPosition = 'outEnd';
}
s.addChart(chartType, data, opts);
}
// ══════════════════════════════════════════════════════
// 7. Content Rendering
// ══════════════════════════════════════════════════════
function renderTable(s, tableItem, t, startY, maxY, startX, totalW) {
if (!tableItem.rows || tableItem.rows.length === 0) return startY;
var colCount = Math.max.apply(null, tableItem.rows.map(function(r) { return r.length; }));
var cw = totalW / colCount;
var rowH = 0.35;
for (var r = 0; r < tableItem.rows.length; r++) {
var ry = startY + r * rowH;
if (ry + rowH > maxY) break;
if (r === 0) {
s.addShape('rect', {
x: startX - 0.05, y: ry, w: colCount * cw + 0.1, h: rowH,
fill: { color: t.light }
});
} else if (r % 2 === 0) {
s.addShape('rect', {
x: startX - 0.05, y: ry, w: colCount * cw + 0.1, h: rowH,
fill: { color: t.lighter || t.bg }
});
}
for (var c = 0; c < tableItem.rows[r].length; c++) {
s.addText(tableItem.rows[r][c], {
x: startX + c * cw, y: ry, w: cw - 0.05, h: rowH,
fontSize: 10, color: r === 0 ? t.title : t.text,
fontFace: 'Arial', bold: r === 0, valign: 'middle'
});
}
}
var renderedRows = Math.min(tableItem.rows.length, Math.floor((maxY - startY) / rowH));
return startY + renderedRows * rowH + 0.2;
}
function renderContent(s, content, t, opts) {
var startY = (opts && opts.startY) || 1.4;
var maxY = (opts && opts.maxY) || 5.0;
var x = (opts && opts.x) || 0.4;
var w = (opts && opts.w) || 8.5;
var y = startY;
for (var idx = 0; idx < content.length; idx++) {
var item = content[idx];
if (y > maxY) break;
if (item.type === 'h3') {
s.addText(item.text, {
x: x, y: y, w: w, h: 0.4,
fontSize: 16, color: t.title, fontFace: 'Arial', bold: true
});
y += 0.5;
}
else if (item.type === 'list') {
for (var li = 0; li < item.items.length; li++) {
if (y > maxY) break;
s.addShape('ellipse', {
x: x + 0.02, y: y + 0.13, w: 0.09, h: 0.09,
fill: { color: t.accent }
});
s.addText(item.items[li], {
x: x + 0.22, y: y, w: w - 0.3, h: 0.35,
fontSize: 13, color: t.text, fontFace: 'Arial'
});
y += 0.42;
}
}
else if (item.type === 'olist') {
for (var oi = 0; oi < item.items.length; oi++) {
if (y > maxY) break;
s.addShape('ellipse', {
x: x, y: y + 0.05, w: 0.22, h: 0.22,
fill: { color: t.accent }
});
s.addText(String(oi + 1), {
x: x, y: y + 0.05, w: 0.22, h: 0.22,
fontSize: 9, color: 'FFFFFF', fontFace: 'Arial', bold: true,
align: 'center', valign: 'middle'
});
s.addText(item.items[oi], {
x: x + 0.3, y: y, w: w - 0.4, h: 0.35,
fontSize: 13, color: t.text, fontFace: 'Arial'
});
y += 0.42;
}
}
else if (item.type === 'code') {
var lineCount = item.code.split('\n').length;
var ch = Math.min(2.5, lineCount * 0.22 + 0.3);
s.addShape('roundRect', {
x: x - 0.1, y: y, w: w + 0.2, h: ch,
fill: { color: '1E1E1E' }, rectRadius: 0.05
});
s.addText(item.code, {
x: x + 0.05, y: y + 0.1, w: w - 0.1, h: ch - 0.2,
fontSize: 10, color: 'D4D4D4', fontFace: 'Consolas', valign: 'top'
});
y += ch + 0.2;
}
else if (item.type === 'quote') {
var quoteText = item.lines.join('\n');
var qLines = item.lines.length;
var qh = Math.min(2.0, qLines * 0.25 + 0.2);
s.addShape('rect', {
x: x, y: y, w: 0.05, h: qh,
fill: { color: t.accent }
});
s.addShape('rect', {
x: x + 0.05, y: y, w: w - 0.05, h: qh,
fill: { color: t.light }
});
s.addText(quoteText, {
x: x + 0.2, y: y + 0.05, w: w - 0.3, h: qh - 0.1,
fontSize: 12, color: t.text, fontFace: 'Arial', italic: true, valign: 'top'
});
y += qh + 0.15;
}
else if (item.type === 'table') {
y = renderTable(s, item, t, y, maxY, x, w);
}
else if (item.type === 'text') {
s.addText(item.text, {
x: x, y: y, w: w, h: 0.35,
fontSize: 12, color: t.text, fontFace: 'Arial'
});
y += 0.4;
}
}
return y;
}
// ══════════════════════════════════════════════════════
// 8. Slide Renderers
// ══════════════════════════════════════════════════════
function addDecorations(s, t) {
s.addShape('rect', { x: 0, y: 0, w: 10, h: 0.12, fill: { color: t.accent } });
s.addShape('rect', { x: 0, y: 0, w: 0.10, h: 5.625, fill: { color: t.accent } });
}
function renderCoverSlide(s, slide, t) {
s.addShape('rect', { x: 0.4, y: 1.3, w: 0.06, h: 1.6, fill: { color: t.accent } });
s.addText(slide.title, {
x: 0.7, y: 1.3, w: 8.5, h: 1.4,
fontSize: 40, color: t.title, fontFace: 'Arial', bold: true, valign: 'middle'
});
if (slide.subtitle) {
s.addText(slide.subtitle, {
x: 0.7, y: 3.0, w: 8, h: 0.8,
fontSize: 18, color: t.text, fontFace: 'Arial'
});
}
s.addShape('rect', { x: 0.7, y: 4.2, w: 2.5, h: 0.03, fill: { color: t.secondary } });
if (slide.content && slide.content.length > 0) {
renderContent(s, slide.content, t, { startY: 4.5, maxY: 5.3 });
}
}
function renderEndingSlide(s, slide, t) {
s.addShape('rect', { x: 1.5, y: 1.0, w: 7, h: 3.5, fill: { color: t.light } });
s.addShape('rect', { x: 2.5, y: 1.3, w: 5, h: 0.04, fill: { color: t.accent } });
s.addShape('rect', { x: 2.5, y: 4.2, w: 5, h: 0.04, fill: { color: t.accent } });
s.addText(slide.title, {
x: 1, y: 1.5, w: 8, h: 1.5,
fontSize: 44, color: t.title, fontFace: 'Arial', bold: true,
align: 'center', valign: 'middle'
});
if (slide.content && slide.content.length > 0) {
var texts = [];
for (var i = 0; i < slide.content.length; i++) {
var ci = slide.content[i];
if (ci.type === 'text') texts.push(ci.text);
if (ci.type === 'list') texts = texts.concat(ci.items);
if (ci.type === 'olist') texts = texts.concat(ci.items);
}
if (texts.length > 0) {
s.addText(texts.join('\n'), {
x: 2, y: 3.0, w: 6, h: 1.2,
fontSize: 14, color: t.text, fontFace: 'Arial',
align: 'center', valign: 'top'
});
}
}
}
function renderContentSlide(s, pres, slide, slideIndex, totalSlides, colors, t) {
s.addShape('rect', { x: 0, y: 0.15, w: 10, h: 1.0, fill: { color: t.light } });
s.addText(slide.title, {
x: 0.5, y: 0.25, w: 9, h: 0.8,
fontSize: 26, color: t.title, fontFace: 'Arial', bold: true, valign: 'middle'
});
var chartData = null;
var chartTableIdx = -1;
for (var ci = 0; ci < slide.content.length; ci++) {
if (slide.content[ci].type === 'table') {
var detected = detectChart(slide.content[ci], slide, ci);
if (detected) {
chartData = detected;
chartTableIdx = ci;
break;
}
}
}
if (chartData) {
var remaining = slide.content.filter(function(_, idx) {
return idx !== chartTableIdx && idx !== chartData.hintIndex;
});
var hasExtra = remaining.length > 0;
try {
var isPie = chartData.type === 'pie';
var chartW = hasExtra ? (isPie ? 5.5 : 6.0) : (isPie ? 6.5 : 9.0);
addChartToSlide(s, pres, chartData, colors, t, {
x: 0.5, y: 1.4, w: chartW, h: 3.8
});
if (hasExtra) {
var sideX = chartW + 0.8;
var sideW = 10 - sideX - 0.3;
if (sideW > 1.5) {
renderContent(s, remaining, t, { startY: 1.5, maxY: 5.0, x: sideX, w: sideW });
}
}
} catch (err) {
renderContent(s, slide.content, t);
}
} else {
renderContent(s, slide.content, t);
}
s.addText((slideIndex + 1) + ' / ' + totalSlides, {
x: 8.5, y: 5.3, w: 1.3, h: 0.25,
fontSize: 9, color: t.secondary, fontFace: 'Arial', align: 'right'
});
}
// ══════════════════════════════════════════════════════
// 9. Main Generator
// ══════════════════════════════════════════════════════
function createPPTX(markdownText, options) {
options = options || {};
var themeName = options.theme || 'ocean';
var t = THEMES[themeName] || THEMES.ocean;
var colors = (CHART_COLORS[themeName] || CHART_COLORS.ocean).slice();
var pres = new PptxGenJS();
pres.layout = 'LAYOUT_16x9';
var slides = parse(markdownText);
if (slides.length === 0) {
var emptySlide = pres.addSlide();
emptySlide.background = { color: t.bg };
emptySlide.addText('(empty content)', {
x: 1, y: 2, w: 8, h: 1.5,
fontSize: 24, color: t.text, fontFace: 'Arial', align: 'center', valign: 'middle'
});
return pres;
}
for (var si = 0; si < slides.length; si++) {
var slide = slides[si];
var s = pres.addSlide();
s.background = { color: t.bg };
addDecorations(s, t);
if (slide.type === 'cover') {
renderCoverSlide(s, slide, t);
} else if (slide.type === 'ending') {
renderEndingSlide(s, slide, t);
s.addText((si + 1) + ' / ' + slides.length, {
x: 8.5, y: 5.3, w: 1.3, h: 0.25,
fontSize: 9, color: t.secondary, fontFace: 'Arial', align: 'right'
});
} else {
renderContentSlide(s, pres, slide, si, slides.length, colors, t);
}
}
return pres;
}
// ══════════════════════════════════════════════════════
// 10. Module Export
// ══════════════════════════════════════════════════════
module.exports = {
createPPTX: createPPTX,
parse: parse,
THEMES: THEMES,
CHART_COLORS: CHART_COLORS
};