@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
package/src/store/ai-context.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { aiEventClient } from '@tanstack/ai/event-client'
|
|
|
4
4
|
import type { ParentComponent } from 'solid-js'
|
|
5
5
|
|
|
6
6
|
interface MessagePart {
|
|
7
|
-
type: 'text' | 'tool-call' | 'tool-result'
|
|
7
|
+
type: 'text' | 'tool-call' | 'tool-result' | 'thinking'
|
|
8
8
|
content?: string
|
|
9
9
|
toolCallId?: string
|
|
10
10
|
toolName?: string
|
|
@@ -84,6 +84,8 @@ export interface Chunk {
|
|
|
84
84
|
duration?: number
|
|
85
85
|
/** Number of raw chunks that were merged into this consolidated chunk */
|
|
86
86
|
chunkCount: number
|
|
87
|
+
/** Whether this is a client-side tool execution */
|
|
88
|
+
isClientTool?: boolean
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
export interface SummarizeOperation {
|
|
@@ -96,6 +98,13 @@ export interface SummarizeOperation {
|
|
|
96
98
|
status: 'started' | 'completed'
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
export interface ActivityEvent {
|
|
102
|
+
id: string
|
|
103
|
+
name: string
|
|
104
|
+
timestamp: number
|
|
105
|
+
payload: unknown
|
|
106
|
+
}
|
|
107
|
+
|
|
99
108
|
export interface Conversation {
|
|
100
109
|
id: string
|
|
101
110
|
type: 'client' | 'server'
|
|
@@ -111,12 +120,21 @@ export interface Conversation {
|
|
|
111
120
|
iterationCount?: number
|
|
112
121
|
toolNames?: Array<string>
|
|
113
122
|
options?: Record<string, unknown>
|
|
114
|
-
|
|
123
|
+
modelOptions?: Record<string, unknown>
|
|
124
|
+
systemPrompts?: Array<string>
|
|
115
125
|
/** Flags for which operation types this conversation has */
|
|
116
126
|
hasChat?: boolean
|
|
117
127
|
hasSummarize?: boolean
|
|
128
|
+
hasImage?: boolean
|
|
129
|
+
hasSpeech?: boolean
|
|
130
|
+
hasTranscription?: boolean
|
|
131
|
+
hasVideo?: boolean
|
|
118
132
|
/** Summarize operations in this conversation */
|
|
119
133
|
summaries?: Array<SummarizeOperation>
|
|
134
|
+
imageEvents?: Array<ActivityEvent>
|
|
135
|
+
speechEvents?: Array<ActivityEvent>
|
|
136
|
+
transcriptionEvents?: Array<ActivityEvent>
|
|
137
|
+
videoEvents?: Array<ActivityEvent>
|
|
120
138
|
}
|
|
121
139
|
|
|
122
140
|
interface AIStoreState {
|
|
@@ -336,6 +354,10 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
336
354
|
label,
|
|
337
355
|
messages: [],
|
|
338
356
|
chunks: [],
|
|
357
|
+
imageEvents: [],
|
|
358
|
+
speechEvents: [],
|
|
359
|
+
transcriptionEvents: [],
|
|
360
|
+
videoEvents: [],
|
|
339
361
|
status: 'active',
|
|
340
362
|
startedAt: Date.now(),
|
|
341
363
|
})
|
|
@@ -398,6 +420,9 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
398
420
|
return
|
|
399
421
|
}
|
|
400
422
|
}
|
|
423
|
+
|
|
424
|
+
// Fallback: add to conversation's main chunks array if no assistant message found
|
|
425
|
+
addChunk(conversationId, chunk)
|
|
401
426
|
}
|
|
402
427
|
|
|
403
428
|
function updateMessageUsage(
|
|
@@ -553,6 +578,38 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
553
578
|
queueChunk(conversationId, chunk)
|
|
554
579
|
}
|
|
555
580
|
|
|
581
|
+
function addActivityEvent(
|
|
582
|
+
conversationId: string,
|
|
583
|
+
activity:
|
|
584
|
+
| 'imageEvents'
|
|
585
|
+
| 'speechEvents'
|
|
586
|
+
| 'transcriptionEvents'
|
|
587
|
+
| 'videoEvents',
|
|
588
|
+
event: ActivityEvent,
|
|
589
|
+
): void {
|
|
590
|
+
if (!state.conversations[conversationId]) return
|
|
591
|
+
|
|
592
|
+
setState(
|
|
593
|
+
'conversations',
|
|
594
|
+
conversationId,
|
|
595
|
+
activity,
|
|
596
|
+
produce((events: Array<ActivityEvent> | undefined) => {
|
|
597
|
+
if (!events) return [event]
|
|
598
|
+
events.push(event)
|
|
599
|
+
return events
|
|
600
|
+
}),
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
const activityFlagMap: Record<typeof activity, keyof Conversation> = {
|
|
604
|
+
imageEvents: 'hasImage',
|
|
605
|
+
speechEvents: 'hasSpeech',
|
|
606
|
+
transcriptionEvents: 'hasTranscription',
|
|
607
|
+
videoEvents: 'hasVideo',
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
setState('conversations', conversationId, activityFlagMap[activity], true)
|
|
611
|
+
}
|
|
612
|
+
|
|
556
613
|
/**
|
|
557
614
|
* For server conversations, ensure a message exists for the given messageId.
|
|
558
615
|
* This creates a placeholder message that will be updated as chunks arrive.
|
|
@@ -598,58 +655,146 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
598
655
|
}),
|
|
599
656
|
)
|
|
600
657
|
|
|
658
|
+
// ============= Message Events =============
|
|
659
|
+
|
|
601
660
|
cleanupFns.push(
|
|
602
|
-
aiEventClient.on('
|
|
603
|
-
const clientId =
|
|
604
|
-
|
|
661
|
+
aiEventClient.on('text:message:created', (e) => {
|
|
662
|
+
const { clientId, streamId, messageId, role, content, timestamp } =
|
|
663
|
+
e.payload
|
|
664
|
+
const conversationId =
|
|
665
|
+
clientId ||
|
|
666
|
+
(streamId ? streamToConversation.get(streamId) : undefined)
|
|
667
|
+
|
|
668
|
+
if (!conversationId) return
|
|
669
|
+
if (role === 'tool' || role === 'system') return
|
|
670
|
+
|
|
671
|
+
if (!state.conversations[conversationId]) {
|
|
605
672
|
getOrCreateConversation(
|
|
606
|
-
|
|
607
|
-
'client',
|
|
608
|
-
|
|
673
|
+
conversationId,
|
|
674
|
+
clientId ? 'client' : 'server',
|
|
675
|
+
clientId
|
|
676
|
+
? `Client Chat (${conversationId.substring(0, 8)})`
|
|
677
|
+
: `Server Chat (${conversationId.substring(0, 8)})`,
|
|
609
678
|
)
|
|
610
679
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
680
|
+
|
|
681
|
+
const conv = state.conversations[conversationId]
|
|
682
|
+
if (!conv) return
|
|
683
|
+
|
|
684
|
+
const existingIndex = conv.messages.findIndex(
|
|
685
|
+
(message) => message.id === messageId,
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
const parts = e.payload.parts?.map((part) => {
|
|
689
|
+
if (part.type === 'text') {
|
|
690
|
+
return { type: 'text', content: part.content } satisfies MessagePart
|
|
691
|
+
}
|
|
692
|
+
if (part.type === 'tool-call') {
|
|
693
|
+
return {
|
|
694
|
+
type: 'tool-call',
|
|
695
|
+
toolCallId: part.id,
|
|
696
|
+
toolName: part.name,
|
|
697
|
+
arguments: part.arguments,
|
|
698
|
+
state: part.state,
|
|
699
|
+
output: part.output,
|
|
700
|
+
content: part.approval
|
|
701
|
+
? JSON.stringify(part.approval)
|
|
702
|
+
: undefined,
|
|
703
|
+
} satisfies MessagePart
|
|
704
|
+
}
|
|
705
|
+
if (part.type === 'tool-result') {
|
|
706
|
+
return {
|
|
707
|
+
type: 'tool-result',
|
|
708
|
+
toolCallId: part.toolCallId,
|
|
709
|
+
content: part.content,
|
|
710
|
+
state: part.state,
|
|
711
|
+
error: part.error,
|
|
712
|
+
} satisfies MessagePart
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
type: 'thinking',
|
|
716
|
+
content: part.content,
|
|
717
|
+
} satisfies MessagePart
|
|
617
718
|
})
|
|
618
|
-
|
|
719
|
+
|
|
720
|
+
const toolCalls = e.payload.toolCalls?.map((toolCall) => ({
|
|
721
|
+
id: toolCall.id,
|
|
722
|
+
name: toolCall.function.name,
|
|
723
|
+
arguments: toolCall.function.arguments,
|
|
724
|
+
state: 'input-complete',
|
|
725
|
+
}))
|
|
726
|
+
|
|
727
|
+
const source = e.payload.source ?? (clientId ? 'client' : 'server')
|
|
728
|
+
|
|
729
|
+
if (role === 'user' && conv.type === 'client' && source === 'server') {
|
|
730
|
+
return
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Skip empty assistant messages from client - the real content comes from server
|
|
734
|
+
if (
|
|
735
|
+
role === 'assistant' &&
|
|
736
|
+
source === 'client' &&
|
|
737
|
+
!content &&
|
|
738
|
+
(!toolCalls || toolCalls.length === 0)
|
|
739
|
+
) {
|
|
740
|
+
return
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const messagePayload: Message = {
|
|
744
|
+
id: messageId,
|
|
745
|
+
role,
|
|
746
|
+
content,
|
|
747
|
+
timestamp,
|
|
748
|
+
parts,
|
|
749
|
+
toolCalls,
|
|
750
|
+
source,
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (existingIndex >= 0) {
|
|
754
|
+
updateMessage(conversationId, existingIndex, messagePayload)
|
|
755
|
+
} else {
|
|
756
|
+
addMessage(conversationId, messagePayload)
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
updateConversation(conversationId, { status: 'active', hasChat: true })
|
|
619
760
|
}),
|
|
620
761
|
)
|
|
621
762
|
|
|
622
763
|
cleanupFns.push(
|
|
623
|
-
aiEventClient.on('
|
|
624
|
-
const clientId = e.payload
|
|
625
|
-
const
|
|
764
|
+
aiEventClient.on('text:message:user', (e) => {
|
|
765
|
+
const { clientId, streamId, messageId, content, timestamp } = e.payload
|
|
766
|
+
const conversationId =
|
|
767
|
+
clientId ||
|
|
768
|
+
(streamId ? streamToConversation.get(streamId) : undefined)
|
|
769
|
+
if (!conversationId) return
|
|
626
770
|
|
|
627
|
-
|
|
628
|
-
if (!
|
|
771
|
+
const conv = state.conversations[conversationId]
|
|
772
|
+
if (!conv) return
|
|
629
773
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
addMessage(clientId, {
|
|
641
|
-
id: e.payload.messageId,
|
|
642
|
-
role: 'tool',
|
|
643
|
-
content: e.payload.contentPreview,
|
|
644
|
-
timestamp: e.payload.timestamp,
|
|
645
|
-
source: 'client',
|
|
646
|
-
})
|
|
774
|
+
const existingIndex = conv.messages.findIndex(
|
|
775
|
+
(message) => message.id === messageId,
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
if (existingIndex >= 0) return
|
|
779
|
+
|
|
780
|
+
const source = e.payload.source ?? (clientId ? 'client' : 'server')
|
|
781
|
+
|
|
782
|
+
if (conv.type === 'client' && source === 'server') {
|
|
783
|
+
return
|
|
647
784
|
}
|
|
785
|
+
|
|
786
|
+
addMessage(conversationId, {
|
|
787
|
+
id: messageId,
|
|
788
|
+
role: 'user',
|
|
789
|
+
content,
|
|
790
|
+
timestamp,
|
|
791
|
+
source,
|
|
792
|
+
})
|
|
648
793
|
}),
|
|
649
794
|
)
|
|
650
795
|
|
|
651
796
|
cleanupFns.push(
|
|
652
|
-
aiEventClient.on('client:loading
|
|
797
|
+
aiEventClient.on('client:loading:changed', (e) => {
|
|
653
798
|
const clientId = e.payload.clientId
|
|
654
799
|
if (state.conversations[clientId]) {
|
|
655
800
|
updateConversation(clientId, {
|
|
@@ -672,7 +817,7 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
672
817
|
)
|
|
673
818
|
|
|
674
819
|
cleanupFns.push(
|
|
675
|
-
aiEventClient.on('client:messages
|
|
820
|
+
aiEventClient.on('client:messages:cleared', (e) => {
|
|
676
821
|
const clientId = e.payload.clientId
|
|
677
822
|
if (state.conversations[clientId]) {
|
|
678
823
|
updateConversation(clientId, {
|
|
@@ -698,7 +843,7 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
698
843
|
)
|
|
699
844
|
|
|
700
845
|
cleanupFns.push(
|
|
701
|
-
aiEventClient.on('client:error
|
|
846
|
+
aiEventClient.on('client:error:changed', (e) => {
|
|
702
847
|
const clientId = e.payload.clientId
|
|
703
848
|
if (state.conversations[clientId] && e.payload.error) {
|
|
704
849
|
updateConversation(clientId, { status: 'error' })
|
|
@@ -706,112 +851,10 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
706
851
|
}),
|
|
707
852
|
)
|
|
708
853
|
|
|
709
|
-
cleanupFns.push(
|
|
710
|
-
aiEventClient.on('client:assistant-message-updated', (e) => {
|
|
711
|
-
const clientId = e.payload.clientId
|
|
712
|
-
const messageId = e.payload.messageId
|
|
713
|
-
const content = e.payload.content
|
|
714
|
-
|
|
715
|
-
if (!state.conversations[clientId]) return
|
|
716
|
-
|
|
717
|
-
const conv = state.conversations[clientId]
|
|
718
|
-
|
|
719
|
-
// Find message by ID anywhere in the list, not just the last one
|
|
720
|
-
const messageIndex = conv.messages.findIndex(
|
|
721
|
-
(m: Message) => m.id === messageId,
|
|
722
|
-
)
|
|
723
|
-
|
|
724
|
-
// Only update existing messages, don't create new ones
|
|
725
|
-
// (client:message-appended is responsible for creating messages)
|
|
726
|
-
if (messageIndex >= 0) {
|
|
727
|
-
updateMessage(clientId, messageIndex, {
|
|
728
|
-
content,
|
|
729
|
-
model: conv.model,
|
|
730
|
-
})
|
|
731
|
-
}
|
|
732
|
-
}),
|
|
733
|
-
)
|
|
734
|
-
|
|
735
|
-
cleanupFns.push(
|
|
736
|
-
aiEventClient.on('client:tool-call-updated', (e) => {
|
|
737
|
-
const {
|
|
738
|
-
clientId,
|
|
739
|
-
messageId,
|
|
740
|
-
toolCallId,
|
|
741
|
-
toolName,
|
|
742
|
-
state: toolCallState,
|
|
743
|
-
arguments: args,
|
|
744
|
-
} = e.payload as {
|
|
745
|
-
clientId: string
|
|
746
|
-
messageId: string
|
|
747
|
-
toolCallId: string
|
|
748
|
-
toolName: string
|
|
749
|
-
state: string
|
|
750
|
-
arguments: unknown
|
|
751
|
-
timestamp: number
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
if (!state.conversations[clientId]) return
|
|
755
|
-
|
|
756
|
-
const conv = state.conversations[clientId]
|
|
757
|
-
const messageIndex = conv.messages.findIndex(
|
|
758
|
-
(m: Message) => m.id === messageId,
|
|
759
|
-
)
|
|
760
|
-
if (messageIndex === -1) return
|
|
761
|
-
|
|
762
|
-
const message = conv.messages[messageIndex]
|
|
763
|
-
if (!message) return
|
|
764
|
-
|
|
765
|
-
const toolCalls = message.toolCalls || []
|
|
766
|
-
const existingToolIndex = toolCalls.findIndex(
|
|
767
|
-
(t: ToolCall) => t.id === toolCallId,
|
|
768
|
-
)
|
|
769
|
-
|
|
770
|
-
const toolCall: ToolCall = {
|
|
771
|
-
id: toolCallId,
|
|
772
|
-
name: toolName,
|
|
773
|
-
arguments: JSON.stringify(args, null, 2),
|
|
774
|
-
state: toolCallState,
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
if (existingToolIndex >= 0) {
|
|
778
|
-
updateToolCall(clientId, messageIndex, existingToolIndex, toolCall)
|
|
779
|
-
} else {
|
|
780
|
-
setToolCalls(clientId, messageIndex, [...toolCalls, toolCall])
|
|
781
|
-
}
|
|
782
|
-
}),
|
|
783
|
-
)
|
|
784
|
-
|
|
785
|
-
cleanupFns.push(
|
|
786
|
-
aiEventClient.on('client:approval-requested', (e) => {
|
|
787
|
-
const { clientId, messageId, toolCallId, approvalId } = e.payload
|
|
788
|
-
|
|
789
|
-
if (!state.conversations[clientId]) return
|
|
790
|
-
|
|
791
|
-
const conv = state.conversations[clientId]
|
|
792
|
-
const messageIndex = conv.messages.findIndex((m) => m.id === messageId)
|
|
793
|
-
if (messageIndex === -1) return
|
|
794
|
-
|
|
795
|
-
const message = conv.messages[messageIndex]
|
|
796
|
-
if (!message?.toolCalls) return
|
|
797
|
-
|
|
798
|
-
const toolCallIndex = message.toolCalls.findIndex(
|
|
799
|
-
(t) => t.id === toolCallId,
|
|
800
|
-
)
|
|
801
|
-
if (toolCallIndex === -1) return
|
|
802
|
-
|
|
803
|
-
updateToolCall(clientId, messageIndex, toolCallIndex, {
|
|
804
|
-
approvalRequired: true,
|
|
805
|
-
approvalId,
|
|
806
|
-
state: 'approval-requested',
|
|
807
|
-
})
|
|
808
|
-
}),
|
|
809
|
-
)
|
|
810
|
-
|
|
811
854
|
// ============= Tool Events =============
|
|
812
855
|
|
|
813
856
|
cleanupFns.push(
|
|
814
|
-
aiEventClient.on('
|
|
857
|
+
aiEventClient.on('tools:result:added', (e) => {
|
|
815
858
|
const {
|
|
816
859
|
clientId,
|
|
817
860
|
toolCallId,
|
|
@@ -821,10 +864,23 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
821
864
|
timestamp,
|
|
822
865
|
} = e.payload
|
|
823
866
|
|
|
867
|
+
if (!clientId) return
|
|
824
868
|
if (!state.conversations[clientId]) return
|
|
825
869
|
|
|
826
870
|
const conv = state.conversations[clientId]
|
|
827
871
|
|
|
872
|
+
// Always create a chunk for the tool result
|
|
873
|
+
const chunk: Chunk = {
|
|
874
|
+
id: `chunk-tool-result-${toolCallId}-${Date.now()}`,
|
|
875
|
+
type: 'tool_result',
|
|
876
|
+
toolCallId,
|
|
877
|
+
toolName,
|
|
878
|
+
result: output,
|
|
879
|
+
timestamp,
|
|
880
|
+
chunkCount: 1,
|
|
881
|
+
isClientTool: true,
|
|
882
|
+
}
|
|
883
|
+
|
|
828
884
|
// Find the message with the tool call and update it
|
|
829
885
|
for (
|
|
830
886
|
let messageIndex = conv.messages.length - 1;
|
|
@@ -844,28 +900,25 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
844
900
|
state: resultState === 'output-error' ? 'error' : 'complete',
|
|
845
901
|
})
|
|
846
902
|
|
|
847
|
-
//
|
|
848
|
-
|
|
849
|
-
id: `chunk-tool-result-${toolCallId}-${Date.now()}`,
|
|
850
|
-
type: 'tool_result',
|
|
851
|
-
messageId: message.id,
|
|
852
|
-
toolCallId,
|
|
853
|
-
toolName,
|
|
854
|
-
result: output,
|
|
855
|
-
timestamp,
|
|
856
|
-
chunkCount: 1,
|
|
857
|
-
}
|
|
903
|
+
// Add chunk with message ID
|
|
904
|
+
chunk.messageId = message.id
|
|
858
905
|
addChunkToMessage(clientId, chunk)
|
|
859
906
|
return
|
|
860
907
|
}
|
|
861
908
|
}
|
|
909
|
+
|
|
910
|
+
// If we couldn't find the message with toolCalls, still add the chunk
|
|
911
|
+
// This handles cases where the message hasn't been processed yet
|
|
912
|
+
// or the tool call is a client-only tool
|
|
913
|
+
addChunkToMessage(clientId, chunk)
|
|
862
914
|
}),
|
|
863
915
|
)
|
|
864
916
|
|
|
865
917
|
cleanupFns.push(
|
|
866
|
-
aiEventClient.on('
|
|
918
|
+
aiEventClient.on('tools:approval:responded', (e) => {
|
|
867
919
|
const { clientId, toolCallId, approved } = e.payload
|
|
868
920
|
|
|
921
|
+
if (!clientId) return
|
|
869
922
|
if (!state.conversations[clientId]) return
|
|
870
923
|
|
|
871
924
|
const conv = state.conversations[clientId]
|
|
@@ -892,52 +945,82 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
892
945
|
}),
|
|
893
946
|
)
|
|
894
947
|
|
|
895
|
-
// ============= Stream Events =============
|
|
896
|
-
|
|
897
948
|
cleanupFns.push(
|
|
898
|
-
aiEventClient.on('
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
949
|
+
aiEventClient.on('tools:call:updated', (e) => {
|
|
950
|
+
const {
|
|
951
|
+
clientId,
|
|
952
|
+
streamId,
|
|
953
|
+
messageId,
|
|
954
|
+
toolCallId,
|
|
955
|
+
toolName,
|
|
956
|
+
state: toolCallState,
|
|
957
|
+
arguments: args,
|
|
958
|
+
} = e.payload
|
|
902
959
|
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
content: e.payload.content,
|
|
908
|
-
delta: e.payload.delta,
|
|
909
|
-
timestamp: e.payload.timestamp,
|
|
910
|
-
chunkCount: 1,
|
|
911
|
-
}
|
|
960
|
+
const conversationId =
|
|
961
|
+
clientId ||
|
|
962
|
+
(streamId ? streamToConversation.get(streamId) : undefined)
|
|
963
|
+
if (!conversationId || !state.conversations[conversationId]) return
|
|
912
964
|
|
|
913
965
|
const conv = state.conversations[conversationId]
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
966
|
+
const messageIndex = conv.messages.findIndex(
|
|
967
|
+
(m: Message) => m.id === messageId,
|
|
968
|
+
)
|
|
969
|
+
if (messageIndex === -1) return
|
|
970
|
+
|
|
971
|
+
const message = conv.messages[messageIndex]
|
|
972
|
+
if (!message) return
|
|
973
|
+
|
|
974
|
+
const toolCalls = message.toolCalls || []
|
|
975
|
+
const existingToolIndex = toolCalls.findIndex(
|
|
976
|
+
(t: ToolCall) => t.id === toolCallId,
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
const toolCall: ToolCall = {
|
|
980
|
+
id: toolCallId,
|
|
981
|
+
name: toolName,
|
|
982
|
+
arguments: args,
|
|
983
|
+
state: toolCallState,
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (existingToolIndex >= 0) {
|
|
987
|
+
updateToolCall(
|
|
918
988
|
conversationId,
|
|
919
|
-
|
|
920
|
-
|
|
989
|
+
messageIndex,
|
|
990
|
+
existingToolIndex,
|
|
991
|
+
toolCall,
|
|
921
992
|
)
|
|
922
|
-
|
|
993
|
+
} else {
|
|
994
|
+
setToolCalls(conversationId, messageIndex, [...toolCalls, toolCall])
|
|
923
995
|
}
|
|
924
996
|
}),
|
|
925
997
|
)
|
|
926
998
|
|
|
927
999
|
cleanupFns.push(
|
|
928
|
-
aiEventClient.on('
|
|
929
|
-
const
|
|
930
|
-
|
|
1000
|
+
aiEventClient.on('tools:input:available', (e) => {
|
|
1001
|
+
const {
|
|
1002
|
+
streamId,
|
|
1003
|
+
messageId,
|
|
1004
|
+
toolCallId,
|
|
1005
|
+
toolName,
|
|
1006
|
+
input,
|
|
1007
|
+
timestamp,
|
|
1008
|
+
clientId,
|
|
1009
|
+
} = e.payload
|
|
1010
|
+
|
|
1011
|
+
const conversationId =
|
|
1012
|
+
clientId ||
|
|
1013
|
+
(streamId ? streamToConversation.get(streamId) : undefined)
|
|
931
1014
|
if (!conversationId) return
|
|
932
1015
|
|
|
933
1016
|
const chunk: Chunk = {
|
|
934
1017
|
id: `chunk-${Date.now()}-${Math.random()}`,
|
|
935
1018
|
type: 'tool_call',
|
|
936
|
-
messageId:
|
|
937
|
-
toolCallId
|
|
938
|
-
toolName
|
|
939
|
-
arguments:
|
|
940
|
-
timestamp
|
|
1019
|
+
messageId: messageId,
|
|
1020
|
+
toolCallId,
|
|
1021
|
+
toolName,
|
|
1022
|
+
arguments: JSON.stringify(input),
|
|
1023
|
+
timestamp,
|
|
941
1024
|
chunkCount: 1,
|
|
942
1025
|
}
|
|
943
1026
|
|
|
@@ -945,36 +1028,209 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
945
1028
|
if (conv?.type === 'client') {
|
|
946
1029
|
addChunkToMessage(conversationId, chunk)
|
|
947
1030
|
} else {
|
|
948
|
-
ensureMessageForChunk(
|
|
949
|
-
conversationId,
|
|
950
|
-
e.payload.messageId,
|
|
951
|
-
e.payload.timestamp,
|
|
952
|
-
)
|
|
953
1031
|
addChunk(conversationId, chunk)
|
|
954
1032
|
}
|
|
955
1033
|
}),
|
|
956
1034
|
)
|
|
957
1035
|
|
|
958
1036
|
cleanupFns.push(
|
|
959
|
-
aiEventClient.on('
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
1037
|
+
aiEventClient.on('tools:call:completed', (e) => {
|
|
1038
|
+
const {
|
|
1039
|
+
streamId,
|
|
1040
|
+
toolCallId,
|
|
1041
|
+
toolName,
|
|
1042
|
+
result,
|
|
1043
|
+
duration,
|
|
1044
|
+
messageId,
|
|
1045
|
+
timestamp,
|
|
1046
|
+
clientId,
|
|
1047
|
+
} = e.payload
|
|
963
1048
|
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
toolCallId: e.payload.toolCallId,
|
|
969
|
-
content: e.payload.result,
|
|
970
|
-
timestamp: e.payload.timestamp,
|
|
971
|
-
chunkCount: 1,
|
|
972
|
-
}
|
|
1049
|
+
const conversationId =
|
|
1050
|
+
clientId ||
|
|
1051
|
+
(streamId ? streamToConversation.get(streamId) : undefined)
|
|
1052
|
+
if (!conversationId || !state.conversations[conversationId]) return
|
|
973
1053
|
|
|
974
1054
|
const conv = state.conversations[conversationId]
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1055
|
+
|
|
1056
|
+
const chunk: Chunk = {
|
|
1057
|
+
id: `chunk-tool-result-${toolCallId}-${Date.now()}`,
|
|
1058
|
+
type: 'tool_result',
|
|
1059
|
+
messageId: messageId,
|
|
1060
|
+
toolCallId,
|
|
1061
|
+
toolName,
|
|
1062
|
+
result,
|
|
1063
|
+
duration,
|
|
1064
|
+
timestamp,
|
|
1065
|
+
chunkCount: 1,
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
if (conv.type === 'client' && messageId) {
|
|
1069
|
+
const messageIndex = conv.messages.findIndex(
|
|
1070
|
+
(m) => m.id === messageId,
|
|
1071
|
+
)
|
|
1072
|
+
if (messageIndex !== -1) {
|
|
1073
|
+
queueMessageChunk(conversationId, messageIndex, chunk)
|
|
1074
|
+
} else {
|
|
1075
|
+
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
1076
|
+
if (conv.messages[i]?.role === 'assistant') {
|
|
1077
|
+
queueMessageChunk(conversationId, i, chunk)
|
|
1078
|
+
break
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
} else {
|
|
1083
|
+
addChunk(conversationId, chunk)
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
1087
|
+
const message = conv.messages[i]
|
|
1088
|
+
if (!message?.toolCalls) continue
|
|
1089
|
+
|
|
1090
|
+
const toolCallIndex = message.toolCalls.findIndex(
|
|
1091
|
+
(t) => t.id === toolCallId,
|
|
1092
|
+
)
|
|
1093
|
+
if (toolCallIndex >= 0) {
|
|
1094
|
+
updateToolCall(conversationId, i, toolCallIndex, {
|
|
1095
|
+
duration,
|
|
1096
|
+
result,
|
|
1097
|
+
})
|
|
1098
|
+
return
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}),
|
|
1102
|
+
)
|
|
1103
|
+
|
|
1104
|
+
// ============= Stream Events =============
|
|
1105
|
+
|
|
1106
|
+
cleanupFns.push(
|
|
1107
|
+
aiEventClient.on('text:chunk:content', (e) => {
|
|
1108
|
+
const streamId = e.payload.streamId
|
|
1109
|
+
const conversationId = streamToConversation.get(streamId)
|
|
1110
|
+
if (!conversationId) return
|
|
1111
|
+
|
|
1112
|
+
const chunk: Chunk = {
|
|
1113
|
+
id: `chunk-${Date.now()}-${Math.random()}`,
|
|
1114
|
+
type: 'content',
|
|
1115
|
+
messageId: e.payload.messageId,
|
|
1116
|
+
content: e.payload.content,
|
|
1117
|
+
delta: e.payload.delta,
|
|
1118
|
+
timestamp: e.payload.timestamp,
|
|
1119
|
+
chunkCount: 1,
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
const conv = state.conversations[conversationId]
|
|
1123
|
+
if (conv?.type === 'client') {
|
|
1124
|
+
addChunkToMessage(conversationId, chunk)
|
|
1125
|
+
} else {
|
|
1126
|
+
ensureMessageForChunk(
|
|
1127
|
+
conversationId,
|
|
1128
|
+
e.payload.messageId,
|
|
1129
|
+
e.payload.timestamp,
|
|
1130
|
+
)
|
|
1131
|
+
addChunk(conversationId, chunk)
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (e.payload.messageId) {
|
|
1135
|
+
const messageIndex = conv?.messages.findIndex(
|
|
1136
|
+
(msg) => msg.id === e.payload.messageId,
|
|
1137
|
+
)
|
|
1138
|
+
if (messageIndex !== undefined && messageIndex >= 0) {
|
|
1139
|
+
updateMessage(conversationId, messageIndex, {
|
|
1140
|
+
content: e.payload.content,
|
|
1141
|
+
})
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}),
|
|
1145
|
+
)
|
|
1146
|
+
|
|
1147
|
+
cleanupFns.push(
|
|
1148
|
+
aiEventClient.on('text:chunk:tool-call', (e) => {
|
|
1149
|
+
const streamId = e.payload.streamId
|
|
1150
|
+
const conversationId = streamToConversation.get(streamId)
|
|
1151
|
+
if (!conversationId) return
|
|
1152
|
+
|
|
1153
|
+
const chunk: Chunk = {
|
|
1154
|
+
id: `chunk-${Date.now()}-${Math.random()}`,
|
|
1155
|
+
type: 'tool_call',
|
|
1156
|
+
messageId: e.payload.messageId,
|
|
1157
|
+
toolCallId: e.payload.toolCallId,
|
|
1158
|
+
toolName: e.payload.toolName,
|
|
1159
|
+
arguments: e.payload.arguments,
|
|
1160
|
+
timestamp: e.payload.timestamp,
|
|
1161
|
+
chunkCount: 1,
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
const conv = state.conversations[conversationId]
|
|
1165
|
+
if (conv?.type === 'client') {
|
|
1166
|
+
addChunkToMessage(conversationId, chunk)
|
|
1167
|
+
} else {
|
|
1168
|
+
ensureMessageForChunk(
|
|
1169
|
+
conversationId,
|
|
1170
|
+
e.payload.messageId,
|
|
1171
|
+
e.payload.timestamp,
|
|
1172
|
+
)
|
|
1173
|
+
addChunk(conversationId, chunk)
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
if (e.payload.messageId) {
|
|
1177
|
+
const messageIndex = conv?.messages.findIndex(
|
|
1178
|
+
(msg) => msg.id === e.payload.messageId,
|
|
1179
|
+
)
|
|
1180
|
+
if (messageIndex !== undefined && messageIndex >= 0) {
|
|
1181
|
+
const message = conv?.messages[messageIndex]
|
|
1182
|
+
if (!message) return
|
|
1183
|
+
|
|
1184
|
+
const toolCalls = message.toolCalls || []
|
|
1185
|
+
const existingToolIndex = toolCalls.findIndex(
|
|
1186
|
+
(t: ToolCall) => t.id === e.payload.toolCallId,
|
|
1187
|
+
)
|
|
1188
|
+
|
|
1189
|
+
const toolCall: ToolCall = {
|
|
1190
|
+
id: e.payload.toolCallId,
|
|
1191
|
+
name: e.payload.toolName,
|
|
1192
|
+
arguments: e.payload.arguments,
|
|
1193
|
+
state: 'input-streaming',
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
if (existingToolIndex >= 0) {
|
|
1197
|
+
updateToolCall(
|
|
1198
|
+
conversationId,
|
|
1199
|
+
messageIndex,
|
|
1200
|
+
existingToolIndex,
|
|
1201
|
+
toolCall,
|
|
1202
|
+
)
|
|
1203
|
+
} else {
|
|
1204
|
+
setToolCalls(conversationId, messageIndex, [
|
|
1205
|
+
...toolCalls,
|
|
1206
|
+
toolCall,
|
|
1207
|
+
])
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}),
|
|
1212
|
+
)
|
|
1213
|
+
|
|
1214
|
+
cleanupFns.push(
|
|
1215
|
+
aiEventClient.on('text:chunk:tool-result', (e) => {
|
|
1216
|
+
const streamId = e.payload.streamId
|
|
1217
|
+
const conversationId = streamToConversation.get(streamId)
|
|
1218
|
+
if (!conversationId) return
|
|
1219
|
+
|
|
1220
|
+
const chunk: Chunk = {
|
|
1221
|
+
id: `chunk-${Date.now()}-${Math.random()}`,
|
|
1222
|
+
type: 'tool_result',
|
|
1223
|
+
messageId: e.payload.messageId,
|
|
1224
|
+
toolCallId: e.payload.toolCallId,
|
|
1225
|
+
result: e.payload.result,
|
|
1226
|
+
timestamp: e.payload.timestamp,
|
|
1227
|
+
chunkCount: 1,
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
const conv = state.conversations[conversationId]
|
|
1231
|
+
if (conv?.type === 'client') {
|
|
1232
|
+
addChunkToMessage(conversationId, chunk)
|
|
1233
|
+
} else {
|
|
978
1234
|
ensureMessageForChunk(
|
|
979
1235
|
conversationId,
|
|
980
1236
|
e.payload.messageId,
|
|
@@ -1005,7 +1261,7 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
1005
1261
|
)
|
|
1006
1262
|
|
|
1007
1263
|
cleanupFns.push(
|
|
1008
|
-
aiEventClient.on('
|
|
1264
|
+
aiEventClient.on('text:chunk:thinking', (e) => {
|
|
1009
1265
|
const streamId = e.payload.streamId
|
|
1010
1266
|
const conversationId = streamToConversation.get(streamId)
|
|
1011
1267
|
if (!conversationId) return
|
|
@@ -1046,7 +1302,7 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
1046
1302
|
)
|
|
1047
1303
|
|
|
1048
1304
|
cleanupFns.push(
|
|
1049
|
-
aiEventClient.on('
|
|
1305
|
+
aiEventClient.on('text:chunk:done', (e) => {
|
|
1050
1306
|
const streamId = e.payload.streamId
|
|
1051
1307
|
const conversationId = streamToConversation.get(streamId)
|
|
1052
1308
|
if (!conversationId) return
|
|
@@ -1089,7 +1345,7 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
1089
1345
|
)
|
|
1090
1346
|
|
|
1091
1347
|
cleanupFns.push(
|
|
1092
|
-
aiEventClient.on('
|
|
1348
|
+
aiEventClient.on('text:chunk:error', (e) => {
|
|
1093
1349
|
const streamId = e.payload.streamId
|
|
1094
1350
|
const conversationId = streamToConversation.get(streamId)
|
|
1095
1351
|
if (!conversationId) return
|
|
@@ -1123,20 +1379,7 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
1123
1379
|
)
|
|
1124
1380
|
|
|
1125
1381
|
cleanupFns.push(
|
|
1126
|
-
aiEventClient.on('
|
|
1127
|
-
const streamId = e.payload.streamId
|
|
1128
|
-
const conversationId = streamToConversation.get(streamId)
|
|
1129
|
-
if (!conversationId) return
|
|
1130
|
-
|
|
1131
|
-
updateConversation(conversationId, {
|
|
1132
|
-
status: 'completed',
|
|
1133
|
-
completedAt: e.payload.timestamp,
|
|
1134
|
-
})
|
|
1135
|
-
}),
|
|
1136
|
-
)
|
|
1137
|
-
|
|
1138
|
-
cleanupFns.push(
|
|
1139
|
-
aiEventClient.on('stream:approval-requested', (e) => {
|
|
1382
|
+
aiEventClient.on('tools:approval:requested', (e) => {
|
|
1140
1383
|
const {
|
|
1141
1384
|
streamId,
|
|
1142
1385
|
messageId,
|
|
@@ -1145,9 +1388,11 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
1145
1388
|
input,
|
|
1146
1389
|
approvalId,
|
|
1147
1390
|
timestamp,
|
|
1391
|
+
clientId,
|
|
1148
1392
|
} = e.payload
|
|
1149
1393
|
|
|
1150
|
-
const conversationId =
|
|
1394
|
+
const conversationId =
|
|
1395
|
+
clientId || streamToConversation.get(streamId) || ''
|
|
1151
1396
|
if (!conversationId) return
|
|
1152
1397
|
|
|
1153
1398
|
const conv = state.conversations[conversationId]
|
|
@@ -1192,112 +1437,10 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
1192
1437
|
}),
|
|
1193
1438
|
)
|
|
1194
1439
|
|
|
1195
|
-
// ============= Processor Events =============
|
|
1196
|
-
|
|
1197
|
-
cleanupFns.push(
|
|
1198
|
-
aiEventClient.on('processor:text-updated', (e) => {
|
|
1199
|
-
const streamId = e.payload.streamId
|
|
1200
|
-
|
|
1201
|
-
let conversationId = streamToConversation.get(streamId)
|
|
1202
|
-
|
|
1203
|
-
if (!conversationId) {
|
|
1204
|
-
const activeClients = Object.values(state.conversations)
|
|
1205
|
-
.filter((c) => c.type === 'client' && c.status === 'active')
|
|
1206
|
-
.sort((a, b) => b.startedAt - a.startedAt)
|
|
1207
|
-
|
|
1208
|
-
if (activeClients.length > 0 && activeClients[0]) {
|
|
1209
|
-
conversationId = activeClients[0].id
|
|
1210
|
-
streamToConversation.set(streamId, conversationId)
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
if (!conversationId) return
|
|
1215
|
-
|
|
1216
|
-
const conv = state.conversations[conversationId]
|
|
1217
|
-
if (!conv) return
|
|
1218
|
-
|
|
1219
|
-
// Only update existing assistant messages, don't create new ones
|
|
1220
|
-
// (client:message-appended is responsible for creating messages)
|
|
1221
|
-
const lastMessage = conv.messages[conv.messages.length - 1]
|
|
1222
|
-
if (lastMessage && lastMessage.role === 'assistant') {
|
|
1223
|
-
updateMessage(conversationId, conv.messages.length - 1, {
|
|
1224
|
-
content: e.payload.content,
|
|
1225
|
-
})
|
|
1226
|
-
}
|
|
1227
|
-
}),
|
|
1228
|
-
)
|
|
1229
|
-
|
|
1230
|
-
cleanupFns.push(
|
|
1231
|
-
aiEventClient.on('processor:tool-call-state-changed', (e) => {
|
|
1232
|
-
const streamId = e.payload.streamId
|
|
1233
|
-
const conversationId = streamToConversation.get(streamId)
|
|
1234
|
-
|
|
1235
|
-
if (!conversationId || !state.conversations[conversationId]) return
|
|
1236
|
-
|
|
1237
|
-
const conv = state.conversations[conversationId]
|
|
1238
|
-
const lastMessage = conv.messages[conv.messages.length - 1]
|
|
1239
|
-
|
|
1240
|
-
if (lastMessage && lastMessage.role === 'assistant') {
|
|
1241
|
-
const toolCalls = lastMessage.toolCalls || []
|
|
1242
|
-
const existingToolIndex = toolCalls.findIndex(
|
|
1243
|
-
(t) => t.id === e.payload.toolCallId,
|
|
1244
|
-
)
|
|
1245
|
-
|
|
1246
|
-
const toolCall: ToolCall = {
|
|
1247
|
-
id: e.payload.toolCallId,
|
|
1248
|
-
name: e.payload.toolName,
|
|
1249
|
-
arguments: JSON.stringify(e.payload.arguments, null, 2),
|
|
1250
|
-
state: e.payload.state,
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
if (existingToolIndex >= 0) {
|
|
1254
|
-
updateToolCall(
|
|
1255
|
-
conversationId,
|
|
1256
|
-
conv.messages.length - 1,
|
|
1257
|
-
existingToolIndex,
|
|
1258
|
-
toolCall,
|
|
1259
|
-
)
|
|
1260
|
-
} else {
|
|
1261
|
-
setToolCalls(conversationId, conv.messages.length - 1, [
|
|
1262
|
-
...toolCalls,
|
|
1263
|
-
toolCall,
|
|
1264
|
-
])
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
}),
|
|
1268
|
-
)
|
|
1269
|
-
|
|
1270
|
-
cleanupFns.push(
|
|
1271
|
-
aiEventClient.on('processor:tool-result-state-changed', (e) => {
|
|
1272
|
-
const streamId = e.payload.streamId
|
|
1273
|
-
const conversationId = streamToConversation.get(streamId)
|
|
1274
|
-
|
|
1275
|
-
if (!conversationId || !state.conversations[conversationId]) return
|
|
1276
|
-
|
|
1277
|
-
const conv = state.conversations[conversationId]
|
|
1278
|
-
|
|
1279
|
-
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
1280
|
-
const message = conv.messages[i]
|
|
1281
|
-
if (!message?.toolCalls) continue
|
|
1282
|
-
|
|
1283
|
-
const toolCallIndex = message.toolCalls.findIndex(
|
|
1284
|
-
(t) => t.id === e.payload.toolCallId,
|
|
1285
|
-
)
|
|
1286
|
-
if (toolCallIndex >= 0) {
|
|
1287
|
-
updateToolCall(conversationId, i, toolCallIndex, {
|
|
1288
|
-
result: e.payload.content,
|
|
1289
|
-
state: e.payload.error ? 'error' : e.payload.state,
|
|
1290
|
-
})
|
|
1291
|
-
return
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
}),
|
|
1295
|
-
)
|
|
1296
|
-
|
|
1297
1440
|
// ============= Chat Events (for usage tracking) =============
|
|
1298
1441
|
|
|
1299
1442
|
cleanupFns.push(
|
|
1300
|
-
aiEventClient.on('text:started', (e) => {
|
|
1443
|
+
aiEventClient.on('text:request:started', (e) => {
|
|
1301
1444
|
const streamId = e.payload.streamId
|
|
1302
1445
|
const model = e.payload.model
|
|
1303
1446
|
const provider = e.payload.provider
|
|
@@ -1305,7 +1448,13 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
1305
1448
|
|
|
1306
1449
|
if (clientId && state.conversations[clientId]) {
|
|
1307
1450
|
streamToConversation.set(streamId, clientId)
|
|
1308
|
-
|
|
1451
|
+
requestToConversation.set(e.payload.requestId, clientId)
|
|
1452
|
+
updateConversation(clientId, {
|
|
1453
|
+
status: 'active',
|
|
1454
|
+
...e.payload,
|
|
1455
|
+
systemPrompts: e.payload.systemPrompts,
|
|
1456
|
+
hasChat: true,
|
|
1457
|
+
})
|
|
1309
1458
|
return
|
|
1310
1459
|
}
|
|
1311
1460
|
|
|
@@ -1315,7 +1464,12 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
1315
1464
|
|
|
1316
1465
|
if (activeClient) {
|
|
1317
1466
|
streamToConversation.set(streamId, activeClient.id)
|
|
1318
|
-
|
|
1467
|
+
requestToConversation.set(e.payload.requestId, activeClient.id)
|
|
1468
|
+
updateConversation(activeClient.id, {
|
|
1469
|
+
...e.payload,
|
|
1470
|
+
systemPrompts: e.payload.systemPrompts,
|
|
1471
|
+
hasChat: true,
|
|
1472
|
+
})
|
|
1319
1473
|
} else {
|
|
1320
1474
|
const existingServerConv = Object.values(state.conversations).find(
|
|
1321
1475
|
(c) => c.type === 'server' && c.model === model,
|
|
@@ -1323,22 +1477,33 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
1323
1477
|
|
|
1324
1478
|
if (existingServerConv) {
|
|
1325
1479
|
streamToConversation.set(streamId, existingServerConv.id)
|
|
1480
|
+
requestToConversation.set(
|
|
1481
|
+
e.payload.requestId,
|
|
1482
|
+
existingServerConv.id,
|
|
1483
|
+
)
|
|
1326
1484
|
updateConversation(existingServerConv.id, {
|
|
1327
1485
|
status: 'active',
|
|
1328
1486
|
...e.payload,
|
|
1487
|
+
systemPrompts: e.payload.systemPrompts,
|
|
1488
|
+
hasChat: true,
|
|
1329
1489
|
})
|
|
1330
1490
|
} else {
|
|
1331
1491
|
const serverId = `server-${model}`
|
|
1332
1492
|
getOrCreateConversation(serverId, 'server', `${model} Server`)
|
|
1333
1493
|
streamToConversation.set(streamId, serverId)
|
|
1334
|
-
|
|
1494
|
+
requestToConversation.set(e.payload.requestId, serverId)
|
|
1495
|
+
updateConversation(serverId, {
|
|
1496
|
+
...e.payload,
|
|
1497
|
+
systemPrompts: e.payload.systemPrompts,
|
|
1498
|
+
hasChat: true,
|
|
1499
|
+
})
|
|
1335
1500
|
}
|
|
1336
1501
|
}
|
|
1337
1502
|
}),
|
|
1338
1503
|
)
|
|
1339
1504
|
|
|
1340
1505
|
cleanupFns.push(
|
|
1341
|
-
aiEventClient.on('text:completed', (e) => {
|
|
1506
|
+
aiEventClient.on('text:request:completed', (e) => {
|
|
1342
1507
|
const { requestId, usage } = e.payload
|
|
1343
1508
|
|
|
1344
1509
|
const conversationId = requestToConversation.get(requestId)
|
|
@@ -1359,20 +1524,7 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
1359
1524
|
)
|
|
1360
1525
|
|
|
1361
1526
|
cleanupFns.push(
|
|
1362
|
-
aiEventClient.on('text:
|
|
1363
|
-
const { requestId, iterationNumber } = e.payload
|
|
1364
|
-
|
|
1365
|
-
const conversationId = requestToConversation.get(requestId)
|
|
1366
|
-
if (conversationId && state.conversations[conversationId]) {
|
|
1367
|
-
updateConversation(conversationId, {
|
|
1368
|
-
iterationCount: iterationNumber,
|
|
1369
|
-
})
|
|
1370
|
-
}
|
|
1371
|
-
}),
|
|
1372
|
-
)
|
|
1373
|
-
|
|
1374
|
-
cleanupFns.push(
|
|
1375
|
-
aiEventClient.on('usage:tokens', (e) => {
|
|
1527
|
+
aiEventClient.on('text:usage', (e) => {
|
|
1376
1528
|
const { requestId, usage, messageId } = e.payload
|
|
1377
1529
|
|
|
1378
1530
|
const conversationId = requestToConversation.get(requestId)
|
|
@@ -1383,81 +1535,10 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
1383
1535
|
}),
|
|
1384
1536
|
)
|
|
1385
1537
|
|
|
1386
|
-
// ============= Tool Call Completed (with duration) =============
|
|
1387
|
-
|
|
1388
|
-
cleanupFns.push(
|
|
1389
|
-
aiEventClient.on('tool:call-completed', (e) => {
|
|
1390
|
-
const {
|
|
1391
|
-
streamId,
|
|
1392
|
-
toolCallId,
|
|
1393
|
-
toolName,
|
|
1394
|
-
result,
|
|
1395
|
-
duration,
|
|
1396
|
-
messageId,
|
|
1397
|
-
timestamp,
|
|
1398
|
-
} = e.payload
|
|
1399
|
-
|
|
1400
|
-
const conversationId = streamToConversation.get(streamId)
|
|
1401
|
-
if (!conversationId || !state.conversations[conversationId]) return
|
|
1402
|
-
|
|
1403
|
-
const conv = state.conversations[conversationId]
|
|
1404
|
-
|
|
1405
|
-
// Add a tool_result chunk to show the result in the chunks view
|
|
1406
|
-
const chunk: Chunk = {
|
|
1407
|
-
id: `chunk-tool-result-${toolCallId}-${Date.now()}`,
|
|
1408
|
-
type: 'tool_result',
|
|
1409
|
-
messageId: messageId,
|
|
1410
|
-
toolCallId,
|
|
1411
|
-
toolName,
|
|
1412
|
-
result,
|
|
1413
|
-
duration,
|
|
1414
|
-
timestamp,
|
|
1415
|
-
chunkCount: 1,
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
// Add chunk to message if it's a client conversation, otherwise to conversation
|
|
1419
|
-
if (conv.type === 'client' && messageId) {
|
|
1420
|
-
const messageIndex = conv.messages.findIndex(
|
|
1421
|
-
(m) => m.id === messageId,
|
|
1422
|
-
)
|
|
1423
|
-
if (messageIndex !== -1) {
|
|
1424
|
-
queueMessageChunk(conversationId, messageIndex, chunk)
|
|
1425
|
-
} else {
|
|
1426
|
-
// If message not found, add to last assistant message
|
|
1427
|
-
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
1428
|
-
if (conv.messages[i]?.role === 'assistant') {
|
|
1429
|
-
queueMessageChunk(conversationId, i, chunk)
|
|
1430
|
-
break
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
} else {
|
|
1435
|
-
addChunk(conversationId, chunk)
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
// Update the tool call with duration
|
|
1439
|
-
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
1440
|
-
const message = conv.messages[i]
|
|
1441
|
-
if (!message?.toolCalls) continue
|
|
1442
|
-
|
|
1443
|
-
const toolCallIndex = message.toolCalls.findIndex(
|
|
1444
|
-
(t) => t.id === toolCallId,
|
|
1445
|
-
)
|
|
1446
|
-
if (toolCallIndex >= 0) {
|
|
1447
|
-
updateToolCall(conversationId, i, toolCallIndex, {
|
|
1448
|
-
duration,
|
|
1449
|
-
result,
|
|
1450
|
-
})
|
|
1451
|
-
return
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
}),
|
|
1455
|
-
)
|
|
1456
|
-
|
|
1457
1538
|
// ============= Summarize Events =============
|
|
1458
1539
|
|
|
1459
1540
|
cleanupFns.push(
|
|
1460
|
-
aiEventClient.on('summarize:started', (e) => {
|
|
1541
|
+
aiEventClient.on('summarize:request:started', (e) => {
|
|
1461
1542
|
const { requestId, model, inputLength, timestamp, clientId } = e.payload
|
|
1462
1543
|
|
|
1463
1544
|
// Try to find an active conversation to attach to, or create a new one
|
|
@@ -1505,7 +1586,7 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
1505
1586
|
)
|
|
1506
1587
|
|
|
1507
1588
|
cleanupFns.push(
|
|
1508
|
-
aiEventClient.on('summarize:completed', (e) => {
|
|
1589
|
+
aiEventClient.on('summarize:request:completed', (e) => {
|
|
1509
1590
|
const { requestId, outputLength, duration } = e.payload
|
|
1510
1591
|
|
|
1511
1592
|
const conversationId = requestToConversation.get(requestId)
|
|
@@ -1533,6 +1614,290 @@ export const AIProvider: ParentComponent = (props) => {
|
|
|
1533
1614
|
}),
|
|
1534
1615
|
)
|
|
1535
1616
|
|
|
1617
|
+
// ============= Image Events =============
|
|
1618
|
+
|
|
1619
|
+
cleanupFns.push(
|
|
1620
|
+
aiEventClient.on('image:request:started', (e) => {
|
|
1621
|
+
const { requestId, clientId, timestamp } = e.payload
|
|
1622
|
+
|
|
1623
|
+
let conversationId = clientId
|
|
1624
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1625
|
+
conversationId = `image-${requestId}`
|
|
1626
|
+
getOrCreateConversation(
|
|
1627
|
+
conversationId,
|
|
1628
|
+
'server',
|
|
1629
|
+
`Image (${requestId.substring(0, 8)})`,
|
|
1630
|
+
)
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
addActivityEvent(conversationId, 'imageEvents', {
|
|
1634
|
+
id: requestId,
|
|
1635
|
+
name: 'image:request:started',
|
|
1636
|
+
timestamp,
|
|
1637
|
+
payload: e.payload,
|
|
1638
|
+
})
|
|
1639
|
+
}),
|
|
1640
|
+
)
|
|
1641
|
+
|
|
1642
|
+
cleanupFns.push(
|
|
1643
|
+
aiEventClient.on('image:request:completed', (e) => {
|
|
1644
|
+
const { requestId, clientId, timestamp } = e.payload
|
|
1645
|
+
|
|
1646
|
+
let conversationId = clientId
|
|
1647
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1648
|
+
conversationId = `image-${requestId}`
|
|
1649
|
+
getOrCreateConversation(
|
|
1650
|
+
conversationId,
|
|
1651
|
+
'server',
|
|
1652
|
+
`Image (${requestId.substring(0, 8)})`,
|
|
1653
|
+
)
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
addActivityEvent(conversationId, 'imageEvents', {
|
|
1657
|
+
id: requestId,
|
|
1658
|
+
name: 'image:request:completed',
|
|
1659
|
+
timestamp,
|
|
1660
|
+
payload: e.payload,
|
|
1661
|
+
})
|
|
1662
|
+
}),
|
|
1663
|
+
)
|
|
1664
|
+
|
|
1665
|
+
cleanupFns.push(
|
|
1666
|
+
aiEventClient.on('image:usage', (e) => {
|
|
1667
|
+
const { requestId, clientId, timestamp } = e.payload
|
|
1668
|
+
|
|
1669
|
+
let conversationId = clientId
|
|
1670
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1671
|
+
conversationId = `image-${requestId}`
|
|
1672
|
+
getOrCreateConversation(
|
|
1673
|
+
conversationId,
|
|
1674
|
+
'server',
|
|
1675
|
+
`Image (${requestId.substring(0, 8)})`,
|
|
1676
|
+
)
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
addActivityEvent(conversationId, 'imageEvents', {
|
|
1680
|
+
id: requestId,
|
|
1681
|
+
name: 'image:usage',
|
|
1682
|
+
timestamp,
|
|
1683
|
+
payload: e.payload,
|
|
1684
|
+
})
|
|
1685
|
+
}),
|
|
1686
|
+
)
|
|
1687
|
+
|
|
1688
|
+
// ============= Speech Events =============
|
|
1689
|
+
|
|
1690
|
+
cleanupFns.push(
|
|
1691
|
+
aiEventClient.on('speech:request:started', (e) => {
|
|
1692
|
+
const { requestId, clientId, timestamp } = e.payload
|
|
1693
|
+
|
|
1694
|
+
let conversationId = clientId
|
|
1695
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1696
|
+
conversationId = `speech-${requestId}`
|
|
1697
|
+
getOrCreateConversation(
|
|
1698
|
+
conversationId,
|
|
1699
|
+
'server',
|
|
1700
|
+
`Speech (${requestId.substring(0, 8)})`,
|
|
1701
|
+
)
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
addActivityEvent(conversationId, 'speechEvents', {
|
|
1705
|
+
id: requestId,
|
|
1706
|
+
name: 'speech:request:started',
|
|
1707
|
+
timestamp,
|
|
1708
|
+
payload: e.payload,
|
|
1709
|
+
})
|
|
1710
|
+
}),
|
|
1711
|
+
)
|
|
1712
|
+
|
|
1713
|
+
cleanupFns.push(
|
|
1714
|
+
aiEventClient.on('speech:request:completed', (e) => {
|
|
1715
|
+
const { requestId, clientId, timestamp } = e.payload
|
|
1716
|
+
|
|
1717
|
+
let conversationId = clientId
|
|
1718
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1719
|
+
conversationId = `speech-${requestId}`
|
|
1720
|
+
getOrCreateConversation(
|
|
1721
|
+
conversationId,
|
|
1722
|
+
'server',
|
|
1723
|
+
`Speech (${requestId.substring(0, 8)})`,
|
|
1724
|
+
)
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
addActivityEvent(conversationId, 'speechEvents', {
|
|
1728
|
+
id: requestId,
|
|
1729
|
+
name: 'speech:request:completed',
|
|
1730
|
+
timestamp,
|
|
1731
|
+
payload: e.payload,
|
|
1732
|
+
})
|
|
1733
|
+
}),
|
|
1734
|
+
)
|
|
1735
|
+
|
|
1736
|
+
cleanupFns.push(
|
|
1737
|
+
aiEventClient.on('speech:usage', (e) => {
|
|
1738
|
+
const { requestId, clientId, timestamp } = e.payload
|
|
1739
|
+
|
|
1740
|
+
let conversationId = clientId
|
|
1741
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1742
|
+
conversationId = `speech-${requestId}`
|
|
1743
|
+
getOrCreateConversation(
|
|
1744
|
+
conversationId,
|
|
1745
|
+
'server',
|
|
1746
|
+
`Speech (${requestId.substring(0, 8)})`,
|
|
1747
|
+
)
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
addActivityEvent(conversationId, 'speechEvents', {
|
|
1751
|
+
id: requestId,
|
|
1752
|
+
name: 'speech:usage',
|
|
1753
|
+
timestamp,
|
|
1754
|
+
payload: e.payload,
|
|
1755
|
+
})
|
|
1756
|
+
}),
|
|
1757
|
+
)
|
|
1758
|
+
|
|
1759
|
+
// ============= Transcription Events =============
|
|
1760
|
+
|
|
1761
|
+
cleanupFns.push(
|
|
1762
|
+
aiEventClient.on('transcription:request:started', (e) => {
|
|
1763
|
+
const { requestId, clientId, timestamp } = e.payload
|
|
1764
|
+
|
|
1765
|
+
let conversationId = clientId
|
|
1766
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1767
|
+
conversationId = `transcription-${requestId}`
|
|
1768
|
+
getOrCreateConversation(
|
|
1769
|
+
conversationId,
|
|
1770
|
+
'server',
|
|
1771
|
+
`Transcription (${requestId.substring(0, 8)})`,
|
|
1772
|
+
)
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
addActivityEvent(conversationId, 'transcriptionEvents', {
|
|
1776
|
+
id: requestId,
|
|
1777
|
+
name: 'transcription:request:started',
|
|
1778
|
+
timestamp,
|
|
1779
|
+
payload: e.payload,
|
|
1780
|
+
})
|
|
1781
|
+
}),
|
|
1782
|
+
)
|
|
1783
|
+
|
|
1784
|
+
cleanupFns.push(
|
|
1785
|
+
aiEventClient.on('transcription:request:completed', (e) => {
|
|
1786
|
+
const { requestId, clientId, timestamp } = e.payload
|
|
1787
|
+
|
|
1788
|
+
let conversationId = clientId
|
|
1789
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1790
|
+
conversationId = `transcription-${requestId}`
|
|
1791
|
+
getOrCreateConversation(
|
|
1792
|
+
conversationId,
|
|
1793
|
+
'server',
|
|
1794
|
+
`Transcription (${requestId.substring(0, 8)})`,
|
|
1795
|
+
)
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
addActivityEvent(conversationId, 'transcriptionEvents', {
|
|
1799
|
+
id: requestId,
|
|
1800
|
+
name: 'transcription:request:completed',
|
|
1801
|
+
timestamp,
|
|
1802
|
+
payload: e.payload,
|
|
1803
|
+
})
|
|
1804
|
+
}),
|
|
1805
|
+
)
|
|
1806
|
+
|
|
1807
|
+
cleanupFns.push(
|
|
1808
|
+
aiEventClient.on('transcription:usage', (e) => {
|
|
1809
|
+
const { requestId, clientId, timestamp } = e.payload
|
|
1810
|
+
|
|
1811
|
+
let conversationId = clientId
|
|
1812
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1813
|
+
conversationId = `transcription-${requestId}`
|
|
1814
|
+
getOrCreateConversation(
|
|
1815
|
+
conversationId,
|
|
1816
|
+
'server',
|
|
1817
|
+
`Transcription (${requestId.substring(0, 8)})`,
|
|
1818
|
+
)
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
addActivityEvent(conversationId, 'transcriptionEvents', {
|
|
1822
|
+
id: requestId,
|
|
1823
|
+
name: 'transcription:usage',
|
|
1824
|
+
timestamp,
|
|
1825
|
+
payload: e.payload,
|
|
1826
|
+
})
|
|
1827
|
+
}),
|
|
1828
|
+
)
|
|
1829
|
+
|
|
1830
|
+
// ============= Video Events =============
|
|
1831
|
+
|
|
1832
|
+
cleanupFns.push(
|
|
1833
|
+
aiEventClient.on('video:request:started', (e) => {
|
|
1834
|
+
const { requestId, clientId, timestamp } = e.payload
|
|
1835
|
+
|
|
1836
|
+
let conversationId = clientId
|
|
1837
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1838
|
+
conversationId = `video-${requestId}`
|
|
1839
|
+
getOrCreateConversation(
|
|
1840
|
+
conversationId,
|
|
1841
|
+
'server',
|
|
1842
|
+
`Video (${requestId.substring(0, 8)})`,
|
|
1843
|
+
)
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
addActivityEvent(conversationId, 'videoEvents', {
|
|
1847
|
+
id: requestId,
|
|
1848
|
+
name: 'video:request:started',
|
|
1849
|
+
timestamp,
|
|
1850
|
+
payload: e.payload,
|
|
1851
|
+
})
|
|
1852
|
+
}),
|
|
1853
|
+
)
|
|
1854
|
+
|
|
1855
|
+
cleanupFns.push(
|
|
1856
|
+
aiEventClient.on('video:request:completed', (e) => {
|
|
1857
|
+
const { requestId, clientId, timestamp } = e.payload
|
|
1858
|
+
|
|
1859
|
+
let conversationId = clientId
|
|
1860
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1861
|
+
conversationId = `video-${requestId}`
|
|
1862
|
+
getOrCreateConversation(
|
|
1863
|
+
conversationId,
|
|
1864
|
+
'server',
|
|
1865
|
+
`Video (${requestId.substring(0, 8)})`,
|
|
1866
|
+
)
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
addActivityEvent(conversationId, 'videoEvents', {
|
|
1870
|
+
id: requestId,
|
|
1871
|
+
name: 'video:request:completed',
|
|
1872
|
+
timestamp,
|
|
1873
|
+
payload: e.payload,
|
|
1874
|
+
})
|
|
1875
|
+
}),
|
|
1876
|
+
)
|
|
1877
|
+
|
|
1878
|
+
cleanupFns.push(
|
|
1879
|
+
aiEventClient.on('video:usage', (e) => {
|
|
1880
|
+
const { requestId, clientId, timestamp } = e.payload
|
|
1881
|
+
|
|
1882
|
+
let conversationId = clientId
|
|
1883
|
+
if (!conversationId || !state.conversations[conversationId]) {
|
|
1884
|
+
conversationId = `video-${requestId}`
|
|
1885
|
+
getOrCreateConversation(
|
|
1886
|
+
conversationId,
|
|
1887
|
+
'server',
|
|
1888
|
+
`Video (${requestId.substring(0, 8)})`,
|
|
1889
|
+
)
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
addActivityEvent(conversationId, 'videoEvents', {
|
|
1893
|
+
id: requestId,
|
|
1894
|
+
name: 'video:usage',
|
|
1895
|
+
timestamp,
|
|
1896
|
+
payload: e.payload,
|
|
1897
|
+
})
|
|
1898
|
+
}),
|
|
1899
|
+
)
|
|
1900
|
+
|
|
1536
1901
|
// Cleanup all listeners on unmount
|
|
1537
1902
|
onCleanup(() => {
|
|
1538
1903
|
for (const cleanup of cleanupFns) {
|