feat(news): index news packages and research persistent events
Extract reusable literal keywords once per source revision, assign complete news packages to long-lived events, and research all event members with immutable evidence and references. Add additive research storage, vocabulary snapshots, the two-stage workspace, isolated acceptance coverage, and a paused rollout with an initial fixed 50-news production trial.
This commit is contained in:
1 parent
07252fea0f
commit
4929a32a0a
69 files changed
+8215
-2092
No files matched your search
@@ -0,0 +1,20 @@
|
||||
import type * as Research from "./type"
|
||||
import { request } from "@/http/axios"
|
||||
|
||||
export const getSettingsApi = () => request<Research.Settings>({ url: "/risk/settings", method: "get" })
|
||||
export const saveSettingsApi = (data: Research.Config) => request<null>({ url: "/risk/settings", method: "put", data })
|
||||
export const enableApi = () => request<null>({ url: "/risk/enable", method: "post" })
|
||||
export const pauseApi = () => request<null>({ url: "/risk/pause", method: "post" })
|
||||
export const createSkillsApi = () => request<{ index: number, research: number }>({ url: "/risk/skill-templates", method: "post" })
|
||||
export const listEventsApi = (q: string, page: number) => request<{ items: Research.Event[], total: number }>({ url: "/risk/events", method: "get", params: { q, page } })
|
||||
export const getEventApi = (id: number) => request<Research.EventDetail>({ url: `/risk/events/${id}`, method: "get" })
|
||||
export const retryEventApi = (id: number) => request<null>({ url: `/risk/events/${id}/retry`, method: "post" })
|
||||
export const listPackagesApi = (q: string, page: number) => request<{ items: Research.Package[], total: number }>({ url: "/risk/packages", method: "get", params: { q, page } })
|
||||
export const getPackageApi = (id: number) => request<Research.PackageDetail>({ url: `/risk/packages/${id}`, method: "get" })
|
||||
export const retryPackageApi = (id: number) => request<null>({ url: `/risk/packages/${id}/retry`, method: "post" })
|
||||
export const listNewsPackagesApi = (id: number, revision: number) => request<Research.Package[]>({ url: `/risk/news/${id}/packages`, method: "get", params: { revision_no: revision } })
|
||||
export const enrollApi = (news_ids: number[]) => request<null>({ url: "/risk/packages", method: "post", data: { news_ids } })
|
||||
|
||||
export const getVocabularyApi = () => request<Research.Vocabulary>({ url: "/risk/tag-vocabulary", method: "get" })
|
||||
export const saveVocabularyApi = (data: { base_revision: number, entries: Research.VocabularyEntry[] }) => request<Research.Vocabulary>({ url: "/risk/tag-vocabulary", method: "put", data })
|
||||
export const getEventVersionApi = (event: number, version: number) => request<Research.VersionDetail>({ url: `/risk/events/${event}/versions/${version}`, method: "get" })
|
||||
@@ -0,0 +1,14 @@
|
||||
export interface Keyword { canonical: string, aliases: string[], quote: string }
|
||||
export interface ModelConfig { provider_id: number | null, model: string, reasoning_effort: string | null, skill_id: number | null }
|
||||
export interface Config { index: ModelConfig, research: ModelConfig, concurrency: number }
|
||||
export interface Settings { config: Partial<Config>, enabled: boolean, start_at: string }
|
||||
export interface Package { id: number, news_id: number, title: string, url: string, state: string, error: string | null, keywords: Keyword[], events: { id: number, title: string, reason: string, quote: string }[] }
|
||||
export interface Point { time_precision: "minute" | "day" | "month" | "unknown", planned_precision: "minute" | "day" | "month" | "unknown", package_ids: number[], time_text: string, description: string, occurred_at: string | null, planned_at: string | null, reported_at: string | null, period_text: string, evidence_refs: { article_id: number, quote: string }[] }
|
||||
export interface Result { title: string, summary: string, facts: string, reasoning: string, uncertainty: string, opinions: string, counter_evidence: string, watchpoints: string, asset_impacts: string, importance_stars: number, risk_stars: number, confidence: string, verification: string, timeline_points: Point[] }
|
||||
export interface Event { id: number, title: string, state: string, revision: number, error: string | null, tags: string[], result: Result | null }
|
||||
export interface EventDetail extends Event { packages: Package[], references: { event_id: number, title: string, version_id: number, role: string, reason: string, changed: boolean }[], versions: { id: number, revision: number, state: string, result: Result | null, error: string | null, usage: unknown, evidence: unknown }[] }
|
||||
export interface PackageDetail extends Package { article: { title: string, summary: string, content: string }, attempts: { id: number, stage: string, state: string, error: string | null, output: unknown, usage: unknown }[] }
|
||||
|
||||
export interface VocabularyEntry { canonical: string, aliases: string[] }
|
||||
export interface Vocabulary { revision: number, entries: VocabularyEntry[] }
|
||||
export interface VersionDetail { evidence: unknown, usage: unknown, id: number, event_id: number, revision: number, state: string, result: Result, packages: { id: number, news_id: number }[], articles: { id: number, title: string, summary: string, content: string }[], references: EventDetail["references"] }
|
||||
@@ -5,14 +5,13 @@ export const getRiskSettingsApi = () => request<Risk.Settings>({ url: "/risk/set
|
||||
export const saveRiskSettingsApi = (data: Risk.Settings) => request<null>({ url: "/risk/settings", method: "put", data })
|
||||
export const enableRiskApi = (data: Risk.Settings) => request<null>({ url: "/risk/enable", method: "post", timeout: 650000, data })
|
||||
export const getRiskOverviewApi = (hours: number) => request<Risk.Overview>({ url: "/risk/overview", method: "get", params: { hours } })
|
||||
export const listRiskEventsApi = (params: Risk.EventQuery) => request<{ items: Risk.RiskEvent[], total: number }>({ url: "/risk/summaries", method: "get", params })
|
||||
export const getRiskEventApi = (id: number) => request<{ related_events?: { id: number, reason: string }[], corrections?: { id: number, result: unknown }[], grouping_versions?: { id: number, state: string, error: string | null }[], evidence: { id: number, kind: string, data: unknown, fetched_at: string }[], event: Risk.RiskEvent, history: Risk.Analysis[], versions: { id: number, revision: number, state: string, model: string, result: Risk.RiskResult | null, error: string | null, analyzed_at: string | null }[] }>({ url: `/risk/summaries/${id}`, method: "get" })
|
||||
export const listRiskEventsApi = (params: Risk.EventQuery) => request<{ items: Risk.NewsEvent[], total: number }>({ url: "/risk/events", method: "get", params })
|
||||
export const getRiskPackageApi = (id: number) => request<{ corrections?: { id: number, result: unknown }[], grouping_versions?: { id: number, state: string, error: string | null }[], event: Risk.NewsPackage, history: Risk.Analysis[] }>({ url: `/risk/packages/${id}`, method: "get" })
|
||||
export const listRiskAnalysesApi = (params: { news_id?: number, revision_no?: number, failed?: boolean, stage?: string }, notify = true) => request<Risk.Analysis[]>({ url: "/risk/tasks", method: "get", params, notify })
|
||||
export const retryRiskAnalysisApi = (id: number, stage = "grouping") => request<null>({ url: `/risk/tasks/${id}/retry`, method: "post", params: { stage } })
|
||||
|
||||
export const listNewsSummariesApi = (newsId: number, revision: number) => request<Risk.RiskEvent[]>({ url: `/risk/news/${newsId}/summaries`, method: "get", params: { revision_no: revision } })
|
||||
export const listNewsPackagesApi = (newsId: number, revision: number) => request<Risk.NewsPackage[]>({ url: `/risk/news/${newsId}/packages`, method: "get", params: { revision_no: revision } })
|
||||
export const createNewsSkillTemplatesApi = (refreshExisting = false) => request<{ grouping_skill_id: number, analysis_skill_id: number, outlook_skill_id: number }>({ url: "/risk/skill-templates", method: "post", data: { refresh_existing: refreshExisting } })
|
||||
export const listNewsEventsApi = (page = 1) => request<{ items: Risk.NewsEvent[], total: number }>({ url: "/risk/events", method: "get", params: { page } })
|
||||
export const getNewsEventApi = (id: number) => request<Risk.EventDetail>({ url: `/risk/events/${id}`, method: "get" })
|
||||
export const previewReprocessApi = (data: Risk.ReprocessRange) => request<Risk.ReprocessPreview>({ url: "/risk/reprocess/preview", method: "post", data })
|
||||
export const startReprocessApi = (data: { preview_id: string, run_mode?: "shared" | "history_only" }) => request<{ id: number }>({ url: "/risk/reprocess", method: "post", data })
|
||||
|
||||
@@ -20,24 +20,19 @@ export interface RiskResult {
|
||||
related_packages?: { id: number, relation: string, reason: string }[]
|
||||
evidence_refs?: { article_id: number, quote: string }[]
|
||||
}
|
||||
export interface RiskEvent {
|
||||
main_event_id?: number | null
|
||||
replacement_ids?: number[]
|
||||
export interface NewsPackage {
|
||||
main_event_id: number | null
|
||||
replacement_ids: number[]
|
||||
fact: string
|
||||
tags: string[]
|
||||
content_kind: string
|
||||
importance_stars: number
|
||||
article_count: number
|
||||
id: number
|
||||
title: string
|
||||
category: string
|
||||
entities: string[]
|
||||
risk_stars: number
|
||||
peak_stars: number
|
||||
result: RiskResult | null
|
||||
state: string
|
||||
analyzed_at: string | null
|
||||
revision: number
|
||||
published_revision: number
|
||||
error: string | null
|
||||
published_at: string
|
||||
updated_at: string
|
||||
@@ -115,11 +110,20 @@ export interface NewsEvent {
|
||||
title: string
|
||||
state: string
|
||||
error: string | null
|
||||
result: { summary: string, importance_stars: number, risk_stars: number, counter_evidence: string, timeline_points: TimelinePoint[], uncertainty: string, opinions: string, watchpoints: string } | null
|
||||
revision: number
|
||||
published_revision: number
|
||||
updated_at: string
|
||||
tags: string[]
|
||||
category: string
|
||||
package_count: number
|
||||
risk_stars: number
|
||||
importance_stars: number
|
||||
result: (RiskResult & { timeline_points: TimelinePoint[] }) | null
|
||||
}
|
||||
export interface EventDetail extends NewsEvent {
|
||||
packages: RiskEvent[]
|
||||
versions: { id: number, revision: number, state: string, error: string | null, result: NewsEvent["result"] }[]
|
||||
packages: NewsPackage[]
|
||||
evidence: { id: number, version_id: number, data: unknown, fetched_at: string }[]
|
||||
versions: { id: number, revision: number, state: string, error: string | null, result: NewsEvent["result"], usage: unknown }[]
|
||||
}
|
||||
export interface ReprocessRange { selection?: "range" | "attempted", start?: string, end?: string, modes: ("missing" | "review")[] }
|
||||
export interface ReprocessPreview { articles: number, packages: number, events: number, total: number, missing: number, review: number, preview_id: string, models: Record<string, { model: string, reasoning_effort: string | null }> }
|
||||
|
||||
@@ -18,7 +18,7 @@ let version = 0
|
||||
let busy = false
|
||||
let alive = true
|
||||
let timer: ReturnType<typeof setInterval> | undefined
|
||||
const labels: Record<string, string> = { grouping: "新闻分包", analysis: "包研判", events: "事件时间线", outlook: "阅读编排", backfill: "历史新闻", running: "运行中", queued: "排队", waiting: "等待条件", failed: "失败", done: "已完成", unknown: "状态未知" }
|
||||
const labels: Record<string, string> = { grouping: "新闻分包", analysis: "事件研判", events: "事件研判", outlook: "阅读编排", backfill: "历史新闻", running: "运行中", queued: "排队", waiting: "等待条件", failed: "失败", done: "已完成", unknown: "状态未知" }
|
||||
async function load() {
|
||||
if (busy) return
|
||||
busy = true
|
||||
@@ -82,7 +82,7 @@ onBeforeUnmount(() => {
|
||||
<el-option v-for="s in ['running', 'queued', 'waiting', 'failed', 'done']" :key="s" :label="labels[s]" :value="s" />
|
||||
</el-select>
|
||||
<el-select v-if="expanded" v-model="stage" clearable placeholder="全部阶段" class="!w-36">
|
||||
<el-option v-for="s in ['grouping', 'analysis', 'events', 'outlook', 'backfill']" :key="s" :label="labels[s]" :value="s" />
|
||||
<el-option v-for="s in ['grouping', 'events', 'outlook', 'backfill']" :key="s" :label="labels[s]" :value="s" />
|
||||
</el-select>
|
||||
<el-select v-if="expanded" v-model="origin" clearable placeholder="全部来源" class="!w-36">
|
||||
<el-option label="实时" value="realtime" /><el-option label="回补" value="history" />
|
||||
|
||||
@@ -6,9 +6,6 @@ import { useUserStore } from "@/pinia/stores/user"
|
||||
|
||||
const emit = defineEmits<{ package: [id: number] }>()
|
||||
const user = useUserStore()
|
||||
const items = ref<Risk.NewsEvent[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const detail = ref<Risk.EventDetail | null>(null)
|
||||
const opened = ref(false)
|
||||
const detailError = ref(false)
|
||||
@@ -53,10 +50,8 @@ async function load() {
|
||||
const seq = ++sequence
|
||||
const version = user.sessionVersion
|
||||
try {
|
||||
const [events, runs] = await Promise.all([api.listNewsEventsApi(page.value), api.listReprocessApi()])
|
||||
const runs = await api.listReprocessApi()
|
||||
if (!active || seq !== sequence || version !== user.sessionVersion) return
|
||||
items.value = events.items
|
||||
total.value = events.total
|
||||
jobs.value = runs
|
||||
error.value = false
|
||||
if (batchId.value && !batchLoading.value) await openBatch(batchId.value, batchPage.value)
|
||||
@@ -119,7 +114,6 @@ async function action(id: number, retry: boolean) {
|
||||
watch([range, modes], () => {
|
||||
preview.value = null
|
||||
})
|
||||
watch(page, load)
|
||||
watch(() => user.sessionVersion, () => {
|
||||
batchSequence++
|
||||
sequence++
|
||||
@@ -127,7 +121,6 @@ watch(() => user.sessionVersion, () => {
|
||||
operation++
|
||||
busy.value = false
|
||||
detailError.value = false
|
||||
items.value = []
|
||||
jobs.value = []
|
||||
batch.value = null
|
||||
batchId.value = 0
|
||||
@@ -160,25 +153,12 @@ defineExpose({ open })
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
事件与时间线
|
||||
AI 历史回补
|
||||
</template>
|
||||
<el-alert v-if="error" title="事件读取失败,可重试" type="warning" :closable="false" />
|
||||
<el-button link @click="load">
|
||||
刷新
|
||||
</el-button>
|
||||
<div v-for="event in items" :key="event.id" class="my-3">
|
||||
<el-button link @click="open(event.id)">
|
||||
{{ event.title }}
|
||||
</el-button>
|
||||
<p>{{ event.result?.summary || '正在整理组成包的时间线' }}</p>
|
||||
<p v-if="event.result">
|
||||
重要性 {{ event.result.importance_stars }} 星 · 风险 {{ event.result.risk_stars }}X
|
||||
</p>
|
||||
<p v-if="event.error">
|
||||
更新失败:{{ event.error }};保留上一成功版本
|
||||
</p>
|
||||
</div>
|
||||
<el-pagination v-model:current-page="page" :total="total" :page-size="20" layout="prev, pager, next" />
|
||||
<details class="mt-4">
|
||||
<summary>AI 历史回补</summary>
|
||||
<p>按新闻发布时间选择范围,使用现存最新修订。不会还原历史时点;共享包和事件可能同时更新。AI 暂停时任务等待。</p>
|
||||
@@ -223,7 +203,7 @@ defineExpose({ open })
|
||||
成功 {{ job.counts.done ?? 0 }} · 处理中 {{ job.counts.processing ?? 0 }} · 待处理 {{ job.counts.pending ?? 0 }} · 失败 {{ job.counts.failed ?? 0 }} · 跳过 {{ job.counts.skipped ?? 0 }}
|
||||
</p>
|
||||
<p v-for="(counts, stage) in job.stages" :key="stage">
|
||||
{{ stage === 'analysis' ? '包研判' : '事件时间线' }}:待处理 {{ counts.pending ?? 0 }} · 运行 {{ counts.processing ?? 0 }} · 完成 {{ counts.done ?? 0 }} · 失败 {{ counts.failed ?? 0 }}
|
||||
{{ stage === 'analysis' ? '事件研判' : '事件研判' }}:待处理 {{ counts.pending ?? 0 }} · 运行 {{ counts.processing ?? 0 }} · 完成 {{ counts.done ?? 0 }} · 失败 {{ counts.failed ?? 0 }}
|
||||
</p>
|
||||
<el-button link @click="openBatch(job.id)">
|
||||
查看新闻明细
|
||||
@@ -235,7 +215,7 @@ defineExpose({ open })
|
||||
重试失败项
|
||||
</el-button>
|
||||
<p v-for="failure in job.failures" :key="`${failure.stage}:${failure.id}`">
|
||||
{{ ({ grouping: "新闻初筛", analysis: "包研判", events: "事件时间线", outlook: "阅读编排" })[failure.stage] }} #{{ failure.id }}:{{ failure.error }}
|
||||
{{ ({ grouping: "新闻初筛", analysis: "事件研判", events: "事件研判", outlook: "阅读编排" })[failure.stage] }} #{{ failure.id }}:{{ failure.error }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="batchId">
|
||||
@@ -251,7 +231,7 @@ defineExpose({ open })
|
||||
</div>
|
||||
</details>
|
||||
</el-card>
|
||||
<el-drawer v-model="opened" title="事件发展时间线" size="min(760px, 100vw)">
|
||||
<el-drawer v-model="opened" title="事件研判与时间线" size="min(760px, 100vw)">
|
||||
<el-alert v-if="detailError" title="事件详情读取失败" type="warning" :closable="false" />
|
||||
<el-button v-if="detailError" @click="open(detailId)">
|
||||
重试
|
||||
@@ -269,6 +249,7 @@ defineExpose({ open })
|
||||
{{ ref.quote }}
|
||||
</blockquote>
|
||||
</div>
|
||||
<p>事实:{{ detail.result?.facts }}</p><p>市场影响:{{ detail.result?.asset_impacts }}</p><p>判断依据:{{ detail.result?.reasoning }}</p>
|
||||
<p>反证:{{ detail.result?.counter_evidence }}</p><p>不确定性:{{ detail.result?.uncertainty }}</p><p>意见:{{ detail.result?.opinions }}</p><p>关注:{{ detail.result?.watchpoints }}</p>
|
||||
<p>组成包</p>
|
||||
<div v-for="item in detail.packages" :key="item.id">
|
||||
@@ -276,10 +257,15 @@ defineExpose({ open })
|
||||
{{ item.title }}
|
||||
</el-button>
|
||||
</div>
|
||||
<p>核验记录</p>
|
||||
<details v-for="record in detail.evidence" :key="record.id">
|
||||
<summary>证据 #{{ record.id }} · {{ formatTime(record.fetched_at) }}</summary>
|
||||
<pre class="whitespace-pre-wrap break-all">{{ record.data }}</pre>
|
||||
</details>
|
||||
<p>版本历史</p>
|
||||
<details v-for="version in detail.versions" :key="version.id" class="my-2">
|
||||
<summary>v{{ version.revision }} · {{ version.state }} {{ version.error }}</summary>
|
||||
<p>{{ version.result?.summary }}</p>
|
||||
<p>{{ version.result?.summary }}</p><pre class="whitespace-pre-wrap break-all">{{ version.usage }}</pre>
|
||||
<p v-for="(point, index) in version.result?.timeline_points" :key="index">
|
||||
{{ point.time_text }} · {{ point.description }} · 包 #{{ point.package_id }} v{{ point.package_revision }}
|
||||
</p>
|
||||
|
||||
+520
-498
File diff suppressed because it is too large.
Load diff
@@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { Analysis, RiskEvent } from "@/common/apis/risk/type"
|
||||
import { listNewsSummariesApi, listRiskAnalysesApi } from "@/common/apis/risk"
|
||||
import type { Package } from "@@/apis/news-research/type"
|
||||
import { listNewsPackagesApi } from "@@/apis/news-research"
|
||||
import { useUserStore } from "@/pinia/stores/user"
|
||||
|
||||
const props = defineProps<{ newsId: number, revision: number }>()
|
||||
const user = useUserStore()
|
||||
const analysis = ref<Analysis | null>(null)
|
||||
const summaries = ref<RiskEvent[]>([])
|
||||
const summaries = ref<Package[]>([])
|
||||
const failed = ref(false)
|
||||
const loading = ref(false)
|
||||
const ownerVersion = user.sessionVersion
|
||||
@@ -19,15 +18,13 @@ async function load(reset = true) {
|
||||
const version = user.sessionVersion
|
||||
loading.value = true
|
||||
if (reset) {
|
||||
analysis.value = null
|
||||
summaries.value = []
|
||||
failed.value = false
|
||||
}
|
||||
try {
|
||||
const [rows, events] = await Promise.all([listRiskAnalysesApi({ news_id: props.newsId, revision_no: props.revision }, reset), listNewsSummariesApi(props.newsId, props.revision)])
|
||||
const events = await listNewsPackagesApi(props.newsId, props.revision)
|
||||
if (seq === sequence && version === user.sessionVersion) {
|
||||
summaries.value = events
|
||||
analysis.value = rows[0] ?? null
|
||||
failed.value = false
|
||||
}
|
||||
} catch {
|
||||
@@ -39,7 +36,7 @@ async function load(reset = true) {
|
||||
watch(() => [props.newsId, props.revision], () => void load(), { immediate: true })
|
||||
onMounted(() => {
|
||||
timer = setInterval(() => {
|
||||
if (!document.hidden && !loading.value && (!["done"].includes(analysis.value?.state ?? "") || summaries.value.some(e => ["pending", "processing"].includes(e.state)))) void load(false)
|
||||
if (!document.hidden && !loading.value && (!summaries.value.length || summaries.value.some(e => e.state !== "done"))) void load(false)
|
||||
}, 15000)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
@@ -53,16 +50,20 @@ onBeforeUnmount(() => {
|
||||
<div class="rounded bg-sky-50 p-3 text-sm">
|
||||
<b>AI 关联新闻包</b>
|
||||
<div v-for="event in summaries" :key="event.id" class="my-2">
|
||||
<router-link :to="{ path: '/home', query: { summary: event.id } }">
|
||||
<router-link :to="{ path: '/home', query: { package: event.id } }">
|
||||
{{ event.title }}
|
||||
</router-link>
|
||||
<p>{{ event.result?.summary || '事件归并完成,等待二阶段总结' }}</p>
|
||||
<div class="my-2 flex flex-wrap gap-2">
|
||||
<el-tag v-for="word in event.keywords" :key="word.canonical">
|
||||
{{ word.canonical }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<p v-if="event.error">
|
||||
更新失败:{{ event.error }}
|
||||
</p>
|
||||
</div>
|
||||
<p v-if="!summaries.length">
|
||||
{{ failed ? '关联事件读取失败' : analysis?.state === 'failed' ? `初筛失败:${analysis.error}` : '暂无关联总结;新流程只处理启用后的新闻' }}
|
||||
{{ failed ? '关联事件读取失败' : '尚未建立关键词索引' }}
|
||||
<el-button v-if="failed" link :loading="loading" @click="load()">
|
||||
重试
|
||||
</el-button>
|
||||
|
||||
@@ -84,13 +84,7 @@ onBeforeUnmount(() => media?.removeEventListener("change", updateMobile))
|
||||
{{ item.summary || item.excerpt }}
|
||||
</div>
|
||||
<div class="mt-1 line-clamp-2 text-xs text-slate-500">
|
||||
<template v-if="item.risk?.state === 'done'">
|
||||
<span class="text-amber-600">{{ item.risk.verification === 'insufficient' ? '信息不足,无法判断' : item.risk.stars ? '★'.repeat(item.risk.stars) : '无明显风险' }}</span>
|
||||
· {{ item.risk.summary }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ item.risk?.state === 'skipped' ? '非财经新闻,原文保留' : item.risk?.state === 'failed' ? 'AI 分析失败' : item.risk?.state === 'processing' ? 'AI 分析中' : '未纳入新流程或等待总结' }}
|
||||
</template>
|
||||
{{ item.risk?.state === 'done' ? '关键词与事件归属已完成,研判请查看关联事件' : item.risk?.state?.endsWith('_failed') ? '关键词索引或事件分类失败' : item.risk ? '关键词索引与事件分类处理中' : '尚未建立关键词索引' }}
|
||||
</div>
|
||||
</div>
|
||||
<el-alert v-if="failed" type="error" :closable="false" title="新闻读取失败;已有内容保留">
|
||||
|
||||
Reference in new issue
Block a user