Toggle navigation
Toggle navigation
This project
Loading...
Sign in
万朱浩
/
Venue-Ops
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
戒酒的李白
2025-02-11 22:30:52 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
a60a0f3296d797443a2ebfb6ff99d257b3d148cb
a60a0f32
1 parent
8ec05efe
A more comprehensive large model analysis setup, front-end interface optimization.
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
357 additions
and
51 deletions
utils/ai_analyzer.py
views/page/page.py
views/page/templates/yuqingpredict.html
utils/ai_analyzer.py
View file @
a60a0f3
...
...
@@ -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']}"
)
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
)
all_results
=
[]
# 解析返回结果
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
()
\ No newline at end of file
...
...
views/page/page.py
View file @
a60a0f3
...
...
@@ -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
:
...
...
views/page/templates/yuqingpredict.html
View file @
a60a0f3
...
...
@@ -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 %}
...
...
Please
register
or
login
to post a comment