ui-svelte 0.2.11 → 0.2.13

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 (225) hide show
  1. package/README.md +2 -2
  2. package/dist/charts/ArcChart.svelte +9 -14
  3. package/dist/charts/ArcChart.svelte.d.ts +3 -3
  4. package/dist/charts/AreaChart.svelte +347 -118
  5. package/dist/charts/AreaChart.svelte.d.ts +33 -4
  6. package/dist/charts/BarChart.svelte +288 -66
  7. package/dist/charts/BarChart.svelte.d.ts +26 -1
  8. package/dist/charts/Candlestick.svelte +53 -50
  9. package/dist/charts/Candlestick.svelte.d.ts +8 -8
  10. package/dist/charts/LineChart.svelte +391 -91
  11. package/dist/charts/LineChart.svelte.d.ts +26 -3
  12. package/dist/charts/PieChart.svelte +333 -92
  13. package/dist/charts/PieChart.svelte.d.ts +33 -5
  14. package/dist/charts/css/arc-chart.css +3 -3
  15. package/dist/charts/css/area-chart.css +127 -29
  16. package/dist/charts/css/bar-chart.css +114 -8
  17. package/dist/charts/css/candlestick.css +2 -0
  18. package/dist/charts/css/line-chart.css +111 -13
  19. package/dist/charts/css/pie-chart.css +92 -20
  20. package/dist/control/Audio.svelte +86 -44
  21. package/dist/control/Audio.svelte.d.ts +4 -1
  22. package/dist/control/Button.svelte +18 -27
  23. package/dist/control/Button.svelte.d.ts +3 -2
  24. package/dist/control/Fab.svelte +103 -0
  25. package/dist/control/Fab.svelte.d.ts +25 -0
  26. package/dist/control/IconButton.svelte +17 -27
  27. package/dist/control/IconButton.svelte.d.ts +3 -3
  28. package/dist/control/Image.svelte +123 -0
  29. package/dist/control/Image.svelte.d.ts +13 -0
  30. package/dist/control/Record.svelte +141 -98
  31. package/dist/control/Record.svelte.d.ts +2 -1
  32. package/dist/control/ToggleGroup.svelte +22 -8
  33. package/dist/control/ToggleGroup.svelte.d.ts +2 -1
  34. package/dist/control/ToggleTheme.svelte +13 -11
  35. package/dist/control/ToggleTheme.svelte.d.ts +3 -2
  36. package/dist/control/Video.svelte +57 -29
  37. package/dist/control/Video.svelte.d.ts +1 -0
  38. package/dist/control/css/btn.css +200 -152
  39. package/dist/control/css/fab.css +84 -0
  40. package/dist/control/css/image.css +56 -0
  41. package/dist/control/css/media.css +95 -30
  42. package/dist/control/css/toggle-group.css +253 -84
  43. package/dist/control/css/video.css +1 -14
  44. package/dist/css/animations.css +5 -9
  45. package/dist/css/base.css +13 -347
  46. package/dist/css/decorations.css +561 -0
  47. package/dist/css/rich-text.css +485 -0
  48. package/dist/css/transitions.css +158 -0
  49. package/dist/css/typography.css +291 -0
  50. package/dist/css/utilities.css +0 -4
  51. package/dist/display/Accordion.svelte +28 -4
  52. package/dist/display/Accordion.svelte.d.ts +2 -1
  53. package/dist/display/Alert.svelte +32 -12
  54. package/dist/display/Alert.svelte.d.ts +2 -3
  55. package/dist/display/Avatar.svelte +23 -18
  56. package/dist/display/Avatar.svelte.d.ts +4 -1
  57. package/dist/display/AvatarGroup.svelte +20 -18
  58. package/dist/display/AvatarGroup.svelte.d.ts +6 -3
  59. package/dist/display/Badge.svelte +11 -4
  60. package/dist/display/Badge.svelte.d.ts +2 -1
  61. package/dist/display/Card.svelte +15 -14
  62. package/dist/display/Card.svelte.d.ts +2 -3
  63. package/dist/display/Carousel.svelte +130 -99
  64. package/dist/display/Carousel.svelte.d.ts +6 -4
  65. package/dist/display/ChatBox.svelte +245 -106
  66. package/dist/display/ChatBox.svelte.d.ts +32 -5
  67. package/dist/display/Chip.svelte +31 -17
  68. package/dist/display/Chip.svelte.d.ts +3 -2
  69. package/dist/display/Code.svelte +7 -4
  70. package/dist/display/Code.svelte.d.ts +1 -0
  71. package/dist/display/Collapsible.svelte +30 -4
  72. package/dist/display/Collapsible.svelte.d.ts +2 -1
  73. package/dist/display/Countdown.svelte +169 -0
  74. package/dist/display/Countdown.svelte.d.ts +21 -0
  75. package/dist/display/Empty.svelte +37 -3
  76. package/dist/display/Empty.svelte.d.ts +3 -0
  77. package/dist/display/Item.svelte +42 -11
  78. package/dist/display/Item.svelte.d.ts +4 -2
  79. package/dist/display/Map.svelte +488 -0
  80. package/dist/display/Map.svelte.d.ts +44 -0
  81. package/dist/display/Marquee.svelte +0 -2
  82. package/dist/display/Section.svelte +14 -12
  83. package/dist/display/Section.svelte.d.ts +2 -3
  84. package/dist/display/Skeleton.svelte +32 -0
  85. package/dist/display/Skeleton.svelte.d.ts +10 -0
  86. package/dist/display/Table.svelte +94 -132
  87. package/dist/display/Table.svelte.d.ts +10 -1
  88. package/dist/display/css/accordion.css +349 -52
  89. package/dist/display/css/alert.css +38 -18
  90. package/dist/display/css/avatar-group.css +38 -75
  91. package/dist/display/css/avatar.css +139 -121
  92. package/dist/display/css/badge.css +50 -27
  93. package/dist/display/css/card.css +123 -71
  94. package/dist/display/css/carousel.css +25 -5
  95. package/dist/display/css/chat-box.css +158 -26
  96. package/dist/display/css/chip.css +142 -68
  97. package/dist/display/css/code.css +2 -6
  98. package/dist/display/css/collapsible.css +349 -45
  99. package/dist/display/css/countdown.css +206 -0
  100. package/dist/display/css/divider.css +8 -6
  101. package/dist/display/css/empty.css +7 -0
  102. package/dist/display/css/item.css +330 -84
  103. package/dist/display/css/map.css +164 -0
  104. package/dist/display/css/marquee.css +0 -3
  105. package/dist/display/css/section.css +89 -65
  106. package/dist/display/css/skeleton.css +58 -0
  107. package/dist/display/css/table.css +309 -193
  108. package/dist/form/Checkbox.svelte +11 -5
  109. package/dist/form/Checkbox.svelte.d.ts +2 -1
  110. package/dist/form/ColorField.svelte +601 -0
  111. package/dist/form/ColorField.svelte.d.ts +29 -0
  112. package/dist/form/ComboBox.svelte +24 -9
  113. package/dist/form/ComboBox.svelte.d.ts +2 -2
  114. package/dist/form/CsvField.svelte +62 -136
  115. package/dist/form/CsvField.svelte.d.ts +2 -2
  116. package/dist/form/DateField.svelte +33 -15
  117. package/dist/form/DateField.svelte.d.ts +2 -1
  118. package/dist/form/DateRange.svelte +436 -0
  119. package/dist/form/DateRange.svelte.d.ts +24 -0
  120. package/dist/form/DragDrop.svelte +578 -0
  121. package/dist/form/DragDrop.svelte.d.ts +33 -0
  122. package/dist/form/Dropzone.svelte +28 -8
  123. package/dist/form/Dropzone.svelte.d.ts +2 -2
  124. package/dist/form/Editor.svelte +626 -0
  125. package/dist/form/Editor.svelte.d.ts +50 -0
  126. package/dist/form/ImageCropper.svelte +422 -61
  127. package/dist/form/ImageCropper.svelte.d.ts +15 -1
  128. package/dist/form/{PasswordStrength.svelte → PasswordField.svelte} +58 -24
  129. package/dist/form/{PasswordStrength.svelte.d.ts → PasswordField.svelte.d.ts} +6 -5
  130. package/dist/form/PhoneField.svelte +26 -14
  131. package/dist/form/PhoneField.svelte.d.ts +4 -3
  132. package/dist/form/PinField.svelte +39 -31
  133. package/dist/form/PinField.svelte.d.ts +3 -3
  134. package/dist/form/RadioGroup.svelte +9 -5
  135. package/dist/form/RadioGroup.svelte.d.ts +1 -1
  136. package/dist/form/Select.svelte +20 -19
  137. package/dist/form/Select.svelte.d.ts +2 -2
  138. package/dist/form/Slider.svelte +10 -4
  139. package/dist/form/Slider.svelte.d.ts +2 -1
  140. package/dist/form/TextField.svelte +29 -11
  141. package/dist/form/TextField.svelte.d.ts +5 -4
  142. package/dist/form/Textarea.svelte +15 -6
  143. package/dist/form/Textarea.svelte.d.ts +2 -2
  144. package/dist/form/Toggle.svelte +7 -3
  145. package/dist/form/Toggle.svelte.d.ts +1 -1
  146. package/dist/form/css/checkbox.css +18 -2
  147. package/dist/form/css/color-field.css +141 -0
  148. package/dist/form/css/control.css +193 -82
  149. package/dist/form/css/csv-field.css +221 -0
  150. package/dist/form/css/date-range.css +122 -0
  151. package/dist/form/css/date.css +24 -2
  152. package/dist/form/css/drag-drop.css +234 -0
  153. package/dist/form/css/dropzone.css +153 -34
  154. package/dist/form/css/editor.css +367 -0
  155. package/dist/form/css/field.css +4 -0
  156. package/dist/form/css/image-cropper.css +242 -20
  157. package/dist/form/css/radio-group.css +26 -1
  158. package/dist/form/css/select.css +2 -2
  159. package/dist/form/css/slider.css +37 -0
  160. package/dist/form/css/textarea.css +178 -75
  161. package/dist/form/css/toggle.css +15 -3
  162. package/dist/hooks/use-chat.svelte.js +1 -1
  163. package/dist/hooks/use-form.svelte.js +3 -3
  164. package/dist/hooks/use-search.svelte.js +0 -3
  165. package/dist/hooks/use-table.svelte.d.ts +1 -0
  166. package/dist/hooks/use-table.svelte.js +6 -0
  167. package/dist/icons/index.d.ts +34 -2
  168. package/dist/icons/index.js +36 -4
  169. package/dist/index.css +44 -49
  170. package/dist/index.d.ts +14 -4
  171. package/dist/index.js +13 -3
  172. package/dist/layout/AppBar.svelte +22 -14
  173. package/dist/layout/AppBar.svelte.d.ts +2 -1
  174. package/dist/layout/Footer.svelte +19 -11
  175. package/dist/layout/Footer.svelte.d.ts +2 -1
  176. package/dist/layout/Provider.svelte +32 -9
  177. package/dist/layout/Provider.svelte.d.ts +3 -1
  178. package/dist/layout/Sidebar.svelte +17 -8
  179. package/dist/layout/Sidebar.svelte.d.ts +2 -1
  180. package/dist/layout/css/app-bar.css +63 -66
  181. package/dist/layout/css/footer.css +62 -65
  182. package/dist/layout/css/sidebar.css +120 -59
  183. package/dist/navigation/BottomNav.svelte +51 -14
  184. package/dist/navigation/FooterGroup.svelte +1 -1
  185. package/dist/navigation/NavMenu.svelte +47 -23
  186. package/dist/navigation/NavMenu.svelte.d.ts +29 -0
  187. package/dist/navigation/Pagination.svelte +158 -0
  188. package/dist/navigation/Pagination.svelte.d.ts +18 -0
  189. package/dist/navigation/SideNav.svelte +30 -25
  190. package/dist/navigation/SideNav.svelte.d.ts +2 -3
  191. package/dist/navigation/Tabs.svelte +17 -7
  192. package/dist/navigation/Tabs.svelte.d.ts +2 -2
  193. package/dist/navigation/css/bottom-nav.css +319 -257
  194. package/dist/navigation/css/footer-group.css +1 -1
  195. package/dist/navigation/css/footer-nav.css +1 -1
  196. package/dist/navigation/css/nav-menu.css +331 -106
  197. package/dist/navigation/css/pagination.css +74 -0
  198. package/dist/navigation/css/side-nav.css +514 -75
  199. package/dist/navigation/css/tabs.css +246 -52
  200. package/dist/overlay/AlertDialog.svelte +58 -0
  201. package/dist/overlay/AlertDialog.svelte.d.ts +14 -25
  202. package/dist/overlay/Command.svelte +347 -0
  203. package/dist/overlay/Command.svelte.d.ts +33 -25
  204. package/dist/overlay/Drawer.svelte +49 -21
  205. package/dist/overlay/Drawer.svelte.d.ts +2 -2
  206. package/dist/overlay/Dropdown.svelte +3 -3
  207. package/dist/overlay/Modal.svelte +51 -16
  208. package/dist/overlay/Modal.svelte.d.ts +3 -3
  209. package/dist/overlay/Toast.svelte +41 -17
  210. package/dist/overlay/Toast.svelte.d.ts +1 -1
  211. package/dist/overlay/Tooltip.svelte +36 -27
  212. package/dist/overlay/Tooltip.svelte.d.ts +2 -2
  213. package/dist/overlay/css/command.css +68 -0
  214. package/dist/overlay/css/drawer.css +63 -24
  215. package/dist/overlay/css/dropdown.css +1 -1
  216. package/dist/overlay/css/hovercard.css +1 -1
  217. package/dist/overlay/css/modal.css +79 -27
  218. package/dist/overlay/css/toast.css +40 -24
  219. package/dist/overlay/css/tooltip.css +110 -67
  220. package/dist/stores/theme.svelte.js +44 -12
  221. package/dist/stores/toast.svelte.d.ts +4 -4
  222. package/dist/stores/toast.svelte.js +2 -2
  223. package/package.json +1 -1
  224. package/dist/utils/charts.d.ts +0 -27
  225. package/dist/utils/charts.js +0 -140
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { ChatState } from '../hooks/use-chat.svelte.js';
2
+ import type { ChatState, Message } from '../hooks/use-chat.svelte.js';
3
3
  import {
4
4
  Attach24RegularIcon,
5
5
  Camera24RegularIcon,
@@ -8,27 +8,48 @@
8
8
  Search24RegularIcon,
9
9
  Send24RegularIcon
10
10
  } from '../icons/index.js';
