@tanstack/ai-devtools-core 0.2.1 → 0.2.3

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 (29) hide show
  1. package/README.md +1 -0
  2. package/dist/esm/components/ConversationDetails.js +74 -1
  3. package/dist/esm/components/ConversationDetails.js.map +1 -1
  4. package/dist/esm/components/conversation/ActivityEventsTab.d.ts +8 -0
  5. package/dist/esm/components/conversation/ActivityEventsTab.js +72 -0
  6. package/dist/esm/components/conversation/ActivityEventsTab.js.map +1 -0
  7. package/dist/esm/components/conversation/ConversationHeader.js +109 -66
  8. package/dist/esm/components/conversation/ConversationHeader.js.map +1 -1
  9. package/dist/esm/components/conversation/ConversationTabs.d.ts +1 -1
  10. package/dist/esm/components/conversation/ConversationTabs.js +107 -3
  11. package/dist/esm/components/conversation/ConversationTabs.js.map +1 -1
  12. package/dist/esm/components/conversation/MessageCard.js +83 -28
  13. package/dist/esm/components/conversation/MessageCard.js.map +1 -1
  14. package/dist/esm/components/conversation/index.d.ts +1 -0
  15. package/dist/esm/store/ai-context.d.ts +19 -2
  16. package/dist/esm/store/ai-context.js +528 -262
  17. package/dist/esm/store/ai-context.js.map +1 -1
  18. package/dist/esm/styles/use-styles.d.ts +7 -0
  19. package/dist/esm/styles/use-styles.js +65 -1
  20. package/dist/esm/styles/use-styles.js.map +1 -1
  21. package/package.json +2 -2
  22. package/src/components/ConversationDetails.tsx +70 -1
  23. package/src/components/conversation/ActivityEventsTab.tsx +57 -0
  24. package/src/components/conversation/ConversationHeader.tsx +52 -36
  25. package/src/components/conversation/ConversationTabs.tsx +121 -2
  26. package/src/components/conversation/MessageCard.tsx +45 -10
  27. package/src/components/conversation/index.ts +1 -0
  28. package/src/store/ai-context.tsx +783 -418
  29. package/src/styles/use-styles.ts +65 -1
@@ -1,9 +1,16 @@
1
- import { Show } from 'solid-js'
1
+ import { Show, createEffect, createSignal } from 'solid-js'
2
2
  import { useStyles } from '../../styles/use-styles'
3
3
  import type { Component } from 'solid-js'
4
4
  import type { Conversation } from '../../store/ai-context'
5
5
 
6
- export type TabType = 'messages' | 'chunks' | 'summaries'
6
+ export type TabType =
7
+ | 'messages'
8
+ | 'chunks'
9
+ | 'summaries'
10
+ | 'image'
11
+ | 'speech'
12
+ | 'transcription'
13
+ | 'video'
7
14
 
