@albionlabs/chat-widget 0.1.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/dist/ChatWidget.svelte +118 -0
- package/dist/ChatWidget.svelte.d.ts +8 -0
- package/dist/ChatWidgetFloating.svelte +433 -0
- package/dist/ChatWidgetFloating.svelte.d.ts +8 -0
- package/dist/components/ChatBubble.svelte +824 -0
- package/dist/components/ChatBubble.svelte.d.ts +7 -0
- package/dist/components/MessageInput.svelte +85 -0
- package/dist/components/MessageInput.svelte.d.ts +3 -0
- package/dist/components/MessageList.svelte +60 -0
- package/dist/components/MessageList.svelte.d.ts +3 -0
- package/dist/components/WalletStatusIndicator.svelte +83 -0
- package/dist/components/WalletStatusIndicator.svelte.d.ts +6 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/services/gateway-api.d.ts +11 -0
- package/dist/services/gateway-api.js +71 -0
- package/dist/services/gateway-types.d.ts +134 -0
- package/dist/services/gateway-types.js +3 -0
- package/dist/services/siwe.d.ts +10 -0
- package/dist/services/siwe.js +24 -0
- package/dist/stores/auth.d.ts +9 -0
- package/dist/stores/auth.js +14 -0
- package/dist/stores/chat.d.ts +15 -0
- package/dist/stores/chat.js +387 -0
- package/dist/stores/wallet.d.ts +9 -0
- package/dist/stores/wallet.js +90 -0
- package/dist/types.d.ts +44 -0
- package/dist/types.js +1 -0
- package/package.json +34 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, onDestroy } from 'svelte';
|
|
3
|
+
import { connect, disconnect, reconnect, chat } from './stores/chat.js';
|
|
4
|
+
import MessageList from './components/MessageList.svelte';
|
|
5
|
+
import MessageInput from './components/MessageInput.svelte';
|
|
6
|
+
import type { ChatWidgetConfig } from './types.js';
|
|
7
|
+
|
|
8
|
+
let { config, hideHeader = false }: { config: ChatWidgetConfig; hideHeader?: boolean } = $props();
|
|
9
|
+
|
|
10
|
+
onMount(() => {
|
|
11
|
+
if (config.token) {
|
|
12
|
+
connect(config.gatewayUrl, config.token);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
onDestroy(() => {
|
|
17
|
+
disconnect();
|
|
18
|
+
});
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<div class="chat-widget">
|
|
22
|
+
{#if !hideHeader}
|
|
23
|
+
<div class="chat-header">
|
|
24
|
+
<span class="chat-title">quant.bot</span>
|
|
25
|
+
{#if $chat.reconnecting}
|
|
26
|
+
<span class="status-dot reconnecting"></span>
|
|
27
|
+
<span class="reconnecting-label">Reconnecting...</span>
|
|
28
|
+
{:else}
|
|
29
|
+
<span class="status-dot" class:connected={$chat.connected}></span>
|
|
30
|
+
{#if !$chat.connected}
|
|
31
|
+
<button class="reconnect-btn" onclick={reconnect}>Reconnect</button>
|
|
32
|
+
{/if}
|
|
33
|
+
{/if}
|
|
34
|
+
{#if $chat.connected && $chat.backendVersion}
|
|
35
|
+
<span class="version-label">v {$chat.backendVersion}</span>
|
|
36
|
+
{/if}
|
|
37
|
+
</div>
|
|
38
|
+
{/if}
|
|
39
|
+
<MessageList />
|
|
40
|
+
<MessageInput />
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<style>
|
|
44
|
+
.chat-widget {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
width: 100%;
|
|
48
|
+
height: 100%;
|
|
49
|
+
min-height: 400px;
|
|
50
|
+
border: 1px solid #e5e7eb;
|
|
51
|
+
border-radius: 0.75rem;
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
54
|
+
background: white;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.chat-header {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: 0.5rem;
|
|
61
|
+
padding: 0.75rem 1rem;
|
|
62
|
+
background: #1f2937;
|
|
63
|
+
color: white;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.chat-title {
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
font-size: 0.9rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.status-dot {
|
|
72
|
+
width: 0.5rem;
|
|
73
|
+
height: 0.5rem;
|
|
74
|
+
border-radius: 50%;
|
|
75
|
+
background: #ef4444;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.status-dot.connected {
|
|
79
|
+
background: #22c55e;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.status-dot.reconnecting {
|
|
83
|
+
background: #f59e0b;
|
|
84
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@keyframes pulse {
|
|
88
|
+
0%, 100% { opacity: 1; }
|
|
89
|
+
50% { opacity: 0.4; }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.reconnecting-label {
|
|
93
|
+
font-size: 0.7rem;
|
|
94
|
+
color: #f59e0b;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.reconnect-btn {
|
|
98
|
+
font-size: 0.7rem;
|
|
99
|
+
padding: 0.15rem 0.5rem;
|
|
100
|
+
border: 1px solid #6b7280;
|
|
101
|
+
border-radius: 0.25rem;
|
|
102
|
+
background: transparent;
|
|
103
|
+
color: white;
|
|
104
|
+
cursor: pointer;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.reconnect-btn:hover {
|
|
108
|
+
background: rgba(255, 255, 255, 0.1);
|
|
109
|
+
border-color: #9ca3af;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.version-label {
|
|
113
|
+
margin-left: auto;
|
|
114
|
+
font-size: 0.65rem;
|
|
115
|
+
font-family: monospace;
|
|
116
|
+
color: #9ca3af;
|
|
117
|
+
}
|
|
118
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ChatWidgetConfig } from './types.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
config: ChatWidgetConfig;
|
|
4
|
+
hideHeader?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare const ChatWidget: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type ChatWidget = ReturnType<typeof ChatWidget>;
|
|
8
|
+
export default ChatWidget;
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import { get } from 'svelte/store';
|
|
4
|
+
import ChatWidget from './ChatWidget.svelte';
|
|
5
|
+
import WalletStatusIndicator from './components/WalletStatusIndicator.svelte';
|
|
6
|
+
import { chat, connect, reconnect } from './stores/chat.js';
|
|
7
|
+
import { auth, setAuth } from './stores/auth.js';
|
|
8
|
+
import { walletProvider } from './stores/wallet.js';
|
|
9
|
+
import { setGatewayBaseUrl, login } from './services/gateway-api.js';
|
|
10
|
+
import { createSiweMessage, generateNonce } from './services/siwe.js';
|
|
11
|
+
import type { FloatingChatWidgetConfig, FloatingChatCallbacks, ChatWidgetConfig } from './types.js';
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
config,
|
|
15
|
+
callbacks = {}
|
|
16
|
+
}: {
|
|
17
|
+
config: FloatingChatWidgetConfig;
|
|
18
|
+
callbacks?: FloatingChatCallbacks;
|
|
19
|
+
} = $props();
|
|
20
|
+
|
|
21
|
+
const position = $derived(config.position ?? 'bottom-right');
|
|
22
|
+
const offset = $derived(config.offset ?? { x: 24, y: 24 });
|
|
23
|
+
|
|
24
|
+
let isOpen = $state(false);
|
|
25
|
+
let unreadCount = $state(0);
|
|
26
|
+
let lastSeenMessageCount = $state(0);
|
|
27
|
+
let signingIn = $state(false);
|
|
28
|
+
let siweError = $state<string | null>(null);
|
|
29
|
+
|
|
30
|
+
const httpBaseUrl = $derived(
|
|
31
|
+
config.gatewayUrl.replace(/^ws:\/\//, 'http://').replace(/^wss:\/\//, 'https://')
|
|
32
|
+
);
|
|
33
|
+
const wsUrl = $derived(
|
|
34
|
+
config.gatewayUrl.replace(/^http:\/\//, 'ws://').replace(/^https:\/\//, 'wss://')
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const chatConfig = $derived<ChatWidgetConfig>({
|
|
38
|
+
gatewayUrl: wsUrl,
|
|
39
|
+
token: $auth.token ?? undefined
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Track unread messages while collapsed
|
|
43
|
+
$effect(() => {
|
|
44
|
+
const messages = $chat.messages;
|
|
45
|
+
if (isOpen) {
|
|
46
|
+
lastSeenMessageCount = messages.length;
|
|
47
|
+
unreadCount = 0;
|
|
48
|
+
} else {
|
|
49
|
+
const newMessages = messages.slice(lastSeenMessageCount);
|
|
50
|
+
const newAssistant = newMessages.filter((m) => m.role === 'assistant').length;
|
|
51
|
+
if (newAssistant > 0) {
|
|
52
|
+
unreadCount += newAssistant;
|
|
53
|
+
}
|
|
54
|
+
lastSeenMessageCount = messages.length;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Auto-trigger SIWE when wallet appears and not authed
|
|
59
|
+
$effect(() => {
|
|
60
|
+
const provider = $walletProvider;
|
|
61
|
+
const authenticated = $auth.authenticated;
|
|
62
|
+
if (provider && !authenticated && !signingIn) {
|
|
63
|
+
handleSiweLogin();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// When auth completes, connect WebSocket
|
|
68
|
+
$effect(() => {
|
|
69
|
+
const authenticated = $auth.authenticated;
|
|
70
|
+
const token = $auth.token;
|
|
71
|
+
if (authenticated && token) {
|
|
72
|
+
connect(wsUrl, token, config.apiKey);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
onMount(() => {
|
|
77
|
+
setGatewayBaseUrl(httpBaseUrl, config.apiKey);
|
|
78
|
+
if (config.startOpen) {
|
|
79
|
+
isOpen = true;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
function toggle() {
|
|
84
|
+
isOpen = !isOpen;
|
|
85
|
+
if (isOpen) {
|
|
86
|
+
unreadCount = 0;
|
|
87
|
+
lastSeenMessageCount = get(chat).messages.length;
|
|
88
|
+
callbacks.onOpen?.();
|
|
89
|
+
} else {
|
|
90
|
+
callbacks.onClose?.();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function handleSiweLogin() {
|
|
95
|
+
const provider = get(walletProvider);
|
|
96
|
+
if (!provider || signingIn) return;
|
|
97
|
+
|
|
98
|
+
siweError = null;
|
|
99
|
+
signingIn = true;
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// Get wallet address
|
|
103
|
+
const accounts = await provider.request({ method: 'eth_accounts' }) as string[];
|
|
104
|
+
const walletAddress = accounts?.[0];
|
|
105
|
+
if (!walletAddress) throw new Error('No wallet address available');
|
|
106
|
+
|
|
107
|
+
const message = createSiweMessage({
|
|
108
|
+
domain: window.location.host,
|
|
109
|
+
address: walletAddress,
|
|
110
|
+
uri: window.location.origin,
|
|
111
|
+
chainId: 1,
|
|
112
|
+
nonce: generateNonce(),
|
|
113
|
+
statement: 'Sign in to quant.bot'
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const signature = await provider.request({
|
|
117
|
+
method: 'personal_sign',
|
|
118
|
+
params: [message, walletAddress]
|
|
119
|
+
}) as string;
|
|
120
|
+
|
|
121
|
+
const result = await login(signature, message, walletAddress);
|
|
122
|
+
setAuth(result.token, walletAddress, result.user.id);
|
|
123
|
+
} catch (e) {
|
|
124
|
+
siweError = e instanceof Error ? e.message : 'Sign-in failed';
|
|
125
|
+
} finally {
|
|
126
|
+
signingIn = false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
</script>
|
|
130
|
+
|
|
131
|
+
<div
|
|
132
|
+
class="floating-container"
|
|
133
|
+
class:bottom-right={position === 'bottom-right'}
|
|
134
|
+
class:bottom-left={position === 'bottom-left'}
|
|
135
|
+
style="--offset-x: {offset.x}px; --offset-y: {offset.y}px;"
|
|
136
|
+
>
|
|
137
|
+
<!-- Chat Panel -->
|
|
138
|
+
<div class="chat-panel" class:open={isOpen}>
|
|
139
|
+
<div class="panel-header">
|
|
140
|
+
<span class="panel-title">quant.bot</span>
|
|
141
|
+
<WalletStatusIndicator onRequestWalletConnect={callbacks.onRequestWalletConnect} />
|
|
142
|
+
{#if $chat.reconnecting}
|
|
143
|
+
<span class="status-dot reconnecting"></span>
|
|
144
|
+
{:else if $auth.authenticated}
|
|
145
|
+
<span class="status-dot" class:connected={$chat.connected}></span>
|
|
146
|
+
{#if !$chat.connected}
|
|
147
|
+
<button class="reconnect-btn" onclick={reconnect}>Reconnect</button>
|
|
148
|
+
{/if}
|
|
149
|
+
{/if}
|
|
150
|
+
{#if $chat.connected && $chat.backendVersion}
|
|
151
|
+
<span class="version-label">v {$chat.backendVersion}</span>
|
|
152
|
+
{/if}
|
|
153
|
+
<button class="close-btn" onclick={toggle} aria-label="Close chat">
|
|
154
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
|
155
|
+
<path d="M1 1L13 13M13 1L1 13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
156
|
+
</svg>
|
|
157
|
+
</button>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<div class="panel-body">
|
|
161
|
+
{#if !$walletProvider && !$auth.authenticated}
|
|
162
|
+
<div class="auth-prompt">
|
|
163
|
+
<p>Connect your wallet to sign in</p>
|
|
164
|
+
<button class="connect-wallet-btn" onclick={() => callbacks.onRequestWalletConnect?.()}>
|
|
165
|
+
Connect Wallet
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
{:else if signingIn}
|
|
169
|
+
<div class="auth-prompt">
|
|
170
|
+
<p>Signing in...</p>
|
|
171
|
+
<div class="spinner"></div>
|
|
172
|
+
</div>
|
|
173
|
+
{:else if siweError}
|
|
174
|
+
<div class="auth-prompt">
|
|
175
|
+
<p class="error-text">{siweError}</p>
|
|
176
|
+
<button class="connect-wallet-btn" onclick={handleSiweLogin}>
|
|
177
|
+
Retry Sign In
|
|
178
|
+
</button>
|
|
179
|
+
</div>
|
|
180
|
+
{:else if $auth.authenticated}
|
|
181
|
+
<ChatWidget config={chatConfig} hideHeader={true} />
|
|
182
|
+
{:else}
|
|
183
|
+
<div class="auth-prompt">
|
|
184
|
+
<p>Preparing sign-in...</p>
|
|
185
|
+
<div class="spinner"></div>
|
|
186
|
+
</div>
|
|
187
|
+
{/if}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<!-- Floating Bubble -->
|
|
192
|
+
<button class="chat-bubble-btn" onclick={toggle} aria-label={isOpen ? 'Close chat' : 'Open chat'}>
|
|
193
|
+
{#if isOpen}
|
|
194
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
195
|
+
<path d="M6 6L18 18M18 6L6 18" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
|
196
|
+
</svg>
|
|
197
|
+
{:else}
|
|
198
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
199
|
+
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
200
|
+
</svg>
|
|
201
|
+
{/if}
|
|
202
|
+
{#if unreadCount > 0 && !isOpen}
|
|
203
|
+
<span class="unread-badge">{unreadCount > 9 ? '9+' : unreadCount}</span>
|
|
204
|
+
{/if}
|
|
205
|
+
</button>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<style>
|
|
209
|
+
.floating-container {
|
|
210
|
+
position: fixed;
|
|
211
|
+
z-index: 9999;
|
|
212
|
+
display: flex;
|
|
213
|
+
flex-direction: column;
|
|
214
|
+
align-items: flex-end;
|
|
215
|
+
gap: 0.75rem;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.floating-container.bottom-right {
|
|
219
|
+
bottom: var(--offset-y);
|
|
220
|
+
right: var(--offset-x);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.floating-container.bottom-left {
|
|
224
|
+
bottom: var(--offset-y);
|
|
225
|
+
left: var(--offset-x);
|
|
226
|
+
align-items: flex-start;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.chat-panel {
|
|
230
|
+
width: 400px;
|
|
231
|
+
height: 600px;
|
|
232
|
+
max-width: calc(100vw - 48px);
|
|
233
|
+
max-height: calc(100vh - 120px);
|
|
234
|
+
border-radius: 0.75rem;
|
|
235
|
+
border: 1px solid #e5e7eb;
|
|
236
|
+
background: white;
|
|
237
|
+
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
238
|
+
display: flex;
|
|
239
|
+
flex-direction: column;
|
|
240
|
+
overflow: hidden;
|
|
241
|
+
transform: scale(0.95) translateY(10px);
|
|
242
|
+
opacity: 0;
|
|
243
|
+
pointer-events: none;
|
|
244
|
+
transition: transform 0.2s ease, opacity 0.2s ease;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.chat-panel.open {
|
|
248
|
+
transform: scale(1) translateY(0);
|
|
249
|
+
opacity: 1;
|
|
250
|
+
pointer-events: auto;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.panel-header {
|
|
254
|
+
display: flex;
|
|
255
|
+
align-items: center;
|
|
256
|
+
gap: 0.5rem;
|
|
257
|
+
padding: 0.6rem 0.75rem;
|
|
258
|
+
background: #1f2937;
|
|
259
|
+
color: white;
|
|
260
|
+
flex-shrink: 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.panel-title {
|
|
264
|
+
font-weight: 600;
|
|
265
|
+
font-size: 0.85rem;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.status-dot {
|
|
269
|
+
width: 0.45rem;
|
|
270
|
+
height: 0.45rem;
|
|
271
|
+
border-radius: 50%;
|
|
272
|
+
background: #ef4444;
|
|
273
|
+
flex-shrink: 0;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.status-dot.connected {
|
|
277
|
+
background: #22c55e;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.status-dot.reconnecting {
|
|
281
|
+
background: #f59e0b;
|
|
282
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@keyframes pulse {
|
|
286
|
+
0%, 100% { opacity: 1; }
|
|
287
|
+
50% { opacity: 0.4; }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.reconnect-btn {
|
|
291
|
+
font-size: 0.65rem;
|
|
292
|
+
padding: 0.1rem 0.4rem;
|
|
293
|
+
border: 1px solid #6b7280;
|
|
294
|
+
border-radius: 0.25rem;
|
|
295
|
+
background: transparent;
|
|
296
|
+
color: white;
|
|
297
|
+
cursor: pointer;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.reconnect-btn:hover {
|
|
301
|
+
background: rgba(255, 255, 255, 0.1);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.version-label {
|
|
305
|
+
margin-left: auto;
|
|
306
|
+
font-size: 0.6rem;
|
|
307
|
+
font-family: monospace;
|
|
308
|
+
color: #9ca3af;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.close-btn {
|
|
312
|
+
display: flex;
|
|
313
|
+
align-items: center;
|
|
314
|
+
justify-content: center;
|
|
315
|
+
width: 1.5rem;
|
|
316
|
+
height: 1.5rem;
|
|
317
|
+
border: none;
|
|
318
|
+
border-radius: 0.25rem;
|
|
319
|
+
background: transparent;
|
|
320
|
+
color: #9ca3af;
|
|
321
|
+
cursor: pointer;
|
|
322
|
+
flex-shrink: 0;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.close-btn:hover {
|
|
326
|
+
color: white;
|
|
327
|
+
background: rgba(255, 255, 255, 0.1);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.panel-body {
|
|
331
|
+
flex: 1;
|
|
332
|
+
overflow: hidden;
|
|
333
|
+
display: flex;
|
|
334
|
+
flex-direction: column;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.auth-prompt {
|
|
338
|
+
flex: 1;
|
|
339
|
+
display: flex;
|
|
340
|
+
flex-direction: column;
|
|
341
|
+
align-items: center;
|
|
342
|
+
justify-content: center;
|
|
343
|
+
gap: 0.75rem;
|
|
344
|
+
padding: 2rem;
|
|
345
|
+
color: #6b7280;
|
|
346
|
+
font-size: 0.875rem;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.auth-prompt p {
|
|
350
|
+
margin: 0;
|
|
351
|
+
text-align: center;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.error-text {
|
|
355
|
+
color: #dc2626;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.connect-wallet-btn {
|
|
359
|
+
padding: 0.45rem 1rem;
|
|
360
|
+
border: none;
|
|
361
|
+
border-radius: 0.5rem;
|
|
362
|
+
background: #1f2937;
|
|
363
|
+
color: white;
|
|
364
|
+
font-size: 0.8rem;
|
|
365
|
+
font-weight: 500;
|
|
366
|
+
cursor: pointer;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.connect-wallet-btn:hover {
|
|
370
|
+
background: #374151;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.spinner {
|
|
374
|
+
width: 1.5rem;
|
|
375
|
+
height: 1.5rem;
|
|
376
|
+
border: 2px solid #e5e7eb;
|
|
377
|
+
border-top-color: #1f2937;
|
|
378
|
+
border-radius: 50%;
|
|
379
|
+
animation: spin 0.6s linear infinite;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
@keyframes spin {
|
|
383
|
+
to { transform: rotate(360deg); }
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.chat-bubble-btn {
|
|
387
|
+
width: 56px;
|
|
388
|
+
height: 56px;
|
|
389
|
+
border-radius: 50%;
|
|
390
|
+
border: none;
|
|
391
|
+
background: #1f2937;
|
|
392
|
+
color: white;
|
|
393
|
+
cursor: pointer;
|
|
394
|
+
display: flex;
|
|
395
|
+
align-items: center;
|
|
396
|
+
justify-content: center;
|
|
397
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
398
|
+
position: relative;
|
|
399
|
+
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
400
|
+
flex-shrink: 0;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.chat-bubble-btn:hover {
|
|
404
|
+
transform: scale(1.05);
|
|
405
|
+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.unread-badge {
|
|
409
|
+
position: absolute;
|
|
410
|
+
top: -4px;
|
|
411
|
+
right: -4px;
|
|
412
|
+
min-width: 1.1rem;
|
|
413
|
+
height: 1.1rem;
|
|
414
|
+
border-radius: 9999px;
|
|
415
|
+
background: #ef4444;
|
|
416
|
+
color: white;
|
|
417
|
+
font-size: 0.65rem;
|
|
418
|
+
font-weight: 700;
|
|
419
|
+
display: flex;
|
|
420
|
+
align-items: center;
|
|
421
|
+
justify-content: center;
|
|
422
|
+
padding: 0 0.25rem;
|
|
423
|
+
line-height: 1;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@media (max-width: 480px) {
|
|
427
|
+
.chat-panel {
|
|
428
|
+
width: calc(100vw - 24px);
|
|
429
|
+
height: calc(100vh - 100px);
|
|
430
|
+
max-width: none;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FloatingChatWidgetConfig, FloatingChatCallbacks } from './types.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
config: FloatingChatWidgetConfig;
|
|
4
|
+
callbacks?: FloatingChatCallbacks;
|
|
5
|
+
};
|
|
6
|
+
declare const ChatWidgetFloating: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type ChatWidgetFloating = ReturnType<typeof ChatWidgetFloating>;
|
|
8
|
+
export default ChatWidgetFloating;
|