@stainless-api/docs 0.1.0-beta.13 → 0.1.0-beta.131
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 +1110 -0
- package/ambient.d.ts +6 -0
- package/eslint-suppressions.json +90 -0
- package/{eslint.config.js → eslint.config.ts} +0 -2
- package/locals.d.ts +17 -0
- package/package.json +62 -44
- package/playground-virtual-modules.d.ts +96 -0
- package/plugin/assets/languages/cli.svg +14 -0
- package/plugin/assets/languages/csharp.svg +1 -0
- package/plugin/assets/languages/php.svg +4 -0
- package/plugin/buildAlgoliaIndex.ts +40 -39
- package/plugin/components/MethodDescription.tsx +54 -0
- package/plugin/components/RequestBuilder/ParamEditor.tsx +55 -0
- package/plugin/components/RequestBuilder/SnippetStainlessIsland.tsx +107 -0
- package/plugin/components/RequestBuilder/index.tsx +40 -0
- package/plugin/components/RequestBuilder/props.ts +9 -0
- package/plugin/components/RequestBuilder/spec-helpers.ts +47 -0
- package/plugin/components/RequestBuilder/styles.css +67 -0
- package/plugin/components/SDKSelect.astro +18 -111
- package/plugin/components/SnippetCode.tsx +112 -70
- package/plugin/components/StainlessIslands.tsx +126 -0
- package/plugin/components/search/SearchAlgolia.astro +46 -29
- package/plugin/components/search/SearchIsland.tsx +61 -37
- package/plugin/generateAPIReferenceLink.ts +0 -40
- package/plugin/globalJs/ai-dropdown-options.ts +248 -0
- package/plugin/globalJs/code-snippets.ts +45 -16
- package/plugin/globalJs/copy.ts +115 -27
- package/plugin/globalJs/create-playground.shim.ts +3 -0
- package/plugin/globalJs/method-descriptions.ts +33 -0
- package/plugin/globalJs/navigation.ts +24 -44
- package/plugin/globalJs/playground-data.shim.ts +1 -0
- package/plugin/globalJs/playground-data.ts +14 -0
- package/plugin/globalJs/summary-selection-tweak.ts +29 -0
- package/plugin/helpers/generateDocsRoutes.ts +59 -0
- package/plugin/helpers/multiSpec.ts +8 -0
- package/plugin/index.ts +317 -141
- package/plugin/languages.ts +8 -2
- package/plugin/loadPluginConfig.ts +284 -109
- package/plugin/markdown/highlighter.ts +100 -0
- package/plugin/markdown/index.ts +39 -0
- package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +3 -1
- package/plugin/react/Routing.tsx +98 -263
- package/plugin/referencePlaceholderUtils.ts +17 -14
- package/plugin/replaceSidebarPlaceholderMiddleware.ts +39 -35
- package/plugin/routes/Docs.astro +72 -111
- package/plugin/routes/DocsStatic.astro +6 -5
- package/plugin/routes/Overview.astro +46 -22
- package/plugin/routes/llms.ts +186 -0
- package/plugin/routes/markdown.ts +13 -12
- package/plugin/{cms → sidebar-utils}/sidebar-builder.ts +84 -69
- package/plugin/specs/FileCache.ts +99 -0
- package/plugin/specs/fetchSpecSSR.ts +27 -0
- package/plugin/specs/generateSpec.ts +112 -0
- package/plugin/specs/index.ts +132 -0
- package/plugin/specs/inputResolver.ts +148 -0
- package/plugin/{cms → specs}/worker.ts +82 -5
- package/plugin/vendor/preview.worker.docs.js +27121 -16890
- package/plugin/vendor/templates/cli.md +1 -0
- package/plugin/vendor/templates/go.md +4 -2
- package/plugin/vendor/templates/java.md +5 -1
- package/plugin/vendor/templates/kotlin.md +5 -1
- package/plugin/vendor/templates/node.md +4 -2
- package/plugin/vendor/templates/python.md +4 -2
- package/plugin/vendor/templates/ruby.md +4 -2
- package/plugin/vendor/templates/terraform.md +1 -1
- package/plugin/vendor/templates/typescript.md +3 -1
- package/resolveSrcFile.ts +10 -0
- package/scripts/vendor_deps.ts +5 -5
- package/shared/conditionalIntegration.ts +28 -0
- package/shared/getProsePages.ts +41 -0
- package/shared/getSharedLogger.ts +15 -0
- package/shared/terminalUtils.ts +3 -0
- package/shared/virtualModule.ts +46 -1
- package/src/content.config.ts +9 -0
- package/stl-docs/aiChatExamples.ts +95 -0
- package/stl-docs/chat/docs-chat-handler.ts +18 -0
- package/stl-docs/chat/hook.ts +215 -0
- package/stl-docs/chat/schemas.ts +70 -0
- package/stl-docs/chat/stainless-handler/index.ts +126 -0
- package/stl-docs/chat/stream-util.ts +16 -0
- package/stl-docs/chat/ui/AiChat.module.css +591 -0
- package/stl-docs/chat/ui/AiChat.tsx +188 -0
- package/stl-docs/chat/ui/Trigger.tsx +154 -0
- package/stl-docs/chat/ui/components/ChatControls.tsx +51 -0
- package/stl-docs/chat/ui/components/ChatEmpty.tsx +42 -0
- package/stl-docs/chat/ui/components/ChatLog.tsx +96 -0
- package/stl-docs/chat/ui/components/ChatMessage.tsx +47 -0
- package/stl-docs/chat/ui/components/CodeBlock.tsx +33 -0
- package/stl-docs/chat/ui/components/MessageFeedback.tsx +109 -0
- package/stl-docs/chat/ui/components/Table.tsx +15 -0
- package/stl-docs/chat/ui/components/ToolCall.tsx +34 -0
- package/stl-docs/chat/ui/components/hljs-github.css +81 -0
- package/stl-docs/chat/ui/scroll-manager.ts +86 -0
- package/stl-docs/chat/ui/types.ts +45 -0
- package/stl-docs/components/AIDropdown.tsx +63 -0
- package/stl-docs/components/AiChatIsland.tsx +16 -0
- package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +2 -2
- package/stl-docs/components/ContentPanel.astro +9 -0
- package/stl-docs/components/Footer.astro +89 -0
- package/stl-docs/components/Head.astro +20 -0
- package/stl-docs/components/Header.astro +3 -9
- package/stl-docs/components/PageFrame.astro +37 -0
- package/stl-docs/components/PageSidebar.astro +11 -0
- package/stl-docs/components/PageTitle.astro +82 -0
- package/stl-docs/components/StainlessLogo.svg +4 -0
- package/stl-docs/components/ThemeProvider.astro +36 -0
- package/stl-docs/components/ThemeSelect.astro +84 -146
- package/stl-docs/components/TwoColumnContent.astro +2 -0
- package/stl-docs/components/headers/DefaultHeader.astro +6 -8
- package/stl-docs/components/headers/StackedHeader.astro +10 -53
- package/stl-docs/components/icons/chat-gpt.tsx +2 -2
- package/stl-docs/components/icons/cursor.tsx +10 -0
- package/stl-docs/components/icons/gemini.tsx +19 -0
- package/stl-docs/components/icons/markdown.tsx +1 -1
- package/stl-docs/components/index.ts +1 -0
- package/stl-docs/components/mintlify-compat/Accordion.astro +2 -2
- package/stl-docs/components/mintlify-compat/AccordionGroup.astro +0 -4
- package/stl-docs/components/mintlify-compat/Columns.astro +2 -2
- package/stl-docs/components/mintlify-compat/Frame.astro +6 -6
- package/stl-docs/components/mintlify-compat/Tab.astro +2 -2
- package/stl-docs/components/mintlify-compat/callouts/Callout.astro +2 -2
- package/stl-docs/components/mintlify-compat/callouts/Check.astro +0 -4
- package/stl-docs/components/mintlify-compat/callouts/Danger.astro +0 -4
- package/stl-docs/components/mintlify-compat/callouts/Info.astro +0 -4
- package/stl-docs/components/mintlify-compat/callouts/Note.astro +0 -4
- package/stl-docs/components/mintlify-compat/callouts/Tip.astro +0 -4
- package/stl-docs/components/mintlify-compat/callouts/Warning.astro +0 -4
- package/stl-docs/components/mintlify-compat/card.css +4 -4
- package/stl-docs/components/mintlify-compat/index.ts +2 -4
- package/stl-docs/components/nav-tabs/NavDropdown.astro +38 -77
- package/stl-docs/components/nav-tabs/NavTabs.astro +81 -81
- package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +1 -2
- package/stl-docs/components/nav-tabs/buildNavLinks.ts +5 -2
- package/stl-docs/components/pagination/HomeLink.astro +10 -0
- package/stl-docs/components/pagination/Pagination.astro +177 -0
- package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +22 -0
- package/stl-docs/components/pagination/PaginationLinkQuiet.astro +13 -0
- package/stl-docs/components/pagination/util.ts +71 -0
- package/stl-docs/components/scripts.ts +1 -0
- package/stl-docs/components/sidebars/BaseSidebar.astro +80 -2
- package/stl-docs/components/sidebars/SidebarWithComponents.tsx +10 -0
- package/stl-docs/components/sidebars/convertAstroSidebarToStl.tsx +62 -0
- package/stl-docs/disableCalloutSyntax.ts +36 -0
- package/stl-docs/fonts.ts +186 -0
- package/stl-docs/index.ts +176 -58
- package/stl-docs/loadStlDocsConfig.ts +73 -8
- package/stl-docs/proseDocSync.test.ts +74 -0
- package/stl-docs/proseDocSync.ts +344 -0
- package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +53 -0
- package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +41 -0
- package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
- package/stl-docs/proseSearchIndexing.ts +218 -0
- package/stl-docs/tabsMiddleware.ts +14 -5
- package/styles/code.css +53 -49
- package/styles/links.css +2 -37
- package/styles/method-descriptions.css +36 -0
- package/styles/overrides.css +28 -46
- package/styles/page.css +228 -38
- package/styles/sdk_select.css +9 -6
- package/styles/search.css +11 -21
- package/styles/sidebar.css +28 -215
- package/styles/{variables.css → sl-variables.css} +4 -8
- package/styles/stldocs-variables.css +6 -0
- package/styles/toc.css +19 -8
- package/theme.css +11 -9
- package/tsconfig.json +1 -4
- package/virtual-module.d.ts +66 -8
- package/components/variables.css +0 -112
- package/plugin/cms/client.ts +0 -62
- package/plugin/cms/server.ts +0 -268
- package/plugin/globalJs/ai-dropdown.ts +0 -57
- package/stl-docs/components/APIReferenceAIDropdown.tsx +0 -58
- package/stl-docs/components/ClientRouterHead.astro +0 -41
- package/stl-docs/components/content-panel/ContentPanel.astro +0 -69
- package/stl-docs/components/content-panel/ProseAIDropdown.tsx +0 -55
- package/stl-docs/components/headers/SplashMobileMenuToggle.astro +0 -49
- package/stl-docs/components/mintlify-compat/Step.astro +0 -56
- package/stl-docs/components/mintlify-compat/Steps.astro +0 -15
- package/styles/fonts.css +0 -68
- /package/{plugin/assets → assets}/fonts/geist/OFL.txt +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-latin.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin.woff2 +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { CheckIcon, CopyIcon, ThumbsDownIcon, ThumbsUpIcon } from 'lucide-react';
|
|
2
|
+
import { motion } from 'motion/react';
|
|
3
|
+
import { startTransition, useCallback, useOptimistic, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import type { AssistantTextMessage } from '../types';
|
|
6
|
+
|
|
7
|
+
import { Button } from '@stainless-api/ui-primitives';
|
|
8
|
+
import clsx from 'clsx';
|
|
9
|
+
import styles from '../AiChat.module.css';
|
|
10
|
+
|
|
11
|
+
export default function MessageFeedbackButtons({
|
|
12
|
+
spanId,
|
|
13
|
+
messages,
|
|
14
|
+
rateMessage,
|
|
15
|
+
onCopyMessage,
|
|
16
|
+
}: {
|
|
17
|
+
spanId: string;
|
|
18
|
+
messages: AssistantTextMessage[];
|
|
19
|
+
rateMessage?: (spanId: string, rating: 'up' | 'down') => Promise<boolean>;
|
|
20
|
+
onCopyMessage?: (spanId: string) => void;
|
|
21
|
+
}) {
|
|
22
|
+
// Copy response as markdown
|
|
23
|
+
const [copied, setCopied] = useState(false);
|
|
24
|
+
const handleCopy = useCallback(() => {
|
|
25
|
+
onCopyMessage?.(spanId);
|
|
26
|
+
const combinedText = messages.map((msg) => msg.content).join('\n\n');
|
|
27
|
+
navigator.clipboard
|
|
28
|
+
.writeText(combinedText)
|
|
29
|
+
.then(() => {
|
|
30
|
+
setCopied(true);
|
|
31
|
+
setTimeout(() => setCopied(false), 2000);
|
|
32
|
+
})
|
|
33
|
+
.catch(() => {
|
|
34
|
+
setCopied(false);
|
|
35
|
+
});
|
|
36
|
+
}, [messages, spanId, onCopyMessage]);
|
|
37
|
+
|
|
38
|
+
// Provide message rating
|
|
39
|
+
const [rating, setRating] = useState<'up' | 'down' | null>(null);
|
|
40
|
+
const [optimisticRating, setOptimisticRating] = useOptimistic(
|
|
41
|
+
rating,
|
|
42
|
+
(current, newRating: NonNullable<typeof rating>) => newRating,
|
|
43
|
+
);
|
|
44
|
+
const handleRate = useCallback(
|
|
45
|
+
(newRating: 'up' | 'down') => {
|
|
46
|
+
if (!rateMessage) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
startTransition(async () => {
|
|
51
|
+
setOptimisticRating(newRating);
|
|
52
|
+
const success = await rateMessage(spanId, newRating).catch(() => false);
|
|
53
|
+
if (success) setRating(newRating);
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
[spanId, setOptimisticRating, rateMessage],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<motion.li
|
|
61
|
+
layout="position"
|
|
62
|
+
data-message-role="assistant"
|
|
63
|
+
className={clsx(styles['chat-message'], styles['feedback-buttons'])}
|
|
64
|
+
>
|
|
65
|
+
{rateMessage != null && (
|
|
66
|
+
<>
|
|
67
|
+
<Button
|
|
68
|
+
type="button"
|
|
69
|
+
variant="ghost"
|
|
70
|
+
size="sm"
|
|
71
|
+
onClick={() => handleRate('up')}
|
|
72
|
+
className={clsx(optimisticRating === 'up' && styles.active)}
|
|
73
|
+
aria-label="Thumbs up"
|
|
74
|
+
>
|
|
75
|
+
<Button.Icon icon={ThumbsUpIcon} size={15} />
|
|
76
|
+
</Button>
|
|
77
|
+
|
|
78
|
+
<Button
|
|
79
|
+
type="button"
|
|
80
|
+
variant="ghost"
|
|
81
|
+
size="sm"
|
|
82
|
+
onClick={() => handleRate('down')}
|
|
83
|
+
className={clsx(optimisticRating === 'down' && styles.active)}
|
|
84
|
+
aria-label="Thumbs down"
|
|
85
|
+
>
|
|
86
|
+
<Button.Icon icon={ThumbsDownIcon} size={15} />
|
|
87
|
+
</Button>
|
|
88
|
+
</>
|
|
89
|
+
)}
|
|
90
|
+
|
|
91
|
+
{messages.length > 0 && messages.some((msg) => msg.content.trim().length) && (
|
|
92
|
+
<Button
|
|
93
|
+
type="button"
|
|
94
|
+
variant="ghost"
|
|
95
|
+
size="sm"
|
|
96
|
+
onClick={handleCopy}
|
|
97
|
+
className={clsx(copied && styles.active)}
|
|
98
|
+
aria-label={copied ? 'Copied' : 'Copy response'}
|
|
99
|
+
>
|
|
100
|
+
<Button.Icon
|
|
101
|
+
// TODO: nicer cross-fade transition
|
|
102
|
+
icon={copied ? CheckIcon : CopyIcon}
|
|
103
|
+
size={15}
|
|
104
|
+
/>
|
|
105
|
+
</Button>
|
|
106
|
+
)}
|
|
107
|
+
</motion.li>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import type { Components } from 'react-markdown';
|
|
3
|
+
import styles from '../AiChat.module.css';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
table(props) {
|
|
7
|
+
const { children, ...rest } = props;
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div className={clsx(styles['chat-table'])}>
|
|
11
|
+
<table {...rest}>{children}</table>
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
},
|
|
15
|
+
} satisfies Components;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { motion } from 'motion/react';
|
|
2
|
+
import z from 'zod';
|
|
3
|
+
|
|
4
|
+
import type { AssistantToolCallMessage } from '../types';
|
|
5
|
+
|
|
6
|
+
import clsx from 'clsx';
|
|
7
|
+
import styles from '../AiChat.module.css';
|
|
8
|
+
|
|
9
|
+
export default function ToolCall({
|
|
10
|
+
message,
|
|
11
|
+
}: {
|
|
12
|
+
message: Pick<AssistantToolCallMessage, 'id' | 'toolName' | 'input'>;
|
|
13
|
+
}) {
|
|
14
|
+
// Render docs searches
|
|
15
|
+
if (message.toolName.endsWith('search_docs')) {
|
|
16
|
+
const parsed = z.object({ query: z.string() }).safeParse(message.input);
|
|
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
|
+
}
|
|
31
|
+
|
|
32
|
+
// No other tool renderers yet
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
.hljs-github {
|
|
2
|
+
/* color: light-dark(#24292e, #c9d1d9); */
|
|
3
|
+
/* background: light-dark(#ffffff, #0d1117); */
|
|
4
|
+
|
|
5
|
+
.hljs-doctag,
|
|
6
|
+
.hljs-keyword,
|
|
7
|
+
.hljs-meta .hljs-keyword,
|
|
8
|
+
.hljs-template-tag,
|
|
9
|
+
.hljs-template-variable,
|
|
10
|
+
.hljs-type,
|
|
11
|
+
.hljs-variable.language_ {
|
|
12
|
+
color: light-dark(#d73a49, #ff7b72);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.hljs-title,
|
|
16
|
+
.hljs-title.class_,
|
|
17
|
+
.hljs-title.class_.inherited__,
|
|
18
|
+
.hljs-title.function_ {
|
|
19
|
+
color: light-dark(#6f42c1, #d2a8ff);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.hljs-attr,
|
|
23
|
+
.hljs-attribute,
|
|
24
|
+
.hljs-literal,
|
|
25
|
+
.hljs-meta,
|
|
26
|
+
.hljs-number,
|
|
27
|
+
.hljs-operator,
|
|
28
|
+
.hljs-variable,
|
|
29
|
+
.hljs-selector-attr,
|
|
30
|
+
.hljs-selector-class,
|
|
31
|
+
.hljs-selector-id {
|
|
32
|
+
color: light-dark(#005cc5, #79c0ff);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.hljs-regexp,
|
|
36
|
+
.hljs-string,
|
|
37
|
+
.hljs-meta .hljs-string {
|
|
38
|
+
color: light-dark(#032f62, #a5d6ff);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.hljs-built_in,
|
|
42
|
+
.hljs-symbol {
|
|
43
|
+
color: light-dark(#e36209, #ffa657);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.hljs-comment,
|
|
47
|
+
.hljs-code,
|
|
48
|
+
.hljs-formula {
|
|
49
|
+
color: light-dark(#6a737d, #8b949e);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.hljs-name,
|
|
53
|
+
.hljs-quote,
|
|
54
|
+
.hljs-selector-tag,
|
|
55
|
+
.hljs-selector-pseudo {
|
|
56
|
+
color: light-dark(#22863a, #7ee787);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.hljs-subst {
|
|
60
|
+
color: light-dark(#24292e, #c9d1d9);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.hljs-section {
|
|
64
|
+
color: light-dark(#005cc5, #1f6feb);
|
|
65
|
+
font-weight: bold;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.hljs-bullet {
|
|
69
|
+
color: light-dark(#735c0f, #f2cc60);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.hljs-emphasis {
|
|
73
|
+
color: light-dark(#24292e, #c9d1d9);
|
|
74
|
+
font-style: italic;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.hljs-strong {
|
|
78
|
+
color: light-dark(#24292e, #c9d1d9);
|
|
79
|
+
font-weight: bold;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
const THRESHOLD_FROM_BOTTOM = 32; // px
|
|
4
|
+
|
|
5
|
+
/** Keep chat area scrolled to bottom */
|
|
6
|
+
export function useScrollToBottom(
|
|
7
|
+
scrollAreaRef: React.RefObject<HTMLDivElement | null>,
|
|
8
|
+
scrollContentsRef: React.RefObject<HTMLDivElement | null>,
|
|
9
|
+
watchValue: unknown,
|
|
10
|
+
) {
|
|
11
|
+
/**
|
|
12
|
+
* Set `overflow` based on whether content is actually overflowing.
|
|
13
|
+
* - The reason this behavior is different than `overflow: auto` is that `overflow: auto` would
|
|
14
|
+
* show scrollbars while `motion` transitions are in flight applying scale correction to the
|
|
15
|
+
* children (i.e. while the chat area is animating to accommodate increased child height).
|
|
16
|
+
* - If we measure with `offsetHeight`/`clientHeight`, we only see the eventual “resting”
|
|
17
|
+
* dimensions (unaffected by transforms) so we know better whether a scrollbar should actually
|
|
18
|
+
* show, and avoid distracting flicker of scrollbars appearing & disappearing
|
|
19
|
+
*/
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!scrollAreaRef.current || !scrollContentsRef.current) return;
|
|
22
|
+
const containerStyle = getComputedStyle(scrollAreaRef.current);
|
|
23
|
+
const scrollAreaHeight = scrollAreaRef.current.clientHeight;
|
|
24
|
+
const scrollContentsHeight = // scrollHeight is sometimes off; maybe because of scale correction?
|
|
25
|
+
scrollContentsRef.current.offsetHeight +
|
|
26
|
+
parseFloat(containerStyle.paddingTop) +
|
|
27
|
+
parseFloat(containerStyle.paddingBottom);
|
|
28
|
+
const canScroll = scrollContentsHeight > scrollAreaHeight + 1;
|
|
29
|
+
scrollAreaRef.current.style.overflowY = canScroll ? 'auto' : 'hidden';
|
|
30
|
+
}, [watchValue, scrollAreaRef, scrollContentsRef]);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Flip scroll direction based on user’s scroll position
|
|
34
|
+
* - By default, we need to keep the container scrolled all the way to the end during streaming,
|
|
35
|
+
* so that the user sees the new content that is appearing.
|
|
36
|
+
* - However, if the user scrolls upwards to read previous content, we shouldn’t scroll them
|
|
37
|
+
* back to the bottom while they’re trying to read content further up, even if more content is
|
|
38
|
+
* streaming.
|
|
39
|
+
*
|
|
40
|
+
* The smoothest way to accomplish this interaction is to flip the container’s scroll direction
|
|
41
|
+
* using flex-direction:
|
|
42
|
+
* - When the scroll position is near the bottom, scrolling should run in reverse direction—so
|
|
43
|
+
* that at a constant `0` scrollTop, the scroll stays at the end (no js-driven scrolling with
|
|
44
|
+
* distracting scrollbar flashing).
|
|
45
|
+
* - When the user scrolls upwards by a certain threshold, we flip scroll direction to normal,
|
|
46
|
+
* so that as scroll position holds constant, the still-streaming content below doesn’t push
|
|
47
|
+
* up the content the user is trying to read.
|
|
48
|
+
*
|
|
49
|
+
* When changing the flex direction, the meaning of `scrollTop` changes dramatically, so we need
|
|
50
|
+
* to manually adjust `scrollTop` to keep the scroll position visually seamless.
|
|
51
|
+
*/
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!scrollAreaRef.current) return;
|
|
54
|
+
const ac = new AbortController();
|
|
55
|
+
scrollAreaRef.current.addEventListener(
|
|
56
|
+
'scroll',
|
|
57
|
+
(e) => {
|
|
58
|
+
const scroller = e.currentTarget;
|
|
59
|
+
if (!(scroller instanceof HTMLElement)) return;
|
|
60
|
+
const appliedScrollDirection = scroller.classList.contains('scrolls-up') ? 'up' : 'down';
|
|
61
|
+
if (!appliedScrollDirection) return;
|
|
62
|
+
const { scrollHeight, clientHeight, scrollTop } = scroller;
|
|
63
|
+
// scrollTop is measured from the opposite edge depending on the appliedScrollDirection
|
|
64
|
+
// prettier-ignore
|
|
65
|
+
const distanceFromBottom = appliedScrollDirection === 'up'
|
|
66
|
+
? -scrollTop
|
|
67
|
+
: scrollHeight - clientHeight - scrollTop;
|
|
68
|
+
|
|
69
|
+
// apply reverse scroll when the container is scrolled near the bottom, so that new content
|
|
70
|
+
// pushes old content up and the bottom edge stays constant.
|
|
71
|
+
if (appliedScrollDirection === 'down' && distanceFromBottom < THRESHOLD_FROM_BOTTOM) {
|
|
72
|
+
scroller.classList.add('scrolls-up');
|
|
73
|
+
scroller.scrollTop = -distanceFromBottom;
|
|
74
|
+
}
|
|
75
|
+
// flip to forward scroll when the user scrolls up from the bottom, so that new content
|
|
76
|
+
// doesn’t push up the content the user is trying to read.
|
|
77
|
+
if (appliedScrollDirection === 'up' && distanceFromBottom > THRESHOLD_FROM_BOTTOM) {
|
|
78
|
+
scroller.classList.remove('scrolls-up');
|
|
79
|
+
scroller.scrollTop = scrollHeight - clientHeight + scrollTop;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{ capture: false, signal: ac.signal },
|
|
83
|
+
);
|
|
84
|
+
return () => ac.abort();
|
|
85
|
+
}, [scrollAreaRef]);
|
|
86
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Base
|
|
2
|
+
type BaseMessage = { id: string };
|
|
3
|
+
type BaseTextMessage = BaseMessage & { content: string };
|
|
4
|
+
// User
|
|
5
|
+
export type UserMessage = BaseTextMessage & { role: 'user' };
|
|
6
|
+
// Assistant
|
|
7
|
+
export type AssistantTextMessage = BaseMessage & {
|
|
8
|
+
role: 'assistant';
|
|
9
|
+
respondingTo: string;
|
|
10
|
+
messageType: 'text';
|
|
11
|
+
content: string;
|
|
12
|
+
isComplete: boolean;
|
|
13
|
+
};
|
|
14
|
+
export type AssistantToolCallMessage = BaseMessage & {
|
|
15
|
+
role: 'assistant';
|
|
16
|
+
respondingTo: string;
|
|
17
|
+
messageType: 'tool_use';
|
|
18
|
+
toolName: string;
|
|
19
|
+
input: Record<string, unknown> | undefined;
|
|
20
|
+
};
|
|
21
|
+
export type AssistantDoneMessage = BaseMessage & {
|
|
22
|
+
role: 'assistant';
|
|
23
|
+
respondingTo: string;
|
|
24
|
+
messageType: 'done';
|
|
25
|
+
spanId: string;
|
|
26
|
+
};
|
|
27
|
+
export type AssistantErrorMessage = BaseMessage & {
|
|
28
|
+
role: 'assistant';
|
|
29
|
+
respondingTo: string;
|
|
30
|
+
messageType: 'error';
|
|
31
|
+
errorMessage: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type ChatMessage =
|
|
35
|
+
| UserMessage
|
|
36
|
+
| AssistantTextMessage
|
|
37
|
+
| AssistantToolCallMessage
|
|
38
|
+
| AssistantDoneMessage
|
|
39
|
+
| AssistantErrorMessage;
|
|
40
|
+
|
|
41
|
+
export type ExamplePrompt = {
|
|
42
|
+
longPrompt: string;
|
|
43
|
+
shortPrompt: string;
|
|
44
|
+
icon: React.ComponentType;
|
|
45
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { DropdownButton } from '@stainless-api/ui-primitives';
|
|
2
|
+
import { CopyIcon } from 'lucide-react';
|
|
3
|
+
import { ChatGPTIcon } from './icons/chat-gpt';
|
|
4
|
+
import { ClaudeIcon } from './icons/claude';
|
|
5
|
+
import { GeminiIcon } from './icons/gemini';
|
|
6
|
+
import { MarkdownIcon } from './icons/markdown';
|
|
7
|
+
import { CursorIcon } from './icons/cursor';
|
|
8
|
+
import React from 'react';
|
|
9
|
+
|
|
10
|
+
import { getAIDropdownOptions, type DropdownIcon } from '../../plugin/globalJs/ai-dropdown-options';
|
|
11
|
+
|
|
12
|
+
const iconMap: { [key in DropdownIcon]: React.ReactNode } = {
|
|
13
|
+
markdown: <MarkdownIcon />,
|
|
14
|
+
copy: <CopyIcon size={16} />,
|
|
15
|
+
claude: <ClaudeIcon />,
|
|
16
|
+
chatgpt: <ChatGPTIcon />,
|
|
17
|
+
gemini: <GeminiIcon />,
|
|
18
|
+
cursor: <CursorIcon />,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const { primaryAction, groups } = getAIDropdownOptions();
|
|
22
|
+
|
|
23
|
+
function ItemLabel({ label }: { label: string[] }) {
|
|
24
|
+
if (label.length > 1) {
|
|
25
|
+
return (
|
|
26
|
+
<DropdownButton.MenuItemText subtle>
|
|
27
|
+
{label[0]}
|
|
28
|
+
<strong>{label[1]}</strong>
|
|
29
|
+
</DropdownButton.MenuItemText>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<DropdownButton.MenuItemText>
|
|
35
|
+
<strong>{label[0]}</strong>
|
|
36
|
+
</DropdownButton.MenuItemText>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function AIDropdown() {
|
|
41
|
+
return (
|
|
42
|
+
<DropdownButton data-dropdown-id="ai-dropdown-button">
|
|
43
|
+
<DropdownButton.PrimaryAction>
|
|
44
|
+
{iconMap[primaryAction.icon]}
|
|
45
|
+
<DropdownButton.PrimaryActionText>{primaryAction.label}</DropdownButton.PrimaryActionText>
|
|
46
|
+
</DropdownButton.PrimaryAction>
|
|
47
|
+
<DropdownButton.Trigger />
|
|
48
|
+
<DropdownButton.Menu>
|
|
49
|
+
{groups.map((group) => (
|
|
50
|
+
<React.Fragment key={group.reactKey}>
|
|
51
|
+
{group.options.map((option) => (
|
|
52
|
+
<DropdownButton.MenuItem value={option.id} isExternalLink={option.external} key={option.id}>
|
|
53
|
+
<DropdownButton.Icon>{iconMap[option.icon]}</DropdownButton.Icon>
|
|
54
|
+
<ItemLabel label={option.label} />
|
|
55
|
+
</DropdownButton.MenuItem>
|
|
56
|
+
))}
|
|
57
|
+
{group.isLast ? null : <hr />}
|
|
58
|
+
</React.Fragment>
|
|
59
|
+
))}
|
|
60
|
+
</DropdownButton.Menu>
|
|
61
|
+
</DropdownButton>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Conditionally load DocsChat only if it is enabled
|
|
2
|
+
// this way the virtual module imports aren’t evaluated and don’t cause errors when the feature is disabled
|
|
3
|
+
|
|
4
|
+
// This conditional can’t be inlined into PageFrame because it breaks Astro’s static analysis of imports of client islands
|
|
5
|
+
const AiChat = __STLDOCS_ENABLE_AI_CHAT__ ? (await import('../chat/ui/AiChat')).default : null;
|
|
6
|
+
|
|
7
|
+
const STAINLESS_PROJECT = __STLDOCS_HAS_API_REFERENCE__
|
|
8
|
+
? (await import('virtual:stl-starlight-virtual-module')).STAINLESS_PROJECT
|
|
9
|
+
: undefined;
|
|
10
|
+
|
|
11
|
+
export default function DocsChatLazy(
|
|
12
|
+
props: Omit<React.ComponentProps<NonNullable<typeof AiChat>>, 'stainlessProject'>,
|
|
13
|
+
) {
|
|
14
|
+
if (!AiChat || !STAINLESS_PROJECT) return null;
|
|
15
|
+
return <AiChat {...props} stainlessProject={STAINLESS_PROJECT} />;
|
|
16
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChevronRight } from 'lucide-react';
|
|
2
|
-
import { Join } from '@stainless-api/docs-ui/
|
|
3
|
-
import style from '@stainless-api/docs-ui/
|
|
2
|
+
import { Join } from '@stainless-api/docs-ui/components';
|
|
3
|
+
import style from '@stainless-api/docs-ui/style';
|
|
4
4
|
import type { StarlightRouteData } from '@astrojs/starlight/route-data';
|
|
5
5
|
|
|
6
6
|
type Breadcrumb = { title: string; href: string };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
import EditLink from 'virtual:starlight/components/EditLink';
|
|
3
|
+
import LastUpdated from 'virtual:starlight/components/LastUpdated';
|
|
4
|
+
import Pagination from 'virtual:starlight/components/Pagination';
|
|
5
|
+
import { RENDER_CREDITS } from 'virtual:stl-docs-virtual-module';
|
|
6
|
+
import StainlessLogo from './StainlessLogo.svg';
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<footer class="stl-footer">
|
|
10
|
+
<div class="stl-footer-row meta">
|
|
11
|
+
<EditLink />
|
|
12
|
+
<LastUpdated />
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<Pagination />
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
RENDER_CREDITS && (
|
|
19
|
+
<div class="stl-footer-row">
|
|
20
|
+
<a
|
|
21
|
+
class="stl-footer-credits"
|
|
22
|
+
href="https://www.stainless.com/products/docs"
|
|
23
|
+
target="_blank"
|
|
24
|
+
rel="noopener noreferrer"
|
|
25
|
+
>
|
|
26
|
+
Powered by <StainlessLogo />
|
|
27
|
+
</a>
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
</footer>
|
|
32
|
+
|
|
33
|
+
<style is:global>
|
|
34
|
+
.stl-footer {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
gap: 1.5rem;
|
|
38
|
+
margin-block-start: 4rem;
|
|
39
|
+
padding-block-start: 1rem;
|
|
40
|
+
padding-block-end: 4rem;
|
|
41
|
+
|
|
42
|
+
.stl-footer-row {
|
|
43
|
+
display: flex;
|
|
44
|
+
gap: 1rem;
|
|
45
|
+
justify-content: space-between;
|
|
46
|
+
|
|
47
|
+
&:empty,
|
|
48
|
+
&:not(:has(> *)) {
|
|
49
|
+
display: none;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.pagination-links {
|
|
54
|
+
margin-inline: calc(-0.25 * var(--stl-ui-pagination-border-radius-outer));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.stl-footer-credits {
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: row;
|
|
61
|
+
align-items: center;
|
|
62
|
+
text-decoration: none;
|
|
63
|
+
margin-inline-start: auto;
|
|
64
|
+
margin-block-start: 1rem;
|
|
65
|
+
font-size: var(--stl-typography-scale-sm);
|
|
66
|
+
|
|
67
|
+
/* duck text opacity by more than image opacity */
|
|
68
|
+
opacity: 0.75;
|
|
69
|
+
color: rgb(from var(--stl-color-foreground-reduced) r g b / calc(alpha * 0.9));
|
|
70
|
+
transition:
|
|
71
|
+
opacity 0.15s ease,
|
|
72
|
+
color 0.15s ease;
|
|
73
|
+
/* both text & image to full opacity */
|
|
74
|
+
&:hover {
|
|
75
|
+
opacity: 1;
|
|
76
|
+
color: var(--stl-color-foreground-reduced);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
svg {
|
|
80
|
+
height: 1.4em;
|
|
81
|
+
width: auto;
|
|
82
|
+
display: inline-block;
|
|
83
|
+
margin-inline-start: 0.5ch;
|
|
84
|
+
image {
|
|
85
|
+
transform: translateX(0.15ch);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Font } from 'astro:assets';
|
|
3
|
+
import { FONTS } from 'virtual:stl-docs-virtual-module';
|
|
4
|
+
import Default from '@astrojs/starlight/components/Head.astro';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
const mdPath = path.posix.join(Astro.url.pathname, 'index.md');
|
|
8
|
+
const fonts = [FONTS.primary, FONTS.heading, FONTS.mono, ...(FONTS.additional ?? [])].filter(Boolean);
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<Default />
|
|
12
|
+
|
|
13
|
+
{fonts.map((font) => <Font cssVariable={font.cssVariable} preload={font.preload} />)}
|
|
14
|
+
|
|
15
|
+
<link rel="alternate" type="text/markdown" href={mdPath} />
|
|
16
|
+
|
|
17
|
+
<script>
|
|
18
|
+
import { wireAIDropdown } from '../../plugin/globalJs/ai-dropdown-options.ts';
|
|
19
|
+
wireAIDropdown();
|
|
20
|
+
</script>
|
|
@@ -23,14 +23,6 @@ const shouldRenderSearch = !!(
|
|
|
23
23
|
|
|
24
24
|
<style is:global>
|
|
25
25
|
@layer starlight.core {
|
|
26
|
-
header {
|
|
27
|
-
border-color: var(--sl-color-hairline);
|
|
28
|
-
|
|
29
|
-
&.header {
|
|
30
|
-
border-bottom-color: var(--sl-color-hairline);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
26
|
.header {
|
|
35
27
|
display: flex;
|
|
36
28
|
gap: var(--sl-nav-gap);
|
|
@@ -46,6 +38,8 @@ const shouldRenderSearch = !!(
|
|
|
46
38
|
padding: 0.25rem;
|
|
47
39
|
margin: -0.25rem;
|
|
48
40
|
min-width: 0;
|
|
41
|
+
/* Max height should match button height */
|
|
42
|
+
max-height: 32px;
|
|
49
43
|
}
|
|
50
44
|
|
|
51
45
|
.default-tabs-container {
|
|
@@ -83,7 +77,7 @@ const shouldRenderSearch = !!(
|
|
|
83
77
|
|
|
84
78
|
@media (min-width: 50rem) {
|
|
85
79
|
.default-tabs-container {
|
|
86
|
-
min-width:
|
|
80
|
+
min-width: 540px;
|
|
87
81
|
}
|
|
88
82
|
}
|
|
89
83
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Sidebar from 'virtual:starlight/components/Sidebar';
|
|
3
|
+
import MobileMenuToggle from 'virtual:starlight/components/MobileMenuToggle';
|
|
4
|
+
|
|
5
|
+
import AiChatIsland from './AiChatIsland.tsx'; // entrypoint for client island can’t be conditionally import()ed
|
|
6
|
+
import clsx from 'clsx';
|
|
7
|
+
|
|
8
|
+
import starlightConfig from 'virtual:starlight/user-config';
|
|
9
|
+
const locale = Astro.currentLocale ?? starlightConfig.defaultLocale.lang;
|
|
10
|
+
const siteTitle = locale && starlightConfig.title[locale];
|
|
11
|
+
|
|
12
|
+
const { hasSidebar } = Astro.locals.starlightRoute;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<div class="page">
|
|
16
|
+
<header>
|
|
17
|
+
<slot name="header" slot="header" />
|
|
18
|
+
</header>
|
|
19
|
+
|
|
20
|
+
<nav
|
|
21
|
+
class={clsx('sidebar', !hasSidebar && 'hidden')}
|
|
22
|
+
aria-label={Astro.locals.t('sidebarNav.accessibleLabel')}
|
|
23
|
+
>
|
|
24
|
+
<MobileMenuToggle />
|
|
25
|
+
<div id="starlight__sidebar" class="sidebar-pane">
|
|
26
|
+
<Sidebar />
|
|
27
|
+
</div>
|
|
28
|
+
</nav>
|
|
29
|
+
|
|
30
|
+
<slot />
|
|
31
|
+
|
|
32
|
+
{
|
|
33
|
+
__STLDOCS_ENABLE_AI_CHAT__ && (
|
|
34
|
+
<AiChatIsland client:load currentLanguage={Astro.locals.language} siteTitle={siteTitle} />
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
</div>
|