@tanstack/ai-devtools-core 0.3.37 → 0.4.0

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 (52) hide show
  1. package/dist/Shell/DKCFIT6H.js +12142 -0
  2. package/dist/Shell/SLOX2TPU.js +12119 -0
  3. package/dist/Shell/Z6HLJOVZ.js +10170 -0
  4. package/dist/dev.js +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/production/Shell/DKCFIT6H.js +12142 -0
  7. package/dist/production/Shell/SLOX2TPU.js +12119 -0
  8. package/dist/production/Shell/Z6HLJOVZ.js +10170 -0
  9. package/dist/production/dev.js +1 -1
  10. package/dist/production/index.js +1 -1
  11. package/dist/production/server.js +1 -1
  12. package/dist/server.js +1 -1
  13. package/package.json +4 -4
  14. package/src/components/Shell.tsx +74 -30
  15. package/src/components/conversation/IterationCard.tsx +261 -12
  16. package/src/components/conversation/IterationTimeline.tsx +66 -16
  17. package/src/components/conversation/index.ts +0 -9
  18. package/src/components/hooks/FixtureNamePopover.tsx +69 -0
  19. package/src/components/hooks/GenerationPanel.tsx +847 -0
  20. package/src/components/hooks/HookDashboard.tsx +292 -0
  21. package/src/components/hooks/HookDetails.tsx +1757 -0
  22. package/src/components/hooks/ToolFixtureForm.tsx +347 -0
  23. package/src/components/hooks/hook-dashboard-model.ts +134 -0
  24. package/src/components/hooks/index.ts +4 -0
  25. package/src/components/hooks/preview-messages.ts +117 -0
  26. package/src/components/hooks/preview-model.ts +195 -0
  27. package/src/components/utils/format.ts +0 -58
  28. package/src/store/ai-context.tsx +1165 -159
  29. package/src/store/ai-store.ts +0 -1
  30. package/src/store/hook-registry.ts +815 -0
  31. package/src/store/message-event-utils.ts +60 -0
  32. package/src/styles/use-styles.ts +1558 -99
  33. package/dist/Shell/MRWZEHAJ.js +0 -6225
  34. package/dist/Shell/RRRNZJ5E.js +0 -7560
  35. package/dist/production/Shell/MRWZEHAJ.js +0 -6225
  36. package/dist/production/Shell/RRRNZJ5E.js +0 -7560
  37. package/src/components/ConversationDetails.tsx +0 -216
  38. package/src/components/ConversationsList.tsx +0 -21
  39. package/src/components/conversation/ActivityEventsTab.tsx +0 -57
  40. package/src/components/conversation/ChunkBadges.tsx +0 -51
  41. package/src/components/conversation/ChunkItem.tsx +0 -273
  42. package/src/components/conversation/ChunksCollapsible.tsx +0 -56
  43. package/src/components/conversation/ChunksTab.tsx +0 -87
  44. package/src/components/conversation/ConversationHeader.tsx +0 -106
  45. package/src/components/conversation/ConversationTabs.tsx +0 -205
  46. package/src/components/conversation/MessageCard.tsx +0 -174
  47. package/src/components/conversation/MessageGroup.tsx +0 -101
  48. package/src/components/conversation/MessagesTab.tsx +0 -30
  49. package/src/components/conversation/SummariesTab.tsx +0 -91
  50. package/src/components/conversation/ToolCallDisplay.tsx +0 -99
  51. package/src/components/list/ConversationRow.tsx +0 -85
  52. package/src/components/list/index.ts +0 -1
@@ -1,8 +1,14 @@
1
1
  import { For, Index, Show, createMemo, createSignal } from 'solid-js'
2
2
  import { JsonTree } from '@tanstack/devtools-ui'
3
3
  import { useStyles } from '../../styles/use-styles'
4
+ import {
5
+ createHoverTarget,
6
+ getHoverDataAttributes,
7
+ isMessageHighlighted,
8
+ } from '../hooks/preview-model'
4
9
  import { formatDuration } from '../utils'
5
10
  import { IterationCard } from './IterationCard'
11
+ import type { HoverTarget } from '../hooks/preview-model'
6
12
  import type { Iteration, Message } from '../../store/ai-store'
7
13
  import type { Component } from 'solid-js'
8
14
 
