@ncukondo/slide-generation 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/README_ja.md +46 -0
- package/dist/cli/index.js +999 -136
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +21 -7
- package/dist/index.js +264 -91
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/special/bibliography.yaml +4 -4
package/dist/cli/index.js
CHANGED
|
@@ -923,20 +923,20 @@ var ReferenceManager = class {
|
|
|
923
923
|
return items[0] || null;
|
|
924
924
|
}
|
|
925
925
|
/**
|
|
926
|
-
* Get multiple references by IDs
|
|
926
|
+
* Get multiple references by IDs using ref export for better performance
|
|
927
927
|
*/
|
|
928
928
|
async getByIds(ids) {
|
|
929
929
|
if (ids.length === 0) {
|
|
930
930
|
return /* @__PURE__ */ new Map();
|
|
931
931
|
}
|
|
932
|
-
const
|
|
933
|
-
const
|
|
934
|
-
|
|
932
|
+
const idsArg = ids.map((id) => `"${id}"`).join(" ");
|
|
933
|
+
const result = await this.execCommand(
|
|
934
|
+
`${this.command} export ${idsArg}`
|
|
935
|
+
);
|
|
936
|
+
const items = this.parseJSON(result);
|
|
935
937
|
const map = /* @__PURE__ */ new Map();
|
|
936
|
-
for (const item of
|
|
937
|
-
|
|
938
|
-
map.set(item.id, item);
|
|
939
|
-
}
|
|
938
|
+
for (const item of items) {
|
|
939
|
+
map.set(item.id, item);
|
|
940
940
|
}
|
|
941
941
|
return map;
|
|
942
942
|
}
|
|
@@ -1080,6 +1080,82 @@ var CitationExtractor = class {
|
|
|
1080
1080
|
}
|
|
1081
1081
|
};
|
|
1082
1082
|
|
|
1083
|
+
// src/references/utils.ts
|
|
1084
|
+
var JAPANESE_PATTERN = /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/;
|
|
1085
|
+
function isJapaneseAuthors(authors) {
|
|
1086
|
+
if (!authors || authors.length === 0) {
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
return JAPANESE_PATTERN.test(authors[0].family);
|
|
1090
|
+
}
|
|
1091
|
+
function getYear(item) {
|
|
1092
|
+
const dateParts = item.issued?.["date-parts"];
|
|
1093
|
+
if (dateParts && dateParts[0] && dateParts[0][0]) {
|
|
1094
|
+
return dateParts[0][0];
|
|
1095
|
+
}
|
|
1096
|
+
return 0;
|
|
1097
|
+
}
|
|
1098
|
+
function getIdentifier(item) {
|
|
1099
|
+
if (item.PMID) {
|
|
1100
|
+
return `PMID: ${item.PMID}`;
|
|
1101
|
+
}
|
|
1102
|
+
if (item.DOI) {
|
|
1103
|
+
return `DOI: ${item.DOI}`;
|
|
1104
|
+
}
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
function getFirstAuthorFamily(item) {
|
|
1108
|
+
return item.author?.[0]?.family || "";
|
|
1109
|
+
}
|
|
1110
|
+
function formatAuthorsFull(authors, isJapanese) {
|
|
1111
|
+
if (!authors || authors.length === 0) {
|
|
1112
|
+
return "Unknown";
|
|
1113
|
+
}
|
|
1114
|
+
if (isJapanese) {
|
|
1115
|
+
return authors.map((a) => `${a.family}${a.given || ""}`).join(", ");
|
|
1116
|
+
}
|
|
1117
|
+
if (authors.length === 1) {
|
|
1118
|
+
const a = authors[0];
|
|
1119
|
+
const initial = a.given ? `${a.given.charAt(0)}.` : "";
|
|
1120
|
+
return `${a.family}, ${initial}`;
|
|
1121
|
+
}
|
|
1122
|
+
const formatted = authors.map((a, i) => {
|
|
1123
|
+
const initial = a.given ? `${a.given.charAt(0)}.` : "";
|
|
1124
|
+
if (i === authors.length - 1) {
|
|
1125
|
+
return `& ${a.family}, ${initial}`;
|
|
1126
|
+
}
|
|
1127
|
+
return `${a.family}, ${initial}`;
|
|
1128
|
+
});
|
|
1129
|
+
return formatted.join(", ").replace(", &", ", &");
|
|
1130
|
+
}
|
|
1131
|
+
function formatFullEntry(item) {
|
|
1132
|
+
const parts = [];
|
|
1133
|
+
const japanese = isJapaneseAuthors(item.author);
|
|
1134
|
+
const authors = formatAuthorsFull(item.author, japanese);
|
|
1135
|
+
parts.push(authors);
|
|
1136
|
+
const year = getYear(item);
|
|
1137
|
+
parts.push(`(${year}).`);
|
|
1138
|
+
if (item.title) {
|
|
1139
|
+
parts.push(`${item.title}.`);
|
|
1140
|
+
}
|
|
1141
|
+
if (item["container-title"]) {
|
|
1142
|
+
const journal = japanese ? item["container-title"] : `*${item["container-title"]}*`;
|
|
1143
|
+
let location = "";
|
|
1144
|
+
if (item.volume) {
|
|
1145
|
+
location = item.issue ? `${item.volume}(${item.issue})` : item.volume;
|
|
1146
|
+
}
|
|
1147
|
+
if (item.page) {
|
|
1148
|
+
location = location ? `${location}, ${item.page}` : item.page;
|
|
1149
|
+
}
|
|
1150
|
+
parts.push(location ? `${journal}, ${location}.` : `${journal}.`);
|
|
1151
|
+
}
|
|
1152
|
+
const identifier = getIdentifier(item);
|
|
1153
|
+
if (identifier) {
|
|
1154
|
+
parts.push(identifier);
|
|
1155
|
+
}
|
|
1156
|
+
return parts.join(" ");
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1083
1159
|
// src/references/formatter.ts
|
|
1084
1160
|
var DEFAULT_CONFIG = {
|
|
1085
1161
|
author: {
|
|
@@ -1094,7 +1170,6 @@ var DEFAULT_CONFIG = {
|
|
|
1094
1170
|
multiSep: "), ("
|
|
1095
1171
|
}
|
|
1096
1172
|
};
|
|
1097
|
-
var JAPANESE_PATTERN = /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/;
|
|
1098
1173
|
var CITATION_BRACKET_PATTERN2 = /\[(@[\w-]+(?:,\s*[^;\]]+)?(?:;\s*@[\w-]+(?:,\s*[^;\]]+)?)*)\]/g;
|
|
1099
1174
|
var SINGLE_CITATION_PATTERN2 = /@([\w-]+)(?:,\s*([^;\]]+))?/g;
|
|
1100
1175
|
var CitationFormatter = class {
|
|
@@ -1106,6 +1181,18 @@ var CitationFormatter = class {
|
|
|
1106
1181
|
};
|
|
1107
1182
|
}
|
|
1108
1183
|
config;
|
|
1184
|
+
availabilityChecked = false;
|
|
1185
|
+
isAvailable = false;
|
|
1186
|
+
/**
|
|
1187
|
+
* Check if reference-manager is available (cached)
|
|
1188
|
+
*/
|
|
1189
|
+
async checkAvailability() {
|
|
1190
|
+
if (!this.availabilityChecked) {
|
|
1191
|
+
this.isAvailable = await this.manager.isAvailable();
|
|
1192
|
+
this.availabilityChecked = true;
|
|
1193
|
+
}
|
|
1194
|
+
return this.isAvailable;
|
|
1195
|
+
}
|
|
1109
1196
|
/**
|
|
1110
1197
|
* Format an inline citation
|
|
1111
1198
|
* e.g., "(Smith et al., 2024; PMID: 12345678)"
|
|
@@ -1125,13 +1212,16 @@ var CitationFormatter = class {
|
|
|
1125
1212
|
if (!item) {
|
|
1126
1213
|
return `[${id}]`;
|
|
1127
1214
|
}
|
|
1128
|
-
return
|
|
1215
|
+
return formatFullEntry(item);
|
|
1129
1216
|
}
|
|
1130
1217
|
/**
|
|
1131
1218
|
* Expand all citations in text
|
|
1132
1219
|
* e.g., "[@smith2024]" -> "(Smith et al., 2024; PMID: 12345678)"
|
|
1133
1220
|
*/
|
|
1134
1221
|
async expandCitations(text) {
|
|
1222
|
+
if (!await this.checkAvailability()) {
|
|
1223
|
+
return text;
|
|
1224
|
+
}
|
|
1135
1225
|
const ids = /* @__PURE__ */ new Set();
|
|
1136
1226
|
CITATION_BRACKET_PATTERN2.lastIndex = 0;
|
|
1137
1227
|
let match;
|
|
@@ -1186,117 +1276,102 @@ var CitationFormatter = class {
|
|
|
1186
1276
|
sortedItems = ids.map((id) => items.get(id)).filter((item) => item !== void 0);
|
|
1187
1277
|
} else if (sort === "author") {
|
|
1188
1278
|
sortedItems = [...items.values()].sort((a, b) => {
|
|
1189
|
-
const authorA =
|
|
1190
|
-
const authorB =
|
|
1279
|
+
const authorA = getFirstAuthorFamily(a);
|
|
1280
|
+
const authorB = getFirstAuthorFamily(b);
|
|
1191
1281
|
return authorA.localeCompare(authorB);
|
|
1192
1282
|
});
|
|
1193
1283
|
} else {
|
|
1194
1284
|
sortedItems = [...items.values()].sort((a, b) => {
|
|
1195
|
-
const yearA =
|
|
1196
|
-
const yearB =
|
|
1285
|
+
const yearA = getYear(a);
|
|
1286
|
+
const yearB = getYear(b);
|
|
1197
1287
|
return yearA - yearB;
|
|
1198
1288
|
});
|
|
1199
1289
|
}
|
|
1200
|
-
return sortedItems.map((item) =>
|
|
1290
|
+
return sortedItems.map((item) => formatFullEntry(item));
|
|
1201
1291
|
}
|
|
1202
1292
|
formatInlineItem(item) {
|
|
1203
1293
|
const author = this.formatAuthorInline(item.author);
|
|
1204
|
-
const year =
|
|
1205
|
-
const identifier =
|
|
1294
|
+
const year = getYear(item);
|
|
1295
|
+
const identifier = getIdentifier(item);
|
|
1206
1296
|
if (identifier) {
|
|
1207
1297
|
return `(${author}, ${year}; ${identifier})`;
|
|
1208
1298
|
}
|
|
1209
1299
|
return `(${author}, ${year})`;
|
|
1210
1300
|
}
|
|
1211
|
-
formatFullItem(item) {
|
|
1212
|
-
const parts = [];
|
|
1213
|
-
const isJapanese = this.isJapaneseAuthors(item.author);
|
|
1214
|
-
const authors = this.formatAuthorsFull(item.author, isJapanese);
|
|
1215
|
-
parts.push(authors);
|
|
1216
|
-
const year = this.getYear(item);
|
|
1217
|
-
parts.push(`(${year}).`);
|
|
1218
|
-
if (item.title) {
|
|
1219
|
-
parts.push(`${item.title}.`);
|
|
1220
|
-
}
|
|
1221
|
-
if (item["container-title"]) {
|
|
1222
|
-
const journal = isJapanese ? item["container-title"] : `*${item["container-title"]}*`;
|
|
1223
|
-
let location = "";
|
|
1224
|
-
if (item.volume) {
|
|
1225
|
-
location = item.issue ? `${item.volume}(${item.issue})` : item.volume;
|
|
1226
|
-
}
|
|
1227
|
-
if (item.page) {
|
|
1228
|
-
location = location ? `${location}, ${item.page}` : item.page;
|
|
1229
|
-
}
|
|
1230
|
-
parts.push(location ? `${journal}, ${location}.` : `${journal}.`);
|
|
1231
|
-
}
|
|
1232
|
-
const identifier = this.getIdentifier(item);
|
|
1233
|
-
if (identifier) {
|
|
1234
|
-
parts.push(identifier);
|
|
1235
|
-
}
|
|
1236
|
-
return parts.join(" ");
|
|
1237
|
-
}
|
|
1238
1301
|
formatAuthorInline(authors) {
|
|
1239
1302
|
if (!authors || authors.length === 0) {
|
|
1240
1303
|
return "Unknown";
|
|
1241
1304
|
}
|
|
1242
|
-
const
|
|
1305
|
+
const japanese = isJapaneseAuthors(authors);
|
|
1243
1306
|
const { etAl, etAlJa, separatorJa } = this.config.author;
|
|
1244
1307
|
const firstAuthor = authors[0];
|
|
1245
1308
|
if (authors.length === 1) {
|
|
1246
1309
|
return firstAuthor.family;
|
|
1247
1310
|
}
|
|
1248
1311
|
if (authors.length === 2) {
|
|
1249
|
-
const separator =
|
|
1312
|
+
const separator = japanese ? separatorJa : " & ";
|
|
1250
1313
|
return `${firstAuthor.family}${separator}${authors[1].family}`;
|
|
1251
1314
|
}
|
|
1252
|
-
const suffix =
|
|
1315
|
+
const suffix = japanese ? etAlJa : ` ${etAl}`;
|
|
1253
1316
|
return `${firstAuthor.family}${suffix}`;
|
|
1254
1317
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
}
|
|
1262
|
-
if (authors.length === 1) {
|
|
1263
|
-
const a = authors[0];
|
|
1264
|
-
const initial = a.given ? `${a.given.charAt(0)}.` : "";
|
|
1265
|
-
return `${a.family}, ${initial}`;
|
|
1266
|
-
}
|
|
1267
|
-
const formatted = authors.map((a, i) => {
|
|
1268
|
-
const initial = a.given ? `${a.given.charAt(0)}.` : "";
|
|
1269
|
-
if (i === authors.length - 1) {
|
|
1270
|
-
return `& ${a.family}, ${initial}`;
|
|
1271
|
-
}
|
|
1272
|
-
return `${a.family}, ${initial}`;
|
|
1273
|
-
});
|
|
1274
|
-
return formatted.join(", ").replace(", &", ", &");
|
|
1318
|
+
};
|
|
1319
|
+
|
|
1320
|
+
// src/references/bibliography.ts
|
|
1321
|
+
var BibliographyGenerator = class {
|
|
1322
|
+
constructor(manager) {
|
|
1323
|
+
this.manager = manager;
|
|
1275
1324
|
}
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1325
|
+
/**
|
|
1326
|
+
* Generate bibliography entries from citation IDs
|
|
1327
|
+
*/
|
|
1328
|
+
async generate(citationIds, options = {}) {
|
|
1329
|
+
const { sort = "citation-order" } = options;
|
|
1330
|
+
if (citationIds.length === 0) {
|
|
1331
|
+
return { entries: [], items: [], missing: [] };
|
|
1279
1332
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1333
|
+
const uniqueIds = [...new Set(citationIds)];
|
|
1334
|
+
const itemsMap = await this.manager.getByIds(uniqueIds);
|
|
1335
|
+
const missing = [];
|
|
1336
|
+
const foundItems = [];
|
|
1337
|
+
for (const id of uniqueIds) {
|
|
1338
|
+
const item = itemsMap.get(id);
|
|
1339
|
+
if (item) {
|
|
1340
|
+
foundItems.push(item);
|
|
1341
|
+
} else {
|
|
1342
|
+
missing.push(id);
|
|
1343
|
+
}
|
|
1289
1344
|
}
|
|
1290
|
-
|
|
1345
|
+
const sortedItems = this.sortItems(foundItems, sort);
|
|
1346
|
+
const entries = sortedItems.map((item) => formatFullEntry(item));
|
|
1347
|
+
return {
|
|
1348
|
+
entries,
|
|
1349
|
+
items: sortedItems,
|
|
1350
|
+
missing
|
|
1351
|
+
};
|
|
1291
1352
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1353
|
+
/**
|
|
1354
|
+
* Sort items according to the specified order
|
|
1355
|
+
*/
|
|
1356
|
+
sortItems(items, sort) {
|
|
1357
|
+
switch (sort) {
|
|
1358
|
+
case "citation-order":
|
|
1359
|
+
return items;
|
|
1360
|
+
case "author":
|
|
1361
|
+
return [...items].sort((a, b) => {
|
|
1362
|
+
const authorA = getFirstAuthorFamily(a);
|
|
1363
|
+
const authorB = getFirstAuthorFamily(b);
|
|
1364
|
+
return authorA.localeCompare(authorB);
|
|
1365
|
+
});
|
|
1366
|
+
case "year":
|
|
1367
|
+
return [...items].sort((a, b) => {
|
|
1368
|
+
const yearA = getYear(a);
|
|
1369
|
+
const yearB = getYear(b);
|
|
1370
|
+
return yearA - yearB;
|
|
1371
|
+
});
|
|
1372
|
+
default:
|
|
1373
|
+
return items;
|
|
1298
1374
|
}
|
|
1299
|
-
return null;
|
|
1300
1375
|
}
|
|
1301
1376
|
};
|
|
1302
1377
|
|
|
@@ -1335,6 +1410,7 @@ var Pipeline = class {
|
|
|
1335
1410
|
}
|
|
1336
1411
|
}
|
|
1337
1412
|
);
|
|
1413
|
+
this.bibliographyGenerator = new BibliographyGenerator(this.referenceManager);
|
|
1338
1414
|
this.transformer = new Transformer(
|
|
1339
1415
|
this.templateEngine,
|
|
1340
1416
|
this.templateLoader,
|
|
@@ -1351,6 +1427,7 @@ var Pipeline = class {
|
|
|
1351
1427
|
referenceManager;
|
|
1352
1428
|
citationExtractor;
|
|
1353
1429
|
citationFormatter;
|
|
1430
|
+
bibliographyGenerator;
|
|
1354
1431
|
transformer;
|
|
1355
1432
|
renderer;
|
|
1356
1433
|
warnings = [];
|
|
@@ -1360,9 +1437,10 @@ var Pipeline = class {
|
|
|
1360
1437
|
async run(inputPath, options) {
|
|
1361
1438
|
this.warnings = [];
|
|
1362
1439
|
try {
|
|
1363
|
-
|
|
1440
|
+
let presentation = await this.parseSource(inputPath);
|
|
1364
1441
|
const citationIds = this.collectCitations(presentation);
|
|
1365
1442
|
await this.resolveReferences(citationIds);
|
|
1443
|
+
presentation = await this.processBibliography(presentation, citationIds);
|
|
1366
1444
|
const transformedSlides = await this.transformSlides(presentation);
|
|
1367
1445
|
const output = this.render(transformedSlides, presentation);
|
|
1368
1446
|
if (options?.outputPath) {
|
|
@@ -1386,9 +1464,10 @@ var Pipeline = class {
|
|
|
1386
1464
|
async runWithResult(inputPath, options) {
|
|
1387
1465
|
this.warnings = [];
|
|
1388
1466
|
try {
|
|
1389
|
-
|
|
1467
|
+
let presentation = await this.parseSource(inputPath);
|
|
1390
1468
|
const citationIds = this.collectCitations(presentation);
|
|
1391
1469
|
await this.resolveReferences(citationIds);
|
|
1470
|
+
presentation = await this.processBibliography(presentation, citationIds);
|
|
1392
1471
|
const transformedSlides = await this.transformSlides(presentation);
|
|
1393
1472
|
const output = this.render(transformedSlides, presentation);
|
|
1394
1473
|
if (options?.outputPath) {
|
|
@@ -1464,6 +1543,13 @@ var Pipeline = class {
|
|
|
1464
1543
|
if (!this.config.references.enabled || ids.length === 0) {
|
|
1465
1544
|
return /* @__PURE__ */ new Map();
|
|
1466
1545
|
}
|
|
1546
|
+
const isAvailable = await this.referenceManager.isAvailable();
|
|
1547
|
+
if (!isAvailable) {
|
|
1548
|
+
this.warnings.push(
|
|
1549
|
+
"reference-manager CLI is not available. Install it to enable citation features: npm install -g @ncukondo/reference-manager"
|
|
1550
|
+
);
|
|
1551
|
+
return /* @__PURE__ */ new Map();
|
|
1552
|
+
}
|
|
1467
1553
|
try {
|
|
1468
1554
|
const items = await this.referenceManager.getByIds(ids);
|
|
1469
1555
|
for (const id of ids) {
|
|
@@ -1512,10 +1598,97 @@ var Pipeline = class {
|
|
|
1512
1598
|
);
|
|
1513
1599
|
}
|
|
1514
1600
|
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Process bibliography slides with autoGenerate: true
|
|
1603
|
+
* Populates references array from collected citations
|
|
1604
|
+
*/
|
|
1605
|
+
async processBibliography(presentation, citationIds) {
|
|
1606
|
+
if (!this.config.references.enabled || citationIds.length === 0) {
|
|
1607
|
+
return presentation;
|
|
1608
|
+
}
|
|
1609
|
+
const hasBibliographyAutoGenerate = presentation.slides.some(
|
|
1610
|
+
(slide) => slide.template === "bibliography" && slide.content?.["autoGenerate"] === true
|
|
1611
|
+
);
|
|
1612
|
+
if (!hasBibliographyAutoGenerate) {
|
|
1613
|
+
return presentation;
|
|
1614
|
+
}
|
|
1615
|
+
const isAvailable = await this.referenceManager.isAvailable();
|
|
1616
|
+
if (!isAvailable) {
|
|
1617
|
+
return presentation;
|
|
1618
|
+
}
|
|
1619
|
+
try {
|
|
1620
|
+
const updatedSlides = await Promise.all(
|
|
1621
|
+
presentation.slides.map(async (slide) => {
|
|
1622
|
+
if (slide.template === "bibliography" && slide.content?.["autoGenerate"] === true) {
|
|
1623
|
+
const sort = slide.content["sort"] || "citation-order";
|
|
1624
|
+
const result = await this.bibliographyGenerator.generate(
|
|
1625
|
+
citationIds,
|
|
1626
|
+
{ sort }
|
|
1627
|
+
);
|
|
1628
|
+
for (const id of result.missing) {
|
|
1629
|
+
this.warnings.push(`Bibliography: reference not found: ${id}`);
|
|
1630
|
+
}
|
|
1631
|
+
const references = result.items.map((item) => ({
|
|
1632
|
+
id: item.id,
|
|
1633
|
+
authors: this.formatAuthorsForTemplate(item.author),
|
|
1634
|
+
title: item.title || "",
|
|
1635
|
+
year: this.getYear(item),
|
|
1636
|
+
journal: item["container-title"],
|
|
1637
|
+
volume: item.volume,
|
|
1638
|
+
pages: item.page,
|
|
1639
|
+
doi: item.DOI,
|
|
1640
|
+
url: item.URL
|
|
1641
|
+
}));
|
|
1642
|
+
return {
|
|
1643
|
+
...slide,
|
|
1644
|
+
content: {
|
|
1645
|
+
...slide.content,
|
|
1646
|
+
references,
|
|
1647
|
+
_autoGenerated: true,
|
|
1648
|
+
_generatedEntries: result.entries
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
return slide;
|
|
1653
|
+
})
|
|
1654
|
+
);
|
|
1655
|
+
return {
|
|
1656
|
+
...presentation,
|
|
1657
|
+
slides: updatedSlides
|
|
1658
|
+
};
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
this.warnings.push(
|
|
1661
|
+
`Failed to auto-generate bibliography: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1662
|
+
);
|
|
1663
|
+
return presentation;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Format authors for template-compatible format
|
|
1668
|
+
*/
|
|
1669
|
+
formatAuthorsForTemplate(authors) {
|
|
1670
|
+
if (!authors || authors.length === 0) {
|
|
1671
|
+
return void 0;
|
|
1672
|
+
}
|
|
1673
|
+
return authors.map((a) => {
|
|
1674
|
+
const initial = a.given ? `${a.given.charAt(0)}.` : "";
|
|
1675
|
+
return initial ? `${a.family}, ${initial}` : a.family;
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Get year from CSL item
|
|
1680
|
+
*/
|
|
1681
|
+
getYear(item) {
|
|
1682
|
+
const dateParts = item.issued?.["date-parts"];
|
|
1683
|
+
if (dateParts && dateParts[0] && dateParts[0][0]) {
|
|
1684
|
+
return dateParts[0][0];
|
|
1685
|
+
}
|
|
1686
|
+
return void 0;
|
|
1687
|
+
}
|
|
1515
1688
|
};
|
|
1516
1689
|
|
|
1517
1690
|
// src/index.ts
|
|
1518
|
-
var VERSION = "0.
|
|
1691
|
+
var VERSION = "0.3.0";
|
|
1519
1692
|
|
|
1520
1693
|
// src/cli/commands/convert.ts
|
|
1521
1694
|
import { Command } from "commander";
|
|
@@ -2593,12 +2766,167 @@ import { dirname as dirname3 } from "path";
|
|
|
2593
2766
|
import chalk2 from "chalk";
|
|
2594
2767
|
import ora2 from "ora";
|
|
2595
2768
|
import { parse as parseYaml6, stringify as yamlStringify } from "yaml";
|
|
2769
|
+
|
|
2770
|
+
// src/references/validator.ts
|
|
2771
|
+
var ReferenceValidator = class {
|
|
2772
|
+
constructor(manager) {
|
|
2773
|
+
this.manager = manager;
|
|
2774
|
+
}
|
|
2775
|
+
extractor = new CitationExtractor();
|
|
2776
|
+
availableCache = null;
|
|
2777
|
+
/**
|
|
2778
|
+
* Check if reference-manager is available (cached)
|
|
2779
|
+
*/
|
|
2780
|
+
async checkAvailable() {
|
|
2781
|
+
if (this.availableCache === null) {
|
|
2782
|
+
this.availableCache = await this.manager.isAvailable();
|
|
2783
|
+
}
|
|
2784
|
+
return this.availableCache;
|
|
2785
|
+
}
|
|
2786
|
+
async validateCitations(citationIds) {
|
|
2787
|
+
const available = await this.checkAvailable();
|
|
2788
|
+
if (!available) {
|
|
2789
|
+
return {
|
|
2790
|
+
valid: true,
|
|
2791
|
+
found: [],
|
|
2792
|
+
missing: [],
|
|
2793
|
+
skipped: true,
|
|
2794
|
+
reason: "reference-manager CLI is not available"
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2797
|
+
if (citationIds.length === 0) {
|
|
2798
|
+
return {
|
|
2799
|
+
valid: true,
|
|
2800
|
+
found: [],
|
|
2801
|
+
missing: []
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
const references = await this.manager.getByIds(citationIds);
|
|
2805
|
+
const found = [];
|
|
2806
|
+
const missing = [];
|
|
2807
|
+
for (const id of citationIds) {
|
|
2808
|
+
if (references.has(id)) {
|
|
2809
|
+
found.push(id);
|
|
2810
|
+
} else {
|
|
2811
|
+
missing.push(id);
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
return {
|
|
2815
|
+
valid: missing.length === 0,
|
|
2816
|
+
found,
|
|
2817
|
+
missing
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
async validateWithLocations(slides) {
|
|
2821
|
+
const available = await this.checkAvailable();
|
|
2822
|
+
if (!available) {
|
|
2823
|
+
return {
|
|
2824
|
+
valid: true,
|
|
2825
|
+
found: [],
|
|
2826
|
+
missing: [],
|
|
2827
|
+
skipped: true,
|
|
2828
|
+
reason: "reference-manager CLI is not available",
|
|
2829
|
+
missingDetails: []
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
const citationLocations = /* @__PURE__ */ new Map();
|
|
2833
|
+
const allCitationIds = [];
|
|
2834
|
+
for (let i = 0; i < slides.length; i++) {
|
|
2835
|
+
const slide = slides[i];
|
|
2836
|
+
const slideNumber = i + 1;
|
|
2837
|
+
const citations = this.extractor.extractFromSlide(slide);
|
|
2838
|
+
for (const citation of citations) {
|
|
2839
|
+
if (!citationLocations.has(citation.id)) {
|
|
2840
|
+
citationLocations.set(citation.id, []);
|
|
2841
|
+
allCitationIds.push(citation.id);
|
|
2842
|
+
}
|
|
2843
|
+
const text = this.findCitationText(slide, citation.id);
|
|
2844
|
+
citationLocations.get(citation.id).push({
|
|
2845
|
+
slide: slideNumber,
|
|
2846
|
+
text
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
if (allCitationIds.length === 0) {
|
|
2851
|
+
return {
|
|
2852
|
+
valid: true,
|
|
2853
|
+
found: [],
|
|
2854
|
+
missing: [],
|
|
2855
|
+
missingDetails: []
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
const references = await this.manager.getByIds(allCitationIds);
|
|
2859
|
+
const found = [];
|
|
2860
|
+
const missing = [];
|
|
2861
|
+
const missingDetails = [];
|
|
2862
|
+
for (const id of allCitationIds) {
|
|
2863
|
+
if (references.has(id)) {
|
|
2864
|
+
found.push(id);
|
|
2865
|
+
} else {
|
|
2866
|
+
missing.push(id);
|
|
2867
|
+
missingDetails.push({
|
|
2868
|
+
id,
|
|
2869
|
+
locations: citationLocations.get(id) || []
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
return {
|
|
2874
|
+
valid: missing.length === 0,
|
|
2875
|
+
found,
|
|
2876
|
+
missing,
|
|
2877
|
+
missingDetails
|
|
2878
|
+
};
|
|
2879
|
+
}
|
|
2880
|
+
/**
|
|
2881
|
+
* Generate suggestions for adding missing references
|
|
2882
|
+
*/
|
|
2883
|
+
static generateSuggestions(missingIds) {
|
|
2884
|
+
if (missingIds.length === 0) {
|
|
2885
|
+
return "";
|
|
2886
|
+
}
|
|
2887
|
+
const lines = [
|
|
2888
|
+
"Missing citations:",
|
|
2889
|
+
...missingIds.map((id) => ` - @${id}`),
|
|
2890
|
+
"",
|
|
2891
|
+
"To add these references, use:",
|
|
2892
|
+
" ref add --pmid <pmid> # Add by PubMed ID",
|
|
2893
|
+
' ref add "<doi>" # Add by DOI',
|
|
2894
|
+
" ref add --isbn <isbn> # Add by ISBN"
|
|
2895
|
+
];
|
|
2896
|
+
return lines.join("\n");
|
|
2897
|
+
}
|
|
2898
|
+
findCitationText(slide, citationId) {
|
|
2899
|
+
const searchValue = (value) => {
|
|
2900
|
+
if (typeof value === "string") {
|
|
2901
|
+
if (value.includes(`@${citationId}`)) {
|
|
2902
|
+
return value;
|
|
2903
|
+
}
|
|
2904
|
+
} else if (Array.isArray(value)) {
|
|
2905
|
+
for (const item of value) {
|
|
2906
|
+
const found = searchValue(item);
|
|
2907
|
+
if (found) return found;
|
|
2908
|
+
}
|
|
2909
|
+
} else if (value && typeof value === "object") {
|
|
2910
|
+
for (const v of Object.values(value)) {
|
|
2911
|
+
const found = searchValue(v);
|
|
2912
|
+
if (found) return found;
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
return null;
|
|
2916
|
+
};
|
|
2917
|
+
return searchValue(slide.content) || `@${citationId}`;
|
|
2918
|
+
}
|
|
2919
|
+
};
|
|
2920
|
+
|
|
2921
|
+
// src/cli/commands/validate.ts
|
|
2596
2922
|
function getHintForErrorType(errorType) {
|
|
2597
2923
|
switch (errorType) {
|
|
2598
2924
|
case "unknown_template":
|
|
2599
2925
|
return "Run `slide-gen templates list --format llm` to see available templates.";
|
|
2600
2926
|
case "unknown_icon":
|
|
2601
2927
|
return "Run `slide-gen icons search <query>` to find icons.";
|
|
2928
|
+
case "missing_reference":
|
|
2929
|
+
return 'Run `ref add --pmid <pmid>` or `ref add "<doi>"` to add the reference.';
|
|
2602
2930
|
default:
|
|
2603
2931
|
return null;
|
|
2604
2932
|
}
|
|
@@ -2660,7 +2988,9 @@ async function executeValidate(inputPath, options) {
|
|
|
2660
2988
|
slideCount: 0,
|
|
2661
2989
|
templatesFound: false,
|
|
2662
2990
|
iconsResolved: false,
|
|
2663
|
-
referencesCount: 0
|
|
2991
|
+
referencesCount: 0,
|
|
2992
|
+
referencesValidated: false,
|
|
2993
|
+
missingReferences: []
|
|
2664
2994
|
}
|
|
2665
2995
|
};
|
|
2666
2996
|
try {
|
|
@@ -2841,15 +3171,45 @@ ${formatExampleAsYaml(template.example, 2)}` : void 0;
|
|
|
2841
3171
|
}
|
|
2842
3172
|
}
|
|
2843
3173
|
}
|
|
2844
|
-
const
|
|
2845
|
-
const
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
3174
|
+
const citationExtractor = new CitationExtractor();
|
|
3175
|
+
const extractedCitations = citationExtractor.extractFromPresentation(presentation);
|
|
3176
|
+
const citationIds = citationExtractor.getUniqueIds(extractedCitations);
|
|
3177
|
+
result.stats.referencesCount = citationIds.length;
|
|
3178
|
+
if (config.references?.enabled && citationIds.length > 0) {
|
|
3179
|
+
const refManager = new ReferenceManager();
|
|
3180
|
+
const refValidator = new ReferenceValidator(refManager);
|
|
3181
|
+
const refValidationResult = await refValidator.validateWithLocations(
|
|
3182
|
+
presentation.slides
|
|
3183
|
+
);
|
|
3184
|
+
if (refValidationResult.skipped) {
|
|
3185
|
+
result.warnings.push(
|
|
3186
|
+
`Reference validation skipped: ${refValidationResult.reason}`
|
|
3187
|
+
);
|
|
3188
|
+
} else {
|
|
3189
|
+
result.stats.referencesValidated = true;
|
|
3190
|
+
result.stats.missingReferences = refValidationResult.missing;
|
|
3191
|
+
if (!refValidationResult.valid) {
|
|
3192
|
+
for (const missingDetail of refValidationResult.missingDetails) {
|
|
3193
|
+
for (const location of missingDetail.locations) {
|
|
3194
|
+
const slideLine = result.slideLines[location.slide - 1];
|
|
3195
|
+
result.warnings.push(
|
|
3196
|
+
`Citation not found in library: ${missingDetail.id} (Slide ${location.slide})`
|
|
3197
|
+
);
|
|
3198
|
+
const structuredError = {
|
|
3199
|
+
slide: location.slide,
|
|
3200
|
+
template: presentation.slides[location.slide - 1]?.template || "unknown",
|
|
3201
|
+
message: `Citation not found in library: ${missingDetail.id}`,
|
|
3202
|
+
errorType: "missing_reference"
|
|
3203
|
+
};
|
|
3204
|
+
if (slideLine !== void 0) {
|
|
3205
|
+
structuredError.line = slideLine;
|
|
3206
|
+
}
|
|
3207
|
+
result.structuredErrors.push(structuredError);
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
2850
3211
|
}
|
|
2851
3212
|
}
|
|
2852
|
-
result.stats.referencesCount = references.size;
|
|
2853
3213
|
if (result.errors.length > 0) {
|
|
2854
3214
|
result.valid = false;
|
|
2855
3215
|
}
|
|
@@ -2885,7 +3245,16 @@ function outputResult(result, options) {
|
|
|
2885
3245
|
valid: result.valid,
|
|
2886
3246
|
errors: result.errors,
|
|
2887
3247
|
warnings: result.warnings,
|
|
2888
|
-
stats:
|
|
3248
|
+
stats: {
|
|
3249
|
+
yamlSyntax: result.stats.yamlSyntax,
|
|
3250
|
+
metaValid: result.stats.metaValid,
|
|
3251
|
+
slideCount: result.stats.slideCount,
|
|
3252
|
+
templatesFound: result.stats.templatesFound,
|
|
3253
|
+
iconsResolved: result.stats.iconsResolved,
|
|
3254
|
+
referencesCount: result.stats.referencesCount,
|
|
3255
|
+
referencesValidated: result.stats.referencesValidated,
|
|
3256
|
+
missingReferences: result.stats.missingReferences
|
|
3257
|
+
}
|
|
2889
3258
|
},
|
|
2890
3259
|
null,
|
|
2891
3260
|
2
|
|
@@ -2922,6 +3291,22 @@ function outputResult(result, options) {
|
|
|
2922
3291
|
if (result.stats.referencesCount > 0) {
|
|
2923
3292
|
console.log(chalk2.green("\u2713") + ` ${result.stats.referencesCount} references found`);
|
|
2924
3293
|
}
|
|
3294
|
+
if (result.stats.referencesValidated) {
|
|
3295
|
+
if (result.stats.missingReferences.length === 0) {
|
|
3296
|
+
console.log(chalk2.green("\u2713") + " All references validated");
|
|
3297
|
+
} else {
|
|
3298
|
+
console.log(
|
|
3299
|
+
chalk2.yellow("\u26A0") + ` ${result.stats.missingReferences.length} reference(s) not found in library`
|
|
3300
|
+
);
|
|
3301
|
+
const suggestions = ReferenceValidator.generateSuggestions(
|
|
3302
|
+
result.stats.missingReferences
|
|
3303
|
+
);
|
|
3304
|
+
if (suggestions) {
|
|
3305
|
+
console.log("");
|
|
3306
|
+
console.log(chalk2.dim(suggestions));
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
2925
3310
|
for (const error of result.errors) {
|
|
2926
3311
|
console.log(chalk2.red("\u2717") + ` ${error}`);
|
|
2927
3312
|
}
|
|
@@ -4889,14 +5274,105 @@ var missingItemSchema = z8.object({
|
|
|
4889
5274
|
status: z8.string().optional(),
|
|
4890
5275
|
notes: z8.string().optional()
|
|
4891
5276
|
});
|
|
5277
|
+
var referenceItemSchema = z8.object({
|
|
5278
|
+
id: z8.string(),
|
|
5279
|
+
status: z8.enum(["pending", "added", "existing"]),
|
|
5280
|
+
slide: z8.number(),
|
|
5281
|
+
purpose: z8.string(),
|
|
5282
|
+
requirement: z8.enum(["required", "recommended"]).optional(),
|
|
5283
|
+
added_date: z8.string().optional(),
|
|
5284
|
+
suggested_search: z8.array(z8.string()).optional(),
|
|
5285
|
+
notes: z8.string().optional()
|
|
5286
|
+
});
|
|
5287
|
+
var referencesStatusSchema = z8.object({
|
|
5288
|
+
required: z8.number().default(0),
|
|
5289
|
+
found: z8.number().default(0),
|
|
5290
|
+
pending: z8.number().default(0)
|
|
5291
|
+
});
|
|
5292
|
+
var referencesSectionSchema = z8.object({
|
|
5293
|
+
status: referencesStatusSchema.optional(),
|
|
5294
|
+
items: z8.array(referenceItemSchema).default([])
|
|
5295
|
+
});
|
|
4892
5296
|
var sourcesYamlSchema = z8.object({
|
|
4893
5297
|
project: projectSchema,
|
|
4894
5298
|
context: contextSchema.optional(),
|
|
4895
5299
|
sources: z8.array(sourceEntrySchema).optional(),
|
|
4896
5300
|
dependencies: z8.record(dependencySchema).optional(),
|
|
4897
|
-
missing: z8.array(missingItemSchema).optional()
|
|
5301
|
+
missing: z8.array(missingItemSchema).optional(),
|
|
5302
|
+
references: referencesSectionSchema.optional()
|
|
4898
5303
|
});
|
|
4899
5304
|
|
|
5305
|
+
// src/sources/references-tracker.ts
|
|
5306
|
+
var ReferencesTracker = class {
|
|
5307
|
+
items = [];
|
|
5308
|
+
constructor(initial) {
|
|
5309
|
+
if (initial?.items) {
|
|
5310
|
+
this.items = [...initial.items];
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
5313
|
+
/**
|
|
5314
|
+
* Add a pending reference that needs to be found
|
|
5315
|
+
*/
|
|
5316
|
+
addPending(ref) {
|
|
5317
|
+
this.items.push({
|
|
5318
|
+
...ref,
|
|
5319
|
+
status: "pending"
|
|
5320
|
+
});
|
|
5321
|
+
}
|
|
5322
|
+
/**
|
|
5323
|
+
* Mark a pending reference as added with its actual citation key
|
|
5324
|
+
*/
|
|
5325
|
+
markAdded(pendingId, actualId) {
|
|
5326
|
+
const item = this.items.find((i) => i.id === pendingId);
|
|
5327
|
+
if (item) {
|
|
5328
|
+
item.id = actualId;
|
|
5329
|
+
item.status = "added";
|
|
5330
|
+
item.added_date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5331
|
+
}
|
|
5332
|
+
}
|
|
5333
|
+
/**
|
|
5334
|
+
* Mark a reference as existing in the bibliography
|
|
5335
|
+
*/
|
|
5336
|
+
markExisting(ref) {
|
|
5337
|
+
this.items.push({
|
|
5338
|
+
...ref,
|
|
5339
|
+
status: "existing"
|
|
5340
|
+
});
|
|
5341
|
+
}
|
|
5342
|
+
/**
|
|
5343
|
+
* Get all reference items
|
|
5344
|
+
*/
|
|
5345
|
+
getItems() {
|
|
5346
|
+
return [...this.items];
|
|
5347
|
+
}
|
|
5348
|
+
/**
|
|
5349
|
+
* Get only pending references
|
|
5350
|
+
*/
|
|
5351
|
+
getPending() {
|
|
5352
|
+
return this.items.filter((i) => i.status === "pending");
|
|
5353
|
+
}
|
|
5354
|
+
/**
|
|
5355
|
+
* Calculate status summary
|
|
5356
|
+
*/
|
|
5357
|
+
getStatus() {
|
|
5358
|
+
const required = this.items.filter(
|
|
5359
|
+
(i) => i.requirement === "required"
|
|
5360
|
+
).length;
|
|
5361
|
+
const pending = this.items.filter((i) => i.status === "pending").length;
|
|
5362
|
+
const found = this.items.filter((i) => i.status !== "pending").length;
|
|
5363
|
+
return { required, found, pending };
|
|
5364
|
+
}
|
|
5365
|
+
/**
|
|
5366
|
+
* Convert to YAML-compatible object
|
|
5367
|
+
*/
|
|
5368
|
+
toYaml() {
|
|
5369
|
+
return {
|
|
5370
|
+
status: this.getStatus(),
|
|
5371
|
+
items: this.items
|
|
5372
|
+
};
|
|
5373
|
+
}
|
|
5374
|
+
};
|
|
5375
|
+
|
|
4900
5376
|
// src/sources/manager.ts
|
|
4901
5377
|
var SourcesManager = class _SourcesManager {
|
|
4902
5378
|
sourcesDir;
|
|
@@ -5071,6 +5547,44 @@ var SourcesManager = class _SourcesManager {
|
|
|
5071
5547
|
const data = await this.load();
|
|
5072
5548
|
return data.sources?.filter((s) => s.type === type) ?? [];
|
|
5073
5549
|
}
|
|
5550
|
+
/**
|
|
5551
|
+
* Get references from sources.yaml
|
|
5552
|
+
*/
|
|
5553
|
+
async getReferences() {
|
|
5554
|
+
const data = await this.load();
|
|
5555
|
+
const tracker = new ReferencesTracker(data.references);
|
|
5556
|
+
return tracker.toYaml();
|
|
5557
|
+
}
|
|
5558
|
+
/**
|
|
5559
|
+
* Add a pending reference that needs to be found
|
|
5560
|
+
*/
|
|
5561
|
+
async addPendingReference(ref) {
|
|
5562
|
+
const data = await this.load();
|
|
5563
|
+
const tracker = new ReferencesTracker(data.references);
|
|
5564
|
+
tracker.addPending(ref);
|
|
5565
|
+
data.references = tracker.toYaml();
|
|
5566
|
+
await this.save(data);
|
|
5567
|
+
}
|
|
5568
|
+
/**
|
|
5569
|
+
* Mark a pending reference as added with its actual citation key
|
|
5570
|
+
*/
|
|
5571
|
+
async markReferenceAdded(pendingId, actualId) {
|
|
5572
|
+
const data = await this.load();
|
|
5573
|
+
const tracker = new ReferencesTracker(data.references);
|
|
5574
|
+
tracker.markAdded(pendingId, actualId);
|
|
5575
|
+
data.references = tracker.toYaml();
|
|
5576
|
+
await this.save(data);
|
|
5577
|
+
}
|
|
5578
|
+
/**
|
|
5579
|
+
* Mark a reference as existing in the bibliography
|
|
5580
|
+
*/
|
|
5581
|
+
async markReferenceExisting(ref) {
|
|
5582
|
+
const data = await this.load();
|
|
5583
|
+
const tracker = new ReferencesTracker(data.references);
|
|
5584
|
+
tracker.markExisting(ref);
|
|
5585
|
+
data.references = tracker.toYaml();
|
|
5586
|
+
await this.save(data);
|
|
5587
|
+
}
|
|
5074
5588
|
};
|
|
5075
5589
|
|
|
5076
5590
|
// src/sources/importer.ts
|
|
@@ -5467,6 +5981,51 @@ allowed-tools: Read Write Edit Bash Glob Grep
|
|
|
5467
5981
|
|
|
5468
5982
|
Helps create Marp slides using the slide-gen CLI tool.
|
|
5469
5983
|
|
|
5984
|
+
## First Question
|
|
5985
|
+
|
|
5986
|
+
**Always start by asking about the user's material situation:**
|
|
5987
|
+
|
|
5988
|
+
> "Let's create slides. What materials do you have?
|
|
5989
|
+
>
|
|
5990
|
+
> A) I have detailed materials organized in a directory
|
|
5991
|
+
> (scenarios, scripts, data files, images, etc.)
|
|
5992
|
+
>
|
|
5993
|
+
> B) I have partial materials like a scenario or script
|
|
5994
|
+
> (but need to supplement with additional info)
|
|
5995
|
+
>
|
|
5996
|
+
> C) I don't have materials yet
|
|
5997
|
+
> (starting from scratch, will collect info through dialogue)"
|
|
5998
|
+
|
|
5999
|
+
Then follow the appropriate pattern below.
|
|
6000
|
+
|
|
6001
|
+
## Workflow Patterns
|
|
6002
|
+
|
|
6003
|
+
### Pattern A: Explore Mode (Detailed Materials Exist)
|
|
6004
|
+
|
|
6005
|
+
When user has materials organized in a directory:
|
|
6006
|
+
1. Ask for directory path and scan with Glob
|
|
6007
|
+
2. Read and classify files (scenario, scripts, data, images)
|
|
6008
|
+
3. Summarize findings and confirm with user
|
|
6009
|
+
4. Configure \`sources/\` directory
|
|
6010
|
+
|
|
6011
|
+
### Pattern B: Supplement Mode (Partial Materials)
|
|
6012
|
+
|
|
6013
|
+
When user has only a scenario or partial materials:
|
|
6014
|
+
1. Read and analyze provided content
|
|
6015
|
+
2. Identify what's present vs. missing
|
|
6016
|
+
3. Ask targeted questions to fill gaps
|
|
6017
|
+
4. Configure \`sources/\` directory
|
|
6018
|
+
|
|
6019
|
+
### Pattern C: Interview Mode (Starting from Scratch)
|
|
6020
|
+
|
|
6021
|
+
When user has no materials:
|
|
6022
|
+
1. Ask basic questions (purpose, audience, duration)
|
|
6023
|
+
2. Collect data and examples
|
|
6024
|
+
3. Propose slide structure for approval
|
|
6025
|
+
4. Configure \`sources/\` directory
|
|
6026
|
+
|
|
6027
|
+
**See [references/workflows.md](references/workflows.md) for detailed steps.**
|
|
6028
|
+
|
|
5470
6029
|
## Capabilities
|
|
5471
6030
|
|
|
5472
6031
|
1. **Project initialization**: \`slide-gen init\`
|
|
@@ -5476,21 +6035,14 @@ Helps create Marp slides using the slide-gen CLI tool.
|
|
|
5476
6035
|
5. **Conversion**: \`slide-gen convert\`
|
|
5477
6036
|
6. **Screenshot**: \`slide-gen screenshot\` (for AI review)
|
|
5478
6037
|
|
|
5479
|
-
##
|
|
5480
|
-
|
|
5481
|
-
### New Project
|
|
5482
|
-
|
|
5483
|
-
1. Run \`slide-gen init <directory>\` to initialize
|
|
5484
|
-
2. Gather requirements from user
|
|
5485
|
-
3. Check templates with \`slide-gen templates list --format llm\`
|
|
5486
|
-
4. Create presentation.yaml
|
|
5487
|
-
5. Validate and convert
|
|
6038
|
+
## Slide Creation Flow
|
|
5488
6039
|
|
|
5489
|
-
|
|
6040
|
+
After material collection (Pattern A/B/C above):
|
|
5490
6041
|
|
|
5491
|
-
1.
|
|
5492
|
-
2.
|
|
5493
|
-
3. Validate
|
|
6042
|
+
1. Check templates with \`slide-gen templates list --format llm\`
|
|
6043
|
+
2. Create presentation.yaml
|
|
6044
|
+
3. Validate: \`slide-gen validate presentation.yaml\`
|
|
6045
|
+
4. Convert: \`slide-gen convert presentation.yaml\`
|
|
5494
6046
|
|
|
5495
6047
|
## YAML Source Format
|
|
5496
6048
|
|
|
@@ -5573,6 +6125,33 @@ The \`validate --format llm\` command provides:
|
|
|
5573
6125
|
- Error locations with line numbers
|
|
5574
6126
|
- Fix examples from template definitions
|
|
5575
6127
|
- Contextual hints for unknown templates/icons
|
|
6128
|
+
|
|
6129
|
+
## Reference Management
|
|
6130
|
+
|
|
6131
|
+
For academic presentations, manage citations and references:
|
|
6132
|
+
|
|
6133
|
+
1. **Analyze** content for citation needs
|
|
6134
|
+
2. **Search** existing library: \`ref search\`
|
|
6135
|
+
3. **Add** new references: \`ref add pmid:XXX\`
|
|
6136
|
+
4. **Validate** citations: \`slide-gen validate\`
|
|
6137
|
+
|
|
6138
|
+
See \`.skills/slide-assistant/references/skill.md\` for detailed workflow.
|
|
6139
|
+
|
|
6140
|
+
### Quick Commands
|
|
6141
|
+
|
|
6142
|
+
\`\`\`bash
|
|
6143
|
+
# Search library
|
|
6144
|
+
ref search "keyword" --format json
|
|
6145
|
+
|
|
6146
|
+
# Add from PMID
|
|
6147
|
+
ref add pmid:38941256
|
|
6148
|
+
|
|
6149
|
+
# Add from DOI
|
|
6150
|
+
ref add "10.1038/xxxxx"
|
|
6151
|
+
|
|
6152
|
+
# Validate citations
|
|
6153
|
+
slide-gen validate presentation.yaml
|
|
6154
|
+
\`\`\`
|
|
5576
6155
|
`;
|
|
5577
6156
|
}
|
|
5578
6157
|
|
|
@@ -5835,31 +6414,72 @@ Image with text side by side.
|
|
|
5835
6414
|
function generateWorkflowsRef() {
|
|
5836
6415
|
return `# Workflow Reference
|
|
5837
6416
|
|
|
6417
|
+
## Entry Point
|
|
6418
|
+
|
|
6419
|
+
**Always start by asking this question:**
|
|
6420
|
+
|
|
6421
|
+
> "Let's create slides. What materials do you have?
|
|
6422
|
+
>
|
|
6423
|
+
> A) I have detailed materials organized in a directory
|
|
6424
|
+
> (scenarios, scripts, data files, images, etc.)
|
|
6425
|
+
>
|
|
6426
|
+
> B) I have partial materials like a scenario or script
|
|
6427
|
+
> (but need to supplement with additional info)
|
|
6428
|
+
>
|
|
6429
|
+
> C) I don't have materials yet
|
|
6430
|
+
> (starting from scratch, will collect info through dialogue)"
|
|
6431
|
+
|
|
6432
|
+
Based on the answer, follow the appropriate pattern below.
|
|
6433
|
+
|
|
5838
6434
|
## Source Collection Flow
|
|
5839
6435
|
|
|
5840
|
-
### Pattern A:
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
6436
|
+
### Pattern A: Explore Mode (Detailed Materials Exist)
|
|
6437
|
+
|
|
6438
|
+
When user has materials organized in a directory:
|
|
6439
|
+
|
|
6440
|
+
1. **Ask for the directory path**
|
|
6441
|
+
2. **Scan directory structure** with Glob tool
|
|
6442
|
+
3. **Read and analyze each file**
|
|
6443
|
+
4. **Classify files** into categories:
|
|
6444
|
+
- Scenario/scripts
|
|
6445
|
+
- Data files (CSV, JSON, etc.)
|
|
6446
|
+
- Images and diagrams
|
|
6447
|
+
- Reference documents
|
|
6448
|
+
5. **Summarize findings** and confirm with user
|
|
6449
|
+
6. **Ask clarifying questions** about gaps
|
|
6450
|
+
7. **Configure \`sources/\` directory** with organized materials
|
|
6451
|
+
8. Proceed to slide creation
|
|
6452
|
+
|
|
6453
|
+
### Pattern B: Supplement Mode (Partial Materials)
|
|
6454
|
+
|
|
6455
|
+
When user has only a scenario or partial materials:
|
|
6456
|
+
|
|
6457
|
+
1. **Ask user** to provide the file path or paste content
|
|
6458
|
+
2. **Analyze the content** thoroughly
|
|
6459
|
+
3. **Identify what information is present** vs. missing
|
|
6460
|
+
4. **Ask targeted questions** to fill gaps:
|
|
6461
|
+
- Purpose and audience
|
|
6462
|
+
- Duration and format
|
|
6463
|
+
- Key messages
|
|
6464
|
+
- Available data/examples
|
|
6465
|
+
5. **Load any additional files** user mentions
|
|
6466
|
+
6. **Configure \`sources/\` directory**
|
|
6467
|
+
7. Proceed to slide creation
|
|
6468
|
+
|
|
6469
|
+
### Pattern C: Interview Mode (Starting from Scratch)
|
|
6470
|
+
|
|
5857
6471
|
When user has no materials:
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
6472
|
+
|
|
6473
|
+
1. **Ask basic questions**:
|
|
6474
|
+
- "What is this presentation about?"
|
|
6475
|
+
- "Who is the audience?"
|
|
6476
|
+
- "How long is the presentation?"
|
|
6477
|
+
2. **Ask purpose-specific questions** (proposal, report, introduction, etc.)
|
|
6478
|
+
3. **Collect data and examples** user can provide
|
|
6479
|
+
4. **Propose slide structure** for approval
|
|
6480
|
+
5. **Incorporate feedback**
|
|
6481
|
+
6. **Configure \`sources/\` directory** from conversation
|
|
6482
|
+
7. Proceed to slide creation
|
|
5863
6483
|
|
|
5864
6484
|
## Slide Creation Flow
|
|
5865
6485
|
|
|
@@ -5911,6 +6531,158 @@ Handle adjustments (cropping, replacement) as needed.
|
|
|
5911
6531
|
`;
|
|
5912
6532
|
}
|
|
5913
6533
|
|
|
6534
|
+
// src/cli/templates/ai/references/skill-references.ts
|
|
6535
|
+
function generateReferenceSkillMd() {
|
|
6536
|
+
return `## Reference Management Skill
|
|
6537
|
+
|
|
6538
|
+
### When to Invoke
|
|
6539
|
+
|
|
6540
|
+
Use this skill when:
|
|
6541
|
+
- Creating academic or research presentations
|
|
6542
|
+
- User mentions needing citations or references
|
|
6543
|
+
- Scenario contains statistical claims or research findings
|
|
6544
|
+
- User provides literature (URL, PDF, DOI, PMID)
|
|
6545
|
+
|
|
6546
|
+
### Citation Requirement Analysis
|
|
6547
|
+
|
|
6548
|
+
Analyze scenario/script for statements requiring citations:
|
|
6549
|
+
|
|
6550
|
+
| Statement Type | Requirement | Example |
|
|
6551
|
+
|---------------|-------------|---------|
|
|
6552
|
+
| Statistical claims | Required | "Accuracy exceeds 90%" |
|
|
6553
|
+
| Research findings | Required | "Studies show that..." |
|
|
6554
|
+
| Methodology references | Required | "Using the XYZ method" |
|
|
6555
|
+
| Comparative claims | Recommended | "Better than conventional" |
|
|
6556
|
+
| Historical facts | Recommended | "First introduced in 2020" |
|
|
6557
|
+
| General knowledge | Not required | "AI is widely used" |
|
|
6558
|
+
|
|
6559
|
+
### Workflow
|
|
6560
|
+
|
|
6561
|
+
#### Phase 1: Analyze Content
|
|
6562
|
+
1. Read scenario/script thoroughly
|
|
6563
|
+
2. Identify statements requiring citations
|
|
6564
|
+
3. Categorize as Required or Recommended
|
|
6565
|
+
4. Note the slide number and exact statement
|
|
6566
|
+
|
|
6567
|
+
#### Phase 2: Search Existing Library
|
|
6568
|
+
\`\`\`bash
|
|
6569
|
+
# List all references
|
|
6570
|
+
ref list --format json
|
|
6571
|
+
|
|
6572
|
+
# Search by keyword
|
|
6573
|
+
ref search "diagnostic accuracy" --format json
|
|
6574
|
+
\`\`\`
|
|
6575
|
+
|
|
6576
|
+
#### Phase 3: Match or Request
|
|
6577
|
+
|
|
6578
|
+
**If found in library:**
|
|
6579
|
+
- Confirm relevance with user
|
|
6580
|
+
- Insert \`[@id]\` citation in YAML
|
|
6581
|
+
|
|
6582
|
+
**If not found:**
|
|
6583
|
+
- Present clear request to user
|
|
6584
|
+
- Specify what type of source is needed
|
|
6585
|
+
- Provide suggested search terms
|
|
6586
|
+
|
|
6587
|
+
#### Phase 4: Add New References
|
|
6588
|
+
|
|
6589
|
+
From user-provided input:
|
|
6590
|
+
|
|
6591
|
+
\`\`\`bash
|
|
6592
|
+
# From PMID
|
|
6593
|
+
ref add pmid:38941256
|
|
6594
|
+
|
|
6595
|
+
# From DOI
|
|
6596
|
+
ref add "10.1038/s41591-024-xxxxx"
|
|
6597
|
+
|
|
6598
|
+
# From ISBN
|
|
6599
|
+
ref add "ISBN:978-4-00-000000-0"
|
|
6600
|
+
|
|
6601
|
+
# From BibTeX file
|
|
6602
|
+
ref add paper.bib
|
|
6603
|
+
\`\`\`
|
|
6604
|
+
|
|
6605
|
+
#### Phase 5: Insert Citations
|
|
6606
|
+
|
|
6607
|
+
Update presentation.yaml:
|
|
6608
|
+
\`\`\`yaml
|
|
6609
|
+
items:
|
|
6610
|
+
- "This claim is supported [@smith2024]"
|
|
6611
|
+
\`\`\`
|
|
6612
|
+
|
|
6613
|
+
### Extracting from Non-Standard Input
|
|
6614
|
+
|
|
6615
|
+
#### URL Patterns
|
|
6616
|
+
- PubMed: Extract PMID from \`pubmed.ncbi.nlm.nih.gov/XXXXXXXX\`
|
|
6617
|
+
- DOI: Extract from \`doi.org/10.XXXX/XXXXX\`
|
|
6618
|
+
- Publisher sites: Fetch page, extract DOI from metadata
|
|
6619
|
+
|
|
6620
|
+
#### PDF Files
|
|
6621
|
+
1. Read PDF file
|
|
6622
|
+
2. Extract DOI from first page or metadata
|
|
6623
|
+
3. If not found, extract title and search databases
|
|
6624
|
+
|
|
6625
|
+
#### Free Text
|
|
6626
|
+
1. Parse author, year, journal information
|
|
6627
|
+
2. Search PubMed/CrossRef
|
|
6628
|
+
3. Present candidates for user confirmation
|
|
6629
|
+
|
|
6630
|
+
### User Communication Templates
|
|
6631
|
+
|
|
6632
|
+
**Analyzing content:**
|
|
6633
|
+
\`\`\`
|
|
6634
|
+
I've analyzed your scenario and identified citation needs:
|
|
6635
|
+
|
|
6636
|
+
Required Citations (N)
|
|
6637
|
+
----------------------
|
|
6638
|
+
1. Slide X: '[statement]'
|
|
6639
|
+
-> Needs: [type of source]
|
|
6640
|
+
|
|
6641
|
+
Recommended Citations (M)
|
|
6642
|
+
-------------------------
|
|
6643
|
+
...
|
|
6644
|
+
|
|
6645
|
+
Let me check your reference library...
|
|
6646
|
+
\`\`\`
|
|
6647
|
+
|
|
6648
|
+
**Requesting references:**
|
|
6649
|
+
\`\`\`
|
|
6650
|
+
I need your help finding references:
|
|
6651
|
+
|
|
6652
|
+
[REQUIRED] Reference 1: [Topic]
|
|
6653
|
+
-------------------------------
|
|
6654
|
+
Purpose: Support claim '[statement]' on Slide X
|
|
6655
|
+
|
|
6656
|
+
Ideal source type:
|
|
6657
|
+
- [type1]
|
|
6658
|
+
- [type2]
|
|
6659
|
+
|
|
6660
|
+
Suggested search terms:
|
|
6661
|
+
- [term1]
|
|
6662
|
+
- [term2]
|
|
6663
|
+
|
|
6664
|
+
How to provide:
|
|
6665
|
+
A) DOI or PMID (e.g., 'PMID: 38941256')
|
|
6666
|
+
B) URL (PubMed, journal site, etc.)
|
|
6667
|
+
C) PDF file
|
|
6668
|
+
D) Manual citation details
|
|
6669
|
+
\`\`\`
|
|
6670
|
+
|
|
6671
|
+
**Confirming addition:**
|
|
6672
|
+
\`\`\`
|
|
6673
|
+
Reference added successfully:
|
|
6674
|
+
-----------------------------
|
|
6675
|
+
Citation key: [@id]
|
|
6676
|
+
Authors: ...
|
|
6677
|
+
Title: '...'
|
|
6678
|
+
Journal: ...
|
|
6679
|
+
Year: XXXX
|
|
6680
|
+
|
|
6681
|
+
I'll use this for Slide X.
|
|
6682
|
+
\`\`\`
|
|
6683
|
+
`;
|
|
6684
|
+
}
|
|
6685
|
+
|
|
5914
6686
|
// src/cli/templates/ai/commands/slide-create.ts
|
|
5915
6687
|
function generateSlideCreateCommand() {
|
|
5916
6688
|
return `Create slides from user requirements.
|
|
@@ -6070,6 +6842,72 @@ function generateSlideThemeCommand() {
|
|
|
6070
6842
|
`;
|
|
6071
6843
|
}
|
|
6072
6844
|
|
|
6845
|
+
// src/cli/templates/ai/commands/slide-references.ts
|
|
6846
|
+
function generateSlideReferencesCommand() {
|
|
6847
|
+
return `Manage references and citations for the presentation.
|
|
6848
|
+
|
|
6849
|
+
## Available Actions
|
|
6850
|
+
|
|
6851
|
+
### 1. Analyze - Find citation needs
|
|
6852
|
+
Analyze the scenario/content for statements that need citations.
|
|
6853
|
+
|
|
6854
|
+
### 2. Search - Find in library
|
|
6855
|
+
Search existing reference-manager library for relevant papers.
|
|
6856
|
+
|
|
6857
|
+
### 3. Add - Add new reference
|
|
6858
|
+
Add a reference from PMID, DOI, URL, or file.
|
|
6859
|
+
|
|
6860
|
+
### 4. List - Show all references
|
|
6861
|
+
List all references currently in the library.
|
|
6862
|
+
|
|
6863
|
+
## Usage
|
|
6864
|
+
|
|
6865
|
+
### Analyze scenario for citation needs:
|
|
6866
|
+
\`\`\`bash
|
|
6867
|
+
# First, read the presentation
|
|
6868
|
+
cat presentation.yaml
|
|
6869
|
+
|
|
6870
|
+
# Then analyze content for citation requirements
|
|
6871
|
+
\`\`\`
|
|
6872
|
+
Report statements needing citations with slide numbers.
|
|
6873
|
+
|
|
6874
|
+
### Search library:
|
|
6875
|
+
\`\`\`bash
|
|
6876
|
+
ref search "keyword" --format json
|
|
6877
|
+
ref list --format json
|
|
6878
|
+
\`\`\`
|
|
6879
|
+
|
|
6880
|
+
### Add reference:
|
|
6881
|
+
\`\`\`bash
|
|
6882
|
+
# From PMID
|
|
6883
|
+
ref add pmid:38941256
|
|
6884
|
+
|
|
6885
|
+
# From DOI
|
|
6886
|
+
ref add "10.1038/s41591-024-xxxxx"
|
|
6887
|
+
|
|
6888
|
+
# From URL (extract identifier first)
|
|
6889
|
+
# PubMed URL -> extract PMID
|
|
6890
|
+
# DOI URL -> extract DOI
|
|
6891
|
+
|
|
6892
|
+
# From file
|
|
6893
|
+
ref add paper.bib
|
|
6894
|
+
ref add export.ris
|
|
6895
|
+
\`\`\`
|
|
6896
|
+
|
|
6897
|
+
### Validate citations:
|
|
6898
|
+
\`\`\`bash
|
|
6899
|
+
slide-gen validate presentation.yaml
|
|
6900
|
+
\`\`\`
|
|
6901
|
+
|
|
6902
|
+
## Notes
|
|
6903
|
+
|
|
6904
|
+
- Always check library before requesting new references
|
|
6905
|
+
- Extract PMID/DOI from URLs before adding
|
|
6906
|
+
- Report missing citations with suggested search terms
|
|
6907
|
+
- Update presentation.yaml with [@id] format
|
|
6908
|
+
`;
|
|
6909
|
+
}
|
|
6910
|
+
|
|
6073
6911
|
// src/cli/commands/init.ts
|
|
6074
6912
|
function getPackageRoot() {
|
|
6075
6913
|
const __dirname = dirname6(fileURLToPath(import.meta.url));
|
|
@@ -6380,13 +7218,18 @@ async function generateAiConfig(targetDir) {
|
|
|
6380
7218
|
join16(targetDir, ".skills", "slide-assistant", "references", "workflows.md"),
|
|
6381
7219
|
generateWorkflowsRef()
|
|
6382
7220
|
);
|
|
7221
|
+
await writeFileIfNotExists(
|
|
7222
|
+
join16(targetDir, ".skills", "slide-assistant", "references", "skill.md"),
|
|
7223
|
+
generateReferenceSkillMd()
|
|
7224
|
+
);
|
|
6383
7225
|
await writeFileIfNotExists(join16(targetDir, "CLAUDE.md"), generateClaudeMd());
|
|
6384
7226
|
const commandGenerators = {
|
|
6385
7227
|
"slide-create": generateSlideCreateCommand,
|
|
6386
7228
|
"slide-validate": generateSlideValidateCommand,
|
|
6387
7229
|
"slide-preview": generateSlidePreviewCommand,
|
|
6388
7230
|
"slide-screenshot": generateSlideScreenshotCommand,
|
|
6389
|
-
"slide-theme": generateSlideThemeCommand
|
|
7231
|
+
"slide-theme": generateSlideThemeCommand,
|
|
7232
|
+
"slide-references": generateSlideReferencesCommand
|
|
6390
7233
|
};
|
|
6391
7234
|
for (const [name, generator] of Object.entries(commandGenerators)) {
|
|
6392
7235
|
await writeFileIfNotExists(
|
|
@@ -7499,6 +8342,26 @@ async function executeSourcesStatus(projectDir, _options) {
|
|
|
7499
8342
|
output += "\n";
|
|
7500
8343
|
}
|
|
7501
8344
|
}
|
|
8345
|
+
const refs = await manager.getReferences();
|
|
8346
|
+
if (refs.items.length > 0) {
|
|
8347
|
+
output += "\n";
|
|
8348
|
+
output += chalk10.cyan("References:\n");
|
|
8349
|
+
output += ` Required: ${refs.status?.required ?? 0}
|
|
8350
|
+
`;
|
|
8351
|
+
output += ` Found: ${refs.status?.found ?? 0}
|
|
8352
|
+
`;
|
|
8353
|
+
output += ` Pending: ${refs.status?.pending ?? 0}
|
|
8354
|
+
`;
|
|
8355
|
+
const pendingRefs = refs.items.filter((i) => i.status === "pending");
|
|
8356
|
+
if (pendingRefs.length > 0) {
|
|
8357
|
+
output += "\n";
|
|
8358
|
+
output += chalk10.yellow(" \u26A0 Pending references:\n");
|
|
8359
|
+
for (const ref of pendingRefs) {
|
|
8360
|
+
output += ` - ${ref.id} (Slide ${ref.slide}): ${ref.purpose}
|
|
8361
|
+
`;
|
|
8362
|
+
}
|
|
8363
|
+
}
|
|
8364
|
+
}
|
|
7502
8365
|
if (data.project.updated) {
|
|
7503
8366
|
output += "\n";
|
|
7504
8367
|
output += chalk10.gray(`Last updated: ${data.project.updated}
|