blume 2.0.0 → 2.0.2
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 -1
- package/CHANGELOG.md +110 -0
- package/README.md +2 -2
- package/dist/cli/{chunk-f2972sbt.js → chunk-00gs3wqs.js} +1 -1
- package/dist/cli/{chunk-fa25z98p.js → chunk-273ygyr4.js} +4 -3
- package/dist/cli/chunk-273ygyr4.js.map +11 -0
- package/dist/cli/{chunk-pat2zzwc.js → chunk-3yce002v.js} +21 -8
- package/dist/cli/{chunk-pat2zzwc.js.map → chunk-3yce002v.js.map} +4 -4
- package/dist/cli/{chunk-d1tadaw7.js → chunk-4e9b9ra6.js} +17 -7
- package/dist/cli/chunk-4e9b9ra6.js.map +10 -0
- package/dist/cli/{chunk-zg2gtj10.js → chunk-5r8g91qn.js} +352 -676
- package/dist/cli/chunk-5r8g91qn.js.map +34 -0
- package/dist/cli/{chunk-11j0384y.js → chunk-6dtt0zfn.js} +14 -14
- package/dist/cli/{chunk-11j0384y.js.map → chunk-6dtt0zfn.js.map} +3 -3
- package/dist/cli/{chunk-bw22s759.js → chunk-6k4ftwze.js} +38 -19
- package/dist/cli/{chunk-bw22s759.js.map → chunk-6k4ftwze.js.map} +4 -4
- package/dist/cli/{chunk-sqn5t4q0.js → chunk-7mbqtmgb.js} +16 -7
- package/dist/cli/chunk-7mbqtmgb.js.map +10 -0
- package/dist/cli/{chunk-79njf86q.js → chunk-7vtckvaw.js} +21 -19
- package/dist/cli/chunk-7vtckvaw.js.map +11 -0
- package/dist/cli/{chunk-fh5hj5jt.js → chunk-8g8ytmgx.js} +17 -18
- package/dist/cli/{chunk-fh5hj5jt.js.map → chunk-8g8ytmgx.js.map} +2 -2
- package/dist/cli/{chunk-1w8dp3qb.js → chunk-91ws1n6j.js} +18 -15
- package/dist/cli/chunk-91ws1n6j.js.map +10 -0
- package/dist/cli/{chunk-7ez8ny0t.js → chunk-bbnwccaz.js} +2 -2
- package/dist/cli/{chunk-5a2z0198.js → chunk-bfwp9vp6.js} +18 -10
- package/dist/cli/chunk-bfwp9vp6.js.map +10 -0
- package/dist/cli/{chunk-ernrthtr.js → chunk-d1v5rhy0.js} +14 -15
- package/dist/cli/{chunk-ernrthtr.js.map → chunk-d1v5rhy0.js.map} +2 -2
- package/dist/cli/{chunk-a9kptbw5.js → chunk-ddndchfr.js} +21 -6
- package/dist/cli/chunk-ddndchfr.js.map +14 -0
- package/dist/cli/{chunk-zxccj738.js → chunk-esh98wmb.js} +1 -1
- package/dist/cli/{chunk-88cpgt6h.js → chunk-fsmrqk8a.js} +1 -1
- package/dist/cli/{chunk-b5aj94ah.js → chunk-g698a744.js} +5 -5
- package/dist/cli/{chunk-jts8mvcz.js → chunk-gs7r695n.js} +9 -3
- package/dist/cli/{chunk-jts8mvcz.js.map → chunk-gs7r695n.js.map} +3 -3
- package/dist/cli/{chunk-41za066z.js → chunk-h2ez8dzb.js} +4 -4
- package/dist/cli/{chunk-by2290sx.js → chunk-h7k3nq3v.js} +2 -2
- package/dist/cli/{chunk-j8mw0za6.js → chunk-hqp2ajnh.js} +252 -123
- package/dist/cli/chunk-hqp2ajnh.js.map +35 -0
- package/dist/cli/{chunk-mnqj32sj.js → chunk-hr8ne106.js} +119 -50
- package/dist/cli/chunk-hr8ne106.js.map +13 -0
- package/dist/cli/{chunk-mwt1k8n7.js → chunk-j85scx15.js} +74 -30
- package/dist/cli/chunk-j85scx15.js.map +10 -0
- package/dist/cli/{chunk-z01ze5c1.js → chunk-k7pj68a8.js} +63 -22
- package/dist/cli/chunk-k7pj68a8.js.map +11 -0
- package/dist/cli/{chunk-2q1dwty4.js → chunk-mqc662a6.js} +8 -8
- package/dist/cli/{chunk-2q1dwty4.js.map → chunk-mqc662a6.js.map} +4 -4
- package/dist/cli/{chunk-y3e45rc8.js → chunk-n1yg3tj3.js} +4 -4
- package/dist/cli/{chunk-bctazmbk.js → chunk-nfcyttvj.js} +17 -6
- package/dist/cli/chunk-nfcyttvj.js.map +10 -0
- package/dist/cli/{chunk-6k8vp3ta.js → chunk-pbg5a4s3.js} +60 -28
- package/dist/cli/chunk-pbg5a4s3.js.map +19 -0
- package/dist/cli/{chunk-d80hr03s.js → chunk-ppzjqwx2.js} +24 -17
- package/dist/cli/{chunk-d80hr03s.js.map → chunk-ppzjqwx2.js.map} +4 -4
- package/dist/cli/{chunk-beat36xx.js → chunk-qkb5a8sa.js} +25 -10
- package/dist/cli/chunk-qkb5a8sa.js.map +10 -0
- package/dist/cli/{chunk-bnbmcwfb.js → chunk-sqw4ekg1.js} +5 -5
- package/dist/cli/{chunk-bnbmcwfb.js.map → chunk-sqw4ekg1.js.map} +3 -3
- package/dist/cli/{chunk-xaz13gwg.js → chunk-v6ya5kcb.js} +3268 -1150
- package/dist/cli/chunk-v6ya5kcb.js.map +189 -0
- package/dist/cli/{chunk-tzne8qfq.js → chunk-w4bxdvsa.js} +18 -15
- package/dist/cli/chunk-w4bxdvsa.js.map +10 -0
- package/dist/cli/{chunk-f7t03s3g.js → chunk-wdrt2k2v.js} +2 -2
- package/dist/cli/{chunk-z1f5arsg.js → chunk-xh43dwgw.js} +170 -55
- package/dist/cli/chunk-xh43dwgw.js.map +36 -0
- package/dist/cli/{chunk-pnnvybbk.js → chunk-zp79m0ts.js} +5 -5
- package/dist/cli/{chunk-pnnvybbk.js.map → chunk-zp79m0ts.js.map} +2 -2
- package/dist/cli/index.js +160 -35
- package/dist/cli/index.js.map +4 -4
- package/dist/types/ai/agent-readability.d.ts +1 -1
- package/dist/types/ai/agent-surface.d.ts +32 -0
- package/dist/types/ai/api/paths.d.ts +1 -1
- package/dist/types/ai/api-catalog.d.ts +7 -1
- package/dist/types/ai/ask-context.d.ts +14 -7
- package/dist/types/ai/ask.d.ts +43 -43
- package/dist/types/ai/component-markdown.d.ts +4 -4
- package/dist/types/ai/index.d.ts +3 -3
- package/dist/types/ai/openapi-components.d.ts +1 -1
- package/dist/types/ai/relative-links.d.ts +9 -4
- package/dist/types/ai/serializers.d.ts +1 -1
- package/dist/types/ai/static-expression.d.ts +28 -0
- package/dist/types/ai/visibility.d.ts +1 -1
- package/dist/types/analytics/databuddy.d.ts +43 -0
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/schema.d.ts +14 -0
- package/dist/types/cli/env.d.ts +5 -0
- package/dist/types/cli/init/scaffold.d.ts +19 -3
- package/dist/types/cli/init/starter-spec.d.ts +11 -0
- package/dist/types/core/base-path.d.ts +9 -0
- package/dist/types/core/config-input.d.ts +21 -21
- package/dist/types/core/config.d.ts +5 -5
- package/dist/types/core/data.d.ts +3 -3
- package/dist/types/core/graph.d.ts +2 -0
- package/dist/types/core/i18n-ui.d.ts +37 -8
- package/dist/types/core/i18n.d.ts +7 -1
- package/dist/types/core/links.d.ts +3 -1
- package/dist/types/core/load-module.d.ts +10 -0
- package/dist/types/core/locale-links.d.ts +12 -2
- package/dist/types/core/meta.d.ts +5 -1
- package/dist/types/core/nav-diagnostics.d.ts +10 -0
- package/dist/types/core/navigation.d.ts +38 -0
- package/dist/types/core/ordering-prefix.d.ts +4 -0
- package/dist/types/core/safe-links.d.ts +3 -1
- package/dist/types/core/schema.d.ts +57 -18
- package/dist/types/core/sources/github-releases.d.ts +5 -0
- package/dist/types/core/sources/lower.d.ts +14 -2
- package/dist/types/core/sources/normalize.d.ts +10 -1
- package/dist/types/core/sources/remote.d.ts +11 -1
- package/dist/types/core/sources/resolve.d.ts +12 -0
- package/dist/types/core/sources/types.d.ts +31 -0
- package/dist/types/core/types.d.ts +9 -0
- package/dist/types/core/unrecognized-keys.d.ts +1 -1
- package/dist/types/deploy/adapters/node.d.ts +5 -2
- package/dist/types/deploy/adapters/types.d.ts +7 -0
- package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
- package/dist/types/deploy/headers.d.ts +31 -7
- package/dist/types/deploy/node-headers.d.ts +43 -8
- package/dist/types/deploy/platforms/netlify.d.ts +27 -2
- package/dist/types/deploy/platforms/node.d.ts +6 -5
- package/dist/types/deploy/platforms/types.d.ts +7 -0
- package/dist/types/deploy/platforms/vercel.d.ts +3 -2
- package/dist/types/deploy/redirects.d.ts +7 -2
- package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
- package/dist/types/openapi/model.d.ts +20 -6
- package/dist/types/search/documents.d.ts +1 -1
- package/dist/types/search/orama-index.d.ts +1 -1
- package/dist/types/search/sync/algolia.d.ts +3 -1
- package/docs/01-quickstart.mdx +3 -2
- package/docs/02-deployment.mdx +21 -12
- package/docs/03-upgrading.mdx +22 -9
- package/docs/04-migrating.mdx +4 -4
- package/docs/08-faq.mdx +12 -5
- package/docs/advanced/changelog.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +5 -3
- package/docs/advanced/skills.mdx +1 -1
- package/docs/cli/audit.mdx +5 -5
- package/docs/cli/doctor.mdx +4 -4
- package/docs/cli/evals.mdx +9 -9
- package/docs/cli/index.mdx +6 -3
- package/docs/cli/translate.mdx +8 -8
- package/docs/configuration/analytics.mdx +23 -2
- package/docs/configuration/{ask-ai.mdx → assistant.mdx} +20 -20
- package/docs/configuration/customization.mdx +5 -4
- package/docs/configuration/index.mdx +7 -5
- package/docs/configuration/meta.ts +1 -1
- package/docs/configuration/search.mdx +2 -2
- package/docs/content/components.mdx +1 -1
- package/docs/content/frontmatter.mdx +5 -1
- package/docs/content/i18n.mdx +3 -3
- package/docs/content/index.mdx +4 -2
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +5 -3
- package/docs/content/navigation.mdx +29 -4
- package/docs/content/sources.mdx +18 -12
- package/docs/content/versioning.mdx +1 -0
- package/docs/discoverability/agent-discovery.mdx +22 -8
- package/docs/discoverability/index.mdx +3 -3
- package/docs/discoverability/llms-txt.mdx +2 -5
- package/docs/discoverability/markdown.mdx +5 -3
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +3 -2
- package/docs/discoverability/open-graph.mdx +6 -4
- package/docs/discoverability/rss.mdx +3 -1
- package/docs/index.mdx +2 -2
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/openapi.mdx +3 -3
- package/package.json +1 -1
- package/skills/blume/SKILL.md +5 -5
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +6 -6
- package/skills/blume-migrate/references/mintlify.md +3 -3
- package/skills/blume-migrate/references/monorepo.md +1 -1
- package/skills/blume-migrate/references/nextra.md +1 -1
- package/skills/blume-migrate/references/starlight.md +5 -5
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +2 -2
- package/skills/blume-update-docs/references/audit-checklist.md +1 -1
- package/src/ai/agent-readability.ts +4 -4
- package/src/ai/agent-surface.ts +56 -0
- package/src/ai/api/paths.ts +1 -1
- package/src/ai/api/spec.ts +15 -2
- package/src/ai/api-catalog.ts +7 -1
- package/src/ai/ask-context.ts +21 -9
- package/src/ai/ask-data.ts +28 -14
- package/src/ai/ask.ts +84 -71
- package/src/ai/component-markdown.ts +43 -37
- package/src/ai/cors.ts +3 -3
- package/src/ai/index.ts +16 -16
- package/src/ai/link-headers.ts +8 -3
- package/src/ai/llms.ts +4 -3
- package/src/ai/markdown.ts +23 -11
- package/src/ai/mcp/server.ts +78 -7
- package/src/ai/openapi-components.ts +1 -1
- package/src/ai/relative-links.ts +78 -10
- package/src/ai/serializers.ts +1 -1
- package/src/ai/static-expression.ts +416 -0
- package/src/ai/visibility.ts +46 -15
- package/src/analytics/databuddy.ts +67 -0
- package/src/analytics/head.ts +4 -0
- package/src/analytics/index.ts +2 -0
- package/src/analytics/posthog.ts +21 -3
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +90 -45
- package/src/astro/module-types.ts +1 -1
- package/src/astro/runtime-deps.ts +6 -6
- package/src/astro/templates.ts +87 -49
- package/src/audit/catalog.ts +2 -2
- package/src/audit/checks/assets.ts +25 -5
- package/src/audit/checks/content.ts +20 -2
- package/src/audit/checks/i18n.ts +46 -8
- package/src/audit/checks/indexability.ts +39 -19
- package/src/audit/checks/links.ts +14 -0
- package/src/audit/checks/llms.ts +6 -3
- package/src/audit/checks/network.ts +3 -1
- package/src/audit/checks/og-image.ts +10 -0
- package/src/audit/checks/robots.ts +6 -1
- package/src/audit/checks/sitemap.ts +56 -31
- package/src/audit/checks/social.ts +21 -2
- package/src/audit/crawl.ts +88 -15
- package/src/audit/report.ts +54 -17
- package/src/audit/run.ts +1 -0
- package/src/audit/types.ts +18 -0
- package/src/audit/url.ts +37 -6
- package/src/blume-modules.d.ts +2 -2
- package/src/cli/build-failure.ts +50 -0
- package/src/cli/commands/audit.ts +8 -1
- package/src/cli/commands/build.ts +19 -5
- package/src/cli/commands/check.ts +2 -0
- package/src/cli/commands/doctor.ts +7 -5
- package/src/cli/commands/eval.ts +4 -10
- package/src/cli/commands/init.ts +15 -5
- package/src/cli/commands/migrate.ts +2 -2
- package/src/cli/commands/preview.ts +15 -0
- package/src/cli/commands/sync.ts +2 -0
- package/src/cli/commands/translate.ts +12 -9
- package/src/cli/commands/upgrade.ts +13 -2
- package/src/cli/eject-scripts.ts +32 -7
- package/src/cli/env.ts +12 -1
- package/src/cli/init/scaffold.ts +117 -11
- package/src/cli/init/starter-spec.ts +235 -0
- package/src/cli/required-secrets.ts +3 -3
- package/src/components/content/AccordionItem.astro +26 -22
- package/src/components/content/Badge.astro +2 -9
- package/src/components/content/Card.astro +2 -2
- package/src/components/content/Component.astro +9 -3
- package/src/components/content/Expandable.astro +5 -1
- package/src/components/content/Frame.astro +2 -7
- package/src/components/content/GithubInfo.astro +12 -2
- package/src/components/content/Prompt.astro +2 -7
- package/src/components/content/Tabs.astro +54 -5
- package/src/components/content/Tile.astro +1 -1
- package/src/components/content/Tooltip.astro +69 -7
- package/src/components/content/Tree.astro +7 -2
- package/src/components/content/TypeTable.astro +10 -5
- package/src/components/content/Update.astro +8 -2
- package/src/components/content/auto-type-table.ts +4 -1
- package/src/components/content/badge-color.ts +17 -0
- package/src/components/content/base-href.ts +18 -3
- package/src/components/content/inline-markdown.ts +27 -7
- package/src/components/copy-feedback.ts +36 -9
- package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
- package/src/components/islands/{ask-ai.tsx → assistant.tsx} +39 -25
- package/src/components/islands/hooks.ts +21 -14
- package/src/components/islands/webmcp.ts +12 -8
- package/src/components/layout/Banner.astro +23 -4
- package/src/components/layout/Header.astro +32 -19
- package/src/components/layout/Logo.astro +5 -0
- package/src/components/layout/NavSelector.astro +6 -1
- package/src/components/layout/NavTabMenu.astro +133 -0
- package/src/components/layout/NavTree.astro +15 -6
- package/src/components/layout/NavTreeCache.astro +5 -2
- package/src/components/layout/NavTreeScript.astro +45 -5
- package/src/components/layout/PageLayout.astro +28 -14
- package/src/components/layout/Pagination.astro +7 -7
- package/src/components/layout/ReferenceLayout.astro +5 -2
- package/src/components/layout/RootLayout.astro +27 -14
- package/src/components/layout/Search.astro +18 -14
- package/src/components/layout/analytics-client.ts +3 -1
- package/src/components/layout/drawer-inert.ts +1 -1
- package/src/components/openapi/GraphqlType.astro +11 -3
- package/src/components/openapi/MessageComposer.astro +1 -1
- package/src/components/openapi/Operation.astro +21 -5
- package/src/components/openapi/PanelTabs.astro +4 -1
- package/src/components/openapi/Playground.astro +8 -2
- package/src/components/openapi/RequestPanel.astro +9 -5
- package/src/components/openapi/SchemaProperty.astro +11 -33
- package/src/components/openapi/SchemaTable.astro +27 -52
- package/src/components/openapi/description.ts +2 -2
- package/src/components/openapi/helpers.ts +90 -12
- package/src/components/openapi/message-composer.ts +8 -0
- package/src/components/openapi/message-model.ts +12 -2
- package/src/components/openapi/message.ts +4 -1
- package/src/components/openapi/operation-model.ts +40 -8
- package/src/components/openapi/panel.ts +29 -5
- package/src/components/openapi/playground-client.ts +37 -7
- package/src/components/openapi/playground-schema.ts +25 -6
- package/src/components/openapi/request.ts +2 -0
- package/src/components/openapi/schema-tree.ts +203 -0
- package/src/components/openapi/snippets.ts +30 -15
- package/src/components/openapi/validate-json.ts +1 -1
- package/src/core/base-path.ts +15 -0
- package/src/core/code-fences.ts +1 -1
- package/src/core/config-input.ts +23 -23
- package/src/core/config.ts +21 -7
- package/src/core/data.ts +3 -3
- package/src/core/diagnostics.ts +217 -30
- package/src/core/graph.ts +120 -11
- package/src/core/i18n-ui.ts +74 -11
- package/src/core/i18n.ts +13 -2
- package/src/core/links.ts +7 -4
- package/src/core/load-module.ts +20 -0
- package/src/core/locale-links.ts +17 -17
- package/src/core/manifest.ts +3 -2
- package/src/core/meta.ts +69 -6
- package/src/core/nav-diagnostics.ts +56 -1
- package/src/core/navigation.ts +245 -76
- package/src/core/ordering-prefix.ts +27 -0
- package/src/core/project-graph.ts +23 -1
- package/src/core/request-body.ts +1 -1
- package/src/core/safe-href.ts +53 -1
- package/src/core/safe-links.ts +11 -2
- package/src/core/schema.ts +95 -34
- package/src/core/server-features.ts +2 -2
- package/src/core/sources/assets.ts +83 -41
- package/src/core/sources/contentful-rich-text.ts +3 -2
- package/src/core/sources/contentful.ts +25 -12
- package/src/core/sources/filesystem.ts +5 -1
- package/src/core/sources/github-releases.ts +106 -5
- package/src/core/sources/lexical.ts +9 -4
- package/src/core/sources/lower.ts +79 -26
- package/src/core/sources/mdx-remote.ts +1 -0
- package/src/core/sources/normalize.ts +132 -30
- package/src/core/sources/notion.ts +26 -10
- package/src/core/sources/obsidian.ts +17 -3
- package/src/core/sources/payload.ts +1 -0
- package/src/core/sources/portable-text.ts +63 -44
- package/src/core/sources/remote.ts +18 -2
- package/src/core/sources/resolve.ts +52 -33
- package/src/core/sources/sanity.ts +1 -0
- package/src/core/sources/strapi-blocks.ts +9 -4
- package/src/core/sources/strapi.ts +1 -0
- package/src/core/sources/types.ts +31 -0
- package/src/core/types.ts +9 -0
- package/src/core/ui-packs/ar.ts +17 -5
- package/src/core/ui-packs/bg.ts +17 -5
- package/src/core/ui-packs/bn.ts +17 -5
- package/src/core/ui-packs/ca.ts +17 -5
- package/src/core/ui-packs/cs.ts +17 -5
- package/src/core/ui-packs/da.ts +17 -5
- package/src/core/ui-packs/de.ts +17 -5
- package/src/core/ui-packs/el.ts +17 -5
- package/src/core/ui-packs/es.ts +17 -5
- package/src/core/ui-packs/fa.ts +17 -5
- package/src/core/ui-packs/fi.ts +17 -5
- package/src/core/ui-packs/fr.ts +17 -5
- package/src/core/ui-packs/he.ts +17 -5
- package/src/core/ui-packs/hi.ts +17 -5
- package/src/core/ui-packs/hr.ts +17 -5
- package/src/core/ui-packs/hu.ts +17 -5
- package/src/core/ui-packs/id.ts +17 -5
- package/src/core/ui-packs/it.ts +17 -5
- package/src/core/ui-packs/ja.ts +17 -5
- package/src/core/ui-packs/ko.ts +17 -5
- package/src/core/ui-packs/nl.ts +17 -5
- package/src/core/ui-packs/no.ts +17 -5
- package/src/core/ui-packs/pl.ts +17 -5
- package/src/core/ui-packs/pt-br.ts +17 -5
- package/src/core/ui-packs/pt.ts +17 -5
- package/src/core/ui-packs/ro.ts +17 -5
- package/src/core/ui-packs/ru.ts +17 -5
- package/src/core/ui-packs/sk.ts +17 -5
- package/src/core/ui-packs/sr.ts +17 -5
- package/src/core/ui-packs/sv.ts +17 -5
- package/src/core/ui-packs/th.ts +17 -5
- package/src/core/ui-packs/tr.ts +17 -5
- package/src/core/ui-packs/uk.ts +17 -5
- package/src/core/ui-packs/vi.ts +17 -5
- package/src/core/ui-packs/zh-tw.ts +17 -5
- package/src/core/ui-packs/zh.ts +17 -5
- package/src/core/unrecognized-keys.ts +1 -1
- package/src/core/version-cut.ts +17 -2
- package/src/core/versions.ts +4 -1
- package/src/deploy/adapters/node.ts +5 -2
- package/src/deploy/adapters/registry.ts +2 -1
- package/src/deploy/adapters/types.ts +13 -1
- package/src/deploy/artifacts.ts +25 -5
- package/src/deploy/cloudflare-negotiation.ts +23 -4
- package/src/deploy/headers.ts +67 -51
- package/src/deploy/node-headers.ts +148 -27
- package/src/deploy/platforms/cloudflare.ts +1 -0
- package/src/deploy/platforms/netlify.ts +82 -5
- package/src/deploy/platforms/node.ts +7 -5
- package/src/deploy/platforms/static.ts +1 -0
- package/src/deploy/platforms/types.ts +7 -0
- package/src/deploy/platforms/vercel.ts +9 -3
- package/src/deploy/redirects.ts +14 -3
- package/src/deploy/vercel-negotiation.ts +35 -3
- package/src/eval/agents.ts +10 -2
- package/src/eval/run.ts +25 -0
- package/src/markdown/base-links.ts +55 -8
- package/src/markdown/index.ts +6 -3
- package/src/markdown/relative-links.ts +3 -23
- package/src/markdown/route-snapshot.ts +37 -0
- package/src/og/card.ts +20 -4
- package/src/og/derive.ts +145 -4
- package/src/openapi/graphql-build.ts +28 -2
- package/src/openapi/model.ts +77 -13
- package/src/openapi/render-mdx.ts +10 -1
- package/src/registry/eject.ts +135 -37
- package/src/search/documents.ts +24 -3
- package/src/search/orama-index.ts +1 -1
- package/src/search/sync/algolia.ts +36 -2
- package/src/sources/registry.ts +5 -0
- package/src/theme/entry.ts +11 -4
- package/src/translate/agents.ts +6 -1
- package/src/translate/ledger.ts +26 -3
- package/src/translate/meta.ts +11 -3
- package/src/translate/report.ts +1 -1
- package/src/translate/run.ts +11 -5
- package/src/translate/validate.ts +10 -1
- package/src/translate/work-list.ts +36 -3
- package/src/upgrade/upgrade.ts +37 -5
- package/dist/cli/chunk-1w8dp3qb.js.map +0 -10
- package/dist/cli/chunk-5a2z0198.js.map +0 -10
- package/dist/cli/chunk-6k8vp3ta.js.map +0 -19
- package/dist/cli/chunk-79njf86q.js.map +0 -11
- package/dist/cli/chunk-a9kptbw5.js.map +0 -14
- package/dist/cli/chunk-abh8yjkn.js +0 -31
- package/dist/cli/chunk-abh8yjkn.js.map +0 -10
- package/dist/cli/chunk-bctazmbk.js.map +0 -10
- package/dist/cli/chunk-beat36xx.js.map +0 -10
- package/dist/cli/chunk-d1tadaw7.js.map +0 -10
- package/dist/cli/chunk-fa25z98p.js.map +0 -11
- package/dist/cli/chunk-j8mw0za6.js.map +0 -35
- package/dist/cli/chunk-mnqj32sj.js.map +0 -12
- package/dist/cli/chunk-mwt1k8n7.js.map +0 -10
- package/dist/cli/chunk-nk3ts2xk.js +0 -51
- package/dist/cli/chunk-nk3ts2xk.js.map +0 -10
- package/dist/cli/chunk-sqn5t4q0.js.map +0 -10
- package/dist/cli/chunk-tzne8qfq.js.map +0 -10
- package/dist/cli/chunk-xaz13gwg.js.map +0 -182
- package/dist/cli/chunk-z01ze5c1.js.map +0 -10
- package/dist/cli/chunk-z1f5arsg.js.map +0 -36
- package/dist/cli/chunk-zg2gtj10.js.map +0 -35
- /package/dist/cli/{chunk-f2972sbt.js.map → chunk-00gs3wqs.js.map} +0 -0
- /package/dist/cli/{chunk-7ez8ny0t.js.map → chunk-bbnwccaz.js.map} +0 -0
- /package/dist/cli/{chunk-zxccj738.js.map → chunk-esh98wmb.js.map} +0 -0
- /package/dist/cli/{chunk-88cpgt6h.js.map → chunk-fsmrqk8a.js.map} +0 -0
- /package/dist/cli/{chunk-b5aj94ah.js.map → chunk-g698a744.js.map} +0 -0
- /package/dist/cli/{chunk-41za066z.js.map → chunk-h2ez8dzb.js.map} +0 -0
- /package/dist/cli/{chunk-by2290sx.js.map → chunk-h7k3nq3v.js.map} +0 -0
- /package/dist/cli/{chunk-y3e45rc8.js.map → chunk-n1yg3tj3.js.map} +0 -0
- /package/dist/cli/{chunk-f7t03s3g.js.map → chunk-wdrt2k2v.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { UIStrings } from "../../core/i18n-ui.ts";
|
|
3
3
|
import { resolveIcon } from "../../theme/icons.ts";
|
|
4
|
-
import
|
|
4
|
+
import Assistant from "./assistant.tsx";
|
|
5
5
|
|
|
6
6
|
interface Suggestion {
|
|
7
7
|
icon?: string;
|
|
@@ -10,7 +10,7 @@ interface Suggestion {
|
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
12
|
endpoint?: string;
|
|
13
|
-
strings?: UIStrings["
|
|
13
|
+
strings?: UIStrings["assistant"];
|
|
14
14
|
suggestions?: Suggestion[];
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -48,19 +48,19 @@ const icons = {
|
|
|
48
48
|
the draft input, and the open panel — across client-router navigations
|
|
49
49
|
instead of remounting it empty on every page. The island re-portals its
|
|
50
50
|
panel and re-stamps the body push attribute itself on astro:after-swap
|
|
51
|
-
(see
|
|
52
|
-
<
|
|
51
|
+
(see assistant.tsx), since the swap replaces <body> wholesale. */}
|
|
52
|
+
<Assistant
|
|
53
53
|
client:load
|
|
54
54
|
endpoint={endpoint}
|
|
55
55
|
icons={icons}
|
|
56
56
|
strings={strings}
|
|
57
57
|
suggestions={items}
|
|
58
|
-
transition:persist="blume-
|
|
58
|
+
transition:persist="blume-assistant"
|
|
59
59
|
/>
|
|
60
60
|
|
|
61
61
|
<style is:global>
|
|
62
62
|
:root {
|
|
63
|
-
--blume-
|
|
63
|
+
--blume-assistant-width: min(30rem, 100vw);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/* Desktop docks the panel and shrinks the page to fit, like vercel.com/docs.
|
|
@@ -70,8 +70,8 @@ const icons = {
|
|
|
70
70
|
transition: padding-inline-end 0.2s ease-out;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
body[data-blume-
|
|
74
|
-
padding-inline-end: var(--blume-
|
|
73
|
+
body[data-blume-assistant="open"] {
|
|
74
|
+
padding-inline-end: var(--blume-assistant-width);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -81,11 +81,11 @@ const icons = {
|
|
|
81
81
|
changelog index) is single-column, and forcing the sidebar tracks onto it
|
|
82
82
|
would squeeze its only item — <main> — into the 17.5rem track. */
|
|
83
83
|
@media (min-width: 1280px) {
|
|
84
|
-
body[data-blume-
|
|
84
|
+
body[data-blume-assistant="open"] [data-blume-toc] {
|
|
85
85
|
display: none;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
body[data-blume-
|
|
88
|
+
body[data-blume-assistant="open"] [data-blume-doc-grid]:has([data-blume-toc]) {
|
|
89
89
|
grid-template-columns: 17.5rem minmax(0, 1fr);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -7,7 +7,7 @@ import { createPortal } from "react-dom";
|
|
|
7
7
|
import type { UIStrings } from "../../core/i18n-ui.ts";
|
|
8
8
|
import { copyText } from "../copy-feedback.ts";
|
|
9
9
|
import { joinBase, prefixBase } from "./base-path.ts";
|
|
10
|
-
import {
|
|
10
|
+
import { useAssistant } from "./hooks.ts";
|
|
11
11
|
|
|
12
12
|
/** A resolved empty-state prompt; `icon` is ready-to-inline SVG (or null). */
|
|
13
13
|
interface Suggestion {
|
|
@@ -16,10 +16,10 @@ interface Suggestion {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* The panel's chrome glyphs, resolved server-side in `
|
|
19
|
+
* The panel's chrome glyphs, resolved server-side in `Assistant.astro` and passed in
|
|
20
20
|
* as ready-to-inline Lucide bodies so this client island ships no icon data.
|
|
21
21
|
*/
|
|
22
|
-
interface
|
|
22
|
+
interface AssistantIcons {
|
|
23
23
|
arrowUp: string;
|
|
24
24
|
chat: string;
|
|
25
25
|
clear: string;
|
|
@@ -29,7 +29,7 @@ interface AskIcons {
|
|
|
29
29
|
|
|
30
30
|
// Empty bodies so the island still renders (iconless) if instantiated without
|
|
31
31
|
// the Astro wrapper that resolves the real Lucide glyphs.
|
|
32
|
-
const EMPTY_ICONS:
|
|
32
|
+
const EMPTY_ICONS: AssistantIcons = {
|
|
33
33
|
arrowUp: "",
|
|
34
34
|
chat: "",
|
|
35
35
|
clear: "",
|
|
@@ -38,7 +38,7 @@ const EMPTY_ICONS: AskIcons = {
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
// English fallback so the island renders even if no dictionary is passed.
|
|
41
|
-
const DEFAULT_ASK: UIStrings["
|
|
41
|
+
const DEFAULT_ASK: UIStrings["assistant"] = {
|
|
42
42
|
ai: "AI",
|
|
43
43
|
clear: "Clear conversation",
|
|
44
44
|
close: "Close",
|
|
@@ -49,7 +49,7 @@ const DEFAULT_ASK: UIStrings["ask"] = {
|
|
|
49
49
|
placeholder: "Ask a question…",
|
|
50
50
|
send: "Send",
|
|
51
51
|
tip: "Tip: You can open and close chat with",
|
|
52
|
-
title: "
|
|
52
|
+
title: "Assistant",
|
|
53
53
|
you: "You",
|
|
54
54
|
};
|
|
55
55
|
|
|
@@ -77,9 +77,18 @@ const markdown = new Marked({
|
|
|
77
77
|
const renderMarkdown = (content: string): string =>
|
|
78
78
|
DOMPurify.sanitize(markdown.parse(content, { async: false }));
|
|
79
79
|
|
|
80
|
-
const Glyph = ({
|
|
80
|
+
const Glyph = ({
|
|
81
|
+
className,
|
|
82
|
+
path,
|
|
83
|
+
size = 16,
|
|
84
|
+
}: {
|
|
85
|
+
className?: string;
|
|
86
|
+
path: string;
|
|
87
|
+
size?: number;
|
|
88
|
+
}) => (
|
|
81
89
|
<svg
|
|
82
90
|
aria-hidden="true"
|
|
91
|
+
className={className}
|
|
83
92
|
// `path` is a trusted, server-resolved Lucide glyph body (inline SVG),
|
|
84
93
|
// not user content; it must be injected as markup to render the icon.
|
|
85
94
|
// oxlint-disable-next-line react/no-danger -- trusted server-resolved inline SVG glyph
|
|
@@ -130,15 +139,15 @@ const ICON_BUTTON_CLASS =
|
|
|
130
139
|
const ANSWER_CLASS =
|
|
131
140
|
"prose prose-sm max-w-none text-foreground [&_a]:inline-flex [&_a]:items-center [&_a]:gap-1 [&_a]:rounded-full [&_a]:bg-muted [&_a]:px-2 [&_a]:py-1 [&_a]:align-middle [&_a]:font-medium [&_a]:text-[0.7rem] [&_a]:leading-none [&_a]:text-muted-foreground! [&_a]:no-underline! [&_a:hover]:text-foreground!";
|
|
132
141
|
|
|
133
|
-
const
|
|
142
|
+
const Assistant = ({
|
|
134
143
|
endpoint = DEFAULT_ASK_ENDPOINT,
|
|
135
144
|
icons = EMPTY_ICONS,
|
|
136
145
|
strings,
|
|
137
146
|
suggestions = EMPTY_SUGGESTIONS,
|
|
138
147
|
}: {
|
|
139
148
|
endpoint?: string;
|
|
140
|
-
icons?:
|
|
141
|
-
strings?: UIStrings["
|
|
149
|
+
icons?: AssistantIcons;
|
|
150
|
+
strings?: UIStrings["assistant"];
|
|
142
151
|
suggestions?: Suggestion[];
|
|
143
152
|
}) => {
|
|
144
153
|
// Merge per key (not `strings ?? …`) so a dictionary from a stale snapshot
|
|
@@ -147,14 +156,14 @@ const AskAI = ({
|
|
|
147
156
|
const [open, setOpen] = useState(false);
|
|
148
157
|
const [input, setInput] = useState("");
|
|
149
158
|
// The streaming client — request shaping, optimistic assistant bubble,
|
|
150
|
-
// stale-stream/abort guards, error-body handling — is the public
|
|
159
|
+
// stale-stream/abort guards, error-body handling — is the public useAssistant
|
|
151
160
|
// hook, so the built-in panel and custom UIs share one implementation.
|
|
152
161
|
const {
|
|
153
162
|
ask,
|
|
154
163
|
loading: busy,
|
|
155
164
|
messages,
|
|
156
165
|
reset,
|
|
157
|
-
} =
|
|
166
|
+
} = useAssistant({ endpoint, errorMessage: t.error });
|
|
158
167
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
159
168
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
160
169
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
@@ -167,7 +176,7 @@ const AskAI = ({
|
|
|
167
176
|
// Null until mount (guards SSR), then refreshed on every client-router swap:
|
|
168
177
|
// the island rides across navigations via transition:persist, but each swap
|
|
169
178
|
// installs a NEW <body>, discarding the portaled panel with the old one and
|
|
170
|
-
// resetting the `data-blume-
|
|
179
|
+
// resetting the `data-blume-assistant` push attribute to the incoming page's
|
|
171
180
|
// server-rendered set. Reading document.body inline in render would NOT
|
|
172
181
|
// recover from that — it isn't a reactive value, so the memoized portal
|
|
173
182
|
// keeps its stale (detached) container. State identity is what re-anchors
|
|
@@ -183,10 +192,10 @@ const AskAI = ({
|
|
|
183
192
|
return () => document.removeEventListener("astro:after-swap", onSwap);
|
|
184
193
|
}, []);
|
|
185
194
|
|
|
186
|
-
// The search modal forwards its query so "
|
|
195
|
+
// The search modal forwards its query so "Assistant: <query>" carries straight in.
|
|
187
196
|
useEffect(() => {
|
|
188
197
|
const handler = (event: Event) => {
|
|
189
|
-
// SAFETY: `blume:open-
|
|
198
|
+
// SAFETY: `blume:open-assistant` is only ever dispatched as a CustomEvent
|
|
190
199
|
// whose optional detail carries the search query.
|
|
191
200
|
const query = (event as CustomEvent<{ query?: string }>).detail?.query;
|
|
192
201
|
if (query) {
|
|
@@ -194,8 +203,8 @@ const AskAI = ({
|
|
|
194
203
|
}
|
|
195
204
|
setOpen(true);
|
|
196
205
|
};
|
|
197
|
-
window.addEventListener("blume:open-
|
|
198
|
-
return () => window.removeEventListener("blume:open-
|
|
206
|
+
window.addEventListener("blume:open-assistant", handler);
|
|
207
|
+
return () => window.removeEventListener("blume:open-assistant", handler);
|
|
199
208
|
}, []);
|
|
200
209
|
|
|
201
210
|
// ⌘I / Ctrl+I toggles the panel; Escape closes it. Shift/Alt chords are
|
|
@@ -226,17 +235,17 @@ const AskAI = ({
|
|
|
226
235
|
return () => window.removeEventListener("keydown", onKey);
|
|
227
236
|
}, [open]);
|
|
228
237
|
|
|
229
|
-
// Drive the desktop content push from a body attribute (see
|
|
238
|
+
// Drive the desktop content push from a body attribute (see Assistant.astro CSS).
|
|
230
239
|
useEffect(() => {
|
|
231
240
|
if (open) {
|
|
232
241
|
returnFocusRef.current =
|
|
233
242
|
document.activeElement instanceof HTMLElement
|
|
234
243
|
? document.activeElement
|
|
235
244
|
: null;
|
|
236
|
-
document.body.dataset.
|
|
245
|
+
document.body.dataset.blumeAssistant = "open";
|
|
237
246
|
inputRef.current?.focus();
|
|
238
247
|
} else {
|
|
239
|
-
delete document.body.dataset.
|
|
248
|
+
delete document.body.dataset.blumeAssistant;
|
|
240
249
|
// Return focus to the element that opened the panel (or the trigger when
|
|
241
250
|
// it's gone), so closing doesn't strand keyboard focus in an inert tree.
|
|
242
251
|
// `returnFocusRef` is only set on open, so initial mount is a no-op.
|
|
@@ -249,7 +258,7 @@ const AskAI = ({
|
|
|
249
258
|
}
|
|
250
259
|
}
|
|
251
260
|
return () => {
|
|
252
|
-
delete document.body.dataset.
|
|
261
|
+
delete document.body.dataset.blumeAssistant;
|
|
253
262
|
};
|
|
254
263
|
}, [open]);
|
|
255
264
|
|
|
@@ -261,7 +270,7 @@ const AskAI = ({
|
|
|
261
270
|
// document.body (not portalTarget) so the compiler doesn't flag a state
|
|
262
271
|
// mutation; by the time this runs for a swap, they are the same element.
|
|
263
272
|
if (open && portalTarget) {
|
|
264
|
-
document.body.dataset.
|
|
273
|
+
document.body.dataset.blumeAssistant = "open";
|
|
265
274
|
}
|
|
266
275
|
// oxlint-disable-next-line react/exhaustive-effect-dependencies -- see above
|
|
267
276
|
}, [open, portalTarget]);
|
|
@@ -383,11 +392,14 @@ const AskAI = ({
|
|
|
383
392
|
<aside
|
|
384
393
|
aria-hidden={open ? undefined : "true"}
|
|
385
394
|
aria-label={t.title}
|
|
395
|
+
// Print hides the chrome by this marker, not by tag: content renders
|
|
396
|
+
// <aside> too (Callout, Panel), and that must print.
|
|
397
|
+
data-blume-assistant-panel=""
|
|
386
398
|
ref={panelRef}
|
|
387
399
|
// The closed panel is only translated off-screen; `inert` drops its
|
|
388
400
|
// buttons/textarea from the tab order and the accessibility tree.
|
|
389
401
|
inert={!open}
|
|
390
|
-
className={`border-border bg-background fixed inset-y-0 end-0 z-[60] flex w-[var(--blume-
|
|
402
|
+
className={`border-border bg-background fixed inset-y-0 end-0 z-[60] flex w-[var(--blume-assistant-width)] flex-col border-s shadow-2xl transition-transform duration-200 ease-out ${
|
|
391
403
|
open ? "translate-x-0" : "translate-x-full rtl:-translate-x-full"
|
|
392
404
|
}`}
|
|
393
405
|
>
|
|
@@ -418,7 +430,9 @@ const AskAI = ({
|
|
|
418
430
|
onClick={() => setOpen(false)}
|
|
419
431
|
type="button"
|
|
420
432
|
>
|
|
421
|
-
|
|
433
|
+
{/* Chevrons toward the inline end, where the panel docks: they
|
|
434
|
+
mirror with the panel in a right-to-left locale. */}
|
|
435
|
+
<Glyph className="rtl:-scale-x-100" path={icons.close} size={18} />
|
|
422
436
|
</button>
|
|
423
437
|
</div>
|
|
424
438
|
</header>
|
|
@@ -539,4 +553,4 @@ const AskAI = ({
|
|
|
539
553
|
);
|
|
540
554
|
};
|
|
541
555
|
|
|
542
|
-
export default
|
|
556
|
+
export default Assistant;
|
|
@@ -12,7 +12,7 @@ import { joinBase, stripBase } from "./base-path.ts";
|
|
|
12
12
|
* project data can't come through context. Instead the layout serializes a small
|
|
13
13
|
* snapshot into a `<script type="application/json" id="blume-client-data">` tag,
|
|
14
14
|
* and {@link useBlume}/{@link usePage} read it after mount. {@link useSearch} and
|
|
15
|
-
* {@link
|
|
15
|
+
* {@link useAssistant} wrap the generated search client and the assistant endpoint.
|
|
16
16
|
*
|
|
17
17
|
* Import them from `blume/hooks`:
|
|
18
18
|
*
|
|
@@ -131,14 +131,14 @@ export const useSearch = (): UseSearch => {
|
|
|
131
131
|
return { loading, results, search };
|
|
132
132
|
};
|
|
133
133
|
|
|
134
|
-
/** A single
|
|
134
|
+
/** A single assistant chat message. */
|
|
135
135
|
export interface AskMessage {
|
|
136
136
|
content: string;
|
|
137
137
|
role: "assistant" | "user";
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
/** State + actions returned by {@link
|
|
141
|
-
export interface
|
|
140
|
+
/** State + actions returned by {@link useAssistant}. */
|
|
141
|
+
export interface UseAssistant {
|
|
142
142
|
ask: (question: string) => Promise<void>;
|
|
143
143
|
loading: boolean;
|
|
144
144
|
messages: AskMessage[];
|
|
@@ -147,8 +147,8 @@ export interface UseAskAI {
|
|
|
147
147
|
|
|
148
148
|
const DEFAULT_ASK_ENDPOINT = joinBase(import.meta.env.BASE_URL, "api/ask");
|
|
149
149
|
|
|
150
|
-
export interface
|
|
151
|
-
/** Existing
|
|
150
|
+
export interface UseAssistantOptions {
|
|
151
|
+
/** Existing assistant endpoint; defaults to Blume's generated `/api/ask`. */
|
|
152
152
|
endpoint?: string;
|
|
153
153
|
/**
|
|
154
154
|
* Shown as the assistant's answer when the request fails or throws.
|
|
@@ -161,11 +161,11 @@ export interface UseAskAIOptions {
|
|
|
161
161
|
/** Shown as the assistant's answer when the request fails or throws. */
|
|
162
162
|
const ASK_ERROR = "Something went wrong answering that. Please try again.";
|
|
163
163
|
|
|
164
|
-
/** How the generated
|
|
165
|
-
const NOT_CONFIGURED = /^
|
|
164
|
+
/** How the assistant's generated route opens its missing-credential notice. */
|
|
165
|
+
const NOT_CONFIGURED = /^The assistant is not configured/u;
|
|
166
166
|
|
|
167
167
|
/**
|
|
168
|
-
* The route's own explanation of a 503 — the generated route's "
|
|
168
|
+
* The route's own explanation of a 503 — the generated route's "The assistant is not
|
|
169
169
|
* configured: set AI_GATEWAY_API_KEY." — when the response is that notice, or
|
|
170
170
|
* null. It names only the variable to set, never a value, so it's safe to
|
|
171
171
|
* show, and it tells a site owner trying a fresh deploy what's missing instead
|
|
@@ -188,10 +188,12 @@ const currentPath = (): string =>
|
|
|
188
188
|
stripBase(import.meta.env.BASE_URL, window.location.pathname);
|
|
189
189
|
|
|
190
190
|
/**
|
|
191
|
-
* Stream answers from the
|
|
191
|
+
* Stream answers from the assistant endpoint. Mirrors the built-in assistant island so
|
|
192
192
|
* a custom chat UI shares the same grounded, page-aware backend.
|
|
193
193
|
*/
|
|
194
|
-
export const
|
|
194
|
+
export const useAssistant = (
|
|
195
|
+
options: UseAssistantOptions = {}
|
|
196
|
+
): UseAssistant => {
|
|
195
197
|
const endpoint = options.endpoint ?? DEFAULT_ASK_ENDPOINT;
|
|
196
198
|
const errorMessage = options.errorMessage ?? ASK_ERROR;
|
|
197
199
|
const [messages, setMessages] = useState<AskMessage[]>([]);
|
|
@@ -208,7 +210,7 @@ export const useAskAI = (options: UseAskAIOptions = {}): UseAskAI => {
|
|
|
208
210
|
// preserves a stable `ask` identity for consumers that depend on it. With the
|
|
209
211
|
// compiler on it's redundant but inert.
|
|
210
212
|
// oxlint-disable-next-line react-doctor/react-compiler-no-manual-memoization -- see above
|
|
211
|
-
const ask = useCallback<
|
|
213
|
+
const ask = useCallback<UseAssistant["ask"]>(
|
|
212
214
|
async (question) => {
|
|
213
215
|
const trimmed = question.trim();
|
|
214
216
|
if (!trimmed || loading) {
|
|
@@ -306,12 +308,17 @@ export const useAskAI = (options: UseAskAIOptions = {}): UseAskAI => {
|
|
|
306
308
|
}
|
|
307
309
|
}
|
|
308
310
|
if (live()) {
|
|
309
|
-
// A 200 with nothing in it (no body, an empty stream)
|
|
310
|
-
//
|
|
311
|
+
// A 200 with nothing in it (no body, an empty stream) is not an
|
|
312
|
+
// answer. It is also how a provider failure after the 200 arrives
|
|
313
|
+
// (a bad key, a rate limit, an unknown model): the route's text
|
|
314
|
+
// stream carries only text, so the error ends it empty. Show the
|
|
315
|
+
// error notice instead of leaving a pulsing blank bubble.
|
|
311
316
|
if (assistant.content) {
|
|
312
317
|
outcome("ask_answer", { chars: assistant.content.length });
|
|
313
318
|
} else {
|
|
314
319
|
outcome("ask_error", { status });
|
|
320
|
+
assistant.content = errorMessage;
|
|
321
|
+
setMessages([...history, { ...assistant }]);
|
|
315
322
|
}
|
|
316
323
|
}
|
|
317
324
|
} catch {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { unescape } from "html-escaper";
|
|
2
|
+
|
|
1
3
|
import type { SearchFn } from "../layout/search/types.ts";
|
|
2
4
|
import { joinBase, prefixBase } from "./base-path.ts";
|
|
3
5
|
|
|
@@ -80,13 +82,15 @@ const isCallable = <T extends (...args: never[]) => void>(
|
|
|
80
82
|
const TAG = /<[^>]*>?/gu;
|
|
81
83
|
|
|
82
84
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
85
|
+
* Search-hit HTML as the plain text an agent reads: the `<mark>`
|
|
86
|
+
* highlighting (and any other markup) dropped, then the entities the
|
|
87
|
+
* highlighter escaped decoded (`<T> &` → `<T> &`). The closing `>`
|
|
88
|
+
* is optional so every `<` starts a strip: a dangling `<script` fragment
|
|
89
|
+
* can't survive the way it would if a full `<...>` pair were required. The
|
|
90
|
+
* input is HTML, where a literal `<` is `<`, so consuming from every raw
|
|
91
|
+
* `<` loses nothing legitimate. Stripping repeats to a fixed point so the
|
|
92
|
+
* no-fragment guarantee is explicit rather than an artifact of the regex
|
|
93
|
+
* shape; decoding comes last, so a decoded `<` is text, never a tag.
|
|
90
94
|
*/
|
|
91
95
|
const plain = (html: string): string => {
|
|
92
96
|
let stripped = html;
|
|
@@ -95,7 +99,7 @@ const plain = (html: string): string => {
|
|
|
95
99
|
previous = stripped;
|
|
96
100
|
stripped = stripped.replaceAll(TAG, "");
|
|
97
101
|
} while (stripped !== previous);
|
|
98
|
-
return stripped;
|
|
102
|
+
return unescape(stripped);
|
|
99
103
|
};
|
|
100
104
|
|
|
101
105
|
export interface WebMcpToolOptions {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import data from "blume:data";
|
|
2
3
|
import { EN_UI } from "../../core/i18n-ui.ts";
|
|
3
4
|
import type { UIStrings } from "../../core/i18n-ui.ts";
|
|
4
5
|
import { withBase } from "../islands/base-path.ts";
|
|
@@ -18,13 +19,31 @@ interface Props {
|
|
|
18
19
|
| null;
|
|
19
20
|
/** Localized banner labels. */
|
|
20
21
|
strings?: UIStrings["banner"];
|
|
22
|
+
/**
|
|
23
|
+
* Active locale of the page. The link follows it the way the sidebar's
|
|
24
|
+
* featured links do (`/guide` -> `/fr/guide` when French serves it).
|
|
25
|
+
* Absent, the link resolves for the default locale.
|
|
26
|
+
*/
|
|
27
|
+
locale?: string;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
const { banner, strings } = Astro.props;
|
|
30
|
+
const { banner, strings, locale } = Astro.props;
|
|
24
31
|
// Merge over the English baseline per key (rather than `strings ?? …`) so a
|
|
25
32
|
// partial — or missing — strings object still resolves every label to a
|
|
26
33
|
// default, matching the pattern PageFeedback and Search use.
|
|
27
34
|
const b = { ...EN_UI.banner, ...strings };
|
|
35
|
+
// The configured `banner.link.href` is authored as if the site were mounted at
|
|
36
|
+
// root, in the default locale's path space; the locale's navigation carries it
|
|
37
|
+
// localized and mounted under `basePath` (the default navigation on a
|
|
38
|
+
// single-locale site). A banner a custom page passes with a link of its own
|
|
39
|
+
// renders that link as given.
|
|
40
|
+
const navigation =
|
|
41
|
+
(locale === undefined ? undefined : data.navigationByLocale[locale]) ??
|
|
42
|
+
data.navigation;
|
|
43
|
+
const linkHref =
|
|
44
|
+
banner?.link && banner.link.href === data.config.banner?.link?.href
|
|
45
|
+
? (navigation.bannerHref ?? banner.link.href)
|
|
46
|
+
: banner?.link?.href;
|
|
28
47
|
---
|
|
29
48
|
|
|
30
49
|
{
|
|
@@ -37,13 +56,13 @@ const b = { ...EN_UI.banner, ...strings };
|
|
|
37
56
|
{/* The message is authored in one language, whatever the page's
|
|
38
57
|
direction: `dir="auto"` keeps its punctuation where it was typed. */}
|
|
39
58
|
<span dir="auto">{banner.content}</span>
|
|
40
|
-
{banner.link && (
|
|
59
|
+
{banner.link && linkHref && (
|
|
41
60
|
<a
|
|
42
61
|
class="inline-flex items-center gap-1 font-medium underline underline-offset-2"
|
|
43
|
-
href={withBase(
|
|
62
|
+
href={withBase(linkHref)}
|
|
44
63
|
>
|
|
45
64
|
{banner.link.text}
|
|
46
|
-
<Icon name="arrow-right" size={14} />
|
|
65
|
+
<Icon class="rtl:-scale-x-100" name="arrow-right" size={14} />
|
|
47
66
|
</a>
|
|
48
67
|
)}
|
|
49
68
|
{banner.dismissible && (
|
|
@@ -17,6 +17,7 @@ import LanguageSwitcher from "./LanguageSwitcher.astro";
|
|
|
17
17
|
import Logo from "./Logo.astro";
|
|
18
18
|
import { currentTabForRoute } from "./nav-utils.ts";
|
|
19
19
|
import NavSelector from "./NavSelector.astro";
|
|
20
|
+
import NavTabMenu from "./NavTabMenu.astro";
|
|
20
21
|
import { resolveSlot } from "./overrides.ts";
|
|
21
22
|
import Search from "./Search.astro";
|
|
22
23
|
|
|
@@ -39,13 +40,13 @@ interface Props {
|
|
|
39
40
|
*/
|
|
40
41
|
locale?: string;
|
|
41
42
|
/**
|
|
42
|
-
* Whether the search modal offers
|
|
43
|
-
*
|
|
43
|
+
* Whether the search modal offers a hand-off to the assistant. Defaults to whether
|
|
44
|
+
* the assistant is configured, so no page has to pass it; a layout can still opt a
|
|
44
45
|
* shell out explicitly.
|
|
45
46
|
*/
|
|
46
|
-
|
|
47
|
-
/** Localized
|
|
48
|
-
|
|
47
|
+
assistantEnabled?: boolean;
|
|
48
|
+
/** Localized assistant strings for the active locale. */
|
|
49
|
+
assistantStrings?: UIStrings["assistant"];
|
|
49
50
|
// The mobile menu button toggles the docs sidebar drawer; custom pages
|
|
50
51
|
// without a sidebar (e.g. a landing page) pass `false` to hide it.
|
|
51
52
|
hasSidebar?: boolean;
|
|
@@ -91,11 +92,11 @@ const {
|
|
|
91
92
|
route,
|
|
92
93
|
searchEnabled,
|
|
93
94
|
locale,
|
|
94
|
-
// `config.
|
|
95
|
+
// `config.assistant` is null whenever the assistant is off, so the header is the one place
|
|
95
96
|
// that has to know — the Ask trigger below and the search modal's hand-off to
|
|
96
97
|
// it both switch on this, and every page gets both for free.
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
assistantEnabled = Boolean(data.config.assistant),
|
|
99
|
+
assistantStrings,
|
|
99
100
|
hasSidebar = true,
|
|
100
101
|
hasDrawer = true,
|
|
101
102
|
searchStrings,
|
|
@@ -216,15 +217,27 @@ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-b
|
|
|
216
217
|
{
|
|
217
218
|
navigation.tabs.length > 0 && (
|
|
218
219
|
<nav aria-label={n.sections} class={tabsNavClass}>
|
|
219
|
-
{navigation.tabs.map((tab) =>
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
220
|
+
{navigation.tabs.map((tab) =>
|
|
221
|
+
tab.items && tab.items.length > 0 ? (
|
|
222
|
+
<NavTabMenu
|
|
223
|
+
active={tab === activeTab}
|
|
224
|
+
route={route}
|
|
225
|
+
tab={tab}
|
|
226
|
+
variant="bar"
|
|
227
|
+
/>
|
|
228
|
+
) : (
|
|
229
|
+
<a
|
|
230
|
+
aria-current={tab === activeTab ? "page" : undefined}
|
|
231
|
+
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 font-medium text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:text-foreground"
|
|
232
|
+
href={withBase(tab.href ?? tab.path)}
|
|
233
|
+
>
|
|
234
|
+
{tab.icon && (
|
|
235
|
+
<Icon class="shrink-0" name={tab.icon} size={14} />
|
|
236
|
+
)}
|
|
237
|
+
{tab.label}
|
|
238
|
+
</a>
|
|
239
|
+
)
|
|
240
|
+
)}
|
|
228
241
|
</nav>
|
|
229
242
|
)
|
|
230
243
|
}
|
|
@@ -250,7 +263,7 @@ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-b
|
|
|
250
263
|
{
|
|
251
264
|
searchEnabled && (
|
|
252
265
|
<SearchSlot
|
|
253
|
-
|
|
266
|
+
assistantEnabled={assistantEnabled}
|
|
254
267
|
locale={searchLocale}
|
|
255
268
|
navigation={navigation}
|
|
256
269
|
popularPages={data.config.search.popular}
|
|
@@ -314,7 +327,7 @@ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-b
|
|
|
314
327
|
<span class="inline-flex dark:hidden"><Icon name="sun" size={18} /></span>
|
|
315
328
|
<span class="hidden dark:inline-flex"><Icon name="moon" size={18} /></span>
|
|
316
329
|
</button>
|
|
317
|
-
{
|
|
330
|
+
{assistantEnabled && <Ask strings={assistantStrings} />}
|
|
318
331
|
</div>
|
|
319
332
|
</header>
|
|
320
333
|
<script is:inline set:html={clickScript} />
|
|
@@ -47,9 +47,14 @@ const brandHref =
|
|
|
47
47
|
// it's omitted. An explicit `""` hides it (an image-only logo whose mark already
|
|
48
48
|
// carries the wordmark).
|
|
49
49
|
const brandText = logo?.text ?? site.title;
|
|
50
|
+
// Without a wordmark the link's only content is the mark, and an inline SVG
|
|
51
|
+
// mark is decorative (`aria-hidden`), so the link would have no accessible
|
|
52
|
+
// name. Name it after the image's `alt`, else the site title.
|
|
53
|
+
const linkLabel = brandText ? undefined : logoAlt || site.title;
|
|
50
54
|
---
|
|
51
55
|
|
|
52
56
|
<a
|
|
57
|
+
aria-label={linkLabel}
|
|
53
58
|
class="inline-flex min-w-0 items-center gap-2 font-semibold text-base text-foreground"
|
|
54
59
|
href={withBase(brandHref)}
|
|
55
60
|
>
|
|
@@ -38,6 +38,11 @@ const active =
|
|
|
38
38
|
// it switches. Longer product names still collapse to the icon there.
|
|
39
39
|
const label = active?.label ?? selector.label;
|
|
40
40
|
const labelClass = label.length <= 6 ? undefined : "max-sm:hidden";
|
|
41
|
+
// The accessible name carries the visible active label too (WCAG 2.5.3,
|
|
42
|
+
// label in name), so a voice-control user can say "v2" to open it — the
|
|
43
|
+
// language switcher's pattern.
|
|
44
|
+
const summaryLabel =
|
|
45
|
+
label === selector.label ? selector.label : `${selector.label}: ${label}`;
|
|
41
46
|
|
|
42
47
|
const iconButton =
|
|
43
48
|
"inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-full border border-border bg-background px-3 text-muted-foreground text-sm transition-colors hover:border-foreground hover:text-foreground";
|
|
@@ -49,7 +54,7 @@ const menuRowClass =
|
|
|
49
54
|
selector.items.length > 0 && (
|
|
50
55
|
<details class="group relative" data-blume-dropdown>
|
|
51
56
|
<summary
|
|
52
|
-
aria-label={
|
|
57
|
+
aria-label={summaryLabel}
|
|
53
58
|
class={`${iconButton} list-none [&::-webkit-details-marker]:hidden`}
|
|
54
59
|
>
|
|
55
60
|
{active?.icon && <Icon name={active.icon} size={16} />}
|