- BFF 薄适配层(FastAPI,8090):QA 代理/SSE 中继/Neo4j 直连/抽取代理/会话持久化 - Vue3 前端:五相位检索流水线、文献来源与证据链、知识图谱、提取任务四 tab - 四档响应式(桌面/笔记本/平板/手机)、深浅主题 - 81 个 FR 全部实现,测试追溯矩阵 81/81,后端 121 测/前端 156 测 - 真实凭据已脱敏,运行时配置在 backend/.env(不入 git) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
2.9 KiB
JavaScript
80 lines
2.9 KiB
JavaScript
// stores/health.js —— B1 归属。上游健康轮询与降级状态(FR-HEALTH-001/002/003/004)。
|
||
// 数据源:GET /api/health(30s 轮询)+ GET /api/qa/info(QA 版本号)。
|
||
// 上游状态驱动各 store 的降级消费:导出 getter qaOk / neo4jOk / extractionOk。
|
||
import { defineStore } from 'pinia';
|
||
import { api } from '../api/http.js';
|
||
|
||
const HEALTH_INTERVAL = 30000;
|
||
|
||
export const useHealthStore = defineStore('health', {
|
||
state: () => ({
|
||
/** 最近一次成功轮询的 health 数据(失败时保留旧值,供降级展示) */
|
||
data: null,
|
||
/** 探活错误 */
|
||
error: null,
|
||
/** 轮询是否在运行 */
|
||
polling: false,
|
||
/** 最近成功轮询的 ISO 时间 */
|
||
lastOkAt: null,
|
||
/** /api/qa/info 结果 */
|
||
qaInfo: null,
|
||
/** 首次探测尚未返回(未知态:显示空心点而非错误) */
|
||
unknown: true,
|
||
}),
|
||
getters: {
|
||
bffOk: (s) => !!s.data && !s.error,
|
||
/** QA 上游可用(FR-HEALTH-002:不可用→作曲器禁用) */
|
||
qaOk: (s) => !!s.data && s.data.upstreams?.qa?.ok === true,
|
||
/** Neo4j 可用(FR-HEALTH-003:不可用→图谱搜索/邻域禁用) */
|
||
neo4jOk: (s) => !!s.data && s.data.upstreams?.neo4j?.ok === true,
|
||
/** 抽取上游可用(FR-HEALTH-004:8000 或 7900 任一在线) */
|
||
extractionOk: (s) => !!s.data && (s.data.upstreams?.rag?.ok === true || s.data.upstreams?.ragAlt?.ok === true),
|
||
/** BFF 整体状态(status:"ok" 即未降级) */
|
||
bffHealthy: (s) => !!s.data && s.data.status === 'ok',
|
||
qaVersion: (s) => s.qaInfo?.version || null,
|
||
/** 会话数(status bar 展示) */
|
||
sessionCount: (s) => (s.data && typeof s.data.db?.sessions === 'number' ? s.data.db.sessions : null),
|
||
},
|
||
actions: {
|
||
async _fetch() {
|
||
try {
|
||
const data = await api.get('/api/health');
|
||
this.data = data;
|
||
this.error = null;
|
||
this.unknown = false;
|
||
this.lastOkAt = new Date().toISOString();
|
||
} catch (e) {
|
||
this.error = e;
|
||
this.unknown = false;
|
||
// 失败不弹窗,仅标记;旧 data 保留供降级展示
|
||
}
|
||
},
|
||
/** 启动轮询:先取一次,此后每 30s 一次;页面 hidden 时跳过 */
|
||
start() {
|
||
if (this.polling) return;
|
||
this.polling = true;
|
||
this._fetch();
|
||
this._timer = setInterval(() => {
|
||
if (typeof document !== 'undefined' && document.hidden) return;
|
||
this._fetch();
|
||
}, HEALTH_INTERVAL);
|
||
},
|
||
/** 停止轮询(组件卸载时调用) */
|
||
stop() {
|
||
if (this._timer) {
|
||
clearInterval(this._timer);
|
||
this._timer = null;
|
||
}
|
||
this.polling = false;
|
||
},
|
||
/** 拉取 QA 版本号(FR-SHELL-003:版本来自 /api/qa/info;失败不阻塞渲染) */
|
||
async fetchQaInfo() {
|
||
try {
|
||
this.qaInfo = await api.get('/api/qa/info');
|
||
} catch (e) {
|
||
this.qaInfo = null;
|
||
}
|
||
},
|
||
},
|
||
});
|