import OpenAI from 'openai';
import {
DeepSeekConfig,
PatternGenerationOptions,
PatternEnhancementOptions,
AIPatternResult,
PatternAnalysisResult,
SYSTEM_PROMPTS,
DEFAULT_DEEPSEEK_CONFIG
} from '../types/DeepSeek.js';
import { Logger } from '../utils/Logger.js';
import { PatternGenerator } from './PatternGenerator.js';
/**
* Service for AI-powered pattern generation using DeepSeek API
* Uses OpenAI SDK with DeepSeek's OpenAI-compatible endpoint
*/
export class DeepSeekService {
private client: OpenAI | null = null;
private config: DeepSeekConfig;
private logger: Logger;
private fallbackGenerator: PatternGenerator;
private isConfigured: boolean = false;
constructor(config?: Partial<DeepSeekConfig>) {
this.logger = new Logger();
this.fallbackGenerator = new PatternGenerator();
// Get API key from environment or config
const apiKey = config?.apiKey || process.env.DEEPSEEK_API_KEY || '';
this.config = {
...DEFAULT_DEEPSEEK_CONFIG,
...config,
apiKey
};
if (apiKey) {
this.initializeClient();
} else {
this.logger.warn('DeepSeek API key not configured. AI features will use fallback generation.');
}
}
/**
* Initialize the OpenAI client with DeepSeek configuration
*/
private initializeClient(): void {
try {
this.client = new OpenAI({
apiKey: this.config.apiKey,
baseURL: this.config.baseUrl
});
this.isConfigured = true;
this.logger.info('DeepSeek API client initialized successfully');
} catch (error: any) {
this.logger.error('Failed to initialize DeepSeek client', error);
this.isConfigured = false;
}
}
/**
* Check if the service is properly configured
*/
isAvailable(): boolean {
return this.isConfigured && this.client !== null;
}
/**
* Generate a Strudel pattern from a natural language description
*/
async generatePatternFromPrompt(options: PatternGenerationOptions): Promise<AIPatternResult> {
const { prompt, style, key, bpm, complexity, includeComments } = options;
// If not configured, fall back to static generation
if (!this.isAvailable()) {
return this.generateFallbackPattern(options);
}
try {
const userPrompt = this.buildGenerationPrompt(options);
const response = await this.client!.chat.completions.create({
model: this.config.model,
messages: [
{ role: 'system', content: SYSTEM_PROMPTS.patternGeneration },
{ role: 'user', content: userPrompt }
],
max_tokens: this.config.maxTokens,
temperature: this.config.temperature
});
const content = response.choices[0]?.message?.content;
if (!content) {
throw new Error('Empty response from DeepSeek API');
}
// Extract code from response (remove markdown if present)
const pattern = this.extractCode(content);
return {
success: true,
pattern,
explanation: includeComments ? content : undefined,
model: this.config.model,
usage: response.usage ? {
promptTokens: response.usage.prompt_tokens,
completionTokens: response.usage.completion_tokens,
totalTokens: response.usage.total_tokens
} : undefined
};
} catch (error: any) {
this.logger.error('DeepSeek pattern generation failed', error);
// Extract detailed error information
let errorMessage = error.message || 'Unknown error';
if (error.status) errorMessage += ` (HTTP ${error.status})`;
if (error.code) errorMessage += ` [${error.code}]`;
if (error.response?.data?.error?.message) {
errorMessage = error.response.data.error.message;
if (error.status) errorMessage += ` (HTTP ${error.status})`;
}
if (error.type) errorMessage += ` [Type: ${error.type}]`;
// Fall back to static generation
const fallback = this.generateFallbackPattern(options);
fallback.error = `AI generation failed: ${errorMessage}. Using fallback.`;
return fallback;
}
}
/**
* Enhance an existing pattern using AI
*/
async enhancePattern(options: PatternEnhancementOptions): Promise<AIPatternResult> {
const { pattern, enhancementType, targetStyle, intensity } = options;
if (!this.isAvailable()) {
return {
success: false,
error: 'DeepSeek API not configured',
isFallback: true,
pattern: this.fallbackGenerator.generateVariation(pattern, 'subtle')
};
}
try {
const userPrompt = `Enhance this Strudel pattern with a "${enhancementType}" modification${targetStyle ? ` targeting ${targetStyle} style` : ''}${intensity ? ` at intensity ${intensity}` : ''}:
${pattern}
Output only the enhanced Strudel code.`;
const response = await this.client!.chat.completions.create({
model: this.config.model,
messages: [
{ role: 'system', content: SYSTEM_PROMPTS.patternEnhancement },
{ role: 'user', content: userPrompt }
],
max_tokens: this.config.maxTokens,
temperature: Math.min(this.config.temperature + (intensity || 0) * 0.3, 1.5)
});
const content = response.choices[0]?.message?.content;
if (!content) {
throw new Error('Empty response from DeepSeek API');
}
return {
success: true,
pattern: this.extractCode(content),
model: this.config.model,
usage: response.usage ? {
promptTokens: response.usage.prompt_tokens,
completionTokens: response.usage.completion_tokens,
totalTokens: response.usage.total_tokens
} : undefined
};
} catch (error: any) {
this.logger.error('DeepSeek pattern enhancement failed', error);
// Extract detailed error information
let errorMessage = error.message || 'Unknown error';
if (error.status) errorMessage += ` (HTTP ${error.status})`;
if (error.code) errorMessage += ` [${error.code}]`;
if (error.response?.data?.error?.message) {
errorMessage = error.response.data.error.message;
if (error.status) errorMessage += ` (HTTP ${error.status})`;
}
if (error.type) errorMessage += ` [Type: ${error.type}]`;
return {
success: false,
error: errorMessage,
isFallback: true,
pattern: this.fallbackGenerator.generateVariation(pattern, enhancementType === 'complexity' ? 'extreme' : 'subtle')
};
}
}
/**
* Analyze a pattern and provide insights
*/
async analyzePattern(pattern: string): Promise<PatternAnalysisResult> {
if (!this.isAvailable()) {
return {
success: false,
error: 'DeepSeek API not configured'
};
}
try {
const response = await this.client!.chat.completions.create({
model: this.config.model,
messages: [
{ role: 'system', content: SYSTEM_PROMPTS.patternAnalysis },
{ role: 'user', content: `Analyze this Strudel pattern:\n\n${pattern}` }
],
max_tokens: 1024,
temperature: 0.3 // Lower temperature for analysis
});
const content = response.choices[0]?.message?.content;
if (!content) {
throw new Error('Empty response from DeepSeek API');
}
// Parse JSON response
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
const analysis = JSON.parse(jsonMatch[0]);
return {
success: true,
analysis
};
}
// If no JSON found, create structured response from text
return {
success: true,
analysis: {
description: content,
complexity: 'moderate',
suggestions: []
}
};
} catch (error: any) {
this.logger.error('DeepSeek pattern analysis failed', error);
// Extract detailed error information
let errorMessage = error.message || 'Unknown error';
if (error.status) errorMessage += ` (HTTP ${error.status})`;
if (error.code) errorMessage += ` [${error.code}]`;
if (error.response?.data?.error?.message) {
errorMessage = error.response.data.error.message;
if (error.status) errorMessage += ` (HTTP ${error.status})`;
}
if (error.type) errorMessage += ` [Type: ${error.type}]`;
return {
success: false,
error: errorMessage
};
}
}
/**
* Generate variations of a pattern using AI
*/
async generateVariations(pattern: string, count: number = 3): Promise<AIPatternResult[]> {
if (!this.isAvailable()) {
// Generate static variations
const variations = ['subtle', 'moderate', 'extreme'];
return variations.slice(0, count).map(type => ({
success: true,
pattern: this.fallbackGenerator.generateVariation(pattern, type),
isFallback: true
}));
}
try {
const response = await this.client!.chat.completions.create({
model: this.config.model,
messages: [
{ role: 'system', content: SYSTEM_PROMPTS.variationGeneration },
{ role: 'user', content: `Generate ${count} creative variations of this Strudel pattern. Separate each variation with "---":
${pattern}` }
],
max_tokens: this.config.maxTokens * count,
temperature: 0.9 // Higher temperature for creativity
});
const content = response.choices[0]?.message?.content;
if (!content) {
throw new Error('Empty response from DeepSeek API');
}
// Split by separator and extract code
const variations = content.split('---').map((v: string) => this.extractCode(v.trim())).filter((v: string) => v.length > 0);
return variations.map((v: string) => ({
success: true,
pattern: v,
model: this.config.model
}));
} catch (error: any) {
this.logger.error('DeepSeek variation generation failed', error);
// Extract detailed error information
let errorMessage = error.message || 'Unknown error';
if (error.status) errorMessage += ` (HTTP ${error.status})`;
if (error.code) errorMessage += ` [${error.code}]`;
if (error.response?.data?.error?.message) {
errorMessage = error.response.data.error.message;
if (error.status) errorMessage += ` (HTTP ${error.status})`;
}
if (error.type) errorMessage += ` [Type: ${error.type}]`;
return [{
success: false,
error: errorMessage,
isFallback: true
}];
}
}
/**
* Get an explanation of what a pattern does
*/
async explainPattern(pattern: string): Promise<string> {
if (!this.isAvailable()) {
return 'DeepSeek API not configured. Unable to explain pattern.';
}
try {
const response = await this.client!.chat.completions.create({
model: this.config.model,
messages: [
{ role: 'system', content: 'You are a helpful Strudel.cc expert. Explain what the given pattern does in simple terms.' },
{ role: 'user', content: `Explain this Strudel pattern:\n\n${pattern}` }
],
max_tokens: 512,
temperature: 0.5
});
return response.choices[0]?.message?.content || 'Unable to generate explanation.';
} catch (error: any) {
this.logger.error('DeepSeek explanation failed', error);
// Extract detailed error information
let errorMessage = error.message || 'Unknown error';
if (error.status) errorMessage += ` (HTTP ${error.status})`;
if (error.code) errorMessage += ` [${error.code}]`;
if (error.response?.data?.error?.message) {
errorMessage = error.response.data.error.message;
if (error.status) errorMessage += ` (HTTP ${error.status})`;
}
if (error.type) errorMessage += ` [Type: ${error.type}]`;
return `Error explaining pattern: ${errorMessage}`;
}
}
/**
* Build a detailed prompt for pattern generation
*/
private buildGenerationPrompt(options: PatternGenerationOptions): string {
const { prompt, style, key, bpm, complexity } = options;
let fullPrompt = prompt;
const details: string[] = [];
if (style) details.push(`style: ${style}`);
if (key) details.push(`key: ${key}`);
if (bpm) details.push(`tempo: ${bpm} BPM`);
if (complexity !== undefined) details.push(`complexity: ${Math.round(complexity * 100)}%`);
if (details.length > 0) {
fullPrompt += `\n\nSpecifications: ${details.join(', ')}`;
}
return fullPrompt;
}
/**
* Generate a fallback pattern using static generation
*/
private generateFallbackPattern(options: PatternGenerationOptions): AIPatternResult {
const { style = 'techno', key = 'C', bpm = 120, complexity = 0.5 } = options;
try {
const pattern = this.fallbackGenerator.generateCompletePattern(style, key, bpm);
return {
success: true,
pattern,
isFallback: true,
explanation: `Generated ${style} pattern in ${key} at ${bpm} BPM (static fallback)`
};
} catch (error: any) {
return {
success: false,
error: `Fallback generation failed: ${error.message}`,
isFallback: true
};
}
}
/**
* Extract code from a response that might include markdown
*/
private extractCode(content: string): string {
// Remove markdown code blocks if present
let code = content;
// Match ```javascript, ```js, ``` or similar
const codeBlockMatch = content.match(/```(?:javascript|js|strudel)?\n?([\s\S]*?)```/);
if (codeBlockMatch) {
code = codeBlockMatch[1];
}
return code.trim();
}
/**
* Update configuration at runtime
*/
updateConfig(config: Partial<DeepSeekConfig>): void {
this.config = { ...this.config, ...config };
if (config.apiKey) {
this.initializeClient();
}
}
/**
* Get current configuration (without API key)
*/
getConfig(): Omit<DeepSeekConfig, 'apiKey'> {
const { apiKey, ...rest } = this.config;
return rest;
}
}