@stainless-api/docs-ai-chat 0.1.0-beta.5 → 0.1.0-beta.50

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 CHANGED
@@ -1,54 +1,215 @@
1
1
  # @stainless-api/docs-ai-chat
2
2
 
3
- ## 0.1.0-beta.5
3
+ ## 0.1.0-beta.50
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - Updated dependencies [d2e3686]
8
- - Updated dependencies [5d6e5fa]
9
- - @stainless-api/docs@0.1.0-beta.63
10
- - @stainless-api/ui-primitives@0.1.0-beta.39
11
- - @stainless-api/docs-ui@0.1.0-beta.52
7
+ - Updated dependencies [134885b]
8
+ - Updated dependencies [5ba9135]
9
+ - Updated dependencies [df0c958]
10
+ - Updated dependencies [e641d33]
11
+ - @stainless-api/docs-ui@0.1.0-beta.81
12
12
 
13
- ## 0.1.0-beta.4
13
+ ## 0.1.0-beta.49
14
14
 
15
15
  ### Patch Changes
16
16
 
17
- - Updated dependencies [3c4a030]
18
- - Updated dependencies [cd86726]
19
- - Updated dependencies [aa9d333]
20
- - Updated dependencies [07a2c87]
21
- - @stainless-api/docs@0.1.0-beta.62
22
- - @stainless-api/docs-ui@0.1.0-beta.51
23
- - @stainless-api/ui-primitives@0.1.0-beta.38
17
+ - Updated dependencies [05cd046]
18
+ - @stainless-api/docs-ui@0.1.0-beta.80
24
19
 
25
- ## 0.1.0-beta.3
20
+ ## 0.1.0-beta.48
26
21
 
27
22
  ### Patch Changes
28
23
 
29
- - 77c0d47: steelie: submit on enter
24
+ - 8838d9f: remember presentation when window shrinks
25
+ - Updated dependencies [8838d9f]
26
+ - @stainless-api/ai-chat@0.1.0-beta.10
30
27
 
31
- ## 0.1.0-beta.2
28
+ ## 0.1.0-beta.47
29
+
30
+ ### Minor Changes
31
+
32
+ - a8ed299: Adds the ability for AI chat to move into a sidebar “panel” position.
32
33
 
33
34
  ### Patch Changes
34
35
 
35
- - a1502cf: fix: close AIChat when clicking on SVG elements outside its bounds
36
- - 88a9894: patch vite optimizeDeps for docs-ai-chat
37
- - Updated dependencies [2a79bae]
38
- - Updated dependencies [88a9894]
39
- - @stainless-api/ui-primitives@0.1.0-beta.38
40
- - @stainless-api/docs@0.1.0-beta.61
41
- - @stainless-api/docs-ui@0.1.0-beta.51
36
+ - Updated dependencies [5b3b798]
37
+ - Updated dependencies [a8ed299]
38
+ - @stainless-api/ai-chat@0.1.0-beta.9
42
39
 
43
- ## 0.1.0-beta.1
40
+ ## 0.1.0-beta.46
44
41
 
45
42
  ### Minor Changes
46
43
 
