agent-hustle-demo 1.0.1
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 +429 -0
- package/dist/HustleChat-BC9wvWVA.d.ts +90 -0
- package/dist/HustleChat-BcrKkkyn.d.cts +90 -0
- package/dist/browser/hustle-react.js +14854 -0
- package/dist/browser/hustle-react.js.map +1 -0
- package/dist/components/index.cjs +3141 -0
- package/dist/components/index.cjs.map +1 -0
- package/dist/components/index.d.cts +20 -0
- package/dist/components/index.d.ts +20 -0
- package/dist/components/index.js +3112 -0
- package/dist/components/index.js.map +1 -0
- package/dist/hooks/index.cjs +845 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +6 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.js +838 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hustle-Kj0X8qXC.d.cts +193 -0
- package/dist/hustle-Kj0X8qXC.d.ts +193 -0
- package/dist/index-ChUsRBwL.d.ts +152 -0
- package/dist/index-DE1N7C3W.d.cts +152 -0
- package/dist/index-DuPFrMZy.d.cts +214 -0
- package/dist/index-kFIdHjNw.d.ts +214 -0
- package/dist/index.cjs +3746 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +271 -0
- package/dist/index.d.ts +271 -0
- package/dist/index.js +3697 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/index.cjs +844 -0
- package/dist/providers/index.cjs.map +1 -0
- package/dist/providers/index.d.cts +5 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.js +838 -0
- package/dist/providers/index.js.map +1 -0
- package/package.json +80 -0
- package/src/components/AuthStatus.tsx +352 -0
- package/src/components/ConnectButton.tsx +421 -0
- package/src/components/HustleChat.tsx +1273 -0
- package/src/components/MarkdownContent.tsx +431 -0
- package/src/components/index.ts +15 -0
- package/src/hooks/index.ts +40 -0
- package/src/hooks/useEmblemAuth.ts +27 -0
- package/src/hooks/useHustle.ts +36 -0
- package/src/hooks/usePlugins.ts +135 -0
- package/src/index.ts +142 -0
- package/src/plugins/index.ts +48 -0
- package/src/plugins/migrateFun.ts +211 -0
- package/src/plugins/predictionMarket.ts +411 -0
- package/src/providers/EmblemAuthProvider.tsx +319 -0
- package/src/providers/HustleProvider.tsx +540 -0
- package/src/providers/index.ts +6 -0
- package/src/styles/index.ts +2 -0
- package/src/styles/tokens.ts +447 -0
- package/src/types/auth.ts +85 -0
- package/src/types/hustle.ts +217 -0
- package/src/types/index.ts +49 -0
- package/src/types/plugin.ts +180 -0
- package/src/utils/index.ts +122 -0
- package/src/utils/pluginRegistry.ts +375 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3697 @@
|
|
|
1
|
+
import { createContext, useState, useRef, useCallback, useEffect, useContext, useMemo } from 'react';
|
|
2
|
+
import { EmblemAuthSDK } from 'emblem-auth-sdk';
|
|
3
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import { HustleIncognitoClient } from 'hustle-incognito';
|
|
5
|
+
import { marked } from 'marked';
|
|
6
|
+
import hljs from 'highlight.js/lib/core';
|
|
7
|
+
import javascript from 'highlight.js/lib/languages/javascript';
|
|
8
|
+
import typescript from 'highlight.js/lib/languages/typescript';
|
|
9
|
+
import python from 'highlight.js/lib/languages/python';
|
|
10
|
+
import json from 'highlight.js/lib/languages/json';
|
|
11
|
+
import bash from 'highlight.js/lib/languages/bash';
|
|
12
|
+
import shell from 'highlight.js/lib/languages/shell';
|
|
13
|
+
import css from 'highlight.js/lib/languages/css';
|
|
14
|
+
import xml from 'highlight.js/lib/languages/xml';
|
|
15
|
+
import markdown from 'highlight.js/lib/languages/markdown';
|
|
16
|
+
import sql from 'highlight.js/lib/languages/sql';
|
|
17
|
+
import yaml from 'highlight.js/lib/languages/yaml';
|
|
18
|
+
import rust from 'highlight.js/lib/languages/rust';
|
|
19
|
+
import go from 'highlight.js/lib/languages/go';
|
|
20
|
+
import java from 'highlight.js/lib/languages/java';
|
|
21
|
+
import cpp from 'highlight.js/lib/languages/cpp';
|
|
22
|
+
import csharp from 'highlight.js/lib/languages/csharp';
|
|
23
|
+
import php from 'highlight.js/lib/languages/php';
|
|
24
|
+
import ruby from 'highlight.js/lib/languages/ruby';
|
|
25
|
+
import swift from 'highlight.js/lib/languages/swift';
|
|
26
|
+
import kotlin from 'highlight.js/lib/languages/kotlin';
|
|
27
|
+
|
|
28
|
+
var globalSDKInstance = null;
|
|
29
|
+
var isSDKInitializing = false;
|
|
30
|
+
var EmblemAuthContext = createContext(void 0);
|
|
31
|
+
function EmblemAuthProvider({
|
|
32
|
+
children,
|
|
33
|
+
appId,
|
|
34
|
+
apiUrl,
|
|
35
|
+
modalUrl,
|
|
36
|
+
debug = false
|
|
37
|
+
}) {
|
|
38
|
+
const [session, setSession] = useState(null);
|
|
39
|
+
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
40
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
41
|
+
const [error, setError] = useState(null);
|
|
42
|
+
const [vaultInfo, setVaultInfo] = useState(null);
|
|
43
|
+
const [authSDK, setAuthSDK] = useState(globalSDKInstance);
|
|
44
|
+
const initialized = useRef(false);
|
|
45
|
+
const log = useCallback(
|
|
46
|
+
(message, ...args) => {
|
|
47
|
+
if (debug) {
|
|
48
|
+
console.log(`[EmblemAuth] ${message}`, ...args);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
[debug]
|
|
52
|
+
);
|
|
53
|
+
const fetchVaultInfo = useCallback(
|
|
54
|
+
async (sdk) => {
|
|
55
|
+
try {
|
|
56
|
+
const info = await sdk.getVaultInfo();
|
|
57
|
+
if (info) {
|
|
58
|
+
setVaultInfo(info);
|
|
59
|
+
log("Vault info loaded:", info);
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
log("Failed to fetch vault info:", err);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
[log]
|
|
66
|
+
);
|
|
67
|
+
const handleAuthSuccess = useCallback(
|
|
68
|
+
(newSession, sdk) => {
|
|
69
|
+
log("Auth success - session:", newSession);
|
|
70
|
+
setSession(newSession);
|
|
71
|
+
setIsAuthenticated(true);
|
|
72
|
+
setIsLoading(false);
|
|
73
|
+
setError(null);
|
|
74
|
+
fetchVaultInfo(sdk);
|
|
75
|
+
},
|
|
76
|
+
[log, fetchVaultInfo]
|
|
77
|
+
);
|
|
78
|
+
const handleAuthError = useCallback(
|
|
79
|
+
(err) => {
|
|
80
|
+
log("Auth error:", err);
|
|
81
|
+
setError(err);
|
|
82
|
+
setIsLoading(false);
|
|
83
|
+
setIsAuthenticated(false);
|
|
84
|
+
setSession(null);
|
|
85
|
+
},
|
|
86
|
+
[log]
|
|
87
|
+
);
|
|
88
|
+
const handleSessionExpired = useCallback(() => {
|
|
89
|
+
log("Session expired");
|
|
90
|
+
setSession(null);
|
|
91
|
+
setIsAuthenticated(false);
|
|
92
|
+
setVaultInfo(null);
|
|
93
|
+
}, [log]);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (initialized.current || globalSDKInstance || isSDKInitializing) {
|
|
96
|
+
if (globalSDKInstance && !authSDK) {
|
|
97
|
+
setAuthSDK(globalSDKInstance);
|
|
98
|
+
const existingSession2 = globalSDKInstance.getSession();
|
|
99
|
+
if (existingSession2) {
|
|
100
|
+
handleAuthSuccess(existingSession2, globalSDKInstance);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
initialized.current = true;
|
|
106
|
+
isSDKInitializing = true;
|
|
107
|
+
log("Initializing SDK with appId:", appId);
|
|
108
|
+
const sdk = new EmblemAuthSDK({
|
|
109
|
+
appId,
|
|
110
|
+
apiUrl,
|
|
111
|
+
modalUrl,
|
|
112
|
+
onSuccess: (newSession) => {
|
|
113
|
+
handleAuthSuccess(newSession, sdk);
|
|
114
|
+
},
|
|
115
|
+
onError: (err) => {
|
|
116
|
+
handleAuthError(err);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
globalSDKInstance = sdk;
|
|
120
|
+
isSDKInitializing = false;
|
|
121
|
+
setAuthSDK(sdk);
|
|
122
|
+
const existingSession = sdk.getSession();
|
|
123
|
+
if (existingSession) {
|
|
124
|
+
log("Found existing session");
|
|
125
|
+
handleAuthSuccess(existingSession, sdk);
|
|
126
|
+
}
|
|
127
|
+
const handleSessionUpdate = (updatedSession) => {
|
|
128
|
+
if (updatedSession) {
|
|
129
|
+
setSession(updatedSession);
|
|
130
|
+
setIsAuthenticated(true);
|
|
131
|
+
} else {
|
|
132
|
+
handleSessionExpired();
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
sdk.on("session", handleSessionUpdate);
|
|
136
|
+
sdk.on("sessionExpired", handleSessionExpired);
|
|
137
|
+
return () => {
|
|
138
|
+
sdk.off("session", handleSessionUpdate);
|
|
139
|
+
sdk.off("sessionExpired", handleSessionExpired);
|
|
140
|
+
};
|
|
141
|
+
}, [appId, apiUrl, modalUrl, log, handleAuthSuccess, handleAuthError, handleSessionExpired, authSDK]);
|
|
142
|
+
const openAuthModal = useCallback(async () => {
|
|
143
|
+
if (!authSDK) {
|
|
144
|
+
setError(new Error("Auth SDK not initialized"));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
log("Opening auth modal");
|
|
148
|
+
setIsLoading(true);
|
|
149
|
+
setError(null);
|
|
150
|
+
try {
|
|
151
|
+
await authSDK.openAuthModal();
|
|
152
|
+
} catch (err) {
|
|
153
|
+
setIsLoading(false);
|
|
154
|
+
setError(err instanceof Error ? err : new Error("Failed to open auth modal"));
|
|
155
|
+
}
|
|
156
|
+
}, [authSDK, log]);
|
|
157
|
+
const logout = useCallback(() => {
|
|
158
|
+
if (!authSDK) return;
|
|
159
|
+
log("Logging out");
|
|
160
|
+
authSDK.logout();
|
|
161
|
+
setSession(null);
|
|
162
|
+
setIsAuthenticated(false);
|
|
163
|
+
setVaultInfo(null);
|
|
164
|
+
setError(null);
|
|
165
|
+
}, [authSDK, log]);
|
|
166
|
+
const refreshSession = useCallback(async () => {
|
|
167
|
+
if (!authSDK) return null;
|
|
168
|
+
log("Refreshing session");
|
|
169
|
+
try {
|
|
170
|
+
const refreshedSession = await authSDK.refreshSession();
|
|
171
|
+
if (refreshedSession) {
|
|
172
|
+
setSession(refreshedSession);
|
|
173
|
+
setIsAuthenticated(true);
|
|
174
|
+
return refreshedSession;
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
log("Failed to refresh session:", err);
|
|
179
|
+
setError(err instanceof Error ? err : new Error("Failed to refresh session"));
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}, [authSDK, log]);
|
|
183
|
+
const vaultId = session?.user?.vaultId ?? null;
|
|
184
|
+
const walletAddress = session?.user?.evmAddress ?? null;
|
|
185
|
+
const value = {
|
|
186
|
+
// State
|
|
187
|
+
session,
|
|
188
|
+
isAuthenticated,
|
|
189
|
+
isLoading,
|
|
190
|
+
error,
|
|
191
|
+
vaultInfo,
|
|
192
|
+
// Derived
|
|
193
|
+
vaultId,
|
|
194
|
+
walletAddress,
|
|
195
|
+
// Actions
|
|
196
|
+
openAuthModal,
|
|
197
|
+
logout,
|
|
198
|
+
refreshSession,
|
|
199
|
+
// For Hustle integration
|
|
200
|
+
authSDK
|
|
201
|
+
};
|
|
202
|
+
return /* @__PURE__ */ jsx(EmblemAuthContext.Provider, { value, children });
|
|
203
|
+
}
|
|
204
|
+
function useEmblemAuth() {
|
|
205
|
+
const context = useContext(EmblemAuthContext);
|
|
206
|
+
if (context === void 0) {
|
|
207
|
+
throw new Error("useEmblemAuth must be used within an EmblemAuthProvider");
|
|
208
|
+
}
|
|
209
|
+
return context;
|
|
210
|
+
}
|
|
211
|
+
function resetAuthSDK() {
|
|
212
|
+
globalSDKInstance = null;
|
|
213
|
+
isSDKInitializing = false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/utils/pluginRegistry.ts
|
|
217
|
+
var PLUGINS_KEY = "hustle-plugins";
|
|
218
|
+
function getEnabledStateKey(instanceId) {
|
|
219
|
+
return `hustle-plugin-state-${instanceId}`;
|
|
220
|
+
}
|
|
221
|
+
function serializeFunction(fn) {
|
|
222
|
+
return fn.toString();
|
|
223
|
+
}
|
|
224
|
+
function deserializeExecutor(code) {
|
|
225
|
+
try {
|
|
226
|
+
return eval(`(${code})`);
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.error("[Hustle] Failed to deserialize executor:", err);
|
|
229
|
+
return async () => ({ error: "Failed to deserialize executor", code });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function deserializeHook(code) {
|
|
233
|
+
try {
|
|
234
|
+
return eval(`(${code})`);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
console.error("[Hustle] Failed to deserialize hook:", err);
|
|
237
|
+
return (() => {
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function serializePluginTools(tools, executors) {
|
|
242
|
+
if (!tools) return [];
|
|
243
|
+
return tools.map((tool) => ({
|
|
244
|
+
...tool,
|
|
245
|
+
executorCode: executors?.[tool.name] ? serializeFunction(executors[tool.name]) : void 0
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
function serializeHooks(hooks) {
|
|
249
|
+
if (!hooks) return void 0;
|
|
250
|
+
const serialized = {};
|
|
251
|
+
if (hooks.onRegister) {
|
|
252
|
+
serialized.onRegisterCode = serializeFunction(hooks.onRegister);
|
|
253
|
+
}
|
|
254
|
+
if (hooks.beforeRequest) {
|
|
255
|
+
serialized.beforeRequestCode = serializeFunction(hooks.beforeRequest);
|
|
256
|
+
}
|
|
257
|
+
if (hooks.afterResponse) {
|
|
258
|
+
serialized.afterResponseCode = serializeFunction(hooks.afterResponse);
|
|
259
|
+
}
|
|
260
|
+
if (hooks.onError) {
|
|
261
|
+
serialized.onErrorCode = serializeFunction(hooks.onError);
|
|
262
|
+
}
|
|
263
|
+
return Object.keys(serialized).length > 0 ? serialized : void 0;
|
|
264
|
+
}
|
|
265
|
+
function hydratePlugin(stored) {
|
|
266
|
+
const executors = {};
|
|
267
|
+
if (stored.tools) {
|
|
268
|
+
for (const tool of stored.tools) {
|
|
269
|
+
if (tool.executorCode) {
|
|
270
|
+
executors[tool.name] = deserializeExecutor(tool.executorCode);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
let hooks;
|
|
275
|
+
if (stored.hooksCode) {
|
|
276
|
+
hooks = {};
|
|
277
|
+
if (stored.hooksCode.onRegisterCode) {
|
|
278
|
+
hooks.onRegister = deserializeHook(stored.hooksCode.onRegisterCode);
|
|
279
|
+
}
|
|
280
|
+
if (stored.hooksCode.beforeRequestCode) {
|
|
281
|
+
hooks.beforeRequest = deserializeHook(stored.hooksCode.beforeRequestCode);
|
|
282
|
+
}
|
|
283
|
+
if (stored.hooksCode.afterResponseCode) {
|
|
284
|
+
hooks.afterResponse = deserializeHook(stored.hooksCode.afterResponseCode);
|
|
285
|
+
}
|
|
286
|
+
if (stored.hooksCode.onErrorCode) {
|
|
287
|
+
hooks.onError = deserializeHook(stored.hooksCode.onErrorCode);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
...stored,
|
|
292
|
+
executors: Object.keys(executors).length > 0 ? executors : void 0,
|
|
293
|
+
hooks
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
var PluginRegistry = class {
|
|
297
|
+
constructor() {
|
|
298
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Get listeners for a specific instance
|
|
302
|
+
*/
|
|
303
|
+
getListeners(instanceId) {
|
|
304
|
+
if (!this.listeners.has(instanceId)) {
|
|
305
|
+
this.listeners.set(instanceId, /* @__PURE__ */ new Set());
|
|
306
|
+
}
|
|
307
|
+
return this.listeners.get(instanceId);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Load installed plugins (global)
|
|
311
|
+
*/
|
|
312
|
+
loadInstalledPlugins() {
|
|
313
|
+
if (typeof window === "undefined") return [];
|
|
314
|
+
try {
|
|
315
|
+
const stored = localStorage.getItem(PLUGINS_KEY);
|
|
316
|
+
return stored ? JSON.parse(stored) : [];
|
|
317
|
+
} catch {
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Save installed plugins (global)
|
|
323
|
+
* Serializes executors as executorCode strings
|
|
324
|
+
*/
|
|
325
|
+
saveInstalledPlugins(plugins) {
|
|
326
|
+
if (typeof window === "undefined") return;
|
|
327
|
+
localStorage.setItem(PLUGINS_KEY, JSON.stringify(plugins));
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Load enabled state for an instance
|
|
331
|
+
*/
|
|
332
|
+
loadEnabledState(instanceId) {
|
|
333
|
+
if (typeof window === "undefined") return {};
|
|
334
|
+
try {
|
|
335
|
+
const stored = localStorage.getItem(getEnabledStateKey(instanceId));
|
|
336
|
+
return stored ? JSON.parse(stored) : {};
|
|
337
|
+
} catch {
|
|
338
|
+
return {};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Save enabled state for an instance
|
|
343
|
+
*/
|
|
344
|
+
saveEnabledState(state, instanceId) {
|
|
345
|
+
if (typeof window === "undefined") return;
|
|
346
|
+
localStorage.setItem(getEnabledStateKey(instanceId), JSON.stringify(state));
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Load plugins with instance-specific enabled state
|
|
350
|
+
* Combines global plugin list with per-instance enabled state
|
|
351
|
+
*/
|
|
352
|
+
loadFromStorage(instanceId = "default") {
|
|
353
|
+
const installed = this.loadInstalledPlugins();
|
|
354
|
+
const enabledState = this.loadEnabledState(instanceId);
|
|
355
|
+
return installed.map((plugin) => ({
|
|
356
|
+
...plugin,
|
|
357
|
+
// Default to enabled if no state exists for this instance
|
|
358
|
+
enabled: enabledState[plugin.name] ?? true
|
|
359
|
+
}));
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Register a new plugin (global - available to all instances)
|
|
363
|
+
* Serializes executors as executorCode for persistence
|
|
364
|
+
*
|
|
365
|
+
* @param plugin The plugin to install
|
|
366
|
+
* @param enabled Initial enabled state for this instance (default: true)
|
|
367
|
+
* @param instanceId Instance to set initial enabled state for
|
|
368
|
+
*/
|
|
369
|
+
register(plugin, enabled = true, instanceId = "default") {
|
|
370
|
+
const installed = this.loadInstalledPlugins();
|
|
371
|
+
const existing = installed.findIndex((p) => p.name === plugin.name);
|
|
372
|
+
const storedPlugin = {
|
|
373
|
+
name: plugin.name,
|
|
374
|
+
version: plugin.version,
|
|
375
|
+
description: plugin.description,
|
|
376
|
+
tools: serializePluginTools(plugin.tools, plugin.executors),
|
|
377
|
+
hooksCode: serializeHooks(plugin.hooks),
|
|
378
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
379
|
+
};
|
|
380
|
+
if (existing >= 0) {
|
|
381
|
+
installed[existing] = storedPlugin;
|
|
382
|
+
} else {
|
|
383
|
+
installed.push(storedPlugin);
|
|
384
|
+
}
|
|
385
|
+
this.saveInstalledPlugins(installed);
|
|
386
|
+
const enabledState = this.loadEnabledState(instanceId);
|
|
387
|
+
enabledState[plugin.name] = enabled;
|
|
388
|
+
this.saveEnabledState(enabledState, instanceId);
|
|
389
|
+
this.notifyListeners(instanceId);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Unregister a plugin (global - removes from all instances)
|
|
393
|
+
*/
|
|
394
|
+
unregister(pluginName, instanceId = "default") {
|
|
395
|
+
const installed = this.loadInstalledPlugins().filter((p) => p.name !== pluginName);
|
|
396
|
+
this.saveInstalledPlugins(installed);
|
|
397
|
+
const enabledState = this.loadEnabledState(instanceId);
|
|
398
|
+
delete enabledState[pluginName];
|
|
399
|
+
this.saveEnabledState(enabledState, instanceId);
|
|
400
|
+
this.notifyListeners(instanceId);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Enable or disable a plugin (instance-scoped)
|
|
404
|
+
*/
|
|
405
|
+
setEnabled(pluginName, enabled, instanceId = "default") {
|
|
406
|
+
const enabledState = this.loadEnabledState(instanceId);
|
|
407
|
+
enabledState[pluginName] = enabled;
|
|
408
|
+
this.saveEnabledState(enabledState, instanceId);
|
|
409
|
+
this.notifyListeners(instanceId);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Check if a plugin is installed (global)
|
|
413
|
+
*/
|
|
414
|
+
isRegistered(pluginName) {
|
|
415
|
+
return this.loadInstalledPlugins().some((p) => p.name === pluginName);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Get a specific plugin with instance-specific enabled state
|
|
419
|
+
*/
|
|
420
|
+
getPlugin(pluginName, instanceId = "default") {
|
|
421
|
+
return this.loadFromStorage(instanceId).find((p) => p.name === pluginName);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Get all enabled plugins for an instance (hydrated with executors)
|
|
425
|
+
*/
|
|
426
|
+
getEnabledPlugins(instanceId = "default") {
|
|
427
|
+
return this.loadFromStorage(instanceId).filter((p) => p.enabled).map(hydratePlugin);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Subscribe to plugin changes for a specific instance
|
|
431
|
+
*/
|
|
432
|
+
onChange(callback, instanceId = "default") {
|
|
433
|
+
const listeners = this.getListeners(instanceId);
|
|
434
|
+
listeners.add(callback);
|
|
435
|
+
return () => listeners.delete(callback);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Notify all listeners for a specific instance
|
|
439
|
+
*/
|
|
440
|
+
notifyListeners(instanceId = "default") {
|
|
441
|
+
const plugins = this.loadFromStorage(instanceId);
|
|
442
|
+
const listeners = this.getListeners(instanceId);
|
|
443
|
+
listeners.forEach((cb) => cb(plugins));
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Clear enabled state for an instance (plugins remain installed globally)
|
|
447
|
+
*/
|
|
448
|
+
clear(instanceId = "default") {
|
|
449
|
+
if (typeof window === "undefined") return;
|
|
450
|
+
localStorage.removeItem(getEnabledStateKey(instanceId));
|
|
451
|
+
this.notifyListeners(instanceId);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Clear all installed plugins globally
|
|
455
|
+
*/
|
|
456
|
+
clearAll() {
|
|
457
|
+
if (typeof window === "undefined") return;
|
|
458
|
+
localStorage.removeItem(PLUGINS_KEY);
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
var pluginRegistry = new PluginRegistry();
|
|
462
|
+
|
|
463
|
+
// src/hooks/usePlugins.ts
|
|
464
|
+
function getStorageKey(instanceId) {
|
|
465
|
+
return `hustle-plugins-${instanceId}`;
|
|
466
|
+
}
|
|
467
|
+
function usePlugins(instanceId = "default") {
|
|
468
|
+
const [plugins, setPlugins] = useState([]);
|
|
469
|
+
useEffect(() => {
|
|
470
|
+
setPlugins(pluginRegistry.loadFromStorage(instanceId));
|
|
471
|
+
const unsubscribe = pluginRegistry.onChange(setPlugins, instanceId);
|
|
472
|
+
const storageKey = getStorageKey(instanceId);
|
|
473
|
+
const handleStorage = (e) => {
|
|
474
|
+
if (e.key === storageKey) {
|
|
475
|
+
setPlugins(pluginRegistry.loadFromStorage(instanceId));
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
window.addEventListener("storage", handleStorage);
|
|
479
|
+
return () => {
|
|
480
|
+
unsubscribe();
|
|
481
|
+
window.removeEventListener("storage", handleStorage);
|
|
482
|
+
};
|
|
483
|
+
}, [instanceId]);
|
|
484
|
+
const registerPlugin = useCallback((plugin) => {
|
|
485
|
+
pluginRegistry.register(plugin, true, instanceId);
|
|
486
|
+
}, [instanceId]);
|
|
487
|
+
const unregisterPlugin = useCallback((name) => {
|
|
488
|
+
pluginRegistry.unregister(name, instanceId);
|
|
489
|
+
}, [instanceId]);
|
|
490
|
+
const enablePlugin = useCallback((name) => {
|
|
491
|
+
pluginRegistry.setEnabled(name, true, instanceId);
|
|
492
|
+
}, [instanceId]);
|
|
493
|
+
const disablePlugin = useCallback((name) => {
|
|
494
|
+
pluginRegistry.setEnabled(name, false, instanceId);
|
|
495
|
+
}, [instanceId]);
|
|
496
|
+
const isRegistered = useCallback(
|
|
497
|
+
(name) => plugins.some((p) => p.name === name),
|
|
498
|
+
[plugins]
|
|
499
|
+
);
|
|
500
|
+
const isEnabled = useCallback(
|
|
501
|
+
(name) => plugins.some((p) => p.name === name && p.enabled),
|
|
502
|
+
[plugins]
|
|
503
|
+
);
|
|
504
|
+
const enabledPlugins = plugins.filter((p) => p.enabled).map(hydratePlugin);
|
|
505
|
+
return {
|
|
506
|
+
plugins,
|
|
507
|
+
enabledPlugins,
|
|
508
|
+
registerPlugin,
|
|
509
|
+
unregisterPlugin,
|
|
510
|
+
enablePlugin,
|
|
511
|
+
disablePlugin,
|
|
512
|
+
isRegistered,
|
|
513
|
+
isEnabled
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
var HustleContext = createContext(void 0);
|
|
517
|
+
var DEFAULT_HUSTLE_API_URL = "https://agenthustle.ai";
|
|
518
|
+
var instanceCounter = 0;
|
|
519
|
+
var mountedAutoInstances = /* @__PURE__ */ new Set();
|
|
520
|
+
function HustleProvider({
|
|
521
|
+
children,
|
|
522
|
+
hustleApiUrl = DEFAULT_HUSTLE_API_URL,
|
|
523
|
+
debug = false,
|
|
524
|
+
instanceId: explicitInstanceId
|
|
525
|
+
}) {
|
|
526
|
+
const [resolvedInstanceId] = useState(() => {
|
|
527
|
+
if (explicitInstanceId) {
|
|
528
|
+
return explicitInstanceId;
|
|
529
|
+
}
|
|
530
|
+
const autoId = `instance-${++instanceCounter}`;
|
|
531
|
+
mountedAutoInstances.add(autoId);
|
|
532
|
+
return autoId;
|
|
533
|
+
});
|
|
534
|
+
const isAutoInstance = !explicitInstanceId;
|
|
535
|
+
useEffect(() => {
|
|
536
|
+
if (isAutoInstance && mountedAutoInstances.size > 1 && process.env.NODE_ENV !== "production") {
|
|
537
|
+
console.warn(
|
|
538
|
+
`[Hustle] Multiple HustleProviders detected without explicit instanceId. For stable settings persistence, consider adding instanceId prop:
|
|
539
|
+
<HustleProvider instanceId="my-chat-name">`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
return () => {
|
|
543
|
+
if (isAutoInstance) {
|
|
544
|
+
mountedAutoInstances.delete(resolvedInstanceId);
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
}, [isAutoInstance, resolvedInstanceId]);
|
|
548
|
+
const { authSDK, isAuthenticated } = useEmblemAuth();
|
|
549
|
+
const { enabledPlugins } = usePlugins(resolvedInstanceId);
|
|
550
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
551
|
+
const [error, setError] = useState(null);
|
|
552
|
+
const [models, setModels] = useState([]);
|
|
553
|
+
const registeredPluginsRef = useRef(/* @__PURE__ */ new Set());
|
|
554
|
+
const SETTINGS_KEY = `hustle-settings-${resolvedInstanceId}`;
|
|
555
|
+
const loadSettings = () => {
|
|
556
|
+
if (typeof window === "undefined") return { selectedModel: "", systemPrompt: "", skipServerPrompt: false };
|
|
557
|
+
try {
|
|
558
|
+
const stored = localStorage.getItem(SETTINGS_KEY);
|
|
559
|
+
if (stored) {
|
|
560
|
+
return JSON.parse(stored);
|
|
561
|
+
}
|
|
562
|
+
} catch {
|
|
563
|
+
}
|
|
564
|
+
return { selectedModel: "", systemPrompt: "", skipServerPrompt: false };
|
|
565
|
+
};
|
|
566
|
+
const initialSettings = loadSettings();
|
|
567
|
+
const [selectedModel, setSelectedModelState] = useState(initialSettings.selectedModel);
|
|
568
|
+
const [systemPrompt, setSystemPromptState] = useState(initialSettings.systemPrompt);
|
|
569
|
+
const [skipServerPrompt, setSkipServerPromptState] = useState(initialSettings.skipServerPrompt);
|
|
570
|
+
const saveSettings = useCallback((settings) => {
|
|
571
|
+
if (typeof window === "undefined") return;
|
|
572
|
+
try {
|
|
573
|
+
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
|
|
574
|
+
} catch {
|
|
575
|
+
}
|
|
576
|
+
}, []);
|
|
577
|
+
const setSelectedModel = useCallback((value2) => {
|
|
578
|
+
setSelectedModelState(value2);
|
|
579
|
+
saveSettings({ selectedModel: value2, systemPrompt, skipServerPrompt });
|
|
580
|
+
}, [systemPrompt, skipServerPrompt, saveSettings]);
|
|
581
|
+
const setSystemPrompt = useCallback((value2) => {
|
|
582
|
+
setSystemPromptState(value2);
|
|
583
|
+
saveSettings({ selectedModel, systemPrompt: value2, skipServerPrompt });
|
|
584
|
+
}, [selectedModel, skipServerPrompt, saveSettings]);
|
|
585
|
+
const setSkipServerPrompt = useCallback((value2) => {
|
|
586
|
+
setSkipServerPromptState(value2);
|
|
587
|
+
saveSettings({ selectedModel, systemPrompt, skipServerPrompt: value2 });
|
|
588
|
+
}, [selectedModel, systemPrompt, saveSettings]);
|
|
589
|
+
const log = useCallback(
|
|
590
|
+
(message, ...args) => {
|
|
591
|
+
if (debug) {
|
|
592
|
+
console.log(`[Hustle] ${message}`, ...args);
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
[debug]
|
|
596
|
+
);
|
|
597
|
+
const client = useMemo(() => {
|
|
598
|
+
if (!authSDK || !isAuthenticated) {
|
|
599
|
+
log("Client not created - auth not ready");
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
log("Creating HustleIncognitoClient with auth SDK");
|
|
603
|
+
try {
|
|
604
|
+
const hustleClient = new HustleIncognitoClient({
|
|
605
|
+
sdk: authSDK,
|
|
606
|
+
// CORRECT: Pass auth SDK instance, NOT apiKey
|
|
607
|
+
hustleApiUrl,
|
|
608
|
+
debug
|
|
609
|
+
});
|
|
610
|
+
hustleClient.on("tool_start", (event) => {
|
|
611
|
+
log("Tool start:", event);
|
|
612
|
+
});
|
|
613
|
+
hustleClient.on("tool_end", (event) => {
|
|
614
|
+
log("Tool end:", event);
|
|
615
|
+
});
|
|
616
|
+
hustleClient.on("stream_end", (event) => {
|
|
617
|
+
log("Stream end:", event);
|
|
618
|
+
});
|
|
619
|
+
return hustleClient;
|
|
620
|
+
} catch (err) {
|
|
621
|
+
log("Failed to create client:", err);
|
|
622
|
+
setError(err instanceof Error ? err : new Error("Failed to create Hustle client"));
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
}, [authSDK, isAuthenticated, hustleApiUrl, debug, log]);
|
|
626
|
+
const isReady = client !== null;
|
|
627
|
+
useEffect(() => {
|
|
628
|
+
if (!client) return;
|
|
629
|
+
const registerPlugins = async () => {
|
|
630
|
+
const enabledNames = new Set(enabledPlugins.map((p) => p.name));
|
|
631
|
+
for (const name of registeredPluginsRef.current) {
|
|
632
|
+
if (!enabledNames.has(name)) {
|
|
633
|
+
log("Unregistering plugin:", name);
|
|
634
|
+
try {
|
|
635
|
+
await client.unuse(name);
|
|
636
|
+
registeredPluginsRef.current.delete(name);
|
|
637
|
+
log("Plugin unregistered:", name);
|
|
638
|
+
} catch (err) {
|
|
639
|
+
log("Failed to unregister plugin:", name, err);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
for (const plugin of enabledPlugins) {
|
|
644
|
+
if (!registeredPluginsRef.current.has(plugin.name)) {
|
|
645
|
+
log("Registering plugin:", plugin.name);
|
|
646
|
+
try {
|
|
647
|
+
if (plugin.executors || plugin.hooks) {
|
|
648
|
+
await client.use({
|
|
649
|
+
name: plugin.name,
|
|
650
|
+
version: plugin.version,
|
|
651
|
+
tools: plugin.tools,
|
|
652
|
+
executors: plugin.executors,
|
|
653
|
+
hooks: plugin.hooks
|
|
654
|
+
});
|
|
655
|
+
registeredPluginsRef.current.add(plugin.name);
|
|
656
|
+
log("Plugin registered:", plugin.name);
|
|
657
|
+
} else {
|
|
658
|
+
log("Plugin has no executors/hooks, skipping registration:", plugin.name);
|
|
659
|
+
}
|
|
660
|
+
} catch (err) {
|
|
661
|
+
log("Failed to register plugin:", plugin.name, err);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
registerPlugins();
|
|
667
|
+
}, [client, enabledPlugins, log]);
|
|
668
|
+
const loadModels = useCallback(async () => {
|
|
669
|
+
if (!client) {
|
|
670
|
+
log("Cannot load models - client not ready");
|
|
671
|
+
return [];
|
|
672
|
+
}
|
|
673
|
+
log("Loading models");
|
|
674
|
+
setIsLoading(true);
|
|
675
|
+
try {
|
|
676
|
+
const modelList = await client.getModels();
|
|
677
|
+
setModels(modelList);
|
|
678
|
+
log("Loaded models:", modelList.length);
|
|
679
|
+
return modelList;
|
|
680
|
+
} catch (err) {
|
|
681
|
+
log("Failed to load models:", err);
|
|
682
|
+
setError(err instanceof Error ? err : new Error("Failed to load models"));
|
|
683
|
+
return [];
|
|
684
|
+
} finally {
|
|
685
|
+
setIsLoading(false);
|
|
686
|
+
}
|
|
687
|
+
}, [client, log]);
|
|
688
|
+
useEffect(() => {
|
|
689
|
+
if (client) {
|
|
690
|
+
loadModels();
|
|
691
|
+
}
|
|
692
|
+
}, [client, loadModels]);
|
|
693
|
+
const chat = useCallback(
|
|
694
|
+
async (options) => {
|
|
695
|
+
if (!client) {
|
|
696
|
+
throw new Error("Hustle client not ready. Please authenticate first.");
|
|
697
|
+
}
|
|
698
|
+
log("Chat request:", options.messages.length, "messages");
|
|
699
|
+
setIsLoading(true);
|
|
700
|
+
setError(null);
|
|
701
|
+
try {
|
|
702
|
+
const effectiveSystemPrompt = options.systemPrompt || systemPrompt;
|
|
703
|
+
const messagesWithSystem = [];
|
|
704
|
+
if (effectiveSystemPrompt) {
|
|
705
|
+
messagesWithSystem.push({ role: "system", content: effectiveSystemPrompt });
|
|
706
|
+
}
|
|
707
|
+
messagesWithSystem.push(...options.messages);
|
|
708
|
+
const sdkOptions = {
|
|
709
|
+
messages: messagesWithSystem,
|
|
710
|
+
processChunks: true
|
|
711
|
+
};
|
|
712
|
+
if (options.model || selectedModel) {
|
|
713
|
+
sdkOptions.model = options.model || selectedModel;
|
|
714
|
+
}
|
|
715
|
+
if (options.overrideSystemPrompt ?? skipServerPrompt) {
|
|
716
|
+
sdkOptions.overrideSystemPrompt = true;
|
|
717
|
+
}
|
|
718
|
+
if (options.attachments) {
|
|
719
|
+
sdkOptions.attachments = options.attachments;
|
|
720
|
+
}
|
|
721
|
+
const response = await client.chat(sdkOptions);
|
|
722
|
+
log("Chat response received");
|
|
723
|
+
return response;
|
|
724
|
+
} catch (err) {
|
|
725
|
+
log("Chat error:", err);
|
|
726
|
+
const error2 = err instanceof Error ? err : new Error("Chat request failed");
|
|
727
|
+
setError(error2);
|
|
728
|
+
throw error2;
|
|
729
|
+
} finally {
|
|
730
|
+
setIsLoading(false);
|
|
731
|
+
}
|
|
732
|
+
},
|
|
733
|
+
[client, selectedModel, systemPrompt, skipServerPrompt, log]
|
|
734
|
+
);
|
|
735
|
+
const chatStreamImpl = useCallback(
|
|
736
|
+
(options) => {
|
|
737
|
+
if (!client) {
|
|
738
|
+
return {
|
|
739
|
+
[Symbol.asyncIterator]: async function* () {
|
|
740
|
+
yield { type: "error", value: { message: "Hustle client not ready. Please authenticate first." } };
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
log("Chat stream request:", options.messages.length, "messages");
|
|
745
|
+
setError(null);
|
|
746
|
+
const effectiveSystemPrompt = options.systemPrompt || systemPrompt;
|
|
747
|
+
const messagesWithSystem = [];
|
|
748
|
+
if (effectiveSystemPrompt) {
|
|
749
|
+
messagesWithSystem.push({ role: "system", content: effectiveSystemPrompt });
|
|
750
|
+
}
|
|
751
|
+
messagesWithSystem.push(...options.messages);
|
|
752
|
+
const sdkOptions = {
|
|
753
|
+
messages: messagesWithSystem,
|
|
754
|
+
processChunks: options.processChunks ?? true
|
|
755
|
+
};
|
|
756
|
+
if (options.model || selectedModel) {
|
|
757
|
+
sdkOptions.model = options.model || selectedModel;
|
|
758
|
+
}
|
|
759
|
+
if (options.overrideSystemPrompt ?? skipServerPrompt) {
|
|
760
|
+
sdkOptions.overrideSystemPrompt = true;
|
|
761
|
+
}
|
|
762
|
+
if (options.attachments) {
|
|
763
|
+
sdkOptions.attachments = options.attachments;
|
|
764
|
+
}
|
|
765
|
+
const stream = client.chatStream(sdkOptions);
|
|
766
|
+
return {
|
|
767
|
+
[Symbol.asyncIterator]: async function* () {
|
|
768
|
+
try {
|
|
769
|
+
for await (const chunk of stream) {
|
|
770
|
+
const typedChunk = chunk;
|
|
771
|
+
if (typedChunk.type === "text") {
|
|
772
|
+
const textValue = typedChunk.value;
|
|
773
|
+
log("Stream text chunk:", textValue?.substring(0, 50));
|
|
774
|
+
yield { type: "text", value: textValue };
|
|
775
|
+
} else if (typedChunk.type === "tool_call") {
|
|
776
|
+
log("Stream tool call:", typedChunk.value);
|
|
777
|
+
yield { type: "tool_call", value: typedChunk.value };
|
|
778
|
+
} else if (typedChunk.type === "tool_result") {
|
|
779
|
+
log("Stream tool result");
|
|
780
|
+
yield { type: "tool_result", value: typedChunk.value };
|
|
781
|
+
} else if (typedChunk.type === "error") {
|
|
782
|
+
const errorValue = typedChunk.value;
|
|
783
|
+
log("Stream error:", errorValue);
|
|
784
|
+
setError(new Error(errorValue?.message || "Stream error"));
|
|
785
|
+
yield { type: "error", value: { message: errorValue?.message || "Stream error" } };
|
|
786
|
+
} else {
|
|
787
|
+
yield chunk;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
} catch (err) {
|
|
791
|
+
log("Stream error:", err);
|
|
792
|
+
const error2 = err instanceof Error ? err : new Error("Stream failed");
|
|
793
|
+
setError(error2);
|
|
794
|
+
yield { type: "error", value: { message: error2.message } };
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
},
|
|
799
|
+
[client, selectedModel, systemPrompt, skipServerPrompt, log]
|
|
800
|
+
);
|
|
801
|
+
const uploadFile = useCallback(
|
|
802
|
+
async (file) => {
|
|
803
|
+
if (!client) {
|
|
804
|
+
throw new Error("Hustle client not ready. Please authenticate first.");
|
|
805
|
+
}
|
|
806
|
+
log("Uploading file:", file.name);
|
|
807
|
+
setIsLoading(true);
|
|
808
|
+
try {
|
|
809
|
+
const attachment = await client.uploadFile(file);
|
|
810
|
+
log("File uploaded:", attachment);
|
|
811
|
+
return attachment;
|
|
812
|
+
} catch (err) {
|
|
813
|
+
log("Upload error:", err);
|
|
814
|
+
const error2 = err instanceof Error ? err : new Error("File upload failed");
|
|
815
|
+
setError(error2);
|
|
816
|
+
throw error2;
|
|
817
|
+
} finally {
|
|
818
|
+
setIsLoading(false);
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
[client, log]
|
|
822
|
+
);
|
|
823
|
+
const value = {
|
|
824
|
+
// Instance ID for scoped storage
|
|
825
|
+
instanceId: resolvedInstanceId,
|
|
826
|
+
// State
|
|
827
|
+
isReady,
|
|
828
|
+
isLoading,
|
|
829
|
+
error,
|
|
830
|
+
models,
|
|
831
|
+
// Client (for advanced use)
|
|
832
|
+
client,
|
|
833
|
+
// Chat methods
|
|
834
|
+
chat,
|
|
835
|
+
chatStream: chatStreamImpl,
|
|
836
|
+
// File upload
|
|
837
|
+
uploadFile,
|
|
838
|
+
// Data fetching
|
|
839
|
+
loadModels,
|
|
840
|
+
// Settings
|
|
841
|
+
selectedModel,
|
|
842
|
+
setSelectedModel,
|
|
843
|
+
systemPrompt,
|
|
844
|
+
setSystemPrompt,
|
|
845
|
+
skipServerPrompt,
|
|
846
|
+
setSkipServerPrompt
|
|
847
|
+
};
|
|
848
|
+
return /* @__PURE__ */ jsx(HustleContext.Provider, { value, children });
|
|
849
|
+
}
|
|
850
|
+
function useHustle() {
|
|
851
|
+
const context = useContext(HustleContext);
|
|
852
|
+
if (context === void 0) {
|
|
853
|
+
throw new Error("useHustle must be used within a HustleProvider (which requires EmblemAuthProvider)");
|
|
854
|
+
}
|
|
855
|
+
return context;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/styles/tokens.ts
|
|
859
|
+
var defaults = {
|
|
860
|
+
colors: {
|
|
861
|
+
// Backgrounds
|
|
862
|
+
bgPrimary: "#0b0d10",
|
|
863
|
+
bgSecondary: "#12161b",
|
|
864
|
+
bgTertiary: "#1a1f25",
|
|
865
|
+
bgHover: "#252b33",
|
|
866
|
+
bgOverlay: "rgba(0, 0, 0, 0.7)",
|
|
867
|
+
// Borders
|
|
868
|
+
borderPrimary: "#222b35",
|
|
869
|
+
borderSecondary: "#333",
|
|
870
|
+
borderHover: "#444",
|
|
871
|
+
// Text
|
|
872
|
+
textPrimary: "#e6eef8",
|
|
873
|
+
textSecondary: "#8892a4",
|
|
874
|
+
textTertiary: "#6b7280",
|
|
875
|
+
textInverse: "#fff",
|
|
876
|
+
// Accent - Primary (blue)
|
|
877
|
+
accentPrimary: "#4c9aff",
|
|
878
|
+
accentPrimaryHover: "#7bb6ff",
|
|
879
|
+
accentPrimaryBg: "rgba(76, 154, 255, 0.1)",
|
|
880
|
+
// Accent - Success (green)
|
|
881
|
+
accentSuccess: "#10b981",
|
|
882
|
+
accentSuccessHover: "#34d399",
|
|
883
|
+
accentSuccessBg: "rgba(16, 185, 129, 0.1)",
|
|
884
|
+
// Accent - Warning (yellow/orange)
|
|
885
|
+
accentWarning: "#f59e0b",
|
|
886
|
+
accentWarningBg: "rgba(245, 158, 11, 0.1)",
|
|
887
|
+
// Accent - Error (red)
|
|
888
|
+
accentError: "#dc2626",
|
|
889
|
+
accentErrorHover: "#ef4444",
|
|
890
|
+
accentErrorBg: "rgba(239, 68, 68, 0.1)",
|
|
891
|
+
// Messages
|
|
892
|
+
msgUser: "#1e3a5f",
|
|
893
|
+
msgAssistant: "#1a2633"
|
|
894
|
+
},
|
|
895
|
+
typography: {
|
|
896
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
897
|
+
fontFamilyMono: '"SF Mono", Monaco, monospace',
|
|
898
|
+
fontSizeXs: "11px",
|
|
899
|
+
fontSizeSm: "13px",
|
|
900
|
+
fontSizeMd: "14px",
|
|
901
|
+
fontSizeLg: "16px",
|
|
902
|
+
fontSizeXl: "18px",
|
|
903
|
+
fontWeightNormal: "400",
|
|
904
|
+
fontWeightMedium: "500",
|
|
905
|
+
fontWeightSemibold: "600",
|
|
906
|
+
lineHeightTight: "1.3",
|
|
907
|
+
lineHeightNormal: "1.5",
|
|
908
|
+
lineHeightRelaxed: "1.7"
|
|
909
|
+
},
|
|
910
|
+
spacing: {
|
|
911
|
+
xs: "4px",
|
|
912
|
+
sm: "8px",
|
|
913
|
+
md: "12px",
|
|
914
|
+
lg: "16px",
|
|
915
|
+
xl: "20px",
|
|
916
|
+
xxl: "24px"
|
|
917
|
+
},
|
|
918
|
+
radius: {
|
|
919
|
+
sm: "4px",
|
|
920
|
+
md: "6px",
|
|
921
|
+
lg: "8px",
|
|
922
|
+
xl: "12px",
|
|
923
|
+
pill: "20px",
|
|
924
|
+
full: "50%"
|
|
925
|
+
},
|
|
926
|
+
shadows: {
|
|
927
|
+
sm: "0 2px 8px rgba(0,0,0,0.2)",
|
|
928
|
+
md: "0 4px 16px rgba(0,0,0,0.3)",
|
|
929
|
+
lg: "0 8px 32px rgba(0,0,0,0.4)",
|
|
930
|
+
xl: "0 16px 48px rgba(0,0,0,0.5)"
|
|
931
|
+
},
|
|
932
|
+
// Glow effects (for enhanced themes)
|
|
933
|
+
glows: {
|
|
934
|
+
primary: "rgba(76, 154, 255, 0.4)",
|
|
935
|
+
success: "rgba(16, 185, 129, 0.4)",
|
|
936
|
+
error: "rgba(239, 68, 68, 0.4)",
|
|
937
|
+
ambient: "rgba(76, 154, 255, 0.08)"
|
|
938
|
+
},
|
|
939
|
+
transitions: {
|
|
940
|
+
fast: "0.15s ease",
|
|
941
|
+
normal: "0.2s ease",
|
|
942
|
+
slow: "0.3s ease"
|
|
943
|
+
},
|
|
944
|
+
zIndex: {
|
|
945
|
+
dropdown: "100",
|
|
946
|
+
modal: "1000",
|
|
947
|
+
fullscreen: "1000",
|
|
948
|
+
modalOverFullscreen: "10000"
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
function toVarName(category, key) {
|
|
952
|
+
const kebab = key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
953
|
+
return `--hustle-${category}-${kebab}`;
|
|
954
|
+
}
|
|
955
|
+
function generateCSSVariables() {
|
|
956
|
+
const lines = [":root {"];
|
|
957
|
+
for (const [key, value] of Object.entries(defaults.colors)) {
|
|
958
|
+
lines.push(` ${toVarName("color", key)}: ${value};`);
|
|
959
|
+
}
|
|
960
|
+
for (const [key, value] of Object.entries(defaults.typography)) {
|
|
961
|
+
lines.push(` ${toVarName("font", key)}: ${value};`);
|
|
962
|
+
}
|
|
963
|
+
for (const [key, value] of Object.entries(defaults.spacing)) {
|
|
964
|
+
lines.push(` ${toVarName("space", key)}: ${value};`);
|
|
965
|
+
}
|
|
966
|
+
for (const [key, value] of Object.entries(defaults.radius)) {
|
|
967
|
+
lines.push(` ${toVarName("radius", key)}: ${value};`);
|
|
968
|
+
}
|
|
969
|
+
for (const [key, value] of Object.entries(defaults.shadows)) {
|
|
970
|
+
lines.push(` ${toVarName("shadow", key)}: ${value};`);
|
|
971
|
+
}
|
|
972
|
+
for (const [key, value] of Object.entries(defaults.glows)) {
|
|
973
|
+
lines.push(` ${toVarName("glow", key)}: ${value};`);
|
|
974
|
+
}
|
|
975
|
+
for (const [key, value] of Object.entries(defaults.transitions)) {
|
|
976
|
+
lines.push(` ${toVarName("transition", key)}: ${value};`);
|
|
977
|
+
}
|
|
978
|
+
for (const [key, value] of Object.entries(defaults.zIndex)) {
|
|
979
|
+
lines.push(` ${toVarName("z", key)}: ${value};`);
|
|
980
|
+
}
|
|
981
|
+
lines.push("}");
|
|
982
|
+
return lines.join("\n");
|
|
983
|
+
}
|
|
984
|
+
generateCSSVariables();
|
|
985
|
+
function createColorTokens() {
|
|
986
|
+
const result = {};
|
|
987
|
+
for (const [key, defaultValue] of Object.entries(defaults.colors)) {
|
|
988
|
+
result[key] = `var(${toVarName("color", key)}, ${defaultValue})`;
|
|
989
|
+
}
|
|
990
|
+
return result;
|
|
991
|
+
}
|
|
992
|
+
function createTypographyTokens() {
|
|
993
|
+
const result = {};
|
|
994
|
+
for (const [key, defaultValue] of Object.entries(defaults.typography)) {
|
|
995
|
+
result[key] = `var(${toVarName("font", key)}, ${defaultValue})`;
|
|
996
|
+
}
|
|
997
|
+
return result;
|
|
998
|
+
}
|
|
999
|
+
function createSpacingTokens() {
|
|
1000
|
+
const result = {};
|
|
1001
|
+
for (const [key, defaultValue] of Object.entries(defaults.spacing)) {
|
|
1002
|
+
result[key] = `var(${toVarName("space", key)}, ${defaultValue})`;
|
|
1003
|
+
}
|
|
1004
|
+
return result;
|
|
1005
|
+
}
|
|
1006
|
+
function createRadiusTokens() {
|
|
1007
|
+
const result = {};
|
|
1008
|
+
for (const [key, defaultValue] of Object.entries(defaults.radius)) {
|
|
1009
|
+
result[key] = `var(${toVarName("radius", key)}, ${defaultValue})`;
|
|
1010
|
+
}
|
|
1011
|
+
return result;
|
|
1012
|
+
}
|
|
1013
|
+
function createShadowTokens() {
|
|
1014
|
+
const result = {};
|
|
1015
|
+
for (const [key, defaultValue] of Object.entries(defaults.shadows)) {
|
|
1016
|
+
result[key] = `var(${toVarName("shadow", key)}, ${defaultValue})`;
|
|
1017
|
+
}
|
|
1018
|
+
return result;
|
|
1019
|
+
}
|
|
1020
|
+
function createGlowTokens() {
|
|
1021
|
+
const result = {};
|
|
1022
|
+
for (const [key, defaultValue] of Object.entries(defaults.glows)) {
|
|
1023
|
+
result[key] = `var(${toVarName("glow", key)}, ${defaultValue})`;
|
|
1024
|
+
}
|
|
1025
|
+
return result;
|
|
1026
|
+
}
|
|
1027
|
+
function createTransitionTokens() {
|
|
1028
|
+
const result = {};
|
|
1029
|
+
for (const [key, defaultValue] of Object.entries(defaults.transitions)) {
|
|
1030
|
+
result[key] = `var(${toVarName("transition", key)}, ${defaultValue})`;
|
|
1031
|
+
}
|
|
1032
|
+
return result;
|
|
1033
|
+
}
|
|
1034
|
+
function createZIndexTokens() {
|
|
1035
|
+
const result = {};
|
|
1036
|
+
for (const [key, defaultValue] of Object.entries(defaults.zIndex)) {
|
|
1037
|
+
result[key] = parseInt(defaultValue, 10);
|
|
1038
|
+
}
|
|
1039
|
+
return result;
|
|
1040
|
+
}
|
|
1041
|
+
var tokens = {
|
|
1042
|
+
colors: createColorTokens(),
|
|
1043
|
+
typography: createTypographyTokens(),
|
|
1044
|
+
spacing: createSpacingTokens(),
|
|
1045
|
+
radius: createRadiusTokens(),
|
|
1046
|
+
shadows: createShadowTokens(),
|
|
1047
|
+
glows: createGlowTokens(),
|
|
1048
|
+
transitions: createTransitionTokens(),
|
|
1049
|
+
zIndex: createZIndexTokens()
|
|
1050
|
+
};
|
|
1051
|
+
var presets = {
|
|
1052
|
+
base: {
|
|
1053
|
+
fontFamily: tokens.typography.fontFamily,
|
|
1054
|
+
fontSize: tokens.typography.fontSizeMd,
|
|
1055
|
+
lineHeight: tokens.typography.lineHeightNormal,
|
|
1056
|
+
color: tokens.colors.textPrimary
|
|
1057
|
+
},
|
|
1058
|
+
card: {
|
|
1059
|
+
background: tokens.colors.bgSecondary,
|
|
1060
|
+
border: `1px solid ${tokens.colors.borderPrimary}`,
|
|
1061
|
+
borderRadius: tokens.radius.xl
|
|
1062
|
+
},
|
|
1063
|
+
input: {
|
|
1064
|
+
background: tokens.colors.bgTertiary,
|
|
1065
|
+
border: `1px solid ${tokens.colors.borderSecondary}`,
|
|
1066
|
+
borderRadius: tokens.radius.lg,
|
|
1067
|
+
color: tokens.colors.textPrimary,
|
|
1068
|
+
fontSize: tokens.typography.fontSizeMd,
|
|
1069
|
+
padding: `${tokens.spacing.md} ${tokens.spacing.lg}`,
|
|
1070
|
+
transition: `border-color ${tokens.transitions.normal}`
|
|
1071
|
+
},
|
|
1072
|
+
button: {
|
|
1073
|
+
display: "inline-flex",
|
|
1074
|
+
alignItems: "center",
|
|
1075
|
+
justifyContent: "center",
|
|
1076
|
+
gap: tokens.spacing.sm,
|
|
1077
|
+
padding: `${tokens.spacing.sm} ${tokens.spacing.lg}`,
|
|
1078
|
+
borderRadius: tokens.radius.lg,
|
|
1079
|
+
fontSize: tokens.typography.fontSizeMd,
|
|
1080
|
+
fontWeight: tokens.typography.fontWeightMedium,
|
|
1081
|
+
cursor: "pointer",
|
|
1082
|
+
transition: `all ${tokens.transitions.normal}`,
|
|
1083
|
+
border: `1px solid ${tokens.colors.borderSecondary}`,
|
|
1084
|
+
outline: "none"
|
|
1085
|
+
},
|
|
1086
|
+
buttonPrimary: {
|
|
1087
|
+
background: tokens.colors.accentPrimary,
|
|
1088
|
+
color: tokens.colors.textInverse,
|
|
1089
|
+
borderColor: tokens.colors.accentPrimary
|
|
1090
|
+
},
|
|
1091
|
+
buttonSecondary: {
|
|
1092
|
+
background: tokens.colors.bgTertiary,
|
|
1093
|
+
color: tokens.colors.textPrimary,
|
|
1094
|
+
borderColor: tokens.colors.borderSecondary
|
|
1095
|
+
},
|
|
1096
|
+
buttonIcon: {
|
|
1097
|
+
width: "36px",
|
|
1098
|
+
height: "36px",
|
|
1099
|
+
padding: 0,
|
|
1100
|
+
// background inherited from global button styles
|
|
1101
|
+
color: tokens.colors.textSecondary
|
|
1102
|
+
},
|
|
1103
|
+
mono: {
|
|
1104
|
+
fontFamily: tokens.typography.fontFamilyMono,
|
|
1105
|
+
fontSize: tokens.typography.fontSizeSm
|
|
1106
|
+
},
|
|
1107
|
+
label: {
|
|
1108
|
+
fontSize: tokens.typography.fontSizeXs,
|
|
1109
|
+
color: tokens.colors.textTertiary,
|
|
1110
|
+
marginBottom: tokens.spacing.xs
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
var animations = `
|
|
1114
|
+
@keyframes hustle-spin {
|
|
1115
|
+
to { transform: rotate(360deg); }
|
|
1116
|
+
}
|
|
1117
|
+
@keyframes hustle-pulse {
|
|
1118
|
+
0%, 100% { opacity: 1; }
|
|
1119
|
+
50% { opacity: 0.5; }
|
|
1120
|
+
}
|
|
1121
|
+
@keyframes hustle-glow {
|
|
1122
|
+
0%, 100% {
|
|
1123
|
+
opacity: 1;
|
|
1124
|
+
text-shadow: 0 0 4px ${defaults.colors.accentPrimaryBg};
|
|
1125
|
+
}
|
|
1126
|
+
50% {
|
|
1127
|
+
opacity: 0.6;
|
|
1128
|
+
text-shadow: 0 0 8px ${defaults.colors.accentPrimary};
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
`;
|
|
1132
|
+
function truncateAddress(address) {
|
|
1133
|
+
if (!address || address.length < 10) return address || "";
|
|
1134
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
1135
|
+
}
|
|
1136
|
+
async function copyToClipboard(text) {
|
|
1137
|
+
try {
|
|
1138
|
+
await navigator.clipboard.writeText(text);
|
|
1139
|
+
return true;
|
|
1140
|
+
} catch {
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
var styles = {
|
|
1145
|
+
wrapper: {
|
|
1146
|
+
position: "relative",
|
|
1147
|
+
display: "inline-flex",
|
|
1148
|
+
alignItems: "center",
|
|
1149
|
+
gap: tokens.spacing.sm
|
|
1150
|
+
},
|
|
1151
|
+
button: {
|
|
1152
|
+
...presets.button,
|
|
1153
|
+
padding: `${tokens.spacing.sm} ${tokens.spacing.xl}`
|
|
1154
|
+
},
|
|
1155
|
+
disconnected: {
|
|
1156
|
+
background: tokens.colors.bgTertiary,
|
|
1157
|
+
color: tokens.colors.textPrimary,
|
|
1158
|
+
borderColor: tokens.colors.borderSecondary
|
|
1159
|
+
},
|
|
1160
|
+
disconnectedHover: {
|
|
1161
|
+
background: tokens.colors.bgHover,
|
|
1162
|
+
borderColor: tokens.colors.borderHover
|
|
1163
|
+
},
|
|
1164
|
+
connected: {
|
|
1165
|
+
background: "transparent",
|
|
1166
|
+
color: tokens.colors.accentSuccess,
|
|
1167
|
+
borderColor: tokens.colors.accentSuccess,
|
|
1168
|
+
borderRadius: tokens.radius.pill
|
|
1169
|
+
},
|
|
1170
|
+
connectedHover: {
|
|
1171
|
+
background: tokens.colors.accentSuccessBg
|
|
1172
|
+
},
|
|
1173
|
+
loading: {
|
|
1174
|
+
background: tokens.colors.borderSecondary,
|
|
1175
|
+
color: tokens.colors.textSecondary,
|
|
1176
|
+
cursor: "wait"
|
|
1177
|
+
},
|
|
1178
|
+
disabled: {
|
|
1179
|
+
background: tokens.colors.borderSecondary,
|
|
1180
|
+
color: tokens.colors.textTertiary,
|
|
1181
|
+
cursor: "not-allowed",
|
|
1182
|
+
opacity: 0.5
|
|
1183
|
+
},
|
|
1184
|
+
icon: {
|
|
1185
|
+
fontSize: tokens.typography.fontSizeLg
|
|
1186
|
+
},
|
|
1187
|
+
spinner: {
|
|
1188
|
+
display: "inline-block",
|
|
1189
|
+
width: "14px",
|
|
1190
|
+
height: "14px",
|
|
1191
|
+
border: "2px solid currentColor",
|
|
1192
|
+
borderTopColor: "transparent",
|
|
1193
|
+
borderRadius: tokens.radius.full,
|
|
1194
|
+
animation: "hustle-spin 0.8s linear infinite"
|
|
1195
|
+
},
|
|
1196
|
+
address: {
|
|
1197
|
+
...presets.mono,
|
|
1198
|
+
color: tokens.colors.textPrimary
|
|
1199
|
+
},
|
|
1200
|
+
dot: {
|
|
1201
|
+
color: tokens.colors.textSecondary
|
|
1202
|
+
},
|
|
1203
|
+
check: {
|
|
1204
|
+
color: tokens.colors.accentSuccess
|
|
1205
|
+
},
|
|
1206
|
+
arrow: {
|
|
1207
|
+
fontSize: "10px",
|
|
1208
|
+
color: tokens.colors.textSecondary,
|
|
1209
|
+
marginLeft: tokens.spacing.xs
|
|
1210
|
+
},
|
|
1211
|
+
// Disconnect button
|
|
1212
|
+
disconnectBtn: {
|
|
1213
|
+
display: "flex",
|
|
1214
|
+
alignItems: "center",
|
|
1215
|
+
justifyContent: "center",
|
|
1216
|
+
width: "36px",
|
|
1217
|
+
height: "36px",
|
|
1218
|
+
background: "transparent",
|
|
1219
|
+
border: `1px solid ${tokens.colors.borderSecondary}`,
|
|
1220
|
+
borderRadius: tokens.radius.lg,
|
|
1221
|
+
color: tokens.colors.textSecondary,
|
|
1222
|
+
cursor: "pointer",
|
|
1223
|
+
fontSize: "16px",
|
|
1224
|
+
transition: `all ${tokens.transitions.normal}`
|
|
1225
|
+
},
|
|
1226
|
+
disconnectBtnHover: {
|
|
1227
|
+
borderColor: tokens.colors.accentError,
|
|
1228
|
+
color: tokens.colors.accentError
|
|
1229
|
+
},
|
|
1230
|
+
// Vault info dropdown
|
|
1231
|
+
dropdown: {
|
|
1232
|
+
position: "absolute",
|
|
1233
|
+
top: "100%",
|
|
1234
|
+
left: 0,
|
|
1235
|
+
marginTop: tokens.spacing.xs,
|
|
1236
|
+
background: tokens.colors.bgPrimary,
|
|
1237
|
+
border: `1px solid ${tokens.colors.accentSuccess}`,
|
|
1238
|
+
borderRadius: tokens.radius.xl,
|
|
1239
|
+
padding: tokens.spacing.lg,
|
|
1240
|
+
minWidth: "300px",
|
|
1241
|
+
zIndex: tokens.zIndex.dropdown,
|
|
1242
|
+
boxShadow: `0 8px 32px rgba(0,0,0,0.4), 0 0 0 1px ${tokens.colors.accentSuccessBg}`
|
|
1243
|
+
},
|
|
1244
|
+
dropdownHeader: {
|
|
1245
|
+
fontSize: tokens.typography.fontSizeXs,
|
|
1246
|
+
fontWeight: tokens.typography.fontWeightSemibold,
|
|
1247
|
+
color: tokens.colors.textSecondary,
|
|
1248
|
+
letterSpacing: "0.5px",
|
|
1249
|
+
marginBottom: tokens.spacing.lg,
|
|
1250
|
+
textTransform: "uppercase"
|
|
1251
|
+
},
|
|
1252
|
+
dropdownRow: {
|
|
1253
|
+
marginBottom: tokens.spacing.md
|
|
1254
|
+
},
|
|
1255
|
+
dropdownLabel: {
|
|
1256
|
+
display: "block",
|
|
1257
|
+
fontSize: tokens.typography.fontSizeXs,
|
|
1258
|
+
color: tokens.colors.textTertiary,
|
|
1259
|
+
marginBottom: tokens.spacing.xs
|
|
1260
|
+
},
|
|
1261
|
+
dropdownValueRow: {
|
|
1262
|
+
display: "flex",
|
|
1263
|
+
alignItems: "center",
|
|
1264
|
+
justifyContent: "space-between",
|
|
1265
|
+
gap: tokens.spacing.sm
|
|
1266
|
+
},
|
|
1267
|
+
dropdownValue: {
|
|
1268
|
+
fontSize: tokens.typography.fontSizeMd,
|
|
1269
|
+
color: tokens.colors.textPrimary,
|
|
1270
|
+
fontWeight: tokens.typography.fontWeightMedium,
|
|
1271
|
+
flex: 1
|
|
1272
|
+
},
|
|
1273
|
+
dropdownValueMono: {
|
|
1274
|
+
...presets.mono,
|
|
1275
|
+
wordBreak: "break-all"
|
|
1276
|
+
},
|
|
1277
|
+
copyBtn: {
|
|
1278
|
+
background: "transparent",
|
|
1279
|
+
border: `1px solid ${tokens.colors.borderSecondary}`,
|
|
1280
|
+
color: tokens.colors.textSecondary,
|
|
1281
|
+
padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,
|
|
1282
|
+
borderRadius: tokens.radius.sm,
|
|
1283
|
+
cursor: "pointer",
|
|
1284
|
+
fontSize: tokens.typography.fontSizeXs,
|
|
1285
|
+
transition: `all ${tokens.transitions.normal}`,
|
|
1286
|
+
whiteSpace: "nowrap"
|
|
1287
|
+
},
|
|
1288
|
+
copyBtnHover: {
|
|
1289
|
+
background: tokens.colors.bgHover,
|
|
1290
|
+
borderColor: tokens.colors.accentPrimary,
|
|
1291
|
+
color: tokens.colors.accentPrimary
|
|
1292
|
+
},
|
|
1293
|
+
copyBtnCopied: {
|
|
1294
|
+
background: tokens.colors.accentSuccess,
|
|
1295
|
+
borderColor: tokens.colors.accentSuccess,
|
|
1296
|
+
color: tokens.colors.textInverse
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
function ConnectButton({
|
|
1300
|
+
className = "",
|
|
1301
|
+
style,
|
|
1302
|
+
connectLabel = "Connect",
|
|
1303
|
+
loadingLabel = "Connecting...",
|
|
1304
|
+
onConnect,
|
|
1305
|
+
onDisconnect,
|
|
1306
|
+
showVaultInfo = true,
|
|
1307
|
+
disabled = false
|
|
1308
|
+
}) {
|
|
1309
|
+
const {
|
|
1310
|
+
isAuthenticated,
|
|
1311
|
+
isLoading,
|
|
1312
|
+
walletAddress,
|
|
1313
|
+
vaultId,
|
|
1314
|
+
openAuthModal,
|
|
1315
|
+
logout
|
|
1316
|
+
} = useEmblemAuth();
|
|
1317
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
1318
|
+
const [showDropdown, setShowDropdown] = useState(false);
|
|
1319
|
+
const [disconnectHovered, setDisconnectHovered] = useState(false);
|
|
1320
|
+
const [copiedField, setCopiedField] = useState(null);
|
|
1321
|
+
const [copyHovered, setCopyHovered] = useState(null);
|
|
1322
|
+
const handleClick = useCallback(async () => {
|
|
1323
|
+
if (disabled) return;
|
|
1324
|
+
if (!isAuthenticated && !isLoading) {
|
|
1325
|
+
await openAuthModal();
|
|
1326
|
+
onConnect?.();
|
|
1327
|
+
}
|
|
1328
|
+
}, [disabled, isAuthenticated, isLoading, openAuthModal, onConnect]);
|
|
1329
|
+
const handleDisconnect = useCallback(() => {
|
|
1330
|
+
logout();
|
|
1331
|
+
onDisconnect?.();
|
|
1332
|
+
setShowDropdown(false);
|
|
1333
|
+
}, [logout, onDisconnect]);
|
|
1334
|
+
const handleCopy = useCallback(async (field, value) => {
|
|
1335
|
+
const success = await copyToClipboard(value);
|
|
1336
|
+
if (success) {
|
|
1337
|
+
setCopiedField(field);
|
|
1338
|
+
setTimeout(() => setCopiedField(null), 1500);
|
|
1339
|
+
}
|
|
1340
|
+
}, []);
|
|
1341
|
+
let buttonStyle = { ...styles.button };
|
|
1342
|
+
let content = connectLabel;
|
|
1343
|
+
if (disabled) {
|
|
1344
|
+
buttonStyle = { ...buttonStyle, ...styles.disconnected, ...styles.disabled };
|
|
1345
|
+
} else if (isLoading) {
|
|
1346
|
+
buttonStyle = { ...buttonStyle, ...styles.disconnected, ...styles.loading };
|
|
1347
|
+
content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1348
|
+
/* @__PURE__ */ jsx("span", { style: styles.spinner }),
|
|
1349
|
+
loadingLabel
|
|
1350
|
+
] });
|
|
1351
|
+
} else if (isAuthenticated) {
|
|
1352
|
+
buttonStyle = { ...buttonStyle, ...styles.connected };
|
|
1353
|
+
if (isHovered || showDropdown) {
|
|
1354
|
+
buttonStyle = { ...buttonStyle, ...styles.connectedHover };
|
|
1355
|
+
}
|
|
1356
|
+
const truncated = truncateAddress(walletAddress || "");
|
|
1357
|
+
content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1358
|
+
/* @__PURE__ */ jsx("span", { style: styles.check, children: "\u2713" }),
|
|
1359
|
+
/* @__PURE__ */ jsx("span", { children: "Connected" }),
|
|
1360
|
+
/* @__PURE__ */ jsx("span", { style: styles.dot, children: "\u2022" }),
|
|
1361
|
+
/* @__PURE__ */ jsx("span", { style: styles.address, children: truncated }),
|
|
1362
|
+
/* @__PURE__ */ jsx("span", { style: styles.arrow, children: "\u25BE" })
|
|
1363
|
+
] });
|
|
1364
|
+
} else {
|
|
1365
|
+
buttonStyle = { ...buttonStyle, ...styles.disconnected };
|
|
1366
|
+
if (isHovered) {
|
|
1367
|
+
buttonStyle = { ...buttonStyle, ...styles.disconnectedHover };
|
|
1368
|
+
}
|
|
1369
|
+
content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1370
|
+
/* @__PURE__ */ jsx("span", { style: styles.icon, children: "\u2192" }),
|
|
1371
|
+
connectLabel
|
|
1372
|
+
] });
|
|
1373
|
+
}
|
|
1374
|
+
if (style) {
|
|
1375
|
+
buttonStyle = { ...buttonStyle, ...style };
|
|
1376
|
+
}
|
|
1377
|
+
const renderCopyBtn = (field, value) => {
|
|
1378
|
+
const isCopied = copiedField === field;
|
|
1379
|
+
const isHover = copyHovered === field;
|
|
1380
|
+
return /* @__PURE__ */ jsx(
|
|
1381
|
+
"button",
|
|
1382
|
+
{
|
|
1383
|
+
type: "button",
|
|
1384
|
+
onClick: (e) => {
|
|
1385
|
+
e.stopPropagation();
|
|
1386
|
+
handleCopy(field, value);
|
|
1387
|
+
},
|
|
1388
|
+
style: {
|
|
1389
|
+
...styles.copyBtn,
|
|
1390
|
+
...isCopied ? styles.copyBtnCopied : isHover ? styles.copyBtnHover : {}
|
|
1391
|
+
},
|
|
1392
|
+
onMouseEnter: () => setCopyHovered(field),
|
|
1393
|
+
onMouseLeave: () => setCopyHovered(null),
|
|
1394
|
+
children: isCopied ? "Copied!" : "Copy"
|
|
1395
|
+
}
|
|
1396
|
+
);
|
|
1397
|
+
};
|
|
1398
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1399
|
+
/* @__PURE__ */ jsx("style", { children: animations }),
|
|
1400
|
+
/* @__PURE__ */ jsxs(
|
|
1401
|
+
"div",
|
|
1402
|
+
{
|
|
1403
|
+
style: styles.wrapper,
|
|
1404
|
+
onMouseEnter: () => isAuthenticated && showVaultInfo && setShowDropdown(true),
|
|
1405
|
+
onMouseLeave: () => setShowDropdown(false),
|
|
1406
|
+
children: [
|
|
1407
|
+
/* @__PURE__ */ jsx(
|
|
1408
|
+
"button",
|
|
1409
|
+
{
|
|
1410
|
+
type: "button",
|
|
1411
|
+
onClick: handleClick,
|
|
1412
|
+
disabled: disabled || isLoading,
|
|
1413
|
+
className,
|
|
1414
|
+
style: buttonStyle,
|
|
1415
|
+
onMouseEnter: () => setIsHovered(true),
|
|
1416
|
+
onMouseLeave: () => setIsHovered(false),
|
|
1417
|
+
children: content
|
|
1418
|
+
}
|
|
1419
|
+
),
|
|
1420
|
+
isAuthenticated && /* @__PURE__ */ jsx(
|
|
1421
|
+
"button",
|
|
1422
|
+
{
|
|
1423
|
+
type: "button",
|
|
1424
|
+
onClick: handleDisconnect,
|
|
1425
|
+
style: {
|
|
1426
|
+
...styles.disconnectBtn,
|
|
1427
|
+
...disconnectHovered ? styles.disconnectBtnHover : {}
|
|
1428
|
+
},
|
|
1429
|
+
onMouseEnter: () => setDisconnectHovered(true),
|
|
1430
|
+
onMouseLeave: () => setDisconnectHovered(false),
|
|
1431
|
+
title: "Disconnect",
|
|
1432
|
+
children: "\u23FB"
|
|
1433
|
+
}
|
|
1434
|
+
),
|
|
1435
|
+
isAuthenticated && showVaultInfo && showDropdown && /* @__PURE__ */ jsxs("div", { style: styles.dropdown, children: [
|
|
1436
|
+
/* @__PURE__ */ jsx("div", { style: styles.dropdownHeader, children: "Vault Information" }),
|
|
1437
|
+
/* @__PURE__ */ jsxs("div", { style: styles.dropdownRow, children: [
|
|
1438
|
+
/* @__PURE__ */ jsx("span", { style: styles.dropdownLabel, children: "Vault ID" }),
|
|
1439
|
+
/* @__PURE__ */ jsxs("div", { style: styles.dropdownValueRow, children: [
|
|
1440
|
+
/* @__PURE__ */ jsxs("span", { style: styles.dropdownValue, children: [
|
|
1441
|
+
"#",
|
|
1442
|
+
vaultId
|
|
1443
|
+
] }),
|
|
1444
|
+
renderCopyBtn("vaultId", vaultId || "")
|
|
1445
|
+
] })
|
|
1446
|
+
] }),
|
|
1447
|
+
/* @__PURE__ */ jsxs("div", { style: { ...styles.dropdownRow, marginBottom: 0 }, children: [
|
|
1448
|
+
/* @__PURE__ */ jsx("span", { style: styles.dropdownLabel, children: "Connected Wallet" }),
|
|
1449
|
+
/* @__PURE__ */ jsxs("div", { style: styles.dropdownValueRow, children: [
|
|
1450
|
+
/* @__PURE__ */ jsx("span", { style: { ...styles.dropdownValue, ...styles.dropdownValueMono }, children: walletAddress }),
|
|
1451
|
+
renderCopyBtn("wallet", walletAddress || "")
|
|
1452
|
+
] })
|
|
1453
|
+
] })
|
|
1454
|
+
] })
|
|
1455
|
+
]
|
|
1456
|
+
}
|
|
1457
|
+
)
|
|
1458
|
+
] });
|
|
1459
|
+
}
|
|
1460
|
+
async function copyToClipboard2(text) {
|
|
1461
|
+
try {
|
|
1462
|
+
await navigator.clipboard.writeText(text);
|
|
1463
|
+
return true;
|
|
1464
|
+
} catch {
|
|
1465
|
+
return false;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
var s = {
|
|
1469
|
+
container: {
|
|
1470
|
+
position: "relative",
|
|
1471
|
+
display: "inline-flex",
|
|
1472
|
+
alignItems: "center",
|
|
1473
|
+
gap: tokens.spacing.sm,
|
|
1474
|
+
fontFamily: tokens.typography.fontFamily
|
|
1475
|
+
},
|
|
1476
|
+
disconnected: {
|
|
1477
|
+
display: "inline-flex",
|
|
1478
|
+
alignItems: "center",
|
|
1479
|
+
gap: tokens.spacing.sm,
|
|
1480
|
+
color: tokens.colors.textSecondary,
|
|
1481
|
+
fontSize: tokens.typography.fontSizeMd
|
|
1482
|
+
},
|
|
1483
|
+
dot: {
|
|
1484
|
+
display: "inline-block",
|
|
1485
|
+
width: "8px",
|
|
1486
|
+
height: "8px",
|
|
1487
|
+
borderRadius: tokens.radius.full,
|
|
1488
|
+
backgroundColor: tokens.colors.textTertiary
|
|
1489
|
+
},
|
|
1490
|
+
dotConnected: {
|
|
1491
|
+
backgroundColor: tokens.colors.accentSuccess
|
|
1492
|
+
},
|
|
1493
|
+
spinner: {
|
|
1494
|
+
display: "inline-block",
|
|
1495
|
+
width: "12px",
|
|
1496
|
+
height: "12px",
|
|
1497
|
+
border: `2px solid ${tokens.colors.textSecondary}`,
|
|
1498
|
+
borderTopColor: "transparent",
|
|
1499
|
+
borderRadius: tokens.radius.full,
|
|
1500
|
+
animation: "hustle-spin 0.8s linear infinite"
|
|
1501
|
+
},
|
|
1502
|
+
logoutBtn: {
|
|
1503
|
+
...presets.buttonIcon,
|
|
1504
|
+
border: `1px solid ${tokens.colors.borderSecondary}`,
|
|
1505
|
+
borderRadius: tokens.radius.lg,
|
|
1506
|
+
transition: `all ${tokens.transitions.normal}`
|
|
1507
|
+
},
|
|
1508
|
+
logoutBtnHover: {
|
|
1509
|
+
borderColor: tokens.colors.accentError,
|
|
1510
|
+
color: tokens.colors.accentError
|
|
1511
|
+
},
|
|
1512
|
+
vaultInfoWrapper: {
|
|
1513
|
+
position: "relative"
|
|
1514
|
+
},
|
|
1515
|
+
vaultInfo: {
|
|
1516
|
+
position: "absolute",
|
|
1517
|
+
top: "100%",
|
|
1518
|
+
right: 0,
|
|
1519
|
+
marginTop: tokens.spacing.sm,
|
|
1520
|
+
background: tokens.colors.bgSecondary,
|
|
1521
|
+
border: `1px solid ${tokens.colors.borderPrimary}`,
|
|
1522
|
+
borderRadius: tokens.radius.xl,
|
|
1523
|
+
padding: tokens.spacing.lg,
|
|
1524
|
+
minWidth: "380px",
|
|
1525
|
+
zIndex: tokens.zIndex.dropdown,
|
|
1526
|
+
boxShadow: tokens.shadows.lg
|
|
1527
|
+
},
|
|
1528
|
+
vaultInfoHeader: {
|
|
1529
|
+
fontSize: tokens.typography.fontSizeXs,
|
|
1530
|
+
fontWeight: tokens.typography.fontWeightSemibold,
|
|
1531
|
+
color: tokens.colors.textSecondary,
|
|
1532
|
+
letterSpacing: "0.5px",
|
|
1533
|
+
marginBottom: tokens.spacing.lg,
|
|
1534
|
+
textTransform: "uppercase"
|
|
1535
|
+
},
|
|
1536
|
+
vaultInfoRow: {
|
|
1537
|
+
marginBottom: tokens.spacing.md
|
|
1538
|
+
},
|
|
1539
|
+
vaultLabel: {
|
|
1540
|
+
display: "block",
|
|
1541
|
+
fontSize: "12px",
|
|
1542
|
+
color: tokens.colors.textTertiary,
|
|
1543
|
+
marginBottom: tokens.spacing.xs
|
|
1544
|
+
},
|
|
1545
|
+
vaultValueRow: {
|
|
1546
|
+
display: "flex",
|
|
1547
|
+
alignItems: "center",
|
|
1548
|
+
justifyContent: "space-between",
|
|
1549
|
+
gap: tokens.spacing.sm
|
|
1550
|
+
},
|
|
1551
|
+
vaultValue: {
|
|
1552
|
+
fontSize: tokens.typography.fontSizeMd,
|
|
1553
|
+
color: tokens.colors.textPrimary,
|
|
1554
|
+
fontWeight: tokens.typography.fontWeightMedium,
|
|
1555
|
+
flex: 1
|
|
1556
|
+
},
|
|
1557
|
+
vaultValueMono: {
|
|
1558
|
+
...presets.mono,
|
|
1559
|
+
wordBreak: "break-all"
|
|
1560
|
+
},
|
|
1561
|
+
copyBtn: {
|
|
1562
|
+
background: "transparent",
|
|
1563
|
+
border: `1px solid ${tokens.colors.borderSecondary}`,
|
|
1564
|
+
color: tokens.colors.textSecondary,
|
|
1565
|
+
padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,
|
|
1566
|
+
borderRadius: tokens.radius.sm,
|
|
1567
|
+
cursor: "pointer",
|
|
1568
|
+
fontSize: tokens.typography.fontSizeXs,
|
|
1569
|
+
transition: `all ${tokens.transitions.normal}`,
|
|
1570
|
+
whiteSpace: "nowrap"
|
|
1571
|
+
},
|
|
1572
|
+
copyBtnHover: {
|
|
1573
|
+
background: tokens.colors.bgHover,
|
|
1574
|
+
borderColor: tokens.colors.accentPrimary,
|
|
1575
|
+
color: tokens.colors.accentPrimary
|
|
1576
|
+
},
|
|
1577
|
+
copyBtnCopied: {
|
|
1578
|
+
background: tokens.colors.accentSuccess,
|
|
1579
|
+
borderColor: tokens.colors.accentSuccess,
|
|
1580
|
+
color: tokens.colors.textInverse
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
function AuthStatus({
|
|
1584
|
+
className = "",
|
|
1585
|
+
style,
|
|
1586
|
+
showVaultInfo = false,
|
|
1587
|
+
showLogout = false
|
|
1588
|
+
}) {
|
|
1589
|
+
const {
|
|
1590
|
+
isAuthenticated,
|
|
1591
|
+
isLoading,
|
|
1592
|
+
walletAddress,
|
|
1593
|
+
vaultId,
|
|
1594
|
+
vaultInfo,
|
|
1595
|
+
logout
|
|
1596
|
+
} = useEmblemAuth();
|
|
1597
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
1598
|
+
const [logoutHovered, setLogoutHovered] = useState(false);
|
|
1599
|
+
const [copiedField, setCopiedField] = useState(null);
|
|
1600
|
+
const [copyHovered, setCopyHovered] = useState(null);
|
|
1601
|
+
const handleCopy = useCallback(async (field, value) => {
|
|
1602
|
+
const success = await copyToClipboard2(value);
|
|
1603
|
+
if (success) {
|
|
1604
|
+
setCopiedField(field);
|
|
1605
|
+
setTimeout(() => setCopiedField(null), 1500);
|
|
1606
|
+
}
|
|
1607
|
+
}, []);
|
|
1608
|
+
if (!isAuthenticated) {
|
|
1609
|
+
if (isLoading) {
|
|
1610
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1611
|
+
/* @__PURE__ */ jsx("style", { children: animations }),
|
|
1612
|
+
/* @__PURE__ */ jsxs("div", { className, style: { ...s.disconnected, ...style }, children: [
|
|
1613
|
+
/* @__PURE__ */ jsx("span", { style: s.spinner }),
|
|
1614
|
+
/* @__PURE__ */ jsx("span", { children: "Connecting..." })
|
|
1615
|
+
] })
|
|
1616
|
+
] });
|
|
1617
|
+
}
|
|
1618
|
+
return /* @__PURE__ */ jsxs("div", { className, style: { ...s.disconnected, ...style }, children: [
|
|
1619
|
+
/* @__PURE__ */ jsx("span", { style: s.dot }),
|
|
1620
|
+
/* @__PURE__ */ jsx("span", { children: "Not connected" })
|
|
1621
|
+
] });
|
|
1622
|
+
}
|
|
1623
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1624
|
+
/* @__PURE__ */ jsx("style", { children: animations }),
|
|
1625
|
+
/* @__PURE__ */ jsxs("div", { className, style: { ...s.container, ...style }, children: [
|
|
1626
|
+
/* @__PURE__ */ jsxs(
|
|
1627
|
+
"div",
|
|
1628
|
+
{
|
|
1629
|
+
style: s.vaultInfoWrapper,
|
|
1630
|
+
onMouseEnter: () => showVaultInfo && setIsHovered(true),
|
|
1631
|
+
onMouseLeave: () => showVaultInfo && setIsHovered(false),
|
|
1632
|
+
children: [
|
|
1633
|
+
/* @__PURE__ */ jsx("span", { style: { ...s.dot, ...s.dotConnected }, title: "Connected" }),
|
|
1634
|
+
showVaultInfo && isHovered && /* @__PURE__ */ jsxs("div", { style: s.vaultInfo, children: [
|
|
1635
|
+
/* @__PURE__ */ jsx("div", { style: s.vaultInfoHeader, children: "Vault Information" }),
|
|
1636
|
+
/* @__PURE__ */ jsxs("div", { style: s.vaultInfoRow, children: [
|
|
1637
|
+
/* @__PURE__ */ jsx("span", { style: s.vaultLabel, children: "Vault ID" }),
|
|
1638
|
+
/* @__PURE__ */ jsxs("div", { style: s.vaultValueRow, children: [
|
|
1639
|
+
/* @__PURE__ */ jsxs("span", { style: s.vaultValue, children: [
|
|
1640
|
+
"#",
|
|
1641
|
+
vaultId
|
|
1642
|
+
] }),
|
|
1643
|
+
/* @__PURE__ */ jsx(
|
|
1644
|
+
CopyButton,
|
|
1645
|
+
{
|
|
1646
|
+
field: "vaultId",
|
|
1647
|
+
value: vaultId || "",
|
|
1648
|
+
copiedField,
|
|
1649
|
+
copyHovered,
|
|
1650
|
+
setCopyHovered,
|
|
1651
|
+
onCopy: handleCopy
|
|
1652
|
+
}
|
|
1653
|
+
)
|
|
1654
|
+
] })
|
|
1655
|
+
] }),
|
|
1656
|
+
/* @__PURE__ */ jsxs("div", { style: s.vaultInfoRow, children: [
|
|
1657
|
+
/* @__PURE__ */ jsx("span", { style: s.vaultLabel, children: "Connected Wallet" }),
|
|
1658
|
+
/* @__PURE__ */ jsxs("div", { style: s.vaultValueRow, children: [
|
|
1659
|
+
/* @__PURE__ */ jsx("span", { style: { ...s.vaultValue, ...s.vaultValueMono }, children: walletAddress }),
|
|
1660
|
+
/* @__PURE__ */ jsx(
|
|
1661
|
+
CopyButton,
|
|
1662
|
+
{
|
|
1663
|
+
field: "wallet",
|
|
1664
|
+
value: walletAddress || "",
|
|
1665
|
+
copiedField,
|
|
1666
|
+
copyHovered,
|
|
1667
|
+
setCopyHovered,
|
|
1668
|
+
onCopy: handleCopy
|
|
1669
|
+
}
|
|
1670
|
+
)
|
|
1671
|
+
] })
|
|
1672
|
+
] }),
|
|
1673
|
+
vaultInfo?.evmAddress && /* @__PURE__ */ jsxs("div", { style: s.vaultInfoRow, children: [
|
|
1674
|
+
/* @__PURE__ */ jsx("span", { style: s.vaultLabel, children: "Vault EVM Address" }),
|
|
1675
|
+
/* @__PURE__ */ jsxs("div", { style: s.vaultValueRow, children: [
|
|
1676
|
+
/* @__PURE__ */ jsx("span", { style: { ...s.vaultValue, ...s.vaultValueMono }, children: vaultInfo.evmAddress }),
|
|
1677
|
+
/* @__PURE__ */ jsx(
|
|
1678
|
+
CopyButton,
|
|
1679
|
+
{
|
|
1680
|
+
field: "evmAddress",
|
|
1681
|
+
value: vaultInfo.evmAddress,
|
|
1682
|
+
copiedField,
|
|
1683
|
+
copyHovered,
|
|
1684
|
+
setCopyHovered,
|
|
1685
|
+
onCopy: handleCopy
|
|
1686
|
+
}
|
|
1687
|
+
)
|
|
1688
|
+
] })
|
|
1689
|
+
] }),
|
|
1690
|
+
vaultInfo?.solanaAddress && /* @__PURE__ */ jsxs("div", { style: s.vaultInfoRow, children: [
|
|
1691
|
+
/* @__PURE__ */ jsx("span", { style: s.vaultLabel, children: "Vault Solana Address" }),
|
|
1692
|
+
/* @__PURE__ */ jsxs("div", { style: s.vaultValueRow, children: [
|
|
1693
|
+
/* @__PURE__ */ jsx("span", { style: { ...s.vaultValue, ...s.vaultValueMono }, children: vaultInfo.solanaAddress }),
|
|
1694
|
+
/* @__PURE__ */ jsx(
|
|
1695
|
+
CopyButton,
|
|
1696
|
+
{
|
|
1697
|
+
field: "solAddress",
|
|
1698
|
+
value: vaultInfo.solanaAddress,
|
|
1699
|
+
copiedField,
|
|
1700
|
+
copyHovered,
|
|
1701
|
+
setCopyHovered,
|
|
1702
|
+
onCopy: handleCopy
|
|
1703
|
+
}
|
|
1704
|
+
)
|
|
1705
|
+
] })
|
|
1706
|
+
] })
|
|
1707
|
+
] })
|
|
1708
|
+
]
|
|
1709
|
+
}
|
|
1710
|
+
),
|
|
1711
|
+
showLogout && /* @__PURE__ */ jsx(
|
|
1712
|
+
"button",
|
|
1713
|
+
{
|
|
1714
|
+
type: "button",
|
|
1715
|
+
onClick: logout,
|
|
1716
|
+
style: {
|
|
1717
|
+
...s.logoutBtn,
|
|
1718
|
+
...logoutHovered ? s.logoutBtnHover : {}
|
|
1719
|
+
},
|
|
1720
|
+
onMouseEnter: () => setLogoutHovered(true),
|
|
1721
|
+
onMouseLeave: () => setLogoutHovered(false),
|
|
1722
|
+
title: "Disconnect",
|
|
1723
|
+
children: "\u23FB"
|
|
1724
|
+
}
|
|
1725
|
+
)
|
|
1726
|
+
] })
|
|
1727
|
+
] });
|
|
1728
|
+
}
|
|
1729
|
+
function CopyButton({ field, value, copiedField, copyHovered, setCopyHovered, onCopy }) {
|
|
1730
|
+
const isCopied = copiedField === field;
|
|
1731
|
+
const isHovered = copyHovered === field;
|
|
1732
|
+
return /* @__PURE__ */ jsx(
|
|
1733
|
+
"button",
|
|
1734
|
+
{
|
|
1735
|
+
type: "button",
|
|
1736
|
+
onClick: () => onCopy(field, value),
|
|
1737
|
+
style: {
|
|
1738
|
+
...s.copyBtn,
|
|
1739
|
+
...isCopied ? s.copyBtnCopied : isHovered ? s.copyBtnHover : {}
|
|
1740
|
+
},
|
|
1741
|
+
onMouseEnter: () => setCopyHovered(field),
|
|
1742
|
+
onMouseLeave: () => setCopyHovered(null),
|
|
1743
|
+
children: isCopied ? "Copied!" : "Copy"
|
|
1744
|
+
}
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
// src/plugins/predictionMarket.ts
|
|
1749
|
+
var DOME_API_BASE = "https://api.domeapi.io/v1";
|
|
1750
|
+
var predictionMarketPlugin = {
|
|
1751
|
+
name: "prediction-market-alpha",
|
|
1752
|
+
version: "1.1.0",
|
|
1753
|
+
description: "Search and analyze prediction markets on Polymarket and Kalshi",
|
|
1754
|
+
tools: [
|
|
1755
|
+
{
|
|
1756
|
+
name: "get_supported_platforms",
|
|
1757
|
+
description: "Get a list of supported prediction market platforms. Call this first to know which platforms are available for querying.",
|
|
1758
|
+
parameters: {
|
|
1759
|
+
type: "object",
|
|
1760
|
+
properties: {}
|
|
1761
|
+
}
|
|
1762
|
+
},
|
|
1763
|
+
{
|
|
1764
|
+
name: "search_prediction_markets",
|
|
1765
|
+
description: "Search for prediction markets. Find markets about politics, crypto, sports, and more. Returns market titles, current odds, volume, and status. You MUST provide tags to filter results.",
|
|
1766
|
+
parameters: {
|
|
1767
|
+
type: "object",
|
|
1768
|
+
properties: {
|
|
1769
|
+
platform: {
|
|
1770
|
+
type: "string",
|
|
1771
|
+
enum: ["polymarket", "kalshi"],
|
|
1772
|
+
description: "The prediction market platform to search (default: polymarket)"
|
|
1773
|
+
},
|
|
1774
|
+
tags: {
|
|
1775
|
+
type: "array",
|
|
1776
|
+
items: { type: "string" },
|
|
1777
|
+
description: 'REQUIRED: Single word categories or tags to identify a market segment (e.g., "politics", "crypto", "sports", "finance", "entertainment", "ai")'
|
|
1778
|
+
},
|
|
1779
|
+
status: {
|
|
1780
|
+
type: "string",
|
|
1781
|
+
enum: ["open", "closed"],
|
|
1782
|
+
description: "Filter by market status"
|
|
1783
|
+
},
|
|
1784
|
+
limit: {
|
|
1785
|
+
type: "number",
|
|
1786
|
+
description: "Number of results (1-100)",
|
|
1787
|
+
default: 10
|
|
1788
|
+
}
|
|
1789
|
+
},
|
|
1790
|
+
required: ["tags"]
|
|
1791
|
+
}
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
name: "get_market_details",
|
|
1795
|
+
description: "Get detailed information about a specific prediction market including current prices, trading history, and resolution source.",
|
|
1796
|
+
parameters: {
|
|
1797
|
+
type: "object",
|
|
1798
|
+
properties: {
|
|
1799
|
+
platform: {
|
|
1800
|
+
type: "string",
|
|
1801
|
+
enum: ["polymarket", "kalshi"],
|
|
1802
|
+
description: "The prediction market platform (default: polymarket)"
|
|
1803
|
+
},
|
|
1804
|
+
market_slug: {
|
|
1805
|
+
type: "string",
|
|
1806
|
+
description: "The market slug/identifier (ticker for Kalshi)"
|
|
1807
|
+
}
|
|
1808
|
+
},
|
|
1809
|
+
required: ["market_slug"]
|
|
1810
|
+
}
|
|
1811
|
+
},
|
|
1812
|
+
{
|
|
1813
|
+
name: "get_market_prices",
|
|
1814
|
+
description: "Get current prices/odds for a prediction market. Shows probability for each outcome.",
|
|
1815
|
+
parameters: {
|
|
1816
|
+
type: "object",
|
|
1817
|
+
properties: {
|
|
1818
|
+
platform: {
|
|
1819
|
+
type: "string",
|
|
1820
|
+
enum: ["polymarket", "kalshi"],
|
|
1821
|
+
description: "The prediction market platform (default: polymarket)"
|
|
1822
|
+
},
|
|
1823
|
+
market_slug: {
|
|
1824
|
+
type: "string",
|
|
1825
|
+
description: "The market slug/identifier (ticker for Kalshi)"
|
|
1826
|
+
}
|
|
1827
|
+
},
|
|
1828
|
+
required: ["market_slug"]
|
|
1829
|
+
}
|
|
1830
|
+
},
|
|
1831
|
+
{
|
|
1832
|
+
name: "get_market_trades",
|
|
1833
|
+
description: "Get recent orders/trading activity for a prediction market.",
|
|
1834
|
+
parameters: {
|
|
1835
|
+
type: "object",
|
|
1836
|
+
properties: {
|
|
1837
|
+
platform: {
|
|
1838
|
+
type: "string",
|
|
1839
|
+
enum: ["polymarket", "kalshi"],
|
|
1840
|
+
description: "The prediction market platform (default: polymarket)"
|
|
1841
|
+
},
|
|
1842
|
+
market_slug: {
|
|
1843
|
+
type: "string",
|
|
1844
|
+
description: "The market slug/identifier (ticker for Kalshi)"
|
|
1845
|
+
},
|
|
1846
|
+
limit: {
|
|
1847
|
+
type: "number",
|
|
1848
|
+
description: "Number of orders to return (max 100)",
|
|
1849
|
+
default: 20
|
|
1850
|
+
}
|
|
1851
|
+
},
|
|
1852
|
+
required: ["market_slug"]
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
],
|
|
1856
|
+
executors: {
|
|
1857
|
+
get_supported_platforms: async () => {
|
|
1858
|
+
return {
|
|
1859
|
+
platforms: [
|
|
1860
|
+
{
|
|
1861
|
+
id: "polymarket",
|
|
1862
|
+
name: "Polymarket",
|
|
1863
|
+
description: "Decentralized prediction market on Polygon",
|
|
1864
|
+
features: ["tags", "volume_filtering"]
|
|
1865
|
+
},
|
|
1866
|
+
{
|
|
1867
|
+
id: "kalshi",
|
|
1868
|
+
name: "Kalshi",
|
|
1869
|
+
description: "CFTC-regulated prediction market exchange",
|
|
1870
|
+
features: ["regulated", "event_based"]
|
|
1871
|
+
}
|
|
1872
|
+
]
|
|
1873
|
+
};
|
|
1874
|
+
},
|
|
1875
|
+
search_prediction_markets: async (args) => {
|
|
1876
|
+
const platform = args.platform || "polymarket";
|
|
1877
|
+
const params = new URLSearchParams();
|
|
1878
|
+
if (args.limit) params.append("limit", String(args.limit));
|
|
1879
|
+
if (platform === "polymarket") {
|
|
1880
|
+
if (args.tags) params.append("tags", args.tags.join(","));
|
|
1881
|
+
if (args.status) params.append("status", args.status);
|
|
1882
|
+
const response = await fetch(`${DOME_API_BASE}/polymarket/markets?${params}`);
|
|
1883
|
+
if (!response.ok) {
|
|
1884
|
+
throw new Error(`Dome API error: ${response.status} ${response.statusText}`);
|
|
1885
|
+
}
|
|
1886
|
+
const data = await response.json();
|
|
1887
|
+
return {
|
|
1888
|
+
platform: "polymarket",
|
|
1889
|
+
markets: data.markets?.map((m) => ({
|
|
1890
|
+
slug: m.market_slug,
|
|
1891
|
+
title: m.title,
|
|
1892
|
+
status: m.status,
|
|
1893
|
+
volume: m.volume_total,
|
|
1894
|
+
tags: m.tags,
|
|
1895
|
+
outcomes: [
|
|
1896
|
+
{ label: m.side_a?.label, id: m.side_a?.id },
|
|
1897
|
+
{ label: m.side_b?.label, id: m.side_b?.id }
|
|
1898
|
+
],
|
|
1899
|
+
winner: m.winning_side
|
|
1900
|
+
})) || [],
|
|
1901
|
+
total: data.pagination?.total || 0,
|
|
1902
|
+
hasMore: data.pagination?.has_more || false
|
|
1903
|
+
};
|
|
1904
|
+
} else if (platform === "kalshi") {
|
|
1905
|
+
if (args.status) params.append("status", args.status);
|
|
1906
|
+
const response = await fetch(`${DOME_API_BASE}/kalshi/markets?${params}`);
|
|
1907
|
+
if (!response.ok) {
|
|
1908
|
+
throw new Error(`Dome API error: ${response.status} ${response.statusText}`);
|
|
1909
|
+
}
|
|
1910
|
+
const data = await response.json();
|
|
1911
|
+
return {
|
|
1912
|
+
platform: "kalshi",
|
|
1913
|
+
markets: data.markets?.map((m) => ({
|
|
1914
|
+
ticker: m.ticker,
|
|
1915
|
+
title: m.title,
|
|
1916
|
+
status: m.status,
|
|
1917
|
+
category: m.category,
|
|
1918
|
+
subtitle: m.subtitle,
|
|
1919
|
+
yesAsk: m.yes_ask,
|
|
1920
|
+
yesBid: m.yes_bid,
|
|
1921
|
+
volume: m.volume
|
|
1922
|
+
})) || [],
|
|
1923
|
+
total: data.pagination?.total || 0,
|
|
1924
|
+
hasMore: data.pagination?.has_more || false
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
1928
|
+
},
|
|
1929
|
+
get_market_details: async (args) => {
|
|
1930
|
+
const platform = args.platform || "polymarket";
|
|
1931
|
+
if (platform === "polymarket") {
|
|
1932
|
+
const response = await fetch(
|
|
1933
|
+
`${DOME_API_BASE}/polymarket/markets?market_slug=${args.market_slug}`
|
|
1934
|
+
);
|
|
1935
|
+
if (!response.ok) {
|
|
1936
|
+
throw new Error(`Dome API error: ${response.status} ${response.statusText}`);
|
|
1937
|
+
}
|
|
1938
|
+
const data = await response.json();
|
|
1939
|
+
const market = data.markets?.[0];
|
|
1940
|
+
if (!market) {
|
|
1941
|
+
throw new Error(`Market not found: ${args.market_slug}`);
|
|
1942
|
+
}
|
|
1943
|
+
return {
|
|
1944
|
+
platform: "polymarket",
|
|
1945
|
+
slug: market.market_slug,
|
|
1946
|
+
title: market.title,
|
|
1947
|
+
status: market.status,
|
|
1948
|
+
startTime: market.start_time ? new Date(market.start_time * 1e3).toISOString() : null,
|
|
1949
|
+
endTime: market.end_time ? new Date(market.end_time * 1e3).toISOString() : null,
|
|
1950
|
+
volume: {
|
|
1951
|
+
total: market.volume_total,
|
|
1952
|
+
week: market.volume_1_week,
|
|
1953
|
+
month: market.volume_1_month
|
|
1954
|
+
},
|
|
1955
|
+
resolutionSource: market.resolution_source,
|
|
1956
|
+
outcomes: [
|
|
1957
|
+
{ label: market.side_a?.label, id: market.side_a?.id },
|
|
1958
|
+
{ label: market.side_b?.label, id: market.side_b?.id }
|
|
1959
|
+
],
|
|
1960
|
+
winner: market.winning_side,
|
|
1961
|
+
tags: market.tags
|
|
1962
|
+
};
|
|
1963
|
+
} else if (platform === "kalshi") {
|
|
1964
|
+
const response = await fetch(
|
|
1965
|
+
`${DOME_API_BASE}/kalshi/markets?ticker=${args.market_slug}`
|
|
1966
|
+
);
|
|
1967
|
+
if (!response.ok) {
|
|
1968
|
+
throw new Error(`Dome API error: ${response.status} ${response.statusText}`);
|
|
1969
|
+
}
|
|
1970
|
+
const data = await response.json();
|
|
1971
|
+
const market = data.markets?.[0];
|
|
1972
|
+
if (!market) {
|
|
1973
|
+
throw new Error(`Market not found: ${args.market_slug}`);
|
|
1974
|
+
}
|
|
1975
|
+
return {
|
|
1976
|
+
platform: "kalshi",
|
|
1977
|
+
ticker: market.ticker,
|
|
1978
|
+
title: market.title,
|
|
1979
|
+
subtitle: market.subtitle,
|
|
1980
|
+
status: market.status,
|
|
1981
|
+
category: market.category,
|
|
1982
|
+
yesAsk: market.yes_ask,
|
|
1983
|
+
yesBid: market.yes_bid,
|
|
1984
|
+
volume: market.volume,
|
|
1985
|
+
openInterest: market.open_interest
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
1989
|
+
},
|
|
1990
|
+
get_market_prices: async (args) => {
|
|
1991
|
+
const platform = args.platform || "polymarket";
|
|
1992
|
+
if (platform === "polymarket") {
|
|
1993
|
+
const response = await fetch(
|
|
1994
|
+
`${DOME_API_BASE}/polymarket/market-price?market_slug=${args.market_slug}`
|
|
1995
|
+
);
|
|
1996
|
+
if (!response.ok) {
|
|
1997
|
+
throw new Error(`Dome API error: ${response.status} ${response.statusText}`);
|
|
1998
|
+
}
|
|
1999
|
+
const data = await response.json();
|
|
2000
|
+
return { platform: "polymarket", ...data };
|
|
2001
|
+
} else if (platform === "kalshi") {
|
|
2002
|
+
const response = await fetch(
|
|
2003
|
+
`${DOME_API_BASE}/kalshi/markets?ticker=${args.market_slug}`
|
|
2004
|
+
);
|
|
2005
|
+
if (!response.ok) {
|
|
2006
|
+
throw new Error(`Dome API error: ${response.status} ${response.statusText}`);
|
|
2007
|
+
}
|
|
2008
|
+
const data = await response.json();
|
|
2009
|
+
const market = data.markets?.[0];
|
|
2010
|
+
if (!market) {
|
|
2011
|
+
throw new Error(`Market not found: ${args.market_slug}`);
|
|
2012
|
+
}
|
|
2013
|
+
return {
|
|
2014
|
+
platform: "kalshi",
|
|
2015
|
+
ticker: market.ticker,
|
|
2016
|
+
yesAsk: market.yes_ask,
|
|
2017
|
+
yesBid: market.yes_bid,
|
|
2018
|
+
lastPrice: market.last_price
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
2022
|
+
},
|
|
2023
|
+
get_market_trades: async (args) => {
|
|
2024
|
+
const platform = args.platform || "polymarket";
|
|
2025
|
+
const limit = String(args.limit || 20);
|
|
2026
|
+
if (platform === "polymarket") {
|
|
2027
|
+
const params = new URLSearchParams({
|
|
2028
|
+
market_slug: args.market_slug,
|
|
2029
|
+
limit
|
|
2030
|
+
});
|
|
2031
|
+
const response = await fetch(
|
|
2032
|
+
`${DOME_API_BASE}/polymarket/orders?${params}`
|
|
2033
|
+
);
|
|
2034
|
+
if (!response.ok) {
|
|
2035
|
+
throw new Error(`Dome API error: ${response.status} ${response.statusText}`);
|
|
2036
|
+
}
|
|
2037
|
+
const data = await response.json();
|
|
2038
|
+
return { platform: "polymarket", ...data };
|
|
2039
|
+
} else if (platform === "kalshi") {
|
|
2040
|
+
const params = new URLSearchParams({
|
|
2041
|
+
ticker: args.market_slug,
|
|
2042
|
+
limit
|
|
2043
|
+
});
|
|
2044
|
+
const response = await fetch(
|
|
2045
|
+
`${DOME_API_BASE}/kalshi/trades?${params}`
|
|
2046
|
+
);
|
|
2047
|
+
if (!response.ok) {
|
|
2048
|
+
throw new Error(`Dome API error: ${response.status} ${response.statusText}`);
|
|
2049
|
+
}
|
|
2050
|
+
const data = await response.json();
|
|
2051
|
+
return { platform: "kalshi", ...data };
|
|
2052
|
+
}
|
|
2053
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
2054
|
+
}
|
|
2055
|
+
},
|
|
2056
|
+
hooks: {
|
|
2057
|
+
onRegister: () => {
|
|
2058
|
+
console.log("[Plugin] Prediction Market Alpha v1.1.0 registered (Polymarket + Kalshi)");
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
};
|
|
2062
|
+
|
|
2063
|
+
// src/plugins/migrateFun.ts
|
|
2064
|
+
var QA = [
|
|
2065
|
+
{ id: "migration-steps-bonk", question: "What are all the steps for migrations to Bonk?", answer: `Here's how MigrateFun migrates your token to BonkFun:
|
|
2066
|
+
1) The creator/team sets up a migration portal through migratefun - Create new CA for BonkFun (Ticker, name, image, supply) and set timeline period (1-30 days, 14 days average)
|
|
2067
|
+
2) After portal setup, holders commit their tokens to migration - tokens are locked in migration vault until time period ends
|
|
2068
|
+
3) Once migration ends, ALL tokens are market sold in a single candle to retrieve as much SOL as possible from the locked liquidity pool
|
|
2069
|
+
4) The recovered SOL seeds the new LP paired with appropriate amount of tokens. Set market cap at or slightly below current market cap
|
|
2070
|
+
5) Claims open for 90 days - users burn MFTs and receive new tokens`, keywords: ["bonk", "bonkfun", "steps", "process", "how"], category: "process" },
|
|
2071
|
+
{ id: "migration-steps-pumpfun", question: "How does a migration work to Pump Fun?", answer: `Here's how the migration to Pump Fun works:
|
|
2072
|
+
1) Set up a new CA for pumpfun using MigrateFun creator dashboard (Ticker, CA, image)
|
|
2073
|
+
2) Users migrate their tokens in the migration portal for a specific time period
|
|
2074
|
+
3) Once migration ends, MigrateFun sells all migrated tokens
|
|
2075
|
+
4) MigrateFun takes all recovered SOL and buys out new token's bonding curve + purchases until it reaches old market cap levels
|
|
2076
|
+
5) Users return to migratefun and claim their new tokens
|
|
2077
|
+
6) 90-day claim period for all users, regardless if they migrated on time or late
|
|
2078
|
+
7) The claim period can also be used to swap tokens with centralized exchanges
|
|
2079
|
+
8) After 90 days, all unclaimed tokens and remaining SOL are returned to the team`, keywords: ["pump", "pumpfun", "steps", "process", "how"], category: "process" },
|
|
2080
|
+
{ id: "migration-steps-raydium", question: "How does Migrate Fun migrate tokens to Raydium?", answer: `1) The creator/team sets up a migration portal through migratefun.com - Create new CA (Ticker, name, image, supply) and set timeline period (1-30 days, 14 days average)
|
|
2081
|
+
2) After portal setup, holders commit their tokens to migration and get MFTs in exchange - tokens are locked in migration vault until time period ends
|
|
2082
|
+
3) Once migration ends, ALL old tokens are market sold in a single candle to retrieve as much SOL as possible
|
|
2083
|
+
4) The recovered SOL seeds the new LP paired with appropriate amount of new tokens. Market Cap is set by the user
|
|
2084
|
+
5) Claims open for 90 days - users burn MFTs and receive new tokens. Late migrators can swap at discounted rate
|
|
2085
|
+
6) At the end of the 90 day claim window all remaining tokens can be claimed by the team`, keywords: ["raydium", "steps", "process", "how"], category: "process" },
|
|
2086
|
+
{ id: "post-migration-checklist", question: "What are the steps I need to do after the migration?", answer: `Admin Checklist - places to register your new CA:
|
|
2087
|
+
|
|
2088
|
+
PRIMARY:
|
|
2089
|
+
- Coingecko (Free + migration application process)
|
|
2090
|
+
- Dexscreener ($300)
|
|
2091
|
+
- GeckoTerminal (free for 5 days wait)
|
|
2092
|
+
- Holderscan (Listing free, Verify $125)
|
|
2093
|
+
|
|
2094
|
+
SECONDARY:
|
|
2095
|
+
- Solscan (if metadata updates needed)
|
|
2096
|
+
- CoinMarketCap ($5000 for immediate listing)
|
|
2097
|
+
- Dextools ($300 for verification)
|
|
2098
|
+
- Photon (2 SOL)
|
|
2099
|
+
- Cookie.fun (Free, DM needed) [For AI accounts]
|
|
2100
|
+
- Kaito (DM needed) [For AI accounts]
|
|
2101
|
+
|
|
2102
|
+
Note: Coingecko and CoinMarketCap will ask for a post from official twitter with migration details and new CA.`, keywords: ["after", "post", "checklist", "listing", "dexscreener", "coingecko"], category: "post-migration" },
|
|
2103
|
+
{ id: "post-migration-approval", question: "Do we need to do anything after the migration ends?", answer: `Yes, the team needs to approve several steps in the migration portal including:
|
|
2104
|
+
1) Selling the migrated tokens into the old LP
|
|
2105
|
+
2) Setting the new market cap
|
|
2106
|
+
3) Opening the claim portal
|
|
2107
|
+
|
|
2108
|
+
Video tutorial: https://www.youtube.com/watch?v=SjPN-1DnXtM`, keywords: ["after", "ends", "approve", "portal"], category: "post-migration" },
|
|
2109
|
+
{ id: "market-cap-setting", question: "What market cap should we set?", answer: `Set at or slightly below the ending market cap at migration. For example, if your ending market cap is $1 million, set it to around $950,000. This accounts for the fact you won't receive 1:1 liquidity from the old pool compared to the new pool, which is determined by migration participation.`, keywords: ["market cap", "marketcap", "set", "recommend"], category: "settings" },
|
|
2110
|
+
{ id: "migrate-fun-cost", question: "What does Migrate Fun cost?", answer: `Migrate Fun charges a flat 3.75% fee on the total SOL unlocked from the old Liquidity Pool. This fee is taken automatically during the migration process.`, keywords: ["cost", "fee", "price", "charge", "3.75", "percent"], category: "fees" },
|
|
2111
|
+
{ id: "claim-fees-bonk", question: "How does the team claim their fees on Bonk Fun?", answer: `Go to https://bonk.fun/creator-rewards to claim your creator fees.`, keywords: ["claim", "fees", "bonk", "creator", "rewards"], category: "fees" },
|
|
2112
|
+
{ id: "claim-fees-raydium", question: "How do I claim fees on Raydium?", answer: `You can claim fees from https://raydium.io/portfolio/ with the same wallet that set up the migration.`, keywords: ["claim", "fees", "raydium", "portfolio"], category: "fees" },
|
|
2113
|
+
{ id: "claim-fees-pumpfun", question: "How do I claim fees on Pump Fun?", answer: `Fees are paid automatically to the wallet used to set up the migration. Once migrated to PumpSwap you will receive creator rewards based on their Ascend Program. Details: https://pump.fun/docs/fees`, keywords: ["claim", "fees", "pump", "pumpfun", "automatic"], category: "fees" },
|
|
2114
|
+
{ id: "audit-info", question: "Has Migrate Fun been audited?", answer: `Yes. Migrate Fun was audited by Halborn, the same auditing firm used by the Solana Foundation.
|
|
2115
|
+
Audit: https://www.halborn.com/audits/emblem-vault/migratefun-8ad34b
|
|
2116
|
+
Announcement: https://x.com/HalbornSecurity/status/1978869642744811933`, keywords: ["audit", "audited", "security", "halborn", "safe"], category: "security" },
|
|
2117
|
+
{ id: "user-experience", question: "What is the process like for the user?", answer: `Super easy - takes less than 20 seconds.
|
|
2118
|
+
1) During migration: users swap old tokens for Migrate Fun Tokens (MFTs)
|
|
2119
|
+
2) Once migration ends: claim period opens for 90 days, users burn MFTs to claim new tokens
|
|
2120
|
+
3) Late migrators: can swap old tokens for new at a discounted rate set by the team
|
|
2121
|
+
|
|
2122
|
+
Video guides:
|
|
2123
|
+
- Migration: https://x.com/MigrateFun/status/1971259552856408433
|
|
2124
|
+
- Claims: https://x.com/MigrateFun/status/1976376597906325767`, keywords: ["user", "experience", "process", "simple", "easy"], category: "user-experience" },
|
|
2125
|
+
{ id: "what-is-migrate-fun", question: "What is Migrate Fun?", answer: `Migrate Fun is the category-defining platform that created the migration meta. It allows users to migrate locked liquidity from one launchpad to another.
|
|
2126
|
+
|
|
2127
|
+
As of October 2025: 15 migrations completed, $5+ million in liquidity moved, largest migration was $35M market cap project. Supports migrations to Bonk Fun, Raydium, and Pump Fun.
|
|
2128
|
+
|
|
2129
|
+
Links:
|
|
2130
|
+
- Website: https://migrate.fun/
|
|
2131
|
+
- X: https://x.com/MigrateFun
|
|
2132
|
+
- Docs: https://github.com/EmblemCompany/Migrate-fun-docs/
|
|
2133
|
+
- Audit: https://www.halborn.com/audits/emblem-vault/migratefun-8ad34b
|
|
2134
|
+
- Calculator: https://migrate.fun/migration-calculator`, keywords: ["what", "migrate fun", "about", "general", "overview"], category: "general" },
|
|
2135
|
+
{ id: "sol-recovery-estimate", question: "How can I see how much SOL we can get from the old LP?", answer: `Use the migration calculator to estimate SOL recovery based on participation percentages: https://migrate.fun/migration-calculator`, keywords: ["sol", "recovery", "estimate", "calculator", "liquidity", "how much"], category: "tools" },
|
|
2136
|
+
{ id: "documentation", question: "Do you have documentation?", answer: `Yes, documentation is available at: https://github.com/EmblemCompany/Migrate-fun-docs/`, keywords: ["documentation", "docs", "guide", "help"], category: "general" },
|
|
2137
|
+
{ id: "rebrand", question: "What if I want to rebrand?", answer: `The migration enables a full rebrand - reset metadata, image, logo, name, everything. Or keep it all the same if you prefer.`, keywords: ["rebrand", "change", "name", "logo", "image", "metadata"], category: "features" },
|
|
2138
|
+
{ id: "sniper-protection", question: "How do you prevent snipers when migrating to Pump Fun?", answer: `On Solana you can stack transactions. When deploying the bonding curve, you are first to purchase so you can buyout the bonding curve and more in the first transaction, preventing snipers.`, keywords: ["sniper", "snipers", "protection", "front-run", "mev"], category: "security" },
|
|
2139
|
+
{ id: "claim-period-flexibility", question: "Is there flexibility on the 90 day claim period?", answer: `The 90-day claim period is mandatory. Reasons:
|
|
2140
|
+
- All users need time to claim tokens after migration
|
|
2141
|
+
- Those who missed migration need a window
|
|
2142
|
+
- Users don't get tokens until after migration completes
|
|
2143
|
+
- Allowing team to withdraw during claims would be risky (potential rug)`, keywords: ["90 day", "claim", "period", "flexibility", "change"], category: "settings" },
|
|
2144
|
+
{ id: "migration-duration", question: "How long is the migration?", answer: `You can set the migration window for as long as you'd like. Average is 14 days. Some teams choose 7 days (works great), some go up to 30 days.
|
|
2145
|
+
|
|
2146
|
+
Note: Majority of participation happens in the first and last 24 hours. Example: 76% participation with 50%+ migrating in first 24 hours and additional 10% on the last day.`, keywords: ["how long", "duration", "time", "days", "period", "window"], category: "settings" },
|
|
2147
|
+
{ id: "migration-performance", question: "Can you give examples of token performance after migration?", answer: `Migration squeeze: When large percentage of old tokens migrate, sell pressure reduces, often causing market cap spike near migration end.
|
|
2148
|
+
|
|
2149
|
+
Example charts:
|
|
2150
|
+
- ZERA Old: https://dexscreener.com/solana/95at5r4i85gfqeew2yr6byfg8rlry1d9ztps7qrskdvc
|
|
2151
|
+
New: https://dexscreener.com/solana/nn9vmhjtqgg9l9f8sp3geufwc5zvuhradcwehh7n7di
|
|
2152
|
+
- HUSTLE Old: https://dexscreener.com/solana/gjckb2eesjk65nuvpaw4tn2rabnr8wmfcwcwpagk5dzs
|
|
2153
|
+
New: https://dexscreener.com/solana/hxo1wrcrdewek8l2j6rxswnolumej2mweh38gajxtw7y
|
|
2154
|
+
|
|
2155
|
+
Thread: https://x.com/jakegallen_/status/1973051293213028468`, keywords: ["performance", "charts", "example", "before", "after", "squeeze"], category: "examples" },
|
|
2156
|
+
{ id: "why-migrate", question: "Why would teams want to migrate?", answer: `Top reasons:
|
|
2157
|
+
- Access to creator rewards
|
|
2158
|
+
- Rebrand opportunity
|
|
2159
|
+
- Fresh chart
|
|
2160
|
+
- Reclaim part of the token supply
|
|
2161
|
+
- Reinvigorated community on the other side`, keywords: ["why", "reasons", "benefits", "advantages", "should"], category: "general" },
|
|
2162
|
+
{ id: "migration-recommendation-steps", question: "What steps do you recommend when considering a migration?", answer: `1) Discuss with Migrate Fun team: process details, where to move LP (Raydium, Bonk Fun, or Pump Fun)
|
|
2163
|
+
2) Discuss benefits with your community, especially whale holders - get buy-in
|
|
2164
|
+
3) Announce on all social channels - maximize awareness for maximum participation`, keywords: ["recommend", "steps", "considering", "planning", "prepare"], category: "process" },
|
|
2165
|
+
{ id: "multiple-wallets", question: "Do holders with multiple wallets need to consolidate?", answer: `No. Migration is linear - users can migrate all tokens together or separately. Makes no difference.`, keywords: ["multiple", "wallets", "consolidate", "separate"], category: "user-experience" },
|
|
2166
|
+
{ id: "mft-value", question: "Do MFTs show value in wallets?", answer: `MFTs (Migrate Fun Tokens) are just placeholder tokens for the migration. They are valueless.`, keywords: ["mft", "migrate fun tokens", "value", "placeholder"], category: "user-experience" },
|
|
2167
|
+
{ id: "announcement-examples", question: "Can you give me sample migration announcements?", answer: `Here are announcements made by teams:
|
|
2168
|
+
- https://x.com/radrdotfun/status/1952127168101949620
|
|
2169
|
+
- https://x.com/project_89/status/1951345024656089368
|
|
2170
|
+
- https://x.com/HKittyOnSol/status/1948925330032349210
|
|
2171
|
+
- https://x.com/ModernStoicAI/status/1948129627362218483
|
|
2172
|
+
- https://x.com/pokithehamster/status/1950238636928327927
|
|
2173
|
+
- https://x.com/IQ6900_/status/1953002036599173499
|
|
2174
|
+
- https://x.com/TheBongoCat/status/1965538945132843333`, keywords: ["announcement", "sample", "example", "post", "twitter"], category: "examples" },
|
|
2175
|
+
{ id: "graphics", question: "Does Migrate Fun provide graphics for announcements?", answer: `No. For your own migration announcement you create the graphic. Migrate Fun's designer creates group migration announcements only.`, keywords: ["graphics", "images", "design", "announcement"], category: "general" },
|
|
2176
|
+
{ id: "developer-required", question: "Do I need to be a developer to migrate?", answer: `No development required. The entire process is a few clicks for both pre-migration and post-migration.`, keywords: ["developer", "technical", "coding", "code"], category: "general" },
|
|
2177
|
+
{ id: "exchange-tokens", question: "What happens to tokens on exchanges or locked in Streamflow?", answer: `They will miss the migration as those tokens are considered circulating supply onchain. Options:
|
|
2178
|
+
1) Join as late migrator during 90-day claim window
|
|
2179
|
+
2) After 90-day period, team takes possession of unclaimed tokens and can reimburse directly`, keywords: ["exchange", "streamflow", "locked", "vested", "cex"], category: "edge-cases" },
|
|
2180
|
+
{ id: "unclaimed-tokens", question: "Can we get unclaimed tokens?", answer: `After the 90-day claim period ends, all unclaimed tokens are returned to whichever wallet set up the migration (the team).`, keywords: ["unclaimed", "supply", "remaining", "tokens", "team"], category: "post-migration" },
|
|
2181
|
+
{ id: "participation-rate", question: "What is typical participation percentage?", answer: `Nearly all projects have had over 50% migration participation. View all stats at https://migrate.fun/projects`, keywords: ["participation", "percentage", "rate", "typical", "average"], category: "statistics" },
|
|
2182
|
+
{ id: "late-penalty", question: "What penalty can teams set for late migrators?", answer: `Teams can set 0-100% penalty for late migrators who swap during the 90-day claim window. 25% seems to be a good balance - encourages participation without being overly punishing.`, keywords: ["penalty", "late", "discount", "punish", "percent"], category: "settings" },
|
|
2183
|
+
{ id: "new-ca", question: "Will we get a new CA or just change the pair?", answer: `A new CA. If migrating to Bonk Fun your CA will end with "bonk". If migrating to Pump Fun it will end with "pump".`, keywords: ["ca", "contract", "address", "new", "pair"], category: "process" },
|
|
2184
|
+
{ id: "vanity-ca", question: "Can we create a vanity CA?", answer: `Yes, but if migrating to Bonk Fun it needs to end with "bonk", or for Pump Fun it needs to end with "pump".`, keywords: ["vanity", "ca", "custom", "address", "contract"], category: "features" },
|
|
2185
|
+
{ id: "vested-tokens", question: "What about team tokens locked with Streamflow?", answer: `Those tokens can't be migrated. They won't be lost - you'll recapture that supply post-migration. Any unmigrated tokens return to team at full or discounted rate depending on your late migrator penalty setting.`, keywords: ["vested", "streamflow", "locked", "team"], category: "edge-cases" },
|
|
2186
|
+
{ id: "wallet-tracking", question: "Can I track wallet addresses post-migration?", answer: `At the end of migration, there's a snapshot tool that lets you download a CSV of all wallets holding the old token.`, keywords: ["wallet", "track", "snapshot", "csv", "addresses"], category: "tools" },
|
|
2187
|
+
{ id: "old-listings", question: "What happens to old token listings?", answer: `You will need to apply to new directories. Migrate Fun provides a list once you begin the migration process.`, keywords: ["listings", "directories", "old", "new", "update"], category: "post-migration" },
|
|
2188
|
+
{ id: "lp-locking", question: "Does the new LP get locked?", answer: `If migrating to Bonk Fun or Pump Fun: LP is locked (their rules). If migrating to Raydium: LP is unlocked and you control it.`, keywords: ["lp", "locked", "liquidity", "pool", "control"], category: "process" },
|
|
2189
|
+
{ id: "sol-pairs", question: "Are Bonk migrations confined to SOL pairs?", answer: `As of October 2025, they are SOL migrations. Check with the team to see if USD1 pairs are possible.`, keywords: ["sol", "pair", "usd1", "usdc", "bonk"], category: "settings" },
|
|
2190
|
+
{ id: "change-penalty", question: "Can I change the penalty after setup?", answer: `No. The penalty must be set during migration creation and cannot be changed after.`, keywords: ["change", "penalty", "modify", "update", "after"], category: "settings" },
|
|
2191
|
+
{ id: "unhappy-holders", question: "What if someone is unhappy about the migration?", answer: `They can sell their tokens and not participate. Migrations are a fresh start - holders who migrate are voting with their tokens that they support the team and believe in the project's future.`, keywords: ["unhappy", "disagree", "against", "sell"], category: "user-experience" },
|
|
2192
|
+
{ id: "sample-announcement", question: "What is a good announcement template?", answer: `Sample for Bonk Fun migration:
|
|
2193
|
+
|
|
2194
|
+
"We're excited to announce that we're migrating with @MigrateFun and officially joining the @bonk_inu ecosystem next month!
|
|
2195
|
+
|
|
2196
|
+
With our 1-year anniversary less than a month away, this migration to @bonk_fun marks the beginning of our next chapter.
|
|
2197
|
+
|
|
2198
|
+
Why Bonk Fun?
|
|
2199
|
+
\u{1F9F0} Purpose-built tools for community projects
|
|
2200
|
+
\u{1F4B8} Transaction fee rev share
|
|
2201
|
+
\u{1F501} Seamless LP migration
|
|
2202
|
+
\u{1F91D} Strategic alignment with top meme coin teams
|
|
2203
|
+
|
|
2204
|
+
Our migration timeline + holder instructions drop soon."`, keywords: ["announcement", "template", "sample", "post"], category: "examples" },
|
|
2205
|
+
{ id: "share-link", question: "Do you have a link to share explaining Migrate Fun?", answer: `Overview thread: https://x.com/migratefun/status/1957492884355314035
|
|
2206
|
+
Deep dive docs: https://github.com/EmblemCompany/Migrate-fun-docs/`, keywords: ["link", "share", "explain", "overview"], category: "general" },
|
|
2207
|
+
{ id: "exchange-rate", question: "Is the exchange rate always 1:1?", answer: `Yes, 1 old token = 1 new token for users who participate in migration. Users who miss can swap during the 90-day claim window at a discounted rate set by the team.`, keywords: ["exchange", "rate", "1:1", "ratio", "same"], category: "process" },
|
|
2208
|
+
{ id: "change-supply", question: "Can I change the total supply?", answer: `Generally yes, with some constraints depending on destination platform. Reach out to Migrate Fun team to discuss specifics.`, keywords: ["supply", "change", "total", "amount"], category: "features" },
|
|
2209
|
+
{ id: "contact", question: "How can I get in touch with the Migrate Fun team?", answer: `Send a direct message to the Migrate Fun X account: https://x.com/MigrateFun`, keywords: ["contact", "reach", "touch", "dm", "message", "talk"], category: "general" },
|
|
2210
|
+
{ id: "ready-to-migrate", question: "I am ready to migrate, what is next?", answer: `Send a direct message to the Migrate Fun X account: https://x.com/MigrateFun`, keywords: ["ready", "start", "begin", "next"], category: "general" },
|
|
2211
|
+
{ id: "risks", question: "What are the risks of migrating?", answer: `Main risk: Failed migration where not enough tokens migrate to fund the new LP. In that case, migrated tokens are sold into the old LP and SOL is returned to community members who participated.`, keywords: ["risk", "danger", "fail", "problem", "issue"], category: "security" },
|
|
2212
|
+
{ id: "how-detect-non-migrators", question: "How does Migrate Fun know who did not migrate?", answer: `Migrate Fun has a snapshot and claim tool that puts onchain all wallets holding old tokens at migration end. This enables late migrators to swap old tokens for new during the 90-day claim window at the team-set discount.`, keywords: ["snapshot", "detect", "know", "non-migrators"], category: "tools" },
|
|
2213
|
+
{ id: "no-penalty", question: "What if I do not want to penalize non-migrators?", answer: `Set the late claim penalty to zero. Holders who didn't migrate can then swap old tokens for new at a 1:1 rate during the 90-day claim window.`, keywords: ["no penalty", "zero", "fair"], category: "settings" },
|
|
2214
|
+
{ id: "missed-claim-window", question: "What happens if holders miss both migration and claim window?", answer: `They won't have access to new tokens through Migrate Fun platform. All remaining tokens go to the team after 90-day window closes. The team has a snapshot of all old token holders and can handle at their discretion.`, keywords: ["missed", "both", "claim", "window", "late"], category: "edge-cases" },
|
|
2215
|
+
{ id: "exchange-options", question: "What options do exchanges have to swap tokens?", answer: `Two options:
|
|
2216
|
+
|
|
2217
|
+
Option 1: Participate onchain through the migration portal (same as retail). Load tokens into Phantom wallet, migrate, then claim. ~10 seconds each step.
|
|
2218
|
+
|
|
2219
|
+
Option 2: Admin withdraw function during 90-day claim period. Migration admin can withdraw new tokens from claims vault and manually swap with exchange.
|
|
2220
|
+
- 90-day window for exchange procedures
|
|
2221
|
+
- Exchange can observe migration complete before acting
|
|
2222
|
+
- Can receive new tokens before sending old
|
|
2223
|
+
- Can pause trading during process`, keywords: ["exchange", "cex", "swap", "options", "centralized"], category: "edge-cases" },
|
|
2224
|
+
{ id: "buy-prevent-dump", question: "Can we buy supply before migration to prevent dumping?", answer: `I would recommend buying now. There is no arb opportunity as you set the market cap of the new token.
|
|
2225
|
+
|
|
2226
|
+
To regain control of non-migrated tokens, you can penalize non-migrators. Example: If 60% migrate, 40% didn't. With a 50% penalty on non-migrators, you'd recoup 20% of total supply if everyone claimed.`, keywords: ["buy", "dump", "supply", "control", "arb"], category: "strategy" }
|
|
2227
|
+
];
|
|
2228
|
+
var migrateFunPlugin = {
|
|
2229
|
+
name: "migrate-fun-knowledge",
|
|
2230
|
+
version: "1.0.0",
|
|
2231
|
+
description: "Search Migrate.fun knowledge base for token migration answers",
|
|
2232
|
+
tools: [
|
|
2233
|
+
{
|
|
2234
|
+
name: "search_migrate_fun_docs",
|
|
2235
|
+
description: "Search the Migrate.fun knowledge base for answers about token migrations. Use this tool when users ask about: how migrations work (Bonk Fun, Pump Fun, Raydium), costs/fees/timelines, post-migration steps, user experience, technical details (LP, CA, penalties), or examples. Returns ranked Q&A pairs from real support conversations.",
|
|
2236
|
+
parameters: {
|
|
2237
|
+
type: "object",
|
|
2238
|
+
properties: {
|
|
2239
|
+
query: {
|
|
2240
|
+
type: "string",
|
|
2241
|
+
description: "The search query - user question or key terms about token migrations"
|
|
2242
|
+
},
|
|
2243
|
+
topK: {
|
|
2244
|
+
type: "number",
|
|
2245
|
+
description: "Number of results to return (default: 5, max: 10)"
|
|
2246
|
+
}
|
|
2247
|
+
},
|
|
2248
|
+
required: ["query"]
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
],
|
|
2252
|
+
executors: {
|
|
2253
|
+
search_migrate_fun_docs: async (args) => {
|
|
2254
|
+
const query = args.query;
|
|
2255
|
+
const topK = Math.min(Math.max(1, args.topK || 5), 10);
|
|
2256
|
+
const queryLower = query.toLowerCase();
|
|
2257
|
+
const queryWords = queryLower.split(/\s+/).filter((w) => w.length > 2);
|
|
2258
|
+
const scored = QA.map((qa) => {
|
|
2259
|
+
let score = 0;
|
|
2260
|
+
const questionLower = qa.question.toLowerCase();
|
|
2261
|
+
const answerLower = qa.answer.toLowerCase();
|
|
2262
|
+
const keywordsStr = qa.keywords.join(" ").toLowerCase();
|
|
2263
|
+
const fullText = `${questionLower} ${answerLower} ${keywordsStr}`;
|
|
2264
|
+
if (questionLower.includes(queryLower)) score += 15;
|
|
2265
|
+
if (fullText.includes(queryLower)) score += 8;
|
|
2266
|
+
for (const word of queryWords) {
|
|
2267
|
+
if (qa.keywords.some((kw) => kw.toLowerCase().includes(word) || word.includes(kw.toLowerCase()))) {
|
|
2268
|
+
score += 4;
|
|
2269
|
+
}
|
|
2270
|
+
if (questionLower.includes(word)) score += 3;
|
|
2271
|
+
if (answerLower.includes(word)) score += 1;
|
|
2272
|
+
}
|
|
2273
|
+
return { ...qa, score };
|
|
2274
|
+
});
|
|
2275
|
+
const results = scored.filter((qa) => qa.score > 0).sort((a, b) => b.score - a.score).slice(0, topK).map((qa, i) => ({
|
|
2276
|
+
rank: i + 1,
|
|
2277
|
+
relevance: Math.round(qa.score / 30 * 100) / 100,
|
|
2278
|
+
question: qa.question,
|
|
2279
|
+
answer: qa.answer,
|
|
2280
|
+
category: qa.category
|
|
2281
|
+
}));
|
|
2282
|
+
return {
|
|
2283
|
+
success: true,
|
|
2284
|
+
query,
|
|
2285
|
+
resultCount: results.length,
|
|
2286
|
+
results,
|
|
2287
|
+
hint: results.length === 0 ? "No matches found. Suggest contacting @MigrateFun on X: https://x.com/MigrateFun" : "Use these Q&A pairs to answer. Include relevant links from the answers."
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
},
|
|
2291
|
+
hooks: {
|
|
2292
|
+
onRegister: () => {
|
|
2293
|
+
console.log("[Plugin] Migrate.fun Knowledge Base v1.0.0 registered");
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
};
|
|
2297
|
+
|
|
2298
|
+
// src/plugins/index.ts
|
|
2299
|
+
var availablePlugins = [
|
|
2300
|
+
{
|
|
2301
|
+
...predictionMarketPlugin,
|
|
2302
|
+
description: "Search and analyze Polymarket and Kalshi prediction markets"
|
|
2303
|
+
},
|
|
2304
|
+
{
|
|
2305
|
+
...migrateFunPlugin,
|
|
2306
|
+
description: "Search Migrate.fun knowledge base for token migration answers"
|
|
2307
|
+
}
|
|
2308
|
+
];
|
|
2309
|
+
function getAvailablePlugin(name) {
|
|
2310
|
+
return availablePlugins.find((p) => p.name === name);
|
|
2311
|
+
}
|
|
2312
|
+
hljs.registerLanguage("javascript", javascript);
|
|
2313
|
+
hljs.registerLanguage("js", javascript);
|
|
2314
|
+
hljs.registerLanguage("typescript", typescript);
|
|
2315
|
+
hljs.registerLanguage("ts", typescript);
|
|
2316
|
+
hljs.registerLanguage("python", python);
|
|
2317
|
+
hljs.registerLanguage("py", python);
|
|
2318
|
+
hljs.registerLanguage("json", json);
|
|
2319
|
+
hljs.registerLanguage("bash", bash);
|
|
2320
|
+
hljs.registerLanguage("sh", bash);
|
|
2321
|
+
hljs.registerLanguage("shell", shell);
|
|
2322
|
+
hljs.registerLanguage("css", css);
|
|
2323
|
+
hljs.registerLanguage("xml", xml);
|
|
2324
|
+
hljs.registerLanguage("html", xml);
|
|
2325
|
+
hljs.registerLanguage("markdown", markdown);
|
|
2326
|
+
hljs.registerLanguage("md", markdown);
|
|
2327
|
+
hljs.registerLanguage("sql", sql);
|
|
2328
|
+
hljs.registerLanguage("yaml", yaml);
|
|
2329
|
+
hljs.registerLanguage("yml", yaml);
|
|
2330
|
+
hljs.registerLanguage("rust", rust);
|
|
2331
|
+
hljs.registerLanguage("go", go);
|
|
2332
|
+
hljs.registerLanguage("java", java);
|
|
2333
|
+
hljs.registerLanguage("cpp", cpp);
|
|
2334
|
+
hljs.registerLanguage("c", cpp);
|
|
2335
|
+
hljs.registerLanguage("csharp", csharp);
|
|
2336
|
+
hljs.registerLanguage("cs", csharp);
|
|
2337
|
+
hljs.registerLanguage("php", php);
|
|
2338
|
+
hljs.registerLanguage("ruby", ruby);
|
|
2339
|
+
hljs.registerLanguage("rb", ruby);
|
|
2340
|
+
hljs.registerLanguage("swift", swift);
|
|
2341
|
+
hljs.registerLanguage("kotlin", kotlin);
|
|
2342
|
+
hljs.registerLanguage("kt", kotlin);
|
|
2343
|
+
var containerStyle = {
|
|
2344
|
+
fontFamily: tokens.typography.fontFamily,
|
|
2345
|
+
fontSize: tokens.typography.fontSizeMd,
|
|
2346
|
+
lineHeight: 1.5,
|
|
2347
|
+
color: "inherit",
|
|
2348
|
+
wordBreak: "break-word"
|
|
2349
|
+
};
|
|
2350
|
+
var scopedStyles = `
|
|
2351
|
+
.hljs-md p {
|
|
2352
|
+
margin: 0 0 0.5em 0;
|
|
2353
|
+
}
|
|
2354
|
+
.hljs-md p:last-child {
|
|
2355
|
+
margin-bottom: 0;
|
|
2356
|
+
}
|
|
2357
|
+
.hljs-md ul,
|
|
2358
|
+
.hljs-md ol {
|
|
2359
|
+
margin: 0.25em 0 0.5em 0;
|
|
2360
|
+
padding-left: 1.5em;
|
|
2361
|
+
}
|
|
2362
|
+
.hljs-md li {
|
|
2363
|
+
margin: 0.1em 0;
|
|
2364
|
+
line-height: 1.4;
|
|
2365
|
+
}
|
|
2366
|
+
.hljs-md li > p {
|
|
2367
|
+
margin: 0;
|
|
2368
|
+
display: inline;
|
|
2369
|
+
}
|
|
2370
|
+
.hljs-md li > ul,
|
|
2371
|
+
.hljs-md li > ol {
|
|
2372
|
+
margin: 0.1em 0;
|
|
2373
|
+
}
|
|
2374
|
+
.hljs-md .code-block-wrapper {
|
|
2375
|
+
position: relative;
|
|
2376
|
+
margin: 0.5em 0;
|
|
2377
|
+
}
|
|
2378
|
+
.hljs-md .code-block-wrapper pre {
|
|
2379
|
+
position: relative;
|
|
2380
|
+
margin: 0;
|
|
2381
|
+
padding: 0.75em 1em;
|
|
2382
|
+
border-radius: 6px;
|
|
2383
|
+
background: #1e1e1e;
|
|
2384
|
+
overflow-x: auto;
|
|
2385
|
+
}
|
|
2386
|
+
.hljs-md .code-block-toolbar {
|
|
2387
|
+
position: absolute;
|
|
2388
|
+
top: 6px;
|
|
2389
|
+
right: 6px;
|
|
2390
|
+
display: flex;
|
|
2391
|
+
gap: 4px;
|
|
2392
|
+
z-index: 10;
|
|
2393
|
+
}
|
|
2394
|
+
.hljs-md .code-block-btn {
|
|
2395
|
+
display: flex;
|
|
2396
|
+
align-items: center;
|
|
2397
|
+
justify-content: center;
|
|
2398
|
+
width: 26px;
|
|
2399
|
+
height: 26px;
|
|
2400
|
+
padding: 0;
|
|
2401
|
+
background: #2d2d2d;
|
|
2402
|
+
border: 1px solid #404040;
|
|
2403
|
+
border-radius: 4px;
|
|
2404
|
+
color: #888;
|
|
2405
|
+
cursor: pointer;
|
|
2406
|
+
transition: all 0.15s ease;
|
|
2407
|
+
opacity: 0.7;
|
|
2408
|
+
}
|
|
2409
|
+
.hljs-md .code-block-wrapper:hover .code-block-btn {
|
|
2410
|
+
opacity: 1;
|
|
2411
|
+
}
|
|
2412
|
+
.hljs-md .code-block-btn:hover {
|
|
2413
|
+
background: #3d3d3d;
|
|
2414
|
+
border-color: #555;
|
|
2415
|
+
color: #ccc;
|
|
2416
|
+
}
|
|
2417
|
+
.hljs-md .code-block-btn.copied {
|
|
2418
|
+
color: ${tokens.colors.accentSuccess};
|
|
2419
|
+
}
|
|
2420
|
+
.hljs-md code {
|
|
2421
|
+
font-family: ${tokens.typography.fontFamilyMono};
|
|
2422
|
+
font-size: 0.9em;
|
|
2423
|
+
}
|
|
2424
|
+
.hljs-md :not(pre) > code {
|
|
2425
|
+
padding: 0.15em 0.4em;
|
|
2426
|
+
border-radius: 4px;
|
|
2427
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2428
|
+
}
|
|
2429
|
+
.hljs-md pre code {
|
|
2430
|
+
padding: 0;
|
|
2431
|
+
background: transparent;
|
|
2432
|
+
font-size: 0.875em;
|
|
2433
|
+
line-height: 1.5;
|
|
2434
|
+
}
|
|
2435
|
+
.hljs-md h1, .hljs-md h2, .hljs-md h3, .hljs-md h4 {
|
|
2436
|
+
margin: 0.5em 0 0.25em 0;
|
|
2437
|
+
font-weight: 600;
|
|
2438
|
+
line-height: 1.3;
|
|
2439
|
+
}
|
|
2440
|
+
.hljs-md h1:first-child, .hljs-md h2:first-child, .hljs-md h3:first-child {
|
|
2441
|
+
margin-top: 0;
|
|
2442
|
+
}
|
|
2443
|
+
.hljs-md h1 { font-size: 1.5em; }
|
|
2444
|
+
.hljs-md h2 { font-size: 1.25em; }
|
|
2445
|
+
.hljs-md h3 { font-size: 1.1em; }
|
|
2446
|
+
.hljs-md h4 { font-size: 1em; }
|
|
2447
|
+
.hljs-md blockquote {
|
|
2448
|
+
margin: 0.5em 0;
|
|
2449
|
+
padding: 0.5em 0 0.5em 1em;
|
|
2450
|
+
border-left: 3px solid ${tokens.colors.borderSecondary};
|
|
2451
|
+
color: ${tokens.colors.textSecondary};
|
|
2452
|
+
}
|
|
2453
|
+
.hljs-md a {
|
|
2454
|
+
color: ${tokens.colors.accentPrimary};
|
|
2455
|
+
text-decoration: underline;
|
|
2456
|
+
}
|
|
2457
|
+
.hljs-md hr {
|
|
2458
|
+
margin: 1em 0;
|
|
2459
|
+
border: none;
|
|
2460
|
+
border-top: 1px solid ${tokens.colors.borderSecondary};
|
|
2461
|
+
}
|
|
2462
|
+
.hljs-md table {
|
|
2463
|
+
width: 100%;
|
|
2464
|
+
margin: 0.5em 0;
|
|
2465
|
+
border-collapse: collapse;
|
|
2466
|
+
}
|
|
2467
|
+
.hljs-md th, .hljs-md td {
|
|
2468
|
+
padding: 0.5em;
|
|
2469
|
+
border: 1px solid ${tokens.colors.borderPrimary};
|
|
2470
|
+
text-align: left;
|
|
2471
|
+
}
|
|
2472
|
+
.hljs-md th {
|
|
2473
|
+
font-weight: 600;
|
|
2474
|
+
background: rgba(255, 255, 255, 0.05);
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
/* highlight.js dark theme (VS Code Dark+ inspired) */
|
|
2478
|
+
.hljs {
|
|
2479
|
+
color: #d4d4d4;
|
|
2480
|
+
background: #1e1e1e;
|
|
2481
|
+
}
|
|
2482
|
+
.hljs-keyword,
|
|
2483
|
+
.hljs-selector-tag,
|
|
2484
|
+
.hljs-title,
|
|
2485
|
+
.hljs-section,
|
|
2486
|
+
.hljs-doctag,
|
|
2487
|
+
.hljs-name,
|
|
2488
|
+
.hljs-strong {
|
|
2489
|
+
color: #569cd6;
|
|
2490
|
+
font-weight: normal;
|
|
2491
|
+
}
|
|
2492
|
+
.hljs-built_in,
|
|
2493
|
+
.hljs-literal,
|
|
2494
|
+
.hljs-type,
|
|
2495
|
+
.hljs-params,
|
|
2496
|
+
.hljs-meta,
|
|
2497
|
+
.hljs-link {
|
|
2498
|
+
color: #4ec9b0;
|
|
2499
|
+
}
|
|
2500
|
+
.hljs-string,
|
|
2501
|
+
.hljs-symbol,
|
|
2502
|
+
.hljs-bullet,
|
|
2503
|
+
.hljs-addition {
|
|
2504
|
+
color: #ce9178;
|
|
2505
|
+
}
|
|
2506
|
+
.hljs-number {
|
|
2507
|
+
color: #b5cea8;
|
|
2508
|
+
}
|
|
2509
|
+
.hljs-comment,
|
|
2510
|
+
.hljs-quote,
|
|
2511
|
+
.hljs-deletion {
|
|
2512
|
+
color: #6a9955;
|
|
2513
|
+
}
|
|
2514
|
+
.hljs-variable,
|
|
2515
|
+
.hljs-template-variable,
|
|
2516
|
+
.hljs-attr {
|
|
2517
|
+
color: #9cdcfe;
|
|
2518
|
+
}
|
|
2519
|
+
.hljs-regexp,
|
|
2520
|
+
.hljs-selector-id,
|
|
2521
|
+
.hljs-selector-class {
|
|
2522
|
+
color: #d7ba7d;
|
|
2523
|
+
}
|
|
2524
|
+
.hljs-emphasis {
|
|
2525
|
+
font-style: italic;
|
|
2526
|
+
}
|
|
2527
|
+
`;
|
|
2528
|
+
marked.use({
|
|
2529
|
+
gfm: true,
|
|
2530
|
+
breaks: false
|
|
2531
|
+
});
|
|
2532
|
+
function highlightCode(code2, lang) {
|
|
2533
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
2534
|
+
try {
|
|
2535
|
+
return hljs.highlight(code2, { language: lang }).value;
|
|
2536
|
+
} catch {
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
try {
|
|
2540
|
+
return hljs.highlightAuto(code2).value;
|
|
2541
|
+
} catch {
|
|
2542
|
+
return code2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
var codeBlockCounter = 0;
|
|
2546
|
+
function generateCodeBlockId() {
|
|
2547
|
+
return `code-block-${++codeBlockCounter}`;
|
|
2548
|
+
}
|
|
2549
|
+
function renderMarkdown(content) {
|
|
2550
|
+
const html = marked.parse(content, { async: false });
|
|
2551
|
+
const codeBlockRegex = /<pre><code class="language-(\w+)">([\s\S]*?)<\/code><\/pre>/g;
|
|
2552
|
+
let result = html.replace(codeBlockRegex, (_, lang, code2) => {
|
|
2553
|
+
const decoded = code2.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, '"');
|
|
2554
|
+
const highlighted = highlightCode(decoded, lang);
|
|
2555
|
+
const id = generateCodeBlockId();
|
|
2556
|
+
const encodedCode = encodeURIComponent(decoded);
|
|
2557
|
+
return `<div class="code-block-wrapper" data-code-id="${id}" data-code="${encodedCode}"><pre><code class="hljs language-${lang}">${highlighted}</code></pre><div class="code-block-toolbar"><button class="code-block-btn" data-action="copy" data-code-id="${id}" title="Copy code"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button><button class="code-block-btn" data-action="emblem" data-code-id="${id}" title="Open in Emblem AI"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg></button></div></div>`;
|
|
2558
|
+
});
|
|
2559
|
+
const plainCodeRegex = /<pre><code>([\s\S]*?)<\/code><\/pre>/g;
|
|
2560
|
+
result = result.replace(plainCodeRegex, (_, code2) => {
|
|
2561
|
+
const decoded = code2.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, '"');
|
|
2562
|
+
const highlighted = highlightCode(decoded, "");
|
|
2563
|
+
const id = generateCodeBlockId();
|
|
2564
|
+
const encodedCode = encodeURIComponent(decoded);
|
|
2565
|
+
return `<div class="code-block-wrapper" data-code-id="${id}" data-code="${encodedCode}"><pre><code class="hljs">${highlighted}</code></pre><div class="code-block-toolbar"><button class="code-block-btn" data-action="copy" data-code-id="${id}" title="Copy code"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button><button class="code-block-btn" data-action="emblem" data-code-id="${id}" title="Open in Emblem AI"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg></button></div></div>`;
|
|
2566
|
+
});
|
|
2567
|
+
return result;
|
|
2568
|
+
}
|
|
2569
|
+
function MarkdownContent({ content, className }) {
|
|
2570
|
+
const [rendered, setRendered] = useState("");
|
|
2571
|
+
const containerRef = useRef(null);
|
|
2572
|
+
useEffect(() => {
|
|
2573
|
+
try {
|
|
2574
|
+
const html = renderMarkdown(content);
|
|
2575
|
+
setRendered(html);
|
|
2576
|
+
} catch (err) {
|
|
2577
|
+
console.error("[MarkdownContent] Render error:", err);
|
|
2578
|
+
setRendered(
|
|
2579
|
+
content.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\n/g, "<br>")
|
|
2580
|
+
);
|
|
2581
|
+
}
|
|
2582
|
+
}, [content]);
|
|
2583
|
+
useEffect(() => {
|
|
2584
|
+
const container = containerRef.current;
|
|
2585
|
+
if (!container) return;
|
|
2586
|
+
const handleClick = async (e) => {
|
|
2587
|
+
const target = e.target;
|
|
2588
|
+
const button = target.closest("[data-action]");
|
|
2589
|
+
if (!button) return;
|
|
2590
|
+
const action = button.dataset.action;
|
|
2591
|
+
const codeId = button.dataset.codeId;
|
|
2592
|
+
if (!codeId) return;
|
|
2593
|
+
const wrapper = container.querySelector(`[data-code-id="${codeId}"]`);
|
|
2594
|
+
if (!wrapper) return;
|
|
2595
|
+
const encodedCode = wrapper.dataset.code;
|
|
2596
|
+
if (!encodedCode) return;
|
|
2597
|
+
const code2 = decodeURIComponent(encodedCode);
|
|
2598
|
+
if (action === "copy") {
|
|
2599
|
+
e.preventDefault();
|
|
2600
|
+
try {
|
|
2601
|
+
await navigator.clipboard.writeText(code2);
|
|
2602
|
+
button.classList.add("copied");
|
|
2603
|
+
button.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2604
|
+
<polyline points="20 6 9 17 4 12"></polyline>
|
|
2605
|
+
</svg>`;
|
|
2606
|
+
setTimeout(() => {
|
|
2607
|
+
button.classList.remove("copied");
|
|
2608
|
+
button.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2609
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
2610
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
2611
|
+
</svg>`;
|
|
2612
|
+
}, 2e3);
|
|
2613
|
+
} catch (err) {
|
|
2614
|
+
console.error("Failed to copy:", err);
|
|
2615
|
+
}
|
|
2616
|
+
} else if (action === "emblem") {
|
|
2617
|
+
e.preventDefault();
|
|
2618
|
+
const url = `https://build.emblemvault.ai/?q=${encodeURIComponent(code2)}`;
|
|
2619
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
container.addEventListener("click", handleClick);
|
|
2623
|
+
return () => container.removeEventListener("click", handleClick);
|
|
2624
|
+
}, [rendered]);
|
|
2625
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2626
|
+
/* @__PURE__ */ jsx("style", { children: scopedStyles }),
|
|
2627
|
+
/* @__PURE__ */ jsx(
|
|
2628
|
+
"div",
|
|
2629
|
+
{
|
|
2630
|
+
ref: containerRef,
|
|
2631
|
+
style: containerStyle,
|
|
2632
|
+
className: `hljs-md ${className || ""}`,
|
|
2633
|
+
dangerouslySetInnerHTML: { __html: rendered }
|
|
2634
|
+
}
|
|
2635
|
+
)
|
|
2636
|
+
] });
|
|
2637
|
+
}
|
|
2638
|
+
var styles2 = {
|
|
2639
|
+
// Container
|
|
2640
|
+
container: {
|
|
2641
|
+
display: "flex",
|
|
2642
|
+
flexDirection: "column",
|
|
2643
|
+
height: "100%",
|
|
2644
|
+
background: tokens.colors.bgSecondary,
|
|
2645
|
+
borderRadius: tokens.radius.xl,
|
|
2646
|
+
border: `1px solid ${tokens.colors.borderPrimary}`,
|
|
2647
|
+
fontFamily: tokens.typography.fontFamily,
|
|
2648
|
+
color: tokens.colors.textPrimary
|
|
2649
|
+
},
|
|
2650
|
+
// Not ready / auth required states
|
|
2651
|
+
placeholder: {
|
|
2652
|
+
padding: tokens.spacing.xxl,
|
|
2653
|
+
background: tokens.colors.bgSecondary,
|
|
2654
|
+
borderRadius: tokens.radius.xl,
|
|
2655
|
+
border: `1px solid ${tokens.colors.borderPrimary}`
|
|
2656
|
+
},
|
|
2657
|
+
placeholderContent: {
|
|
2658
|
+
color: tokens.colors.textSecondary
|
|
2659
|
+
},
|
|
2660
|
+
placeholderTitle: {
|
|
2661
|
+
fontSize: tokens.typography.fontSizeLg,
|
|
2662
|
+
fontWeight: tokens.typography.fontWeightMedium,
|
|
2663
|
+
marginBottom: tokens.spacing.xs
|
|
2664
|
+
},
|
|
2665
|
+
placeholderText: {
|
|
2666
|
+
fontSize: tokens.typography.fontSizeSm,
|
|
2667
|
+
color: tokens.colors.textTertiary
|
|
2668
|
+
},
|
|
2669
|
+
loadingSpinner: {
|
|
2670
|
+
border: `2px solid ${tokens.colors.textTertiary}`,
|
|
2671
|
+
borderRadius: tokens.radius.full,
|
|
2672
|
+
marginBottom: tokens.spacing.sm
|
|
2673
|
+
},
|
|
2674
|
+
// Header - darker shade
|
|
2675
|
+
header: {
|
|
2676
|
+
display: "flex",
|
|
2677
|
+
alignItems: "center",
|
|
2678
|
+
justifyContent: "space-between",
|
|
2679
|
+
padding: `${tokens.spacing.md} ${tokens.spacing.lg}`,
|
|
2680
|
+
background: tokens.colors.bgPrimary,
|
|
2681
|
+
borderBottom: `1px solid ${tokens.colors.borderPrimary}`,
|
|
2682
|
+
borderRadius: `${tokens.radius.xl} ${tokens.radius.xl} 0 0`
|
|
2683
|
+
},
|
|
2684
|
+
headerTitle: {
|
|
2685
|
+
fontWeight: tokens.typography.fontWeightSemibold,
|
|
2686
|
+
color: tokens.colors.textPrimary,
|
|
2687
|
+
fontSize: tokens.typography.fontSizeMd
|
|
2688
|
+
},
|
|
2689
|
+
headerActions: {
|
|
2690
|
+
display: "flex",
|
|
2691
|
+
alignItems: "center",
|
|
2692
|
+
gap: tokens.spacing.sm
|
|
2693
|
+
},
|
|
2694
|
+
// Model selector
|
|
2695
|
+
select: {
|
|
2696
|
+
fontSize: tokens.typography.fontSizeSm,
|
|
2697
|
+
padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,
|
|
2698
|
+
border: `1px solid ${tokens.colors.borderSecondary}`,
|
|
2699
|
+
borderRadius: tokens.radius.md,
|
|
2700
|
+
background: tokens.colors.bgTertiary,
|
|
2701
|
+
color: tokens.colors.textPrimary},
|
|
2702
|
+
// Settings button
|
|
2703
|
+
settingsBtn: {
|
|
2704
|
+
...presets.buttonIcon,
|
|
2705
|
+
borderRadius: tokens.radius.md
|
|
2706
|
+
},
|
|
2707
|
+
settingsBtnActive: {
|
|
2708
|
+
background: tokens.colors.accentPrimaryBg,
|
|
2709
|
+
color: tokens.colors.accentPrimary
|
|
2710
|
+
},
|
|
2711
|
+
settingsBtnInactive: {
|
|
2712
|
+
color: tokens.colors.textSecondary
|
|
2713
|
+
},
|
|
2714
|
+
// Settings Modal
|
|
2715
|
+
modalOverlay: {
|
|
2716
|
+
position: "fixed",
|
|
2717
|
+
top: 0,
|
|
2718
|
+
left: 0,
|
|
2719
|
+
right: 0,
|
|
2720
|
+
bottom: 0,
|
|
2721
|
+
background: tokens.colors.bgOverlay,
|
|
2722
|
+
display: "flex",
|
|
2723
|
+
alignItems: "center",
|
|
2724
|
+
justifyContent: "center",
|
|
2725
|
+
zIndex: tokens.zIndex.modal
|
|
2726
|
+
},
|
|
2727
|
+
modal: {
|
|
2728
|
+
background: tokens.colors.bgSecondary,
|
|
2729
|
+
borderRadius: tokens.radius.xl,
|
|
2730
|
+
border: `1px solid ${tokens.colors.borderPrimary}`,
|
|
2731
|
+
width: "100%",
|
|
2732
|
+
maxWidth: "440px",
|
|
2733
|
+
maxHeight: "90vh",
|
|
2734
|
+
overflow: "auto",
|
|
2735
|
+
boxShadow: tokens.shadows.xl
|
|
2736
|
+
},
|
|
2737
|
+
modalHeader: {
|
|
2738
|
+
display: "flex",
|
|
2739
|
+
alignItems: "center",
|
|
2740
|
+
justifyContent: "space-between",
|
|
2741
|
+
padding: `${tokens.spacing.lg} ${tokens.spacing.xl}`,
|
|
2742
|
+
borderBottom: `1px solid ${tokens.colors.borderPrimary}`
|
|
2743
|
+
},
|
|
2744
|
+
modalTitle: {
|
|
2745
|
+
fontSize: tokens.typography.fontSizeLg,
|
|
2746
|
+
fontWeight: tokens.typography.fontWeightSemibold,
|
|
2747
|
+
color: tokens.colors.textPrimary
|
|
2748
|
+
},
|
|
2749
|
+
modalClose: {
|
|
2750
|
+
background: "transparent",
|
|
2751
|
+
border: "none",
|
|
2752
|
+
color: tokens.colors.textTertiary,
|
|
2753
|
+
fontSize: "20px",
|
|
2754
|
+
cursor: "pointer",
|
|
2755
|
+
padding: tokens.spacing.xs,
|
|
2756
|
+
lineHeight: 1,
|
|
2757
|
+
transition: `color ${tokens.transitions.fast}`
|
|
2758
|
+
},
|
|
2759
|
+
modalBody: {
|
|
2760
|
+
padding: tokens.spacing.xl
|
|
2761
|
+
},
|
|
2762
|
+
// Settings sections
|
|
2763
|
+
settingGroup: {
|
|
2764
|
+
marginBottom: tokens.spacing.xl
|
|
2765
|
+
},
|
|
2766
|
+
settingLabel: {
|
|
2767
|
+
display: "block",
|
|
2768
|
+
fontSize: tokens.typography.fontSizeMd,
|
|
2769
|
+
fontWeight: tokens.typography.fontWeightMedium,
|
|
2770
|
+
color: tokens.colors.textPrimary,
|
|
2771
|
+
marginBottom: tokens.spacing.xs
|
|
2772
|
+
},
|
|
2773
|
+
settingDescription: {
|
|
2774
|
+
fontSize: tokens.typography.fontSizeSm,
|
|
2775
|
+
color: tokens.colors.textTertiary,
|
|
2776
|
+
marginBottom: tokens.spacing.md
|
|
2777
|
+
},
|
|
2778
|
+
settingSelect: {
|
|
2779
|
+
width: "100%",
|
|
2780
|
+
padding: `${tokens.spacing.md} ${tokens.spacing.lg}`,
|
|
2781
|
+
fontSize: tokens.typography.fontSizeMd,
|
|
2782
|
+
background: tokens.colors.bgTertiary,
|
|
2783
|
+
border: `1px solid ${tokens.colors.borderSecondary}`,
|
|
2784
|
+
borderRadius: tokens.radius.lg,
|
|
2785
|
+
color: tokens.colors.textPrimary,
|
|
2786
|
+
outline: "none",
|
|
2787
|
+
cursor: "pointer",
|
|
2788
|
+
appearance: "none",
|
|
2789
|
+
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238892a4' d='M6 8L1 3h10z'/%3E%3C/svg%3E")`,
|
|
2790
|
+
backgroundRepeat: "no-repeat",
|
|
2791
|
+
backgroundPosition: "right 12px center",
|
|
2792
|
+
paddingRight: "36px"
|
|
2793
|
+
},
|
|
2794
|
+
modelInfo: {
|
|
2795
|
+
fontSize: tokens.typography.fontSizeXs,
|
|
2796
|
+
color: tokens.colors.textTertiary,
|
|
2797
|
+
marginTop: tokens.spacing.sm
|
|
2798
|
+
},
|
|
2799
|
+
// Toggle switch row
|
|
2800
|
+
toggleRow: {
|
|
2801
|
+
display: "flex",
|
|
2802
|
+
alignItems: "center",
|
|
2803
|
+
justifyContent: "space-between",
|
|
2804
|
+
padding: `${tokens.spacing.md} ${tokens.spacing.lg}`,
|
|
2805
|
+
background: tokens.colors.bgTertiary,
|
|
2806
|
+
borderRadius: tokens.radius.lg,
|
|
2807
|
+
marginBottom: tokens.spacing.sm
|
|
2808
|
+
},
|
|
2809
|
+
toggleLabel: {
|
|
2810
|
+
fontSize: tokens.typography.fontSizeMd,
|
|
2811
|
+
color: tokens.colors.textPrimary
|
|
2812
|
+
},
|
|
2813
|
+
toggleSwitch: {
|
|
2814
|
+
position: "relative",
|
|
2815
|
+
width: "44px",
|
|
2816
|
+
height: "24px",
|
|
2817
|
+
background: tokens.colors.borderSecondary,
|
|
2818
|
+
borderRadius: "12px",
|
|
2819
|
+
cursor: "pointer",
|
|
2820
|
+
transition: `background ${tokens.transitions.fast}`
|
|
2821
|
+
},
|
|
2822
|
+
toggleSwitchActive: {
|
|
2823
|
+
background: tokens.colors.accentPrimary
|
|
2824
|
+
},
|
|
2825
|
+
toggleKnob: {
|
|
2826
|
+
position: "absolute",
|
|
2827
|
+
top: "2px",
|
|
2828
|
+
left: "2px",
|
|
2829
|
+
width: "20px",
|
|
2830
|
+
height: "20px",
|
|
2831
|
+
background: tokens.colors.textPrimary,
|
|
2832
|
+
borderRadius: tokens.radius.full,
|
|
2833
|
+
transition: `transform ${tokens.transitions.fast}`
|
|
2834
|
+
},
|
|
2835
|
+
toggleKnobActive: {
|
|
2836
|
+
transform: "translateX(20px)"
|
|
2837
|
+
},
|
|
2838
|
+
// Settings textarea
|
|
2839
|
+
settingTextarea: {
|
|
2840
|
+
width: "100%",
|
|
2841
|
+
minHeight: "100px",
|
|
2842
|
+
padding: tokens.spacing.lg,
|
|
2843
|
+
fontSize: tokens.typography.fontSizeMd,
|
|
2844
|
+
background: tokens.colors.bgTertiary,
|
|
2845
|
+
border: `1px solid ${tokens.colors.borderSecondary}`,
|
|
2846
|
+
borderRadius: tokens.radius.lg,
|
|
2847
|
+
color: tokens.colors.textPrimary,
|
|
2848
|
+
outline: "none",
|
|
2849
|
+
resize: "vertical",
|
|
2850
|
+
fontFamily: tokens.typography.fontFamily
|
|
2851
|
+
},
|
|
2852
|
+
// Messages area
|
|
2853
|
+
messagesArea: {
|
|
2854
|
+
flex: 1,
|
|
2855
|
+
overflowY: "auto",
|
|
2856
|
+
padding: tokens.spacing.lg,
|
|
2857
|
+
background: tokens.colors.bgSecondary
|
|
2858
|
+
},
|
|
2859
|
+
messagesEmpty: {
|
|
2860
|
+
textAlign: "center",
|
|
2861
|
+
color: tokens.colors.textTertiary,
|
|
2862
|
+
padding: tokens.spacing.xxl
|
|
2863
|
+
},
|
|
2864
|
+
messagesContainer: {
|
|
2865
|
+
display: "flex",
|
|
2866
|
+
flexDirection: "column",
|
|
2867
|
+
gap: tokens.spacing.lg
|
|
2868
|
+
},
|
|
2869
|
+
// Tool calls indicator
|
|
2870
|
+
toolCallsIndicator: {
|
|
2871
|
+
display: "flex",
|
|
2872
|
+
flexWrap: "wrap",
|
|
2873
|
+
gap: tokens.spacing.sm,
|
|
2874
|
+
padding: `0 ${tokens.spacing.lg}`
|
|
2875
|
+
},
|
|
2876
|
+
toolCallBadge: {
|
|
2877
|
+
display: "inline-flex",
|
|
2878
|
+
alignItems: "center",
|
|
2879
|
+
gap: tokens.spacing.xs,
|
|
2880
|
+
padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,
|
|
2881
|
+
fontSize: tokens.typography.fontSizeXs,
|
|
2882
|
+
background: tokens.colors.accentWarningBg,
|
|
2883
|
+
color: tokens.colors.accentWarning,
|
|
2884
|
+
borderRadius: tokens.radius.pill
|
|
2885
|
+
},
|
|
2886
|
+
toolCallDot: {
|
|
2887
|
+
width: "8px",
|
|
2888
|
+
height: "8px",
|
|
2889
|
+
background: tokens.colors.accentWarning,
|
|
2890
|
+
borderRadius: tokens.radius.full,
|
|
2891
|
+
animation: "hustle-pulse 1s ease-in-out infinite"
|
|
2892
|
+
},
|
|
2893
|
+
// Attachments preview
|
|
2894
|
+
attachmentsPreview: {
|
|
2895
|
+
padding: `${tokens.spacing.sm} ${tokens.spacing.lg}`,
|
|
2896
|
+
borderTop: `1px solid ${tokens.colors.borderPrimary}`,
|
|
2897
|
+
display: "flex",
|
|
2898
|
+
flexWrap: "wrap",
|
|
2899
|
+
gap: tokens.spacing.sm
|
|
2900
|
+
},
|
|
2901
|
+
attachmentItem: {
|
|
2902
|
+
display: "inline-flex",
|
|
2903
|
+
alignItems: "center",
|
|
2904
|
+
gap: tokens.spacing.xs,
|
|
2905
|
+
padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,
|
|
2906
|
+
background: tokens.colors.bgTertiary,
|
|
2907
|
+
borderRadius: tokens.radius.md,
|
|
2908
|
+
fontSize: tokens.typography.fontSizeSm
|
|
2909
|
+
},
|
|
2910
|
+
attachmentName: {
|
|
2911
|
+
maxWidth: "100px",
|
|
2912
|
+
overflow: "hidden",
|
|
2913
|
+
textOverflow: "ellipsis",
|
|
2914
|
+
whiteSpace: "nowrap"
|
|
2915
|
+
},
|
|
2916
|
+
attachmentRemove: {
|
|
2917
|
+
background: "none",
|
|
2918
|
+
border: "none",
|
|
2919
|
+
color: tokens.colors.textTertiary,
|
|
2920
|
+
cursor: "pointer",
|
|
2921
|
+
fontSize: "14px",
|
|
2922
|
+
padding: 0,
|
|
2923
|
+
lineHeight: 1
|
|
2924
|
+
},
|
|
2925
|
+
// Input area - slightly darker than messages
|
|
2926
|
+
inputArea: {
|
|
2927
|
+
padding: tokens.spacing.lg,
|
|
2928
|
+
background: tokens.colors.bgPrimary,
|
|
2929
|
+
borderTop: `1px solid ${tokens.colors.borderPrimary}`,
|
|
2930
|
+
borderRadius: `0 0 ${tokens.radius.xl} ${tokens.radius.xl}`
|
|
2931
|
+
},
|
|
2932
|
+
inputRow: {
|
|
2933
|
+
display: "flex",
|
|
2934
|
+
alignItems: "center",
|
|
2935
|
+
gap: tokens.spacing.sm
|
|
2936
|
+
},
|
|
2937
|
+
inputContainer: {
|
|
2938
|
+
flex: 1,
|
|
2939
|
+
display: "flex",
|
|
2940
|
+
alignItems: "center",
|
|
2941
|
+
background: tokens.colors.bgTertiary,
|
|
2942
|
+
border: `1px solid ${tokens.colors.borderSecondary}`,
|
|
2943
|
+
borderRadius: tokens.radius.lg,
|
|
2944
|
+
overflow: "hidden"
|
|
2945
|
+
},
|
|
2946
|
+
attachBtn: {
|
|
2947
|
+
width: "40px",
|
|
2948
|
+
height: "40px",
|
|
2949
|
+
padding: 0,
|
|
2950
|
+
background: "transparent",
|
|
2951
|
+
border: "none",
|
|
2952
|
+
borderRadius: 0,
|
|
2953
|
+
color: tokens.colors.textTertiary,
|
|
2954
|
+
flexShrink: 0
|
|
2955
|
+
},
|
|
2956
|
+
inputWrapper: {
|
|
2957
|
+
flex: 1
|
|
2958
|
+
},
|
|
2959
|
+
input: {
|
|
2960
|
+
width: "100%",
|
|
2961
|
+
padding: `${tokens.spacing.md} ${tokens.spacing.sm}`,
|
|
2962
|
+
background: "transparent",
|
|
2963
|
+
border: "none",
|
|
2964
|
+
color: tokens.colors.textPrimary,
|
|
2965
|
+
fontSize: tokens.typography.fontSizeMd,
|
|
2966
|
+
outline: "none",
|
|
2967
|
+
resize: "none"
|
|
2968
|
+
},
|
|
2969
|
+
inputDisabled: {
|
|
2970
|
+
background: tokens.colors.bgTertiary,
|
|
2971
|
+
cursor: "not-allowed"
|
|
2972
|
+
},
|
|
2973
|
+
sendBtn: {
|
|
2974
|
+
// Inherits global button styles from CSS
|
|
2975
|
+
height: "40px",
|
|
2976
|
+
padding: `0 ${tokens.spacing.lg}`,
|
|
2977
|
+
fontWeight: tokens.typography.fontWeightMedium
|
|
2978
|
+
},
|
|
2979
|
+
sendBtnDisabled: {
|
|
2980
|
+
opacity: 0.5,
|
|
2981
|
+
cursor: "not-allowed"
|
|
2982
|
+
},
|
|
2983
|
+
sendSpinner: {
|
|
2984
|
+
display: "inline-block",
|
|
2985
|
+
width: "16px",
|
|
2986
|
+
height: "16px",
|
|
2987
|
+
border: "2px solid currentColor",
|
|
2988
|
+
borderTopColor: "transparent",
|
|
2989
|
+
borderRadius: tokens.radius.full,
|
|
2990
|
+
animation: "hustle-spin 0.8s linear infinite"
|
|
2991
|
+
},
|
|
2992
|
+
// Error display
|
|
2993
|
+
errorBox: {
|
|
2994
|
+
marginTop: tokens.spacing.sm,
|
|
2995
|
+
padding: `${tokens.spacing.sm} ${tokens.spacing.md}`,
|
|
2996
|
+
background: tokens.colors.accentErrorBg,
|
|
2997
|
+
color: tokens.colors.accentError,
|
|
2998
|
+
fontSize: tokens.typography.fontSizeSm,
|
|
2999
|
+
borderRadius: tokens.radius.md
|
|
3000
|
+
},
|
|
3001
|
+
// Message bubbles
|
|
3002
|
+
messageBubbleContainer: {
|
|
3003
|
+
display: "flex"
|
|
3004
|
+
},
|
|
3005
|
+
messageBubbleUser: {
|
|
3006
|
+
justifyContent: "flex-end"
|
|
3007
|
+
},
|
|
3008
|
+
messageBubbleAssistant: {
|
|
3009
|
+
justifyContent: "flex-start"
|
|
3010
|
+
},
|
|
3011
|
+
messageBubble: {
|
|
3012
|
+
maxWidth: "80%",
|
|
3013
|
+
padding: `${tokens.spacing.sm} ${tokens.spacing.lg}`,
|
|
3014
|
+
borderRadius: tokens.radius.lg
|
|
3015
|
+
},
|
|
3016
|
+
messageBubbleUserStyle: {
|
|
3017
|
+
background: tokens.colors.msgUser,
|
|
3018
|
+
color: tokens.colors.textPrimary
|
|
3019
|
+
},
|
|
3020
|
+
messageBubbleAssistantStyle: {
|
|
3021
|
+
background: tokens.colors.msgAssistant,
|
|
3022
|
+
color: tokens.colors.textPrimary
|
|
3023
|
+
},
|
|
3024
|
+
messageBubbleSystemStyle: {
|
|
3025
|
+
background: tokens.colors.bgTertiary,
|
|
3026
|
+
color: tokens.colors.textSecondary,
|
|
3027
|
+
fontSize: tokens.typography.fontSizeSm,
|
|
3028
|
+
fontStyle: "italic"
|
|
3029
|
+
},
|
|
3030
|
+
messageContent: {
|
|
3031
|
+
whiteSpace: "pre-wrap",
|
|
3032
|
+
wordBreak: "break-word",
|
|
3033
|
+
lineHeight: tokens.typography.lineHeightRelaxed
|
|
3034
|
+
},
|
|
3035
|
+
streamingCursor: {
|
|
3036
|
+
display: "inline-block",
|
|
3037
|
+
width: "2px",
|
|
3038
|
+
height: "16px",
|
|
3039
|
+
marginLeft: tokens.spacing.xs,
|
|
3040
|
+
background: "currentColor",
|
|
3041
|
+
animation: "hustle-pulse 0.8s ease-in-out infinite"
|
|
3042
|
+
},
|
|
3043
|
+
// Tool calls debug
|
|
3044
|
+
toolCallsDebug: {
|
|
3045
|
+
marginTop: tokens.spacing.sm,
|
|
3046
|
+
paddingTop: tokens.spacing.sm,
|
|
3047
|
+
borderTop: `1px solid ${tokens.colors.borderSecondary}`,
|
|
3048
|
+
fontSize: tokens.typography.fontSizeXs
|
|
3049
|
+
},
|
|
3050
|
+
toolCallsDebugTitle: {
|
|
3051
|
+
fontWeight: tokens.typography.fontWeightMedium,
|
|
3052
|
+
color: tokens.colors.textSecondary,
|
|
3053
|
+
marginBottom: tokens.spacing.xs
|
|
3054
|
+
},
|
|
3055
|
+
toolCallDebugItem: {
|
|
3056
|
+
background: "rgba(255,255,255,0.05)",
|
|
3057
|
+
borderRadius: tokens.radius.sm,
|
|
3058
|
+
padding: tokens.spacing.xs,
|
|
3059
|
+
marginTop: tokens.spacing.xs
|
|
3060
|
+
},
|
|
3061
|
+
toolCallDebugName: {
|
|
3062
|
+
...presets.mono
|
|
3063
|
+
},
|
|
3064
|
+
toolCallDebugArgs: {
|
|
3065
|
+
...presets.mono,
|
|
3066
|
+
marginTop: tokens.spacing.xs,
|
|
3067
|
+
fontSize: "10px",
|
|
3068
|
+
overflow: "auto"
|
|
3069
|
+
},
|
|
3070
|
+
// Plugin management styles
|
|
3071
|
+
settingDivider: {
|
|
3072
|
+
height: "1px",
|
|
3073
|
+
background: tokens.colors.borderPrimary,
|
|
3074
|
+
margin: `${tokens.spacing.xl} 0`
|
|
3075
|
+
},
|
|
3076
|
+
pluginList: {
|
|
3077
|
+
display: "flex",
|
|
3078
|
+
flexDirection: "column",
|
|
3079
|
+
gap: tokens.spacing.sm
|
|
3080
|
+
},
|
|
3081
|
+
pluginRow: {
|
|
3082
|
+
display: "flex",
|
|
3083
|
+
alignItems: "center",
|
|
3084
|
+
justifyContent: "space-between",
|
|
3085
|
+
padding: `${tokens.spacing.md} ${tokens.spacing.lg}`,
|
|
3086
|
+
background: tokens.colors.bgTertiary,
|
|
3087
|
+
borderRadius: tokens.radius.lg
|
|
3088
|
+
},
|
|
3089
|
+
pluginInfo: {
|
|
3090
|
+
display: "flex",
|
|
3091
|
+
alignItems: "center",
|
|
3092
|
+
gap: tokens.spacing.md,
|
|
3093
|
+
flex: 1,
|
|
3094
|
+
minWidth: 0
|
|
3095
|
+
},
|
|
3096
|
+
pluginIcon: {
|
|
3097
|
+
fontSize: "20px",
|
|
3098
|
+
flexShrink: 0
|
|
3099
|
+
},
|
|
3100
|
+
pluginDetails: {
|
|
3101
|
+
flex: 1,
|
|
3102
|
+
minWidth: 0
|
|
3103
|
+
},
|
|
3104
|
+
pluginName: {
|
|
3105
|
+
display: "block",
|
|
3106
|
+
fontWeight: tokens.typography.fontWeightMedium,
|
|
3107
|
+
color: tokens.colors.textPrimary,
|
|
3108
|
+
whiteSpace: "nowrap",
|
|
3109
|
+
overflow: "hidden",
|
|
3110
|
+
textOverflow: "ellipsis"
|
|
3111
|
+
},
|
|
3112
|
+
pluginMeta: {
|
|
3113
|
+
display: "block",
|
|
3114
|
+
fontSize: tokens.typography.fontSizeXs,
|
|
3115
|
+
color: tokens.colors.textTertiary
|
|
3116
|
+
},
|
|
3117
|
+
pluginEmpty: {
|
|
3118
|
+
padding: tokens.spacing.lg,
|
|
3119
|
+
textAlign: "center",
|
|
3120
|
+
color: tokens.colors.textTertiary,
|
|
3121
|
+
fontSize: tokens.typography.fontSizeSm
|
|
3122
|
+
},
|
|
3123
|
+
availablePluginsHeader: {
|
|
3124
|
+
fontSize: tokens.typography.fontSizeXs,
|
|
3125
|
+
fontWeight: tokens.typography.fontWeightSemibold,
|
|
3126
|
+
color: tokens.colors.textSecondary,
|
|
3127
|
+
textTransform: "uppercase",
|
|
3128
|
+
letterSpacing: "0.5px",
|
|
3129
|
+
marginTop: tokens.spacing.lg,
|
|
3130
|
+
marginBottom: tokens.spacing.sm
|
|
3131
|
+
},
|
|
3132
|
+
installBtn: {
|
|
3133
|
+
padding: `${tokens.spacing.xs} ${tokens.spacing.md}`,
|
|
3134
|
+
fontSize: tokens.typography.fontSizeSm,
|
|
3135
|
+
background: "transparent",
|
|
3136
|
+
border: `1px solid ${tokens.colors.accentPrimary}`,
|
|
3137
|
+
borderRadius: tokens.radius.md,
|
|
3138
|
+
color: tokens.colors.accentPrimary,
|
|
3139
|
+
cursor: "pointer",
|
|
3140
|
+
transition: `all ${tokens.transitions.fast}`,
|
|
3141
|
+
whiteSpace: "nowrap"
|
|
3142
|
+
},
|
|
3143
|
+
uninstallBtn: {
|
|
3144
|
+
padding: `${tokens.spacing.xs} ${tokens.spacing.md}`,
|
|
3145
|
+
fontSize: tokens.typography.fontSizeSm,
|
|
3146
|
+
background: "transparent",
|
|
3147
|
+
border: `1px solid ${tokens.colors.accentError}`,
|
|
3148
|
+
borderRadius: tokens.radius.md,
|
|
3149
|
+
color: tokens.colors.accentError,
|
|
3150
|
+
cursor: "pointer",
|
|
3151
|
+
transition: `all ${tokens.transitions.fast}`,
|
|
3152
|
+
whiteSpace: "nowrap"
|
|
3153
|
+
}
|
|
3154
|
+
};
|
|
3155
|
+
function generateId() {
|
|
3156
|
+
return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
3157
|
+
}
|
|
3158
|
+
function HustleChat({
|
|
3159
|
+
className = "",
|
|
3160
|
+
placeholder = "Type a message...",
|
|
3161
|
+
showSettings = false,
|
|
3162
|
+
showDebug = false,
|
|
3163
|
+
initialSystemPrompt = "",
|
|
3164
|
+
onMessage,
|
|
3165
|
+
onToolCall,
|
|
3166
|
+
onResponse
|
|
3167
|
+
}) {
|
|
3168
|
+
const { isAuthenticated } = useEmblemAuth();
|
|
3169
|
+
const {
|
|
3170
|
+
instanceId,
|
|
3171
|
+
isReady,
|
|
3172
|
+
isLoading,
|
|
3173
|
+
error,
|
|
3174
|
+
models,
|
|
3175
|
+
chatStream,
|
|
3176
|
+
uploadFile,
|
|
3177
|
+
selectedModel,
|
|
3178
|
+
setSelectedModel,
|
|
3179
|
+
systemPrompt,
|
|
3180
|
+
setSystemPrompt,
|
|
3181
|
+
skipServerPrompt,
|
|
3182
|
+
setSkipServerPrompt
|
|
3183
|
+
} = useHustle();
|
|
3184
|
+
const {
|
|
3185
|
+
plugins,
|
|
3186
|
+
registerPlugin,
|
|
3187
|
+
unregisterPlugin,
|
|
3188
|
+
enablePlugin,
|
|
3189
|
+
disablePlugin
|
|
3190
|
+
} = usePlugins(instanceId);
|
|
3191
|
+
const [messages, setMessages] = useState([]);
|
|
3192
|
+
const [inputValue, setInputValue] = useState("");
|
|
3193
|
+
const [isStreaming, setIsStreaming] = useState(false);
|
|
3194
|
+
const [attachments, setAttachments] = useState([]);
|
|
3195
|
+
const [currentToolCalls, setCurrentToolCalls] = useState([]);
|
|
3196
|
+
const [showSettingsPanel, setShowSettingsPanel] = useState(false);
|
|
3197
|
+
const messagesEndRef = useRef(null);
|
|
3198
|
+
const fileInputRef = useRef(null);
|
|
3199
|
+
useEffect(() => {
|
|
3200
|
+
if (initialSystemPrompt && !systemPrompt) {
|
|
3201
|
+
setSystemPrompt(initialSystemPrompt);
|
|
3202
|
+
}
|
|
3203
|
+
}, [initialSystemPrompt, systemPrompt, setSystemPrompt]);
|
|
3204
|
+
useEffect(() => {
|
|
3205
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
3206
|
+
}, [messages]);
|
|
3207
|
+
const handleFileSelect = useCallback(
|
|
3208
|
+
async (e) => {
|
|
3209
|
+
const files = e.target.files;
|
|
3210
|
+
if (!files || files.length === 0) return;
|
|
3211
|
+
for (const file of Array.from(files)) {
|
|
3212
|
+
try {
|
|
3213
|
+
const attachment = await uploadFile(file);
|
|
3214
|
+
setAttachments((prev) => [...prev, attachment]);
|
|
3215
|
+
} catch (err) {
|
|
3216
|
+
console.error("Upload failed:", err);
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
if (fileInputRef.current) {
|
|
3220
|
+
fileInputRef.current.value = "";
|
|
3221
|
+
}
|
|
3222
|
+
},
|
|
3223
|
+
[uploadFile]
|
|
3224
|
+
);
|
|
3225
|
+
const removeAttachment = useCallback((index) => {
|
|
3226
|
+
setAttachments((prev) => prev.filter((_, i) => i !== index));
|
|
3227
|
+
}, []);
|
|
3228
|
+
const sendMessage = useCallback(async () => {
|
|
3229
|
+
const content = inputValue.trim();
|
|
3230
|
+
if (!content || isStreaming || !isReady) return;
|
|
3231
|
+
const userMessage = {
|
|
3232
|
+
id: generateId(),
|
|
3233
|
+
role: "user",
|
|
3234
|
+
content
|
|
3235
|
+
};
|
|
3236
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
3237
|
+
setInputValue("");
|
|
3238
|
+
onMessage?.(userMessage);
|
|
3239
|
+
const assistantMessage = {
|
|
3240
|
+
id: generateId(),
|
|
3241
|
+
role: "assistant",
|
|
3242
|
+
content: "",
|
|
3243
|
+
isStreaming: true,
|
|
3244
|
+
toolCalls: []
|
|
3245
|
+
};
|
|
3246
|
+
setMessages((prev) => [...prev, assistantMessage]);
|
|
3247
|
+
setIsStreaming(true);
|
|
3248
|
+
setCurrentToolCalls([]);
|
|
3249
|
+
try {
|
|
3250
|
+
const chatMessages = messages.filter((m) => !m.isStreaming).map((m) => ({ role: m.role, content: m.content }));
|
|
3251
|
+
chatMessages.push({ role: "user", content });
|
|
3252
|
+
const stream = chatStream({
|
|
3253
|
+
messages: chatMessages,
|
|
3254
|
+
attachments: attachments.length > 0 ? attachments : void 0,
|
|
3255
|
+
processChunks: true
|
|
3256
|
+
});
|
|
3257
|
+
setAttachments([]);
|
|
3258
|
+
let fullContent = "";
|
|
3259
|
+
const toolCallsAccumulated = [];
|
|
3260
|
+
for await (const chunk of stream) {
|
|
3261
|
+
if (chunk.type === "text") {
|
|
3262
|
+
fullContent += chunk.value;
|
|
3263
|
+
setMessages(
|
|
3264
|
+
(prev) => prev.map(
|
|
3265
|
+
(m) => m.id === assistantMessage.id ? { ...m, content: fullContent } : m
|
|
3266
|
+
)
|
|
3267
|
+
);
|
|
3268
|
+
} else if (chunk.type === "tool_call") {
|
|
3269
|
+
const toolCall = chunk.value;
|
|
3270
|
+
toolCallsAccumulated.push(toolCall);
|
|
3271
|
+
setCurrentToolCalls([...toolCallsAccumulated]);
|
|
3272
|
+
setMessages(
|
|
3273
|
+
(prev) => prev.map(
|
|
3274
|
+
(m) => m.id === assistantMessage.id ? { ...m, toolCalls: [...toolCallsAccumulated] } : m
|
|
3275
|
+
)
|
|
3276
|
+
);
|
|
3277
|
+
onToolCall?.(toolCall);
|
|
3278
|
+
} else if (chunk.type === "error") {
|
|
3279
|
+
console.error("Stream error:", chunk.value);
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
setMessages(
|
|
3283
|
+
(prev) => prev.map(
|
|
3284
|
+
(m) => m.id === assistantMessage.id ? { ...m, isStreaming: false, content: fullContent || "(No response)" } : m
|
|
3285
|
+
)
|
|
3286
|
+
);
|
|
3287
|
+
onResponse?.(fullContent);
|
|
3288
|
+
} catch (err) {
|
|
3289
|
+
console.error("Chat error:", err);
|
|
3290
|
+
setMessages(
|
|
3291
|
+
(prev) => prev.map(
|
|
3292
|
+
(m) => m.id === assistantMessage.id ? { ...m, isStreaming: false, content: `Error: ${err instanceof Error ? err.message : "Unknown error"}` } : m
|
|
3293
|
+
)
|
|
3294
|
+
);
|
|
3295
|
+
} finally {
|
|
3296
|
+
setIsStreaming(false);
|
|
3297
|
+
setCurrentToolCalls([]);
|
|
3298
|
+
}
|
|
3299
|
+
}, [inputValue, isStreaming, isReady, messages, chatStream, attachments, onMessage, onToolCall, onResponse]);
|
|
3300
|
+
const handleKeyPress = useCallback(
|
|
3301
|
+
(e) => {
|
|
3302
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
3303
|
+
e.preventDefault();
|
|
3304
|
+
sendMessage();
|
|
3305
|
+
}
|
|
3306
|
+
},
|
|
3307
|
+
[sendMessage]
|
|
3308
|
+
);
|
|
3309
|
+
const getPlaceholderMessage = () => {
|
|
3310
|
+
if (!isAuthenticated) {
|
|
3311
|
+
return "Connect to start chatting...";
|
|
3312
|
+
}
|
|
3313
|
+
if (!isReady) {
|
|
3314
|
+
return "Initializing...";
|
|
3315
|
+
}
|
|
3316
|
+
return "Start a conversation...";
|
|
3317
|
+
};
|
|
3318
|
+
const canChat = isAuthenticated && isReady;
|
|
3319
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3320
|
+
/* @__PURE__ */ jsx("style", { children: animations }),
|
|
3321
|
+
/* @__PURE__ */ jsxs("div", { className, style: styles2.container, children: [
|
|
3322
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.header, children: [
|
|
3323
|
+
/* @__PURE__ */ jsx("h2", { style: styles2.headerTitle, children: "Chat" }),
|
|
3324
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.headerActions, children: [
|
|
3325
|
+
selectedModel && /* @__PURE__ */ jsx("span", { style: { fontSize: tokens.typography.fontSizeSm, color: tokens.colors.textSecondary }, children: selectedModel.split("/").pop() }),
|
|
3326
|
+
showSettings && /* @__PURE__ */ jsx(
|
|
3327
|
+
"button",
|
|
3328
|
+
{
|
|
3329
|
+
type: "button",
|
|
3330
|
+
onClick: () => setShowSettingsPanel(!showSettingsPanel),
|
|
3331
|
+
style: {
|
|
3332
|
+
...styles2.settingsBtn,
|
|
3333
|
+
...showSettingsPanel ? styles2.settingsBtnActive : styles2.settingsBtnInactive
|
|
3334
|
+
},
|
|
3335
|
+
title: "Settings",
|
|
3336
|
+
children: /* @__PURE__ */ jsx(SettingsIcon, {})
|
|
3337
|
+
}
|
|
3338
|
+
)
|
|
3339
|
+
] })
|
|
3340
|
+
] }),
|
|
3341
|
+
showSettings && showSettingsPanel && /* @__PURE__ */ jsx("div", { style: styles2.modalOverlay, onClick: () => setShowSettingsPanel(false), children: /* @__PURE__ */ jsxs("div", { style: styles2.modal, onClick: (e) => e.stopPropagation(), children: [
|
|
3342
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.modalHeader, children: [
|
|
3343
|
+
/* @__PURE__ */ jsx("span", { style: styles2.modalTitle, children: "Settings" }),
|
|
3344
|
+
/* @__PURE__ */ jsx(
|
|
3345
|
+
"button",
|
|
3346
|
+
{
|
|
3347
|
+
type: "button",
|
|
3348
|
+
style: styles2.modalClose,
|
|
3349
|
+
onClick: () => setShowSettingsPanel(false),
|
|
3350
|
+
children: "\xD7"
|
|
3351
|
+
}
|
|
3352
|
+
)
|
|
3353
|
+
] }),
|
|
3354
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.modalBody, children: [
|
|
3355
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.settingGroup, children: [
|
|
3356
|
+
/* @__PURE__ */ jsx("label", { style: styles2.settingLabel, children: "Model" }),
|
|
3357
|
+
/* @__PURE__ */ jsx("p", { style: styles2.settingDescription, children: "Select the AI model to use for chat responses" }),
|
|
3358
|
+
/* @__PURE__ */ jsxs(
|
|
3359
|
+
"select",
|
|
3360
|
+
{
|
|
3361
|
+
value: selectedModel,
|
|
3362
|
+
onChange: (e) => setSelectedModel(e.target.value),
|
|
3363
|
+
style: styles2.settingSelect,
|
|
3364
|
+
children: [
|
|
3365
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Default (server decides)" }),
|
|
3366
|
+
(() => {
|
|
3367
|
+
const grouped = {};
|
|
3368
|
+
models.forEach((model) => {
|
|
3369
|
+
const [provider] = model.id.split("/");
|
|
3370
|
+
if (!grouped[provider]) grouped[provider] = [];
|
|
3371
|
+
grouped[provider].push(model);
|
|
3372
|
+
});
|
|
3373
|
+
return Object.entries(grouped).map(([provider, providerModels]) => /* @__PURE__ */ jsx("optgroup", { label: provider.charAt(0).toUpperCase() + provider.slice(1), children: providerModels.map((model) => /* @__PURE__ */ jsx("option", { value: model.id, children: model.name }, model.id)) }, provider));
|
|
3374
|
+
})()
|
|
3375
|
+
]
|
|
3376
|
+
}
|
|
3377
|
+
),
|
|
3378
|
+
selectedModel && (() => {
|
|
3379
|
+
const model = models.find((m) => m.id === selectedModel);
|
|
3380
|
+
if (!model) return null;
|
|
3381
|
+
const contextK = Math.round(model.context_length / 1e3);
|
|
3382
|
+
const promptCost = parseFloat(model.pricing?.prompt || "0") * 1e6;
|
|
3383
|
+
const completionCost = parseFloat(model.pricing?.completion || "0") * 1e6;
|
|
3384
|
+
return /* @__PURE__ */ jsxs("div", { style: styles2.modelInfo, children: [
|
|
3385
|
+
"Context: ",
|
|
3386
|
+
contextK,
|
|
3387
|
+
"K tokens | Cost: $",
|
|
3388
|
+
promptCost.toFixed(2),
|
|
3389
|
+
"/$",
|
|
3390
|
+
completionCost.toFixed(2),
|
|
3391
|
+
" per 1M tokens"
|
|
3392
|
+
] });
|
|
3393
|
+
})()
|
|
3394
|
+
] }),
|
|
3395
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.settingGroup, children: [
|
|
3396
|
+
/* @__PURE__ */ jsx("label", { style: styles2.settingLabel, children: "Server System Prompt" }),
|
|
3397
|
+
/* @__PURE__ */ jsxs(
|
|
3398
|
+
"div",
|
|
3399
|
+
{
|
|
3400
|
+
style: styles2.toggleRow,
|
|
3401
|
+
onClick: () => setSkipServerPrompt(!skipServerPrompt),
|
|
3402
|
+
children: [
|
|
3403
|
+
/* @__PURE__ */ jsx("span", { style: styles2.toggleLabel, children: "Skip server-provided system prompt" }),
|
|
3404
|
+
/* @__PURE__ */ jsx("div", { style: {
|
|
3405
|
+
...styles2.toggleSwitch,
|
|
3406
|
+
...skipServerPrompt ? styles2.toggleSwitchActive : {}
|
|
3407
|
+
}, children: /* @__PURE__ */ jsx("div", { style: {
|
|
3408
|
+
...styles2.toggleKnob,
|
|
3409
|
+
...skipServerPrompt ? styles2.toggleKnobActive : {}
|
|
3410
|
+
} }) })
|
|
3411
|
+
]
|
|
3412
|
+
}
|
|
3413
|
+
),
|
|
3414
|
+
/* @__PURE__ */ jsx("p", { style: styles2.settingDescription, children: "When enabled, the server's default system prompt will not be used" })
|
|
3415
|
+
] }),
|
|
3416
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.settingGroup, children: [
|
|
3417
|
+
/* @__PURE__ */ jsx("label", { style: styles2.settingLabel, children: "Custom System Prompt" }),
|
|
3418
|
+
/* @__PURE__ */ jsx("p", { style: styles2.settingDescription, children: "Provide instructions for how the AI should behave" }),
|
|
3419
|
+
/* @__PURE__ */ jsx(
|
|
3420
|
+
"textarea",
|
|
3421
|
+
{
|
|
3422
|
+
value: systemPrompt,
|
|
3423
|
+
onChange: (e) => setSystemPrompt(e.target.value),
|
|
3424
|
+
placeholder: "You are a helpful assistant...",
|
|
3425
|
+
style: styles2.settingTextarea
|
|
3426
|
+
}
|
|
3427
|
+
)
|
|
3428
|
+
] }),
|
|
3429
|
+
/* @__PURE__ */ jsx("div", { style: styles2.settingDivider }),
|
|
3430
|
+
/* @__PURE__ */ jsxs("div", { style: { ...styles2.settingGroup, marginBottom: 0 }, children: [
|
|
3431
|
+
/* @__PURE__ */ jsx("label", { style: styles2.settingLabel, children: "Plugins" }),
|
|
3432
|
+
/* @__PURE__ */ jsx("p", { style: styles2.settingDescription, children: "Extend the AI with custom tools" }),
|
|
3433
|
+
plugins.length > 0 ? /* @__PURE__ */ jsx("div", { style: styles2.pluginList, children: plugins.map((plugin) => /* @__PURE__ */ jsxs("div", { style: styles2.pluginRow, children: [
|
|
3434
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.pluginInfo, children: [
|
|
3435
|
+
/* @__PURE__ */ jsx("span", { style: styles2.pluginIcon, children: plugin.enabled ? "\u{1F50C}" : "\u26AA" }),
|
|
3436
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.pluginDetails, children: [
|
|
3437
|
+
/* @__PURE__ */ jsx("span", { style: styles2.pluginName, children: plugin.name }),
|
|
3438
|
+
/* @__PURE__ */ jsxs("span", { style: styles2.pluginMeta, children: [
|
|
3439
|
+
"v",
|
|
3440
|
+
plugin.version,
|
|
3441
|
+
" \u2022 ",
|
|
3442
|
+
plugin.tools?.length || 0,
|
|
3443
|
+
" tools"
|
|
3444
|
+
] })
|
|
3445
|
+
] })
|
|
3446
|
+
] }),
|
|
3447
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: tokens.spacing.sm }, children: [
|
|
3448
|
+
/* @__PURE__ */ jsx(
|
|
3449
|
+
"div",
|
|
3450
|
+
{
|
|
3451
|
+
style: {
|
|
3452
|
+
...styles2.toggleSwitch,
|
|
3453
|
+
...plugin.enabled ? styles2.toggleSwitchActive : {}
|
|
3454
|
+
},
|
|
3455
|
+
onClick: () => plugin.enabled ? disablePlugin(plugin.name) : enablePlugin(plugin.name),
|
|
3456
|
+
children: /* @__PURE__ */ jsx("div", { style: {
|
|
3457
|
+
...styles2.toggleKnob,
|
|
3458
|
+
...plugin.enabled ? styles2.toggleKnobActive : {}
|
|
3459
|
+
} })
|
|
3460
|
+
}
|
|
3461
|
+
),
|
|
3462
|
+
/* @__PURE__ */ jsx(
|
|
3463
|
+
"button",
|
|
3464
|
+
{
|
|
3465
|
+
type: "button",
|
|
3466
|
+
style: styles2.uninstallBtn,
|
|
3467
|
+
onClick: () => unregisterPlugin(plugin.name),
|
|
3468
|
+
children: "Remove"
|
|
3469
|
+
}
|
|
3470
|
+
)
|
|
3471
|
+
] })
|
|
3472
|
+
] }, plugin.name)) }) : /* @__PURE__ */ jsx("div", { style: styles2.pluginEmpty, children: "No plugins installed" }),
|
|
3473
|
+
availablePlugins.filter((p) => !plugins.some((installed) => installed.name === p.name)).length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3474
|
+
/* @__PURE__ */ jsx("div", { style: styles2.availablePluginsHeader, children: "Available" }),
|
|
3475
|
+
/* @__PURE__ */ jsx("div", { style: styles2.pluginList, children: availablePlugins.filter((p) => !plugins.some((installed) => installed.name === p.name)).map((plugin) => /* @__PURE__ */ jsxs("div", { style: styles2.pluginRow, children: [
|
|
3476
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.pluginInfo, children: [
|
|
3477
|
+
/* @__PURE__ */ jsx("span", { style: styles2.pluginIcon, children: "\u{1F4E6}" }),
|
|
3478
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.pluginDetails, children: [
|
|
3479
|
+
/* @__PURE__ */ jsx("span", { style: styles2.pluginName, children: plugin.name }),
|
|
3480
|
+
/* @__PURE__ */ jsx("span", { style: styles2.pluginMeta, children: plugin.description })
|
|
3481
|
+
] })
|
|
3482
|
+
] }),
|
|
3483
|
+
/* @__PURE__ */ jsx(
|
|
3484
|
+
"button",
|
|
3485
|
+
{
|
|
3486
|
+
type: "button",
|
|
3487
|
+
style: styles2.installBtn,
|
|
3488
|
+
onClick: () => registerPlugin(plugin),
|
|
3489
|
+
children: "+ Install"
|
|
3490
|
+
}
|
|
3491
|
+
)
|
|
3492
|
+
] }, plugin.name)) })
|
|
3493
|
+
] })
|
|
3494
|
+
] })
|
|
3495
|
+
] })
|
|
3496
|
+
] }) }),
|
|
3497
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.messagesArea, children: [
|
|
3498
|
+
messages.length === 0 && /* @__PURE__ */ jsx("div", { style: styles2.messagesEmpty, children: /* @__PURE__ */ jsx("p", { children: getPlaceholderMessage() }) }),
|
|
3499
|
+
/* @__PURE__ */ jsx("div", { style: styles2.messagesContainer, children: messages.map((message) => /* @__PURE__ */ jsx(
|
|
3500
|
+
MessageBubble,
|
|
3501
|
+
{
|
|
3502
|
+
message,
|
|
3503
|
+
showDebug
|
|
3504
|
+
},
|
|
3505
|
+
message.id
|
|
3506
|
+
)) }),
|
|
3507
|
+
currentToolCalls.length > 0 && /* @__PURE__ */ jsx("div", { style: styles2.toolCallsIndicator, children: currentToolCalls.map((tool) => /* @__PURE__ */ jsxs("span", { style: styles2.toolCallBadge, children: [
|
|
3508
|
+
/* @__PURE__ */ jsx("span", { style: styles2.toolCallDot }),
|
|
3509
|
+
tool.toolName
|
|
3510
|
+
] }, tool.toolCallId)) }),
|
|
3511
|
+
/* @__PURE__ */ jsx("div", { ref: messagesEndRef })
|
|
3512
|
+
] }),
|
|
3513
|
+
attachments.length > 0 && /* @__PURE__ */ jsx("div", { style: styles2.attachmentsPreview, children: attachments.map((att, index) => /* @__PURE__ */ jsxs("div", { style: styles2.attachmentItem, children: [
|
|
3514
|
+
/* @__PURE__ */ jsx("span", { style: styles2.attachmentName, children: att.name }),
|
|
3515
|
+
/* @__PURE__ */ jsx(
|
|
3516
|
+
"button",
|
|
3517
|
+
{
|
|
3518
|
+
type: "button",
|
|
3519
|
+
onClick: () => removeAttachment(index),
|
|
3520
|
+
style: styles2.attachmentRemove,
|
|
3521
|
+
children: "\xD7"
|
|
3522
|
+
}
|
|
3523
|
+
)
|
|
3524
|
+
] }, index)) }),
|
|
3525
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.inputArea, children: [
|
|
3526
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.inputRow, children: [
|
|
3527
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.inputContainer, children: [
|
|
3528
|
+
/* @__PURE__ */ jsx(
|
|
3529
|
+
"button",
|
|
3530
|
+
{
|
|
3531
|
+
type: "button",
|
|
3532
|
+
onClick: () => fileInputRef.current?.click(),
|
|
3533
|
+
style: styles2.attachBtn,
|
|
3534
|
+
title: "Attach file",
|
|
3535
|
+
children: /* @__PURE__ */ jsx(AttachIcon, {})
|
|
3536
|
+
}
|
|
3537
|
+
),
|
|
3538
|
+
/* @__PURE__ */ jsx(
|
|
3539
|
+
"input",
|
|
3540
|
+
{
|
|
3541
|
+
ref: fileInputRef,
|
|
3542
|
+
type: "file",
|
|
3543
|
+
accept: "image/*",
|
|
3544
|
+
multiple: true,
|
|
3545
|
+
onChange: handleFileSelect,
|
|
3546
|
+
style: { display: "none" }
|
|
3547
|
+
}
|
|
3548
|
+
),
|
|
3549
|
+
/* @__PURE__ */ jsx("div", { style: styles2.inputWrapper, children: /* @__PURE__ */ jsx(
|
|
3550
|
+
"textarea",
|
|
3551
|
+
{
|
|
3552
|
+
value: inputValue,
|
|
3553
|
+
onChange: (e) => setInputValue(e.target.value),
|
|
3554
|
+
onKeyPress: handleKeyPress,
|
|
3555
|
+
placeholder,
|
|
3556
|
+
disabled: !canChat || isStreaming || isLoading,
|
|
3557
|
+
rows: 1,
|
|
3558
|
+
style: {
|
|
3559
|
+
...styles2.input,
|
|
3560
|
+
...!canChat || isStreaming || isLoading ? styles2.inputDisabled : {}
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
) })
|
|
3564
|
+
] }),
|
|
3565
|
+
/* @__PURE__ */ jsx(
|
|
3566
|
+
"button",
|
|
3567
|
+
{
|
|
3568
|
+
type: "button",
|
|
3569
|
+
onClick: sendMessage,
|
|
3570
|
+
disabled: !canChat || !inputValue.trim() || isStreaming || isLoading,
|
|
3571
|
+
style: {
|
|
3572
|
+
...styles2.sendBtn,
|
|
3573
|
+
...!canChat || !inputValue.trim() || isStreaming || isLoading ? styles2.sendBtnDisabled : {}
|
|
3574
|
+
},
|
|
3575
|
+
children: isStreaming ? /* @__PURE__ */ jsx("span", { style: styles2.sendSpinner }) : "Send"
|
|
3576
|
+
}
|
|
3577
|
+
)
|
|
3578
|
+
] }),
|
|
3579
|
+
error && /* @__PURE__ */ jsx("div", { style: styles2.errorBox, children: error.message })
|
|
3580
|
+
] })
|
|
3581
|
+
] })
|
|
3582
|
+
] });
|
|
3583
|
+
}
|
|
3584
|
+
function MessageBubble({ message, showDebug }) {
|
|
3585
|
+
const isUser = message.role === "user";
|
|
3586
|
+
const isSystem = message.role === "system";
|
|
3587
|
+
const containerStyle2 = {
|
|
3588
|
+
...styles2.messageBubbleContainer,
|
|
3589
|
+
...isUser ? styles2.messageBubbleUser : styles2.messageBubbleAssistant
|
|
3590
|
+
};
|
|
3591
|
+
const bubbleStyle = {
|
|
3592
|
+
...styles2.messageBubble,
|
|
3593
|
+
...isUser ? styles2.messageBubbleUserStyle : isSystem ? styles2.messageBubbleSystemStyle : styles2.messageBubbleAssistantStyle
|
|
3594
|
+
};
|
|
3595
|
+
return /* @__PURE__ */ jsx("div", { style: containerStyle2, children: /* @__PURE__ */ jsxs("div", { style: bubbleStyle, children: [
|
|
3596
|
+
/* @__PURE__ */ jsxs("div", { style: styles2.messageContent, children: [
|
|
3597
|
+
isUser || isSystem ? (
|
|
3598
|
+
// User and system messages: plain text
|
|
3599
|
+
message.content
|
|
3600
|
+
) : (
|
|
3601
|
+
// Assistant messages: render markdown
|
|
3602
|
+
/* @__PURE__ */ jsx(MarkdownContent, { content: message.content })
|
|
3603
|
+
),
|
|
3604
|
+
message.isStreaming && /* @__PURE__ */ jsx("span", { style: styles2.streamingCursor })
|
|
3605
|
+
] }),
|
|
3606
|
+
showDebug && message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles2.toolCallsDebug, children: [
|
|
3607
|
+
/* @__PURE__ */ jsx("div", { style: styles2.toolCallsDebugTitle, children: "Tool calls:" }),
|
|
3608
|
+
message.toolCalls.map((tool) => /* @__PURE__ */ jsxs("div", { style: styles2.toolCallDebugItem, children: [
|
|
3609
|
+
/* @__PURE__ */ jsx("span", { style: styles2.toolCallDebugName, children: tool.toolName }),
|
|
3610
|
+
tool.args && /* @__PURE__ */ jsx("pre", { style: styles2.toolCallDebugArgs, children: JSON.stringify(tool.args, null, 2) })
|
|
3611
|
+
] }, tool.toolCallId))
|
|
3612
|
+
] })
|
|
3613
|
+
] }) });
|
|
3614
|
+
}
|
|
3615
|
+
function SettingsIcon() {
|
|
3616
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
3617
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
|
|
3618
|
+
/* @__PURE__ */ jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" })
|
|
3619
|
+
] });
|
|
3620
|
+
}
|
|
3621
|
+
function AttachIcon() {
|
|
3622
|
+
return /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) });
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
// src/utils/index.ts
|
|
3626
|
+
function truncateAddress2(address, startChars = 6, endChars = 4) {
|
|
3627
|
+
if (!address) return "";
|
|
3628
|
+
if (address.length <= startChars + endChars) return address;
|
|
3629
|
+
return `${address.slice(0, startChars)}...${address.slice(-endChars)}`;
|
|
3630
|
+
}
|
|
3631
|
+
async function copyToClipboard3(text) {
|
|
3632
|
+
try {
|
|
3633
|
+
await navigator.clipboard.writeText(text);
|
|
3634
|
+
return true;
|
|
3635
|
+
} catch {
|
|
3636
|
+
try {
|
|
3637
|
+
const textarea = document.createElement("textarea");
|
|
3638
|
+
textarea.value = text;
|
|
3639
|
+
textarea.style.position = "fixed";
|
|
3640
|
+
textarea.style.opacity = "0";
|
|
3641
|
+
document.body.appendChild(textarea);
|
|
3642
|
+
textarea.select();
|
|
3643
|
+
const success = document.execCommand("copy");
|
|
3644
|
+
document.body.removeChild(textarea);
|
|
3645
|
+
return success;
|
|
3646
|
+
} catch {
|
|
3647
|
+
return false;
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3651
|
+
function generateId2(prefix = "id") {
|
|
3652
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
3653
|
+
}
|
|
3654
|
+
function decodeJwtPayload(token) {
|
|
3655
|
+
try {
|
|
3656
|
+
const parts = token.split(".");
|
|
3657
|
+
if (parts.length !== 3) return null;
|
|
3658
|
+
const payload = JSON.parse(atob(parts[1]));
|
|
3659
|
+
return payload;
|
|
3660
|
+
} catch {
|
|
3661
|
+
return null;
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
function isJwtExpired(token) {
|
|
3665
|
+
const payload = decodeJwtPayload(token);
|
|
3666
|
+
if (!payload || typeof payload.exp !== "number") return true;
|
|
3667
|
+
return Date.now() >= payload.exp * 1e3;
|
|
3668
|
+
}
|
|
3669
|
+
function formatFileSize(bytes) {
|
|
3670
|
+
if (bytes === 0) return "0 Bytes";
|
|
3671
|
+
const k = 1024;
|
|
3672
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
3673
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
3674
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
|
3675
|
+
}
|
|
3676
|
+
function debounce(fn, delay) {
|
|
3677
|
+
let timeoutId;
|
|
3678
|
+
return (...args) => {
|
|
3679
|
+
clearTimeout(timeoutId);
|
|
3680
|
+
timeoutId = setTimeout(() => fn(...args), delay);
|
|
3681
|
+
};
|
|
3682
|
+
}
|
|
3683
|
+
var STORAGE_KEYS = {
|
|
3684
|
+
AUTH_SESSION: "emblem_auth_session",
|
|
3685
|
+
HUSTLE_SETTINGS: "hustle_settings",
|
|
3686
|
+
CHAT_HISTORY: "hustle_chat_history",
|
|
3687
|
+
PLUGINS: "hustle-plugins"
|
|
3688
|
+
};
|
|
3689
|
+
var DEFAULTS = {
|
|
3690
|
+
HUSTLE_API_URL: "https://agenthustle.ai",
|
|
3691
|
+
EMBLEM_API_URL: "https://api.emblemvault.ai",
|
|
3692
|
+
EMBLEM_MODAL_URL: "https://emblemvault.ai/connect"
|
|
3693
|
+
};
|
|
3694
|
+
|
|
3695
|
+
export { AuthStatus, ConnectButton, DEFAULTS, EmblemAuthProvider, HustleChat, HustleProvider, STORAGE_KEYS, availablePlugins, copyToClipboard3 as copyToClipboard, debounce, decodeJwtPayload, formatFileSize, generateId2 as generateId, getAvailablePlugin, hydratePlugin, isJwtExpired, migrateFunPlugin, pluginRegistry, predictionMarketPlugin, resetAuthSDK, truncateAddress2 as truncateAddress, useEmblemAuth, useHustle, usePlugins };
|
|
3696
|
+
//# sourceMappingURL=index.js.map
|
|
3697
|
+
//# sourceMappingURL=index.js.map
|