Add three-stage cross-domain news research and reading briefings
Group original news into tagged fact packages, review each package with evidence and read-only research, and build complete 72-hour reading lists with independent model selections. Add paused RSS/Atom subscriptions, news-scoped Skills, versioned analysis records, and locked deployment support for SearXNG. Preserve original news and keep live migration and legacy AI cleanup separately authorized. Validate with 604 backend tests, 49 frontend tests, 19 CI control tests, lint, type checks, production build, and disposable PostgreSQL migration and three-stage smoke checks.
This commit is contained in:
1 parent
ae35d3d608
commit
80d266dba7
44 files changed
+2682
-574
No files matched your search
@@ -3,6 +3,7 @@ import type { CrawlLog, SourceInfo } from "@@/apis/crawler/type"
|
||||
import type { NewsBrief } from "@@/apis/news/type"
|
||||
import { patchSourceApi } from "@@/apis/crawler"
|
||||
import { listNewsApi } from "@@/apis/news"
|
||||
import { useSessionScope } from "@@/composables/useSessionScope"
|
||||
import { formatTime } from "@@/utils/text"
|
||||
import { CircleCheck, RefreshRight } from "@element-plus/icons-vue"
|
||||
import dayjs from "dayjs"
|
||||
@@ -20,10 +21,18 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const latest = ref<NewsBrief[]>([])
|
||||
const sessionScope = useSessionScope()
|
||||
const sourceName = ref(props.source.name)
|
||||
async function saveName() {
|
||||
sessionScope.assertCurrent()
|
||||
await patchSourceApi(props.source.code, { name: sourceName.value })
|
||||
if (sessionScope.isCurrent()) emit("changed")
|
||||
}
|
||||
const interval = ref(props.source.interval_seconds)
|
||||
|
||||
async function loadLatest() {
|
||||
latest.value = (await listNewsApi({ source: [props.source.code], limit: 5 })).items
|
||||
const result = await listNewsApi({ source: [props.source.code], limit: 5 })
|
||||
if (sessionScope.isCurrent()) latest.value = result.items
|
||||
}
|
||||
|
||||
// 本来源有新的“本轮完成”日志时刷新最新入库列表
|
||||
@@ -97,6 +106,11 @@ onMounted(loadLatest)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="source.code.startsWith('rss_')" class="flex gap-2">
|
||||
<el-input v-model="sourceName" placeholder="订阅名称" /><el-button @click="saveName">
|
||||
保存名称
|
||||
</el-button>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col gap-3 border rounded-lg border-solid p-4 shadow-sm"
|
||||
:class="alert ? 'border-rose-300 bg-rose-50/50' : 'border-slate-200 bg-white'"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { SourcesInfo } from "@@/apis/crawler/type"
|
||||
import { listSourcesApi, restartCrawlerApi } from "@@/apis/crawler"
|
||||
import { createRssApi, listSourcesApi, restartCrawlerApi } from "@@/apis/crawler"
|
||||
import { useSessionScope } from "@@/composables/useSessionScope"
|
||||
import { formatTime } from "@@/utils/text"
|
||||
import { Monitor, RefreshRight } from "@element-plus/icons-vue"
|
||||
@@ -9,6 +9,22 @@ import CalendarSourcePanel from "./components/CalendarSourcePanel.vue"
|
||||
import SourcePanel from "./components/SourcePanel.vue"
|
||||
import { useCrawlerLogs } from "./composables/useCrawlerLogs"
|
||||
|
||||
const rssVisible = ref(false)
|
||||
const rssSaving = ref(false)
|
||||
const rssDraft = reactive({ name: "", url: "", interval_seconds: 300 })
|
||||
async function addRss() {
|
||||
if (rssSaving.value) return
|
||||
rssSaving.value = true
|
||||
sessionScope.assertCurrent()
|
||||
try {
|
||||
await createRssApi({ ...rssDraft })
|
||||
if (!sessionScope.isCurrent()) return
|
||||
rssVisible.value = false
|
||||
await refresh()
|
||||
} finally {
|
||||
rssSaving.value = false
|
||||
}
|
||||
}
|
||||
const sessionScope = useSessionScope()
|
||||
|
||||
// 连续失败达到该次数时卡片告警
|
||||
@@ -89,6 +105,22 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog v-model="rssVisible" title="新增RSS/Atom订阅(保存后默认暂停)">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="名称">
|
||||
<el-input v-model="rssDraft.name" />
|
||||
</el-form-item><el-form-item label="公开订阅地址">
|
||||
<el-input v-model="rssDraft.url" />
|
||||
</el-form-item><el-form-item label="间隔(秒)">
|
||||
<el-input-number v-model="rssDraft.interval_seconds" :min="60" :max="86400" />
|
||||
</el-form-item><el-button :loading="rssSaving" @click="addRss">
|
||||
保存订阅
|
||||
</el-button>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
<el-button @click="rssVisible = true">
|
||||
新增 RSS/Atom 订阅
|
||||
</el-button>
|
||||
<div v-if="!info" v-loading="loading" class="min-h-40">
|
||||
<el-result v-if="failed" icon="error" title="采集状态读取失败">
|
||||
<template #extra>
|
||||
|
||||
Reference in new issue
Block a user