Skip to content

Commit 88fa4ef

Browse files
feat: improve the devtools reporting (#43)
* feat: improve the devtools reporting * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent b3b2c7b commit 88fa4ef

26 files changed

Lines changed: 1343 additions & 280 deletions

docs/reference/functions/chat.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ title: chat
99
function chat<TAdapter, TModel>(options): AsyncIterable<StreamChunk>;
1010
```
1111

12-
Defined in: [core/chat.ts:738](https://github.com/TanStack/ai/blob/main/packages/typescript/ai/src/core/chat.ts#L738)
12+
Defined in: [core/chat.ts:762](https://github.com/TanStack/ai/blob/main/packages/typescript/ai/src/core/chat.ts#L762)
1313

1414
Standalone chat streaming function with type inference from adapter
1515
Returns an async iterable of StreamChunks for streaming responses

docs/reference/functions/embedding.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ title: embedding
99
function embedding<TAdapter>(options): Promise<EmbeddingResult>;
1010
```
1111

12-
Defined in: [core/embedding.ts:11](https://github.com/TanStack/ai/blob/main/packages/typescript/ai/src/core/embedding.ts#L11)
12+
Defined in: [core/embedding.ts:16](https://github.com/TanStack/ai/blob/main/packages/typescript/ai/src/core/embedding.ts#L16)
1313

1414
Standalone embedding function with type inference from adapter
1515

docs/reference/functions/summarize.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ title: summarize
99
function summarize<TAdapter>(options): Promise<SummarizationResult>;
1010
```
1111

12-
Defined in: [core/summarize.ts:11](https://github.com/TanStack/ai/blob/main/packages/typescript/ai/src/core/summarize.ts#L11)
12+
Defined in: [core/summarize.ts:16](https://github.com/TanStack/ai/blob/main/packages/typescript/ai/src/core/summarize.ts#L16)
1313

1414
Standalone summarize function with type inference from adapter
1515

docs/reference/variables/aiEventClient.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ title: aiEventClient
99
const aiEventClient: AiEventClient;
1010
```
1111

12-
Defined in: [event-client.ts:357](https://github.com/TanStack/ai/blob/main/packages/typescript/ai/src/event-client.ts#L357)
12+
Defined in: [event-client.ts:387](https://github.com/TanStack/ai/blob/main/packages/typescript/ai/src/event-client.ts#L387)

examples/ts-react-chat/src/routes/api.tanchat.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createFileRoute } from '@tanstack/react-router'
22
import { chat, maxIterations, toStreamResponse } from '@tanstack/ai'
3-
// import { openai } from '@tanstack/ai-openai'
3+
import { openai } from '@tanstack/ai-openai'
44
// import { ollama } from "@tanstack/ai-ollama";
55
import { anthropic } from '@tanstack/ai-anthropic'
66
// import { gemini } from "@tanstack/ai-gemini";
@@ -47,12 +47,13 @@ export const Route = createFileRoute('/api/tanchat')({
4747
try {
4848
// Use the stream abort signal for proper cancellation handling
4949
const stream = chat({
50-
adapter: anthropic(),
50+
adapter: openai(),
51+
model: 'gpt-5',
5152
// For thinking/reasoning support, use one of these models:
5253
// - OpenAI: "gpt-5", "o3", "o3-pro", "o3-mini" (with reasoning option)
5354
// - Anthropic: "claude-sonnet-4-5-20250929", "claude-opus-4-5-20251101" (with thinking option)
5455
// - Gemini: "gemini-3-pro-preview", "gemini-2.5-pro" (with thinkingConfig option)
55-
model: 'claude-sonnet-4-5-20250929',
56+
// model: 'claude-sonnet-4-5-20250929',
5657
// model: "claude-sonnet-4-5-20250929",
5758
// model: "smollm",
5859
// model: "gemini-2.5-flash",

packages/typescript/ai-devtools/src/components/ConversationDetails.tsx

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,18 @@ import {
55
ChunksTab,
66
ConversationHeader,
77
ConversationTabs,
8+
EmbeddingsTab,
89
MessagesTab,
10+
SummariesTab,
911
} from './conversation'
12+
import type { TabType } from './conversation'
1013
import type { Conversation } from '../store/ai-context'
1114
import type { Component } from 'solid-js'
1215

1316
export const ConversationDetails: Component = () => {
1417
const { state } = useAIStore()
1518
const styles = useStyles()
16-
const [activeTab, setActiveTab] = createSignal<'messages' | 'chunks'>(
17-
'messages',
18-
)
19+
const [activeTab, setActiveTab] = createSignal<TabType>('messages')
1920

2021
const activeConversation = (): Conversation | undefined => {
2122
if (!state.activeConversationId) return undefined
@@ -26,9 +27,23 @@ export const ConversationDetails: Component = () => {
2627
createEffect(() => {
2728
const conv = activeConversation()
2829
if (conv) {
29-
// For server conversations, default to chunks tab
30+
// For server conversations, always use chunks (messages tab is hidden)
3031
if (conv.type === 'server') {
31-
setActiveTab('chunks')
32+
if (conv.chunks.length > 0) {
33+
setActiveTab('chunks')
34+
} else if (
35+
conv.hasEmbedding ||
36+
(conv.embeddings && conv.embeddings.length > 0)
37+
) {
38+
setActiveTab('embeddings')
39+
} else if (
40+
conv.hasSummarize ||
41+
(conv.summaries && conv.summaries.length > 0)
42+
) {
43+
setActiveTab('summaries')
44+
} else {
45+
setActiveTab('chunks')
46+
}
3247
} else {
3348
// For client conversations, default to messages tab
3449
setActiveTab('messages')
@@ -58,7 +73,13 @@ export const ConversationDetails: Component = () => {
5873
<MessagesTab messages={conv().messages} />
5974
</Show>
6075
<Show when={activeTab() === 'chunks'}>
61-
<ChunksTab chunks={conv().chunks} />
76+
<ChunksTab chunks={conv().chunks} messages={conv().messages} />
77+
</Show>
78+
<Show when={activeTab() === 'embeddings'}>
79+
<EmbeddingsTab embeddings={conv().embeddings ?? []} />
80+
</Show>
81+
<Show when={activeTab() === 'summaries'}>
82+
<SummariesTab summaries={conv().summaries ?? []} />
6283
</Show>
6384
</div>
6485
</div>

packages/typescript/ai-devtools/src/components/conversation/ChunkItem.tsx

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Show, createSignal } from 'solid-js'
2+
import { JsonTree } from '@tanstack/devtools-ui'
23
import { useStyles } from '../../styles/use-styles'
3-
import { formatTimestamp, getChunkTypeColor } from '../utils'
4+
import { formatDuration, formatTimestamp, getChunkTypeColor } from '../utils'
45
import type { Component } from 'solid-js'
56
import type { Chunk } from '../../store/ai-store'
67

@@ -16,6 +17,28 @@ export const ChunkItem: Component<ChunkItemProps> = (props) => {
1617
const isLarge = () => props.variant === 'large'
1718
const chunkCount = () => props.chunk.chunkCount || 1
1819

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+
1942
return (
2043
<div
2144
class={
@@ -185,6 +208,52 @@ export const ChunkItem: Component<ChunkItemProps> = (props) => {
185208
</Show>
186209
</div>
187210
</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>
188257
</Show>
189258

190259
{/* Raw JSON View */}

packages/typescript/ai-devtools/src/components/conversation/ChunksCollapsible.tsx

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,24 @@ export const ChunksCollapsible: Component<ChunksCollapsibleProps> = (props) => {
2525
return (
2626
<details class={styles().conversationDetails.chunksDetails}>
2727
<summary class={styles().conversationDetails.chunksSummary}>
28-
<div class={styles().conversationDetails.chunksSummaryContent}>
29-
{/* Header */}
30-
<div class={styles().conversationDetails.chunksSummaryHeader}>
31-
<span>📦 Server Chunks ({totalRawChunks()})</span>
32-
<ChunkBadges chunks={props.chunks} />
33-
</div>
34-
35-
{/* Accumulated Content Preview */}
36-
<Show when={accumulatedContent()}>
37-
<div
38-
class={styles().conversationDetails.contentPreview}
39-
title={accumulatedContent()}
40-
>
41-
{accumulatedContent()}
42-
</div>
43-
</Show>
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} />
4436
</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>
4546
</summary>
4647
<div class={styles().conversationDetails.chunksContainer}>
4748
<div class={styles().conversationDetails.chunksList}>

packages/typescript/ai-devtools/src/components/conversation/ChunksTab.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,27 @@ import { For, Show } from 'solid-js'
22
import { useStyles } from '../../styles/use-styles'
33
import { MessageGroup } from './MessageGroup'
44
import type { Component } from 'solid-js'
5-
import type { Chunk } from '../../store/ai-store'
5+
import type { Chunk, Message } from '../../store/ai-store'
66

77
interface ChunksTabProps {
88
chunks: Array<Chunk>
9+
messages?: Array<Message>
910
}
1011

1112
export const ChunksTab: Component<ChunksTabProps> = (props) => {
1213
const styles = useStyles()
1314

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+
1426
const groupedChunks = () => {
1527
const groups = new Map<string, Array<Chunk>>()
1628

@@ -62,6 +74,7 @@ export const ChunksTab: Component<ChunksTabProps> = (props) => {
6274
messageId={messageId}
6375
chunks={chunks}
6476
groupIndex={groupIndex()}
77+
usage={usageByMessageId().get(messageId)}
6578
/>
6679
)}
6780
</For>

0 commit comments

Comments
 (0)