11
- import { Avatar, Record, Audio, Button, Icon } from '../index.js';
11
+ import { Avatar, Record as RecordAudio, Audio, Button, Icon } from '../index.js';
12
12
  import { cn } from '../utils/class-names.js';
13
13
  import type { Snippet } from 'svelte';
14
14
 
15
+ export interface PromptMessage {
16
+ id: string;
17
+ role: 'user' | 'assistant' | 'system';
18
+ content: string;
19
+ timestamp?: Date | string;
20
+ isStreaming?: boolean;
21
+ metadata?: Record<string, any>;
22
+ }
23
+
24
+ export interface PromptState {
25
+ messages: PromptMessage[];
26
+ isLoading: boolean;
27
+ isStreaming: boolean;
28
+ error: any;
29
+ sendMessage: (content: string) => Promise<void>;
30
+ stopGeneration?: () => void;
31
+ }
32
+
15
33
  type Props = {
16
34
  rootClass?: string;
17
- chat: ChatState;
18
- currentUserId: string;
19
- variant?:
20
- | 'primary'
21
- | 'secondary'
22
- | 'success'
23
- | 'info'
24
- | 'warning'
25
- | 'danger'
26
- | 'muted'
27
- | 'outlined';
28
- userName: string;
35
+ mode?: 'conversation' | 'prompt';
36
+ chat?: ChatState;
37
+ currentUserId?: string;
38
+ prompt?: PromptState;
39
+ color?: 'primary' | 'secondary' | 'muted' | 'success' | 'info' | 'warning' | 'danger';
40
+ variant?: 'solid' | 'soft' | 'outlined';
41
+ userName?: string;
29
42
  userAvatar?: string;
30
43
  userStatus?: string;
44
+ showHeader?: boolean;
31
45
  headerActions?: Snippet;
46
+ placeholder?: string;
47
+ showAttachments?: boolean;
48
+ showVoiceNote?: boolean;
49
+ showCamera?: boolean;
50
+ messageRenderer?: Snippet<[PromptMessage | Message]>;
51
+ assistantAvatar?: string;
52
+ assistantName?: string;
32
53
  onVoiceNote?: (blob: Blob, url: string) => void;
33
54
  onFileAttach?: (file: File) => void;
34
55
  onCameraCapture?: () => void;
@@ -36,26 +57,68 @@
36
57
 
37
58
  let {
38
59
  rootClass,
60
+ mode = 'conversation',
39
61
  chat,
40
- currentUserId,
41
- variant = 'primary',
42
- userName,
62
+ currentUserId = '',
63
+ prompt,
64
+ color = 'primary',
65
+ variant = 'solid',
66
+ userName = '',
43
67
  userAvatar,
44
68
  userStatus = 'Online',
69
+ showHeader = true,
45
70
  headerActions,
71
+ placeholder = 'Type a message...',
72
+ showAttachments = true,
73
+ showVoiceNote = true,
74
+ showCamera = false,
75
+ messageRenderer,
76
+ assistantAvatar,
77
+ assistantName = 'Assistant',
46
78
  onVoiceNote,
47
79
  onFileAttach,
48
80
  onCameraCapture
49
81
  }: Props = $props();
50
82
 
83
+ const colors = {
84
+ primary: 'is-primary',
85
+ secondary: 'is-secondary',
86
+ muted: 'is-muted',
87
+ success: 'is-success',
88
+ info: 'is-info',
89
+ warning: 'is-warning',
90
+ danger: 'is-danger'
91
+ };
92
+
93
+ const variants = {
94
+ solid: 'is-solid',
95
+ soft: 'is-soft',
96
+ outlined: 'is-outlined'
97
+ };
98
+
51
99
  let messageInput = $state('');
52
100
  let messagesContainer: HTMLDivElement;
53
- let fileInput: HTMLInputElement;
101
+ let fileInput = $state<HTMLInputElement>();
54
102
  let isRecording = $state(false);
55
103
 
104
+ const isPromptMode = $derived(mode === 'prompt');
105
+ const messages = $derived(isPromptMode ? (prompt?.messages ?? []) : (chat?.messages ?? []));
106
+ const isLoading = $derived(
107
+ isPromptMode ? (prompt?.isLoading ?? false) : (chat?.isLoading ?? false)
108
+ );
109
+ const isSending = $derived(
110
+ isPromptMode ? (prompt?.isLoading ?? false) : (chat?.isSending ?? false)
111
+ );
112
+ const isStreaming = $derived(isPromptMode ? (prompt?.isStreaming ?? false) : false);
113
+ const hasMore = $derived(isPromptMode ? false : (chat?.hasMore ?? false));
114
+
56
115
  function handleSend() {
57
116
  if (messageInput.trim()) {
58
- chat.sendMessage(messageInput);
117
+ if (isPromptMode && prompt) {
118
+ prompt.sendMessage(messageInput);
119
+ } else if (chat) {
120
+ chat.sendMessage(messageInput);
121
+ }
59
122
  messageInput = '';
60
123
  scrollToBottom();
61
124
  }
@@ -79,7 +142,7 @@
79
142
  function handleFileSelect(e: Event) {
80
143
  const target = e.target as HTMLInputElement;
81
144
  const file = target.files?.[0];
82
- if (file) {
145
+ if (file && chat) {
83
146
  if (onFileAttach) {
84
147
  onFileAttach(file);
85
148
  } else {
@@ -99,7 +162,7 @@
99
162
  function handleRecordingComplete(blob: Blob, url: string) {
100
163
  if (onVoiceNote) {
101
164
  onVoiceNote(blob, url);
102
- } else {
165
+ } else if (chat) {
103
166
  chat.sendMessage('Voice note', 'voice', {
104
167
  fileUrl: url,
105
168
  fileName: 'voice-note.webm'
@@ -112,138 +175,214 @@
112
175
  isRecording = !isRecording;
113
176
  }
114
177
 
115
- function formatTime(timestamp: Date | string) {
178
+ function formatTime(timestamp: Date | string | undefined) {
179
+ if (!timestamp) return '';
116
180
  const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;
117
181
  return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
118
182
  }
119
183
 
184
+ function isOwnMessage(message: PromptMessage | Message): boolean {
185
+ if (isPromptMode) {
186
+ return (message as PromptMessage).role === 'user';
187
+ }
188
+ return (message as Message).senderId === currentUserId;
189
+ }
190
+
191
+ function getMessageRole(
192
+ message: PromptMessage | Message
193
+ ): 'user' | 'assistant' | 'system' | 'other' {
194
+ if (isPromptMode) {
195
+ return (message as PromptMessage).role;
196
+ }
197
+ return (message as Message).senderId === currentUserId ? 'user' : 'other';
198
+ }
199
+
120
200
  $effect(() => {
121
- if (chat.messages.length > 0) {
201
+ if (messages.length > 0) {
122
202
  scrollToBottom();
123
203
  }
124
204
  });
125
205
  </script>
126
206
 
127
- <div class={cn('chatbox', `chatbox-${variant}`, rootClass)}>
128
- <!-- Header -->
129
- <div class="chatbox-header">
130
- <div class="chatbox-header-start">
131
- <Avatar src={userAvatar} alt={userName} size="sm" />
132
- <div class="chatbox-header-center">
133
- <div class="chatbox-header-name">{userName}</div>
134
- <div class="chatbox-header-status">{userStatus}</div>
207
+ <div
208
+ class={cn('chatbox', colors[color], variants[variant], isPromptMode && 'is-prompt', rootClass)}
209
+ >
210
+ {#if showHeader && !isPromptMode}
211
+ <div class="chatbox-header">
212
+ <div class="chatbox-header-start">
213
+ <Avatar src={userAvatar} alt={userName} size="sm" />
214
+ <div class="chatbox-header-center">
215
+ <div class="chatbox-header-name">{userName}</div>
216
+ <div class="chatbox-header-status">{userStatus}</div>
217
+ </div>
218
+ </div>
219
+ <div class="chatbox-header-end">
220
+ {#if headerActions}
221
+ {@render headerActions()}
222
+ {:else}
223
+ <Button size="sm" {color} variant="ghost">
224
+ <Icon icon={Search24RegularIcon} />
225
+ </Button>
226
+ <Button size="sm" {color} variant="ghost">
227
+ <Icon icon={MoreVertical24RegularIcon} />
228
+ </Button>
229
+ {/if}
135
230
  </div>
136
231
  </div>
137
- <div class="chatbox-header-end">
138
- {#if headerActions}
139
- {@render headerActions()}
140
- {:else}
141
- <Button size="sm" variant="ghost">
142
- <Icon icon={Search24RegularIcon} />
143
- </Button>
144
- <Button size="sm" variant="ghost">
145
- <Icon icon={MoreVertical24RegularIcon} />
146
- </Button>
147
- {/if}
148
- </div>
149
- </div>
232
+ {/if}
150
233
 
151
- <!-- Messages -->
152
234
  <div class="chatbox-messages" bind:this={messagesContainer}>
153
- {#if chat.hasMore}
154
- <button class="load-more-btn" onclick={() => chat.loadMore()} disabled={chat.isLoading}>
155
- {chat.isLoading ? 'Loading...' : 'Load more messages'}
235
+ {#if hasMore && chat}
236
+ <button class="load-more-btn" onclick={() => chat?.loadMore()} disabled={isLoading}>
237
+ {isLoading ? 'Loading...' : 'Load more messages'}
156
238
  </button>
157
239
  {/if}
158
240
 
159
- {#each chat.messages as message}
160
- {@const isOwn = message.senderId === currentUserId}
161
- <div class="message-wrapper" class:own={isOwn}>
162
- <div class="message" class:own={isOwn}>
163
- {#if message.type === 'image'}
241
+ {#each messages as message}
242
+ {@const isOwn = isOwnMessage(message)}
243
+ {@const role = getMessageRole(message)}
244
+ <div
245
+ class="message-wrapper"
246
+ class:own={isOwn}
247
+ class:assistant={role === 'assistant'}
248
+ class:system={role === 'system'}
249
+ >
250
+ {#if isPromptMode && role === 'assistant'}
251
+ <div class="message-avatar">
252
+ <Avatar src={assistantAvatar} alt={assistantName} size="sm" />
253
+ </div>
254
+ {/if}
255
+
256
+ <div
257
+ class="message"
258
+ class:own={isOwn}
259
+ class:assistant={role === 'assistant'}
260
+ class:system={role === 'system'}
261
+ >
262
+ {#if messageRenderer}
263
+ {@render messageRenderer(message)}
264
+ {:else if 'type' in message && message.type === 'image'}
164
265
  <!-- svelte-ignore a11y_img_redundant_alt -->
165
266
  <img src={message.metadata?.fileUrl} alt="Shared image" class="message-image" />
166
- {:else if message.type === ('voice' as any)}
167
- <Audio
168
- src={message.metadata?.fileUrl || ''}
169
- variant={isOwn ? variant : ('muted' as any)}
170
- />
171
- {:else if message.type === 'file'}
267
+ {:else if 'type' in message && message.type === ('voice' as any)}
268
+ <Audio src={message.metadata?.fileUrl || ''} color={isOwn ? color : 'muted'} />
269
+ {:else if 'type' in message && message.type === 'file'}
172
270
  <div class="message-file">
173
271
  <span class="message-file-icon">📎</span>
174
272
  <span class="message-file-name">{message.metadata?.fileName || 'File'}</span>
175
273
  </div>
176
274
  {:else}
177
- <div class="message-content">{message.content}</div>
275
+ <div class="message-content">
276
+ {message.content}
277
+ {#if isPromptMode && (message as PromptMessage).isStreaming}
278
+ <span class="streaming-cursor">▊</span>
279
+ {/if}
280
+ </div>
178
281
  {/if}
179
282
 
180
- <div class="message-meta">
181
- <span class="message-time">{formatTime(message.timestamp)}</span>
182
- {#if isOwn}
183
- <span class="message-status">
184
- {#if message.status === 'sending'}⏱{/if}
185
- {#if message.status === 'sent'}✓{/if}
186
- {#if message.status === 'delivered'}✓✓{/if}
187
- {#if message.status === 'read'}
188
- <span class="message-status-read">✓✓</span>
189
- {/if}
190
- {#if message.status === 'error'}⚠{/if}
191
- </span>
192
- {/if}
193
- </div>
283
+ {#if !isPromptMode || role !== 'system'}
284
+ <div class="message-meta">
285
+ <span class="message-time"
286
+ >{formatTime('timestamp' in message ? message.timestamp : undefined)}</span
287
+ >
288
+ {#if !isPromptMode && isOwn && 'status' in message}
289
+ <span class="message-status">
290
+ {#if message.status === 'sending'}⏱{/if}
291
+ {#if message.status === 'sent'}✓{/if}
292
+ {#if message.status === 'delivered'}✓✓{/if}
293
+ {#if message.status === 'read'}
294
+ <span class="message-status-read">✓✓</span>
295
+ {/if}
296
+ {#if message.status === 'error'}⚠{/if}
297
+ </span>
298
+ {/if}
299
+ </div>
300
+ {/if}
194
301
  </div>
302
+
303
+ {#if isPromptMode && role === 'user'}
304
+ <div class="message-avatar">
305
+ <Avatar src={userAvatar} alt="You" size="sm" />
306
+ </div>
307
+ {/if}
195
308
  </div>
196
309
  {/each}
310
+
311
+ {#if isStreaming}
312
+ <div class="message-wrapper assistant">
313
+ <div class="message-avatar">
314
+ <Avatar src={assistantAvatar} alt={assistantName} size="sm" />
315
+ </div>
316
+ <div class="message assistant">
317
+ <div class="typing-indicator">
318
+ <span></span>
319
+ <span></span>
320
+ <span></span>
321
+ </div>
322
+ </div>
323
+ </div>
324
+ {/if}
197
325
  </div>
198
326
 
199
- <!-- Recording Overlay -->
200
- {#if isRecording}
327
+ {#if isRecording && !isPromptMode}
201
328
  <div class="chatbox-record-overlay">
202
- <Record name="voice-note" {variant} onRecordingComplete={handleRecordingComplete} />
329
+ <RecordAudio name="voice-note" {color} onRecordingComplete={handleRecordingComplete} />
203
330
  </div>
204
331
  {/if}
205
332
 
206
- <!-- Footer -->
207
333
  <div class="chatbox-footer">
208
334
  <label class="chatbox-input-wrapper">
209
335
  <label class="flex-1">
210
336
  <input
211
337
  bind:value={messageInput}
212
- placeholder="Type a message..."
338
+ {placeholder}
213
339
  onkeydown={handleKeyPress}
214
- disabled={chat.isSending}
340
+ disabled={isSending}
215
341
  class="chatbox-input"
216
342
  />
217
343
  </label>
218
344
 
219
- <div class="chatbox-input-actions">
220
- <Button size="sm" variant="ghost" onclick={toggleRecording}>
221
- <Icon icon={Microphone2LinearIcon} />
345
+ {#if !isPromptMode}
346
+ <div class="chatbox-input-actions">
347
+ {#if showVoiceNote}
348
+ <Button size="sm" {color} variant="ghost" onclick={toggleRecording}>
349
+ <Icon icon={Microphone2LinearIcon} />
350
+ </Button>
351
+ {/if}
352
+ {#if showAttachments}
353
+ <Button size="sm" {color} variant="ghost" onclick={() => fileInput?.click()}>
354
+ <Icon icon={Attach24RegularIcon} />
355
+ </Button>
356
+ <input
357
+ type="file"
358
+ bind:this={fileInput}
359
+ onchange={handleFileSelect}
360
+ style="display: none"
361
+ />
362
+ {/if}
363
+ {#if showCamera && onCameraCapture}
364
+ <Button size="sm" {color} variant="ghost" onclick={onCameraCapture}>
365
+ <Icon icon={Camera24RegularIcon} />
366
+ </Button>
367
+ {/if}
368
+ </div>
369
+ {/if}
370
+
371
+ {#if isPromptMode && isStreaming && prompt?.stopGeneration}
372
+ <Button size="sm" color="danger" variant="soft" onclick={() => prompt?.stopGeneration?.()}>
373
+ Stop
222
374
  </Button>
223
- <Button size="sm" variant="ghost" onclick={() => fileInput.click()}>
224
- <Icon icon={Attach24RegularIcon} />
375
+ {:else}
376
+ <Button
377
+ size="sm"
378
+ {color}
379
+ variant={variant === 'outlined' ? 'outlined' : 'solid'}
380
+ onclick={handleSend}
381
+ isDisabled={isSending || !messageInput.trim()}
382
+ >
383
+ <Icon icon={Send24RegularIcon} />
225
384
  </Button>
226
- <input
227
- type="file"
228
- bind:this={fileInput}
229
- onchange={handleFileSelect}
230
- style="display: none"
231
- />
232
- {#if onCameraCapture}
233
- <Button size="sm" variant="ghost" onclick={onCameraCapture}>
234
- <Icon icon={Camera24RegularIcon} />
235
- </Button>
236
- {/if}
237
- </div>
238
-
239
- <Button
240
- size="sm"
241
- {variant}
242
- onclick={handleSend}
243
- isDisabled={chat.isSending || !messageInput.trim()}
244
- >
245
- <Icon icon={Send24RegularIcon} />
246
- </Button>
385
+ {/if}
247
386
  </label>
248
387
  </div>
249
388
  </div>
@@ -1,14 +1,41 @@
1
- import type { ChatState } from '../hooks/use-chat.svelte.js';
1
+ import type { ChatState, Message } from '../hooks/use-chat.svelte.js';
2
2
  import type { Snippet } from 'svelte';
3
+ export interface PromptMessage {
4
+ id: string;
5
+ role: 'user' | 'assistant' | 'system';
6
+ content: string;
7
+ timestamp?: Date | string;
8
+ isStreaming?: boolean;
9
+ metadata?: Record<string, any>;
10
+ }
11
+ export interface PromptState {
12
+ messages: PromptMessage[];
13
+ isLoading: boolean;
14
+ isStreaming: boolean;
15
+ error: any;
16
+ sendMessage: (content: string) => Promise<void>;
17
+ stopGeneration?: () => void;
18
+ }
3
19
  type Props = {
4
20
  rootClass?: string;
5
- chat: ChatState;
6
- currentUserId: string;
7
- variant?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'muted' | 'outlined';
8
- userName: string;
21
+ mode?: 'conversation' | 'prompt';
22
+ chat?: ChatState;
23
+ currentUserId?: string;
24
+ prompt?: PromptState;
25
+ color?: 'primary' | 'secondary' | 'muted' | 'success' | 'info' | 'warning' | 'danger';
26
+ variant?: 'solid' | 'soft' | 'outlined';
27
+ userName?: string;
9
28
  userAvatar?: string;
10
29
  userStatus?: string;
30
+ showHeader?: boolean;
11
31
  headerActions?: Snippet;
32
+ placeholder?: string;
33
+ showAttachments?: boolean;
34
+ showVoiceNote?: boolean;
35
+ showCamera?: boolean;
36
+ messageRenderer?: Snippet<[PromptMessage | Message]>;
37
+ assistantAvatar?: string;
38
+ assistantName?: string;
12
39
  onVoiceNote?: (blob: Blob, url: string) => void;
13
40
  onFileAttach?: (file: File) => void;
14
41
  onCameraCapture?: () => void;
@@ -2,14 +2,16 @@
2
2
  import { cn } from '../utils/class-names.js';
3
3
  import type { Snippet } from 'svelte';
4
4
  import type { IconData } from './Icon.svelte';
5
- import { Icon } from '../index.js';
5
+ import { Icon, IconButton } from '../index.js';
6
+ import { Dismiss24RegularIcon } from '../icons/index.js';
6
7
 
7
8
  type Props = {
8
9
  children: Snippet;
9
10
  onclose?: () => void;
10
11
  type?: 'solid' | 'soft';
11
- variant?: 'primary' | 'secondary' | 'muted' | 'success' | 'info' | 'danger' | 'warning';
12
- size?: 'sm' | 'md' | 'lg';
12
+ color?: 'primary' | 'secondary' | 'muted' | 'success' | 'info' | 'danger' | 'warning';
13
+ variant?: 'solid' | 'soft' | 'outlined' | 'overlay';
14
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
13
15
  class?: string;
14
16
  startIcon?: IconData;
15
17
  endIcon?: IconData;
@@ -20,40 +22,47 @@
20
22
  const {
21
23
  children,
22
24
  onclose,
23
- variant = 'primary',
24
- size = 'sm',
25
+ color = 'primary',
26
+ variant = 'solid',
27
+ size = 'md',
25
28
  class: className,
26
29
  startIcon,
27
- endIcon,
28
- hasShadow,
29
- isSolid
30
+ endIcon
30
31
  }: Props = $props();
31
32
 
32
- const variants = {
33
+ const colors = {
33
34
  primary: 'is-primary',
34
35
  secondary: 'is-secondary',
35
36
  muted: 'is-muted',
36
37
  success: 'is-success',
37
38
  info: 'is-info',
38
- warning: 'is-warning',
39
- danger: 'is-danger'
39
+ danger: 'is-danger',
40
+ warning: 'is-warning'
41
+ };
42
+
43
+ const variants = {
44
+ solid: 'is-solid',
45
+ soft: 'is-soft',
46
+ outlined: 'is-outlined',
47
+ overlay: 'is-overlay'
40
48
  };
41
49
 
42
50
  const sizes = {
51
+ xs: 'is-xs',
43
52
  sm: 'is-sm',
44
53
  md: 'is-md',
45
- lg: 'is-lg'
54
+ lg: 'is-lg',
55
+ xl: 'is-xl'
46
56
  };
47
57
  </script>
48
58
 
49
- <button
50
- onclick={() => onclose?.()}
59
+ <div
51
60
  class={cn(
52
61
  'chip',
53
62
  variants[variant],
54
63
  sizes[size],
55
- isSolid && 'is-solid',
56
- hasShadow && 'has-shadow',
64
+ colors[color],
65
+ onclose && 'has-close',
57
66
  className
58
67
  )}
59
68
  >
@@ -64,4 +73,9 @@
64
73
  {#if endIcon}
65
74
  <Icon icon={endIcon} />
66
75
  {/if}
67
- </button>
76
+ {#if onclose}
77
+ <button class="chip-close" onclick={() => onclose?.()}>
78
+ <Icon icon={Dismiss24RegularIcon} />
79
+ </button>
80
+ {/if}
81
+ </div>
@@ -4,8 +4,9 @@ type Props = {
4
4
  children: Snippet;
5
5
  onclose?: () => void;
6
6
  type?: 'solid' | 'soft';
7
- variant?: 'primary' | 'secondary' | 'muted' | 'success' | 'info' | 'danger' | 'warning';
8
- size?: 'sm' | 'md' | 'lg';
7
+ color?: 'primary' | 'secondary' | 'muted' | 'success' | 'info' | 'danger' | 'warning';
8
+ variant?: 'solid' | 'soft' | 'outlined' | 'overlay';
9
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
9
10
  class?: string;
10
11
  startIcon?: IconData;
11
12
  endIcon?: IconData;
@@ -13,16 +13,18 @@
13
13
  darkTheme?: string;
14
14
  disableCopy?: boolean;
15
15
  hideLang?: boolean;
16
+ copyContent?: string;
16
17
  class?: string;
17
18
  };
18
19
 
19
20
  let {
20
21
  code,
21
22
  lang,
22
- lightTheme = 'catppuccin-latte',
23
+ lightTheme = 'github-light',
23
24
  darkTheme = 'catppuccin-frappe',
24
25
  disableCopy,
25
26
  hideLang,
27
+ copyContent,
26
28
  class: className
27
29
  }: Props = $props();
28
30
 
@@ -46,7 +48,7 @@
46
48
  const clipboard = useClipboard();
47
49
 
48
50
  const handleCopy = () => {
49
- clipboard.copy(code);
51
+ clipboard.copy(copyContent ?? code);
50
52
  };
51
53
  </script>
52
54
 
@@ -59,14 +61,15 @@
59
61
  {#if open}
60
62
  {#if (!hover && !hideLang && !clipboard.copied) || (disableCopy && !hideLang)}
61
63
  <div class="code-info">
62
- <Chip variant="muted">{lang}</Chip>
64
+ <Chip color="muted" variant="solid" size="xs">{lang}</Chip>
63
65
  </div>
64
66
  {/if}
65
67
  {#if (hover && !disableCopy) || clipboard.copied || (!disableCopy && hideLang)}
66
68
  <div class="code-info">
67
69
  <IconButton
68
70
  onclick={handleCopy}
69
- variant="muted"
71
+ variant="solid"
72
+ color="muted"
70
73
  size="sm"
71
74
  icon={clipboard.copied ? Checkmark24RegularIcon : Copy24RegularIcon}
72
75
  />
@@ -5,6 +5,7 @@ type Props = {
5
5
  darkTheme?: string;
6
6
  disableCopy?: boolean;
7
7
  hideLang?: boolean;
8
+ copyContent?: string;
8
9
  class?: string;
9
10
  };
10
11
  declare const Code: import("svelte").Component<Props, {}, "">;