@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.
- package/README.md +1 -0
- package/dist/esm/components/ConversationDetails.js +74 -1
- package/dist/esm/components/ConversationDetails.js.map +1 -1
- package/dist/esm/components/conversation/ActivityEventsTab.d.ts +8 -0
- package/dist/esm/components/conversation/ActivityEventsTab.js +72 -0
- package/dist/esm/components/conversation/ActivityEventsTab.js.map +1 -0
- package/dist/esm/components/conversation/ConversationHeader.js +109 -66
- package/dist/esm/components/conversation/ConversationHeader.js.map +1 -1
- package/dist/esm/components/conversation/ConversationTabs.d.ts +1 -1
- package/dist/esm/components/conversation/ConversationTabs.js +107 -3
- package/dist/esm/components/conversation/ConversationTabs.js.map +1 -1
- package/dist/esm/components/conversation/MessageCard.js +83 -28
- package/dist/esm/components/conversation/MessageCard.js.map +1 -1
- package/dist/esm/components/conversation/index.d.ts +1 -0
- package/dist/esm/store/ai-context.d.ts +19 -2
- package/dist/esm/store/ai-context.js +528 -262
- package/dist/esm/store/ai-context.js.map +1 -1
- package/dist/esm/styles/use-styles.d.ts +7 -0
- package/dist/esm/styles/use-styles.js +65 -1
- package/dist/esm/styles/use-styles.js.map +1 -1
- package/package.json +2 -2
- package/src/components/ConversationDetails.tsx +70 -1
- package/src/components/conversation/ActivityEventsTab.tsx +57 -0
- package/src/components/conversation/ConversationHeader.tsx +52 -36
- package/src/components/conversation/ConversationTabs.tsx +121 -2
- package/src/components/conversation/MessageCard.tsx +45 -10
- package/src/components/conversation/index.ts +1 -0
- package/src/store/ai-context.tsx +783 -418
- 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 =
|
|
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
|
-
|
|
35
|
-
|
|
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={
|
|
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
|
-
|
|
60
|
-
?
|
|
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
|
-
<
|
|
118
|
-
{msg().
|
|
119
|
-
|
|
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}>
|