戒酒的李白

A more comprehensive large model analysis setup, front-end interface optimization.

@@ -14,9 +14,26 @@ class AIAnalyzer: @@ -14,9 +14,26 @@ class AIAnalyzer:
14 14
15 openai.api_key = self.api_key 15 openai.api_key = self.api_key
16 16
17 - # 系统提示词,限制AI的输出格式  
18 - self.system_prompt = """你是一个专业的舆情分析助手。你的任务是分析每条消息的情感倾向、关键词和潜在影响。  
19 -请严格按照以下JSON格式返回分析结果: 17 + # 不同深度的分析提示词
  18 + self.prompt_templates = {
  19 + 'basic': """你是一个专业的舆情分析助手。请对每条消息进行基础的情感分析。
  20 +请按以下JSON格式返回:
  21 +{
  22 + "analysis_results": [
  23 + {
  24 + "message_id": "消息ID",
  25 + "sentiment": "情感倾向 (积极/消极/中性)",
  26 + "sentiment_score": "情感分数 (0-1)",
  27 + "keywords": ["关键词1", "关键词2"],
  28 + "key_points": "简要概述",
  29 + "influence_analysis": "基础影响分析",
  30 + "risk_level": "风险等级 (低/中/高)",
  31 + "timestamp": "分析时间戳"
  32 + }
  33 + ]
  34 +}""",
  35 + 'standard': """你是一个专业的舆情分析助手。请对每条消息进行标准深度的分析。
  36 +请按以下JSON格式返回:
20 { 37 {
21 "analysis_results": [ 38 "analysis_results": [
22 { 39 {
@@ -30,41 +47,69 @@ class AIAnalyzer: @@ -30,41 +47,69 @@ class AIAnalyzer:
30 "timestamp": "分析时间戳" 47 "timestamp": "分析时间戳"
31 } 48 }
32 ] 49 ]
33 -}  
34 -请确保每个字段都有值,并保持JSON格式的一致性。""" 50 +}""",
  51 + 'deep': """你是一个专业的舆情分析助手。请对每条消息进行深度分析。
  52 +请按以下JSON格式返回:
  53 +{
  54 + "analysis_results": [
  55 + {
  56 + "message_id": "消息ID",
  57 + "sentiment": "情感倾向 (积极/消极/中性)",
  58 + "sentiment_score": "情感分数 (0-1)",
  59 + "keywords": ["关键词1", "关键词2", "关键词3", "关键词4", "关键词5"],
  60 + "key_points": "详细的核心观点分析",
  61 + "influence_analysis": "深度影响分析,包括短期和长期影响",
  62 + "risk_factors": ["风险因素1", "风险因素2", "风险因素3"],
  63 + "risk_level": "风险等级 (低/中/高)",
  64 + "suggestions": ["建议1", "建议2", "建议3"],
  65 + "timestamp": "分析时间戳"
  66 + }
  67 + ]
  68 +}"""
  69 + }
35 70
36 - async def analyze_messages(self, messages: List[Dict]) -> List[Dict]: 71 + async def analyze_messages(self, messages: List[Dict], batch_size: int = 50,
  72 + model_type: str = "gpt-3.5-turbo",
  73 + analysis_depth: str = "standard") -> List[Dict]:
37 """分析一批消息并返回分析结果""" 74 """分析一批消息并返回分析结果"""
38 try: 75 try:
39 - # 构建输入消息 76 + all_results = []
  77 +
  78 + # 分批处理消息
  79 + for i in range(0, len(messages), batch_size):
  80 + batch = messages[i:i + batch_size]
40 formatted_messages = [] 81 formatted_messages = []
41 - for msg in messages: 82 + for msg in batch:
42 formatted_messages.append(f"消息ID: {msg['id']}\n内容: {msg['content']}") 83 formatted_messages.append(f"消息ID: {msg['id']}\n内容: {msg['content']}")
43 84
44 messages_text = "\n---\n".join(formatted_messages) 85 messages_text = "\n---\n".join(formatted_messages)
45 86
  87 + # 获取对应深度的提示词
  88 + system_prompt = self.prompt_templates.get(analysis_depth, self.prompt_templates['standard'])
  89 +
