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