Skip to content

Commit 882a86f

Browse files
committed
feat: update tool and prompts
1 parent be42c60 commit 882a86f

File tree

11 files changed

+372
-75
lines changed

11 files changed

+372
-75
lines changed

apps/client/app/components/sections/Messages.tsx

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,45 +13,104 @@ interface MessagesProps {
1313

1414
const Messages: React.FC<MessagesProps> = ({ messages }) => {
1515
const messagesEndRef = useRef<HTMLDivElement | null>(null);
16+
1617
useEffect(() => {
1718
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
1819
}, [messages]);
1920

21+
const formatJSONResponse = (jsonString: string) => {
22+
try {
23+
const parsed = JSON.parse(jsonString);
24+
if (Array.isArray(parsed)) {
25+
// Format array of responses
26+
return parsed.map(item => (
27+
<div className="mb-2">
28+
{item.reasoning && (
29+
<div className="text-gray-500 text-sm italic mb-1">
30+
{item.reasoning}
31+
</div>
32+
)}
33+
<div className="font-medium">
34+
{typeof item.response === 'object'
35+
? <pre className="bg-gray-50 rounded-md p-3 text-sm overflow-x-auto">
36+
{JSON.stringify(item.response, null, 2)}
37+
</pre>
38+
: <div className="text-base">{item.response}</div>
39+
}
40+
</div>
41+
</div>
42+
));
43+
}
44+
// Format single response object
45+
return (
46+
<div>
47+
{parsed.reasoning && (
48+
<div className="text-gray-500 text-sm italic mb-1">
49+
{parsed.reasoning}
50+
</div>
51+
)}
52+
<div className="font-medium">
53+
{typeof parsed.response === 'object'
54+
? <pre className="bg-gray-50 rounded-md p-3 text-sm overflow-x-auto">
55+
{JSON.stringify(parsed.response, null, 2)}
56+
</pre>
57+
: <div className="text-base">{parsed.response}</div>
58+
}
59+
</div>
60+
</div>
61+
);
62+
} catch (error) {
63+
return jsonString;
64+
}
65+
};
66+
2067
const processedMessages = messages.map((message) => {
2168
try {
22-
const parsed = JSON.parse(message.message);
23-
if (parsed && typeof parsed === 'object') {
24-
return { ...message, isHTML: true };
69+
if (message.sender === 'ai') {
70+
const parsed = JSON.parse(message.message);
71+
if (parsed && typeof parsed === 'object') {
72+
return {
73+
...message,
74+
message: message.message, // Keep original JSON for formatting
75+
isHTML: true
76+
};
77+
}
2578
}
2679
} catch (error) {
27-
// Not a JSON string, keep as is
80+
// Not a JSON string or parsing failed, keep as is
2881
}
29-
return { ...message, isHTML: false };
82+
return message;
3083
});
3184

3285
return (
33-
<div className="h-full overflow-y-auto relative">
34-
{processedMessages.map((message, index) => (
35-
<div
36-
key={index}
37-
className={`relative mb-3 p-3 rounded-md w-fit md:max-w-[40%] break-words opacity-100 ${
38-
message.sender === 'user'
39-
? 'bg-blue-500 text-white self-end ml-auto text-right'
40-
: 'bg-gray-300 text-black self-start mr-auto text-left'
41-
}`}
42-
>
43-
{message.isHTML ? (
86+
<div className="flex flex-col h-full overflow-hidden">
87+
<div className="flex-1 overflow-y-auto px-4 py-2 space-y-4">
88+
{processedMessages.map((message, index) => (
89+
<div
90+
key={index}
91+
className={`flex ${message.sender === 'user' ? 'justify-end' : 'justify-start'}`}
92+
>
4493
<div
45-
dangerouslySetInnerHTML={{
46-
__html: JSONFormatter.format(JSON.parse(message.message))
47-
}}
48-
/>
49-
) : (
50-
<div>{message.message}</div>
51-
)}
52-
</div>
53-
))}
54-
<div ref={messagesEndRef} />
94+
className={`relative p-4 rounded-lg max-w-[85%] md:max-w-[70%] ${
95+
message.sender === 'user'
96+
? 'bg-blue-500 text-white'
97+
: 'bg-gray-100 text-black'
98+
}`}
99+
>
100+
{message.isHTML ? (
101+
<div className="text-sm">
102+
{formatJSONResponse(message.message)}
103+
</div>
104+
) : (
105+
<div className="whitespace-pre-wrap break-words text-base">
106+
{message.message}
107+
</div>
108+
)}
109+
</div>
110+
</div>
111+
))}
112+
<div ref={messagesEndRef} />
113+
</div>
55114
</div>
56115
);
57116
};

apps/client/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,8 @@
2828
"react-icons": "^5.4.0",
2929
"tailwindcss": "3.3.2",
3030
"typescript": "5.1.3"
31+
},
32+
"devDependencies": {
33+
"@tailwindcss/typography": "^0.5.16"
3134
}
3235
}

