@makolabs/ripple 1.2.3 → 1.2.4

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 (106) hide show
  1. package/README.md +77 -0
  2. package/dist/adapters/ai/OpenAIAdapter.js +16 -11
  3. package/dist/adapters/ai/types.d.ts +3 -3
  4. package/dist/adapters/storage/BaseAdapter.d.ts +1 -1
  5. package/dist/adapters/storage/BaseAdapter.js +1 -1
  6. package/dist/adapters/storage/S3Adapter.js +2 -2
  7. package/dist/ai/AIChatInterface.svelte +32 -34
  8. package/dist/ai/AIChatInterface.svelte.d.ts +0 -1
  9. package/dist/ai/AIChatInterfaceTestWrapper.svelte +26 -0
  10. package/dist/ai/AIChatInterfaceTestWrapper.svelte.d.ts +17 -0
  11. package/dist/ai/ChatInput.svelte +7 -15
  12. package/dist/ai/ChatInput.svelte.d.ts +0 -2
  13. package/dist/ai/CodeRenderer.svelte +25 -12
  14. package/dist/ai/ComposeDropdown.svelte +17 -14
  15. package/dist/ai/MermaidRenderer.svelte +21 -17
  16. package/dist/ai/MermaidRenderer.svelte.d.ts +0 -1
  17. package/dist/ai/MessageBox.svelte +10 -7
  18. package/dist/ai/ThinkingDisplay.svelte +67 -43
  19. package/dist/ai/ai-chat-interface.d.ts +22 -21
  20. package/dist/ai/ai-chat-interface.js +8 -7
  21. package/dist/ai/content-detector.js +2 -2
  22. package/dist/button/ButtonTestWrapper.svelte +10 -0
  23. package/dist/button/ButtonTestWrapper.svelte.d.ts +7 -0
  24. package/dist/charts/Chart.svelte +6 -1
  25. package/dist/config/ai.js +1 -0
  26. package/dist/drawer/DrawerTestWrapper.svelte +19 -0
  27. package/dist/drawer/DrawerTestWrapper.svelte.d.ts +9 -0
  28. package/dist/drawer/drawer.d.ts +19 -18
  29. package/dist/drawer/drawer.js +7 -6
  30. package/dist/elements/accordion/Accordion.svelte +1 -1
  31. package/dist/elements/accordion/Accordion.svelte.d.ts +1 -1
  32. package/dist/elements/accordion/AccordionTestWrapper.svelte +21 -0
  33. package/dist/elements/accordion/AccordionTestWrapper.svelte.d.ts +10 -0
  34. package/dist/elements/badge/Badge.svelte +5 -4
  35. package/dist/elements/badge/BadgeTestWrapper.svelte +14 -0
  36. package/dist/elements/badge/BadgeTestWrapper.svelte.d.ts +9 -0
  37. package/dist/elements/badge/badge.d.ts +40 -39
  38. package/dist/elements/badge/badge.js +14 -13
  39. package/dist/elements/dropdown/Dropdown.svelte +0 -1
  40. package/dist/elements/pagination/Pagination.svelte +20 -26
  41. package/dist/elements/progress/Progress.svelte +3 -3
  42. package/dist/elements/timeline/Timeline.svelte +1 -1
  43. package/dist/file-browser/FileBrowser.svelte +7 -10
  44. package/dist/filters/CompactFilters.svelte +3 -3
  45. package/dist/forms/Checkbox.svelte +0 -1
  46. package/dist/forms/CheckboxTestWrapper.svelte +8 -0
  47. package/dist/forms/CheckboxTestWrapper.svelte.d.ts +4 -0
  48. package/dist/forms/DateRange.svelte +186 -198
  49. package/dist/forms/Form.svelte +1 -0
  50. package/dist/forms/Input.svelte +14 -5
  51. package/dist/forms/InputTestWrapper.svelte +8 -0
  52. package/dist/forms/InputTestWrapper.svelte.d.ts +4 -0
  53. package/dist/forms/NumberInput.svelte +2 -2
  54. package/dist/forms/RadioInputs.svelte +1 -1
  55. package/dist/forms/RadioPill.svelte +1 -1
  56. package/dist/forms/Slider.svelte +2 -2
  57. package/dist/forms/Tags.svelte +3 -3
  58. package/dist/forms/ToggleTestWrapper.svelte +8 -0
  59. package/dist/forms/ToggleTestWrapper.svelte.d.ts +7 -0
  60. package/dist/forms/slider.js +1 -1
  61. package/dist/header/PageHeader.svelte +2 -1
  62. package/dist/header/breadcrumbs.d.ts +47 -33
  63. package/dist/header/breadcrumbs.js +12 -11
  64. package/dist/index.d.ts +3 -2
  65. package/dist/index.js +2 -0
  66. package/dist/layout/activity-list/ActivityList.svelte +9 -11
  67. package/dist/layout/card/CardTestWrapper.svelte +15 -0
  68. package/dist/layout/card/CardTestWrapper.svelte.d.ts +7 -0
  69. package/dist/layout/card/RankedCard.svelte +2 -3
  70. package/dist/layout/navbar/navbar.d.ts +19 -18
  71. package/dist/layout/navbar/navbar.js +7 -6
  72. package/dist/layout/sidebar/NavGroup.svelte +1 -0
  73. package/dist/layout/table/Cells.svelte +5 -5
  74. package/dist/layout/table/Table.svelte +8 -8
  75. package/dist/layout/table/table.d.ts +28 -24
  76. package/dist/layout/table/table.js +14 -13
  77. package/dist/modal/Modal.svelte +1 -1
  78. package/dist/modal/ModalTestWrapper.svelte +20 -0
  79. package/dist/modal/ModalTestWrapper.svelte.d.ts +8 -0
  80. package/dist/modal/modal.d.ts +1 -20
  81. package/dist/pipeline/Pipeline.svelte +29 -17
  82. package/dist/user-management/README.md +417 -0
  83. package/dist/user-management/UserManagement.svelte +184 -0
  84. package/dist/user-management/UserManagement.svelte.d.ts +4 -0
  85. package/dist/user-management/UserManagementTestWrapper.svelte +47 -0
  86. package/dist/user-management/UserManagementTestWrapper.svelte.d.ts +7 -0
  87. package/dist/user-management/UserModal.svelte +303 -0
  88. package/dist/user-management/UserModal.svelte.d.ts +4 -0
  89. package/dist/user-management/UserModalTestWrapper.svelte +22 -0
  90. package/dist/user-management/UserModalTestWrapper.svelte.d.ts +7 -0
  91. package/dist/user-management/UserTable.svelte +219 -0
  92. package/dist/user-management/UserTable.svelte.d.ts +4 -0
  93. package/dist/user-management/UserTableTestWrapper.svelte +41 -0
  94. package/dist/user-management/UserTableTestWrapper.svelte.d.ts +7 -0
  95. package/dist/user-management/UserViewModal.svelte +282 -0
  96. package/dist/user-management/UserViewModal.svelte.d.ts +4 -0
  97. package/dist/user-management/UserViewModalTestWrapper.svelte +22 -0
  98. package/dist/user-management/UserViewModalTestWrapper.svelte.d.ts +7 -0
  99. package/dist/user-management/index.d.ts +10 -0
  100. package/dist/user-management/index.js +11 -0
  101. package/dist/user-management/user-management.d.ts +99 -0
  102. package/dist/user-management/user-management.js +42 -0
  103. package/package.json +3 -1
  104. package/dist/types/markdown.d.ts +0 -14
  105. package/dist/types/variants.d.ts +0 -1
  106. package/dist/types/variants.js +0 -1
