Skip to content

Commit 09402ba

Browse files
committed
feat: add gemini api support
1 parent bd67da9 commit 09402ba

5 files changed

Lines changed: 66 additions & 23 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ build/
3333

3434
# Optional: lock files (if you want to avoid committing them)
3535
# package-lock.json
36-
# yarn.lock
36+
# yarn.lock
37+
bun.lock

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,5 @@ build/
6464
# Temporary folders
6565
tmp/
6666
temp/
67+
68+
bun.lock

src/ai/ai-analyzer.js

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,52 @@ dotenv.config();
99
* AI-Powered Website Analysis using OpenAI GPT-4o
1010
*/
1111
export class AIAnalyzer {
12-
constructor() {
12+
constructor(aiModel = 'gpt-4o') {
1313
this.openai = null;
1414
this.isEnabled = false;
15+
this.aiModel = aiModel;
1516
this.initializeOpenAI();
1617
}
1718

1819
initializeOpenAI() {
19-
const apiKey = process.env.OPENAI_API_KEY;
20+
const isGemini = this.aiModel.toLowerCase().includes('gemini');
21+
let apiKey = isGemini ? process.env.GEMINI_API_KEY : process.env.OPENAI_API_KEY;
2022

23+
// Fallback if specific key is missing
2124
if (!apiKey) {
25+
apiKey = process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY;
26+
}
27+
28+
if (!apiKey) {
29+
const keyName = isGemini ? 'GEMINI_API_KEY' : 'OPENAI_API_KEY';
2230
console.log(
2331
chalk.yellow(
24-
'⚠️ OpenAI API key not found. AI analysis will be disabled.',
32+
`⚠️ AI analysis API key not found. AI analysis will be disabled.`,
2533
),
2634
);
2735
console.log(
2836
chalk.gray(
29-
' Set OPENAI_API_KEY environment variable to enable AI features.',
37+
` Set ${keyName} environment variable to enable AI features.`,
3038
),
3139
);
3240
return;
3341
}
3442

3543
try {
36-
this.openai = new OpenAI({ apiKey });
44+
const config = { apiKey };
45+
46+
// Use Gemini OpenAI-compatible endpoint if model is gemini or GEMINI_API_KEY is used
47+
if (isGemini || process.env.GEMINI_API_KEY === apiKey) {
48+
config.baseURL = 'https://generativelanguage.googleapis.com/v1beta/openai/';
49+
console.log(chalk.green(`✅ AI analysis enabled with Gemini model: ${this.aiModel}`));
50+
} else {
51+
console.log(chalk.green(`✅ AI analysis enabled with OpenAI model: ${this.aiModel}`));
52+
}
53+
54+
this.openai = new OpenAI(config);
3755
this.isEnabled = true;
38-
console.log(chalk.green('✅ AI analysis enabled with OpenAI GPT-4o'));
3956
} catch (error) {
40-
console.log(chalk.red('❌ Failed to initialize OpenAI:'), error.message);
57+
console.log(chalk.red('❌ Failed to initialize AI client:'), error.message);
4158
}
4259
}
4360

@@ -72,7 +89,7 @@ export class AIAnalyzer {
7289
);
7390

7491
const response = await this.openai.chat.completions.create({
75-
model: 'gpt-4o',
92+
model: this.aiModel,
7693
messages: [
7794
{
7895
role: 'system',
@@ -225,7 +242,7 @@ Provide actionable optimization steps in JSON format:
225242
}`;
226243

227244
const response = await this.openai.chat.completions.create({
228-
model: 'gpt-4o',
245+
model: this.aiModel,
229246
messages: [
230247
{
231248
role: 'system',

src/cli.js

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,33 +48,48 @@ function loadEnvWithPriority() {
4848
loadEnvWithPriority();
4949

5050
/**
51-
* Validate and configure OpenAI API key for AI features.
52-
* Uses OPENAI_API_KEY from environment (shell, .env.local, .env) or --openai-key if provided.
51+
* Validate and configure AI API key for AI features.
52+
* Priority:
53+
* 1. CLI flag (--openai-key)
54+
* 2. GEMINI_API_KEY (if model is gemini-*)
55+
* 3. OPENAI_API_KEY
5356
*/
54-
function validateAISetup(aiEnabled, openaiApiKey) {
57+
function validateAISetup(aiEnabled, openaiApiKey, aiModel) {
5558
if (!aiEnabled) return false;
5659

57-
// CLI flag has highest priority
58-
const finalApiKey = openaiApiKey || process.env.OPENAI_API_KEY;
60+
const isGemini = aiModel.toLowerCase().includes('gemini');
61+
62+
// CLI flag has highest priority (for OpenAI)
63+
let finalApiKey = openaiApiKey;
64+
65+
if (!finalApiKey) {
66+
if (isGemini) {
67+
finalApiKey = process.env.GEMINI_API_KEY || process.env.OPENAI_API_KEY;
68+
} else {
69+
finalApiKey = process.env.OPENAI_API_KEY;
70+
}
71+
}
5972

6073
if (!finalApiKey) {
74+
const keyName = isGemini ? 'GEMINI_API_KEY or OPENAI_API_KEY' : 'OPENAI_API_KEY';
6175
console.log('');
6276
console.log(
6377
chalk.yellow(
64-
'⚠️ AI features requested but no OPENAI_API_KEY found (checked env, .env.local, and .env).',
78+
`⚠️ AI features requested but no ${keyName} found (checked env, .env.local, and .env).`,
6579
),
6680
);
6781
console.log(
6882
chalk.white(
69-
'Add OPENAI_API_KEY to your .env.local or .env file, export it in your shell, or pass --openai-key "sk-..."',
83+
'Add the API key to your .env.local or .env file, export it in your shell, or pass --openai-key "sk-..."',
7084
),
7185
);
7286
console.log(chalk.dim('Continuing without AI features...'));
7387
console.log('');
7488
return false;
7589
}
7690

77-
if (!finalApiKey.startsWith('sk-')) {
91+
// Basic format check (OpenAI starts with sk-, Gemini often doesn't but we'll be flexible)
92+
if (!isGemini && !finalApiKey.startsWith('sk-')) {
7893
console.log('');
7994
console.log(
8095
chalk.yellow('⚠️ Invalid OpenAI API key format (must start with "sk-")'),
@@ -84,9 +99,11 @@ function validateAISetup(aiEnabled, openaiApiKey) {
8499
return false;
85100
}
86101

87-
// If provided via flag, ensure it’s in process.env for downstream code
102+
// Ensure key is in process.env for downstream code if provided via flag or specific var
88103
if (openaiApiKey) {
89104
process.env.OPENAI_API_KEY = openaiApiKey;
105+
} else if (isGemini && process.env.GEMINI_API_KEY) {
106+
// Already in env
90107
}
91108

92109
return true;
@@ -112,12 +129,17 @@ program
112129
)
113130
.option(
114131
'--ai',
115-
'Enable AI-powered website analysis (reads OPENAI_API_KEY from env, .env.local, or .env)',
132+
'Enable AI-powered website analysis (reads OPENAI_API_KEY or GEMINI_API_KEY from env)',
116133
false,
117134
)
135+
.option(
136+
'--ai-model <model>',
137+
'AI model to use for analysis',
138+
'gemini-3-flash-preview',
139+
)
118140
.option(
119141
'--openai-key <key>',
120-
'OpenAI API key for AI features (overrides env/.env.local/.env for this run)',
142+
'OpenAI API key for AI features (overrides env for this run)',
121143
)
122144
.option('--debug', 'Enable detailed debug logging and error traces', false)
123145
.option(
@@ -135,12 +157,13 @@ program
135157
if (!url.startsWith('http')) url = 'https://' + url;
136158
new URL(url); // validate
137159

138-
const aiEnabled = validateAISetup(options.ai, options.openaiKey);
160+
const aiEnabled = validateAISetup(options.ai, options.openaiKey, options.aiModel);
139161

140162
const config = {
141163
outputDir: options.output,
142164
clean: options.clean,
143165
ai: aiEnabled,
166+
aiModel: options.aiModel,
144167
debug: options.debug,
145168
timeout: parseInt(options.timeout),
146169
headless: options.headless !== 'false',

src/core/mirror-cloner.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export class MirrorCloner {
6060
this.logger = new Logger(this.options);
6161

6262
// Optional AI
63-
this.aiAnalyzer = this.options.ai ? new AIAnalyzer() : null;
63+
this.aiAnalyzer = this.options.ai ? new AIAnalyzer(this.options.aiModel) : null;
6464

6565
// Data
6666
this.$ = null; // Cheerio DOM instance

0 commit comments

Comments
 (0)