47
- - 2d00f0d: first version of docs-ai-chat
44
+ - 9d24589: Steelie empty state
45
+ - 5263b85: Loading state for ai chat
46
+
47
+ ### Patch Changes
48
+
49
+ - 9d24589: Various steelie UI fixes
50
+ - Updated dependencies [9d24589]
51
+ - Updated dependencies [9d24589]
52
+ - Updated dependencies [5263b85]
53
+ - @stainless-api/ai-chat@0.1.0-beta.8
54
+
55
+ ## 0.1.0-beta.45
56
+
57
+ ### Patch Changes
58
+
59
+ - Updated dependencies [6d9933d]
60
+ - @stainless-api/docs-ui@0.1.0-beta.79
61
+ - @stainless-api/ai-chat@0.1.0-beta.7
62
+
63
+ ## 0.1.0-beta.44
64
+
65
+ ### Patch Changes
66
+
67
+ - Updated dependencies [54add64]
68
+ - @stainless-api/docs-ui@0.1.0-beta.78
69
+
70
+ ## 0.1.0-beta.43
71
+
72
+ ### Patch Changes
73
+
74
+ - Updated dependencies [21e50d3]
75
+ - @stainless-api/docs-ui@0.1.0-beta.77
76
+
77
+ ## 0.1.0-beta.42
78
+
79
+ ### Patch Changes
80
+
81
+ - Updated dependencies [95fe66c]
82
+ - Updated dependencies [5701494]
83
+ - @stainless-api/docs-ui@0.1.0-beta.76
84
+ - @stainless-api/ai-chat@0.1.0-beta.6
85
+
86
+ ## 0.1.0-beta.41
87
+
88
+ ### Patch Changes
89
+
90
+ - 6e762e2: fix steelie for multi-turn conversations
91
+
92
+ ## 0.1.0-beta.40
93
+
94
+ ### Patch Changes
95
+
96
+ - Updated dependencies [2919b0a]
97
+ - Updated dependencies [e005e5c]
98
+ - @stainless-api/docs-ui@0.1.0-beta.75
99
+
100
+ ## 0.1.0-beta.39
101
+
102
+ ### Patch Changes
103
+
104
+ - Updated dependencies [415629f]
105
+ - @stainless-api/docs-ui@0.1.0-beta.74
106
+
107
+ ## 0.1.0-beta.38
108
+
109
+ ### Patch Changes
110
+
111
+ - Updated dependencies [5c36876]
112
+ - @stainless-api/docs-ui@0.1.0-beta.73
113
+
114
+ ## 0.1.0-beta.37
115
+
116
+ ### Patch Changes
117
+
118
+ - Updated dependencies [6b86a8b]
119
+ - @stainless-api/docs-ui@0.1.0-beta.72
120
+
121
+ ## 0.1.0-beta.36
122
+
123
+ ### Patch Changes
124
+
125
+ - Updated dependencies [cd578b7]
126
+ - @stainless-api/docs-ui@0.1.0-beta.71
127
+
128
+ ## 0.1.0-beta.35
129
+
130
+ ### Patch Changes
131
+
132
+ - Updated dependencies [93c8f94]
133
+ - @stainless-api/docs-ui@0.1.0-beta.70
134
+
135
+ ## 0.1.0-beta.34
136
+
137
+ ### Patch Changes
138
+
139
+ - Updated dependencies [61ba36f]
140
+ - @stainless-api/docs-ui@0.1.0-beta.69
141
+
142
+ ## 0.1.0-beta.33
143
+
144
+ ### Patch Changes
145
+
146
+ - Updated dependencies [a3f1ede]
147
+ - @stainless-api/docs-ui@0.1.0-beta.68
148
+
149
+ ## 0.1.0-beta.32
150
+
151
+ ### Patch Changes
152
+
153
+ - @stainless-api/ai-chat@0.1.0-beta.5
154
+ - @stainless-api/docs-ui@0.1.0-beta.67
155
+
156
+ ## 0.1.0-beta.31
157
+
158
+ ### Patch Changes
159
+
160
+ - Updated dependencies [65a1c9b]
161
+ - Updated dependencies [4f1cee7]
162
+ - Updated dependencies [4c72a83]
163
+ - Updated dependencies [068469b]
164
+ - @stainless-api/docs-ui@0.1.0-beta.66
165
+
166
+ ## 0.1.0-beta.30
167
+
168
+ ### Patch Changes
169
+
170
+ - Updated dependencies [b62eb05]
171
+ - @stainless-api/docs-ui@0.1.0-beta.65
172
+ - @stainless-api/ai-chat@0.1.0-beta.4
173
+
174
+ ## 0.1.0-beta.29
175
+
176
+ ### Patch Changes
177
+
178
+ - Updated dependencies [52ece13]
179
+ - Updated dependencies [3411ffe]
180
+ - Updated dependencies [7439be7]
181
+ - @stainless-api/ai-chat@0.1.0-beta.3
182
+ - @stainless-api/docs-ui@0.1.0-beta.64
183
+
184
+ ## 0.1.0-beta.28
185
+
186
+ ### Patch Changes
187
+
188
+ - Updated dependencies [274cefc]
189
+ - @stainless-api/docs-ui@0.1.0-beta.63
190
+
191
+ ## 0.1.0-beta.27
192
+
193
+ ### Patch Changes
194
+
195
+ - Updated dependencies [6ef241e]
196
+ - Updated dependencies [d3a85b5]
197
+ - Updated dependencies [d3a85b5]
198
+ - Updated dependencies [2dcb5fb]
199
+ - @stainless-api/docs-ui@0.1.0-beta.62
200
+
201
+ ## 0.1.0-beta.26
202
+
203
+ ### Patch Changes
204
+
205
+ - Updated dependencies [7155fae]
206
+ - @stainless-api/ai-chat@0.1.0-beta.2
207
+ - @stainless-api/docs-ui@0.1.0-beta.61
208
+
209
+ ## 0.1.0-beta.25
48
210
 
