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

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,222 @@
1
1
  # @stainless-api/docs-ai-chat
2
2
 
3
- ## 0.1.0-beta.5
3
+ ## 0.1.0-beta.51
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 [37cc2ac]
8
+ - @stainless-api/docs-ui@0.1.0-beta.82
12
9
 
13
- ## 0.1.0-beta.4
10
+ ## 0.1.0-beta.50
14
11
 
15
12
  ### Patch Changes
16
13
 
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
14
+ - Updated dependencies [134885b]
15
+ - Updated dependencies [5ba9135]
16
+ - Updated dependencies [df0c958]
17
+ - Updated dependencies [e641d33]
18
+ - @stainless-api/docs-ui@0.1.0-beta.81
24
19
 
25
- ## 0.1.0-beta.3
20
+ ## 0.1.0-beta.49
26
21
 
27
22
  ### Patch Changes
28
23
 
29
- - 77c0d47: steelie: submit on enter
24
+ - Updated dependencies [05cd046]
25
+ - @stainless-api/docs-ui@0.1.0-beta.80
30
26
 
31
- ## 0.1.0-beta.2
27
+ ## 0.1.0-beta.48
32
28
 
33
29
  ### Patch Changes
34
30
 
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
31
+ - 8838d9f: remember presentation when window shrinks
32
+ - Updated dependencies [8838d9f]
33
+ - @stainless-api/ai-chat@0.1.0-beta.10
42
34
 
43
- ## 0.1.0-beta.1
35
+ ## 0.1.0-beta.47
44
36
 
45
37
  ### Minor Changes
46
38
 
47
- - 2d00f0d: first version of docs-ai-chat
39
+ - a8ed299: Adds the ability for AI chat to move into a sidebar “panel” position.
48
40
 
49
41
  ### Patch Changes
