@makolabs/ripple 0.4.0 → 0.5.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.
- package/README.md +165 -205
- package/dist/adapters/ai/OpenAIAdapter.d.ts +115 -0
- package/dist/adapters/ai/OpenAIAdapter.js +568 -0
- package/dist/adapters/ai/index.d.ts +3 -0
- package/dist/adapters/ai/index.js +3 -0
- package/dist/adapters/ai/types.d.ts +108 -0
- package/dist/adapters/ai/types.js +31 -0
- package/dist/adapters/storage/BaseAdapter.js +31 -31
- package/dist/ai/AIChatInterface.svelte +435 -0
- package/dist/ai/AIChatInterface.svelte.d.ts +18 -0
- package/dist/ai/ChatInput.svelte +211 -0
- package/dist/ai/ChatInput.svelte.d.ts +18 -0
- package/dist/ai/CodeRenderer.svelte +174 -0
- package/dist/ai/CodeRenderer.svelte.d.ts +8 -0
- package/dist/ai/ComposeDropdown.svelte +171 -0
- package/dist/ai/ComposeDropdown.svelte.d.ts +9 -0
- package/dist/ai/MermaidRenderer.svelte +89 -0
- package/dist/ai/MermaidRenderer.svelte.d.ts +7 -0
- package/dist/ai/MessageBox.svelte +403 -0
- package/dist/ai/MessageBox.svelte.d.ts +12 -0
- package/dist/ai/ThinkingDisplay.svelte +275 -0
- package/dist/ai/ThinkingDisplay.svelte.d.ts +9 -0
- package/dist/ai/ai-chat-interface.d.ts +161 -0
- package/dist/ai/ai-chat-interface.js +63 -0
- package/dist/ai/content-detector.d.ts +41 -0
- package/dist/ai/content-detector.js +153 -0
- package/dist/config/ai.d.ts +13 -0
- package/dist/config/ai.js +43 -0
- package/dist/elements/accordion/accordion.js +1 -1
- package/dist/elements/badge/Badge.svelte +14 -3
- package/dist/elements/dropdown/Dropdown.svelte +2 -2
- package/dist/elements/dropdown/Select.svelte +1 -1
- package/dist/elements/progress/Progress.svelte +7 -10
- package/dist/file-browser/FileBrowser.svelte +1 -1
- package/dist/forms/DateRange.svelte +18 -16
- package/dist/forms/NumberInput.svelte +1 -1
- package/dist/forms/RadioInputs.svelte +1 -1
- package/dist/forms/RadioPill.svelte +1 -1
- package/dist/forms/Tags.svelte +2 -2
- package/dist/helper/date.d.ts +1 -0
- package/dist/helper/date.js +6 -0
- package/dist/index.d.ts +67 -3
- package/dist/index.js +11 -0
- package/dist/layout/activity-list/ActivityList.svelte +94 -0
- package/dist/layout/activity-list/ActivityList.svelte.d.ts +4 -0
- package/dist/layout/activity-list/activity-list.d.ts +152 -0
- package/dist/layout/activity-list/activity-list.js +59 -0
- package/dist/layout/card/Card.svelte +1 -5
- package/dist/layout/card/metric-card.d.ts +18 -18
- package/dist/layout/table/Cells.svelte +1 -7
- package/dist/layout/table/Cells.svelte.d.ts +1 -1
- package/dist/modal/Modal.svelte +4 -2
- package/dist/modal/Modal.svelte.d.ts +1 -1
- package/dist/modal/modal.d.ts +19 -18
- package/dist/modal/modal.js +7 -6
- package/dist/sonner/sonner.svelte +1 -7
- package/dist/types/markdown.d.ts +14 -0
- package/dist/utils/Portal.svelte +1 -1
- package/package.json +128 -121
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error types for AI adapters
|
|
3
|
+
*/
|
|
4
|
+
export class AIAdapterError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
adapter;
|
|
7
|
+
constructor(message, code, adapter) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.adapter = adapter;
|
|
11
|
+
this.name = 'AIAdapterError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class AIConfigurationError extends AIAdapterError {
|
|
15
|
+
constructor(message, adapter) {
|
|
16
|
+
super(message, 'CONFIGURATION_ERROR', adapter);
|
|
17
|
+
this.name = 'AIConfigurationError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class AINetworkError extends AIAdapterError {
|
|
21
|
+
constructor(message, adapter) {
|
|
22
|
+
super(message, 'NETWORK_ERROR', adapter);
|
|
23
|
+
this.name = 'AINetworkError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class AIRateLimitError extends AIAdapterError {
|
|
27
|
+
constructor(message, adapter) {
|
|
28
|
+
super(message, 'RATE_LIMIT_ERROR', adapter);
|
|
29
|
+
this.name = 'AIRateLimitError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -87,7 +87,7 @@ export class BaseAdapter {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
// Count successes and failures
|
|
90
|
-
const succeeded = individualResults.filter(r => r.success).length;
|
|
90
|
+
const succeeded = individualResults.filter((r) => r.success).length;
|
|
91
91
|
const failed = individualResults.length - succeeded;
|
|
92
92
|
// Return batch import result
|
|
93
93
|
return {
|
|
@@ -98,7 +98,7 @@ export class BaseAdapter {
|
|
|
98
98
|
succeeded,
|
|
99
99
|
failed
|
|
100
100
|
},
|
|
101
|
-
results: individualResults.map(r => ({
|
|
101
|
+
results: individualResults.map((r) => ({
|
|
102
102
|
key: r.fileId || '',
|
|
103
103
|
success: r.success,
|
|
104
104
|
fileId: r.fileId,
|
|
@@ -125,21 +125,21 @@ export class BaseAdapter {
|
|
|
125
125
|
// Helper method to get detailed status
|
|
126
126
|
getDetailedStatus(status) {
|
|
127
127
|
const statusMap = {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
128
|
+
pending: 'Waiting to start',
|
|
129
|
+
pending_import: 'Preparing import',
|
|
130
|
+
initializing: 'Initializing import',
|
|
131
|
+
pending_file_retrieval: 'Retrieving file',
|
|
132
|
+
retrieving_file: 'Downloading file',
|
|
133
|
+
file_retrieved: 'File downloaded',
|
|
134
|
+
loading_file_contents_to_db: 'Loading data',
|
|
135
|
+
file_contents_loaded: 'Data loaded',
|
|
136
|
+
ready_to_process: 'Ready for processing',
|
|
137
|
+
validating_source_file_rows: 'Validating file',
|
|
138
|
+
converting_to_business_objects: 'Converting data',
|
|
139
|
+
processing: 'Processing data',
|
|
140
|
+
completed: 'Import completed',
|
|
141
|
+
failed: 'Import failed',
|
|
142
|
+
partially_processed: 'Partially processed'
|
|
143
143
|
};
|
|
144
144
|
return statusMap[status] || status;
|
|
145
145
|
}
|
|
@@ -151,20 +151,20 @@ export class BaseAdapter {
|
|
|
151
151
|
}
|
|
152
152
|
// Otherwise, use status-based progress estimates
|
|
153
153
|
const progressMap = {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
154
|
+
pending: 5,
|
|
155
|
+
pending_import: 10,
|
|
156
|
+
initializing: 15,
|
|
157
|
+
pending_file_retrieval: 20,
|
|
158
|
+
retrieving_file: 30,
|
|
159
|
+
file_retrieved: 40,
|
|
160
|
+
loading_file_contents_to_db: 50,
|
|
161
|
+
file_contents_loaded: 60,
|
|
162
|
+
ready_to_process: 70,
|
|
163
|
+
validating_source_file_rows: 80,
|
|
164
|
+
converting_to_business_objects: 90,
|
|
165
|
+
completed: 100,
|
|
166
|
+
failed: 0,
|
|
167
|
+
partially_processed: 95
|
|
168
168
|
};
|
|
169
169
|
return progressMap[status] || 50;
|
|
170
170
|
}
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../helper/cls.js';
|
|
3
|
+
import { aiChatInterface } from './ai-chat-interface.js';
|
|
4
|
+
import MessageBox from './MessageBox.svelte';
|
|
5
|
+
import ChatInput from './ChatInput.svelte';
|
|
6
|
+
import ThinkingDisplay from './ThinkingDisplay.svelte';
|
|
7
|
+
import type { ChatMessage, ChatAction, VariantColors, StreamingCallback } from '../index.js';
|
|
8
|
+
import type { AIAdapter } from '../adapters/ai/index.js';
|
|
9
|
+
|
|
10
|
+
interface AIChatInterfaceProps {
|
|
11
|
+
adapter: AIAdapter;
|
|
12
|
+
title?: string;
|
|
13
|
+
subtitle?: string;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
color?: VariantColors;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
loading?: boolean;
|
|
18
|
+
messages?: ChatMessage[];
|
|
19
|
+
class?: string;
|
|
20
|
+
context?: Record<string, unknown>;
|
|
21
|
+
showHeader?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
adapter,
|
|
26
|
+
title = 'AI Assistant',
|
|
27
|
+
subtitle = 'How can I help you today?',
|
|
28
|
+
placeholder = 'Type your message...',
|
|
29
|
+
color = 'primary' as VariantColors,
|
|
30
|
+
disabled = false,
|
|
31
|
+
loading = false,
|
|
32
|
+
messages = $bindable([]),
|
|
33
|
+
class: className = '',
|
|
34
|
+
context = {},
|
|
35
|
+
showHeader = true
|
|
36
|
+
}: AIChatInterfaceProps = $props();
|
|
37
|
+
|
|
38
|
+
let userInput = $state('');
|
|
39
|
+
let isProcessing = $state(false);
|
|
40
|
+
let error = $state<string | null>(null);
|
|
41
|
+
let chatContainer: HTMLDivElement | undefined = $state();
|
|
42
|
+
let isAdapterConfigured = $state(false);
|
|
43
|
+
let textareaRef: HTMLTextAreaElement | undefined = $state();
|
|
44
|
+
let thinkingMode = $state(true);
|
|
45
|
+
let availableHeight = $state('100vh');
|
|
46
|
+
|
|
47
|
+
const { sendButton, background } = $derived(
|
|
48
|
+
aiChatInterface({
|
|
49
|
+
color: color as
|
|
50
|
+
| 'default'
|
|
51
|
+
| 'primary'
|
|
52
|
+
| 'secondary'
|
|
53
|
+
| 'info'
|
|
54
|
+
| 'success'
|
|
55
|
+
| 'warning'
|
|
56
|
+
| 'danger',
|
|
57
|
+
loading: loading || isProcessing,
|
|
58
|
+
disabled: disabled || !isAdapterConfigured
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
function generateId(): string {
|
|
63
|
+
return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Simple and reliable height calculation using native ResizeObserver
|
|
67
|
+
function findLayoutParent() {
|
|
68
|
+
if (!chatContainer) return null;
|
|
69
|
+
|
|
70
|
+
let parent = chatContainer.parentElement;
|
|
71
|
+
while (parent && parent !== document.body) {
|
|
72
|
+
const style = window.getComputedStyle(parent);
|
|
73
|
+
// Look for containers with explicit height or overflow control
|
|
74
|
+
if (style.height !== 'auto' || style.overflow !== 'visible' || style.position !== 'static') {
|
|
75
|
+
return parent;
|
|
76
|
+
}
|
|
77
|
+
parent = parent.parentElement;
|
|
78
|
+
}
|
|
79
|
+
return document.body;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function updateAvailableHeight() {
|
|
83
|
+
const layoutParent = findLayoutParent();
|
|
84
|
+
if (layoutParent) {
|
|
85
|
+
const rect = layoutParent.getBoundingClientRect();
|
|
86
|
+
availableHeight = `${Math.max(rect.height, 300)}px`;
|
|
87
|
+
} else {
|
|
88
|
+
// Fallback: use full viewport since we couldn't find a layout parent
|
|
89
|
+
// This means the chat is likely the main content
|
|
90
|
+
availableHeight = `${Math.max(window.innerHeight, 300)}px`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Use ResizeObserver for efficient and accurate size tracking
|
|
95
|
+
$effect(() => {
|
|
96
|
+
if (typeof window !== 'undefined' && chatContainer) {
|
|
97
|
+
// Initial calculation
|
|
98
|
+
updateAvailableHeight();
|
|
99
|
+
|
|
100
|
+
// Set up ResizeObserver for the layout parent
|
|
101
|
+
const layoutParent = findLayoutParent();
|
|
102
|
+
if (layoutParent && 'ResizeObserver' in window) {
|
|
103
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
104
|
+
updateAvailableHeight();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
resizeObserver.observe(layoutParent);
|
|
108
|
+
|
|
109
|
+
// Also observe viewport changes
|
|
110
|
+
const handleResize = () => updateAvailableHeight();
|
|
111
|
+
window.addEventListener('resize', handleResize);
|
|
112
|
+
|
|
113
|
+
return () => {
|
|
114
|
+
resizeObserver.disconnect();
|
|
115
|
+
window.removeEventListener('resize', handleResize);
|
|
116
|
+
};
|
|
117
|
+
} else {
|
|
118
|
+
// Fallback for browsers without ResizeObserver
|
|
119
|
+
const handleResize = () => updateAvailableHeight();
|
|
120
|
+
window.addEventListener('resize', handleResize);
|
|
121
|
+
|
|
122
|
+
return () => {
|
|
123
|
+
window.removeEventListener('resize', handleResize);
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Container classes for the main layout - use calculated height
|
|
130
|
+
const containerClasses = $derived(
|
|
131
|
+
cn(
|
|
132
|
+
'w-full flex flex-col',
|
|
133
|
+
messages.length === 0
|
|
134
|
+
? 'mx-auto max-w-4xl items-center justify-center px-6'
|
|
135
|
+
: 'overflow-hidden',
|
|
136
|
+
messages.length > 0 && background,
|
|
137
|
+
className
|
|
138
|
+
)
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
function addMessage(
|
|
144
|
+
type: 'chat' | 'action' | 'thinking',
|
|
145
|
+
content: string,
|
|
146
|
+
action?: ChatAction,
|
|
147
|
+
messageId?: string,
|
|
148
|
+
thinkingContent?: string,
|
|
149
|
+
isThinkingComplete?: boolean
|
|
150
|
+
): ChatMessage {
|
|
151
|
+
const message: ChatMessage = {
|
|
152
|
+
id: messageId || generateId(),
|
|
153
|
+
type,
|
|
154
|
+
content,
|
|
155
|
+
timestamp: new Date(),
|
|
156
|
+
action,
|
|
157
|
+
thinkingContent,
|
|
158
|
+
isThinkingComplete
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
messages = [...messages, message];
|
|
162
|
+
|
|
163
|
+
return message;
|
|
164
|
+
}
|
|
165
|
+
|
|
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
|
+
}
|
|
175
|
+
: msg
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function handleSubmit() {
|
|
180
|
+
if (!userInput.trim() || isProcessing || disabled || !isAdapterConfigured) return;
|
|
181
|
+
|
|
182
|
+
const input = userInput.trim();
|
|
183
|
+
userInput = '';
|
|
184
|
+
error = null;
|
|
185
|
+
isProcessing = true;
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
// Add user message with proper prefix
|
|
189
|
+
addMessage('chat', `User: ${input}`);
|
|
190
|
+
|
|
191
|
+
// Check if adapter supports streaming and use it
|
|
192
|
+
if (adapter.sendMessageStream) {
|
|
193
|
+
// Create streaming callback
|
|
194
|
+
const onStream: StreamingCallback = (response) => {
|
|
195
|
+
if (response.messageId) {
|
|
196
|
+
// Check if message exists, if not create it
|
|
197
|
+
const existingMessage = messages.find((msg) => msg.id === response.messageId);
|
|
198
|
+
|
|
199
|
+
if (!existingMessage) {
|
|
200
|
+
// Only create new streaming message if there's content or thinking content
|
|
201
|
+
if (response.content || response.thinkingContent) {
|
|
202
|
+
addMessage(
|
|
203
|
+
response.type,
|
|
204
|
+
response.content,
|
|
205
|
+
response.action,
|
|
206
|
+
response.messageId,
|
|
207
|
+
response.thinkingContent,
|
|
208
|
+
response.isThinkingComplete
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
// Update existing message
|
|
213
|
+
updateMessageById(
|
|
214
|
+
response.messageId,
|
|
215
|
+
response.content,
|
|
216
|
+
response.thinkingContent,
|
|
217
|
+
response.isThinkingComplete
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Send with streaming (include thinking mode context)
|
|
224
|
+
const contextWithThinking = { ...context, thinkingMode } as Record<string, unknown>;
|
|
225
|
+
await adapter.sendMessageStream(input, onStream, contextWithThinking);
|
|
226
|
+
} else {
|
|
227
|
+
// Fallback to regular sendMessage
|
|
228
|
+
const contextWithThinking = { ...context, thinkingMode } as Record<string, unknown>;
|
|
229
|
+
const response = await adapter.sendMessage(input, contextWithThinking);
|
|
230
|
+
addMessage(
|
|
231
|
+
response.type,
|
|
232
|
+
response.content,
|
|
233
|
+
response.action,
|
|
234
|
+
undefined,
|
|
235
|
+
response.thinkingContent,
|
|
236
|
+
response.isThinkingComplete
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
} catch (err) {
|
|
240
|
+
console.error('AI request failed:', err);
|
|
241
|
+
|
|
242
|
+
let errorMessage = 'Something went wrong. Please try again.';
|
|
243
|
+
|
|
244
|
+
if (err instanceof Error) {
|
|
245
|
+
errorMessage = err.message;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
addMessage('chat', `Sorry, ${errorMessage}`);
|
|
249
|
+
|
|
250
|
+
error = errorMessage;
|
|
251
|
+
setTimeout(() => {
|
|
252
|
+
error = null;
|
|
253
|
+
}, 5000);
|
|
254
|
+
} finally {
|
|
255
|
+
isProcessing = false;
|
|
256
|
+
|
|
257
|
+
// Auto-focus the input after response is complete
|
|
258
|
+
setTimeout(() => {
|
|
259
|
+
textareaRef?.focus();
|
|
260
|
+
}, 100);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
265
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
266
|
+
event.preventDefault();
|
|
267
|
+
handleSubmit();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function handleThinkingToggle(enabled: boolean) {
|
|
272
|
+
thinkingMode = enabled;
|
|
273
|
+
// You can add additional logic here when thinking mode changes
|
|
274
|
+
console.log('Thinking mode:', enabled ? 'enabled' : 'disabled');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
// Check adapter configuration
|
|
280
|
+
$effect(() => {
|
|
281
|
+
if (adapter) {
|
|
282
|
+
isAdapterConfigured = adapter.isConfigured();
|
|
283
|
+
|
|
284
|
+
// Load existing history if available
|
|
285
|
+
if (adapter.getHistory && messages.length === 0) {
|
|
286
|
+
const history = adapter.getHistory();
|
|
287
|
+
if (history.length > 0) {
|
|
288
|
+
messages = history;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
isAdapterConfigured = false;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Simple auto-scroll to bottom
|
|
297
|
+
$effect(() => {
|
|
298
|
+
if (chatContainer && messages.length > 0) {
|
|
299
|
+
setTimeout(() => {
|
|
300
|
+
chatContainer?.scrollTo({ top: chatContainer.scrollHeight, behavior: 'smooth' });
|
|
301
|
+
}, 100);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
</script>
|
|
306
|
+
|
|
307
|
+
<!-- Unified Layout Container -->
|
|
308
|
+
<div class={containerClasses} bind:this={chatContainer} style="height: {availableHeight}">
|
|
309
|
+
<!-- Empty State Content (only shown when no messages) -->
|
|
310
|
+
{#if messages.length === 0}
|
|
311
|
+
<!-- Configuration Error -->
|
|
312
|
+
{#if !isAdapterConfigured}
|
|
313
|
+
<div class="mb-8 w-full max-w-2xl rounded-xl border border-amber-200 bg-amber-50 px-6 py-4">
|
|
314
|
+
<div class="flex items-center gap-3">
|
|
315
|
+
<div class="flex h-10 w-10 items-center justify-center rounded-full bg-amber-100">
|
|
316
|
+
<svg
|
|
317
|
+
class="h-5 w-5 text-amber-600"
|
|
318
|
+
fill="none"
|
|
319
|
+
stroke="currentColor"
|
|
320
|
+
viewBox="0 0 24 24"
|
|
321
|
+
>
|
|
322
|
+
<path
|
|
323
|
+
stroke-linecap="round"
|
|
324
|
+
stroke-linejoin="round"
|
|
325
|
+
stroke-width="2"
|
|
326
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16c-.77.833.192 2.5 1.732 2.5z"
|
|
327
|
+
/>
|
|
328
|
+
</svg>
|
|
329
|
+
</div>
|
|
330
|
+
<div>
|
|
331
|
+
<h3 class="font-medium text-amber-900">Configuration Required</h3>
|
|
332
|
+
<p class="text-sm text-amber-700">
|
|
333
|
+
Please configure your AI adapter to start chatting.
|
|
334
|
+
</p>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
{/if}
|
|
339
|
+
|
|
340
|
+
<!-- Centered Content -->
|
|
341
|
+
<div class="mb-8 text-center">
|
|
342
|
+
<h1 class="mb-8 text-4xl font-medium text-gray-900">{title}</h1>
|
|
343
|
+
{#if subtitle}
|
|
344
|
+
<p class="mb-6 text-lg text-gray-600">{subtitle}</p>
|
|
345
|
+
{/if}
|
|
346
|
+
</div>
|
|
347
|
+
{:else}
|
|
348
|
+
<!-- Chat Layout Header -->
|
|
349
|
+
<div
|
|
350
|
+
class="sticky top-0 z-10 flex flex-shrink-0 items-center justify-between border-b border-gray-200 bg-white/50 px-6 py-3 backdrop-blur-sm"
|
|
351
|
+
>
|
|
352
|
+
<h1 class="text-sm font-medium text-gray-900">{title}</h1>
|
|
353
|
+
<div class="flex items-center gap-2">
|
|
354
|
+
{#if isAdapterConfigured}
|
|
355
|
+
<div
|
|
356
|
+
class="flex items-center gap-2 rounded-full bg-emerald-100/50 px-2 py-1 text-xs font-medium text-emerald-700"
|
|
357
|
+
>
|
|
358
|
+
<div class="h-1.5 w-1.5 rounded-full bg-emerald-500"></div>
|
|
359
|
+
Online
|
|
360
|
+
</div>
|
|
361
|
+
{:else}
|
|
362
|
+
<div
|
|
363
|
+
class="flex items-center gap-2 rounded-full bg-gray-100/50 px-2 py-1 text-xs font-medium text-gray-500"
|
|
364
|
+
>
|
|
365
|
+
<div class="h-1.5 w-1.5 rounded-full bg-gray-400"></div>
|
|
366
|
+
Offline
|
|
367
|
+
</div>
|
|
368
|
+
{/if}
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<!-- Chat Messages Area -->
|
|
373
|
+
<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">
|
|
375
|
+
{#each messages as message (message.id)}
|
|
376
|
+
{@const isUser = message.content.startsWith('User:')}
|
|
377
|
+
{@const displayContent = isUser ? message.content.replace('User: ', '') : message.content}
|
|
378
|
+
|
|
379
|
+
<!-- Show thinking display for AI messages when thinking mode is enabled -->
|
|
380
|
+
{#if !isUser && message.thinkingContent}
|
|
381
|
+
<ThinkingDisplay
|
|
382
|
+
content={message.thinkingContent}
|
|
383
|
+
isComplete={message.isThinkingComplete || false}
|
|
384
|
+
class="mb-2"
|
|
385
|
+
/>
|
|
386
|
+
{/if}
|
|
387
|
+
|
|
388
|
+
<!-- Only show message if it has content -->
|
|
389
|
+
{#if displayContent.trim()}
|
|
390
|
+
<div class={cn('mb-4 flex w-full', isUser ? 'justify-end' : 'justify-start')}>
|
|
391
|
+
<MessageBox content={displayContent} {isUser} {color} timestamp={message.timestamp} />
|
|
392
|
+
</div>
|
|
393
|
+
{/if}
|
|
394
|
+
{/each}
|
|
395
|
+
|
|
396
|
+
<!-- Typing Indicator -->
|
|
397
|
+
{#if isProcessing}
|
|
398
|
+
<div class="flex w-full justify-start">
|
|
399
|
+
<div
|
|
400
|
+
class="rounded-md border border-gray-200 bg-white/80 px-4 py-3 shadow-sm backdrop-blur-sm"
|
|
401
|
+
>
|
|
402
|
+
<div class="flex items-center space-x-1">
|
|
403
|
+
<div class="h-2 w-2 animate-bounce rounded-full bg-gray-400"></div>
|
|
404
|
+
<div
|
|
405
|
+
class="h-2 w-2 animate-bounce rounded-full bg-gray-400"
|
|
406
|
+
style="animation-delay: 0.1s"
|
|
407
|
+
></div>
|
|
408
|
+
<div
|
|
409
|
+
class="h-2 w-2 animate-bounce rounded-full bg-gray-400"
|
|
410
|
+
style="animation-delay: 0.2s"
|
|
411
|
+
></div>
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
{/if}
|
|
416
|
+
</div>
|
|
417
|
+
</main>
|
|
418
|
+
{/if}
|
|
419
|
+
|
|
420
|
+
<!-- Single Input Area (used by both layouts) -->
|
|
421
|
+
<ChatInput
|
|
422
|
+
bind:userInput
|
|
423
|
+
bind:textareaRef
|
|
424
|
+
bind:thinkingMode
|
|
425
|
+
placeholder={isAdapterConfigured ? placeholder : 'Configure AI adapter to start chatting...'}
|
|
426
|
+
{isProcessing}
|
|
427
|
+
{disabled}
|
|
428
|
+
{isAdapterConfigured}
|
|
429
|
+
{color}
|
|
430
|
+
hasMessages={messages.length > 0}
|
|
431
|
+
onSubmit={handleSubmit}
|
|
432
|
+
onKeyDown={handleKeyDown}
|
|
433
|
+
onThinkingToggle={handleThinkingToggle}
|
|
434
|
+
/>
|
|
435
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ChatMessage, VariantColors } from '../index.js';
|
|
2
|
+
import type { AIAdapter } from '../adapters/ai/index.js';
|
|
3
|
+
interface AIChatInterfaceProps {
|
|
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
|
+
showHeader?: boolean;
|
|
15
|
+
}
|
|
16
|
+
declare const AiChatInterface: import("svelte").Component<AIChatInterfaceProps, {}, "messages">;
|
|
17
|
+
type AiChatInterface = ReturnType<typeof AiChatInterface>;
|
|
18
|
+
export default AiChatInterface;
|