@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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -0
  3. package/dist/esm/components/ConversationDetails.d.ts +2 -0
  4. package/dist/esm/components/ConversationDetails.js +134 -0
  5. package/dist/esm/components/ConversationDetails.js.map +1 -0
  6. package/dist/esm/components/ConversationsList.d.ts +2 -0
  7. package/dist/esm/components/ConversationsList.js +30 -0
  8. package/dist/esm/components/ConversationsList.js.map +1 -0
  9. package/dist/esm/components/Shell.d.ts +1 -0
  10. package/dist/esm/components/Shell.js +94 -0
  11. package/dist/esm/components/Shell.js.map +1 -0
  12. package/dist/esm/components/conversation/ChunkBadges.d.ts +7 -0
  13. package/dist/esm/components/conversation/ChunkBadges.js +54 -0
  14. package/dist/esm/components/conversation/ChunkBadges.js.map +1 -0
  15. package/dist/esm/components/conversation/ChunkItem.d.ts +9 -0
  16. package/dist/esm/components/conversation/ChunkItem.js +247 -0
  17. package/dist/esm/components/conversation/ChunkItem.js.map +1 -0
  18. package/dist/esm/components/conversation/ChunksCollapsible.d.ts +7 -0
  19. package/dist/esm/components/conversation/ChunksCollapsible.js +77 -0
  20. package/dist/esm/components/conversation/ChunksCollapsible.js.map +1 -0
  21. package/dist/esm/components/conversation/ChunksTab.d.ts +8 -0
  22. package/dist/esm/components/conversation/ChunksTab.js +87 -0
  23. package/dist/esm/components/conversation/ChunksTab.js.map +1 -0
  24. package/dist/esm/components/conversation/ConversationHeader.d.ts +7 -0
  25. package/dist/esm/components/conversation/ConversationHeader.js +187 -0
  26. package/dist/esm/components/conversation/ConversationHeader.js.map +1 -0
  27. package/dist/esm/components/conversation/ConversationTabs.d.ts +10 -0
  28. package/dist/esm/components/conversation/ConversationTabs.js +88 -0
  29. package/dist/esm/components/conversation/ConversationTabs.js.map +1 -0
  30. package/dist/esm/components/conversation/EmbeddingsTab.d.ts +7 -0
  31. package/dist/esm/components/conversation/EmbeddingsTab.js +90 -0
  32. package/dist/esm/components/conversation/EmbeddingsTab.js.map +1 -0
  33. package/dist/esm/components/conversation/MessageCard.d.ts +7 -0
  34. package/dist/esm/components/conversation/MessageCard.js +152 -0
  35. package/dist/esm/components/conversation/MessageCard.js.map +1 -0
  36. package/dist/esm/components/conversation/MessageGroup.d.ts +15 -0
  37. package/dist/esm/components/conversation/MessageGroup.js +119 -0
  38. package/dist/esm/components/conversation/MessageGroup.js.map +1 -0
  39. package/dist/esm/components/conversation/MessagesTab.d.ts +7 -0
  40. package/dist/esm/components/conversation/MessagesTab.js +37 -0
  41. package/dist/esm/components/conversation/MessagesTab.js.map +1 -0
  42. package/dist/esm/components/conversation/SummariesTab.d.ts +7 -0
  43. package/dist/esm/components/conversation/SummariesTab.js +110 -0
  44. package/dist/esm/components/conversation/SummariesTab.js.map +1 -0
  45. package/dist/esm/components/conversation/ToolCallDisplay.d.ts +7 -0
  46. package/dist/esm/components/conversation/ToolCallDisplay.js +123 -0
  47. package/dist/esm/components/conversation/ToolCallDisplay.js.map +1 -0
  48. package/dist/esm/components/conversation/index.d.ts +7 -0
  49. package/dist/esm/components/list/ConversationRow.d.ts +7 -0
  50. package/dist/esm/components/list/ConversationRow.js +122 -0
  51. package/dist/esm/components/list/ConversationRow.js.map +1 -0
  52. package/dist/esm/components/list/index.d.ts +1 -0
  53. package/dist/esm/components/utils/format.d.ts +6 -0
  54. package/dist/esm/components/utils/format.js +68 -0
  55. package/dist/esm/components/utils/format.js.map +1 -0
  56. package/dist/esm/components/utils/index.d.ts +1 -0
  57. package/dist/esm/core.d.ts +18 -0
  58. package/dist/esm/core.js +9 -0
  59. package/dist/esm/core.js.map +1 -0
  60. package/dist/esm/index.d.ts +9 -0
  61. package/dist/esm/index.js +6 -0
  62. package/dist/esm/index.js.map +1 -0
  63. package/dist/esm/production.d.ts +2 -0
  64. package/dist/esm/production.js +5 -0
  65. package/dist/esm/production.js.map +1 -0
  66. package/dist/esm/store/ai-context.d.ts +126 -0
  67. package/dist/esm/store/ai-context.js +1081 -0
  68. package/dist/esm/store/ai-context.js.map +1 -0
  69. package/dist/esm/store/ai-store.d.ts +1 -0
  70. package/dist/esm/styles/tokens.d.ts +298 -0
  71. package/dist/esm/styles/tokens.js +107 -0
  72. package/dist/esm/styles/tokens.js.map +1 -0
  73. package/dist/esm/styles/use-styles.d.ts +226 -0
  74. package/dist/esm/styles/use-styles.js +1538 -0
  75. package/dist/esm/styles/use-styles.js.map +1 -0
  76. package/package.json +61 -0
  77. package/src/components/ConversationDetails.tsx +89 -0
  78. package/src/components/ConversationsList.tsx +21 -0
  79. package/src/components/Shell.tsx +93 -0
  80. package/src/components/conversation/ChunkBadges.tsx +51 -0
  81. package/src/components/conversation/ChunkItem.tsx +273 -0
  82. package/src/components/conversation/ChunksCollapsible.tsx +58 -0
  83. package/src/components/conversation/ChunksTab.tsx +85 -0
  84. package/src/components/conversation/ConversationHeader.tsx +130 -0
  85. package/src/components/conversation/ConversationTabs.tsx +104 -0
  86. package/src/components/conversation/EmbeddingsTab.tsx +80 -0
  87. package/src/components/conversation/MessageCard.tsx +137 -0
  88. package/src/components/conversation/MessageGroup.tsx +101 -0
  89. package/src/components/conversation/MessagesTab.tsx +30 -0
  90. package/src/components/conversation/SummariesTab.tsx +91 -0
  91. package/src/components/conversation/ToolCallDisplay.tsx +99 -0
  92. package/src/components/conversation/index.ts +8 -0
  93. package/src/components/list/ConversationRow.tsx +85 -0
  94. package/src/components/list/index.ts +1 -0
  95. package/src/components/utils/format.ts +61 -0
  96. package/src/components/utils/index.ts +1 -0
  97. package/src/core.tsx +10 -0
  98. package/src/index.ts +8 -0
  99. package/src/production.ts +3 -0
  100. package/src/store/ai-context.tsx +1784 -0
  101. package/src/store/ai-store.ts +2 -0
  102. package/src/styles/tokens.ts +305 -0
  103. package/src/styles/use-styles.ts +1537 -0
