blume 2.0.3 → 2.1.1
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/AGENTS.md +1 -0
- package/CHANGELOG.md +142 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
- package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
- package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
- package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
- package/dist/cli/chunk-150jk7km.js +52 -0
- package/dist/cli/chunk-150jk7km.js.map +10 -0
- package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
- package/dist/cli/chunk-313vnn40.js.map +10 -0
- package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
- package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
- package/dist/cli/chunk-3x6x3pph.js.map +10 -0
- package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
- package/dist/cli/chunk-42t4jxdx.js.map +12 -0
- package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
- package/dist/cli/chunk-54ztw1nj.js.map +10 -0
- package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
- package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
- package/dist/cli/chunk-66w9j7p6.js +322 -0
- package/dist/cli/chunk-66w9j7p6.js.map +14 -0
- package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
- package/dist/cli/chunk-7ppf1rva.js.map +10 -0
- package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
- package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
- package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
- package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
- package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
- package/dist/cli/chunk-cfyg47zg.js.map +10 -0
- package/dist/cli/chunk-cm89scdv.js +443 -0
- package/dist/cli/chunk-cm89scdv.js.map +11 -0
- package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
- package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
- package/dist/cli/chunk-e6atmn4h.js.map +36 -0
- package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
- package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
- package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
- package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
- package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
- package/dist/cli/chunk-ehweg4fr.js.map +218 -0
- package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
- package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
- package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
- package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
- package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
- package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
- package/dist/cli/chunk-gb420vvz.js.map +17 -0
- package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
- package/dist/cli/chunk-hebn0tpx.js.map +14 -0
- package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
- package/dist/cli/chunk-htrwgabr.js.map +15 -0
- package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
- package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
- package/dist/cli/chunk-j134vrp2.js +65 -0
- package/dist/cli/chunk-j134vrp2.js.map +10 -0
- package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
- package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
- package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
- package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
- package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
- package/dist/cli/chunk-ncj7w596.js.map +35 -0
- package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
- package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
- package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
- package/dist/cli/chunk-nrns10mj.js.map +10 -0
- package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
- package/dist/cli/chunk-t2m5j4pr.js +104 -0
- package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
- package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
- package/dist/cli/chunk-tphac74f.js +126 -0
- package/dist/cli/chunk-tphac74f.js.map +11 -0
- package/dist/cli/chunk-w1r4vase.js +18 -0
- package/dist/cli/chunk-w1r4vase.js.map +10 -0
- package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
- package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
- package/dist/cli/index.js +31 -20
- package/dist/cli/index.js.map +5 -5
- package/dist/types/ai/agent-surface.d.ts +8 -7
- package/dist/types/ai/ai-catalog.d.ts +7 -0
- package/dist/types/ai/ask-context.d.ts +26 -0
- package/dist/types/ai/ask.d.ts +271 -58
- package/dist/types/ai/component-markdown.d.ts +2 -0
- package/dist/types/ai/index.d.ts +2 -2
- package/dist/types/ai/openapi-components.d.ts +4 -1
- package/dist/types/ai/site-skill.d.ts +26 -0
- package/dist/types/ai/skills.d.ts +16 -0
- package/dist/types/astro/integration.d.ts +9 -0
- package/dist/types/astro/markdown-negotiation.d.ts +3 -0
- package/dist/types/astro/pages.d.ts +10 -0
- package/dist/types/captcha/hcaptcha.d.ts +27 -0
- package/dist/types/captcha/index.d.ts +28 -0
- package/dist/types/captcha/schema.d.ts +29 -0
- package/dist/types/captcha/turnstile.d.ts +28 -0
- package/dist/types/cli/init/scaffold.d.ts +1 -1
- package/dist/types/components/content/api-field.d.ts +62 -0
- package/dist/types/components/content/api-page.d.ts +100 -0
- package/dist/types/components/layout/nav-utils.d.ts +26 -0
- package/dist/types/components/openapi/code-samples.d.ts +29 -0
- package/dist/types/components/openapi/helpers.d.ts +158 -0
- package/dist/types/components/openapi/security.d.ts +95 -0
- package/dist/types/components/openapi/snippets.d.ts +60 -0
- package/dist/types/consent/ethyca.d.ts +48 -0
- package/dist/types/consent/index.d.ts +37 -0
- package/dist/types/consent/native.d.ts +28 -0
- package/dist/types/consent/osano.d.ts +42 -0
- package/dist/types/consent/schema.d.ts +110 -0
- package/dist/types/core/changelog-index.d.ts +8 -0
- package/dist/types/core/config-input.d.ts +217 -23
- package/dist/types/core/data.d.ts +53 -4
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/i18n-ui.d.ts +88 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/links.d.ts +8 -0
- package/dist/types/core/localizable.d.ts +7 -0
- package/dist/types/core/page-modes.d.ts +29 -0
- package/dist/types/core/probe.d.ts +2 -0
- package/dist/types/core/project-graph.d.ts +6 -0
- package/dist/types/core/redirect-patterns.d.ts +152 -0
- package/dist/types/core/related.d.ts +40 -0
- package/dist/types/core/schema.d.ts +670 -56
- package/dist/types/core/sources/cache.d.ts +3 -0
- package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
- package/dist/types/core/sources/contentful.d.ts +17 -1
- package/dist/types/core/sources/lexical.d.ts +5 -0
- package/dist/types/core/sources/normalize.d.ts +20 -1
- package/dist/types/core/sources/read.d.ts +5 -0
- package/dist/types/core/sources/remote.d.ts +37 -10
- package/dist/types/core/sources/sanity.d.ts +1 -1
- package/dist/types/core/types.d.ts +18 -1
- package/dist/types/core/variables.d.ts +42 -0
- package/dist/types/deploy/adapter-output.d.ts +2 -1
- package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
- package/dist/types/deploy/adapters/node.d.ts +58 -23
- package/dist/types/deploy/adapters/registry.d.ts +10 -0
- package/dist/types/deploy/adapters/types.d.ts +3 -2
- package/dist/types/deploy/node-headers.d.ts +13 -2
- package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
- package/dist/types/deploy/platforms/netlify.d.ts +7 -0
- package/dist/types/deploy/platforms/node.d.ts +3 -1
- package/dist/types/deploy/platforms/types.d.ts +18 -7
- package/dist/types/deploy/platforms/vercel.d.ts +26 -1
- package/dist/types/deploy/redirects.d.ts +55 -11
- package/dist/types/deploy/sitemap.d.ts +2 -1
- package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
- package/dist/types/markdown/code-title.d.ts +17 -0
- package/dist/types/markdown/features.d.ts +3 -2
- package/dist/types/narration/build.d.ts +70 -0
- package/dist/types/narration/provider.d.ts +26 -0
- package/dist/types/narration/script.d.ts +154 -0
- package/dist/types/openapi/checks.d.ts +27 -0
- package/dist/types/openapi/graphql.d.ts +6 -0
- package/dist/types/openapi/model.d.ts +27 -10
- package/dist/types/openapi/overlay.d.ts +54 -0
- package/dist/types/openapi/parse.d.ts +16 -0
- package/dist/types/openapi/references.d.ts +30 -4
- package/dist/types/ratelimit/cloudflare.d.ts +39 -0
- package/dist/types/ratelimit/index.d.ts +27 -0
- package/dist/types/ratelimit/memory.d.ts +36 -0
- package/dist/types/ratelimit/schema.d.ts +119 -0
- package/dist/types/ratelimit/upstash.d.ts +46 -0
- package/dist/types/ratelimit/wrangler.d.ts +23 -0
- package/dist/types/reference/asyncapi.d.ts +10 -10
- package/dist/types/reference/graphql.d.ts +89 -11
- package/dist/types/reference/index.d.ts +1 -1
- package/dist/types/reference/openapi.d.ts +33 -12
- package/dist/types/reference/options.d.ts +82 -6
- package/dist/types/reference/scalar.d.ts +13 -0
- package/dist/types/reference/schema.d.ts +144 -36
- package/dist/types/search/adapters/registry.d.ts +3 -0
- package/dist/types/search/adapters/typesense.d.ts +14 -4
- package/dist/types/search/build.d.ts +3 -1
- package/dist/types/search/documents.d.ts +12 -1
- package/dist/types/search/orama-index.d.ts +13 -9
- package/dist/types/search/sync/algolia.d.ts +5 -2
- package/dist/types/search/sync/index.d.ts +3 -1
- package/dist/types/search/sync/typesense.d.ts +11 -7
- package/dist/types/seo/metatags.d.ts +20 -0
- package/dist/types/sources/contentful.d.ts +16 -0
- package/dist/types/sources/github-releases.d.ts +8 -1
- package/dist/types/sources/payload.d.ts +2 -0
- package/dist/types/sources/registry.d.ts +54 -0
- package/dist/types/sources/shared.d.ts +3 -0
- package/dist/types/sources/strapi.d.ts +2 -0
- package/docs/01-quickstart.mdx +4 -2
- package/docs/02-deployment.mdx +71 -17
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/advanced/changelog.mdx +13 -2
- package/docs/advanced/skills.mdx +15 -1
- package/docs/cli/audit.mdx +51 -8
- package/docs/cli/doctor.mdx +5 -1
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/index.mdx +1 -0
- package/docs/cli/translate.mdx +1 -0
- package/docs/cli/validate.mdx +4 -3
- package/docs/configuration/analytics.mdx +3 -1
- package/docs/configuration/assistant.mdx +194 -38
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +8 -6
- package/docs/configuration/export.mdx +5 -3
- package/docs/configuration/index.mdx +64 -5
- package/docs/configuration/meta.ts +3 -0
- package/docs/configuration/narration.mdx +119 -0
- package/docs/configuration/rate-limiting.mdx +90 -0
- package/docs/configuration/search.mdx +63 -12
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +108 -2
- package/docs/content/frontmatter.mdx +80 -2
- package/docs/content/i18n.mdx +39 -4
- package/docs/content/includes.mdx +16 -0
- package/docs/content/meta.mdx +1 -0
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +32 -10
- package/docs/content/sources/contentful.mdx +35 -0
- package/docs/content/sources/custom.mdx +43 -0
- package/docs/content/sources/github-releases.mdx +39 -0
- package/docs/content/sources/index.mdx +91 -0
- package/docs/content/sources/meta.ts +16 -0
- package/docs/content/sources/notion.mdx +32 -0
- package/docs/content/sources/obsidian.mdx +37 -0
- package/docs/content/sources/payload.mdx +31 -0
- package/docs/content/sources/remote-mdx.mdx +44 -0
- package/docs/content/sources/sanity.mdx +29 -0
- package/docs/content/sources/strapi.mdx +32 -0
- package/docs/content/syntax.mdx +69 -4
- package/docs/content/variables.mdx +41 -0
- package/docs/discoverability/agent-discovery.mdx +29 -2
- package/docs/discoverability/index.mdx +2 -2
- package/docs/discoverability/json-api.mdx +2 -0
- package/docs/discoverability/llms-txt.mdx +2 -2
- package/docs/discoverability/markdown.mdx +3 -1
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +4 -2
- package/docs/references/graphql.mdx +14 -2
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +148 -7
- package/docs/references/scalar.mdx +3 -1
- package/package.json +43 -8
- package/skills/blume/SKILL.md +3 -0
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +7 -7
- package/skills/blume-migrate/references/fumadocs.md +17 -13
- package/skills/blume-migrate/references/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +10 -7
- package/skills/blume-migrate/references/starlight.md +11 -8
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
- package/skills/blume-write-skill/SKILL.md +82 -0
- package/src/ai/agent-readability.ts +2 -1
- package/src/ai/agent-surface.ts +9 -7
- package/src/ai/ai-catalog.ts +6 -3
- package/src/ai/ask-context.ts +103 -20
- package/src/ai/ask-data.ts +6 -0
- package/src/ai/ask-limits.ts +13 -0
- package/src/ai/ask-tools.ts +142 -0
- package/src/ai/ask.ts +354 -76
- package/src/ai/changelog-markdown.ts +11 -6
- package/src/ai/component-markdown.ts +76 -1
- package/src/ai/cors.ts +14 -3
- package/src/ai/index.ts +12 -0
- package/src/ai/llms.ts +6 -2
- package/src/ai/markdown.ts +2 -1
- package/src/ai/mcp/data.ts +6 -0
- package/src/ai/mcp/query.ts +14 -6
- package/src/ai/openapi-components.ts +36 -6
- package/src/ai/relative-links.ts +4 -38
- package/src/ai/site-skill.ts +274 -0
- package/src/ai/skills.ts +35 -2
- package/src/ai/write-skill.ts +97 -0
- package/src/astro/generate.ts +210 -66
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +42 -4
- package/src/astro/markdown-negotiation.ts +3 -0
- package/src/astro/module-types.ts +9 -1
- package/src/astro/pages.ts +23 -0
- package/src/astro/templates.ts +406 -108
- package/src/astro/variables.ts +31 -0
- package/src/audit/catalog.ts +38 -3
- package/src/audit/checks/contrast.ts +183 -0
- package/src/audit/checks/i18n.ts +2 -3
- package/src/audit/checks/indexability.ts +2 -3
- package/src/audit/checks/links.ts +19 -13
- package/src/audit/checks/network.ts +110 -6
- package/src/audit/checks/redirects.ts +6 -4
- package/src/audit/checks/robots.ts +58 -2
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +5 -0
- package/src/audit/types.ts +4 -1
- package/src/audit/url.ts +14 -5
- package/src/blume-modules.d.ts +12 -1
- package/src/captcha/client.ts +152 -0
- package/src/captcha/hcaptcha.ts +36 -0
- package/src/captcha/index.ts +28 -0
- package/src/captcha/schema.ts +32 -0
- package/src/captcha/turnstile.ts +38 -0
- package/src/captcha/verify.ts +71 -0
- package/src/cli/args.ts +51 -0
- package/src/cli/build-failure.ts +23 -0
- package/src/cli/command-meta.ts +5 -0
- package/src/cli/commands/audit.ts +10 -1
- package/src/cli/commands/dev.ts +18 -2
- package/src/cli/commands/doctor.ts +67 -28
- package/src/cli/commands/eval.ts +36 -4
- package/src/cli/commands/init.ts +6 -4
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/commands/validate.ts +10 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/init/scaffold.ts +3 -3
- package/src/cli/prepare.ts +6 -3
- package/src/cli/required-secrets.ts +72 -30
- package/src/components/BlumePage.astro +2 -0
- package/src/components/content/ApiEndpoint.astro +18 -0
- package/src/components/content/ApiField.astro +79 -0
- package/src/components/content/ApiPlayground.astro +66 -0
- package/src/components/content/ApiRail.astro +19 -0
- package/src/components/content/Color.astro +1 -0
- package/src/components/content/FileTree.astro +1 -0
- package/src/components/content/GithubInfo.astro +1 -0
- package/src/components/content/Panel.astro +1 -0
- package/src/components/content/ParamField.astro +25 -0
- package/src/components/content/RequestExample.astro +17 -0
- package/src/components/content/ResponseExample.astro +17 -0
- package/src/components/content/ResponseField.astro +32 -0
- package/src/components/content/Step.astro +1 -0
- package/src/components/content/Tile.astro +1 -0
- package/src/components/content/Tooltip.astro +1 -0
- package/src/components/content/Tree.astro +1 -0
- package/src/components/content/TypeTable.astro +4 -1
- package/src/components/content/View.astro +21 -0
- package/src/components/content/ViewSwitcher.astro +168 -0
- package/src/components/content/api-field.ts +103 -0
- package/src/components/content/api-page.ts +248 -0
- package/src/components/content/mermaid-element.ts +15 -1
- package/src/components/content/view-switcher.ts +71 -0
- package/src/components/index.ts +2 -0
- package/src/components/islands/Assistant.astro +10 -1
- package/src/components/islands/assistant.tsx +104 -14
- package/src/components/islands/code-question.ts +62 -0
- package/src/components/islands/hooks.ts +136 -21
- package/src/components/islands/support-link.ts +68 -0
- package/src/components/layout/Analytics.astro +11 -2
- package/src/components/layout/Banner.astro +14 -10
- package/src/components/layout/Breadcrumbs.astro +6 -2
- package/src/components/layout/ConsentBanner.astro +62 -0
- package/src/components/layout/ConsentHead.astro +38 -0
- package/src/components/layout/Directory.astro +94 -0
- package/src/components/layout/DrawerSwitchers.astro +51 -0
- package/src/components/layout/Empty.astro +2 -2
- package/src/components/layout/Header.astro +122 -80
- package/src/components/layout/LanguageSwitcher.astro +48 -15
- package/src/components/layout/NarrationPlayer.astro +1085 -0
- package/src/components/layout/NavSelector.astro +33 -7
- package/src/components/layout/PageActions.astro +139 -24
- package/src/components/layout/PageFeedback.astro +54 -49
- package/src/components/layout/PageLayout.astro +71 -3
- package/src/components/layout/ReferenceLayout.astro +17 -6
- package/src/components/layout/RelatedPages.astro +42 -0
- package/src/components/layout/RootLayout.astro +318 -47
- package/src/components/layout/Search.astro +47 -19
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -0
- package/src/components/layout/TableOfContents.astro +24 -17
- package/src/components/layout/consent/native.ts +79 -0
- package/src/components/layout/consent/types.ts +37 -0
- package/src/components/layout/footer-links.ts +53 -0
- package/src/components/layout/footer-socials.ts +87 -0
- package/src/components/layout/head-scripts.ts +6 -1
- package/src/components/layout/locale-redirect.ts +107 -0
- package/src/components/layout/nav-utils.ts +83 -0
- package/src/components/layout/page-feedback.ts +96 -0
- package/src/components/layout/search/algolia.ts +10 -1
- package/src/components/layout/search/analytics.ts +126 -0
- package/src/components/layout/search/endpoint.ts +48 -5
- package/src/components/layout/search/flexsearch.ts +17 -5
- package/src/components/layout/search/orama-cloud.ts +26 -14
- package/src/components/layout/search/pagefind.ts +114 -5
- package/src/components/layout/search/types.ts +32 -0
- package/src/components/layout/search/typesense.ts +19 -3
- package/src/components/openapi/AsyncApiOperation.astro +14 -7
- package/src/components/openapi/Authorization.astro +10 -0
- package/src/components/openapi/GraphqlOperation.astro +5 -0
- package/src/components/openapi/Operation.astro +184 -13
- package/src/components/openapi/ParametersTable.astro +8 -4
- package/src/components/openapi/Playground.astro +7 -1
- package/src/components/openapi/RequestBody.astro +24 -5
- package/src/components/openapi/RequestPanel.astro +59 -7
- package/src/components/openapi/Responses.astro +7 -4
- package/src/components/openapi/SchemaTable.astro +15 -3
- package/src/components/openapi/async-snippets.ts +65 -9
- package/src/components/openapi/async.ts +123 -29
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/graphql-helpers.ts +35 -12
- package/src/components/openapi/helpers.ts +36 -2
- package/src/components/openapi/message-model.ts +54 -11
- package/src/components/openapi/message.ts +14 -4
- package/src/components/openapi/operation-model.ts +16 -7
- package/src/components/openapi/playground-client.ts +24 -5
- package/src/components/openapi/playground-schema.ts +1 -1
- package/src/components/openapi/schema-tree.ts +44 -14
- package/src/components/openapi/security.ts +16 -3
- package/src/components/openapi/snippets.ts +873 -10
- package/src/components/openapi/validate-json.ts +4 -3
- package/src/components/props.ts +6 -0
- package/src/consent/client.ts +95 -0
- package/src/consent/clients.ts +19 -0
- package/src/consent/ethyca.ts +71 -0
- package/src/consent/head.ts +24 -0
- package/src/consent/index.ts +37 -0
- package/src/consent/init.ts +29 -0
- package/src/consent/native.ts +36 -0
- package/src/consent/osano.ts +59 -0
- package/src/consent/schema.ts +30 -0
- package/src/core/builtin-tags.ts +5 -0
- package/src/core/changelog-index.ts +31 -1
- package/src/core/client-address.ts +19 -0
- package/src/core/component-diagnostics.ts +53 -0
- package/src/core/component-overrides.ts +32 -0
- package/src/core/config-input.ts +225 -23
- package/src/core/data.ts +46 -4
- package/src/core/diagnostics.ts +8 -2
- package/src/core/footer.ts +26 -0
- package/src/core/graph.ts +39 -36
- package/src/core/i18n-ui.ts +84 -2
- package/src/core/includes.ts +21 -4
- package/src/core/links.ts +63 -19
- package/src/core/localizable.ts +26 -0
- package/src/core/navigation.ts +43 -12
- package/src/core/page-modes.ts +56 -0
- package/src/core/probe.ts +26 -6
- package/src/core/project-graph.ts +163 -16
- package/src/core/redirect-patterns.ts +520 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +339 -66
- package/src/core/sources/cache.ts +6 -0
- package/src/core/sources/contentful-rich-text.ts +29 -6
- package/src/core/sources/contentful.ts +135 -25
- package/src/core/sources/filesystem.ts +26 -4
- package/src/core/sources/github-releases.ts +12 -1
- package/src/core/sources/lexical.ts +15 -10
- package/src/core/sources/mdx-remote.ts +62 -10
- package/src/core/sources/normalize.ts +228 -41
- package/src/core/sources/notion.ts +7 -1
- package/src/core/sources/obsidian.ts +16 -3
- package/src/core/sources/payload.ts +38 -15
- package/src/core/sources/read.ts +8 -3
- package/src/core/sources/remote.ts +72 -15
- package/src/core/sources/resolve.ts +11 -1
- package/src/core/sources/sanity.ts +43 -25
- package/src/core/sources/strapi.ts +2 -2
- package/src/core/types.ts +19 -1
- package/src/core/ui-packs/ar.ts +44 -0
- package/src/core/ui-packs/bg.ts +46 -0
- package/src/core/ui-packs/bn.ts +44 -0
- package/src/core/ui-packs/ca.ts +47 -0
- package/src/core/ui-packs/cs.ts +45 -0
- package/src/core/ui-packs/da.ts +46 -0
- package/src/core/ui-packs/de.ts +47 -0
- package/src/core/ui-packs/el.ts +46 -0
- package/src/core/ui-packs/es.ts +47 -0
- package/src/core/ui-packs/fa.ts +46 -0
- package/src/core/ui-packs/fi.ts +46 -0
- package/src/core/ui-packs/fr.ts +47 -0
- package/src/core/ui-packs/he.ts +44 -0
- package/src/core/ui-packs/hi.ts +45 -0
- package/src/core/ui-packs/hr.ts +46 -0
- package/src/core/ui-packs/hu.ts +45 -0
- package/src/core/ui-packs/id.ts +46 -0
- package/src/core/ui-packs/it.ts +46 -0
- package/src/core/ui-packs/ja.ts +46 -0
- package/src/core/ui-packs/ko.ts +45 -0
- package/src/core/ui-packs/nl.ts +47 -0
- package/src/core/ui-packs/no.ts +45 -0
- package/src/core/ui-packs/pl.ts +46 -0
- package/src/core/ui-packs/pt-br.ts +47 -0
- package/src/core/ui-packs/pt.ts +46 -0
- package/src/core/ui-packs/ro.ts +46 -0
- package/src/core/ui-packs/ru.ts +46 -0
- package/src/core/ui-packs/sk.ts +45 -0
- package/src/core/ui-packs/sr.ts +46 -0
- package/src/core/ui-packs/sv.ts +45 -0
- package/src/core/ui-packs/th.ts +44 -0
- package/src/core/ui-packs/tr.ts +46 -0
- package/src/core/ui-packs/uk.ts +46 -0
- package/src/core/ui-packs/vi.ts +46 -0
- package/src/core/ui-packs/zh-tw.ts +44 -0
- package/src/core/ui-packs/zh.ts +44 -0
- package/src/core/variables.ts +103 -0
- package/src/deploy/adapter-output.ts +7 -2
- package/src/deploy/adapters/cloudflare.ts +7 -5
- package/src/deploy/adapters/node.ts +47 -9
- package/src/deploy/adapters/types.ts +7 -3
- package/src/deploy/artifacts.ts +79 -9
- package/src/deploy/cloudflare-negotiation.ts +35 -5
- package/src/deploy/headers.ts +2 -1
- package/src/deploy/node-headers.ts +63 -9
- package/src/deploy/platforms/cloudflare.ts +45 -9
- package/src/deploy/platforms/netlify.ts +35 -2
- package/src/deploy/platforms/node.ts +12 -2
- package/src/deploy/platforms/types.ts +21 -7
- package/src/deploy/platforms/vercel.ts +149 -17
- package/src/deploy/redirects.ts +151 -24
- package/src/deploy/rss.ts +3 -1
- package/src/deploy/sitemap.ts +39 -5
- package/src/deploy/vercel-negotiation.ts +326 -102
- package/src/eval/agents.ts +18 -1
- package/src/eval/prompts.ts +6 -3
- package/src/eval/report.ts +34 -13
- package/src/eval/run.ts +5 -0
- package/src/markdown/api-rail.ts +259 -0
- package/src/markdown/code-title.ts +35 -9
- package/src/markdown/features.ts +3 -2
- package/src/markdown/fence-meta.ts +2 -0
- package/src/markdown/include.ts +11 -1
- package/src/markdown/index.ts +28 -5
- package/src/markdown/math.ts +4 -4
- package/src/markdown/relative-links.ts +3 -2
- package/src/markdown/variables.ts +56 -0
- package/src/markdown/views.ts +82 -0
- package/src/narration/build.ts +412 -0
- package/src/narration/player.ts +249 -0
- package/src/narration/provider.ts +29 -0
- package/src/narration/script.ts +592 -0
- package/src/og/card.ts +7 -6
- package/src/openapi/checks.ts +273 -0
- package/src/openapi/graphql-build.ts +21 -0
- package/src/openapi/graphql.ts +11 -0
- package/src/openapi/model.ts +72 -27
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +182 -4
- package/src/openapi/references.ts +64 -8
- package/src/openapi/render-mdx.ts +5 -1
- package/src/openapi/scalar.ts +40 -6
- package/src/openapi/source.ts +40 -15
- package/src/ratelimit/cloudflare.ts +54 -0
- package/src/ratelimit/index.ts +30 -0
- package/src/ratelimit/memory.ts +45 -0
- package/src/ratelimit/runtime.ts +247 -0
- package/src/ratelimit/schema.ts +28 -0
- package/src/ratelimit/upstash.ts +60 -0
- package/src/ratelimit/wrangler.ts +70 -0
- package/src/reference/asyncapi.ts +2 -2
- package/src/reference/graphql.ts +20 -3
- package/src/reference/index.ts +1 -0
- package/src/reference/openapi.ts +21 -7
- package/src/reference/options.ts +80 -8
- package/src/reference/scalar.ts +33 -8
- package/src/registry/eject.ts +23 -5
- package/src/registry/registry.ts +7 -0
- package/src/search/adapters/locale-scope.ts +30 -0
- package/src/search/adapters/typesense.ts +14 -4
- package/src/search/build.ts +21 -3
- package/src/search/documents.ts +26 -2
- package/src/search/orama-index.ts +191 -34
- package/src/search/pagefind-ranking.ts +39 -0
- package/src/search/source-pages.ts +29 -0
- package/src/search/sync/algolia.ts +111 -10
- package/src/search/sync/index.ts +52 -13
- package/src/search/sync/orama-cloud.ts +3 -0
- package/src/search/sync/typesense.ts +76 -26
- package/src/seo/metatags.ts +67 -0
- package/src/sources/contentful.ts +12 -0
- package/src/sources/github-releases.ts +7 -1
- package/src/sources/shared.ts +3 -0
- package/src/theme/chrome-icons.ts +1 -0
- package/src/theme/color.ts +500 -0
- package/src/theme/entry.ts +85 -6
- package/src/theme/palette.ts +121 -45
- package/src/translate/prompts.ts +1 -0
- package/src/translate/validate.ts +31 -3
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
- package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
- package/dist/cli/chunk-35d4wj9f.js.map +0 -15
- package/dist/cli/chunk-364znk6q.js +0 -75
- package/dist/cli/chunk-364znk6q.js.map +0 -11
- package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
- package/dist/cli/chunk-8ktnccpt.js.map +0 -10
- package/dist/cli/chunk-9t7a85s3.js.map +0 -10
- package/dist/cli/chunk-b07cmahc.js +0 -145
- package/dist/cli/chunk-b07cmahc.js.map +0 -11
- package/dist/cli/chunk-c8chx29p.js.map +0 -36
- package/dist/cli/chunk-e04dxsz1.js +0 -39
- package/dist/cli/chunk-e04dxsz1.js.map +0 -10
- package/dist/cli/chunk-j85vccga.js.map +0 -36
- package/dist/cli/chunk-p3v96n38.js.map +0 -10
- package/dist/cli/chunk-pehfxfta.js.map +0 -10
- package/dist/cli/chunk-pv29h0wf.js.map +0 -190
- package/dist/cli/chunk-q58y5e6a.js.map +0 -10
- package/dist/cli/chunk-vg9r4eb9.js.map +0 -14
- package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
- package/dist/cli/chunk-yfyb25rh.js.map +0 -14
- package/docs/content/sources.mdx +0 -334
- /package/dist/cli/{chunk-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
- /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
- /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
- /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
- /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
- /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
- /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
- /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Assistant
|
|
3
|
-
description: An in-page assistant grounded in your docs — suggested questions, custom instructions, retrieval sizing, provider adapters
|
|
3
|
+
description: An in-page assistant grounded in your docs — suggested questions, custom instructions, retrieval sizing, provider adapters for OpenAI, Anthropic, Gemini, Grok, the Vercel AI Gateway, and any OpenAI-compatible endpoint, and the server output it needs.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
Add an assistant that answers reader questions in an in-page chat panel, backed by a streaming server endpoint and the [AI SDK](https://ai-sdk.dev). It's opt-in, and static docs stay fully static until you turn it on:
|
|
@@ -34,6 +34,27 @@ ai: {
|
|
|
34
34
|
|
|
35
35
|
`label` is the question that gets asked; `icon` is optional. Leave `suggestions` unset (or empty) and the panel opens to a plain input.
|
|
36
36
|
|
|
37
|
+
## Asking about code
|
|
38
|
+
|
|
39
|
+
With the assistant on, every code block gets an **Ask** button beside its copy button. It opens the assistant with the block attached, shown as a chip above the input, so the reader can ask what it does, why it fails, or how to adapt it. Sent without a question, it asks the assistant to explain the code. The code goes to the model as a fenced block after the question, up to 6,000 characters.
|
|
40
|
+
|
|
41
|
+
A custom chat UI built on `useAssistant` can do the same: listen for `blume:open-assistant` on `window`, whose `detail.code` holds the block's `language`, `source`, and `title`.
|
|
42
|
+
|
|
43
|
+
## Contact support
|
|
44
|
+
|
|
45
|
+
When the docs can't answer, hand the reader to a person. Set `support`, and once there's a conversation the panel shows a **Contact support** link under it:
|
|
46
|
+
|
|
47
|
+
```ts blume.config.ts lineNumbers
|
|
48
|
+
ai: {
|
|
49
|
+
assistant: {
|
|
50
|
+
enabled: true,
|
|
51
|
+
support: "mailto:help@example.com", // or "https://example.com/support", or "/support"
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
A `mailto:` address starts an email with the conversation as its body, so the reader doesn't have to explain again (a long conversation keeps its latest turns). A URL or a page on your site gets the conversation's id as a `thread` query parameter. The assistant's `ask`, `ask_answer`, and `ask_error` [analytics events](#analytics) carry the same `thread`, so your support team can find the conversation in your analytics.
|
|
57
|
+
|
|
37
58
|
## Custom instructions
|
|
38
59
|
|
|
39
60
|
Add your own system-prompt text with `instructions` — identity, language, tone, or anything else the assistant should keep in mind:
|
|
@@ -54,10 +75,28 @@ Your text is **appended to** the built-in instructions rather than replacing the
|
|
|
54
75
|
|
|
55
76
|
The assistant is **grounded in your docs**. For each question it retrieves the most relevant pages — using the same lexical [Orama](/docs/configuration/search) index that powers on-page search — and injects them into the model's system prompt, so answers come from your content instead of the model's own knowledge. The assistant is told to answer only from the retrieved pages, to say when something isn't covered, and to cite the pages it drew from.
|
|
56
77
|
|
|
57
|
-
The page the reader is currently on is added to the context first and used to scope retrieval to that page's language — and, on a [versioned](/docs/content/versioning) site, to its docs version — so answers stay relevant to where they are in the docs. Retrieval runs at request time from a snapshot baked into the build, so it works regardless of your [search](/docs/configuration/search) provider — even with `search: false` — and needs no configuration.
|
|
78
|
+
The page the reader is currently on is added to the context first and used to scope retrieval to that page's language — and, on a [versioned](/docs/content/versioning) site, to its docs version — so answers stay relevant to where they are in the docs. A question from outside the docs, such as a landing page, keeps to the language the URL's locale prefix names (`/ja`), or to the default language. Retrieval runs at request time from a snapshot baked into the build, so it works regardless of your [search](/docs/configuration/search) provider — even with `search: false` — and needs no configuration.
|
|
58
79
|
|
|
59
80
|
Grounding is on for every adapter except **[Inkeep](#inkeep)**, which runs its own retrieval over the content you've indexed in its dashboard.
|
|
60
81
|
|
|
82
|
+
## Searching and reading pages
|
|
83
|
+
|
|
84
|
+
The retrieved pages are a head start. The assistant can also look things up itself: it has a `search_docs` tool that searches your docs, and a `read_page` tool that reads a whole page, for when an excerpt stops short of the answer. It can search, read a page or two, search again, and then answer, all within one question. Tool calls run on your server against the same snapshot as the grounding, keep to the reader's language and docs version, and never reach the reader; they only see the answer.
|
|
85
|
+
|
|
86
|
+
The tools are on by default for every adapter whose models support tool calling. They're off for [OpenAI-compatible endpoints](#openai-compatible-endpoints), since a self-hosted model may not. Set `tools` to change either default:
|
|
87
|
+
|
|
88
|
+
```ts blume.config.ts lineNumbers
|
|
89
|
+
ai: {
|
|
90
|
+
assistant: {
|
|
91
|
+
enabled: true,
|
|
92
|
+
provider: openai({ baseUrl, model, apiKeyEnv }),
|
|
93
|
+
tools: true, // this model supports tool calling
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Each question takes at most five model steps: up to four rounds of tool calls, then the answer. With `tools: false`, the assistant answers from the retrieved pages alone, as it did before tools existed.
|
|
99
|
+
|
|
61
100
|
## Retrieval size
|
|
62
101
|
|
|
63
102
|
How much documentation a question carries is the biggest lever on how long the reader waits for the first word: the model reads every injected character before it emits a token. On a hosted frontier model that's invisible, but on a self-hosted backend it dominates. `retrieval` sizes it:
|
|
@@ -85,6 +124,8 @@ The three aren't interchangeable. `contextBudget` caps the whole injection, `exc
|
|
|
85
124
|
|
|
86
125
|
The defaults suit a hosted model. Lower them when you're serving from your own hardware and time-to-first-token matters more than recall; answers stay grounded either way, and the assistant is told to say when something isn't covered rather than fill the gap.
|
|
87
126
|
|
|
127
|
+
On your own hardware, also size the model's context window for the whole prompt, not only the excerpts: the conversation so far adds as many as 24,000 characters, and with `tools` on, one `read_page` result as many as 20,000, about 5,000 tokens on its own. Ollama, for one, defaults to a 4k-token window on smaller GPUs, and a prompt that outgrows it doesn't fail: Ollama drops the start of the prompt, where the instructions and excerpts are, and the model answers without your docs.
|
|
128
|
+
|
|
88
129
|
## External endpoint
|
|
89
130
|
|
|
90
131
|
Already have an API backend for AI? Point the panel at it and keep the docs build static:
|
|
@@ -107,6 +148,8 @@ Blume sends the same `POST` body as its built-in route:
|
|
|
107
148
|
}
|
|
108
149
|
```
|
|
109
150
|
|
|
151
|
+
`messages` is the conversation so far, ending with the new question. The built-in route takes at most 40 messages and 24,000 characters of them as JSON, so once a conversation outgrows either, the panel sends its latest turns that fit and drops the oldest.
|
|
152
|
+
|
|
110
153
|
Return a successful response whose body is a plain UTF-8 text stream. If the endpoint is on another origin, allow the docs origin with CORS: accept `OPTIONS` and `POST`, permit the `content-type` request header, and return the CORS headers on both the preflight and streamed response. With `endpoint` set, Blume generates the chat UI but no server route, grounding snapshot, provider dependency, or provider-secret warning; your backend owns retrieval, authentication, rate limiting, model access, and citations. An adapter set alongside it is ignored.
|
|
111
154
|
|
|
112
155
|
## Cross-origin callers
|
|
@@ -122,7 +165,7 @@ ai: {
|
|
|
122
165
|
}
|
|
123
166
|
```
|
|
124
167
|
|
|
125
|
-
The route then answers the browser's `OPTIONS` preflight and names a listed origin on every response — the streamed answer and the error statuses alike, so the caller can tell a rejected body from a provider failure. Origins that aren't listed get no header and stay subject to the browser's same-origin rule. Each entry is reduced to its origin, so `https://www.example.com/docs/` and `https://www.example.com` mean the same thing. To let any page call the route, list `"*"` instead of origins.
|
|
168
|
+
The route then answers the browser's `OPTIONS` preflight and names a listed origin on every response — the streamed answer and the error statuses alike, so the caller can tell a rejected body from a provider failure. Those responses expose `Retry-After` too, so after a [rate limit's](/docs/configuration/rate-limiting) `429` the caller can read how many seconds to wait. Origins that aren't listed get no header and stay subject to the browser's same-origin rule. Each entry is reduced to its origin, so `https://www.example.com/docs/` and `https://www.example.com` mean the same thing. To let any page call the route, list `"*"` instead of origins.
|
|
126
169
|
|
|
127
170
|
The caller sends the same `POST` body the [external endpoint](#external-endpoint) contract describes and reads back the same text stream. Send it as JSON with a `content-type: application/json` header:
|
|
128
171
|
|
|
@@ -160,13 +203,13 @@ A static build with the assistant enabled and no external `endpoint` fails fast
|
|
|
160
203
|
|
|
161
204
|
```ts blume.config.ts lineNumbers
|
|
162
205
|
import { defineConfig } from "blume";
|
|
163
|
-
import {
|
|
206
|
+
import { anthropic } from "blume/ai";
|
|
164
207
|
|
|
165
208
|
export default defineConfig({
|
|
166
209
|
ai: {
|
|
167
210
|
assistant: {
|
|
168
211
|
enabled: true,
|
|
169
|
-
provider:
|
|
212
|
+
provider: anthropic({ model: "claude-sonnet-5" }),
|
|
170
213
|
},
|
|
171
214
|
},
|
|
172
215
|
});
|
|
@@ -174,16 +217,120 @@ export default defineConfig({
|
|
|
174
217
|
|
|
175
218
|
| Adapter | Answers with | API key env var | SDK to install |
|
|
176
219
|
| --- | --- | --- | --- |
|
|
220
|
+
| [`openai()`](#openai) | an [OpenAI](https://platform.openai.com) model, or any [OpenAI-compatible endpoint](#openai-compatible-endpoints) | `OPENAI_API_KEY` | `@ai-sdk/openai` (`@ai-sdk/openai-compatible` with a `baseUrl`) |
|
|
221
|
+
| [`anthropic()`](#anthropic) | a [Claude](https://platform.claude.com) model | `ANTHROPIC_API_KEY` | `@ai-sdk/anthropic` |
|
|
222
|
+
| [`gemini()`](#gemini) | a [Gemini](https://ai.google.dev) model | `GEMINI_API_KEY` | `@ai-sdk/google` |
|
|
223
|
+
| [`grok()`](#grok) | an [xAI](https://x.ai/api) Grok model | `XAI_API_KEY` | `@ai-sdk/xai` |
|
|
177
224
|
| [`gateway()`](#vercel-ai-gateway) (default) | a `provider/model` string via the Vercel AI Gateway | `AI_GATEWAY_API_KEY` | none — ships with Blume |
|
|
178
225
|
| [`openrouter()`](#openrouter) | any [OpenRouter](https://openrouter.ai) model | `OPENROUTER_API_KEY` | `@openrouter/ai-sdk-provider` |
|
|
179
226
|
| [`llmgateway()`](#llmgateway) | any [LLMGateway](https://llmgateway.io) model | `LLMGATEWAY_API_KEY` | `@ai-sdk/openai-compatible` |
|
|
180
227
|
| [`inkeep()`](#inkeep) | an [Inkeep](https://inkeep.com) QA model | `INKEEP_API_KEY` | `@ai-sdk/openai-compatible` |
|
|
181
|
-
| [`openaiCompatible()`](#openai-compatible-endpoints) | whatever your endpoint serves | the `apiKeyEnv` you name | `@ai-sdk/openai-compatible` |
|
|
182
228
|
|
|
183
|
-
The SDKs are optional peer dependencies, so add the one your adapter needs to your project (`npm install @
|
|
229
|
+
`openai()`, `anthropic()`, `gemini()`, and `grok()` call the provider's own API with your key — nothing sits in between. The SDKs are optional peer dependencies, so add the one your adapter needs to your project (`npm install @ai-sdk/anthropic`, say). If it's missing, `blume build` stops before Vite runs, naming the package and the command that installs it, and [`blume doctor`](/docs/cli/doctor) reports it too.
|
|
184
230
|
|
|
185
231
|
The descriptor an adapter returns is plain data — its kind, its options, the env vars it reads, and the SDK it needs — so the generated route (and the [ejected](/docs/configuration/customization#eject) one) inlines it as literals and imports the provider SDK by name. Nothing reads `blume.config.ts` at request time, and no secret is ever written into a route: adapters take the **name** of the env var holding the key, and the route reads the value through Astro's [`getSecret()`](https://docs.astro.build/en/guides/environment-variables/#retrieving-secrets-programmatically), so each deployment adapter supplies it its own way — environment variables on Node, Vercel, and Netlify, and the Worker's [bindings](https://docs.astro.build/en/guides/integrations-guide/cloudflare/#environment-variables-and-secrets) on Cloudflare.
|
|
186
232
|
|
|
233
|
+
### OpenAI
|
|
234
|
+
|
|
235
|
+
Any [OpenAI](https://platform.openai.com) model, called directly through OpenAI's own AI SDK provider:
|
|
236
|
+
|
|
237
|
+
```ts blume.config.ts lineNumbers
|
|
238
|
+
import { defineConfig } from "blume";
|
|
239
|
+
import { openai } from "blume/ai";
|
|
240
|
+
|
|
241
|
+
export default defineConfig({
|
|
242
|
+
ai: {
|
|
243
|
+
assistant: {
|
|
244
|
+
enabled: true,
|
|
245
|
+
provider: openai({ model: "gpt-5.5" }),
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### OpenAI-compatible endpoints
|
|
252
|
+
|
|
253
|
+
Any endpoint that speaks the OpenAI API — a self-hosted model, an internal gateway — works through `openai()` too: add its `baseUrl`, and usually `apiKeyEnv` for the env var holding its key (it defaults to `OPENAI_API_KEY`). `name` is the provider name the AI SDK reports, and the key the endpoint reads [`providerOptions`](#options-every-adapter-takes) under; it defaults to `openai-compatible`:
|
|
254
|
+
|
|
255
|
+
```ts blume.config.ts lineNumbers
|
|
256
|
+
import { defineConfig } from "blume";
|
|
257
|
+
import { openai } from "blume/ai";
|
|
258
|
+
|
|
259
|
+
export default defineConfig({
|
|
260
|
+
ai: {
|
|
261
|
+
assistant: {
|
|
262
|
+
enabled: true,
|
|
263
|
+
provider: openai({
|
|
264
|
+
baseUrl: "https://my-gateway.example.com/v1",
|
|
265
|
+
apiKeyEnv: "MY_GATEWAY_API_KEY",
|
|
266
|
+
model: "gpt-4o",
|
|
267
|
+
name: "my-gateway",
|
|
268
|
+
}),
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
With a `baseUrl`, the route calls the endpoint's Chat Completions API through `@ai-sdk/openai-compatible`, so install that package instead of `@ai-sdk/openai`: most compatible endpoints don't serve OpenAI's Responses API. The [docs tools](#searching-and-reading-pages) are off by default, since a self-hosted model may not support tool calling.
|
|
275
|
+
|
|
276
|
+
:::note
|
|
277
|
+
`openaiCompatible()` from earlier Blume versions still works: it's the same adapter as `openai()` with a `baseUrl`.
|
|
278
|
+
:::
|
|
279
|
+
|
|
280
|
+
### Anthropic
|
|
281
|
+
|
|
282
|
+
Any [Claude](https://platform.claude.com) model, called directly through Anthropic's AI SDK provider:
|
|
283
|
+
|
|
284
|
+
```ts blume.config.ts lineNumbers
|
|
285
|
+
import { defineConfig } from "blume";
|
|
286
|
+
import { anthropic } from "blume/ai";
|
|
287
|
+
|
|
288
|
+
export default defineConfig({
|
|
289
|
+
ai: {
|
|
290
|
+
assistant: {
|
|
291
|
+
enabled: true,
|
|
292
|
+
provider: anthropic({ model: "claude-sonnet-5" }),
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Gemini
|
|
299
|
+
|
|
300
|
+
Any [Gemini](https://ai.google.dev) model, called directly through Google's AI SDK provider. The key is an API key from Google AI Studio:
|
|
301
|
+
|
|
302
|
+
```ts blume.config.ts lineNumbers
|
|
303
|
+
import { defineConfig } from "blume";
|
|
304
|
+
import { gemini } from "blume/ai";
|
|
305
|
+
|
|
306
|
+
export default defineConfig({
|
|
307
|
+
ai: {
|
|
308
|
+
assistant: {
|
|
309
|
+
enabled: true,
|
|
310
|
+
provider: gemini({ model: "gemini-3.5-flash" }),
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Grok
|
|
317
|
+
|
|
318
|
+
Any [xAI](https://x.ai/api) Grok model, called directly through xAI's AI SDK provider:
|
|
319
|
+
|
|
320
|
+
```ts blume.config.ts lineNumbers
|
|
321
|
+
import { defineConfig } from "blume";
|
|
322
|
+
import { grok } from "blume/ai";
|
|
323
|
+
|
|
324
|
+
export default defineConfig({
|
|
325
|
+
ai: {
|
|
326
|
+
assistant: {
|
|
327
|
+
enabled: true,
|
|
328
|
+
provider: grok({ model: "grok-4.7" }),
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
187
334
|
### Vercel AI Gateway
|
|
188
335
|
|
|
189
336
|
The default. `model` is a `provider/model` string, so you switch models by changing it (`openai/gpt-5.5`, `anthropic/claude-sonnet-4-5`, and so on) with no provider SDK to install. The gateway reads `AI_GATEWAY_API_KEY` from your environment and is wired up automatically when you deploy on Vercel, where it can also authenticate with the deployment's OIDC token:
|
|
@@ -265,29 +412,6 @@ export default defineConfig({
|
|
|
265
412
|
|
|
266
413
|
`baseUrl` overrides the preset endpoint (`https://api.inkeep.com/v1`).
|
|
267
414
|
|
|
268
|
-
### OpenAI-compatible endpoints
|
|
269
|
-
|
|
270
|
-
Any endpoint that speaks the OpenAI API works through `openaiCompatible()` — supply its `baseUrl`, the `model` it serves, and the env var holding its key. A generic endpoint has no preset for any of these, so all three are required; `name` is the provider name the AI SDK reports and defaults to `openai-compatible`:
|
|
271
|
-
|
|
272
|
-
```ts blume.config.ts lineNumbers
|
|
273
|
-
import { defineConfig } from "blume";
|
|
274
|
-
import { openaiCompatible } from "blume/ai";
|
|
275
|
-
|
|
276
|
-
export default defineConfig({
|
|
277
|
-
ai: {
|
|
278
|
-
assistant: {
|
|
279
|
-
enabled: true,
|
|
280
|
-
provider: openaiCompatible({
|
|
281
|
-
baseUrl: "https://my-gateway.example.com/v1",
|
|
282
|
-
apiKeyEnv: "MY_GATEWAY_API_KEY",
|
|
283
|
-
model: "gpt-4o",
|
|
284
|
-
name: "my-gateway",
|
|
285
|
-
}),
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
});
|
|
289
|
-
```
|
|
290
|
-
|
|
291
415
|
### Options every adapter takes
|
|
292
416
|
|
|
293
417
|
**`apiKeyEnv`** points an adapter at a different env var than its default — `gateway({ apiKeyEnv: "DOCS_GATEWAY_KEY" })` reads that variable instead of `AI_GATEWAY_API_KEY`, and the missing-secret warning at `blume dev`/`build` checks it too. Until the key is set, the deployed route answers `503` with a message naming the variable. The route reads a request body only up to 64 KB and answers anything larger with `413`.
|
|
@@ -295,7 +419,7 @@ export default defineConfig({
|
|
|
295
419
|
**`headers`** sends static request headers with every call — a caller-identifying header for a shared backend, say, so its own observability or rate limiting can tell your docs apart from other traffic:
|
|
296
420
|
|
|
297
421
|
```ts blume.config.ts lineNumbers
|
|
298
|
-
provider:
|
|
422
|
+
provider: openai({
|
|
299
423
|
baseUrl: "https://llm.internal.example.com/v1",
|
|
300
424
|
apiKeyEnv: "INTERNAL_LLM_API_KEY",
|
|
301
425
|
model: "gpt-4o",
|
|
@@ -314,7 +438,7 @@ provider: gateway({
|
|
|
314
438
|
}),
|
|
315
439
|
```
|
|
316
440
|
|
|
317
|
-
Blume maps only the options it names (`model`, `reasoning`, `apiKeyEnv`, `headers`) and forwards `providerOptions` verbatim, so it has to be JSON — it's inlined into the route — and it has to use the key the underlying provider expects (`openai` for an OpenAI model behind the gateway, `openrouter` on OpenRouter). Enabling the assistant also turns on React for the in-page island — see [Customization](/docs/configuration/customization#interactive-islands).
|
|
441
|
+
Blume maps only the options it names (`model`, `reasoning`, `apiKeyEnv`, `headers`) and forwards `providerOptions` verbatim, so it has to be JSON — it's inlined into the route — and it has to use the key the underlying provider expects (`openai` for `openai()` or an OpenAI model behind the gateway, `anthropic` for `anthropic()`, `google` for `gemini()`, `xai` for `grok()`, `openrouter` on OpenRouter, and the adapter's `name` on an OpenAI-compatible endpoint). Enabling the assistant also turns on React for the in-page island — see [Customization](/docs/configuration/customization#interactive-islands).
|
|
318
442
|
|
|
319
443
|
## Reasoning
|
|
320
444
|
|
|
@@ -328,10 +452,13 @@ Each adapter sends the level as its backend's own reasoning control, which is wh
|
|
|
328
452
|
|
|
329
453
|
| Adapter | What the level becomes |
|
|
330
454
|
| --- | --- |
|
|
455
|
+
| `openai()` | OpenAI's `reasoning_effort`. With a `baseUrl`, it's sent as `reasoning_effort` in the request, so the endpoint has to accept that parameter. |
|
|
456
|
+
| `anthropic()` | Claude's thinking: an effort level on models with adaptive thinking, a thinking budget on older ones, and thinking off for `"none"`. |
|
|
457
|
+
| `gemini()` | Gemini's thinking level, or a thinking budget on models that take one. |
|
|
458
|
+
| `grok()` | xAI's reasoning effort, on the models that offer one. |
|
|
331
459
|
| `gateway()` | The AI SDK's [`reasoning`](https://ai-sdk.dev/docs/ai-sdk-core/reasoning) call option, which the gateway maps to the model's own setting — OpenAI's `reasoning_effort`, for example. |
|
|
332
460
|
| `openrouter()` | OpenRouter's `reasoning.effort`, set on the model. Its provider ignores the AI SDK's call option, so the level is placed where OpenRouter reads it. |
|
|
333
461
|
| `llmgateway()` | `reasoning_effort` in the request, through the AI SDK's call option. |
|
|
334
|
-
| `openaiCompatible()` | `reasoning_effort` in the request, so the endpoint has to accept that parameter. |
|
|
335
462
|
| `inkeep()` | Not available. Inkeep runs its own QA pipeline with no reasoning control, so the adapter has no `reasoning` option and setting one is a config error. |
|
|
336
463
|
|
|
337
464
|
The model has to support the level you pick: OpenAI rejects a level a model doesn't offer (`"none"` and `"xhigh"` exist only on some), so check the model's documentation before setting one. Leave it unset to keep the model's default. Like [retrieval size](#retrieval-size), it trades thoroughness for time-to-first-token, and answers stay grounded either way.
|
|
@@ -342,16 +469,45 @@ With an [analytics provider](/docs/configuration/analytics) configured, the assi
|
|
|
342
469
|
|
|
343
470
|
| Event | When | Properties |
|
|
344
471
|
| --- | --- | --- |
|
|
345
|
-
| `ask` | A question is sent | `path`, `questionChars` |
|
|
346
|
-
| `ask_answer` | The answer finishes streaming | `path`, `questionChars`, `ms`, `chars` |
|
|
347
|
-
| `ask_error` | The request fails, breaks, or comes back empty | `path`, `questionChars`, `ms`, `status` |
|
|
472
|
+
| `ask` | A question is sent | `path`, `questionChars`, `thread` |
|
|
473
|
+
| `ask_answer` | The answer finishes streaming | `path`, `questionChars`, `thread`, `ms`, `chars` |
|
|
474
|
+
| `ask_error` | The request fails, breaks, or comes back empty | `path`, `questionChars`, `thread`, `ms`, `status` |
|
|
348
475
|
|
|
349
|
-
`path` is the page the reader asked from (the served pathname, so it matches the feedback widget and your pageviews under a `base`), `questionChars` the question's length, `ms` the time from sending the question to the last chunk, and `chars` the answer's length. `status` is the HTTP status: `0` when no response arrived at all (offline, DNS, CORS), and `200` when the response was fine but its stream broke mid-answer — how a provider or credential error surfaces, since the backend has already sent its headers — or delivered nothing. Clearing the conversation mid-answer reports neither outcome.
|
|
476
|
+
`path` is the page the reader asked from (the served pathname, so it matches the feedback widget and your pageviews under a `base`), `questionChars` the question's length, `thread` the conversation's id (new when the reader clears it), `ms` the time from sending the question to the last chunk, and `chars` the answer's length. `status` is the HTTP status: `0` when no response arrived at all (offline, DNS, CORS), and `200` when the response was fine but its stream broke mid-answer — how a provider or credential error surfaces, since the backend has already sent its headers — or delivered nothing. Clearing the conversation mid-answer reports neither outcome.
|
|
350
477
|
|
|
351
478
|
The question's text never reaches a provider: it is free-form reader input (pasted keys, error logs, names) that would breach most providers' terms and per-value size limits. It travels only on the `blume:track` DOM event, as `question` in `detail.props`, so a listener you write can forward it wherever you decide it belongs. A custom chat UI built on `useAssistant` from `blume/hooks` reports the same events. With no provider configured the built-in provider calls are no-ops, but the `blume:track` event still fires, so a custom integration listening for it receives them.
|
|
352
479
|
|
|
353
480
|
## Rate limiting
|
|
354
481
|
|
|
355
|
-
The `POST /api/ask` endpoint is **unauthenticated** — it has to be, so the in-page assistant can call it. Blume validates each request — rejecting malformed bodies, capping it to 1–40 messages, and accepting only `user`/`assistant` roles so a caller can't inject their own system prompt and repurpose the route as a general LLM proxy — to bound how much a single call can spend against your model
|
|
482
|
+
The `POST /api/ask` endpoint is **unauthenticated** — it has to be, so the in-page assistant can call it. Blume validates each request — rejecting malformed bodies, capping it to 1–40 messages, and accepting only `user`/`assistant` roles so a caller can't inject their own system prompt and repurpose the route as a general LLM proxy — to bound how much a single call can spend against your model. [Rate limiting](/docs/configuration/rate-limiting), on by default, then caps how many questions one reader can ask: past it, the panel tells them to try again in a few minutes.
|
|
483
|
+
|
|
484
|
+
## Bot protection
|
|
485
|
+
|
|
486
|
+
Rate limiting counts by IP address, so a script that spreads its questions across many addresses slips past it. To stop scripts, add a bot check from `blume/captcha`: before each question, the panel gets a token from the check, and the route verifies it with the provider before the model runs. Most readers never see a challenge; the check only asks for a click when it's unsure.
|
|
487
|
+
|
|
488
|
+
```ts blume.config.ts lineNumbers
|
|
489
|
+
import { defineConfig } from "blume";
|
|
490
|
+
import { turnstile } from "blume/captcha";
|
|
491
|
+
|
|
492
|
+
export default defineConfig({
|
|
493
|
+
ai: {
|
|
494
|
+
assistant: {
|
|
495
|
+
enabled: true,
|
|
496
|
+
captcha: turnstile({ siteKey: "0x4AAAAAAA…" }),
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
| Adapter | Provider | Secret key env var |
|
|
503
|
+
| --- | --- | --- |
|
|
504
|
+
| `turnstile()` | [Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/) | `TURNSTILE_SECRET_KEY` |
|
|
505
|
+
| `hcaptcha()` | [hCaptcha](https://www.hcaptcha.com), run invisibly | `HCAPTCHA_SECRET_KEY` |
|
|
506
|
+
|
|
507
|
+
The site key goes to the browser and is safe to commit; the secret key stays on the server. For Turnstile, create a widget in the Cloudflare dashboard and choose **Managed** or **Invisible** mode. Until the secret is set, the assistant answers that it isn't configured, and `blume build` warns. The provider's script loads with a reader's first question, not with the page.
|
|
508
|
+
|
|
509
|
+
A failed check shows the reader a translated "we couldn't check that you're human" notice, and the route answers `403`. Both providers publish test keys that always pass, for trying the check locally: Turnstile's `1x00000000000000000000BB` with the secret `1x0000000000000000000000000000000AA`, and hCaptcha's `10000000-ffff-ffff-ffff-000000000001` with `0x0000000000000000000000000000000000000000`.
|
|
510
|
+
|
|
511
|
+
With an [external endpoint](#external-endpoint), the panel still sends the token, as `captcha` in the request body, for your backend to verify.
|
|
356
512
|
|
|
357
513
|
The endpoint is advertised in the [agent readability manifest](/docs/discoverability/agent-discovery#agent-readability) alongside the rest of the site's machine-readable surface.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Cookie consent
|
|
3
|
+
description: Ask readers before analytics runs, with Blume's own banner or a hosted consent manager like Osano or Ethyca, as an adapter in blume.config.ts.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Set `consent` in `blume.config.ts` to ask readers before your [analytics](/docs/configuration/analytics) runs. It takes one adapter from `blume/consent`: Blume's own banner, or a hosted consent manager you already use.
|
|
7
|
+
|
|
8
|
+
```ts blume.config.ts lineNumbers
|
|
9
|
+
import { defineConfig } from "blume";
|
|
10
|
+
import { googleAnalytics, posthog } from "blume/analytics";
|
|
11
|
+
import { native } from "blume/consent";
|
|
12
|
+
|
|
13
|
+
export default defineConfig({
|
|
14
|
+
analytics: [
|
|
15
|
+
googleAnalytics({ id: "G-XXXXXXXXXX" }),
|
|
16
|
+
posthog({ key: "phc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }),
|
|
17
|
+
],
|
|
18
|
+
consent: native({ policy: "/privacy" }),
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
With `consent` set, every analytics adapter waits. Its tags are in the page but don't run until the reader allows analytics, and then they run in the order you listed them. A reader who declines never loads them, and doesn't see the [page rating](/docs/configuration#page-feedback) or its comment box either, since their answers would have nowhere to go. Vercel Web Analytics loads either way, since its component isn't a tag Blume can hold, but it sends nothing until the reader allows analytics.
|
|
23
|
+
|
|
24
|
+
:::note
|
|
25
|
+
Consent is opt-in. Without `consent`, analytics runs as the page loads, as it always has.
|
|
26
|
+
:::
|
|
27
|
+
|
|
28
|
+
## Adapters
|
|
29
|
+
|
|
30
|
+
| Adapter | Consent manager | Required |
|
|
31
|
+
| ---------- | ------------------------------------ | ------------------------ |
|
|
32
|
+
| `native()` | [Blume's own banner](#blumes-banner) | — |
|
|
33
|
+
| `osano()` | [Osano](#osano) | `customerId`, `configId` |
|
|
34
|
+
| `ethyca()` | [Ethyca](#ethyca) | `privacyCenter` |
|
|
35
|
+
|
|
36
|
+
## Blume's banner
|
|
37
|
+
|
|
38
|
+
`native()` shows a small card at the foot of the page until the reader answers, with **Accept** and **Decline** side by side, so declining is as easy as accepting. The answer stays in the reader's browser, and the banner doesn't come back once they've given it.
|
|
39
|
+
|
|
40
|
+
```ts blume.config.ts lineNumbers
|
|
41
|
+
consent: native({
|
|
42
|
+
policy: "/privacy", // optional: links the banner to your privacy policy
|
|
43
|
+
}),
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`policy` takes a page route or a full URL. The banner's text is translated in every built-in language. To change the wording, override the `consent` [UI strings](/docs/content/i18n#translated-ui) (`message`, `accept`, `decline`, `policy`, `settings`) with `i18n.ui`.
|
|
47
|
+
|
|
48
|
+
The banner also works in `blume dev`, so you can see it while you write. Analytics itself only loads in production builds.
|
|
49
|
+
|
|
50
|
+
## Osano
|
|
51
|
+
|
|
52
|
+
`osano()` loads [Osano Cookie Consent](https://www.osano.com), which shows the banner and drawer you set up in the Osano dashboard. Blume runs your analytics once Osano reports the reader's analytics consent, and again whenever they change it. Both IDs come from the script tag Osano gives you: `https://cmp.osano.com/<customerId>/<configId>/osano.js`.
|
|
53
|
+
|
|
54
|
+
```ts blume.config.ts lineNumbers
|
|
55
|
+
import { osano } from "blume/consent";
|
|
56
|
+
|
|
57
|
+
consent: osano({ customerId: "AzZdRbSIoEzZU2", configId: "7b9e2f4a-…" }),
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Osano decides what counts as consent in each region, following the compliance mode you chose in its dashboard.
|
|
61
|
+
|
|
62
|
+
## Ethyca
|
|
63
|
+
|
|
64
|
+
`ethyca()` loads [Ethyca's Fides](https://www.ethyca.com) consent manager from your privacy center, which shows the banner and modal you set up in Fides. Blume runs your analytics once the reader's analytics notice is on: opted in, not opted out of, or a notice that only asks them to acknowledge it.
|
|
65
|
+
|
|
66
|
+
```ts blume.config.ts lineNumbers
|
|
67
|
+
import { ethyca } from "blume/consent";
|
|
68
|
+
|
|
69
|
+
consent: ethyca({
|
|
70
|
+
privacyCenter: "https://privacy.example.com",
|
|
71
|
+
propertyId: "FDS-XXXXXX", // optional: which Fides property to load
|
|
72
|
+
notice: "analytics", // optional: the notice key that covers analytics
|
|
73
|
+
}),
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`notice` defaults to `analytics`. Set it when your Fides notice for analytics has another key.
|
|
77
|
+
|
|
78
|
+
Like analytics, Osano and Fides load in production builds only, so testing in `blume dev` creates no consent records. Preview them with `blume build` and `blume preview`.
|
|
79
|
+
|
|
80
|
+
## Changing an answer
|
|
81
|
+
|
|
82
|
+
When `consent` is set, the site footer shows a **Cookie settings** link. It reopens Blume's banner, or the hosted consent manager's preferences. If you replace the footer with your own [`Footer`](/docs/configuration/customization#layout-slots), give any element the `data-blume-consent-open` attribute to make it the link:
|
|
83
|
+
|
|
84
|
+
```html
|
|
85
|
+
<button data-blume-consent-open type="button">Cookie settings</button>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
A reader who allows analytics and later takes it back gets a page reload, the one way to stop scripts that already started. From then on, analytics stays off.
|
|
89
|
+
|
|
90
|
+
## Your own scripts
|
|
91
|
+
|
|
92
|
+
Hold any script of your own the same way: give it `type="text/plain"` and `data-blume-consent="analytics"`, and it runs when the reader allows analytics. To react to answers yourself, listen for the `blume:consent` event on `window`, whose `detail.analytics` is the reader's choice. `window.blumeConsent.analytics` holds the current one: `null` until the consent manager answers, then `true` or `false`.
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
window.addEventListener("blume:consent", (event) => {
|
|
96
|
+
if (event.detail.analytics) {
|
|
97
|
+
// start something that needs consent
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
```
|
|
@@ -79,12 +79,12 @@ import Logo from "./components/Logo.astro";
|
|
|
79
79
|
export default defineComponents({
|
|
80
80
|
layout: {
|
|
81
81
|
Logo, // brand mark + title in the header
|
|
82
|
-
Footer, // site-wide footer
|
|
82
|
+
Footer, // site-wide footer, in place of the one `footer` configures
|
|
83
83
|
},
|
|
84
84
|
});
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
Wired slots:
|
|
87
|
+
Wired slots (names are case-sensitive; any other key under `layout` renders nowhere, and Blume warns about it):
|
|
88
88
|
|
|
89
89
|
| Slot | Replaces | Props |
|
|
90
90
|
| --- | --- | --- |
|
|
@@ -95,14 +95,16 @@ Wired slots:
|
|
|
95
95
|
| `Sidebar` | The primary navigation tree | `items`, `currentRoute` |
|
|
96
96
|
| `MobileNav` | The nav inside the mobile drawer (defaults to `Sidebar`) | `items`, `currentRoute` |
|
|
97
97
|
| `Breadcrumbs` | The breadcrumb trail | `crumbs` |
|
|
98
|
-
| `TableOfContents` | The on-this-page outline | `headings`, `title`, `variant` |
|
|
98
|
+
| `TableOfContents` | The on-this-page outline, in the right rail (`variant: "desktop"`) and in the dropdown above the content on narrower screens (`"mobile"`) | `headings`, `title`, `variant` |
|
|
99
99
|
| `Pagination` | The prev/next footer links | `prev`, `next`, `strings` |
|
|
100
|
-
| `Feedback` | The "Was this page helpful?" rating below the article (rendered only when [`feedback`](/docs/configuration#page-feedback) is on) | `strings` |
|
|
100
|
+
| `Feedback` | The "Was this page helpful?" rating below the article (rendered only when [`feedback`](/docs/configuration#page-feedback) is on) | `strings`, `comments` |
|
|
101
101
|
| `PageHeader` | An injection point above the article (no built-in) | `page`, `headings`, `route` |
|
|
102
102
|
| `PageFooter` | An injection point below the article (no built-in) | `page`, `headings`, `route` |
|
|
103
|
-
| `Footer` |
|
|
103
|
+
| `Footer` | The [site footer](/docs/configuration#footer) after the content grid: links, social profiles, the repository link, and Cookie settings | `footer`, `locale`, `site`, `navigation`, `ui` |
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
The `mobile` variant also receives the [page actions](/docs/content/navigation#page-actions) as its children (an empty element the actions move into from the right rail below 1,280px), so render a `<slot />` in it to keep them on narrower screens. It renders on a page with no headings as well, with `headings` empty, so the actions still show there.
|
|
106
|
+
|
|
107
|
+
`PageHeader` and `PageFooter` have no built-in component — they render nothing until you set them, which makes them handy injection points for a promo banner or a "last updated" note. The built-in `Footer` holds the only links to your repository and social profiles, so a `Footer` override (the place for a marketing footer the config can't express) should keep them. With `consent` set, also keep a way to reopen it: any element with `data-blume-consent-open` works as the [Cookie settings](/docs/configuration/consent#changing-an-answer) link.
|
|
106
108
|
|
|
107
109
|
Layout slots accept the same [three reference forms](#reference-form) as MDX overrides, so a slot can be a path string or a hydrated descriptor (`{ component, client }`) when you want an interactive header or footer.
|
|
108
110
|
|
|
@@ -26,7 +26,7 @@ Any format you omit (or set to `false`) is left out of the menu. With both off
|
|
|
26
26
|
|
|
27
27
|
## PDF
|
|
28
28
|
|
|
29
|
-
**Export to PDF** opens your browser's print dialog with a print stylesheet that strips the site chrome — header, sidebars, navigation — down to just the article. Choose **Save as PDF** to finish.
|
|
29
|
+
**Export to PDF** opens your browser's print dialog with a print stylesheet that strips the site chrome — header, sidebars, navigation, and everything after the article (last updated, feedback, previous and next links, the site footer), plus the copy buttons on code blocks — down to just the article. Collapsed [`expandable`](/docs/content/syntax#long-lines-and-long-blocks) code blocks print in full, and a page read in the dark theme prints in the light one. Choose **Save as PDF** to finish. The same stylesheet applies when a reader prints from the browser's own menu.
|
|
30
30
|
|
|
31
31
|
Because it prints the live, fully-styled page, the result keeps crisp, selectable text, real fonts, and syntax-highlighted code. It needs no dependencies and works the same in dev, in production, and on static hosts.
|
|
32
32
|
|
|
@@ -38,11 +38,13 @@ PDF export goes through the browser's native print dialog, so the exact "Save as
|
|
|
38
38
|
|
|
39
39
|
**Export to EPUB** generates a self-contained `.epub` of the current page in the browser and downloads it — ready for Apple Books, Calibre, or any e-reader. The generator is loaded only when a reader clicks Export, so it never weighs down the rest of your site.
|
|
40
40
|
|
|
41
|
-
To keep the file readable on a device with no stylesheet or JavaScript of its own, Blume rewrites the page into clean, standalone HTML: syntax-highlighted code becomes plain monospace, decorative icons and copy buttons are dropped, and a built-in e-reader stylesheet handles spacing, code blocks, and tables.
|
|
41
|
+
To keep the file readable on a device with no stylesheet or JavaScript of its own, Blume rewrites the page into clean, standalone HTML: syntax-highlighted code becomes plain monospace, decorative icons and copy buttons are dropped, and a built-in e-reader stylesheet handles spacing, code blocks, and tables. Links to other pages point at your live site, since the book holds only this page.
|
|
42
|
+
|
|
43
|
+
The book is titled after the page's heading, takes the page's language, and names your site's [`title`](/docs/configuration#site) as its author and publisher. Images are fetched into the book; one the browser can't fetch, such as an image on another host that doesn't allow cross-origin requests, is left out, with a warning in the browser console.
|
|
42
44
|
|
|
43
45
|
## What gets exported
|
|
44
46
|
|
|
45
|
-
Both formats export the **single page** a reader is viewing — not the whole site — which matches where the action lives, beneath that page's table of contents.
|
|
47
|
+
Both formats export the **single page** a reader is viewing — not the whole site — which matches where the action lives, beneath that page's table of contents. Like the other [page actions](/docs/content/navigation#page-actions), it sits in the right-hand column in windows at least 1,280px wide, and in the “On this page” dropdown above the content in narrower windows and on phones.
|
|
46
48
|
|
|
47
49
|
:::note
|
|
48
50
|
In an EPUB there's no JavaScript to switch tabs, so tabbed content — like [package-install](/docs/content/syntax) blocks and code groups — is expanded to show every panel at once. Client-rendered embeds such as Mermaid diagrams aren't included.
|