@happyvertical/smrt-chat 0.30.0

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 (169) hide show
  1. package/AGENTS.md +35 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +163 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/chunks/ChatService-Dpzc1Pa5.js +2044 -0
  8. package/dist/chunks/ChatService-Dpzc1Pa5.js.map +1 -0
  9. package/dist/collections/AgentSessionCollection.d.ts +57 -0
  10. package/dist/collections/AgentSessionCollection.d.ts.map +1 -0
  11. package/dist/collections/ChatMessageCollection.d.ts +79 -0
  12. package/dist/collections/ChatMessageCollection.d.ts.map +1 -0
  13. package/dist/collections/ChatParticipantCollection.d.ts +26 -0
  14. package/dist/collections/ChatParticipantCollection.d.ts.map +1 -0
  15. package/dist/collections/ChatReactionCollection.d.ts +23 -0
  16. package/dist/collections/ChatReactionCollection.d.ts.map +1 -0
  17. package/dist/collections/ChatRoomCollection.d.ts +43 -0
  18. package/dist/collections/ChatRoomCollection.d.ts.map +1 -0
  19. package/dist/collections/ChatThreadCollection.d.ts +9 -0
  20. package/dist/collections/ChatThreadCollection.d.ts.map +1 -0
  21. package/dist/index.d.ts +5 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +18 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/internal/agent-runtime.d.ts +16 -0
  26. package/dist/internal/agent-runtime.d.ts.map +1 -0
  27. package/dist/internal/agent-runtime.js +5 -0
  28. package/dist/internal/agent-runtime.js.map +1 -0
  29. package/dist/manifest.json +2811 -0
  30. package/dist/models/AgentSession.d.ts +70 -0
  31. package/dist/models/AgentSession.d.ts.map +1 -0
  32. package/dist/models/ChatMessage.d.ts +55 -0
  33. package/dist/models/ChatMessage.d.ts.map +1 -0
  34. package/dist/models/ChatParticipant.d.ts +32 -0
  35. package/dist/models/ChatParticipant.d.ts.map +1 -0
  36. package/dist/models/ChatReaction.d.ts +19 -0
  37. package/dist/models/ChatReaction.d.ts.map +1 -0
  38. package/dist/models/ChatRoom.d.ts +44 -0
  39. package/dist/models/ChatRoom.d.ts.map +1 -0
  40. package/dist/models/ChatThread.d.ts +24 -0
  41. package/dist/models/ChatThread.d.ts.map +1 -0
  42. package/dist/models/index.d.ts +7 -0
  43. package/dist/models/index.d.ts.map +1 -0
  44. package/dist/playground.d.ts +2 -0
  45. package/dist/playground.d.ts.map +1 -0
  46. package/dist/playground.js +166 -0
  47. package/dist/playground.js.map +1 -0
  48. package/dist/services/ChatService.d.ts +390 -0
  49. package/dist/services/ChatService.d.ts.map +1 -0
  50. package/dist/services/index.d.ts +2 -0
  51. package/dist/services/index.d.ts.map +1 -0
  52. package/dist/smrt-knowledge.json +1507 -0
  53. package/dist/svelte/components/agent/AgentChat.svelte +542 -0
  54. package/dist/svelte/components/agent/AgentChat.svelte.d.ts +21 -0
  55. package/dist/svelte/components/agent/AgentChat.svelte.d.ts.map +1 -0
  56. package/dist/svelte/components/agent/AgentSelector.svelte +175 -0
  57. package/dist/svelte/components/agent/AgentSelector.svelte.d.ts +11 -0
  58. package/dist/svelte/components/agent/AgentSelector.svelte.d.ts.map +1 -0
  59. package/dist/svelte/components/agent/AgentSessionPanel.svelte +322 -0
  60. package/dist/svelte/components/agent/AgentSessionPanel.svelte.d.ts +15 -0
  61. package/dist/svelte/components/agent/AgentSessionPanel.svelte.d.ts.map +1 -0
  62. package/dist/svelte/components/agent/ToolCallDisplay.svelte +335 -0
  63. package/dist/svelte/components/agent/ToolCallDisplay.svelte.d.ts +9 -0
  64. package/dist/svelte/components/agent/ToolCallDisplay.svelte.d.ts.map +1 -0
  65. package/dist/svelte/components/agent/message-blocks.d.ts +12 -0
  66. package/dist/svelte/components/agent/message-blocks.d.ts.map +1 -0
  67. package/dist/svelte/components/agent/message-blocks.js +41 -0
  68. package/dist/svelte/components/agent/message-blocks.test.js +31 -0
  69. package/dist/svelte/components/dialogs/RoomCreateDialog.svelte +403 -0
  70. package/dist/svelte/components/dialogs/RoomCreateDialog.svelte.d.ts +16 -0
  71. package/dist/svelte/components/dialogs/RoomCreateDialog.svelte.d.ts.map +1 -0
  72. package/dist/svelte/components/dialogs/SearchMessages.svelte +457 -0
  73. package/dist/svelte/components/dialogs/SearchMessages.svelte.d.ts +17 -0
  74. package/dist/svelte/components/dialogs/SearchMessages.svelte.d.ts.map +1 -0
  75. package/dist/svelte/components/layout/ChatLayout.svelte +150 -0
  76. package/dist/svelte/components/layout/ChatLayout.svelte.d.ts +18 -0
  77. package/dist/svelte/components/layout/ChatLayout.svelte.d.ts.map +1 -0
  78. package/dist/svelte/components/layout/MemberList.svelte +389 -0
  79. package/dist/svelte/components/layout/MemberList.svelte.d.ts +11 -0
  80. package/dist/svelte/components/layout/MemberList.svelte.d.ts.map +1 -0
  81. package/dist/svelte/components/layout/RoomHeader.svelte +241 -0
  82. package/dist/svelte/components/layout/RoomHeader.svelte.d.ts +15 -0
  83. package/dist/svelte/components/layout/RoomHeader.svelte.d.ts.map +1 -0
  84. package/dist/svelte/components/layout/RoomList.svelte +471 -0
  85. package/dist/svelte/components/layout/RoomList.svelte.d.ts +15 -0
  86. package/dist/svelte/components/layout/RoomList.svelte.d.ts.map +1 -0
  87. package/dist/svelte/components/messages/MessageInput.svelte +232 -0
  88. package/dist/svelte/components/messages/MessageInput.svelte.d.ts +20 -0
  89. package/dist/svelte/components/messages/MessageInput.svelte.d.ts.map +1 -0
  90. package/dist/svelte/components/messages/MessageItem.svelte +431 -0
  91. package/dist/svelte/components/messages/MessageItem.svelte.d.ts +19 -0
  92. package/dist/svelte/components/messages/MessageItem.svelte.d.ts.map +1 -0
  93. package/dist/svelte/components/messages/MessageList.svelte +129 -0
  94. package/dist/svelte/components/messages/MessageList.svelte.d.ts +17 -0
  95. package/dist/svelte/components/messages/MessageList.svelte.d.ts.map +1 -0
  96. package/dist/svelte/components/messages/ThreadPanel.svelte +156 -0
  97. package/dist/svelte/components/messages/ThreadPanel.svelte.d.ts +17 -0
  98. package/dist/svelte/components/messages/ThreadPanel.svelte.d.ts.map +1 -0
  99. package/dist/svelte/components/messages/__tests__/MessageInput.test.js +38 -0
  100. package/dist/svelte/components/shared/Avatar.svelte +30 -0
  101. package/dist/svelte/components/shared/Avatar.svelte.d.ts +14 -0
  102. package/dist/svelte/components/shared/Avatar.svelte.d.ts.map +1 -0
  103. package/dist/svelte/components/shared/FileUpload.svelte +382 -0
  104. package/dist/svelte/components/shared/FileUpload.svelte.d.ts +14 -0
  105. package/dist/svelte/components/shared/FileUpload.svelte.d.ts.map +1 -0
  106. package/dist/svelte/components/shared/LinkPreview.svelte +108 -0
  107. package/dist/svelte/components/shared/LinkPreview.svelte.d.ts +18 -0
  108. package/dist/svelte/components/shared/LinkPreview.svelte.d.ts.map +1 -0
  109. package/dist/svelte/components/shared/MentionAutocomplete.svelte +168 -0
  110. package/dist/svelte/components/shared/MentionAutocomplete.svelte.d.ts +18 -0
  111. package/dist/svelte/components/shared/MentionAutocomplete.svelte.d.ts.map +1 -0
  112. package/dist/svelte/components/shared/MessageBubble.svelte +81 -0
  113. package/dist/svelte/components/shared/MessageBubble.svelte.d.ts +16 -0
  114. package/dist/svelte/components/shared/MessageBubble.svelte.d.ts.map +1 -0
  115. package/dist/svelte/components/shared/ReactionPicker.svelte +103 -0
  116. package/dist/svelte/components/shared/ReactionPicker.svelte.d.ts +10 -0
  117. package/dist/svelte/components/shared/ReactionPicker.svelte.d.ts.map +1 -0
  118. package/dist/svelte/components/shared/ReadReceipts.svelte +127 -0
  119. package/dist/svelte/components/shared/ReadReceipts.svelte.d.ts +13 -0
  120. package/dist/svelte/components/shared/ReadReceipts.svelte.d.ts.map +1 -0
  121. package/dist/svelte/components/shared/TypingIndicator.svelte +90 -0
  122. package/dist/svelte/components/shared/TypingIndicator.svelte.d.ts +12 -0
  123. package/dist/svelte/components/shared/TypingIndicator.svelte.d.ts.map +1 -0
  124. package/dist/svelte/components/shared/UserPresence.svelte +65 -0
  125. package/dist/svelte/components/shared/UserPresence.svelte.d.ts +13 -0
  126. package/dist/svelte/components/shared/UserPresence.svelte.d.ts.map +1 -0
  127. package/dist/svelte/components/shared/__tests__/Avatar.test.js +20 -0
  128. package/dist/svelte/components/shared/__tests__/LinkPreview.test.js +29 -0
  129. package/dist/svelte/components/shared/__tests__/MessageBubble.test.js +21 -0
  130. package/dist/svelte/components/shared/__tests__/ReactionPicker.test.js +35 -0
  131. package/dist/svelte/components/shared/__tests__/ReadReceipts.test.js +28 -0
  132. package/dist/svelte/components/shared/__tests__/TypingIndicator.test.js +27 -0
  133. package/dist/svelte/components/shared/__tests__/UserPresence.test.js +23 -0
  134. package/dist/svelte/components/tabs/ChatTab.svelte +240 -0
  135. package/dist/svelte/components/tabs/ChatTab.svelte.d.ts +21 -0
  136. package/dist/svelte/components/tabs/ChatTab.svelte.d.ts.map +1 -0
  137. package/dist/svelte/components/tabs/ChatTabList.svelte +158 -0
  138. package/dist/svelte/components/tabs/ChatTabList.svelte.d.ts +13 -0
  139. package/dist/svelte/components/tabs/ChatTabList.svelte.d.ts.map +1 -0
  140. package/dist/svelte/components/tabs/ChatTabs.svelte +88 -0
  141. package/dist/svelte/components/tabs/ChatTabs.svelte.d.ts +21 -0
  142. package/dist/svelte/components/tabs/ChatTabs.svelte.d.ts.map +1 -0
  143. package/dist/svelte/components/tabs/MiniChat.svelte +253 -0
  144. package/dist/svelte/components/tabs/MiniChat.svelte.d.ts +15 -0
  145. package/dist/svelte/components/tabs/MiniChat.svelte.d.ts.map +1 -0
  146. package/dist/svelte/i18n.d.ts +51 -0
  147. package/dist/svelte/i18n.d.ts.map +1 -0
  148. package/dist/svelte/i18n.js +72 -0
  149. package/dist/svelte/i18n.messages.d.ts +50 -0
  150. package/dist/svelte/i18n.messages.d.ts.map +1 -0
  151. package/dist/svelte/i18n.messages.js +69 -0
  152. package/dist/svelte/index.d.ts +48 -0
  153. package/dist/svelte/index.d.ts.map +1 -0
  154. package/dist/svelte/index.js +117 -0
  155. package/dist/svelte/playground.d.ts +171 -0
  156. package/dist/svelte/playground.d.ts.map +1 -0
  157. package/dist/svelte/playground.js +161 -0
  158. package/dist/svelte/types.d.ts +116 -0
  159. package/dist/svelte/types.d.ts.map +1 -0
  160. package/dist/svelte/types.js +1 -0
  161. package/dist/types.d.ts +99 -0
  162. package/dist/types.d.ts.map +1 -0
  163. package/dist/types.js +2 -0
  164. package/dist/types.js.map +1 -0
  165. package/dist/ui.d.ts +4 -0
  166. package/dist/ui.d.ts.map +1 -0
  167. package/dist/ui.js +92 -0
  168. package/dist/ui.js.map +1 -0
  169. package/package.json +95 -0
