@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,18 @@
1
+ export interface Props {
2
+ /** Current search query (text after @) */
3
+ query: string;
4
+ /** Matching suggestions */
5
+ suggestions: Array<{
6
+ id: string;
7
+ name: string;
8
+ avatarUrl?: string;
9
+ }>;
10
+ /** Select a suggestion */
11
+ onselect: (id: string) => void;
12
+ /** Whether the popup is visible */
13
+ isVisible: boolean;
14
+ }
15
+ declare const MentionAutocomplete: import("svelte").Component<Props, {}, "">;
16
+ type MentionAutocomplete = ReturnType<typeof MentionAutocomplete>;
17
+ export default MentionAutocomplete;
18
+ //# sourceMappingURL=MentionAutocomplete.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MentionAutocomplete.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/MentionAutocomplete.svelte.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,KAAK;IACpB,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,WAAW,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrE,0BAA0B;IAC1B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,mCAAmC;IACnC,SAAS,EAAE,OAAO,CAAC;CACpB;AA+ED,QAAA,MAAM,mBAAmB,2CAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,81 @@
1
+ <script lang="ts">
2
+ /**
3
+ * MessageBubble - Styled message bubble
4
+ * Different styling for own messages, others' messages, agent, and system.
5
+ */
6
+
7
+ export interface Props {
8
+ /** Message content text */
9
+ content: string;
10
+ /** Whether this message was sent by the current user */
11
+ isOwn: boolean;
12
+ /** Visual variant of the bubble */
13
+ variant?: 'default' | 'agent' | 'system';
14
+ }
15
+
16
+ const { content, isOwn, variant = 'default' }: Props = $props();
17
+ </script>
18
+
19
+ <div
20
+ class="bubble {variant}"
21
+ class:own={isOwn}
22
+ >
23
+ <p class="bubble__content">{content}</p>
24
+ </div>
25
+
26
+ <style>
27
+ .bubble {
28
+ max-width: 75%;
29
+ padding: var(--smrt-spacing-3, 0.75rem) var(--smrt-spacing-4, 1rem);
30
+ border-radius: var(--smrt-radius-large, 1rem);
31
+ word-break: break-word;
32
+ line-height: 1.4;
33
+ font-size: var(--smrt-typography-body-medium-size, 0.875rem);
34
+ }
35
+
36
+ .bubble__content {
37
+ margin: 0;
38
+ white-space: pre-wrap;
39
+ }
40
+
41
+ /* Default variant - received */
42
+ .default {
43
+ background: var(--smrt-color-surface-container, #f3f4f6);
44
+ color: var(--smrt-color-on-surface, #1b1b1f);
45
+ border-bottom-left-radius: var(--smrt-radius-small, 0.25rem);
46
+ }
47
+
48
+ /* Default variant - own message */
49
+ .default.own {
50
+ background: var(--smrt-color-primary, #005ac1);
51
+ color: var(--smrt-color-on-primary, #ffffff);
52
+ border-bottom-right-radius: var(--smrt-radius-small, 0.25rem);
53
+ border-bottom-left-radius: var(--smrt-radius-large, 1rem);
54
+ }
55
+
56
+ /* Agent variant */
57
+ .agent {
58
+ background: var(--smrt-color-tertiary-container, #ffd8e4);
59
+ color: var(--smrt-color-on-tertiary-container, #31111d);
60
+ border-bottom-left-radius: var(--smrt-radius-small, 0.25rem);
61
+ border-left: 3px solid var(--smrt-color-tertiary, #7d5260);
62
+ }
63
+
64
+ .agent.own {
65
+ border-left: none;
66
+ border-right: 3px solid var(--smrt-color-tertiary, #7d5260);
67
+ border-bottom-right-radius: var(--smrt-radius-small, 0.25rem);
68
+ border-bottom-left-radius: var(--smrt-radius-large, 1rem);
69
+ }
70
+
71
+ /* System variant */
72
+ .system {
73
+ max-width: 100%;
74
+ background: transparent;
75
+ color: var(--smrt-color-on-surface-variant, #43474e);
76
+ text-align: center;
77
+ font-size: var(--smrt-typography-body-small-size, 0.75rem);
78
+ padding: var(--smrt-spacing-2, 0.375rem) var(--smrt-spacing-4, 1rem);
79
+ border-radius: 0;
80
+ }
81
+ </style>
@@ -0,0 +1,16 @@
1
+ /**
2
+ * MessageBubble - Styled message bubble
3
+ * Different styling for own messages, others' messages, agent, and system.
4
+ */
5
+ export interface Props {
6
+ /** Message content text */
7
+ content: string;
8
+ /** Whether this message was sent by the current user */
9
+ isOwn: boolean;
10
+ /** Visual variant of the bubble */
11
+ variant?: 'default' | 'agent' | 'system';
12
+ }
13
+ declare const MessageBubble: import("svelte").Component<Props, {}, "">;
14
+ type MessageBubble = ReturnType<typeof MessageBubble>;
15
+ export default MessageBubble;
16
+ //# sourceMappingURL=MessageBubble.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageBubble.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/MessageBubble.svelte.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,MAAM,WAAW,KAAK;IACpB,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,KAAK,EAAE,OAAO,CAAC;IACf,mCAAmC;IACnC,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;CAC1C;AAeD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,103 @@
1
+ <script lang="ts">
2
+ /**
3
+ * ReactionPicker - Emoji reaction selector
4
+ * Compact popup grid of common emojis.
5
+ */
6
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
7
+ import { M } from '../../i18n.js';
8
+
9
+ const { t } = useI18n();
10
+
11
+ export interface Props {
12
+ /** Callback when an emoji is selected */
13
+ onreact: (emoji: string) => void;
14
+ /** Whether the picker is visible */
15
+ isOpen?: boolean;
16
+ }
17
+
18
+ const { onreact, isOpen = false }: Props = $props();
19
+
20
+ const emojis = [
21
+ '\u{1F44D}',
22
+ '\u{1F44E}',
23
+ '\u{2764}\u{FE0F}',
24
+ '\u{1F602}',
25
+ '\u{1F62E}',
26
+ '\u{1F622}',
27
+ '\u{1F525}',
28
+ '\u{1F389}',
29
+ '\u{1F44F}',
30
+ '\u{1F914}',
31
+ '\u{1F60D}',
32
+ '\u{1F680}',
33
+ '\u{1F440}',
34
+ '\u{2705}',
35
+ '\u{274C}',
36
+ '\u{1F4AF}',
37
+ ];
38
+
39
+ function handleSelect(emoji: string) {
40
+ onreact(emoji);
41
+ }
42
+
43
+ function handleKeydown(event: KeyboardEvent, emoji: string) {
44
+ if (event.key === 'Enter' || event.key === ' ') {
45
+ event.preventDefault();
46
+ handleSelect(emoji);
47
+ }
48
+ }
49
+ </script>
50
+
51
+ {#if isOpen}
52
+ <div class="reaction-picker" role="group" aria-label={t(M['chat.reaction_picker.reactions'])}>
53
+ {#each emojis as emoji}
54
+ <button
55
+ class="reaction-picker__item"
56
+ type="button"
57
+ onclick={() => handleSelect(emoji)}
58
+ aria-label={t(M['chat.reaction_picker.react_with'], { emoji })}
59
+ >
60
+ {emoji}
61
+ </button>
62
+ {/each}
63
+ </div>
64
+ {/if}
65
+
66
+ <style>
67
+ .reaction-picker {
68
+ display: grid;
69
+ grid-template-columns: repeat(8, 1fr);
70
+ gap: var(--smrt-spacing-1, 0.25rem);
71
+ padding: var(--smrt-spacing-2, 0.375rem);
72
+ background: var(--smrt-color-surface, #ffffff);
73
+ border: 1px solid var(--smrt-color-outline-variant, #c4c6cf);
74
+ border-radius: var(--smrt-radius-medium, 0.5rem);
75
+ box-shadow: var(--smrt-elevation-2, 0 2px 6px rgba(0, 0, 0, 0.15));
76
+ width: max-content;
77
+ }
78
+
79
+ .reaction-picker__item {
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ width: 32px;
84
+ height: 32px;
85
+ border: none;
86
+ border-radius: var(--smrt-radius-small, 0.25rem);
87
+ background: transparent;
88
+ cursor: pointer;
89
+ font-size: var(--smrt-typography-body-large-size, 1.125rem);
90
+ line-height: 1;
91
+ padding: 0;
92
+ transition: background var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
93
+ }
94
+
95
+ .reaction-picker__item:hover {
96
+ background: var(--smrt-color-surface-container-high, #e1e3e8);
97
+ }
98
+
99
+ .reaction-picker__item:focus-visible {
100
+ outline: 2px solid var(--smrt-color-primary, #005ac1);
101
+ outline-offset: -2px;
102
+ }
103
+ </style>
@@ -0,0 +1,10 @@
1
+ export interface Props {
2
+ /** Callback when an emoji is selected */
3
+ onreact: (emoji: string) => void;
4
+ /** Whether the picker is visible */
5
+ isOpen?: boolean;
6
+ }
7
+ declare const ReactionPicker: import("svelte").Component<Props, {}, "">;
8
+ type ReactionPicker = ReturnType<typeof ReactionPicker>;
9
+ export default ReactionPicker;
10
+ //# sourceMappingURL=ReactionPicker.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactionPicker.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/ReactionPicker.svelte.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,KAAK;IACpB,yCAAyC;IACzC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,oCAAoC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAwDD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,127 @@
1
+ <script lang="ts">
2
+ /**
3
+ * ReadReceipts - Message read status display
4
+ * Shows checkmarks or small avatars indicating who has read a message.
5
+ */
6
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
7
+ import { M } from '../../i18n.js';
8
+
9
+ const { t } = useI18n();
10
+
11
+ export interface Props {
12
+ /** Users who have read the message */
13
+ readBy: Array<{ name: string; avatarUrl?: string }>;
14
+ /** Total participants in the conversation */
15
+ totalParticipants: number;
16
+ }
17
+
18
+ const { readBy, totalParticipants }: Props = $props();
19
+
20
+ const allRead = $derived(readBy.length >= totalParticipants - 1);
21
+ const showAvatars = $derived(readBy.length <= 5);
22
+
23
+ function getInitial(name: string): string {
24
+ return (name[0] ?? '?').toUpperCase();
25
+ }
26
+ </script>
27
+
28
+ <span class="read-receipts" aria-label={t(M['chat.read_receipts.read_status'], { readCount: readBy.length, total: totalParticipants })}>
29
+ {#if readBy.length === 0}
30
+ <!-- Single check: sent -->
31
+ <svg class="read-receipts__icon" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
32
+ <path d="M13.5 4.5l-7 7L3 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
33
+ </svg>
34
+ {:else if !showAvatars}
35
+ <!-- Double check with count -->
36
+ <svg class="read-receipts__icon read-receipts__icon--read" viewBox="0 0 20 16" width="18" height="14" aria-hidden="true">
37
+ <path d="M15.5 4.5l-7 7L5 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
38
+ <path d="M19 4.5l-7 7" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
39
+ </svg>
40
+ <span class="read-receipts__count">{readBy.length}</span>
41
+ {:else}
42
+ <!-- Double check with mini avatars -->
43
+ <svg class="read-receipts__icon" class:read-receipts__icon--read={allRead} viewBox="0 0 20 16" width="18" height="14" aria-hidden="true">
44
+ <path d="M15.5 4.5l-7 7L5 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
45
+ <path d="M19 4.5l-7 7" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
46
+ </svg>
47
+ {#if readBy.length > 0}
48
+ <span class="read-receipts__avatars">
49
+ {#each readBy.slice(0, 3) as reader}
50
+ {#if reader.avatarUrl}
51
+ <img class="read-receipts__mini-avatar" src={reader.avatarUrl} alt={reader.name} />
52
+ {:else}
53
+ <span class="read-receipts__mini-avatar read-receipts__mini-avatar--initials" title={reader.name}>
54
+ {getInitial(reader.name)}
55
+ </span>
56
+ {/if}
57
+ {/each}
58
+ {#if readBy.length > 3}
59
+ <span class="read-receipts__mini-avatar read-receipts__mini-avatar--more">
60
+ +{readBy.length - 3}
61
+ </span>
62
+ {/if}
63
+ </span>
64
+ {/if}
65
+ {/if}
66
+ </span>
67
+
68
+ <style>
69
+ .read-receipts {
70
+ display: inline-flex;
71
+ align-items: center;
72
+ gap: var(--smrt-spacing-1, 0.25rem);
73
+ color: var(--smrt-color-outline, #74777f);
74
+ }
75
+
76
+ .read-receipts__icon {
77
+ flex-shrink: 0;
78
+ }
79
+
80
+ .read-receipts__icon--read {
81
+ color: var(--smrt-color-primary, #005ac1);
82
+ }
83
+
84
+ .read-receipts__count {
85
+ font-size: var(--smrt-typography-label-small-size, 0.625rem);
86
+ color: var(--smrt-color-on-surface-variant, #43474e);
87
+ }
88
+
89
+ .read-receipts__avatars {
90
+ display: inline-flex;
91
+ align-items: center;
92
+ }
93
+
94
+ .read-receipts__mini-avatar {
95
+ width: 16px;
96
+ height: 16px;
97
+ border-radius: var(--smrt-radius-full, 9999px);
98
+ border: 1px solid var(--smrt-color-surface, #ffffff);
99
+ margin-left: -4px;
100
+ object-fit: cover;
101
+ flex-shrink: 0;
102
+ }
103
+
104
+ .read-receipts__mini-avatar:first-child {
105
+ margin-left: 0;
106
+ }
107
+
108
+ .read-receipts__mini-avatar--initials {
109
+ display: inline-flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ background: var(--smrt-color-surface-container-high, #e1e3e8);
113
+ color: var(--smrt-color-on-surface-variant, #43474e);
114
+ font-size: var(--smrt-typography-label-small-size, 0.5rem);
115
+ font-weight: var(--smrt-typography-weight-medium, 500);
116
+ }
117
+
118
+ .read-receipts__mini-avatar--more {
119
+ display: inline-flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ background: var(--smrt-color-surface-container, #f3f4f6);
123
+ color: var(--smrt-color-on-surface-variant, #43474e);
124
+ font-size: var(--smrt-typography-label-small-size, 0.5rem);
125
+ font-weight: var(--smrt-typography-weight-medium, 500);
126
+ }
127
+ </style>
@@ -0,0 +1,13 @@
1
+ export interface Props {
2
+ /** Users who have read the message */
3
+ readBy: Array<{
4
+ name: string;
5
+ avatarUrl?: string;
6
+ }>;
7
+ /** Total participants in the conversation */
8
+ totalParticipants: number;
9
+ }
10
+ declare const ReadReceipts: import("svelte").Component<Props, {}, "">;
11
+ type ReadReceipts = ReturnType<typeof ReadReceipts>;
12
+ export default ReadReceipts;
13
+ //# sourceMappingURL=ReadReceipts.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReadReceipts.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/ReadReceipts.svelte.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,KAAK;IACpB,sCAAsC;IACtC,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,6CAA6C;IAC7C,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AA+DD,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,90 @@
1
+ <script lang="ts">
2
+ /**
3
+ * TypingIndicator - Animated "typing..." display
4
+ * Shows "Alice is typing..." or "Alice and Bob are typing..." with animated dots.
5
+ */
6
+
7
+ export interface Props {
8
+ /** Names of users currently typing */
9
+ names: string[];
10
+ }
11
+
12
+ const { names }: Props = $props();
13
+
14
+ const label = $derived.by(() => {
15
+ if (names.length === 0) return '';
16
+ if (names.length === 1) return `${names[0]} is typing`;
17
+ if (names.length === 2) return `${names[0]} and ${names[1]} are typing`;
18
+ return `${names[0]} and ${names.length - 1} others are typing`;
19
+ });
20
+ </script>
21
+
22
+ {#if names.length > 0}
23
+ <div class="typing" aria-live="polite" aria-label="{label}">
24
+ <span class="typing__text">{label}</span>
25
+ <span class="typing__dots" aria-hidden="true">
26
+ <span class="typing__dot"></span>
27
+ <span class="typing__dot"></span>
28
+ <span class="typing__dot"></span>
29
+ </span>
30
+ </div>
31
+ {/if}
32
+
33
+ <style>
34
+ .typing {
35
+ display: inline-flex;
36
+ align-items: center;
37
+ gap: var(--smrt-spacing-1, 0.25rem);
38
+ padding: var(--smrt-spacing-1, 0.25rem) var(--smrt-spacing-3, 0.75rem);
39
+ font-size: var(--smrt-typography-body-small-size, 0.75rem);
40
+ color: var(--smrt-color-on-surface-variant, #43474e);
41
+ }
42
+
43
+ .typing__text {
44
+ white-space: nowrap;
45
+ }
46
+
47
+ .typing__dots {
48
+ display: inline-flex;
49
+ align-items: center;
50
+ gap: var(--smrt-spacing-1, 4px);
51
+ }
52
+
53
+ .typing__dot {
54
+ width: 4px;
55
+ height: 4px;
56
+ border-radius: var(--smrt-radius-full, 9999px);
57
+ background: var(--smrt-color-on-surface-variant, #43474e);
58
+ animation: typing-bounce 1.4s infinite ease-in-out both;
59
+ }
60
+
61
+ .typing__dot:nth-child(1) {
62
+ animation-delay: 0s;
63
+ }
64
+
65
+ .typing__dot:nth-child(2) {
66
+ animation-delay: 0.2s;
67
+ }
68
+
69
+ .typing__dot:nth-child(3) {
70
+ animation-delay: 0.4s;
71
+ }
72
+
73
+ @keyframes typing-bounce {
74
+ 0%, 80%, 100% {
75
+ opacity: 0.3;
76
+ transform: scale(0.8);
77
+ }
78
+ 40% {
79
+ opacity: 1;
80
+ transform: scale(1);
81
+ }
82
+ }
83
+
84
+ @media (prefers-reduced-motion: reduce) {
85
+ .typing__dot {
86
+ animation: none;
87
+ opacity: 0.6;
88
+ }
89
+ }
90
+ </style>
@@ -0,0 +1,12 @@
1
+ /**
2
+ * TypingIndicator - Animated "typing..." display
3
+ * Shows "Alice is typing..." or "Alice and Bob are typing..." with animated dots.
4
+ */
5
+ export interface Props {
6
+ /** Names of users currently typing */
7
+ names: string[];
8
+ }
9
+ declare const TypingIndicator: import("svelte").Component<Props, {}, "">;
10
+ type TypingIndicator = ReturnType<typeof TypingIndicator>;
11
+ export default TypingIndicator;
12
+ //# sourceMappingURL=TypingIndicator.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TypingIndicator.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/TypingIndicator.svelte.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,MAAM,WAAW,KAAK;IACpB,sCAAsC;IACtC,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AA6BD,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -0,0 +1,65 @@
1
+ <script lang="ts">
2
+ /**
3
+ * UserPresence - Online/offline status indicator with optional label
4
+ */
5
+
6
+ export interface Props {
7
+ /** Current presence status */
8
+ status: 'online' | 'away' | 'dnd' | 'offline';
9
+ /** Whether to show a text label next to the dot */
10
+ showLabel?: boolean;
11
+ }
12
+
13
+ const { status, showLabel = false }: Props = $props();
14
+
15
+ const labelText: Record<string, string> = {
16
+ online: 'Online',
17
+ away: 'Away',
18
+ dnd: 'Do not disturb',
19
+ offline: 'Offline',
20
+ };
21
+ </script>
22
+
23
+ <span class="presence" aria-label={labelText[status]}>
24
+ <span class="presence__dot {status}"></span>
25
+ {#if showLabel}
26
+ <span class="presence__label">{labelText[status]}</span>
27
+ {/if}
28
+ </span>
29
+
30
+ <style>
31
+ .presence {
32
+ display: inline-flex;
33
+ align-items: center;
34
+ gap: var(--smrt-spacing-2, 0.375rem);
35
+ }
36
+
37
+ .presence__dot {
38
+ width: 8px;
39
+ height: 8px;
40
+ border-radius: var(--smrt-radius-full, 9999px);
41
+ flex-shrink: 0;
42
+ }
43
+
44
+ .presence__dot.online {
45
+ background: var(--smrt-color-success, #4caf50);
46
+ }
47
+
48
+ .presence__dot.away {
49
+ background: var(--smrt-color-warning, #ff9800);
50
+ }
51
+
52
+ .presence__dot.dnd {
53
+ background: var(--smrt-color-error, #f44336);
54
+ }
55
+
56
+ .presence__dot.offline {
57
+ background: var(--smrt-color-outline, #74777f);
58
+ }
59
+
60
+ .presence__label {
61
+ font-size: var(--smrt-typography-body-small-size, 0.75rem);
62
+ color: var(--smrt-color-on-surface-variant, #43474e);
63
+ line-height: 1;
64
+ }
65
+ </style>
@@ -0,0 +1,13 @@
1
+ /**
2
+ * UserPresence - Online/offline status indicator with optional label
3
+ */
4
+ export interface Props {
5
+ /** Current presence status */
6
+ status: 'online' | 'away' | 'dnd' | 'offline';
7
+ /** Whether to show a text label next to the dot */
8
+ showLabel?: boolean;
9
+ }
10
+ declare const UserPresence: import("svelte").Component<Props, {}, "">;
11
+ type UserPresence = ReturnType<typeof UserPresence>;
12
+ export default UserPresence;
13
+ //# sourceMappingURL=UserPresence.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserPresence.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/components/shared/UserPresence.svelte.ts"],"names":[],"mappings":"AAGA;;GAEG;AAEH,MAAM,WAAW,KAAK;IACpB,8BAA8B;IAC9B,MAAM,EAAE,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;IAC9C,mDAAmD;IACnD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAyBD,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,20 @@
1
+ // @vitest-environment jsdom
2
+ /**
3
+ * First component test in smrt-chat via the shared S11 harness (#1416). Avatar is
4
+ * the thin adapter over the library Avatar (maps avatarUrl→src, dnd→busy).
5
+ */
6
+ import { expectNoA11yViolations, render, screen, } from '@happyvertical/smrt-vitest/svelte';
7
+ import { describe, expect, it } from 'vitest';
8
+ import Avatar from '../Avatar.svelte';
9
+ describe('Avatar', () => {
10
+ it('renders the initials derived from the name', () => {
11
+ render(Avatar, { props: { name: 'Ada Lovelace' } });
12
+ expect(screen.getByText('AL')).toBeInTheDocument();
13
+ });
14
+ it('is axe-clean', async () => {
15
+ const { container } = render(Avatar, {
16
+ props: { name: 'Ada Lovelace', onlineStatus: 'online' },
17
+ });
18
+ await expectNoA11yViolations(container);
19
+ });
20
+ });
@@ -0,0 +1,29 @@
1
+ // @vitest-environment jsdom
2
+ /**
3
+ * Component coverage for LinkPreview via the shared S11 harness (#1416).
4
+ */
5
+ import { expectNoA11yViolations, render, screen, } from '@happyvertical/smrt-vitest/svelte';
6
+ import { describe, expect, it } from 'vitest';
7
+ import LinkPreview from '../LinkPreview.svelte';
8
+ describe('LinkPreview', () => {
9
+ it('renders a link with title, description, and hostname', () => {
10
+ render(LinkPreview, {
11
+ props: {
12
+ url: 'https://example.com/article',
13
+ title: 'An Article',
14
+ description: 'A short summary',
15
+ },
16
+ });
17
+ const link = screen.getByRole('link');
18
+ expect(link).toHaveAttribute('href', 'https://example.com/article');
19
+ expect(screen.getByText('An Article')).toBeInTheDocument();
20
+ expect(screen.getByText('A short summary')).toBeInTheDocument();
21
+ expect(screen.getByText('example.com')).toBeInTheDocument();
22
+ });
23
+ it('is axe-clean', async () => {
24
+ const { container } = render(LinkPreview, {
25
+ props: { url: 'https://example.com', title: 'Example' },
26
+ });
27
+ await expectNoA11yViolations(container);
28
+ });
29
+ });
@@ -0,0 +1,21 @@
1
+ // @vitest-environment jsdom
2
+ /**
3
+ * Component coverage for MessageBubble via the shared S11 harness (#1416).
4
+ */
5
+ import { expectNoA11yViolations, render, screen, } from '@happyvertical/smrt-vitest/svelte';
6
+ import { describe, expect, it } from 'vitest';
7
+ import MessageBubble from '../MessageBubble.svelte';
8
+ describe('MessageBubble', () => {
9
+ it('renders the message content', () => {
10
+ render(MessageBubble, {
11
+ props: { content: 'Hello there', isOwn: false },
12
+ });
13
+ expect(screen.getByText('Hello there')).toBeInTheDocument();
14
+ });
15
+ it('is axe-clean for an own message', async () => {
16
+ const { container } = render(MessageBubble, {
17
+ props: { content: 'Hi', isOwn: true },
18
+ });
19
+ await expectNoA11yViolations(container);
20
+ });
21
+ });