+
+
+ {state.step === HEALTH_CHANNEL_STEPS.IDLE && (
+
+
+
+
+
+ )}
+
+ {state.step !== HEALTH_CHANNEL_STEPS.IDLE && (
+
+ )}
+
+ {message &&
{message}
}
+
+
+ 最近记录
+ {grouped.length === 0 ? (
+ 还没有确认过的血压记录。
+ ) : (
+
+ {grouped.map((item) => (
+ -
+ {new Date(item.createdAt).toLocaleString()} · {item.systolic}/{item.diastolic} · {item.context || 'other'}
+
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/src/types.ts b/src/types.ts
index 494baaa..3cbbc21 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1016,6 +1016,7 @@ export type AuthStatus = {
unrestricted?: boolean;
agentCodeRun?: AgentCodeRunClientPolicy | null;
goalRun?: GoalRunClientPolicy | null;
+ health?: { enabled: boolean } | null;
};
export type PageTemplateCatalogItem = {
diff --git a/wechat-mp.mjs b/wechat-mp.mjs
index 5272196..ed3ce37 100644
--- a/wechat-mp.mjs
+++ b/wechat-mp.mjs
@@ -40,6 +40,10 @@ import { isIntentTransactionEnabled } from './intent-transaction-config.mjs';
import { handleWechatScheduleIntent } from './wechat/handlers/schedule.mjs';
import { handleWechatIntentTransaction } from './wechat/handlers/intent-transaction.mjs';
import { handleWechatScheduledTaskIntent } from './wechat/handlers/scheduled-task.mjs';
+import { handleWechatHealthChannel } from './wechat/handlers/health.mjs';
+import { shouldFallThroughWechatHealthMenuEvent } from './health-wechat-turn.mjs';
+import { isMemindHealthEnabled } from './health-feature.mjs';
+import { createHealthChannelSessionStore } from './health-channel-session-store.mjs';
import {
buildStatusText,
buildSubscribeWelcomeText,
@@ -1667,7 +1671,12 @@ export function createWechatMpService({
linkExists = defaultPublicHtmlLinkExists,
h5Root = '',
logger = console,
+ healthChannelStore = null,
+ healthObservationStore = null,
+ env = process.env,
}) {
+ const resolvedHealthChannelStore =
+ healthChannelStore ?? createHealthChannelSessionStore();
const sessionStore = resolveSessionAccess({ userAuth, sessionAccess });
if (!config?.enabled) {
return {
@@ -3810,8 +3819,10 @@ export function createWechatMpService({
}
if (inbound.msgType === 'event') {
- await persistIntentDetail({ intent, rawXmlHash });
- return successResponse();
+ if (!shouldFallThroughWechatHealthMenuEvent(inbound, env)) {
+ await persistIntentDetail({ intent, rawXmlHash });
+ return successResponse();
+ }
}
const supportedByConfig =
@@ -3822,7 +3833,8 @@ export function createWechatMpService({
(intent.msgType === 'link' && config.acceptLink) ||
intent.msgType === 'text' ||
intent.msgType === 'video' ||
- intent.msgType === 'shortvideo';
+ intent.msgType === 'shortvideo' ||
+ shouldFallThroughWechatHealthMenuEvent(inbound, env);
if (!supportedByConfig) {
await persistIntentDetail({ intent, rawXmlHash });
@@ -4149,6 +4161,38 @@ export function createWechatMpService({
};
}
+ const healthReply = await handleWechatHealthChannel({
+ enabled: isMemindHealthEnabled(env),
+ userId: boundUser.userId,
+ intent,
+ inbound,
+ store: resolvedHealthChannelStore,
+ observationStore: healthObservationStore,
+ }).catch((err) => {
+ logger.warn?.(
+ 'WeChat MP health channel failed open:',
+ err instanceof Error ? err.message : err,
+ );
+ return null;
+ });
+ if (healthReply) {
+ if (inbound.msgId && typeof userAuth.finishWechatMpMessage === 'function') {
+ await userAuth.finishWechatMpMessage({
+ appId: config.appId,
+ openid: inbound.fromUserName,
+ msgId: inbound.msgId,
+ status: 'done',
+ agentSessionId: null,
+ });
+ }
+ return {
+ ok: true,
+ status: 200,
+ contentType: 'application/xml; charset=utf-8',
+ body: await buildPassiveReplyBody(healthReply),
+ };
+ }
+
const intentTransactionReply =
intent.msgType === 'text' || intent.msgType === 'voice'
? await handleWechatIntentTransaction({
diff --git a/wechat/handlers/health.mjs b/wechat/handlers/health.mjs
new file mode 100644
index 0000000..c7db4bb
--- /dev/null
+++ b/wechat/handlers/health.mjs
@@ -0,0 +1,62 @@
+import { isMemindHealthEnabled } from '../../health-feature.mjs';
+import { applyHealthWechatTurn } from '../../health-wechat-turn.mjs';
+
+export async function handleWechatHealthChannel({
+ enabled = isMemindHealthEnabled(),
+ userId,
+ intent,
+ inbound,
+ store,
+ observationStore = null,
+ now = Date.now(),
+} = {}) {
+ if (!enabled || !userId || !store) return null;
+
+ const existing = store.get(userId);
+ const session = existing?.expired ? null : existing?.session ?? null;
+ const text = String(intent?.agentText ?? intent?.displayText ?? '').trim();
+ const eventKey = String(inbound?.eventKey ?? '').trim();
+ const msgType = String(intent?.msgType ?? inbound?.msgType ?? 'text').toLowerCase();
+
+ const result = applyHealthWechatTurn({
+ session,
+ text,
+ msgType,
+ eventKey,
+ now,
+ });
+ if (!result.handled) return null;
+
+ if (result.session) {
+ store.set(userId, result.session);
+ } else {
+ store.clear(userId);
+ }
+
+ if (result.commit && observationStore) {
+ const values = result.commit.values;
+ const observedAt = values.observedAt ?? now;
+ await observationStore.insert(userId, {
+ confirmed: true,
+ observedAt,
+ metricType: 'bp_systolic',
+ valueNum: values.systolic,
+ unit: 'mmHg',
+ context: values.context,
+ source: 'wechat',
+ qualityFlag: 'ok',
+ });
+ await observationStore.insert(userId, {
+ confirmed: true,
+ observedAt,
+ metricType: 'bp_diastolic',
+ valueNum: values.diastolic,
+ unit: 'mmHg',
+ context: values.context,
+ source: 'wechat',
+ qualityFlag: 'ok',
+ });
+ }
+
+ return result.reply;
+}