hedgequantx 2.4.43 → 2.5.0
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/README.md +1 -1
- package/package.json +1 -1
- package/src/app.js +44 -6
- package/src/menus/ai-agent.js +406 -0
- package/src/menus/dashboard.js +17 -5
- package/src/services/ai/index.js +371 -0
- package/src/services/ai/providers/index.js +490 -0
package/README.md
CHANGED
|
@@ -208,7 +208,7 @@ Mirror trades from Lead to Follower accounts.
|
|
|
208
208
|
<div align="center">
|
|
209
209
|
|
|
210
210
|
[](https://discord.gg/UBKCERctZu)
|
|
211
|
-
[](https://github.com/HedgeQuantX/
|
|
211
|
+
[](https://github.com/HedgeQuantX/HedgeQuantX/issues)
|
|
212
212
|
|
|
213
213
|
</div>
|
|
214
214
|
|
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -27,6 +27,7 @@ const {
|
|
|
27
27
|
dashboardMenu,
|
|
28
28
|
handleUpdate,
|
|
29
29
|
} = require('./menus');
|
|
30
|
+
const { aiAgentMenu } = require('./menus/ai-agent');
|
|
30
31
|
|
|
31
32
|
/** @type {Object|null} */
|
|
32
33
|
let currentService = null;
|
|
@@ -143,6 +144,38 @@ const banner = async (clear = true) => {
|
|
|
143
144
|
|
|
144
145
|
const tagline = isMobile ? `HQX v${version}` : `PROP FUTURES ALGO TRADING v${version}`;
|
|
145
146
|
console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
|
|
147
|
+
|
|
148
|
+
// Close box for loading state
|
|
149
|
+
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Display banner WITHOUT bottom border (for dashboard to continue)
|
|
154
|
+
*/
|
|
155
|
+
const bannerOpen = async () => {
|
|
156
|
+
const termWidth = process.stdout.columns || 100;
|
|
157
|
+
const isMobile = termWidth < 60;
|
|
158
|
+
const boxWidth = isMobile ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
|
|
159
|
+
const innerWidth = boxWidth - 2;
|
|
160
|
+
const version = require('../package.json').version;
|
|
161
|
+
|
|
162
|
+
console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
|
|
163
|
+
|
|
164
|
+
const logoLines = isMobile ? getMobileLogo() : getFullLogo();
|
|
165
|
+
|
|
166
|
+
for (const [hq, x] of logoLines) {
|
|
167
|
+
const fullLine = chalk.cyan(hq) + chalk.yellow(x);
|
|
168
|
+
const totalLen = hq.length + x.length;
|
|
169
|
+
const padding = innerWidth - totalLen;
|
|
170
|
+
const leftPad = Math.floor(padding / 2);
|
|
171
|
+
console.log(chalk.cyan('║') + ' '.repeat(leftPad) + fullLine + ' '.repeat(padding - leftPad) + chalk.cyan('║'));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
175
|
+
|
|
176
|
+
const tagline = isMobile ? `HQX v${version}` : `PROP FUTURES ALGO TRADING v${version}`;
|
|
177
|
+
console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
|
|
178
|
+
// NO bottom border - dashboardMenu will continue
|
|
146
179
|
};
|
|
147
180
|
|
|
148
181
|
/**
|
|
@@ -172,13 +205,10 @@ const getMobileLogo = () => [
|
|
|
172
205
|
];
|
|
173
206
|
|
|
174
207
|
/**
|
|
175
|
-
* Display banner with closing border
|
|
208
|
+
* Display banner with closing border (alias for banner)
|
|
176
209
|
*/
|
|
177
210
|
const bannerClosed = async () => {
|
|
178
211
|
await banner();
|
|
179
|
-
const termWidth = process.stdout.columns || 100;
|
|
180
|
-
const boxWidth = termWidth < 60 ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
|
|
181
|
-
console.log(chalk.cyan('╚' + '═'.repeat(boxWidth - 2) + '╝'));
|
|
182
212
|
};
|
|
183
213
|
|
|
184
214
|
// ==================== MENUS ====================
|
|
@@ -262,13 +292,17 @@ const run = async () => {
|
|
|
262
292
|
await refreshStats();
|
|
263
293
|
}
|
|
264
294
|
} else {
|
|
265
|
-
//
|
|
295
|
+
// Show banner with loading spinner
|
|
266
296
|
console.clear();
|
|
267
297
|
await banner(false);
|
|
268
298
|
|
|
269
299
|
const spinner = ora({ text: 'LOADING DASHBOARD...', color: 'cyan' }).start();
|
|
270
300
|
await refreshStats();
|
|
271
|
-
spinner.
|
|
301
|
+
spinner.stop();
|
|
302
|
+
|
|
303
|
+
// Redraw clean dashboard (banner without bottom border + menu)
|
|
304
|
+
console.clear();
|
|
305
|
+
await bannerOpen();
|
|
272
306
|
|
|
273
307
|
const action = await dashboardMenu(currentService);
|
|
274
308
|
|
|
@@ -303,6 +337,10 @@ const run = async () => {
|
|
|
303
337
|
}
|
|
304
338
|
break;
|
|
305
339
|
|
|
340
|
+
case 'ai_agent':
|
|
341
|
+
await aiAgentMenu();
|
|
342
|
+
break;
|
|
343
|
+
|
|
306
344
|
case 'update':
|
|
307
345
|
await handleUpdate();
|
|
308
346
|
break;
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Agent Menu
|
|
3
|
+
* Configure AI provider connection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const ora = require('ora');
|
|
8
|
+
|
|
9
|
+
const { getLogoWidth, drawBoxHeader, drawBoxFooter } = require('../ui');
|
|
10
|
+
const { prompts } = require('../utils');
|
|
11
|
+
const aiService = require('../services/ai');
|
|
12
|
+
const { getCategories, getProvidersByCategory } = require('../services/ai/providers');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Main AI Agent menu
|
|
16
|
+
*/
|
|
17
|
+
const aiAgentMenu = async () => {
|
|
18
|
+
const boxWidth = getLogoWidth();
|
|
19
|
+
const W = boxWidth - 2;
|
|
20
|
+
|
|
21
|
+
const makeLine = (content, align = 'left') => {
|
|
22
|
+
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
23
|
+
const padding = W - plainLen;
|
|
24
|
+
if (align === 'center') {
|
|
25
|
+
const leftPad = Math.floor(padding / 2);
|
|
26
|
+
return chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(padding - leftPad) + chalk.cyan('║');
|
|
27
|
+
}
|
|
28
|
+
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
console.clear();
|
|
32
|
+
drawBoxHeader('AI AGENT', boxWidth);
|
|
33
|
+
|
|
34
|
+
// Show current status
|
|
35
|
+
const connection = aiService.getConnection();
|
|
36
|
+
|
|
37
|
+
if (connection) {
|
|
38
|
+
console.log(makeLine(chalk.green('STATUS: ● CONNECTED'), 'left'));
|
|
39
|
+
console.log(makeLine(chalk.white(`PROVIDER: ${connection.provider.name}`), 'left'));
|
|
40
|
+
console.log(makeLine(chalk.white(`MODEL: ${connection.model}`), 'left'));
|
|
41
|
+
} else {
|
|
42
|
+
console.log(makeLine(chalk.gray('STATUS: ○ NOT CONNECTED'), 'left'));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
46
|
+
|
|
47
|
+
// Menu options
|
|
48
|
+
const options = [];
|
|
49
|
+
|
|
50
|
+
if (!connection) {
|
|
51
|
+
options.push({ label: chalk.green('[1] CONNECT PROVIDER'), value: 'connect' });
|
|
52
|
+
} else {
|
|
53
|
+
options.push({ label: chalk.cyan('[1] CHANGE PROVIDER'), value: 'connect' });
|
|
54
|
+
options.push({ label: chalk.yellow('[2] CHANGE MODEL'), value: 'model' });
|
|
55
|
+
options.push({ label: chalk.red('[3] DISCONNECT'), value: 'disconnect' });
|
|
56
|
+
}
|
|
57
|
+
options.push({ label: chalk.gray('[<] BACK'), value: 'back' });
|
|
58
|
+
|
|
59
|
+
for (const opt of options) {
|
|
60
|
+
console.log(makeLine(opt.label, 'left'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
drawBoxFooter(boxWidth);
|
|
64
|
+
|
|
65
|
+
const choice = await prompts.textInput(chalk.cyan('SELECT:'));
|
|
66
|
+
|
|
67
|
+
switch (choice?.toLowerCase()) {
|
|
68
|
+
case '1':
|
|
69
|
+
return await selectCategory();
|
|
70
|
+
case '2':
|
|
71
|
+
if (connection) {
|
|
72
|
+
return await selectModel(connection.provider);
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
case '3':
|
|
76
|
+
if (connection) {
|
|
77
|
+
aiService.disconnect();
|
|
78
|
+
console.log(chalk.yellow('\n AI AGENT DISCONNECTED'));
|
|
79
|
+
await prompts.waitForEnter();
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
case '<':
|
|
83
|
+
case 'b':
|
|
84
|
+
return;
|
|
85
|
+
default:
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Select provider category
|
|
92
|
+
*/
|
|
93
|
+
const selectCategory = async () => {
|
|
94
|
+
const boxWidth = getLogoWidth();
|
|
95
|
+
const W = boxWidth - 2;
|
|
96
|
+
|
|
97
|
+
const makeLine = (content) => {
|
|
98
|
+
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
99
|
+
const padding = W - plainLen;
|
|
100
|
+
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
console.clear();
|
|
104
|
+
drawBoxHeader('SELECT PROVIDER TYPE', boxWidth);
|
|
105
|
+
|
|
106
|
+
const categories = getCategories();
|
|
107
|
+
|
|
108
|
+
categories.forEach((cat, index) => {
|
|
109
|
+
const color = cat.id === 'unified' ? chalk.green :
|
|
110
|
+
cat.id === 'local' ? chalk.yellow : chalk.cyan;
|
|
111
|
+
console.log(makeLine(color(`[${index + 1}] ${cat.name}`)));
|
|
112
|
+
console.log(makeLine(chalk.gray(' ' + cat.description)));
|
|
113
|
+
console.log(makeLine(''));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
console.log(makeLine(chalk.gray('[<] BACK')));
|
|
117
|
+
|
|
118
|
+
drawBoxFooter(boxWidth);
|
|
119
|
+
|
|
120
|
+
const choice = await prompts.textInput(chalk.cyan('SELECT (1-4):'));
|
|
121
|
+
|
|
122
|
+
if (choice === '<' || choice?.toLowerCase() === 'b') {
|
|
123
|
+
return await aiAgentMenu();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const index = parseInt(choice) - 1;
|
|
127
|
+
if (isNaN(index) || index < 0 || index >= categories.length) {
|
|
128
|
+
return await aiAgentMenu();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const selectedCategory = categories[index];
|
|
132
|
+
return await selectProvider(selectedCategory.id);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Select AI provider from category
|
|
137
|
+
*/
|
|
138
|
+
const selectProvider = async (categoryId) => {
|
|
139
|
+
const boxWidth = getLogoWidth();
|
|
140
|
+
const W = boxWidth - 2;
|
|
141
|
+
|
|
142
|
+
const makeLine = (content) => {
|
|
143
|
+
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
144
|
+
const padding = W - plainLen;
|
|
145
|
+
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
console.clear();
|
|
149
|
+
|
|
150
|
+
const categories = getCategories();
|
|
151
|
+
const category = categories.find(c => c.id === categoryId);
|
|
152
|
+
drawBoxHeader(category.name, boxWidth);
|
|
153
|
+
|
|
154
|
+
const providers = getProvidersByCategory(categoryId);
|
|
155
|
+
|
|
156
|
+
if (providers.length === 0) {
|
|
157
|
+
console.log(makeLine(chalk.gray('No providers in this category')));
|
|
158
|
+
drawBoxFooter(boxWidth);
|
|
159
|
+
await prompts.waitForEnter();
|
|
160
|
+
return await selectCategory();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Display providers
|
|
164
|
+
providers.forEach((provider, index) => {
|
|
165
|
+
const isRecommended = provider.id === 'openrouter';
|
|
166
|
+
const color = isRecommended ? chalk.green : chalk.cyan;
|
|
167
|
+
console.log(makeLine(color(`[${index + 1}] ${provider.name}`)));
|
|
168
|
+
console.log(makeLine(chalk.gray(' ' + provider.description)));
|
|
169
|
+
if (provider.models && provider.models.length > 0) {
|
|
170
|
+
const modelList = provider.models.slice(0, 3).join(', ');
|
|
171
|
+
console.log(makeLine(chalk.gray(' Models: ' + modelList + (provider.models.length > 3 ? '...' : ''))));
|
|
172
|
+
}
|
|
173
|
+
console.log(makeLine(''));
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
console.log(makeLine(chalk.gray('[<] BACK')));
|
|
177
|
+
|
|
178
|
+
drawBoxFooter(boxWidth);
|
|
179
|
+
|
|
180
|
+
const choice = await prompts.textInput(chalk.cyan('SELECT PROVIDER:'));
|
|
181
|
+
|
|
182
|
+
if (choice === '<' || choice?.toLowerCase() === 'b') {
|
|
183
|
+
return await selectCategory();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const index = parseInt(choice) - 1;
|
|
187
|
+
if (isNaN(index) || index < 0 || index >= providers.length) {
|
|
188
|
+
return await selectCategory();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const selectedProvider = providers[index];
|
|
192
|
+
return await selectProviderOption(selectedProvider);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Select connection option for provider
|
|
197
|
+
*/
|
|
198
|
+
const selectProviderOption = async (provider) => {
|
|
199
|
+
const boxWidth = getLogoWidth();
|
|
200
|
+
const W = boxWidth - 2;
|
|
201
|
+
|
|
202
|
+
const makeLine = (content) => {
|
|
203
|
+
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
204
|
+
const padding = W - plainLen;
|
|
205
|
+
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// If only one option, skip selection
|
|
209
|
+
if (provider.options.length === 1) {
|
|
210
|
+
return await setupConnection(provider, provider.options[0]);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.clear();
|
|
214
|
+
drawBoxHeader(provider.name, boxWidth);
|
|
215
|
+
|
|
216
|
+
console.log(makeLine(chalk.white('SELECT CONNECTION METHOD:')));
|
|
217
|
+
console.log(makeLine(''));
|
|
218
|
+
|
|
219
|
+
provider.options.forEach((option, index) => {
|
|
220
|
+
console.log(makeLine(chalk.cyan(`[${index + 1}] ${option.label}`)));
|
|
221
|
+
option.description.forEach(desc => {
|
|
222
|
+
console.log(makeLine(chalk.gray(' ' + desc)));
|
|
223
|
+
});
|
|
224
|
+
console.log(makeLine(''));
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
console.log(makeLine(chalk.gray('[<] BACK')));
|
|
228
|
+
|
|
229
|
+
drawBoxFooter(boxWidth);
|
|
230
|
+
|
|
231
|
+
const choice = await prompts.textInput(chalk.cyan('SELECT:'));
|
|
232
|
+
|
|
233
|
+
if (choice === '<' || choice?.toLowerCase() === 'b') {
|
|
234
|
+
return await selectProvider(provider.category);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const index = parseInt(choice) - 1;
|
|
238
|
+
if (isNaN(index) || index < 0 || index >= provider.options.length) {
|
|
239
|
+
return await selectProvider(provider.category);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const selectedOption = provider.options[index];
|
|
243
|
+
return await setupConnection(provider, selectedOption);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Setup connection with credentials
|
|
248
|
+
*/
|
|
249
|
+
const setupConnection = async (provider, option) => {
|
|
250
|
+
const boxWidth = getLogoWidth();
|
|
251
|
+
const W = boxWidth - 2;
|
|
252
|
+
|
|
253
|
+
const makeLine = (content) => {
|
|
254
|
+
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
255
|
+
const padding = W - plainLen;
|
|
256
|
+
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
console.clear();
|
|
260
|
+
drawBoxHeader(`CONNECT TO ${provider.name}`, boxWidth);
|
|
261
|
+
|
|
262
|
+
// Show instructions
|
|
263
|
+
if (option.url) {
|
|
264
|
+
console.log(makeLine(chalk.white('GET YOUR CREDENTIALS:')));
|
|
265
|
+
console.log(makeLine(chalk.cyan(option.url)));
|
|
266
|
+
console.log(makeLine(''));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
drawBoxFooter(boxWidth);
|
|
270
|
+
console.log();
|
|
271
|
+
|
|
272
|
+
// Collect credentials based on fields
|
|
273
|
+
const credentials = {};
|
|
274
|
+
|
|
275
|
+
for (const field of option.fields) {
|
|
276
|
+
let value;
|
|
277
|
+
|
|
278
|
+
switch (field) {
|
|
279
|
+
case 'apiKey':
|
|
280
|
+
value = await prompts.passwordInput('ENTER API KEY:');
|
|
281
|
+
if (!value) return await selectProviderOption(provider);
|
|
282
|
+
credentials.apiKey = value;
|
|
283
|
+
break;
|
|
284
|
+
|
|
285
|
+
case 'sessionKey':
|
|
286
|
+
value = await prompts.passwordInput('ENTER SESSION KEY:');
|
|
287
|
+
if (!value) return await selectProviderOption(provider);
|
|
288
|
+
credentials.sessionKey = value;
|
|
289
|
+
break;
|
|
290
|
+
|
|
291
|
+
case 'accessToken':
|
|
292
|
+
value = await prompts.passwordInput('ENTER ACCESS TOKEN:');
|
|
293
|
+
if (!value) return await selectProviderOption(provider);
|
|
294
|
+
credentials.accessToken = value;
|
|
295
|
+
break;
|
|
296
|
+
|
|
297
|
+
case 'endpoint':
|
|
298
|
+
const defaultEndpoint = option.defaultEndpoint || '';
|
|
299
|
+
value = await prompts.textInput(`ENDPOINT [${defaultEndpoint || 'required'}]:`);
|
|
300
|
+
credentials.endpoint = value || defaultEndpoint;
|
|
301
|
+
if (!credentials.endpoint) return await selectProviderOption(provider);
|
|
302
|
+
break;
|
|
303
|
+
|
|
304
|
+
case 'model':
|
|
305
|
+
value = await prompts.textInput('MODEL NAME:');
|
|
306
|
+
if (!value) return await selectProviderOption(provider);
|
|
307
|
+
credentials.model = value;
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Validate connection
|
|
313
|
+
const spinner = ora({ text: 'VALIDATING CONNECTION...', color: 'cyan' }).start();
|
|
314
|
+
|
|
315
|
+
const validation = await aiService.validateConnection(provider.id, option.id, credentials);
|
|
316
|
+
|
|
317
|
+
if (!validation.valid) {
|
|
318
|
+
spinner.fail(`CONNECTION FAILED: ${validation.error}`);
|
|
319
|
+
await prompts.waitForEnter();
|
|
320
|
+
return await selectProviderOption(provider);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Save connection
|
|
324
|
+
try {
|
|
325
|
+
const model = credentials.model || provider.defaultModel;
|
|
326
|
+
await aiService.connect(provider.id, option.id, credentials, model);
|
|
327
|
+
spinner.succeed(`CONNECTED TO ${provider.name}`);
|
|
328
|
+
|
|
329
|
+
// Show available models for local providers
|
|
330
|
+
if (validation.models && validation.models.length > 0) {
|
|
331
|
+
console.log(chalk.gray(` AVAILABLE MODELS: ${validation.models.slice(0, 5).join(', ')}`));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
console.log(chalk.gray(` USING MODEL: ${model}`));
|
|
335
|
+
} catch (error) {
|
|
336
|
+
spinner.fail(`FAILED TO SAVE: ${error.message}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
await prompts.waitForEnter();
|
|
340
|
+
return await aiAgentMenu();
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Select/change model for current provider
|
|
345
|
+
*/
|
|
346
|
+
const selectModel = async (provider) => {
|
|
347
|
+
const boxWidth = getLogoWidth();
|
|
348
|
+
const W = boxWidth - 2;
|
|
349
|
+
|
|
350
|
+
const makeLine = (content) => {
|
|
351
|
+
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
352
|
+
const padding = W - plainLen;
|
|
353
|
+
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
console.clear();
|
|
357
|
+
drawBoxHeader('SELECT MODEL', boxWidth);
|
|
358
|
+
|
|
359
|
+
const models = provider.models || [];
|
|
360
|
+
|
|
361
|
+
if (models.length === 0) {
|
|
362
|
+
console.log(makeLine(chalk.gray('No predefined models. Enter model name manually.')));
|
|
363
|
+
drawBoxFooter(boxWidth);
|
|
364
|
+
|
|
365
|
+
const model = await prompts.textInput('ENTER MODEL NAME:');
|
|
366
|
+
if (model) {
|
|
367
|
+
const settings = aiService.getAISettings();
|
|
368
|
+
settings.model = model;
|
|
369
|
+
aiService.saveAISettings(settings);
|
|
370
|
+
console.log(chalk.green(`\n MODEL CHANGED TO: ${model}`));
|
|
371
|
+
}
|
|
372
|
+
await prompts.waitForEnter();
|
|
373
|
+
return await aiAgentMenu();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
models.forEach((model, index) => {
|
|
377
|
+
console.log(makeLine(chalk.cyan(`[${index + 1}] ${model}`)));
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
console.log(makeLine(''));
|
|
381
|
+
console.log(makeLine(chalk.gray('[<] BACK')));
|
|
382
|
+
|
|
383
|
+
drawBoxFooter(boxWidth);
|
|
384
|
+
|
|
385
|
+
const choice = await prompts.textInput(chalk.cyan('SELECT MODEL:'));
|
|
386
|
+
|
|
387
|
+
if (choice === '<' || choice?.toLowerCase() === 'b') {
|
|
388
|
+
return await aiAgentMenu();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const index = parseInt(choice) - 1;
|
|
392
|
+
if (isNaN(index) || index < 0 || index >= models.length) {
|
|
393
|
+
return await aiAgentMenu();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const selectedModel = models[index];
|
|
397
|
+
const settings = aiService.getAISettings();
|
|
398
|
+
settings.model = selectedModel;
|
|
399
|
+
aiService.saveAISettings(settings);
|
|
400
|
+
|
|
401
|
+
console.log(chalk.green(`\n MODEL CHANGED TO: ${selectedModel}`));
|
|
402
|
+
await prompts.waitForEnter();
|
|
403
|
+
return await aiAgentMenu();
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
module.exports = { aiAgentMenu };
|
package/src/menus/dashboard.js
CHANGED
|
@@ -10,6 +10,7 @@ const { connections } = require('../services');
|
|
|
10
10
|
const { getLogoWidth, centerText, prepareStdin } = require('../ui');
|
|
11
11
|
const { getCachedStats } = require('../services/stats-cache');
|
|
12
12
|
const { prompts } = require('../utils');
|
|
13
|
+
const aiService = require('../services/ai');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Dashboard menu after login
|
|
@@ -60,9 +61,18 @@ const dashboardMenu = async (service) => {
|
|
|
60
61
|
pnlDisplay = '--';
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
// AI status
|
|
65
|
+
const aiConnection = aiService.getConnection();
|
|
66
|
+
const aiStatus = aiConnection
|
|
67
|
+
? aiConnection.provider.name.split(' ')[0] // Just "CLAUDE" or "OPENAI"
|
|
68
|
+
: null;
|
|
69
|
+
|
|
63
70
|
// Yellow icons: ✔ for each stat
|
|
64
71
|
const icon = chalk.yellow('✔ ');
|
|
65
|
-
const
|
|
72
|
+
const aiIcon = aiStatus ? chalk.magenta('✔ ') : chalk.gray('○ ');
|
|
73
|
+
const aiText = aiStatus ? chalk.magenta(aiStatus) : chalk.gray('NONE');
|
|
74
|
+
|
|
75
|
+
const statsPlain = `✔ CONNECTIONS: ${statsInfo.connections} ✔ ACCOUNTS: ${statsInfo.accounts} ✔ BALANCE: ${balStr} AI: ${aiStatus || 'NONE'}`;
|
|
66
76
|
const statsLeftPad = Math.max(0, Math.floor((W - statsPlain.length) / 2));
|
|
67
77
|
const statsRightPad = Math.max(0, W - statsPlain.length - statsLeftPad);
|
|
68
78
|
|
|
@@ -70,7 +80,7 @@ const dashboardMenu = async (service) => {
|
|
|
70
80
|
icon + chalk.white(`CONNECTIONS: ${statsInfo.connections}`) + ' ' +
|
|
71
81
|
icon + chalk.white(`ACCOUNTS: ${statsInfo.accounts}`) + ' ' +
|
|
72
82
|
icon + chalk.white('BALANCE: ') + balColor(balStr) + ' ' +
|
|
73
|
-
|
|
83
|
+
aiIcon + chalk.white('AI: ') + aiText +
|
|
74
84
|
' '.repeat(Math.max(0, statsRightPad)) + chalk.cyan('║'));
|
|
75
85
|
}
|
|
76
86
|
|
|
@@ -87,19 +97,21 @@ const dashboardMenu = async (service) => {
|
|
|
87
97
|
};
|
|
88
98
|
|
|
89
99
|
menuRow(chalk.cyan('[1] VIEW ACCOUNTS'), chalk.cyan('[2] VIEW STATS'));
|
|
90
|
-
menuRow(chalk.cyan('[+] ADD PROP-ACCOUNT'), chalk.magenta('[A] ALGO
|
|
91
|
-
menuRow(chalk.
|
|
100
|
+
menuRow(chalk.cyan('[+] ADD PROP-ACCOUNT'), chalk.magenta('[A] ALGO TRADING'));
|
|
101
|
+
menuRow(chalk.magenta('[I] AI AGENT'), chalk.yellow('[U] UPDATE HQX'));
|
|
102
|
+
menuRow(chalk.red('[X] DISCONNECT'), '');
|
|
92
103
|
|
|
93
104
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
94
105
|
|
|
95
106
|
// Simple input - no duplicate menu
|
|
96
|
-
const input = await prompts.textInput(chalk.cyan('SELECT (1/2/+/A/U/X)'));
|
|
107
|
+
const input = await prompts.textInput(chalk.cyan('SELECT (1/2/+/A/I/U/X)'));
|
|
97
108
|
|
|
98
109
|
const actionMap = {
|
|
99
110
|
'1': 'accounts',
|
|
100
111
|
'2': 'stats',
|
|
101
112
|
'+': 'add_prop_account',
|
|
102
113
|
'a': 'algotrading',
|
|
114
|
+
'i': 'ai_agent',
|
|
103
115
|
'u': 'update',
|
|
104
116
|
'x': 'disconnect'
|
|
105
117
|
};
|