@notes-sync/service
Version:
Background service for AI-powered note synchronization
148 lines (144 loc) • 6.15 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeminiProvider = void 0;
const shared_1 = require("@notes-sync/shared");
const genai_1 = require("@google/genai");
const logger_1 = require("../../logger");
class GeminiProvider {
constructor(apiKey, model = 'gemini-2.5-flash-lite', quoteConfig) {
this.apiKey = apiKey;
this.model = model;
this.name = 'gemini';
if (!apiKey) {
throw new Error('Gemini API key is required');
}
// Initialize the Google GenAI client
this.ai = new genai_1.GoogleGenAI({ apiKey: this.apiKey });
// Set defaults for quote configuration
this.config = {
maxLength: quoteConfig?.maxLength || 30,
focus: quoteConfig?.focus || ['productivity', 'personal growth'],
adjectives: quoteConfig?.adjectives || ['historical', 'motivational'],
additionalRules: quoteConfig?.additionalRules || [
'If quoting from spiritual texts, include the book name as author',
'Prefer wisdom that applies to daily work and life',
],
};
}
async generateQuote(request) {
try {
const prompt = this.buildQuotePrompt(request);
const response = await this.ai.models.generateContent({
model: this.model,
contents: prompt,
config: {
temperature: 0.8,
topK: 20,
topP: 0.8,
maxOutputTokens: 150,
},
});
if (!response.text) {
throw new shared_1.AIError('No text content in Gemini API response', this.name);
}
const parsed = this.parseQuoteResponse(response.text);
logger_1.Logger.log(`Generated quote via Gemini: "${parsed.quote}" - ${parsed.author}`);
return parsed;
}
catch (error) {
// Handle rate limiting errors
if (error instanceof Error && error.message.includes('429')) {
throw new shared_1.AIRateLimitError(this.name);
}
if (error instanceof shared_1.AIError) {
throw error;
}
logger_1.Logger.error(`Gemini quote generation failed: ${error.message}`);
throw new shared_1.AIError(`Failed to generate quote: ${error.message}`, this.name);
}
}
buildQuotePrompt(request) {
if (!this.config) {
return 'Find a short, daily quote for someone focused on productivity in the format -> "Quote text" - Author Name -> Under 50 characters.';
}
const theme = request.theme || 'productivity';
const context = request.context
? `\n\nContext about the user's recent work: ${request.context}`
: '';
const additionalRulesText = this.config.additionalRules?.length
? `\n- ${this.config.additionalRules.join('\n- ')}`
: '';
return `Generate a short, daily quote for someone focused on ${this.config.focus?.join(' and ')}.
Theme: ${theme}
${context}
Requirements:
- Keep it under ${this.config.maxLength} words
- Make it ${this.config.adjectives?.join(' and ')}
- Include the author's name or passage if from spiritual book of unknown author
- Format as: "Quote text" - Author Name
- Avoid cliches and focus on practical wisdom${additionalRulesText}
Examples:
"The man who lies to himself and listens to his own lie comes to a point that he cannot distinguish the truth within him, or around him, and so loses all respect for himself and for others." - Fyodor Dostoyevsky
"Pain is inevitable. Suffering is optional. The difference lies in what we choose to do with our pain." - Haruki Murakami
"Your future self is counting on what you do today" - Unknown
Find a real quote ${this.config.allowGenerated && ' or generate one.'}`;
}
async processQuery(request) {
try {
const response = await this.ai.models.generateContent({
model: this.model,
contents: request.query,
config: {
temperature: 0.7,
topK: 40,
topP: 0.9,
maxOutputTokens: request.maxLength || 500,
},
});
if (!response.text) {
throw new shared_1.AIError('No text content in Gemini API response', this.name);
}
logger_1.Logger.log(`Processed AI query: ${request.query.substring(0, 50)}...`);
return {
response: response.text.trim(),
};
}
catch (error) {
// Handle rate limiting errors
if (error instanceof Error && error.message.includes('429')) {
throw new shared_1.AIRateLimitError(this.name);
}
if (error instanceof shared_1.AIError) {
throw error;
}
logger_1.Logger.error(`Gemini query processing failed: ${error.message}`);
throw new shared_1.AIError(`Failed to process query: ${error.message}`, this.name);
}
}
parseQuoteResponse(text) {
// Try to parse quote in format: "Quote text" - Author
const quoteMatch = text.match(/[""]([^"""]+)[""] - (.+)/);
if (quoteMatch) {
return {
quote: quoteMatch[1].trim(),
author: quoteMatch[2].trim(),
};
}
// Fallback: try to find any quoted text
const fallbackMatch = text.match(/[""]([^"""]+)[""]/) || text.match(/"([^"]+)"/);
if (fallbackMatch) {
return {
quote: fallbackMatch[1].trim(),
author: 'Unknown',
};
}
// Last resort: use the first line and attribute to Unknown
const firstLine = text.split('\n')[0].trim();
return {
quote: firstLine,
author: 'Unknown',
};
}
}
exports.GeminiProvider = GeminiProvider;
//# sourceMappingURL=gemini-provider.js.map