A more comprehensive large model analysis setup, front-end interface optimization.
This commit is contained in:
+89
-35
@@ -14,9 +14,26 @@ class AIAnalyzer:
|
||||
|
||||
openai.api_key = self.api_key
|
||||
|
||||
# 系统提示词,限制AI的输出格式
|
||||
self.system_prompt = """你是一个专业的舆情分析助手。你的任务是分析每条消息的情感倾向、关键词和潜在影响。
|
||||
请严格按照以下JSON格式返回分析结果:
|
||||
# 不同深度的分析提示词
|
||||
self.prompt_templates = {
|
||||
'basic': """你是一个专业的舆情分析助手。请对每条消息进行基础的情感分析。
|
||||
请按以下JSON格式返回:
|
||||
{
|
||||
"analysis_results": [
|
||||
{
|
||||
"message_id": "消息ID",
|
||||
"sentiment": "情感倾向 (积极/消极/中性)",
|
||||
"sentiment_score": "情感分数 (0-1)",
|
||||
"keywords": ["关键词1", "关键词2"],
|
||||
"key_points": "简要概述",
|
||||
"influence_analysis": "基础影响分析",
|
||||
"risk_level": "风险等级 (低/中/高)",
|
||||
"timestamp": "分析时间戳"
|
||||
}
|
||||
]
|
||||
}""",
|
||||
'standard': """你是一个专业的舆情分析助手。请对每条消息进行标准深度的分析。
|
||||
请按以下JSON格式返回:
|
||||
{
|
||||
"analysis_results": [
|
||||
{
|
||||
@@ -30,41 +47,69 @@ class AIAnalyzer:
|
||||
"timestamp": "分析时间戳"
|
||||
}
|
||||
]
|
||||
}
|
||||
请确保每个字段都有值,并保持JSON格式的一致性。"""
|
||||
}""",
|
||||
'deep': """你是一个专业的舆情分析助手。请对每条消息进行深度分析。
|
||||
请按以下JSON格式返回:
|
||||
{
|
||||
"analysis_results": [
|
||||
{
|
||||
"message_id": "消息ID",
|
||||
"sentiment": "情感倾向 (积极/消极/中性)",
|
||||
"sentiment_score": "情感分数 (0-1)",
|
||||
"keywords": ["关键词1", "关键词2", "关键词3", "关键词4", "关键词5"],
|
||||
"key_points": "详细的核心观点分析",
|
||||
"influence_analysis": "深度影响分析,包括短期和长期影响",
|
||||
"risk_factors": ["风险因素1", "风险因素2", "风险因素3"],
|
||||
"risk_level": "风险等级 (低/中/高)",
|
||||
"suggestions": ["建议1", "建议2", "建议3"],
|
||||
"timestamp": "分析时间戳"
|
||||
}
|
||||
]
|
||||
}"""
|
||||
}
|
||||
|
||||
async def analyze_messages(self, messages: List[Dict]) -> List[Dict]:
|
||||
async def analyze_messages(self, messages: List[Dict], batch_size: int = 50,
|
||||
model_type: str = "gpt-3.5-turbo",
|
||||
analysis_depth: str = "standard") -> List[Dict]:
|
||||
"""分析一批消息并返回分析结果"""
|
||||
try:
|
||||
# 构建输入消息
|
||||
formatted_messages = []
|
||||
for msg in messages:
|
||||
formatted_messages.append(f"消息ID: {msg['id']}\n内容: {msg['content']}")
|
||||
all_results = []
|
||||
|
||||
messages_text = "\n---\n".join(formatted_messages)
|
||||
|
||||
# 调用OpenAI API
|
||||
response = await openai.ChatCompletion.acreate(
|
||||
model="gpt-3.5-turbo",
|
||||
messages=[
|
||||
{"role": "system", "content": self.system_prompt},
|
||||
{"role": "user", "content": f"请分析以下消息:\n{messages_text}"}
|
||||
],
|
||||
temperature=0.3, # 降低随机性
|
||||
max_tokens=2000,
|
||||
n=1
|
||||
)
|
||||
|
||||
# 解析返回结果
|
||||
try:
|
||||
result = json.loads(response.choices[0].message.content)
|
||||
# 验证结果格式
|
||||
if not isinstance(result, dict) or 'analysis_results' not in result:
|
||||
raise ValueError("AI返回格式不正确")
|
||||
return result['analysis_results']
|
||||
except json.JSONDecodeError:
|
||||
logging.error("AI返回结果解析失败")
|
||||
return []
|
||||
# 分批处理消息
|
||||
for i in range(0, len(messages), batch_size):
|
||||
batch = messages[i:i + batch_size]
|
||||
formatted_messages = []
|
||||
for msg in batch:
|
||||
formatted_messages.append(f"消息ID: {msg['id']}\n内容: {msg['content']}")
|
||||
|
||||
messages_text = "\n---\n".join(formatted_messages)
|
||||
|
||||
# 获取对应深度的提示词
|
||||
system_prompt = self.prompt_templates.get(analysis_depth, self.prompt_templates['standard'])
|
||||
|
||||
# 调用OpenAI API
|
||||
response = await openai.ChatCompletion.acreate(
|
||||
model=model_type,
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": f"请分析以下消息:\n{messages_text}"}
|
||||
],
|
||||
temperature=0.3, # 降低随机性
|
||||
max_tokens=2000 if analysis_depth != 'deep' else 3000,
|
||||
n=1
|
||||
)
|
||||
|
||||
try:
|
||||
result = json.loads(response.choices[0].message.content)
|
||||
if isinstance(result, dict) and 'analysis_results' in result:
|
||||
all_results.extend(result['analysis_results'])
|
||||
else:
|
||||
logging.error(f"API返回格式不正确: {response.choices[0].message.content}")
|
||||
except json.JSONDecodeError as e:
|
||||
logging.error(f"JSON解析失败: {e}")
|
||||
continue
|
||||
|
||||
return all_results
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"AI分析过程出错: {e}")
|
||||
@@ -72,7 +117,7 @@ class AIAnalyzer:
|
||||
|
||||
def format_analysis_for_display(self, analysis: Dict) -> Dict:
|
||||
"""将分析结果格式化为前端显示格式"""
|
||||
return {
|
||||
base_result = {
|
||||
'id': analysis['message_id'],
|
||||
'sentiment': analysis['sentiment'],
|
||||
'sentiment_score': f"{float(analysis['sentiment_score']):.2%}",
|
||||
@@ -84,6 +129,15 @@ class AIAnalyzer:
|
||||
float(analysis['timestamp'])
|
||||
).strftime('%Y-%m-%d %H:%M:%S')
|
||||
}
|
||||
|
||||
# 如果是深度分析,添加额外信息
|
||||
if 'risk_factors' in analysis:
|
||||
base_result.update({
|
||||
'risk_factors': analysis['risk_factors'],
|
||||
'suggestions': analysis['suggestions']
|
||||
})
|
||||
|
||||
return base_result
|
||||
|
||||
# 创建全局AI分析器实例
|
||||
ai_analyzer = AIAnalyzer()
|
||||
+33
-4
@@ -308,11 +308,33 @@ def articleChar(id):
|
||||
@pb.route('/api/analyze_messages', methods=['POST'])
|
||||
async def analyze_messages():
|
||||
try:
|
||||
# 获取最近50条消息
|
||||
messages = getRecentMessages(50) # 需要实现这个函数
|
||||
# 获取请求参数
|
||||
data = request.get_json()
|
||||
batch_size = data.get('batch_size', 50)
|
||||
model_type = data.get('model_type', 'gpt-3.5-turbo')
|
||||
analysis_depth = data.get('analysis_depth', 'standard')
|
||||
|
||||
# 获取最近的消息
|
||||
messages = getRecentMessages(batch_size)
|
||||
if not messages:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': '没有找到需要分析的消息'
|
||||
}), 404
|
||||
|
||||
# 调用AI进行分析
|
||||
analysis_results = await ai_analyzer.analyze_messages(messages)
|
||||
analysis_results = await ai_analyzer.analyze_messages(
|
||||
messages=messages,
|
||||
batch_size=batch_size,
|
||||
model_type=model_type,
|
||||
analysis_depth=analysis_depth
|
||||
)
|
||||
|
||||
if not analysis_results:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': '分析过程中出现错误'
|
||||
}), 500
|
||||
|
||||
# 保存到数据库
|
||||
with Session(engine) as session:
|
||||
@@ -337,7 +359,14 @@ async def analyze_messages():
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': display_results
|
||||
'data': display_results,
|
||||
'meta': {
|
||||
'total_messages': len(messages),
|
||||
'analyzed_messages': len(analysis_results),
|
||||
'batch_size': batch_size,
|
||||
'model_type': model_type,
|
||||
'analysis_depth': analysis_depth
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -454,11 +454,74 @@
|
||||
<div class="header-title">
|
||||
<h4 class="card-title">AI深度分析</h4>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="requestAIAnalysis()">
|
||||
开始AI分析
|
||||
</button>
|
||||
<div class="analysis-controls">
|
||||
<!-- 批量处理设置 -->
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="form-group mx-2 mb-0">
|
||||
<select id="batchSize" class="form-control form-control-sm">
|
||||
<option value="10">每批10条</option>
|
||||
<option value="20">每批20条</option>
|
||||
<option value="50" selected>每批50条</option>
|
||||
<option value="100">每批100条</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mx-2 mb-0">
|
||||
<select id="modelType" class="form-control form-control-sm">
|
||||
<option value="gpt-3.5-turbo" selected>GPT-3.5</option>
|
||||
<option value="gpt-4">GPT-4</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mx-2 mb-0">
|
||||
<select id="analysisDepth" class="form-control form-control-sm">
|
||||
<option value="basic">基础分析</option>
|
||||
<option value="standard" selected>标准分析</option>
|
||||
<option value="deep">深度分析</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="custom-control custom-switch mx-2">
|
||||
<input type="checkbox" class="custom-control-input" id="autoUpdate">
|
||||
<label class="custom-control-label" for="autoUpdate">自动更新</label>
|
||||
</div>
|
||||
<button class="btn btn-primary btn-sm" onclick="requestAIAnalysis()">
|
||||
开始分析
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- 进度显示 -->
|
||||
<div id="analysis-progress" class="mb-3" style="display: none;">
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div>
|
||||
</div>
|
||||
<small class="text-muted mt-1 d-block">正在分析: <span id="progress-text">0/0</span></small>
|
||||
</div>
|
||||
|
||||
<!-- 分析结果过滤器 -->
|
||||
<div class="mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="form-group mb-0 mr-2">
|
||||
<input type="text" class="form-control form-control-sm" id="keywordFilter" placeholder="按关键词过滤">
|
||||
</div>
|
||||
<div class="form-group mb-0 mr-2">
|
||||
<select class="form-control form-control-sm" id="sentimentFilter">
|
||||
<option value="">全部情感</option>
|
||||
<option value="积极">积极</option>
|
||||
<option value="消极">消极</option>
|
||||
<option value="中性">中性</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<select class="form-control form-control-sm" id="riskFilter">
|
||||
<option value="">全部风险等级</option>
|
||||
<option value="高">高风险</option>
|
||||
<option value="中">中风险</option>
|
||||
<option value="低">低风险</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ai-analysis-results" class="analysis-container">
|
||||
<!-- 分析结果将在这里动态显示 -->
|
||||
</div>
|
||||
@@ -467,7 +530,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加必要的CSS样式 -->
|
||||
<!-- 更新CSS样式 -->
|
||||
<style>
|
||||
.analysis-container {
|
||||
max-height: 600px;
|
||||
@@ -481,10 +544,12 @@
|
||||
margin-bottom: 15px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.analysis-card:hover {
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.risk-level {
|
||||
@@ -521,32 +586,167 @@
|
||||
padding: 4px 8px;
|
||||
border-radius: 16px;
|
||||
font-size: 0.9em;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.keyword-tag:hover {
|
||||
background-color: #1976d2;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sentiment-score {
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.sentiment-positive {
|
||||
background-color: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.sentiment-negative {
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.sentiment-neutral {
|
||||
background-color: #f5f5f5;
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 0.5rem;
|
||||
}
|
||||
|
||||
.analysis-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.analysis-controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 添加必要的JavaScript代码 -->
|
||||
<!-- 更新JavaScript代码 -->
|
||||
<script>
|
||||
let currentAnalysis = {
|
||||
inProgress: false,
|
||||
totalMessages: 0,
|
||||
processedMessages: 0,
|
||||
autoUpdateInterval: null
|
||||
};
|
||||
|
||||
async function requestAIAnalysis() {
|
||||
if (currentAnalysis.inProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
currentAnalysis.inProgress = true;
|
||||
showProgress();
|
||||
|
||||
const batchSize = parseInt(document.getElementById('batchSize').value);
|
||||
const modelType = document.getElementById('modelType').value;
|
||||
const analysisDepth = document.getElementById('analysisDepth').value;
|
||||
|
||||
const response = await fetch('/page/api/analyze_messages', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
body: JSON.stringify({
|
||||
batch_size: batchSize,
|
||||
model_type: modelType,
|
||||
analysis_depth: analysisDepth
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
displayAnalysisResults(result.data);
|
||||
setupAutoUpdate();
|
||||
} else {
|
||||
alert('分析失败: ' + result.error);
|
||||
showError(result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('AI分析请求失败:', error);
|
||||
alert('请求失败,请稍后重试');
|
||||
showError('请求失败,请稍后重试');
|
||||
} finally {
|
||||
currentAnalysis.inProgress = false;
|
||||
hideProgress();
|
||||
}
|
||||
}
|
||||
|
||||
function showProgress() {
|
||||
const progressDiv = document.getElementById('analysis-progress');
|
||||
progressDiv.style.display = 'block';
|
||||
updateProgress(0, 100);
|
||||
}
|
||||
|
||||
function hideProgress() {
|
||||
const progressDiv = document.getElementById('analysis-progress');
|
||||
progressDiv.style.display = 'none';
|
||||
}
|
||||
|
||||
function updateProgress(current, total) {
|
||||
const progressBar = document.querySelector('.progress-bar');
|
||||
const progressText = document.getElementById('progress-text');
|
||||
const percentage = (current / total) * 100;
|
||||
|
||||
progressBar.style.width = `${percentage}%`;
|
||||
progressText.textContent = `${current}/${total}`;
|
||||
}
|
||||
|
||||
function setupAutoUpdate() {
|
||||
const autoUpdate = document.getElementById('autoUpdate').checked;
|
||||
if (autoUpdate && !currentAnalysis.autoUpdateInterval) {
|
||||
currentAnalysis.autoUpdateInterval = setInterval(requestAIAnalysis, 300000); // 5分钟更新一次
|
||||
} else if (!autoUpdate && currentAnalysis.autoUpdateInterval) {
|
||||
clearInterval(currentAnalysis.autoUpdateInterval);
|
||||
currentAnalysis.autoUpdateInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function filterResults() {
|
||||
const keyword = document.getElementById('keywordFilter').value.toLowerCase();
|
||||
const sentiment = document.getElementById('sentimentFilter').value;
|
||||
const risk = document.getElementById('riskFilter').value;
|
||||
|
||||
const cards = document.querySelectorAll('.analysis-card');
|
||||
cards.forEach(card => {
|
||||
let show = true;
|
||||
|
||||
// 关键词过滤
|
||||
if (keyword) {
|
||||
const content = card.textContent.toLowerCase();
|
||||
show = show && content.includes(keyword);
|
||||
}
|
||||
|
||||
// 情感过滤
|
||||
if (sentiment) {
|
||||
const cardSentiment = card.querySelector('.sentiment-text').textContent;
|
||||
show = show && cardSentiment.includes(sentiment);
|
||||
}
|
||||
|
||||
// 风险等级过滤
|
||||
if (risk) {
|
||||
const cardRisk = card.querySelector('.risk-level').textContent;
|
||||
show = show && cardRisk.includes(risk);
|
||||
}
|
||||
|
||||
card.style.display = show ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function displayAnalysisResults(results) {
|
||||
const container = document.getElementById('ai-analysis-results');
|
||||
container.innerHTML = ''; // 清空现有结果
|
||||
@@ -558,6 +758,10 @@ function displayAnalysisResults(results) {
|
||||
const riskLevelClass =
|
||||
analysis.risk_level === '高' ? 'risk-high' :
|
||||
analysis.risk_level === '中' ? 'risk-medium' : 'risk-low';
|
||||
|
||||
const sentimentClass =
|
||||
analysis.sentiment === '积极' ? 'sentiment-positive' :
|
||||
analysis.sentiment === '消极' ? 'sentiment-negative' : 'sentiment-neutral';
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
@@ -567,8 +771,9 @@ function displayAnalysisResults(results) {
|
||||
</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<strong>情感倾向:</strong> ${analysis.sentiment}
|
||||
<span class="ml-2">(${analysis.sentiment_score})</span>
|
||||
<strong>情感倾向:</strong>
|
||||
<span class="sentiment-text">${analysis.sentiment}</span>
|
||||
<span class="sentiment-score ${sentimentClass}">${analysis.sentiment_score}</span>
|
||||
</div>
|
||||
<div class="keywords-container">
|
||||
${analysis.keywords.split(',').map(keyword =>
|
||||
@@ -590,10 +795,28 @@ function displayAnalysisResults(results) {
|
||||
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
// 应用当前的过滤器
|
||||
filterResults();
|
||||
}
|
||||
|
||||
// 页面加载完成后自动请求一次AI分析
|
||||
document.addEventListener('DOMContentLoaded', requestAIAnalysis);
|
||||
function showError(message) {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
// 设置事件监听器
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 自动更新切换
|
||||
document.getElementById('autoUpdate').addEventListener('change', setupAutoUpdate);
|
||||
|
||||
// 过滤器变化监听
|
||||
document.getElementById('keywordFilter').addEventListener('input', filterResults);
|
||||
document.getElementById('sentimentFilter').addEventListener('change', filterResults);
|
||||
document.getElementById('riskFilter').addEventListener('change', filterResults);
|
||||
|
||||
// 首次加载时请求分析
|
||||
requestAIAnalysis();
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user