@stainless-api/docs 0.1.0-beta.13 → 0.1.0-beta.130

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.
Files changed (188) hide show
  1. package/CHANGELOG.md +1102 -0
  2. package/ambient.d.ts +6 -0
  3. package/eslint-suppressions.json +90 -0
  4. package/{eslint.config.js → eslint.config.ts} +0 -2
  5. package/locals.d.ts +17 -0
  6. package/package.json +62 -44
  7. package/playground-virtual-modules.d.ts +96 -0
  8. package/plugin/assets/languages/cli.svg +14 -0
  9. package/plugin/assets/languages/csharp.svg +1 -0
  10. package/plugin/assets/languages/php.svg +4 -0
  11. package/plugin/buildAlgoliaIndex.ts +40 -39
  12. package/plugin/components/MethodDescription.tsx +54 -0
  13. package/plugin/components/RequestBuilder/ParamEditor.tsx +55 -0
  14. package/plugin/components/RequestBuilder/SnippetStainlessIsland.tsx +107 -0
  15. package/plugin/components/RequestBuilder/index.tsx +40 -0
  16. package/plugin/components/RequestBuilder/props.ts +9 -0
  17. package/plugin/components/RequestBuilder/spec-helpers.ts +47 -0
  18. package/plugin/components/RequestBuilder/styles.css +67 -0
  19. package/plugin/components/SDKSelect.astro +18 -111
  20. package/plugin/components/SnippetCode.tsx +112 -70
  21. package/plugin/components/StainlessIslands.tsx +126 -0
  22. package/plugin/components/search/SearchAlgolia.astro +46 -29
  23. package/plugin/components/search/SearchIsland.tsx +61 -37
  24. package/plugin/generateAPIReferenceLink.ts +0 -40
  25. package/plugin/globalJs/ai-dropdown-options.ts +248 -0
  26. package/plugin/globalJs/code-snippets.ts +45 -16
  27. package/plugin/globalJs/copy.ts +115 -27
  28. package/plugin/globalJs/create-playground.shim.ts +3 -0
  29. package/plugin/globalJs/method-descriptions.ts +33 -0
  30. package/plugin/globalJs/navigation.ts +24 -44
  31. package/plugin/globalJs/playground-data.shim.ts +1 -0
  32. package/plugin/globalJs/playground-data.ts +14 -0
  33. package/plugin/globalJs/summary-selection-tweak.ts +29 -0
  34. package/plugin/helpers/generateDocsRoutes.ts +59 -0
  35. package/plugin/helpers/multiSpec.ts +8 -0
  36. package/plugin/index.ts +317 -141
  37. package/plugin/languages.ts +8 -2
  38. package/plugin/loadPluginConfig.ts +284 -109
  39. package/plugin/markdown/highlighter.ts +100 -0
  40. package/plugin/markdown/index.ts +39 -0
  41. package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +3 -1
  42. package/plugin/react/Routing.tsx +98 -263
  43. package/plugin/referencePlaceholderUtils.ts +17 -14
  44. package/plugin/replaceSidebarPlaceholderMiddleware.ts +39 -35
  45. package/plugin/routes/Docs.astro +72 -111
  46. package/plugin/routes/DocsStatic.astro +6 -5
  47. package/plugin/routes/Overview.astro +46 -22
  48. package/plugin/routes/llms.ts +186 -0
  49. package/plugin/routes/markdown.ts +13 -12
  50. package/plugin/{cms → sidebar-utils}/sidebar-builder.ts +84 -69
  51. package/plugin/specs/FileCache.ts +99 -0
  52. package/plugin/specs/fetchSpecSSR.ts +27 -0
  53. package/plugin/specs/generateSpec.ts +112 -0
  54. package/plugin/specs/index.ts +132 -0
  55. package/plugin/specs/inputResolver.ts +148 -0
  56. package/plugin/{cms → specs}/worker.ts +82 -5
  57. package/plugin/vendor/preview.worker.docs.js +27121 -16890
  58. package/plugin/vendor/templates/cli.md +1 -0
  59. package/plugin/vendor/templates/go.md +4 -2
  60. package/plugin/vendor/templates/java.md +5 -1
  61. package/plugin/vendor/templates/kotlin.md +5 -1
  62. package/plugin/vendor/templates/node.md +4 -2
  63. package/plugin/vendor/templates/python.md +4 -2
  64. package/plugin/vendor/templates/ruby.md +4 -2
  65. package/plugin/vendor/templates/terraform.md +1 -1
  66. package/plugin/vendor/templates/typescript.md +3 -1
  67. package/resolveSrcFile.ts +10 -0
  68. package/scripts/vendor_deps.ts +5 -5
  69. package/shared/conditionalIntegration.ts +28 -0
  70. package/shared/getProsePages.ts +41 -0
  71. package/shared/getSharedLogger.ts +15 -0
  72. package/shared/terminalUtils.ts +3 -0
  73. package/shared/virtualModule.ts +46 -1
  74. package/src/content.config.ts +9 -0
  75. package/stl-docs/aiChatExamples.ts +95 -0
  76. package/stl-docs/chat/docs-chat-handler.ts +18 -0
  77. package/stl-docs/chat/hook.ts +215 -0
  78. package/stl-docs/chat/schemas.ts +70 -0
  79. package/stl-docs/chat/stainless-handler/index.ts +126 -0
  80. package/stl-docs/chat/stream-util.ts +16 -0
  81. package/stl-docs/chat/ui/AiChat.module.css +591 -0
  82. package/stl-docs/chat/ui/AiChat.tsx +188 -0
  83. package/stl-docs/chat/ui/Trigger.tsx +154 -0
  84. package/stl-docs/chat/ui/components/ChatControls.tsx +51 -0
  85. package/stl-docs/chat/ui/components/ChatEmpty.tsx +42 -0
  86. package/stl-docs/chat/ui/components/ChatLog.tsx +96 -0
  87. package/stl-docs/chat/ui/components/ChatMessage.tsx +47 -0
  88. package/stl-docs/chat/ui/components/CodeBlock.tsx +33 -0
  89. package/stl-docs/chat/ui/components/MessageFeedback.tsx +109 -0
  90. package/stl-docs/chat/ui/components/Table.tsx +15 -0
  91. package/stl-docs/chat/ui/components/ToolCall.tsx +34 -0
  92. package/stl-docs/chat/ui/components/hljs-github.css +81 -0
  93. package/stl-docs/chat/ui/scroll-manager.ts +86 -0
  94. package/stl-docs/chat/ui/types.ts +45 -0
  95. package/stl-docs/components/AIDropdown.tsx +63 -0
  96. package/stl-docs/components/AiChatIsland.tsx +16 -0
  97. package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +2 -2
  98. package/stl-docs/components/ContentPanel.astro +9 -0
  99. package/stl-docs/components/Footer.astro +89 -0
  100. package/stl-docs/components/Head.astro +20 -0
  101. package/stl-docs/components/Header.astro +3 -9
  102. package/stl-docs/components/PageFrame.astro +37 -0
  103. package/stl-docs/components/PageSidebar.astro +11 -0
  104. package/stl-docs/components/PageTitle.astro +82 -0
  105. package/stl-docs/components/StainlessLogo.svg +4 -0
  106. package/stl-docs/components/ThemeProvider.astro +36 -0
  107. package/stl-docs/components/ThemeSelect.astro +84 -146
  108. package/stl-docs/components/TwoColumnContent.astro +2 -0
  109. package/stl-docs/components/headers/DefaultHeader.astro +6 -8
  110. package/stl-docs/components/headers/StackedHeader.astro +10 -53
  111. package/stl-docs/components/icons/chat-gpt.tsx +2 -2
  112. package/stl-docs/components/icons/cursor.tsx +10 -0
  113. package/stl-docs/components/icons/gemini.tsx +19 -0
  114. package/stl-docs/components/icons/markdown.tsx +1 -1
  115. package/stl-docs/components/index.ts +1 -0
  116. package/stl-docs/components/mintlify-compat/Accordion.astro +2 -2
  117. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +0 -4
  118. package/stl-docs/components/mintlify-compat/Columns.astro +2 -2
  119. package/stl-docs/components/mintlify-compat/Frame.astro +6 -6
  120. package/stl-docs/components/mintlify-compat/Tab.astro +2 -2
  121. package/stl-docs/components/mintlify-compat/callouts/Callout.astro +2 -2
  122. package/stl-docs/components/mintlify-compat/callouts/Check.astro +0 -4
  123. package/stl-docs/components/mintlify-compat/callouts/Danger.astro +0 -4
  124. package/stl-docs/components/mintlify-compat/callouts/Info.astro +0 -4
  125. package/stl-docs/components/mintlify-compat/callouts/Note.astro +0 -4
  126. package/stl-docs/components/mintlify-compat/callouts/Tip.astro +0 -4
  127. package/stl-docs/components/mintlify-compat/callouts/Warning.astro +0 -4
  128. package/stl-docs/components/mintlify-compat/card.css +4 -4
  129. package/stl-docs/components/mintlify-compat/index.ts +2 -4
  130. package/stl-docs/components/nav-tabs/NavDropdown.astro +38 -77
  131. package/stl-docs/components/nav-tabs/NavTabs.astro +81 -81
  132. package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +1 -2
  133. package/stl-docs/components/nav-tabs/buildNavLinks.ts +5 -2
  134. package/stl-docs/components/pagination/HomeLink.astro +10 -0
  135. package/stl-docs/components/pagination/Pagination.astro +177 -0
  136. package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +22 -0
  137. package/stl-docs/components/pagination/PaginationLinkQuiet.astro +13 -0
  138. package/stl-docs/components/pagination/util.ts +71 -0
  139. package/stl-docs/components/scripts.ts +1 -0
  140. package/stl-docs/components/sidebars/BaseSidebar.astro +80 -2
  141. package/stl-docs/components/sidebars/SidebarWithComponents.tsx +10 -0
  142. package/stl-docs/components/sidebars/convertAstroSidebarToStl.tsx +62 -0
  143. package/stl-docs/disableCalloutSyntax.ts +36 -0
  144. package/stl-docs/fonts.ts +186 -0
  145. package/stl-docs/index.ts +176 -58
  146. package/stl-docs/loadStlDocsConfig.ts +73 -8
  147. package/stl-docs/proseDocSync.test.ts +74 -0
  148. package/stl-docs/proseDocSync.ts +344 -0
  149. package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +53 -0
  150. package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +41 -0
  151. package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
  152. package/stl-docs/proseSearchIndexing.ts +218 -0
  153. package/stl-docs/tabsMiddleware.ts +14 -5
  154. package/styles/code.css +53 -49
  155. package/styles/links.css +2 -37
  156. package/styles/method-descriptions.css +36 -0
  157. package/styles/overrides.css +28 -46
  158. package/styles/page.css +228 -38
  159. package/styles/sdk_select.css +9 -6
  160. package/styles/search.css +11 -21
  161. package/styles/sidebar.css +28 -215
  162. package/styles/{variables.css → sl-variables.css} +4 -8
  163. package/styles/stldocs-variables.css +6 -0
  164. package/styles/toc.css +19 -8
  165. package/theme.css +11 -9
  166. package/tsconfig.json +1 -4
  167. package/virtual-module.d.ts +66 -8
  168. package/components/variables.css +0 -112
  169. package/plugin/cms/client.ts +0 -62
  170. package/plugin/cms/server.ts +0 -268
  171. package/plugin/globalJs/ai-dropdown.ts +0 -57
  172. package/stl-docs/components/APIReferenceAIDropdown.tsx +0 -58
  173. package/stl-docs/components/ClientRouterHead.astro +0 -41
  174. package/stl-docs/components/content-panel/ContentPanel.astro +0 -69
  175. package/stl-docs/components/content-panel/ProseAIDropdown.tsx +0 -55
  176. package/stl-docs/components/headers/SplashMobileMenuToggle.astro +0 -49
  177. package/stl-docs/components/mintlify-compat/Step.astro +0 -56
  178. package/stl-docs/components/mintlify-compat/Steps.astro +0 -15
  179. package/styles/fonts.css +0 -68
  180. /package/{plugin/assets → assets}/fonts/geist/OFL.txt +0 -0
  181. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin-ext.woff2 +0 -0
  182. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin.woff2 +0 -0
  183. /package/{plugin/assets → assets}/fonts/geist/geist-latin-ext.woff2 +0 -0
  184. /package/{plugin/assets → assets}/fonts/geist/geist-latin.woff2 +0 -0
  185. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin-ext.woff2 +0 -0
  186. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin.woff2 +0 -0
  187. /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin-ext.woff2 +0 -0
  188. /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/src/components';
3
- import style from '@stainless-api/docs-ui/src/style';
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,9 @@
1
+ <div class="stl-ui-prose stl-content-panel">
2
+ <slot />
3
+ </div>
4
+
5
+ <style>
6
+ .stl-content-panel + .stl-content-panel {
7
+ margin-top: 24px;
8
+ }
9
+ </style>
@@ -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: 440px;
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>
@@ -0,0 +1,11 @@
1
+ ---
2
+ import TableOfContents from 'virtual:starlight/components/TableOfContents';
3
+ ---
4
+
5
+ {
6
+ Astro.locals.starlightRoute.toc && (
7
+ <div class="right-sidebar-panel">
8
+ <TableOfContents />
9
+ </div>
10
+ )
11
+ }