package/README.md CHANGED
@@ -560,3 +560,80 @@ You can still import specific component types when needed:
560
560
 
561
561
  <Button {...myButton}>Custom Button</Button>
562
562
  ```
563
+
564
+ ## Testing
565
+
566
+ Ripple UI uses [Vitest](https://vitest.dev/) for unit and component testing. The test suite includes tests for core library components using Svelte 5's native testing APIs.
567
+
568
+ ### Running Tests Locally
569
+
570
+ Run all tests once:
571
+
572
+ ```bash
573
+ npm run test:unit -- --run
574
+ ```
575
+
576
+ Run tests in watch mode (for development):
577
+
578
+ ```bash
579
+ npm run test:unit
580
+ ```
581
+
582
+ Run all tests (unit + e2e):
583
+
584
+ ```bash
585
+ npm test
586
+ ```
587
+
588
+ ### Writing Tests
589
+
590
+ Component tests use Svelte 5's `mount` and `unmount` APIs for testing components. Here's an example test for a Button component:
591
+
592
+ ```typescript
593
+ import { flushSync, mount, unmount } from 'svelte';
594
+ import { expect, test } from 'vitest';
595
+ import ButtonTestWrapper from './ButtonTestWrapper.svelte';
596
+
597
+ test('renders as a button with slot content and respects disabled prop', () => {
598
+ const component = mount(ButtonTestWrapper, {
599
+ target: document.body,
600
+ props: {
601
+ disabled: true,
602
+ text: 'Click me'
603
+ }
604
+ });
605
+
606
+ const btn = document.body.querySelector('button') as HTMLButtonElement;
607
+ expect(btn).toBeTruthy();
608
+ expect(btn.disabled).toBe(true);
609
+ expect(btn.textContent).toContain('Click me');
610
+
611
+ unmount(component);
612
+ });
613
+ ```
614
+
615
+ For components with snippet props (like `children` or `footer`), create a test wrapper component that accepts simple props and renders them as slots.
616
+
617
+ ### Test Configuration
618
+
619
+ Tests are configured using Vitest workspaces in `vite.config.ts`:
620
+
621
+ - **Client tests**: Run in jsdom environment, include files matching `src/**/*.svelte.{test,spec}.{js,ts}`
622
+ - **Server tests**: Run in node environment, include files matching `src/**/*.{test,spec}.{js,ts}` (excluding `.svelte` tests)
623
+
624
+ The setup file `vitest-setup-client.ts` includes:
625
+
626
+ - `@testing-library/jest-dom` matchers for enhanced assertions
627
+ - `matchMedia` mock for jsdom compatibility with Svelte 5
628
+
629
+ ### Continuous Integration
630
+
631
+ For CI environments (GitHub Actions, etc.), use:
632
+
633
+ ```yaml
634
+ - name: Install dependencies
635
+ run: npm ci
636
+
637
+ - name: Run tests
638
+ run: npm run test:unit -- --run
639
+ ```
@@ -107,10 +107,12 @@ graph TB
107
107
  model: this.config.model,
108
108
  input: messages,
109
109
  max_output_tokens: this.config.maxTokens,
110
- ...(thinkingMode && { reasoning: {
110
+ ...(thinkingMode && {
111
+ reasoning: {
111
112
  effort: 'medium',
112
113
  summary: 'auto'
113
- } })
114
+ }
115
+ })
114
116
  };
115
117
  // Call OpenAI API directly
116
118
  const response = await fetch(`${this.config.baseUrl}/responses`, {
@@ -142,15 +144,15 @@ graph TB
142
144
  if (outputItem.type === 'reasoning' && outputItem.summary) {
143
145
  // Extract reasoning summary text
144
146
  reasoningContent = outputItem.summary
145
- .filter(s => s.type === 'summary_text')
146
- .map(s => s.text)
147
+ .filter((s) => s.type === 'summary_text')
148
+ .map((s) => s.text)
147
149
  .join('');
148
150
  }
149
151
  else if (outputItem.type === 'message' && outputItem.content) {
150
152
  // Extract message content text
151
153
  aiResponseContent = outputItem.content
152
- .filter(c => c.type === 'output_text')
153
- .map(c => c.text)
154
+ .filter((c) => c.type === 'output_text')
155
+ .map((c) => c.text)
154
156
  .join('');
155
157
  }
156
158
  }
@@ -210,10 +212,12 @@ graph TB
210
212
  input: messages,
211
213
  stream: true,
212
214
  max_output_tokens: this.config.maxTokens,
213
- ...(thinkingMode && { reasoning: {
215
+ ...(thinkingMode && {
216
+ reasoning: {
214
217
  effort: 'medium',
215
218
  summary: 'auto'
216
- } })
219
+ }
220
+ })
217
221
  };
218
222
  // Call OpenAI API with streaming
219
223
  const response = await fetch(`${this.config.baseUrl}/responses`, {
@@ -298,7 +302,7 @@ graph TB
298
302
  break;
299
303
  }
300
304
  }
301
- catch (e) {
305
+ catch {
302
306
  // Skip invalid JSON chunks
303
307
  continue;
304
308
  }
@@ -361,6 +365,7 @@ graph TB
361
365
  * Get current configuration (without API key for security)
362
366
  */
363
367
  getConfig() {
368
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
364
369
  const { apiKey, ...configWithoutKey } = this.config;
365
370
  return configWithoutKey;
366
371
  }
@@ -448,8 +453,8 @@ graph TB
448
453
  for (const outputItem of data.output || []) {
449
454
  if (outputItem.type === 'message' && outputItem.content) {
450
455
  const content = outputItem.content
451
- .filter(c => c.type === 'output_text')
452
- .map(c => c.text)
456
+ .filter((c) => c.type === 'output_text')
457
+ .map((c) => c.text)
453
458
  .join('');
454
459
  return content.trim() || null;
455
460
  }
@@ -46,15 +46,15 @@ export interface AIAdapter {
46
46
  /**
47
47
  * Get memory manager if supported
48
48
  */
49
- getMemoryManager?(): MemoryManager | null;
49
+ getMemoryManager?(): unknown | null;
50
50
  /**
51
51
  * Get memory statistics if supported
52
52
  */
53
- getMemoryStats?(): any;
53
+ getMemoryStats?(): Record<string, unknown>;
54
54
  /**
55
55
  * Get essential context if supported
56
56
  */
57
- getEssentialContext?(): any;
57
+ getEssentialContext?(): Record<string, unknown>;
58
58
  /**
59
59
  * Get context prompt if supported
60
60
  */
@@ -9,7 +9,7 @@ export declare abstract class BaseAdapter implements StorageAdapter {
9
9
  abstract isConfigured(): Promise<boolean>;
10
10
  abstract authenticate?(): Promise<boolean>;
11
11
  import(file: FileItem, options: ImportOptions): Promise<ImportResult>;
12
- getImportStatus(importId: string, fileKey: string): Promise<ImportStatus>;
12
+ getImportStatus(importId: string): Promise<ImportStatus>;
13
13
  batchImport(files: FileItem[], options: ImportOptions): Promise<BatchImportResult>;
14
14
  protected abstract getApiPath(): string;
15
15
  setReopenFlag(): void;
@@ -42,7 +42,7 @@ export class BaseAdapter {
42
42
  };
43
43
  }
44
44
  }
45
- async getImportStatus(importId, fileKey) {
45
+ async getImportStatus(importId) {
46
46
  try {
47
47
  const response = await fetch(`/api/v1/uploads/${importId}`);
48
48
  if (!response.ok) {
@@ -146,7 +146,7 @@ export class S3Adapter extends BaseAdapter {
146
146
  // Helper method to remove file extensions
147
147
  removeFileExtensions(filename) {
148
148
  // First remove .csv.gz if it exists
149
- let name = filename.replace(/\.csv\.gz$/, '');
149
+ const name = filename.replace(/\.csv\.gz$/, '');
150
150
  // Then remove any remaining extension
151
151
  return name.replace(/\.[^/.]+$/, '');
152
152
  }
@@ -159,7 +159,7 @@ export class S3Adapter extends BaseAdapter {
159
159
  // Extract parts for display
160
160
  const pathWithoutBase = path.startsWith(this.basePath) ? path.slice(this.basePath.length) : '';
161
161
  // Add base path as the first item
162
- let breadcrumbs = [{ name: 'S3', path: this.basePath, current: false, clickable: true }];
162
+ const breadcrumbs = [{ name: 'S3', path: this.basePath, current: false, clickable: true }];
163
163
  // Add current directory parts
164
164
  if (pathWithoutBase) {
165
165
  const parts = pathWithoutBase.split('/').filter(Boolean);
@@ -18,7 +18,6 @@
18
18
  messages?: ChatMessage[];
19
19
  class?: string;
20
20
  context?: Record<string, unknown>;
21
- showHeader?: boolean;
22
21
  }
23
22
 
24
23
  let {
@@ -31,12 +30,12 @@
31
30
  loading = false,
32
31
  messages = $bindable([]),
33
32
  class: className = '',
34
- context = {},
35
- showHeader = true
33
+ context = {}
36
34
  }: AIChatInterfaceProps = $props();
37
35
 
38
36
  let userInput = $state('');
39
37
  let isProcessing = $state(false);
38
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
40
39
  let error = $state<string | null>(null);
41
40
  let chatContainer: HTMLDivElement | undefined = $state();
42
41
  let isAdapterConfigured = $state(false);
@@ -44,7 +43,7 @@
44
43
  let thinkingMode = $state(true);
45
44
  let availableHeight = $state('100vh');
46
45
 
47
- const { sendButton, background } = $derived(
46
+ const { background } = $derived(
48
47
  aiChatInterface({
49
48
  color: color as
50
49
  | 'default'
@@ -66,7 +65,7 @@
66
65
  // Simple and reliable height calculation using native ResizeObserver
67
66
  function findLayoutParent() {
68
67
  if (!chatContainer) return null;
69
-
68
+
70
69
  let parent = chatContainer.parentElement;
71
70
  while (parent && parent !== document.body) {
72
71
  const style = window.getComputedStyle(parent);
@@ -96,20 +95,20 @@
96
95
  if (typeof window !== 'undefined' && chatContainer) {
97
96
  // Initial calculation
98
97
  updateAvailableHeight();
99
-
98
+
100
99
  // Set up ResizeObserver for the layout parent
101
100
  const layoutParent = findLayoutParent();
102
101
  if (layoutParent && 'ResizeObserver' in window) {
103
102
  const resizeObserver = new ResizeObserver(() => {
104
103
  updateAvailableHeight();
105
104
  });
106
-
105
+
107
106
  resizeObserver.observe(layoutParent);
108
-
107
+
109
108
  // Also observe viewport changes
110
109
  const handleResize = () => updateAvailableHeight();
111
110
  window.addEventListener('resize', handleResize);
112
-
111
+
113
112
  return () => {
114
113
  resizeObserver.disconnect();
115
114
  window.removeEventListener('resize', handleResize);
@@ -118,7 +117,7 @@
118
117
  // Fallback for browsers without ResizeObserver
119
118
  const handleResize = () => updateAvailableHeight();
120
119
  window.addEventListener('resize', handleResize);
121
-
120
+
122
121
  return () => {
123
122
  window.removeEventListener('resize', handleResize);
124
123
  };
@@ -138,8 +137,6 @@
138
137
  )
139
138
  );
140
139
 
141
-
142
-
143
140
  function addMessage(
144
141
  type: 'chat' | 'action' | 'thinking',
145
142
  content: string,
@@ -163,15 +160,20 @@
163
160
  return message;
164
161
  }
165
162
 
166
- function updateMessageById(messageId: string, content: string, thinkingContent?: string, isThinkingComplete?: boolean): void {
167
- messages = messages.map((msg) =>
168
- msg.id === messageId
169
- ? {
170
- ...msg,
171
- content,
172
- ...(thinkingContent !== undefined && { thinkingContent }),
173
- ...(isThinkingComplete !== undefined && { isThinkingComplete })
174
- }
163
+ function updateMessageById(
164
+ messageId: string,
165
+ content: string,
166
+ thinkingContent?: string,
167
+ isThinkingComplete?: boolean
168
+ ): void {
169
+ messages = messages.map((msg) =>
170
+ msg.id === messageId
171
+ ? {
172
+ ...msg,
173
+ content,
174
+ ...(thinkingContent !== undefined && { thinkingContent }),
175
+ ...(isThinkingComplete !== undefined && { isThinkingComplete })
176
+ }
175
177
  : msg
176
178
  );
177
179
  }
@@ -200,9 +202,9 @@
200
202
  // Only create new streaming message if there's content or thinking content
201
203
  if (response.content || response.thinkingContent) {
202
204
  addMessage(
203
- response.type,
204
- response.content,
205
- response.action,
205
+ response.type,
206
+ response.content,
207
+ response.action,
206
208
  response.messageId,
207
209
  response.thinkingContent,
208
210
  response.isThinkingComplete
@@ -211,7 +213,7 @@
211
213
  } else {
212
214
  // Update existing message
213
215
  updateMessageById(
214
- response.messageId,
216
+ response.messageId,
215
217
  response.content,
216
218
  response.thinkingContent,
217
219
  response.isThinkingComplete
@@ -228,9 +230,9 @@
228
230
  const contextWithThinking = { ...context, thinkingMode } as Record<string, unknown>;
229
231
  const response = await adapter.sendMessage(input, contextWithThinking);
230
232
  addMessage(
231
- response.type,
232
- response.content,
233
- response.action,
233
+ response.type,
234
+ response.content,
235
+ response.action,
234
236
  undefined,
235
237
  response.thinkingContent,
236
238
  response.isThinkingComplete
@@ -274,8 +276,6 @@
274
276
  console.log('Thinking mode:', enabled ? 'enabled' : 'disabled');
275
277
  }
276
278
 
277
-
278
-
279
279
  // Check adapter configuration
280
280
  $effect(() => {
281
281
  if (adapter) {
@@ -301,7 +301,6 @@
301
301
  }, 100);
302
302
  }
303
303
  });
304
-
305
304
  </script>
306
305
 
307
306
  <!-- Unified Layout Container -->
@@ -371,14 +370,14 @@
371
370
 
372
371
  <!-- Chat Messages Area -->
373
372
  <main class="flex flex-1 flex-col overflow-y-auto">
374
- <div class="mx-auto w-full max-w-4xl space-y-4 px-6 py-4 flex-1">
373
+ <div class="mx-auto w-full max-w-4xl flex-1 space-y-4 px-6 py-4">
375
374
  {#each messages as message (message.id)}
376
375
  {@const isUser = message.content.startsWith('User:')}
377
376
  {@const displayContent = isUser ? message.content.replace('User: ', '') : message.content}
378
377
 
379
378
  <!-- Show thinking display for AI messages when thinking mode is enabled -->
380
379
  {#if !isUser && message.thinkingContent}
381
- <ThinkingDisplay
380
+ <ThinkingDisplay
382
381
  content={message.thinkingContent}
383
382
  isComplete={message.isThinkingComplete || false}
384
383
  class="mb-2"
@@ -426,7 +425,6 @@
426
425
  {isProcessing}
427
426
  {disabled}
428
427
  {isAdapterConfigured}
429
- {color}
430
428
  hasMessages={messages.length > 0}
431
429
  onSubmit={handleSubmit}
432
430
  onKeyDown={handleKeyDown}
@@ -11,7 +11,6 @@ interface AIChatInterfaceProps {
11
11
  messages?: ChatMessage[];
12
12
  class?: string;
13
13
  context?: Record<string, unknown>;
14
- showHeader?: boolean;
15
14
  }
16
15
  declare const AiChatInterface: import("svelte").Component<AIChatInterfaceProps, {}, "messages">;
17
16
  type AiChatInterface = ReturnType<typeof AiChatInterface>;
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import AIChatInterface from './AIChatInterface.svelte';
3
+ import type { AIAdapter } from '../adapters/ai/index.js';
4
+ import type { ChatMessage, VariantColors } from '../index.js';
5
+
6
+ interface AIChatInterfaceTestWrapperProps {
7
+ adapter: AIAdapter;
8
+ title?: string;
9
+ subtitle?: string;
10
+ placeholder?: string;
11
+ color?: VariantColors;
12
+ disabled?: boolean;
13
+ loading?: boolean;
14
+ messages?: ChatMessage[];
15
+ class?: string;
16
+ context?: Record<string, unknown>;
17
+ }
18
+
19
+ let {
20
+ adapter,
21
+ messages = $bindable([]),
22
+ ...restProps
23
+ }: AIChatInterfaceTestWrapperProps = $props();
24
+ </script>
25
+
26
+ <AIChatInterface {adapter} bind:messages {...restProps} />
@@ -0,0 +1,17 @@
1
+ import type { AIAdapter } from '../adapters/ai/index.js';
2
+ import type { ChatMessage, VariantColors } from '../index.js';
3
+ interface AIChatInterfaceTestWrapperProps {
4
+ adapter: AIAdapter;
5
+ title?: string;
6
+ subtitle?: string;
7
+ placeholder?: string;
8
+ color?: VariantColors;
9
+ disabled?: boolean;
10
+ loading?: boolean;
11
+ messages?: ChatMessage[];
12
+ class?: string;
13
+ context?: Record<string, unknown>;
14
+ }
15
+ declare const AiChatInterfaceTestWrapper: import("svelte").Component<AIChatInterfaceTestWrapperProps, {}, "messages">;
16
+ type AiChatInterfaceTestWrapper = ReturnType<typeof AiChatInterfaceTestWrapper>;
17
+ export default AiChatInterfaceTestWrapper;
@@ -3,7 +3,6 @@
3
3
  import { browser } from '$app/environment';
4
4
  import { tv } from 'tailwind-variants';
5
5
  import ComposeDropdown from './ComposeDropdown.svelte';
6
- import type { VariantColors } from '../index.js';
7
6
 
8
7
  interface ChatInputProps {
9
8
  userInput: string;
@@ -12,7 +11,6 @@
12
11
  isProcessing: boolean;
13
12
  disabled: boolean;
14
13
  isAdapterConfigured: boolean;
15
- color?: VariantColors;
16
14
  hasMessages: boolean;
17
15
  thinkingMode?: boolean;
18
16
  onSubmit: () => void;
@@ -27,7 +25,6 @@
27
25
  isProcessing,
28
26
  disabled,
29
27
  isAdapterConfigured,
30
- color = 'primary',
31
28
  hasMessages,
32
29
  thinkingMode = $bindable(),
33
30
  onSubmit,
@@ -60,13 +57,12 @@
60
57
  variants: {
61
58
  state: {
62
59
  disabled: 'bg-gray-200 text-gray-400 cursor-not-allowed',
63
- enabled: 'text-white bg-primary-500 hover:bg-primary-600 focus:ring-2 focus:ring-primary-500/20'
60
+ enabled:
61
+ 'text-white bg-primary-500 hover:bg-primary-600 focus:ring-2 focus:ring-primary-500/20'
64
62
  }
65
63
  }
66
64
  });
67
65
 
68
-
69
-
70
66
  const inputArea = tv({
71
67
  variants: {
72
68
  layout: {
@@ -96,12 +92,13 @@
96
92
 
97
93
  const sendButtonClasses = $derived(
98
94
  sendButton({
99
- state: disabled || !isAdapterConfigured || isProcessing || !userInput.trim() ? 'disabled' : 'enabled'
95
+ state:
96
+ disabled || !isAdapterConfigured || isProcessing || !userInput.trim()
97
+ ? 'disabled'
98
+ : 'enabled'
100
99
  })
101
100
  );
102
101
 
103
-
104
-
105
102
  const inputAreaClasses = $derived(
106
103
  inputArea({
107
104
  layout: hasMessages ? 'chat' : 'empty'
@@ -158,12 +155,7 @@
158
155
  <div class={inputAreaClasses}>
159
156
  <div class={inputContainerClasses}>
160
157
  <!-- Compose Dropdown -->
161
- <ComposeDropdown
162
- {disabled}
163
- {isAdapterConfigured}
164
- bind:thinkingMode
165
- {onThinkingToggle}
166
- />
158
+ <ComposeDropdown {disabled} {isAdapterConfigured} bind:thinkingMode {onThinkingToggle} />
167
159
 
168
160
  <textarea
169
161
  bind:this={textareaRef}
@@ -1,4 +1,3 @@
1
- import type { VariantColors } from '../index.js';
2
1
  interface ChatInputProps {
3
2
  userInput: string;
4
3
  textareaRef?: HTMLTextAreaElement;
@@ -6,7 +5,6 @@ interface ChatInputProps {
6
5
  isProcessing: boolean;
7
6
  disabled: boolean;
8
7
  isAdapterConfigured: boolean;
9
- color?: VariantColors;
10
8
  hasMessages: boolean;
11
9
  thinkingMode?: boolean;
12
10
  onSubmit: () => void;
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import Highlight from 'svelte-highlight';
3
3
  import { HighlightAuto } from 'svelte-highlight';
4
-
4
+
5
5
  // Import common languages
6
6
  import javascript from 'svelte-highlight/languages/javascript';
7
7
  import typescript from 'svelte-highlight/languages/typescript';
@@ -24,7 +24,7 @@
24
24
  import powershell from 'svelte-highlight/languages/powershell';
25
25
  import dockerfile from 'svelte-highlight/languages/dockerfile';
26
26
  import markdown from 'svelte-highlight/languages/markdown';
27
-
27
+
28
28
  // Import a clean dark theme
29
29
  import githubDark from 'svelte-highlight/styles/github-dark';
30
30
 
@@ -37,7 +37,7 @@
37
37
  let { code, language = 'text', class: className = '' }: Props = $props();
38
38
 
39
39
  // Language mapping
40
- const languageMap: Record<string, any> = {
40
+ const languageMap: Record<string, unknown> = {
41
41
  javascript: javascript,
42
42
  js: javascript,
43
43
  typescript: typescript,
@@ -97,32 +97,45 @@
97
97
  </script>
98
98
 
99
99
  <svelte:head>
100
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
100
101
  {@html githubDark}
101
102
  </svelte:head>
102
103
 
103
- <div class="code-renderer my-4 overflow-hidden rounded-lg border border-gray-700 bg-gray-900 {className}">
104
+ <div
105
+ class="code-renderer my-4 overflow-hidden rounded-lg border border-gray-700 bg-gray-900 {className}"
106
+ >
104
107
  <!-- Header -->
105
- <div class="flex items-center justify-between bg-gray-800 px-4 py-2 border-b border-gray-700">
106
- <span class="text-xs font-medium text-gray-300 uppercase tracking-wide">
108
+ <div class="flex items-center justify-between border-b border-gray-700 bg-gray-800 px-4 py-2">
109
+ <span class="text-xs font-medium tracking-wide text-gray-300 uppercase">
107
110
  {displayLanguage}
108
111
  </span>
109
112
  <button
110
113
  onclick={copyCode}
111
- class="flex items-center gap-1.5 rounded px-2 py-1 text-xs transition-all duration-200 {copied
112
- ? 'text-green-400 border border-green-500 bg-green-500/10'
114
+ class="flex items-center gap-1.5 rounded px-2 py-1 text-xs transition-all duration-200 {copied
115
+ ? 'border border-green-500 bg-green-500/10 text-green-400'
113
116
  : 'text-gray-400 hover:bg-gray-700 hover:text-gray-200'}"
114
- title={copied ? "Copied!" : "Copy code"}
117
+ title={copied ? 'Copied!' : 'Copy code'}
115
118
  >
116
119
  {#if copied}
117
120
  <!-- Checkmark icon -->
118
121
  <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
119
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
122
+ <path
123
+ stroke-linecap="round"
124
+ stroke-linejoin="round"
125
+ stroke-width="2"
126
+ d="M5 13l4 4L19 7"
127
+ />
120
128
  </svg>
121
129
  Copied!
122
130
  {:else}
123
131
  <!-- Copy icon -->
124
132
  <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
125
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
133
+ <path
134
+ stroke-linecap="round"
135
+ stroke-linejoin="round"
136
+ stroke-width="2"
137
+ d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
138
+ />
126
139
  </svg>
127
140
  Copy
128
141
  {/if}
@@ -171,4 +184,4 @@
171
184
  :global(.code-renderer .hljs::-webkit-scrollbar-thumb:hover) {
172
185
  background: #6e7681;
173
186
  }
174
- </style>
187
+ </style>