This commit is contained in:
Your Name
2026-08-11 17:41:36 +08:00
parent cfe4c82c90
commit 03fe4ddf9d
18771 changed files with 3617239 additions and 0 deletions
@@ -0,0 +1,307 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const tongji_utils_aiStreamRequest = require("../utils/aiStreamRequest.js");
const DIET_AI_PREFILL_KEY = "tongji_diet_ai_prefill";
const emptyDietAiRecommend = () => ({
breakfast: "",
drinks: "",
lunch: "",
dinner: "",
tips: "",
avoid: [],
analysis: [],
exercise: null,
disclaimer: "",
rules_summary: "",
source: "",
date: ""
});
function useDietAi(proxy, { diagnosisId, showUserToast, formatUserMessage }) {
const dietAiRecommend = common_vendor.ref(emptyDietAiRecommend());
const dietAiLoading = common_vendor.ref(false);
const dietAiStreaming = common_vendor.ref(false);
const dietAiReplacing = common_vendor.ref(false);
const dietAiStreamField = common_vendor.ref("");
let dietStreamBuffer = "";
const dietAiAskText = common_vendor.ref("");
const dietAiAsking = common_vendor.ref(false);
const dietAiAskResult = common_vendor.ref({ advice: "", level: "", level_label: "", portion: "", food: "" });
const hasDietAiMealContent = common_vendor.computed(() => {
const r = dietAiRecommend.value;
return !!(r.breakfast || r.drinks || r.lunch || r.dinner || r.tips);
});
function detectDietStreamField(text) {
const tail = String(text || "").match(/(?:^|\n)(早餐|喝的|午餐|晚餐|提示|少碰)[::]\s*([^\n]*)$/);
if (!tail)
return "breakfast";
const map = { "早餐": "breakfast", "喝的": "drinks", "午餐": "lunch", "晚餐": "dinner", "提示": "tips", "少碰": "tips" };
return map[tail[1]] || "breakfast";
}
function mergePartialDietFromStream() {
const partial = tongji_utils_aiStreamRequest.parsePartialDietPlainText(dietStreamBuffer);
const next = { ...dietAiRecommend.value };
for (const key of ["breakfast", "drinks", "lunch", "dinner", "tips"]) {
if (partial[key] != null)
next[key] = partial[key];
}
if (Array.isArray(partial.avoid))
next.avoid = partial.avoid;
dietAiRecommend.value = next;
dietAiStreamField.value = detectDietStreamField(dietStreamBuffer);
}
function applyDietRecommendPayload(payload) {
if (!payload)
return;
const prevAnalysis = dietAiRecommend.value.analysis;
const prevExercise = dietAiRecommend.value.exercise;
dietAiRecommend.value = {
...emptyDietAiRecommend(),
...payload,
avoid: Array.isArray(payload.avoid) ? payload.avoid : [],
analysis: Array.isArray(payload.analysis) && payload.analysis.length ? payload.analysis : Array.isArray(prevAnalysis) ? prevAnalysis : [],
exercise: payload.exercise && typeof payload.exercise === "object" ? payload.exercise : prevExercise || null
};
}
function resolveDietDonePayload(payload) {
if (!payload)
return payload;
if (payload.source !== "rule" || !dietStreamBuffer)
return payload;
const streamed = tongji_utils_aiStreamRequest.parsePartialDietPlainText(dietStreamBuffer);
if (!streamed.breakfast)
return payload;
return {
...emptyDietAiRecommend(),
...payload,
breakfast: streamed.breakfast,
drinks: streamed.drinks || payload.drinks || "",
lunch: streamed.lunch || payload.lunch || "",
dinner: streamed.dinner || payload.dinner || "",
tips: streamed.tips || payload.tips || "",
avoid: Array.isArray(streamed.avoid) && streamed.avoid.length ? streamed.avoid : payload.avoid || [],
source: "ai",
disclaimer: "AI 建议供参考,仍请结合医嘱与血糖监测。"
};
}
function onDietAiStreamEvent(event, payload) {
if (event === "analysis") {
if (Array.isArray(payload == null ? void 0 : payload.analysis)) {
dietAiRecommend.value = { ...dietAiRecommend.value, analysis: payload.analysis };
}
} else if (event === "exercise") {
if ((payload == null ? void 0 : payload.exercise) && typeof payload.exercise === "object") {
dietAiRecommend.value = { ...dietAiRecommend.value, exercise: payload.exercise };
}
} else if (event === "delta") {
dietAiStreaming.value = true;
if (payload.buffer != null) {
dietStreamBuffer = payload.buffer;
} else {
dietStreamBuffer += payload.text || "";
}
mergePartialDietFromStream();
} else if (event === "done") {
applyDietRecommendPayload(resolveDietDonePayload(payload));
dietStreamBuffer = "";
dietAiStreamField.value = "";
} else if (event === "error") {
showUserToast(formatUserMessage(payload == null ? void 0 : payload.message, "饮食建议加载失败"));
}
}
async function fetchDietAiRecommendFallback(refresh) {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyDietAiRecommend",
method: "GET",
timeout: 35e3,
data: {
diagnosis_id: diagnosisId.value,
refresh: refresh ? 1 : 0,
...refresh ? { _t: Date.now() } : {}
}
}, false);
if ((res == null ? void 0 : res.code) === 1 && res.data) {
applyDietRecommendPayload(res.data);
} else if (res == null ? void 0 : res.msg) {
showUserToast(res.msg);
}
}
async function fetchDietAiRecommend(refresh = false) {
if (!diagnosisId.value || dietAiLoading.value)
return;
dietAiLoading.value = true;
dietAiStreaming.value = false;
dietAiReplacing.value = !!refresh;
dietStreamBuffer = "";
dietAiStreamField.value = "";
if (refresh) {
applyDietRecommendPayload(emptyDietAiRecommend());
}
try {
await tongji_utils_aiStreamRequest.requestAiStream({
baseUrl: proxy.$url,
url: "/api/tcm/dailyDietAiRecommendStream",
method: "GET",
data: {
diagnosis_id: diagnosisId.value,
refresh: refresh ? 1 : 0,
...refresh ? { _t: Date.now() } : {}
},
onEvent: onDietAiStreamEvent,
fallback: () => fetchDietAiRecommendFallback(refresh)
});
} catch (e) {
try {
await fetchDietAiRecommendFallback(refresh);
} catch (e2) {
showUserToast("饮食建议加载失败");
}
} finally {
dietAiLoading.value = false;
dietAiStreaming.value = false;
dietAiReplacing.value = false;
dietAiStreamField.value = "";
}
}
function refreshDietAiRecommend() {
if (dietAiLoading.value)
return;
fetchDietAiRecommend(true);
}
async function askDietAiFoodFallback(q) {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyDietAiAsk",
method: "POST",
timeout: 35e3,
data: {
diagnosis_id: diagnosisId.value,
question: q
}
}, false);
if ((res == null ? void 0 : res.code) === 1 && res.data) {
dietAiAskResult.value = {
advice: res.data.advice || "",
level: res.data.level || "",
level_label: res.data.level_label || "",
portion: res.data.portion || "",
food: res.data.food || q
};
} else {
showUserToast(formatUserMessage(res == null ? void 0 : res.msg, "咨询失败"));
}
}
async function askDietAiFood() {
const q = String(dietAiAskText.value || "").trim();
if (!q) {
showUserToast("请输入想咨询的食物");
return;
}
if (!diagnosisId.value)
return;
dietAiAskText.value = "";
dietAiAsking.value = true;
dietAiAskResult.value = { advice: "", level: "", level_label: "", portion: "", food: "" };
try {
await tongji_utils_aiStreamRequest.requestAiStream({
baseUrl: proxy.$url,
url: "/api/tcm/dailyDietAiAskStream",
method: "POST",
data: {
diagnosis_id: diagnosisId.value,
question: q
},
onEvent: (event, payload) => {
if (event === "start" && !dietAiAskResult.value.advice) {
dietAiAskResult.value = { ...dietAiAskResult.value, advice: payload.message || "正在想…" };
} else if (event === "delta") {
dietAiAskResult.value = {
...dietAiAskResult.value,
advice: payload.advice || dietAiAskResult.value.advice
};
} else if (event === "done") {
dietAiAskResult.value = {
advice: payload.advice || "",
level: payload.level || "",
level_label: payload.level_label || "",
portion: payload.portion || "",
food: payload.food || q
};
} else if (event === "error") {
showUserToast(formatUserMessage(payload == null ? void 0 : payload.message, "咨询失败"));
}
},
fallback: () => askDietAiFoodFallback(q)
});
} catch (e) {
try {
await askDietAiFoodFallback(q);
} catch (e2) {
showUserToast("网络异常,请稍后再试");
}
} finally {
dietAiAsking.value = false;
}
}
function onDietAiAskInput(e) {
var _a;
dietAiAskText.value = ((_a = e == null ? void 0 : e.detail) == null ? void 0 : _a.value) ?? "";
}
function saveDietPrefillToStorage() {
const r = dietAiRecommend.value;
if (!r.breakfast)
return;
try {
common_vendor.index.setStorageSync(DIET_AI_PREFILL_KEY, {
breakfast: r.breakfast,
drinks: r.drinks || "",
lunch: r.lunch,
dinner: r.dinner,
tips: r.tips || ""
});
} catch (e) {
}
}
function goMoreDietForm(withPrefill = false) {
if (withPrefill)
saveDietPrefillToStorage();
common_vendor.index.navigateTo({ url: "/tongji/pages/more?openDiet=1" });
}
async function applyDietAiToForm() {
if (!dietAiRecommend.value.breakfast) {
await fetchDietAiRecommend(false);
}
goMoreDietForm(true);
}
function openDietForm() {
goMoreDietForm(false);
}
return {
dietAiRecommend,
dietAiLoading,
dietAiStreaming,
dietAiReplacing,
dietAiStreamField,
dietAiAskText,
dietAiAsking,
dietAiAskResult,
hasDietAiMealContent,
fetchDietAiRecommend,
refreshDietAiRecommend,
askDietAiFood,
onDietAiAskInput,
applyDietAiToForm,
openDietForm
};
}
function consumeDietAiPrefill() {
try {
const data = common_vendor.index.getStorageSync(DIET_AI_PREFILL_KEY);
common_vendor.index.removeStorageSync(DIET_AI_PREFILL_KEY);
if (data && typeof data === "object" && data.breakfast)
return data;
} catch (e) {
}
return null;
}
exports.consumeDietAiPrefill = consumeDietAiPrefill;
exports.useDietAi = useDietAi;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/composables/useDietAi.js.map
@@ -0,0 +1,291 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const MIN_HOLD_MS = 280;
function isBenignSttError(msg, retcode) {
const m = String(msg || "").toLowerCase();
if (/internal voice data failed/i.test(m))
return true;
if (/voice data failed/i.test(m))
return true;
if (/recordfailed|record failed|record manager/i.test(m))
return true;
if (/please stop after start/i.test(m))
return true;
if (/no recognition|not start|未开始|无识别/i.test(m))
return true;
const code = Number(retcode);
if (code === -30002 || code === -30003 || code === -30012)
return true;
return false;
}
function friendlySttError(msg) {
const m = String(msg || "").trim();
if (!m || isBenignSttError(m))
return "";
if (/not allowed|auth|permission|权限|麦克风/i.test(m))
return "请允许使用麦克风";
if (/network|网络|timeout/i.test(m))
return "网络异常,请稍后再试";
return "语音识别失败,请重试";
}
let sharedManager = null;
let sharedManagerInited = false;
let sharedRecording = false;
let activeCtl = null;
function getSharedRecordManager() {
if (sharedManagerInited)
return sharedManager;
sharedManagerInited = true;
try {
const plugin = requirePlugin("WechatSI");
sharedManager = plugin.getRecordRecognitionManager();
} catch (e) {
common_vendor.index.__f__("warn", "at tongji/composables/useSpeechToText.js:44", "WechatSI record recognition not available", e);
sharedManager = null;
return null;
}
if (!sharedManager)
return null;
sharedManager.onStart = () => {
sharedRecording = true;
if (activeCtl)
activeCtl.handleStart();
};
sharedManager.onRecognize = (res) => {
if (activeCtl)
activeCtl.handleRecognize(res);
};
sharedManager.onStop = (res) => {
sharedRecording = false;
if (activeCtl)
activeCtl.handleStop(res);
};
sharedManager.onError = (res) => {
sharedRecording = false;
if (activeCtl)
activeCtl.handleError(res);
};
return sharedManager;
}
function useSpeechToText({ onResult, onSettle, onPartial, onError, showToast } = {}) {
const sttListening = common_vendor.ref(false);
const sttHolding = common_vendor.ref(false);
const sttSupported = common_vendor.ref(false);
let wxRecordManager = null;
let holdStartTs = 0;
let holdSessionId = 0;
let startRequested = false;
let recordAuthorized = null;
let pendingStartSession = 0;
function notify(msg) {
if (!msg)
return;
if (typeof showToast === "function") {
showToast(msg);
return;
}
common_vendor.index.showToast({ title: msg, icon: "none", duration: 2e3 });
}
function emitResult(text) {
const t = String(text || "").trim();
const heldMs = Date.now() - holdStartTs;
if (typeof onSettle === "function") {
const handled = onSettle(t, { heldMs });
if (handled === true)
return;
}
if (!t) {
if (heldMs < MIN_HOLD_MS)
return;
notify("没听清,请再试一次");
return;
}
if (typeof onResult === "function") {
onResult(t);
}
}
function stopListening() {
const ownsRecording = sharedRecording && activeCtl === controller;
if (wxRecordManager && (sttListening.value || ownsRecording)) {
try {
wxRecordManager.stop();
} catch (e) {
}
}
startRequested = false;
sttHolding.value = false;
sttListening.value = false;
}
function ensureRecordAuth(onGranted) {
if (recordAuthorized === true) {
onGranted();
return;
}
common_vendor.index.authorize({
scope: "scope.record",
success() {
recordAuthorized = true;
onGranted();
},
fail() {
recordAuthorized = false;
sttHolding.value = false;
common_vendor.index.showModal({
title: "需要麦克风权限",
content: "请在设置中允许使用麦克风,以便长按说话",
confirmText: "去设置",
success(res) {
if (res.confirm) {
common_vendor.index.openSetting({});
}
}
});
}
});
}
function actuallyStartWechat(session) {
if (!wxRecordManager || session !== holdSessionId || !sttHolding.value) {
return;
}
try {
startRequested = true;
wxRecordManager.start({
duration: 6e4,
lang: "zh_CN"
});
} catch (e) {
startRequested = false;
sharedRecording = false;
sttHolding.value = false;
notify("无法开始录音");
}
}
function startWechatRecord(session) {
if (!wxRecordManager || session !== holdSessionId || !sttHolding.value) {
return;
}
if (sharedRecording || startRequested) {
pendingStartSession = session;
try {
wxRecordManager.stop();
} catch (e) {
}
return;
}
actuallyStartWechat(session);
}
function beginHoldSpeech() {
if (!sttSupported.value) {
notify("当前环境不支持语音输入");
return;
}
if (sttHolding.value)
return;
const session = ++holdSessionId;
holdStartTs = Date.now();
sttHolding.value = true;
activeCtl = controller;
ensureRecordAuth(() => startWechatRecord(session));
}
function endHoldSpeech(force = false) {
let recording = false;
recording = sharedRecording && activeCtl === controller;
if (!force && !sttHolding.value && !sttListening.value && !startRequested && !recording) {
return;
}
sttHolding.value = false;
pendingStartSession = 0;
if (sttListening.value || recording) {
stopListening();
return;
}
if (startRequested) {
startRequested = false;
holdSessionId += 1;
return;
}
holdSessionId += 1;
}
const controller = {
handleStart() {
if (!sttHolding.value) {
startRequested = false;
sttListening.value = false;
return;
}
startRequested = false;
sttListening.value = true;
},
handleRecognize(res) {
if (typeof onPartial !== "function")
return;
const t = String((res == null ? void 0 : res.result) || "").trim();
if (t)
onPartial(t);
},
handleStop(res) {
sttListening.value = false;
startRequested = false;
if (pendingStartSession && pendingStartSession === holdSessionId && sttHolding.value) {
const s = pendingStartSession;
pendingStartSession = 0;
actuallyStartWechat(s);
return;
}
pendingStartSession = 0;
emitResult(res == null ? void 0 : res.result);
},
handleError(res) {
sttListening.value = false;
startRequested = false;
const msg = res && res.msg || "";
const retcode = res && (res.retcode ?? res.errCode ?? res.code);
if (/please stop after start/i.test(msg)) {
try {
wxRecordManager.stop();
} catch (e) {
}
pendingStartSession = sttHolding.value ? holdSessionId : 0;
return;
}
pendingStartSession = 0;
sttHolding.value = false;
const heldMs = Date.now() - holdStartTs;
if (isBenignSttError(msg, retcode) || heldMs < MIN_HOLD_MS) {
if (typeof onError === "function")
onError(msg || "");
return;
}
if (typeof onError === "function") {
const handled = onError(msg || "");
if (handled === true)
return;
}
const tip = friendlySttError(msg);
if (tip)
notify(tip);
}
};
wxRecordManager = getSharedRecordManager();
sttSupported.value = !!wxRecordManager;
common_vendor.onUnmounted(() => {
sttHolding.value = false;
holdSessionId += 1;
pendingStartSession = 0;
stopListening();
if (activeCtl === controller) {
activeCtl = null;
}
});
return {
sttListening,
sttHolding,
sttSupported,
beginHoldSpeech,
endHoldSpeech,
stopSpeechToText: endHoldSpeech
};
}
exports.isBenignSttError = isBenignSttError;
exports.useSpeechToText = useSpeechToText;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/composables/useSpeechToText.js.map