@@ -0,0 +1,80 @@
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 { EmbeddingOperation } from '../../store/ai-context'
6
+
7
+ interface EmbeddingsTabProps {
8
+ embeddings: Array<EmbeddingOperation>
9
+ }
10
+
11
+ export const EmbeddingsTab: Component<EmbeddingsTabProps> = (props) => {
12
+ const styles = useStyles()
13
+
14
+ return (
15
+ <Show
16
+ when={props.embeddings.length > 0}
17
+ fallback={
18
+ <div class={styles().conversationDetails.emptyMessages}>
19
+ No embedding operations yet
20
+ </div>
21
+ }
22
+ >
23
+ <div class={styles().conversationDetails.messagesList}>
24
+ <For each={props.embeddings}>
25
+ {(embedding) => (
26
+ <div class={styles().conversationDetails.operationCard}>
27
+ <div class={styles().conversationDetails.operationHeader}>
28
+ <div class={styles().conversationDetails.operationIcon}>🔢</div>
29
+ <div class={styles().conversationDetails.operationTitle}>
30
+ Embedding
31
+ </div>
32
+ <div
33
+ class={`${styles().conversationDetails.operationStatus} ${
34
+ embedding.status === 'completed'
35
+ ? styles().conversationDetails.operationStatusCompleted
36
+ : styles().conversationDetails.operationStatusPending
37
+ }`}
38
+ >
39
+ {embedding.status}
40
+ </div>
41
+ <Show when={embedding.duration > 0}>
42
+ <div class={styles().conversationDetails.durationBadge}>
43
+ ⏱️ {formatDuration(embedding.duration)}
44
+ </div>
45
+ </Show>
46
+ </div>
47
+ <div class={styles().conversationDetails.operationDetails}>
48
+ <div class={styles().conversationDetails.operationDetail}>
49
+ <span class={styles().conversationDetails.operationLabel}>
50
+ Model:
51
+ </span>
52
+ <span class={styles().conversationDetails.operationValue}>
53
+ {embedding.model}
54
+ </span>
55
+ </div>
56
+ <div class={styles().conversationDetails.operationDetail}>
57
+ <span class={styles().conversationDetails.operationLabel}>
58
+ Inputs:
59
+ </span>
60
+ <span class={styles().conversationDetails.operationValue}>
61
+ {embedding.inputCount} item
62
+ {embedding.inputCount !== 1 ? 's' : ''}
63
+ </span>
64
+ </div>
65
+ <div class={styles().conversationDetails.operationDetail}>
66
+ <span class={styles().conversationDetails.operationLabel}>
67
+ Started:
68
+ </span>
69
+ <span class={styles().conversationDetails.operationValue}>
70
+ {new Date(embedding.timestamp).toLocaleTimeString()}
71
+ </span>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ )}
76
+ </For>
77
+ </div>
78
+ </Show>
79
+ )
80
+ }
@@ -0,0 +1,137 @@
1
+ import { For, Show } from 'solid-js'
2
+ import { useStyles } from '../../styles/use-styles'
3
+ import { formatTimestamp } from '../utils'
4
+ import { ToolCallDisplay } from './ToolCallDisplay'
5
+ import { ChunksCollapsible } from './ChunksCollapsible'
6
+ import type { Message } from '../../store/ai-store'
7
+ import type { Component } from 'solid-js'
8
+
9
+ interface MessageCardProps {
10
+ message: Message
11
+ }
12
+
13
+ export const MessageCard: Component<MessageCardProps> = (props) => {
14
+ const styles = useStyles()
15
+ // Access message through props directly for proper SolidJS reactivity
16
+ const msg = () => props.message
17
+
18
+ // Check if message is from client (explicitly marked) or server (default)
19
+ const isClientMessage = () => msg().source === 'client'
20
+
21
+ // Determine card class based on role and source
22
+ const getCardClass = () => {
23
+ const base = styles().conversationDetails.messageCard
24
+ if (msg().role === 'user') {
25
+ return `${base} ${styles().conversationDetails.messageCardUser}`
26
+ }
27
+ // For assistant messages: client if explicitly marked, otherwise server
28
+ if (isClientMessage()) {
29
+ return `${base} ${styles().conversationDetails.messageCardClient}`
30
+ }
31
+ return `${base} ${styles().conversationDetails.messageCardServer}`
32
+ }
33
+
34
+ // Check if this is a non-user message (needs source banner and content wrapper)
35
+ const isSourcedMessage = () => msg().role !== 'user'
36
+
37
+ return (
38
+ <div class={getCardClass()}>
39
+ {/* Source indicator banner at top of card */}
40
+ <Show when={isSourcedMessage()}>
41
+ <div
42
+ class={`${styles().conversationDetails.sourceBanner} ${
43
+ isClientMessage()
44
+ ? styles().conversationDetails.sourceBannerClient
45
+ : styles().conversationDetails.sourceBannerServer
46
+ }`}
47
+ >
48
+ <span class={styles().conversationDetails.sourceBannerIcon}>
49
+ {isClientMessage() ? '📱' : '☁️'}
50
+ </span>
51
+ <span class={styles().conversationDetails.sourceBannerText}>
52
+ {isClientMessage() ? 'Client Message' : 'Server Message'}
53
+ </span>
54
+ </div>
55
+ </Show>
56
+ {/* Content wrapper with padding */}
57
+ <div
58
+ class={
59
+ isSourcedMessage()
60
+ ? styles().conversationDetails.messageCardContent
61
+ : ''
62
+ }
63
+ >
64
+ <div class={styles().conversationDetails.messageHeader}>
65
+ <div
66
+ class={
67
+ msg().role === 'user'
68
+ ? styles().conversationDetails.avatarUser
69
+ : isClientMessage()
70
+ ? styles().conversationDetails.avatarClient
71
+ : styles().conversationDetails.avatarServer
72
+ }
73
+ >
74
+ {msg().role === 'user' ? 'U' : msg().role === 'tool' ? '🔧' : '🤖'}
75
+ </div>
76
+ <div class={styles().conversationDetails.roleLabel}>
77
+ <div
78
+ class={
79
+ msg().role === 'user'
80
+ ? styles().conversationDetails.roleLabelUser
81
+ : isClientMessage()
82
+ ? styles().conversationDetails.roleLabelClient
83
+ : styles().conversationDetails.roleLabelServer
84
+ }
85
+ >
86
+ {msg().role}
87
+ </div>
88
+ </div>
89
+ <div class={styles().conversationDetails.timestamp}>
90
+ {formatTimestamp(msg().timestamp)}
91
+ </div>
92
+ {/* Per-message token usage */}
93
+ <Show when={msg().usage}>
94
+ <div class={styles().conversationDetails.messageUsage}>
95
+ <span class={styles().conversationDetails.messageUsageIcon}>
96
+ 🎯
97
+ </span>
98
+ <span>{msg().usage?.promptTokens.toLocaleString()} in</span>
99
+ <span>•</span>
100
+ <span>{msg().usage?.completionTokens.toLocaleString()} out</span>
101
+ </div>
102
+ </Show>
103
+ </div>
104
+
105
+ {/* Thinking content (for extended thinking models) */}
106
+ <Show when={msg().thinkingContent}>
107
+ <details class={styles().conversationDetails.thinkingDetails}>
108
+ <summary class={styles().conversationDetails.thinkingSummary}>
109
+ 💭 Thinking...
110
+ </summary>
111
+ <div class={styles().conversationDetails.thinkingContent}>
112
+ {msg().thinkingContent}
113
+ </div>
114
+ </details>
115
+ </Show>
116
+
117
+ <div class={styles().conversationDetails.messageContent}>
118
+ {msg().content}
119
+ </div>
120
+
121
+ {/* Tool Calls Display */}
122
+ <Show when={msg().toolCalls && msg().toolCalls!.length > 0}>
123
+ <div class={styles().conversationDetails.toolCallsContainer}>
124
+ <For each={msg().toolCalls}>
125
+ {(tool) => <ToolCallDisplay tool={tool} />}
126
+ </For>
127
+ </div>
128
+ </Show>
129
+
130
+ {/* Chunks Display (for client conversations with server chunks) */}
131
+ <Show when={msg().chunks && msg().chunks!.length > 0}>
132
+ <ChunksCollapsible chunks={msg().chunks!} />
133
+ </Show>
134
+ </div>
135
+ </div>
136
+ )
137
+ }
@@ -0,0 +1,101 @@
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 TokenUsage {
9
+ promptTokens: number
10
+ completionTokens: number
11
+ totalTokens: number
12
+ }
13
+
14
+ interface MessageGroupProps {
15
+ messageId: string
16
+ chunks: Array<Chunk>
17
+ groupIndex: number
18
+ usage?: TokenUsage
19
+ }
20
+
21
+ export const MessageGroup: Component<MessageGroupProps> = (props) => {
22
+ const styles = useStyles()
23
+
24
+ const accumulatedContent = () =>
25
+ props.chunks
26
+ .filter((c) => c.type === 'content' && (c.content || c.delta))
27
+ .map((c) => c.content)
28
+ .join('')
29
+
30
+ // Total raw chunks = sum of all chunkCounts
31
+ const totalRawChunks = () =>
32
+ props.chunks.reduce((sum, c) => sum + (c.chunkCount || 1), 0)
33
+ // Consolidated entries = number of entries in the chunks array
34
+ const consolidatedEntries = () => props.chunks.length
35
+
36
+ return (
37
+ <details class={styles().conversationDetails.messageGroupDetails}>
38
+ <summary class={styles().conversationDetails.messageGroupSummary}>
39
+ <div class={styles().conversationDetails.messageGroupContent}>
40
+ {/* Header */}
41
+ <div class={styles().conversationDetails.messageGroupHeader}>
42
+ <span>Message #{props.groupIndex + 1}</span>
43
+ <div
44
+ class={`${styles().conversationDetails.chunkBadge} ${styles().conversationDetails.chunkBadgeCount}`}
45
+ >
46
+ 📦 {totalRawChunks()} chunks
47
+ <Show when={consolidatedEntries() !== totalRawChunks()}>
48
+ <span style={{ opacity: 0.7, 'margin-left': '4px' }}>
49
+ ({consolidatedEntries()} entries)
50
+ </span>
51
+ </Show>
52
+ </div>
53
+ <ChunkBadges chunks={props.chunks} />
54
+ {/* Usage info */}
55
+ <Show when={props.usage}>
56
+ <div class={styles().conversationDetails.messageUsage}>
57
+ <span class={styles().conversationDetails.messageUsageIcon}>
58
+ 🎯
59
+ </span>
60
+ <span>{props.usage?.promptTokens.toLocaleString()} in</span>
61
+ <span>•</span>
62
+ <span>
63
+ {props.usage?.completionTokens.toLocaleString()} out
64
+ </span>
65
+ </div>
66
+ </Show>
67
+ </div>
68
+
69
+ {/* Message ID */}
70
+ <div
71
+ class={styles().conversationDetails.messageId}
72
+ title={props.messageId}
73
+ >
74
+ ID: {props.messageId}
75
+ </div>
76
+
77
+ {/* Accumulated Content Preview */}
78
+ <Show when={accumulatedContent()}>
79
+ <div
80
+ class={styles().conversationDetails.contentPreview}
81
+ title={accumulatedContent()}
82
+ >
83
+ {accumulatedContent()}
84
+ </div>
85
+ </Show>
86
+ </div>
87
+ </summary>
88
+
89
+ {/* Chunks in this group */}
90
+ <div class={styles().conversationDetails.chunksContainer}>
91
+ <div class={styles().conversationDetails.chunksList}>
92
+ <For each={props.chunks}>
93
+ {(chunk, chunkIndex) => (
94
+ <ChunkItem chunk={chunk} index={chunkIndex()} variant="large" />
95
+ )}
96
+ </For>
97
+ </div>
98
+ </div>
99
+ </details>
100
+ )
101
+ }
@@ -0,0 +1,30 @@
1
+ import { For, Show } from 'solid-js'
2
+ import { useStyles } from '../../styles/use-styles'
3
+ import { MessageCard } from './MessageCard'
4
+ import type { Component } from 'solid-js'
5
+ import type { Message } from '../../store/ai-store'
6
+
7
+ interface MessagesTabProps {
8
+ messages: Array<Message>
9
+ }
10
+
11
+ export const MessagesTab: Component<MessagesTabProps> = (props) => {
12
+ const styles = useStyles()
13
+
14
+ return (
15
+ <Show
16
+ when={props.messages.length > 0}
17
+ fallback={
18
+ <div class={styles().conversationDetails.emptyMessages}>
19
+ No messages yet. Start a conversation to see messages here.
20
+ </div>
21
+ }
22
+ >
23
+ <div class={styles().conversationDetails.messagesList}>
24
+ <For each={props.messages}>
25
+ {(msg) => <MessageCard message={msg} />}
26
+ </For>
27
+ </div>
28
+ </Show>
29
+ )
30
+ }
@@ -0,0 +1,91 @@
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 { SummarizeOperation } from '../../store/ai-context'
6
+
7
+ interface SummariesTabProps {
8
+ summaries: Array<SummarizeOperation>
9
+ }
10
+
11
+ export const SummariesTab: Component<SummariesTabProps> = (props) => {
12
+ const styles = useStyles()
13
+
14
+ return (
15
+ <Show
16
+ when={props.summaries.length > 0}
17
+ fallback={
18
+ <div class={styles().conversationDetails.emptyMessages}>
19
+ No summarize operations yet
20
+ </div>
21
+ }
22
+ >
23
+ <div class={styles().conversationDetails.messagesList}>
24
+ <For each={props.summaries}>
25
+ {(summary) => (
26
+ <div class={styles().conversationDetails.operationCard}>
27
+ <div class={styles().conversationDetails.operationHeader}>
28
+ <div class={styles().conversationDetails.operationIcon}>📝</div>
29
+ <div class={styles().conversationDetails.operationTitle}>
30
+ Summarize
31
+ </div>
32
+ <div
33
+ class={`${styles().conversationDetails.operationStatus} ${
34
+ summary.status === 'completed'
35
+ ? styles().conversationDetails.operationStatusCompleted
36
+ : styles().conversationDetails.operationStatusPending
37
+ }`}
38
+ >
39
+ {summary.status}
40
+ </div>
41
+ <Show
42
+ when={summary.duration !== undefined && summary.duration > 0}
43
+ >
44
+ <div class={styles().conversationDetails.durationBadge}>
45
+ ⏱️ {formatDuration(summary.duration)}
46
+ </div>
47
+ </Show>
48
+ </div>
49
+ <div class={styles().conversationDetails.operationDetails}>
50
+ <div class={styles().conversationDetails.operationDetail}>
51
+ <span class={styles().conversationDetails.operationLabel}>
52
+ Model:
53
+ </span>
54
+ <span class={styles().conversationDetails.operationValue}>
55
+ {summary.model}
56
+ </span>
57
+ </div>
58
+ <div class={styles().conversationDetails.operationDetail}>
59
+ <span class={styles().conversationDetails.operationLabel}>
60
+ Input:
61
+ </span>
62
+ <span class={styles().conversationDetails.operationValue}>
63
+ {summary.inputLength.toLocaleString()} chars
64
+ </span>
65
+ </div>
66
+ <Show when={summary.outputLength !== undefined}>
67
+ <div class={styles().conversationDetails.operationDetail}>
68
+ <span class={styles().conversationDetails.operationLabel}>
69
+ Output:
70
+ </span>
71
+ <span class={styles().conversationDetails.operationValue}>
72
+ {summary.outputLength?.toLocaleString()} chars
73
+ </span>
74
+ </div>
75
+ </Show>
76
+ <div class={styles().conversationDetails.operationDetail}>
77
+ <span class={styles().conversationDetails.operationLabel}>
78
+ Started:
79
+ </span>
80
+ <span class={styles().conversationDetails.operationValue}>
81
+ {new Date(summary.timestamp).toLocaleTimeString()}
82
+ </span>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ )}
87
+ </For>
88
+ </div>
89
+ </Show>
90
+ )
91
+ }
@@ -0,0 +1,99 @@
1
+ import { Show } from 'solid-js'
2
+ import { JsonTree } from '@tanstack/devtools-ui'
3
+ import { useStyles } from '../../styles/use-styles'
4
+ import { formatDuration } from '../utils'
5
+ import type { Component } from 'solid-js'
6
+ import type { ToolCall } from '../../store/ai-context'
7
+
8
+ interface ToolCallDisplayProps {
9
+ tool: ToolCall
10
+ }
11
+
12
+ export const ToolCallDisplay: Component<ToolCallDisplayProps> = (props) => {
13
+ const styles = useStyles()
14
+ const tool = () => props.tool
15
+
16
+ // Parse arguments if they're a string
17
+ const parsedArguments = () => {
18
+ const args = tool().arguments
19
+ if (typeof args === 'string') {
20
+ try {
21
+ return JSON.parse(args)
22
+ } catch {
23
+ return args
24
+ }
25
+ }
26
+ return args
27
+ }
28
+
29
+ return (
30
+ <details
31
+ class={`${styles().conversationDetails.toolCall} ${
32
+ tool().approvalRequired
33
+ ? styles().conversationDetails.toolCallApproval
34
+ : styles().conversationDetails.toolCallNormal
35
+ }`}
36
+ >
37
+ <summary class={styles().conversationDetails.toolCallHeader}>
38
+ <div
39
+ class={`${styles().conversationDetails.toolCallName} ${
40
+ tool().approvalRequired
41
+ ? styles().conversationDetails.toolCallNameApproval
42
+ : styles().conversationDetails.toolCallNameNormal
43
+ }`}
44
+ >
45
+ {tool().approvalRequired ? '⚠️' : '🔧'} {tool().name}
46
+ </div>
47
+ <div
48
+ class={`${styles().conversationDetails.toolStateBadge} ${
49
+ tool().approvalRequired
50
+ ? styles().conversationDetails.toolStateBadgeApproval
51
+ : styles().conversationDetails.toolStateBadgeNormal
52
+ }`}
53
+ >
54
+ {tool().state}
55
+ </div>
56
+ <Show when={tool().duration !== undefined && tool().duration! > 0}>
57
+ <div class={styles().conversationDetails.durationBadge}>
58
+ ⏱️ {formatDuration(tool().duration)}
59
+ </div>
60
+ </Show>
61
+ <Show when={tool().approvalRequired}>
62
+ <div class={styles().conversationDetails.approvalRequiredBadge}>
63
+ APPROVAL REQUIRED
64
+ </div>
65
+ </Show>
66
+ </summary>
67
+ <div class={styles().conversationDetails.toolCallContent}>
68
+ <Show when={tool().arguments}>
69
+ <div class={styles().conversationDetails.toolSection}>
70
+ <div class={styles().conversationDetails.toolSectionLabel}>
71
+ Arguments
72
+ </div>
73
+ <div class={styles().conversationDetails.toolJsonContainer}>
74
+ <JsonTree
75
+ value={parsedArguments() as Record<string, unknown>}
76
+ defaultExpansionDepth={2}
77
+ copyable
78
+ />
79
+ </div>
80
+ </div>
81
+ </Show>
82
+ <Show when={tool().result !== undefined}>
83
+ <div class={styles().conversationDetails.toolSection}>
84
+ <div class={styles().conversationDetails.toolSectionLabel}>
85
+ Result
86
+ </div>
87
+ <div class={styles().conversationDetails.toolJsonContainer}>
88
+ <JsonTree
89
+ value={tool().result as Record<string, unknown>}
90
+ defaultExpansionDepth={2}
91
+ copyable
92
+ />
93
+ </div>
94
+ </div>
95
+ </Show>
96
+ </div>
97
+ </details>
98
+ )
99
+ }
@@ -0,0 +1,8 @@
1
+ export { ConversationHeader } from './ConversationHeader'
2
+ export { ConversationTabs } from './ConversationTabs'
3
+ export type { TabType } from './ConversationTabs'
4
+
5
+ export { MessagesTab } from './MessagesTab'
6
+ export { ChunksTab } from './ChunksTab'
7
+ export { EmbeddingsTab } from './EmbeddingsTab'
8
+ export { SummariesTab } from './SummariesTab'
@@ -0,0 +1,85 @@
1
+ import { Show } from 'solid-js'
2
+ import { useStyles } from '../../styles/use-styles'
3
+ import { useAIStore } from '../../store/ai-context'
4
+ import { getStatusColor, getTypeColor } from '../utils'
5
+ import type { Conversation } from '../../store/ai-context'
6
+ import type { Component } from 'solid-js'
7
+
8
+ interface ConversationRowProps {
9
+ conversation: Conversation
10
+ }
11
+
12
+ export const ConversationRow: Component<ConversationRowProps> = (props) => {
13
+ const { state, selectConversation } = useAIStore()
14
+ const styles = useStyles()
15
+ const conv = () => props.conversation
16
+
17
+ const hasToolCalls = () => {
18
+ return conv().messages.some(
19
+ (msg) => msg.toolCalls && msg.toolCalls.length > 0,
20
+ )
21
+ }
22
+
23
+ const countToolCalls = () => {
24
+ return conv().messages.reduce((total, msg) => {
25
+ return total + (msg.toolCalls?.length || 0)
26
+ }, 0)
27
+ }
28
+
29
+ // Total raw chunks = sum of all chunkCounts from all chunks
30
+ const totalRawChunks = () => {
31
+ return conv().chunks.reduce((sum, c) => sum + (c.chunkCount || 1), 0)
32
+ }
33
+
34
+ return (
35
+ <div
36
+ class={`${styles().utilRow} ${state.activeConversationId === conv().id ? styles().utilRowSelected : ''}`}
37
+ onClick={() => selectConversation(conv().id)}
38
+ >
39
+ <div class={styles().conversationsList.rowMain}>
40
+ <div class={styles().conversationsList.rowTop}>
41
+ <div class={styles().conversationsList.rowInfo}>
42
+ <div
43
+ class={styles().conversationsList.typeDot}
44
+ style={{ background: getTypeColor(conv().type) }}
45
+ />
46
+ <div class={styles().conversationsList.label}>{conv().label}</div>
47
+ <Show when={hasToolCalls()}>
48
+ <div
49
+ class={styles().conversationsList.toolCallsBadge}
50
+ title={`${countToolCalls()} tool call${countToolCalls() !== 1 ? 's' : ''}`}
51
+ >
52
+ 🔧 {countToolCalls()}
53
+ </div>
54
+ </Show>
55
+ </div>
56
+ <div class={styles().conversationsList.rowRight}>
57
+ <Show when={conv().status === 'active'}>
58
+ <div class={styles().conversationsList.loadingIndicator}>⟳</div>
59
+ </Show>
60
+ <div
61
+ class={styles().conversationsList.statusDot}
62
+ style={{ background: getStatusColor(conv().status) }}
63
+ />
64
+ </div>
65
+ </div>
66
+ <div class={styles().conversationsList.stats}>
67
+ <div class={styles().conversationsList.statItem}>
68
+ 💬 {conv().messages.length}
69
+ </div>
70
+ <div class={styles().conversationsList.statItem}>
71
+ 📦 {totalRawChunks()}
72
+ </div>
73
+ <Show when={conv().usage}>
74
+ <div
75
+ class={styles().conversationsList.tokensBadge}
76
+ title={`Prompt: ${conv().usage?.promptTokens || 0} | Completion: ${conv().usage?.completionTokens || 0}`}
77
+ >
78
+ 🎯 {conv().usage?.totalTokens.toLocaleString() || 0}
79
+ </div>
80
+ </Show>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ )
85
+ }
@@ -0,0 +1 @@
1
+ export { ConversationRow } from './ConversationRow'