46 # 调用OpenAI API 90 # 调用OpenAI API
47 response = await openai.ChatCompletion.acreate( 91 response = await openai.ChatCompletion.acreate(
48 - model="gpt-3.5-turbo", 92 + model=model_type,
49 messages=[ 93 messages=[
50 - {"role": "system", "content": self.system_prompt}, 94 + {"role": "system", "content": system_prompt},
51 {"role": "user", "content": f"请分析以下消息:\n{messages_text}"} 95 {"role": "user", "content": f"请分析以下消息:\n{messages_text}"}
52 ], 96 ],
53 temperature=0.3, # 降低随机性 97 temperature=0.3, # 降低随机性
54 - max_tokens=2000, 98 + max_tokens=2000 if analysis_depth != 'deep' else 3000,
55 n=1 99 n=1
56 ) 100 )
57 101
58 - # 解析返回结果  
59 try: 102 try:
60 result = json.loads(response.choices[0].message.content) 103 result = json.loads(response.choices[0].message.content)
61 - # 验证结果格式  
62 - if not isinstance(result, dict) or 'analysis_results' not in result:  
63 - raise ValueError("AI返回格式不正确")  
64 - return result['analysis_results']  
65 - except json.JSONDecodeError:  
66 - logging.error("AI返回结果解析失败")  
67 - return [] 104 + if isinstance(result, dict) and 'analysis_results' in result:
  105 + all_results.extend(result['analysis_results'])
  106 + else:
  107 + logging.error(f"API返回格式不正确: {response.choices[0].message.content}")
  108 + except json.JSONDecodeError as e:
  109 + logging.error(f"JSON解析失败: {e}")
  110 + continue
  111 +
  112 + return all_results
68 113
69 except Exception as e: 114 except Exception as e:
70 logging.error(f"AI分析过程出错: {e}") 115 logging.error(f"AI分析过程出错: {e}")
@@ -72,7 +117,7 @@ class AIAnalyzer: @@ -72,7 +117,7 @@ class AIAnalyzer:
72 117
73 def format_analysis_for_display(self, analysis: Dict) -> Dict: 118 def format_analysis_for_display(self, analysis: Dict) -> Dict:
74 """将分析结果格式化为前端显示格式""" 119 """将分析结果格式化为前端显示格式"""
75 - return { 120 + base_result = {
76 'id': analysis['message_id'], 121 'id': analysis['message_id'],
77 'sentiment': analysis['sentiment'], 122 'sentiment': analysis['sentiment'],
78 'sentiment_score': f"{float(analysis['sentiment_score']):.2%}", 123 'sentiment_score': f"{float(analysis['sentiment_score']):.2%}",
@@ -85,5 +130,14 @@ class AIAnalyzer: @@ -85,5 +130,14 @@ class AIAnalyzer:
85 ).strftime('%Y-%m-%d %H:%M:%S') 130 ).strftime('%Y-%m-%d %H:%M:%S')
86 } 131 }
87 132
  133 + # 如果是深度分析,添加额外信息
  134 + if 'risk_factors' in analysis:
  135 + base_result.update({
  136 + 'risk_factors': analysis['risk_factors'],
  137 + 'suggestions': analysis['suggestions']
  138 + })
  139 +
  140 + return base_result
  141 +
88 # 创建全局AI分析器实例 142 # 创建全局AI分析器实例
89 ai_analyzer = AIAnalyzer() 143 ai_analyzer = AIAnalyzer()
@@ -308,11 +308,33 @@ def articleChar(id): @@ -308,11 +308,33 @@ def articleChar(id):
308 @pb.route('/api/analyze_messages', methods=['POST']) 308 @pb.route('/api/analyze_messages', methods=['POST'])
309 async def analyze_messages(): 309 async def analyze_messages():
310 try: 310 try:
311 - # 获取最近50条消息  
312 - messages = getRecentMessages(50) # 需要实现这个函数 311 + # 获取请求参数
  312 + data = request.get_json()
  313 + batch_size = data.get('batch_size', 50)
  314 + model_type = data.get('model_type', 'gpt-3.5-turbo')
  315 + analysis_depth = data.get('analysis_depth', 'standard')
  316 +
  317 + # 获取最近的消息
  318 + messages = getRecentMessages(batch_size)
  319 + if not messages:
  320 + return jsonify({
  321 + 'success': False,
  322 + 'error': '没有找到需要分析的消息'
  323 + }), 404
