Skip to content

Commit 95cadd2

Browse files
authored
feat: add support for multi-tools (#37)
* feat: add support for multi-tools * update function names and more fixes * update client: add loader * add detailed comments * add logs * update deps' * update packages * update methods * fixes * update price tool
1 parent 2dd88a2 commit 95cadd2

File tree

29 files changed

+2545
-1854
lines changed

29 files changed

+2545
-1854
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const suiAgent = new Agent('your_atoma_sdk_bearer_token');
5353
// Use the agent to process queries
5454
async function processQuery(query: string) {
5555
try {
56-
const result = await suiAgent.SuperVisorAgent(query);
56+
const result = await suiAgent.processUserQueryPipeline(query);
5757
return result;
5858
} catch (error) {
5959
console.error('Error processing query:', error);
@@ -72,7 +72,7 @@ const suiAgent = new Agent(config.atomaSdkBearerAuth);
7272
app.post('/query', async (req, res) => {
7373
try {
7474
const { query } = req.body;
75-
const result = await suiAgent.SuperVisorAgent(query);
75+
const result = await suiAgent.processUserQueryPipeline(query);
7676
res.json(result);
7777
} catch (error) {
7878
res.status(500).json({ error: 'Internal server error' });
Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,59 @@
1-
const PulseLoader = () => (
2-
<div className="flex space-x-2">
3-
<div className="w-4 h-4 bg-blue-500 rounded-full animate-pulse"></div>
4-
<div className="w-4 h-4 bg-blue-500 rounded-full animate-pulse [animation-delay:0.2s]"></div>
5-
<div className="w-4 h-4 bg-blue-500 rounded-full animate-pulse [animation-delay:0.4s]"></div>
6-
</div>
7-
);
1+
import React, { useEffect, useState } from 'react';
2+
3+
const PulseLoader = () => {
4+
const [dots, setDots] = useState('');
5+
6+
useEffect(() => {
7+
const interval = setInterval(() => {
8+
setDots((prev) => (prev.length >= 3 ? '' : prev + '.'));
9+
}, 500);
10+
11+
return () => clearInterval(interval);
12+
}, []);
13+
14+
return (
15+
<div className="flex flex-col items-center space-y-4 p-4">
16+
<div className="flex items-center space-x-3">
17+
<div className="relative w-10 h-10">
18+
{/* Circular pulse animation */}
19+
<div className="absolute inset-0 bg-blue-500/20 rounded-full animate-ping" />
20+
<div className="relative w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center">
21+
<svg
22+
className="w-6 h-6 text-white"
23+
fill="none"
24+
stroke="currentColor"
25+
viewBox="0 0 24 24"
26+
>
27+
<path
28+
strokeLinecap="round"
29+
strokeLinejoin="round"
30+
strokeWidth={2}
31+
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
32+
/>
33+
</svg>
34+
</div>
35+
</div>
36+
<div className="flex flex-col">
37+
<span className="text-gray-700 font-medium">Atomasage</span>
38+
<div className="flex items-center space-x-1">
39+
<div className="w-2 h-2 bg-green-500 rounded-full" />
40+
<span className="text-gray-500 text-sm">thinking{dots}</span>
41+
</div>
42+
</div>
43+
</div>
44+
45+
{/* Typing indicator */}
46+
<div className="flex space-x-1">
47+
{[0, 1, 2].map((i) => (
48+
<div
49+
key={i}
50+
className={`w-2 h-2 bg-blue-500 rounded-full animate-bounce`}
51+
style={{ animationDelay: `${i * 200}ms` }}
52+
/>
53+
))}
54+
</div>
55+
</div>
56+
);
57+
};
858

959
export default PulseLoader;

apps/client/app/utils/JSONFormatter.ts

Lines changed: 123 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,30 @@ class JSONFormatter {
55
* @returns A string containing the formatted HTML.
66
*/
77
public static format(json: any): string {
8-
return this.formatJSON(json, 0);
8+
return `
9+
<div class="json-formatter" style="
10+
max-width: 100%;
11+
overflow-wrap: break-word;
12+
word-wrap: break-word;
13+
word-break: break-word;
14+
">
15+
${this.formatJSON(json, 0)}
16+
</div>
17+
`;
18+
}
19+
20+
/**
21+
* Transforms camelCase, snake_case, and PascalCase text to space-separated words
22+
* @param text - The text to transform
23+
* @returns The transformed text
24+
*/
25+
private static transformKey(text: string): string {
26+
return text
27+
.replace(/([A-Z])/g, ' $1')
28+
.replace(/_/g, ' ')
29+
.trim()
30+
.toLowerCase()
31+
.replace(/^\w/, (c) => c.toUpperCase());
932
}
1033

1134
/**
@@ -31,20 +54,42 @@ class JSONFormatter {
3154
* @returns A string containing the formatted HTML.
3255
*/
3356
private static formatArray(array: any[], indentLevel: number): string {
34-
let html = '';
57+
if (array.length === 0) {
58+
return '<div style="color: #666;">(empty array)</div>';
59+
}
60+
61+
let html = '<div class="json-array" style="width: 100%;">';
3562
array.forEach((item, index) => {
3663
if (typeof item === 'object' && item !== null) {
37-
// Add a styled section for arrays of objects
38-
html += `<div style="margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 5px; background: #f9f9f9;">
39-
${this.formatJSON(item, indentLevel + 1)}
40-
</div>`;
64+
html += `
65+
<div class="json-object-container" style="
66+
margin: 12px 0;
67+
padding: 16px;
68+
border: 1px solid #ddd;
69+
border-radius: 8px;
70+
background: #f9f9f9;
71+
overflow-wrap: break-word;
72+
word-wrap: break-word;
73+
word-break: break-word;
74+
">
75+
${this.formatJSON(item, indentLevel + 1)}
76+
</div>
77+
`;
4178
} else {
42-
// Regular array items (non-objects)
43-
html += `<div style="margin-left: ${
44-
indentLevel * 20
45-
}px;">- ${this.formatJSON(item, indentLevel + 1)}</div>`;
79+
html += `
80+
<div class="json-array-item" style="
81+
padding: 4px 0;
82+
margin-left: ${indentLevel * 24}px;
83+
display: flex;
84+
align-items: flex-start;
85+
">
86+
<span style="margin-right: 8px;">•</span>
87+
<div style="flex: 1;">${this.formatJSON(item, indentLevel + 1)}</div>
88+
</div>
89+
`;
4690
}
4791
});
92+
html += '</div>';
4893
return html;
4994
}
5095

@@ -55,13 +100,64 @@ class JSONFormatter {
55100
* @returns A string containing the formatted HTML.
56101
*/
57102
private static formatObject(obj: { [key: string]: any }, indentLevel: number): string {
58-
let html = '';
103+
if (Object.keys(obj).length === 0) {
104+
return '<div style="color: #666;">(empty object)</div>';
105+
}
106+
107+
let html = '<div class="json-object" style="width: 100%;">';
59108
const keys = Object.keys(obj);
60109
keys.forEach((key, index) => {
61-
html += `<div style="margin-left: ${indentLevel * 20}px;">
62-
<strong>${key}:</strong> ${this.formatJSON(obj[key], indentLevel + 1)}
63-
</div>`;
110+
const value = obj[key];
111+
const isNested = typeof value === 'object' && value !== null;
112+
113+
if (isNested) {
114+
// Nested objects/arrays get their own block
115+
html += `
116+
<div class="json-property nested" style="
117+
padding: 8px 0;
118+
margin-left: ${indentLevel * 24}px;
119+
">
120+
<div style="
121+
font-weight: bold;
122+
margin-bottom: 8px;
123+
">${this.transformKey(key)}:</div>
124+
<div style="
125+
padding-left: 24px;
126+
">
127+
${this.formatJSON(value, indentLevel + 1)}
128+
</div>
129+
</div>
130+
`;
131+
} else {
132+
// Primitive values stay inline
133+
html += `
134+
<div class="json-property" style="
135+
padding: 8px 0;
136+
margin-left: ${indentLevel * 24}px;
137+
display: flex;
138+
flex-wrap: wrap;
139+
align-items: flex-start;
140+
">
141+
<strong style="
142+
min-width: 120px;
143+
max-width: 100%;
144+
margin-right: 12px;
145+
margin-bottom: 4px;
146+
">${this.transformKey(key)}:</strong>
147+
<div style="
148+
flex: 1;
149+
min-width: 200px;
150+
overflow-wrap: break-word;
151+
word-wrap: break-word;
152+
word-break: break-word;
153+
">
154+
${this.formatJSON(value, indentLevel + 1)}
155+
</div>
156+
</div>
157+
`;
158+
}
64159
});
160+
html += '</div>';
65161
return html;
66162
}
67163

@@ -72,15 +168,24 @@ class JSONFormatter {
72168
* @returns A string containing the formatted HTML.
73169
*/
74170
private static formatPrimitive(value: any, indentLevel: number): string {
171+
const style = `
172+
color: #444;
173+
font-family: monospace;
174+
overflow-wrap: break-word;
175+
word-wrap: break-word;
176+
word-break: break-word;
177+
`;
178+
75179
if (typeof value === 'string') {
76-
return `<span>"${value}"</span>`; // Wrap strings in quotes for clarity
180+
return `<span style="${style}">"${value}"</span>`;
77181
} else if (value === null) {
78-
return '<span>null</span>'; // Handle null values
182+
return `<span style="${style}">null</span>`;
79183
} else if (typeof value === 'undefined') {
80-
return '<span>undefined</span>'; // Handle undefined values
184+
return `<span style="${style}">undefined</span>`;
81185
} else {
82-
return `<span>${value.toString()}</span>`; // Numbers, booleans, etc.
186+
return `<span style="${style}">${value.toString()}</span>`;
83187
}
84188
}
85189
}
190+
86191
export default JSONFormatter;

apps/web/src/controllers/conversation.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class ConversationController {
3939
res.status(400).json({ error: 'Invalid conversation ID' });
4040
return;
4141
}
42-
const result = await suiAgent.SuperVisorAgent(message, walletAddress);
42+
const result = await suiAgent.processUserQueryPipeline(message, walletAddress);
4343
const newMessage = await this.messageService.createMessage({
4444
sender: sender ? sender : 'user',
4545
walletAddress,

apps/web/src/logs/atomaHealth.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import https from 'https';
2+
import Atoma from '@atoma-agents/sui-agent/src/config/atoma';
3+
4+
interface ChatResponse {
5+
model: string;
6+
choices: Array<{
7+
message: {
8+
role: string;
9+
content: string;
10+
};
11+
}>;
12+
}
13+
14+
// Function to check API availability
15+
const checkApiAvailability = (): Promise<boolean> => {
16+
return new Promise((resolve) => {
17+
const req = https.get('https://api.atoma.network/health', (res) => {
18+
resolve(res.statusCode === 200);
19+
});
20+
21+
req.on('error', () => {
22+
resolve(false);
23+
});
24+
25+
req.end();
26+
});
27+
};
28+
29+
// Function to create a timeout promise
30+
const timeoutPromise = (ms: number): Promise<never> =>
31+
new Promise((_, reject) => setTimeout(() => reject(new Error('Request timed out')), ms));
32+
33+
interface AtomaError {
34+
statusCode: number;
35+
body: string;
36+
contentType: string;
37+
rawResponse: unknown;
38+
}
39+
40+
// Atoma SDK health check
41+
export const checkAtomaSDK = async (bearerAuth: string): Promise<void> => {
42+
const atomaSDK = new Atoma(bearerAuth);
43+
44+
try {
45+
console.log('\n=== Atoma SDK Diagnostic Check ===');
46+
console.log(`Bearer Token length: ${bearerAuth.length} characters`);
47+
console.log(
48+
`Bearer Token: ${bearerAuth.substring(0, 4)}...${bearerAuth.substring(bearerAuth.length - 4)}`
49+
);
50+
51+
const apiAvailable = await checkApiAvailability();
52+
console.log(`API Health Check: ${apiAvailable ? 'OK' : 'Failed'}`);
53+
54+
if (!apiAvailable) {
55+
throw new Error('API endpoint is not available');
56+
}
57+
58+
console.log('\nAttempting to connect to Atoma API...');
59+
60+
const result = (await Promise.race([
61+
atomaSDK.atomaChat([
62+
{
63+
role: 'user',
64+
content: 'Hi, are you there?'
65+
}
66+
]),
67+
timeoutPromise(30000)
68+
])) as ChatResponse;
69+
70+
console.log('=== Chat Completion Response ===');
71+
console.log(`Timestamp: ${new Date().toISOString()}`);
72+
console.log(`Model: ${result.model}`);
73+
console.log('\nResponse Content:');
74+
result.choices.forEach((choice, index: number) => {
75+
console.log(`Choice ${index + 1}:`);
76+
console.log(` Role: ${choice.message.role}`);
77+
console.log(` Content: ${choice.message.content}`);
78+
});
79+
console.log('\nAtoma SDK Check Complete ✅');
80+
} catch (error) {
81+
console.error('\n=== Atoma SDK Check Error ===');
82+
if (error && typeof error === 'object' && 'rawResponse' in error) {
83+
const atomaError = error as AtomaError;
84+
console.error(`Status Code: ${atomaError.statusCode}`);
85+
console.error(`Response Body: ${atomaError.body}`);
86+
console.error(`Content Type: ${atomaError.contentType}`);
87+
88+
switch (atomaError.statusCode) {
89+
case 402:
90+
console.error('\nBalance Error:');
91+
console.error('Your account has insufficient balance to make this request.');
92+
console.error('\nSuggested actions:');
93+
console.error('1. Check your account balance at https://atoma.network');
94+
console.error('2. Add credits to your account');
95+
console.error('3. Consider using a different model with lower cost');
96+
console.error('4. Contact support if you believe this is an error');
97+
break;
98+
99+
case 500:
100+
console.error('\nPossible issues:');
101+
console.error('1. Invalid or expired bearer token');
102+
console.error('2. Server-side issue with the model');
103+
console.error('3. Rate limiting or quota exceeded');
104+
console.error('\nSuggested actions:');
105+
console.error('- Verify your bearer token is valid');
106+
console.error('- Try a different model');
107+
console.error('- Check your API usage quota');
108+
console.error('- Contact support if the issue persists');
109+
break;
110+
}
111+
}
112+
console.error('\nFull Error Stack:', error);
113+
console.error('\nAtoma SDK Check Failed ❌');
114+
}
115+
};

0 commit comments

Comments
 (0)