Commit 74735286 by luoqi

feat(web): 助手小窗开场建议场景化(规则生成,与 /assistant 页不共用)

- AssistantChat 加 examples 覆盖参数(不传 = 页面版默认三条 → 两版不共用)。
- 详情页 ready 时把 {planId, 患者名} 发布到 plan-sync-store.current;
  小窗按规则出建议(非 AI):有当前患者 → ①为什么被召回/捋关键事实 ②画像+潜在治疗
  ③这通电话怎么开口;无上下文 → 召回池TOP/今日工作 兜底。切患者建议自动跟随。

web tsc 0 + Next 生产构建过。仅本地,未部署。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent ce6bec2d
......@@ -13,6 +13,7 @@ import { PlanDetailApp } from '@/components/plan-detail/plan-detail-app';
import { adaptData } from '@/components/plan-detail/adapt-data';
import { usePlanAggregate } from '@/components/plans/use-plan-aggregate';
import { useAuthStore } from '@/stores/auth-store';
import { usePlanSyncStore } from '@/stores/plan-sync-store';
/**
* /plans/[planId] — Plan 详情(生产版)。
......@@ -53,6 +54,13 @@ function PlanDetailLoader({ planId }: { planId: string }) {
if (state.status === 'ready') {
lastAggregateCache = { planId, data: state.data };
}
// 发布"当前工作台患者"(右下角助手按它出场景化开场建议)
const currentPatientName = state.status === 'ready' ? (state.data.patient.name ?? '') : null;
useEffect(() => {
if (currentPatientName) {
usePlanSyncStore.getState().setCurrent({ planId, patientName: currentPatientName });
}
}, [planId, currentPatientName]);
// W4 末:单患者刷新后 plan 可能已被 supersede / abandoned(scenario 不再命中)
// → /full 接口返回 PLAN_NOT_FOUND → 这里捕获后 toast + 跳列表(避免用户卡在 error 页)
......
......@@ -427,10 +427,13 @@ function ModelSelect({
export function AssistantChat({
variant = 'page',
onClose,
examples,
}: {
/** page = /assistant 整页;widget = 详情页右下角吸附小窗(紧凑头部 + 可收起) */
variant?: 'page' | 'widget';
onClose?: () => void;
/** 空态开场建议(规则生成,调用方按场景定制);不传用页面版默认 */
examples?: string[];
} = {}) {
const { messages, status, model, setModel, send, stop } = useAssistantChat();
const [input, setInput] = useState('');
......@@ -609,7 +612,7 @@ export function AssistantChat({
问我关于患者的问题,我会查询 PAC 实时数据。
</div>
<div className="flex flex-wrap justify-center gap-1.5">
{EXAMPLES.map((ex) => (
{(examples ?? EXAMPLES).map((ex) => (
<button
key={ex}
type="button"
......
......@@ -5,6 +5,7 @@ import { Bot } from 'lucide-react';
import { Permission } from '@pac/types';
import { cn } from '@/lib/utils';
import { useHasPermission } from '@/hooks/use-permission';
import { usePlanSyncStore } from '@/stores/plan-sync-store';
import { AssistantChat } from './assistant-chat';
/**
......@@ -18,6 +19,15 @@ import { AssistantChat } from './assistant-chat';
export function AssistantWidget() {
const allowed = useHasPermission(Permission.AGENT_INVOKE);
const [open, setOpen] = useState(false);
// 场景化开场建议(规则,非 AI):有当前患者 → 围绕该患者;否则兜底通用
const current = usePlanSyncStore((s) => s.current);
const examples = current?.patientName
? [
`${current.patientName}为什么被召回?帮我捋一下关键事实`,
`查一下${current.patientName}的画像和潜在治疗`,
`这通电话怎么开口比较好?给我两句开场`,
]
: ['现在召回池里优先级最高的是谁?', '帮我准备一下今天要跟进的召回工作'];
if (!allowed) return null;
......@@ -32,7 +42,7 @@ export function AssistantWidget() {
open ? 'translate-y-0 opacity-100' : 'pointer-events-none invisible translate-y-2 opacity-0',
)}
>
<AssistantChat variant="widget" onClose={() => setOpen(false)} />
<AssistantChat variant="widget" onClose={() => setOpen(false)} examples={examples} />
</div>
{/* 悬浮开关钮 */}
{!open && (
......
......@@ -12,6 +12,9 @@ interface PlanSyncState {
/** 新状态(submitExecution 回的 planStatus 等);null = 只提示"该行变了"(消费方自行 refresh) */
status: string | null;
notify: (planId: string, status?: string) => void;
/** 当前工作台正在看的患者(详情页 ready 时发布;右下角助手按它出场景化开场建议) */
current: { planId: string; patientName: string } | null;
setCurrent: (current: { planId: string; patientName: string } | null) => void;
}
export const usePlanSyncStore = create<PlanSyncState>((set) => ({
......@@ -20,4 +23,6 @@ export const usePlanSyncStore = create<PlanSyncState>((set) => ({
status: null,
notify: (planId, status) =>
set((s) => ({ seq: s.seq + 1, planId, status: status ?? null })),
current: null,
setCurrent: (current) => set({ current }),
}));
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment