devicely 1.0.5
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/LICENSE +7 -0
- package/README.md +243 -0
- package/bin/devicely.js +3 -0
- package/config/apps_presets.conf +271 -0
- package/config/devices.conf +20 -0
- package/lib/aiProviders.js +518 -0
- package/lib/aiProviders.js.backup +301 -0
- package/lib/aiProvidersConfig.js +176 -0
- package/lib/aiProviders_new.js +70 -0
- package/lib/androidDeviceDetection.js +2 -0
- package/lib/appMappings.js +1 -0
- package/lib/deviceDetection.js +2 -0
- package/lib/devices.conf +20 -0
- package/lib/devices.js +1 -0
- package/lib/doctor.js +1 -0
- package/lib/executor.js +1 -0
- package/lib/fix_logs.sh +18 -0
- package/lib/frontend/asset-manifest.json +13 -0
- package/lib/frontend/index.html +1 -0
- package/lib/frontend/static/css/main.23bd35c0.css +2 -0
- package/lib/frontend/static/css/main.23bd35c0.css.map +1 -0
- package/lib/frontend/static/js/main.3f13aeaf.js +1 -0
- package/lib/frontend/static/js/main.3f13aeaf.js.LICENSE.txt +48 -0
- package/lib/frontend/static/js/main.3f13aeaf.js.map +1 -0
- package/lib/frontend/voice-test.html +156 -0
- package/lib/index.js +1 -0
- package/lib/package-lock.json +1678 -0
- package/lib/package.json +30 -0
- package/lib/server.js +1 -0
- package/lib/server.js.bak +3380 -0
- package/package.json +78 -0
- package/scripts/postinstall.js +110 -0
- package/scripts/shell/android_device_control +0 -0
- package/scripts/shell/connect_android_usb +0 -0
- package/scripts/shell/connect_android_usb_multi_final +0 -0
- package/scripts/shell/connect_android_wireless +0 -0
- package/scripts/shell/connect_android_wireless_multi_final +0 -0
- package/scripts/shell/connect_ios_usb +0 -0
- package/scripts/shell/connect_ios_usb_multi_final +0 -0
- package/scripts/shell/connect_ios_wireless_multi_final +0 -0
- package/scripts/shell/find_element_coordinates +0 -0
- package/scripts/shell/find_wda +0 -0
- package/scripts/shell/install_uiautomator2 +0 -0
- package/scripts/shell/ios_device_control +0 -0
- package/scripts/shell/setup +0 -0
- package/scripts/shell/setup_android +0 -0
- package/scripts/shell/start +0 -0
- package/scripts/shell/test_android_locators +0 -0
- package/scripts/shell/test_connect +0 -0
- package/scripts/shell/test_device_detection +0 -0
- package/scripts/shell/test_fixes +0 -0
- package/scripts/shell/test_getlocators_fix +0 -0
- package/scripts/shell/test_recording_feature +0 -0
- package/scripts/shell/verify_distribution +0 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
// AI Provider Abstraction Layer - Enhanced Multi-Provider Support
|
|
2
|
+
// Supports: OpenAI, Google Gemini, Anthropic Claude, GitHub Copilot, Groq, Cohere, Mistral AI
|
|
3
|
+
|
|
4
|
+
const { OpenAI } = require('openai');
|
|
5
|
+
const { GoogleGenerativeAI } = require('@google/generative-ai');
|
|
6
|
+
const { Anthropic } = require('@anthropic-ai/sdk');
|
|
7
|
+
const { getPackageId, APP_MAPPINGS } = require('./appMappings');
|
|
8
|
+
|
|
9
|
+
class AIProviderManager {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.provider = process.env.AI_PROVIDER || 'gemini';
|
|
12
|
+
this.model = process.env.AI_MODEL || null; // Auto-select default if null
|
|
13
|
+
this.initializeProviders();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
initializeProviders() {
|
|
17
|
+
// OpenAI
|
|
18
|
+
if (process.env.OPENAI_API_KEY) {
|
|
19
|
+
this.openai = new OpenAI({
|
|
20
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Google Gemini
|
|
25
|
+
if (process.env.GEMINI_API_KEY) {
|
|
26
|
+
this.gemini = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Anthropic Claude
|
|
30
|
+
if (process.env.CLAUDE_API_KEY) {
|
|
31
|
+
this.claude = new Anthropic({
|
|
32
|
+
apiKey: process.env.CLAUDE_API_KEY,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// GitHub Copilot
|
|
37
|
+
if (process.env.GITHUB_TOKEN) {
|
|
38
|
+
this.copilot = new OpenAI({
|
|
39
|
+
apiKey: process.env.GITHUB_TOKEN,
|
|
40
|
+
baseURL: 'https://api.githubcopilot.com',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Groq (ultra-fast inference)
|
|
45
|
+
if (process.env.GROQ_API_KEY) {
|
|
46
|
+
this.groq = new OpenAI({
|
|
47
|
+
apiKey: process.env.GROQ_API_KEY,
|
|
48
|
+
baseURL: 'https://api.groq.com/openai/v1',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Cohere
|
|
53
|
+
if (process.env.COHERE_API_KEY) {
|
|
54
|
+
this.cohere = new OpenAI({
|
|
55
|
+
apiKey: process.env.COHERE_API_KEY,
|
|
56
|
+
baseURL: 'https://api.cohere.ai/v1',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Mistral AI
|
|
61
|
+
if (process.env.MISTRAL_API_KEY) {
|
|
62
|
+
this.mistral = new OpenAI({
|
|
63
|
+
apiKey: process.env.MISTRAL_API_KEY,
|
|
64
|
+
baseURL: 'https://api.mistral.ai/v1',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getSystemPrompt(platform = null) {
|
|
70
|
+
// Generate app list based on platform
|
|
71
|
+
let appListSection = '';
|
|
72
|
+
|
|
73
|
+
if (platform === 'both') {
|
|
74
|
+
// Multi-platform mode - use generic app names
|
|
75
|
+
const commonApps = Object.keys(APP_MAPPINGS)
|
|
76
|
+
.filter(app => APP_MAPPINGS[app].ios && APP_MAPPINGS[app].android)
|
|
77
|
+
.slice(0, 30);
|
|
78
|
+
|
|
79
|
+
appListSection = `\n\nMULTI-PLATFORM MODE (iOS + Android devices)
|
|
80
|
+
Available apps: ${commonApps.join(', ')}
|
|
81
|
+
|
|
82
|
+
IMPORTANT: Use generic app names (e.g., "launch settings", "launch chrome")
|
|
83
|
+
The system will automatically convert to platform-specific package IDs:
|
|
84
|
+
${commonApps.slice(0, 15).map(app => `- ${app} -> iOS: ${APP_MAPPINGS[app].ios} / Android: ${APP_MAPPINGS[app].android}`).join('\n')}
|
|
85
|
+
|
|
86
|
+
Commands will execute SIMULTANEOUSLY on all devices with correct package IDs.
|
|
87
|
+
`;
|
|
88
|
+
} else if (platform) {
|
|
89
|
+
const availableApps = Object.keys(APP_MAPPINGS)
|
|
90
|
+
.filter(app => APP_MAPPINGS[app][platform])
|
|
91
|
+
.slice(0, 50);
|
|
92
|
+
|
|
93
|
+
appListSection = `\n\nPLATFORM: ${platform.toUpperCase()}
|
|
94
|
+
Available apps for ${platform}: ${availableApps.join(', ')}
|
|
95
|
+
|
|
96
|
+
APP PACKAGE MAPPINGS:
|
|
97
|
+
When user says "launch chrome", "open chrome", etc., use the correct package ID:
|
|
98
|
+
${availableApps.slice(0, 20).map(app => `- ${app} -> launch ${APP_MAPPINGS[app][platform]}`).join('\n')}
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return `You are a mobile device automation command converter. Convert natural language requests into executable commands for iOS and Android devices.
|
|
103
|
+
|
|
104
|
+
Available commands (work on both iOS & Android):
|
|
105
|
+
- launch <app_name>: Launch an app using generic name (e.g., "launch settings", "launch chrome")
|
|
106
|
+
- kill <app_name>: Close/force stop an app
|
|
107
|
+
- home: Go to home screen (press home button)
|
|
108
|
+
- back: Navigate back (Android/iOS)
|
|
109
|
+
- url <url>: Open URL in browser
|
|
110
|
+
- click <text>: Click on a button or element by visible text
|
|
111
|
+
- click <x,y>: Click at specific coordinates (e.g., click 500,1000)
|
|
112
|
+
- tap <text/coords>: Same as click
|
|
113
|
+
- longpress <text/coords>: Long press on element or coordinates
|
|
114
|
+
- swipe <direction>: Swipe up/down/left/right (use for scrolling)
|
|
115
|
+
- type <text>: Type text into focused field (just the text, no "type" prefix)
|
|
116
|
+
- screenshot: Take screenshot
|
|
117
|
+
- restart: Restart device
|
|
118
|
+
- rotate <left/right/portrait/landscape>: Rotate screen
|
|
119
|
+
|
|
120
|
+
iOS-specific commands:
|
|
121
|
+
- darkmode/lightmode: Change appearance
|
|
122
|
+
- airplane <on/off>: Toggle airplane mode
|
|
123
|
+
- wifi <on/off>: Toggle WiFi
|
|
124
|
+
- volume <up/down/mute>: Control volume
|
|
125
|
+
|
|
126
|
+
Android-specific commands:
|
|
127
|
+
- getLocators: Get all interactive elements on current screen
|
|
128
|
+
- recent: Open recent apps
|
|
129
|
+
- notifications: Open notification panel
|
|
130
|
+
- quicksettings: Open quick settings
|
|
131
|
+
${appListSection}
|
|
132
|
+
|
|
133
|
+
COMMON PHRASE MAPPINGS:
|
|
134
|
+
- "scroll up" OR "scroll down" -> swipe up OR swipe down
|
|
135
|
+
- "go to <url>" OR "open <url>" OR "visit <url>" -> url https://<url>
|
|
136
|
+
- "press home" OR "go home" OR "home button" -> home
|
|
137
|
+
- "open settings" OR "launch settings" -> launch settings
|
|
138
|
+
- "open camera" OR "launch camera" -> launch camera
|
|
139
|
+
|
|
140
|
+
Examples:
|
|
141
|
+
- "open chrome" -> launch chrome
|
|
142
|
+
- "launch settings" -> launch settings
|
|
143
|
+
- "open camera" -> launch camera
|
|
144
|
+
- "scroll up" -> swipe up
|
|
145
|
+
- "scroll down" -> swipe down
|
|
146
|
+
- "click on the login button" -> click Login
|
|
147
|
+
- "tap at center of screen" -> click 540,1000
|
|
148
|
+
- "swipe down" -> swipe down
|
|
149
|
+
- "type hello world" -> hello world
|
|
150
|
+
- "take a screenshot" -> screenshot
|
|
151
|
+
- "go back" -> back
|
|
152
|
+
- "press home" -> home
|
|
153
|
+
- "go to google.com" -> url https://www.google.com
|
|
154
|
+
- "visit youtube.com" -> url https://www.youtube.com
|
|
155
|
+
- "launch Chrome and search google.com" -> launch chrome
|
|
156
|
+
WAIT 3000
|
|
157
|
+
url https://www.google.com
|
|
158
|
+
- "launch settings scroll up launch camera go to google.com press home" ->
|
|
159
|
+
launch settings
|
|
160
|
+
WAIT 3000
|
|
161
|
+
swipe up
|
|
162
|
+
WAIT 1000
|
|
163
|
+
home
|
|
164
|
+
WAIT 500
|
|
165
|
+
launch camera
|
|
166
|
+
WAIT 3000
|
|
167
|
+
home
|
|
168
|
+
WAIT 500
|
|
169
|
+
url https://www.google.com
|
|
170
|
+
WAIT 2000
|
|
171
|
+
home
|
|
172
|
+
|
|
173
|
+
Convert this request to commands: "{INPUT}"
|
|
174
|
+
|
|
175
|
+
CRITICAL RULES - YOU MUST FOLLOW THESE EXACTLY:
|
|
176
|
+
1. Output ONLY executable commands, one per line
|
|
177
|
+
2. NO explanations, NO markdown code blocks, NO comments, NO extra text
|
|
178
|
+
3. For multi-step actions, insert WAIT <milliseconds> between commands
|
|
179
|
+
4. Use GENERIC app names (e.g., "launch settings", "launch chrome", "launch camera")
|
|
180
|
+
5. Do NOT use platform-specific package IDs - use simple app names
|
|
181
|
+
6. The system will automatically convert to correct package IDs for each platform
|
|
182
|
+
5. For URLs, always use: url https://example.com
|
|
183
|
+
6. For scrolling, use: swipe up OR swipe down (never "scroll")
|
|
184
|
+
7. For text input, output ONLY the text (never include "type" prefix)
|
|
185
|
+
8. For home button, output: home (never "press home" or "go home")
|
|
186
|
+
9. WAIT timings: apps=3000ms, pages=2000ms, UI=1000ms, quick=500ms
|
|
187
|
+
10. Parse compound requests into individual steps with WAIT between each
|
|
188
|
+
|
|
189
|
+
OUTPUT FORMAT EXAMPLE:
|
|
190
|
+
launch com.apple.Preferences
|
|
191
|
+
WAIT 3000
|
|
192
|
+
swipe up
|
|
193
|
+
WAIT 1000
|
|
194
|
+
home
|
|
195
|
+
|
|
196
|
+
DO NOT include any other text. Start your response with the first command.`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async convertCommand(text, platform = null, providerOverride = null) {
|
|
200
|
+
const provider = providerOverride || this.provider;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
switch (provider) {
|
|
204
|
+
case 'openai':
|
|
205
|
+
return await this.convertWithOpenAI(text, platform);
|
|
206
|
+
case 'gemini':
|
|
207
|
+
return await this.convertWithGemini(text, platform);
|
|
208
|
+
case 'claude':
|
|
209
|
+
return await this.convertWithClaude(text, platform);
|
|
210
|
+
case 'copilot':
|
|
211
|
+
return await this.convertWithCopilot(text, platform);
|
|
212
|
+
case 'groq':
|
|
213
|
+
return await this.convertWithGroq(text, platform);
|
|
214
|
+
case 'cohere':
|
|
215
|
+
return await this.convertWithCohere(text, platform);
|
|
216
|
+
case 'mistral':
|
|
217
|
+
return await this.convertWithMistral(text, platform);
|
|
218
|
+
default:
|
|
219
|
+
throw new Error(`Unknown AI provider: ${provider}`);
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error(`Error with ${provider}:`, error.message);
|
|
223
|
+
throw new Error(`AI conversion failed (${provider}): ${error.message}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Get model to use (from env or default)
|
|
228
|
+
getModelForProvider(provider) {
|
|
229
|
+
// If specific model is set, use it
|
|
230
|
+
if (this.model) return this.model;
|
|
231
|
+
|
|
232
|
+
// Otherwise use defaults (optimized for speed)
|
|
233
|
+
const defaults = {
|
|
234
|
+
openai: 'gpt-4o-mini', // Faster than gpt-4o
|
|
235
|
+
gemini: 'gemini-2.5-flash', // Fast and reliable
|
|
236
|
+
claude: 'claude-3-5-sonnet-20241022',
|
|
237
|
+
copilot: 'gpt-4o',
|
|
238
|
+
groq: 'llama-3.3-70b-versatile',
|
|
239
|
+
cohere: 'command-r-plus',
|
|
240
|
+
mistral: 'mistral-large-latest',
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
return defaults[provider] || 'gpt-4';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async convertWithOpenAI(text, platform = null) {
|
|
247
|
+
if (!this.openai) {
|
|
248
|
+
throw new Error('OpenAI not configured');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const model = this.getModelForProvider('openai');
|
|
252
|
+
|
|
253
|
+
const response = await this.openai.chat.completions.create({
|
|
254
|
+
model: model,
|
|
255
|
+
messages: [
|
|
256
|
+
{ role: 'system', content: this.getSystemPrompt(platform) },
|
|
257
|
+
{ role: 'user', content: text }
|
|
258
|
+
],
|
|
259
|
+
temperature: 0.3,
|
|
260
|
+
max_tokens: 500,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
let convertedText = response.choices[0].message.content.trim();
|
|
264
|
+
convertedText = this.cleanAIResponse(convertedText);
|
|
265
|
+
|
|
266
|
+
return convertedText;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async convertWithGemini(text, platform = null) {
|
|
270
|
+
if (!this.gemini) {
|
|
271
|
+
throw new Error('Gemini not configured');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let modelName = this.getModelForProvider('gemini');
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
// Use default Gemini config for best accuracy
|
|
278
|
+
const model = this.gemini.getGenerativeModel({ model: modelName });
|
|
279
|
+
|
|
280
|
+
// Build prompt and sanitize for Gemini API (requires ASCII-safe ByteString)
|
|
281
|
+
const rawPrompt = this.getSystemPrompt(platform).replace('{INPUT}', text);
|
|
282
|
+
|
|
283
|
+
// Remove ALL non-ASCII characters that could cause ByteString errors
|
|
284
|
+
const sanitizedPrompt = rawPrompt
|
|
285
|
+
.replace(/[^\x00-\x7F]/g, ' ') // Replace non-ASCII with spaces
|
|
286
|
+
.replace(/\s+/g, ' ') // Normalize multiple spaces
|
|
287
|
+
.trim();
|
|
288
|
+
|
|
289
|
+
const result = await model.generateContent(sanitizedPrompt);
|
|
290
|
+
const response = await result.response;
|
|
291
|
+
let convertedText = response.text().trim();
|
|
292
|
+
|
|
293
|
+
return this.cleanAIResponse(convertedText);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
// If the model fails, try with a faster fallback
|
|
296
|
+
if (modelName !== 'gemini-2.5-flash') {
|
|
297
|
+
console.warn(`Gemini model ${modelName} failed, falling back to gemini-2.5-flash`);
|
|
298
|
+
const fallbackModel = this.gemini.getGenerativeModel({ model: 'gemini-2.5-flash' });
|
|
299
|
+
const prompt = this.getSystemPrompt(platform).replace('{INPUT}', text);
|
|
300
|
+
const sanitizedPrompt = prompt.replace(/[^\x00-\x7F]/g, " ").replace(/\s+/g, ' ').trim();
|
|
301
|
+
const result = await fallbackModel.generateContent(sanitizedPrompt);
|
|
302
|
+
const response = await result.response;
|
|
303
|
+
return this.cleanAIResponse(response.text().trim());
|
|
304
|
+
}
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async convertWithCopilot(text, platform = null) {
|
|
310
|
+
if (!this.copilot) {
|
|
311
|
+
throw new Error('GitHub Copilot not configured');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const model = this.getModelForProvider('copilot');
|
|
315
|
+
|
|
316
|
+
const response = await this.copilot.chat.completions.create({
|
|
317
|
+
model: model,
|
|
318
|
+
messages: [
|
|
319
|
+
{ role: 'system', content: this.getSystemPrompt(platform) },
|
|
320
|
+
{ role: 'user', content: text }
|
|
321
|
+
],
|
|
322
|
+
temperature: 0.3,
|
|
323
|
+
max_tokens: 500,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
let convertedText = response.choices[0].message.content.trim();
|
|
327
|
+
convertedText = this.cleanAIResponse(convertedText);
|
|
328
|
+
|
|
329
|
+
return convertedText;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async convertWithClaude(text, platform = null) {
|
|
333
|
+
if (!this.claude) {
|
|
334
|
+
throw new Error('Claude not configured');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const model = this.getModelForProvider('claude');
|
|
338
|
+
const systemPrompt = this.getSystemPrompt(platform);
|
|
339
|
+
|
|
340
|
+
const response = await this.claude.messages.create({
|
|
341
|
+
model: model,
|
|
342
|
+
max_tokens: 500,
|
|
343
|
+
system: systemPrompt,
|
|
344
|
+
messages: [
|
|
345
|
+
{ role: 'user', content: text }
|
|
346
|
+
],
|
|
347
|
+
temperature: 0.3,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
let convertedText = response.content[0].text.trim();
|
|
351
|
+
convertedText = this.cleanAIResponse(convertedText);
|
|
352
|
+
|
|
353
|
+
return convertedText;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async convertWithGroq(text, platform = null) {
|
|
357
|
+
if (!this.groq) {
|
|
358
|
+
throw new Error('Groq not configured');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const model = this.getModelForProvider('groq');
|
|
362
|
+
|
|
363
|
+
const response = await this.groq.chat.completions.create({
|
|
364
|
+
model: model,
|
|
365
|
+
messages: [
|
|
366
|
+
{ role: 'system', content: this.getSystemPrompt(platform) },
|
|
367
|
+
{ role: 'user', content: text }
|
|
368
|
+
],
|
|
369
|
+
temperature: 0.3,
|
|
370
|
+
max_tokens: 500,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
let convertedText = response.choices[0].message.content.trim();
|
|
374
|
+
convertedText = this.cleanAIResponse(convertedText);
|
|
375
|
+
|
|
376
|
+
return convertedText;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async convertWithCohere(text, platform = null) {
|
|
380
|
+
if (!this.cohere) {
|
|
381
|
+
throw new Error('Cohere not configured');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const model = this.getModelForProvider('cohere');
|
|
385
|
+
|
|
386
|
+
const response = await this.cohere.chat.completions.create({
|
|
387
|
+
model: model,
|
|
388
|
+
messages: [
|
|
389
|
+
{ role: 'system', content: this.getSystemPrompt(platform) },
|
|
390
|
+
{ role: 'user', content: text }
|
|
391
|
+
],
|
|
392
|
+
temperature: 0.3,
|
|
393
|
+
max_tokens: 500,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
let convertedText = response.choices[0].message.content.trim();
|
|
397
|
+
convertedText = this.cleanAIResponse(convertedText);
|
|
398
|
+
|
|
399
|
+
return convertedText;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async convertWithMistral(text, platform = null) {
|
|
403
|
+
if (!this.mistral) {
|
|
404
|
+
throw new Error('Mistral AI not configured');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const model = this.getModelForProvider('mistral');
|
|
408
|
+
|
|
409
|
+
const response = await this.mistral.chat.completions.create({
|
|
410
|
+
model: model,
|
|
411
|
+
messages: [
|
|
412
|
+
{ role: 'system', content: this.getSystemPrompt(platform) },
|
|
413
|
+
{ role: 'user', content: text }
|
|
414
|
+
],
|
|
415
|
+
temperature: 0.3,
|
|
416
|
+
max_tokens: 500,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
let convertedText = response.choices[0].message.content.trim();
|
|
420
|
+
convertedText = this.cleanAIResponse(convertedText);
|
|
421
|
+
|
|
422
|
+
return convertedText;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Clean up AI responses - remove markdown, explanations, etc.
|
|
426
|
+
cleanAIResponse(text) {
|
|
427
|
+
// Remove markdown code blocks
|
|
428
|
+
text = text.replace(/```[\s\S]*?```/g, '').trim();
|
|
429
|
+
text = text.replace(/```/g, '').trim();
|
|
430
|
+
|
|
431
|
+
// Remove any lines that look like explanations (starting with explanatory text)
|
|
432
|
+
const lines = text.split('\n');
|
|
433
|
+
const cleanedLines = lines.filter(line => {
|
|
434
|
+
const trimmed = line.trim();
|
|
435
|
+
if (!trimmed) return false;
|
|
436
|
+
|
|
437
|
+
// Keep lines that are commands or WAIT
|
|
438
|
+
if (trimmed.startsWith('launch ')) return true;
|
|
439
|
+
if (trimmed.startsWith('kill ')) return true;
|
|
440
|
+
if (trimmed === 'home') return true;
|
|
441
|
+
if (trimmed === 'back') return true;
|
|
442
|
+
if (trimmed.startsWith('url ')) return true;
|
|
443
|
+
if (trimmed.startsWith('click ')) return true;
|
|
444
|
+
if (trimmed.startsWith('tap ')) return true;
|
|
445
|
+
if (trimmed.startsWith('longpress ')) return true;
|
|
446
|
+
if (trimmed.startsWith('swipe ')) return true;
|
|
447
|
+
if (trimmed.startsWith('WAIT ')) return true;
|
|
448
|
+
if (trimmed.startsWith('screenshot')) return true;
|
|
449
|
+
if (trimmed.startsWith('restart')) return true;
|
|
450
|
+
if (trimmed.startsWith('rotate ')) return true;
|
|
451
|
+
if (trimmed.startsWith('darkmode')) return true;
|
|
452
|
+
if (trimmed.startsWith('lightmode')) return true;
|
|
453
|
+
if (trimmed.startsWith('airplane ')) return true;
|
|
454
|
+
if (trimmed.startsWith('wifi ')) return true;
|
|
455
|
+
if (trimmed.startsWith('volume ')) return true;
|
|
456
|
+
if (trimmed === 'getLocators') return true;
|
|
457
|
+
if (trimmed === 'recent') return true;
|
|
458
|
+
if (trimmed === 'notifications') return true;
|
|
459
|
+
if (trimmed === 'quicksettings') return true;
|
|
460
|
+
|
|
461
|
+
// If it doesn't start with a known command, it might be text to type
|
|
462
|
+
// Check if previous line was a command that expects text input
|
|
463
|
+
return true; // For now, include it (could be text to type)
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
return cleanedLines.join('\n').trim();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
getAvailableProviders() {
|
|
470
|
+
const available = [];
|
|
471
|
+
|
|
472
|
+
if (this.openai) available.push({ id: 'openai', name: 'OpenAI', icon: '🤖' });
|
|
473
|
+
if (this.gemini) available.push({ id: 'gemini', name: 'Google Gemini', icon: '✨' });
|
|
474
|
+
if (this.claude) available.push({ id: 'claude', name: 'Anthropic Claude', icon: '🧠' });
|
|
475
|
+
if (this.copilot) available.push({ id: 'copilot', name: 'GitHub Copilot', icon: '🐙' });
|
|
476
|
+
if (this.groq) available.push({ id: 'groq', name: 'Groq', icon: '⚡' });
|
|
477
|
+
if (this.cohere) available.push({ id: 'cohere', name: 'Cohere', icon: '🌊' });
|
|
478
|
+
if (this.mistral) available.push({ id: 'mistral', name: 'Mistral AI', icon: '🌬️' });
|
|
479
|
+
|
|
480
|
+
return available;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
setProvider(provider, model = null) {
|
|
484
|
+
const available = this.getAvailableProviders().map(p => p.id);
|
|
485
|
+
if (available.includes(provider)) {
|
|
486
|
+
this.provider = provider;
|
|
487
|
+
if (model) {
|
|
488
|
+
this.model = model;
|
|
489
|
+
}
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
getCurrentProvider() {
|
|
496
|
+
return {
|
|
497
|
+
id: this.provider,
|
|
498
|
+
name: this.getProviderName(this.provider),
|
|
499
|
+
model: this.model || this.getModelForProvider(this.provider),
|
|
500
|
+
available: this.getAvailableProviders(),
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
getProviderName(providerId) {
|
|
505
|
+
const names = {
|
|
506
|
+
openai: 'OpenAI',
|
|
507
|
+
gemini: 'Google Gemini',
|
|
508
|
+
claude: 'Anthropic Claude',
|
|
509
|
+
copilot: 'GitHub Copilot',
|
|
510
|
+
groq: 'Groq',
|
|
511
|
+
cohere: 'Cohere',
|
|
512
|
+
mistral: 'Mistral AI',
|
|
513
|
+
};
|
|
514
|
+
return names[providerId] || 'Unknown';
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
module.exports = AIProviderManager;
|