organ-ai-zer
Version:
AI-powered file organizer CLI tool
246 lines (242 loc) ⢠10.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OrganizationConversation = void 0;
const types_1 = require("../types");
const conversation_1 = require("./conversation");
const organization_context_1 = require("./organization-context");
const organization_utility_1 = require("../util/organization-utility");
const inquirer_1 = __importDefault(require("inquirer"));
class OrganizationConversation extends conversation_1.Conversation {
constructor(aiProvider, files, baseDirectory, intent, subject, config = {}) {
// Setup organization-specific system prompt
const systemPrompt = organization_utility_1.buildOrganizationSystemPrompt;
const conversationConfig = {
systemPrompt,
maxTurns: 10,
temperature: 0.4,
maxContextSize: 15000,
...config
};
const context = new organization_context_1.OrganizationContext(subject, files, baseDirectory, intent, conversationConfig);
super(aiProvider, subject, config);
this.context = context;
}
async startAnalysis() {
this.context.setPhase('analysis');
const analysisPrompt = (0, organization_utility_1.buildInitialAnalysisPrompt)(this.context);
try {
const analysisRequest = {
prompt: analysisPrompt,
schema: types_1.AIAnalysisResponseSchema
};
const result = await this.continueWithPrompt(analysisRequest);
if (result.suggestions) {
result.suggestions = result.suggestions.map((s) => ({
file: this.context.getFiles().find(f => f.name === s.file),
suggestedPath: s.suggestedPath,
reason: s.reason,
confidence: s.confidence || 0.7,
category: s.category
}));
}
if (result.discoveredCategories) {
const discoveredCategories = {};
Object.entries(result.discoveredCategories).forEach(([category, fileNames]) => {
discoveredCategories[category] = (fileNames).map(fileName => this.context.getFiles().find(f => f.name === fileName.name)).filter(Boolean);
});
this.context.setDiscoveredCategories(discoveredCategories);
}
return result;
}
catch (error) {
throw new Error(`Failed to start AI analysis: ${error}`);
}
}
async continueConversation(userMessage) {
this.context.setPhase('conversation');
try {
const analysisRequest = {
prompt: userMessage,
schema: types_1.AISuggestionsResponseSchema
};
const result = await this.continueWithPrompt(analysisRequest);
if (result.discoveredCategories) {
Object.entries(result.discoveredCategories).forEach(([category, files]) => {
this.context.addDiscoveredCategory(category, files);
});
}
return result;
}
catch (error) {
throw new Error(`Conversation failed: ${error}`);
}
}
async generateFinalSuggestions() {
this.context.setPhase('organization');
try {
const finalSuggestionsPrompt = (0, organization_utility_1.buildFinalSuggestionsPrompt)(this.context);
const analysisRequest = {
prompt: finalSuggestionsPrompt,
schema: types_1.FinalSuggestionsSchema
};
const response = await this.continueWithPrompt(analysisRequest);
response.suggestions = this.parseFinalSuggestions(response);
return response;
}
catch (error) {
throw new Error(`Failed to generate final suggestions: ${error}`);
}
}
parseFinalSuggestions(response) {
try {
return response.suggestions.map((s) => {
const file = this.context.getFiles().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.context.getFiles().map((file) => ({
file,
suggestedPath: `Organized/${file.name}`,
reason: 'Fallback organization due to parsing error',
confidence: 0.5
}));
}
}
async processFeedback(feedback) {
let feedbackMessage = '';
if (feedback.approved) {
feedbackMessage = 'Perfect! I approve these organization suggestions. Please proceed with the organization.';
this.context.setPhase('organization');
return this.generateFinalSuggestions();
}
if (feedback.feedback) {
feedbackMessage = feedback.feedback;
}
else {
feedbackMessage = 'I have some issues with the organization suggestions.';
if (feedback.specificIssues && feedback.specificIssues.length > 0) {
feedbackMessage += ` Specifically: ${feedback.specificIssues.join(', ')}. `;
}
if (feedback.selectedSuggestions && feedback.selectedSuggestions.length > 0) {
this.context.getRejectedSuggestions().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);
}
async handleClarification(questions, reason, phase, context = '') {
if (!questions || questions.length === 0) {
return false;
}
console.log(`\nš¤ ${reason}\n`);
const clarifications = [];
for (let i = 0; i < questions.length; i++) {
const question = questions[i];
console.log(`ā Question ${i + 1}/${questions.length}:`);
const { answer } = await inquirer_1.default.prompt([
{
type: 'input',
name: 'answer',
message: question,
validate: (input) => input.trim().length > 0 || 'Please provide an answer'
}
]);
const 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`);
}
this.context.setClarifications(clarifications);
console.log(`š Recorded ${clarifications.length} clarification(s). Let me reconsider with this new information...\n`);
return true;
}
addPatternHints(hints) {
hints.forEach(hint => {
this.context.addPatternHint(hint);
});
}
getContext() {
return this.context;
}
complete() {
super.complete();
}
async getCategoryConversation(category, files) {
try {
const categoryPrompt = this.buildCategoryConversationPrompt(category, files);
// Use direct AI provider call to get properly typed response
if (this.context.getState() !== 'active') {
throw new Error(`Cannot send message in ${this.context.getState()} state`);
}
if (this.context.getTurnCount() >= this.context.getConfig().maxTurns) {
this.context.setState('failed');
throw new Error('Maximum conversation turns exceeded');
}
this.context.addUserMessage(categoryPrompt);
this.context.incrementTurnCount();
const response = await this.aiProvider.generateResponse(this.context, types_1.CategoryConversationSchema);
const aiResponse = response.reasoning || 'Category conversation generated';
this.context.addAssistantMessage(aiResponse);
return response;
}
catch (error) {
throw new Error(`Failed to get category conversation for ${category}: ${error}`);
}
}
buildCategoryConversationPrompt(category, files) {
const formatFileSize = (bytes) => {
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]}`;
};
return `I need to understand how the user wants to organize their ${category} files. I have ${files.length} files in this category.
Sample files:
${files.slice(0, 5).map(f => `- ${f.name} (${f.extension}, ${formatFileSize(f.size)})`).join('\n')}
${files.length > 5 ? `... and ${files.length - 5} more files` : ''}
Based on these ${category} files, please:
1. Analyze the file types, naming patterns, and potential organization strategies
2. Determine what organization approach would work best for this specific content
3. Generate 3-5 specific, practical organization options that make sense for this content type
4. Decide whether the user should choose from your suggested options OR provide free-form input
5. Create an appropriate, clear question to ask the user
Guidelines:
- If there are clear, common patterns (like movies, TV shows, music), provide structured choices
- If the content is unique or mixed, use free-form input
- Make your question specific and actionable
- Ensure your options are genuinely different approaches, not just variations
Please respond with a JSON object containing:
- "question": "A clear, specific question to ask the user"
- "inputType": "choice" or "freeform"
- "choices": ["option1", "option2", "option3"] (only include if inputType is "choice")
- "reasoning": "Brief explanation of why this approach makes sense for this content type"`;
}
}
exports.OrganizationConversation = OrganizationConversation;
//# sourceMappingURL=organization-conversation.js.map