8
15
  interface ConversationTabsProps {
9
16
  conversation: Conversation
@@ -20,6 +27,57 @@ export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
20
27
  conv().chunks.reduce((sum, c) => sum + (c.chunkCount || 1), 0)
21
28
 
22
29
  const summariesCount = () => conv().summaries?.length ?? 0
30
+ const imageCount = () => conv().imageEvents?.length ?? 0
31
+ const speechCount = () => conv().speechEvents?.length ?? 0
32
+ const transcriptionCount = () => conv().transcriptionEvents?.length ?? 0
33
+ const videoCount = () => conv().videoEvents?.length ?? 0
34
+
35
+ const [imagePulse, setImagePulse] = createSignal(false)
36
+ const [speechPulse, setSpeechPulse] = createSignal(false)
37
+ const [transcriptionPulse, setTranscriptionPulse] = createSignal(false)
38
+ const [videoPulse, setVideoPulse] = createSignal(false)
39
+
40
+ const triggerPulse = (setter: (value: boolean) => void) => {
41
+ setter(true)
42
+ setTimeout(() => setter(false), 2000)
43
+ }
44
+
45
+ let previousImageCount = 0
46
+ let previousSpeechCount = 0
47
+ let previousTranscriptionCount = 0
48
+ let previousVideoCount = 0
49
+
50
+ createEffect(() => {
51
+ const count = imageCount()
52
+ if (count > 0 && previousImageCount === 0) {
53
+ triggerPulse(setImagePulse)
54
+ }
55
+ previousImageCount = count
56
+ })
57
+
58
+ createEffect(() => {
59
+ const count = speechCount()
60
+ if (count > 0 && previousSpeechCount === 0) {
61
+ triggerPulse(setSpeechPulse)
62
+ }
63
+ previousSpeechCount = count
64
+ })
65
+
66
+ createEffect(() => {
67
+ const count = transcriptionCount()
68
+ if (count > 0 && previousTranscriptionCount === 0) {
69
+ triggerPulse(setTranscriptionPulse)
70
+ }
71
+ previousTranscriptionCount = count
72
+ })
73
+
74
+ createEffect(() => {
75
+ const count = videoCount()
76
+ if (count > 0 && previousVideoCount === 0) {
77
+ triggerPulse(setVideoPulse)
78
+ }
79
+ previousVideoCount = count
80
+ })
23
81
 
24
82
  // Determine if we should show any chat-related tabs
25
83
  // For server conversations, don't show messages tab - only chunks
@@ -27,6 +85,11 @@ export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
27
85
  conv().type === 'client' && conv().messages.length > 0
28
86
  const hasChunks = () => conv().chunks.length > 0 || conv().type === 'server'
29
87
  const hasSummaries = () => conv().hasSummarize || summariesCount() > 0
88
+ const hasImage = () => conv().hasImage || imageCount() > 0
89
+ const hasSpeech = () => conv().hasSpeech || speechCount() > 0
90
+ const hasTranscription = () =>
91
+ conv().hasTranscription || transcriptionCount() > 0
92
+ const hasVideo = () => conv().hasVideo || videoCount() > 0
30
93
 
31
94
  // Count how many tabs would be visible
32
95
  const visibleTabCount = () => {
@@ -34,6 +97,10 @@ export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
34
97
  if (hasMessages()) count++
35
98
  if (hasChunks() && conv().type === 'server') count++
36
99
  if (hasSummaries()) count++
100
+ if (hasImage()) count++
101
+ if (hasSpeech()) count++
102
+ if (hasTranscription()) count++
103
+ if (hasVideo()) count++
37
104
  return count
38
105
  }
39
106
 
@@ -83,6 +150,58 @@ export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
83
150
  📝 Summaries ({summariesCount()})
84
151
  </button>
85
152
  </Show>
153
+ <Show when={hasImage()}>
154
+ <button
155
+ class={`${styles().actionButton} ${
156
+ props.activeTab === 'image'
157
+ ? styles().conversationDetails.tabButtonActive
158
+ : ''
159
+ } ${imagePulse() ? styles().conversationDetails.tabButtonPulse : ''}`}
160
+ onClick={() => props.onTabChange('image')}
161
+ >
162
+ 🖼️ Image ({imageCount()})
163
+ </button>
164
+ </Show>
165
+ <Show when={hasSpeech()}>
166
+ <button
167
+ class={`${styles().actionButton} ${
168
+ props.activeTab === 'speech'
169
+ ? styles().conversationDetails.tabButtonActive
170
+ : ''
171
+ } ${speechPulse() ? styles().conversationDetails.tabButtonPulse : ''}`}
172
+ onClick={() => props.onTabChange('speech')}
173
+ >
174
+ 🔊 Speech ({speechCount()})
175
+ </button>
176
+ </Show>
177
+ <Show when={hasTranscription()}>
178
+ <button
179
+ class={`${styles().actionButton} ${
180
+ props.activeTab === 'transcription'
181
+ ? styles().conversationDetails.tabButtonActive
182
+ : ''
183
+ } ${
184
+ transcriptionPulse()
185
+ ? styles().conversationDetails.tabButtonPulse
186
+ : ''
187
+ }`}
188
+ onClick={() => props.onTabChange('transcription')}
189
+ >
190
+ 📝 Transcription ({transcriptionCount()})
191
+ </button>
192
+ </Show>
193
+ <Show when={hasVideo()}>
194
+ <button
195
+ class={`${styles().actionButton} ${
196
+ props.activeTab === 'video'
197
+ ? styles().conversationDetails.tabButtonActive
198
+ : ''
199
+ } ${videoPulse() ? styles().conversationDetails.tabButtonPulse : ''}`}
200
+ onClick={() => props.onTabChange('video')}
201
+ >
202
+ 🎬 Video ({videoCount()})
203
+ </button>
204
+ </Show>
86
205
  </div>
87
206
  )
88
207
  }
