@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.
- package/README.md +77 -0
- package/dist/adapters/ai/OpenAIAdapter.js +16 -11
- package/dist/adapters/ai/types.d.ts +3 -3
- package/dist/adapters/storage/BaseAdapter.d.ts +1 -1
- package/dist/adapters/storage/BaseAdapter.js +1 -1
- package/dist/adapters/storage/S3Adapter.js +2 -2
- package/dist/ai/AIChatInterface.svelte +32 -34
- package/dist/ai/AIChatInterface.svelte.d.ts +0 -1
- package/dist/ai/AIChatInterfaceTestWrapper.svelte +26 -0
- package/dist/ai/AIChatInterfaceTestWrapper.svelte.d.ts +17 -0
- package/dist/ai/ChatInput.svelte +7 -15
- package/dist/ai/ChatInput.svelte.d.ts +0 -2
- package/dist/ai/CodeRenderer.svelte +25 -12
- package/dist/ai/ComposeDropdown.svelte +17 -14
- package/dist/ai/MermaidRenderer.svelte +21 -17
- package/dist/ai/MermaidRenderer.svelte.d.ts +0 -1
- package/dist/ai/MessageBox.svelte +10 -7
- package/dist/ai/ThinkingDisplay.svelte +67 -43
- package/dist/ai/ai-chat-interface.d.ts +22 -21
- package/dist/ai/ai-chat-interface.js +8 -7
- package/dist/ai/content-detector.js +2 -2
- package/dist/button/ButtonTestWrapper.svelte +10 -0
- package/dist/button/ButtonTestWrapper.svelte.d.ts +7 -0
- package/dist/charts/Chart.svelte +6 -1
- package/dist/config/ai.js +1 -0
- package/dist/drawer/DrawerTestWrapper.svelte +19 -0
- package/dist/drawer/DrawerTestWrapper.svelte.d.ts +9 -0
- package/dist/drawer/drawer.d.ts +19 -18
- package/dist/drawer/drawer.js +7 -6
- package/dist/elements/accordion/Accordion.svelte +1 -1
- package/dist/elements/accordion/Accordion.svelte.d.ts +1 -1
- package/dist/elements/accordion/AccordionTestWrapper.svelte +21 -0
- package/dist/elements/accordion/AccordionTestWrapper.svelte.d.ts +10 -0
- package/dist/elements/badge/Badge.svelte +5 -4
- package/dist/elements/badge/BadgeTestWrapper.svelte +14 -0
- package/dist/elements/badge/BadgeTestWrapper.svelte.d.ts +9 -0
- package/dist/elements/badge/badge.d.ts +40 -39
- package/dist/elements/badge/badge.js +14 -13
- package/dist/elements/dropdown/Dropdown.svelte +0 -1
- package/dist/elements/pagination/Pagination.svelte +20 -26
- package/dist/elements/progress/Progress.svelte +3 -3
- package/dist/elements/timeline/Timeline.svelte +1 -1
- package/dist/file-browser/FileBrowser.svelte +7 -10
- package/dist/filters/CompactFilters.svelte +3 -3
- package/dist/forms/Checkbox.svelte +0 -1
- package/dist/forms/CheckboxTestWrapper.svelte +8 -0
- package/dist/forms/CheckboxTestWrapper.svelte.d.ts +4 -0
- package/dist/forms/DateRange.svelte +186 -198
- package/dist/forms/Form.svelte +1 -0
- package/dist/forms/Input.svelte +14 -5
- package/dist/forms/InputTestWrapper.svelte +8 -0
- package/dist/forms/InputTestWrapper.svelte.d.ts +4 -0
- package/dist/forms/NumberInput.svelte +2 -2
- package/dist/forms/RadioInputs.svelte +1 -1
- package/dist/forms/RadioPill.svelte +1 -1
- package/dist/forms/Slider.svelte +2 -2
- package/dist/forms/Tags.svelte +3 -3
- package/dist/forms/ToggleTestWrapper.svelte +8 -0
- package/dist/forms/ToggleTestWrapper.svelte.d.ts +7 -0
- package/dist/forms/slider.js +1 -1
- package/dist/header/PageHeader.svelte +2 -1
- package/dist/header/breadcrumbs.d.ts +47 -33
- package/dist/header/breadcrumbs.js +12 -11
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -0
- package/dist/layout/activity-list/ActivityList.svelte +9 -11
- package/dist/layout/card/CardTestWrapper.svelte +15 -0
- package/dist/layout/card/CardTestWrapper.svelte.d.ts +7 -0
- package/dist/layout/card/RankedCard.svelte +2 -3
- package/dist/layout/navbar/navbar.d.ts +19 -18
- package/dist/layout/navbar/navbar.js +7 -6
- package/dist/layout/sidebar/NavGroup.svelte +1 -0
- package/dist/layout/table/Cells.svelte +5 -5
- package/dist/layout/table/Table.svelte +8 -8
- package/dist/layout/table/table.d.ts +28 -24
- package/dist/layout/table/table.js +14 -13
- package/dist/modal/Modal.svelte +1 -1
- package/dist/modal/ModalTestWrapper.svelte +20 -0
- package/dist/modal/ModalTestWrapper.svelte.d.ts +8 -0
- package/dist/modal/modal.d.ts +1 -20
- package/dist/pipeline/Pipeline.svelte +29 -17
- package/dist/user-management/README.md +417 -0
- package/dist/user-management/UserManagement.svelte +184 -0
- package/dist/user-management/UserManagement.svelte.d.ts +4 -0
- package/dist/user-management/UserManagementTestWrapper.svelte +47 -0
- package/dist/user-management/UserManagementTestWrapper.svelte.d.ts +7 -0
- package/dist/user-management/UserModal.svelte +303 -0
- package/dist/user-management/UserModal.svelte.d.ts +4 -0
- package/dist/user-management/UserModalTestWrapper.svelte +22 -0
- package/dist/user-management/UserModalTestWrapper.svelte.d.ts +7 -0
- package/dist/user-management/UserTable.svelte +219 -0
- package/dist/user-management/UserTable.svelte.d.ts +4 -0
- package/dist/user-management/UserTableTestWrapper.svelte +41 -0
- package/dist/user-management/UserTableTestWrapper.svelte.d.ts +7 -0
- package/dist/user-management/UserViewModal.svelte +282 -0
- package/dist/user-management/UserViewModal.svelte.d.ts +4 -0
- package/dist/user-management/UserViewModalTestWrapper.svelte +22 -0
- package/dist/user-management/UserViewModalTestWrapper.svelte.d.ts +7 -0
- package/dist/user-management/index.d.ts +10 -0
- package/dist/user-management/index.js +11 -0
- package/dist/user-management/user-management.d.ts +99 -0
- package/dist/user-management/user-management.js +42 -0
- package/package.json +3 -1
- package/dist/types/markdown.d.ts +0 -14
- package/dist/types/variants.d.ts +0 -1
- 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 && {
|
|
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 && {
|
|
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
|
|
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?():
|
|
49
|
+
getMemoryManager?(): unknown | null;
|
|
50
50
|
/**
|
|
51
51
|
* Get memory statistics if supported
|
|
52
52
|
*/
|
|
53
|
-
getMemoryStats?():
|
|
53
|
+
getMemoryStats?(): Record<string, unknown>;
|
|
54
54
|
/**
|
|
55
55
|
* Get essential context if supported
|
|
56
56
|
*/
|
|
57
|
-
getEssentialContext?():
|
|
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
|
|
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;
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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 {
|
|
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(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
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;
|
package/dist/ai/ChatInput.svelte
CHANGED
|
@@ -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:
|
|
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:
|
|
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,
|
|
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
|
|
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
|
|
106
|
-
<span class="text-xs font-medium text-gray-300 uppercase
|
|
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
|
-
? '
|
|
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 ?
|
|
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
|
|
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
|
|
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>
|