import { applyCandidateSelectionResponseFixture, applyCommandProjectionFixture, applyReportInterpretationProjectionFixture, applyServerProjectionFixture, buildCandidateSelectionInteraction, buildCommandConfirmationRequest, buildCommandRejectionRequest, closeReportView, createInitialState, evaluateReportInterpretationGate, grantConsent, hasValidReportConsent, loadScenario, openReportDetails, rejectConsent, requestReportInterpretation, revokeConsent, selectHomeTasks, selectReports, selectValidReportResult, submitReportFeedback, submitInteraction, } from "./patient-service-desk-v3.state.mjs"; export const navItems = Object.freeze([ { id: "home", label: "首页", icon: "house" }, { id: "care", label: "就医", icon: "stethoscope" }, { id: "assistant", label: "AI助手", icon: "sparkles" }, { id: "records", label: "健康档案", icon: "folder-heart" }, { id: "profile", label: "我的", icon: "user-round" }, ]); export const demoScenarioGroups = Object.freeze({ REPORT_CLOSURE: Object.freeze([ "REPORT_ARRIVED", "REPORT_DETAIL", "REPORT_CONSENT", "REPORT_GENERATING", "REPORT_READY", "REPORT_NEED_REVIEW", "REPORT_CRITICAL", "REPORT_INVALIDATED", ]), CARE_CLOSURE: Object.freeze([ "EMPTY_HOME", "REGISTRATION_QUERY", "COMMAND_PREPARED", "APPOINTMENT_CREATED", "CHECKIN_AVAILABLE", "QUEUE_UPDATED", ]), }); export const demoScenarios = Object.freeze( Object.values(demoScenarioGroups).flat(), ); const scenarioLabels = { EMPTY_HOME: "空闲首页", REPORT_ARRIVED: "报告到达", REPORT_DETAIL: "报告详情", REPORT_CONSENT: "报告授权", REPORT_GENERATING: "解读生成中", REPORT_READY: "报告已解读", REPORT_NEED_REVIEW: "报告需复核", REPORT_CRITICAL: "报告危急值", REPORT_INVALIDATED: "报告已失效", REGISTRATION_QUERY: "自然语言查号", COMMAND_PREPARED: "挂号待确认", APPOINTMENT_CREATED: "挂号成功", CHECKIN_AVAILABLE: "到院可签到", QUEUE_UPDATED: "候诊更新", }; const homeServices = Object.freeze([ { label: "预约挂号", icon: "calendar-plus" }, { label: "智能导诊", icon: "scan-heart" }, { label: "报告查询", icon: "file-chart-column" }, { label: "门诊缴费", icon: "wallet-cards" }, { label: "检查预约", icon: "clipboard-clock" }, { label: "住院服务", icon: "bed" }, { label: "院内导航", icon: "map-pinned" }, { label: "在线问诊", icon: "messages-square" }, ]); const actionLabels = { INTERPRET_REPORT: "AI 解读报告", VIEW_REPORT: "查看原报告", CONFIRM_CHECKIN: "确认签到", VIEW_ROUTE: "查看路线", VIEW_QUEUE: "查看候诊", }; const actionTokens = { INTERPRET_REPORT: "interpret-report", VIEW_REPORT: "view-report", CONFIRM_CHECKIN: "open-checkin-confirmation", VIEW_ROUTE: "open-route", VIEW_QUEUE: "open-queue", }; const statusPresentation = { ACTION_REQUIRED: { className: "action-required", icon: "circle-alert", label: "需要处理", }, IN_PROGRESS: { className: "in-progress", icon: "loader-circle", label: "进行中", }, UPCOMING: { className: "upcoming", icon: "calendar-clock", label: "即将开始", }, COMPLETED: { className: "completed", icon: "circle-check-big", label: "已完成", }, }; const unknownStatusPresentation = { className: "unknown", icon: "circle-help", label: "状态待确认", }; const escapeHtml = (value) => String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); const getActionLabel = (action) => ( typeof action === "string" && Object.hasOwn(actionLabels, action) ? actionLabels[action] : null ); const renderActionAttributes = (action, task) => { const token = typeof action === "string" && Object.hasOwn(actionTokens, action) ? actionTokens[action] : null; if (!token) { return ""; } if (typeof task?.reportId === "string") { return ` data-action="${token}" data-report-id="${escapeHtml(task.reportId)}"`; } if (typeof task?.appointmentId === "string") { return ` data-action="${token}" data-appointment-id="${escapeHtml(task.appointmentId)}"`; } return ""; }; const getStatusPresentation = (status) => ( typeof status === "string" && Object.hasOwn(statusPresentation, status) ? statusPresentation[status] : unknownStatusPresentation ); const getShellElements = (root) => { if (!root?.getElementById) { return null; } const appHeader = root.getElementById("appHeader"); const appView = root.getElementById("appView"); const bottomNav = root.getElementById("bottomNav"); return appHeader && appView && bottomNav ? { appHeader, appView, bottomNav } : null; }; const renderIcons = () => { const lucide = globalThis.lucide; if (lucide?.createIcons) { lucide.createIcons({ attrs: { "aria-hidden": "true" } }); } }; const patientDisplayNames = { "patient-zhang-san": "张先生", "patient-li-na": "李女士", }; const patientDisplayName = (state) => ( patientDisplayNames[state?.activePatientId] ?? "当前就诊人" ); const renderHeader = (state) => { const messages = Array.isArray(state?.messages) ? state.messages : []; const unreadCount = messages.filter((message) => message?.unread).length; const messageLabel = unreadCount > 0 ? `消息,${unreadCount} 条未读` : "消息"; return `

空海医院

当前院区 · 空海医院本院

`; }; const journeyStages = [ { stage: "WAITING_ARRIVAL", label: "到院", time: "09:10" }, { stage: "CHECKIN_AVAILABLE", label: "签到", time: "09:20" }, { stage: "QUEUE_WAITING", label: "候诊", time: "09:30" }, { stage: "VISIT_IN_PROGRESS", label: "医生接诊", time: "10:05" }, { stage: "POST_VISIT", label: "诊后服务", time: "10:25" }, ]; const renderJourneyProjection = (journey) => { const activeIndex = Math.max( 0, journeyStages.findIndex(({ stage }) => stage === journey.stage), ); const start = Math.max( 0, Math.min(activeIndex - 1, journeyStages.length - 3), ); const visibleStages = journeyStages.slice(start, start + 3); return `
${escapeHtml(journey.title)}

${escapeHtml(journey.location ?? journey.room ?? "院内行程持续更新")}

${journey.stage === "QUEUE_WAITING" ? ` ` : ""}
    ${visibleStages.map((node) => `
  1. ${node.label}
  2. `).join("")}
`; }; export const renderHome = (state) => { const tasks = selectHomeTasks(state); const journeys = Array.isArray(state?.journeyProjections) ? state.journeyProjections : []; const primaryTaskIndex = tasks.findIndex( ({ primaryAction, status }) => ( status === "ACTION_REQUIRED" || status === "IN_PROGRESS" ) && Boolean(getActionLabel(primaryAction)), ); const activeTaskCount = tasks.filter( ({ status }) => status === "ACTION_REQUIRED" || status === "IN_PROGRESS", ).length; return `

AI 就医助手

${patientDisplayName(state)},上午好

${activeTaskCount > 0 ? `有 ${activeTaskCount} 项就医事项需要关注` : "今天暂无待处理的就医事项"}

空海医院 AI 医生助手

就医任务

现在要做

${tasks.length === 0 ? `
今天没有待处理事项

新的报告、缴费或预约提醒会显示在这里。

` : tasks.map((task, index) => { const isCompleted = task.status === "COMPLETED"; const status = getStatusPresentation(task.status); const isPrimaryTask = index === primaryTaskIndex; const primaryActionLabel = getActionLabel(task.primaryAction); const secondaryActionLabel = getActionLabel(task.secondaryAction); const canAct = ( task.status === "ACTION_REQUIRED" || task.status === "IN_PROGRESS" ) && Boolean(primaryActionLabel); const recommendation = isCompleted ? "无需操作" : canAct ? primaryActionLabel : task.status === "UPCOMING" ? "等待事项开始" : "确认状态与可用操作"; return `
${status.label}

${escapeHtml(task.title)}

发生了什么:${escapeHtml(task.description)}

为什么关注:${isCompleted ? "该事项已处理完成。" : "及时处理有助于顺利推进后续就医安排。"}

建议操作 ${recommendation}
${canAct ? `
${secondaryActionLabel ? ` ` : ""}
` : ""}
`; }).join("")}

院内进度

今日旅程

${journeys.length > 0 ? journeys.map(renderJourneyProjection).join("") : `
今天暂无院内行程

挂号或检查安排确认后,将展示前后步骤和当前节点。

`}

快捷入口

常用服务

${homeServices.map(({ label, icon }) => ` `).join("")}

健康提醒

主动关怀

暂无新的主动关怀提醒

用药、复诊与检查准备提醒会在确认后展示。

`; }; const candidateToken = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; const renderAssistantMessages = (messages) => ( Array.isArray(messages) ? messages .filter((message) => ( message && typeof message === "object" && (message.role === "user" || message.role === "assistant") )) .map(({ role, text }) => `
${role === "user" ? "你" : "AI 助手"}

${escapeHtml(text)}

`).join("") : "" ); export const renderAssistant = (state) => { const task = state?.activeAgentTaskProjection; const result = typeof task?.activeResultRef === "string" ? state?.resultSnapshotProjections?.[task.activeResultRef] : null; const items = task?.stage === "CANDIDATE_SELECTION" && result?.resultRef === task.activeResultRef && Array.isArray(result?.items) ? result.items : []; const candidates = items.filter((item) => ( item && typeof item === "object" && typeof item.candidateId === "string" && candidateToken.test(item.candidateId) )); const hasTask = task?.type === "REGISTRATION"; return `

自然语言就医服务

AI 助手

${hasTask ? ` ` : ""}

场景建议

${renderAssistantMessages(state?.messages)} ${task?.stage === "REQUERY_REQUIRED" ? ` ` : ""} ${result ? `

可选号源

${escapeHtml(result.text)}

${candidates.length > 0 ? `
${candidates.map((item) => ` `).join("")}
` : ""}
` : ""} ${task?.stage === "AWAITING_REGISTRATION_CONFIRMATION" ? ` ` : ""}
`; }; const reportFamilyLabels = { LAB: "检验报告", EXAM: "检查报告" }; const reportSourceStatusLabels = { FINAL: "已审核", PRELIMINARY: "待审核", }; const reportInterpretationLabels = { NOT_REQUESTED: "尚未进行 AI 辅助解读", INTERPRETING: "AI 辅助解读中", READY: "AI 辅助解读已生成", NEED_REVIEW: "解读结果建议由医生确认", }; const metricFlagLabels = { HIGH: "偏高", LOW: "偏低", NORMAL: "正常" }; const abnormalMetricsOf = (report) => ( Array.isArray(report?.metrics) ? report.metrics.filter((metric) => ["HIGH", "LOW"].includes(metric?.sourceFlag)) : [] ); const renderMetric = (metric) => { const name = escapeHtml(metric?.name || "未命名指标"); const value = metric?.value === null || metric?.value === undefined ? "—" : escapeHtml(metric.value); const unit = metric?.unit ? ` ${escapeHtml(metric.unit)}` : ""; const range = metric?.referenceRange ? `参考范围 ${escapeHtml(metric.referenceRange)}${unit}` : "参考范围未提供"; const flag = metricFlagLabels[metric?.sourceFlag] ?? "结果待确认"; return `
  • ${name}${value}${unit}${range}${flag}
  • `; }; export const renderReportCenter = (state) => { const filters = { family: ["ALL", "LAB", "EXAM"].includes(state?.reportFilters?.family) ? state.reportFilters.family : "ALL", status: ["ALL", "UNREAD", "READ"].includes(state?.reportFilters?.status) ? state.reportFilters.status : "ALL", }; const reports = selectReports(state, filters); return `
    ${pageHeader("健康档案", "报告中心", "医院原始报告和 AI 辅助解读统一查看。")}

    当前就诊人 ${escapeHtml(patientDisplayName(state))}

    ${[["ALL", "全部"], ["LAB", "检验报告"], ["EXAM", "检查报告"]].map(([id, label]) => ` `).join("")}
    ${[["ALL", "全部"], ["UNREAD", "未读"], ["READ", "已读"]].map(([id, label]) => ` `).join("")}
    ${reports.length ? reports.map((report) => { const abnormalCount = abnormalMetricsOf(report).length; return ` `; }).join("") : `
    暂无符合筛选条件的报告

    可以调整报告类型或阅读状态。

    `}
    `; }; export const renderReportDetail = (report = {}, expanded = false) => { const metrics = Array.isArray(report?.metrics) ? report.metrics : []; const abnormalMetrics = abnormalMetricsOf(report); const hiddenMetrics = metrics.filter( (metric) => !abnormalMetrics.includes(metric), ); const visibleMetrics = expanded ? [...abnormalMetrics, ...hiddenMetrics] : abnormalMetrics; const reportId = report?.reportId ?? ""; return `

    医院原始报告

    ${escapeHtml(report?.title || "未命名报告")}

    ${escapeHtml(report?.hospitalName || "医院信息待确认")} · ${escapeHtml(report?.departmentName || "科室待确认")}

    出具时间:${escapeHtml(report?.issuedAt || "待确认")}

    ${escapeHtml(reportSourceStatusLabels[report?.sourceStatus] ?? "报告状态待确认")}

    AI 辅助解读仅供参考,报告结果需结合症状、病史并由医生判断。

    指标概览

    ${abnormalMetrics.length ? `${abnormalMetrics.length} 项异常指标` : "未标记异常指标"}

    ${hiddenMetrics.length ? ` ` : ""}
    `; }; const safeReportText = (value, fallback) => ( typeof value === "string" && value.trim() ? escapeHtml(value.trim()) : fallback ); export const renderExaminationReportDetail = (report = {}) => { const reportId = typeof report?.reportId === "string" ? report.reportId : ""; return `

    医院检查报告

    ${safeReportText(report?.title, "检查名称待确认")}

    检查部位:${safeReportText(report?.bodyPart, "待确认")}

    ${safeReportText(report?.hospitalName, "医院信息待确认")} · ${safeReportText(report?.departmentName, "科室待确认")}

    出具时间:${safeReportText(report?.issuedAt, "待确认")}

    检查所见

    检查所见

    ${safeReportText(report?.findings, "检查所见待确认")}

    报告结论

    报告结论

    ${safeReportText(report?.conclusion, "报告结论待确认")}

    报告医生:${safeReportText(report?.reportingDoctor, "报告医生待确认")}

    审核状态:${safeReportText( report?.reviewStatus, reportSourceStatusLabels[report?.sourceStatus] ?? "审核状态待确认", )}

    该类型暂未开放AI辅助解读

    `; }; export const renderOriginalReport = (report = {}) => { if (report?.family === "EXAM") { return `

    医院检查报告原件

    ${safeReportText(report?.title, "检查名称待确认")}

    ${safeReportText(report?.originalFormat, "图像或PDF")}

    原始图像或PDF由医院报告系统提供,本原型展示统一查看入口。

    `; } const metrics = Array.isArray(report?.metrics) ? report.metrics : []; return `

    医院原始报告

    原始报告内容

    ${escapeHtml(report?.title || "未命名报告")}

    ${escapeHtml(report?.hospitalName || "医院信息待确认")} · ${escapeHtml(report?.departmentName || "科室待确认")}

    出具时间:${escapeHtml(report?.issuedAt || "待确认")}

    `; }; export const renderConsentOverlay = (state = {}) => { const reportId = state?.overlays?.reportId; const report = state?.reportSourceProjections?.[reportId]; return ` `; }; const commandPresentation = { PREPARED: { label: "准备中", icon: "loader-circle", tone: "pending" }, AWAITING_CONFIRMATION: { label: "待确认", icon: "shield-check", tone: "warning" }, EXECUTING: { label: "执行中", icon: "loader-circle", tone: "pending" }, SUCCEEDED: { label: "挂号成功", icon: "circle-check-big", tone: "success" }, FAILED: { label: "执行失败", icon: "circle-x", tone: "danger" }, EXPIRED: { label: "已过期", icon: "clock-alert", tone: "danger" }, UNKNOWN: { label: "结果确认中,请勿重复操作", icon: "circle-help", tone: "warning" }, REJECTED: { label: "已暂不操作", icon: "circle-minus", tone: "muted" }, }; export const renderCommandOverlay = (command) => { const presentation = commandPresentation[command?.status] ?? commandPresentation.PREPARED; const details = command?.details ?? {}; const statusLabel = command?.status === "SUCCEEDED" && !details.patientName ? "模拟完成" : presentation.label; const awaiting = command?.status === "AWAITING_CONFIRMATION"; const canRequery = command?.status === "EXPIRED" || command?.status === "FAILED"; const canClose = command?.status === "SUCCEEDED" || command?.status === "REJECTED"; return ` `; }; export const renderReportInterpreting = () => `

    AI 辅助解释

    正在整理报告解读

    正在结合参考范围整理便于理解的信息,请稍候。结果仅供辅助理解,不代表诊断结论。

    分析仍在进行,暂不提供确定性结论
    `; const reportUnavailablePresentation = Object.freeze({ OCR_REVIEW_REQUIRED: Object.freeze({ title: "当前报告暂不适合自动解读", description: "报告内容需要人工核对,您仍可查看医院原始报告。", actions: Object.freeze([ Object.freeze({ action: "view-original-report", label: "查看原始报告", tone: "primary", }), Object.freeze({ action: "contact-human-service", label: "联系人工服务", tone: "secondary", }), ]), }), CRITICAL_NOTICE: Object.freeze({ title: "报告含医院危急提示", description: "请立即按医院通知联系医务人员,并优先遵循医院既有处置流程。", actions: Object.freeze([ Object.freeze({ action: "contact-human-service", label: "立即联系人工服务", tone: "primary", }), Object.freeze({ action: "view-original-report", label: "查看原始报告", tone: "secondary", }), ]), }), AI_UNAVAILABLE: Object.freeze({ title: "AI辅助解读暂不可用", description: "医院原始报告仍可正常查看,请稍后重试。", }), REPORT_INVALIDATED: Object.freeze({ title: "医院已更新或撤回报告", description: "原AI辅助解释已失效。最新版本尚未同步完成,请返回报告中心查看状态。", actions: Object.freeze([ Object.freeze({ action: "open-report-center", label: "查看报告状态", tone: "primary", }), Object.freeze({ action: "contact-human-service", label: "联系人工服务", tone: "secondary", }), ]), }), UNSUPPORTED: Object.freeze({ title: "暂不支持自动解读", description: "您可以查看原始报告或联系人工服务。", }), }); export const renderReportUnavailable = ({ reason, reportId, serviceNotice, } = {}) => { const presentation = Object.hasOwn(reportUnavailablePresentation, reason) ? reportUnavailablePresentation[reason] : reportUnavailablePresentation.UNSUPPORTED; const safeReportId = typeof reportId === "string" && /^[A-Za-z0-9._-]+$/.test(reportId) ? reportId : ""; const actions = presentation.actions ?? [ { action: "view-original-report", label: presentation.primaryActionLabel ?? "查看原始报告", tone: "primary", }, { action: "contact-human-service", label: "联系人工服务", tone: "secondary", }, ]; return `

    ${escapeHtml(presentation.title)}

    ${escapeHtml(presentation.description)}

    ${typeof serviceNotice === "string" && serviceNotice ? `

    ${escapeHtml(serviceNotice)}

    ` : ""}
    ${actions.map(({ action, label, tone }) => ` `).join("")}
    `; }; export const renderReportServiceOverlay = (state = {}) => { const reason = state?.overlays?.reportUnavailableReason; const presentation = Object.hasOwn(reportUnavailablePresentation, reason) ? reportUnavailablePresentation[reason] : reportUnavailablePresentation.UNSUPPORTED; return ` `; }; export const renderReportInterpretationResult = (result, options = {}) => { const interpretation = result?.interpretation; if ( !interpretation || typeof interpretation !== "object" || Array.isArray(interpretation) ) { return ""; } const highlights = Array.isArray(interpretation.highlights) ? interpretation.highlights : []; const explanations = Array.isArray(interpretation.explanations) ? interpretation.explanations : []; const commonFactors = Array.isArray(interpretation.commonFactors) ? interpretation.commonFactors : explanations.map((item) => item?.commonFactors).filter(Boolean); const canDo = Array.isArray(interpretation.canDo) ? interpretation.canDo : []; const evidence = Array.isArray(interpretation.evidence) ? interpretation.evidence : []; const reportId = typeof result?.reportId === "string" ? result.reportId : ""; const reportAction = (action) => ( `data-action="${action}" data-report-id="${escapeHtml(reportId)}"` ); const serviceNotice = typeof options?.serviceNotice === "string" ? options.serviceNotice : ""; const feedbackCategories = [ ["HELPFUL", "有帮助"], ["METRIC_INACCURATE", "指标解释不准确"], ["HARD_TO_UNDERSTAND", "内容难理解"], ["DIFFERS_FROM_DOCTOR", "与医生说明不一致"], ["OTHER", "其他问题"], ]; return `

    AI 辅助解释 · 结果版本 ${escapeHtml(result.resultVersion)}

    结果概括

    ${escapeHtml(interpretation.headline)}

    ${escapeHtml(interpretation.summary)}

    安全提示

    ${escapeHtml(interpretation.safety)}

    需要关注

    指标解释

    ${explanations.map((item) => `

    ${escapeHtml(item.indicator)}

    ${escapeHtml(item.explanation)}

    `).join("")}

    常见相关因素

    上述因素只是帮助理解指标的常见情况,不代表你患有相应疾病,也不能作为诊断结果。

    可以怎么做

    内容依据

    结果版本 ${escapeHtml(result.resultVersion)} · 对应当前医院原始报告

    ${reportId ? `

    需要进一步帮助

    ${serviceNotice ? `

    ${escapeHtml(serviceNotice)}

    ` : ""}
    ` : ""} ${reportId && options?.feedbackOpen ? `

    这次解释对你有帮助吗?

    请选择最符合的一项,反馈仅用于改进本次辅助解释。

    ` : ""} ${options?.feedbackSubmitted ? `

    感谢反馈,已记录你的选择。

    ` : ""}
    `; }; export const renderQueueStatus = (state, appointmentId) => { const queue = (Array.isArray(state?.patientWorkItems) ? state.patientWorkItems : []).find((item) => ( item?.type === "WAITING_QUEUE" && item.appointmentId === appointmentId )); if (!queue) { return `

    候诊信息待更新

    完成签到后,排队号和预计时间会显示在这里。

    `; } const updatedAt = typeof queue.updatedAt === "string" ? queue.updatedAt.slice(11, 16) : "待更新"; return `

    神经内科 · 实时候诊

    排队号 ${escapeHtml(queue.queueNumber ?? "待更新")}

    ${escapeHtml(queue.room ?? "诊室待分配")}

    ${queue.delayed ? `
    候诊时间可能延后,请留意叫号更新。
    ` : ""}
    前方人数前方 ${escapeHtml(queue.peopleAhead ?? 0)} 人
    预计时间预计等待 ${escapeHtml(queue.estimatedMinutes ?? 0)} 分钟

    当前诊室${escapeHtml(queue.room ?? "待分配")}

    队列更新时间${escapeHtml(updatedAt)}

    `; }; export const renderRouteView = (location) => `

    院内路线

    ${escapeHtml(location ?? "门诊三楼神经内科诊区")}

    从门诊大厅乘 2 号电梯至三楼,出电梯后右转约 30 米。

    预计步行4 分钟

    无障碍路线可用

    `; const pageHeader = (eyebrow, title, description) => `

    ${eyebrow}

    ${title}

    ${description}

    `; const careGroupDefinitions = [ { title: "待办理", statuses: ["ACTION_REQUIRED", "UPCOMING"], fallback: "门诊费用待确认", }, { title: "进行中", statuses: ["IN_PROGRESS"], fallback: "今日就医旅程", }, { title: "已完成", statuses: ["COMPLETED"], fallback: "身份信息已核验", }, { title: "需要人工处理", statuses: ["MANUAL_REQUIRED"], fallback: "人工服务随时可用", }, ]; export const renderCareOverview = (state) => { const items = Array.isArray(state?.patientWorkItems) ? state.patientWorkItems.filter((item) => item && typeof item === "object") : []; return `
    ${pageHeader("就医总览", "我的就医", "按办理状态集中查看门诊、检查和诊后事项。")}
    ${careGroupDefinitions.map((group) => { const matches = items.filter((item) => ( group.statuses.includes(item.status) )); const visible = matches.length > 0 ? matches : [{ title: group.fallback, description: "进入详情查看下一步" }]; return `

    ${group.title}

    ${visible.length}
    ${visible.slice(0, 3).map((item) => `
    ${escapeHtml(item.title)}

    ${escapeHtml(item.description ?? "状态和可用操作持续更新")}

    `).join("")}
    `; }).join("")}
    `; }; const recordEntries = [ { id: "lab-reports", label: "检验报告", meta: "血常规 · 3 项异常", filter: "abnormal", icon: "flask-conical", action: "open-report-center", reportFamily: "LAB" }, { id: "exam-reports", label: "检查报告", meta: "影像与超声报告", filter: "all", icon: "scan-line", action: "open-report-center", reportFamily: "EXAM" }, { id: "visits", label: "门诊记录", meta: "历次接诊与诊后记录", filter: "all", icon: "notebook-tabs" }, { id: "medication", label: "处方与用药", meta: "处方、用药提醒", filter: "all", icon: "pill" }, { id: "allergies", label: "过敏史", meta: "暂未记录药物过敏", filter: "all", icon: "shield-alert" }, { id: "consents", label: "授权记录", meta: "查看和撤回数据授权", filter: "unread", icon: "file-lock-2" }, ]; export const renderRecords = (state) => { const activeFilter = ["all", "abnormal", "unread"].includes( state?.recordsFilter, ) ? state.recordsFilter : "all"; const visible = activeFilter === "all" ? recordEntries : recordEntries.filter((entry) => entry.filter === activeFilter); return `
    ${pageHeader("健康档案", "我的健康记录", "报告、门诊记录和授权信息统一归档。")}
    ${[ ["all", "全部"], ["abnormal", "异常"], ["unread", "未解读"], ].map(([id, label]) => ` `).join("")}
    ${visible.map((entry) => ` <${entry.action ? "button" : "article"} class="compact-row"${entry.action ? ` type="button" data-action="${entry.action}" data-filter="${entry.reportFamily}"` : ""}>
    ${entry.label}

    ${entry.meta}

    `).join("")}
    `; }; const profileEntries = [ ["就诊人管理", "users-round"], ["授权中心", "shield-check"], ["消息设置", "bell-ring"], ["常用院区", "hospital"], ["医保和支付", "badge-cent"], ["电子票据", "receipt-text"], ["人工客服", "headphones"], ["关于 AI", "sparkles"], ]; export const renderProfile = () => `
    ${pageHeader("个人中心", "我的", "管理就诊人、隐私授权和常用服务设置。")}
    ${profileEntries.map(([label, icon]) => ` `).join("")}
    `; const messageCategories = ["报告", "就医", "缴费", "随访", "系统"]; export const renderMessages = (state) => { const messages = Array.isArray(state?.messages) ? state.messages.filter((message) => message && typeof message === "object") : []; return `
    ${pageHeader("消息中心", "消息", "按服务类型定位到对应就医事项,不会直接执行办理操作。")}
    ${messageCategories.map((category) => `${category}`).join("")}
    ${messages.length > 0 ? messages.map((message) => ` `).join("") : `
    暂无新消息

    新的报告和就医提醒会显示在这里。

    `}
    `; }; export const renderSystemState = (type, state) => { const cachedTaskCount = Array.isArray(state?.patientWorkItems) ? state.patientWorkItems.length : 0; const definitions = { INITIAL_LOADING: ["正在加载患者服务台", "正在同步最新就医事项,请稍候。", "loader-circle"], EMPTY: ["暂无相关内容", "可以返回首页或尝试其他服务。", "inbox"], OFFLINE: ["网络连接不可用", `已保留 ${cachedTaskCount} 项本地任务,联网后将自动更新。`, "wifi-off"], DEGRADED: ["部分智能服务暂不可用", "基础就医事项仍可查看,也可以联系人工服务。", "shield-alert"], QUERY_FAILED: ["查询失败", "请稍后重新查询,旧结果不会用于继续办理。", "search-x"], COMMAND_FAILED: ["办理失败", "本次操作未完成,请重新查询或联系人工服务。", "circle-x"], COMMAND_UNKNOWN: ["结果确认中,请勿重复操作", "正在核验医院系统结果,可以稍后查看状态。", "circle-ellipsis"], }; const [title, description, icon] = definitions[type] ?? definitions.EMPTY; return `

    ${title}

    ${description}

    `; }; export const renderScenarioPanel = (state) => { const activeScenario = demoScenarios.includes(state?.demoScenario) ? state.demoScenario : "EMPTY_HOME"; const activeTask = state?.activeAgentTaskProjection; const commands = Object.values(state?.commandProjections ?? {}); const latestFixture = state?.appliedProjectionFixtureIds?.at?.(-1) ?? "无"; const renderScenarioGroup = (groupId, title, scenarios) => `

    ${title}

    ${scenarios.map((scenario) => ` `).join("")}
    `; return `

    方案演示

    ${escapeHtml(scenarioLabels[activeScenario])}
    ${renderScenarioGroup( "reportClosureScenarios", "报告闭环", demoScenarioGroups.REPORT_CLOSURE, )} ${renderScenarioGroup( "careClosureScenarios", "就医闭环", demoScenarioGroups.CARE_CLOSURE, )}
    当前 fixture
    ${escapeHtml(latestFixture)}
    Agent task
    ${escapeHtml(activeTask ? `${activeTask.type}/${activeTask.stage}` : "无")}
    PatientWorkItem
    ${escapeHtml(state?.patientWorkItems?.length ?? 0)} 项
    Command
    ${escapeHtml(commands.map((item) => `${item.commandId}/${item.status}`).join(", ") || "无")}

    ←/→ 切换 · R 重置 · P 显示/隐藏

    `; }; const renderCurrentView = (state) => { const { reportId, reportView } = state?.overlays ?? {}; if (state?.systemState === "AI_INTERPRETING") { return renderReportInterpreting(); } if (state?.systemState) { return renderSystemState(state.systemState, state); } if (state?.overlays?.journeyView === "CHECKIN_CONFIRMATION") { return `

    到院签到

    请确认本人已到达诊区

    签到需要患者明确确认;本页面不会根据位置自动完成签到。

    确认后仍以医院签到接口返回结果为准。

    `; } if (state?.overlays?.journeyView === "ROUTE") { const journey = state.journeyProjections?.find( (item) => item?.appointmentId === state.overlays.appointmentId, ); const checkin = state.patientWorkItems?.find( (item) => ( item?.type === "CHECK_IN" && item.appointmentId === state.overlays.appointmentId ), ); return renderRouteView( journey?.location ?? checkin?.location ?? journey?.room, ); } if ( state?.overlays?.journeyView === "QUEUE" && state?.overlays?.appointmentId ) { return renderQueueStatus(state, state.overlays.appointmentId); } if (reportView === "REPORT_DETAIL" && reportId) { const report = state.reportSourceProjections?.[reportId]; return report?.family === "EXAM" ? renderExaminationReportDetail(report) : renderReportDetail(report, Boolean(state.overlays.reportExpanded)); } if (reportView === "ORIGINAL_REPORT" && reportId) { return renderOriginalReport(state.reportSourceProjections?.[reportId]); } if (reportView === "REPORT_UNAVAILABLE" && reportId) { return renderReportUnavailable({ reason: state?.overlays?.reportUnavailableReason, reportId, serviceNotice: state?.overlays?.reportServiceNotice, }); } if (reportView === "INTERPRETING" && reportId) { return renderReportInterpreting(); } if ( reportView === "INTERPRETED" && hasValidReportConsent(state, reportId) ) { return renderReportInterpretationResult( selectValidReportResult(state, reportId), { feedbackOpen: state?.overlays?.reportFeedbackOpen === true, feedbackSubmitted: state?.overlays?.reportFeedbackSubmitted === true, serviceNotice: state?.overlays?.reportServiceNotice, }, ); } if (state?.activeTab === "assistant") { return renderAssistant(state); } if (state?.activeTab === "care") { return renderCareOverview(state); } if (state?.activeTab === "records") { return state?.overlays?.reportView === "REPORT_CENTER" ? renderReportCenter(state) : renderRecords(state); } if (state?.activeTab === "profile") { return renderProfile(state); } if (state?.activeTab === "messages") { return renderMessages(state); } return renderHome(state); }; let modalOverlayIdentity = null; let modalOverlayDocument = null; let consentFocusReturn = null; let scenarioPanelVisible = true; let demoViewport = "390"; const hasConsentDialog = (state) => ( typeof state?.overlays?.consentId === "string" && state.overlays.consentId.startsWith("consent-ai-") ); const hasCommandDialog = (state) => Boolean( state?.overlays?.commandId && state?.commandProjections?.[state.overlays.commandId] ?.status === "AWAITING_CONFIRMATION" ); const hasReportServiceDialog = (state) => ( state?.overlays?.reportView === "REPORT_UNAVAILABLE" && state?.overlays?.reportServiceNotice === "已为你打开人工服务入口。" ); const focusModalDialog = (overlayRoot) => { const dialog = overlayRoot?.querySelector?.('[role="dialog"]'); const firstButton = dialog?.querySelector?.("button"); (firstButton ?? dialog)?.focus?.(); }; const restoreConsentFocus = (root, elements) => { const matchingTrigger = Array.from( root?.querySelectorAll?.('[data-action="interpret-report"]') ?? [], ).find((trigger) => ( trigger.dataset?.reportId === consentFocusReturn?.reportId )); const fallback = elements.appView.querySelector?.( "button, [href], [tabindex]", ) ?? elements.appView; (matchingTrigger ?? fallback)?.focus?.(); consentFocusReturn = null; }; export const renderAppShell = (state = createInitialState()) => { const root = globalThis.document; const elements = getShellElements(root); if (!elements) { return false; } if (root !== modalOverlayDocument) { modalOverlayIdentity = null; modalOverlayDocument = root; } elements.appHeader.innerHTML = renderHeader(state); elements.appView.innerHTML = renderCurrentView(state); elements.bottomNav.innerHTML = navItems.map( ({ id, label, icon }) => ` `, ).join(""); const scenarioPanel = root?.getElementById?.("scenarioPanel"); const phoneShell = root?.querySelector?.(".phone-shell"); if (scenarioPanel) { scenarioPanel.innerHTML = renderScenarioPanel(state); scenarioPanel.hidden = !scenarioPanelVisible; } if (phoneShell?.dataset) { phoneShell.dataset.viewport = demoViewport; } const overlayRoot = root?.getElementById?.("overlayRoot"); const hasConsentOverlay = Boolean(overlayRoot) && hasConsentDialog(state); const activeCommand = state?.overlays?.commandId ? state?.commandProjections?.[state.overlays.commandId] : null; const hasCommandOverlay = Boolean(overlayRoot) && hasCommandDialog(state); const hasReportServiceOverlay = Boolean(overlayRoot) && hasReportServiceDialog(state); const nextModalIdentity = hasConsentOverlay ? `consent:${state.overlays.consentId}` : hasCommandOverlay ? `command:${activeCommand.commandId}:${activeCommand.status}` : hasReportServiceOverlay ? `report-service:${state.overlays.reportId}` : null; const hasModalOverlay = Boolean(nextModalIdentity); for (const element of [ elements.appHeader, elements.appView, elements.bottomNav, scenarioPanel, ]) { if (!element) { continue; } element.inert = hasModalOverlay; element.setAttribute?.( "aria-hidden", hasModalOverlay ? "true" : "false", ); } if (overlayRoot) { overlayRoot.innerHTML = hasConsentOverlay ? renderConsentOverlay(state) : hasCommandOverlay ? renderCommandOverlay(activeCommand) : hasReportServiceOverlay ? renderReportServiceOverlay(state) : ""; if (nextModalIdentity && nextModalIdentity !== modalOverlayIdentity) { focusModalDialog(overlayRoot); } else if (!nextModalIdentity && modalOverlayIdentity) { restoreConsentFocus(root, elements); } } modalOverlayIdentity = nextModalIdentity; renderIcons(); return true; }; let demoState = loadScenario(createInitialState(), "EMPTY_HOME"); const boundDocuments = new WeakSet(); let reportProjectionGeneration = 0; let activeReportProjectionTimer = null; let registrationSelectionFixtureSequence = 0; let commandConfirmationInFlight = null; let lastCommandConfirmationRequest = null; let commandRejectionInFlight = null; let lastCommandRejectionRequest = null; export const getDemoState = () => demoState; export const getLastCommandConfirmationRequest = () => ( lastCommandConfirmationRequest ); export const getLastCommandRejectionRequest = () => ( lastCommandRejectionRequest ); const simulateCandidateSelectionBackendFixture = (state, interaction) => { registrationSelectionFixtureSequence += 1; const task = state?.activeAgentTaskProjection; const result = task?.activeResultRef ? state?.resultSnapshotProjections?.[task.activeResultRef] : null; const now = Date.parse(state?.demoNow); const expiresAt = Date.parse(result?.expiresAt); const versionMatched = interaction?.resultRef === result?.resultRef && interaction?.resultVersion === result?.resultVersion; const notExpired = Number.isFinite(now) && Number.isFinite(expiresAt) && expiresAt > now; const realtimeAvailable = Array.isArray(result?.items) && result.items.some( (item) => item?.candidateId === interaction?.candidateId, ); const errorCode = !notExpired ? "RESULT_EXPIRED" : !versionMatched ? "RESULT_VERSION_CONFLICT" : !realtimeAvailable ? "SLOT_UNAVAILABLE" : null; const fixtureId = `selection-response-${registrationSelectionFixtureSequence}`; if (errorCode) { return { id: fixtureId, errorCode, validation: { tenantMatched: true, patientMatched: true, sessionMatched: true, versionMatched, notExpired, realtimeAvailable, l1LockSucceeded: false, }, }; } const lockExpiresAt = new Date( Math.min(now + 5 * 60 * 1000, expiresAt - 1), ).toISOString(); return { id: fixtureId, outcome: "SUCCESS", ...interaction, validation: { tenantMatched: true, patientMatched: true, sessionMatched: true, versionMatched, notExpired, realtimeAvailable, l1LockSucceeded: true, }, lock: { lockId: `appointment-lock-${registrationSelectionFixtureSequence}`, expiresAt: lockExpiresAt, }, }; }; const submitAssistantText = (state, text) => { const normalizedText = typeof text === "string" ? text.trim() : text; const interaction = buildCandidateSelectionInteraction( state, normalizedText, ); if ( !interaction && !( normalizedText === "选第一个" && state?.activeAgentTaskProjection?.type === "REGISTRATION" ) ) { return submitInteraction(state, normalizedText); } const responseFixture = simulateCandidateSelectionBackendFixture( state, interaction, ); const selectedState = applyCandidateSelectionResponseFixture( state, responseFixture, ); return applyRegistrationCommandBackendFixture(selectedState); }; const applyRegistrationCommandBackendFixture = (state) => { const task = state?.activeAgentTaskProjection; const result = state?.resultSnapshotProjections?.[task?.activeResultRef]; const selected = result?.items?.find( (item) => item?.candidateId === task?.selectedCandidateId, ); if (task?.stage !== "AWAITING_REGISTRATION_CONFIRMATION" || !selected) { return state; } const commands = Object.values(state?.commandProjections ?? {}); const nonTerminalStatuses = new Set([ "PREPARED", "AWAITING_CONFIRMATION", "EXECUTING", "UNKNOWN", ]); const existing = commands.find((command) => ( command?.details?.candidateId === selected.candidateId && nonTerminalStatuses.has(command.status) )); if (existing) { return { ...state, overlays: { ...state.overlays, commandId: existing.commandId }, }; } const nextSequence = commands.reduce((highest, command) => { const sequence = Number.parseInt( command?.commandId?.match(/^command-register-(\d+)$/)?.[1], 10, ); return Number.isInteger(sequence) ? Math.max(highest, sequence) : highest; }, 0) + 1; const commandId = `command-register-${String(nextSequence).padStart(3, "0")}`; return applyCommandProjectionFixture(state, { id: `command-awaiting-${commandId}`, commandId, version: 1, riskLevel: "L2", status: "AWAITING_CONFIRMATION", details: { operationName: "确认挂号", patientName: "张先生", departmentName: selected.departmentName, doctorName: selected.doctorName, doctorTitle: selected.doctorTitle, visitAt: `${selected.visitDate} ${selected.visitTime}`, fee: selected.fee, expiresAt: task.lock?.expiresAt, countdown: "剩余 5 分钟", candidateId: selected.candidateId, }, }); }; const reduceDemoAction = ( state, action, reportId, candidateId, assistantText, appointmentId, tabId, filter, messageId, scenarioName, viewport, ) => { switch (action) { case "view-report": return openReportDetails(state, reportId); case "view-original-report": return state.reportSourceProjections?.[reportId] ? { ...state, overlays: { ...state.overlays, reportId, reportView: "ORIGINAL_REPORT", }, } : state; case "open-report-center": { const reportCenterState = closeReportView(state); return { ...reportCenterState, activeTab: "records", reportFilters: { ...reportCenterState.reportFilters, family: ["LAB", "EXAM"].includes(filter) ? filter : "ALL", }, overlays: { ...reportCenterState.overlays, reportView: "REPORT_CENTER", }, }; } case "set-report-family-filter": return { ...state, activeTab: "records", reportFilters: { ...state.reportFilters, family: ["ALL", "LAB", "EXAM"].includes(filter) ? filter : "ALL", }, }; case "set-report-status-filter": return { ...state, activeTab: "records", reportFilters: { ...state.reportFilters, status: ["ALL", "UNREAD", "READ"].includes(filter) ? filter : "ALL", }, }; case "toggle-report-metrics": return { ...state, overlays: { ...state.overlays, reportExpanded: !state.overlays?.reportExpanded, }, }; case "interpret-report": return requestReportInterpretation(state, reportId); case "grant-report-consent": return grantConsent(state, state?.overlays?.consentId); case "reject-report-consent": return rejectConsent(state, state?.overlays?.consentId); case "open-report-feedback": return ( reportId === state?.overlays?.reportId && state?.overlays?.reportView === "INTERPRETED" && hasValidReportConsent(state, reportId) && selectValidReportResult(state, reportId) ) ? { ...state, overlays: { ...state.overlays, reportFeedbackOpen: true, reportFeedbackSubmitted: false, reportServiceNotice: null, }, } : state; case "submit-report-feedback": { if ( reportId !== state?.overlays?.reportId || state?.overlays?.reportView !== "INTERPRETED" ) { return state; } const feedbackState = submitReportFeedback(state, { reportId, category: filter, }); return feedbackState === state ? state : { ...feedbackState, overlays: { ...feedbackState.overlays, reportFeedbackOpen: false, reportFeedbackSubmitted: true, reportServiceNotice: null, }, }; } case "consult-ordering-doctor": case "online-consultation": case "contact-human-service": { if ( action === "contact-human-service" && reportId === state?.overlays?.reportId && state?.overlays?.reportView === "REPORT_UNAVAILABLE" && state?.reportSourceProjections?.[reportId] ) { return { ...state, overlays: { ...state.overlays, reportServiceNotice: "已为你打开人工服务入口。", }, }; } if ( reportId !== state?.overlays?.reportId || state?.overlays?.reportView !== "INTERPRETED" || !hasValidReportConsent(state, reportId) || !selectValidReportResult(state, reportId) ) { return state; } const notices = { "consult-ordering-doctor": "已为你打开开单医生咨询入口。", "online-consultation": "已为你打开在线咨询入口。", "contact-human-service": "已为你打开人工服务入口。", }; return { ...state, overlays: { ...state.overlays, reportFeedbackOpen: false, reportServiceNotice: notices[action], }, }; } case "close-report-service": return hasReportServiceDialog(state) ? { ...state, overlays: { ...state.overlays, reportServiceNotice: null, }, } : state; case "revoke-report-consent": { const activeReportId = state?.overlays?.reportId; const activeConsentId = typeof activeReportId === "string" ? `consent-ai-${activeReportId}` : Object.keys(state?.consents ?? {}).find( (consentId) => state.consents[consentId]?.status === "GRANTED", ); return revokeConsent(state, activeConsentId); } case "go-home": { const homeState = closeReportView(state); return { ...homeState, activeTab: "home", overlays: { ...homeState.overlays, appointmentId: null, journeyView: null, }, }; } case "open-checkin-confirmation": return { ...state, overlays: { ...state.overlays, appointmentId, journeyView: "CHECKIN_CONFIRMATION", }, }; case "open-queue": return { ...state, overlays: { ...state.overlays, appointmentId, journeyView: "QUEUE", }, }; case "open-route": return { ...state, overlays: { ...state.overlays, appointmentId, journeyView: "ROUTE", }, }; case "switch-patient": { commandConfirmationInFlight = null; commandRejectionInFlight = null; invalidateReportProjectionTimer(); const nextPatientId = state.activePatientId === "patient-li-na" ? "patient-zhang-san" : "patient-li-na"; return { ...createInitialState({ activePatientId: nextPatientId }), demoScenario: "EMPTY_HOME", }; } case "submit-checkin": { const checkedIn = applyServerProjectionFixture(state, { id: `checkin-completed-${appointmentId}`, type: "CHECKIN_COMPLETED", occurredAt: state.demoNow, payload: { appointmentId, queueNumber: "A023", room: "3F-08诊室", }, }); return { ...checkedIn, overlays: { ...checkedIn.overlays, appointmentId, journeyView: "QUEUE", }, }; } case "open-assistant": case "return-current-task": return { ...closeReportView(state), activeTab: "assistant", }; case "navigate-tab": { const tabState = closeReportView(state); return { ...tabState, activeTab: navItems.some((item) => item.id === tabId) ? tabId : "home", systemState: null, overlays: { ...tabState.overlays, appointmentId: null, journeyView: null, }, }; } case "open-messages": return { ...closeReportView(state), activeTab: "messages", systemState: null, }; case "set-record-filter": return { ...state, activeTab: "records", recordsFilter: ["all", "abnormal", "unread"].includes(filter) ? filter : "all", }; case "open-message": { const message = (Array.isArray(state.messages) ? state.messages : []) .find((item) => item?.id === messageId); if (message?.type === "REPORT") { const reportId = typeof message.reportId === "string" ? message.reportId : null; return reportId ? openReportDetails(state, reportId) : state; } if (message?.type === "JOURNEY") { const journey = state.journeyProjections?.[0]; return journey ? { ...state, activeTab: journey.stage === "QUEUE_WAITING" ? state.activeTab : "care", overlays: { ...state.overlays, appointmentId: journey.appointmentId, journeyView: journey.stage === "QUEUE_WAITING" ? "QUEUE" : null, }, } : state; } return state; } case "load-scenario": case "reset-scenario": { commandConfirmationInFlight = null; commandRejectionInFlight = null; invalidateReportProjectionTimer(); return loadScenario( state, action === "reset-scenario" ? state.demoScenario ?? "EMPTY_HOME" : scenarioName, ); } case "previous-scenario": case "next-scenario": { const currentIndex = Math.max( 0, demoScenarios.indexOf(state.demoScenario), ); const delta = action === "previous-scenario" ? -1 : 1; const nextIndex = ( currentIndex + delta + demoScenarios.length ) % demoScenarios.length; commandConfirmationInFlight = null; commandRejectionInFlight = null; invalidateReportProjectionTimer(); return loadScenario(state, demoScenarios[nextIndex]); } case "toggle-scenario-panel": scenarioPanelVisible = !scenarioPanelVisible; return state; case "set-demo-viewport": demoViewport = viewport === "430" ? "430" : "390"; return state; case "submit-registration-demo": return submitAssistantText( state, "我想挂明天神经内科李明主任的号", ); case "submit-assistant-text": return submitAssistantText(state, assistantText); case "select-candidate": { const interaction = buildCandidateSelectionInteraction( state, candidateId, ); return applyRegistrationCommandBackendFixture( applyCandidateSelectionResponseFixture( state, simulateCandidateSelectionBackendFixture(state, interaction), ), ); } case "close-command": return { ...state, activeTab: "home", overlays: { ...state.overlays, commandId: null }, }; case "requery-registration": commandConfirmationInFlight = null; return submitInteraction({ ...state, overlays: { ...state.overlays, commandId: null }, }, "我想挂明天神经内科李明主任的号"); default: return state; } }; const selectInterpretingReportId = (state) => { const reportId = state?.overlays?.reportId; const hasInterpretingItem = state?.patientWorkItems?.some( (item) => item?.type === "REPORT_REVIEW" && item.reportId === reportId && item.stage === "INTERPRETING", ); return typeof reportId === "string" && state?.overlays?.reportView === "INTERPRETING" && evaluateReportInterpretationGate(state, reportId).allowed && state?.reportSourceProjections?.[reportId]?.interpretationStatus === "GENERATING" && hasValidReportConsent(state, reportId) && hasInterpretingItem ? reportId : null; }; const invalidateReportProjectionTimer = () => { activeReportProjectionTimer = null; reportProjectionGeneration += 1; }; const ensureReportInterpretationProjection = () => { if (demoState?.demoScenario === "REPORT_GENERATING") { invalidateReportProjectionTimer(); return; } const reportId = selectInterpretingReportId(demoState); if (!reportId) { invalidateReportProjectionTimer(); return; } if ( activeReportProjectionTimer?.reportId === reportId && activeReportProjectionTimer.generation === reportProjectionGeneration ) { return; } if (typeof globalThis.setTimeout !== "function") { return; } const scheduledGeneration = reportProjectionGeneration; const consentId = `consent-ai-${reportId}`; const timerToken = { generation: scheduledGeneration, reportId, patientId: demoState?.activePatientId, stateContext: demoState, consentContext: demoState?.consents?.[consentId], bindingContext: demoState?.consentScopeBindings, }; activeReportProjectionTimer = timerToken; globalThis.setTimeout(() => { if (activeReportProjectionTimer !== timerToken) { return; } activeReportProjectionTimer = null; const isCurrentInterpretation = ( reportProjectionGeneration === scheduledGeneration && selectInterpretingReportId(demoState) === reportId && demoState?.activePatientId === timerToken.patientId && demoState === timerToken.stateContext && demoState?.consents?.[consentId] === timerToken.consentContext && demoState?.consentScopeBindings === timerToken.bindingContext ); if (!isCurrentInterpretation) { return; } demoState = applyReportInterpretationProjectionFixture( demoState, reportId, ); renderAppShell(demoState); }, 120); }; const beginCommandConfirmation = () => { const commandId = demoState?.overlays?.commandId; if (commandConfirmationInFlight || commandRejectionInFlight || !commandId) { return; } lastCommandConfirmationRequest = buildCommandConfirmationRequest( demoState, commandId, { channel: "CARD", idempotencyKey: `confirm-${commandId.replace(/^command-/, "")}`, }, ); const token = { commandId, request: lastCommandConfirmationRequest }; commandConfirmationInFlight = token; if (typeof globalThis.setTimeout !== "function") { return; } globalThis.setTimeout(() => { if ( commandConfirmationInFlight !== token || demoState?.commandProjections?.[commandId]?.status !== "AWAITING_CONFIRMATION" ) { return; } demoState = applyCommandProjectionFixture(demoState, { id: `command-executing-${commandId}`, commandId, version: demoState.commandProjections[commandId].version + 1, status: "EXECUTING", }); renderAppShell(demoState); globalThis.setTimeout(() => { if ( commandConfirmationInFlight !== token || demoState?.commandProjections?.[commandId]?.status !== "EXECUTING" ) { return; } demoState = applyCommandProjectionFixture(demoState, { id: `command-succeeded-${commandId}`, commandId, version: demoState.commandProjections[commandId].version + 1, status: "SUCCEEDED", result: { type: "APPOINTMENT_CREATED", eventId: `appointment-event-${commandId}`, appointmentId: `appointment-${commandId}`, patientId: demoState.activePatientId, summary: { visitDate: demoState.commandProjections[commandId] .details?.visitAt?.split(" ")?.[0], visitTime: demoState.commandProjections[commandId] .details?.visitAt?.split(" ")?.[1], departmentName: demoState.commandProjections[commandId] .details?.departmentName, doctorName: demoState.commandProjections[commandId] .details?.doctorName, doctorTitle: demoState.commandProjections[commandId] .details?.doctorTitle, fee: demoState.commandProjections[commandId].details?.fee, }, }, }); commandConfirmationInFlight = null; renderAppShell(demoState); }, 120); }, 80); }; const beginCommandRejection = () => { const commandId = demoState?.overlays?.commandId; if (commandRejectionInFlight || commandConfirmationInFlight || !commandId) { return; } lastCommandRejectionRequest = buildCommandRejectionRequest( demoState, commandId, { reason: "暂不操作", idempotencyKey: `reject-${commandId.replace(/^command-/, "")}`, }, ); const token = { commandId, request: lastCommandRejectionRequest }; commandRejectionInFlight = token; if (typeof globalThis.setTimeout !== "function") { return; } globalThis.setTimeout(() => { if ( commandRejectionInFlight !== token || demoState?.commandProjections?.[commandId]?.status !== "AWAITING_CONFIRMATION" ) { return; } demoState = applyCommandProjectionFixture(demoState, { id: `command-rejected-${commandId}`, commandId, version: demoState.commandProjections[commandId].version + 1, status: "REJECTED", }); commandRejectionInFlight = null; renderAppShell(demoState); }, 80); }; export const initializePatientServiceDesk = (state = demoState) => { if (state !== demoState) { invalidateReportProjectionTimer(); commandConfirmationInFlight = null; commandRejectionInFlight = null; lastCommandConfirmationRequest = null; lastCommandRejectionRequest = null; } demoState = state; const root = globalThis.document; if (root?.addEventListener && !boundDocuments.has(root)) { root.addEventListener("click", (event) => { const trigger = event.target?.closest?.("[data-action]"); const overlayRoot = root.getElementById?.("overlayRoot"); const appView = root.getElementById?.("appView"); const scenarioPanel = root.getElementById?.("scenarioPanel"); const appShell = root.querySelector?.(".phone-shell") ?? appView?.closest?.(".phone-shell"); const withinApp = Boolean( appShell?.contains?.(trigger) || appView?.contains?.(trigger) || overlayRoot?.contains?.(trigger) || scenarioPanel?.contains?.(trigger), ); if (!trigger || !withinApp) { return; } if ( ( hasConsentDialog(demoState) || hasCommandDialog(demoState) || hasReportServiceDialog(demoState) ) && scenarioPanel?.contains?.(trigger) ) { return; } if (trigger.dataset?.action === "interpret-report") { consentFocusReturn = { reportId: trigger.dataset?.reportId, }; } if (trigger.dataset?.action === "confirm-command") { beginCommandConfirmation(); return; } if (trigger.dataset?.action === "reject-command") { beginCommandRejection(); return; } demoState = reduceDemoAction( demoState, trigger.dataset?.action, trigger.dataset?.reportId, trigger.dataset?.candidateId, root.getElementById?.("assistantComposerInput")?.value, trigger.dataset?.appointmentId, trigger.dataset?.tabId, trigger.dataset?.filter, trigger.dataset?.messageId, trigger.dataset?.scenario, trigger.dataset?.viewport, ); renderAppShell(demoState); ensureReportInterpretationProjection(); }); root.addEventListener("keydown", (event) => { if ( event.key === "Enter" && event.target?.id === "assistantComposerInput" ) { const appView = root.getElementById?.("appView"); if (!appView?.contains?.(event.target)) { return; } event.preventDefault?.(); demoState = reduceDemoAction( demoState, "submit-assistant-text", undefined, undefined, event.target.value, ); renderAppShell(demoState); ensureReportInterpretationProjection(); return; } const targetTag = event.target?.tagName?.toLowerCase?.(); const isTyping = ( ["input", "textarea", "select"].includes(targetTag) || event.target?.isContentEditable ); const isPresentationShortcut = ( !isTyping && ["ArrowLeft", "ArrowRight", "r", "R", "p", "P"] .includes(event.key) ); const consentDialogOpen = hasConsentDialog(demoState); const commandDialogOpen = hasCommandDialog(demoState); const reportServiceDialogOpen = hasReportServiceDialog(demoState); if ( isPresentationShortcut && ( consentDialogOpen || commandDialogOpen || reportServiceDialogOpen ) ) { event.preventDefault?.(); return; } if (isPresentationShortcut) { event.preventDefault?.(); if (event.key === "p" || event.key === "P") { scenarioPanelVisible = !scenarioPanelVisible; renderAppShell(demoState); return; } const currentIndex = Math.max( 0, demoScenarios.indexOf(demoState.demoScenario), ); const nextScenario = event.key === "r" || event.key === "R" ? demoState.demoScenario ?? "EMPTY_HOME" : demoScenarios[( currentIndex + (event.key === "ArrowLeft" ? -1 : 1) + demoScenarios.length ) % demoScenarios.length]; initializePatientServiceDesk(loadScenario(demoState, nextScenario)); return; } if ( !consentDialogOpen && !commandDialogOpen && !reportServiceDialogOpen ) { return; } if (event.key === "Escape" && reportServiceDialogOpen) { event.preventDefault?.(); demoState = reduceDemoAction( demoState, "close-report-service", ); renderAppShell(demoState); return; } if (event.key === "Escape" && consentDialogOpen) { event.preventDefault?.(); demoState = rejectConsent( demoState, demoState.overlays.consentId, ); renderAppShell(demoState); ensureReportInterpretationProjection(); return; } if (event.key !== "Tab") { return; } const overlayRoot = root.getElementById?.("overlayRoot"); const focusables = Array.from( overlayRoot?.querySelectorAll?.( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', ) ?? [], ); if (focusables.length === 0) { event.preventDefault?.(); overlayRoot?.querySelector?.('[role="dialog"]')?.focus?.(); return; } const activeIndex = focusables.indexOf(root.activeElement); const nextIndex = event.shiftKey ? (activeIndex <= 0 ? focusables.length - 1 : activeIndex - 1) : (activeIndex === focusables.length - 1 ? 0 : activeIndex + 1); event.preventDefault?.(); focusables[nextIndex]?.focus?.(); }); boundDocuments.add(root); } const rendered = renderAppShell(demoState); ensureReportInterpretationProjection(); return rendered; }; if (globalThis.document) { if (globalThis.document.readyState === "loading") { globalThis.document.addEventListener( "DOMContentLoaded", () => initializePatientServiceDesk(), { once: true }, ); } else { initializePatientServiceDesk(); } }