This commit is contained in:
Bryce
2025-08-11 06:37:24 -07:00
parent 0dac428217
commit cc1523cbb2
6 changed files with 92 additions and 173 deletions

View File

@@ -60,37 +60,6 @@ class AIService:
return None
def generate_single_rule(self, folder_name: str, folder_type: str = 'destination', rule_text: str ='') -> Tuple[Optional[str], Optional[Dict]]:
"""Generate a single email organization rule using AI."""
prompt = self._build_single_rule_prompt(folder_name, folder_type, rule_text)
payload = {
'model': self.model,
'messages': [
{'role': 'system', 'content': 'You are an expert email organizer assistant.'},
{'role': 'user', 'content': prompt}
],
'max_tokens': 800,
'temperature': 0.7
}
result = self._make_request('chat/completions', payload)
if not result or 'choices' not in result or not result['choices']:
return None, {'error': 'No response from AI service'}
try:
rule_text = result['choices'][0]['message']['content'].strip()
quality_score = self._assess_rule_quality(rule_text, folder_name, folder_type)
return rule_text, {
'quality_score': quality_score,
'model_used': self.model,
'generated_at': datetime.utcnow().isoformat()
}
except (KeyError, IndexError) as e:
return None, {'error': f'Failed to parse AI response: {str(e)}'}
def generate_multiple_rules(self, folder_name: str, folder_type: str = 'destination', rule_text:str = '', count: int = 3) -> Tuple[Optional[List[Dict]], Optional[Dict]]:
"""Generate multiple email organization rule options using AI."""
prompt = self._build_multiple_rules_prompt(folder_name, folder_type, rule_text, count)

View File

@@ -27,7 +27,9 @@ def index():
@login_required
def new_folder_modal():
"""Return the add folder modal."""
response = make_response(render_template('partials/folder_modal.html'))
response = make_response(render_template('partials/folder_modal.html', folder_data={'rule_text': folder.rule_text,
'show_ai_rules': True,
'errors': None }))
response.headers['HX-Trigger'] = 'open-modal'
return response
@@ -59,7 +61,10 @@ def add_folder():
# If there are validation errors, return the modal with errors
if errors:
response = make_response(render_template('partials/folder_modal.html', errors=errors, name=name, rule_text=rule_text, priority=priority))
response = make_response(render_template('partials/folder_modal.html', errors=errors, name=name, rule_text=rule_text, priority=priority,
folder_data={'rule_text': '',
'show_ai_rules': True,
'errors': None }))
response.headers['HX-Retarget'] = '#folder-modal'
response.headers['HX-Reswap'] = 'outerHTML'
return response
@@ -235,7 +240,10 @@ def update_folder(folder_id):
# If there are validation errors, return the modal with errors
if errors:
response = make_response(render_template('partials/folder_modal.html', folder=folder, errors=errors, name=name, rule_text=rule_text, priority=priority))
response = make_response(render_template('partials/folder_modal.html', folder=folder, errors=errors, name=name, rule_text=rule_text, priority=priority,
folder_data={'rule_text': folder.rule_text,
'show_ai_rules': True,
'errors': None }))
return response
# Update folder
@@ -263,7 +271,10 @@ def update_folder(folder_id):
db.session.rollback()
# Return error in modal
errors = {'general': 'An unexpected error occurred. Please try again.'}
response = make_response(render_template('partials/folder_modal.html', folder=folder, errors=errors, name=name, rule_text=rule_text, priority=priority))
response = make_response(render_template('partials/folder_modal.html', folder=folder, errors=errors, name=name, rule_text=rule_text, priority=priority,
folder_data={'rule_text': folder.rule_text,
'show_ai_rules': True,
'errors': None }))
response.headers['HX-Retarget'] = '#folder-modal'
response.headers['HX-Reswap'] = 'outerHTML'
return response
@@ -365,79 +376,41 @@ def generate_rule():
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()
# Only support multiple rules now
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,
'rule': rule_text,
'metadata': metadata,
'quality_score': metadata.get('quality_score', 0)
'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)
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)
# 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

View File

@@ -63,7 +63,7 @@ x-data='{{ folder_data|tojson }}'
placeholder="e.g., Move emails from 'newsletter@company.com' to this folder"
required
x-model="rule_text"
>{% if rule_text is defined %}{{ rule_text }}{% elif folder %}{{ folder.rule_text }}{% endif %}</textarea>
></textarea>
{% if errors and errors.rule_text %}
<div class="text-error text-sm mt-1">{{ errors.rule_text }}</div>
{% endif %}