locatai-ts
Version:
Enterprise-grade AI-powered element locator for Selenium WebDriver - TypeScript implementation
181 lines (179 loc) • 6.95 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeminiProvider = void 0;
const generative_ai_1 = require("@google/generative-ai");
const readline = __importStar(require("readline"));
class GeminiProvider {
constructor(config) {
const apiKey = config?.apiKey;
if (!apiKey) {
throw new Error('API key is not set. Please check your appsettings.json file');
}
this.modelName = config?.model || 'gemini-2.0-flash';
this.client = new generative_ai_1.GoogleGenerativeAI(apiKey);
this.model = this.client.getGenerativeModel({
model: this.modelName,
generationConfig: {
temperature: 0.7,
maxOutputTokens: 2048,
}
});
}
createInterface() {
return readline.createInterface({
input: process.stdin,
output: process.stdout
});
}
async getUserInput(prompt) {
const rl = this.createInterface();
return new Promise((resolve) => {
rl.question(prompt, (answer) => {
rl.close();
resolve(answer);
});
});
}
async generateResponse(userInput, systemPrompt) {
try {
// Create properly typed chat session
const chat = this.model.startChat({
// Note: Gemini doesn't directly support system prompt, but we can try to prepend it
history: systemPrompt ? [
{
role: "user",
parts: [{ text: `SYSTEM: ${systemPrompt}` }],
},
{
role: "model",
parts: [{ text: "I understand and will follow those instructions." }],
}
] : []
});
// Optimize for HTML content if present
let promptText = userInput;
if (userInput.includes('DOM Content:')) {
promptText = this.optimizeHtmlContent(userInput);
}
const result = await chat.sendMessage(promptText);
const response = result.response.text();
return this.cleanLocatorResponse(response);
}
catch (error) {
console.error('Error calling Gemini API:', error);
throw error;
}
}
async interact() {
console.log(`Gemini ${this.modelName} Chat (Type 'exit' to quit)`);
while (true) {
const userInput = await this.getUserInput("\nYou: ");
if (userInput.toLowerCase() === 'exit') {
console.log("Goodbye!");
break;
}
console.log("\nThinking...");
try {
const response = await this.generateResponse(userInput);
console.log(`\nAI: ${response}`);
}
catch (error) {
console.log("Sorry, there was an error generating a response.");
}
}
}
/**
* Optimizes HTML content in the prompt to reduce token usage
*/
optimizeHtmlContent(prompt) {
// Extract element description from the prompt
const elementMatch = prompt.match(/Generate a locator for: (.+?)$/m);
if (!elementMatch)
return prompt;
const elementDescription = elementMatch[1];
// Extract HTML content between DOM Content: and Generate a locator
const match = prompt.match(/DOM Content:\s*(.+?)(?=\s*\n\s*Generate a locator)/s);
if (match && match[1]) {
const fullHtml = match[1];
// Truncate HTML to reduce token count
let truncatedHtml = fullHtml;
if (fullHtml.length > 3000) {
truncatedHtml = fullHtml.substring(0, 3000) + "...";
}
return `DOM Content (truncated for efficiency):
${truncatedHtml}
Generate a locator for: ${elementDescription}
Return the locator in this format: locatorType=value|confidence=0.XX
For example: id=username|confidence=0.9
You can provide multiple locators with confidence scores.`;
}
return prompt;
}
/**
* Cleans and standardizes the locator response
*/
cleanLocatorResponse(response) {
if (!response)
return '';
// Remove markdown code formatting and explanations
let cleaned = response.replace(/```[\s\S]*?```/g, '');
cleaned = cleaned.replace(/`/g, '');
// Split by lines and keep only those that seem to contain locators
const lines = cleaned.split('\n').filter(line => {
const trimmed = line.trim();
return (trimmed.includes('=') ||
trimmed.includes(':') ||
/^[a-z]+ selector/i.test(trimmed));
});
// Convert "xpath: //div" format to "xpath=//div" if needed
const standardized = lines.map(line => {
if (line.includes(':') && !line.includes('=')) {
return line.replace(/([a-z-]+):\s+/gi, '$1=');
}
return line;
});
// Add confidence scores to lines that don't have them
const withConfidence = standardized.map(line => {
if (!line.includes('confidence=') && !line.includes('|')) {
return `${line}|confidence=0.8`;
}
return line;
});
return withConfidence.join('\n');
}
}
exports.GeminiProvider = GeminiProvider;
//# sourceMappingURL=GeminiProvider.js.map