49
211
  ### Patch Changes
50
212
 
51
- - Updated dependencies [2d00f0d]
52
- - @stainless-api/docs@0.1.0-beta.60
53
- - @stainless-api/docs-ui@0.1.0-beta.50
54
- - @stainless-api/ui-primitives@0.1.0-beta.37
213
+ - 5c257e2: separate steelie into separate packages
214
+ - Updated dependencies [9dda4cf]
215
+ - @stainless-api/ai-chat@0.1.0-beta.1
package/eslint.config.js CHANGED
@@ -1,2 +1,2 @@
1
- import { config } from '@stainless/eslint-config/react-internal';
1
+ import { config } from '@stainless/eslint-config/react';
2
2
  export default config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/docs-ai-chat",
3
- "version": "0.1.0-beta.5",
3
+ "version": "0.1.0-beta.50",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -8,25 +8,16 @@
8
8
  "peerDependencies": {
9
9
  "react": ">=19.0.0",
10
10
  "react-dom": ">=19.0.0",
11
- "@stainless-api/docs": "0.1.0-beta.63",
12
- "@stainless-api/docs-ui": "0.1.0-beta.52",
13
- "@stainless-api/ui-primitives": "0.1.0-beta.39"
11
+ "@stainless-api/docs-ui": "0.1.0-beta.81"
14
12
  },
15
13
  "dependencies": {
16
14
  "@streamparser/json-whatwg": "^0.0.22",
17
- "clsx": "^2.1.1",
18
- "lucide-react": "^0.561.0",
19
- "motion": "^12.23.25",
20
- "react": "^19.2.3",
21
- "react-markdown": "^10.1.0",
22
- "react-syntax-highlighter": "^16.1.0",
23
- "remend": "^1.0.1",
24
- "zod": "^4.1.13"
15
+ "zod": "^4.3.6",
16
+ "@stainless-api/ai-chat": "0.1.0-beta.10"
25
17
  },
26
18
  "devDependencies": {
27
- "@types/react": "19.2.7",
19
+ "@types/react": "19.2.14",
28
20
  "@types/react-dom": "^19.2.3",
29
- "@types/react-syntax-highlighter": "^15.5.13",
30
21
  "typescript": "5.9.3",
31
22
  "@stainless/eslint-config": "0.1.0-beta.1"
32
23
  },
@@ -34,8 +25,8 @@
34
25
  "./plugin": {
35
26
  "import": "./plugin.tsx"
36
27
  },
37
- "./src/AiChat.tsx": {
38
- "import": "./src/AiChat.tsx"
28
+ "./src/DocsChat.tsx": {
29
+ "import": "./src/DocsChat.tsx"
39
30
  }
40
31
  },
