nyxora 1.7.0 → 1.7.2

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.
Files changed (59) hide show
  1. package/.dockerignore +12 -0
  2. package/CHANGELOG.md +21 -0
  3. package/DOCKER.md +68 -0
  4. package/Dockerfile +43 -0
  5. package/bin/nyxora.mjs +8 -0
  6. package/package.json +1 -2
  7. package/packages/core/package.json +1 -1
  8. package/packages/core/src/agent/limitOrderManager.ts +2 -2
  9. package/packages/core/src/agent/reasoning.ts +12 -10
  10. package/packages/core/src/config/parser.ts +105 -11
  11. package/packages/core/src/gateway/cli.ts +28 -2
  12. package/packages/core/src/gateway/googleAuthModule.ts +19 -0
  13. package/packages/core/src/gateway/server.ts +9 -3
  14. package/packages/core/src/gateway/setup.ts +44 -9
  15. package/packages/core/src/gateway/telegram.ts +3 -1
  16. package/packages/core/src/system/skills/searchWeb.ts +187 -21
  17. package/packages/core/src/web3/config.ts +7 -1
  18. package/packages/core/src/web3/skills/bridgeToken.ts +4 -3
  19. package/packages/core/src/web3/skills/checkAddress.ts +2 -2
  20. package/packages/core/src/web3/skills/checkPortfolio.ts +2 -2
  21. package/packages/core/src/web3/skills/checkSecurity.ts +3 -2
  22. package/packages/core/src/web3/skills/customTx.ts +2 -2
  23. package/packages/core/src/web3/skills/getBalance.ts +3 -3
  24. package/packages/core/src/web3/skills/marketAnalysis.ts +2 -2
  25. package/packages/core/src/web3/skills/mintNft.ts +2 -2
  26. package/packages/core/src/web3/skills/swapToken.ts +4 -3
  27. package/packages/core/src/web3/skills/transfer.ts +2 -2
  28. package/packages/core/src/web3/utils/tokens.ts +8 -0
  29. package/packages/dashboard/dist/assets/{index-24OeXn-k.css → index-BSk4CLkG.css} +1 -1
  30. package/packages/dashboard/dist/assets/{index-DQtaOlOl.js → index-cGPka4s1.js} +14 -14
  31. package/packages/dashboard/dist/index.html +2 -2
  32. package/packages/dashboard/package.json +1 -1
  33. package/packages/mcp-server/package.json +2 -2
  34. package/packages/policy/package.json +2 -2
  35. package/packages/policy/src/server.ts +2 -2
  36. package/packages/signer/package.json +2 -2
  37. package/packages/dashboard/public/favicon.svg +0 -10
  38. package/packages/dashboard/public/icons.svg +0 -24
  39. package/packages/dashboard/src/App.css +0 -184
  40. package/packages/dashboard/src/App.tsx +0 -585
  41. package/packages/dashboard/src/BalanceWidget.tsx +0 -65
  42. package/packages/dashboard/src/MarketWidget.tsx +0 -73
  43. package/packages/dashboard/src/NetworkSelector.tsx +0 -64
  44. package/packages/dashboard/src/NyxoraLogo.tsx +0 -25
  45. package/packages/dashboard/src/OsSkills.tsx +0 -352
  46. package/packages/dashboard/src/Overview.tsx +0 -157
  47. package/packages/dashboard/src/PendingTransactions.tsx +0 -75
  48. package/packages/dashboard/src/Settings.tsx +0 -338
  49. package/packages/dashboard/src/Skills.tsx +0 -200
  50. package/packages/dashboard/src/SwapWidget.tsx +0 -141
  51. package/packages/dashboard/src/TransactionWidget.tsx +0 -95
  52. package/packages/dashboard/src/assets/hero.png +0 -0
  53. package/packages/dashboard/src/assets/react.svg +0 -1
  54. package/packages/dashboard/src/assets/vite.svg +0 -1
  55. package/packages/dashboard/src/components/PillSelect.tsx +0 -65
  56. package/packages/dashboard/src/index.css +0 -807
  57. package/packages/dashboard/src/main.tsx +0 -10
  58. package/packages/dashboard/src/overview.css +0 -304
  59. package/packages/dashboard/src/utils/api.ts +0 -31
