forked from admin/PlantAI-rag-new
feat(bioflow): 分析路径规划中提示——planning 帧+前端思考中状态
- 后端 chat 流在规划开始前发 planning 帧(seq=-2,与 plan_ready 同机制) - 前端收到 planning 帧立即创建 bioflow_path 块(status=running, 脉动圆点+「正在分析需求并规划分析路径…」),plan_ready 填入候选并关闭 - bioflow store 增加 planning/planningText 状态 - 339 后端用例 + 121 r3debug + 373 主套件全绿;已部署 176 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -220,6 +220,10 @@ async def chat_stream(body: ChatStreamRequest) -> StreamingResponse:
|
||||
return
|
||||
|
||||
# 1. 规划(候选路径;复用/LLM/降级)
|
||||
# 规划开始帧:前端据此显示「分析路径规划中…」占位(planning 状态)
|
||||
yield _sse({"type": "planning", "seq": -2, "data": {
|
||||
"message": "正在分析需求并规划分析路径…",
|
||||
}})
|
||||
try:
|
||||
plan: PlanResult = await ctx.planner.plan(body.message)
|
||||
except PlannerError as exc:
|
||||
|
||||
@@ -288,7 +288,7 @@ def test_catalog_unknown_chain(api):
|
||||
|
||||
def test_chat_stream_plan_ready(api_context):
|
||||
"""TC-BIO-API-001/002:chat/stream SSE,plan_ready 下发候选。"""
|
||||
events = _read_sse(_chat_stream_call("帮我跑水稻 SPL 家族鉴定"), max_events=1)
|
||||
events = _read_sse(_chat_stream_call("帮我跑水稻 SPL 家族鉴定"), max_events=2)
|
||||
types = [e["type"] for e in events]
|
||||
assert "plan_ready" in types
|
||||
plan = [e for e in events if e["type"] == "plan_ready"][0]["data"]
|
||||
@@ -305,7 +305,7 @@ def test_chat_stream_unknown_stage_rejected(api_context, monkeypatch):
|
||||
{"purpose": "坏", "stages": ["fake_stage_xyz"]},
|
||||
{"purpose": "好", "stages": ["identify"]},
|
||||
]))
|
||||
events = _read_sse(_chat_stream_call("帮我跑水稻 SPL 家族鉴定"), max_events=1)
|
||||
events = _read_sse(_chat_stream_call("帮我跑水稻 SPL 家族鉴定"), max_events=2)
|
||||
plan = [e for e in events if e["type"] == "plan_ready"][0]["data"]
|
||||
for c in plan["candidates"]:
|
||||
assert "fake_stage_xyz" not in c["stages"]
|
||||
@@ -343,7 +343,7 @@ def test_chat_stream_llm_timeout_degrade(api_context, monkeypatch):
|
||||
from conftest import FakeLLM
|
||||
import app.services.bioflow.planner as planner_mod
|
||||
monkeypatch.setattr(planner_mod, "complete_json", FakeLLM(error="timeout"))
|
||||
events = _read_sse(_chat_stream_call("帮我跑水稻 SPL 家族鉴定"), max_events=1)
|
||||
events = _read_sse(_chat_stream_call("帮我跑水稻 SPL 家族鉴定"), max_events=2)
|
||||
plan = [e for e in events if e["type"] == "plan_ready"][0]["data"]
|
||||
assert plan["fallback"] # 降级提示
|
||||
assert plan["candidates"]
|
||||
|
||||
@@ -109,7 +109,11 @@ defineExpose({ select: pick, confirm: confirmCreate });
|
||||
|
||||
<!-- 选择视图(FR-BIO-002/003) -->
|
||||
<template v-if="view === 'select'">
|
||||
<div v-if="!candidates.length" class="fpc-empty">{{ BF.pathEmpty }}</div>
|
||||
<div v-if="block.planning" class="fpc-planning" role="status" aria-live="polite">
|
||||
<span class="fpc-planning-dot" aria-hidden="true"></span>
|
||||
<span>{{ block.planningText || BF.pathPlanning }}</span>
|
||||
</div>
|
||||
<div v-else-if="!candidates.length" class="fpc-empty">{{ BF.pathEmpty }}</div>
|
||||
<div
|
||||
v-for="(c, i) in candidates"
|
||||
:key="i"
|
||||
@@ -211,6 +215,26 @@ defineExpose({ select: pick, confirm: confirmCreate });
|
||||
font-size: 12px;
|
||||
color: var(--pa-ink-subtle);
|
||||
}
|
||||
/* 规划中提示:脉动圆点 + 文案(thinking/运行中视觉) */
|
||||
.fpc-planning {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 0;
|
||||
font-size: 12px;
|
||||
color: var(--pa-ink-subtle);
|
||||
}
|
||||
.fpc-planning-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--pa-g-from, #5b8def);
|
||||
animation: fpc-pulse 1.1s ease-in-out infinite;
|
||||
}
|
||||
@keyframes fpc-pulse {
|
||||
0%, 100% { opacity: 0.35; transform: scale(0.85); }
|
||||
50% { opacity: 1; transform: scale(1.1); }
|
||||
}
|
||||
/* 候选路径行:padding 8px 10px,r-md,hover surface-3 */
|
||||
.fpc-row {
|
||||
padding: 8px 10px;
|
||||
|
||||
@@ -17,6 +17,7 @@ export const BF = {
|
||||
pathAutoFilled: '已自动补全',
|
||||
pathConfirm: '确认此路径',
|
||||
pathEmpty: '暂无候选路径',
|
||||
pathPlanning: '正在分析需求并规划分析路径…',
|
||||
pathConfirming: '确认中…',
|
||||
pathFallback: '规划降级:',
|
||||
pathReuseNote: '复用存档',
|
||||
|
||||
@@ -161,6 +161,8 @@ export const useBioflowStore = defineStore('bioflow', {
|
||||
autoContinue: false,
|
||||
archiveId: null,
|
||||
},
|
||||
planning: false, // 规划中(planning 帧开、plan_ready 关;供面板/占位提示)
|
||||
planningText: '',
|
||||
jobEventsSource: {}, // sessionId -> { es: EventSource|null, lastSeq, closedByServer }
|
||||
// ---- 面板:任务列表/详情 ----
|
||||
jobs: [],
|
||||
@@ -230,7 +232,15 @@ export const useBioflowStore = defineStore('bioflow', {
|
||||
// ---------------------------------------------------------------
|
||||
/** chat/stream 事件应用(plan_ready/job_created/…;message_done 收尾由 chat store 处理) */
|
||||
applyChatEvent(type, data) {
|
||||
if (type === 'planning') {
|
||||
// 规划中:仅打点状态,不落 currentJob(plan_ready 才建 plan)
|
||||
this.planning = true;
|
||||
this.planningText = (data && data.message) || '';
|
||||
return;
|
||||
}
|
||||
if (type === 'plan_ready') {
|
||||
this.planning = false;
|
||||
this.planningText = '';
|
||||
this.currentJob = {
|
||||
...this.currentJob,
|
||||
plan: {
|
||||
|
||||
@@ -153,6 +153,8 @@ export function ensureBioflowPathBlock(aMsg) {
|
||||
blk = {
|
||||
type: 'bioflow_path',
|
||||
status: 'running',
|
||||
planning: false, // 规划中提示(planning 帧开启、plan_ready 关闭)
|
||||
planningText: '', // 规划提示文案
|
||||
candidates: [],
|
||||
selected: null,
|
||||
autoFilled: [],
|
||||
@@ -165,6 +167,15 @@ export function ensureBioflowPathBlock(aMsg) {
|
||||
return blk;
|
||||
}
|
||||
|
||||
/** planning 帧:创建/点亮路径块「思考中」状态(FR-BIO-002 前置视觉) */
|
||||
export function patchBioflowPlanning(aMsg, data) {
|
||||
const blk = ensureBioflowPathBlock(aMsg);
|
||||
blk.planning = true;
|
||||
blk.planningText = (data && data.message) || '正在分析需求并规划分析路径…';
|
||||
blk.status = 'running';
|
||||
return blk;
|
||||
}
|
||||
|
||||
/** 找到(或创建)唯一的进度块(bioflow_progress) */
|
||||
export function ensureBioflowProgressBlock(aMsg) {
|
||||
let blk = (aMsg.blocks || []).find((b) => b.type === 'bioflow_progress');
|
||||
@@ -185,6 +196,7 @@ export function patchBioflowPlan(aMsg, data) {
|
||||
blk.reuseArchive = data.reuse_archive || null;
|
||||
blk.reuseName = data.reuse_name || null;
|
||||
blk.fallback = data.fallback || null;
|
||||
blk.planning = false; // 规划结束,进入候选选择
|
||||
blk.status = 'done';
|
||||
if (blk.reuseArchive && !blk.selected) blk.selected = 0; // 复用句式:直接选中唯一候选
|
||||
return blk;
|
||||
@@ -1646,6 +1658,9 @@ export const useChatStore = defineStore('chat', {
|
||||
patchBioflowPlan(aMsg, data);
|
||||
bioflow.applyChatEvent('plan_ready', data);
|
||||
this.scrollSeq++;
|
||||
} else if (type === 'planning') {
|
||||
patchBioflowPlanning(aMsg, data);
|
||||
this.scrollSeq++;
|
||||
} else if (type === 'job_created') {
|
||||
patchBioflowEvent(aMsg, { type, data });
|
||||
bioflow.applyJobEvent({ type, data });
|
||||
|
||||
Reference in New Issue
Block a user