@stainless-api/docs 0.1.0-beta.136 → 0.1.0-beta.138
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 +20 -0
- package/eslint-suppressions.json +0 -5
- package/package.json +13 -13
- package/plugin/components/RequestBuilder/index.tsx +0 -3
- package/plugin/globalJs/ai-dropdown-options.ts +15 -7
- package/plugin/index.ts +5 -1
- package/plugin/loadPluginConfig.ts +1 -1
- package/plugin/specs/generateSpec.ts +1 -3
- package/plugin/vendor/preview.worker.docs.js +15523 -27699
- package/shared/virtualModule.ts +0 -17
- package/stl-docs/aiChatExamples.ts +11 -75
- package/stl-docs/chat/docs-chat-handler.ts +4 -5
- package/stl-docs/chat/hook.ts +19 -9
- package/stl-docs/chat/schemas.ts +0 -43
- package/stl-docs/chat/ui/AiChat.tsx +11 -24
- package/stl-docs/chat/ui/components/ChatLog.tsx +0 -3
- package/stl-docs/chat/ui/components/MessageFeedback.tsx +1 -4
- package/stl-docs/chat/ui/components/ToolCall.tsx +20 -20
- package/stl-docs/chat/ui/types.ts +2 -2
- package/stl-docs/components/AiChatIsland.tsx +2 -6
- package/stl-docs/components/PageFrame.astro +1 -5
- package/stl-docs/fonts.ts +4 -4
- package/stl-docs/index.ts +33 -32
- package/stl-docs/loadStlDocsConfig.ts +2 -2
- package/virtual-module.d.ts +6 -1
- package/stl-docs/chat/stainless-handler/index.ts +0 -126
- package/stl-docs/chat/stream-util.ts +0 -16
- package/stl-docs/proseSearchIndexing.ts +0 -218
package/shared/virtualModule.ts
CHANGED
|
@@ -10,23 +10,6 @@ export function buildVirtualModuleString<T extends Record<string, unknown>>(vars
|
|
|
10
10
|
.join('\n');
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function makeVirtualModPlugin(bareId: string, content: string): VitePlugin {
|
|
14
|
-
return {
|
|
15
|
-
name: `stl-virtual-module-loader-${bareId}`,
|
|
16
|
-
resolveId(id) {
|
|
17
|
-
// The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
|
|
18
|
-
if (id === bareId) {
|
|
19
|
-
return `\0${bareId}`;
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
load(id) {
|
|
23
|
-
if (id === `\0${bareId}`) {
|
|
24
|
-
return content;
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
13
|
export function makeAsyncVirtualModPlugin<T extends Record<string, unknown>>(
|
|
31
14
|
bareId: string,
|
|
32
15
|
contentLoader: () => Promise<T | string>,
|
|
@@ -1,95 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import z from 'zod';
|
|
3
|
-
import {
|
|
4
|
-
buildVirtualModuleString,
|
|
5
|
-
makeAsyncVirtualModPlugin,
|
|
6
|
-
makeVirtualModPlugin,
|
|
7
|
-
} from '../shared/virtualModule';
|
|
1
|
+
import { buildVirtualModuleString } from '../shared/virtualModule';
|
|
8
2
|
import type * as virtualExampleModule from 'virtual:stl-docs-ai-chat-examples';
|
|
9
3
|
type VirtualExampleModule = typeof virtualExampleModule;
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}),
|
|
17
|
-
);
|
|
18
|
-
export type ExamplePromptResponse = z.infer<typeof exampleSchema>;
|
|
5
|
+
export type ExamplePromptResponse = {
|
|
6
|
+
shortPrompt: string;
|
|
7
|
+
longPrompt: string;
|
|
8
|
+
icon: string;
|
|
9
|
+
}[];
|
|
19
10
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
);
|
|
11
|
+
export function generateExamplesVirtualModule(exampleOverrides: ExamplePromptResponse | undefined): string {
|
|
12
|
+
if (!exampleOverrides) {
|
|
13
|
+
return buildVirtualModuleString({ examples: undefined } satisfies VirtualExampleModule);
|
|
60
14
|
}
|
|
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
15
|
|
|
80
16
|
// Generate icon imports
|
|
81
17
|
// prettier-ignore
|
|
82
18
|
const pascalToKebab = (str: string) => str.split(/(?=[A-Z])/).join('-').toLowerCase();
|
|
83
19
|
const iconImportPath = (iconName: string) =>
|
|
84
20
|
import.meta.resolve(`lucide-react/dist/esm/icons/${pascalToKebab(iconName)}.js`);
|
|
85
|
-
const iconImports =
|
|
21
|
+
const iconImports = exampleOverrides.map(
|
|
86
22
|
({ icon }) => `import ${icon} from ${JSON.stringify(iconImportPath(icon))}`,
|
|
87
23
|
);
|
|
88
24
|
|
|
89
25
|
// Reference icon imports in `examples` exported object
|
|
90
26
|
// "icon":"Sparkles" -> "icon":Sparkles
|
|
91
27
|
const iconStringsToIdents = (jsonBlob: string) => jsonBlob.replace(/"icon":\s*"(\w+)"/g, '"icon":$1');
|
|
92
|
-
const exportBody = `export const examples = ${iconStringsToIdents(JSON.stringify(
|
|
28
|
+
const exportBody = `export const examples = ${iconStringsToIdents(JSON.stringify(exampleOverrides))};`;
|
|
93
29
|
|
|
94
30
|
return [...iconImports, exportBody].join('\n');
|
|
95
31
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ResponseChunk } from './schemas';
|
|
2
|
+
export { responseChunk, type ResponseChunk } from './schemas';
|
|
2
3
|
|
|
3
4
|
export type DocsChatHandler = {
|
|
4
5
|
generateResponse: (
|
|
@@ -7,12 +8,10 @@ export type DocsChatHandler = {
|
|
|
7
8
|
priorMessages,
|
|
8
9
|
}: {
|
|
9
10
|
query: string;
|
|
10
|
-
priorMessages:
|
|
11
|
+
priorMessages: { role: 'user' | 'assistant'; content: string }[];
|
|
11
12
|
},
|
|
12
13
|
abortSignal: AbortSignal,
|
|
13
14
|
) => AsyncGenerator<ResponseChunk>;
|
|
14
15
|
|
|
15
|
-
onRate
|
|
16
|
-
|
|
17
|
-
onAssignMetadata: (spanId: string, metadata: Record<string, string>) => Promise<MetadataResponseBody>;
|
|
16
|
+
onRate?: (spanId: string, score: 0 | 1) => Promise<unknown>;
|
|
18
17
|
};
|
package/stl-docs/chat/hook.ts
CHANGED
|
@@ -129,6 +129,7 @@ export function useChat({ handler }: { handler: DocsChatHandler }) {
|
|
|
129
129
|
|
|
130
130
|
try {
|
|
131
131
|
let chunk: ResponseChunk | undefined = undefined;
|
|
132
|
+
let sawDone = false;
|
|
132
133
|
for await (chunk of handler.generateResponse(
|
|
133
134
|
{
|
|
134
135
|
query: question,
|
|
@@ -147,6 +148,7 @@ export function useChat({ handler }: { handler: DocsChatHandler }) {
|
|
|
147
148
|
|
|
148
149
|
if (chunk.type === 'done') {
|
|
149
150
|
dispatch({ type: 'completeResponse', respondingTo: userMessageId, spanId: chunk.span_id });
|
|
151
|
+
sawDone = true;
|
|
150
152
|
// stop reading from the stream on done
|
|
151
153
|
break;
|
|
152
154
|
}
|
|
@@ -190,6 +192,12 @@ export function useChat({ handler }: { handler: DocsChatHandler }) {
|
|
|
190
192
|
respondingTo: userMessageId,
|
|
191
193
|
errorMessage: 'No response received. Please try again.',
|
|
192
194
|
});
|
|
195
|
+
} else if (!sawDone && !abortControllerRef.current.signal.aborted) {
|
|
196
|
+
// Generator exhausted without a `done` chunk — synthesize completion.
|
|
197
|
+
if (lastChunkType === 'text') {
|
|
198
|
+
dispatch({ type: 'completeMessage', id: currentResponseId });
|
|
199
|
+
}
|
|
200
|
+
dispatch({ type: 'completeResponse', respondingTo: userMessageId, spanId: crypto.randomUUID() });
|
|
193
201
|
}
|
|
194
202
|
} catch {
|
|
195
203
|
dispatch({
|
|
@@ -202,14 +210,16 @@ export function useChat({ handler }: { handler: DocsChatHandler }) {
|
|
|
202
210
|
[chatMessages, handler],
|
|
203
211
|
);
|
|
204
212
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
+
const rateMessage = handler.onRate
|
|
214
|
+
? async (spanId: string, rating: 'up' | 'down') => {
|
|
215
|
+
try {
|
|
216
|
+
await handler.onRate?.(spanId, { up: 1 as const, down: 0 as const }[rating]);
|
|
217
|
+
return true;
|
|
218
|
+
} catch {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
: undefined;
|
|
213
223
|
|
|
214
|
-
return { chatMessages, sendMessage, rateMessage
|
|
224
|
+
return { chatMessages, sendMessage, rateMessage };
|
|
215
225
|
}
|
package/stl-docs/chat/schemas.ts
CHANGED
|
@@ -1,38 +1,5 @@
|
|
|
1
1
|
import z from 'zod';
|
|
2
2
|
|
|
3
|
-
// TODO: replace with generated SDK types instead of copy/pasting from other repo
|
|
4
|
-
export const requestBody = z.object({
|
|
5
|
-
query: z.string(),
|
|
6
|
-
sdk: z.object({
|
|
7
|
-
project: z.string(),
|
|
8
|
-
language: z.string(),
|
|
9
|
-
version: z.string().optional(),
|
|
10
|
-
}),
|
|
11
|
-
stream: z.boolean().optional(),
|
|
12
|
-
budget: z
|
|
13
|
-
.object({
|
|
14
|
-
maxTokens: z.number().optional(),
|
|
15
|
-
})
|
|
16
|
-
.optional(),
|
|
17
|
-
session_id: z.string().optional(),
|
|
18
|
-
additionalContext: z
|
|
19
|
-
.object({
|
|
20
|
-
prior_messages: z.array(
|
|
21
|
-
z.object({
|
|
22
|
-
role: z.enum(['user', 'assistant']),
|
|
23
|
-
content: z.string(),
|
|
24
|
-
}),
|
|
25
|
-
),
|
|
26
|
-
code: z.string().optional(),
|
|
27
|
-
intent: z.string().optional(),
|
|
28
|
-
lsp: z.string().optional(),
|
|
29
|
-
errors: z.string().optional(),
|
|
30
|
-
})
|
|
31
|
-
.optional(),
|
|
32
|
-
browser_id: z.string().optional(),
|
|
33
|
-
});
|
|
34
|
-
export type RequestBody = z.input<typeof requestBody>;
|
|
35
|
-
|
|
36
3
|
export const responseChunk = z.discriminatedUnion('type', [
|
|
37
4
|
z.object({
|
|
38
5
|
type: z.literal('text'),
|
|
@@ -58,13 +25,3 @@ export const responseChunk = z.discriminatedUnion('type', [
|
|
|
58
25
|
}),
|
|
59
26
|
]);
|
|
60
27
|
export type ResponseChunk = z.infer<typeof responseChunk>;
|
|
61
|
-
|
|
62
|
-
export const feedbackRequestBody = z.object({ score: z.number().min(0).max(1) });
|
|
63
|
-
export type FeedbackRequestBody = z.infer<typeof feedbackRequestBody>;
|
|
64
|
-
export const feedbackResponseBody = z.object({ success: z.boolean() });
|
|
65
|
-
export type FeedbackResponseBody = z.infer<typeof feedbackResponseBody>;
|
|
66
|
-
|
|
67
|
-
export const metadataRequestBody = z.object({ metadata: z.record(z.string(), z.string()) });
|
|
68
|
-
export type MetadataRequestBody = z.infer<typeof metadataRequestBody>;
|
|
69
|
-
export const metadataResponseBody = z.object({ success: z.boolean() });
|
|
70
|
-
export type MetadataResponseBody = z.infer<typeof metadataResponseBody>;
|
|
@@ -2,13 +2,13 @@ import { motion } from 'motion/react';
|
|
|
2
2
|
import { Suspense, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
|
|
3
3
|
import { useScrollToBottom } from './scroll-manager';
|
|
4
4
|
import { useChat } from '../hook';
|
|
5
|
-
import {
|
|
5
|
+
import type { DocsChatHandler } from '../docs-chat-handler';
|
|
6
6
|
|
|
7
7
|
import ChatLog from './components/ChatLog';
|
|
8
8
|
import AiChatTrigger from './Trigger';
|
|
9
9
|
import ChatEmpty from './components/ChatEmpty';
|
|
10
10
|
import ChatControls from './components/ChatControls';
|
|
11
|
-
import {
|
|
11
|
+
import { AI_CHAT_HANDLER } from 'virtual:stl-docs-ai-chat';
|
|
12
12
|
|
|
13
13
|
import styles from './AiChat.module.css';
|
|
14
14
|
import clsx from 'clsx';
|
|
@@ -26,30 +26,18 @@ const examplesPromise = import('virtual:stl-docs-ai-chat-examples')
|
|
|
26
26
|
.then((mod) => mod.examples)
|
|
27
27
|
.catch(() => undefined);
|
|
28
28
|
|
|
29
|
-
export default function AiChat({
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
const handler = useMemo(
|
|
39
|
-
() => new StainlessHandler(currentLanguage ?? 'http', siteTitle, stainlessProject),
|
|
40
|
-
[currentLanguage, siteTitle, stainlessProject],
|
|
41
|
-
);
|
|
42
|
-
const { chatMessages, sendMessage, rateMessage, setMetadata } = useChat({
|
|
29
|
+
export default function AiChat({ siteTitle }: { siteTitle?: string }) {
|
|
30
|
+
if (!AI_CHAT_HANDLER) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return <AiChatInner siteTitle={siteTitle} handler={AI_CHAT_HANDLER} />;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function AiChatInner({ siteTitle, handler }: { siteTitle?: string; handler: DocsChatHandler }) {
|
|
37
|
+
const { chatMessages, sendMessage, rateMessage } = useChat({
|
|
43
38
|
handler,
|
|
44
39
|
});
|
|
45
40
|
|
|
46
|
-
const onCopyMessage = useCallback(
|
|
47
|
-
(spanId: string) => {
|
|
48
|
-
setMetadata(spanId, { copied_to_clipboard: 'true' }).catch(() => {});
|
|
49
|
-
},
|
|
50
|
-
[setMetadata],
|
|
51
|
-
);
|
|
52
|
-
|
|
53
41
|
// panel mode is supported only on larger viewports
|
|
54
42
|
const supportsPanel = useSyncExternalStore(
|
|
55
43
|
(cb) => {
|
|
@@ -168,7 +156,6 @@ export default function AiChat({
|
|
|
168
156
|
<ChatLog
|
|
169
157
|
messages={chatMessages}
|
|
170
158
|
rateMessage={rateMessage}
|
|
171
|
-
onCopyMessage={onCopyMessage}
|
|
172
159
|
responsePending={pendingResponses > 0}
|
|
173
160
|
/>
|
|
174
161
|
) : (
|
|
@@ -11,12 +11,10 @@ import { LoaderCircleIcon } from 'lucide-react';
|
|
|
11
11
|
export default function ChatLog({
|
|
12
12
|
messages,
|
|
13
13
|
rateMessage,
|
|
14
|
-
onCopyMessage,
|
|
15
14
|
responsePending = false,
|
|
16
15
|
}: {
|
|
17
16
|
messages: ChatMessage[];
|
|
18
17
|
rateMessage?: (spanId: string, rating: 'up' | 'down') => Promise<boolean>;
|
|
19
|
-
onCopyMessage?: (spanId: string) => void;
|
|
20
18
|
responsePending?: boolean;
|
|
21
19
|
}) {
|
|
22
20
|
const lastMessage = messages.at(-1);
|
|
@@ -57,7 +55,6 @@ export default function ChatLog({
|
|
|
57
55
|
key={msg.id}
|
|
58
56
|
spanId={msg.spanId}
|
|
59
57
|
rateMessage={rateMessage}
|
|
60
|
-
onCopyMessage={onCopyMessage}
|
|
61
58
|
// all "text" responses to the given message
|
|
62
59
|
messages={messages.flatMap((msg2) =>
|
|
63
60
|
msg2.role === 'assistant' &&
|
|
@@ -12,17 +12,14 @@ export default function MessageFeedbackButtons({
|
|
|
12
12
|
spanId,
|
|
13
13
|
messages,
|
|
14
14
|
rateMessage,
|
|
15
|
-
onCopyMessage,
|
|
16
15
|
}: {
|
|
17
16
|
spanId: string;
|
|
18
17
|
messages: AssistantTextMessage[];
|
|
19
18
|
rateMessage?: (spanId: string, rating: 'up' | 'down') => Promise<boolean>;
|
|
20
|
-
onCopyMessage?: (spanId: string) => void;
|
|
21
19
|
}) {
|
|
22
20
|
// Copy response as markdown
|
|
23
21
|
const [copied, setCopied] = useState(false);
|
|
24
22
|
const handleCopy = useCallback(() => {
|
|
25
|
-
onCopyMessage?.(spanId);
|
|
26
23
|
const combinedText = messages.map((msg) => msg.content).join('\n\n');
|
|
27
24
|
navigator.clipboard
|
|
28
25
|
.writeText(combinedText)
|
|
@@ -33,7 +30,7 @@ export default function MessageFeedbackButtons({
|
|
|
33
30
|
.catch(() => {
|
|
34
31
|
setCopied(false);
|
|
35
32
|
});
|
|
36
|
-
}, [messages
|
|
33
|
+
}, [messages]);
|
|
37
34
|
|
|
38
35
|
// Provide message rating
|
|
39
36
|
const [rating, setRating] = useState<'up' | 'down' | null>(null);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { motion } from 'motion/react';
|
|
2
|
-
import z from 'zod';
|
|
3
2
|
|
|
4
3
|
import type { AssistantToolCallMessage } from '../types';
|
|
5
4
|
|
|
@@ -11,24 +10,25 @@ export default function ToolCall({
|
|
|
11
10
|
}: {
|
|
12
11
|
message: Pick<AssistantToolCallMessage, 'id' | 'toolName' | 'input'>;
|
|
13
12
|
}) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (parsed.success) {
|
|
18
|
-
return (
|
|
19
|
-
<motion.li
|
|
20
|
-
layout="position"
|
|
21
|
-
data-message-role="assistant"
|
|
22
|
-
className={clsx(styles['chat-message'], styles['tool-use'])}
|
|
23
|
-
>
|
|
24
|
-
<p>
|
|
25
|
-
Fetching docs for <em>{parsed.data.query}</em>
|
|
26
|
-
</p>
|
|
27
|
-
</motion.li>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
13
|
+
const firstStringArg = message.input
|
|
14
|
+
? Object.values(message.input).find((v): v is string => typeof v === 'string')
|
|
15
|
+
: undefined;
|
|
31
16
|
|
|
32
|
-
|
|
33
|
-
|
|
17
|
+
return (
|
|
18
|
+
<motion.li
|
|
19
|
+
layout="position"
|
|
20
|
+
data-message-role="assistant"
|
|
21
|
+
className={clsx(styles['chat-message'], styles['tool-use'])}
|
|
22
|
+
>
|
|
23
|
+
<p>
|
|
24
|
+
Calling <code>{message.toolName}</code>
|
|
25
|
+
{firstStringArg && (
|
|
26
|
+
<>
|
|
27
|
+
{' '}
|
|
28
|
+
with <em>{firstStringArg}</em>
|
|
29
|
+
</>
|
|
30
|
+
)}
|
|
31
|
+
</p>
|
|
32
|
+
</motion.li>
|
|
33
|
+
);
|
|
34
34
|
}
|
|
@@ -18,13 +18,13 @@ export type AssistantToolCallMessage = BaseMessage & {
|
|
|
18
18
|
toolName: string;
|
|
19
19
|
input: Record<string, unknown> | undefined;
|
|
20
20
|
};
|
|
21
|
-
|
|
21
|
+
type AssistantDoneMessage = BaseMessage & {
|
|
22
22
|
role: 'assistant';
|
|
23
23
|
respondingTo: string;
|
|
24
24
|
messageType: 'done';
|
|
25
25
|
spanId: string;
|
|
26
26
|
};
|
|
27
|
-
|
|
27
|
+
type AssistantErrorMessage = BaseMessage & {
|
|
28
28
|
role: 'assistant';
|
|
29
29
|
respondingTo: string;
|
|
30
30
|
messageType: 'error';
|
|
@@ -4,13 +4,9 @@
|
|
|
4
4
|
// This conditional can’t be inlined into PageFrame because it breaks Astro’s static analysis of imports of client islands
|
|
5
5
|
const AiChat = __STLDOCS_ENABLE_AI_CHAT__ ? (await import('../chat/ui/AiChat')).default : null;
|
|
6
6
|
|
|
7
|
-
const STAINLESS_PROJECT = __STLDOCS_HAS_API_REFERENCE__
|
|
8
|
-
? (await import('virtual:stl-starlight-virtual-module')).STAINLESS_PROJECT
|
|
9
|
-
: undefined;
|
|
10
|
-
|
|
11
7
|
export default function DocsChatLazy(
|
|
12
8
|
props: Omit<React.ComponentProps<NonNullable<typeof AiChat>>, 'stainlessProject'>,
|
|
13
9
|
) {
|
|
14
|
-
if (!AiChat
|
|
15
|
-
return <AiChat {...props}
|
|
10
|
+
if (!AiChat) return null;
|
|
11
|
+
return <AiChat {...props} />;
|
|
16
12
|
}
|
|
@@ -29,9 +29,5 @@ const { hasSidebar } = Astro.locals.starlightRoute;
|
|
|
29
29
|
|
|
30
30
|
<slot />
|
|
31
31
|
|
|
32
|
-
{
|
|
33
|
-
__STLDOCS_ENABLE_AI_CHAT__ && (
|
|
34
|
-
<AiChatIsland client:load currentLanguage={Astro.locals.language} siteTitle={siteTitle} />
|
|
35
|
-
)
|
|
36
|
-
}
|
|
32
|
+
{__STLDOCS_ENABLE_AI_CHAT__ && <AiChatIsland client:load siteTitle={siteTitle} />}
|
|
37
33
|
</div>
|
package/stl-docs/fonts.ts
CHANGED
|
@@ -7,7 +7,7 @@ type AstroFontConfigEntry = Defined<AstroConfig['fonts']>[number];
|
|
|
7
7
|
|
|
8
8
|
// Apply Omit to each member of the union while preserving union structure
|
|
9
9
|
type PreloadFilter = { preload?: FontPreloadFilter };
|
|
10
|
-
|
|
10
|
+
type StlDocsFontConfigEntry = (AstroFontConfigEntry extends infer T
|
|
11
11
|
? T extends unknown
|
|
12
12
|
? Omit<T, 'cssVariable'>
|
|
13
13
|
: never
|
|
@@ -165,19 +165,19 @@ export function flattenFonts(fonts: StlDocsFontConfig | undefined): AstroFontCon
|
|
|
165
165
|
fontConfigs.push({
|
|
166
166
|
...fonts.primary,
|
|
167
167
|
cssVariable: '--stl-typography-font' as const,
|
|
168
|
-
}
|
|
168
|
+
});
|
|
169
169
|
}
|
|
170
170
|
if (fonts.heading) {
|
|
171
171
|
fontConfigs.push({
|
|
172
172
|
...fonts.heading,
|
|
173
173
|
cssVariable: '--stl-typography-font-heading' as const,
|
|
174
|
-
}
|
|
174
|
+
});
|
|
175
175
|
}
|
|
176
176
|
if (fonts.mono) {
|
|
177
177
|
fontConfigs.push({
|
|
178
178
|
...fonts.mono,
|
|
179
179
|
cssVariable: '--stl-typography-font-mono' as const,
|
|
180
|
-
}
|
|
180
|
+
});
|
|
181
181
|
}
|
|
182
182
|
if (fonts.additional) {
|
|
183
183
|
fontConfigs.push(...fonts.additional.map((font) => font as AstroFontConfigEntry));
|
package/stl-docs/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { AstroIntegration } from 'astro';
|
|
|
7
7
|
|
|
8
8
|
import { normalizeRedirects, type NormalizedRedirectConfig } from './redirects';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
10
11
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
11
12
|
import {
|
|
12
13
|
parseStlDocsConfig,
|
|
@@ -17,16 +18,14 @@ import {
|
|
|
17
18
|
type StarlightSidebarConfig,
|
|
18
19
|
} from './loadStlDocsConfig';
|
|
19
20
|
import { buildVirtualModuleString } from '../shared/virtualModule';
|
|
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 {
|
|
23
|
+
import { setSharedLogger } from '../shared/getSharedLogger';
|
|
24
24
|
import { stainlessDocsVectorProseIndexing } from './proseDocSync';
|
|
25
|
-
import { stainlessDocsAlgoliaProseIndexing } from './proseSearchIndexing';
|
|
26
25
|
import { stainlessStarlight } from '../plugin';
|
|
27
26
|
import { getFontRoles, flattenFonts } from './fonts';
|
|
28
27
|
import conditionalIntegration from '../shared/conditionalIntegration';
|
|
29
|
-
import
|
|
28
|
+
import { generateExamplesVirtualModule } from './aiChatExamples';
|
|
30
29
|
import { ogImageStarlightPlugin } from './og-image';
|
|
31
30
|
|
|
32
31
|
export * from '../plugin';
|
|
@@ -172,8 +171,7 @@ function stainlessDocsIntegration(
|
|
|
172
171
|
return {
|
|
173
172
|
name: 'stl-docs-astro',
|
|
174
173
|
hooks: {
|
|
175
|
-
'astro:config:setup':
|
|
176
|
-
const logger = getSharedLogger({ fallback: localLogger });
|
|
174
|
+
'astro:config:setup': ({ updateConfig, command, config: astroConfig }) => {
|
|
177
175
|
// we only handle redirects for builds
|
|
178
176
|
// in dev, Astro handles them for us
|
|
179
177
|
if (command === 'build' && astroConfig.redirects) {
|
|
@@ -184,9 +182,21 @@ function stainlessDocsIntegration(
|
|
|
184
182
|
const withBase = (link: string) =>
|
|
185
183
|
/^([a-z][a-z0-9+.-]*:|\/\/)/.test(link) ? link : path.posix.join(base, link);
|
|
186
184
|
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
185
|
+
let vmAiChatHandlerExport = 'export const AI_CHAT_HANDLER = undefined;';
|
|
186
|
+
if (config.aiChat?.handlerEntrypoint) {
|
|
187
|
+
const rawEntrypoint = config.aiChat.handlerEntrypoint;
|
|
188
|
+
const handlerEntrypoint = rawEntrypoint.startsWith('file://')
|
|
189
|
+
? fileURLToPath(rawEntrypoint)
|
|
190
|
+
: path.isAbsolute(rawEntrypoint)
|
|
191
|
+
? rawEntrypoint
|
|
192
|
+
: path.resolve(fileURLToPath(astroConfig.root), rawEntrypoint);
|
|
193
|
+
vmAiChatHandlerExport = `export { default as AI_CHAT_HANDLER } from '${handlerEntrypoint}';`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const virtualModules = new Map<string, string>([
|
|
197
|
+
[
|
|
198
|
+
'virtual:stl-docs-virtual-module',
|
|
199
|
+
buildVirtualModuleString({
|
|
190
200
|
TABS: config.tabs.map((tab) => ({ ...tab, link: withBase(tab.link) })),
|
|
191
201
|
SPLIT_TABS_ENABLED: config.splitTabsEnabled,
|
|
192
202
|
HEADER_LINKS: config.header.links.map((link) => ({ ...link, link: withBase(link.link) })),
|
|
@@ -194,16 +204,24 @@ function stainlessDocsIntegration(
|
|
|
194
204
|
ENABLE_CLIENT_ROUTER: config.enableClientRouter,
|
|
195
205
|
API_REFERENCE_BASE_PATH: apiReferenceBasePath ?? '/api',
|
|
196
206
|
ENABLE_PROSE_MARKDOWN_RENDERING: config.enableProseMarkdownRendering,
|
|
197
|
-
ENABLE_CONTEXT_MENU: config.contextMenu, // TODO: do not duplicate this between both virtual modules
|
|
207
|
+
ENABLE_CONTEXT_MENU: !!config.contextMenu, // TODO: do not duplicate this between both virtual modules
|
|
208
|
+
CONTEXT_MENU_ENABLE_THIRD_PARTY:
|
|
209
|
+
(typeof config.contextMenu === 'object' ? config.contextMenu.thirdParty : null) ?? true,
|
|
198
210
|
RENDER_PAGE_DESCRIPTIONS: config.renderPageDescriptions,
|
|
199
211
|
FONTS: getFontRoles(config.fonts),
|
|
200
212
|
LINK_GROUP_TITLES_TO_OVERVIEW_PAGES: config.linkGroupTitlesToOverviewPages,
|
|
201
213
|
RENDER_CREDITS: config.credits,
|
|
202
214
|
SITE_TITLE: config.siteTitle,
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
);
|
|
215
|
+
}),
|
|
216
|
+
],
|
|
217
|
+
['virtual:stl-docs-ai-chat', vmAiChatHandlerExport],
|
|
218
|
+
]);
|
|
219
|
+
if (config.aiChat) {
|
|
220
|
+
virtualModules.set(
|
|
221
|
+
'virtual:stl-docs-ai-chat-examples',
|
|
222
|
+
generateExamplesVirtualModule(config.aiChat.examples),
|
|
223
|
+
);
|
|
224
|
+
}
|
|
207
225
|
|
|
208
226
|
updateConfig({
|
|
209
227
|
fonts: [...flattenFonts(config.fonts), ...(astroConfig?.fonts ?? [])],
|
|
@@ -216,7 +234,7 @@ function stainlessDocsIntegration(
|
|
|
216
234
|
{
|
|
217
235
|
name: 'stl-docs-virtual-modules',
|
|
218
236
|
resolveId(id) {
|
|
219
|
-
// The '\0' prefix tells Vite
|
|
237
|
+
// The '\0' prefix tells Vite "this is a virtual module" and prevents it from being resolved again.
|
|
220
238
|
if (virtualModules.has(id)) return `\0${id}`;
|
|
221
239
|
},
|
|
222
240
|
load(id) {
|
|
@@ -224,18 +242,6 @@ function stainlessDocsIntegration(
|
|
|
224
242
|
if (virtualModules.has(bare)) return virtualModules.get(bare);
|
|
225
243
|
},
|
|
226
244
|
},
|
|
227
|
-
// Separate plugin for the examples because it has async resolution; not a simple string
|
|
228
|
-
// like the above plugins
|
|
229
|
-
...(config.aiChat
|
|
230
|
-
? [
|
|
231
|
-
await generateExamplesPlugin({
|
|
232
|
-
projectName: config.apiReference?.stainlessProject ?? undefined,
|
|
233
|
-
logger,
|
|
234
|
-
exampleOverrides:
|
|
235
|
-
typeof config.aiChat === 'object' ? config.aiChat.exampleOverrides : undefined,
|
|
236
|
-
}),
|
|
237
|
-
]
|
|
238
|
-
: []),
|
|
239
245
|
],
|
|
240
246
|
},
|
|
241
247
|
build: {
|
|
@@ -297,11 +303,6 @@ export function stainlessDocs(config: StainlessDocsUserConfig): AstroIntegration
|
|
|
297
303
|
integration: stainlessDocsMarkdownRenderer({ apiReferenceBasePath }),
|
|
298
304
|
reason: 'disabled by experimental config "disableProseMarkdownRendering"',
|
|
299
305
|
}),
|
|
300
|
-
conditionalIntegration({
|
|
301
|
-
condition: !config.experimental?.disableStainlessProseIndexing,
|
|
302
|
-
integration: stainlessDocsAlgoliaProseIndexing({ apiReferenceBasePath }),
|
|
303
|
-
reason: 'disabled by experimental config "disableStainlessProseIndexing"',
|
|
304
|
-
}),
|
|
305
306
|
conditionalIntegration({
|
|
306
307
|
condition: !config.experimental?.disableStainlessProseIndexing,
|
|
307
308
|
integration: stainlessDocsVectorProseIndexing(normalizedConfig, apiReferenceBasePath),
|
|
@@ -81,7 +81,7 @@ export type StainlessDocsUserConfig = {
|
|
|
81
81
|
*/
|
|
82
82
|
disableProseMarkdownRendering?: boolean;
|
|
83
83
|
disableStainlessProseIndexing?: boolean;
|
|
84
|
-
aiChat?: {
|
|
84
|
+
aiChat?: { handlerEntrypoint: string; examples?: ExamplePromptResponse };
|
|
85
85
|
/**
|
|
86
86
|
* Whether to link group titles to overview pages. Note: overview pages must already be present in the sidebar for this to work.
|
|
87
87
|
*
|
|
@@ -94,7 +94,7 @@ export type StainlessDocsUserConfig = {
|
|
|
94
94
|
*
|
|
95
95
|
* @default true
|
|
96
96
|
*/
|
|
97
|
-
contextMenu?: boolean;
|
|
97
|
+
contextMenu?: boolean | { thirdParty?: boolean };
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
100
|
* Whether to render page descriptions in prose page headers
|