@tanstack/ai-devtools-core 0.0.1
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/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/esm/components/ConversationDetails.d.ts +2 -0
- package/dist/esm/components/ConversationDetails.js +134 -0
- package/dist/esm/components/ConversationDetails.js.map +1 -0
- package/dist/esm/components/ConversationsList.d.ts +2 -0
- package/dist/esm/components/ConversationsList.js +30 -0
- package/dist/esm/components/ConversationsList.js.map +1 -0
- package/dist/esm/components/Shell.d.ts +1 -0
- package/dist/esm/components/Shell.js +94 -0
- package/dist/esm/components/Shell.js.map +1 -0
- package/dist/esm/components/conversation/ChunkBadges.d.ts +7 -0
- package/dist/esm/components/conversation/ChunkBadges.js +54 -0
- package/dist/esm/components/conversation/ChunkBadges.js.map +1 -0
- package/dist/esm/components/conversation/ChunkItem.d.ts +9 -0
- package/dist/esm/components/conversation/ChunkItem.js +247 -0
- package/dist/esm/components/conversation/ChunkItem.js.map +1 -0
- package/dist/esm/components/conversation/ChunksCollapsible.d.ts +7 -0
- package/dist/esm/components/conversation/ChunksCollapsible.js +77 -0
- package/dist/esm/components/conversation/ChunksCollapsible.js.map +1 -0
- package/dist/esm/components/conversation/ChunksTab.d.ts +8 -0
- package/dist/esm/components/conversation/ChunksTab.js +87 -0
- package/dist/esm/components/conversation/ChunksTab.js.map +1 -0
- package/dist/esm/components/conversation/ConversationHeader.d.ts +7 -0
- package/dist/esm/components/conversation/ConversationHeader.js +187 -0
- package/dist/esm/components/conversation/ConversationHeader.js.map +1 -0
- package/dist/esm/components/conversation/ConversationTabs.d.ts +10 -0
- package/dist/esm/components/conversation/ConversationTabs.js +88 -0
- package/dist/esm/components/conversation/ConversationTabs.js.map +1 -0
- package/dist/esm/components/conversation/EmbeddingsTab.d.ts +7 -0
- package/dist/esm/components/conversation/EmbeddingsTab.js +90 -0
- package/dist/esm/components/conversation/EmbeddingsTab.js.map +1 -0
- package/dist/esm/components/conversation/MessageCard.d.ts +7 -0
- package/dist/esm/components/conversation/MessageCard.js +152 -0
- package/dist/esm/components/conversation/MessageCard.js.map +1 -0
- package/dist/esm/components/conversation/MessageGroup.d.ts +15 -0
- package/dist/esm/components/conversation/MessageGroup.js +119 -0
- package/dist/esm/components/conversation/MessageGroup.js.map +1 -0
- package/dist/esm/components/conversation/MessagesTab.d.ts +7 -0
- package/dist/esm/components/conversation/MessagesTab.js +37 -0
- package/dist/esm/components/conversation/MessagesTab.js.map +1 -0
- package/dist/esm/components/conversation/SummariesTab.d.ts +7 -0
- package/dist/esm/components/conversation/SummariesTab.js +110 -0
- package/dist/esm/components/conversation/SummariesTab.js.map +1 -0
- package/dist/esm/components/conversation/ToolCallDisplay.d.ts +7 -0
- package/dist/esm/components/conversation/ToolCallDisplay.js +123 -0
- package/dist/esm/components/conversation/ToolCallDisplay.js.map +1 -0
- package/dist/esm/components/conversation/index.d.ts +7 -0
- package/dist/esm/components/list/ConversationRow.d.ts +7 -0
- package/dist/esm/components/list/ConversationRow.js +122 -0
- package/dist/esm/components/list/ConversationRow.js.map +1 -0
- package/dist/esm/components/list/index.d.ts +1 -0
- package/dist/esm/components/utils/format.d.ts +6 -0
- package/dist/esm/components/utils/format.js +68 -0
- package/dist/esm/components/utils/format.js.map +1 -0
- package/dist/esm/components/utils/index.d.ts +1 -0
- package/dist/esm/core.d.ts +18 -0
- package/dist/esm/core.js +9 -0
- package/dist/esm/core.js.map +1 -0
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/production.d.ts +2 -0
- package/dist/esm/production.js +5 -0
- package/dist/esm/production.js.map +1 -0
- package/dist/esm/store/ai-context.d.ts +126 -0
- package/dist/esm/store/ai-context.js +1081 -0
- package/dist/esm/store/ai-context.js.map +1 -0
- package/dist/esm/store/ai-store.d.ts +1 -0
- package/dist/esm/styles/tokens.d.ts +298 -0
- package/dist/esm/styles/tokens.js +107 -0
- package/dist/esm/styles/tokens.js.map +1 -0
- package/dist/esm/styles/use-styles.d.ts +226 -0
- package/dist/esm/styles/use-styles.js +1538 -0
- package/dist/esm/styles/use-styles.js.map +1 -0
- package/package.json +61 -0
- package/src/components/ConversationDetails.tsx +89 -0
- package/src/components/ConversationsList.tsx +21 -0
- package/src/components/Shell.tsx +93 -0
- package/src/components/conversation/ChunkBadges.tsx +51 -0
- package/src/components/conversation/ChunkItem.tsx +273 -0
- package/src/components/conversation/ChunksCollapsible.tsx +58 -0
- package/src/components/conversation/ChunksTab.tsx +85 -0
- package/src/components/conversation/ConversationHeader.tsx +130 -0
- package/src/components/conversation/ConversationTabs.tsx +104 -0
- package/src/components/conversation/EmbeddingsTab.tsx +80 -0
- package/src/components/conversation/MessageCard.tsx +137 -0
- package/src/components/conversation/MessageGroup.tsx +101 -0
- package/src/components/conversation/MessagesTab.tsx +30 -0
- package/src/components/conversation/SummariesTab.tsx +91 -0
- package/src/components/conversation/ToolCallDisplay.tsx +99 -0
- package/src/components/conversation/index.ts +8 -0
- package/src/components/list/ConversationRow.tsx +85 -0
- package/src/components/list/index.ts +1 -0
- package/src/components/utils/format.ts +61 -0
- package/src/components/utils/index.ts +1 -0
- package/src/core.tsx +10 -0
- package/src/index.ts +8 -0
- package/src/production.ts +3 -0
- package/src/store/ai-context.tsx +1784 -0
- package/src/store/ai-store.ts +2 -0
- package/src/styles/tokens.ts +305 -0
- package/src/styles/use-styles.ts +1537 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { Show, createSignal } from 'solid-js'
|
|
2
|
+
import { JsonTree } from '@tanstack/devtools-ui'
|
|
3
|
+
import { useStyles } from '../../styles/use-styles'
|
|
4
|
+
import { formatDuration, formatTimestamp, getChunkTypeColor } from '../utils'
|
|
5
|
+
import type { Component } from 'solid-js'
|
|
6
|
+
import type { Chunk } from '../../store/ai-store'
|
|
7
|
+
|
|
8
|
+
interface ChunkItemProps {
|
|
9
|
+
chunk: Chunk
|
|
10
|
+
index: number
|
|
11
|
+
variant?: 'small' | 'large'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ChunkItem: Component<ChunkItemProps> = (props) => {
|
|
15
|
+
const styles = useStyles()
|
|
16
|
+
const [showRaw, setShowRaw] = createSignal(false)
|
|
17
|
+
const isLarge = () => props.variant === 'large'
|
|
18
|
+
const chunkCount = () => props.chunk.chunkCount || 1
|
|
19
|
+
|
|
20
|
+
const parseToolArguments = (): Record<string, unknown> => {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(props.chunk.arguments || '{}') as Record<
|
|
23
|
+
string,
|
|
24
|
+
unknown
|
|
25
|
+
>
|
|
26
|
+
} catch {
|
|
27
|
+
return { raw: props.chunk.arguments }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const parseToolResult = (): Record<string, unknown> => {
|
|
32
|
+
try {
|
|
33
|
+
if (typeof props.chunk.result === 'string') {
|
|
34
|
+
return JSON.parse(props.chunk.result) as Record<string, unknown>
|
|
35
|
+
}
|
|
36
|
+
return props.chunk.result as Record<string, unknown>
|
|
37
|
+
} catch {
|
|
38
|
+
return { raw: props.chunk.result }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
class={
|
|
45
|
+
isLarge()
|
|
46
|
+
? styles().conversationDetails.chunkItemLarge
|
|
47
|
+
: styles().conversationDetails.chunkItem
|
|
48
|
+
}
|
|
49
|
+
>
|
|
50
|
+
{/* Chunk Header */}
|
|
51
|
+
<div
|
|
52
|
+
class={
|
|
53
|
+
isLarge()
|
|
54
|
+
? styles().conversationDetails.chunkHeaderLarge
|
|
55
|
+
: styles().conversationDetails.chunkHeader
|
|
56
|
+
}
|
|
57
|
+
>
|
|
58
|
+
{/* Chunk Number */}
|
|
59
|
+
<div
|
|
60
|
+
class={
|
|
61
|
+
isLarge()
|
|
62
|
+
? styles().conversationDetails.chunkNumberLarge
|
|
63
|
+
: styles().conversationDetails.chunkNumber
|
|
64
|
+
}
|
|
65
|
+
>
|
|
66
|
+
#{props.index + 1}
|
|
67
|
+
<Show when={chunkCount() > 1}>
|
|
68
|
+
<span style={{ 'margin-left': '4px', opacity: 0.7 }}>
|
|
69
|
+
({chunkCount()} chunks)
|
|
70
|
+
</span>
|
|
71
|
+
</Show>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{/* Type Badge */}
|
|
75
|
+
<div
|
|
76
|
+
class={
|
|
77
|
+
isLarge()
|
|
78
|
+
? styles().conversationDetails.chunkTypeBadgeLarge
|
|
79
|
+
: styles().conversationDetails.chunkTypeBadge
|
|
80
|
+
}
|
|
81
|
+
>
|
|
82
|
+
<div
|
|
83
|
+
class={
|
|
84
|
+
isLarge()
|
|
85
|
+
? styles().conversationDetails.chunkTypeDotLarge
|
|
86
|
+
: styles().conversationDetails.chunkTypeDot
|
|
87
|
+
}
|
|
88
|
+
style={{ background: getChunkTypeColor(props.chunk.type) }}
|
|
89
|
+
/>
|
|
90
|
+
<div
|
|
91
|
+
class={
|
|
92
|
+
isLarge()
|
|
93
|
+
? styles().conversationDetails.chunkTypeLabelLarge
|
|
94
|
+
: styles().conversationDetails.chunkTypeLabel
|
|
95
|
+
}
|
|
96
|
+
>
|
|
97
|
+
{props.chunk.type}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{/* Tool Name Badge */}
|
|
102
|
+
<Show when={props.chunk.toolName}>
|
|
103
|
+
<div
|
|
104
|
+
class={
|
|
105
|
+
isLarge()
|
|
106
|
+
? styles().conversationDetails.chunkToolBadgeLarge
|
|
107
|
+
: styles().conversationDetails.chunkToolBadge
|
|
108
|
+
}
|
|
109
|
+
>
|
|
110
|
+
🔧 {props.chunk.toolName}
|
|
111
|
+
</div>
|
|
112
|
+
</Show>
|
|
113
|
+
|
|
114
|
+
{/* Timestamp */}
|
|
115
|
+
<div
|
|
116
|
+
class={
|
|
117
|
+
isLarge()
|
|
118
|
+
? styles().conversationDetails.chunkTimestampLarge
|
|
119
|
+
: styles().conversationDetails.chunkTimestamp
|
|
120
|
+
}
|
|
121
|
+
>
|
|
122
|
+
{formatTimestamp(props.chunk.timestamp)}
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* Toggle Raw JSON Button */}
|
|
126
|
+
<button
|
|
127
|
+
onClick={() => setShowRaw(!showRaw())}
|
|
128
|
+
class={`${
|
|
129
|
+
isLarge()
|
|
130
|
+
? styles().conversationDetails.rawJsonButtonLarge
|
|
131
|
+
: styles().conversationDetails.rawJsonButton
|
|
132
|
+
} ${
|
|
133
|
+
showRaw()
|
|
134
|
+
? styles().conversationDetails.rawJsonButtonActive
|
|
135
|
+
: styles().conversationDetails.rawJsonButtonInactive
|
|
136
|
+
}`}
|
|
137
|
+
title={showRaw() ? 'Show formatted' : 'Show raw JSON'}
|
|
138
|
+
>
|
|
139
|
+
{'{}'}
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
{/* Chunk Content */}
|
|
144
|
+
<Show when={!showRaw()}>
|
|
145
|
+
<Show when={props.chunk.content || props.chunk.delta}>
|
|
146
|
+
<div
|
|
147
|
+
class={
|
|
148
|
+
isLarge()
|
|
149
|
+
? styles().conversationDetails.chunkContentLarge
|
|
150
|
+
: styles().conversationDetails.chunkContent
|
|
151
|
+
}
|
|
152
|
+
>
|
|
153
|
+
{props.chunk.content}
|
|
154
|
+
</div>
|
|
155
|
+
</Show>
|
|
156
|
+
<Show when={props.chunk.error}>
|
|
157
|
+
<div
|
|
158
|
+
class={
|
|
159
|
+
isLarge()
|
|
160
|
+
? styles().conversationDetails.chunkErrorLarge
|
|
161
|
+
: styles().conversationDetails.chunkError
|
|
162
|
+
}
|
|
163
|
+
>
|
|
164
|
+
❌ {props.chunk.error}
|
|
165
|
+
</div>
|
|
166
|
+
</Show>
|
|
167
|
+
<Show when={props.chunk.finishReason}>
|
|
168
|
+
<div
|
|
169
|
+
class={
|
|
170
|
+
isLarge()
|
|
171
|
+
? styles().conversationDetails.chunkFinishLarge
|
|
172
|
+
: styles().conversationDetails.chunkFinish
|
|
173
|
+
}
|
|
174
|
+
>
|
|
175
|
+
✓{' '}
|
|
176
|
+
{isLarge()
|
|
177
|
+
? `Finish: ${props.chunk.finishReason}`
|
|
178
|
+
: props.chunk.finishReason}
|
|
179
|
+
</div>
|
|
180
|
+
</Show>
|
|
181
|
+
<Show when={props.chunk.type === 'approval'}>
|
|
182
|
+
<div
|
|
183
|
+
class={
|
|
184
|
+
isLarge()
|
|
185
|
+
? styles().conversationDetails.chunkApprovalLarge
|
|
186
|
+
: styles().conversationDetails.chunkApproval
|
|
187
|
+
}
|
|
188
|
+
>
|
|
189
|
+
<div
|
|
190
|
+
class={
|
|
191
|
+
isLarge()
|
|
192
|
+
? styles().conversationDetails.chunkApprovalTitleLarge
|
|
193
|
+
: styles().conversationDetails.chunkApprovalTitle
|
|
194
|
+
}
|
|
195
|
+
>
|
|
196
|
+
⚠️ Approval Required
|
|
197
|
+
</div>
|
|
198
|
+
<Show when={props.chunk.input}>
|
|
199
|
+
<div
|
|
200
|
+
class={
|
|
201
|
+
isLarge()
|
|
202
|
+
? styles().conversationDetails.chunkApprovalInputLarge
|
|
203
|
+
: styles().conversationDetails.chunkApprovalInput
|
|
204
|
+
}
|
|
205
|
+
>
|
|
206
|
+
Input: {JSON.stringify(props.chunk.input, null, 2)}
|
|
207
|
+
</div>
|
|
208
|
+
</Show>
|
|
209
|
+
</div>
|
|
210
|
+
</Show>
|
|
211
|
+
<Show when={props.chunk.type === 'tool_call' && props.chunk.arguments}>
|
|
212
|
+
<div class={styles().conversationDetails.chunkToolCall}>
|
|
213
|
+
<div class={styles().conversationDetails.chunkToolCallHeader}>
|
|
214
|
+
<span class={styles().conversationDetails.chunkToolCallTitle}>
|
|
215
|
+
📤 Tool Arguments
|
|
216
|
+
</span>
|
|
217
|
+
</div>
|
|
218
|
+
<div class={styles().conversationDetails.toolJsonContainer}>
|
|
219
|
+
<JsonTree
|
|
220
|
+
value={parseToolArguments()}
|
|
221
|
+
defaultExpansionDepth={2}
|
|
222
|
+
copyable
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
</Show>
|
|
227
|
+
<Show
|
|
228
|
+
when={
|
|
229
|
+
props.chunk.type === 'tool_result' &&
|
|
230
|
+
props.chunk.result !== undefined
|
|
231
|
+
}
|
|
232
|
+
>
|
|
233
|
+
<div class={styles().conversationDetails.chunkToolResult}>
|
|
234
|
+
<div class={styles().conversationDetails.chunkToolResultHeader}>
|
|
235
|
+
<span class={styles().conversationDetails.chunkToolResultTitle}>
|
|
236
|
+
✓ Tool Result
|
|
237
|
+
</span>
|
|
238
|
+
<Show
|
|
239
|
+
when={
|
|
240
|
+
props.chunk.duration !== undefined && props.chunk.duration > 0
|
|
241
|
+
}
|
|
242
|
+
>
|
|
243
|
+
<span class={styles().conversationDetails.durationBadge}>
|
|
244
|
+
⏱️ {formatDuration(props.chunk.duration)}
|
|
245
|
+
</span>
|
|
246
|
+
</Show>
|
|
247
|
+
</div>
|
|
248
|
+
<div class={styles().conversationDetails.toolJsonContainer}>
|
|
249
|
+
<JsonTree
|
|
250
|
+
value={parseToolResult()}
|
|
251
|
+
defaultExpansionDepth={2}
|
|
252
|
+
copyable
|
|
253
|
+
/>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
</Show>
|
|
257
|
+
</Show>
|
|
258
|
+
|
|
259
|
+
{/* Raw JSON View */}
|
|
260
|
+
<Show when={showRaw()}>
|
|
261
|
+
<div
|
|
262
|
+
class={
|
|
263
|
+
isLarge()
|
|
264
|
+
? styles().conversationDetails.rawJsonLarge
|
|
265
|
+
: styles().conversationDetails.rawJson
|
|
266
|
+
}
|
|
267
|
+
>
|
|
268
|
+
{JSON.stringify(props.chunk, null, 2)}
|
|
269
|
+
</div>
|
|
270
|
+
</Show>
|
|
271
|
+
</div>
|
|
272
|
+
)
|
|
273
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { For, Show } from 'solid-js'
|
|
2
|
+
import { useStyles } from '../../styles/use-styles'
|
|
3
|
+
import { ChunkItem } from './ChunkItem'
|
|
4
|
+
import { ChunkBadges } from './ChunkBadges'
|
|
5
|
+
import type { Component } from 'solid-js'
|
|
6
|
+
import type { Chunk } from '../../store/ai-store'
|
|
7
|
+
|
|
8
|
+
interface ChunksCollapsibleProps {
|
|
9
|
+
chunks: Array<Chunk>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ChunksCollapsible: Component<ChunksCollapsibleProps> = (props) => {
|
|
13
|
+
const styles = useStyles()
|
|
14
|
+
|
|
15
|
+
const accumulatedContent = () =>
|
|
16
|
+
props.chunks
|
|
17
|
+
.filter((c) => c.type === 'content' && (c.content || c.delta))
|
|
18
|
+
.map((c) => c.delta || c.content)
|
|
19
|
+
.join('')
|
|
20
|
+
|
|
21
|
+
// Total raw chunks = sum of all chunkCounts
|
|
22
|
+
const totalRawChunks = () =>
|
|
23
|
+
props.chunks.reduce((sum, c) => sum + (c.chunkCount || 1), 0)
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<details class={styles().conversationDetails.chunksDetails}>
|
|
27
|
+
<summary class={styles().conversationDetails.chunksSummary}>
|
|
28
|
+
<div class={styles().conversationDetails.chunksSummaryRow}>
|
|
29
|
+
<span class={styles().conversationDetails.chunksSummaryArrow}>
|
|
30
|
+
▶
|
|
31
|
+
</span>
|
|
32
|
+
<span class={styles().conversationDetails.chunksSummaryTitle}>
|
|
33
|
+
📦 {totalRawChunks()} chunks
|
|
34
|
+
</span>
|
|
35
|
+
<ChunkBadges chunks={props.chunks} />
|
|
36
|
+
</div>
|
|
37
|
+
{/* Accumulated Content Preview */}
|
|
38
|
+
<Show when={accumulatedContent()}>
|
|
39
|
+
<div
|
|
40
|
+
class={styles().conversationDetails.contentPreview}
|
|
41
|
+
title={accumulatedContent()}
|
|
42
|
+
>
|
|
43
|
+
{accumulatedContent()}
|
|
44
|
+
</div>
|
|
45
|
+
</Show>
|
|
46
|
+
</summary>
|
|
47
|
+
<div class={styles().conversationDetails.chunksContainer}>
|
|
48
|
+
<div class={styles().conversationDetails.chunksList}>
|
|
49
|
+
<For each={props.chunks}>
|
|
50
|
+
{(chunk, index) => (
|
|
51
|
+
<ChunkItem chunk={chunk} index={index()} variant="small" />
|
|
52
|
+
)}
|
|
53
|
+
</For>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</details>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { For, Show } from 'solid-js'
|
|
2
|
+
import { useStyles } from '../../styles/use-styles'
|
|
3
|
+
import { MessageGroup } from './MessageGroup'
|
|
4
|
+
import type { Component } from 'solid-js'
|
|
5
|
+
import type { Chunk, Message } from '../../store/ai-store'
|
|
6
|
+
|
|
7
|
+
interface ChunksTabProps {
|
|
8
|
+
chunks: Array<Chunk>
|
|
9
|
+
messages?: Array<Message>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ChunksTab: Component<ChunksTabProps> = (props) => {
|
|
13
|
+
const styles = useStyles()
|
|
14
|
+
|
|
15
|
+
// Create a map of messageId to usage for quick lookup
|
|
16
|
+
const usageByMessageId = () => {
|
|
17
|
+
const map = new Map<string, Message['usage']>()
|
|
18
|
+
props.messages?.forEach((msg) => {
|
|
19
|
+
if (msg.usage) {
|
|
20
|
+
map.set(msg.id, msg.usage)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
return map
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const groupedChunks = () => {
|
|
27
|
+
const groups = new Map<string, Array<Chunk>>()
|
|
28
|
+
|
|
29
|
+
props.chunks.forEach((chunk) => {
|
|
30
|
+
const key = chunk.messageId || 'no-message-id'
|
|
31
|
+
if (!groups.has(key)) {
|
|
32
|
+
groups.set(key, [])
|
|
33
|
+
}
|
|
34
|
+
groups.get(key)!.push(chunk)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return Array.from(groups.entries())
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Calculate total raw chunks (sum of all chunkCounts)
|
|
41
|
+
const totalRawChunks = () =>
|
|
42
|
+
props.chunks.reduce((sum, c) => sum + (c.chunkCount || 1), 0)
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Show
|
|
46
|
+
when={props.chunks.length > 0}
|
|
47
|
+
fallback={
|
|
48
|
+
<div class={styles().conversationDetails.noChunks}>No chunks yet</div>
|
|
49
|
+
}
|
|
50
|
+
>
|
|
51
|
+
<div class={styles().conversationDetails.streamContainer}>
|
|
52
|
+
{/* Stream Header */}
|
|
53
|
+
<div class={styles().conversationDetails.streamHeader}>
|
|
54
|
+
<div class={styles().conversationDetails.streamHeaderRow}>
|
|
55
|
+
<div class={styles().conversationDetails.streamTitle}>
|
|
56
|
+
Stream Responses
|
|
57
|
+
</div>
|
|
58
|
+
<div
|
|
59
|
+
class={`${styles().conversationDetails.chunkBadge} ${styles().conversationDetails.chunkBadgeCount}`}
|
|
60
|
+
>
|
|
61
|
+
{totalRawChunks()} chunks · {groupedChunks().length} messages
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div class={styles().conversationDetails.streamSubtitle}>
|
|
65
|
+
Grouped by message ID
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
{/* Message Groups */}
|
|
70
|
+
<div class={styles().conversationDetails.messageGroups}>
|
|
71
|
+
<For each={groupedChunks()}>
|
|
72
|
+
{([messageId, chunks], groupIndex) => (
|
|
73
|
+
<MessageGroup
|
|
74
|
+
messageId={messageId}
|
|
75
|
+
chunks={chunks}
|
|
76
|
+
groupIndex={groupIndex()}
|
|
77
|
+
usage={usageByMessageId().get(messageId)}
|
|
78
|
+
/>
|
|
79
|
+
)}
|
|
80
|
+
</For>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</Show>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { For, Show } from 'solid-js'
|
|
2
|
+
import { useStyles } from '../../styles/use-styles'
|
|
3
|
+
import { formatDuration } from '../utils'
|
|
4
|
+
import type { Component } from 'solid-js'
|
|
5
|
+
import type { Conversation } from '../../store/ai-context'
|
|
6
|
+
|
|
7
|
+
interface ConversationHeaderProps {
|
|
8
|
+
conversation: Conversation
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ConversationHeader: Component<ConversationHeaderProps> = (
|
|
12
|
+
props,
|
|
13
|
+
) => {
|
|
14
|
+
const styles = useStyles()
|
|
15
|
+
const conv = () => props.conversation
|
|
16
|
+
|
|
17
|
+
const toolNames = () => conv().toolNames ?? []
|
|
18
|
+
const options = () => conv().options
|
|
19
|
+
const providerOptions = () => conv().providerOptions
|
|
20
|
+
const iterationCount = () => conv().iterationCount
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div class={styles().panelHeader}>
|
|
24
|
+
<div class={styles().conversationDetails.headerContent}>
|
|
25
|
+
<div class={styles().conversationDetails.headerRow}>
|
|
26
|
+
<div class={styles().conversationDetails.headerLabel}>
|
|
27
|
+
{conv().label}
|
|
28
|
+
</div>
|
|
29
|
+
<div
|
|
30
|
+
class={`${styles().conversationDetails.statusBadge} ${
|
|
31
|
+
conv().status === 'active'
|
|
32
|
+
? styles().conversationDetails.statusActive
|
|
33
|
+
: conv().status === 'completed'
|
|
34
|
+
? styles().conversationDetails.statusCompleted
|
|
35
|
+
: styles().conversationDetails.statusError
|
|
36
|
+
}`}
|
|
37
|
+
>
|
|
38
|
+
{conv().status}
|
|
39
|
+
</div>
|
|
40
|
+
<Show when={iterationCount() !== undefined && iterationCount()! > 1}>
|
|
41
|
+
<div class={styles().conversationDetails.iterationBadge}>
|
|
42
|
+
🔄 {iterationCount()} iterations
|
|
43
|
+
</div>
|
|
44
|
+
</Show>
|
|
45
|
+
</div>
|
|
46
|
+
<div class={styles().conversationDetails.metaInfo}>
|
|
47
|
+
{conv().model && `Model: ${conv().model}`}
|
|
48
|
+
{conv().provider && ` • Provider: ${conv().provider}`}
|
|
49
|
+
{conv().completedAt &&
|
|
50
|
+
` • Duration: ${formatDuration(conv().completedAt! - conv().startedAt)}`}
|
|
51
|
+
</div>
|
|
52
|
+
<Show when={conv().usage}>
|
|
53
|
+
<div class={styles().conversationDetails.usageInfo}>
|
|
54
|
+
<span class={styles().conversationDetails.usageLabel}>
|
|
55
|
+
🎯 Tokens:
|
|
56
|
+
</span>
|
|
57
|
+
<span>
|
|
58
|
+
Prompt: {conv().usage?.promptTokens.toLocaleString() || 0}
|
|
59
|
+
</span>
|
|
60
|
+
<span>•</span>
|
|
61
|
+
<span>
|
|
62
|
+
Completion: {conv().usage?.completionTokens.toLocaleString() || 0}
|
|
63
|
+
</span>
|
|
64
|
+
<span>•</span>
|
|
65
|
+
<span class={styles().conversationDetails.usageBold}>
|
|
66
|
+
Total: {conv().usage?.totalTokens.toLocaleString() || 0}
|
|
67
|
+
</span>
|
|
68
|
+
</div>
|
|
69
|
+
</Show>
|
|
70
|
+
{/* Tools list - always visible */}
|
|
71
|
+
<Show when={toolNames().length > 0}>
|
|
72
|
+
<div class={styles().conversationDetails.toolsRow}>
|
|
73
|
+
<span class={styles().conversationDetails.toolsLabel}>🔧</span>
|
|
74
|
+
<div class={styles().conversationDetails.toolsList}>
|
|
75
|
+
<For each={toolNames()}>
|
|
76
|
+
{(toolName) => (
|
|
77
|
+
<span class={styles().conversationDetails.toolBadge}>
|
|
78
|
+
{toolName}
|
|
79
|
+
</span>
|
|
80
|
+
)}
|
|
81
|
+
</For>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</Show>
|
|
85
|
+
{/* Options - always visible in compact form */}
|
|
86
|
+
<Show when={options() && Object.keys(options()!).length > 0}>
|
|
87
|
+
<div class={styles().conversationDetails.optionsRow}>
|
|
88
|
+
<span class={styles().conversationDetails.optionsLabel}>
|
|
89
|
+
⚙️ Options:
|
|
90
|
+
</span>
|
|
91
|
+
<div class={styles().conversationDetails.optionsCompact}>
|
|
92
|
+
<For each={Object.entries(options()!)}>
|
|
93
|
+
{([key, value]) => (
|
|
94
|
+
<span class={styles().conversationDetails.optionBadge}>
|
|
95
|
+
{key}:{' '}
|
|
96
|
+
{typeof value === 'object'
|
|
97
|
+
? JSON.stringify(value)
|
|
98
|
+
: String(value)}
|
|
99
|
+
</span>
|
|
100
|
+
)}
|
|
101
|
+
</For>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</Show>
|
|
105
|
+
{/* Provider Options - always visible in compact form */}
|
|
106
|
+
<Show
|
|
107
|
+
when={providerOptions() && Object.keys(providerOptions()!).length > 0}
|
|
108
|
+
>
|
|
109
|
+
<div class={styles().conversationDetails.optionsRow}>
|
|
110
|
+
<span class={styles().conversationDetails.optionsLabel}>
|
|
111
|
+
🏷️ Provider:
|
|
112
|
+
</span>
|
|
113
|
+
<div class={styles().conversationDetails.optionsCompact}>
|
|
114
|
+
<For each={Object.entries(providerOptions()!)}>
|
|
115
|
+
{([key, value]) => (
|
|
116
|
+
<span class={styles().conversationDetails.optionBadge}>
|
|
117
|
+
{key}:{' '}
|
|
118
|
+
{typeof value === 'object'
|
|
119
|
+
? JSON.stringify(value)
|
|
120
|
+
: String(value)}
|
|
121
|
+
</span>
|
|
122
|
+
)}
|
|
123
|
+
</For>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</Show>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Show } from 'solid-js'
|
|
2
|
+
import { useStyles } from '../../styles/use-styles'
|
|
3
|
+
import type { Component } from 'solid-js'
|
|
4
|
+
import type { Conversation } from '../../store/ai-context'
|
|
5
|
+
|
|
6
|
+
export type TabType = 'messages' | 'chunks' | 'embeddings' | 'summaries'
|
|
7
|
+
|
|
8
|
+
interface ConversationTabsProps {
|
|
9
|
+
conversation: Conversation
|
|
10
|
+
activeTab: TabType
|
|
11
|
+
onTabChange: (tab: TabType) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
|
|
15
|
+
const styles = useStyles()
|
|
16
|
+
const conv = () => props.conversation
|
|
17
|
+
|
|
18
|
+
// Total raw chunks = sum of all chunkCounts
|
|
19
|
+
const totalRawChunks = () =>
|
|
20
|
+
conv().chunks.reduce((sum, c) => sum + (c.chunkCount || 1), 0)
|
|
21
|
+
|
|
22
|
+
const embeddingsCount = () => conv().embeddings?.length ?? 0
|
|
23
|
+
const summariesCount = () => conv().summaries?.length ?? 0
|
|
24
|
+
|
|
25
|
+
// Determine if we should show any chat-related tabs
|
|
26
|
+
// For server conversations, don't show messages tab - only chunks
|
|
27
|
+
const hasMessages = () =>
|
|
28
|
+
conv().type === 'client' && conv().messages.length > 0
|
|
29
|
+
const hasChunks = () => conv().chunks.length > 0 || conv().type === 'server'
|
|
30
|
+
const hasEmbeddings = () => conv().hasEmbedding || embeddingsCount() > 0
|
|
31
|
+
const hasSummaries = () => conv().hasSummarize || summariesCount() > 0
|
|
32
|
+
|
|
33
|
+
// Count how many tabs would be visible
|
|
34
|
+
const visibleTabCount = () => {
|
|
35
|
+
let count = 0
|
|
36
|
+
if (hasMessages()) count++
|
|
37
|
+
if (hasChunks() && conv().type === 'server') count++
|
|
38
|
+
if (hasEmbeddings()) count++
|
|
39
|
+
if (hasSummaries()) count++
|
|
40
|
+
return count
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Don't render tabs if only one tab would be visible
|
|
44
|
+
if (visibleTabCount() <= 1) {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div class={styles().conversationDetails.tabsContainer}>
|
|
50
|
+
{/* Show messages tab for client conversations or when there are messages */}
|
|
51
|
+
<Show when={hasMessages()}>
|
|
52
|
+
<button
|
|
53
|
+
class={`${styles().actionButton} ${
|
|
54
|
+
props.activeTab === 'messages'
|
|
55
|
+
? styles().conversationDetails.tabButtonActive
|
|
56
|
+
: ''
|
|
57
|
+
}`}
|
|
58
|
+
onClick={() => props.onTabChange('messages')}
|
|
59
|
+
>
|
|
60
|
+
💬 Messages ({conv().messages.length})
|
|
61
|
+
</button>
|
|
62
|
+
</Show>
|
|
63
|
+
{/* Show chunks tab for server conversations or when there are chunks */}
|
|
64
|
+
<Show when={hasChunks() && conv().type === 'server'}>
|
|
65
|
+
<button
|
|
66
|
+
class={`${styles().actionButton} ${
|
|
67
|
+
props.activeTab === 'chunks'
|
|
68
|
+
? styles().conversationDetails.tabButtonActive
|
|
69
|
+
: ''
|
|
70
|
+
}`}
|
|
71
|
+
onClick={() => props.onTabChange('chunks')}
|
|
72
|
+
>
|
|
73
|
+
📦 Chunks ({totalRawChunks()})
|
|
74
|
+
</button>
|
|
75
|
+
</Show>
|
|
76
|
+
{/* Show embeddings tab if there are embedding operations */}
|
|
77
|
+
<Show when={hasEmbeddings()}>
|
|
78
|
+
<button
|
|
79
|
+
class={`${styles().actionButton} ${
|
|
80
|
+
props.activeTab === 'embeddings'
|
|
81
|
+
? styles().conversationDetails.tabButtonActive
|
|
82
|
+
: ''
|
|
83
|
+
}`}
|
|
84
|
+
onClick={() => props.onTabChange('embeddings')}
|
|
85
|
+
>
|
|
86
|
+
🔢 Embeddings ({embeddingsCount()})
|
|
87
|
+
</button>
|
|
88
|
+
</Show>
|
|
89
|
+
{/* Show summaries tab if there are summarize operations */}
|
|
90
|
+
<Show when={hasSummaries()}>
|
|
91
|
+
<button
|
|
92
|
+
class={`${styles().actionButton} ${
|
|
93
|
+
props.activeTab === 'summaries'
|
|
94
|
+
? styles().conversationDetails.tabButtonActive
|
|
95
|
+
: ''
|
|
96
|
+
}`}
|
|
97
|
+
onClick={() => props.onTabChange('summaries')}
|
|
98
|
+
>
|
|
99
|
+
📝 Summaries ({summariesCount()})
|
|
100
|
+
</button>
|
|
101
|
+
</Show>
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|