@@ -0,0 +1,232 @@
1
+ <script lang="ts">
2
+ /**
3
+ * MessageInput - Chat message input bar
4
+ * Text area with send button. Shows reply preview when replying.
5
+ */
6
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
7
+ import { M } from '../../i18n.messages.js';
8
+
9
+ const { t } = useI18n();
10
+
11
+ export interface Props {
12
+ /** Callback when a message is sent */
13
+ onsend: (content: string) => void;
14
+ /** Placeholder text */
15
+ placeholder?: string;
16
+ /** Disable the input */
17
+ disabled?: boolean;
18
+ /** Reply context if replying to a message */
19
+ replyTo?: { id: string; senderName: string; content: string } | null;
20
+ /** Cancel reply callback */
21
+ oncancelreply?: () => void;
22
+ }
23
+
24
+ const {
25
+ onsend,
26
+ placeholder = 'Type a message...',
27
+ disabled = false,
28
+ replyTo = null,
29
+ oncancelreply,
30
+ }: Props = $props();
31
+
32
+ let content = $state('');
33
+ let textareaEl: HTMLTextAreaElement | undefined = $state();
34
+
35
+ function handleSend() {
36
+ const trimmed = content.trim();
37
+ if (!trimmed || disabled) return;
38
+ onsend(trimmed);
39
+ content = '';
40
+ if (textareaEl) {
41
+ textareaEl.style.height = 'auto';
42
+ }
43
+ }
44
+
45
+ function handleKeydown(event: KeyboardEvent) {
46
+ if (event.key === 'Enter' && !event.shiftKey) {
47
+ event.preventDefault();
48
+ handleSend();
49
+ }
50
+ }
51
+
52
+ function handleInput() {
53
+ if (!textareaEl) return;
54
+ textareaEl.style.height = 'auto';
55
+ textareaEl.style.height = `${Math.min(textareaEl.scrollHeight, 120)}px`;
56
+ }
57
+ </script>
58
+
59
+ <div class="message-input" class:disabled>
60
+ {#if replyTo}
61
+ <div class="message-input__reply">
62
+ <div class="message-input__reply-body">
63
+ <span class="message-input__reply-name">{replyTo.senderName}</span>
64
+ <span class="message-input__reply-text">{replyTo.content}</span>
65
+ </div>
66
+ {#if oncancelreply}
67
+ <button
68
+ class="message-input__reply-close"
69
+ type="button"
70
+ onclick={oncancelreply}
71
+ aria-label={t(M['chat.message_input.cancel_reply'])}
72
+ >
73
+ <svg viewBox="0 0 16 16" width="14" height="14"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" /></svg>
74
+ </button>
75
+ {/if}
76
+ </div>
77
+ {/if}
78
+
79
+ <div class="message-input__bar">
80
+ <textarea
81
+ bind:this={textareaEl}
82
+ bind:value={content}
83
+ class="message-input__textarea"
84
+ {placeholder}
85
+ {disabled}
86
+ rows="1"
87
+ onkeydown={handleKeydown}
88
+ oninput={handleInput}
89
+ aria-label={t(M['chat.message_input.input_label'])}
90
+ ></textarea>
91
+ <button
92
+ class="message-input__send"
93
+ type="button"
94
+ onclick={handleSend}
95
+ disabled={disabled || content.trim().length === 0}
96
+ aria-label={t(M['chat.message_input.send'])}
97
+ >
98
+ <svg viewBox="0 0 20 20" width="18" height="18">
99
+ <path
100
+ d="M3.5 10L2 3l16 7-16 7 1.5-7zm0 0h7"
101
+ fill="none"
102
+ stroke="currentColor"
103
+ stroke-width="1.5"
104
+ stroke-linecap="round"
105
+ stroke-linejoin="round"
106
+ />
107
+ </svg>
108
+ </button>
109
+ </div>
110
+ </div>
111
+
112
+ <style>
113
+ .message-input {
114
+ display: flex;
115
+ flex-direction: column;
116
+ border-top: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
117
+ background: var(--smrt-color-surface, #ffffff);
118
+ }
119
+
120
+ .message-input.disabled {
121
+ opacity: 0.6;
122
+ pointer-events: none;
123
+ }
124
+
125
+ .message-input__reply {
126
+ display: flex;
127
+ align-items: center;
128
+ gap: var(--smrt-spacing-2, 0.375rem);
129
+ padding: var(--smrt-spacing-2, 0.375rem) var(--smrt-spacing-4, 1rem);
130
+ background: var(--smrt-color-surface-container-low, #f9fafb);
131
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
132
+ }
133
+
134
+ .message-input__reply-body {
135
+ flex: 1;
136
+ display: flex;
137
+ flex-direction: column;
138
+ gap: var(--smrt-spacing-1, 4px);
139
+ min-width: 0;
140
+ border-left: 2px solid var(--smrt-color-primary, #005ac1);
141
+ padding-left: var(--smrt-spacing-2, 0.375rem);
142
+ }
143
+
144
+ .message-input__reply-name {
145
+ font-size: var(--smrt-typography-body-small-size, 0.75rem);
146
+ font-weight: var(--smrt-typography-weight-medium, 500);
147
+ color: var(--smrt-color-primary, #005ac1);
148
+ }
149
+
150
+ .message-input__reply-text {
151
+ font-size: var(--smrt-typography-body-small-size, 0.75rem);
152
+ color: var(--smrt-color-on-surface-variant, #43474e);
153
+ white-space: nowrap;
154
+ overflow: hidden;
155
+ text-overflow: ellipsis;
156
+ }
157
+
158
+ .message-input__reply-close {
159
+ display: flex;
160
+ align-items: center;
161
+ justify-content: center;
162
+ width: 24px;
163
+ height: 24px;
164
+ border: none;
165
+ border-radius: var(--smrt-radius-small, 0.25rem);
166
+ background: transparent;
167
+ color: var(--smrt-color-on-surface-variant, #43474e);
168
+ cursor: pointer;
169
+ flex-shrink: 0;
170
+ }
171
+
172
+ .message-input__reply-close:hover {
173
+ background: var(--smrt-color-surface-container-high, #e1e3e8);
174
+ }
175
+
176
+ .message-input__bar {
177
+ display: flex;
178
+ align-items: flex-end;
179
+ gap: var(--smrt-spacing-2, 0.375rem);
180
+ padding: var(--smrt-spacing-3, 0.75rem) var(--smrt-spacing-4, 1rem);
181
+ }
182
+
183
+ .message-input__textarea {
184
+ flex: 1;
185
+ resize: none;
186
+ border: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
187
+ border-radius: var(--smrt-radius-medium, 0.5rem);
188
+ padding: var(--smrt-spacing-2, 0.375rem) var(--smrt-spacing-3, 0.75rem);
189
+ font-size: var(--smrt-typography-body-medium-size, 0.875rem);
190
+ font-family: inherit;
191
+ line-height: 1.4;
192
+ background: var(--smrt-color-surface-container-low, #f9fafb);
193
+ color: var(--smrt-color-on-surface, #1b1b1f);
194
+ max-height: 120px;
195
+ overflow-y: auto;
196
+ }
197
+
198
+ .message-input__textarea:focus {
199
+ outline: 2px solid var(--smrt-color-primary, #005ac1);
200
+ outline-offset: -1px;
201
+ border-color: transparent;
202
+ }
203
+
204
+ .message-input__textarea::placeholder {
205
+ color: var(--smrt-color-outline, #74777f);
206
+ }
207
+
208
+ .message-input__send {
209
+ display: flex;
210
+ align-items: center;
211
+ justify-content: center;
212
+ width: 36px;
213
+ height: 36px;
214
+ border: none;
215
+ border-radius: var(--smrt-radius-full, 9999px);
216
+ background: var(--smrt-color-primary, #005ac1);
217
+ color: var(--smrt-color-on-primary, #ffffff);
218
+ cursor: pointer;
219
+ flex-shrink: 0;
220
+ transition: background var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
221
+ }
222
+
223
+ .message-input__send:hover:not(:disabled) {
224
+ background: color-mix(in srgb, var(--smrt-color-primary, #005ac1) 85%, var(--smrt-color-shadow, #000));
225
+ }
226
+
227
+ .message-input__send:disabled {
228
+ background: var(--smrt-color-surface-container-high, #e1e3e8);
229
+ color: var(--smrt-color-outline, #74777f);
230
+ cursor: not-allowed;
231
+ }
232
+ </style>
@@ -0,0 +1,20 @@
1
+ export interface Props {
2
+ /** Callback when a message is sent */
3
+ onsend: (content: string) => void;
4
+ /** Placeholder text */
5
+ placeholder?: string;
6
+ /** Disable the input */
7
+ disabled?: boolean;
8
+ /** Reply context if replying to a message */
9
+ replyTo?: {
10
+ id: string;
11
+ senderName: string;
12
+ content: string;
13
+ } | null;
14
+ /** Cancel reply callback */
15
+ oncancelreply?: () => void;
16
+ }
17
+ declare const MessageInput: import("svelte").Component<Props, {}, "">;
18
+ type MessageInput = ReturnType<typeof MessageInput>;
19
+ export default MessageInput;
20
+ //# sourceMappingURL=MessageInput.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageInput.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/messages/MessageInput.svelte.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,KAAK;IACpB,sCAAsC;IACtC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrE,4BAA4B;IAC5B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAyED,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,431 @@
1
+ <script lang="ts">
2
+ /**
3
+ * MessageItem - Individual message row
4
+ * Displays avatar, message bubble, reactions, reply preview, and tool call data.
5
+ */
6
+
7
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
8
+ import { M } from '../../i18n.messages.js';
9
+ import type { ChatMessageData } from '../../types.js';
10
+ import Avatar from '../shared/Avatar.svelte';
11
+ import MessageBubble from '../shared/MessageBubble.svelte';
12
+ import ReactionPicker from '../shared/ReactionPicker.svelte';
13
+
14
+ const { t } = useI18n();
15
+
16
+ export interface Props {
17
+ /** Message data to render */
18
+ message: ChatMessageData;
19
+ /** Whether this message was sent by the current user */
20
+ isOwn: boolean;
21
+ /** Reply callback */
22
+ onreply?: (id: string) => void;
23
+ /** React callback */
24
+ onreact?: (id: string, emoji: string) => void;
25
+ /** Edit callback */
26
+ onedit?: (id: string) => void;
27
+ /** Delete callback */
28
+ ondelete?: (id: string) => void;
29
+ }
30
+
31
+ const { message, isOwn, onreply, onreact, onedit, ondelete }: Props = $props();
32
+
33
+ let showReactionPicker = $state(false);
34
+ let showActions = $state(false);
35
+
36
+ const bubbleVariant = $derived.by(() => {
37
+ if (message.messageType === 'system' || message.messageType === 'action')
38
+ return 'system';
39
+ if (
40
+ message.role === 'assistant' ||
41
+ message.messageType === 'tool_call' ||
42
+ message.messageType === 'tool_result'
43
+ )
44
+ return 'agent';
45
+ return 'default';
46
+ });
47
+
48
+ const formattedTime = $derived.by(() => {
49
+ const d =
50
+ message.createdAt instanceof Date
51
+ ? message.createdAt
52
+ : new Date(message.createdAt);
53
+ return d.toLocaleTimeString(undefined, {
54
+ hour: '2-digit',
55
+ minute: '2-digit',
56
+ });
57
+ });
58
+
59
+ function handleReact(emoji: string) {
60
+ onreact?.(message.id, emoji);
61
+ showReactionPicker = false;
62
+ }
63
+
64
+ function toggleReactionPicker() {
65
+ showReactionPicker = !showReactionPicker;
66
+ }
67
+ </script>
68
+
69
+ {#if message.messageType === 'system' || message.messageType === 'action'}
70
+ <div class="message-item message-item--system">
71
+ <MessageBubble content={message.content} isOwn={false} variant="system" />
72
+ </div>
73
+ {:else}
74
+ <div
75
+ class="message-item"
76
+ class:message-item--own={isOwn}
77
+ role="article"
78
+ aria-label={t(M['chat.message_item.message_from'], { name: message.senderName })}
79
+ onmouseenter={() => showActions = true}
80
+ onmouseleave={() => { showActions = false; showReactionPicker = false; }}
81
+ >
82
+ {#if !isOwn}
83
+ <div class="message-item__avatar">
84
+ <Avatar
85
+ name={message.senderName}
86
+ avatarUrl={message.senderAvatarUrl}
87
+ size="sm"
88
+ />
89
+ </div>
90
+ {/if}
91
+
92
+ <div class="message-item__body">
93
+ {#if !isOwn}
94
+ <span class="message-item__sender">{message.senderName}</span>
95
+ {/if}
96
+
97
+ {#if message.replyTo}
98
+ <div class="message-item__reply-preview">
99
+ <span class="message-item__reply-name">{message.replyTo.senderName}</span>
100
+ <span class="message-item__reply-text">{message.replyTo.content}</span>
101
+ </div>
102
+ {/if}
103
+
104
+ <MessageBubble
105
+ content={message.content}
106
+ {isOwn}
107
+ variant={bubbleVariant}
108
+ />
109
+
110
+ {#if message.toolCallData}
111
+ <div class="message-item__tool-call">
112
+ <span class="message-item__tool-name">
113
+ {message.toolCallData.toolName}
114
+ </span>
115
+ <span class="message-item__tool-status {message.toolCallData.status}">
116
+ {message.toolCallData.status}
117
+ </span>
118
+ {#if message.toolCallData.duration}
119
+ <span class="message-item__tool-duration">
120
+ {message.toolCallData.duration}ms
121
+ </span>
122
+ {/if}
123
+ {#if message.toolCallData.error}
124
+ <span class="message-item__tool-error">{message.toolCallData.error}</span>
125
+ {/if}
126
+ </div>
127
+ {/if}
128
+
129
+ {#if message.reactions.length > 0}
130
+ <div class="message-item__reactions">
131
+ {#each message.reactions as reaction}
132
+ <button
133
+ class="message-item__reaction"
134
+ class:message-item__reaction--active={reaction.reacted}
135
+ type="button"
136
+ onclick={() => onreact?.(message.id, reaction.emoji)}
137
+ aria-label="{reaction.emoji} {reaction.count}"
138
+ >
139
+ <span class="message-item__reaction-emoji">{reaction.emoji}</span>
140
+ <span class="message-item__reaction-count">{reaction.count}</span>
141
+ </button>
142
+ {/each}
143
+ </div>
144
+ {/if}
145
+
146
+ <div class="message-item__meta">
147
+ <span class="message-item__time">{formattedTime}</span>
148
+ {#if message.isEdited}
149
+ <span class="message-item__edited">(edited)</span>
150
+ {/if}
151
+ </div>
152
+ </div>
153
+
154
+ {#if showActions}
155
+ <div class="message-item__actions">
156
+ {#if onreact}
157
+ <button
158
+ class="message-item__action-btn"
159
+ type="button"
160
+ onclick={toggleReactionPicker}
161
+ aria-label={t(M['chat.message_item.add_reaction'])}
162
+ >
163
+ <svg viewBox="0 0 16 16" width="14" height="14"><circle cx="8" cy="8" r="7" fill="none" stroke="currentColor" stroke-width="1.2" /><circle cx="5.5" cy="6.5" r="0.8" fill="currentColor" /><circle cx="10.5" cy="6.5" r="0.8" fill="currentColor" /><path d="M5.5 9.5c.5 1.2 1.5 1.8 2.5 1.8s2-.6 2.5-1.8" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" /></svg>
164
+ </button>
165
+ {/if}
166
+ {#if onreply}
167
+ <button
168
+ class="message-item__action-btn"
169
+ type="button"
170
+ onclick={() => onreply?.(message.id)}
171
+ aria-label={t(M['chat.message_item.reply'])}
172
+ >
173
+ <svg viewBox="0 0 16 16" width="14" height="14"><path d="M6 3L2 7l4 4" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" /><path d="M2 7h8c2.2 0 4 1.8 4 4v1" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" /></svg>
174
+ </button>
175
+ {/if}
176
+ {#if isOwn && onedit}
177
+ <button
178
+ class="message-item__action-btn"
179
+ type="button"
180
+ onclick={() => onedit?.(message.id)}
181
+ aria-label={t(M['chat.message_item.edit'])}
182
+ >
183
+ <svg viewBox="0 0 16 16" width="14" height="14"><path d="M11.5 1.5l3 3L5 14H2v-3z" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" /></svg>
184
+ </button>
185
+ {/if}
186
+ {#if isOwn && ondelete}
187
+ <button
188
+ class="message-item__action-btn"
189
+ type="button"
190
+ onclick={() => ondelete?.(message.id)}
191
+ aria-label={t(M['chat.message_item.delete'])}
192
+ >
193
+ <svg viewBox="0 0 16 16" width="14" height="14"><path d="M2 4h12M5.3 4V2.7A.7.7 0 016 2h4a.7.7 0 01.7.7V4m1.6 0v9.3a1.4 1.4 0 01-1.4 1.4H5.1a1.4 1.4 0 01-1.4-1.4V4" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" /></svg>
194
+ </button>
195
+ {/if}
196
+ </div>
197
+
198
+ {#if showReactionPicker}
199
+ <div class="message-item__picker-popover">
200
+ <ReactionPicker isOpen={true} onreact={handleReact} />
201
+ </div>
202
+ {/if}
203
+ {/if}
204
+ </div>
205
+ {/if}
206
+
207
+ <style>
208
+ .message-item {
209
+ display: flex;
210
+ align-items: flex-start;
211
+ gap: var(--smrt-spacing-2, 0.375rem);
212
+ padding: var(--smrt-spacing-1, 0.25rem) var(--smrt-spacing-4, 1rem);
213
+ position: relative;
214
+ }
215
+
216
+ .message-item--own {
217
+ flex-direction: row-reverse;
218
+ }
219
+
220
+ .message-item--system {
221
+ justify-content: center;
222
+ padding: var(--smrt-spacing-1, 0.25rem) var(--smrt-spacing-4, 1rem);
223
+ }
224
+
225
+ .message-item__avatar {
226
+ flex-shrink: 0;
227
+ margin-top: var(--smrt-spacing-1, 0.25rem);
228
+ }
229
+
230
+ .message-item__body {
231
+ display: flex;
232
+ flex-direction: column;
233
+ gap: var(--smrt-spacing-1, 0.25rem);
234
+ min-width: 0;
235
+ max-width: 75%;
236
+ }
237
+
238
+ .message-item--own .message-item__body {
239
+ align-items: flex-end;
240
+ }
241
+
242
+ .message-item__sender {
243
+ font-size: var(--smrt-typography-body-small-size, 0.75rem);
244
+ font-weight: var(--smrt-typography-weight-medium, 500);
245
+ color: var(--smrt-color-on-surface-variant, #43474e);
246
+ padding-left: var(--smrt-spacing-1, 0.25rem);
247
+ }
248
+
249
+ .message-item__reply-preview {
250
+ display: flex;
251
+ flex-direction: column;
252
+ gap: var(--smrt-spacing-1, 4px);
253
+ padding: var(--smrt-spacing-1, 0.25rem) var(--smrt-spacing-3, 0.75rem);
254
+ border-left: 2px solid var(--smrt-color-primary, #005ac1);
255
+ border-radius: var(--smrt-radius-small, 0.25rem);
256
+ background: var(--smrt-color-surface-container-low, #f9fafb);
257
+ font-size: var(--smrt-typography-body-small-size, 0.75rem);
258
+ max-width: 100%;
259
+ overflow: hidden;
260
+ }
261
+
262
+ .message-item__reply-name {
263
+ font-weight: var(--smrt-typography-weight-medium, 500);
264
+ color: var(--smrt-color-primary, #005ac1);
265
+ }
266
+
267
+ .message-item__reply-text {
268
+ color: var(--smrt-color-on-surface-variant, #43474e);
269
+ white-space: nowrap;
270
+ overflow: hidden;
271
+ text-overflow: ellipsis;
272
+ }
273
+
274
+ .message-item__tool-call {
275
+ display: flex;
276
+ align-items: center;
277
+ gap: var(--smrt-spacing-2, 0.375rem);
278
+ padding: var(--smrt-spacing-2, 0.375rem) var(--smrt-spacing-3, 0.75rem);
279
+ background: var(--smrt-color-surface-container, #f3f4f6);
280
+ border-radius: var(--smrt-radius-small, 0.25rem);
281
+ font-size: var(--smrt-typography-body-small-size, 0.75rem);
282
+ font-family: var(--smrt-font-family-mono, ui-monospace, monospace);
283
+ }
284
+
285
+ .message-item__tool-name {
286
+ font-weight: var(--smrt-typography-weight-medium, 500);
287
+ color: var(--smrt-color-on-surface, #1b1b1f);
288
+ }
289
+
290
+ .message-item__tool-status {
291
+ padding: var(--smrt-spacing-1, 4px) var(--smrt-spacing-2, 8px);
292
+ border-radius: var(--smrt-radius-small, 0.25rem);
293
+ font-size: var(--smrt-typography-label-small-size, 0.625rem);
294
+ text-transform: uppercase;
295
+ font-weight: var(--smrt-typography-weight-semibold, 600);
296
+ }
297
+
298
+ .message-item__tool-status.pending {
299
+ background: var(--smrt-color-surface-container-high, #e1e3e8);
300
+ color: var(--smrt-color-on-surface-variant, #43474e);
301
+ }
302
+
303
+ .message-item__tool-status.running {
304
+ background: var(--smrt-color-primary-container, #e3f2fd);
305
+ color: var(--smrt-color-on-primary-container, #1565c0);
306
+ }
307
+
308
+ .message-item__tool-status.success {
309
+ background: var(--smrt-color-success-container, #e8f5e9);
310
+ color: var(--smrt-color-on-success-container, #2e7d32);
311
+ }
312
+
313
+ .message-item__tool-status.error {
314
+ background: var(--smrt-color-error-container, #ffebee);
315
+ color: var(--smrt-color-on-error-container, #c62828);
316
+ }
317
+
318
+ .message-item__tool-duration {
319
+ color: var(--smrt-color-outline, #74777f);
320
+ }
321
+
322
+ .message-item__tool-error {
323
+ color: var(--smrt-color-on-error-container, #c62828);
324
+ }
325
+
326
+ .message-item__reactions {
327
+ display: flex;
328
+ flex-wrap: wrap;
329
+ gap: var(--smrt-spacing-1, 0.25rem);
330
+ }
331
+
332
+ .message-item__reaction {
333
+ display: inline-flex;
334
+ align-items: center;
335
+ gap: var(--smrt-spacing-1, 4px);
336
+ padding: var(--smrt-spacing-1, 4px) var(--smrt-spacing-2, 8px);
337
+ border: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
338
+ border-radius: var(--smrt-radius-full, 9999px);
339
+ background: var(--smrt-color-surface, #ffffff);
340
+ cursor: pointer;
341
+ font-size: var(--smrt-typography-label-medium-size, 0.75rem);
342
+ transition: background var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
343
+ }
344
+
345
+ .message-item__reaction:hover {
346
+ background: var(--smrt-color-surface-container-high, #e1e3e8);
347
+ }
348
+
349
+ .message-item__reaction--active {
350
+ border-color: var(--smrt-color-primary, #005ac1);
351
+ background: var(--smrt-color-primary-container, #d6e3ff);
352
+ }
353
+
354
+ .message-item__reaction-emoji {
355
+ font-size: var(--smrt-typography-label-large-size, 0.8125rem);
356
+ line-height: 1;
357
+ }
358
+
359
+ .message-item__reaction-count {
360
+ font-size: var(--smrt-typography-label-small-size, 0.6875rem);
361
+ color: var(--smrt-color-on-surface-variant, #43474e);
362
+ }
363
+
364
+ .message-item__meta {
365
+ display: flex;
366
+ align-items: center;
367
+ gap: var(--smrt-spacing-1, 0.25rem);
368
+ padding-left: var(--smrt-spacing-1, 0.25rem);
369
+ }
370
+
371
+ .message-item__time {
372
+ font-size: var(--smrt-typography-label-small-size, 0.625rem);
373
+ color: var(--smrt-color-outline, #74777f);
374
+ }
375
+
376
+ .message-item__edited {
377
+ font-size: var(--smrt-typography-label-small-size, 0.625rem);
378
+ color: var(--smrt-color-outline, #74777f);
379
+ font-style: italic;
380
+ }
381
+
382
+ .message-item__actions {
383
+ display: flex;
384
+ align-items: center;
385
+ gap: var(--smrt-spacing-1, 4px);
386
+ position: absolute;
387
+ top: 0;
388
+ right: var(--smrt-spacing-4, 1rem);
389
+ background: var(--smrt-color-surface, #ffffff);
390
+ border: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
391
+ border-radius: var(--smrt-radius-small, 0.25rem);
392
+ padding: var(--smrt-spacing-1, 4px);
393
+ box-shadow: var(--smrt-elevation-1, 0 1px 3px rgba(0, 0, 0, 0.12));
394
+ }
395
+
396
+ .message-item--own .message-item__actions {
397
+ right: auto;
398
+ left: var(--smrt-spacing-4, 1rem);
399
+ }
400
+
401
+ .message-item__action-btn {
402
+ display: flex;
403
+ align-items: center;
404
+ justify-content: center;
405
+ width: 26px;
406
+ height: 26px;
407
+ border: none;
408
+ border-radius: var(--smrt-radius-small, 0.25rem);
409
+ background: transparent;
410
+ color: var(--smrt-color-on-surface-variant, #43474e);
411
+ cursor: pointer;
412
+ transition: background var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
413
+ }
414
+
415
+ .message-item__action-btn:hover {
416
+ background: var(--smrt-color-surface-container-high, #e1e3e8);
417
+ }
418
+
419
+ .message-item__picker-popover {
420
+ position: absolute;
421
+ top: -8px;
422
+ right: var(--smrt-spacing-4, 1rem);
423
+ z-index: 10;
424
+ transform: translateY(-100%);
425
+ }
426
+
427
+ .message-item--own .message-item__picker-popover {
428
+ right: auto;
429
+ left: var(--smrt-spacing-4, 1rem);
430
+ }
431
+ </style>
@@ -0,0 +1,19 @@
1
+ import type { ChatMessageData } from '../../types.js';
2
+ export interface Props {
3
+ /** Message data to render */
4
+ message: ChatMessageData;
5
+ /** Whether this message was sent by the current user */
6
+ isOwn: boolean;
7
+ /** Reply callback */
8
+ onreply?: (id: string) => void;
9
+ /** React callback */
10
+ onreact?: (id: string, emoji: string) => void;
11
+ /** Edit callback */
12
+ onedit?: (id: string) => void;
13
+ /** Delete callback */
14
+ ondelete?: (id: string) => void;
15
+ }
16
+ declare const MessageItem: import("svelte").Component<Props, {}, "">;
17
+ type MessageItem = ReturnType<typeof MessageItem>;
18
+ export default MessageItem;
19
+ //# sourceMappingURL=MessageItem.svelte.d.ts.map