313 324
314 # 调用AI进行分析 325 # 调用AI进行分析
315 - analysis_results = await ai_analyzer.analyze_messages(messages) 326 + analysis_results = await ai_analyzer.analyze_messages(
  327 + messages=messages,
  328 + batch_size=batch_size,
  329 + model_type=model_type,
  330 + analysis_depth=analysis_depth
  331 + )
  332 +
  333 + if not analysis_results:
  334 + return jsonify({
  335 + 'success': False,
  336 + 'error': '分析过程中出现错误'
  337 + }), 500
316 338
317 # 保存到数据库 339 # 保存到数据库
318 with Session(engine) as session: 340 with Session(engine) as session:
@@ -337,7 +359,14 @@ async def analyze_messages(): @@ -337,7 +359,14 @@ async def analyze_messages():
337 359
338 return jsonify({ 360 return jsonify({
339 'success': True, 361 'success': True,
340 - 'data': display_results 362 + 'data': display_results,
  363 + 'meta': {
  364 + 'total_messages': len(messages),
  365 + 'analyzed_messages': len(analysis_results),
  366 + 'batch_size': batch_size,
  367 + 'model_type': model_type,
  368 + 'analysis_depth': analysis_depth
  369 + }
341 }) 370 })
342 371
343 except Exception as e: 372 except Exception as e:
@@ -454,11 +454,74 @@ @@ -454,11 +454,74 @@
454 <div class="header-title"> 454 <div class="header-title">
455 <h4 class="card-title">AI深度分析</h4> 455 <h4 class="card-title">AI深度分析</h4>
456 </div> 456 </div>
457 - <button class="btn btn-primary" onclick="requestAIAnalysis()">  
458 - 开始AI分析 457 + <div class="analysis-controls">
  458 + <!-- 批量处理设置 -->
  459 + <div class="d-flex align-items-center">
  460 + <div class="form-group mx-2 mb-0">
  461 + <select id="batchSize" class="form-control form-control-sm">
  462 + <option value="10">每批10条</option>
  463 + <option value="20">每批20条</option>
  464 + <option value="50" selected>每批50条</option>
  465 + <option value="100">每批100条</option>
  466 + </select>
  467 + </div>
  468 + <div class="form-group mx-2 mb-0">
  469 + <select id="modelType" class="form-control form-control-sm">
  470 + <option value="gpt-3.5-turbo" selected>GPT-3.5</option>
  471 + <option value="gpt-4">GPT-4</option>
  472 + </select>
  473 + </div>
  474 + <div class="form-group mx-2 mb-0">
  475 + <select id="analysisDepth" class="form-control form-control-sm">
  476 + <option value="basic">基础分析</option>
  477 + <option value="standard" selected>标准分析</option>
  478 + <option value="deep">深度分析</option>
  479 + </select>
  480 + </div>
  481 + <div class="custom-control custom-switch mx-2">
  482 + <input type="checkbox" class="custom-control-input" id="autoUpdate">
  483 + <label class="custom-control-label" for="autoUpdate">自动更新</label>
  484 + </div>
  485 + <button class="btn btn-primary btn-sm" onclick="requestAIAnalysis()">
  486 + 开始分析
459 </button> 487 </button>
460 </div> 488 </div>
  489 + </div>
  490 + </div>
