radiant-docs 0.1.40 → 0.1.41
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/package.json +1 -1
- package/template/package.json +2 -2
- package/template/public/favicon.svg +16 -8
- package/template/scripts/remove-assistant-for-non-pro.mjs +28 -0
- package/template/src/components/Header.astro +2 -2
- package/template/src/components/chat/AssistantDocsWidget.astro +16 -0
- package/template/src/components/chat/AssistantDocsWidget.tsx +402 -0
- package/template/src/components/chat/AssistantEmbedPanel.tsx +1693 -0
- package/template/src/components/chat/AssistantEmbedPanelPage.astro +95 -0
- package/template/src/components/user/Callout.astro +10 -4
- package/template/src/layouts/Layout.astro +11 -152
- package/template/src/lib/assistant-chrome-defaults.ts +74 -0
- package/template/src/lib/assistant-chrome.ts +39 -0
- package/template/src/lib/assistant-embed-script.ts +897 -0
- package/template/src/lib/assistant-panel-config.ts +80 -0
- package/template/src/lib/favicon.ts +31 -0
- package/template/src/lib/theme-css.ts +176 -0
- package/template/src/lib/validation.ts +418 -41
- package/template/src/pages/-/assistant/embed.js.ts +15 -0
- package/template/src/pages/-/assistant/panel.astro +5 -0
- package/template/src/pages/404.astro +4 -4
|
@@ -208,17 +208,33 @@ export type BaseColorByMode = {
|
|
|
208
208
|
export const DEFAULT_THEME_COLOR_LIGHT = "#171717";
|
|
209
209
|
export const DEFAULT_THEME_COLOR_DARK = "#f5f5f5";
|
|
210
210
|
export type ThemeColorByMode = {
|
|
211
|
-
light
|
|
212
|
-
dark
|
|
211
|
+
light?: string;
|
|
212
|
+
dark?: string;
|
|
213
213
|
};
|
|
214
214
|
export type DocsTheme = {
|
|
215
215
|
baseColor?: BaseColorOption | BaseColorByMode;
|
|
216
216
|
themeColor?: string | ThemeColorByMode;
|
|
217
217
|
};
|
|
218
|
+
export type AssistantIcon = {
|
|
219
|
+
src?: string;
|
|
220
|
+
color?: string;
|
|
221
|
+
};
|
|
222
|
+
export type AssistantButtonSize = "small" | "default";
|
|
223
|
+
export type AssistantButtonConfig = {
|
|
224
|
+
size?: AssistantButtonSize;
|
|
225
|
+
};
|
|
226
|
+
export type AssistantConfig = {
|
|
227
|
+
button?: AssistantButtonConfig;
|
|
228
|
+
heading?: string;
|
|
229
|
+
questions?: string[];
|
|
230
|
+
themeColor?: string | ThemeColorByMode;
|
|
231
|
+
icon?: AssistantIcon;
|
|
232
|
+
};
|
|
218
233
|
export type DocsConfig = {
|
|
219
234
|
title: string;
|
|
220
235
|
logo?: Logo;
|
|
221
236
|
theme?: DocsTheme;
|
|
237
|
+
assistant?: AssistantConfig;
|
|
222
238
|
home?: string;
|
|
223
239
|
navigation: NavigationItem;
|
|
224
240
|
navbar?: {
|
|
@@ -292,6 +308,38 @@ function checkType(
|
|
|
292
308
|
}
|
|
293
309
|
}
|
|
294
310
|
|
|
311
|
+
function normalizeHexColor(
|
|
312
|
+
value: unknown,
|
|
313
|
+
currentPath: Path,
|
|
314
|
+
label: string,
|
|
315
|
+
): string {
|
|
316
|
+
checkType(value, "string", currentPath, label);
|
|
317
|
+
if (typeof value !== "string") {
|
|
318
|
+
throwConfigError(`${label} must be a string.`, currentPath);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const trimmedValue = value.trim();
|
|
322
|
+
if (trimmedValue.length === 0) {
|
|
323
|
+
throwConfigError(`${label} cannot be empty.`, currentPath);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const normalizedValue = trimmedValue.startsWith("#")
|
|
327
|
+
? trimmedValue
|
|
328
|
+
: `#${trimmedValue}`;
|
|
329
|
+
if (
|
|
330
|
+
!/^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/.test(
|
|
331
|
+
normalizedValue,
|
|
332
|
+
)
|
|
333
|
+
) {
|
|
334
|
+
throwConfigError(
|
|
335
|
+
`${label} must be a valid hex color (for example: #1d4ed8).`,
|
|
336
|
+
currentPath,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return normalizedValue.toLowerCase();
|
|
341
|
+
}
|
|
342
|
+
|
|
295
343
|
function validateFileExistence(filePath: string, currentPath: Path): void {
|
|
296
344
|
// Assuming relative path from DOCS_DIR and .mdx extension
|
|
297
345
|
const fullPath = path.join(DOCS_DIR, `${filePath}.mdx`);
|
|
@@ -370,7 +418,11 @@ function normalizeInternalPageHref(
|
|
|
370
418
|
return null;
|
|
371
419
|
}
|
|
372
420
|
|
|
373
|
-
const filePath = normalizeDocsPagePath(
|
|
421
|
+
const filePath = normalizeDocsPagePath(
|
|
422
|
+
normalizedPathname,
|
|
423
|
+
currentPath,
|
|
424
|
+
label,
|
|
425
|
+
);
|
|
374
426
|
validateFileExistence(filePath, currentPath);
|
|
375
427
|
|
|
376
428
|
return {
|
|
@@ -1222,10 +1274,85 @@ function validateLogoImagePath(
|
|
|
1222
1274
|
|
|
1223
1275
|
if (!fs.existsSync(fullPath)) {
|
|
1224
1276
|
throwConfigError(
|
|
1225
|
-
`${label} file not found. Expected: ${normalizedPath}
|
|
1277
|
+
`${label} file not found. Expected: ${normalizedPath}`,
|
|
1278
|
+
currentPath,
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
function validateAssistantIconSource(
|
|
1284
|
+
iconSource: unknown,
|
|
1285
|
+
currentPath: Path,
|
|
1286
|
+
): string {
|
|
1287
|
+
checkType(iconSource, "string", currentPath, "Assistant icon source");
|
|
1288
|
+
if (typeof iconSource !== "string") {
|
|
1289
|
+
throwConfigError("Assistant icon source must be a string.", currentPath);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
const trimmedSource = iconSource.trim();
|
|
1293
|
+
if (trimmedSource.length === 0) {
|
|
1294
|
+
throwConfigError("Assistant icon source cannot be empty.", currentPath);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
if (isUrl(trimmedSource)) {
|
|
1298
|
+
throwConfigError(
|
|
1299
|
+
"Assistant icon source must be a local image path relative to docs.json.",
|
|
1300
|
+
currentPath,
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
if (trimmedSource.includes(":")) {
|
|
1305
|
+
throwConfigError(
|
|
1306
|
+
`Invalid assistant icon source: "${trimmedSource}". Assistant icons must be local image files, not Iconify icon names.`,
|
|
1307
|
+
currentPath,
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (
|
|
1312
|
+
trimmedSource.startsWith("//") ||
|
|
1313
|
+
trimmedSource.startsWith("#") ||
|
|
1314
|
+
trimmedSource.startsWith("?") ||
|
|
1315
|
+
trimmedSource.startsWith("./") ||
|
|
1316
|
+
trimmedSource.startsWith("../")
|
|
1317
|
+
) {
|
|
1318
|
+
throwConfigError(
|
|
1319
|
+
"Assistant icon source must be a local image path relative to docs.json.",
|
|
1320
|
+
currentPath,
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
const parsed = new URL(trimmedSource, "https://docs.invalid/");
|
|
1325
|
+
const validExtensions = [
|
|
1326
|
+
".svg",
|
|
1327
|
+
".png",
|
|
1328
|
+
".jpg",
|
|
1329
|
+
".jpeg",
|
|
1330
|
+
".webp",
|
|
1331
|
+
".gif",
|
|
1332
|
+
".ico",
|
|
1333
|
+
".avif",
|
|
1334
|
+
];
|
|
1335
|
+
const hasValidExtension = validExtensions.some((ext) =>
|
|
1336
|
+
parsed.pathname.toLowerCase().endsWith(ext),
|
|
1337
|
+
);
|
|
1338
|
+
if (!hasValidExtension) {
|
|
1339
|
+
throwConfigError(
|
|
1340
|
+
"Assistant icon source must be a valid image file (.svg, .png, .jpg, .jpeg, .webp, .gif, .ico, .avif).",
|
|
1341
|
+
currentPath,
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
const normalizedPath = parsed.pathname.replace(/^\/+/, "");
|
|
1346
|
+
const fullPath = path.join(DOCS_DIR, normalizedPath);
|
|
1347
|
+
|
|
1348
|
+
if (!fs.existsSync(fullPath)) {
|
|
1349
|
+
throwConfigError(
|
|
1350
|
+
`Assistant icon source file not found. Expected: ${normalizedPath}`,
|
|
1226
1351
|
currentPath,
|
|
1227
1352
|
);
|
|
1228
1353
|
}
|
|
1354
|
+
|
|
1355
|
+
return trimmedSource;
|
|
1229
1356
|
}
|
|
1230
1357
|
|
|
1231
1358
|
function validateLogoVariant(
|
|
@@ -1397,38 +1524,6 @@ function validateTheme(theme: DocsConfig["theme"]): void {
|
|
|
1397
1524
|
return normalizedBaseColor as BaseColorOption;
|
|
1398
1525
|
};
|
|
1399
1526
|
|
|
1400
|
-
const normalizeThemeColor = (
|
|
1401
|
-
value: unknown,
|
|
1402
|
-
currentPath: Path,
|
|
1403
|
-
label: string,
|
|
1404
|
-
): string => {
|
|
1405
|
-
checkType(value, "string", currentPath, label);
|
|
1406
|
-
if (typeof value !== "string") {
|
|
1407
|
-
throwConfigError(`${label} must be a string.`, currentPath);
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
const trimmedValue = (value as string).trim();
|
|
1411
|
-
if (trimmedValue.length === 0) {
|
|
1412
|
-
throwConfigError(`${label} cannot be empty.`, currentPath);
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
const normalizedValue = trimmedValue.startsWith("#")
|
|
1416
|
-
? trimmedValue
|
|
1417
|
-
: `#${trimmedValue}`;
|
|
1418
|
-
if (
|
|
1419
|
-
!/^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/.test(
|
|
1420
|
-
normalizedValue,
|
|
1421
|
-
)
|
|
1422
|
-
) {
|
|
1423
|
-
throwConfigError(
|
|
1424
|
-
`${label} must be a valid hex color (for example: #1d4ed8).`,
|
|
1425
|
-
currentPath,
|
|
1426
|
-
);
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
return normalizedValue.toLowerCase();
|
|
1430
|
-
};
|
|
1431
|
-
|
|
1432
1527
|
if (theme.baseColor !== undefined) {
|
|
1433
1528
|
if (typeof theme.baseColor === "string") {
|
|
1434
1529
|
theme.baseColor = normalizeBaseColor(
|
|
@@ -1504,7 +1599,7 @@ function validateTheme(theme: DocsConfig["theme"]): void {
|
|
|
1504
1599
|
}
|
|
1505
1600
|
|
|
1506
1601
|
if (typeof theme.themeColor === "string") {
|
|
1507
|
-
theme.themeColor =
|
|
1602
|
+
theme.themeColor = normalizeHexColor(
|
|
1508
1603
|
theme.themeColor,
|
|
1509
1604
|
["theme", "themeColor"],
|
|
1510
1605
|
"Theme color",
|
|
@@ -1538,7 +1633,7 @@ function validateTheme(theme: DocsConfig["theme"]): void {
|
|
|
1538
1633
|
|
|
1539
1634
|
const light =
|
|
1540
1635
|
themeColorByMode.light !== undefined
|
|
1541
|
-
?
|
|
1636
|
+
? normalizeHexColor(
|
|
1542
1637
|
themeColorByMode.light,
|
|
1543
1638
|
["theme", "themeColor", "light"],
|
|
1544
1639
|
"Theme color light",
|
|
@@ -1546,7 +1641,7 @@ function validateTheme(theme: DocsConfig["theme"]): void {
|
|
|
1546
1641
|
: undefined;
|
|
1547
1642
|
const dark =
|
|
1548
1643
|
themeColorByMode.dark !== undefined
|
|
1549
|
-
?
|
|
1644
|
+
? normalizeHexColor(
|
|
1550
1645
|
themeColorByMode.dark,
|
|
1551
1646
|
["theme", "themeColor", "dark"],
|
|
1552
1647
|
"Theme color dark",
|
|
@@ -1561,11 +1656,292 @@ function validateTheme(theme: DocsConfig["theme"]): void {
|
|
|
1561
1656
|
}
|
|
1562
1657
|
|
|
1563
1658
|
theme.themeColor = {
|
|
1564
|
-
light
|
|
1565
|
-
dark
|
|
1659
|
+
...(light !== undefined ? { light } : {}),
|
|
1660
|
+
...(dark !== undefined ? { dark } : {}),
|
|
1566
1661
|
};
|
|
1567
1662
|
}
|
|
1568
1663
|
|
|
1664
|
+
function validateAssistant(assistant: DocsConfig["assistant"]): void {
|
|
1665
|
+
if (assistant === undefined) return;
|
|
1666
|
+
|
|
1667
|
+
checkType(assistant, "object", ["assistant"], "Assistant configuration");
|
|
1668
|
+
if (
|
|
1669
|
+
typeof assistant !== "object" ||
|
|
1670
|
+
assistant === null ||
|
|
1671
|
+
Array.isArray(assistant)
|
|
1672
|
+
) {
|
|
1673
|
+
throwConfigError("Assistant configuration must be an object.", [
|
|
1674
|
+
"assistant",
|
|
1675
|
+
]);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
const allowedAssistantKeys = new Set([
|
|
1679
|
+
"button",
|
|
1680
|
+
"heading",
|
|
1681
|
+
"questions",
|
|
1682
|
+
"themeColor",
|
|
1683
|
+
"icon",
|
|
1684
|
+
]);
|
|
1685
|
+
for (const key of Object.keys(assistant)) {
|
|
1686
|
+
if (!allowedAssistantKeys.has(key)) {
|
|
1687
|
+
throwConfigError(
|
|
1688
|
+
"Assistant configuration only supports 'button', 'heading', 'questions', 'themeColor', and 'icon'.",
|
|
1689
|
+
["assistant", key],
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
if (assistant.button !== undefined) {
|
|
1695
|
+
checkType(
|
|
1696
|
+
assistant.button,
|
|
1697
|
+
"object",
|
|
1698
|
+
["assistant", "button"],
|
|
1699
|
+
"Assistant button configuration",
|
|
1700
|
+
);
|
|
1701
|
+
if (
|
|
1702
|
+
typeof assistant.button !== "object" ||
|
|
1703
|
+
assistant.button === null ||
|
|
1704
|
+
Array.isArray(assistant.button)
|
|
1705
|
+
) {
|
|
1706
|
+
throwConfigError("Assistant button configuration must be an object.", [
|
|
1707
|
+
"assistant",
|
|
1708
|
+
"button",
|
|
1709
|
+
]);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
const allowedButtonKeys = new Set(["size"]);
|
|
1713
|
+
for (const key of Object.keys(assistant.button)) {
|
|
1714
|
+
if (!allowedButtonKeys.has(key)) {
|
|
1715
|
+
throwConfigError(
|
|
1716
|
+
"Assistant button configuration only supports 'size'.",
|
|
1717
|
+
["assistant", "button", key],
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
if (assistant.button.size !== undefined) {
|
|
1723
|
+
checkType(
|
|
1724
|
+
assistant.button.size,
|
|
1725
|
+
"string",
|
|
1726
|
+
["assistant", "button", "size"],
|
|
1727
|
+
"Assistant button size",
|
|
1728
|
+
);
|
|
1729
|
+
if (typeof assistant.button.size !== "string") {
|
|
1730
|
+
throwConfigError("Assistant button size must be a string.", [
|
|
1731
|
+
"assistant",
|
|
1732
|
+
"button",
|
|
1733
|
+
"size",
|
|
1734
|
+
]);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
const trimmedSize = assistant.button.size.trim();
|
|
1738
|
+
if (trimmedSize !== "small" && trimmedSize !== "default") {
|
|
1739
|
+
throwConfigError(
|
|
1740
|
+
"Assistant button size must be either 'small' or 'default'.",
|
|
1741
|
+
["assistant", "button", "size"],
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1744
|
+
assistant.button.size = trimmedSize;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
if (assistant.heading !== undefined) {
|
|
1749
|
+
checkType(
|
|
1750
|
+
assistant.heading,
|
|
1751
|
+
"string",
|
|
1752
|
+
["assistant", "heading"],
|
|
1753
|
+
"Assistant heading",
|
|
1754
|
+
);
|
|
1755
|
+
if (typeof assistant.heading !== "string") {
|
|
1756
|
+
throwConfigError("Assistant heading must be a string.", [
|
|
1757
|
+
"assistant",
|
|
1758
|
+
"heading",
|
|
1759
|
+
]);
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
const trimmedHeading = assistant.heading.trim();
|
|
1763
|
+
if (trimmedHeading.length === 0) {
|
|
1764
|
+
throwConfigError("Assistant heading cannot be empty.", [
|
|
1765
|
+
"assistant",
|
|
1766
|
+
"heading",
|
|
1767
|
+
]);
|
|
1768
|
+
}
|
|
1769
|
+
assistant.heading = trimmedHeading;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
if (assistant.questions !== undefined) {
|
|
1773
|
+
checkType(
|
|
1774
|
+
assistant.questions,
|
|
1775
|
+
"array",
|
|
1776
|
+
["assistant", "questions"],
|
|
1777
|
+
"Assistant questions",
|
|
1778
|
+
);
|
|
1779
|
+
if (!Array.isArray(assistant.questions)) {
|
|
1780
|
+
throwConfigError("Assistant questions must be an array.", [
|
|
1781
|
+
"assistant",
|
|
1782
|
+
"questions",
|
|
1783
|
+
]);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
if (assistant.questions.length > 3) {
|
|
1787
|
+
throwConfigError("Assistant questions can include at most 3 questions.", [
|
|
1788
|
+
"assistant",
|
|
1789
|
+
"questions",
|
|
1790
|
+
]);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
assistant.questions = assistant.questions.map((question, index) => {
|
|
1794
|
+
checkType(
|
|
1795
|
+
question,
|
|
1796
|
+
"string",
|
|
1797
|
+
["assistant", "questions", String(index)],
|
|
1798
|
+
"Assistant question",
|
|
1799
|
+
);
|
|
1800
|
+
if (typeof question !== "string") {
|
|
1801
|
+
throwConfigError("Assistant question must be a string.", [
|
|
1802
|
+
"assistant",
|
|
1803
|
+
"questions",
|
|
1804
|
+
String(index),
|
|
1805
|
+
]);
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
const trimmedQuestion = question.trim();
|
|
1809
|
+
if (trimmedQuestion.length === 0) {
|
|
1810
|
+
throwConfigError(
|
|
1811
|
+
"Assistant question cannot be empty.",
|
|
1812
|
+
["assistant", "questions", String(index)],
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
return trimmedQuestion;
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
if (assistant.themeColor !== undefined) {
|
|
1821
|
+
if (typeof assistant.themeColor === "string") {
|
|
1822
|
+
assistant.themeColor = normalizeHexColor(
|
|
1823
|
+
assistant.themeColor,
|
|
1824
|
+
["assistant", "themeColor"],
|
|
1825
|
+
"Assistant theme color",
|
|
1826
|
+
);
|
|
1827
|
+
} else {
|
|
1828
|
+
checkType(
|
|
1829
|
+
assistant.themeColor,
|
|
1830
|
+
"object",
|
|
1831
|
+
["assistant", "themeColor"],
|
|
1832
|
+
"Assistant theme color",
|
|
1833
|
+
);
|
|
1834
|
+
if (
|
|
1835
|
+
typeof assistant.themeColor !== "object" ||
|
|
1836
|
+
assistant.themeColor === null ||
|
|
1837
|
+
Array.isArray(assistant.themeColor)
|
|
1838
|
+
) {
|
|
1839
|
+
throwConfigError(
|
|
1840
|
+
"Assistant theme color must be a string or an object with light/dark values.",
|
|
1841
|
+
["assistant", "themeColor"],
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
const assistantThemeColorByMode = assistant.themeColor as Record<
|
|
1846
|
+
string,
|
|
1847
|
+
unknown
|
|
1848
|
+
>;
|
|
1849
|
+
const allowedThemeColorKeys = new Set(["light", "dark"]);
|
|
1850
|
+
for (const key of Object.keys(assistantThemeColorByMode)) {
|
|
1851
|
+
if (!allowedThemeColorKeys.has(key)) {
|
|
1852
|
+
throwConfigError(
|
|
1853
|
+
"Assistant theme color object only supports 'light' and 'dark'.",
|
|
1854
|
+
["assistant", "themeColor", key],
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
const light =
|
|
1860
|
+
assistantThemeColorByMode.light !== undefined
|
|
1861
|
+
? normalizeHexColor(
|
|
1862
|
+
assistantThemeColorByMode.light,
|
|
1863
|
+
["assistant", "themeColor", "light"],
|
|
1864
|
+
"Assistant theme color light",
|
|
1865
|
+
)
|
|
1866
|
+
: undefined;
|
|
1867
|
+
const dark =
|
|
1868
|
+
assistantThemeColorByMode.dark !== undefined
|
|
1869
|
+
? normalizeHexColor(
|
|
1870
|
+
assistantThemeColorByMode.dark,
|
|
1871
|
+
["assistant", "themeColor", "dark"],
|
|
1872
|
+
"Assistant theme color dark",
|
|
1873
|
+
)
|
|
1874
|
+
: undefined;
|
|
1875
|
+
|
|
1876
|
+
if (!light && !dark) {
|
|
1877
|
+
throwConfigError(
|
|
1878
|
+
"Assistant theme color object must include 'light', 'dark', or both.",
|
|
1879
|
+
["assistant", "themeColor"],
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
assistant.themeColor = {
|
|
1884
|
+
...(light !== undefined ? { light } : {}),
|
|
1885
|
+
...(dark !== undefined ? { dark } : {}),
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
if (assistant.icon === undefined) return;
|
|
1891
|
+
|
|
1892
|
+
checkType(
|
|
1893
|
+
assistant.icon,
|
|
1894
|
+
"object",
|
|
1895
|
+
["assistant", "icon"],
|
|
1896
|
+
"Assistant icon",
|
|
1897
|
+
);
|
|
1898
|
+
if (
|
|
1899
|
+
typeof assistant.icon !== "object" ||
|
|
1900
|
+
assistant.icon === null ||
|
|
1901
|
+
Array.isArray(assistant.icon)
|
|
1902
|
+
) {
|
|
1903
|
+
throwConfigError("Assistant icon must be an object.", [
|
|
1904
|
+
"assistant",
|
|
1905
|
+
"icon",
|
|
1906
|
+
]);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
const allowedIconKeys = new Set(["src", "color"]);
|
|
1910
|
+
for (const key of Object.keys(assistant.icon)) {
|
|
1911
|
+
if (!allowedIconKeys.has(key)) {
|
|
1912
|
+
throwConfigError(
|
|
1913
|
+
"Assistant icon only supports 'src' and 'color'.",
|
|
1914
|
+
["assistant", "icon", key],
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
if (
|
|
1920
|
+
assistant.icon.src === undefined &&
|
|
1921
|
+
assistant.icon.color === undefined
|
|
1922
|
+
) {
|
|
1923
|
+
throwConfigError(
|
|
1924
|
+
"Assistant icon must include 'src', 'color', or both.",
|
|
1925
|
+
["assistant", "icon"],
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
if (assistant.icon.src !== undefined) {
|
|
1930
|
+
assistant.icon.src = validateAssistantIconSource(
|
|
1931
|
+
assistant.icon.src,
|
|
1932
|
+
["assistant", "icon", "src"],
|
|
1933
|
+
);
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
if (assistant.icon.color !== undefined) {
|
|
1937
|
+
assistant.icon.color = normalizeHexColor(
|
|
1938
|
+
assistant.icon.color,
|
|
1939
|
+
["assistant", "icon", "color"],
|
|
1940
|
+
"Assistant icon color",
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1569
1945
|
function validateHome(home: DocsConfig["home"]): string | undefined {
|
|
1570
1946
|
if (home === undefined) return undefined;
|
|
1571
1947
|
|
|
@@ -1765,6 +2141,7 @@ async function validateConfig(config: any): Promise<DocsConfig> {
|
|
|
1765
2141
|
validateTitle(config.title);
|
|
1766
2142
|
validateLogo(config.logo);
|
|
1767
2143
|
validateTheme(config.theme);
|
|
2144
|
+
validateAssistant(config.assistant);
|
|
1768
2145
|
await validateNavigation(config.navigation);
|
|
1769
2146
|
config.home = validateHome(config.home);
|
|
1770
2147
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { renderAssistantEmbedScript } from "../../../lib/assistant-embed-script";
|
|
2
|
+
import { getConfig } from "../../../lib/validation";
|
|
3
|
+
|
|
4
|
+
export const prerender = true;
|
|
5
|
+
|
|
6
|
+
export async function GET(): Promise<Response> {
|
|
7
|
+
const config = await getConfig();
|
|
8
|
+
|
|
9
|
+
return new Response(renderAssistantEmbedScript(config), {
|
|
10
|
+
headers: {
|
|
11
|
+
"Content-Type": "application/javascript; charset=utf-8",
|
|
12
|
+
"Cache-Control": "no-cache, max-age=0, must-revalidate",
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -9,10 +9,10 @@ import Layout from "../layouts/Layout.astro";
|
|
|
9
9
|
pageDescription="The page you requested could not be found."
|
|
10
10
|
>
|
|
11
11
|
<section
|
|
12
|
-
class="mx-auto mt-
|
|
12
|
+
class="mx-auto mt-20 h-full my-auto flex max-w-xl flex-col items-center gap-5 rounded-2xl text-center"
|
|
13
13
|
>
|
|
14
14
|
<p
|
|
15
|
-
class="font-mono text-sm font-medium tracking-[0.22em] text-neutral-500 uppercase dark:
|
|
15
|
+
class="rounded border border-neutral-200 bg-neutral-50 px-1 py-px pr-0.5 font-mono text-sm font-medium tracking-[0.22em] text-neutral-500 uppercase dark:border-neutral-800 dark:bg-neutral-900/60 dark:text-neutral-400"
|
|
16
16
|
>
|
|
17
17
|
404
|
|
18
18
|
</p>
|
|
@@ -27,7 +27,7 @@ import Layout from "../layouts/Layout.astro";
|
|
|
27
27
|
<div class="flex flex-wrap items-center justify-center gap-3">
|
|
28
28
|
<button
|
|
29
29
|
type="button"
|
|
30
|
-
class="inline-flex items-center justify-center gap-1.5 rounded-lg [corner-shape:superellipse(1.2)] border
|
|
30
|
+
class="inline-flex items-center justify-center gap-1.5 rounded-lg [corner-shape:superellipse(1.2)] border border-neutral-200 bg-white px-4 py-2 text-sm font-medium text-neutral-700/85 shadow-xs transition hover:bg-neutral-50 hover:text-neutral-700 dark:border-neutral-800 dark:bg-neutral-900/60 dark:text-neutral-300 dark:hover:bg-neutral-800/70 dark:hover:text-neutral-100 cursor-pointer"
|
|
31
31
|
onclick="history.back()"
|
|
32
32
|
>
|
|
33
33
|
<Icon name="lucide:arrow-big-left-dash" class="size-4" />
|
|
@@ -35,7 +35,7 @@ import Layout from "../layouts/Layout.astro";
|
|
|
35
35
|
</button>
|
|
36
36
|
<a
|
|
37
37
|
href={withBasePath("/")}
|
|
38
|
-
class="inline-flex items-center justify-center gap-2 rounded-lg [corner-shape:superellipse(1.2)] border border-border px-4 py-2 text-sm font-[350]
|
|
38
|
+
class="inline-flex items-center justify-center gap-2 rounded-lg [corner-shape:superellipse(1.2)] border border-border bg-linear-to-b from-neutral-900/85 to-neutral-900 px-4 py-2 text-sm font-[350] text-white shadow-sm transition hover:opacity-95 dark:from-neutral-100 dark:to-neutral-200 dark:font-[450] dark:text-neutral-950"
|
|
39
39
|
>
|
|
40
40
|
<Icon name="lucide:house" class="size-4" />
|
|
41
41
|
Take me home
|