@@ -15,6 +21,8 @@ interface UserMessageGroup {
15
21
  interface IterationTimelineProps {
16
22
  iterations: Array<Iteration>
17
23
  messages: Array<Message>
24
+ hoverTarget?: HoverTarget | null
25
+ onHoverTarget?: (target: HoverTarget | null) => void
18
26
  }
19
27
 
20
28
  export const IterationTimeline: Component<IterationTimelineProps> = (props) => {
@@ -79,6 +87,8 @@ export const IterationTimeline: Component<IterationTimelineProps> = (props) => {
79
87
  <UserMessageGroupCard
80
88
  group={group()}
81
89
  allMessages={props.messages}
90
+ hoverTarget={props.hoverTarget ?? null}
91
+ onHoverTarget={props.onHoverTarget}
82
92
  />
83
93
  )}
84
94
  </Index>
@@ -127,6 +137,8 @@ export const SystemPromptItem: Component<{ prompt: string; index: number }> = (
127
137
  const UserMessageGroupCard: Component<{
128
138
  group: UserMessageGroup
129
139
  allMessages: Array<Message>
140
+ hoverTarget: HoverTarget | null
141
+ onHoverTarget?: (target: HoverTarget | null) => void
130
142
  }> = (props) => {
131
143
  const styles = useStyles()
132
144
  const s = () => styles().iterationTimeline
@@ -135,6 +147,7 @@ const UserMessageGroupCard: Component<{
135
147
 
136
148
  const group = () => props.group
137
149
  const userMsg = () => group().userMessage
150
+ const userMessageId = () => userMsg()?.id
138
151
  const iters = () => group().iterations
139
152
 
140
153
  const totalDuration = createMemo(() => {
@@ -170,42 +183,42 @@ const UserMessageGroupCard: Component<{
170
183
  return count
171
184
  })
172
185
 
173
- /**
174
- * Total usage across this group.
175
- * Iterations store CUMULATIVE usage per request, so we take the MAX
176
- * cumulative from each request group (the last iteration's value
177
- * represents the total for that request).
178
- */
186
+ // Iterations store CUMULATIVE usage per request, so keep the MAX totalTokens
187
+ // reading per requestId. Comparing on totalTokens directly (not prompt +
188
+ // completion) is necessary because some providers — Anthropic, OpenAI o-series
189
+ // — bundle reasoning tokens into totalTokens that are not summed into
190
+ // prompt + completion, and a later reading with fewer reasoning tokens would
191
+ // otherwise overwrite a larger earlier one.
179
192
  const totalUsage = createMemo(() => {
180
193
  const maxByRequest = new Map<
181
194
  string,
182
- { prompt: number; completion: number }
195
+ { prompt: number; completion: number; total: number }
183
196
  >()
184
197
  for (const it of iters()) {
185
198
  if (!it.usage) continue
186
199
  const key = it.requestId || '__default__'
187
200
  const existing = maxByRequest.get(key)
188
- if (
189
- !existing ||
190
- it.usage.totalTokens > existing.prompt + existing.completion
191
- ) {
201
+ if (!existing || it.usage.totalTokens > existing.total) {
192
202
  maxByRequest.set(key, {
193
203
  prompt: it.usage.promptTokens,
194
204
  completion: it.usage.completionTokens,
205
+ total: it.usage.totalTokens,
195
206
  })
196
207
  }
197
208
  }
198
209
  let prompt = 0
199
210
  let completion = 0
211
+ let total = 0
200
212
  for (const v of maxByRequest.values()) {
201
213
  prompt += v.prompt
202
214
  completion += v.completion
215
+ total += v.total
203
216
  }
204
- if (prompt + completion === 0) return undefined
217
+ if (total === 0) return undefined
205
218
  return {
206
219
  promptTokens: prompt,
207
220
  completionTokens: completion,
208
- totalTokens: prompt + completion,
221
+ totalTokens: total,
209
222
  }
210
223
  })
211
224
 
@@ -228,6 +241,29 @@ const UserMessageGroupCard: Component<{
228
241
  return text.length > 120 ? text.slice(0, 120) + '...' : text
229
242
  }
230
243
 
244
+ const handleUserMouseEnter = () => {
245
+ const messageId = userMessageId()
246
+ if (messageId) {
247
+ props.onHoverTarget?.(
248
+ createHoverTarget({
249
+ messageIds: [messageId],
250
+ origin: 'timeline',
251
+ }),
252
+ )
253
+ }
254
+ }
255
+
256
+ const handleUserMouseLeave = () => {
257
+ props.onHoverTarget?.(null)
258
+ }
259
+
260
+ const isUserMessageHighlighted = () => {
261
+ const messageId = userMessageId()
262
+ return messageId
263
+ ? isMessageHighlighted(messageId, props.hoverTarget)
264
+ : false
265
+ }
266
+
231
267
  // Config from the first iteration of this group
232
268
  const firstIter = createMemo(() => iters()[0])
233
269
 
@@ -262,9 +298,21 @@ const UserMessageGroupCard: Component<{
262
298
  toolNames().length > 0 || systemPrompts().length > 0 || hasModelOptions()
263
299
 
264
300
  return (
265
- <div class={`${s().card} ${groupAccentClass()}`}>
301
+ <div
302
+ {...getHoverDataAttributes({
303
+ messageIds: userMessageId() ? [userMessageId() ?? ''] : [],
304
+ })}
305
+ class={`${s().card} ${groupAccentClass()} ${
306
+ isUserMessageHighlighted() ? s().cardHighlighted : ''
307
+ }`}
308
+ >
266
309
  {/* User message header */}
267
- <div class={s().cardHeader} onClick={() => setIsOpen(!isOpen())}>
310
+ <div
311
+ class={s().cardHeader}
312
+ onClick={() => setIsOpen(!isOpen())}
313
+ onMouseEnter={handleUserMouseEnter}
314
+ onMouseLeave={handleUserMouseLeave}
315
+ >
268
316
  <div class={s().userBubble}>U</div>
269
317
  <div class={s().cardHeaderContent}>
270
318
  <span class={s().cardHeaderLabel}>{userContent()}</span>
@@ -390,7 +438,7 @@ const UserMessageGroupCard: Component<{
390
438
  <span class={s().configPanelLabel}>Model Options</span>
391
439
  <div class={s().configJsonTreeContainer}>
392
440
  <JsonTree
393
- value={modelOptions() as Record<string, unknown>}
441
+ value={modelOptions()}
394
442
  defaultExpansionDepth={2}
395
443
  copyable
396
444
  />
@@ -415,6 +463,8 @@ const UserMessageGroupCard: Component<{
415
463
  messages={props.allMessages}
416
464
  index={index()}
417
465
  isLast={index() === iters().length - 1}
466
+ hoverTarget={props.hoverTarget}
467
+ onHoverTarget={props.onHoverTarget}
418
468
  />
419
469
  )}
420
470
  </For>
@@ -1,11 +1,2 @@
1
- export { ConversationHeader } from './ConversationHeader'
2
- export { ConversationTabs } from './ConversationTabs'
3
- export type { TabType } from './ConversationTabs'
4
-
5
- export { MessagesTab } from './MessagesTab'
6
- export { ChunksTab } from './ChunksTab'
7
- export { SummariesTab } from './SummariesTab'
8
- export { ActivityEventsTab } from './ActivityEventsTab'
9
-
10
1
  export { IterationTimeline } from './IterationTimeline'
11
2
  export { IterationCard } from './IterationCard'
@@ -0,0 +1,69 @@
1
+ import { createEffect, createSignal } from 'solid-js'
2
+ import { useStyles } from '../../styles/use-styles'
3
+ import type { Component } from 'solid-js'
4
+
5
+ interface FixtureNamePopoverProps {
6
+ defaultName: string
7
+ onCancel: () => void
8
+ onSave: (name: string) => void
9
+ }
10
+
11
+ export const FixtureNamePopover: Component<FixtureNamePopoverProps> = (
12
+ props,
13
+ ) => {
14
+ const styles = useStyles()
15
+ const [name, setName] = createSignal(props.defaultName)
16
+ let input: HTMLInputElement | undefined
17
+
18
+ createEffect(() => {
19
+ setName(props.defaultName)
20
+ queueMicrotask(() => input?.focus())
21
+ })
22
+
23
+ const handleSave = () => {
24
+ const trimmedName = name().trim()
25
+ if (!trimmedName) return
26
+ props.onSave(trimmedName)
27
+ }
28
+
29
+ return (
30
+ <form
31
+ class={styles().hookDetails.fixturePopover}
32
+ data-testid="ai-devtools-fixture-save-popover"
33
+ onSubmit={(event) => {
34
+ event.preventDefault()
35
+ handleSave()
36
+ }}
37
+ >
38
+ <label class={styles().hookDetails.fixtureField}>
39
+ <span class={styles().hookDetails.fixtureLabel}>Fixture name</span>
40
+ <input
41
+ ref={(element) => {
42
+ input = element
43
+ }}
44
+ class={styles().hookDetails.fixtureInput}
45
+ data-testid="ai-devtools-fixture-name-input"
46
+ value={name()}
47
+ onInput={(event) => setName(event.currentTarget.value)}
48
+ />
49
+ </label>
50
+ <div class={styles().hookDetails.fixtureActions}>
51
+ <button
52
+ class={styles().hookDetails.fixtureButton}
53
+ data-testid="ai-devtools-fixture-save-confirm"
54
+ type="submit"
55
+ >
56
+ Save
57
+ </button>
58
+ <button
59
+ class={`${styles().hookDetails.fixtureButton} ${styles().hookDetails.fixtureButtonSecondary}`}
60
+ data-testid="ai-devtools-fixture-save-cancel"
61
+ type="button"
62
+ onClick={props.onCancel}
63
+ >
64
+ Cancel
65
+ </button>
66
+ </div>
67
+ </form>
68
+ )
69
+ }