apps/client/tailwind.config.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,29 @@ module.exports = {
1212
'gradient-conic':
1313
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
1414
},
15+
typography: {
16+
DEFAULT: {
17+
css: {
18+
pre: {
19+
backgroundColor: '#f3f4f6',
20+
padding: '0.5rem',
21+
borderRadius: '0.375rem',
22+
fontSize: '0.875rem',
23+
overflowX: 'auto',
24+
color: '#1f2937'
25+
},
26+
code: {
27+
backgroundColor: '#f3f4f6',
28+
padding: '0.25rem',
29+
borderRadius: '0.25rem',
30+
fontSize: '0.875rem'
31+
}
32+
}
33+
}
34+
}
1535
},
1636
},
17-
plugins: [],
37+
plugins: [
38+
require('@tailwindcss/typography'),
39+
],
1840
}

apps/web/src/logs/atomaHealth.ts

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

apps/web/src/routes/v1/query.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@ import { Router } from 'express';
22
import { Request, Response } from 'express';
33
import config from '../../config/config';
44
import Agent from '@atoma-agents/sui-agent/src/agents/SuiAgent';
5+
import { checkAtomaSDK } from '../../logs/atomaHealth';
6+
57
const { atomaSdkBearerAuth } = config.auth;
68
const suiAgent = new Agent(atomaSdkBearerAuth);
79
const queryRouter: Router = Router();
10+
11+
// Run Atoma SDK check when router is initialized
12+
checkAtomaSDK(atomaSdkBearerAuth);
13+
814
// Health check endpoint
915
queryRouter.get('/health', (req: Request, res: Response) => {
1016
res.json({ status: 'healthy' });
1117
});
18+
1219
// Query endpoint
1320
const handleQuery = async (req: Request, res: Response): Promise<void> => {
1421
try {
@@ -29,12 +36,14 @@ const handleQuery = async (req: Request, res: Response): Promise<void> => {
2936
});
3037
}
3138
};
39+
3240
// Handle unsupported methods
3341
const handleUnsupportedMethod = (req: Request, res: Response): void => {
3442
res.status(405).json({
3543
error: 'Method not allowed'
3644
});
3745
};
46+
3847
queryRouter.post('/', handleQuery);
3948
queryRouter.all('/', handleUnsupportedMethod);
4049

packages/sui-agent/src/@types/interface.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,14 @@ export const NETWORK_CONFIG: NetworkConfigs = {
195195
faucet: 'https://faucet.testnet.sui.io/gas',
196196
},
197197
};
198+
199+
// Add this to your existing interfaces
200+
export interface ChatResponse {
201+
model: string;
202+
choices: Array<{
203+
message: {
204+
role: string;
205+
content: string;
206+
};
207+
}>;
208+
}

packages/sui-agent/src/config/atoma.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,5 @@ class Atoma {
5656
}
5757

5858
export default Atoma;
59+
60+
export { AtomaSDK };
Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
1-
export default "You are the Query Decomposer.\n\n\
2-
\n\
3-
Your task is to analyze the user's query and break it into multiple subqueries **only if necessary**, following strict rules.\n\n\
4-
\n\
5-
### **Rules for Decomposition:**\n\
6-
1️ **Determine if decomposition is needed**\n\
7-
- If the query requires multiple tools or separate logical steps, split it into subqueries.\n\
8-
- If a single tool can handle the query, return it as is.\n\n\
9-
\n\
10-
2️ **Subquery Format (Strict JSON Array)**\n\
11-
- Each subquery must be **clear, self-contained, and executable**.\n\
12-
- Maintain **logical order** for execution.\n\
13-
\n\
14-
### **Output Format:** DO NOT STRAY FROM THE RESPONSE FORMAT (Array or single string) RETURN ONLY THE RESPONSE FORMAT \n\
15-
- If decomposition **is needed**, return an array of strings: ELSE return an array with a single string\n\
16-
";
1+
export default `You are the Query Decomposer.
2+
3+
Your task is to analyze the user's query and break it into multiple subqueries **only if necessary**, following strict rules.
4+
5+
### **Rules for Decomposition**
6+
1. **Determine if decomposition is needed**
7+
- If the query requires multiple tools or separate logical steps, split it into subqueries.
8+
- If a single tool (e.g., straightforward coin price check) can handle the query, return it as a single subquery.
9+
10+
2. **Subquery Format (Strict JSON Array)**
11+
- Each subquery must be **clear, self-contained, and executable**.
12+
- Maintain **logical order** for execution.
13+
14+
3. **Handling Missing Details**
15+
- If the user request references a chain or an environment but is not specified, default to Sui or the best-known environment.
16+
- If the query requests an action requiring additional data (e.g., a coin symbol or address) and is not provided, note the additional info requirement.
17+
18+
### **Output Format**
19+
DO NOT STRAY FROM THE RESPONSE FORMAT (Array or single string). RETURN ONLY THE RESPONSE FORMAT.
20+
21+
- If decomposition **is needed**, return an array of strings.
22+
- If a single step suffices, return an array with a single string.
23+
24+
Remember: It's better to handle default or fallback conditions (like defaulting to Sui chain or a known exchange if none is provided) than to leave the query incomplete.
25+
`;

0 commit comments

Comments
 (0)