41
32
  "scripts": {
package/plugin.tsx CHANGED
@@ -1,3 +1,3 @@
1
- export default function aiChatPlugin() {
2
- return { chatComponentPath: '@stainless-api/docs-ai-chat/src/AiChat.tsx' };
1
+ export default function docsChatPlugin() {
2
+ return { chatComponentPath: '@stainless-api/docs-ai-chat/src/DocsChat.tsx' };
3
3
  }
@@ -0,0 +1,54 @@
1
+ import AiChat from '@stainless-api/ai-chat/src/AiChat.tsx';
2
+ import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
3
+ import { setResponseMetadata, submitResponseFeedback } from './api';
4
+ import { useSyncExternalStore } from 'react';
5
+ import { useChat } from './hook';
6
+
7
+ function onCopyMessage(spanId: string) {
8
+ setResponseMetadata(spanId, { copied_to_clipboard: 'true' }).catch(() => {});
9
+ }
10
+
11
+ async function rateMessage(spanId: string, rating: 'up' | 'down'): Promise<boolean> {
12
+ try {
13
+ const { success } = await submitResponseFeedback(spanId, { up: 1 as const, down: 0 as const }[rating]);
14
+ return success;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ export default function DocsChat({
21
+ projectId,
22
+ language,
23
+ siteTitle,
24
+ }: {
25
+ projectId: string;
26
+ language: DocsLanguage | undefined;
27
+ siteTitle: string | undefined;
28
+ }) {
29
+ const { chatMessages, sendMessage } = useChat({
30
+ projectId,
31
+ language: language ?? 'http',
32
+ siteTitle,
33
+ });
34
+
35
+ // panel mode is supported only on larger viewports
36
+ const supportsPanel = useSyncExternalStore(
37
+ (cb) => {
38
+ window.addEventListener('resize', cb);
39
+ return () => window.removeEventListener('resize', cb);
40
+ },
41
+ () => window.innerWidth >= 968,
42
+ () => false,
43
+ );
44
+
45
+ return (
46
+ <AiChat
47
+ chatMessages={chatMessages}
48
+ sendMessage={sendMessage}
49
+ rateMessage={rateMessage}
50
+ onCopyMessage={onCopyMessage}
51
+ supportsPanel={supportsPanel}
52
+ />
53
+ );
54
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Identifier for tracking unique users in braintrust
3
+ */
4
+ export function getClientId() {
5
+ let clientId = localStorage.getItem('stainless-client-id');
6
+ if (!clientId) {
7
+ clientId = crypto.randomUUID();
8
+ localStorage.setItem('stainless-client-id', clientId);
9
+ }
10
+ return clientId;
11
+ }
package/src/api/index.ts CHANGED
@@ -1,9 +1,37 @@
1
1
  import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
2
2
  import { JSONParser } from '@streamparser/json-whatwg';
3
- import { type RequestBody, responseChunk } from './schemas';
3
+ import {
4
+ type RequestBody,
5
+ responseChunk,
6
+ type FeedbackRequestBody,
7
+ feedbackResponseBody,
8
+ type MetadataRequestBody,
9
+ metadataResponseBody,
10
+ } from './schemas';
4
11
  import { streamAsyncIterator } from './util';
12
+ import { getClientId } from './client-id';
5
13
 
6
- export const CHAT_ENDPOINT = 'https://app.stainless.com/api/ai/get-agentic-help';
14
+ export const API_URL = new URL('https://app.stainless.com/api/');
15
+ export const CHAT_ENDPOINT = new URL('ai/get-agentic-help', API_URL);
16
+
17
+ export const FEEDBACK_ENDPOINT = (spanId: string) => new URL(`ai/agentic-help/${spanId}/score`, API_URL);
18
+ export const METADATA_ENDPOINT = (spanId: string) => new URL(`ai/agentic-help/${spanId}/metadata`, API_URL);
19
+
20
+ /** Context on what the user is currently viewing to pass to the agent */
21
+ function getPageContext({ siteTitle }: { siteTitle: string | undefined }) {
22
+ const { href } = window.location;
23
+ const markdownUrl = `${href.replace(/\/$/, '')}/index.md`;
24
+ const pageTitle = document.querySelector('h1')?.textContent;
25
+ return [
26
+ `The user is viewing a documentation page${siteTitle ? ` for ${siteTitle}` : ''}.`,
27
+ `- Content URL: ${markdownUrl}`,
28
+ pageTitle && `- Page title: “${pageTitle}”`,
29
+ // TODO: include stainless path here? does the agent know how to use it?
30
+ // TODO: pass more of the page content into context without the agent having to retrieve it
31
+ ]
32
+ .filter(Boolean)
33
+ .join('\n');
34
+ }
7
35
 
8
36
  /**
9
37
  * Stream chat response from the server
@@ -14,15 +42,17 @@ export async function* getChatResponse(
14
42
  project,
15
43
  language,
16
44
  priorMessages,
45
+ siteTitle,
17
46
  }: {
18
47
  query: string;
19
48
  project: string;
20
49
  language: DocsLanguage;
21
50
  priorMessages: NonNullable<RequestBody['additionalContext']>['prior_messages'];
51
+ siteTitle: string | undefined;
22
52
  },
23
53
  abortSignal: AbortSignal,
24
54
  ) {
25
- const req = await fetch(CHAT_ENDPOINT, {
55
+ const res = await fetch(CHAT_ENDPOINT, {
26
56
  method: 'POST',
27
57
  headers: {
28
58
  'Content-Type': 'application/json',
@@ -31,17 +61,53 @@ export async function* getChatResponse(
31
61
  query,
32
62
  sdk: { project, language },
33
63
  stream: true,
34
- additionalContext: { prior_messages: priorMessages },
64
+ additionalContext: {
65
+ prior_messages: priorMessages,
66
+ intent: getPageContext({ siteTitle }),
67
+ },
68
+ browser_id: getClientId(),
35
69
  } satisfies RequestBody),
36
70
 
37
71
  signal: abortSignal,
38
72
  });
39
73
 
40
- if (!req.ok || !req.body) throw new Error(`Chat request failed with status ${req.status}`);
74
+ if (!res.ok || !res.body) throw new Error(`Chat request failed with status ${res.status}`);
41
75
 
42
76
  const parser = new JSONParser({ separator: '\n', paths: ['$'] });
43
- for await (const chunk of streamAsyncIterator(req.body.pipeThrough(parser))) {
77
+ for await (const chunk of streamAsyncIterator(res.body.pipeThrough(parser))) {
44
78
  const chunkParsed = responseChunk.safeParse(chunk.value);
45
79
  if (chunkParsed.success) yield chunkParsed.data;
46
80
  }
47
81
  }
82
+
83
+ /**
84
+ * Attach a score to a response
85
+ */
86
+ export async function submitResponseFeedback(spanId: string, score: 0 | 1) {
87
+ const res = await fetch(FEEDBACK_ENDPOINT(spanId), {
88
+ method: 'PUT',
89
+ headers: {
90
+ 'Content-Type': 'application/json',
91
+ },
92
+ body: JSON.stringify({ score } satisfies FeedbackRequestBody),
93
+ });
94
+
95
+ if (!res.ok) throw new Error(`Feedback request failed with status ${res.status}`);
96
+ return feedbackResponseBody.parse(await res.json());
97
+ }
98
+
99
+ /**
100
+ * Attach a score to a response
101
+ */
102
+ export async function setResponseMetadata(spanId: string, metadata: Record<string, string>) {
103
+ const res = await fetch(METADATA_ENDPOINT(spanId), {
104
+ method: 'PUT',
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ },
108
+ body: JSON.stringify({ metadata } satisfies MetadataRequestBody),
109
+ });
110
+
111
+ if (!res.ok) throw new Error(`Metadata request failed with status ${res.status}`);
112
+ return metadataResponseBody.parse(await res.json());
113
+ }
@@ -14,6 +14,7 @@ export const requestBody = z.object({
14
14
  maxTokens: z.number().optional(),
15
15
  })
16
16
  .optional(),
17
+ session_id: z.string().optional(),
17
18
  additionalContext: z
18
19
  .object({
19
20
  prior_messages: z.array(
@@ -28,6 +29,7 @@ export const requestBody = z.object({
28
29
  errors: z.string().optional(),
29
30
  })
30
31
  .optional(),
32
+ browser_id: z.string().optional(),
31
33
  });
32
34
  export type RequestBody = z.input<typeof requestBody>;
33
35
 
@@ -48,6 +50,19 @@ export const responseChunk = z.discriminatedUnion('type', [
48
50
  }),
49
51
  z.object({
50
52
  type: z.literal('done'),
53
+ span_id: z.string(),
54
+ }),
55
+ z.object({
56
+ type: z.literal('start_session'),
57
+ session_id: z.string(),
51
58
  }),
52
59
  ]);
53
60
  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
+
66
+ export const metadataRequestBody = z.object({ metadata: z.record(z.string(), z.string()) });
67
+ export type MetadataRequestBody = z.infer<typeof metadataRequestBody>;
68
+ export const metadataResponseBody = z.object({ success: z.boolean() });