@@ -1,585 +0,0 @@
1
- import { apiFetch } from './utils/api';
2
- import { useState, useEffect, useRef } from 'react';
3
- import { Play, Square, Settings as SettingsIcon, Brain, Cpu, MessageSquare, Plus, Trash2, Code, Shield, Network, Terminal, RefreshCw, Send, Image as ImageIcon, Sparkles, Edit2, Zap, ArrowRight, Wallet, Check, AlertTriangle, Bot, Activity, Database, Mic, Copy, Search, LayoutDashboard } from 'lucide-react';
4
- import Overview from './Overview';
5
- import Settings from './Settings';
6
- import Skills from './Skills';
7
- import OsSkills from './OsSkills';
8
- import { NetworkSelector } from './NetworkSelector';
9
- import PendingTransactions from './PendingTransactions';
10
- import BalanceWidget from './BalanceWidget';
11
- import TransactionWidget from './TransactionWidget';
12
- import MarketWidget from './MarketWidget';
13
- import NyxoraLogo from './NyxoraLogo';
14
- import SwapWidget from './SwapWidget';
15
- import './index.css';
16
-
17
- interface Message {
18
- role: 'user' | 'assistant' | 'tool' | 'system';
19
- content: string;
20
- name?: string;
21
- tool_calls?: any[];
22
- }
23
-
24
- interface Config {
25
- agent: { name: string; default_chain: string };
26
- llm: { provider: string; model: string; temperature: number };
27
- }
28
-
29
- function App() {
30
- const [currentView, setCurrentView] = useState<'chat' | 'overview' | 'settings' | 'skills' | 'osskills'>('chat');
31
- const [trendingTokens, setTrendingTokens] = useState<string[]>(['$BTC', '$ETH', '$SOL', '$SUI']);
32
- const [messages, setMessages] = useState<Message[]>([]);
33
- const [chatSessions, setChatSessions] = useState<any[]>([]);
34
- const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
35
- const [editingSessionId, setEditingSessionId] = useState<string | null>(null);
36
- const [editSessionTitle, setEditSessionTitle] = useState<string>('');
37
- const [input, setInput] = useState('');
38
- const [isLoading, setIsLoading] = useState(false);
39
- const [isListening, setIsListening] = useState(false);
40
- const [isVoiceMode, setIsVoiceMode] = useState(false);
41
- const isVoiceModeRef = useRef(false);
42
- const [isSpeaking, setIsSpeaking] = useState(false);
43
- const [config, setConfig] = useState<Config | null>(null);
44
- const [chatWidth, setChatWidth] = useState(70);
45
- const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
46
- const messagesEndRef = useRef<HTMLDivElement>(null);
47
- const recognitionRef = useRef<any>(null);
48
-
49
-
50
-
51
- useEffect(() => {
52
- // Initialize Speech Recognition
53
- const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;
54
- if (SpeechRecognition) {
55
- recognitionRef.current = new SpeechRecognition();
56
- recognitionRef.current.continuous = false;
57
- recognitionRef.current.interimResults = false;
58
- recognitionRef.current.lang = 'en-US';
59
-
60
- recognitionRef.current.onresult = (event: any) => {
61
- const transcript = event.results[0][0].transcript;
62
- setInput(transcript);
63
- setIsListening(false);
64
- };
65
-
66
- recognitionRef.current.onerror = (event: any) => {
67
- console.error("Speech recognition error", event.error);
68
- setIsListening(false);
69
- };
70
-
71
- recognitionRef.current.onend = () => {
72
- setIsListening(false);
73
- };
74
- }
75
- }, []);
76
-
77
- const startListening = () => {
78
- try {
79
- recognitionRef.current?.start();
80
- setIsListening(true);
81
- } catch (e) {}
82
- };
83
-
84
- const speak = (text: string) => {
85
- if (!('speechSynthesis' in window)) return;
86
- window.speechSynthesis.cancel();
87
-
88
- // Clean markdown before speaking
89
- const cleanText = text.replace(/[*#_`]/g, '');
90
- const utterance = new SpeechSynthesisUtterance(cleanText);
91
- utterance.lang = 'id-ID';
92
-
93
- utterance.onstart = () => setIsSpeaking(true);
94
- utterance.onend = () => {
95
- setIsSpeaking(false);
96
- // Auto-listen if in voice mode
97
- if (isVoiceModeRef.current) {
98
- startListening();
99
- }
100
- };
101
- window.speechSynthesis.speak(utterance);
102
- };
103
-
104
- const toggleVoiceMode = () => {
105
- const newMode = !isVoiceMode;
106
- setIsVoiceMode(newMode);
107
- isVoiceModeRef.current = newMode;
108
-
109
- if (newMode) {
110
- startListening();
111
- } else {
112
- recognitionRef.current?.stop();
113
- setIsListening(false);
114
- window.speechSynthesis.cancel();
115
- setIsSpeaking(false);
116
- }
117
- };
118
-
119
- const fetchHistory = async () => {
120
- if (!activeSessionId) {
121
- setMessages([]);
122
- return;
123
- }
124
- try {
125
- const url = `/api/history?session_id=${activeSessionId}`;
126
- const res = await apiFetch(url);
127
- if (res.ok) {
128
- const data = await res.json();
129
- setMessages(data);
130
- }
131
- } catch (err) {
132
- console.warn('Backend not ready, retrying history fetch in 2s...');
133
- }
134
- };
135
-
136
- const fetchSessions = async () => {
137
- try {
138
- const res = await apiFetch(`/api/sessions`);
139
- if (res.ok) {
140
- const data = await res.json();
141
- setChatSessions(data);
142
- // On first load, if no active session, auto-select the most recent one
143
- if (data.length > 0 && !activeSessionId) {
144
- setActiveSessionId(data[0].id);
145
- }
146
- }
147
- } catch (err) {}
148
- };
149
-
150
- const fetchTrendingTokens = async () => {
151
- try {
152
- const res = await apiFetch(`/api/trending`);
153
- if (res.ok) {
154
- setTrendingTokens(await res.json());
155
- }
156
- } catch (err) {}
157
- };
158
-
159
- const createNewSession = async () => {
160
- try {
161
- const res = await apiFetch(`/api/sessions`, {
162
- method: 'POST',
163
- headers: { 'Content-Type': 'application/json' },
164
- body: JSON.stringify({ title: 'New Chat' })
165
- });
166
- if (res.ok) {
167
- const { id } = await res.json();
168
- setActiveSessionId(id);
169
- setMessages([]);
170
- await fetchSessions();
171
- setCurrentView('chat');
172
- }
173
- } catch (err) {}
174
- };
175
-
176
- const renameSession = async (id: string, newTitle: string) => {
177
- try {
178
- await apiFetch(`/api/sessions/${id}`, {
179
- method: 'PUT',
180
- headers: { 'Content-Type': 'application/json' },
181
- body: JSON.stringify({ title: newTitle })
182
- });
183
- setEditingSessionId(null);
184
- await fetchSessions();
185
- } catch (err) {}
186
- };
187
-
188
- const deleteSession = async (id: string, e: React.MouseEvent) => {
189
- e.stopPropagation();
190
- try {
191
- await apiFetch(`/api/sessions/${id}`, { method: 'DELETE' });
192
- if (activeSessionId === id) {
193
- setActiveSessionId(null);
194
- setMessages([]);
195
- }
196
- await fetchSessions();
197
- } catch (err) {}
198
- };
199
-
200
- const fetchConfig = async () => {
201
- try {
202
- const res = await apiFetch(`/api/config`);
203
- if (res.ok) {
204
- const data = await res.json();
205
- setConfig(data);
206
- } else {
207
- setTimeout(fetchConfig, 2000);
208
- }
209
- } catch (err) {
210
- console.warn('Backend not ready, retrying config fetch in 2s...');
211
- setTimeout(fetchConfig, 2000);
212
- }
213
- };
214
-
215
- const updateConfig = async (newConfig: Config) => {
216
- setConfig(newConfig);
217
- try {
218
- await apiFetch(`/api/config`, {
219
- method: 'POST',
220
- headers: { 'Content-Type': 'application/json' },
221
- body: JSON.stringify(newConfig),
222
- });
223
- } catch (err) {
224
- console.error('Failed to save config', err);
225
- }
226
- };
227
-
228
- useEffect(() => {
229
- fetchHistory();
230
- fetchConfig();
231
- fetchSessions();
232
- fetchTrendingTokens();
233
- const interval = setInterval(() => {
234
- fetchHistory();
235
- fetchSessions();
236
- fetchTrendingTokens();
237
- }, 2000);
238
- return () => clearInterval(interval);
239
- }, [activeSessionId]);
240
-
241
- useEffect(() => {
242
- // Adding a slight timeout to ensure DOM is fully rendered before scrolling
243
- setTimeout(() => {
244
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
245
- }, 10);
246
- }, [messages.length, isLoading, currentView]);
247
-
248
- const handleSubmit = async (e: React.FormEvent) => {
249
- e.preventDefault();
250
- if (!input.trim() || isLoading) return;
251
-
252
- const userMsg = input.trim();
253
- setInput('');
254
- setIsLoading(true);
255
-
256
- let currentSessionId = activeSessionId;
257
-
258
- // Auto-create session if null
259
- if (!currentSessionId) {
260
- try {
261
- const title = userMsg.length > 25 ? userMsg.substring(0, 25) + '...' : userMsg;
262
- const res = await apiFetch(`/api/sessions`, {
263
- method: 'POST',
264
- headers: { 'Content-Type': 'application/json' },
265
- body: JSON.stringify({ title })
266
- });
267
- if (res.ok) {
268
- const { id } = await res.json();
269
- currentSessionId = id;
270
- setActiveSessionId(id);
271
- await fetchSessions();
272
- }
273
- } catch (err) {
274
- console.error("Failed to auto-create session", err);
275
- }
276
- }
277
-
278
- setMessages(prev => [...prev, { role: 'user', content: userMsg }]);
279
-
280
- try {
281
- const res = await apiFetch(`/api/chat`, {
282
- method: 'POST',
283
- headers: { 'Content-Type': 'application/json' },
284
- body: JSON.stringify({ message: userMsg, session_id: currentSessionId }),
285
- });
286
- const data = await res.json();
287
- await fetchHistory();
288
-
289
- // Auto-rename on first prompt
290
- if (messages.length === 0 && currentSessionId) {
291
- const autoTitle = userMsg.length > 25 ? userMsg.substring(0, 25) + '...' : userMsg;
292
- renameSession(currentSessionId, autoTitle);
293
- }
294
-
295
- // Trigger TTS if in voice mode
296
- if (isVoiceModeRef.current && data.response) {
297
- speak(data.response);
298
- }
299
- } catch (error) {
300
- console.error('Error sending message:', error);
301
- } finally {
302
- setIsLoading(false);
303
- }
304
- };
305
-
306
- // Determine active widget for Canvas based on the latest tool call result
307
- let activeWidget: React.ReactNode = null;
308
- const latestToolMessage = [...messages].reverse().find(m => m.role === 'tool');
309
-
310
- if (latestToolMessage && latestToolMessage.name) {
311
- if (latestToolMessage.name === 'get_balance') {
312
- activeWidget = <BalanceWidget data={latestToolMessage.content} />;
313
- } else if (latestToolMessage.name === 'transfer_native') {
314
- activeWidget = <TransactionWidget data={latestToolMessage.content} />;
315
- } else if (latestToolMessage.name === 'get_price') {
316
- activeWidget = <MarketWidget data={latestToolMessage.content} />;
317
- } else if (latestToolMessage.name === 'swap_token') {
318
- activeWidget = <SwapWidget data={latestToolMessage.content} />;
319
- }
320
- }
321
-
322
- const renderMessageContent = (content: string) => {
323
- return content.split(/(\*\*.*?\*\*)/g).map((part, i) => {
324
- if (part.startsWith('**') && part.endsWith('**')) {
325
- return <strong key={i} style={{ color: '#fff' }}>{part.slice(2, -2)}</strong>;
326
- }
327
- return part;
328
- });
329
- };
330
-
331
- return (
332
- <>
333
- <aside className="sidebar">
334
- <div className="agent-identity-card">
335
- <div className="agent-avatar">
336
- <NyxoraLogo size={48} />
337
- </div>
338
- <div className="agent-info">
339
- <div className="agent-name">Nyxora AI</div>
340
- <div className="agent-status">
341
- <span className="status-dot"></span> ONLINE
342
- </div>
343
- </div>
344
- </div>
345
-
346
- <div className="sidebar-scroll-area">
347
- <nav className="sidebar-nav" style={{ paddingTop: '16px' }}>
348
- <div
349
- className="nav-item"
350
- onClick={createNewSession}
351
- >
352
- <Plus size={15} className="nav-icon" /> New Chat
353
- </div>
354
- <div
355
- className={`nav-item ${currentView === 'overview' ? 'active' : ''}`}
356
- onClick={() => setCurrentView('overview')}
357
- >
358
- <LayoutDashboard size={15} className="nav-icon" /> Overview
359
- </div>
360
- <div
361
- className={`nav-item ${currentView === 'skills' ? 'active' : ''}`}
362
- onClick={() => setCurrentView('skills')}
363
- >
364
- <Zap size={15} className="nav-icon" /> Web3 Skills
365
- </div>
366
- <div
367
- className={`nav-item ${currentView === 'osskills' ? 'active' : ''}`}
368
- onClick={() => setCurrentView('osskills')}
369
- >
370
- <Terminal size={15} className="nav-icon" /> OS Skills
371
- </div>
372
- <div
373
- className={`nav-item ${currentView === 'settings' ? 'active' : ''}`}
374
- onClick={() => setCurrentView('settings')}
375
- >
376
- <SettingsIcon size={15} className="nav-icon" /> Settings
377
- </div>
378
- </nav>
379
-
380
- <div className="sidebar-section">
381
- <span>Recent</span>
382
- </div>
383
- <nav className="sidebar-nav sessions-list">
384
- {chatSessions.map((session) => (
385
- <div
386
- key={session.id}
387
- className={`nav-item session-item ${activeSessionId === session.id && currentView === 'chat' ? 'active' : ''}`}
388
- onClick={() => {
389
- setActiveSessionId(session.id);
390
- setCurrentView('chat');
391
- }}
392
- >
393
- <div style={{ display: 'flex', alignItems: 'center', gap: '8px', overflow: 'hidden', flex: 1 }}>
394
- <MessageSquare size={14} className="nav-icon" />
395
- <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', fontSize: '0.85rem' }}>
396
- {session.title}
397
- </span>
398
- </div>
399
- <div style={{ display: 'flex', gap: '4px' }}>
400
- <button className="delete-session-btn" onClick={(e) => { e.stopPropagation(); setEditingSessionId(session.id); setEditSessionTitle(session.title); }} title="Rename Session">
401
- <Edit2 size={12} />
402
- </button>
403
- <button className="delete-session-btn" onClick={(e) => deleteSession(session.id, e)} title="Delete Session">
404
- <Trash2 size={14} />
405
- </button>
406
- </div>
407
- </div>
408
- ))}
409
- </nav>
410
- </div>
411
- </aside>
412
-
413
- <main className="main-content">
414
- <header className="topbar">
415
- <div className="topbar-left">
416
- <span>Nyxora</span>
417
- <span style={{color: '#3b82f6'}}>•</span>
418
- <span style={{color: '#fff'}}>Chat</span>
419
- </div>
420
-
421
- <div className="topbar-right">
422
- {!config ? (
423
- <span style={{ color: '#f59e0b', fontSize: '0.85rem', display: 'flex', alignItems: 'center', gap: '6px' }}>
424
- <span className="dot" style={{ background: '#f59e0b', animation: 'pulse 1s infinite' }}></span> Waiting for API Gateway...
425
- </span>
426
- ) : (
427
- <>
428
- <NetworkSelector
429
- value={config.agent.default_chain}
430
- onChange={(chain) => updateConfig({ ...config, agent: { ...config.agent, default_chain: chain }})}
431
- />
432
- </>
433
- )}
434
- </div>
435
- </header>
436
-
437
- {currentView === 'overview' ? (
438
- <Overview config={config} />
439
- ) : currentView === 'skills' ? (
440
- <Skills />
441
- ) : currentView === 'osskills' ? (
442
- <OsSkills />
443
- ) : currentView === 'settings' ? (
444
- <Settings config={config} onConfigChange={setConfig} />
445
- ) : (
446
- <div className="workspace-container">
447
- <div className="chat-wrapper" style={{ width: '100%', margin: '0 auto', maxWidth: '1000px' }}>
448
- <div className="chat-container">
449
- {messages.map((msg, idx) => {
450
- const handleCopy = () => {
451
- navigator.clipboard.writeText(msg.content);
452
- setCopiedIndex(idx);
453
- setTimeout(() => setCopiedIndex(null), 2000);
454
- };
455
-
456
- if (msg.role === 'user') {
457
- return (
458
- <div key={idx} className="message-wrapper user">
459
- <div className="message-bubble">{msg.content}</div>
460
- <button className="copy-btn" onClick={handleCopy} title="Copy message">
461
- {copiedIndex === idx ? <Check size={14} color="#a3be8c" /> : <Copy size={14} />}
462
- </button>
463
- </div>
464
- );
465
- }
466
- if (msg.role === 'assistant' && msg.content) {
467
- return (
468
- <div key={idx} className="message-wrapper agent">
469
- <div className="message-bubble">{renderMessageContent(msg.content)}</div>
470
- <button className="copy-btn" onClick={handleCopy} title="Copy message">
471
- {copiedIndex === idx ? <Check size={14} color="#a3be8c" /> : <Copy size={14} />}
472
- </button>
473
- </div>
474
- );
475
- }
476
- if (msg.role === 'assistant' && msg.tool_calls) {
477
- return (
478
- <div key={idx} className="message-wrapper agent">
479
- {msg.tool_calls.map((tool: any, tIdx: number) => (
480
- <div key={tIdx} className="tool-call">
481
- <Activity size={16} color="#22c55e" />
482
- Executing: <code>{tool.function.name}</code>
483
- </div>
484
- ))}
485
- </div>
486
- );
487
- }
488
- return null;
489
- })}
490
-
491
- {isLoading && (
492
- <div className="typing-indicator">
493
- <div className="dot"></div>
494
- <div className="dot"></div>
495
- <div className="dot"></div>
496
- </div>
497
- )}
498
- <div ref={messagesEndRef} />
499
- </div>
500
-
501
- <div className="input-area">
502
- <form className="input-form" onSubmit={handleSubmit}>
503
- <button
504
- type="button"
505
- className={`voice-button ${isListening ? 'listening' : ''} ${isSpeaking ? 'speaking' : ''} ${isVoiceMode && !isListening && !isSpeaking ? 'active-mode' : ''}`}
506
- onClick={toggleVoiceMode}
507
- title={isVoiceMode ? "Disable Voice Mode" : "Enable Hands-Free Voice Mode"}
508
- >
509
- <Mic size={20} />
510
- </button>
511
- <input
512
- type="text"
513
- className="chat-input"
514
- placeholder="Message Nyxora Agent (Enter to send)..."
515
- value={input}
516
- onChange={(e) => setInput(e.target.value)}
517
- disabled={isLoading}
518
- />
519
- <button type="submit" className="send-button" disabled={isLoading || !input.trim()}>
520
- <Send size={20} />
521
- </button>
522
- </form>
523
- <div className="trending-tokens">
524
- <span>Trending Tokens:</span>
525
- {trendingTokens.map((token, idx) => (
526
- <span
527
- key={idx}
528
- className="token-tag"
529
- onClick={() => setInput(`Tolong berikan analisis pasar terbaru untuk ${token}`)}
530
- title={`Click to analyze ${token}`}
531
- style={{ cursor: 'pointer' }}
532
- >
533
- {token}
534
- </span>
535
- ))}
536
- </div>
537
- </div>
538
- </div>
539
- </div>
540
- )}
541
- </main>
542
-
543
- {editingSessionId && (
544
- <div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(4px)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 9999 }}>
545
- <div style={{ background: '#1e1e24', borderRadius: '16px', padding: '24px', width: '400px', boxShadow: '0 10px 25px rgba(0,0,0,0.5)', border: '1px solid rgba(255,255,255,0.1)' }}>
546
- <h3 style={{ margin: '0 0 16px 0', fontSize: '1.2rem', color: '#e2e8f0', fontWeight: 500 }}>Rename this chat</h3>
547
- <input
548
- type="text"
549
- value={editSessionTitle}
550
- onChange={(e) => setEditSessionTitle(e.target.value)}
551
- onKeyDown={(e) => {
552
- if (e.key === 'Enter') renameSession(editingSessionId, editSessionTitle);
553
- if (e.key === 'Escape') setEditingSessionId(null);
554
- }}
555
- autoFocus
556
- style={{ width: '100%', background: 'transparent', color: '#fff', border: '1px solid #3f4451', borderRadius: '8px', padding: '14px 16px', fontSize: '0.95rem', outline: 'none', boxSizing: 'border-box' }}
557
- onFocus={(e) => e.target.style.borderColor = '#88c0d0'}
558
- onBlur={(e) => e.target.style.borderColor = '#3f4451'}
559
- />
560
- <div style={{ display: 'flex', justifyContent: 'flex-end', gap: '8px', marginTop: '24px' }}>
561
- <button
562
- onClick={() => setEditingSessionId(null)}
563
- style={{ background: 'transparent', border: 'none', color: '#a0aec0', cursor: 'pointer', padding: '10px 20px', borderRadius: '24px', fontWeight: 500, fontSize: '0.9rem' }}
564
- onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(255,255,255,0.05)'}
565
- onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
566
- >
567
- Cancel
568
- </button>
569
- <button
570
- onClick={() => renameSession(editingSessionId, editSessionTitle)}
571
- style={{ background: '#88c0d0', border: 'none', color: '#13131a', cursor: 'pointer', padding: '10px 20px', borderRadius: '24px', fontWeight: 600, fontSize: '0.9rem' }}
572
- onMouseEnter={(e) => e.currentTarget.style.opacity = '0.9'}
573
- onMouseLeave={(e) => e.currentTarget.style.opacity = '1'}
574
- >
575
- Rename
576
- </button>
577
- </div>
578
- </div>
579
- </div>
580
- )}
581
- </>
582
- );
583
- }
584
-
585
- export default App;
@@ -1,65 +0,0 @@
1
- import React from 'react';
2
- import { Wallet, CheckCircle2 } from 'lucide-react';
3
-
4
- interface BalanceWidgetProps {
5
- data: string;
6
- }
7
-
8
- const BalanceWidget: React.FC<BalanceWidgetProps> = ({ data }) => {
9
- // Try to parse "0.05 on sepolia"
10
- let amount = '0.00';
11
- let chain = 'Unknown';
12
-
13
- if (data.includes(' on ')) {
14
- const parts = data.split(' on ');
15
- const rawAmount = parts[0];
16
- chain = parts[1].toUpperCase();
17
-
18
- // Parse the number to limit decimal places to max 6
19
- const parsedNum = parseFloat(rawAmount);
20
- if (!isNaN(parsedNum)) {
21
- amount = parsedNum.toLocaleString('en-US', { maximumFractionDigits: 6 });
22
- } else {
23
- amount = rawAmount;
24
- }
25
- }
26
-
27
- return (
28
- <div style={{
29
- background: 'linear-gradient(135deg, rgba(30,41,59,0.8) 0%, rgba(15,23,42,0.9) 100%)',
30
- borderRadius: '24px',
31
- padding: '32px',
32
- border: '1px solid rgba(255,255,255,0.1)',
33
- boxShadow: '0 20px 40px -10px rgba(0,0,0,0.5)',
34
- width: '100%',
35
- maxWidth: '400px',
36
- margin: '0 auto',
37
- animation: 'fadeIn 0.5s ease-out forwards'
38
- }}>
39
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '32px' }}>
40
- <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
41
- <div style={{ background: 'rgba(59,130,246,0.2)', padding: '10px', borderRadius: '12px' }}>
42
- <Wallet size={24} color="#3b82f6" />
43
- </div>
44
- <span style={{ color: '#94a3b8', fontWeight: 500, fontSize: '0.9rem', letterSpacing: '1px' }}>WALLET BALANCE</span>
45
- </div>
46
- <div style={{ display: 'flex', alignItems: 'center', gap: '6px', background: 'rgba(34,197,94,0.1)', padding: '6px 12px', borderRadius: '20px' }}>
47
- <CheckCircle2 size={14} color="#4ade80" />
48
- <span style={{ color: '#4ade80', fontSize: '0.75rem', fontWeight: 600 }}>LIVE</span>
49
- </div>
50
- </div>
51
-
52
- <div style={{ marginBottom: '12px', display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', overflow: 'hidden' }}>
53
- <span style={{ fontSize: '2.5rem', fontWeight: 800, color: 'white', letterSpacing: '-1px', wordBreak: 'break-all' }}>{amount}</span>
54
- <span style={{ fontSize: '1.2rem', color: '#94a3b8', marginLeft: '8px', fontWeight: 500 }}>ETH</span>
55
- </div>
56
-
57
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '32px', paddingTop: '20px', borderTop: '1px solid rgba(255,255,255,0.05)' }}>
58
- <span style={{ color: '#64748b', fontSize: '0.85rem' }}>Network</span>
59
- <span style={{ color: '#e2e8f0', fontSize: '0.9rem', fontWeight: 600, fontFamily: 'monospace' }}>{chain}</span>
60
- </div>
61
- </div>
62
- );
63
- };
64
-
65
- export default BalanceWidget;