@@ -1,6 +1,7 @@
1
1
  import { For, Show } from 'solid-js'
2
+ import { JsonTree } from '@tanstack/devtools-ui'
2
3
  import { useStyles } from '../../styles/use-styles'
3
- import { formatTimestamp } from '../utils'
4
+ import { formatDuration, formatTimestamp } from '../utils'
4
5
  import { ToolCallDisplay } from './ToolCallDisplay'
5
6
  import { ChunksCollapsible } from './ChunksCollapsible'
6
7
  import type { Message } from '../../store/ai-store'
@@ -31,13 +32,30 @@ export const MessageCard: Component<MessageCardProps> = (props) => {
31
32
  return `${base} ${styles().conversationDetails.messageCardServer}`
32
33
  }
33
34
 
34
- // Check if this is a non-user message (needs source banner and content wrapper)
35
- const isSourcedMessage = () => msg().role !== 'user'
35
+ const parseJsonContent = () => {
36
+ const content = msg().content
37
+ if (typeof content !== 'string') return null
38
+ const trimmed = content.trim()
39
+ if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) return null
40
+ try {
41
+ return JSON.parse(trimmed) as Record<string, unknown> | Array<unknown>
42
+ } catch {
43
+ return null
44
+ }
45
+ }
46
+
47
+ const toolDuration = () => {
48
+ const durations = msg()
49
+ .toolCalls?.map((tool) => tool.duration || 0)
50
+ .filter((duration) => duration > 0)
51
+ if (!durations || durations.length === 0) return 0
52
+ return durations.reduce((total, duration) => total + duration, 0)
53
+ }
36
54
 
37
55
  return (
38
56
  <div class={getCardClass()}>
39
57
  {/* Source indicator banner at top of card */}
40
- <Show when={isSourcedMessage()}>
58
+ <Show when={msg().role === 'assistant'}>
41
59
  <div
42
60
  class={`${styles().conversationDetails.sourceBanner} ${
43
61
  isClientMessage()
@@ -56,9 +74,9 @@ export const MessageCard: Component<MessageCardProps> = (props) => {
56
74
  {/* Content wrapper with padding */}
57
75
  <div
58
76
  class={
59
- isSourcedMessage()
60
- ? styles().conversationDetails.messageCardContent
61
- : ''
77
+ msg().role === 'user'
78
+ ? ''
79
+ : styles().conversationDetails.messageCardContent
62
80
  }
63
81
  >
64
82
  <div class={styles().conversationDetails.messageHeader}>
@@ -100,6 +118,14 @@ export const MessageCard: Component<MessageCardProps> = (props) => {
100
118
  <span>{msg().usage?.completionTokens.toLocaleString()} out</span>
101
119
  </div>
102
120
  </Show>
121
+ <Show when={toolDuration() > 0}>
122
+ <div class={styles().conversationDetails.messageUsage}>
123
+ <span class={styles().conversationDetails.messageUsageIcon}>
124
+ ⏱️
125
+ </span>
126
+ <span>{formatDuration(toolDuration())}</span>
127
+ </div>
128
+ </Show>
103
129
  </div>
104
130
 
105
131
  {/* Thinking content (for extended thinking models) */}
@@ -114,9 +140,18 @@ export const MessageCard: Component<MessageCardProps> = (props) => {
114
140
  </details>
115
141
  </Show>
116
142
 
117
- <div class={styles().conversationDetails.messageContent}>
118
- {msg().content}
119
- </div>
143
+ <Show
144
+ when={msg().role === 'tool' && parseJsonContent() !== null}
145
+ fallback={
146
+ <div class={styles().conversationDetails.messageContent}>
147
+ {msg().content}
148
+ </div>
149
+ }
150
+ >
151
+ <div class={styles().conversationDetails.toolJsonContainer}>
152
+ <JsonTree value={parseJsonContent()!} />
153
+ </div>
154
+ </Show>
120
155
 
121
156
  {/* Tool Calls Display */}
122
157
  <Show when={msg().toolCalls && msg().toolCalls!.length > 0}>
@@ -5,3 +5,4 @@ export type { TabType } from './ConversationTabs'
5
5
  export { MessagesTab } from './MessagesTab'
6
6
  export { ChunksTab } from './ChunksTab'
7
7
  export { SummariesTab } from './SummariesTab'
8
+ export { ActivityEventsTab } from './ActivityEventsTab'