Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
b5529599
提交
b5529599
authored
3月 24, 2026
作者:
朱政
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat:多智库报告流式显示思考过程功能以及图表ai解读流式显示思考过程功能及样式开发
上级
82ef459c
显示空白字符变更
内嵌
并排
正在显示
11 个修改的文件
包含
919 行增加
和
158 行删除
+919
-158
overview.js
src/api/thinkTank/overview.js
+82
-1
index.vue
src/views/thinkTank/MultiThinkTankViewAnalysis/index.vue
+652
-148
index.vue
src/views/thinkTank/ReportDetail/reportAnalysis/index.vue
+117
-3
index.vue
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
+34
-3
being-analysis-circle.png
src/views/thinkTank/assets/images/being-analysis-circle.png
+0
-0
being-analysis.png
src/views/thinkTank/assets/images/being-analysis.png
+0
-0
blue-down.png
src/views/thinkTank/assets/images/blue-down.png
+0
-0
blue-up.png
src/views/thinkTank/assets/images/blue-up.png
+0
-0
empty-analysis.png
src/views/thinkTank/assets/images/empty-analysis.png
+0
-0
empty-image.png
src/views/thinkTank/assets/images/empty-image.png
+0
-0
index.vue
src/views/thinkTank/index.vue
+34
-3
没有找到文件。
src/api/thinkTank/overview.js
浏览文件 @
b5529599
// 智库概览信息
import
request
from
"@/api/request.js"
;
import
request
,
{
getToken
}
from
"@/api/request.js"
;
// 智库列表
export
function
getThinkTankList
()
{
...
...
@@ -162,6 +162,7 @@ export function getThinkDynamicsReport(params) {
// 智库领域观点分析(流式)
// [POST] 8.140.26.4:10029/report-domain-view-analysis
// 每次请求体:{ domain, report_view_list }(一个 domain);多领域由前端按领域循环多次调用
export
function
postReportDomainViewAnalysis
(
data
)
{
return
request
({
method
:
'POST'
,
...
...
@@ -171,6 +172,86 @@ export function postReportDomainViewAnalysis(data) {
})
}
/**
* 智库领域观点分析(真正流式,逐 chunk 回调)
* @param {object} data
* @param {{ onReasoningChunk?: (chunk: string) => void, onMessage?: (msg: any) => void }} handlers
*/
export
async
function
postReportDomainViewAnalysisStream
(
data
,
handlers
=
{})
{
const
{
onReasoningChunk
,
onMessage
}
=
handlers
const
token
=
getToken
()
const
response
=
await
fetch
(
'/intelligent-api/report-domain-view-analysis'
,
{
method
:
'POST'
,
headers
:
{
'Content-Type'
:
'application/json'
,
...(
token
?
{
token
}
:
{})
},
body
:
JSON
.
stringify
(
data
)
})
if
(
!
response
.
ok
)
{
throw
new
Error
(
`流式分析请求失败:
${
response
.
status
}
`
)
}
// 兜底:非流式返回时仍可读取文本继续后续解析
if
(
!
response
.
body
)
{
return
await
response
.
text
()
}
const
reader
=
response
.
body
.
getReader
()
const
decoder
=
new
TextDecoder
(
'utf-8'
)
let
done
=
false
let
pending
=
''
let
fullText
=
''
while
(
!
done
)
{
const
result
=
await
reader
.
read
()
done
=
result
.
done
if
(
result
.
value
)
{
const
chunkText
=
decoder
.
decode
(
result
.
value
,
{
stream
:
!
done
})
fullText
+=
chunkText
pending
+=
chunkText
const
lines
=
pending
.
split
(
/
\r?\n
/
)
pending
=
lines
.
pop
()
??
''
for
(
const
rawLine
of
lines
)
{
const
line
=
String
(
rawLine
||
''
).
trim
()
if
(
!
line
||
!
line
.
startsWith
(
'data:'
))
continue
const
jsonText
=
line
.
slice
(
5
).
trim
()
if
(
!
jsonText
||
jsonText
===
'[DONE]'
)
continue
try
{
const
msg
=
JSON
.
parse
(
jsonText
)
if
(
typeof
onMessage
===
'function'
)
onMessage
(
msg
)
if
(
msg
?.
type
===
'reasoning'
&&
msg
?.
chunk
!=
null
&&
typeof
onReasoningChunk
===
'function'
)
{
const
c
=
String
(
msg
.
chunk
).
trim
()
if
(
c
)
onReasoningChunk
(
c
)
}
}
catch
(
e
)
{
// 忽略非 JSON 数据行
}
}
}
}
// 处理最后一行残留
const
last
=
String
(
pending
||
''
).
trim
()
if
(
last
.
startsWith
(
'data:'
))
{
const
jsonText
=
last
.
slice
(
5
).
trim
()
if
(
jsonText
&&
jsonText
!==
'[DONE]'
)
{
try
{
const
msg
=
JSON
.
parse
(
jsonText
)
if
(
typeof
onMessage
===
'function'
)
onMessage
(
msg
)
if
(
msg
?.
type
===
'reasoning'
&&
msg
?.
chunk
!=
null
&&
typeof
onReasoningChunk
===
'function'
)
{
const
c
=
String
(
msg
.
chunk
).
trim
()
if
(
c
)
onReasoningChunk
(
c
)
}
}
catch
(
e
)
{
// ignore
}
}
}
return
fullText
}
//提出建议领域分布
export
function
getThinkPolicyIndustry
(
params
)
{
...
...
src/views/thinkTank/MultiThinkTankViewAnalysis/index.vue
浏览文件 @
b5529599
...
...
@@ -160,17 +160,48 @@
</AnalysisBox>
</div>
<div
class=
"box2"
>
<AnalysisBox
title=
"共识观点列表"
:showAllBtn=
"true"
v-if=
"isBox2"
>
<AnalysisBox
title=
"共识观点列表"
:showAllBtn=
"true"
v-if=
"isBox2
|| isAnalysisLoading
"
>
<div
class=
"box2-main"
>
<div
class=
"empty-image"
>
<div
class=
"empty-image"
v-if=
"isBox2 && !isAnalysisLoading"
>
<img
src=
"../assets/images/empty-image.png"
alt=
""
/>
</div>
<div
v-if=
"isAnalysisLoading && !isBeingAnalysisExpanded"
class=
"being-analysis-box"
>
<div
class=
"being-analysis-box-image"
>
<img
src=
"../assets/images/being-analysis-circle.png"
alt=
""
/>
</div>
<div
class=
"being-analysis-box-text"
>
{{ "正在思考" }}
</div>
<div
class=
"being-analysis-box-change"
@
click=
"toggleBeingAnalysisBox"
>
<img
src=
"../assets/images/blue-down.png"
alt=
""
v-if=
"!isBeingAnalysisExpanded"
/>
<img
src=
"../assets/images/blue-up.png"
alt=
""
v-else
/>
</div>
</div>
<div
v-if=
"isAnalysisLoading && isBeingAnalysisExpanded"
class=
"being-analysis-detail-box"
>
<div
class=
"being-analysis-box-header"
>
<div
class=
"being-analysis-box-image"
>
<img
src=
"../assets/images/being-analysis-circle.png"
alt=
""
/>
</div>
<div
class=
"being-analysis-box-text"
>
{{ "正在思考" }}
</div>
<div
class=
"being-analysis-box-change"
@
click=
"toggleBeingAnalysisBox"
>
<img
src=
"../assets/images/blue-down.png"
alt=
""
v-if=
"!isBeingAnalysisExpanded"
/>
<img
src=
"../assets/images/blue-up.png"
alt=
""
v-else
/>
</div>
</div>
<div
class=
"being-analysis-box-content"
ref=
"beingAnalysisContentRef"
>
<div
class=
"being-analysis-content-line"
v-for=
"(line, idx) in beingAnalysisChunks"
:key=
"idx"
>
{{ line }}
</div>
</AnalysisBox>
<AnalysisResultBox
title=
"核心观点分析"
:showAllBtn=
"false"
v-if=
"!isBox2"
@
tab-change=
"handleOpinionTabChange"
>
<div
v-if=
"isAnalysisLoading"
class=
"analysis-loading"
>
正在加载
</div>
</div>
<div
v-if=
"isAnalysisLoading"
class=
"being-analysis-image"
>
<img
src=
"../assets/images/being-analysis.png"
alt=
""
/>
</div>
</div>
</AnalysisBox>
<AnalysisResultBox
title=
"核心观点分析"
:showAllBtn=
"false"
v-if=
"!isBox2 && !isAnalysisLoading"
@
tab-change=
"handleOpinionTabChange"
>
<div
class=
"box2-main-Consensus"
v-if=
"!isAnalysisLoading && activeOpinionTab === 'consensus'"
>
<!-- <div class="box2-main-Consensus-header">
<div class="tech-development-view">{{ `共识观点 ${consensusList.length}条` }}</div>
...
...
@@ -189,34 +220,33 @@
<div
class=
"Consensus-item-title"
>
{{
item
.
consensusContent
}}
</div>
<div
class=
"Consensus-item-right"
>
{{
`${item.reportCount
}
篇报告提及`
}}
<
/div
>
<
div
class
=
"Consensus-item-image"
@
click
=
"toggleConsensusItem(item.id)"
>
<
img
src
=
"../assets/images/down.png"
alt
=
""
/>
<
!--
<
img
src
=
"../assets/images/up.png"
alt
=
""
v
-
else
/> --
>
<
img
src
=
"../assets/images/down.png"
alt
=
""
v
-
if
=
"!openConsensusIds.has(item.id)"
/>
<
img
src
=
"../assets/images/up.png"
alt
=
""
v
-
else
/
>
<
/div
>
<
/div
>
<!--
<
div
class
=
"Consensus-expand"
v
-
if
=
"openConsensusIds.has(item.id)"
>
<
div
class
=
"Consensus-expand-row"
>
<
div
class
=
"Consensus-expand"
v
-
if
=
"openConsensusIds.has(item.id)"
>
<
div
class
=
"Consensus-expand-title"
>
{{
"来源观点列表"
}}
<
/div
>
<
div
class
=
"Consensus-expand-subtitle"
>
<
div
class
=
"Consensus-expand-image"
>
<
img
src
=
"../assets/images/rand-image.png"
alt
=
""
/>
<
div
v
-
for
=
"(sv, svIdx) in item.sourceViewDetails"
:
key
=
"`${sv.report_id
}
-${sv.view_id
}
-${svIdx
}
`"
class
=
"source-view-detail"
>
<
div
class
=
"source-view-detail-title"
>
<
span
class
=
"source-view-detail-title-text"
>
{{
getSourceViewDisplayTitle
(
sv
,
svIdx
)
}}
<
/span
>
<
span
class
=
"source-view-detail-org"
v
-
if
=
"sv.thinktankName || sv.thinktankLogoUrl"
>
<
img
v
-
if
=
"sv.thinktankLogoUrl"
:
src
=
"sv.thinktankLogoUrl"
alt
=
""
/>
<
span
class
=
"source-view-detail-org-text"
>
{{
sv
.
thinktankName
}}
<
/span
>
<
/span
>
<
/div
>
{{
`${item.reportCount
}
条来源观点`
}}
<
div
class
=
"source-view-detail-content"
>
{{
getSourceViewDisplayContent
(
sv
)
}}
<
/div
>
<
/div
>
<
div
class
=
"Consensus-expand-badge is-tech-development"
>
{{
"共识观点"
}}
<
/div
>
<
/div
>
<
div
class
=
"Consensus-expand-content
"
>
{{
item
.
sourceViewText
}}
<
/template
>
<
div
class
=
"empty-image"
v
-
if
=
"consensusList.length === 0
"
>
<
img
src
=
"../assets/images/empty-analysis.png"
alt
=
""
/>
<
/div
>
<
div
class
=
"Consensus-expand-badge is-tech-development
"
>
{{
"共识观点
"
}}
<
div
class
=
"empty-text"
v
-
if
=
"consensusList.length === 0
"
>
{{
"未识别到共识观点...
"
}}
<
/div
>
<
/div> --
>
<
/template
>
<
/div
>
<
/div
>
<
div
class
=
"box2-main-Differences"
v
-
else
-
if
=
"!isAnalysisLoading"
>
...
...
@@ -240,16 +270,22 @@
<
div
class
=
"Differences-expand-content"
>
<
div
class
=
"Differences-expand-top-content"
>
<
div
class
=
"Differences-expand-top-content-left"
>
<
div
class
=
"content-left-title"
>
{{
"来源观点列表"
}}
<
/div
>
<
div
class
=
"content-left-img-name"
>
<
div
class
=
"content-left-img"
>
<
img
src
=
"../assets/images/rand-image.png"
alt
=
""
/>
<
div
class
=
"differences-source-views"
>
<
div
v
-
for
=
"(sv, svIdx) in item.sourceViewDetails"
:
key
=
"`${sv.report_id
}
-${sv.view_id
}
-${svIdx
}
`"
class
=
"source-view-detail"
>
<
div
class
=
"source-view-detail-title"
>
<
span
class
=
"source-view-detail-title-text"
>
{{
getSourceViewDisplayTitle
(
sv
,
svIdx
)
}}
<
/span
>
<
span
class
=
"source-view-detail-org"
v
-
if
=
"sv.thinktankName || sv.thinktankLogoUrl"
>
<
img
v
-
if
=
"sv.thinktankLogoUrl"
:
src
=
"sv.thinktankLogoUrl"
alt
=
""
/>
<
span
class
=
"source-view-detail-org-text"
>
{{
sv
.
thinktankName
}}
<
/span
>
<
/span
>
<
/div
>
<
div
class
=
"content-left-name"
>
{{
`${item.reportCount
}
条来源观点`
}}
<
div
class
=
"source-view-detail-content"
>
{{
getSourceViewDisplayContent
(
sv
)
}}
<
/div
>
<
/div
>
<
/div
>
<
div
class
=
"content-left-text"
>
{{
item
.
sourceViewText
}}
<
/div
>
<
/div
>
<
/div
>
...
...
@@ -261,7 +297,12 @@
<
/div
>
<
/div
>
<
/template
>
<
div
class
=
"empty-image"
v
-
if
=
"differenceList.length === 0"
>
<
img
src
=
"../assets/images/empty-analysis.png"
alt
=
""
/>
<
/div
>
<
div
class
=
"empty-text"
v
-
if
=
"differenceList.length === 0"
>
{{
"未识别到分歧观点..."
}}
<
/div
>
<
/div
>
<
/div
>
<
/AnalysisResultBox
>
...
...
@@ -271,15 +312,40 @@
<
/template
>
<
script
setup
>
import
router
from
'@/router'
;
import
{
onMounted
,
ref
,
computed
,
reactive
,
nextTick
}
from
"vue"
;
import
{
onMounted
,
ref
,
computed
,
reactive
,
nextTick
,
watch
}
from
"vue"
;
import
AnalysisBox
from
"@/components/base/boxBackground/analysisBox.vue"
import
AnalysisResultBox
from
"./boxBackground/analysisBox.vue"
import
{
getThinkTankReport
,
getHylyList
,
getThinkTankReportViewpoint
,
postReportDomainViewAnalysis
}
from
"@/api/thinkTank/overview"
;
import
{
getThinkTankReport
,
getHylyList
,
getThinkTankReportViewpoint
,
postReportDomainViewAnalysisStream
}
from
"@/api/thinkTank/overview"
;
const
sort
=
ref
(
""
);
const
searchPolicy
=
ref
(
""
);
const
isBox2
=
ref
(
true
)
const
isAnalysisLoading
=
ref
(
false
)
const
isBeingAnalysisExpanded
=
ref
(
false
)
const
beingAnalysisChunks
=
ref
([])
const
beingAnalysisContentRef
=
ref
(
null
)
const
activeOpinionTab
=
ref
(
'consensus'
)
const
toggleBeingAnalysisBox
=
()
=>
{
isBeingAnalysisExpanded
.
value
=
!
isBeingAnalysisExpanded
.
value
}
const
scrollBeingAnalysisToBottom
=
async
()
=>
{
await
nextTick
()
const
el
=
beingAnalysisContentRef
.
value
if
(
!
el
)
return
el
.
scrollTop
=
el
.
scrollHeight
}
watch
(()
=>
beingAnalysisChunks
.
value
.
length
,
async
()
=>
{
if
(
!
isBeingAnalysisExpanded
.
value
)
return
await
scrollBeingAnalysisToBottom
()
}
)
watch
(
isBeingAnalysisExpanded
,
async
(
expanded
)
=>
{
if
(
!
expanded
)
return
await
scrollBeingAnalysisToBottom
()
}
)
const
handleOpinionTabChange
=
type
=>
{
activeOpinionTab
.
value
=
type
}
...
...
@@ -358,6 +424,33 @@ const domainName = computed(() => {
return
hit
?.
name
||
'全部领域'
}
)
const
domainViewAnalysisRes
=
ref
(
null
)
/** 观点详情:key 为 `${reportId
}
::${viewId
}
`,与流式结果里 source_views 的 view_id 对应观点接口返回的 id */
const
viewpointDetailByKey
=
ref
({
}
)
const
getViewpointDetailForSource
=
(
reportId
,
viewId
)
=>
{
const
key
=
`${String(reportId)
}
::${String(viewId)
}
`
return
viewpointDetailByKey
.
value
[
key
]
||
{
titleZh
:
""
,
contentZh
:
""
,
title
:
""
,
content
:
""
,
thinktankName
:
""
,
thinktankLogoUrl
:
""
}
}
/** 展开区标题:优先中文标题,否则英文;无标题则返回空串(由上游过滤) */
const
getSourceViewDisplayTitle
=
(
sv
,
idx
)
=>
{
const
zh
=
String
(
sv
.
titleZh
??
""
).
trim
()
if
(
zh
)
return
zh
const
en
=
String
(
sv
.
title
??
""
).
trim
()
if
(
en
)
return
en
return
""
}
/** 展开区正文:优先中文,否则英文 */
const
getSourceViewDisplayContent
=
(
sv
)
=>
{
const
zh
=
String
(
sv
.
contentZh
??
""
).
trim
()
if
(
zh
)
return
zh
return
String
(
sv
.
content
??
""
).
trim
()
}
const
tryParseAnswerFromStreamText
=
(
text
)
=>
{
const
lines
=
String
(
text
||
""
)
.
split
(
/
\r?\n
/
)
...
...
@@ -398,12 +491,31 @@ const consensusList = computed(() => {
const
list
=
Array
.
isArray
(
answer
?.
consensus
)
?
answer
.
consensus
:
[]
return
list
.
map
((
item
,
index
)
=>
{
const
sourceViews
=
Array
.
isArray
(
item
?.
source_views
)
?
item
.
source_views
:
[]
const
sourceViewDetails
=
sourceViews
.
map
((
v
)
=>
{
const
detail
=
getViewpointDetailForSource
(
v
.
report_id
,
v
.
view_id
)
return
{
report_id
:
v
.
report_id
,
view_id
:
v
.
view_id
,
titleZh
:
detail
.
titleZh
,
contentZh
:
detail
.
contentZh
,
title
:
detail
.
title
,
content
:
detail
.
content
,
thinktankName
:
detail
.
thinktankName
,
thinktankLogoUrl
:
detail
.
thinktankLogoUrl
}
}
)
.
filter
((
sv
)
=>
{
const
title
=
getSourceViewDisplayTitle
(
sv
,
0
)
return
Boolean
(
title
)
}
)
const
sourceViewText
=
sourceViews
.
map
((
v
)
=>
`report_id: ${v.report_id
}
, view_id: ${v.view_id
}
`
).
join
(
";"
)
return
{
id
:
`consensus-${index + 1
}
`
,
consensusContent
:
item
?.
consensus_content
||
""
,
reportCount
:
sourceViews
.
length
,
sourceViewText
reportCount
:
sourceViewDetails
.
length
,
sourceViewText
,
sourceViewDetails
}
}
)
}
)
...
...
@@ -412,12 +524,31 @@ const differenceList = computed(() => {
const
list
=
Array
.
isArray
(
answer
?.
differences
)
?
answer
.
differences
:
[]
return
list
.
map
((
item
,
index
)
=>
{
const
sourceViews
=
Array
.
isArray
(
item
?.
source_views
)
?
item
.
source_views
:
[]
const
sourceViewDetails
=
sourceViews
.
map
((
v
)
=>
{
const
detail
=
getViewpointDetailForSource
(
v
.
report_id
,
v
.
view_id
)
return
{
report_id
:
v
.
report_id
,
view_id
:
v
.
view_id
,
titleZh
:
detail
.
titleZh
,
contentZh
:
detail
.
contentZh
,
title
:
detail
.
title
,
content
:
detail
.
content
,
thinktankName
:
detail
.
thinktankName
,
thinktankLogoUrl
:
detail
.
thinktankLogoUrl
}
}
)
.
filter
((
sv
)
=>
{
const
title
=
getSourceViewDisplayTitle
(
sv
,
0
)
return
Boolean
(
title
)
}
)
const
sourceViewText
=
sourceViews
.
map
((
v
)
=>
`report_id: ${v.report_id
}
, view_id: ${v.view_id
}
`
).
join
(
";"
)
return
{
id
:
`difference-${index + 1
}
`
,
disagreementContent
:
item
?.
disagreement_content
||
""
,
reportCount
:
sourceViews
.
length
,
sourceViewText
reportCount
:
sourceViewDetails
.
length
,
sourceViewText
,
sourceViewDetails
}
}
)
}
)
...
...
@@ -456,6 +587,8 @@ const handleAnalysis = async () => {
if
(
!
canProceed
.
value
)
return
isBox2
.
value
=
false
isAnalysisLoading
.
value
=
true
isBeingAnalysisExpanded
.
value
=
false
beingAnalysisChunks
.
value
=
[]
domainViewAnalysisRes
.
value
=
null
// 每次进入“开始分析”默认回到共识观点,避免上次状态残留导致内容错位
activeOpinionTab
.
value
=
'consensus'
...
...
@@ -464,6 +597,10 @@ const handleAnalysis = async () => {
const
handleBack
=
()
=>
{
isChoseItem
.
value
=
true
isBox2
.
value
=
true
// 返回选择时终止“分析中”展示,恢复空态图
isAnalysisLoading
.
value
=
false
isBeingAnalysisExpanded
.
value
=
false
beingAnalysisChunks
.
value
=
[]
// 返回选择时也重置,确保下次进入分析展示一致
activeOpinionTab
.
value
=
'consensus'
}
...
...
@@ -524,10 +661,41 @@ const normalizeViewList = raw => {
.
filter
(
v
=>
v
.
view_id
!=
null
&&
v
.
view_text
!==
""
)
}
/** 从列表接口返回的报告项上取科技领域名称;没有再回退为左侧已选领域名,仍没有则用空字符串(不用「全部领域」) */
const
getDomainsForReport
=
(
report
)
=>
{
const
raw
=
report
?.
domains
if
(
Array
.
isArray
(
raw
)
&&
raw
.
length
)
{
const
names
=
raw
.
map
((
x
)
=>
String
(
x
??
""
).
trim
()).
filter
(
Boolean
)
if
(
names
.
length
)
return
[...
new
Set
(
names
)]
}
if
(
selectedAreaList
.
value
)
{
const
hit
=
Array
.
isArray
(
areaList
.
value
)
?
areaList
.
value
.
find
((
i
)
=>
String
(
i
.
id
)
===
String
(
selectedAreaList
.
value
))
:
null
const
name
=
String
(
hit
?.
name
??
""
).
trim
()
if
(
name
)
return
[
name
]
}
return
[
""
]
}
const
mergeAnalysisAnswerParts
=
(
parts
)
=>
{
const
consensus
=
[]
const
differences
=
[]
for
(
const
p
of
parts
)
{
const
data
=
getAnalysisAnswerData
(
p
)
if
(
!
data
)
continue
if
(
Array
.
isArray
(
data
.
consensus
))
consensus
.
push
(...
data
.
consensus
)
if
(
Array
.
isArray
(
data
.
differences
))
differences
.
push
(...
data
.
differences
)
}
return
{
consensus
,
differences
}
}
const
handlePostReportDomainViewAnalysis
=
async
()
=>
{
try
{
viewpointDetailByKey
.
value
=
{
}
beingAnalysisChunks
.
value
=
[]
const
selectedReports
=
selectedReportList
.
value
||
[]
const
report
ViewList
Raw
=
await
Promise
.
all
(
const
report
Entries
Raw
=
await
Promise
.
all
(
selectedReports
.
map
(
async
(
report
)
=>
{
const
reportId
=
report
?.
id
if
(
!
reportId
)
return
null
...
...
@@ -535,16 +703,49 @@ const handlePostReportDomainViewAnalysis = async () => {
const
viewpointRes
=
await
getThinkTankReportViewpoint
({
reportId
,
currentPage
:
0
,
pageSize
:
100
,
pageSize
:
100
00
,
keyword
:
""
,
orgIds
:
""
}
)
console
.
log
(
"分析时用到的观点"
,
viewpointRes
)
if
(
viewpointRes
?.
code
!==
200
||
!
viewpointRes
?.
data
)
return
null
const
views
=
normalizeViewList
(
viewpointRes
.
data
.
content
??
viewpointRes
.
data
)
const
items
=
Array
.
isArray
(
viewpointRes
.
data
.
content
)
?
viewpointRes
.
data
.
content
:
Array
.
isArray
(
viewpointRes
.
data
)
?
viewpointRes
.
data
:
[]
const
detailMap
=
{
}
for
(
const
row
of
items
)
{
const
vid
=
row
?.
id
??
row
?.
view_id
??
row
?.
viewId
if
(
vid
==
null
||
vid
===
""
)
continue
detailMap
[
`${reportId
}
::${String(vid)
}
`
]
=
{
titleZh
:
String
(
row
.
titleZh
??
row
.
title_zh
??
""
).
trim
(),
contentZh
:
String
(
row
.
contentZh
??
row
.
content_zh
??
""
).
trim
(),
title
:
String
(
row
.
title
??
""
).
trim
(),
content
:
String
(
row
.
content
??
""
).
trim
(),
thinktankName
:
String
(
row
.
thinktankName
??
row
.
thinktank_name
??
row
.
thinkTankName
??
row
.
orgName
??
""
).
trim
(),
thinktankLogoUrl
:
String
(
row
.
thinktankLogoUrl
??
row
.
thinktank_logo_url
??
row
.
thinkTankLogoUrl
??
row
.
orgLogoUrl
??
""
).
trim
()
}
}
const
views
=
normalizeViewList
(
items
)
if
(
!
views
.
length
)
return
null
return
{
report
,
report_id
:
reportId
,
view_list
:
views
view_list
:
views
,
detailMap
}
}
catch
(
error
)
{
console
.
error
(
`获取报告核心论点失败: ${reportId
}
`
,
error
)
...
...
@@ -553,20 +754,62 @@ const handlePostReportDomainViewAnalysis = async () => {
}
)
)
const
reportViewList
=
reportViewListRaw
.
filter
(
Boolean
)
if
(
!
reportViewList
.
length
)
{
const
mergedDetails
=
{
}
for
(
const
e
of
reportEntriesRaw
)
{
if
(
e
?.
detailMap
)
Object
.
assign
(
mergedDetails
,
e
.
detailMap
)
}
viewpointDetailByKey
.
value
=
mergedDetails
const
reportEntries
=
reportEntriesRaw
.
filter
(
Boolean
)
if
(
!
reportEntries
.
length
)
{
domainViewAnalysisRes
.
value
=
null
return
}
const
payload
=
{
domain
:
domainName
.
value
||
"全部领域"
,
report_view_list
:
reportViewList
// 按报告 domains 拆成多组
{
domain
,
report_view_list
};每个领域单独调一次接口(无最外层
domain_report_view_list
)
const
domainToReports
=
new
Map
()
for
(
const
entry
of
reportEntries
)
{
const
domainLabels
=
getDomainsForReport
(
entry
.
report
)
for
(
const
domainLabel
of
domainLabels
)
{
if
(
!
domainToReports
.
has
(
domainLabel
))
{
domainToReports
.
set
(
domainLabel
,
[])
}
const
list
=
domainToReports
.
get
(
domainLabel
)
if
(
!
list
.
some
((
x
)
=>
x
.
report_id
===
entry
.
report_id
))
{
list
.
push
({
report_id
:
entry
.
report_id
,
view_list
:
entry
.
view_list
}
)
}
}
}
const
res
=
await
postReportDomainViewAnalysis
(
payload
)
console
.
log
(
'智库领域观点分析接口返回'
,
res
)
domainViewAnalysisRes
.
value
=
res
const
payloads
=
[...
domainToReports
.
entries
()]
.
filter
(([,
report_view_list
])
=>
report_view_list
.
length
>
0
)
.
map
(([
domain
,
report_view_list
])
=>
({
domain
,
report_view_list
}
))
if
(
!
payloads
.
length
)
{
domainViewAnalysisRes
.
value
=
null
return
}
const
responses
=
[]
for
(
const
payload
of
payloads
)
{
console
.
log
(
"智库领域观点分析入参"
,
payload
)
const
res
=
await
postReportDomainViewAnalysisStream
(
payload
,
{
onReasoningChunk
:
async
(
chunk
)
=>
{
beingAnalysisChunks
.
value
=
[...
beingAnalysisChunks
.
value
,
chunk
]
await
scrollBeingAnalysisToBottom
()
}
}
)
responses
.
push
(
res
)
}
const
merged
=
mergeAnalysisAnswerParts
(
responses
)
domainViewAnalysisRes
.
value
=
{
type
:
"answer"
,
data
:
merged
}
console
.
log
(
"智库领域观点分析合并结果"
,
merged
)
}
catch
(
e
)
{
console
.
error
(
'智库领域观点分析接口调用失败'
,
e
)
domainViewAnalysisRes
.
value
=
null
...
...
@@ -736,16 +979,150 @@ onMounted(async () => {
cursor
:
pointer
;
}
.
analysis
-
loading
{
.
being
-
analysis
-
box
{
width
:
1063
px
;
height
:
40
px
;
background
-
color
:
rgb
(
246
,
250
,
255
);
border
-
radius
:
50
px
;
border
:
1
px
solid
rgb
(
231
,
243
,
255
);
display
:
flex
;
.
being
-
analysis
-
box
-
image
{
width
:
16
px
;
height
:
16
px
;
margin
-
top
:
12
px
;
margin
-
left
:
12
px
;
img
{
width
:
100
%
;
height
:
100
%
;
display
:
block
;
}
}
.
being
-
analysis
-
box
-
text
{
font
-
family
:
"Source Han Sans CN"
;
font
-
weight
:
700
;
font
-
size
:
14
px
;
line
-
height
:
22
px
;
letter
-
spacing
:
0
;
text
-
align
:
left
;
color
:
rgb
(
5
,
95
,
194
);
height
:
22
px
;
margin
-
left
:
12
px
;
margin
-
top
:
9
px
;
}
.
being
-
analysis
-
box
-
change
{
width
:
14
px
;
height
:
24
px
;
margin
-
top
:
8
px
;
margin
-
left
:
auto
;
margin
-
right
:
12
px
;
cursor
:
pointer
;
img
{
width
:
100
%
;
height
:
100
%
;
min
-
height
:
320
px
;
display
:
block
;
}
}
}
.
being
-
analysis
-
detail
-
box
{
width
:
1063
px
;
height
:
160
px
;
background
-
color
:
rgb
(
246
,
250
,
255
);
border
-
radius
:
10
px
;
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font
-
size
:
16
px
;
flex
-
direction
:
column
;
.
being
-
analysis
-
box
-
header
{
width
:
1063
px
;
height
:
40
px
;
background
-
color
:
rgb
(
246
,
250
,
255
);
display
:
flex
;
.
being
-
analysis
-
box
-
image
{
width
:
16
px
;
height
:
16
px
;
margin
-
top
:
12
px
;
margin
-
left
:
12
px
;
img
{
width
:
100
%
;
height
:
100
%
;
display
:
block
;
}
}
.
being
-
analysis
-
box
-
text
{
font
-
family
:
"Source Han Sans CN"
;
font
-
weight
:
700
;
font
-
size
:
14
px
;
line
-
height
:
22
px
;
letter
-
spacing
:
0
;
text
-
align
:
left
;
color
:
rgb
(
5
,
95
,
194
);
height
:
22
px
;
margin
-
left
:
12
px
;
margin
-
top
:
9
px
;
}
.
being
-
analysis
-
box
-
change
{
width
:
14
px
;
height
:
24
px
;
margin
-
top
:
8
px
;
margin
-
left
:
auto
;
margin
-
right
:
12
px
;
cursor
:
pointer
;
img
{
width
:
100
%
;
height
:
100
%
;
display
:
block
;
}
}
}
.
being
-
analysis
-
box
-
content
{
width
:
983
px
;
height
:
104
px
;
margin
-
left
:
40
px
;
overflow
-
y
:
auto
;
overflow
-
x
:
hidden
;
font
-
family
:
"Source Han Sans CN"
,
sans
-
serif
;
font
-
weight
:
400
;
font
-
size
:
14
px
;
line
-
height
:
22
px
;
letter
-
spacing
:
0
;
text
-
align
:
left
;
color
:
rgb
(
59
,
65
,
75
);
}
}
.
being
-
analysis
-
content
-
line
{
line
-
height
:
22
px
;
white
-
space
:
nowrap
;
}
.
being
-
analysis
-
image
{
width
:
100
%
;
height
:
100
%
;
min
-
height
:
320
px
;
display
:
flex
;
top
:
276
px
;
left
:
408
px
;
position
:
absolute
;
img
{
width
:
289
px
;
height
:
242
px
;
display
:
block
;
}
}
.
box
{
...
...
@@ -1320,12 +1697,14 @@ onMounted(async () => {
height
:
866
px
;
width
:
100
%
;
display
:
flex
;
justify
-
content
:
center
;
position
:
relative
;
.
empty
-
image
{
width
:
289
px
;
height
:
2
15
px
;
height
:
2
42
px
;
margin
-
top
:
276
px
;
justify
-
content
:
center
;
display
:
flex
;
...
...
@@ -1343,7 +1722,7 @@ onMounted(async () => {
width
:
100
%
;
display
:
flex
;
padding
-
top
:
4
px
;
padding
-
top
:
13
px
;
padding
-
left
:
24
px
;
padding
-
right
:
24
px
;
padding
-
bottom
:
18
px
;
...
...
@@ -1431,15 +1810,53 @@ onMounted(async () => {
.
box2
-
main
-
Consensus
-
content
{
width
:
1056
px
;
overflow
-
y
:
auto
;
margin
-
top
:
16
px
;
height
:
794
px
;
overflow
-
x
:
hidden
;
height
:
100
%
;
min
-
height
:
0
;
display
:
flex
;
flex
-
direction
:
column
;
overflow
-
x
:
hidden
;
align
-
items
:
stretch
;
position
:
relative
;
.
empty
-
text
{
font
-
family
:
"Source Han Sans CN"
;
font
-
weight
:
400
;
font
-
size
:
16
px
;
line
-
height
:
30
px
;
letter
-
spacing
:
0
px
;
text
-
align
:
center
;
display
:
flex
;
width
:
289
px
;
height
:
30
px
;
position
:
absolute
;
justify
-
content
:
center
;
left
:
384
px
;
bottom
:
330
px
;
color
:
rgb
(
132
,
136
,
142
);
}
/* 展开区向下撑开,子项不被纵向压缩;超出高度由本容器滚动 */
.
empty
-
image
{
height
:
200
px
;
width
:
200
px
;
position
:
absolute
;
left
:
428
px
;
bottom
:
372
px
;
img
{
width
:
100
%
;
height
:
100
%
;
display
:
block
;
object
-
fit
:
contain
;
/* 防止图片变形 */
}
}
.
Consensus
-
item
{
width
:
1056
px
;
min
-
height
:
62
px
;
flex
-
shrink
:
0
;
box
-
sizing
:
border
-
box
;
display
:
flex
;
flex
-
direction
:
row
;
...
...
@@ -1525,12 +1942,13 @@ onMounted(async () => {
.
Consensus
-
expand
{
width
:
1056
px
;
flex
-
shrink
:
0
;
box
-
sizing
:
border
-
box
;
border
-
top
:
none
;
padding
:
16
px
50
px
24
px
58
px
;
padding
:
0
px
50
px
24
px
58
px
;
}
.
Consensus
-
expand
:
last
-
child
{
...
...
@@ -1620,6 +2038,82 @@ onMounted(async () => {
border
-
bottom
:
1
px
solid
rgb
(
234
,
236
,
238
);
}
.
source
-
view
-
detail
{
margin
-
top
:
16
px
;
padding
-
bottom
:
16
px
;
border
-
bottom
:
1
px
solid
rgb
(
234
,
236
,
238
);
&
:
last
-
child
{
border
-
bottom
:
none
;
}
}
.
source
-
view
-
detail
-
title
{
display
:
flex
;
align
-
items
:
flex
-
start
;
justify
-
content
:
space
-
between
;
gap
:
16
px
;
font
-
family
:
"Source Han Sans CN"
;
font
-
weight
:
500
;
font
-
size
:
15
px
;
line
-
height
:
24
px
;
color
:
rgb
(
59
,
65
,
75
);
margin
-
bottom
:
6
px
;
}
.
source
-
view
-
detail
-
title
-
text
{
justify
-
content
:
space
-
between
;
gap
:
16
px
;
font
-
family
:
"Source Han Sans CN"
;
font
-
weight
:
700
;
font
-
size
:
16
px
;
line
-
height
:
30
px
;
letter
-
spacing
:
0
;
text
-
align
:
justify
;
}
.
source
-
view
-
detail
-
org
{
flex
:
0
0
auto
;
display
:
inline
-
flex
;
align
-
items
:
center
;
gap
:
4
px
;
color
:
rgb
(
95
,
101
,
108
);
font
-
size
:
14
px
;
line
-
height
:
22
px
;
margin
-
top
:
4
px
;
img
{
width
:
16
px
;
height
:
16
px
;
display
:
block
;
object
-
fit
:
cover
;
margin
-
top
:
3
px
;
}
.
source
-
view
-
detail
-
org
-
text
{
font
-
family
:
"Source Han Sans CN"
;
font
-
weight
:
400
;
/* Regular */
font
-
size
:
14
px
;
line
-
height
:
22
px
;
letter
-
spacing
:
0
px
;
text
-
align
:
left
;
/* 左对齐 */
color
:
rgb
(
95
,
101
,
108
);
}
}
.
source
-
view
-
detail
-
content
{
font
-
family
:
"Source Han Sans CN"
;
font
-
weight
:
400
;
font
-
size
:
16
px
;
line
-
height
:
30
px
;
letter
-
spacing
:
0
;
text
-
align
:
justify
;
color
:
rgb
(
95
,
101
,
108
);
}
}
...
...
@@ -1631,7 +2125,7 @@ onMounted(async () => {
width
:
100
%
;
display
:
flex
;
padding
-
top
:
4
px
;
padding
-
top
:
13
px
;
padding
-
left
:
24
px
;
padding
-
right
:
24
px
;
padding
-
bottom
:
18
px
;
...
...
@@ -1641,14 +2135,17 @@ onMounted(async () => {
.
box2
-
main
-
Differences
-
content
{
width
:
1056
px
;
overflow
-
y
:
auto
;
height
:
848
px
;
display
:
flex
;
flex
-
direction
:
column
;
overflow
-
x
:
hidden
;
position
:
relative
;
.
Differences
-
item
{
width
:
1056
px
;
min
-
height
:
62
px
;
flex
-
shrink
:
0
;
box
-
sizing
:
border
-
box
;
display
:
flex
;
flex
-
direction
:
row
;
...
...
@@ -1661,6 +2158,39 @@ onMounted(async () => {
}
.
empty
-
image
{
height
:
200
px
;
width
:
200
px
;
position
:
absolute
;
left
:
428
px
;
bottom
:
372
px
;
img
{
width
:
100
%
;
height
:
100
%
;
display
:
block
;
object
-
fit
:
contain
;
/* 防止图片变形 */
}
}
.
empty
-
text
{
font
-
family
:
"Source Han Sans CN"
;
font
-
weight
:
400
;
font
-
size
:
16
px
;
line
-
height
:
30
px
;
letter
-
spacing
:
0
px
;
text
-
align
:
center
;
display
:
flex
;
width
:
289
px
;
height
:
30
px
;
position
:
absolute
;
justify
-
content
:
center
;
left
:
384
px
;
bottom
:
330
px
;
color
:
rgb
(
132
,
136
,
142
);
}
.
Differences
-
item
:
last
-
child
{
border
-
bottom
:
1
px
solid
rgb
(
234
,
236
,
238
);
}
...
...
@@ -1690,8 +2220,8 @@ onMounted(async () => {
}
.
Differences
-
item
-
title
{
width
:
958
px
;
flex
:
1
;
min
-
width
:
0
;
min
-
height
:
30
px
;
margin
-
left
:
18
px
;
...
...
@@ -1703,36 +2233,25 @@ onMounted(async () => {
text
-
align
:
justify
;
color
:
rgb
(
59
,
65
,
75
);
display
:
flex
;
gap
:
36
px
}
.
Differences
-
item
:
has
(
+
.
Differences
-
expand
)
.
Differences
-
item
-
title
{
.
Differences
-
item
:
has
(
+
.
Differences
-
expand
)
.
Differences
-
item
-
title
-
left
{
font
-
weight
:
700
;
}
.
Differences
-
item
-
title
-
right
{
min
-
height
:
30
px
;
width
:
461
px
;
margin
-
left
:
18
px
;
width
:
89
px
;
}
.
Differences
-
item
-
title
-
left
{
min
-
height
:
30
px
;
width
:
461
px
;
width
:
845
px
;
}
.
Differences
-
item
-
right
{
flex
:
0
0
auto
;
width
:
89
px
;
height
:
30
px
;
margin
-
left
:
18
px
;
color
:
rgb
(
95
,
101
,
108
);
font
-
family
:
"Source Han Sans CN"
;
font
-
weight
:
400
;
font
-
size
:
16
px
;
line
-
height
:
30
px
;
letter
-
spacing
:
0
;
text
-
align
:
justify
;
}
.
Differences
-
item
-
image
{
flex
:
0
0
auto
;
...
...
@@ -1749,12 +2268,13 @@ onMounted(async () => {
.
Differences
-
expand
{
width
:
1056
px
;
flex
-
shrink
:
0
;
box
-
sizing
:
border
-
box
;
border
-
top
:
none
;
padding
:
22
px
50
px
24
px
58
px
;
padding
:
0
px
50
px
24
px
58
px
;
display
:
flex
;
}
...
...
@@ -1775,14 +2295,14 @@ onMounted(async () => {
}
.
Differences
-
expand
-
top
-
content
-
left
{
width
:
452
px
;
width
:
948
px
;
display
:
flex
;
flex
-
direction
:
column
;
}
.
content
-
left
-
title
{
width
:
452
px
;
width
:
948
px
;
height
:
24
px
;
display
:
flex
;
overflow
:
hidden
;
...
...
@@ -1798,7 +2318,7 @@ onMounted(async () => {
}
.
content
-
left
-
img
-
name
{
width
:
452
px
;
width
:
948
px
;
height
:
22
px
;
display
:
flex
;
margin
-
top
:
8
px
;
...
...
@@ -1844,76 +2364,82 @@ onMounted(async () => {
}
.
differences
-
source
-
views
{
.
content
-
right
-
title
{
width
:
452
px
;
height
:
24
px
;
display
:
flex
;
.
source
-
view
-
detail
{
margin
-
top
:
16
px
;
padding
-
bottom
:
16
px
;
border
-
bottom
:
1
px
solid
rgb
(
234
,
236
,
238
);
}
/* 关键:超出宽度显示 ... */
overflow
:
hidden
;
white
-
space
:
nowrap
;
text
-
overflow
:
ellipsis
;
color
:
rgb
(
59
,
65
,
75
)
;
.
source
-
view
-
detail
-
title
{
display
:
flex
;
align
-
items
:
flex
-
start
;
justify
-
content
:
space
-
between
;
gap
:
16
px
;
font
-
family
:
"Source Han Sans CN"
;
font
-
weight
:
400
!
important
;
font
-
size
:
16
px
;
font
-
weight
:
500
;
font
-
size
:
15
px
;
line
-
height
:
24
px
;
letter
-
spacing
:
0
px
;
text
-
align
:
justify
;
color
:
rgb
(
59
,
65
,
75
)
;
margin
-
bottom
:
6
px
;
}
.
content
-
right
-
img
-
name
{
width
:
452
px
;
height
:
22
px
;
display
:
flex
;
margin
-
top
:
8
px
;
.
source
-
view
-
detail
-
title
-
text
{
justify
-
content
:
space
-
between
;
font
-
family
:
"Source Han Sans CN"
,
sans
-
serif
;
font
-
weight
:
700
;
font
-
size
:
16
px
;
line
-
height
:
30
px
;
letter
-
spacing
:
0
;
text
-
align
:
justify
;
}
.
content
-
right
-
img
{
width
:
16
px
;
height
:
16
px
;
margin
-
top
:
3
px
;
margin
-
right
:
4
px
;
.
source
-
view
-
detail
-
org
{
flex
:
0
0
auto
;
display
:
inline
-
flex
;
align
-
items
:
center
;
gap
:
4
px
;
color
:
rgb
(
95
,
101
,
108
);
font
-
size
:
14
px
;
line
-
height
:
22
px
;
margin
-
top
:
4
px
;
img
{
width
:
100
%
;
height
:
100
%
;
width
:
16
px
;
height
:
16
px
;
display
:
block
;
}
}
.
content
-
right
-
name
{
object
-
fit
:
cover
;
margin
-
top
:
3
px
;
}
height
:
22
px
;
.
source
-
view
-
detail
-
org
-
text
{
font
-
family
:
"Source Han Sans CN"
;
font
-
weight
:
400
;
/* Regular */
font
-
size
:
14
px
;
line
-
height
:
22
px
;
letter
-
spacing
:
0
;
letter
-
spacing
:
0
px
;
text
-
align
:
left
;
/* 左对齐 */
color
:
rgb
(
95
,
101
,
108
);
}
.
Differences
-
expand
-
top
-
content
-
right
{
width
:
452
px
;
display
:
flex
;
flex
-
direction
:
column
;
}
.
content
-
right
-
tex
t
{
.
source
-
view
-
detail
-
conten
t
{
font
-
family
:
"Source Han Sans CN"
;
font
-
weight
:
400
;
font
-
size
:
16
px
;
line
-
height
:
30
px
;
letter
-
spacing
:
0
px
;
letter
-
spacing
:
0
;
text
-
align
:
justify
;
color
:
rgb
(
95
,
101
,
108
);
margin
-
top
:
8
px
;
}
}
.
Differences
-
expand
-
view
{
width
:
948
px
;
height
:
36
px
;
...
...
@@ -1945,28 +2471,6 @@ onMounted(async () => {
justify
-
content
:
center
;
}
.
Differences
-
expand
-
view
-
right
{
width
:
452
px
;
height
:
36
px
;
}
.
right
-
tag
{
width
:
112
px
;
height
:
28
px
;
color
:
rgb
(
5
,
95
,
194
);
background
-
color
:
rgb
(
231
,
243
,
255
);
border
-
radius
:
4
px
;
margin
-
top
:
8
px
;
font
-
family
:
"Source Han Sans CN"
;
font
-
weight
:
400
;
font
-
size
:
16
px
;
line
-
height
:
24
px
;
letter
-
spacing
:
0
px
;
text
-
align
:
justify
;
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
}
}
...
...
src/views/thinkTank/ReportDetail/reportAnalysis/index.vue
浏览文件 @
b5529599
...
...
@@ -67,12 +67,21 @@
<div
class=
"box5"
>
<AnalysisBox
title=
"报告关键词云"
:showAllBtn=
"true"
>
<div
class=
"box5-main"
>
<div
class=
"box5Chart"
>
<!-- 有数据后再挂载子组件:子组件仅在 onMounted 初始化,异步数据到达后需 v-if + key 强制重新挂载 -->
<WordCloudChart
v-if=
"box5Data.length"
:key=
"box5WordCloudKey"
:data=
"box5Data"
width=
"100%"
height=
"100%"
/>
</div>
<div
class=
"box5-footer"
>
<TipTab
:text=
"REPORT_ANALYSIS_TIP_BOX5"
/>
<div
class=
"ai-wrap"
@
mouseenter=
"handleSwitchAiContentShowBox5(true)"
>
<AiButton
/>
</div>
</div>
<div
class=
"ai-content"
v-if=
"isShowAiContentBox5"
@
mouseleave=
"handleSwitchAiContentShowBox5(false)"
>
<AiPane
:aiContent=
"aiContentBox5"
/>
</div>
</div>
</AnalysisBox>
</div>
...
...
@@ -167,8 +176,8 @@
</div>
</div>
</div>
<div
v-if=
"isOpinionExpanded(item, index)"
class=
"desc"
v-html=
"highlightOpinionText(item.contentZh)"
>
</div>
<div
v-if=
"isOpinionExpanded(item, index)"
class=
"desc"
v-html=
"highlightOpinionText(item.contentZh)"
>
</div>
<!-- <div class="right"> -->
<!-- <div class="tag" v-for="(val, idx) in item.hylyList" :key="idx">
{{ val }}
...
...
@@ -217,6 +226,9 @@ import { useRouter } from "vue-router";
import
"echarts-wordcloud"
;
import
AiSummary
from
'@/components/base/Ai/AiSummary/index.vue'
import
{
getPersonSummaryInfo
}
from
"@/api/common/index"
;
import
AiButton
from
"@/components/base/Ai/AiButton/index.vue"
;
import
AiPane
from
"@/components/base/Ai/AiPane/index.vue"
;
import
TipTab
from
"@/views/thinkTank/TipTab/index.vue"
;
const
router
=
useRouter
();
...
...
@@ -240,6 +252,17 @@ const props = defineProps({
default
:
()
=>
({})
}
});
const
REPORT_ANALYSIS_TIP_BOX5
=
"智库报告关键词云,数据来源:美国兰德公司官网"
;
const
isShowAiContentBox5
=
ref
(
false
);
const
aiContentBox5
=
ref
(
""
);
const
isBox5InterpretLoading
=
ref
(
false
);
const
handleSwitchAiContentShowBox5
=
(
val
)
=>
{
isShowAiContentBox5
.
value
=
val
;
if
(
val
)
{
fetchBox5ChartInterpretation
();
}
};
const
searchOpinions
=
ref
(
''
);
...
...
@@ -578,6 +601,67 @@ const handleGetBox3AnalysisContent = async textJson => {
const
res
=
await
getChartAnalysis
(
params
);
console
.
log
(
"图表解析内容"
,
res
);
};
const
getInterpretationTextFromChartResponse
=
(
res
)
=>
{
const
list
=
res
?.
data
;
const
first
=
Array
.
isArray
(
list
)
?
list
[
0
]
:
null
;
return
(
first
?.[
"解读"
]
||
first
?.[
"interpretation"
]
||
first
?.[
"analysis"
]
||
first
?.[
"content"
]
||
""
);
};
const
appendAiInterpretationChunk
=
(
targetRef
,
chunk
,
loadingText
=
"解读生成中…"
)
=>
{
if
(
!
chunk
)
{
return
;
}
const
current
=
String
(
targetRef
.
value
||
""
);
const
base
=
current
===
loadingText
?
""
:
current
;
targetRef
.
value
=
base
+
String
(
chunk
);
};
const
fetchBox5ChartInterpretation
=
async
()
=>
{
const
list
=
Array
.
isArray
(
box5Data
.
value
)
?
box5Data
.
value
:
[];
if
(
!
list
.
length
)
{
aiContentBox5
.
value
=
"暂无图表数据"
;
return
;
}
const
hasValidContent
=
aiContentBox5
.
value
&&
aiContentBox5
.
value
!==
"解读生成中…"
&&
aiContentBox5
.
value
!==
"解读加载失败"
&&
aiContentBox5
.
value
!==
"暂无图表数据"
;
if
(
hasValidContent
||
isBox5InterpretLoading
.
value
)
{
return
;
}
isBox5InterpretLoading
.
value
=
true
;
aiContentBox5
.
value
=
"解读生成中…"
;
const
chartPayload
=
{
type
:
"词云图"
,
name
:
"报告关键词云"
,
data
:
list
.
map
((
item
)
=>
({
name
:
item
.
name
,
value
:
item
.
value
}))
};
try
{
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
chartPayload
)
},
{
onChunk
:
chunk
=>
{
appendAiInterpretationChunk
(
aiContentBox5
,
chunk
);
}
}
);
const
text
=
getInterpretationTextFromChartResponse
(
res
);
aiContentBox5
.
value
=
text
||
aiContentBox5
.
value
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
console
.
error
(
"报告关键词云图表解读请求失败"
,
error
);
aiContentBox5
.
value
=
"解读加载失败"
;
}
finally
{
isBox5InterpretLoading
.
value
=
false
;
}
};
onMounted
(()
=>
{
handleGetThinkTankReportAbstract
();
...
...
@@ -817,6 +901,7 @@ onMounted(() => {
width
:
480px
;
height
:
415px
;
.box5-main
{
width
:
480px
;
height
:
361px
;
...
...
@@ -825,8 +910,10 @@ onMounted(() => {
padding-top
:
26px
;
padding-bottom
:
43px
;
display
:
flex
;
flex-direction
:
column
;
box-sizing
:
border-box
;
overflow
:
hidden
;
position
:
relative
;
.box5Chart
{
width
:
418px
;
...
...
@@ -834,6 +921,33 @@ onMounted(() => {
margin
:
0
auto
;
overflow
:
hidden
;
}
.box5-footer
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
position
:
absolute
;
bottom
:
18px
;
right
:
0
;
}
.ai-content
{
position
:
absolute
;
bottom
:
0
;
right
:
0
;
min-width
:
480px
;
min-height
:
156px
;
}
.ai-wrap
{
position
:
relative
;
cursor
:
pointer
;
}
}
}
...
...
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
浏览文件 @
b5529599
...
...
@@ -678,6 +678,16 @@ const getInterpretationTextFromChartResponse = (res) => {
);
};
/** 参考科技法案数量变化趋势:按 chunk 增量拼接展示 */
const
appendAiInterpretationChunk
=
(
targetRef
,
chunk
,
loadingText
=
"解读生成中…"
)
=>
{
if
(
!
chunk
)
{
return
;
}
const
current
=
String
(
targetRef
.
value
||
""
);
const
base
=
current
===
loadingText
?
""
:
current
;
targetRef
.
value
=
base
+
String
(
chunk
);
};
/** 政策追踪-领域分布饼图解读(与概览页 box6 入参一致) */
const
fetchPolicyPtBox1ChartInterpretation
=
async
()
=>
{
const
list
=
Array
.
isArray
(
box1Data
.
value
)
?
box1Data
.
value
:
[];
...
...
@@ -705,7 +715,14 @@ const fetchPolicyPtBox1ChartInterpretation = async () => {
}))
};
try
{
const
res
=
await
getChartAnalysis
({
text
:
JSON
.
stringify
(
chartPayload
)
});
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
chartPayload
)
},
{
onChunk
:
chunk
=>
{
appendAiInterpretationChunk
(
aiContentPolicyPt1
,
chunk
);
}
}
);
const
text
=
getInterpretationTextFromChartResponse
(
res
);
aiContentPolicyPt1
.
value
=
text
||
aiContentPolicyPt1
.
value
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
...
...
@@ -743,7 +760,14 @@ const fetchPolicyPtBox2ChartInterpretation = async () => {
}))
};
try
{
const
res
=
await
getChartAnalysis
({
text
:
JSON
.
stringify
(
chartPayload
)
});
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
chartPayload
)
},
{
onChunk
:
chunk
=>
{
appendAiInterpretationChunk
(
aiContentPolicyPt2
,
chunk
);
}
}
);
const
text
=
getInterpretationTextFromChartResponse
(
res
);
aiContentPolicyPt2
.
value
=
text
||
aiContentPolicyPt2
.
value
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
...
...
@@ -789,7 +813,14 @@ const fetchPolicyPtBox3ChartInterpretation = async () => {
})
};
try
{
const
res
=
await
getChartAnalysis
({
text
:
JSON
.
stringify
(
chartPayload
)
});
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
chartPayload
)
},
{
onChunk
:
chunk
=>
{
appendAiInterpretationChunk
(
aiContentPolicyPt3
,
chunk
);
}
}
);
const
text
=
getInterpretationTextFromChartResponse
(
res
);
aiContentPolicyPt3
.
value
=
text
||
aiContentPolicyPt3
.
value
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
...
...
src/views/thinkTank/assets/images/being-analysis-circle.png
0 → 100644
浏览文件 @
b5529599
29.8 KB
src/views/thinkTank/assets/images/being-analysis.png
0 → 100644
浏览文件 @
b5529599
39.4 KB
src/views/thinkTank/assets/images/blue-down.png
0 → 100644
浏览文件 @
b5529599
9.4 KB
src/views/thinkTank/assets/images/blue-up.png
0 → 100644
浏览文件 @
b5529599
9.4 KB
src/views/thinkTank/assets/images/empty-analysis.png
0 → 100644
浏览文件 @
b5529599
20.3 KB
src/views/thinkTank/assets/images/empty-image.png
查看替换文件 @
82ef459c
浏览文件 @
b5529599
49.4 KB
|
W:
|
H:
45.6 KB
|
W:
|
H:
2-up
Swipe
Onion skin
src/views/thinkTank/index.vue
浏览文件 @
b5529599
...
...
@@ -1041,6 +1041,16 @@ const getInterpretationTextFromChartResponse = (res) => {
);
};
/** 参考科技法案数量变化趋势:按 chunk 增量拼接展示 */
const
appendAiInterpretationChunk
=
(
targetRef
,
chunk
,
loadingText
=
"解读生成中…"
)
=>
{
if
(
!
chunk
)
{
return
;
}
const
current
=
String
(
targetRef
.
value
||
""
);
const
base
=
current
===
loadingText
?
""
:
current
;
targetRef
.
value
=
base
+
String
(
chunk
);
};
/** 请求 box5 折线图解读并更新 aiContentBox5(支持流式 SSE) */
const
fetchBox5ChartInterpretation
=
async
()
=>
{
const
v
=
box5ChartDisplayData
.
value
;
...
...
@@ -1070,7 +1080,14 @@ const fetchBox5ChartInterpretation = async () => {
})
};
try
{
const
res
=
await
getChartAnalysis
({
text
:
JSON
.
stringify
(
chartPayload
)
});
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
chartPayload
)
},
{
onChunk
:
chunk
=>
{
appendAiInterpretationChunk
(
aiContentBox5
,
chunk
);
}
}
);
const
text
=
getInterpretationTextFromChartResponse
(
res
);
aiContentBox5
.
value
=
text
||
aiContentBox5
.
value
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
...
...
@@ -1201,7 +1218,14 @@ const fetchBox6ChartInterpretation = async () => {
}))
};
try
{
const
res
=
await
getChartAnalysis
({
text
:
JSON
.
stringify
(
chartPayload
)
});
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
chartPayload
)
},
{
onChunk
:
chunk
=>
{
appendAiInterpretationChunk
(
aiContentBox6
,
chunk
);
}
}
);
const
text
=
getInterpretationTextFromChartResponse
(
res
);
aiContentBox6
.
value
=
text
||
aiContentBox6
.
value
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
...
...
@@ -1419,7 +1443,14 @@ const fetchBox7ChartInterpretation = async () => {
}))
};
try
{
const
res
=
await
getChartAnalysis
({
text
:
JSON
.
stringify
(
chartPayload
)
});
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
chartPayload
)
},
{
onChunk
:
chunk
=>
{
appendAiInterpretationChunk
(
aiContentBox7
,
chunk
);
}
}
);
const
text
=
getInterpretationTextFromChartResponse
(
res
);
aiContentBox7
.
value
=
text
||
aiContentBox7
.
value
||
"未返回有效解读内容"
;
}
catch
(
error
)
{
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论