|
|
@@ -362,6 +362,13 @@ function AssistantExperience({ flow, mode }: { flow: FlowControls; mode: Assista
|
|
|
const [text, setText] = useState("");
|
|
|
const [stage, setStage] = useState<"ready" | "followup" | "complete">("ready");
|
|
|
const [doctor, setDoctor] = useState("");
|
|
|
+ const [editingSlot, setEditingSlot] = useState("");
|
|
|
+ const [slots, setSlots] = useState({
|
|
|
+ department: "神经内科",
|
|
|
+ date: "明天 07-27",
|
|
|
+ time: "上午 09:00",
|
|
|
+ category: "专家号",
|
|
|
+ });
|
|
|
const modeCopy = {
|
|
|
general: {
|
|
|
title: "请说出您的需求",
|
|
|
@@ -413,10 +420,31 @@ function AssistantExperience({ flow, mode }: { flow: FlowControls; mode: Assista
|
|
|
else flow.push(scheduleScreen);
|
|
|
};
|
|
|
|
|
|
+ const updateSlot = (key: keyof typeof slots, value: string) => {
|
|
|
+ setSlots((current) => ({ ...current, [key]: value }));
|
|
|
+ setEditingSlot("");
|
|
|
+ };
|
|
|
+
|
|
|
+ const registrationSlots = [
|
|
|
+ { key: "patient", label: "就诊人", value: "王*明", editable: false },
|
|
|
+ { key: "department", label: "科室", value: slots.department, editable: true },
|
|
|
+ { key: "date", label: "日期", value: slots.date, editable: true },
|
|
|
+ { key: "time", label: "时间", value: slots.time, editable: true },
|
|
|
+ { key: "category", label: "号别", value: slots.category, editable: true },
|
|
|
+ { key: "doctor", label: "医生", value: doctor || "待补充", editable: Boolean(doctor) },
|
|
|
+ ];
|
|
|
+
|
|
|
+ const slotOptions: Record<string, string[]> = {
|
|
|
+ department: ["神经内科", "普通内科"],
|
|
|
+ date: ["明天 07-27", "后天 07-28"],
|
|
|
+ time: ["上午 09:00", "上午 09:30"],
|
|
|
+ category: ["专家号", "普通号"],
|
|
|
+ };
|
|
|
+
|
|
|
return (
|
|
|
<MobileScroll className="task-scroll">
|
|
|
<main className="task-page assistant-page">
|
|
|
- <section className="assistant-welcome">
|
|
|
+ <section className={`assistant-welcome ${stage !== "ready" ? "compact" : ""}`}>
|
|
|
<span className={`assistant-orb ${stage !== "ready" ? "is-active" : ""}`}><SpeakerLoudIcon /></span>
|
|
|
<h2>{modeCopy.title}</h2>
|
|
|
<p>{modeCopy.description}</p>
|
|
|
@@ -459,58 +487,151 @@ function AssistantExperience({ flow, mode }: { flow: FlowControls; mode: Assista
|
|
|
</>
|
|
|
) : (
|
|
|
<section className="slot-dialogue" aria-live="polite">
|
|
|
- <article className="voice-transcript">
|
|
|
- <span>您说</span>
|
|
|
- <p>{text}</p>
|
|
|
- </article>
|
|
|
+ <div className="dialogue-context">
|
|
|
+ <span className="dialogue-label">最近对话</span>
|
|
|
+ <article className="voice-transcript">
|
|
|
+ <span>您说</span>
|
|
|
+ <p>{text}</p>
|
|
|
+ </article>
|
|
|
|
|
|
- {mode === "report" ? (
|
|
|
<article className="assistant-reply">
|
|
|
- <strong>已理解您的需求</strong>
|
|
|
- <p>请上传报告,或从本院已审核报告中选择一份。</p>
|
|
|
- <button className="primary-button inline-action" type="button" onClick={nextTask}>选择报告 <ChevronRightIcon /></button>
|
|
|
+ <strong>
|
|
|
+ {mode === "report"
|
|
|
+ ? "我会帮您解读血常规报告"
|
|
|
+ : mode === "tongue"
|
|
|
+ ? "我会先了解近期身体感受"
|
|
|
+ : stage === "followup"
|
|
|
+ ? "大部分挂号信息已经记下"
|
|
|
+ : "挂号信息已经收集完整"}
|
|
|
+ </strong>
|
|
|
+ <p>
|
|
|
+ {mode === "report"
|
|
|
+ ? "下一步请选择本院报告,或上传清晰图片与 PDF。"
|
|
|
+ : mode === "tongue"
|
|
|
+ ? "完成体质症候问答后,再引导您拍摄舌象。"
|
|
|
+ : stage === "followup"
|
|
|
+ ? "还差医生信息,确认后再查询实时号源。"
|
|
|
+ : "接下来只查询号源,不会直接执行挂号。"}
|
|
|
+ </p>
|
|
|
</article>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {mode === "report" ? (
|
|
|
+ <>
|
|
|
+ <section className="collected-panel">
|
|
|
+ <div className="panel-heading"><span>已确定的信息</span><em>2 项</em></div>
|
|
|
+ <dl className="compact-context-list">
|
|
|
+ <div><dt>当前患者</dt><dd>王*明</dd></div>
|
|
|
+ <div><dt>解读目标</dt><dd>血常规报告</dd></div>
|
|
|
+ <div className="pending"><dt>报告来源</dt><dd>待选择</dd></div>
|
|
|
+ </dl>
|
|
|
+ </section>
|
|
|
+ <section className="current-task-card">
|
|
|
+ <span className="step-kicker">当前任务</span>
|
|
|
+ <h3>选择要解读的报告</h3>
|
|
|
+ <p>可以从本院报告中选择,也可以上传图片或 PDF。</p>
|
|
|
+ <button className="primary-button inline-action" type="button" onClick={nextTask}>选择报告 <ChevronRightIcon /></button>
|
|
|
+ </section>
|
|
|
+ </>
|
|
|
) : mode === "tongue" ? (
|
|
|
- <article className="assistant-reply">
|
|
|
- <strong>可以为您开始中医舌诊</strong>
|
|
|
- <p>接下来会通过语音和选项补充体质症候,再拍摄舌象。</p>
|
|
|
- <button className="primary-button inline-action" type="button" onClick={nextTask}>开始评估 <ChevronRightIcon /></button>
|
|
|
- </article>
|
|
|
+ <>
|
|
|
+ <section className="collected-panel">
|
|
|
+ <div className="panel-heading"><span>已确定的信息</span><em>2 项</em></div>
|
|
|
+ <dl className="compact-context-list">
|
|
|
+ <div><dt>当前患者</dt><dd>王*明</dd></div>
|
|
|
+ <div><dt>健康诉求</dt><dd>了解疲倦与体质</dd></div>
|
|
|
+ <div className="pending"><dt>体质症候</dt><dd>待问答</dd></div>
|
|
|
+ </dl>
|
|
|
+ </section>
|
|
|
+ <section className="current-task-card">
|
|
|
+ <span className="step-kicker">当前任务</span>
|
|
|
+ <h3>开始体质与症候问答</h3>
|
|
|
+ <p>约 2 分钟,完成后再进行舌象拍摄。</p>
|
|
|
+ <button className="primary-button inline-action" type="button" onClick={nextTask}>开始评估 <ChevronRightIcon /></button>
|
|
|
+ </section>
|
|
|
+ </>
|
|
|
) : (
|
|
|
<>
|
|
|
- <article className="assistant-reply">
|
|
|
- <strong>{stage === "followup" ? "还需要确认一项信息" : "挂号信息已收集完整"}</strong>
|
|
|
- <p>{stage === "followup" ? "已记录神经内科、明天上午 9 点和专家号。请问您想挂哪位医生?" : "我会按以下条件查询实时号源,查询前不会执行挂号。"}</p>
|
|
|
- </article>
|
|
|
+ <section className="collected-panel">
|
|
|
+ <div className="panel-heading">
|
|
|
+ <span>已收集挂号信息</span>
|
|
|
+ <em>{doctor ? "6/6 完整" : "5/6 已获取"}</em>
|
|
|
+ </div>
|
|
|
+ <dl className="slot-summary conversational">
|
|
|
+ {registrationSlots.map((slot) => (
|
|
|
+ <div className={!slot.value || slot.value === "待补充" ? "pending" : ""} key={slot.key}>
|
|
|
+ <dt>{slot.label}</dt>
|
|
|
+ <dd>
|
|
|
+ <span>{slot.value}</span>
|
|
|
+ {slot.editable ? (
|
|
|
+ <button type="button" onClick={() => setEditingSlot(slot.key)}>修改</button>
|
|
|
+ ) : (
|
|
|
+ <em>{slot.value === "待补充" ? "待补充" : "已确认"}</em>
|
|
|
+ )}
|
|
|
+ </dd>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </dl>
|
|
|
+ {editingSlot && slotOptions[editingSlot] ? (
|
|
|
+ <div className="slot-edit-options">
|
|
|
+ <span>修改{registrationSlots.find((slot) => slot.key === editingSlot)?.label}</span>
|
|
|
+ <div>
|
|
|
+ {slotOptions[editingSlot].map((option) => (
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ key={option}
|
|
|
+ onClick={() => updateSlot(editingSlot as keyof typeof slots, option)}
|
|
|
+ >
|
|
|
+ {option}
|
|
|
+ </button>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ) : null}
|
|
|
+ </section>
|
|
|
|
|
|
{stage === "followup" ? (
|
|
|
- <>
|
|
|
+ <section className="current-task-card">
|
|
|
+ <span className="step-kicker">还差 1 项信息</span>
|
|
|
+ <h3>您希望挂哪位医生?</h3>
|
|
|
+ <p>可以说医生姓名,也可以选择“不限医生”。</p>
|
|
|
<button className="voice-followup" type="button" onClick={() => chooseDoctor("李明 主任医师")}>
|
|
|
<SpeakerLoudIcon />
|
|
|
- <span><strong>语音回答</strong><small>例如:“李明主任”</small></span>
|
|
|
+ <span><strong>按住说话</strong><small>例如:“李明主任”</small></span>
|
|
|
</button>
|
|
|
<div className="doctor-quick-replies">
|
|
|
<button type="button" onClick={() => chooseDoctor("李明 主任医师")}>李明 主任医师</button>
|
|
|
<button type="button" onClick={() => chooseDoctor("王佳 副主任医师")}>王佳 副主任医师</button>
|
|
|
<button type="button" onClick={() => chooseDoctor("不限医生")}>不限医生</button>
|
|
|
</div>
|
|
|
- </>
|
|
|
+ <label className="task-text-fallback">
|
|
|
+ <input
|
|
|
+ value={doctor}
|
|
|
+ onChange={(event) => setDoctor(event.target.value)}
|
|
|
+ placeholder="也可以输入医生姓名"
|
|
|
+ aria-label="输入医生姓名"
|
|
|
+ />
|
|
|
+ <button type="button" onClick={() => chooseDoctor(doctor || "不限医生")}>确认</button>
|
|
|
+ </label>
|
|
|
+ </section>
|
|
|
) : (
|
|
|
<>
|
|
|
- <dl className="slot-summary">
|
|
|
- <div><dt>就诊人</dt><dd>王*明 <em>已获取</em></dd></div>
|
|
|
- <div><dt>科室</dt><dd>神经内科 <em>已获取</em></dd></div>
|
|
|
- <div><dt>日期</dt><dd>明天 07-27 <em>已获取</em></dd></div>
|
|
|
- <div><dt>时间</dt><dd>上午 09:00 <em>已获取</em></dd></div>
|
|
|
- <div><dt>号别</dt><dd>专家号 <em>已获取</em></dd></div>
|
|
|
- <div><dt>医生</dt><dd>{doctor} <em>已获取</em></dd></div>
|
|
|
- </dl>
|
|
|
<section className="risk-check-line"><CheckCircledIcon /> 未识别到急诊红旗,可继续普通挂号</section>
|
|
|
<button className="primary-button inline-action" type="button" onClick={nextTask}>信息已齐全,查询号源 <ChevronRightIcon /></button>
|
|
|
</>
|
|
|
)}
|
|
|
</>
|
|
|
)}
|
|
|
+
|
|
|
+ <details className="task-history">
|
|
|
+ <summary>本次对话与操作记录</summary>
|
|
|
+ <ol>
|
|
|
+ <li>10:12 接收患者语音并转写</li>
|
|
|
+ <li>10:12 提取服务对象与业务参数</li>
|
|
|
+ <li>10:12 完成医疗安全前置筛查</li>
|
|
|
+ {stage === "complete" ? <li>10:13 挂号必填信息收集完成</li> : null}
|
|
|
+ </ol>
|
|
|
+ </details>
|
|
|
</section>
|
|
|
)}
|
|
|
</main>
|