UNPKG

organ-ai-zer

Version:

AI-powered file organizer CLI tool

570 lines • 26.8 kB
"use strict"; // import { // AIAnalysisResponseSchema, // Clarification, // ClarificationPhase, // ConversationConfig, // FileInfo, // FileOrganizationContext, // FinalSuggestionsSchema, // OrganizationConversationResult, // OrganizationFeedback, // OrganizationSuggestion // } from '../types'; // import { AIAnalysisRequest, AIAnalysisResponse, BaseAIProvider } from './ai-providers/base-ai-provider'; // import { AIConversationContext } from './ai-conversation-context'; // /** // * File Organization Conversation Manager // * Purely AI-driven file organization through conversational interface // */ // export class FileOrganizationConversation { // private conversationContext: AIConversationContext; // private organizationContext: FileOrganizationContext; // constructor( // aiProvider: BaseAIProvider, // files: FileInfo[], // baseDirectory: string, // intent: string, // config: Partial<ConversationConfig> = {} // ) { // // Setup organization-specific system prompt // const systemPrompt = this.buildOrganizationSystemPrompt(); // const conversationConfig: Partial<ConversationConfig> = { // systemPrompt, // maxTurns: 10, // temperature: 0.4, // Slightly higher for creativity in categorization // maxContextSize: 15000, // Larger context for file analysis // ...config // }; // this.conversationContext = new AIConversationContext( // aiProvider, // `File Organization: ${intent}`, // conversationConfig // ); // this.organizationContext = { // files, // baseDirectory, // intent, // rejectedSuggestions: [], // approvedPatterns: [], // discoveredCategories: {}, // clarifications: [], // phase: 'analysis' // }; // // Store organization context in conversation custom context // this.conversationContext.setCustomContext('organization', this.organizationContext); // } // /** // * Get the organization context // */ // getOrganizationContext(): FileOrganizationContext { // return { ...this.organizationContext }; // } // /** // * Get the underlying conversation context // */ // getConversationContext(): AIConversationContext { // return this.conversationContext; // } // /** // * Set organization phase // */ // setPhase(phase: FileOrganizationContext['phase']): void { // this.organizationContext.phase = phase; // this.conversationContext.setCustomContext('organization', this.organizationContext); // } // /** // * Add pattern matching hints (from optional supplemental service) // */ // addPatternHints(hints: string[]): void { // this.organizationContext.patternHints = hints; // this.conversationContext.setCustomContext('organization', this.organizationContext); // } // /** // * Start initial AI analysis - let AI discover categories and patterns // */ // async startAnalysis(): Promise<AIAnalysisResponse> { // this.setPhase('analysis'); // const analysisPrompt = this.buildInitialAnalysisPrompt(); // try { // const analysisRequest: AIAnalysisRequest = { // files: this.organizationContext.files, // baseDirectory: this.organizationContext.baseDirectory, // responseSchema: AIAnalysisResponseSchema, // existingStructure: [], // userPreferences: { // customPrompt: analysisPrompt, // temperature: this.conversationContext.getContext().config.temperature, // ...this.conversationContext.getContext().config.aiSettings // } // }; // const result = await this.conversationContext.continueWithPrompt(analysisRequest); // // Parse AI's category discoveries and initial suggestions // // const parsed = this.refineAnalysisResults(result); // if (result.suggestions) { // result.suggestions = result.suggestions.map((s: any) => ({ // file: this.organizationContext.files.find(f => f.name === s.file)!, // suggestedPath: s.suggestedPath, // reason: s.reason, // confidence: s.confidence || 0.7, // category: s.category // })); // } // // Store discovered categories - convert string arrays to FileInfo arrays // if (result.discoveredCategories) { // const discoveredCategories: Record<string, FileInfo[]> = {}; // Object.entries(result.discoveredCategories).forEach(([category, fileNames]) => { // discoveredCategories[category] = (fileNames).map(fileName => // this.organizationContext.files.find(f => f.name === fileName.name) // ).filter(Boolean) as FileInfo[]; // }); // this.organizationContext.discoveredCategories = discoveredCategories; // this.conversationContext.setCustomContext('organization', this.organizationContext); // } // // Handle clarification questions if needed - with immediate re-analysis // // if (result.questions && result.questions.length > 0) { // // const reason = 'I need some clarification to provide better organization suggestions'; // // const context = 'Initial file analysis and categorization'; // // const clarificationsHandled = await this.handleClarifications( // // result.questions, // // reason, // // ClarificationPhase.INITIAL_ANALYSIS, // // context // // ); // // if (clarificationsHandled) { // // // Immediately re-run analysis with new clarifications // // console.log('šŸ”„ Re-analyzing with your clarifications...\n'); // // return this.startAnalysis(); // Recursive call with new context // // } // // } // return result; // } catch (error) { // throw new Error(`Failed to start AI analysis: ${error}`); // } // } // /** // * Continue conversation with user input // */ // async continueConversation(userMessage: string): Promise<AIAnalysisResponse> { // this.setPhase('conversation'); // try { // const analysisRequest: AIAnalysisRequest = { // files: this.organizationContext.files, // baseDirectory: this.organizationContext.baseDirectory, // responseSchema: AIAnalysisResponseSchema, // existingStructure: [], // userPreferences: { // userMessage, // temperature: this.conversationContext.getContext().config.temperature, // ...this.conversationContext.getContext().config.aiSettings // } // }; // const result = await this.conversationContext.continueWithPrompt(analysisRequest); // // Update context with any new discoveries // if (result.discoveredCategories) { // const newCategories: Record<string, FileInfo[]> = {}; // Object.entries(result.discoveredCategories).forEach(([category, fileNames]) => { // newCategories[category] = (fileNames as FileInfo[]).map(fileName => // this.organizationContext.files.find(f => f.name === fileName.name) // ).filter(Boolean) as FileInfo[]; // }); // this.organizationContext.discoveredCategories = { // ...this.organizationContext.discoveredCategories, // ...newCategories // }; // this.conversationContext.setCustomContext('organization', this.organizationContext); // } // return result; // } catch (error) { // throw new Error(`Conversation failed: ${error}`); // } // } // /** // * Generate final organization suggestions // */ // async generateFinalSuggestions(): Promise<AIAnalysisResponse> { // this.setPhase('organization'); // try { // // Use the conversation context to maintain consistency with the system prompt // const finalSuggestionsPrompt = this.buildFinalSuggestionsPrompt(); // const analysisRequest: AIAnalysisRequest = { // files: this.organizationContext.files, // baseDirectory: this.organizationContext.baseDirectory, // responseSchema: FinalSuggestionsSchema, // existingStructure: [], // userPreferences: { // finalSuggestionsPrompt, // temperature: this.conversationContext.getContext().config.temperature, // ...this.conversationContext.getContext().config.aiSettings // } // }; // const response = await this.conversationContext.continueWithPrompt(analysisRequest); // // Handle clarification questions if needed during suggestions // // if (response.questions && response.questions.length > 0) { // // const clarificationsHandled = await this.handleClarifications( // // response.questions, // // 'I need clarification to provide better organization suggestions', // // ClarificationPhase.SUGGESTIONS, // // 'Final organization suggestions generation' // // ); // // if (clarificationsHandled) { // // // Immediately re-run suggestions generation with new clarifications // // console.log('šŸ”„ Regenerating suggestions with your clarifications...\n'); // // return this.generateFinalSuggestions(); // Recursive call with new context // // } // // } // // Parse the final suggestions from the conversation response // response.suggestions = this.parseFinalSuggestions(response); // return response; // } catch (error) { // throw new Error(`Failed to generate final suggestions: ${error}`); // } // } // /** // * Process user feedback on suggestions // */ // async processFeedback(feedback: OrganizationFeedback): Promise<AIAnalysisResponse> { // let feedbackMessage = ''; // if (feedback.approved) { // feedbackMessage = 'Perfect! I approve these organization suggestions. Please proceed with the organization.'; // this.setPhase('organization'); // return this.generateFinalSuggestions(); // } // // Build conversational feedback message // if (feedback.feedback) { // feedbackMessage = feedback.feedback; // } else { // feedbackMessage = 'I have some concerns about these suggestions. '; // if (feedback.specificIssues && feedback.specificIssues.length > 0) { // feedbackMessage += `Specifically: ${feedback.specificIssues.join(', ')}. `; // } // if (feedback.selectedSuggestions && feedback.selectedSuggestions.length > 0) { // this.organizationContext.rejectedSuggestions.push(...feedback.selectedSuggestions); // feedbackMessage += `I don't like how these files are organized: ${feedback.selectedSuggestions.map(s => s.suggestedPath).slice(0, 3).join(', ')}.`; // } // } // return this.continueConversation(feedbackMessage); // } // /** // * Handle clarification questions from AI at any phase // */ // async handleClarifications( // questions: string[], // reason: string, // phase: ClarificationPhase, // context: string = '' // ): Promise<boolean> { // if (!questions || questions.length === 0) { // return false; // } // console.log(`\nšŸ¤” ${reason}\n`); // const clarifications: Clarification[] = []; // for (let i = 0; i < questions.length; i++) { // const question = questions[i]; // console.log(`ā“ Question ${i + 1}/${questions.length}:`); // const { answer } = await (await import('inquirer')).default.prompt([ // { // type: 'input', // name: 'answer', // message: question, // validate: (input: string) => input.trim().length > 0 || 'Please provide an answer' // } // ]); // const clarification: Clarification = { // id: `${phase}_${Date.now()}_${i}`, // phase, // question, // answer: answer.trim(), // context: context || `Clarification during ${phase} phase`, // timestamp: new Date() // }; // clarifications.push(clarification); // console.log(`āœ… Answer recorded: ${answer}\n`); // } // // Store clarifications // this.organizationContext.clarifications.push(...clarifications); // this.conversationContext.setCustomContext('organization', this.organizationContext); // console.log(`šŸ“ Recorded ${clarifications.length} clarification(s). Let me reconsider with this new information...\n`); // return true; // } // /** // * Get clarifications for a specific phase // */ // getClarificationsForPhase(phase: ClarificationPhase): Clarification[] { // return this.organizationContext.clarifications.filter(c => c.phase === phase); // } // /** // * Get all clarifications as formatted context for AI // */ // getClarificationsContext(): string { // if (this.organizationContext.clarifications.length === 0) { // return 'No clarifications provided yet.'; // } // return this.organizationContext.clarifications // .map((c, index) => `${index + 1}. [${c.phase}] Q: ${c.question} A: ${c.answer}`) // .join('\n'); // } // /** // * Complete the organization conversation // */ // complete(): void { // this.setPhase('complete'); // this.conversationContext.complete(); // } // /** // * Build organization-specific system prompt - purely AI-driven // */ // private buildOrganizationSystemPrompt(): string { // return `You are an intelligent file organization AI assistant. Your role is to analyze files and discover logical organization patterns through conversation with the user. // **CORE CAPABILITIES:** // - Analyze file names, extensions, sizes, and dates to discover content patterns // - Identify custom categories based on actual file content and naming patterns // - Suggest logical organization structures that make sense for the specific files // - Maintain consistency across similar content types // - Adapt and learn from user feedback throughout the conversation // - Ask clarifying questions when genuinely needed to understand user preferences // **Phases:** // - **Analysis**: Discover content types, patterns, and initial organization ideas // - **Conversation**: Discuss findings, ask clarifying questions, and refine organization suggestions // - **Organization**: Generate final organization suggestions based on analysis and conversation // - **Complete**: Finalize organization and provide structured suggestions // **AI-DRIVEN ANALYSIS APPROACH:** // - Do NOT rely on hardcoded file type assumptions // - Analyze actual file names and patterns to discover what types of content exist // - Look for naming patterns, series indicators, version numbers, dates, etc. // - Identify potential projects, collections, series, or related groups // - Suggest custom categories that make sense for the specific files being organized // - Recognize when files belong together (e.g., project files, photo albums, document series) // **DISCOVERY PROCESS:** // 1. **Pattern Recognition**: Analyze file names for patterns, sequences, versions, dates // 2. **Content Inference**: Infer content type from naming conventions and context // 3. **Grouping Logic**: Identify which files should be grouped together // 4. **Category Creation**: Suggest meaningful category names based on discovered content // 5. **Discover Relationships**: Find relationships between files that suggest logical organization // 6. **Consistency Enforcement**: Ensure consistent naming and folder structures across similar content types // 7. **Structure Design**: Propose folder hierarchies that make logical sense // 8. **Discover Categories**: Ensure discoveredCategories object includes all files, even if that required creating an Unknown category // 9. **Iterative Learning**: Adapt suggestions based on user feedback and clarifications // 10. **Address Every File**: Ensure every file has a specific organization suggestion // **CONVERSATIONAL PRINCIPLES:** // - Keep the interaction natural and conversational // - Don't bombard with questions - ask only what's needed // - Explain your reasoning clearly // - Build on previous conversation context // - Acknowledge and incorporate user feedback // - Focus on practical, actionable organization strategies // **FLEXIBILITY REQUIREMENTS:** // - Work with any type of files (not just media) // - Discover custom categories specific to the user's content // - Adapt organization patterns based on what the files actually are // - Support any domain: research papers, photos, projects, documents, media, etc. // **MEDIA FILES:** // - Critical to ensure naming conventions are consistent // - Once a naming pattern is established for a category, apply it consistently // **CODING PROJECTS:** // - Identify related files that belong to the same project // - If a project is identified, structure must be maintained // **RESPONSE STYLE:** // - Be conversational and helpful // - Explain your analysis and reasoning // - Provide structured data when making suggestions // - Ask specific questions only when needed for better organization // - Focus on the user's intent and preferences // **RESPONSE FORMAT:** // Please respond conversationally, but also provide a JSON object based on the requested schema`; // } // /** // * Build initial analysis prompt // */ // private buildInitialAnalysisPrompt(): string { // const patternHints = this.organizationContext.patternHints ? // `\n\n**PATTERN ANALYSIS HINTS:**\n${this.organizationContext.patternHints.join('\n')}` : ''; // return `**INITIAL FILE ANALYSIS REQUEST** // **User's Intent:** ${this.organizationContext.intent} // **Base Directory:** ${this.organizationContext.baseDirectory} // **Files to Analyze (${this.organizationContext.files.length} total):** // ${this.organizationContext.files.map((file, index) => // `${index + 1}. ${file.name} (${file.extension}, ${this.formatFileSize(file.size)}, modified: ${file.modified.toISOString().split('T')[0]})` // ).join('\n')}${patternHints} // **ANALYSIS TASK:** // Please analyze these files and help me understand what we're working with. I'd like you to: // 1. **Discover Content Types**: What types of content do you see? Look at file names, patterns, and extensions to infer what these files actually are. // 2. **Identify Patterns**: Are there naming patterns, series, versions, dates, or other indicators that suggest how these files are related? // 3. **Suggest Categories**: Based on your analysis, what custom categories would make sense for organizing these specific files? // 4. **Initial Organization Ideas**: Do you have any initial thoughts on how these could be logically organized? // 5. **Ask Clarifying Questions**: If you need more information to provide better suggestions, please ask specific questions. If there is enough information to proceed, do not ask questions. // 6. **Provide Discovered Categories**: Please return a JSON object with discovered categories and their files, even if that means creating an "Unknown" category for files that don't fit any pattern. // Please be conversational in your response and explain your analysis. If you need clarification about my preferences or see multiple valid organization approaches, feel free to ask. // Include a JSON block with your discoveries: // \`\`\`json // { // "discoveredCategories": { // "CategoryName": ["file1.ext", "file2.ext"], // "AnotherCategory": ["file3.ext"] // }, // "reasoning": "Overall organization strategy explanation", // "clarificationNeeded": { // "questions": ["Question 1?", "Question 2?"], // "reason": "I need more information to provide better suggestions" // } // \`\`\` // `; // } // /** // * Build suggestion request prompt // */ // private buildSuggestionsPrompt(): string { // return `**ORGANIZATION SUGGESTIONS REQUEST** // **FILES TO ORGANIZE:** // ${this.organizationContext.files.map((file, index) => // `${index + 1}. ${file.name}` // ).join('\n')} // Based on our conversation, please provide organization suggestions for ALL ${this.organizationContext.files.length} files. Make sure every file has a specific organization suggestion. // **PREVIOUSLY DISCOVERED CATEGORIES:** // ${Object.keys(this.organizationContext.discoveredCategories).length > 0 ? // Object.keys(this.organizationContext.discoveredCategories).map(cat => // `- ${cat}`).join('\n') : 'None yet'} // If you think we need new categories no listed above, ensure to add them in your response. // Please respond conversationally, but include a JSON block with your discoveries: // \`\`\`json // { // "suggestions": [ // { // "file": "example.ext", // "suggestedPath": "Category/example.ext", // "reason": "Why this organization makes sense", // "confidence": 0.8 // "category": "category_name", // } // ], // discoveredCategories: { // "CategoryName": ["file1.ext", "file2.ext"], // "AnotherCategory": ["file3.ext"] // }, // "reasoning": "Overall organization strategy explanation", // "clarificationNeeded": { // "questions": ["Question 1?", "Question 2?"], // "reason": "I need more information to provide better suggestions" // } // \`\`\``; // } // /** // * Build final suggestions prompt // */ // private buildFinalSuggestionsPrompt(): string { // const contextSummary = this.buildContextSummary(); // return `**FINAL ORGANIZATION SUGGESTIONS REQUEST** // ${contextSummary} // **FILES TO ORGANIZE:** // ${this.organizationContext.files.map((file, index) => // `${index + 1}. ${file.name}` // ).join('\n')} // Based on our conversation, please provide final organization suggestions for ALL ${this.organizationContext.files.length} files. Make sure every file has a specific organization suggestion. // **CRITICAL CONSISTENCY REQUIREMENTS:** // - **MEDIA FILES**: Critical to ensure naming conventions are consistent. Once a naming pattern is established for a category, apply it consistently to ALL files of that type // - **TV SHOWS**: If you organize one TV show as "TV Shows/SeriesName/Season X/SeriesName SXX EXX Title", then ALL TV shows must follow this EXACT structure and naming format // - **MOVIES**: Use consistent naming pattern for all movies (e.g., if one uses "Movies/Genre/Movie Title (Year)", then ALL movies must use this format) // - **CODING PROJECTS**: Identify related files that belong to the same project. If a project is identified, structure must be maintained // - **SAME TYPE = SAME FORMAT**: Files of the same type MUST use identical folder structure and naming patterns // **REQUIREMENTS:** // - Provide exactly ${this.organizationContext.files.length} suggestions (one for each file) // - Use the categories and patterns we've discussed // - Maintain absolute consistency within each category // - Consider all feedback provided // - Apply the SAME naming pattern and folder structure for files of the same type/category // **RESPONSE FORMAT:** // \`\`\`json // { // "suggestions": [ // { // "fileName": "exact_filename.ext", // "suggestedPath": "Category/Subcategory/filename.ext", // "reason": "Clear explanation", // "confidence": 0.9, // "category": "category_name" // } // ], // "reasoning": "Overall organization strategy explanation" // } // \`\`\` // Please ensure every file is included in your suggestions and that consistency is maintained across similar file types.`; // } // /** // * Build context summary for prompts // */ // private buildContextSummary(): string { // const sections: string[] = []; // sections.push(`**Conversation Context:**`); // sections.push(`- User Intent: ${this.organizationContext.intent}`); // sections.push(`- Phase: ${this.organizationContext.phase}`); // sections.push(`- Base Directory: ${this.organizationContext.baseDirectory}`); // if (Object.keys(this.organizationContext.discoveredCategories).length > 0) { // sections.push(`- Discovered Categories: ${Object.keys(this.organizationContext.discoveredCategories).join(', ')}`); // } // if (this.organizationContext.rejectedSuggestions.length > 0) { // const rejectedPaths = this.organizationContext.rejectedSuggestions.slice(0, 5).map(s => s.suggestedPath); // sections.push(`- Rejected Approaches: ${rejectedPaths.join(', ')}${this.organizationContext.rejectedSuggestions.length > 5 ? '...' : ''}`); // } // if (this.organizationContext.approvedPatterns.length > 0) { // sections.push(`- Approved Patterns: ${this.organizationContext.approvedPatterns.join(', ')}`); // } // if (this.organizationContext.clarifications.length > 0) { // sections.push(`- User Clarifications: ${this.organizationContext.clarifications.length} provided`); // } // return sections.join('\n'); // } // /** // * Parse conversation response // */ // // private parseConversationResponse(response: string): { // // categories?: Record<string, string[]>; // // suggestions?: OrganizationSuggestion[]; // // } { // // // Similar to parseAnalysisResponse but more lenient // // return this.parseAnalysisResponse(response); // // } // /** // * Parse final suggestions // */ // private parseFinalSuggestions(response: AIAnalysisResponse): OrganizationSuggestion[] { // try { // return response.suggestions.map((s: any) => { // const file = this.organizationContext.files.find(f => f.name === s.file); // if (!file) { // throw new Error(`File not found: ${s.file}`); // } // return { // file, // suggestedPath: s.suggestedPath, // reason: s.reason, // confidence: s.confidence || 0.8, // category: s.category, // metadata: s.metadata // }; // }); // } catch (error) { // // Fallback: create basic suggestions // console.warn('Failed to parse final suggestions, using fallback:', error); // return this.organizationContext.files.map((file) => ({ // file, // suggestedPath: `Organized/${file.name}`, // reason: 'Fallback organization due to parsing error', // confidence: 0.5 // })); // } // } // /** // * Format file size // */ // private formatFileSize(bytes: number): string { // const units = ['B', 'KB', 'MB', 'GB']; // let size = bytes; // let unitIndex = 0; // while (size >= 1024 && unitIndex < units.length - 1) { // size /= 1024; // unitIndex++; // } // return `${size.toFixed(1)} ${units[unitIndex]}`; // } // } //# sourceMappingURL=file-organization-conversation.js.map