@stainless-api/docs 0.1.0-beta.121 → 0.1.0-beta.123

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
@@ -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,7 +169,8 @@ 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) {
@@ -177,9 +179,7 @@ function stainlessDocsIntegration(
177
179
 
178
180
  const base = astroConfig.base ?? '/';
179
181
  const withBase = (link: string) =>
180
- ['http://', 'https://', '//'].some((prefix) => link.startsWith(prefix))
181
- ? link
182
- : path.posix.join(base, link);
182
+ /^([a-z][a-z0-9+.-]*:|\/\/)/.test(link) ? link : path.posix.join(base, link);
183
183
 
184
184
  const virtualModules = new Map(
185
185
  Object.entries({
@@ -224,6 +224,15 @@ function stainlessDocsIntegration(
224
224
  if (virtualModules.has(bare)) return virtualModules.get(bare);
225
225
  },
226
226
  },
227
+ ...(config.aiChat
228
+ ? [
229
+ await generateExamplesPlugin({
230
+ projectName: config.apiReference?.stainlessProject ?? undefined,
231
+ logger,
232
+ exampleOverrides: config.aiChat?.exampleOverrides,
233
+ }),
234
+ ]
235
+ : []),
227
236
  ],
228
237
  optimizeDeps: {
229
238
  include: config.aiChat
@@ -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