461 <div class="card-body"> 491 <div class="card-body">
  492 + <!-- 进度显示 -->
  493 + <div id="analysis-progress" class="mb-3" style="display: none;">
  494 + <div class="progress">
  495 + <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div>
  496 + </div>
  497 + <small class="text-muted mt-1 d-block">正在分析: <span id="progress-text">0/0</span></small>
  498 + </div>
  499 +
  500 + <!-- 分析结果过滤器 -->
  501 + <div class="mb-3">
  502 + <div class="d-flex align-items-center">
  503 + <div class="form-group mb-0 mr-2">
  504 + <input type="text" class="form-control form-control-sm" id="keywordFilter" placeholder="按关键词过滤">
  505 + </div>
  506 + <div class="form-group mb-0 mr-2">
  507 + <select class="form-control form-control-sm" id="sentimentFilter">
  508 + <option value="">全部情感</option>
  509 + <option value="积极">积极</option>
  510 + <option value="消极">消极</option>
  511 + <option value="中性">中性</option>
  512 + </select>
  513 + </div>
  514 + <div class="form-group mb-0">
  515 + <select class="form-control form-control-sm" id="riskFilter">
  516 + <option value="">全部风险等级</option>
  517 + <option value="高">高风险</option>
  518 + <option value="中">中风险</option>
  519 + <option value="低">低风险</option>
  520 + </select>
  521 + </div>
  522 + </div>
  523 + </div>
  524 +
462 <div id="ai-analysis-results" class="analysis-container"> 525 <div id="ai-analysis-results" class="analysis-container">
463 <!-- 分析结果将在这里动态显示 --> 526 <!-- 分析结果将在这里动态显示 -->
464 </div> 527 </div>
@@ -467,7 +530,7 @@ @@ -467,7 +530,7 @@
467 </div> 530 </div>
468 </div> 531 </div>
469 532
470 -<!-- 添加必要的CSS样式 --> 533 +<!-- 更新CSS样式 -->
471 <style> 534 <style>
472 .analysis-container { 535 .analysis-container {
473 max-height: 600px; 536 max-height: 600px;
@@ -481,10 +544,12 @@ @@ -481,10 +544,12 @@
481 margin-bottom: 15px; 544 margin-bottom: 15px;
482 background-color: #fff; 545 background-color: #fff;
483 box-shadow: 0 2px 4px rgba(0,0,0,0.1); 546 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  547 + transition: all 0.3s ease;
484 } 548 }
485 549
486 .analysis-card:hover { 550 .analysis-card:hover {
487 box-shadow: 0 4px 8px rgba(0,0,0,0.15); 551 box-shadow: 0 4px 8px rgba(0,0,0,0.15);
  552 + transform: translateY(-2px);
488 } 553 }
489 554
490 .risk-level { 555 .risk-level {
@@ -521,32 +586,167 @@ @@ -521,32 +586,167 @@
521 padding: 4px 8px; 586 padding: 4px 8px;
522 border-radius: 16px; 587 border-radius: 16px;
523 font-size: 0.9em; 588 font-size: 0.9em;
  589 + transition: all 0.2s ease;
  590 +}
  591 +
  592 +.keyword-tag:hover {
  593 + background-color: #1976d2;
  594 + color: #fff;
  595 +}
  596 +
  597 +.sentiment-score {
  598 + display: inline-block;
  599 + padding: 2px 6px;
  600 + border-radius: 4px;
  601 + font-size: 0.9em;
  602 + margin-left: 8px;
  603 +}
  604 +
  605 +.sentiment-positive {
  606 + background-color: #e8f5e9;
  607 + color: #2e7d32;
  608 +}
  609 +
  610 +.sentiment-negative {
  611 + background-color: #ffebee;
  612 + color: #c62828;
  613 +}
  614 +
  615 +.sentiment-neutral {
  616 + background-color: #f5f5f5;
  617 + color: #616161;
  618 +}
  619 +
  620 +.progress {
  621 + height: 0.5rem;
  622 +}
  623 +
  624 +.analysis-controls {
  625 + display: flex;
  626 + align-items: center;
  627 + flex-wrap: wrap;
  628 + gap: 10px;
  629 +}
  630 +
  631 +@media (max-width: 768px) {
  632 + .analysis-controls {
  633 + flex-direction: column;
  634 + align-items: stretch;
  635 + }
524 } 636 }
525 </style> 637 </style>
526 638
527 -<!-- 添加必要的JavaScript代码 --> 639 +<!-- 更新JavaScript代码 -->
528 <script> 640 <script>
  641 +let currentAnalysis = {
  642 + inProgress: false,
  643 + totalMessages: 0,
  644 + processedMessages: 0,
  645 + autoUpdateInterval: null
  646 +};
  647 +
