Add manual AI analysis trigger for existing feedback
Implement dashboard functionality to manually trigger AI analysis for
feedback that was submitted before Phase 4 or failed analysis. Adds
detection mechanism to identify which feedback has been analyzed.
Features:
- Manual analysis trigger route: POST /feedback/{id}/analyze
- Detection of whether feedback has been analyzed (analysis.md exists)
- Dashboard UI button showing "Analyze" or "Re-analyze"
- Visual feedback for feedback without text content
- Comprehensive error handling and flash messages
Implementation:
- app/routes/dashboard.py: Added trigger_analysis() route handler
- app/routes/dashboard.py: Updated detail() to pass analysis status
- app/services/feedback_storage.py: Added has_analysis() helper method
- app/templates/dashboard/detail.html: Added analyze button UI
- tests/contract/test_dashboard_routes.py: Added 3 new contract tests
Testing:
- test_post_trigger_analysis_success: Successful manual analysis
- test_post_trigger_analysis_no_content: Reject empty content
- test_post_trigger_analysis_unauthenticated: Auth required
- All 49 tests passing (1 skipped)
User Experience:
- Green box with "Analyze" button for unanalyzed feedback
- Blue box with "Re-analyze" button for already analyzed feedback
- Red box with info message for feedback without text content
- Flash messages show success/error after analysis
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+92
-2
@@ -1,7 +1,8 @@
|
||||
"""Dashboard routes - product owner feedback management"""
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, send_file, abort
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, send_file, abort, current_app
|
||||
from flask_login import login_required, current_user
|
||||
from app.services.feedback_storage import FeedbackStorageService
|
||||
from app.services.ai_analyzer import ClaudeAnalyzer
|
||||
from app.models.product import Product
|
||||
import os
|
||||
import mimetypes
|
||||
@@ -140,10 +141,18 @@ def detail(feedback_id):
|
||||
# Load product info
|
||||
product = Product.get_by_id(actual_product_id)
|
||||
|
||||
# Check if analysis exists
|
||||
has_analysis = FeedbackStorageService.has_analysis(actual_product_id, feedback_id)
|
||||
|
||||
# Check if feedback can be analyzed (has text content)
|
||||
can_analyze = bool(feedback_data.get('content'))
|
||||
|
||||
return render_template(
|
||||
'dashboard/detail.html',
|
||||
feedback=feedback_data,
|
||||
product=product
|
||||
product=product,
|
||||
has_analysis=has_analysis,
|
||||
can_analyze=can_analyze
|
||||
)
|
||||
|
||||
|
||||
@@ -195,6 +204,87 @@ def update_status(feedback_id):
|
||||
return redirect(url_for('dashboard.detail', feedback_id=feedback_id))
|
||||
|
||||
|
||||
@bp.route('/feedback/<feedback_id>/analyze', methods=['POST'])
|
||||
@login_required
|
||||
def trigger_analysis(feedback_id):
|
||||
"""Manually trigger AI analysis for feedback
|
||||
|
||||
Args:
|
||||
feedback_id: Feedback ID to analyze
|
||||
|
||||
Returns:
|
||||
Redirect to detail page with flash message
|
||||
"""
|
||||
# Find feedback globally first
|
||||
all_products = Product.get_all()
|
||||
actual_product_id = None
|
||||
feedback_data = None
|
||||
|
||||
for product in all_products:
|
||||
feedback_data = FeedbackStorageService.load_feedback_detail(product.product_id, feedback_id)
|
||||
if feedback_data:
|
||||
actual_product_id = product.product_id
|
||||
break
|
||||
|
||||
# If not found globally, return 404
|
||||
if not actual_product_id or not feedback_data:
|
||||
abort(404)
|
||||
|
||||
# Check if user has access to this product
|
||||
if not check_product_access(actual_product_id):
|
||||
abort(403)
|
||||
|
||||
# Check if feedback has text content
|
||||
if not feedback_data.get('content'):
|
||||
flash('Cannot analyze feedback without text content', 'error')
|
||||
return redirect(url_for('dashboard.detail', feedback_id=feedback_id))
|
||||
|
||||
# Get product info for owner language
|
||||
product = Product.get_by_id(actual_product_id)
|
||||
|
||||
try:
|
||||
# Update status to "analyzing"
|
||||
FeedbackStorageService.update_feedback_status_by_id(
|
||||
actual_product_id, feedback_id, 'analyzing'
|
||||
)
|
||||
|
||||
# Get API key from environment
|
||||
api_key = os.getenv('ANTHROPIC_API_KEY')
|
||||
|
||||
if not api_key:
|
||||
raise Exception("ANTHROPIC_API_KEY not configured")
|
||||
|
||||
# Initialize analyzer
|
||||
analyzer = ClaudeAnalyzer(api_key=api_key)
|
||||
|
||||
# Analyze feedback
|
||||
result = analyzer.analyze_feedback(
|
||||
feedback_text=feedback_data['content'],
|
||||
target_language=product.owner_language,
|
||||
product_id=actual_product_id
|
||||
)
|
||||
|
||||
# Save analysis results
|
||||
FeedbackStorageService.save_analysis(actual_product_id, feedback_id, result)
|
||||
|
||||
# Update status to "analyzed"
|
||||
FeedbackStorageService.update_feedback_status_by_id(
|
||||
actual_product_id, feedback_id, 'analyzed'
|
||||
)
|
||||
|
||||
flash('Analysis completed successfully', 'success')
|
||||
|
||||
except Exception as e:
|
||||
# Update status to "analysis_failed" on error
|
||||
FeedbackStorageService.update_feedback_status_by_id(
|
||||
actual_product_id, feedback_id, 'analysis_failed'
|
||||
)
|
||||
current_app.logger.error(f"Manual analysis failed for feedback {feedback_id}: {e}")
|
||||
flash(f'Analysis failed: {str(e)}', 'error')
|
||||
|
||||
return redirect(url_for('dashboard.detail', feedback_id=feedback_id))
|
||||
|
||||
|
||||
@bp.route('/feedback/<feedback_id>/attachment/<filename>')
|
||||
@login_required
|
||||
def download_attachment(feedback_id, filename):
|
||||
|
||||
Reference in New Issue
Block a user