blume 1.5.0 → 1.5.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/CHANGELOG.md +32 -0
- package/README.md +16 -12
- package/dist/cli/index.js +449 -135
- package/dist/cli/index.js.map +24 -23
- package/dist/types/ai/ask-context.d.ts +78 -0
- package/dist/types/core/config-input.d.ts +54 -2
- package/dist/types/core/data.d.ts +19 -2
- package/dist/types/core/open-in-chat.d.ts +9 -0
- package/dist/types/core/schema.d.ts +48 -1
- package/dist/types/core/types.d.ts +10 -3
- package/dist/types/openapi/references.d.ts +9 -0
- package/dist/types/search/orama-index.d.ts +70 -0
- package/dist/types/theme/fonts.d.ts +11 -2
- package/docs/advanced/api-reference.mdx +67 -5
- package/docs/advanced/custom-pages.mdx +5 -1
- package/docs/configuration/ai.mdx +35 -0
- package/docs/configuration/index.mdx +14 -2
- package/docs/configuration/search.mdx +4 -4
- package/docs/configuration/theming.mdx +4 -2
- package/docs/reference/cli.mdx +2 -2
- package/package.json +1 -1
- package/skills/blume-migrate/SKILL.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +1 -1
- package/src/ai/ask-context.ts +51 -11
- package/src/ai/mcp/data.ts +3 -2
- package/src/ai/mcp/server.ts +3 -2
- package/src/assets/icon-dark.png +0 -0
- package/src/astro/generate.ts +172 -18
- package/src/astro/templates.ts +89 -15
- package/src/components/content/AccordionItem.astro +4 -0
- package/src/components/content/Update.astro +3 -0
- package/src/components/islands/AskAI.astro +6 -0
- package/src/components/islands/ask-ai.tsx +39 -9
- package/src/components/layout/Analytics.astro +9 -1
- package/src/components/layout/Favicon.astro +29 -8
- package/src/components/layout/Fonts.astro +23 -3
- package/src/components/layout/Header.astro +2 -2
- package/src/components/layout/NavSelector.astro +1 -1
- package/src/components/layout/PageActions.astro +120 -78
- package/src/components/layout/PageFeedback.astro +12 -3
- package/src/components/layout/PageLayout.astro +79 -5
- package/src/components/layout/ReferenceLayout.astro +12 -9
- package/src/components/layout/RootLayout.astro +153 -121
- package/src/components/layout/Search.astro +41 -26
- package/src/components/layout/drawer-inert.ts +10 -5
- package/src/components/layout/head-scripts.ts +34 -16
- package/src/components/layout/nav-utils.ts +34 -15
- package/src/components/layout/search/orama.ts +3 -2
- package/src/components/openapi/AsyncApiOperation.astro +22 -7
- package/src/components/openapi/MessageComposer.astro +238 -0
- package/src/components/openapi/Operation.astro +26 -12
- package/src/components/openapi/PanelTabs.astro +7 -0
- package/src/components/openapi/Playground.astro +320 -0
- package/src/components/openapi/RequestPanel.astro +1 -0
- package/src/components/openapi/async-snippets.ts +20 -7
- package/src/components/openapi/async.ts +13 -2
- package/src/components/openapi/message-composer.ts +242 -0
- package/src/components/openapi/message-model.ts +108 -0
- package/src/components/openapi/message.ts +153 -0
- package/src/components/openapi/operation-model.ts +260 -0
- package/src/components/openapi/playground-client.ts +486 -0
- package/src/components/openapi/playground-schema.ts +109 -0
- package/src/components/openapi/request.ts +287 -0
- package/src/components/openapi/security.ts +0 -56
- package/src/components/openapi/snippets.ts +23 -136
- package/src/components/openapi/validate-json.ts +144 -0
- package/src/components/openapi/ws-client.ts +194 -0
- package/src/core/config-input.ts +67 -1
- package/src/core/content-assets.ts +66 -15
- package/src/core/data.ts +16 -2
- package/src/core/last-modified.ts +76 -2
- package/src/core/links.ts +30 -4
- package/src/core/navigation.ts +26 -1
- package/src/core/open-in-chat.ts +17 -0
- package/src/core/project-graph.ts +11 -0
- package/src/core/schema.ts +60 -1
- package/src/core/server-features.ts +11 -0
- package/src/core/sources/normalize.ts +10 -2
- package/src/core/types.ts +10 -3
- package/src/deploy/vercel-negotiation.ts +34 -14
- package/src/og/card.ts +3 -1
- package/src/openapi/model.ts +7 -0
- package/src/openapi/proxy.ts +217 -0
- package/src/openapi/references.ts +8 -0
- package/src/openapi/source.ts +13 -0
- package/src/registry/eject.ts +4 -5
- package/src/search/orama-index.ts +109 -36
- package/src/theme/entry.ts +15 -2
- package/src/theme/fonts.ts +75 -3
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { OramaDoc } from "../search/orama-index.ts";
|
|
2
|
+
/** A chat message as posted by the Ask AI island (`{ role, content }`). */
|
|
3
|
+
export interface AskMessage {
|
|
4
|
+
content: string;
|
|
5
|
+
role: string;
|
|
6
|
+
}
|
|
7
|
+
/** The current-page hint the island forwards so the endpoint can prioritize it. */
|
|
8
|
+
export interface AskPage {
|
|
9
|
+
path?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The self-contained snapshot the grounded Ask AI endpoint imports. Bundles the
|
|
13
|
+
* search documents so retrieval works regardless of the configured search
|
|
14
|
+
* provider and needs no filesystem access at request time. Serialized to
|
|
15
|
+
* `generated/ask-data.json` and built by {@link buildAskData}.
|
|
16
|
+
*/
|
|
17
|
+
export interface AskData {
|
|
18
|
+
/**
|
|
19
|
+
* The site's `i18n.defaultLocale`, when i18n is configured. Selects a
|
|
20
|
+
* word-segmenting Orama tokenizer for every non-Latin script, so retrieval
|
|
21
|
+
* can match CJK, Cyrillic, Greek, Hebrew, or Devanagari content.
|
|
22
|
+
*/
|
|
23
|
+
defaultLocale?: string;
|
|
24
|
+
documents: OramaDoc[];
|
|
25
|
+
site: string | null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* How much retrieved documentation a question carries (the `ai.ask.retrieval`
|
|
29
|
+
* config). Every field falls back to the built-in default, so a partial object
|
|
30
|
+
* only changes what it names. Injected characters dominate time-to-first-token
|
|
31
|
+
* on a self-hosted backend, and the three knobs aren't interchangeable: the
|
|
32
|
+
* budget caps the total, `excerptChars` decides how deep into one long page the
|
|
33
|
+
* excerpt reaches, and `maxResults` decides how many pages retrieval adds (the
|
|
34
|
+
* page the reader is viewing is injected on top of them).
|
|
35
|
+
*/
|
|
36
|
+
export interface AskRetrievalOptions {
|
|
37
|
+
/** Overall cap on injected documentation characters. Defaults to `10000`. */
|
|
38
|
+
contextBudget?: number;
|
|
39
|
+
/** Characters kept per injected excerpt. Defaults to `2000`. */
|
|
40
|
+
excerptChars?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Documents retrieved per question. Defaults to `6`. The current page is
|
|
43
|
+
* injected in addition when it isn't among the hits.
|
|
44
|
+
*/
|
|
45
|
+
maxResults?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Excerpt the region of `content` most relevant to `query`, not just its head.
|
|
49
|
+
*
|
|
50
|
+
* Pages are indexed whole (one document each), so a naive head slice of a long
|
|
51
|
+
* page returns its intro and misses sections below the fold — the exact failure
|
|
52
|
+
* where "How does Ask AI work?" retrieves the right page but only sees its
|
|
53
|
+
* opening paragraph. This centers the window on the densest cluster of query
|
|
54
|
+
* terms so the injected text is the part that actually answers the question.
|
|
55
|
+
* Exported for testing; {@link createAskContext} is the runtime entry point.
|
|
56
|
+
*/
|
|
57
|
+
export declare const relevantExcerpt: (content: string, query: string, max: number) => string;
|
|
58
|
+
/**
|
|
59
|
+
* Build the request-time grounding function for the Ask AI endpoint.
|
|
60
|
+
*
|
|
61
|
+
* Lexical retrieval over Orama (the same index/ranking the search dialog and MCP
|
|
62
|
+
* server use). The index is built once and memoized across requests. Returns a
|
|
63
|
+
* grounded system prompt — the retrieved excerpts plus the page the user is
|
|
64
|
+
* viewing — or `undefined` when there is nothing to ground on, so the endpoint
|
|
65
|
+
* can fall back to its plain prompt.
|
|
66
|
+
*
|
|
67
|
+
* `options.instructions` (the `ai.ask.instructions` config) is appended after
|
|
68
|
+
* the base instruction rather than replacing it: the base carries the
|
|
69
|
+
* functional contract (answer only from the excerpts, cite pages as Markdown
|
|
70
|
+
* links) that the panel's citation rendering depends on.
|
|
71
|
+
*
|
|
72
|
+
* `options.retrieval` (the `ai.ask.retrieval` config) sizes how much
|
|
73
|
+
* documentation each question carries; omitted fields keep today's defaults.
|
|
74
|
+
*/
|
|
75
|
+
export declare const createAskContext: (data: AskData, options?: {
|
|
76
|
+
instructions?: string;
|
|
77
|
+
retrieval?: AskRetrievalOptions;
|
|
78
|
+
}) => ((messages: AskMessage[], page?: AskPage) => Promise<string | undefined>);
|
|
@@ -2,7 +2,7 @@ import type { AstroIntegration } from "astro";
|
|
|
2
2
|
import type { ComponentMarkdown } from "../ai/component-markdown.ts";
|
|
3
3
|
import type { CodeTheme } from "../markdown/themes.ts";
|
|
4
4
|
import type { FontSlug } from "../theme/fonts.ts";
|
|
5
|
-
import type { OpenApiSource, SearchProvider, SidebarDisplay, SidebarItemConfig } from "./schema.ts";
|
|
5
|
+
import type { OpenApiSource, OpenInChatProvider, SearchProvider, SidebarDisplay, SidebarItemConfig } from "./schema.ts";
|
|
6
6
|
import type { ContentSource } from "./sources/types.ts";
|
|
7
7
|
import type { StandardSchema } from "./standard-schema.ts";
|
|
8
8
|
/**
|
|
@@ -438,7 +438,7 @@ export type FontInput = LiteralUnion<FontSlug> | RemoteFontInput | LocalFontInpu
|
|
|
438
438
|
export interface FontsConfig {
|
|
439
439
|
/** Body / prose font. Defaults to `inter`. */
|
|
440
440
|
body?: FontInput;
|
|
441
|
-
/** Display / heading font. Defaults to `inter
|
|
441
|
+
/** Display / heading font. Defaults to `inter` (shared with the body). */
|
|
442
442
|
display?: FontInput;
|
|
443
443
|
/** Monospace / code font. Defaults to `ibm-plex-mono`. */
|
|
444
444
|
mono?: FontInput;
|
|
@@ -548,6 +548,27 @@ export interface AskSuggestion {
|
|
|
548
548
|
/** Backends that can route an Ask AI request. */
|
|
549
549
|
type AskProviderGateway = "gateway" | "openrouter" | "llmgateway";
|
|
550
550
|
type AskProvider = AskProviderGateway | "inkeep" | "openai-compatible";
|
|
551
|
+
/** How much retrieved documentation each Ask AI question carries. */
|
|
552
|
+
export interface AskRetrievalConfig {
|
|
553
|
+
/**
|
|
554
|
+
* Total injected documentation characters, across all excerpts. Defaults to
|
|
555
|
+
* `10000`. The single biggest lever on time-to-first-token — the model reads
|
|
556
|
+
* every injected character before it emits a token.
|
|
557
|
+
*/
|
|
558
|
+
contextBudget?: number;
|
|
559
|
+
/**
|
|
560
|
+
* Characters kept per excerpt. Defaults to `2000`. Raise it when one long
|
|
561
|
+
* page holds the whole answer (a table the excerpt cuts in half); the
|
|
562
|
+
* `contextBudget` still caps the total.
|
|
563
|
+
*/
|
|
564
|
+
excerptChars?: number;
|
|
565
|
+
/**
|
|
566
|
+
* Documents retrieved per question. Defaults to `6`. The page the reader is
|
|
567
|
+
* viewing is injected on top of the retrieved ones, so an answer can cite up
|
|
568
|
+
* to one page more than this.
|
|
569
|
+
*/
|
|
570
|
+
maxResults?: number;
|
|
571
|
+
}
|
|
551
572
|
export interface AskConfig {
|
|
552
573
|
/**
|
|
553
574
|
* Name of the env var holding the provider API key. Each provider has a
|
|
@@ -577,6 +598,12 @@ export interface AskConfig {
|
|
|
577
598
|
model?: string;
|
|
578
599
|
/** Which backend routes the request. Defaults to `gateway`. */
|
|
579
600
|
provider?: AskProvider;
|
|
601
|
+
/**
|
|
602
|
+
* How much documentation each question carries into the model's prompt.
|
|
603
|
+
* Lower values cut time-to-first-token — which dominates on a self-hosted
|
|
604
|
+
* backend — at the cost of recall. Defaults keep the built-in behavior.
|
|
605
|
+
*/
|
|
606
|
+
retrieval?: AskRetrievalConfig;
|
|
580
607
|
/** Starter prompts shown before the first question. */
|
|
581
608
|
suggestions?: AskSuggestion[];
|
|
582
609
|
}
|
|
@@ -637,6 +664,19 @@ export interface AiConfig {
|
|
|
637
664
|
markdownComponents?: Record<string, ComponentMarkdown>;
|
|
638
665
|
/** Expose the docs as an MCP server for agents. */
|
|
639
666
|
mcp?: McpConfig;
|
|
667
|
+
/**
|
|
668
|
+
* The "Open in chat" page action, which opens the current page in an AI
|
|
669
|
+
* assistant pre-filled with a prompt pointing at its raw Markdown.
|
|
670
|
+
* Defaults to `true` (every provider). Set `false` to hide the action, or
|
|
671
|
+
* list a subset of providers to show, in order.
|
|
672
|
+
*
|
|
673
|
+
* ```ts
|
|
674
|
+
* ai: {
|
|
675
|
+
* openInChat: ["claude", "chatgpt", "cursor"],
|
|
676
|
+
* }
|
|
677
|
+
* ```
|
|
678
|
+
*/
|
|
679
|
+
openInChat?: boolean | OpenInChatProvider[];
|
|
640
680
|
/**
|
|
641
681
|
* Publish Agent Skills for discovery: a directory (resolved against the
|
|
642
682
|
* project root) whose subdirectories each hold a `SKILL.md`. Skills are
|
|
@@ -1022,6 +1062,18 @@ interface ReferenceConfig {
|
|
|
1022
1062
|
enabled?: boolean;
|
|
1023
1063
|
/** Start nested schema rows expanded (Blume renderer). Defaults to `false`. */
|
|
1024
1064
|
expandSchemas?: boolean;
|
|
1065
|
+
/**
|
|
1066
|
+
* The interactive "Try it" panel on operation pages (Blume renderer). On by
|
|
1067
|
+
* default; `false` hides it. `proxy` is the CORS escape hatch the OpenAPI
|
|
1068
|
+
* Send button routes requests through: a proxy URL, or `true` for the
|
|
1069
|
+
* built-in `/_api-proxy` endpoint (which requires
|
|
1070
|
+
* `deployment.output: "server"`). `proxy` is OpenAPI-only — an event
|
|
1071
|
+
* composer's WebSocket connect is direct.
|
|
1072
|
+
*/
|
|
1073
|
+
playground?: boolean | {
|
|
1074
|
+
enabled?: boolean;
|
|
1075
|
+
proxy?: boolean | string;
|
|
1076
|
+
};
|
|
1025
1077
|
/** Who renders the reference. Defaults to `blume`. */
|
|
1026
1078
|
renderer?: "blume" | "scalar";
|
|
1027
1079
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FontHead } from "../theme/fonts.ts";
|
|
1
2
|
import type { UIStrings } from "./i18n-ui.ts";
|
|
2
3
|
import type { ResolvedConfig, SearchProvider } from "./schema.ts";
|
|
3
4
|
import type { Navigation, RouteAlternate, VersionAlternate } from "./types.ts";
|
|
@@ -32,6 +33,17 @@ export interface BlumeLogo {
|
|
|
32
33
|
export interface BlumeFavicon {
|
|
33
34
|
href: string;
|
|
34
35
|
type?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Dark-scheme variant: the `-dark` sibling of the resolved icon file (e.g.
|
|
38
|
+
* `icon.svg` → `icon-dark.svg`), or the bundled default pair. When set, the
|
|
39
|
+
* layout emits an unconditional light link plus both icons behind
|
|
40
|
+
* `media="(prefers-color-scheme: …)"` so a dark mark doesn't vanish against
|
|
41
|
+
* dark browser chrome.
|
|
42
|
+
*/
|
|
43
|
+
dark?: {
|
|
44
|
+
href: string;
|
|
45
|
+
type?: string;
|
|
46
|
+
};
|
|
35
47
|
}
|
|
36
48
|
/** Announcement banner, normalized from its config (string shorthand or object). */
|
|
37
49
|
export interface BlumeBanner {
|
|
@@ -156,6 +168,11 @@ export interface BlumeDataConfig {
|
|
|
156
168
|
*/
|
|
157
169
|
site?: string;
|
|
158
170
|
};
|
|
171
|
+
/**
|
|
172
|
+
* "Open in chat" page-action providers (`ai.openInChat`), in display order;
|
|
173
|
+
* empty hides the action.
|
|
174
|
+
*/
|
|
175
|
+
openInChat: ResolvedConfig["ai"]["openInChat"];
|
|
159
176
|
/** Repository URL for header/edit links, or `null`. */
|
|
160
177
|
repoUrl: string | null;
|
|
161
178
|
search: {
|
|
@@ -209,8 +226,8 @@ export interface BlumeClientData {
|
|
|
209
226
|
export interface BlumeData {
|
|
210
227
|
config: BlumeDataConfig;
|
|
211
228
|
feeds: BlumeFeed[];
|
|
212
|
-
/**
|
|
213
|
-
fontCssVars:
|
|
229
|
+
/** Configured fonts for the head: CSS variable + preload weights per family. */
|
|
230
|
+
fontCssVars: FontHead[];
|
|
214
231
|
/** Sidebar + tab tree for the default locale. */
|
|
215
232
|
navigation: Navigation;
|
|
216
233
|
/** Per-locale navigation trees, keyed by locale code (empty without i18n). */
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Providers for the "Open in chat" page action, in display order. Shared by
|
|
3
|
+
* the config schema (`ai.openInChat` subsets validate against this list) and
|
|
4
|
+
* the PageActions menu (which renders the full list when the config is `true`).
|
|
5
|
+
* Lives outside `schema.ts` so the component can import the list without
|
|
6
|
+
* pulling the whole config schema into the layout module graph.
|
|
7
|
+
*/
|
|
8
|
+
export declare const openInChatProviders: readonly ["v0", "chatgpt", "claude", "t3", "scira", "cursor"];
|
|
9
|
+
export type OpenInChatProvider = (typeof openInChatProviders)[number];
|
|
@@ -269,6 +269,11 @@ declare const aiConfigSchema: z.ZodObject<{
|
|
|
269
269
|
inkeep: "inkeep";
|
|
270
270
|
"openai-compatible": "openai-compatible";
|
|
271
271
|
}>>;
|
|
272
|
+
retrieval: z.ZodOptional<z.ZodObject<{
|
|
273
|
+
contextBudget: z.ZodOptional<z.ZodNumber>;
|
|
274
|
+
excerptChars: z.ZodOptional<z.ZodNumber>;
|
|
275
|
+
maxResults: z.ZodOptional<z.ZodNumber>;
|
|
276
|
+
}, z.core.$strict>>;
|
|
272
277
|
suggestions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
273
278
|
icon: z.ZodOptional<z.ZodString>;
|
|
274
279
|
label: z.ZodString;
|
|
@@ -291,6 +296,14 @@ declare const aiConfigSchema: z.ZodObject<{
|
|
|
291
296
|
name: z.ZodOptional<z.ZodString>;
|
|
292
297
|
route: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<string, string>>;
|
|
293
298
|
}, z.core.$strict>>;
|
|
299
|
+
openInChat: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodArray<z.ZodEnum<{
|
|
300
|
+
v0: "v0";
|
|
301
|
+
chatgpt: "chatgpt";
|
|
302
|
+
claude: "claude";
|
|
303
|
+
t3: "t3";
|
|
304
|
+
scira: "scira";
|
|
305
|
+
cursor: "cursor";
|
|
306
|
+
}>>]>>, z.ZodTransform<("v0" | "chatgpt" | "claude" | "t3" | "scira" | "cursor")[], boolean | ("v0" | "chatgpt" | "claude" | "t3" | "scira" | "cursor")[]>>;
|
|
294
307
|
skills: z.ZodOptional<z.ZodString>;
|
|
295
308
|
webBotAuth: z.ZodPrefault<z.ZodObject<{
|
|
296
309
|
keys: z.ZodDefault<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
@@ -299,6 +312,8 @@ declare const aiConfigSchema: z.ZodObject<{
|
|
|
299
312
|
}, z.core.$strict>;
|
|
300
313
|
export type AskAiProvider = (typeof askAiProviders)[number];
|
|
301
314
|
export type AskAiConfig = NonNullable<z.infer<typeof aiConfigSchema>["ask"]>;
|
|
315
|
+
export { openInChatProviders } from "./open-in-chat.ts";
|
|
316
|
+
export type { OpenInChatProvider } from "./open-in-chat.ts";
|
|
302
317
|
/** A configured locale: ISO-ish code plus display metadata for the switcher. */
|
|
303
318
|
declare const localeSchema: z.ZodObject<{
|
|
304
319
|
code: z.ZodString;
|
|
@@ -473,6 +488,11 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
473
488
|
inkeep: "inkeep";
|
|
474
489
|
"openai-compatible": "openai-compatible";
|
|
475
490
|
}>>;
|
|
491
|
+
retrieval: z.ZodOptional<z.ZodObject<{
|
|
492
|
+
contextBudget: z.ZodOptional<z.ZodNumber>;
|
|
493
|
+
excerptChars: z.ZodOptional<z.ZodNumber>;
|
|
494
|
+
maxResults: z.ZodOptional<z.ZodNumber>;
|
|
495
|
+
}, z.core.$strict>>;
|
|
476
496
|
suggestions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
477
497
|
icon: z.ZodOptional<z.ZodString>;
|
|
478
498
|
label: z.ZodString;
|
|
@@ -495,6 +515,14 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
495
515
|
name: z.ZodOptional<z.ZodString>;
|
|
496
516
|
route: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<string, string>>;
|
|
497
517
|
}, z.core.$strict>>;
|
|
518
|
+
openInChat: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodArray<z.ZodEnum<{
|
|
519
|
+
v0: "v0";
|
|
520
|
+
chatgpt: "chatgpt";
|
|
521
|
+
claude: "claude";
|
|
522
|
+
t3: "t3";
|
|
523
|
+
scira: "scira";
|
|
524
|
+
cursor: "cursor";
|
|
525
|
+
}>>]>>, z.ZodTransform<("v0" | "chatgpt" | "claude" | "t3" | "scira" | "cursor")[], boolean | ("v0" | "chatgpt" | "claude" | "t3" | "scira" | "cursor")[]>>;
|
|
498
526
|
skills: z.ZodOptional<z.ZodString>;
|
|
499
527
|
webBotAuth: z.ZodPrefault<z.ZodObject<{
|
|
500
528
|
keys: z.ZodDefault<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
@@ -521,6 +549,16 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
521
549
|
codeSamples: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
522
550
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
523
551
|
expandSchemas: z.ZodDefault<z.ZodBoolean>;
|
|
552
|
+
playground: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
553
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
554
|
+
proxy: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>;
|
|
555
|
+
}, z.core.$strict>]>>, z.ZodTransform<{
|
|
556
|
+
enabled: boolean;
|
|
557
|
+
proxy: string | boolean;
|
|
558
|
+
}, boolean | {
|
|
559
|
+
enabled: boolean;
|
|
560
|
+
proxy: string | boolean;
|
|
561
|
+
}>>;
|
|
524
562
|
renderer: z.ZodDefault<z.ZodEnum<{
|
|
525
563
|
blume: "blume";
|
|
526
564
|
scalar: "scalar";
|
|
@@ -814,6 +852,16 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
814
852
|
codeSamples: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
815
853
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
816
854
|
expandSchemas: z.ZodDefault<z.ZodBoolean>;
|
|
855
|
+
playground: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
856
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
857
|
+
proxy: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>;
|
|
858
|
+
}, z.core.$strict>]>>, z.ZodTransform<{
|
|
859
|
+
enabled: boolean;
|
|
860
|
+
proxy: string | boolean;
|
|
861
|
+
}, boolean | {
|
|
862
|
+
enabled: boolean;
|
|
863
|
+
proxy: string | boolean;
|
|
864
|
+
}>>;
|
|
817
865
|
renderer: z.ZodDefault<z.ZodEnum<{
|
|
818
866
|
blume: "blume";
|
|
819
867
|
scalar: "scalar";
|
|
@@ -1149,4 +1197,3 @@ export type SearchProvider = (typeof searchProviders)[number];
|
|
|
1149
1197
|
export type ContentSignals = z.infer<typeof contentSignalsSchema>;
|
|
1150
1198
|
/** The resolved per-signal policy object (present when signals are enabled). */
|
|
1151
1199
|
export type ContentSignalPolicy = NonNullable<ContentSignals>;
|
|
1152
|
-
export {};
|
|
@@ -47,6 +47,10 @@ export interface Heading {
|
|
|
47
47
|
export interface PageLink {
|
|
48
48
|
/** Raw link target as written, e.g. `./foo`, `/api#auth`, `https://x.dev`. */
|
|
49
49
|
target: string;
|
|
50
|
+
/** Set when the target was written as an image embed (``) —
|
|
51
|
+
* only those go through the image pipeline; a plain link to the same path
|
|
52
|
+
* resolves as a site route. */
|
|
53
|
+
image?: boolean;
|
|
50
54
|
/** 1-based line number in the source file. */
|
|
51
55
|
line: number;
|
|
52
56
|
/** 1-based column of the target within the line. */
|
|
@@ -252,9 +256,12 @@ export interface Navigation {
|
|
|
252
256
|
sidebar: NavNode[];
|
|
253
257
|
/**
|
|
254
258
|
* The tree root in final path space — localized and based (`/`, `/en`,
|
|
255
|
-
* `/docs`)
|
|
256
|
-
*
|
|
257
|
-
*
|
|
259
|
+
* `/docs`), and versionized for an archived version tree (`/v1.0`). Tab
|
|
260
|
+
* paths share that space except under a version, where they stay in
|
|
261
|
+
* current-docs space — so the root tab is the tab this root sits under
|
|
262
|
+
* (`isRootTab`), not necessarily the tab at this exact path, and must be
|
|
263
|
+
* scoped as the root tab, not as a section tab. Absent on older serialized
|
|
264
|
+
* graphs; treat as `/`.
|
|
258
265
|
*/
|
|
259
266
|
root?: string;
|
|
260
267
|
/** Pinned links shown above the sidebar sections, unscoped by tab. */
|
|
@@ -16,6 +16,15 @@ export interface ReferenceDisplay {
|
|
|
16
16
|
codeSamples: string[];
|
|
17
17
|
/** Whether nested schema rows start expanded. */
|
|
18
18
|
expandSchemas: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The "Try it" playground: whether operation pages render it, and the CORS
|
|
21
|
+
* proxy the Send button routes through (`false` off, a URL string, or
|
|
22
|
+
* `true` for the built-in `/_api-proxy` endpoint).
|
|
23
|
+
*/
|
|
24
|
+
playground: {
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
proxy: string | boolean;
|
|
27
|
+
};
|
|
19
28
|
}
|
|
20
29
|
/** A spec source resolved to a concrete route, label, and renderer. */
|
|
21
30
|
export interface ReferenceSource {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { AnyOrama } from "@orama/orama";
|
|
2
|
+
/**
|
|
3
|
+
* The minimal document shape both the client-side search dialog and the
|
|
4
|
+
* server-side MCP `search_docs` tool index. Mirrors the `blume-search.json`
|
|
5
|
+
* entries built by `buildSearchDocuments`.
|
|
6
|
+
*/
|
|
7
|
+
export interface OramaDoc {
|
|
8
|
+
content: string;
|
|
9
|
+
description: string;
|
|
10
|
+
route: string;
|
|
11
|
+
title: string;
|
|
12
|
+
/** Locale code; indexed as an enum so queries can filter to one language. */
|
|
13
|
+
locale?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Docs version; indexed as an enum so queries can filter to one version.
|
|
16
|
+
* The current docs carry `""`, which the enum stores and matches exactly.
|
|
17
|
+
*/
|
|
18
|
+
version?: string;
|
|
19
|
+
/** Resolved page `type`; indexed as an enum so queries can filter by type. */
|
|
20
|
+
contentType?: string;
|
|
21
|
+
/** Declared facet values (`content.types.<type>.facets`), key → value. */
|
|
22
|
+
facets?: Record<string, string>;
|
|
23
|
+
/** Carried through for the search dialog's breadcrumb + filter pills. Stored
|
|
24
|
+
* but not indexed, so they ride along on the returned document untouched. */
|
|
25
|
+
breadcrumb?: string[];
|
|
26
|
+
section?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build an in-memory Orama full-text index from search documents. Shared by the
|
|
30
|
+
* Orama client loader (browser), the MCP server, and Ask AI grounding (Node),
|
|
31
|
+
* so ranking is identical wherever docs are queried. `locale` — the site's
|
|
32
|
+
* `i18n.defaultLocale` — swaps in a word-segmenting tokenizer for every
|
|
33
|
+
* non-Latin script, all of which Orama's default tokenizer reduces to zero
|
|
34
|
+
* tokens; the tokenizer belongs to the database, so on a mixed-locale site it
|
|
35
|
+
* applies to every document. That is safe in one direction only: Latin words
|
|
36
|
+
* survive segmentation intact, so English pages on a segmented index stay
|
|
37
|
+
* searchable, but non-Latin translations on a Latin-default index still
|
|
38
|
+
* collapse to zero tokens.
|
|
39
|
+
*/
|
|
40
|
+
export declare const buildOramaIndex: (documents: OramaDoc[], locale?: string) => Promise<AnyOrama>;
|
|
41
|
+
/** Optional exact-match filters applied to a query via Orama's `where`. */
|
|
42
|
+
export interface OramaQueryFilters {
|
|
43
|
+
/** Keep only documents whose `contentType` is in this list. */
|
|
44
|
+
contentTypes?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Keep only documents matching every facet, key → required value. Facet
|
|
47
|
+
* keys and values come from the `facets` field on the indexed documents.
|
|
48
|
+
*/
|
|
49
|
+
facets?: Record<string, string>;
|
|
50
|
+
/** Keep only documents in this locale. */
|
|
51
|
+
locale?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Keep only documents of this docs version (`""` is the current docs — a
|
|
54
|
+
* meaningful filter value, so absence alone disables version filtering).
|
|
55
|
+
*/
|
|
56
|
+
version?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Query the index, returning the matching documents (highest-ranked first).
|
|
60
|
+
* `filters` narrows results by exact `where` matches on the enum fields:
|
|
61
|
+
* `locale` to one language, `contentTypes` to a set of page types, `facets`
|
|
62
|
+
* to documents carrying every requested `key:value` term.
|
|
63
|
+
*
|
|
64
|
+
* On a bigrammed index the strict pass runs first: a term is only meant to
|
|
65
|
+
* match where its bigrams sit together, and scoring them independently lets a
|
|
66
|
+
* page sharing a couple of windows outrank the page the term is about. Terms
|
|
67
|
+
* spanning several words rarely appear in full on one page, so an empty strict
|
|
68
|
+
* result falls back to the default pass rather than reporting no matches.
|
|
69
|
+
*/
|
|
70
|
+
export declare const queryOramaIndex: (db: AnyOrama, term: string, limit: number, filters?: OramaQueryFilters) => Promise<OramaDoc[]>;
|
|
@@ -203,5 +203,14 @@ export declare const buildFontEntries: (fonts: FontsConfig) => FontEntry[];
|
|
|
203
203
|
* config tokens; empty when no fonts are set so defaults stay the system stacks.
|
|
204
204
|
*/
|
|
205
205
|
export declare const buildFontsCss: (fonts: FontsConfig) => string;
|
|
206
|
-
/**
|
|
207
|
-
export
|
|
206
|
+
/** One `<Font>` render in the head: its CSS variable + weights to preload. */
|
|
207
|
+
export interface FontHead {
|
|
208
|
+
cssVariable: string;
|
|
209
|
+
preloadWeights: number[];
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* The fonts to feed Astro's `<Font>` component in the document head, deduped
|
|
213
|
+
* by CSS variable with preload weights unioned across the roles that share a
|
|
214
|
+
* family (so `display` and `body` both set to Inter preload 400/500/600 once).
|
|
215
|
+
*/
|
|
216
|
+
export declare const configuredFonts: (fonts: FontsConfig) => FontHead[];
|
|
@@ -3,7 +3,7 @@ title: OpenAPI / AsyncAPI
|
|
|
3
3
|
description: Drop in an OpenAPI or AsyncAPI spec and get a native API reference — one real page per operation, in your sidebar and search.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Point Blume at an OpenAPI spec and it generates a native API reference: one **real page per operation**, grouped by tag in a tab-scoped sidebar, with schema tables, request/response examples,
|
|
6
|
+
Point Blume at an OpenAPI spec and it generates a native API reference: one **real page per operation**, grouped by tag in a tab-scoped sidebar, with schema tables, request/response examples, generated code samples, and an interactive [Try it](#try-it-playground) panel. Because each operation is a genuine Blume page, it gets its own URL, shows up in **site search** and `llms.txt`, and gets an Open Graph image — the same as any hand-written doc. The config below points Blume at the public Petstore spec as an example.
|
|
7
7
|
|
|
8
8
|
```ts blume.config.ts lineNumbers
|
|
9
9
|
openapi: {
|
|
@@ -62,6 +62,42 @@ openapi: {
|
|
|
62
62
|
}
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
## Try it playground
|
|
66
|
+
|
|
67
|
+
Operation pages rendered natively ship an interactive **Try it** panel by default. Blume generates the form from the operation itself: an input per path, query, and header parameter, a body editor built from the request-body schema, everything prefilled from the spec's examples. A server picker lists the spec's `servers`, with a free-text field for any other base URL, and auth inputs match the operation's [resolved security](#authorization) — bearer token, API key, and basic credentials, with OAuth2 as a token paste field (bring an access token; Blume doesn't run the flow).
|
|
68
|
+
|
|
69
|
+
The panel and the code samples stay in lockstep: values typed into the form update the generated samples live, so a copied curl command always matches exactly what **Send** would do. And it stays out of the way — the panel is server-rendered collapsed, and its JavaScript loads only when a reader first opens it. Readers who never touch it download none of it.
|
|
70
|
+
|
|
71
|
+
`playground: false` is the entire off switch:
|
|
72
|
+
|
|
73
|
+
```ts blume.config.ts lineNumbers
|
|
74
|
+
openapi: {
|
|
75
|
+
enabled: true,
|
|
76
|
+
spec: "./openapi.yaml",
|
|
77
|
+
playground: false,
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Credentials
|
|
82
|
+
|
|
83
|
+
Credentials typed into the auth inputs stay in memory and vanish on reload. Checking **Remember on this device** persists them in `localStorage`, scoped to the docs origin — they're never sent anywhere except the API being called. Code samples keep showing placeholders (`YOUR_TOKEN` and friends) whatever's typed, unless the reader toggles **Include my values in samples**.
|
|
84
|
+
|
|
85
|
+
### CORS and the proxy
|
|
86
|
+
|
|
87
|
+
As with the [Scalar renderer](#the-scalar-renderer), requests go **directly from the browser** to the target API, so the API must allow cross-origin requests from the docs site (`Access-Control-Allow-Origin`). For APIs that can't, set `playground.proxy`: a URL routes requests through a proxy you host, and `true` enables the built-in `/_api-proxy` route — which needs a server build, so it requires [`deployment.output: "server"`](/docs/deployment#server-rendering):
|
|
88
|
+
|
|
89
|
+
```ts blume.config.ts lineNumbers
|
|
90
|
+
openapi: {
|
|
91
|
+
enabled: true,
|
|
92
|
+
spec: "./openapi.yaml",
|
|
93
|
+
playground: {
|
|
94
|
+
proxy: true, // or a URL of your own
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The built-in proxy only forwards requests to the origins your specs declare in `servers` — including across redirects — so a public docs deployment can't be aimed at other hosts on its network. A **Custom base URL** typed into the panel isn't a documented server: with the proxy enabled, requests to it are refused with a 403.
|
|
100
|
+
|
|
65
101
|
## Multiple specs
|
|
66
102
|
|
|
67
103
|
Use `sources` to publish more than one spec. Each source gets its own overview route, operation pages, and header tab. Give each a `label` (used for the tab and to derive its route), or set an explicit `route`:
|
|
@@ -118,7 +154,7 @@ The OpenAPI semantics carry over as written:
|
|
|
118
154
|
|
|
119
155
|
## The Scalar renderer
|
|
120
156
|
|
|
121
|
-
The native renderer is the default. If you'd rather embed [Scalar](https://scalar.com)'s self-contained API reference — its own sidebar, search, theme, and
|
|
157
|
+
The native renderer is the default — operation pages, search integration, and the [Try it playground](#try-it-playground) above are all its work. If you'd rather embed [Scalar](https://scalar.com)'s self-contained API reference UI — its own sidebar, search, theme, and request client on a single route — set `renderer: "scalar"`:
|
|
122
158
|
|
|
123
159
|
```ts blume.config.ts lineNumbers
|
|
124
160
|
openapi: {
|
|
@@ -129,7 +165,7 @@ openapi: {
|
|
|
129
165
|
}
|
|
130
166
|
```
|
|
131
167
|
|
|
132
|
-
A Scalar-rendered reference is a self-contained embed on its own route — it doesn't weave into Blume's sidebar, search, or `llms.txt
|
|
168
|
+
A Scalar-rendered reference is a self-contained embed on its own route — it doesn't weave into Blume's sidebar, search, or `llms.txt`, and Blume's [`playground`](#try-it-playground) config doesn't apply to it. Scalar brings its own request client, which calls your **target API directly from the browser** (the `playground.proxy` route isn't available here), so the API must allow cross-origin requests from the docs site (`Access-Control-Allow-Origin`). `theme` applies to the Scalar renderer only.
|
|
133
169
|
|
|
134
170
|
### Passing Scalar options
|
|
135
171
|
|
|
@@ -153,7 +189,7 @@ Blume's own [`i18n`](/docs/content/i18n) translates the docs chrome, but Scalar
|
|
|
153
189
|
|
|
154
190
|
## AsyncAPI
|
|
155
191
|
|
|
156
|
-
Event-driven APIs use a sibling `asyncapi` block with the same shape — and the same native renderer. Each `send`/`receive` operation becomes a real page with message payload and header schema tables, channel parameters, protocol bindings,
|
|
192
|
+
Event-driven APIs use a sibling `asyncapi` block with the same shape — and the same native renderer. Each `send`/`receive` operation becomes a real page with message payload and header schema tables, channel parameters, protocol bindings, an Authorization section derived from the spec's `securitySchemes` (server-level and operation-level, alternatives as "or" groups), and a [Try it](#try-it-for-events) message composer. Only the default route differs (`/events`):
|
|
157
193
|
|
|
158
194
|
```ts blume.config.ts lineNumbers
|
|
159
195
|
asyncapi: {
|
|
@@ -166,4 +202,30 @@ AsyncAPI **2.x specs are normalized to 3.x automatically** with the official Asy
|
|
|
166
202
|
|
|
167
203
|
Code samples are **protocol-aware**, keyed off the operation's binding (or its servers' protocol): `wscat` and a browser `WebSocket` snippet for WebSockets, `kcat` for Kafka, `mosquitto_pub`/`mosquitto_sub` for MQTT. `codeSamples` filters that set, the same way it picks languages on the `openapi` block; a protocol without a supported tool renders the message payload example alone rather than a fabricated client.
|
|
168
204
|
|
|
169
|
-
Everything documented above carries over
|
|
205
|
+
Everything documented above carries over, [`playground`](#try-it-for-events) included: `route`, `sources` with `label`/`route`, `expandSchemas`, the [per-source indexing](#per-source-indexing) flags, and search indexing by operation summary and tag.
|
|
206
|
+
|
|
207
|
+
Setting `renderer: "scalar"` opts back into the embedded Scalar SPA, where — as with OpenAPI — only `noindex` applies. Scalar has no AsyncAPI playground of its own; its embed auto-detects the document type and renders channels, operations, messages, and a Models section, so that swap trades the composer away.
|
|
208
|
+
|
|
209
|
+
### Try it for events
|
|
210
|
+
|
|
211
|
+
Operation pages rendered natively ship a **Try it** panel here too, on the same terms as the [OpenAPI panel](#try-it-playground): server-rendered collapsed, with its JavaScript loaded only when a reader first opens it.
|
|
212
|
+
|
|
213
|
+
Whatever the protocol, the panel opens with a payload editor prefilled from the message's `examples` — or, when the message declares none, from a value sampled out of the payload schema — validated against the message payload schema as you type. Under it sit an input per channel parameter and a server picker fed by the channel's `servers`, with a free-text field for any other URL. The protocol-aware code samples stay in lockstep with the form exactly as curl, js, and python do on an HTTP operation: the channel address template is filled in with the parameter values you type, so a copied `wscat`, `WebSocket`, `kcat`, or `mosquitto_pub` snippet matches what the form says.
|
|
214
|
+
|
|
215
|
+
Live connect is WebSocket-only. On a `ws` or `wss` binding the panel connects to the resolved channel URL, shows the connection state, and logs every frame with a timestamp. AsyncAPI 3 states an action from the API's side, and the panel follows it: a `receive` operation is one the API receives from you, so it gets a **Send** button that publishes the composed payload; a `send` operation only streams messages at you, so it connects and logs. There's no reconnect logic — once a socket closes, it stays closed until you connect again. Kafka, MQTT, AMQP, and every other protocol get the composer and the copyable CLI samples, and the panel says as much on the page: Blume doesn't fake broker connectivity from a browser tab.
|
|
216
|
+
|
|
217
|
+
`asyncapi.playground` mirrors `openapi.playground` — on by default with the native renderer, and `false` is the entire off switch:
|
|
218
|
+
|
|
219
|
+
```ts blume.config.ts lineNumbers
|
|
220
|
+
asyncapi: {
|
|
221
|
+
enabled: true,
|
|
222
|
+
spec: "./asyncapi.yaml",
|
|
223
|
+
playground: false,
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
:::note
|
|
228
|
+
`playground.proxy` is OpenAPI-only. It forwards HTTP requests, and a WebSocket connect goes straight from the browser to the server named in the URL, so there's nothing for a proxy to sit in front of.
|
|
229
|
+
:::
|
|
230
|
+
|
|
231
|
+
The event composer collects no broker credentials. Each operation page's **Authorization** section documents what the broker expects, and a WebSocket connect carries only what's already in the URL. Nothing is persisted for event operations.
|
|
@@ -231,13 +231,17 @@ Passing `siteUrl` (and `ogEnabled`) derives the page's `canonical` and a generat
|
|
|
231
231
|
siteUrl={config.site}
|
|
232
232
|
ogEnabled={config.og.enabled}
|
|
233
233
|
ogImage="/opengraph-image.png"
|
|
234
|
+
ogImageAlt="Acme — the fastest docs"
|
|
235
|
+
ogImageSize={{ width: 1200, height: 630 }}
|
|
234
236
|
page={{ title: config.title }}
|
|
235
237
|
>
|
|
236
238
|
<!-- page content -->
|
|
237
239
|
</PageLayout>
|
|
238
240
|
```
|
|
239
241
|
|
|
240
|
-
Only this page changes — every other route keeps its generated card — so it's how you give the home page alone a bespoke share image.
|
|
242
|
+
Only this page changes — every other route keeps its generated card — so it's how you give the home page alone a bespoke share image. The generated card declares its size and alt text to crawlers on its own; for your own `ogImage`, pass `ogImageAlt` and `ogImageSize` alongside so the share card gets the same treatment.
|
|
243
|
+
|
|
244
|
+
The page also emits schema.org JSON-LD — the same `WebSite` graph the docs pages carry, so the home page (usually a custom page) isn't the one URL without structured data. Pass `structuredDataEnabled={config.structuredData}` to keep it in sync with the [`structuredData`](/docs/configuration/seo) config, or `structuredDataEnabled={false}` to turn it off for one page.
|
|
241
245
|
|
|
242
246
|
`page.title` is used verbatim as the document title (no `- siteTitle` suffix), since marketing pages usually set their own. To give a custom page the full docs chrome instead — sidebar, TOC, and all — wrap it in `RootLayout`, the layout the generated pages use. Pull the required props straight from `blume:data`:
|
|
243
247
|
|
|
@@ -101,6 +101,14 @@ The **Open in chat** action opens the current page in an AI assistant — v0, Ch
|
|
|
101
101
|
|
|
102
102
|
Like Copy as Markdown, it needs no setup. The assistant fetches the page over its public URL, so it works as soon as the page is deployed.
|
|
103
103
|
|
|
104
|
+
To tailor the action, set `ai.openInChat`. `false` hides it entirely, and an array of provider keys — `"v0"`, `"chatgpt"`, `"claude"`, `"t3"`, `"scira"`, `"cursor"` — shows just those providers, in the order you list them:
|
|
105
|
+
|
|
106
|
+
```ts blume.config.ts lineNumbers
|
|
107
|
+
ai: {
|
|
108
|
+
openInChat: ["claude", "chatgpt", "cursor"],
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
104
112
|
To embed a ready-to-copy prompt inline in your content — rather than a whole-page action — use the [Prompt component](/docs/content/components#prompt), which renders a labeled row with a **Copy prompt** button and an optional open-in-Cursor link.
|
|
105
113
|
|
|
106
114
|
## Ask AI
|
|
@@ -160,6 +168,33 @@ The page the reader is currently on is added to the context first and used to sc
|
|
|
160
168
|
|
|
161
169
|
Grounding is on for every backend except **[Inkeep](#backends)**, which runs its own retrieval over the content you've indexed in its dashboard.
|
|
162
170
|
|
|
171
|
+
### Retrieval size
|
|
172
|
+
|
|
173
|
+
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:
|
|
174
|
+
|
|
175
|
+
```ts blume.config.ts lineNumbers
|
|
176
|
+
ai: {
|
|
177
|
+
ask: {
|
|
178
|
+
enabled: true,
|
|
179
|
+
retrieval: {
|
|
180
|
+
maxResults: 3, // fewer pages retrieved per question
|
|
181
|
+
excerptChars: 1200, // shorter excerpt from each one
|
|
182
|
+
contextBudget: 3000, // smaller total injection
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
| Option | Default | Description |
|
|
189
|
+
| --------------- | ------- | ----------------------------------------------- |
|
|
190
|
+
| `maxResults` | `6` | Documents retrieved per question. |
|
|
191
|
+
| `excerptChars` | `2000` | Characters kept from each retrieved page. |
|
|
192
|
+
| `contextBudget` | `10000` | Total injected characters, across all excerpts. |
|
|
193
|
+
|
|
194
|
+
The three aren't interchangeable. `contextBudget` caps the whole injection, `excerptChars` decides how deep into a single long page its excerpt reaches — raise it when one page holds the whole answer and the excerpt cuts it off — and `maxResults` caps how many pages retrieval adds. The page the reader is viewing is injected on top of the retrieved ones, so an answer can cite up to one page more than `maxResults`.
|
|
195
|
+
|
|
196
|
+
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.
|
|
197
|
+
|
|
163
198
|
### External endpoint
|
|
164
199
|
|
|
165
200
|
Already have an API backend for AI? Point the panel at it and keep the docs build static:
|