Skip to content

Commit 40c51b8

Browse files
committed
Final
1 parent 939d7e5 commit 40c51b8

File tree

9 files changed

+183
-76
lines changed

9 files changed

+183
-76
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "shellmind",
3-
"version": "2.0.9",
3+
"version": "2.0.10",
44
"main": "src/index.js",
55
"type": "module",
66
"bin": {
@@ -14,7 +14,8 @@
1414
"format": "prettier --write .",
1515
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles",
1616
"build": "rollup -c",
17-
"start": "node dist/index.js"
17+
"start": "node dist/index.js",
18+
"link": "npm link"
1819
},
1920
"jest": {
2021
"transform": {}

src/api/api.js

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { GoogleGenerativeAI } from '@google/generative-ai';
22
import { Marked } from 'marked';
33
import cliHtml from 'cli-html';
4-
import { env } from '../configs/env.js';
54
import ora from 'ora';
65
import {
76
setChatHistoryFilePath,
@@ -17,18 +16,34 @@ marked.setOptions({
1716
breaks: true,
1817
});
1918

20-
const genAI = new GoogleGenerativeAI(env.GEMINI_API_KEY);
21-
const model = genAI.getGenerativeModel({
22-
model: 'gemini-1.5-flash',
23-
systemInstruction,
24-
tools: [
25-
{
26-
codeExecution: {},
27-
},
28-
],
29-
});
19+
// Initialize these as null - they'll be set when API key is available
20+
let genAI = null;
21+
let model = null;
22+
23+
// Function to initialize the AI model with the provided API key
24+
export function initializeAI(apiKey) {
25+
if (!apiKey) {
26+
throw new Error('API key is required to initialize the AI model');
27+
}
28+
29+
genAI = new GoogleGenerativeAI(apiKey);
30+
model = genAI.getGenerativeModel({
31+
model: 'gemini-1.5-flash',
32+
systemInstruction,
33+
tools: [
34+
{
35+
codeExecution: {},
36+
},
37+
],
38+
});
39+
}
3040

3141
export async function askQuestion(prompt, fileResponse = '', filePath = null) {
42+
// Check if AI model is initialized
43+
if (!model) {
44+
throw new Error('AI model not initialized. Please call initializeAI() with your API key first.');
45+
}
46+
3247
console.log('Asking question:', prompt);
3348

3449
const spinner = ora('Generating response...').start();
@@ -72,6 +87,8 @@ export async function askQuestion(prompt, fileResponse = '', filePath = null) {
7287
// Save updated chat history
7388
saveChatHistory(chatHistory);
7489
} catch (error) {
90+
spinner.stop();
7591
console.error('Error while generating content:', error);
92+
throw error; // Re-throw so calling code can handle it
7693
}
77-
}
94+
}

src/api/fileManager.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
import { GoogleAIFileManager } from '@google/generative-ai/server';
22
import ora from 'ora';
3-
import { env } from '../configs/env.js';
43
import chalk from 'chalk';
54

6-
const fileManager = new GoogleAIFileManager(env.GEMINI_API_KEY);
5+
// Initialize as null - will be set when API key is available
6+
let fileManager = null;
7+
8+
// Function to initialize the file manager with the provided API key
9+
export function initializeFileManager(apiKey) {
10+
if (!apiKey) {
11+
throw new Error('API key is required to initialize the file manager');
12+
}
13+
14+
fileManager = new GoogleAIFileManager(apiKey);
15+
}
716

817
export async function uploadFile(filePath, mimeType = 'text/plain', displayName = 'file.txt') {
18+
// Check if file manager is initialized
19+
if (!fileManager) {
20+
throw new Error('File manager not initialized. Please call initializeFileManager() with your API key first.');
21+
}
22+
923
const spinner = ora('Uploading file...').start();
1024

1125
try {
@@ -19,4 +33,4 @@ export async function uploadFile(filePath, mimeType = 'text/plain', displayName
1933
console.error(chalk.red('Error uploading file:'), error);
2034
throw error;
2135
}
22-
}
36+
}

src/cli/cli.js

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import path from 'path';
55
import readline from 'readline';
66
import { handleQuestionLoop } from './handleQuestionLoop.js';
77
import { handleSession } from './sessionManager.js';
8+
import { initializeAI } from '../api/api.js';
9+
import { initializeFileManager } from '../api/fileManager.js';
810

911
const program = new Command();
1012

@@ -54,26 +56,40 @@ async function promptAndSaveApiKey() {
5456
async function getApiKey(forceReset = false) {
5557
ensureDirectoryExists();
5658

57-
if (!forceReset && fs.existsSync(CONFIG_PATH)) {
59+
// If forced reset, skip file checking and prompt for new key
60+
if (forceReset) {
61+
console.log(chalk.yellow('🔄 Resetting API key...'));
62+
return await promptAndSaveApiKey();
63+
}
64+
65+
// Check if config file exists
66+
if (fs.existsSync(CONFIG_PATH)) {
5867
try {
5968
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
69+
70+
// Check if API key exists and is not empty
6071
if (config.apiKey && config.apiKey.trim() !== '') {
6172
return config.apiKey;
6273
} else {
63-
console.log(chalk.red('❌ API key missing in config file.'));
74+
// File exists but API key is missing/empty
75+
console.log(chalk.yellow('⚠️ API key not found in existing config file.'));
76+
return await promptAndSaveApiKey();
6477
}
6578
} catch (err) {
79+
// File exists but has invalid JSON
6680
console.error(chalk.red('❌ Invalid JSON in chatbot-key.json. Recreating file...'));
81+
return await promptAndSaveApiKey();
6782
}
83+
} else {
84+
// File doesn't exist - first time setup
85+
console.log(chalk.blue('🆕 First time setup detected.'));
86+
return await promptAndSaveApiKey();
6887
}
69-
70-
// If file missing, corrupted, or forced reset → prompt user
71-
return await promptAndSaveApiKey();
7288
}
7389

7490
// CLI setup
7591
program
76-
.version('2.0.9')
92+
.version('2.0.10')
7793
.description('AI Chatbot CLI')
7894
.option('-f, --file', 'Ask questions from a file')
7995
.option('-s, --session', 'Start a new session')
@@ -86,6 +102,16 @@ program
86102
// Ensure API key is available
87103
const apiKey = await getApiKey(options.resetKey);
88104

105+
// Initialize AI with the API key
106+
try {
107+
initializeAI(apiKey);
108+
initializeFileManager(apiKey);
109+
console.log(chalk.green('✅ AI model and file manager initialized successfully!'));
110+
} catch (error) {
111+
console.error(chalk.red('❌ Failed to initialize AI services:'), error.message);
112+
process.exit(1);
113+
}
114+
89115
// You can now use `apiKey` in your chatbot
90116
if (options.session && options.file) {
91117
const filePath = await handleSession(options.session);
@@ -100,4 +126,4 @@ program
100126
}
101127
});
102128

103-
export const cli = program;
129+
export const cli = program;

src/cli/handleQuestionLoop.js

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@ import { askQuestion } from '../api/api.js';
33
import { selectFile } from './selectFile.js';
44
import chalk from 'chalk';
55

6-
export async function handleQuestionLoop(filePath = null, enableFileSelection = false) {
6+
export async function handleQuestionLoop(filePath = null, enableFileSelection = false, apiKey = null) {
77
let continueAsking = true;
88

9+
// Verify API key is provided
10+
if (!apiKey) {
11+
console.error(chalk.red('❌ No API key provided to handleQuestionLoop. Please restart the application.'));
12+
return;
13+
}
14+
915
while (continueAsking) {
1016
let fileReference = '';
1117

@@ -17,15 +23,38 @@ export async function handleQuestionLoop(filePath = null, enableFileSelection =
1723
});
1824

1925
if (useFile) {
20-
fileReference = await selectFile();
21-
if (!fileReference) {
22-
// Retry file selection if it fails
23-
continueAsking = await confirm({
24-
message: 'Do you want to try again?',
26+
try {
27+
fileReference = await selectFile();
28+
if (!fileReference) {
29+
// Retry file selection if it fails
30+
continueAsking = await confirm({
31+
message: 'Do you want to try again?',
32+
default: true,
33+
});
34+
if (!continueAsking) break;
35+
continue;
36+
}
37+
} catch (error) {
38+
if (error.message && error.message.includes('not initialized')) {
39+
console.error(chalk.red('❌ Services are not properly initialized. Please restart the application.'));
40+
break;
41+
}
42+
console.error(chalk.red('Error during file selection:'), error);
43+
44+
// Ask if user wants to continue without file
45+
const continueWithoutFile = await confirm({
46+
message: 'File selection failed. Do you want to continue without a file?',
2547
default: true,
2648
});
27-
if (!continueAsking) break;
28-
continue;
49+
50+
if (!continueWithoutFile) {
51+
continueAsking = await confirm({
52+
message: 'Do you want to try again?',
53+
default: true,
54+
});
55+
if (!continueAsking) break;
56+
continue;
57+
}
2958
}
3059
}
3160
}
@@ -35,16 +64,42 @@ export async function handleQuestionLoop(filePath = null, enableFileSelection =
3564
message: 'Ask your question: (Save and close editor to submit)',
3665
});
3766

38-
if (!question) {
39-
console.log(chalk.red('No question provided. Exiting...'));
40-
break;
67+
if (!question || question.trim() === '') {
68+
console.log(chalk.yellow('⚠️ No question provided.'));
69+
70+
// Ask if user wants to try again
71+
continueAsking = await confirm({
72+
message: 'Do you want to try asking another question?',
73+
default: true,
74+
});
75+
continue;
4176
}
4277

4378
// Process the Question
4479
try {
4580
await askQuestion(question, fileReference, filePath);
4681
} catch (error) {
47-
console.error(chalk.red('Error processing question:'), error);
82+
if (error.message && error.message.includes('not initialized')) {
83+
console.error(chalk.red('❌ AI services are not properly initialized. Please restart the application.'));
84+
break;
85+
}
86+
87+
console.error(chalk.red('Error processing question:'), error.message || error);
88+
89+
// Ask if user wants to try again with the same question
90+
const retryQuestion = await confirm({
91+
message: 'There was an error processing your question. Do you want to try again?',
92+
default: true,
93+
});
94+
95+
if (retryQuestion) {
96+
// Retry the same question
97+
try {
98+
await askQuestion(question, fileReference, filePath);
99+
} catch (retryError) {
100+
console.error(chalk.red('Error on retry:'), retryError.message || retryError);
101+
}
102+
}
48103
}
49104

50105
// Ask if User Wants to Continue
@@ -54,5 +109,5 @@ export async function handleQuestionLoop(filePath = null, enableFileSelection =
54109
});
55110
}
56111

57-
console.log(chalk.green('Goodbye!'));
58-
}
112+
console.log(chalk.green('👋 Goodbye!'));
113+
}

src/cli/selectFile.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,17 @@ export async function selectFile() {
5353
const editorResponse = await editor({
5454
message: 'Ask your question about the file:',
5555
});
56-
await askQuestion(`${fileContent}\n${editorResponse}`);
56+
57+
// Check if askQuestion is properly initialized
58+
try {
59+
await askQuestion(`${fileContent}\n${editorResponse}`);
60+
} catch (error) {
61+
if (error.message.includes('not initialized')) {
62+
console.error(chalk.red('❌ AI services are not properly initialized. Please restart the application.'));
63+
return null;
64+
}
65+
throw error; // Re-throw other errors
66+
}
5767
return null;
5868
} catch (error) {
5969
console.error(chalk.red('Failed to read file as text:'), error);
@@ -65,7 +75,11 @@ export async function selectFile() {
6575
try {
6676
return await uploadFile(filePath, mimeType, fileDisplayName);
6777
} catch (error) {
78+
if (error.message.includes('not initialized')) {
79+
console.error(chalk.red('❌ File manager is not properly initialized. Please restart the application.'));
80+
return null;
81+
}
6882
console.error(chalk.red('Failed to upload file:'), error);
6983
return null;
7084
}
71-
}
85+
}

src/cli/sessionManager.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ export async function handleSession(enableSession = false) {
66
if (!enableSession) return null; // Skip if session handling is disabled
77

88
const sessionDirectory = path.join(process.cwd(), './jsons/sessions');
9-
if (!fs.existsSync(sessionDirectory)) fs.mkdirSync(sessionDirectory, { recursive: true });
9+
if (!fs.existsSync(sessionDirectory))
10+
fs.mkdirSync(sessionDirectory, { recursive: true });
1011

11-
const sessionFiles = fs.readdirSync(sessionDirectory).filter((file) => file.endsWith('.json'));
12+
const sessionFiles = fs
13+
.readdirSync(sessionDirectory)
14+
.filter((file) => file.endsWith('.json'));
1215

1316
const shouldSelectSession = await confirm({
14-
message: 'Would you like to select a previous session or create a new one?',
17+
message: 'Would you like to select a previous session?',
1518
default: true,
1619
});
1720

0 commit comments

Comments
 (0)