529 async function requestAIAnalysis() { 648 async function requestAIAnalysis() {
  649 + if (currentAnalysis.inProgress) {
  650 + return;
  651 + }
  652 +
530 try { 653 try {
  654 + currentAnalysis.inProgress = true;
  655 + showProgress();
  656 +
  657 + const batchSize = parseInt(document.getElementById('batchSize').value);
  658 + const modelType = document.getElementById('modelType').value;
  659 + const analysisDepth = document.getElementById('analysisDepth').value;
  660 +
531 const response = await fetch('/page/api/analyze_messages', { 661 const response = await fetch('/page/api/analyze_messages', {
532 method: 'POST', 662 method: 'POST',
533 headers: { 663 headers: {
534 'Content-Type': 'application/json' 664 'Content-Type': 'application/json'
535 - } 665 + },
  666 + body: JSON.stringify({
  667 + batch_size: batchSize,
  668 + model_type: modelType,
  669 + analysis_depth: analysisDepth
  670 + })
536 }); 671 });
537 672
538 const result = await response.json(); 673 const result = await response.json();
539 if (result.success) { 674 if (result.success) {
540 displayAnalysisResults(result.data); 675 displayAnalysisResults(result.data);
  676 + setupAutoUpdate();
541 } else { 677 } else {
542 - alert('分析失败: ' + result.error); 678 + showError(result.error);
543 } 679 }
544 } catch (error) { 680 } catch (error) {
545 console.error('AI分析请求失败:', error); 681 console.error('AI分析请求失败:', error);
546 - alert('请求失败,请稍后重试'); 682 + showError('请求失败,请稍后重试');
  683 + } finally {
  684 + currentAnalysis.inProgress = false;
  685 + hideProgress();
547 } 686 }
548 } 687 }
549 688
  689 +function showProgress() {
  690 + const progressDiv = document.getElementById('analysis-progress');
  691 + progressDiv.style.display = 'block';
  692 + updateProgress(0, 100);
  693 +}
  694 +
  695 +function hideProgress() {
  696 + const progressDiv = document.getElementById('analysis-progress');
  697 + progressDiv.style.display = 'none';
  698 +}
  699 +
  700 +function updateProgress(current, total) {
  701 + const progressBar = document.querySelector('.progress-bar');
  702 + const progressText = document.getElementById('progress-text');
  703 + const percentage = (current / total) * 100;
  704 +
  705 + progressBar.style.width = `${percentage}%`;
  706 + progressText.textContent = `${current}/${total}`;
  707 +}
  708 +
  709 +function setupAutoUpdate() {
  710 + const autoUpdate = document.getElementById('autoUpdate').checked;
  711 + if (autoUpdate && !currentAnalysis.autoUpdateInterval) {
  712 + currentAnalysis.autoUpdateInterval = setInterval(requestAIAnalysis, 300000); // 5分钟更新一次
  713 + } else if (!autoUpdate && currentAnalysis.autoUpdateInterval) {
  714 + clearInterval(currentAnalysis.autoUpdateInterval);
  715 + currentAnalysis.autoUpdateInterval = null;
  716 + }
  717 +}
  718 +
  719 +function filterResults() {
  720 + const keyword = document.getElementById('keywordFilter').value.toLowerCase();
  721 + const sentiment = document.getElementById('sentimentFilter').value;
  722 + const risk = document.getElementById('riskFilter').value;
  723 +
  724 + const cards = document.querySelectorAll('.analysis-card');
  725 + cards.forEach(card => {
  726 + let show = true;
  727 +
  728 + // 关键词过滤
  729 + if (keyword) {
  730 + const content = card.textContent.toLowerCase();
  731 + show = show && content.includes(keyword);
  732 + }
  733 +
  734 + // 情感过滤
  735 + if (sentiment) {
  736 + const cardSentiment = card.querySelector('.sentiment-text').textContent;
  737 + show = show && cardSentiment.includes(sentiment);
  738 + }
  739 +
  740 + // 风险等级过滤
  741 + if (risk) {
  742 + const cardRisk = card.querySelector('.risk-level').textContent;
  743 + show = show && cardRisk.includes(risk);
  744 + }
  745 +
  746 + card.style.display = show ? 'block' : 'none';
  747 + });
  748 +}
  749 +
