@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.
@@ -0,0 +1,7 @@
1
+ import type { DisplayMessage } from '../types.js';
2
+ type $$ComponentProps = {
3
+ message: DisplayMessage;
4
+ };
5
+ declare const ChatBubble: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type ChatBubble = ReturnType<typeof ChatBubble>;
7
+ export default ChatBubble;
@@ -0,0 +1,85 @@
1
+ <script lang="ts">
2
+ import { chat, sendMessage } from '../stores/chat.js';
3
+
4
+ let input = $state('');
5
+
6
+ function handleSubmit(e: Event) {
7
+ e.preventDefault();
8
+ const trimmed = input.trim();
9
+ if (!trimmed || !$chat.connected) return;
10
+ sendMessage(trimmed);
11
+ input = '';
12
+ }
13
+
14
+ function handleKeydown(e: KeyboardEvent) {
15
+ if (e.key === 'Enter' && !e.shiftKey) {
16
+ e.preventDefault();
17
+ handleSubmit(e);
18
+ }
19
+ }
20
+ </script>
21
+
22
+ <form class="message-input" onsubmit={handleSubmit}>
23
+ <textarea
24
+ bind:value={input}
25
+ onkeydown={handleKeydown}
26
+ placeholder={$chat.connected ? 'Type a message...' : 'Reconnecting...'}
27
+ disabled={!$chat.connected}
28
+ rows={1}
29
+ ></textarea>
30
+ <button type="submit" disabled={!$chat.connected || !input.trim()}>
31
+ Send
32
+ </button>
33
+ </form>
34
+
35
+ <style>
36
+ .message-input {
37
+ display: flex;
38
+ gap: 0.5rem;
39
+ padding: 0.75rem 1rem;
40
+ border-top: 1px solid #e5e7eb;
41
+ background: white;
42
+ }
43
+
44
+ textarea {
45
+ flex: 1;
46
+ resize: none;
47
+ border: 1px solid #d1d5db;
48
+ border-radius: 0.5rem;
49
+ padding: 0.5rem 0.75rem;
50
+ font-family: inherit;
51
+ font-size: 0.875rem;
52
+ line-height: 1.5;
53
+ outline: none;
54
+ }
55
+
56
+ textarea:focus {
57
+ border-color: #3b82f6;
58
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
59
+ }
60
+
61
+ textarea:disabled {
62
+ background: #f9fafb;
63
+ cursor: not-allowed;
64
+ }
65
+
66
+ button {
67
+ padding: 0.5rem 1rem;
68
+ background: #3b82f6;
69
+ color: white;
70
+ border: none;
71
+ border-radius: 0.5rem;
72
+ font-size: 0.875rem;
73
+ cursor: pointer;
74
+ white-space: nowrap;
75
+ }
76
+
77
+ button:hover:not(:disabled) {
78
+ background: #2563eb;
79
+ }
80
+
81
+ button:disabled {
82
+ opacity: 0.5;
83
+ cursor: not-allowed;
84
+ }
85
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const MessageInput: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type MessageInput = ReturnType<typeof MessageInput>;
3
+ export default MessageInput;
@@ -0,0 +1,60 @@
1
+ <script lang="ts">
2
+ import { chat } from '../stores/chat.js';
3
+ import ChatBubble from './ChatBubble.svelte';
4
+
5
+ let container: HTMLDivElement | undefined = $state();
6
+
7
+ $effect(() => {
8
+ if ($chat.messages.length && container) {
9
+ container.scrollTop = container.scrollHeight;
10
+ }
11
+ });
12
+ </script>
13
+
14
+ <div class="message-list" bind:this={container}>
15
+ {#each $chat.messages as message (message.id)}
16
+ <ChatBubble {message} />
17
+ {/each}
18
+ {#if $chat.loading}
19
+ <div class="typing-indicator">
20
+ <span></span><span></span><span></span>
21
+ </div>
22
+ {/if}
23
+ </div>
24
+
25
+ <style>
26
+ .message-list {
27
+ flex: 1;
28
+ overflow-y: auto;
29
+ padding: 1rem;
30
+ display: flex;
31
+ flex-direction: column;
32
+ gap: 0.25rem;
33
+ }
34
+
35
+ .typing-indicator {
36
+ align-self: flex-start;
37
+ display: flex;
38
+ align-items: center;
39
+ gap: 0.25rem;
40
+ padding: 0.75rem 1rem;
41
+ background: #f3f4f6;
42
+ border-radius: 1rem;
43
+ }
44
+
45
+ .typing-indicator span {
46
+ width: 0.5rem;
47
+ height: 0.5rem;
48
+ background: #9ca3af;
49
+ border-radius: 50%;
50
+ animation: bounce 1.4s infinite ease-in-out both;
51
+ }
52
+
53
+ .typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
54
+ .typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
55
+
56
+ @keyframes bounce {
57
+ 0%, 80%, 100% { transform: scale(0); }
58
+ 40% { transform: scale(1); }
59
+ }
60
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const MessageList: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type MessageList = ReturnType<typeof MessageList>;
3
+ export default MessageList;
@@ -0,0 +1,83 @@
1
+ <script lang="ts">
2
+ import { walletProvider } from '../stores/wallet.js';
3
+ import { auth } from '../stores/auth.js';
4
+
5
+ let {
6
+ onRequestWalletConnect
7
+ }: {
8
+ onRequestWalletConnect?: () => void;
9
+ } = $props();
10
+
11
+ const hasWallet = $derived(!!$walletProvider);
12
+ const truncatedAddress = $derived(
13
+ $auth.address ? `${$auth.address.slice(0, 6)}...${$auth.address.slice(-4)}` : null
14
+ );
15
+ </script>
16
+
17
+ <div class="wallet-status">
18
+ {#if $auth.authenticated && truncatedAddress}
19
+ <span class="dot connected"></span>
20
+ <span class="address">{truncatedAddress}</span>
21
+ {:else if hasWallet}
22
+ <span class="dot wallet-ready"></span>
23
+ <span class="label">Wallet connected</span>
24
+ {:else}
25
+ <span class="dot disconnected"></span>
26
+ <button class="connect-btn" onclick={() => onRequestWalletConnect?.()}>
27
+ Connect Wallet
28
+ </button>
29
+ {/if}
30
+ </div>
31
+
32
+ <style>
33
+ .wallet-status {
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 0.4rem;
37
+ }
38
+
39
+ .dot {
40
+ width: 0.45rem;
41
+ height: 0.45rem;
42
+ border-radius: 50%;
43
+ flex-shrink: 0;
44
+ }
45
+
46
+ .dot.connected {
47
+ background: #22c55e;
48
+ }
49
+
50
+ .dot.wallet-ready {
51
+ background: #f59e0b;
52
+ }
53
+
54
+ .dot.disconnected {
55
+ background: #6b7280;
56
+ }
57
+
58
+ .address {
59
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
60
+ font-size: 0.72rem;
61
+ color: #d1d5db;
62
+ }
63
+
64
+ .label {
65
+ font-size: 0.72rem;
66
+ color: #d1d5db;
67
+ }
68
+
69
+ .connect-btn {
70
+ font-size: 0.7rem;
71
+ padding: 0.1rem 0.45rem;
72
+ border: 1px solid #6b7280;
73
+ border-radius: 0.25rem;
74
+ background: transparent;
75
+ color: white;
76
+ cursor: pointer;
77
+ }
78
+
79
+ .connect-btn:hover {
80
+ background: rgba(255, 255, 255, 0.1);
81
+ border-color: #9ca3af;
82
+ }
83
+ </style>
@@ -0,0 +1,6 @@
1
+ type $$ComponentProps = {
2
+ onRequestWalletConnect?: () => void;
3
+ };
4
+ declare const WalletStatusIndicator: import("svelte").Component<$$ComponentProps, {}, "">;
5
+ type WalletStatusIndicator = ReturnType<typeof WalletStatusIndicator>;
6
+ export default WalletStatusIndicator;
@@ -0,0 +1,10 @@
1
+ export { default as ChatWidget } from './ChatWidget.svelte';
2
+ export { default as ChatWidgetFloating } from './ChatWidgetFloating.svelte';
3
+ export { default as ChatBubble } from './components/ChatBubble.svelte';
4
+ export { default as MessageList } from './components/MessageList.svelte';
5
+ export { default as MessageInput } from './components/MessageInput.svelte';
6
+ export { default as WalletStatusIndicator } from './components/WalletStatusIndicator.svelte';
7
+ export { chat, connect, disconnect, reconnect, sendMessage } from './stores/chat.js';
8
+ export { auth, setAuth, clearAuth } from './stores/auth.js';
9
+ export { setWalletProvider, clearWalletProvider } from './stores/wallet.js';
10
+ export type { ChatWidgetConfig, DisplayMessage, WalletProvider, TxSignRequestPayload, FloatingChatWidgetConfig, FloatingChatCallbacks } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { default as ChatWidget } from './ChatWidget.svelte';
2
+ export { default as ChatWidgetFloating } from './ChatWidgetFloating.svelte';
3
+ export { default as ChatBubble } from './components/ChatBubble.svelte';
4
+ export { default as MessageList } from './components/MessageList.svelte';
5
+ export { default as MessageInput } from './components/MessageInput.svelte';
6
+ export { default as WalletStatusIndicator } from './components/WalletStatusIndicator.svelte';
7
+ export { chat, connect, disconnect, reconnect, sendMessage } from './stores/chat.js';
8
+ export { auth, setAuth, clearAuth } from './stores/auth.js';
9
+ export { setWalletProvider, clearWalletProvider } from './stores/wallet.js';
@@ -0,0 +1,11 @@
1
+ import type { LoginResponse, NpvResponse, OrderbookResponse, SigningBundle, SigningCompleteResponse, TokenLookupResponse, TokenMetadataResponse, TradeHistoryResponse } from './gateway-types.js';
2
+ export declare function setGatewayConfig(gatewayUrl: string, token: string, apiKey?: string): void;
3
+ export declare function setGatewayBaseUrl(gatewayUrl: string, apiKey?: string): void;
4
+ export declare function fetchSigningBundle(signingId: string): Promise<SigningBundle>;
5
+ export declare function completeSigningBundle(signingId: string, txHashes: string[]): Promise<SigningCompleteResponse>;
6
+ export declare function lookupToken(symbolOrAddress: string): Promise<TokenLookupResponse>;
7
+ export declare function fetchTokenMetadata(address: string, limit?: number): Promise<TokenMetadataResponse>;
8
+ export declare function fetchOrderbook(tokenAddress: string, side?: 'buy' | 'sell' | 'both'): Promise<OrderbookResponse>;
9
+ export declare function fetchTrades(tokenAddress: string, limit?: number): Promise<TradeHistoryResponse>;
10
+ export declare function fetchNpv(cashFlows: number[], discountRate: number): Promise<NpvResponse>;
11
+ export declare function login(signature: string, message: string, address: string): Promise<LoginResponse>;
@@ -0,0 +1,71 @@
1
+ let httpBaseUrl = '';
2
+ let authToken = '';
3
+ let apiKeyHeader = '';
4
+ function wsUrlToHttp(wsUrl) {
5
+ return wsUrl.replace(/^wss:\/\//, 'https://').replace(/^ws:\/\//, 'http://');
6
+ }
7
+ export function setGatewayConfig(gatewayUrl, token, apiKey) {
8
+ httpBaseUrl = wsUrlToHttp(gatewayUrl);
9
+ authToken = token;
10
+ apiKeyHeader = apiKey ?? '';
11
+ }
12
+ export function setGatewayBaseUrl(gatewayUrl, apiKey) {
13
+ httpBaseUrl = wsUrlToHttp(gatewayUrl);
14
+ apiKeyHeader = apiKey ?? '';
15
+ }
16
+ async function gatewayFetch(path, init) {
17
+ const headers = {
18
+ 'Content-Type': 'application/json',
19
+ ...init?.headers
20
+ };
21
+ if (authToken) {
22
+ headers['Authorization'] = `Bearer ${authToken}`;
23
+ }
24
+ if (apiKeyHeader) {
25
+ headers['X-Api-Key'] = apiKeyHeader;
26
+ }
27
+ const response = await fetch(`${httpBaseUrl}${path}`, {
28
+ ...init,
29
+ headers
30
+ });
31
+ if (!response.ok) {
32
+ const body = (await response.json().catch(() => ({ error: 'Unknown error' })));
33
+ throw new Error(body.error ?? `Request failed with status ${response.status}`);
34
+ }
35
+ return (await response.json());
36
+ }
37
+ export function fetchSigningBundle(signingId) {
38
+ return gatewayFetch(`/api/signing/${signingId}`);
39
+ }
40
+ export function completeSigningBundle(signingId, txHashes) {
41
+ return gatewayFetch(`/api/signing/${signingId}/complete`, {
42
+ method: 'POST',
43
+ body: JSON.stringify({ txHashes })
44
+ });
45
+ }
46
+ export function lookupToken(symbolOrAddress) {
47
+ return gatewayFetch(`/api/data/tokens/${encodeURIComponent(symbolOrAddress)}`);
48
+ }
49
+ export function fetchTokenMetadata(address, limit) {
50
+ const query = typeof limit === 'number' && Number.isFinite(limit) ? `?limit=${Math.floor(limit)}` : '';
51
+ return gatewayFetch(`/api/data/token-metadata/${encodeURIComponent(address)}${query}`);
52
+ }
53
+ export function fetchOrderbook(tokenAddress, side = 'both') {
54
+ return gatewayFetch(`/api/data/orderbook/${encodeURIComponent(tokenAddress)}?side=${side}`);
55
+ }
56
+ export function fetchTrades(tokenAddress, limit = 20) {
57
+ const boundedLimit = Math.max(1, Math.min(100, Math.floor(limit)));
58
+ return gatewayFetch(`/api/data/trades/${encodeURIComponent(tokenAddress)}?limit=${boundedLimit}`);
59
+ }
60
+ export function fetchNpv(cashFlows, discountRate) {
61
+ return gatewayFetch('/api/data/npv', {
62
+ method: 'POST',
63
+ body: JSON.stringify({ cashFlows, discountRate })
64
+ });
65
+ }
66
+ export function login(signature, message, address) {
67
+ return gatewayFetch('/api/auth/login', {
68
+ method: 'POST',
69
+ body: JSON.stringify({ signature, message, address })
70
+ });
71
+ }
@@ -0,0 +1,134 @@
1
+ export interface User {
2
+ id: string;
3
+ address: string;
4
+ createdAt: number;
5
+ }
6
+ export interface LoginResponse {
7
+ token: string;
8
+ user: User;
9
+ }
10
+ export interface ClientMessage {
11
+ type: 'message';
12
+ content: string;
13
+ sessionId?: string;
14
+ }
15
+ export interface ServerMessage {
16
+ type: 'message' | 'tool_call' | 'tool_result' | 'stream' | 'progress' | 'error' | 'connected';
17
+ sessionId: string;
18
+ role?: 'assistant';
19
+ content?: string;
20
+ name?: string;
21
+ args?: Record<string, unknown>;
22
+ result?: unknown;
23
+ delta?: string;
24
+ status?: string;
25
+ code?: string;
26
+ message?: string;
27
+ version?: string;
28
+ }
29
+ export interface RegistryToken {
30
+ address: string;
31
+ symbol: string;
32
+ name: string;
33
+ decimals: number;
34
+ logoURI?: string;
35
+ }
36
+ export interface TokenLookupResponse {
37
+ token: RegistryToken;
38
+ }
39
+ export interface DecodedMetaEntry {
40
+ id: string;
41
+ metaHash: string;
42
+ sender: string;
43
+ subject: string;
44
+ decodedData: Record<string, unknown> | null;
45
+ timestamp?: number;
46
+ }
47
+ export interface TokenMetadataResponse {
48
+ address: string;
49
+ display: string;
50
+ latest: DecodedMetaEntry | null;
51
+ history: DecodedMetaEntry[];
52
+ }
53
+ export interface OrderSummary {
54
+ orderHash: string;
55
+ price: number | null;
56
+ ioRatio?: number | null;
57
+ maxOutput: string | null;
58
+ inputToken: string;
59
+ outputToken: string;
60
+ inputSymbol: string | null;
61
+ outputSymbol: string | null;
62
+ }
63
+ export interface OrderbookResponse {
64
+ tokenAddress: string;
65
+ display: string;
66
+ bids?: OrderSummary[];
67
+ asks?: OrderSummary[];
68
+ nonUsdOrders?: OrderSummary[];
69
+ bestBid: number | null;
70
+ bestAsk: number | null;
71
+ spread: number | null;
72
+ bidCount: number;
73
+ askCount: number;
74
+ }
75
+ export interface TradeTokenAmount {
76
+ token: string;
77
+ symbol: string | null;
78
+ decimals: number | null;
79
+ amount: string;
80
+ readableAmount?: string;
81
+ }
82
+ export interface NormalizedTrade {
83
+ orderHash: string;
84
+ timestamp: number;
85
+ input: TradeTokenAmount;
86
+ output: TradeTokenAmount;
87
+ txHash: string;
88
+ }
89
+ export interface TradeHistoryResponse {
90
+ tokenAddress: string;
91
+ display: string;
92
+ trades?: NormalizedTrade[];
93
+ total: number;
94
+ }
95
+ export interface NpvResponse {
96
+ npv: number;
97
+ irr?: number;
98
+ }
99
+ export interface StagedTransaction {
100
+ label: string;
101
+ to: string;
102
+ data: string;
103
+ value?: string;
104
+ symbol?: string;
105
+ }
106
+ export interface TransactionSimulation {
107
+ index: number;
108
+ label: string;
109
+ success: boolean;
110
+ status?: 'ok' | 'requires_prior_state' | 'failed';
111
+ reasonCode?: string;
112
+ gasUsed: string;
113
+ error?: string;
114
+ }
115
+ export interface SigningBundle {
116
+ signingId: string;
117
+ chainId: number;
118
+ from: string;
119
+ transactions: Array<StagedTransaction & {
120
+ simulation: TransactionSimulation;
121
+ }>;
122
+ metadata?: {
123
+ operationType?: string;
124
+ strategyKey?: string;
125
+ composedRainlang?: string;
126
+ };
127
+ expiresAt: number;
128
+ }
129
+ export interface SigningCompleteResponse {
130
+ success: boolean;
131
+ orderHash?: string;
132
+ raindexUrl?: string;
133
+ message?: string;
134
+ }
@@ -0,0 +1,3 @@
1
+ // Inlined types from @quant-bot/shared-types for standalone publishing.
2
+ // Keep in sync with packages/shared-types/src/ when modifying.
3
+ export {};
@@ -0,0 +1,10 @@
1
+ export interface SiweParams {
2
+ domain: string;
3
+ address: string;
4
+ uri: string;
5
+ chainId: number;
6
+ nonce: string;
7
+ statement?: string;
8
+ }
9
+ export declare function createSiweMessage(params: SiweParams): string;
10
+ export declare function generateNonce(): string;
@@ -0,0 +1,24 @@
1
+ export function createSiweMessage(params) {
2
+ const issuedAt = new Date().toISOString();
3
+ // EIP-4361 format — must match exactly for siwe@3.x server-side parsing
4
+ let message = `${params.domain} wants you to sign in with your Ethereum account:\n`;
5
+ message += `${params.address}\n\n`;
6
+ if (params.statement) {
7
+ message += `${params.statement}\n\n`;
8
+ }
9
+ message += `URI: ${params.uri}\n`;
10
+ message += `Version: 1\n`;
11
+ message += `Chain ID: ${params.chainId}\n`;
12
+ message += `Nonce: ${params.nonce}\n`;
13
+ message += `Issued At: ${issuedAt}`;
14
+ return message;
15
+ }
16
+ export function generateNonce() {
17
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
18
+ let result = '';
19
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
20
+ for (const byte of bytes) {
21
+ result += chars[byte % chars.length];
22
+ }
23
+ return result;
24
+ }
@@ -0,0 +1,9 @@
1
+ export interface AuthState {
2
+ token: string | null;
3
+ address: string | null;
4
+ userId: string | null;
5
+ authenticated: boolean;
6
+ }
7
+ export declare const auth: import("svelte/store").Writable<AuthState>;
8
+ export declare function setAuth(token: string, address: string, userId: string): void;
9
+ export declare function clearAuth(): void;
@@ -0,0 +1,14 @@
1
+ import { writable } from 'svelte/store';
2
+ const initial = {
3
+ token: null,
4
+ address: null,
5
+ userId: null,
6
+ authenticated: false
7
+ };
8
+ export const auth = writable(initial);
9
+ export function setAuth(token, address, userId) {
10
+ auth.set({ token, address, userId, authenticated: true });
11
+ }
12
+ export function clearAuth() {
13
+ auth.set(initial);
14
+ }
@@ -0,0 +1,15 @@
1
+ import type { DisplayMessage } from '../types.js';
2
+ export interface ChatState {
3
+ messages: DisplayMessage[];
4
+ connected: boolean;
5
+ reconnecting: boolean;
6
+ sessionId: string | null;
7
+ loading: boolean;
8
+ thinkingStatus: string | null;
9
+ backendVersion: string | null;
10
+ }
11
+ export declare const chat: import("svelte/store").Writable<ChatState>;
12
+ export declare function connect(gatewayUrl: string, token: string, apiKey?: string): void;
13
+ export declare function reconnect(): void;
14
+ export declare function sendMessage(content: string): void;
15
+ export declare function disconnect(): void;