unified-ai-interface.ts•23.7 kB
/**
* Unified AI Interface for SRT Chunking Functions
* Provides a single interface that works with Claude, GPT, Gemini, and other AI models
*/
import { SRTSubtitle, SRTChunk } from '../types/srt.js';
import { detectConversations, detectConversationsAdvanced } from '../chunking/conversation-detector.js';
import { TodoToolInterface, SRTProcessingTodoManager, TodoToolFactory } from './todo-tool-integration.js';
import { ClaudeSRTIntegration, ClaudeConfig } from './claude-integration.js';
/**
* Supported AI Models
*/
export type SupportedAIModel = 'claude' | 'gpt' | 'gemini' | 'generic';
/**
* AI Model Capabilities
*/
export interface AIModelCapabilities {
supportsTodoTool: boolean;
maxContextSize: number;
supportsFunctionCalling: boolean;
supportsStreaming: boolean;
supportsMultimodal: boolean;
supportsReasoning: boolean;
maxTokensPerRequest: number;
temperature: number;
processingStrategy: 'sequential' | 'parallel' | 'batch';
}
/**
* AI Model Configuration
*/
export interface UnifiedAIConfig {
modelType: SupportedAIModel;
capabilities: AIModelCapabilities;
chunkingOptions: {
boundaryThreshold: number;
maxChunkSize: number;
minChunkSize: number;
enableSemanticAnalysis: boolean;
enableSpeakerDiarization: boolean;
};
contextOptimization: {
enabled: boolean;
maxContextSize: number;
chunkSizeLimit: number;
contextManagement: 'aggressive' | 'conservative' | 'balanced';
};
todoIntegration: {
enabled: boolean;
progressTracking: boolean;
detailedLogging: boolean;
};
}
/**
* Processing Request
*/
export interface ProcessingRequest {
subtitles: SRTSubtitle[];
processingType: 'translation' | 'analysis' | 'conversation-detection';
targetLanguage?: string;
sourceLanguage?: string;
options?: {
useReasoning?: boolean;
temperature?: number;
maxTokens?: number;
preserveFormatting?: boolean;
contextOptimization?: boolean;
};
}
/**
* Processing Result
*/
export interface UnifiedProcessingResult {
success: boolean;
modelType: SupportedAIModel;
chunks: SRTChunk[];
results: any[];
todoList?: any[];
metadata: ProcessingMetadata;
errors: ProcessingError[];
warnings: ProcessingWarning[];
processingTime: number;
}
/**
* Processing Metadata
*/
export interface ProcessingMetadata {
totalChunks: number;
processedChunks: number;
totalSubtitles: number;
totalDuration: number;
contextEfficiency: number;
modelCapabilities: AIModelCapabilities;
chunkingStrategy: string;
contextOptimization: boolean;
todoIntegration: boolean;
}
/**
* Processing Error
*/
export interface ProcessingError {
chunkId?: string;
error: string;
context?: string;
retryable: boolean;
timestamp: Date;
}
/**
* Processing Warning
*/
export interface ProcessingWarning {
chunkId?: string;
warning: string;
context?: string;
timestamp: Date;
}
/**
* Unified AI Interface
*/
export class UnifiedAIInterface {
private config: UnifiedAIConfig;
private todoManager?: SRTProcessingTodoManager;
private modelIntegration: any;
constructor(config: UnifiedAIConfig) {
this.config = config;
if (config.todoIntegration.enabled) {
this.todoManager = new SRTProcessingTodoManager(config.modelType);
}
this.modelIntegration = this.createModelIntegration();
}
/**
* Process SRT with unified AI interface
*/
async processSRT(request: ProcessingRequest): Promise<UnifiedProcessingResult> {
const startTime = Date.now();
const errors: ProcessingError[] = [];
const warnings: ProcessingWarning[] = [];
try {
// Step 1: Create processing todos if enabled
let todoList: any[] = [];
if (this.todoManager) {
const todoResult = await this.todoManager.createSRTProcessingTodos(
'SRT File',
request.subtitles.length,
request.processingType,
request.targetLanguage
);
todoList = await this.todoManager.getTodosByStage('file-analysis');
}
// Step 2: Detect conversations with model-optimized chunking
const chunks = await this.detectConversationsOptimized(request.subtitles);
// Step 3: Optimize chunks for AI model context
const optimizedChunks = await this.optimizeChunksForModel(chunks);
// Step 4: Process chunks with AI model
const results = await this.processChunksWithModel(
optimizedChunks,
request,
errors,
warnings
);
// Step 5: Update todos if enabled
if (this.todoManager) {
await this.updateProcessingTodos('processing', 'completed');
}
const processingTime = Date.now() - startTime;
return {
success: true,
modelType: this.config.modelType,
chunks: optimizedChunks,
results,
todoList: this.todoManager ? [] : undefined,
metadata: this.generateProcessingMetadata(optimizedChunks, results, processingTime),
errors,
warnings,
processingTime
};
} catch (error) {
errors.push({
error: error instanceof Error ? error.message : 'Unknown error',
retryable: true,
timestamp: new Date()
});
return {
success: false,
modelType: this.config.modelType,
chunks: [],
results: [],
todoList: this.todoManager ? [] : undefined,
metadata: this.generateProcessingMetadata([], [], Date.now() - startTime),
errors,
warnings,
processingTime: Date.now() - startTime
};
}
}
/**
* Detect conversations with model-optimized parameters
*/
private async detectConversationsOptimized(subtitles: SRTSubtitle[]): Promise<SRTChunk[]> {
const options = this.getOptimizedChunkingOptions();
return detectConversationsAdvanced(subtitles, {
boundaryThreshold: options.boundaryThreshold,
maxChunkSize: options.maxChunkSize,
minChunkSize: options.minChunkSize,
enableSemanticAnalysis: options.enableSemanticAnalysis,
enableSpeakerDiarization: options.enableSpeakerDiarization
});
}
/**
* Get optimized chunking options based on model capabilities
*/
private getOptimizedChunkingOptions() {
const { capabilities, chunkingOptions, contextOptimization } = this.config;
let maxChunkSize = Math.min(chunkingOptions.maxChunkSize, capabilities.maxContextSize / 1000);
let boundaryThreshold = chunkingOptions.boundaryThreshold;
// Adjust for context optimization
if (contextOptimization.enabled) {
maxChunkSize = Math.min(maxChunkSize, contextOptimization.chunkSizeLimit);
if (contextOptimization.contextManagement === 'aggressive') {
boundaryThreshold = Math.max(0.5, boundaryThreshold - 0.1);
maxChunkSize = Math.min(maxChunkSize, 5);
} else if (contextOptimization.contextManagement === 'conservative') {
boundaryThreshold = Math.min(0.9, boundaryThreshold + 0.1);
maxChunkSize = Math.min(maxChunkSize, 15);
}
}
return {
boundaryThreshold,
maxChunkSize,
minChunkSize: chunkingOptions.minChunkSize,
enableSemanticAnalysis: chunkingOptions.enableSemanticAnalysis,
enableSpeakerDiarization: chunkingOptions.enableSpeakerDiarization
};
}
/**
* Optimize chunks for AI model context
*/
private async optimizeChunksForModel(chunks: SRTChunk[]): Promise<SRTChunk[]> {
if (!this.config.contextOptimization.enabled) {
return chunks;
}
const optimizedChunks: SRTChunk[] = [];
const maxContextSize = this.config.contextOptimization.maxContextSize;
const chunkSizeLimit = this.config.contextOptimization.chunkSizeLimit;
for (const chunk of chunks) {
const chunkContextSize = this.calculateChunkContextSize(chunk);
if (chunkContextSize <= maxContextSize && chunk.subtitles.length <= chunkSizeLimit) {
optimizedChunks.push(chunk);
} else {
// Split large chunks
const splitChunks = this.splitChunkForModel(chunk, maxContextSize, chunkSizeLimit);
optimizedChunks.push(...splitChunks);
}
}
return optimizedChunks;
}
/**
* Calculate chunk context size
*/
private calculateChunkContextSize(chunk: SRTChunk): number {
let size = 0;
// Base metadata size
size += JSON.stringify(chunk).length;
// Add subtitle text size
for (const subtitle of chunk.subtitles) {
size += (subtitle.text || '').length;
}
// Add context overhead
size += chunk.subtitles.length * 200;
return size;
}
/**
* Split chunk for model context limits
*/
private splitChunkForModel(
chunk: SRTChunk,
maxContextSize: number,
chunkSizeLimit: number
): SRTChunk[] {
const subtitles = chunk.subtitles;
const maxSubtitlesPerChunk = Math.min(
chunkSizeLimit,
Math.floor(maxContextSize / 1000) // Rough estimate
);
const newChunks: SRTChunk[] = [];
for (let i = 0; i < subtitles.length; i += maxSubtitlesPerChunk) {
const chunkSubtitles = subtitles.slice(i, i + maxSubtitlesPerChunk);
const newChunk: SRTChunk = {
...chunk,
id: `${chunk.id}-part-${Math.floor(i / maxSubtitlesPerChunk) + 1}`,
startIndex: chunkSubtitles[0].index,
endIndex: chunkSubtitles[chunkSubtitles.length - 1].index,
subtitles: chunkSubtitles,
context: {
...chunk.context,
conversationId: chunk.context?.conversationId || '',
isSplitChunk: true,
originalChunkId: chunk.id,
partNumber: Math.floor(i / maxSubtitlesPerChunk) + 1,
totalParts: Math.ceil(subtitles.length / maxSubtitlesPerChunk)
}
};
newChunks.push(newChunk);
}
return newChunks;
}
/**
* Process chunks with AI model
*/
private async processChunksWithModel(
chunks: SRTChunk[],
request: ProcessingRequest,
errors: ProcessingError[],
warnings: ProcessingWarning[]
): Promise<any[]> {
const results: any[] = [];
// Choose processing strategy based on model capabilities
const strategy = this.config.capabilities.processingStrategy;
switch (strategy) {
case 'sequential':
await this.processSequentially(chunks, request, results, errors, warnings);
break;
case 'parallel':
await this.processInParallel(chunks, request, results, errors, warnings);
break;
case 'batch':
await this.processInBatches(chunks, request, results, errors, warnings);
break;
}
return results;
}
/**
* Process chunks sequentially
*/
private async processSequentially(
chunks: SRTChunk[],
request: ProcessingRequest,
results: any[],
errors: ProcessingError[],
warnings: ProcessingWarning[]
): Promise<void> {
for (const chunk of chunks) {
try {
const result = await this.processChunkWithModel(chunk, request);
results.push(result);
} catch (error) {
errors.push({
chunkId: chunk.id,
error: error instanceof Error ? error.message : 'Unknown error',
retryable: true,
timestamp: new Date()
});
}
}
}
/**
* Process chunks in parallel
*/
private async processInParallel(
chunks: SRTChunk[],
request: ProcessingRequest,
results: any[],
errors: ProcessingError[],
warnings: ProcessingWarning[]
): Promise<void> {
const batchSize = this.calculateOptimalBatchSize(chunks);
const batches = this.createBatches(chunks, batchSize);
for (const batch of batches) {
const batchPromises = batch.map(chunk => this.processChunkWithModel(chunk, request));
const batchResults = await Promise.allSettled(batchPromises);
batchResults.forEach((result, index) => {
if (result.status === 'fulfilled') {
results.push(result.value);
} else {
errors.push({
chunkId: batch[index].id,
error: result.reason instanceof Error ? result.reason.message : 'Unknown error',
retryable: true,
timestamp: new Date()
});
}
});
}
}
/**
* Process chunks in batches
*/
private async processInBatches(
chunks: SRTChunk[],
request: ProcessingRequest,
results: any[],
errors: ProcessingError[],
warnings: ProcessingWarning[]
): Promise<void> {
const batchSize = this.calculateOptimalBatchSize(chunks);
const batches = this.createBatches(chunks, batchSize);
for (const batch of batches) {
try {
const batchResult = await this.processBatchWithModel(batch, request);
results.push(...batchResult);
} catch (error) {
errors.push({
error: error instanceof Error ? error.message : 'Unknown error',
retryable: true,
timestamp: new Date()
});
}
}
}
/**
* Process individual chunk with model
*/
private async processChunkWithModel(chunk: SRTChunk, request: ProcessingRequest): Promise<any> {
// This would be replaced with actual AI model calls
// For now, simulate processing
return this.simulateChunkProcessing(chunk, request);
}
/**
* Process batch with model
*/
private async processBatchWithModel(chunks: SRTChunk[], request: ProcessingRequest): Promise<any[]> {
const results: any[] = [];
for (const chunk of chunks) {
const result = await this.processChunkWithModel(chunk, request);
results.push(result);
}
return results;
}
/**
* Simulate chunk processing (replace with actual AI model integration)
*/
private async simulateChunkProcessing(chunk: SRTChunk, request: ProcessingRequest): Promise<any> {
// Simulate processing time based on chunk complexity
const processingTime = Math.min(1000, chunk.subtitles.length * 100);
await new Promise(resolve => setTimeout(resolve, processingTime));
return {
chunkId: chunk.id,
processed: true,
processingType: request.processingType,
timestamp: new Date().toISOString(),
modelType: this.config.modelType,
contextSize: this.calculateChunkContextSize(chunk)
};
}
/**
* Calculate optimal batch size
*/
private calculateOptimalBatchSize(chunks: SRTChunk[]): number {
const maxContext = this.config.capabilities.maxContextSize;
const avgChunkSize = chunks.reduce((sum, chunk) =>
sum + this.calculateChunkContextSize(chunk), 0) / chunks.length;
return Math.max(1, Math.floor(maxContext / (avgChunkSize * 2)));
}
/**
* Create batches from chunks
*/
private createBatches(chunks: SRTChunk[], batchSize: number): SRTChunk[][] {
const batches: SRTChunk[][] = [];
for (let i = 0; i < chunks.length; i += batchSize) {
batches.push(chunks.slice(i, i + batchSize));
}
return batches;
}
/**
* Update processing todos
*/
private async updateProcessingTodos(stage: string, status: string): Promise<void> {
if (this.todoManager) {
await this.todoManager.updateProcessingProgress(stage as any, status as any);
}
}
/**
* Generate processing metadata
*/
private generateProcessingMetadata(
chunks: SRTChunk[],
results: any[],
processingTime: number
): ProcessingMetadata {
const totalSubtitles = chunks.reduce((sum, chunk) => sum + chunk.subtitles.length, 0);
const totalDuration = this.calculateTotalDuration(chunks);
const contextEfficiency = this.calculateContextEfficiency(chunks);
return {
totalChunks: chunks.length,
processedChunks: results.length,
totalSubtitles,
totalDuration,
contextEfficiency,
modelCapabilities: this.config.capabilities,
chunkingStrategy: this.config.chunkingOptions.enableSemanticAnalysis ? 'semantic' : 'basic',
contextOptimization: this.config.contextOptimization.enabled,
todoIntegration: this.config.todoIntegration.enabled
};
}
/**
* Calculate total duration
*/
private calculateTotalDuration(chunks: SRTChunk[]): number {
if (chunks.length === 0) return 0;
const firstChunk = chunks[0];
const lastChunk = chunks[chunks.length - 1];
const startTime = firstChunk.subtitles[0].startTime;
const endTime = lastChunk.subtitles[lastChunk.subtitles.length - 1].endTime;
const startMs = (startTime.hours * 3600 + startTime.minutes * 60 + startTime.seconds) * 1000 + startTime.milliseconds;
const endMs = (endTime.hours * 3600 + endTime.minutes * 60 + endTime.seconds) * 1000 + endTime.milliseconds;
return endMs - startMs;
}
/**
* Calculate context efficiency
*/
private calculateContextEfficiency(chunks: SRTChunk[]): number {
const totalContextUsed = chunks.reduce((sum, chunk) =>
sum + this.calculateChunkContextSize(chunk), 0);
const maxPossibleContext = chunks.length * this.config.capabilities.maxContextSize;
return totalContextUsed / maxPossibleContext;
}
/**
* Create model-specific integration
*/
private createModelIntegration(): any {
switch (this.config.modelType) {
case 'claude':
return this.createClaudeIntegration();
case 'gpt':
return this.createGPTIntegration();
case 'gemini':
return this.createGeminiIntegration();
default:
return this.createGenericIntegration();
}
}
/**
* Create Claude integration
*/
private createClaudeIntegration(): any {
const claudeConfig: ClaudeConfig = {
modelType: 'claude',
supportsTodoTool: true,
maxContextSize: 200000,
chunkSizeLimit: 15,
processingStrategy: 'sequential',
contextOptimization: true,
claudeSpecific: {
useAnthropicFormat: true,
enableReasoning: true,
maxTokensPerRequest: 4000,
temperature: 0.7
}
};
return new ClaudeSRTIntegration(claudeConfig, {
modelConfig: claudeConfig,
chunkingStrategy: 'conversation-aware',
contextManagement: 'balanced',
todoIntegration: true,
progressTracking: true
});
}
/**
* Create GPT integration
*/
private createGPTIntegration(): any {
// GPT-specific integration would go here
return {
modelType: 'gpt',
processChunk: async (chunk: SRTChunk) => {
return { chunkId: chunk.id, processed: true, modelType: 'gpt' };
}
};
}
/**
* Create Gemini integration
*/
private createGeminiIntegration(): any {
// Gemini-specific integration would go here
return {
modelType: 'gemini',
processChunk: async (chunk: SRTChunk) => {
return { chunkId: chunk.id, processed: true, modelType: 'gemini' };
}
};
}
/**
* Create generic integration
*/
private createGenericIntegration(): any {
return {
modelType: 'generic',
processChunk: async (chunk: SRTChunk) => {
return { chunkId: chunk.id, processed: true, modelType: 'generic' };
}
};
}
}
/**
* Unified AI Interface Factory
*/
export class UnifiedAIFactory {
/**
* Create Claude configuration
*/
static createClaudeConfig(): UnifiedAIConfig {
return {
modelType: 'claude',
capabilities: {
supportsTodoTool: true,
maxContextSize: 200000,
supportsFunctionCalling: true,
supportsStreaming: true,
supportsMultimodal: false,
supportsReasoning: true,
maxTokensPerRequest: 4000,
temperature: 0.7,
processingStrategy: 'sequential'
},
chunkingOptions: {
boundaryThreshold: 0.7,
maxChunkSize: 15,
minChunkSize: 2,
enableSemanticAnalysis: true,
enableSpeakerDiarization: true
},
contextOptimization: {
enabled: true,
maxContextSize: 50000,
chunkSizeLimit: 8,
contextManagement: 'balanced'
},
todoIntegration: {
enabled: true,
progressTracking: true,
detailedLogging: true
}
};
}
/**
* Create GPT configuration
*/
static createGPTConfig(): UnifiedAIConfig {
return {
modelType: 'gpt',
capabilities: {
supportsTodoTool: true,
maxContextSize: 128000,
supportsFunctionCalling: true,
supportsStreaming: true,
supportsMultimodal: true,
supportsReasoning: false,
maxTokensPerRequest: 4000,
temperature: 0.7,
processingStrategy: 'parallel'
},
chunkingOptions: {
boundaryThreshold: 0.7,
maxChunkSize: 20,
minChunkSize: 2,
enableSemanticAnalysis: true,
enableSpeakerDiarization: true
},
contextOptimization: {
enabled: true,
maxContextSize: 100000,
chunkSizeLimit: 12,
contextManagement: 'balanced'
},
todoIntegration: {
enabled: true,
progressTracking: true,
detailedLogging: true
}
};
}
/**
* Create Gemini configuration
*/
static createGeminiConfig(): UnifiedAIConfig {
return {
modelType: 'gemini',
capabilities: {
supportsTodoTool: true,
maxContextSize: 1000000,
supportsFunctionCalling: true,
supportsStreaming: true,
supportsMultimodal: true,
supportsReasoning: true,
maxTokensPerRequest: 8000,
temperature: 0.7,
processingStrategy: 'batch'
},
chunkingOptions: {
boundaryThreshold: 0.7,
maxChunkSize: 25,
minChunkSize: 2,
enableSemanticAnalysis: true,
enableSpeakerDiarization: true
},
contextOptimization: {
enabled: true,
maxContextSize: 200000,
chunkSizeLimit: 20,
contextManagement: 'conservative'
},
todoIntegration: {
enabled: true,
progressTracking: true,
detailedLogging: true
}
};
}
/**
* Create generic configuration
*/
static createGenericConfig(): UnifiedAIConfig {
return {
modelType: 'generic',
capabilities: {
supportsTodoTool: false,
maxContextSize: 50000,
supportsFunctionCalling: false,
supportsStreaming: false,
supportsMultimodal: false,
supportsReasoning: false,
maxTokensPerRequest: 2000,
temperature: 0.7,
processingStrategy: 'sequential'
},
chunkingOptions: {
boundaryThreshold: 0.7,
maxChunkSize: 10,
minChunkSize: 2,
enableSemanticAnalysis: true,
enableSpeakerDiarization: true
},
contextOptimization: {
enabled: true,
maxContextSize: 30000,
chunkSizeLimit: 5,
contextManagement: 'aggressive'
},
todoIntegration: {
enabled: false,
progressTracking: false,
detailedLogging: false
}
};
}
/**
* Create unified AI interface
*/
static createUnifiedAI(modelType: SupportedAIModel): UnifiedAIInterface {
let config: UnifiedAIConfig;
switch (modelType) {
case 'claude':
config = this.createClaudeConfig();
break;
case 'gpt':
config = this.createGPTConfig();
break;
case 'gemini':
config = this.createGeminiConfig();
break;
default:
config = this.createGenericConfig();
}
return new UnifiedAIInterface(config);
}
}