550 function displayAnalysisResults(results) { 750 function displayAnalysisResults(results) {
551 const container = document.getElementById('ai-analysis-results'); 751 const container = document.getElementById('ai-analysis-results');
552 container.innerHTML = ''; // 清空现有结果 752 container.innerHTML = ''; // 清空现有结果
@@ -559,6 +759,10 @@ function displayAnalysisResults(results) { @@ -559,6 +759,10 @@ function displayAnalysisResults(results) {
559 analysis.risk_level === '高' ? 'risk-high' : 759 analysis.risk_level === '高' ? 'risk-high' :
560 analysis.risk_level === '中' ? 'risk-medium' : 'risk-low'; 760 analysis.risk_level === '中' ? 'risk-medium' : 'risk-low';
561 761
  762 + const sentimentClass =
  763 + analysis.sentiment === '积极' ? 'sentiment-positive' :
  764 + analysis.sentiment === '消极' ? 'sentiment-negative' : 'sentiment-neutral';
  765 +
562 card.innerHTML = ` 766 card.innerHTML = `
563 <div class="d-flex justify-content-between align-items-center"> 767 <div class="d-flex justify-content-between align-items-center">
564 <h5 class="mb-2">消息ID: ${analysis.id}</h5> 768 <h5 class="mb-2">消息ID: ${analysis.id}</h5>
@@ -567,8 +771,9 @@ function displayAnalysisResults(results) { @@ -567,8 +771,9 @@ function displayAnalysisResults(results) {
567 </span> 771 </span>
568 </div> 772 </div>
569 <div class="mb-2"> 773 <div class="mb-2">
570 - <strong>情感倾向:</strong> ${analysis.sentiment}  
571 - <span class="ml-2">(${analysis.sentiment_score})</span> 774 + <strong>情感倾向:</strong>
  775 + <span class="sentiment-text">${analysis.sentiment}</span>
  776 + <span class="sentiment-score ${sentimentClass}">${analysis.sentiment_score}</span>
572 </div> 777 </div>
573 <div class="keywords-container"> 778 <div class="keywords-container">
574 ${analysis.keywords.split(',').map(keyword => 779 ${analysis.keywords.split(',').map(keyword =>
@@ -590,10 +795,28 @@ function displayAnalysisResults(results) { @@ -590,10 +795,28 @@ function displayAnalysisResults(results) {
590 795
591 container.appendChild(card); 796 container.appendChild(card);
592 }); 797 });
  798 +
  799 + // 应用当前的过滤器
  800 + filterResults();
593 } 801 }
594 802
595 -// 页面加载完成后自动请求一次AI分析  
596 -document.addEventListener('DOMContentLoaded', requestAIAnalysis); 803 +function showError(message) {
  804 + alert(message);
  805 +}
  806 +
  807 +// 设置事件监听器
  808 +document.addEventListener('DOMContentLoaded', () => {
  809 + // 自动更新切换
  810 + document.getElementById('autoUpdate').addEventListener('change', setupAutoUpdate);
  811 +
  812 + // 过滤器变化监听
  813 + document.getElementById('keywordFilter').addEventListener('input', filterResults);
  814 + document.getElementById('sentimentFilter').addEventListener('change', filterResults);
  815 + document.getElementById('riskFilter').addEventListener('change', filterResults);
  816 +
  817 + // 首次加载时请求分析
  818 + requestAIAnalysis();
  819 +});
597 </script> 820 </script>
598 821
599 {% endblock %} 822 {% endblock %}