nyxora 1.0.2 → 1.0.4
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/package.json +10 -1
- package/.env.example +0 -17
- package/dashboard/eslint.config.js +0 -22
- package/dashboard/index.html +0 -13
- package/dashboard/package-lock.json +0 -2737
- package/dashboard/package.json +0 -31
- package/dashboard/public/favicon.svg +0 -1
- package/dashboard/public/icons.svg +0 -24
- package/dashboard/src/App.css +0 -184
- package/dashboard/src/App.tsx +0 -485
- package/dashboard/src/BalanceWidget.tsx +0 -65
- package/dashboard/src/MarketWidget.tsx +0 -73
- package/dashboard/src/Memory.tsx +0 -109
- package/dashboard/src/Overview.tsx +0 -156
- package/dashboard/src/Settings.tsx +0 -213
- package/dashboard/src/Skills.tsx +0 -97
- package/dashboard/src/SwapWidget.tsx +0 -130
- package/dashboard/src/TransactionWidget.tsx +0 -86
- package/dashboard/src/assets/hero.png +0 -0
- package/dashboard/src/assets/react.svg +0 -1
- package/dashboard/src/assets/vite.svg +0 -1
- package/dashboard/src/index.css +0 -508
- package/dashboard/src/main.tsx +0 -10
- package/dashboard/src/overview.css +0 -304
- package/dashboard/tsconfig.app.json +0 -25
- package/dashboard/tsconfig.json +0 -7
- package/dashboard/tsconfig.node.json +0 -24
- package/dashboard/vite.config.ts +0 -7
- package/src/agent/reasoning.d.ts +0 -2
- package/src/agent/reasoning.d.ts.map +0 -1
- package/src/agent/reasoning.js +0 -97
- package/src/agent/reasoning.js.map +0 -1
- package/src/agent/reasoning.ts +0 -232
- package/src/config/parser.d.ts +0 -17
- package/src/config/parser.d.ts.map +0 -1
- package/src/config/parser.js +0 -26
- package/src/config/parser.js.map +0 -1
- package/src/config/parser.ts +0 -47
- package/src/config/paths.ts +0 -33
- package/src/gateway/cli.d.ts +0 -3
- package/src/gateway/cli.d.ts.map +0 -1
- package/src/gateway/cli.js +0 -80
- package/src/gateway/cli.js.map +0 -1
- package/src/gateway/cli.ts +0 -69
- package/src/gateway/server.ts +0 -135
- package/src/gateway/telegram.ts +0 -47
- package/src/gateway/test.ts +0 -16
- package/src/gateway/tracker.ts +0 -70
- package/src/memory/logger.d.ts +0 -18
- package/src/memory/logger.d.ts.map +0 -1
- package/src/memory/logger.js +0 -51
- package/src/memory/logger.js.map +0 -1
- package/src/memory/logger.ts +0 -57
- package/src/web3/config.d.ts +0 -793
- package/src/web3/config.d.ts.map +0 -1
- package/src/web3/config.js +0 -81
- package/src/web3/config.js.map +0 -1
- package/src/web3/config.ts +0 -51
- package/src/web3/skills/getBalance.d.ts +0 -25
- package/src/web3/skills/getBalance.d.ts.map +0 -1
- package/src/web3/skills/getBalance.js +0 -46
- package/src/web3/skills/getBalance.js.map +0 -1
- package/src/web3/skills/getBalance.ts +0 -48
- package/src/web3/skills/getPrice.ts +0 -43
- package/src/web3/skills/swapToken.ts +0 -69
- package/src/web3/skills/transfer.ts +0 -47
- package/tsconfig.json +0 -13
package/dashboard/src/App.tsx
DELETED
|
@@ -1,485 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import { Send, Bot, Terminal, Activity, MessageSquare, LayoutDashboard, Settings as SettingsIcon, Compass, Database, Mic } from 'lucide-react';
|
|
3
|
-
import Overview from './Overview';
|
|
4
|
-
import Memory from './Memory';
|
|
5
|
-
import Settings from './Settings';
|
|
6
|
-
import Skills from './Skills';
|
|
7
|
-
import BalanceWidget from './BalanceWidget';
|
|
8
|
-
import TransactionWidget from './TransactionWidget';
|
|
9
|
-
import MarketWidget from './MarketWidget';
|
|
10
|
-
import SwapWidget from './SwapWidget';
|
|
11
|
-
import './index.css';
|
|
12
|
-
|
|
13
|
-
interface Message {
|
|
14
|
-
role: 'user' | 'assistant' | 'tool' | 'system';
|
|
15
|
-
content: string;
|
|
16
|
-
name?: string;
|
|
17
|
-
tool_calls?: any[];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface Config {
|
|
21
|
-
agent: { name: string; default_chain: string };
|
|
22
|
-
llm: { provider: string; model: string; temperature: number };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function App() {
|
|
26
|
-
const [currentView, setCurrentView] = useState<'chat' | 'overview' | 'memory' | 'settings' | 'skills'>('chat');
|
|
27
|
-
const [messages, setMessages] = useState<Message[]>([]);
|
|
28
|
-
const [input, setInput] = useState('');
|
|
29
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
30
|
-
const [isListening, setIsListening] = useState(false);
|
|
31
|
-
const [isVoiceMode, setIsVoiceMode] = useState(false);
|
|
32
|
-
const isVoiceModeRef = useRef(false);
|
|
33
|
-
const [isSpeaking, setIsSpeaking] = useState(false);
|
|
34
|
-
const [config, setConfig] = useState<Config | null>(null);
|
|
35
|
-
const [chatWidth, setChatWidth] = useState(70);
|
|
36
|
-
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
37
|
-
const recognitionRef = useRef<any>(null);
|
|
38
|
-
const workspaceRef = useRef<HTMLDivElement>(null);
|
|
39
|
-
const isDragging = useRef(false);
|
|
40
|
-
|
|
41
|
-
useEffect(() => {
|
|
42
|
-
const handleMouseMove = (e: MouseEvent) => {
|
|
43
|
-
if (!isDragging.current || !workspaceRef.current) return;
|
|
44
|
-
const rect = workspaceRef.current.getBoundingClientRect();
|
|
45
|
-
const newWidth = ((e.clientX - rect.left) / rect.width) * 100;
|
|
46
|
-
if (newWidth > 20 && newWidth < 80) {
|
|
47
|
-
setChatWidth(newWidth);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const handleMouseUp = () => {
|
|
52
|
-
isDragging.current = false;
|
|
53
|
-
document.removeEventListener('mousemove', handleMouseMove);
|
|
54
|
-
document.removeEventListener('mouseup', handleMouseUp);
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// Attach them to document only when dragging is active
|
|
58
|
-
// We will attach them in handleMouseDown
|
|
59
|
-
}, []);
|
|
60
|
-
|
|
61
|
-
const handleMouseDown = () => {
|
|
62
|
-
isDragging.current = true;
|
|
63
|
-
|
|
64
|
-
const handleMouseMove = (e: MouseEvent) => {
|
|
65
|
-
if (!isDragging.current || !workspaceRef.current) return;
|
|
66
|
-
const rect = workspaceRef.current.getBoundingClientRect();
|
|
67
|
-
const newWidth = ((e.clientX - rect.left) / rect.width) * 100;
|
|
68
|
-
if (newWidth > 25 && newWidth < 75) {
|
|
69
|
-
setChatWidth(newWidth);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const handleMouseUp = () => {
|
|
74
|
-
isDragging.current = false;
|
|
75
|
-
document.removeEventListener('mousemove', handleMouseMove);
|
|
76
|
-
document.removeEventListener('mouseup', handleMouseUp);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
document.addEventListener('mousemove', handleMouseMove);
|
|
80
|
-
document.addEventListener('mouseup', handleMouseUp);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
// Initialize Speech Recognition
|
|
85
|
-
const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;
|
|
86
|
-
if (SpeechRecognition) {
|
|
87
|
-
recognitionRef.current = new SpeechRecognition();
|
|
88
|
-
recognitionRef.current.continuous = false;
|
|
89
|
-
recognitionRef.current.interimResults = false;
|
|
90
|
-
recognitionRef.current.lang = 'en-US';
|
|
91
|
-
|
|
92
|
-
recognitionRef.current.onresult = (event: any) => {
|
|
93
|
-
const transcript = event.results[0][0].transcript;
|
|
94
|
-
setInput(transcript);
|
|
95
|
-
setIsListening(false);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
recognitionRef.current.onerror = (event: any) => {
|
|
99
|
-
console.error("Speech recognition error", event.error);
|
|
100
|
-
setIsListening(false);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
recognitionRef.current.onend = () => {
|
|
104
|
-
setIsListening(false);
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
}, []);
|
|
108
|
-
|
|
109
|
-
const startListening = () => {
|
|
110
|
-
try {
|
|
111
|
-
recognitionRef.current?.start();
|
|
112
|
-
setIsListening(true);
|
|
113
|
-
} catch (e) {}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const speak = (text: string) => {
|
|
117
|
-
if (!('speechSynthesis' in window)) return;
|
|
118
|
-
window.speechSynthesis.cancel();
|
|
119
|
-
|
|
120
|
-
// Clean markdown before speaking
|
|
121
|
-
const cleanText = text.replace(/[*#_`]/g, '');
|
|
122
|
-
const utterance = new SpeechSynthesisUtterance(cleanText);
|
|
123
|
-
utterance.lang = 'id-ID';
|
|
124
|
-
|
|
125
|
-
utterance.onstart = () => setIsSpeaking(true);
|
|
126
|
-
utterance.onend = () => {
|
|
127
|
-
setIsSpeaking(false);
|
|
128
|
-
// Auto-listen if in voice mode
|
|
129
|
-
if (isVoiceModeRef.current) {
|
|
130
|
-
startListening();
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
window.speechSynthesis.speak(utterance);
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const toggleVoiceMode = () => {
|
|
137
|
-
const newMode = !isVoiceMode;
|
|
138
|
-
setIsVoiceMode(newMode);
|
|
139
|
-
isVoiceModeRef.current = newMode;
|
|
140
|
-
|
|
141
|
-
if (newMode) {
|
|
142
|
-
startListening();
|
|
143
|
-
} else {
|
|
144
|
-
recognitionRef.current?.stop();
|
|
145
|
-
setIsListening(false);
|
|
146
|
-
window.speechSynthesis.cancel();
|
|
147
|
-
setIsSpeaking(false);
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const fetchHistory = async () => {
|
|
152
|
-
try {
|
|
153
|
-
const res = await fetch('http://localhost:3000/api/history');
|
|
154
|
-
if (res.ok) {
|
|
155
|
-
const data = await res.json();
|
|
156
|
-
setMessages(data);
|
|
157
|
-
} else {
|
|
158
|
-
setTimeout(fetchHistory, 2000);
|
|
159
|
-
}
|
|
160
|
-
} catch (err) {
|
|
161
|
-
console.warn('Backend not ready, retrying history fetch in 2s...');
|
|
162
|
-
setTimeout(fetchHistory, 2000);
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const fetchConfig = async () => {
|
|
167
|
-
try {
|
|
168
|
-
const res = await fetch('http://localhost:3000/api/config');
|
|
169
|
-
if (res.ok) {
|
|
170
|
-
const data = await res.json();
|
|
171
|
-
setConfig(data);
|
|
172
|
-
} else {
|
|
173
|
-
setTimeout(fetchConfig, 2000);
|
|
174
|
-
}
|
|
175
|
-
} catch (err) {
|
|
176
|
-
console.warn('Backend not ready, retrying config fetch in 2s...');
|
|
177
|
-
setTimeout(fetchConfig, 2000);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const updateConfig = async (newConfig: Config) => {
|
|
182
|
-
setConfig(newConfig);
|
|
183
|
-
try {
|
|
184
|
-
await fetch('http://localhost:3000/api/config', {
|
|
185
|
-
method: 'POST',
|
|
186
|
-
headers: { 'Content-Type': 'application/json' },
|
|
187
|
-
body: JSON.stringify(newConfig),
|
|
188
|
-
});
|
|
189
|
-
} catch (err) {
|
|
190
|
-
console.error('Failed to save config', err);
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
fetchHistory();
|
|
196
|
-
fetchConfig();
|
|
197
|
-
}, []);
|
|
198
|
-
|
|
199
|
-
useEffect(() => {
|
|
200
|
-
// Adding a slight timeout to ensure DOM is fully rendered before scrolling
|
|
201
|
-
setTimeout(() => {
|
|
202
|
-
messagesEndRef.current?.scrollIntoView({ behavior: 'auto' });
|
|
203
|
-
}, 10);
|
|
204
|
-
}, [messages, isLoading, currentView]);
|
|
205
|
-
|
|
206
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
207
|
-
e.preventDefault();
|
|
208
|
-
if (!input.trim() || isLoading) return;
|
|
209
|
-
|
|
210
|
-
const userMsg = input.trim();
|
|
211
|
-
setInput('');
|
|
212
|
-
setIsLoading(true);
|
|
213
|
-
|
|
214
|
-
setMessages(prev => [...prev, { role: 'user', content: userMsg }]);
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
const res = await fetch('http://localhost:3000/api/chat', {
|
|
218
|
-
method: 'POST',
|
|
219
|
-
headers: { 'Content-Type': 'application/json' },
|
|
220
|
-
body: JSON.stringify({ message: userMsg }),
|
|
221
|
-
});
|
|
222
|
-
const data = await res.json();
|
|
223
|
-
await fetchHistory();
|
|
224
|
-
|
|
225
|
-
// Trigger TTS if in voice mode
|
|
226
|
-
if (isVoiceModeRef.current && data.response) {
|
|
227
|
-
speak(data.response);
|
|
228
|
-
}
|
|
229
|
-
} catch (error) {
|
|
230
|
-
console.error('Error sending message:', error);
|
|
231
|
-
} finally {
|
|
232
|
-
setIsLoading(false);
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// Determine active widget for Canvas based on the latest tool call result
|
|
237
|
-
let activeWidget: React.ReactNode = null;
|
|
238
|
-
const latestToolMessage = [...messages].reverse().find(m => m.role === 'tool');
|
|
239
|
-
|
|
240
|
-
if (latestToolMessage && latestToolMessage.name) {
|
|
241
|
-
if (latestToolMessage.name === 'get_balance') {
|
|
242
|
-
activeWidget = <BalanceWidget data={latestToolMessage.content} />;
|
|
243
|
-
} else if (latestToolMessage.name === 'transfer_native') {
|
|
244
|
-
activeWidget = <TransactionWidget data={latestToolMessage.content} />;
|
|
245
|
-
} else if (latestToolMessage.name === 'get_price') {
|
|
246
|
-
activeWidget = <MarketWidget data={latestToolMessage.content} />;
|
|
247
|
-
} else if (latestToolMessage.name === 'swap_token') {
|
|
248
|
-
activeWidget = <SwapWidget data={latestToolMessage.content} />;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return (
|
|
253
|
-
<>
|
|
254
|
-
<aside className="sidebar">
|
|
255
|
-
<div className="agent-identity-card">
|
|
256
|
-
<div className="agent-avatar">
|
|
257
|
-
<Bot size={28} color="#3b82f6" />
|
|
258
|
-
</div>
|
|
259
|
-
<div className="agent-info">
|
|
260
|
-
<div className="agent-name">Nyxora AI</div>
|
|
261
|
-
<div className="agent-status">
|
|
262
|
-
<span className="status-dot"></span> ONLINE
|
|
263
|
-
</div>
|
|
264
|
-
</div>
|
|
265
|
-
</div>
|
|
266
|
-
|
|
267
|
-
<div className="sidebar-scroll-area">
|
|
268
|
-
<div className="sidebar-section">WORKSPACE</div>
|
|
269
|
-
<nav className="sidebar-nav">
|
|
270
|
-
<div
|
|
271
|
-
className={`nav-item ${currentView === 'chat' ? 'active' : ''}`}
|
|
272
|
-
onClick={() => setCurrentView('chat')}
|
|
273
|
-
>
|
|
274
|
-
<MessageSquare size={18} className="nav-icon" /> Chat
|
|
275
|
-
</div>
|
|
276
|
-
<div
|
|
277
|
-
className={`nav-item ${currentView === 'overview' ? 'active' : ''}`}
|
|
278
|
-
onClick={() => setCurrentView('overview')}
|
|
279
|
-
>
|
|
280
|
-
<LayoutDashboard size={18} className="nav-icon" /> Overview
|
|
281
|
-
</div>
|
|
282
|
-
</nav>
|
|
283
|
-
|
|
284
|
-
<div className="sidebar-section">KNOWLEDGE</div>
|
|
285
|
-
<nav className="sidebar-nav">
|
|
286
|
-
<div
|
|
287
|
-
className={`nav-item ${currentView === 'skills' ? 'active' : ''}`}
|
|
288
|
-
onClick={() => setCurrentView('skills')}
|
|
289
|
-
>
|
|
290
|
-
<Compass size={18} className="nav-icon" /> Web3 Skills
|
|
291
|
-
</div>
|
|
292
|
-
<div
|
|
293
|
-
className={`nav-item ${currentView === 'memory' ? 'active' : ''}`}
|
|
294
|
-
onClick={() => setCurrentView('memory')}
|
|
295
|
-
>
|
|
296
|
-
<Database size={18} className="nav-icon" /> Memory
|
|
297
|
-
</div>
|
|
298
|
-
</nav>
|
|
299
|
-
|
|
300
|
-
<div className="sidebar-section">SYSTEM</div>
|
|
301
|
-
<nav className="sidebar-nav">
|
|
302
|
-
<div
|
|
303
|
-
className={`nav-item ${currentView === 'settings' ? 'active' : ''}`}
|
|
304
|
-
onClick={() => setCurrentView('settings')}
|
|
305
|
-
>
|
|
306
|
-
<SettingsIcon size={18} className="nav-icon" /> Settings
|
|
307
|
-
</div>
|
|
308
|
-
</nav>
|
|
309
|
-
</div>
|
|
310
|
-
</aside>
|
|
311
|
-
|
|
312
|
-
<main className="main-content">
|
|
313
|
-
<header className="topbar">
|
|
314
|
-
<div className="topbar-left">
|
|
315
|
-
<span>Nyxora</span>
|
|
316
|
-
<span style={{color: '#3b82f6'}}>•</span>
|
|
317
|
-
<span style={{color: '#fff'}}>Chat</span>
|
|
318
|
-
</div>
|
|
319
|
-
|
|
320
|
-
<div className="topbar-right">
|
|
321
|
-
{!config ? (
|
|
322
|
-
<span style={{ color: '#f59e0b', fontSize: '0.85rem', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
323
|
-
<span className="dot" style={{ background: '#f59e0b', animation: 'pulse 1s infinite' }}></span> Waiting for API Gateway...
|
|
324
|
-
</span>
|
|
325
|
-
) : (
|
|
326
|
-
<>
|
|
327
|
-
<select
|
|
328
|
-
className="config-dropdown"
|
|
329
|
-
value={config.agent.default_chain}
|
|
330
|
-
onChange={(e) => updateConfig({ ...config, agent: { ...config.agent, default_chain: e.target.value }})}
|
|
331
|
-
>
|
|
332
|
-
<option value="sepolia">Sepolia (Testnet)</option>
|
|
333
|
-
<option value="base">Base</option>
|
|
334
|
-
<option value="ethereum">Ethereum</option>
|
|
335
|
-
<option value="bsc">BNB Smart Chain</option>
|
|
336
|
-
<option value="arbitrum">Arbitrum</option>
|
|
337
|
-
</select>
|
|
338
|
-
|
|
339
|
-
<select
|
|
340
|
-
className="config-dropdown"
|
|
341
|
-
value={config.llm.provider}
|
|
342
|
-
onChange={(e) => updateConfig({ ...config, llm: { ...config.llm, provider: e.target.value }})}
|
|
343
|
-
>
|
|
344
|
-
<option value="gemini">Google Gemini</option>
|
|
345
|
-
<option value="openai">OpenAI</option>
|
|
346
|
-
<option value="ollama">Local Ollama</option>
|
|
347
|
-
</select>
|
|
348
|
-
|
|
349
|
-
<select
|
|
350
|
-
className="config-dropdown"
|
|
351
|
-
value={config.llm.model}
|
|
352
|
-
onChange={(e) => updateConfig({ ...config, llm: { ...config.llm, model: e.target.value }})}
|
|
353
|
-
>
|
|
354
|
-
{config.llm.provider === 'gemini' && <option value="gemini-2.5-flash">Gemini 2.5 Flash</option>}
|
|
355
|
-
{config.llm.provider === 'gemini' && <option value="gemini-2.5-pro">Gemini 2.5 Pro</option>}
|
|
356
|
-
{config.llm.provider === 'openai' && <option value="gpt-4o">GPT-4o</option>}
|
|
357
|
-
{config.llm.provider === 'openai' && <option value="gpt-4o-mini">GPT-4o Mini</option>}
|
|
358
|
-
{config.llm.provider === 'ollama' && <option value="llama3">Llama 3</option>}
|
|
359
|
-
</select>
|
|
360
|
-
</>
|
|
361
|
-
)}
|
|
362
|
-
</div>
|
|
363
|
-
</header>
|
|
364
|
-
|
|
365
|
-
{currentView === 'overview' ? (
|
|
366
|
-
<Overview config={config} />
|
|
367
|
-
) : currentView === 'skills' ? (
|
|
368
|
-
<Skills />
|
|
369
|
-
) : currentView === 'memory' ? (
|
|
370
|
-
<Memory />
|
|
371
|
-
) : currentView === 'settings' ? (
|
|
372
|
-
<Settings config={config} onConfigChange={setConfig} />
|
|
373
|
-
) : (
|
|
374
|
-
<div className="workspace-container" ref={workspaceRef}>
|
|
375
|
-
<div className="chat-wrapper" style={{ width: `${chatWidth}%` }}>
|
|
376
|
-
<div className="chat-container">
|
|
377
|
-
{messages.map((msg, idx) => {
|
|
378
|
-
if (msg.role === 'user') {
|
|
379
|
-
return (
|
|
380
|
-
<div key={idx} className="message-wrapper user">
|
|
381
|
-
<div className="message-bubble">{msg.content}</div>
|
|
382
|
-
</div>
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
if (msg.role === 'assistant' && msg.content) {
|
|
386
|
-
return (
|
|
387
|
-
<div key={idx} className="message-wrapper agent">
|
|
388
|
-
<div className="message-bubble">{msg.content}</div>
|
|
389
|
-
</div>
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
if (msg.role === 'assistant' && msg.tool_calls) {
|
|
393
|
-
return (
|
|
394
|
-
<div key={idx} className="message-wrapper agent">
|
|
395
|
-
{msg.tool_calls.map((tool: any, tIdx: number) => (
|
|
396
|
-
<div key={tIdx} className="tool-call">
|
|
397
|
-
<Activity size={16} color="#22c55e" />
|
|
398
|
-
Executing: <code>{tool.function.name}</code>
|
|
399
|
-
</div>
|
|
400
|
-
))}
|
|
401
|
-
</div>
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
if (msg.role === 'tool') {
|
|
405
|
-
return (
|
|
406
|
-
<div key={idx} className="message-wrapper agent">
|
|
407
|
-
<div className="tool-call">
|
|
408
|
-
<Terminal size={16} color="#a78bfa" />
|
|
409
|
-
Result: {msg.content.substring(0, 60)}...
|
|
410
|
-
</div>
|
|
411
|
-
</div>
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
return null;
|
|
415
|
-
})}
|
|
416
|
-
|
|
417
|
-
{isLoading && (
|
|
418
|
-
<div className="typing-indicator">
|
|
419
|
-
<div className="dot"></div>
|
|
420
|
-
<div className="dot"></div>
|
|
421
|
-
<div className="dot"></div>
|
|
422
|
-
</div>
|
|
423
|
-
)}
|
|
424
|
-
<div ref={messagesEndRef} />
|
|
425
|
-
</div>
|
|
426
|
-
|
|
427
|
-
<div className="input-area">
|
|
428
|
-
<form className="input-form" onSubmit={handleSubmit}>
|
|
429
|
-
<button
|
|
430
|
-
type="button"
|
|
431
|
-
className={`voice-button ${isListening ? 'listening' : ''} ${isSpeaking ? 'speaking' : ''} ${isVoiceMode && !isListening && !isSpeaking ? 'active-mode' : ''}`}
|
|
432
|
-
onClick={toggleVoiceMode}
|
|
433
|
-
title={isVoiceMode ? "Disable Voice Mode" : "Enable Hands-Free Voice Mode"}
|
|
434
|
-
>
|
|
435
|
-
<Mic size={20} />
|
|
436
|
-
</button>
|
|
437
|
-
<input
|
|
438
|
-
type="text"
|
|
439
|
-
className="chat-input"
|
|
440
|
-
placeholder="Message Nyxora Agent (Enter to send)..."
|
|
441
|
-
value={input}
|
|
442
|
-
onChange={(e) => setInput(e.target.value)}
|
|
443
|
-
disabled={isLoading}
|
|
444
|
-
/>
|
|
445
|
-
<button type="submit" className="send-button" disabled={isLoading || !input.trim()}>
|
|
446
|
-
<Send size={20} />
|
|
447
|
-
</button>
|
|
448
|
-
</form>
|
|
449
|
-
</div>
|
|
450
|
-
</div>
|
|
451
|
-
|
|
452
|
-
<div className="resizer" onMouseDown={handleMouseDown} />
|
|
453
|
-
|
|
454
|
-
<div className="canvas-panel">
|
|
455
|
-
<div className="canvas-header">
|
|
456
|
-
<div className="canvas-title">
|
|
457
|
-
<Compass size={16} />
|
|
458
|
-
LIVE CANVAS
|
|
459
|
-
</div>
|
|
460
|
-
<div style={{ color: '#4ade80', fontSize: '0.75rem', fontWeight: 600, display: 'flex', alignItems: 'center', gap: '4px' }}>
|
|
461
|
-
<span style={{ width: '8px', height: '8px', background: '#4ade80', borderRadius: '50%', display: 'inline-block' }}></span>
|
|
462
|
-
A2UI CONNECTED
|
|
463
|
-
</div>
|
|
464
|
-
</div>
|
|
465
|
-
|
|
466
|
-
{activeWidget ? (
|
|
467
|
-
<div style={{ marginTop: '24px' }}>
|
|
468
|
-
{activeWidget}
|
|
469
|
-
</div>
|
|
470
|
-
) : (
|
|
471
|
-
<div className="canvas-empty">
|
|
472
|
-
<LayoutDashboard size={48} color="rgba(255,255,255,0.1)" />
|
|
473
|
-
<p>Awaiting agent interaction...</p>
|
|
474
|
-
<span style={{ fontSize: '0.8rem' }}>Ask the agent to check your balance or make a transfer.</span>
|
|
475
|
-
</div>
|
|
476
|
-
)}
|
|
477
|
-
</div>
|
|
478
|
-
</div>
|
|
479
|
-
)}
|
|
480
|
-
</main>
|
|
481
|
-
</>
|
|
482
|
-
);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
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;
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { TrendingUp, TrendingDown, Activity } from 'lucide-react';
|
|
3
|
-
|
|
4
|
-
interface MarketWidgetProps {
|
|
5
|
-
data: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const MarketWidget: React.FC<MarketWidgetProps> = ({ data }) => {
|
|
9
|
-
let parsedData = null;
|
|
10
|
-
try {
|
|
11
|
-
parsedData = JSON.parse(data);
|
|
12
|
-
} catch (e) {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (parsedData.error) {
|
|
17
|
-
return (
|
|
18
|
-
<div style={{ background: 'rgba(239, 68, 68, 0.1)', border: '1px solid rgba(239, 68, 68, 0.3)', padding: '16px', borderRadius: '16px' }}>
|
|
19
|
-
<p style={{ color: '#ef4444' }}>{parsedData.error}</p>
|
|
20
|
-
</div>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const { coin, priceUsd, change24h } = parsedData;
|
|
25
|
-
const isPositive = change24h >= 0;
|
|
26
|
-
const color = isPositive ? '#4ade80' : '#ef4444';
|
|
27
|
-
const bgColor = isPositive ? 'rgba(74, 222, 128, 0.1)' : 'rgba(239, 68, 68, 0.1)';
|
|
28
|
-
const Icon = isPositive ? TrendingUp : TrendingDown;
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div style={{
|
|
32
|
-
background: 'linear-gradient(145deg, rgba(30, 41, 59, 0.8) 0%, rgba(15, 23, 42, 0.8) 100%)',
|
|
33
|
-
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
34
|
-
borderRadius: '24px',
|
|
35
|
-
padding: '24px',
|
|
36
|
-
boxShadow: '0 10px 40px -10px rgba(0,0,0,0.5)'
|
|
37
|
-
}}>
|
|
38
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '24px' }}>
|
|
39
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
|
40
|
-
<div style={{ background: 'rgba(59, 130, 246, 0.2)', padding: '12px', borderRadius: '16px', border: '1px solid rgba(59, 130, 246, 0.3)' }}>
|
|
41
|
-
<Activity size={24} color="#3b82f6" />
|
|
42
|
-
</div>
|
|
43
|
-
<div>
|
|
44
|
-
<h3 style={{ margin: 0, fontSize: '1.2rem', color: 'white', textTransform: 'capitalize' }}>{coin}</h3>
|
|
45
|
-
<span style={{ fontSize: '0.85rem', color: '#94a3b8' }}>Live Price (USD)</span>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
<div style={{
|
|
49
|
-
background: bgColor,
|
|
50
|
-
color: color,
|
|
51
|
-
padding: '6px 12px',
|
|
52
|
-
borderRadius: '12px',
|
|
53
|
-
display: 'flex',
|
|
54
|
-
alignItems: 'center',
|
|
55
|
-
gap: '6px',
|
|
56
|
-
fontWeight: 600,
|
|
57
|
-
fontSize: '0.9rem'
|
|
58
|
-
}}>
|
|
59
|
-
<Icon size={16} />
|
|
60
|
-
{isPositive ? '+' : ''}{change24h.toFixed(2)}%
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
<div style={{ display: 'flex', alignItems: 'baseline', gap: '8px', overflow: 'hidden' }}>
|
|
65
|
-
<span style={{ fontSize: '3rem', fontWeight: 800, color: 'white', letterSpacing: '-1px', wordBreak: 'break-all' }}>
|
|
66
|
-
${priceUsd.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 6 })}
|
|
67
|
-
</span>
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export default MarketWidget;
|