@tanstack/ai-devtools-core 0.3.9 → 0.3.12

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.
@@ -2,7 +2,7 @@ import { constructCoreClass } from '@tanstack/devtools-utils/solid/class';
2
2
 
3
3
  // src/core.ts
4
4
  var [AiDevtoolsCore, AiDevtoolsCoreNoOp] = constructCoreClass(
5
- () => import('./Shell/GHXECDJV.js')
5
+ () => import('./Shell/TWTNN4SQ.js')
6
6
  );
7
7
 
8
8
  export { AiDevtoolsCore };
@@ -2,7 +2,7 @@ import { constructCoreClass } from '@tanstack/devtools-utils/solid/class';
2
2
 
3
3
  // src/core.ts
4
4
  var [AiDevtoolsCore, AiDevtoolsCoreNoOp] = constructCoreClass(
5
- () => import('./Shell/GHXECDJV.js')
5
+ () => import('./Shell/TWTNN4SQ.js')
6
6
  );
7
7
 
8
8
  export { AiDevtoolsCore };
@@ -2,7 +2,7 @@ import { constructCoreClass } from '@tanstack/devtools-utils/solid/class';
2
2
 
3
3
  // src/core.ts
4
4
  var [AiDevtoolsCore, AiDevtoolsCoreNoOp] = constructCoreClass(
5
- () => import('./Shell/MUFU35SY.js')
5
+ () => import('./Shell/SHJAKIOY.js')
6
6
  );
7
7
 
8
8
  export { AiDevtoolsCore };
package/dist/server.js CHANGED
@@ -2,7 +2,7 @@ import { constructCoreClass } from '@tanstack/devtools-utils/solid/class';
2
2
 
3
3
  // src/core.ts
4
4
  var [AiDevtoolsCore, AiDevtoolsCoreNoOp] = constructCoreClass(
5
- () => import('./Shell/MUFU35SY.js')
5
+ () => import('./Shell/SHJAKIOY.js')
6
6
  );
7
7
 
8
8
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-devtools-core",
3
- "version": "0.3.9",
3
+ "version": "0.3.12",
4
4
  "description": "Core TanStack AI Devtools",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -72,7 +72,8 @@
72
72
  "@tanstack/devtools-utils": "0.3.2",
73
73
  "goober": "^2.1.18",
74
74
  "solid-js": "^1.9.10",
75
- "@tanstack/ai": "0.6.3"
75
+ "@tanstack/ai": "0.8.1",
76
+ "@tanstack/ai-event-client": "0.1.1"
76
77
  },