50
42
 
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
43
+ - Updated dependencies [5b3b798]
44
+ - Updated dependencies [a8ed299]
45
+ - @stainless-api/ai-chat@0.1.0-beta.9
46
+
47
+ ## 0.1.0-beta.46
48
+
49
+ ### Minor Changes
50
+
51
+ - 9d24589: Steelie empty state
52
+ - 5263b85: Loading state for ai chat
53
+
54
+ ### Patch Changes
55
+
56
+ - 9d24589: Various steelie UI fixes
57
+ - Updated dependencies [9d24589]
58
+ - Updated dependencies [9d24589]
59
+ - Updated dependencies [5263b85]
60
+ - @stainless-api/ai-chat@0.1.0-beta.8
61
+
62
+ ## 0.1.0-beta.45
63
+
64
+ ### Patch Changes
65
+
66
+ - Updated dependencies [6d9933d]
67
+ - @stainless-api/docs-ui@0.1.0-beta.79
68
+ - @stainless-api/ai-chat@0.1.0-beta.7
69
+
70
+ ## 0.1.0-beta.44
71
+
72
+ ### Patch Changes
73
+
74
+ - Updated dependencies [54add64]
75
+ - @stainless-api/docs-ui@0.1.0-beta.78
76
+
77
+ ## 0.1.0-beta.43
78
+
79
+ ### Patch Changes
80
+
81
+ - Updated dependencies [21e50d3]
82
+ - @stainless-api/docs-ui@0.1.0-beta.77
83
+
84
+ ## 0.1.0-beta.42
85
+
86
+ ### Patch Changes
87
+
88
+ - Updated dependencies [95fe66c]
89
+ - Updated dependencies [5701494]
90
+ - @stainless-api/docs-ui@0.1.0-beta.76
91
+ - @stainless-api/ai-chat@0.1.0-beta.6
92
+
93
+ ## 0.1.0-beta.41
94
+
95
+ ### Patch Changes
96
+
97
+ - 6e762e2: fix steelie for multi-turn conversations
98
+
99
+ ## 0.1.0-beta.40
100
+
101
+ ### Patch Changes
102
+
103
+ - Updated dependencies [2919b0a]
104
+ - Updated dependencies [e005e5c]
105
+ - @stainless-api/docs-ui@0.1.0-beta.75
106
+
107
+ ## 0.1.0-beta.39
108
+
109
+ ### Patch Changes
110
+
111
+ - Updated dependencies [415629f]
112
+ - @stainless-api/docs-ui@0.1.0-beta.74
113
+
114
+ ## 0.1.0-beta.38
115
+
116
+ ### Patch Changes
117
+
118
+ - Updated dependencies [5c36876]
119
+ - @stainless-api/docs-ui@0.1.0-beta.73
120
+
121
+ ## 0.1.0-beta.37
122
+
123
+ ### Patch Changes
124
+
125
+ - Updated dependencies [6b86a8b]
126
+ - @stainless-api/docs-ui@0.1.0-beta.72
127
+
128
+ ## 0.1.0-beta.36
129
+
130
+ ### Patch Changes
131
+
132
+ - Updated dependencies [cd578b7]
133
+ - @stainless-api/docs-ui@0.1.0-beta.71
134
+
135
+ ## 0.1.0-beta.35
136
+
137
+ ### Patch Changes
138
+
139
+ - Updated dependencies [93c8f94]
140
+ - @stainless-api/docs-ui@0.1.0-beta.70
141
+
142
+ ## 0.1.0-beta.34
143
+
144
+ ### Patch Changes
145
+
146
+ - Updated dependencies [61ba36f]
147
+ - @stainless-api/docs-ui@0.1.0-beta.69
148
+
149
+ ## 0.1.0-beta.33
150
+
151
+ ### Patch Changes
152
+
153
+ - Updated dependencies [a3f1ede]
154
+ - @stainless-api/docs-ui@0.1.0-beta.68
155
+
156
+ ## 0.1.0-beta.32
157
+
158
+ ### Patch Changes
159
+
160
+ - @stainless-api/ai-chat@0.1.0-beta.5
161
+ - @stainless-api/docs-ui@0.1.0-beta.67
162
+
163
+ ## 0.1.0-beta.31
164
+
165
+ ### Patch Changes
166
+
167
+ - Updated dependencies [65a1c9b]
168
+ - Updated dependencies [4f1cee7]
169
+ - Updated dependencies [4c72a83]
170
+ - Updated dependencies [068469b]
171
+ - @stainless-api/docs-ui@0.1.0-beta.66
172
+
173
+ ## 0.1.0-beta.30
174
+
175
+ ### Patch Changes
176
+
177
+ - Updated dependencies [b62eb05]
178
+ - @stainless-api/docs-ui@0.1.0-beta.65
179
+ - @stainless-api/ai-chat@0.1.0-beta.4
180
+
181
+ ## 0.1.0-beta.29
182
+
183
+ ### Patch Changes
184
+
185
+ - Updated dependencies [52ece13]
186
+ - Updated dependencies [3411ffe]
187
+ - Updated dependencies [7439be7]
188
+ - @stainless-api/ai-chat@0.1.0-beta.3
189
+ - @stainless-api/docs-ui@0.1.0-beta.64
190
+
191
+ ## 0.1.0-beta.28
192
+
193
+ ### Patch Changes
194
+
195
+ - Updated dependencies [274cefc]
196
+ - @stainless-api/docs-ui@0.1.0-beta.63
197
+
198
+ ## 0.1.0-beta.27
199
+
200
+ ### Patch Changes
201
+
202
+ - Updated dependencies [6ef241e]
203
+ - Updated dependencies [d3a85b5]
204
+ - Updated dependencies [d3a85b5]
205
+ - Updated dependencies [2dcb5fb]
206
+ - @stainless-api/docs-ui@0.1.0-beta.62
207
+
208
+ ## 0.1.0-beta.26
209
+
210
+ ### Patch Changes
211
+
212
+ - Updated dependencies [7155fae]
213
+ - @stainless-api/ai-chat@0.1.0-beta.2
214
+ - @stainless-api/docs-ui@0.1.0-beta.61
215
+
216
+ ## 0.1.0-beta.25
217
+
218
+ ### Patch Changes
219
+
220
+ - 5c257e2: separate steelie into separate packages
221
+ - Updated dependencies [9dda4cf]
222
+ - @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.51",
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.82"
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() });