Makes ai rule generation content work good.

This commit is contained in:
Bryce
2025-08-10 21:21:02 -07:00
parent 47a63d2eab
commit 0dac428217
12 changed files with 2129 additions and 8 deletions

View File

@@ -1,8 +1,14 @@
from flask import Blueprint, render_template, request, jsonify, make_response
from flask_login import login_required, current_user
from app import db
from app.models import Folder
from app.models import Folder, AIRuleCache
from app.ai_service import AIService
from datetime import datetime, timedelta
import logging
import json
# Initialize the AI service instance
ai_service = AIService()
folders_bp = Blueprint('folders', __name__)
@@ -180,7 +186,10 @@ def edit_folder_modal(folder_id):
return jsonify({'error': 'Folder not found'}), 404
# Return the edit folder modal with folder data
response = make_response(render_template('partials/folder_modal.html', folder=folder))
response = make_response(render_template('partials/folder_modal.html', folder=folder,
folder_data={'rule_text': folder.rule_text,
'show_ai_rules': True,
'errors': None }))
response.headers['HX-Trigger'] = 'open-modal'
return response
@@ -313,4 +322,160 @@ def get_folders():
return response
else:
return render_template('partials/folders_list.html', folders=folders, show_hidden=show_hidden)
return render_template('partials/folders_list.html', folders=folders, show_hidden=show_hidden)
@folders_bp.route('/api/folders/generate-rule', methods=['POST'])
@login_required
def generate_rule():
"""Generate an email organization rule using AI."""
try:
# Get form data
folder_name = request.form.get('name', '').strip()
folder_type = request.form.get('folder_type', 'destination')
rule_type = request.form.get('rule_type', 'single') # 'single' or 'multiple'
rule_text = request.form.get('rule_text', '')
# Validate inputs
if not folder_name:
return render_template('partials/ai_rule_result.html', result={'success': False, 'error': 'Folder name is required'})
if folder_type not in ['destination', 'tidy', 'ignore']:
return render_template('partials/ai_rule_result.html', result={'success': False, 'error': 'Invalid folder type'})
if rule_type not in ['single', 'multiple']:
return render_template('partials/ai_rule_result.html', result={'success': False, 'error': 'Invalid rule type'})
# Check cache first
cache_key = AIRuleCache.generate_cache_key(folder_name, folder_type, rule_type, rule_text)
cached_rule = AIRuleCache.query.filter_by(
cache_key=cache_key,
user_id=current_user.id,
is_active=True
).first()
if cached_rule and not cached_rule.is_expired():
# Return cached result
result = {
'success': True,
'cached': True,
'rule': cached_rule.rule_text,
'metadata': cached_rule.rule_metadata,
'quality_score': cached_rule.rule_metadata.get('quality_score', 0) if cached_rule.rule_metadata else 0
}
return render_template('partials/ai_rule_result.html', result=result)
# Generate new rule using AI service
if rule_type == 'single':
rule_text, metadata = ai_service.generate_single_rule(folder_name, folder_type, rule_text)
if rule_text is None:
# AI service failed, return fallback
fallback_rule = ai_service.get_fallback_rule(folder_name, folder_type)
result = {
'success': True,
'fallback': True,
'rule': fallback_rule,
'quality_score': 50,
'message': 'AI service unavailable, using fallback rule'
}
return render_template('partials/ai_rule_result.html', result=result)
# Cache the result
expires_at = datetime.utcnow() + timedelta(hours=1) # Cache for 1 hour
cache_entry = AIRuleCache(
user_id=current_user.id,
folder_name=folder_name,
folder_type=folder_type,
rule_text=rule_text,
rule_metadata=metadata,
cache_key=cache_key,
expires_at=expires_at
)
db.session.add(cache_entry)
db.session.commit()
result = {
'success': True,
'rule': rule_text,
'metadata': metadata,
'quality_score': metadata.get('quality_score', 0)
}
return render_template('partials/ai_rule_result.html', result=result)
else: # multiple rules
rules, metadata = ai_service.generate_multiple_rules(folder_name, folder_type, rule_text)
if rules is None:
# AI service failed, return fallback
fallback_rule = ai_service.get_fallback_rule(folder_name, folder_type)
result = {
'success': True,
'fallback': True,
'rules': [{'text': fallback_rule, 'quality_score': 50}],
'message': 'AI service unavailable, using fallback rule'
}
return render_template('partials/ai_rule_result.html', result=result)
# Cache the first rule as representative
expires_at = datetime.utcnow() + timedelta(hours=1)
cache_entry = AIRuleCache(
user_id=current_user.id,
folder_name=folder_name,
folder_type=folder_type,
rule_text=rules[0]['text'] if rules else '',
rule_metadata=metadata,
cache_key=cache_key,
expires_at=expires_at
)
db.session.add(cache_entry)
db.session.commit()
result = {
'success': True,
'rules': rules,
'metadata': metadata
}
return render_template('partials/ai_rule_result.html', result=result)
except Exception as e:
# Print unhandled exceptions to the console as required
logging.exception("Error generating rule: %s", e)
return render_template('partials/ai_rule_result.html', result={'success': False, 'error': 'An unexpected error occurred'})
@folders_bp.route('/api/folders/assess-rule', methods=['POST'])
@login_required
def assess_rule():
"""Assess the quality of an email organization rule."""
try:
# Get form data
rule_text = request.form.get('rule_text', '').strip()
folder_name = request.form.get('folder_name', '').strip()
folder_type = request.form.get('folder_type', 'destination')
# Validate inputs
if not rule_text:
return render_template('partials/ai_rule_result.html', result={'success': False, 'error': 'Rule text is required'})
if not folder_name:
return render_template('partials/ai_rule_result.html', result={'success': False, 'error': 'Folder name is required'})
if folder_type not in ['destination', 'tidy', 'ignore']:
return render_template('partials/ai_rule_result.html', result={'success': False, 'error': 'Invalid folder type'})
# Assess rule quality
quality_assessment = ai_service.assess_rule_quality(rule_text, folder_name, folder_type)
result = {
'success': True,
'assessment': quality_assessment,
'rule': rule_text,
'quality_score': quality_assessment['score']
}
return render_template('partials/ai_rule_result.html', result=result)
except Exception as e:
# Print unhandled exceptions to the console as required
logging.exception("Error assessing rule: %s", e)
return render_template('partials/ai_rule_result.html', result={'success': False, 'error': 'An unexpected error occurred'})