@stainless-api/docs 0.1.0-beta.120 → 0.1.0-beta.122

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.
@@ -0,0 +1,95 @@
1
+ import { AstroIntegrationLogger } from 'astro';
2
+ import z from 'zod';
3
+ import {
4
+ buildVirtualModuleString,
5
+ makeAsyncVirtualModPlugin,
6
+ makeVirtualModPlugin,
7
+ } from '../shared/virtualModule';
8
+ import type * as virtualExampleModule from 'virtual:stl-docs-ai-chat-examples';
9
+ type VirtualExampleModule = typeof virtualExampleModule;
10
+
11
+ const exampleSchema = z.array(
12
+ z.object({
13
+ shortPrompt: z.string(),
14
+ longPrompt: z.string(),
15
+ icon: z.string(),
16
+ }),
17
+ );
18
+ export type ExamplePromptResponse = z.infer<typeof exampleSchema>;
19
+
20
+ // handles actually retrieving the information via the Stainless API
21
+ async function loadExamples(
22
+ projectName: string,
23
+ logger: AstroIntegrationLogger,
24
+ ): Promise<ExamplePromptResponse | undefined> {
25
+ try {
26
+ const response = await fetch(`https://api.stainless.com/api/ai/steelie-examples/${projectName}`, {
27
+ method: 'GET',
28
+ });
29
+
30
+ const text = await response.text();
31
+ if (!response.ok) {
32
+ logger.error(`failed to fetch AI chat examples: ${text}`);
33
+ return undefined;
34
+ }
35
+ const examples = exampleSchema.parse(JSON.parse(text));
36
+ return examples;
37
+ } catch (error) {
38
+ if (error instanceof Error) logger.error(`failed to fetch AI chat examples: ${error.message}`);
39
+ else throw error;
40
+ return undefined;
41
+ }
42
+ }
43
+
44
+ export default async function generateExamplesPlugin({
45
+ projectName,
46
+ logger,
47
+ exampleOverrides,
48
+ }: {
49
+ projectName: string | undefined;
50
+ logger: AstroIntegrationLogger;
51
+ exampleOverrides?: ExamplePromptResponse;
52
+ }) {
53
+ // if the user has specified any examples, return those immediately
54
+ // instead of loading them via the web.
55
+ if (exampleOverrides) {
56
+ return makeVirtualModPlugin(
57
+ 'virtual:stl-docs-ai-chat-examples',
58
+ generateVirtualModuleString(exampleOverrides),
59
+ );
60
+ }
61
+ // if we don't have a defined project name, don't try to fetch examples
62
+ if (!projectName) {
63
+ return makeVirtualModPlugin(
64
+ 'virtual:stl-docs-ai-chat-examples',
65
+ buildVirtualModuleString({ examples: undefined } satisfies VirtualExampleModule),
66
+ );
67
+ }
68
+
69
+ // otherwise, promise to get the right examples at some point later on
70
+ const examplesPromise = loadExamples(projectName, logger);
71
+ return makeAsyncVirtualModPlugin<VirtualExampleModule>('virtual:stl-docs-ai-chat-examples', async () => {
72
+ const examples = await examplesPromise;
73
+ return generateVirtualModuleString(examples);
74
+ });
75
+ }
76
+
77
+ function generateVirtualModuleString(examples: ExamplePromptResponse | undefined) {
78
+ if (!examples) return 'export const examples = undefined;';
79
+
80
+ // Generate icon imports
81
+ // prettier-ignore
82
+ const pascalToKebab = (str: string) => str.split(/(?=[A-Z])/).join('-').toLowerCase();
83
+ const iconImportPath = (iconName: string) =>
84
+ import.meta.resolve(`lucide-react/dist/esm/icons/${pascalToKebab(iconName)}.js`);
85
+ const iconImports = examples.map(
86
+ ({ icon }) => `import ${icon} from ${JSON.stringify(iconImportPath(icon))}`,
87
+ );
88
+
89
+ // Reference icon imports in `examples` exported object
90
+ // "icon":"Sparkles" -> "icon":Sparkles
91
+ const iconStringsToIdents = (jsonBlob: string) => jsonBlob.replace(/"icon":\s*"(\w+)"/g, '"icon":$1');
92
+ const exportBody = `export const examples = ${iconStringsToIdents(JSON.stringify(examples))};`;
93
+
94
+ return [...iconImports, exportBody].join('\n');
95
+ }
package/stl-docs/index.ts CHANGED
@@ -6,7 +6,7 @@ import { disableCalloutSyntaxStarlightPlugin } from './disableCalloutSyntax';
6
6
  import type { AstroIntegration } from 'astro';
7
7
 
8
8
  import { normalizeRedirects, type NormalizedRedirectConfig } from './redirects';
9
- import { join } from 'path';
9
+ import path from 'node:path';
10
10
  import { mkdirSync, writeFileSync } from 'fs';