77
78
  "devDependencies": {
78
79
  "@vitest/coverage-v8": "4.0.14",
@@ -6,6 +6,7 @@ import {
6
6
  ChunksTab,
7
7
  ConversationHeader,
8
8
  ConversationTabs,
9
+ IterationTimeline,
9
10
  MessagesTab,
10
11
  SummariesTab,
11
12
  } from './conversation'
@@ -23,69 +24,83 @@ export const ConversationDetails: Component = () => {
23
24
  return state.conversations[state.activeConversationId]
24
25
  }
25
26
 
26
- // Update active tab when conversation changes
27
+ const hasIterations = () => {
28
+ const conv = activeConversation()
29
+ return conv && conv.iterations.length > 0
30
+ }
31
+
32
+ const hasActivityTabs = () => {
33
+ const conv = activeConversation()
34
+ if (!conv) return false
35
+ return (
36
+ conv.hasSummarize ||
37
+ conv.hasImage ||
38
+ conv.hasSpeech ||
39
+ conv.hasTranscription ||
40
+ conv.hasVideo
41
+ )
42
+ }
43
+
44
+ // Update active tab when conversation changes (only for non-iteration views)
27
45
  createEffect(() => {
28
46
  const conv = activeConversation()
29
- if (conv) {
30
- // For server conversations, always use chunks (messages tab is hidden)
31
- if (conv.type === 'server') {
32
- if (conv.chunks.length > 0) {
33
- setActiveTab('chunks')
34
- } else if (
35
- conv.hasSummarize ||
36
- (conv.summaries && conv.summaries.length > 0)
37
- ) {
38
- setActiveTab('summaries')
39
- } else if (
40
- conv.hasImage ||
41
- (conv.imageEvents && conv.imageEvents.length > 0)
42
- ) {
43
- setActiveTab('image')
44
- } else if (
45
- conv.hasSpeech ||
46
- (conv.speechEvents && conv.speechEvents.length > 0)
47
- ) {
48
- setActiveTab('speech')
49
- } else if (
50
- conv.hasTranscription ||
51
- (conv.transcriptionEvents && conv.transcriptionEvents.length > 0)
52
- ) {
53
- setActiveTab('transcription')
54
- } else if (
55
- conv.hasVideo ||
56
- (conv.videoEvents && conv.videoEvents.length > 0)
57
- ) {
58
- setActiveTab('video')
59
- } else {
60
- setActiveTab('chunks')
61
- }
47
+ if (!conv) return
48
+
49
+ // If iterations exist, the timeline is the primary view — only set tab for activity
50
+ if (conv.iterations.length > 0) {
51
+ if (conv.hasSummarize || (conv.summaries && conv.summaries.length > 0)) {
52
+ setActiveTab('summaries')
53
+ } else if (
54
+ conv.hasImage ||
55
+ (conv.imageEvents && conv.imageEvents.length > 0)
56
+ ) {
57
+ setActiveTab('image')
58
+ }
59
+ return
60
+ }
61
+
62
+ // No iterations — use flat message/chunk view
63
+ if (conv.type === 'server') {
64
+ if (conv.chunks.length > 0) {
65
+ setActiveTab('chunks')
66
+ } else if (
67
+ conv.hasSummarize ||
68
+ (conv.summaries && conv.summaries.length > 0)
69
+ ) {
70
+ setActiveTab('summaries')
71
+ } else if (
72
+ conv.hasImage ||
73
+ (conv.imageEvents && conv.imageEvents.length > 0)
74
+ ) {
75
+ setActiveTab('image')
76
+ } else if (
77
+ conv.hasSpeech ||
78
+ (conv.speechEvents && conv.speechEvents.length > 0)
79
+ ) {
80
+ setActiveTab('speech')
81
+ } else if (
82
+ conv.hasTranscription ||
83
+ (conv.transcriptionEvents && conv.transcriptionEvents.length > 0)
84
+ ) {
85
+ setActiveTab('transcription')
86
+ } else if (
87
+ conv.hasVideo ||
88
+ (conv.videoEvents && conv.videoEvents.length > 0)
89
+ ) {
90
+ setActiveTab('video')
91
+ } else {
92
+ setActiveTab('chunks')
93
+ }
94
+ } else {
95
+ if (conv.messages.length > 0) {
96
+ setActiveTab('messages')
97
+ } else if (
98
+ conv.hasImage ||
99
+ (conv.imageEvents && conv.imageEvents.length > 0)
100
+ ) {
101
+ setActiveTab('image')
62
102
  } else {
63
- // For client conversations, default to messages tab
64
- if (conv.messages.length > 0) {
65
- setActiveTab('messages')
66
- } else if (
67
- conv.hasImage ||
68
- (conv.imageEvents && conv.imageEvents.length > 0)
69
- ) {
70
- setActiveTab('image')
71
- } else if (
72
- conv.hasSpeech ||
73
- (conv.speechEvents && conv.speechEvents.length > 0)
74
- ) {
75
- setActiveTab('speech')
76
- } else if (
77
- conv.hasTranscription ||
78
- (conv.transcriptionEvents && conv.transcriptionEvents.length > 0)
79
- ) {
80
- setActiveTab('transcription')
81
- } else if (
82
- conv.hasVideo ||
83
- (conv.videoEvents && conv.videoEvents.length > 0)
84
- ) {
85
- setActiveTab('video')
86
- } else {
87
- setActiveTab('messages')
88
- }
103
+ setActiveTab('messages')
89
104
  }
90
105
  }
91
106
  })
@@ -102,46 +117,98 @@ export const ConversationDetails: Component = () => {
102
117
  {(conv) => (
103
118
  <div class={styles().conversationDetails.container}>
104
119
  <ConversationHeader conversation={conv()} />
105
- <ConversationTabs
106
- conversation={conv()}
107
- activeTab={activeTab()}
108
- onTabChange={setActiveTab}
109
- />
110
- <div class={styles().conversationDetails.contentArea}>
111
- <Show when={activeTab() === 'messages'}>
112
- <MessagesTab messages={conv().messages} />
113
- </Show>
114
- <Show when={activeTab() === 'chunks'}>
115
- <ChunksTab chunks={conv().chunks} messages={conv().messages} />
116
- </Show>
117
- <Show when={activeTab() === 'summaries'}>
118
- <SummariesTab summaries={conv().summaries ?? []} />
119
- </Show>
120
- <Show when={activeTab() === 'image'}>
121
- <ActivityEventsTab
122
- title="Image Activity"
123
- events={conv().imageEvents ?? []}
124
- />
125
- </Show>
126
- <Show when={activeTab() === 'speech'}>
127
- <ActivityEventsTab
128
- title="Speech Activity"
129
- events={conv().speechEvents ?? []}
130
- />
131
- </Show>
132
- <Show when={activeTab() === 'transcription'}>
133
- <ActivityEventsTab
134
- title="Transcription Activity"
135
- events={conv().transcriptionEvents ?? []}
136
- />
137
- </Show>
138
- <Show when={activeTab() === 'video'}>
139
- <ActivityEventsTab
140
- title="Video Activity"
141
- events={conv().videoEvents ?? []}
120
+
121
+ {/* Primary view: iteration timeline when iterations exist */}
122
+ <Show when={hasIterations()}>
123
+ <div class={styles().conversationDetails.contentArea}>
124
+ <IterationTimeline
125
+ iterations={conv().iterations}
126
+ messages={conv().messages}
142
127
  />
143
- </Show>
144
- </div>
128
+ </div>
129
+ </Show>
130
+
131
+ {/* Fallback: flat message/chunk view when no iterations */}
132
+ <Show when={!hasIterations()}>
133
+ <ConversationTabs
134
+ conversation={conv()}
135
+ activeTab={activeTab()}
136
+ onTabChange={setActiveTab}
137
+ />
138
+ <div class={styles().conversationDetails.contentArea}>
139
+ <Show when={activeTab() === 'messages'}>
140
+ <MessagesTab messages={conv().messages} />
141
+ </Show>
142
+ <Show when={activeTab() === 'chunks'}>
143
+ <ChunksTab chunks={conv().chunks} messages={conv().messages} />
144
+ </Show>
145
+ <Show when={activeTab() === 'summaries'}>
146
+ <SummariesTab summaries={conv().summaries ?? []} />
147
+ </Show>
148
+ <Show when={activeTab() === 'image'}>
149
+ <ActivityEventsTab
150
+ title="Image Activity"
151
+ events={conv().imageEvents ?? []}
152
+ />
153
+ </Show>
154
+ <Show when={activeTab() === 'speech'}>
155
+ <ActivityEventsTab
156
+ title="Speech Activity"
157
+ events={conv().speechEvents ?? []}
158
+ />
159
+ </Show>
160
+ <Show when={activeTab() === 'transcription'}>
161
+ <ActivityEventsTab
162
+ title="Transcription Activity"
163
+ events={conv().transcriptionEvents ?? []}
164
+ />
165
+ </Show>
166
+ <Show when={activeTab() === 'video'}>
167
+ <ActivityEventsTab
168
+ title="Video Activity"
169
+ events={conv().videoEvents ?? []}
170
+ />
171
+ </Show>
172
+ </div>
173
+ </Show>
174
+
175
+ {/* Activity tabs shown below iterations when relevant */}
176
+ <Show when={hasIterations() && hasActivityTabs()}>
177
+ <ConversationTabs
178
+ conversation={conv()}
179
+ activeTab={activeTab()}
180
+ onTabChange={setActiveTab}
181
+ />
182
+ <div class={styles().conversationDetails.contentArea}>
183
+ <Show when={activeTab() === 'summaries'}>
184
+ <SummariesTab summaries={conv().summaries ?? []} />
185
+ </Show>
186
+ <Show when={activeTab() === 'image'}>
187
+ <ActivityEventsTab
188
+ title="Image Activity"
189
+ events={conv().imageEvents ?? []}
190
+ />
191
+ </Show>
192
+ <Show when={activeTab() === 'speech'}>
193
+ <ActivityEventsTab
194
+ title="Speech Activity"
195
+ events={conv().speechEvents ?? []}
196
+ />
197
+ </Show>
198
+ <Show when={activeTab() === 'transcription'}>
199
+ <ActivityEventsTab
200
+ title="Transcription Activity"
201
+ events={conv().transcriptionEvents ?? []}
202
+ />
203
+ </Show>
204
+ <Show when={activeTab() === 'video'}>
205
+ <ActivityEventsTab
206
+ title="Video Activity"
207
+ events={conv().videoEvents ?? []}
208
+ />
209
+ </Show>
210
+ </div>
211
+ </Show>
145
212
  </div>
146
213
  )}
147
214
  </Show>
@@ -1,5 +1,4 @@
1
- import { For, Show } from 'solid-js'
2
- import { JsonTree } from '@tanstack/devtools-ui'
1
+ import { Show } from 'solid-js'
3
2
  import { useStyles } from '../../styles/use-styles'
4
3
  import { formatDuration } from '../utils'
5
4
  import type { Component } from 'solid-js'
@@ -15,11 +14,41 @@ export const ConversationHeader: Component<ConversationHeaderProps> = (
15
14
  const styles = useStyles()
16
15
  const conv = () => props.conversation
17
16
 
18
- const toolNames = () => conv().toolNames ?? []
19
- const options = () => conv().options
20
- const modelOptions = () => conv().modelOptions
21
- const iterationCount = () => conv().iterationCount
22
- const systemPrompts = () => conv().systemPrompts ?? []
17
+ const iterationCount = () => conv().iterationCount ?? conv().iterations.length
18
+ const totalDuration = () => {
19
+ if (!conv().completedAt) return undefined
20
+ return conv().completedAt! - conv().startedAt
21
+ }
22
+
23
+ const totalMessages = () => conv().messages.length
24
+
25
+ const totalToolCalls = () => {
26
+ let count = 0
27
+ for (const iter of conv().iterations) {
28
+ if (iter.finishReason === 'tool_calls') count++
29
+ }
30
+ return count
31
+ }
32
+
33
+ // Sum usage across all iterations
34
+ const totalUsage = () => {
35
+ if (conv().usage) return conv().usage
36
+ if (conv().iterations.length === 0) return undefined
37
+ let promptTokens = 0
38
+ let completionTokens = 0
39
+ for (const iter of conv().iterations) {
40
+ if (iter.usage) {
41
+ promptTokens += iter.usage.promptTokens
42
+ completionTokens += iter.usage.completionTokens
43
+ }
44
+ }
45
+ if (promptTokens === 0 && completionTokens === 0) return undefined
46
+ return {
47
+ promptTokens,
48
+ completionTokens,
49
+ totalTokens: promptTokens + completionTokens,
50
+ }
51
+ }
23
52
 
24
53
  return (
25
54
  <div class={styles().panelHeader}>
@@ -39,107 +68,37 @@ export const ConversationHeader: Component<ConversationHeaderProps> = (
39
68
  >
40
69
  {conv().status}
41
70
  </div>
42
- <Show when={iterationCount() !== undefined && iterationCount()! > 1}>
43
- <div class={styles().conversationDetails.iterationBadge}>
44
- 🔄 {iterationCount()} iterations
45
- </div>
46
- </Show>
47
71
  </div>
48
72
  <div class={styles().conversationDetails.metaInfo}>
49
- {conv().model && `Model: ${conv().model}`}
50
- {conv().provider && ` • Provider: ${conv().provider}`}
51
- {conv().completedAt &&
52
- ` • Duration: ${formatDuration(conv().completedAt! - conv().startedAt)}`}
73
+ {totalDuration() !== undefined && formatDuration(totalDuration())}
74
+ <Show when={iterationCount() > 0}>
75
+ {totalDuration() !== undefined && ' · '}
76
+ {iterationCount()}{' '}
77
+ {iterationCount() === 1 ? 'iteration' : 'iterations'}
78
+ </Show>
79
+ <Show when={totalMessages() > 0}>
80
+ {(totalDuration() !== undefined || iterationCount() > 0) && ' · '}
81
+ {totalMessages()} {totalMessages() === 1 ? 'message' : 'messages'}
82
+ </Show>
83
+ <Show when={totalToolCalls() > 0}>
84
+ {' · '}
85
+ {totalToolCalls()} tool {totalToolCalls() === 1 ? 'call' : 'calls'}
86
+ </Show>
53
87
  </div>
54
- {/* Tools list - always visible */}
55
- <Show when={toolNames().length > 0}>
56
- <div class={styles().conversationDetails.toolsRow}>
57
- <span class={styles().conversationDetails.toolsLabel}>🔧</span>
58
- <div class={styles().conversationDetails.toolsList}>
59
- <For each={toolNames()}>
60
- {(toolName) => (
61
- <span class={styles().conversationDetails.toolBadge}>
62
- {toolName}
63
- </span>
64
- )}
65
- </For>
66
- </div>
67
- </div>
68
- </Show>
69
- {/* Options - always visible in compact form */}
70
- <Show when={options() && Object.keys(options()!).length > 0}>
71
- <div class={styles().conversationDetails.optionsRow}>
72
- <span class={styles().conversationDetails.optionsLabel}>
73
- ⚙️ Options:
74
- </span>
75
- <div class={styles().conversationDetails.optionsCompact}>
76
- <For each={Object.entries(options()!)}>
77
- {([key, value]) => (
78
- <span class={styles().conversationDetails.optionBadge}>
79
- {key}:{' '}
80
- {typeof value === 'object'
81
- ? JSON.stringify(value)
82
- : String(value)}
83
- </span>
84
- )}
85
- </For>
86
- </div>
87
- </div>
88
- </Show>
89
- <Show when={conv().usage}>
88
+ <Show when={totalUsage()}>
90
89
  <div class={styles().conversationDetails.usageInfo}>
91
- <span class={styles().conversationDetails.usageLabel}>
92
- 🎯 Tokens:
93
- </span>
90
+ <span class={styles().conversationDetails.usageLabel}>Tokens:</span>
91
+ <span>{totalUsage()?.promptTokens.toLocaleString() || 0} in</span>
92
+ <span>·</span>
94
93
  <span>
95
- Prompt: {conv().usage?.promptTokens.toLocaleString() || 0}
94
+ {totalUsage()?.completionTokens.toLocaleString() || 0} out
96
95
  </span>
97
- <span>•</span>
98
- <span>
99
- Completion: {conv().usage?.completionTokens.toLocaleString() || 0}
100
- </span>
101
- <span>•</span>
96
+ <span>·</span>
102
97
  <span class={styles().conversationDetails.usageBold}>
103
- Total: {conv().usage?.totalTokens.toLocaleString() || 0}
98
+ {totalUsage()?.totalTokens.toLocaleString() || 0} total
104
99
  </span>
105
100
  </div>
106
101
  </Show>
107
- {/* Model options - collapsible */}
108
- <Show when={modelOptions() && Object.keys(modelOptions()!).length > 0}>
109
- <details class={styles().conversationDetails.collapsibleSection}>
110
- <summary class={styles().conversationDetails.collapsibleSummary}>
111
- 🧪 Model options
112
- </summary>
113
- <div class={styles().conversationDetails.collapsibleContent}>
114
- <JsonTree
115
- value={modelOptions() as Record<string, unknown>}
116
- defaultExpansionDepth={2}
117
- />
118
- </div>
119
- </details>
120
- </Show>
121
- {/* System prompts - collapsible */}
122
- <Show when={systemPrompts().length > 0}>
123
- <details class={styles().conversationDetails.collapsibleSection}>
124
- <summary class={styles().conversationDetails.collapsibleSummary}>
125
- 🧩 System prompts ({systemPrompts().length})
126
- </summary>
127
- <div class={styles().conversationDetails.collapsibleContent}>
128
- <For each={systemPrompts()}>
129
- {(prompt, index) => (
130
- <div class={styles().conversationDetails.systemPromptItem}>
131
- <div class={styles().conversationDetails.systemPromptIndex}>
132
- #{index() + 1}
133
- </div>
134
- <div class={styles().conversationDetails.systemPromptText}>
135
- {prompt}
136
- </div>
137
- </div>
138
- )}
139
- </For>
140
- </div>
141
- </details>
142
- </Show>
143
102
  </div>
144
103
  </div>
145
104
  )
@@ -21,6 +21,7 @@ interface ConversationTabsProps {
21
21
  export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
22
22
  const styles = useStyles()
23
23
  const conv = () => props.conversation
24
+ const hasIterations = () => conv().iterations.length > 0
24
25
 
25
26
  // Total raw chunks = sum of all chunkCounts
26
27
  const totalRawChunks = () =>
@@ -79,11 +80,11 @@ export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
79
80
  previousVideoCount = count
80
81
  })
81
82
 
82
- // Determine if we should show any chat-related tabs
83
- // For server conversations, don't show messages tab - only chunks
83
+ // When iterations exist, only show activity tabs (no messages/chunks)
84
84
  const hasMessages = () =>
85
- conv().type === 'client' && conv().messages.length > 0
86
- const hasChunks = () => conv().chunks.length > 0 || conv().type === 'server'
85
+ !hasIterations() && conv().type === 'client' && conv().messages.length > 0
86
+ const hasChunks = () =>
87
+ !hasIterations() && (conv().chunks.length > 0 || conv().type === 'server')
87
88
  const hasSummaries = () => conv().hasSummarize || summariesCount() > 0
88
89
  const hasImage = () => conv().hasImage || imageCount() > 0
89
90
  const hasSpeech = () => conv().hasSpeech || speechCount() > 0
@@ -111,7 +112,6 @@ export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
111
112
 
112
113
  return (
113
114
  <div class={styles().conversationDetails.tabsContainer}>
114
- {/* Show messages tab for client conversations or when there are messages */}
115
115
  <Show when={hasMessages()}>
116
116
  <button
117
117
  class={`${styles().actionButton} ${
@@ -121,10 +121,9 @@ export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
121
121
  }`}
122
122
  onClick={() => props.onTabChange('messages')}
123
123
  >
124
- 💬 Messages ({conv().messages.length})
124
+ Messages ({conv().messages.length})
125
125
  </button>
126
126
  </Show>
127
- {/* Show chunks tab for server conversations or when there are chunks */}
128
127
  <Show when={hasChunks() && conv().type === 'server'}>
129
128
  <button
130
129
  class={`${styles().actionButton} ${
@@ -134,10 +133,9 @@ export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
134
133
  }`}
135
134
  onClick={() => props.onTabChange('chunks')}
136
135
  >
137
- 📦 Chunks ({totalRawChunks()})
136
+ Chunks ({totalRawChunks()})
138
137
  </button>
139
138
  </Show>
140
- {/* Show summaries tab if there are summarize operations */}
141
139
  <Show when={hasSummaries()}>
142
140
  <button
143
141
  class={`${styles().actionButton} ${
@@ -147,7 +145,7 @@ export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
147
145
  }`}
148
146
  onClick={() => props.onTabChange('summaries')}
149
147
  >
150
- 📝 Summaries ({summariesCount()})
148
+ Summaries ({summariesCount()})
151
149
  </button>
152
150
  </Show>
153
151
  <Show when={hasImage()}>
@@ -159,7 +157,7 @@ export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
159
157
  } ${imagePulse() ? styles().conversationDetails.tabButtonPulse : ''}`}
160
158
  onClick={() => props.onTabChange('image')}
161
159
  >
162
- 🖼️ Image ({imageCount()})
160
+ Image ({imageCount()})
163
161
  </button>
164
162
  </Show>
165
163
  <Show when={hasSpeech()}>
@@ -171,7 +169,7 @@ export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
171
169
  } ${speechPulse() ? styles().conversationDetails.tabButtonPulse : ''}`}
172
170
  onClick={() => props.onTabChange('speech')}
173
171
  >
174
- 🔊 Speech ({speechCount()})
172
+ Speech ({speechCount()})
175
173
  </button>
176
174
  </Show>
177
175
  <Show when={hasTranscription()}>
@@ -187,7 +185,7 @@ export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
187
185
  }`}
188
186
  onClick={() => props.onTabChange('transcription')}
189
187
  >
190
- 📝 Transcription ({transcriptionCount()})
188
+ Transcription ({transcriptionCount()})
191
189
  </button>
192
190
  </Show>
193
191
  <Show when={hasVideo()}>
@@ -199,7 +197,7 @@ export const ConversationTabs: Component<ConversationTabsProps> = (props) => {
199
197
  } ${videoPulse() ? styles().conversationDetails.tabButtonPulse : ''}`}
200
198
  onClick={() => props.onTabChange('video')}
201
199
  >
202
- 🎬 Video ({videoCount()})
200
+ Video ({videoCount()})
203
201
  </button>
204
202
  </Show>
205
203
  </div>