@andesine/pages 0.0.0-stage → 0.4.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/CHANGELOG.md +99 -0
- package/LICENSE +21 -0
- package/README.md +92 -2
- package/dist/answers.d.ts +6 -0
- package/dist/astro/content.astro +118 -0
- package/dist/astro/middleware.js +39 -0
- package/dist/astro/middleware.js.map +1 -0
- package/dist/astro/routes/answers.js +129 -0
- package/dist/astro/routes/answers.js.map +1 -0
- package/dist/astro/routes/entry-pages.js +22 -0
- package/dist/astro/routes/entry-pages.js.map +1 -0
- package/dist/astro/routes/llms-full.js +12 -0
- package/dist/astro/routes/llms-full.js.map +1 -0
- package/dist/astro/routes/llms.js +12 -0
- package/dist/astro/routes/llms.js.map +1 -0
- package/dist/astro/routes/markdown-alias.js +14 -0
- package/dist/astro/routes/markdown-alias.js.map +1 -0
- package/dist/astro/routes/markdown.js +12 -0
- package/dist/astro/routes/markdown.js.map +1 -0
- package/dist/astro/routes/not-found.astro +12 -0
- package/dist/astro/routes/robots.js +9 -0
- package/dist/astro/routes/robots.js.map +1 -0
- package/dist/astro/routes/search.js +42 -0
- package/dist/astro/routes/search.js.map +1 -0
- package/dist/astro/routes/sitemap.js +12 -0
- package/dist/astro/routes/sitemap.js.map +1 -0
- package/dist/astro/routes/skill.js +13 -0
- package/dist/astro/routes/skill.js.map +1 -0
- package/dist/astro/routes/skills.js +24 -0
- package/dist/astro/routes/skills.js.map +1 -0
- package/dist/astro/routes/social.js +91 -0
- package/dist/astro/routes/social.js.map +1 -0
- package/dist/astro/routes/static.astro +53 -0
- package/dist/astro/routes/well-known-llms.js +2 -0
- package/dist/astro.d.ts +34 -0
- package/dist/astro.js +60 -0
- package/dist/astro.js.map +1 -0
- package/dist/chunks/assets-D6WJmZ05.js +21 -0
- package/dist/chunks/assets-D6WJmZ05.js.map +1 -0
- package/dist/chunks/catalog-DU4BUrpw.js +28 -0
- package/dist/chunks/catalog-DU4BUrpw.js.map +1 -0
- package/dist/chunks/config-CuH5JSI4.js +184 -0
- package/dist/chunks/config-CuH5JSI4.js.map +1 -0
- package/dist/chunks/files-BxqiQzFj.js +62 -0
- package/dist/chunks/files-BxqiQzFj.js.map +1 -0
- package/dist/chunks/hash-D4piIIQ6.js +11 -0
- package/dist/chunks/hash-D4piIIQ6.js.map +1 -0
- package/dist/chunks/index-D8wL2B5s.d.ts +571 -0
- package/dist/chunks/index-vsu3hISM.d.ts +62 -0
- package/dist/chunks/limits-Ppc7BQPG.js +45 -0
- package/dist/chunks/limits-Ppc7BQPG.js.map +1 -0
- package/dist/chunks/markdown-DKJSQxR2.js +42 -0
- package/dist/chunks/markdown-DKJSQxR2.js.map +1 -0
- package/dist/chunks/mdx-components-CUUDTapN.d.ts +9 -0
- package/dist/chunks/normalize-DG5nxOLm.js +361 -0
- package/dist/chunks/normalize-DG5nxOLm.js.map +1 -0
- package/dist/chunks/paths-75QgA3Qs.js +16 -0
- package/dist/chunks/paths-75QgA3Qs.js.map +1 -0
- package/dist/chunks/prepare-lnuw_ycM.js +219 -0
- package/dist/chunks/prepare-lnuw_ycM.js.map +1 -0
- package/dist/chunks/read-DCv1lmcM.js +57 -0
- package/dist/chunks/read-DCv1lmcM.js.map +1 -0
- package/dist/chunks/request-DOuBHGQs.js +92 -0
- package/dist/chunks/request-DOuBHGQs.js.map +1 -0
- package/dist/chunks/response-CtzaKMWH.js +15 -0
- package/dist/chunks/response-CtzaKMWH.js.map +1 -0
- package/dist/chunks/site-DY-uZ0PP.js +419 -0
- package/dist/chunks/site-DY-uZ0PP.js.map +1 -0
- package/dist/chunks/skill-CBTGUHQH.js +85 -0
- package/dist/chunks/skill-CBTGUHQH.js.map +1 -0
- package/dist/chunks/sources-Cm6NdIen.js +236 -0
- package/dist/chunks/sources-Cm6NdIen.js.map +1 -0
- package/dist/chunks/stored-CabomV-C.js +354 -0
- package/dist/chunks/stored-CabomV-C.js.map +1 -0
- package/dist/chunks/stored-CcEfvCQH.js +475 -0
- package/dist/chunks/stored-CcEfvCQH.js.map +1 -0
- package/dist/chunks/types-DBzuXfSe.d.ts +53 -0
- package/dist/chunks/view-C-9ZM1zK.js +745 -0
- package/dist/chunks/view-C-9ZM1zK.js.map +1 -0
- package/dist/chunks/view-Dlf_VRI7.d.ts +151 -0
- package/dist/client.d.ts +73 -0
- package/dist/client.js +295 -0
- package/dist/client.js.map +1 -0
- package/dist/collections.d.ts +7 -0
- package/dist/collections.js +215 -0
- package/dist/collections.js.map +1 -0
- package/dist/entry-pages.d.ts +10 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +16 -0
- package/dist/integration.js +435 -0
- package/dist/integration.js.map +1 -0
- package/dist/llms-full.d.ts +5 -0
- package/dist/llms.d.ts +5 -0
- package/dist/markdown-alias.d.ts +7 -0
- package/dist/markdown.d.ts +7 -0
- package/dist/middleware.d.ts +8 -0
- package/dist/robots.d.ts +5 -0
- package/dist/runtime.d.ts +81 -0
- package/dist/runtime.js +56 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scripts/restore-navigation.js +1 -0
- package/dist/search.d.ts +6 -0
- package/dist/sitemap.d.ts +5 -0
- package/dist/skill.d.ts +5 -0
- package/dist/skills.d.ts +10 -0
- package/dist/social.d.ts +7 -0
- package/dist/well-known-llms.d.ts +2 -0
- package/package.json +103 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
First release. The version follows the other Andesine packages.
|
|
6
|
+
|
|
7
|
+
- Add `defineConfig()` with config validation.
|
|
8
|
+
- Add the `andesine()` Astro integration. It reads `andesine.config.ts`, sets the
|
|
9
|
+
Astro site options, and renders pages with the template layout.
|
|
10
|
+
- Add the files source: Markdown and MDX pages from a directory, with folder
|
|
11
|
+
groups, index landing pages, frontmatter validation, and slugs.
|
|
12
|
+
- Add sections, source mounts, URL collision checks, and redirects to the first
|
|
13
|
+
page.
|
|
14
|
+
- Add `PageContext` with navigation, breadcrumbs, headings, and previous and next
|
|
15
|
+
links.
|
|
16
|
+
- Add MDX content components: a Sätteri plugin prepares `Tabs`, `CodeGroup`, and
|
|
17
|
+
`FileTree`, and a Shiki transformer adds code block titles. Pages render with the
|
|
18
|
+
template's component map.
|
|
19
|
+
- Add Andesine sources: build-time loading with copied images, entry property
|
|
20
|
+
mapping, collection navigation, and content rendering with the MDX component map
|
|
21
|
+
and Andesine heading anchors.
|
|
22
|
+
- Add search: a Pagefind index of local pages (build and development), a server
|
|
23
|
+
endpoint for Andesine published search, and a browser client with results
|
|
24
|
+
grouped by source.
|
|
25
|
+
- Add AI answers: a streaming server endpoint for Andesine sources, with cited
|
|
26
|
+
pages mapped to page URLs and follow-up history, the `answers` source option,
|
|
27
|
+
a browser answer client, and `renderAnswer` for safe answer Markdown.
|
|
28
|
+
- Add outputs: Markdown alternatives at `index.md`, `sitemap.xml`, `llms.txt`,
|
|
29
|
+
`robots.txt`, and a not-found page.
|
|
30
|
+
- Add canonical URLs, Markdown URLs, and page actions to `PageContext`, and the
|
|
31
|
+
`pageActions` option.
|
|
32
|
+
- Add the `summary` and `aside` slots for Andesine entry fragments.
|
|
33
|
+
- Remove the `slug`, `navigationLabel`, and `navigationHidden` Andesine properties.
|
|
34
|
+
URLs and labels come from the publication tree, and an `index` entry is its
|
|
35
|
+
collection's landing page.
|
|
36
|
+
- Support Cloudflare Workers for build-time Andesine sources: no social renderer
|
|
37
|
+
externals with the Cloudflare adapter, and `@iconify/json` loads on first use.
|
|
38
|
+
- Read the API key with `getSecret()` from `astro:env/server`, so each adapter
|
|
39
|
+
supplies it from its platform. Hashing uses Web Crypto, fonts decode with
|
|
40
|
+
`atob`, and the build-time image cache is a separate module, so server routes
|
|
41
|
+
do not import Node.js modules.
|
|
42
|
+
- Add the OpenAPI source (`type: "openapi"`): OpenAPI 3.0 and 3.1 specs from a
|
|
43
|
+
file or an HTTPS URL, validated and normalized at build time, with overview,
|
|
44
|
+
tag, and operation pages, navigation, Markdown alternatives, and search.
|
|
45
|
+
- Add `OperationView` for the template's `Operation` component: descriptions,
|
|
46
|
+
parameters, request bodies, responses with field trees, and generated cURL,
|
|
47
|
+
JavaScript, and Python examples, which `x-codeSamples` can replace. Add
|
|
48
|
+
`method` to navigation items.
|
|
49
|
+
- Add operation embeds: `<Operation source id />` in MDX and the `Operation`
|
|
50
|
+
element in Andesine content, resolved with `getOperationView()` from
|
|
51
|
+
`@andesine/pages/astro`. Markdown alternatives link to the operations. Boolean
|
|
52
|
+
props show or hide the description, authentication, parameters, request body,
|
|
53
|
+
responses, and examples; embeds hide the examples by default.
|
|
54
|
+
- Add OpenAPI operation pages to the development search index.
|
|
55
|
+
- Add the `tagPages` option of OpenAPI sources, to leave out tag landing pages.
|
|
56
|
+
- Add `links` to sections, shown after the site's links on the section's pages, and
|
|
57
|
+
`PageContext.links` with the links of the current page.
|
|
58
|
+
- Replace the git commit dates of file pages with an optional `updatedAt`
|
|
59
|
+
frontmatter field. Pages without it have no date.
|
|
60
|
+
- Add agent outputs: `llms-full.txt`, `/.well-known/llms.txt`, `.md` page aliases,
|
|
61
|
+
an index note in each Markdown page, the `agents.instructions` option,
|
|
62
|
+
absolute links in file page Markdown, an "Optional" section in `llms.txt`, and
|
|
63
|
+
`lastmod` in the sitemap.
|
|
64
|
+
- Serve Markdown for requests with `Accept: text/markdown` in development.
|
|
65
|
+
- Add `<Visibility for="agents">` and `<Visibility for="humans">` for MDX files
|
|
66
|
+
and Andesine content.
|
|
67
|
+
- Add schema.org JSON-LD, the last update date, and `noindex` for pages hidden
|
|
68
|
+
from search to `PageContext`.
|
|
69
|
+
- Add social images: a generated image for each page from the template's social
|
|
70
|
+
card, the `social` option, and `page.image`. `satori` and `@resvg/resvg-js`
|
|
71
|
+
are optional peer dependencies.
|
|
72
|
+
- The integration no longer sets `trailingSlash: "always"`, so pages answer with
|
|
73
|
+
and without the trailing slash. Package URLs still end with a slash.
|
|
74
|
+
- The answers endpoint allows 10 questions a minute for each client address.
|
|
75
|
+
- The integration inlines a script into each page's `<head>` that restores saved
|
|
76
|
+
navigation tree groups before the first paint.
|
|
77
|
+
- Add the `socialLinks` option for footer icon links. `links` are icon links at the
|
|
78
|
+
top of the navigation, and need an `icon`.
|
|
79
|
+
- Add `PageContext.meta`, `PageContext.fragments`, and `site.storageKey`;
|
|
80
|
+
`renderTabs` and `readFileTreeItems` in `@andesine/pages/astro` (replacing
|
|
81
|
+
`renderPanels`); and `groupAnswerSources`. The answer client takes earlier
|
|
82
|
+
turns and enforces the API history limits. Social card fonts accept data URLs, and
|
|
83
|
+
`SocialCardData.showSiteName` follows `logo.title`.
|
|
84
|
+
- Add the `html` template tag for social card markup: it escapes values and
|
|
85
|
+
leaves out `false`, `null`, and `undefined`. Card markup can use `tw` classes.
|
|
86
|
+
- Add `format` (`icon` or `full`) and `alt` to the `logo` option, also in social
|
|
87
|
+
cards.
|
|
88
|
+
- Add the `sectionsDisplay` option: sections as header tabs, or as links at the
|
|
89
|
+
top of the navigation.
|
|
90
|
+
- The development search index uses the plain text of MDX pages, without JSX tags and
|
|
91
|
+
Markdown syntax, like the built index.
|
|
92
|
+
- Add a generated `skill.md` with agentskills.io and `skills` CLI discovery
|
|
93
|
+
indexes, and the `agents.skill` option for your own skill file.
|
|
94
|
+
- Add the `publicKey` option to Andesine sources. With a publishable key, search and
|
|
95
|
+
AI answers call the Andesine API from browsers, and the build writes a JSON map of
|
|
96
|
+
entry URLs in place of the `/_andesine/` server routes, so the site stays static.
|
|
97
|
+
Without a secret key, the build also reads the content with the publishable key.
|
|
98
|
+
- Search and AI answers show "Too many…" only for short rate limits. Used-up monthly
|
|
99
|
+
quotas and spending limits show that the feature is not available now.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vrite, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,93 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @andesine/pages
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Astro integration and data layer of Andesine Pages. Use it with the Andesine
|
|
4
|
+
Pages template, which supplies the layouts, styles, and components.
|
|
5
|
+
|
|
6
|
+
> **0.x.** The config and the page context can change in minor releases; the
|
|
7
|
+
> changelog lists the changes. Use the version that the template pins.
|
|
8
|
+
|
|
9
|
+
## Exports
|
|
10
|
+
|
|
11
|
+
| Export | Contents |
|
|
12
|
+
| ----------------------------- | ---------------------------------------- |
|
|
13
|
+
| `@andesine/pages` | `defineConfig()`, config and page types. |
|
|
14
|
+
| `@andesine/pages/integration` | The `andesine()` Astro integration. |
|
|
15
|
+
| `@andesine/pages/collections` | Content collections for the sources. |
|
|
16
|
+
| `@andesine/pages/astro` | Helpers for the template's components. |
|
|
17
|
+
| `@andesine/pages/client` | The browser search client. |
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
`andesine.config.ts`:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { defineConfig } from "@andesine/pages";
|
|
25
|
+
|
|
26
|
+
export default defineConfig({
|
|
27
|
+
name: "Example",
|
|
28
|
+
site: "https://docs.example.com"
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`astro.config.ts`:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { defineConfig } from "astro/config";
|
|
36
|
+
import { andesine } from "@andesine/pages/integration";
|
|
37
|
+
|
|
38
|
+
export default defineConfig({ integrations: [andesine()] });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`src/content.config.ts`:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
export { collections } from "@andesine/pages/collections";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The integration reads `andesine.config.ts` and sets the Astro `site`, `base`, and
|
|
48
|
+
`trailingSlash` options. It adds one static route for the pages of all files
|
|
49
|
+
sources. Each page renders with `src/layouts/page.astro`. The layout receives one
|
|
50
|
+
`page` prop with the type `PageContext`, and the page body in its default slot.
|
|
51
|
+
|
|
52
|
+
A source mount or the site root without a page redirects to the first page. The
|
|
53
|
+
build stops when two pages use the same URL.
|
|
54
|
+
|
|
55
|
+
| Option | Default | Description |
|
|
56
|
+
| ------------ | --------------------------------- | ---------------------------------------- |
|
|
57
|
+
| `config` | `andesine.config.ts` | Config file, relative to the root. |
|
|
58
|
+
| `layout` | `src/layouts/page.astro` | Page layout, relative to the root. |
|
|
59
|
+
| `components` | `src/components/content/index.ts` | MDX component map, relative to the root. |
|
|
60
|
+
|
|
61
|
+
Andesine sources read the latest publication with `@andesine/sdk` at build time.
|
|
62
|
+
They load in a content collection and copy their images into the build.
|
|
63
|
+
Content converts with `@andesine/converters`, so heading IDs match Andesine search
|
|
64
|
+
and answers, and renders with the same component map as MDX.
|
|
65
|
+
|
|
66
|
+
OpenAPI sources read an OpenAPI 3.0 or 3.1 spec at build time, from a file or an
|
|
67
|
+
HTTPS URL, and make overview, tag, and operation pages. The template's `Operation`
|
|
68
|
+
component renders operations with `getOperationView()` from `@andesine/pages/astro`,
|
|
69
|
+
on reference pages and in embeds (`<Operation source="api" id="..." />`).
|
|
70
|
+
|
|
71
|
+
## Search
|
|
72
|
+
|
|
73
|
+
Local pages are in a Pagefind index. The build writes it to `pagefind/`, and the dev
|
|
74
|
+
server builds it in memory from the source text. Andesine sources use the published
|
|
75
|
+
search API. With a `publicKey` (a publishable `adn_pk_` key), the build can read
|
|
76
|
+
the content without a secret key, browsers call the API
|
|
77
|
+
directly, and map results to pages with `/_andesine/pages/<source>.json`, which the
|
|
78
|
+
build writes. Without one, they use `/_andesine/search/`, a server route that keeps
|
|
79
|
+
the API key on the server, so the site needs a server adapter. Results
|
|
80
|
+
of build-time sources link only to pages in the build; results of pages that were
|
|
81
|
+
published after the build are left out. Pages with `searchHidden` are left out of
|
|
82
|
+
results. This is not access control.
|
|
83
|
+
|
|
84
|
+
`createSearchClient(page.site.search)` from `@andesine/pages/client` searches all
|
|
85
|
+
sources and returns results grouped by source.
|
|
86
|
+
|
|
87
|
+
The component map module exports `components`. MDX pages can use these components
|
|
88
|
+
without imports. The integration adds a plugin to the Sätteri Markdown processor
|
|
89
|
+
that prepares `<Tabs>`, `<CodeGroup>`, and `<FileTree>` content, and a Shiki
|
|
90
|
+
transformer that reads `title="..."` from code fences.
|
|
91
|
+
|
|
92
|
+
Relative `logo.src` and `favicon` paths in the config are relative to the config
|
|
93
|
+
file. The integration bundles them as assets.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { Element, RootContent } from "hast";
|
|
3
|
+
import { toHtml } from "hast-util-to-html";
|
|
4
|
+
import { isElement, readElement, toFileTreeItems } from "../runtime.js";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
nodes: RootContent[];
|
|
8
|
+
/** The template's content components, keyed by element name, and `pre` for code. */
|
|
9
|
+
components: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { nodes, components } = Astro.props;
|
|
13
|
+
const marker = "@@andesine-children@@";
|
|
14
|
+
// Subtrees without custom elements or code blocks render as one HTML string.
|
|
15
|
+
const isPlain = (node: RootContent): boolean =>
|
|
16
|
+
!isElement(node) ||
|
|
17
|
+
(!node.properties.dataAndesineElement && node.tagName !== "pre" && node.children.every(isPlain));
|
|
18
|
+
const getComponent = (name: string): any => {
|
|
19
|
+
if (!components[name]) throw new Error(`The template has no component for <${name}>.`);
|
|
20
|
+
|
|
21
|
+
return components[name];
|
|
22
|
+
};
|
|
23
|
+
const getLabels = (items: Element[], label: (item: Element) => string): string[] => {
|
|
24
|
+
const labels = items.map(label);
|
|
25
|
+
|
|
26
|
+
if (new Set(labels).size !== labels.length) {
|
|
27
|
+
throw new Error(`Tabs need a unique label for each tab: ${labels.join(", ")}.`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return labels;
|
|
31
|
+
};
|
|
32
|
+
// Andesine code blocks have no titles, so a repeated language gets a number, e.g. `bash 2`.
|
|
33
|
+
const getCodeLabels = (items: Element[]): string[] => {
|
|
34
|
+
const counts = new Map<string, number>();
|
|
35
|
+
|
|
36
|
+
return items.map((pre) => {
|
|
37
|
+
const language = String(pre.properties.dataLanguage || "Code");
|
|
38
|
+
const count = (counts.get(language) ?? 0) + 1;
|
|
39
|
+
|
|
40
|
+
counts.set(language, count);
|
|
41
|
+
|
|
42
|
+
return count > 1 ? `${language} ${count}` : language;
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
nodes.map((node) => {
|
|
49
|
+
if (isPlain(node)) return <Fragment set:html={toHtml(node)} />;
|
|
50
|
+
|
|
51
|
+
const element = node as Element;
|
|
52
|
+
const custom = readElement(element);
|
|
53
|
+
const children = element.children.filter(isElement);
|
|
54
|
+
|
|
55
|
+
if (custom?.name === "Tabs") {
|
|
56
|
+
const Component = getComponent("Tabs");
|
|
57
|
+
const labels = getLabels(children, (tab) => String(readElement(tab)?.props.label));
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Component values={JSON.stringify(labels)} syncKey={custom.props.syncKey}>
|
|
61
|
+
{(index: number) => (
|
|
62
|
+
<Astro.self nodes={children[index]!.children} components={components} />
|
|
63
|
+
)}
|
|
64
|
+
</Component>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (custom?.name === "CodeGroup") {
|
|
69
|
+
const Component = getComponent("CodeGroup");
|
|
70
|
+
const labels = getCodeLabels(children);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Component values={JSON.stringify(labels)}>
|
|
74
|
+
{(index: number) => <Astro.self nodes={[children[index]!]} components={components} />}
|
|
75
|
+
</Component>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (custom?.name === "FileTree") {
|
|
80
|
+
const Component = getComponent("FileTree");
|
|
81
|
+
|
|
82
|
+
return <Component items={JSON.stringify(toFileTreeItems(children[0]!))} />;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (custom) {
|
|
86
|
+
const Component = getComponent(custom.name);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<Component {...custom.props}>
|
|
90
|
+
<Astro.self nodes={element.children} components={components} />
|
|
91
|
+
</Component>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (element.tagName === "pre" && components.pre) {
|
|
96
|
+
const Code = getComponent("pre");
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<Code data-language={element.properties.dataLanguage}>
|
|
100
|
+
<Fragment set:html={toHtml(element.children)} />
|
|
101
|
+
</Code>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Keeps the element, and renders its children with this component.
|
|
106
|
+
const [open, close] = toHtml({ ...element, children: [{ type: "text", value: marker }] }).split(
|
|
107
|
+
marker
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<>
|
|
112
|
+
<Fragment set:html={open} />
|
|
113
|
+
<Astro.self nodes={element.children} components={components} />
|
|
114
|
+
<Fragment set:html={close} />
|
|
115
|
+
</>
|
|
116
|
+
);
|
|
117
|
+
})
|
|
118
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineMiddleware } from "astro:middleware";
|
|
2
|
+
//#region src/astro/middleware.ts
|
|
3
|
+
/** The highest quality of the given media types in an `Accept` header. */
|
|
4
|
+
const getQuality = (accept, types) => {
|
|
5
|
+
return Math.max(0, ...accept.split(",").flatMap((range) => {
|
|
6
|
+
const [type = "", ...parameters] = range.split(";").map((part) => part.trim());
|
|
7
|
+
const quality = parameters.find((parameter) => parameter.startsWith("q="));
|
|
8
|
+
return types.includes(type.toLowerCase()) ? [quality ? Number(quality.slice(2)) || 0 : 1] : [];
|
|
9
|
+
}));
|
|
10
|
+
};
|
|
11
|
+
/** Agents ask for Markdown with `text/markdown`, or with `text/plain` instead of HTML. */
|
|
12
|
+
const prefersMarkdown = (accept) => {
|
|
13
|
+
const markdown = getQuality(accept, [
|
|
14
|
+
"text/markdown",
|
|
15
|
+
"text/x-markdown",
|
|
16
|
+
"text/plain"
|
|
17
|
+
]);
|
|
18
|
+
return markdown > 0 && markdown > getQuality(accept, ["text/html", "application/xhtml+xml"]);
|
|
19
|
+
};
|
|
20
|
+
const withVary = (response) => {
|
|
21
|
+
const result = new Response(response.body, response);
|
|
22
|
+
result.headers.append("Vary", "Accept");
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Serves a page's Markdown to requests that prefer it. Hosts serve built pages as files, so this
|
|
27
|
+
* works in development only; a host can add the same rule for `Accept: text/markdown`.
|
|
28
|
+
*/
|
|
29
|
+
const onRequest = defineMiddleware(async (context, next) => {
|
|
30
|
+
const { pathname } = context.url;
|
|
31
|
+
const isPage = !/\.[^/]+$/.test(pathname);
|
|
32
|
+
if (context.isPrerendered || !isPage) return next();
|
|
33
|
+
const accept = context.request.headers.get("accept") ?? "";
|
|
34
|
+
return withVary(prefersMarkdown(accept) ? await context.rewrite(`${pathname.replace(/\/?$/, "/")}index.md`) : await next());
|
|
35
|
+
});
|
|
36
|
+
//#endregion
|
|
37
|
+
export { onRequest };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","names":[],"sources":["../../src/astro/middleware.ts"],"sourcesContent":["import { defineMiddleware } from \"astro:middleware\";\n\n/** The highest quality of the given media types in an `Accept` header. */\nconst getQuality = (accept: string, types: string[]): number => {\n return Math.max(\n 0,\n ...accept.split(\",\").flatMap((range) => {\n const [type = \"\", ...parameters] = range.split(\";\").map((part) => part.trim());\n const quality = parameters.find((parameter) => parameter.startsWith(\"q=\"));\n\n return types.includes(type.toLowerCase())\n ? [quality ? Number(quality.slice(2)) || 0 : 1]\n : [];\n })\n );\n};\n/** Agents ask for Markdown with `text/markdown`, or with `text/plain` instead of HTML. */\nconst prefersMarkdown = (accept: string): boolean => {\n const markdown = getQuality(accept, [\"text/markdown\", \"text/x-markdown\", \"text/plain\"]);\n\n return markdown > 0 && markdown > getQuality(accept, [\"text/html\", \"application/xhtml+xml\"]);\n};\nconst withVary = (response: Response): Response => {\n const result = new Response(response.body, response);\n\n result.headers.append(\"Vary\", \"Accept\");\n\n return result;\n};\n\n/**\n * Serves a page's Markdown to requests that prefer it. Hosts serve built pages as files, so this\n * works in development only; a host can add the same rule for `Accept: text/markdown`.\n */\nexport const onRequest = defineMiddleware(async (context, next) => {\n const { pathname } = context.url;\n // Page URLs have no file extension; they can end with or without a slash.\n const isPage = !/\\.[^/]+$/.test(pathname);\n\n if (context.isPrerendered || !isPage) return next();\n\n const accept = context.request.headers.get(\"accept\") ?? \"\";\n\n return withVary(\n prefersMarkdown(accept)\n ? await context.rewrite(`${pathname.replace(/\\/?$/, \"/\")}index.md`)\n : await next()\n );\n});\n"],"mappings":";;;AAGA,MAAM,cAAc,QAAgB,UAA4B;CAC9D,OAAO,KAAK,IACV,GACA,GAAG,OAAO,MAAM,GAAG,CAAC,CAAC,SAAS,UAAU;EACtC,MAAM,CAAC,OAAO,IAAI,GAAG,cAAc,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,SAAS,KAAK,KAAK,CAAC;EAC7E,MAAM,UAAU,WAAW,MAAM,cAAc,UAAU,WAAW,IAAI,CAAC;EAEzE,OAAO,MAAM,SAAS,KAAK,YAAY,CAAC,IACpC,CAAC,UAAU,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC,IAC5C,CAAC;CACP,CAAC,CACH;AACF;;AAEA,MAAM,mBAAmB,WAA4B;CACnD,MAAM,WAAW,WAAW,QAAQ;EAAC;EAAiB;EAAmB;CAAY,CAAC;CAEtF,OAAO,WAAW,KAAK,WAAW,WAAW,QAAQ,CAAC,aAAa,uBAAuB,CAAC;AAC7F;AACA,MAAM,YAAY,aAAiC;CACjD,MAAM,SAAS,IAAI,SAAS,SAAS,MAAM,QAAQ;CAEnD,OAAO,QAAQ,OAAO,QAAQ,QAAQ;CAEtC,OAAO;AACT;;;;;AAMA,MAAa,YAAY,iBAAiB,OAAO,SAAS,SAAS;CACjE,MAAM,EAAE,aAAa,QAAQ;CAE7B,MAAM,SAAS,CAAC,WAAW,KAAK,QAAQ;CAExC,IAAI,QAAQ,iBAAiB,CAAC,QAAQ,OAAO,KAAK;CAElD,MAAM,SAAS,QAAQ,QAAQ,QAAQ,IAAI,QAAQ,KAAK;CAExD,OAAO,SACL,gBAAgB,MAAM,IAClB,MAAM,QAAQ,QAAQ,GAAG,SAAS,QAAQ,QAAQ,GAAG,EAAE,SAAS,IAChE,MAAM,KAAK,CACjB;AACF,CAAC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { r as toAnswerSources } from "../../chunks/limits-Ppc7BQPG.js";
|
|
2
|
+
import { i as toErrorResponse, n as json, r as readRequest, t as createPageResolver } from "../../chunks/request-DOuBHGQs.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import config from "virtual:andesine/config";
|
|
5
|
+
//#region src/search/rate-limit.ts
|
|
6
|
+
/**
|
|
7
|
+
* A small in-memory rate limit per client, e.g. by IP address. Each server instance counts on its
|
|
8
|
+
* own, so it only limits abuse; the Andesine API applies its own limits too.
|
|
9
|
+
*/
|
|
10
|
+
const createRateLimit = (options) => {
|
|
11
|
+
const requests = /* @__PURE__ */ new Map();
|
|
12
|
+
let prunedAt = 0;
|
|
13
|
+
return (client) => {
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
const recent = (requests.get(client) ?? []).filter((time) => now - time < options.window);
|
|
16
|
+
if (requests.size > 1e4 && now - prunedAt >= options.window) {
|
|
17
|
+
prunedAt = now;
|
|
18
|
+
for (const [key, times] of requests) if (times.every((time) => now - time >= options.window)) requests.delete(key);
|
|
19
|
+
}
|
|
20
|
+
if (recent.length >= options.limit) {
|
|
21
|
+
requests.set(client, recent);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
requests.set(client, [...recent, now]);
|
|
25
|
+
return true;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/search/answers.ts
|
|
30
|
+
/** The API limits for questions and history. */
|
|
31
|
+
const bodySchema = z.object({
|
|
32
|
+
source: z.string().min(1),
|
|
33
|
+
question: z.string().trim().min(1).max(1e3),
|
|
34
|
+
history: z.array(z.object({
|
|
35
|
+
role: z.enum(["user", "assistant"]),
|
|
36
|
+
content: z.string().trim().min(1).max(4e3)
|
|
37
|
+
}).strict()).max(10).default([])
|
|
38
|
+
}).strict();
|
|
39
|
+
const encoder = new TextEncoder();
|
|
40
|
+
const allowQuestion = createRateLimit({
|
|
41
|
+
limit: 10,
|
|
42
|
+
window: 6e4
|
|
43
|
+
});
|
|
44
|
+
const encode = (event) => encoder.encode(`${JSON.stringify(event)}\n`);
|
|
45
|
+
/**
|
|
46
|
+
* Streams an AI answer about the published collection of an Andesine source, as one JSON
|
|
47
|
+
* event per line. Cited pages are mapped to page URLs; pages without one are left out.
|
|
48
|
+
*/
|
|
49
|
+
const handleAnswer = async (request, config, clientAddress) => {
|
|
50
|
+
if (clientAddress && !allowQuestion(clientAddress)) return json({ error: "Too many questions. Try again soon." }, 429, { "Retry-After": "60" });
|
|
51
|
+
const result = await readRequest(request, config, {
|
|
52
|
+
schema: bodySchema,
|
|
53
|
+
maxSize: 64e3,
|
|
54
|
+
timeout: 12e4
|
|
55
|
+
});
|
|
56
|
+
if ("response" in result) return result.response;
|
|
57
|
+
const { body, source, client, signal } = result.request;
|
|
58
|
+
if (!source.answers) return json({ error: "AI answers are off for this source." }, 403);
|
|
59
|
+
let resolve;
|
|
60
|
+
let events;
|
|
61
|
+
try {
|
|
62
|
+
[resolve, events] = await Promise.all([createPageResolver(source, config.base), client.search.askPublishedStream({
|
|
63
|
+
question: body.question,
|
|
64
|
+
history: body.history,
|
|
65
|
+
collectionID: source.collection,
|
|
66
|
+
channel: "published"
|
|
67
|
+
}, { signal })]);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return toErrorResponse(error, {
|
|
70
|
+
limited: "Too many questions. Try again soon.",
|
|
71
|
+
failed: "AI answers are not available now."
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const iterator = events[Symbol.asyncIterator]();
|
|
75
|
+
const stream = new ReadableStream({
|
|
76
|
+
async pull(controller) {
|
|
77
|
+
try {
|
|
78
|
+
const next = await iterator.next();
|
|
79
|
+
if (next.done) {
|
|
80
|
+
controller.close();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const event = next.value;
|
|
84
|
+
if (event.type === "textDelta") controller.enqueue(encode(event));
|
|
85
|
+
else if (event.type === "sources") controller.enqueue(encode({
|
|
86
|
+
type: "sources",
|
|
87
|
+
sources: toAnswerSources(event.sources, resolve)
|
|
88
|
+
}));
|
|
89
|
+
else controller.enqueue(encode({
|
|
90
|
+
type: "completed",
|
|
91
|
+
answer: event.answer,
|
|
92
|
+
sources: toAnswerSources(event.sources, resolve)
|
|
93
|
+
}));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (!signal.aborted) console.error(error);
|
|
96
|
+
controller.enqueue(encode({
|
|
97
|
+
type: "error",
|
|
98
|
+
error: "The answer could not be completed."
|
|
99
|
+
}));
|
|
100
|
+
controller.close();
|
|
101
|
+
await iterator.return?.();
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
async cancel() {
|
|
105
|
+
await iterator.return?.();
|
|
106
|
+
}
|
|
107
|
+
}, { highWaterMark: 0 });
|
|
108
|
+
return new Response(stream, { headers: {
|
|
109
|
+
"Content-Type": "application/x-ndjson; charset=utf-8",
|
|
110
|
+
"Cache-Control": "private, no-store, no-transform",
|
|
111
|
+
"X-Accel-Buffering": "no"
|
|
112
|
+
} });
|
|
113
|
+
};
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/astro/routes/answers.ts
|
|
116
|
+
const prerender = false;
|
|
117
|
+
const POST = (context) => {
|
|
118
|
+
let clientAddress;
|
|
119
|
+
try {
|
|
120
|
+
clientAddress = context.clientAddress;
|
|
121
|
+
} catch {
|
|
122
|
+
clientAddress = void 0;
|
|
123
|
+
}
|
|
124
|
+
return handleAnswer(context.request, config, clientAddress);
|
|
125
|
+
};
|
|
126
|
+
//#endregion
|
|
127
|
+
export { POST, prerender };
|
|
128
|
+
|
|
129
|
+
//# sourceMappingURL=answers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"answers.js","names":[],"sources":["../../../src/search/rate-limit.ts","../../../src/search/answers.ts","../../../src/astro/routes/answers.ts"],"sourcesContent":["interface RateLimitOptions {\n /** Requests allowed in each window. */\n limit: number;\n /** Window length, in milliseconds. */\n window: number;\n}\n\n/**\n * A small in-memory rate limit per client, e.g. by IP address. Each server instance counts on its\n * own, so it only limits abuse; the Andesine API applies its own limits too.\n */\nconst createRateLimit = (options: RateLimitOptions): ((client: string) => boolean) => {\n const requests = new Map<string, number[]>();\n\n let prunedAt = 0;\n\n return (client) => {\n const now = Date.now();\n const recent = (requests.get(client) ?? []).filter((time) => now - time < options.window);\n const shouldPrune = requests.size > 10_000 && now - prunedAt >= options.window;\n\n // Forgets clients without recent requests, at most once a window, so memory stays small.\n if (shouldPrune) {\n prunedAt = now;\n\n for (const [key, times] of requests) {\n if (times.every((time) => now - time >= options.window)) requests.delete(key);\n }\n }\n\n if (recent.length >= options.limit) {\n requests.set(client, recent);\n\n return false;\n }\n\n requests.set(client, [...recent, now]);\n\n return true;\n };\n};\n\nexport { createRateLimit };\nexport type { RateLimitOptions };\n","import type { PublishedAnswerEvent } from \"@andesine/sdk\";\nimport { z } from \"zod\";\nimport type { PagesConfig } from \"../config\";\nimport { createRateLimit } from \"./rate-limit\";\nimport { toAnswerSources, type PageResolver } from \"./items\";\nimport { createPageResolver, json, readRequest, toErrorResponse } from \"./request\";\nimport type { AnswerEvent } from \"./types\";\n\n/** The API limits for questions and history. */\nconst bodySchema = z\n .object({\n source: z.string().min(1),\n question: z.string().trim().min(1).max(1000),\n history: z\n .array(\n z\n .object({\n role: z.enum([\"user\", \"assistant\"]),\n content: z.string().trim().min(1).max(4000)\n })\n .strict()\n )\n .max(10)\n .default([])\n })\n .strict();\nconst encoder = new TextEncoder();\n// Each client can ask 10 questions a minute.\nconst allowQuestion = createRateLimit({ limit: 10, window: 60_000 });\n\nconst encode = (event: AnswerEvent): Uint8Array => encoder.encode(`${JSON.stringify(event)}\\n`);\n/**\n * Streams an AI answer about the published collection of an Andesine source, as one JSON\n * event per line. Cited pages are mapped to page URLs; pages without one are left out.\n */\nconst handleAnswer = async (\n request: Request,\n config: PagesConfig,\n clientAddress?: string\n): Promise<Response> => {\n if (clientAddress && !allowQuestion(clientAddress)) {\n return json({ error: \"Too many questions. Try again soon.\" }, 429, { \"Retry-After\": \"60\" });\n }\n\n const result = await readRequest(request, config, {\n schema: bodySchema,\n maxSize: 64_000,\n timeout: 120_000\n });\n\n if (\"response\" in result) return result.response;\n\n const { body, source, client, signal } = result.request;\n\n if (!source.answers) return json({ error: \"AI answers are off for this source.\" }, 403);\n\n let resolve: PageResolver;\n let events: AsyncIterable<PublishedAnswerEvent>;\n\n try {\n [resolve, events] = await Promise.all([\n createPageResolver(source, config.base),\n client.search.askPublishedStream(\n {\n question: body.question,\n history: body.history,\n collectionID: source.collection,\n channel: \"published\"\n },\n { signal }\n )\n ]);\n } catch (error) {\n return toErrorResponse(error, {\n limited: \"Too many questions. Try again soon.\",\n failed: \"AI answers are not available now.\"\n });\n }\n\n const iterator = events[Symbol.asyncIterator]();\n const stream = new ReadableStream<Uint8Array>(\n {\n async pull(controller) {\n try {\n const next = await iterator.next();\n\n if (next.done) {\n controller.close();\n\n return;\n }\n\n const event = next.value;\n\n if (event.type === \"textDelta\") {\n controller.enqueue(encode(event));\n } else if (event.type === \"sources\") {\n controller.enqueue(\n encode({ type: \"sources\", sources: toAnswerSources(event.sources, resolve) })\n );\n } else {\n controller.enqueue(\n encode({\n type: \"completed\",\n answer: event.answer,\n sources: toAnswerSources(event.sources, resolve)\n })\n );\n }\n } catch (error) {\n if (!signal.aborted) console.error(error);\n\n controller.enqueue(\n encode({ type: \"error\", error: \"The answer could not be completed.\" })\n );\n controller.close();\n await iterator.return?.();\n }\n },\n async cancel() {\n await iterator.return?.();\n }\n },\n { highWaterMark: 0 }\n );\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"application/x-ndjson; charset=utf-8\",\n \"Cache-Control\": \"private, no-store, no-transform\",\n \"X-Accel-Buffering\": \"no\"\n }\n });\n};\n\nexport { handleAnswer };\n","import type { APIRoute } from \"astro\";\nimport config from \"virtual:andesine/config\";\nimport { handleAnswer } from \"../../search/answers\";\n\nexport const prerender = false;\n\nexport const POST: APIRoute = (context) => {\n let clientAddress: string | undefined;\n\n // Adapters that do not know the client address throw here; the endpoint then has no limit.\n try {\n clientAddress = context.clientAddress;\n } catch {\n clientAddress = undefined;\n }\n\n return handleAnswer(context.request, config, clientAddress);\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { t as loadAndesineSource } from "../../chunks/stored-CabomV-C.js";
|
|
2
|
+
import { t as textResponse } from "../../chunks/response-CtzaKMWH.js";
|
|
3
|
+
import config from "virtual:andesine/config";
|
|
4
|
+
//#region src/astro/routes/entry-pages.ts
|
|
5
|
+
const sources = config.sources.filter((source) => {
|
|
6
|
+
return source.type === "andesine" && Boolean(source.publicKey);
|
|
7
|
+
});
|
|
8
|
+
const getStaticPaths = () => sources.map((source) => ({ params: { source: source.id } }));
|
|
9
|
+
/**
|
|
10
|
+
* Maps the entry IDs of a source with a publishable key to page URLs, so the browser can link
|
|
11
|
+
* API search results and answer sources. Pages hidden from search are left out.
|
|
12
|
+
*/
|
|
13
|
+
const GET = async ({ params }) => {
|
|
14
|
+
const source = sources.find((item) => item.id === params.source);
|
|
15
|
+
const { pages } = await loadAndesineSource(source, config.base);
|
|
16
|
+
const hrefs = Object.fromEntries(pages.filter((page) => !page.searchHidden).map((page) => [page.id, page.href]));
|
|
17
|
+
return textResponse(JSON.stringify(hrefs), "application/json");
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
export { GET, getStaticPaths };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=entry-pages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-pages.js","names":[],"sources":["../../../src/astro/routes/entry-pages.ts"],"sourcesContent":["import type { APIRoute } from \"astro\";\nimport config from \"virtual:andesine/config\";\nimport type { AndesineSourceConfig } from \"../../config\";\nimport { textResponse } from \"../../output/response\";\nimport { loadAndesineSource } from \"../../sources/andesine/stored\";\n\nconst sources = config.sources.filter((source): source is AndesineSourceConfig => {\n return source.type === \"andesine\" && Boolean(source.publicKey);\n});\n\nexport const getStaticPaths = () => sources.map((source) => ({ params: { source: source.id } }));\n\n/**\n * Maps the entry IDs of a source with a publishable key to page URLs, so the browser can link\n * API search results and answer sources. Pages hidden from search are left out.\n */\nexport const GET: APIRoute = async ({ params }) => {\n const source = sources.find((item) => item.id === params.source)!;\n const { pages } = await loadAndesineSource(source, config.base);\n const hrefs = Object.fromEntries(\n pages.filter((page) => !page.searchHidden).map((page) => [page.id, page.href])\n );\n\n return textResponse(JSON.stringify(hrefs), \"application/json\");\n};\n"],"mappings":";;;;AAMA,MAAM,UAAU,OAAO,QAAQ,QAAQ,WAA2C;CAChF,OAAO,OAAO,SAAS,cAAc,QAAQ,OAAO,SAAS;AAC/D,CAAC;AAED,MAAa,uBAAuB,QAAQ,KAAK,YAAY,EAAE,QAAQ,EAAE,QAAQ,OAAO,GAAG,EAAE,EAAE;;;;;AAM/F,MAAa,MAAgB,OAAO,EAAE,aAAa;CACjD,MAAM,SAAS,QAAQ,MAAM,SAAS,KAAK,OAAO,OAAO,MAAM;CAC/D,MAAM,EAAE,UAAU,MAAM,mBAAmB,QAAQ,OAAO,IAAI;CAC9D,MAAM,QAAQ,OAAO,YACnB,MAAM,QAAQ,SAAS,CAAC,KAAK,YAAY,CAAC,CAAC,KAAK,SAAS,CAAC,KAAK,IAAI,KAAK,IAAI,CAAC,CAC/E;CAEA,OAAO,aAAa,KAAK,UAAU,KAAK,GAAG,kBAAkB;AAC/D"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { t as textResponse } from "../../chunks/response-CtzaKMWH.js";
|
|
2
|
+
import { t as loadCatalog } from "../../chunks/catalog-DU4BUrpw.js";
|
|
3
|
+
import { n as createLLMsFull } from "../../chunks/files-BxqiQzFj.js";
|
|
4
|
+
import config from "virtual:andesine/config";
|
|
5
|
+
//#region src/astro/routes/llms-full.ts
|
|
6
|
+
const GET = async () => {
|
|
7
|
+
return textResponse(createLLMsFull(config, await loadCatalog(config)), "text/plain");
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { GET };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=llms-full.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms-full.js","names":[],"sources":["../../../src/astro/routes/llms-full.ts"],"sourcesContent":["import type { APIRoute } from \"astro\";\nimport config from \"virtual:andesine/config\";\nimport { loadCatalog } from \"../../output/catalog\";\nimport { createLLMsFull } from \"../../output/files\";\nimport { textResponse } from \"../../output/response\";\n\nexport const GET: APIRoute = async () => {\n return textResponse(createLLMsFull(config, await loadCatalog(config)), \"text/plain\");\n};\n"],"mappings":";;;;;AAMA,MAAa,MAAgB,YAAY;CACvC,OAAO,aAAa,eAAe,QAAQ,MAAM,YAAY,MAAM,CAAC,GAAG,YAAY;AACrF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { t as textResponse } from "../../chunks/response-CtzaKMWH.js";
|
|
2
|
+
import { t as loadCatalog } from "../../chunks/catalog-DU4BUrpw.js";
|
|
3
|
+
import { t as createLLMs } from "../../chunks/files-BxqiQzFj.js";
|
|
4
|
+
import config from "virtual:andesine/config";
|
|
5
|
+
//#region src/astro/routes/llms.ts
|
|
6
|
+
const GET = async () => {
|
|
7
|
+
return textResponse(createLLMs(config, await loadCatalog(config)), "text/plain");
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { GET };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=llms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms.js","names":[],"sources":["../../../src/astro/routes/llms.ts"],"sourcesContent":["import type { APIRoute } from \"astro\";\nimport config from \"virtual:andesine/config\";\nimport { loadCatalog } from \"../../output/catalog\";\nimport { createLLMs } from \"../../output/files\";\nimport { textResponse } from \"../../output/response\";\n\nexport const GET: APIRoute = async () => {\n return textResponse(createLLMs(config, await loadCatalog(config)), \"text/plain\");\n};\n"],"mappings":";;;;;AAMA,MAAa,MAAgB,YAAY;CACvC,OAAO,aAAa,WAAW,QAAQ,MAAM,YAAY,MAAM,CAAC,GAAG,YAAY;AACjF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { t as getMarkdownPaths } from "../../chunks/site-DY-uZ0PP.js";
|
|
2
|
+
import { t as textResponse } from "../../chunks/response-CtzaKMWH.js";
|
|
3
|
+
import config from "virtual:andesine/config";
|
|
4
|
+
//#region src/astro/routes/markdown-alias.ts
|
|
5
|
+
const getStaticPaths = async () => {
|
|
6
|
+
return (await getMarkdownPaths(config)).filter((path) => path.params.path);
|
|
7
|
+
};
|
|
8
|
+
const GET = ({ props }) => {
|
|
9
|
+
return textResponse(props.markdown, "text/markdown");
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { GET, getStaticPaths };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=markdown-alias.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-alias.js","names":[],"sources":["../../../src/astro/routes/markdown-alias.ts"],"sourcesContent":["import type { APIRoute } from \"astro\";\nimport config from \"virtual:andesine/config\";\nimport { textResponse } from \"../../output/response\";\nimport { getMarkdownPaths } from \"../../site\";\n\n// Agents often add `.md` to a page URL: `/guide/` becomes `/guide.md`. The home page has no alias.\nexport const getStaticPaths = async () => {\n return (await getMarkdownPaths(config)).filter((path) => path.params.path);\n};\n\nexport const GET: APIRoute = ({ props }) => {\n return textResponse((props as { markdown: string }).markdown, \"text/markdown\");\n};\n"],"mappings":";;;;AAMA,MAAa,iBAAiB,YAAY;CACxC,QAAQ,MAAM,iBAAiB,MAAM,EAAA,CAAG,QAAQ,SAAS,KAAK,OAAO,IAAI;AAC3E;AAEA,MAAa,OAAiB,EAAE,YAAY;CAC1C,OAAO,aAAc,MAA+B,UAAU,eAAe;AAC/E"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { t as getMarkdownPaths } from "../../chunks/site-DY-uZ0PP.js";
|
|
2
|
+
import { t as textResponse } from "../../chunks/response-CtzaKMWH.js";
|
|
3
|
+
import config from "virtual:andesine/config";
|
|
4
|
+
//#region src/astro/routes/markdown.ts
|
|
5
|
+
const getStaticPaths = () => getMarkdownPaths(config);
|
|
6
|
+
const GET = ({ props }) => {
|
|
7
|
+
return textResponse(props.markdown, "text/markdown");
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { GET, getStaticPaths };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","names":[],"sources":["../../../src/astro/routes/markdown.ts"],"sourcesContent":["import type { APIRoute } from \"astro\";\nimport config from \"virtual:andesine/config\";\nimport { textResponse } from \"../../output/response\";\nimport { getMarkdownPaths } from \"../../site\";\n\nexport const getStaticPaths = () => getMarkdownPaths(config);\n\nexport const GET: APIRoute = ({ props }) => {\n return textResponse((props as { markdown: string }).markdown, \"text/markdown\");\n};\n"],"mappings":";;;;AAKA,MAAa,uBAAuB,iBAAiB,MAAM;AAE3D,MAAa,OAAiB,EAAE,YAAY;CAC1C,OAAO,aAAc,MAA+B,UAAU,eAAe;AAC/E"}
|