11
11
  import {
12
12
  parseStlDocsConfig,
@@ -20,12 +20,13 @@ import { buildVirtualModuleString } from '../shared/virtualModule';
20
20
  import type * as StlDocsVirtualModule from 'virtual:stl-docs-virtual-module';
21
21
  import { resolveSrcFile } from '../resolveSrcFile';
22
22
  import { stainlessDocsMarkdownRenderer } from './proseMarkdown/proseMarkdownIntegration';
23
- import { setSharedLogger } from '../shared/getSharedLogger';
23
+ import { getSharedLogger, setSharedLogger } from '../shared/getSharedLogger';
24
24
  import { stainlessDocsVectorProseIndexing } from './proseDocSync';
25
25
  import { stainlessDocsAlgoliaProseIndexing } from './proseSearchIndexing';
26
26
  import { stainlessStarlight } from '../plugin';
27
27
  import { getFontRoles, flattenFonts } from './fonts';
28
28
  import conditionalIntegration from '../shared/conditionalIntegration';
29
+ import generateExamplesPlugin from './aiChatExamples';
29
30
 
30
31
  export * from '../plugin';
31
32
 
@@ -168,19 +169,26 @@ function stainlessDocsIntegration(
168
169
  return {
169
170
  name: 'stl-docs-astro',
170
171
  hooks: {
171
- 'astro:config:setup': ({ updateConfig, command, config: astroConfig }) => {
172
+ 'astro:config:setup': async ({ updateConfig, command, config: astroConfig, logger: localLogger }) => {
173
+ const logger = getSharedLogger({ fallback: localLogger });
172
174
  // we only handle redirects for builds
173
175
  // in dev, Astro handles them for us
174
176
  if (command === 'build' && astroConfig.redirects) {
175
177
  redirects = normalizeRedirects(astroConfig.redirects);
176
178
  }
177
179
 
180
+ const base = astroConfig.base ?? '/';
181
+ const withBase = (link: string) =>
182
+ ['http://', 'https://', '//'].some((prefix) => link.startsWith(prefix))
183
+ ? link
184
+ : path.posix.join(base, link);
185
+
178
186
  const virtualModules = new Map(
179
187
  Object.entries({
180
188
  'virtual:stl-docs-virtual-module': buildVirtualModuleString({
181
- TABS: config.tabs,
189
+ TABS: config.tabs.map((tab) => ({ ...tab, link: withBase(tab.link) })),
182
190
  SPLIT_TABS_ENABLED: config.splitTabsEnabled,
183
- HEADER_LINKS: config.header.links,
191
+ HEADER_LINKS: config.header.links.map((link) => ({ ...link, link: withBase(link.link) })),
184
192
  HEADER_LAYOUT: config.header.layout,
185
193
  ENABLE_CLIENT_ROUTER: config.enableClientRouter,
186
194
  API_REFERENCE_BASE_PATH: apiReferenceBasePath ?? '/api',
@@ -218,6 +226,15 @@ function stainlessDocsIntegration(
218
226
  if (virtualModules.has(bare)) return virtualModules.get(bare);
219
227
  },
220
228
  },
229
+ ...(config.aiChat
230
+ ? [
231
+ await generateExamplesPlugin({
232
+ projectName: config.apiReference?.stainlessProject ?? undefined,
233
+ logger,
234
+ exampleOverrides: config.aiChat?.exampleOverrides,
235
+ }),
236
+ ]
237
+ : []),
221
238
  ],
222
239
  optimizeDeps: {
223
240
  include: config.aiChat
@@ -240,9 +257,9 @@ function stainlessDocsIntegration(
240
257
  },
241
258
  'astro:build:done': ({ dir }) => {
242
259
  if (redirects !== null) {
243
- const stainlessDir = join(dir.pathname, '_stainless');
260
+ const stainlessDir = path.join(dir.pathname, '_stainless');
244
261
  mkdirSync(stainlessDir, { recursive: true });
245
- const outputPath = join(stainlessDir, 'redirects.json');
262
+ const outputPath = path.join(stainlessDir, 'redirects.json');
246
263
  writeFileSync(outputPath, JSON.stringify(redirects, null, 2), {
247
264
  encoding: 'utf-8',
248
265
  });
@@ -4,6 +4,7 @@ import type { ButtonVariant } from '@stainless-api/ui-primitives';
4
4
  import type { AnchorHTMLAttributes } from 'react';
5
5
  import type starlight from '@astrojs/starlight';
6
6
  import { normalizeFonts, type StlDocsFontConfig } from './fonts';
7
+ import type { ExamplePromptResponse } from './aiChatExamples';
7
8
 
8
9
  type StarlightConfig = Parameters<typeof starlight>[0];
9
10
 
@@ -79,7 +80,10 @@ export type StainlessDocsUserConfig = {
79
80
  */
80
81
  disableProseMarkdownRendering?: boolean;
81
82
  disableStainlessProseIndexing?: boolean;
82
- aiChat?: { chatComponentPath: string };
83
+ aiChat?: {
84
+ chatComponentPath: string;
85
+ exampleOverrides?: ExamplePromptResponse;
86
+ };
83
87
  /**
84
88
  * Whether to link group titles to overview pages. Note: overview pages must already be present in the sidebar for this to work.
85
89
  *
@@ -75,6 +75,16 @@ declare module 'virtual:stl-docs/components/AiChat.tsx' {
75
75
  export const STAINLESS_PROJECT: string | undefined;
76
76
  }
77
77
 
78
+ declare module 'virtual:stl-docs-ai-chat-examples' {
79
+ export type ExamplePrompt = {
80
+ longPrompt: string;
81
+ shortPrompt: string;
82
+ icon: React.ComponentType;
83
+ };
84
+
85
+ export const examples: ExamplePrompt[] | undefined;
86
+ }
87
+
78
88
  declare module 'virtual:stainless-apis-manifest' {
79
89
  import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
80
90