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,141 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
function joinApiUrl(base, path) {
const b = String(base || "").replace(/\/+$/, "");
const p = String(path || "").replace(/^\/+/, "");
return p ? `${b}/${p}` : b;
}
function appendQuery(url, data) {
if (!data || typeof data !== "object")
return url;
const keys = Object.keys(data);
if (!keys.length)
return url;
const qs = keys.map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(data[k] ?? "")}`).join("&");
return url + (url.includes("?") ? "&" : "?") + qs;
}
function decodeChunkData(data) {
if (typeof data === "string")
return data;
if (!data)
return "";
if (typeof TextDecoder !== "undefined" && data instanceof ArrayBuffer) {
return new TextDecoder("utf-8").decode(new Uint8Array(data));
}
if (data instanceof ArrayBuffer) {
const bytes = new Uint8Array(data);
let str = "";
for (let i = 0; i < bytes.length; i++)
str += String.fromCharCode(bytes[i]);
try {
return decodeURIComponent(escape(str));
} catch (e) {
return str;
}
}
return "";
}
function consumeSseBuffer(buffer, onEvent) {
let rest = buffer;
let idx = rest.indexOf("\n\n");
while (idx !== -1) {
const block = rest.slice(0, idx);
rest = rest.slice(idx + 2);
let event = "message";
let dataLine = "";
block.split("\n").forEach((line) => {
if (line.startsWith("event:"))
event = line.slice(6).trim();
else if (line.startsWith("data:"))
dataLine += line.slice(5).trim();
});
if (dataLine) {
try {
onEvent(event, JSON.parse(dataLine));
} catch (e) {
onEvent(event, dataLine);
}
}
idx = rest.indexOf("\n\n");
}
return rest;
}
function parsePartialDietPlainText(text) {
const out = {};
if (!text)
return out;
const lineRe = /^(早餐|喝的|午餐|晚餐|提示|少碰)[:]\s*(.*)$/gm;
let match;
while ((match = lineRe.exec(text)) !== null) {
const key = { "早餐": "breakfast", "喝的": "drinks", "午餐": "lunch", "晚餐": "dinner", "提示": "tips", "少碰": "avoid" }[match[1]];
const val = (match[2] || "").trim();
if (key === "avoid") {
out.avoid = val.split(/[、,;\s]+/).map((s) => s.trim()).filter(Boolean);
} else {
out[key] = val;
}
}
const tail = text.match(/(?:^|\n)(早餐|喝的|午餐|晚餐|提示|少碰)[:]\s*([^\n]*)$/);
if (tail) {
const key = { "早餐": "breakfast", "喝的": "drinks", "午餐": "lunch", "晚餐": "dinner", "提示": "tips", "少碰": "avoid" }[tail[1]];
const val = (tail[2] || "").trim();
if (key === "avoid") {
if (val)
out.avoid = val.split(/[、,;\s]+/).map((s) => s.trim()).filter(Boolean);
} else {
out[key] = val;
}
}
return out;
}
function requestAiStream(opts) {
const {
baseUrl,
url,
method = "GET",
data = {},
onEvent,
fallback
} = opts;
return new Promise((resolve, reject) => {
const token = common_vendor.index.getStorageSync("token") || "";
let sseBuffer = "";
let requestUrl = joinApiUrl(baseUrl, url);
if (method === "GET") {
requestUrl = appendQuery(requestUrl, data);
}
const task = common_vendor.wx$1.request({
url: requestUrl,
method,
data: method === "POST" ? data : {},
enableChunked: true,
timeout: 6e4,
header: {
token,
"content-type": "application/x-www-form-urlencoded",
"Cache-Control": "no-cache"
},
success: (res) => {
if (res && res.data) {
sseBuffer += decodeChunkData(res.data);
sseBuffer = consumeSseBuffer(sseBuffer, onEvent);
}
resolve(res);
},
fail: (err) => reject(err)
});
if (task && typeof task.onChunkReceived === "function") {
task.onChunkReceived((res) => {
sseBuffer += decodeChunkData(res.data);
sseBuffer = consumeSseBuffer(sseBuffer, onEvent);
});
} else if (typeof fallback === "function") {
fallback().then(resolve).catch(reject);
} else {
reject(new Error("当前环境不支持流式请求"));
}
});
}
exports.parsePartialDietPlainText = parsePartialDietPlainText;
exports.requestAiStream = requestAiStream;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/aiStreamRequest.js.map
@@ -0,0 +1,48 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
function utf8ToBytes(str) {
const encoded = encodeURIComponent(str);
const bytes = [];
for (let i = 0; i < encoded.length; i++) {
if (encoded.charCodeAt(i) === 37) {
bytes.push(parseInt(encoded.substring(i + 1, i + 3), 16));
i += 2;
} else {
bytes.push(encoded.charCodeAt(i));
}
}
return new Uint8Array(bytes);
}
function bytesToBase64(bytes) {
if (typeof common_vendor.index !== "undefined" && typeof common_vendor.index.arrayBufferToBase64 === "function") {
return common_vendor.index.arrayBufferToBase64(bytes.buffer);
}
if (typeof btoa !== "undefined") {
let binary = "";
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let result = "";
for (let i = 0; i < bytes.length; i += 3) {
const a = bytes[i];
const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
result += chars[a >> 2];
result += chars[(a & 3) << 4 | b >> 4];
result += i + 1 < bytes.length ? chars[(b & 15) << 2 | c >> 6] : "=";
result += i + 2 < bytes.length ? chars[c & 63] : "=";
}
return result;
}
function svgToDataUrl(svg) {
const normalized = String(svg || "").trim();
if (!normalized)
return "";
const base64 = bytesToBase64(utf8ToBytes(normalized));
return `data:image/svg+xml;base64,${base64}`;
}
exports.svgToDataUrl = svgToDataUrl;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/svgDataUrl.js.map
@@ -0,0 +1,60 @@
"use strict";
const TREE_MAX_LEVEL = 9;
const TREE_XP_PER_LEVEL = 50;
const TREE_LEVELS = [
{ level: 0, name: "种子眠", emoji: "🫘", mood: "困困", desc: "稳糖种子在土里打盹" },
{ level: 1, name: "破土芽", emoji: "🌱", mood: "探头", desc: "探出第一抹新绿" },
{ level: 2, name: "展两叶", emoji: "🌿", mood: "好奇", desc: "两片嫩叶迎风展" },
{ level: 3, name: "小树苗", emoji: "🪴", mood: "精神", desc: "身子骨硬朗起来" },
{ level: 4, name: "青枝繁", emoji: "🌳", mood: "茁壮", desc: "枝叶渐密,元气足" },
{ level: 5, name: "拔节高", emoji: "🌲", mood: "挺拔", desc: "一节一节往上蹿" },
{ level: 6, name: "稳糖冠", emoji: "💚", mood: "沉稳", desc: "树冠成形,习惯成自然" },
{ level: 7, name: "初绽香", emoji: "🌸", mood: "开心", desc: "枝头冒出第一朵花" },
{ level: 8, name: "漫开花", emoji: "🌺", mood: "灿烂", desc: "花开满枝,越记越稳" },
{ level: 9, name: "圆满树", emoji: "🏆", mood: "荣耀", desc: "满级大树,习惯大师" }
];
const WHISPERS_BY_LEVEL = {
0: ["种子在睡觉,记一笔就醒啦。", "今天浇第一滴水,芽就要冒出来。"],
1: ["破土啦!再坚持几天就长高。", "小芽最喜欢规律的记录了。"],
2: ["两片叶子为你鼓掌。", "空腹餐后都记全,我会长得更快。"],
3: ["我已经是一棵小树啦。", "家人点赞的时候,我也会发光。"],
4: ["枝叶越来越密,您真棒。", "连续记录,我会开出更多叶子。"],
5: ["拔节中!习惯比完美更重要。", "再浇一点水,我就更高啦。"],
6: ["习惯成自然,树冠成形啦。", "您今天的坚持,小树都记得。"],
7: ["开花啦!闻到春天的味道了吗?", "稳糖花只开给坚持的人。"],
8: ["满树花香,您已是控糖达人。", "明天继续来,花儿会更艳。"],
9: ["满级大树陪您一路稳糖。", "圆满不是终点,习惯才是。"]
};
function calcTreeFromPoints(points) {
const pts = Math.max(0, Number(points) || 0);
const level = Math.min(TREE_MAX_LEVEL, Math.floor(pts / TREE_XP_PER_LEVEL));
const xpInLevel = level >= TREE_MAX_LEVEL ? TREE_XP_PER_LEVEL : pts % TREE_XP_PER_LEVEL;
const progress = level >= TREE_MAX_LEVEL ? 100 : Math.round(xpInLevel / TREE_XP_PER_LEVEL * 100);
const meta = TREE_LEVELS[level] || TREE_LEVELS[0];
const nextMeta = level < TREE_MAX_LEVEL ? TREE_LEVELS[level + 1] : null;
const pointsToNext = level >= TREE_MAX_LEVEL ? 0 : TREE_XP_PER_LEVEL - xpInLevel;
return {
level,
progress,
name: meta.name,
emoji: meta.emoji,
mood: meta.mood,
desc: meta.desc,
xpInLevel,
xpNeed: TREE_XP_PER_LEVEL,
pointsToNext,
nextName: (nextMeta == null ? void 0 : nextMeta.name) || "",
isMax: level >= TREE_MAX_LEVEL
};
}
function pickTreeWhisper(level) {
const lv = Math.min(TREE_MAX_LEVEL, Math.max(0, Number(level) || 0));
const list = WHISPERS_BY_LEVEL[lv] || WHISPERS_BY_LEVEL[0];
return list[Math.floor(Math.random() * list.length)];
}
exports.TREE_LEVELS = TREE_LEVELS;
exports.TREE_MAX_LEVEL = TREE_MAX_LEVEL;
exports.TREE_XP_PER_LEVEL = TREE_XP_PER_LEVEL;
exports.calcTreeFromPoints = calcTreeFromPoints;
exports.pickTreeWhisper = pickTreeWhisper;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/treeLevels.js.map
@@ -0,0 +1,68 @@
"use strict";
const tongji_utils_treeLevels = require("./treeLevels.js");
const TREE_SPECIES_LIST = [
{
id: "classic",
name: "稳糖灵树",
tagline: "经典路线,从种子到圆满",
stages: tongji_utils_treeLevels.TREE_LEVELS.map((l) => l.emoji)
},
{
id: "bonsai",
name: "雅韵盆景",
tagline: "小巧精致,书桌旁的温柔陪伴",
stages: ["🫘", "🌱", "🪴", "🎋", "🪴", "🌳", "🎍", "🌸", "🏵️", "🏆"]
},
{
id: "pine",
name: "苍劲青松",
tagline: "挺拔向上,越记越稳",
stages: ["🫘", "🌱", "🌿", "🌲", "🌲", "🌲", "⛰️", "🌲", "🏔️", "🏆"]
},
{
id: "sakura",
name: "樱花小树",
tagline: "坚持打卡,枝头渐开",
stages: ["🫘", "🌱", "🌿", "🌳", "🌸", "🌸", "🌸", "🌸", "🌺", "🏆"]
},
{
id: "ginkgo",
name: "金秋银杏",
tagline: "叶色渐变,见证每一天",
stages: ["🫘", "🌱", "🍃", "🌳", "🍂", "🍂", "🌳", "🍁", "🌟", "🏆"]
}
];
const DAILY_MAX_TASK_POINTS = 40;
function getTreeSpecies(id) {
return TREE_SPECIES_LIST.find((s) => s.id === id) || TREE_SPECIES_LIST[0];
}
function speciesEmojiAtLevel(speciesId, level) {
const sp = getTreeSpecies(speciesId);
const lv = Math.min(tongji_utils_treeLevels.TREE_MAX_LEVEL, Math.max(0, Number(level) || 0));
return sp.stages[lv] || sp.stages[0] || "🌱";
}
function speciesGrowthRoadmap(speciesId) {
const sp = getTreeSpecies(speciesId);
return tongji_utils_treeLevels.TREE_LEVELS.map((meta, i) => {
const xpTotal = i * tongji_utils_treeLevels.TREE_XP_PER_LEVEL;
const daysHint = i === 0 ? 0 : Math.max(1, Math.ceil(xpTotal / DAILY_MAX_TASK_POINTS));
return {
level: i,
name: meta.name,
emoji: sp.stages[i] || meta.emoji,
desc: meta.desc,
xpTotal,
daysHint
};
});
}
function estimateDaysToMaxLevel() {
const totalXp = tongji_utils_treeLevels.TREE_MAX_LEVEL * tongji_utils_treeLevels.TREE_XP_PER_LEVEL;
return Math.max(1, Math.ceil(totalXp / DAILY_MAX_TASK_POINTS));
}
exports.TREE_SPECIES_LIST = TREE_SPECIES_LIST;
exports.estimateDaysToMaxLevel = estimateDaysToMaxLevel;
exports.getTreeSpecies = getTreeSpecies;
exports.speciesEmojiAtLevel = speciesEmojiAtLevel;
exports.speciesGrowthRoadmap = speciesGrowthRoadmap;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/treeSpecies.js.map
@@ -0,0 +1,310 @@
"use strict";
const CN_DIGIT = {
: 0,
"": 0,
: 1,
: 1,
: 1,
: 2,
: 2,
: 2,
: 3,
: 3,
: 4,
: 4,
: 5,
: 5,
: 6,
: 6,
: 7,
: 7,
: 8,
: 8,
: 9,
: 9
};
const CN_UNIT = { : 10, : 10, : 100, : 100, : 1e3, : 1e3, : 1e4, 亿: 1e8 };
function cnIntToNumber(s) {
let total = 0;
let section = 0;
let number = 0;
let hadUnit = false;
let lastUnit = 0;
let sawZeroAfterUnit = false;
for (const ch of s) {
if (CN_DIGIT[ch] !== void 0) {
number = CN_DIGIT[ch];
if (number === 0)
sawZeroAfterUnit = true;
} else if (CN_UNIT[ch] !== void 0) {
hadUnit = true;
const unit = CN_UNIT[ch];
lastUnit = unit;
sawZeroAfterUnit = false;
if (unit >= 1e4) {
section = (section + number) * unit;
total += section;
section = 0;
} else {
if (number === 0)
number = 1;
section += number * unit;
}
number = 0;
}
}
if (!hadUnit && s.length > 1) {
let joined = "";
for (const ch of s) {
if (CN_DIGIT[ch] !== void 0)
joined += CN_DIGIT[ch];
}
if (joined)
return Number(joined);
}
if (number > 0 && lastUnit >= 100 && !sawZeroAfterUnit) {
number = number * (lastUnit / 10);
}
return total + section + number;
}
function cnSeqToNumber(seq) {
if (seq.includes("点")) {
const parts = seq.split("点");
const intPart = parts[0];
const decPart = parts.slice(1).join("");
const intVal = intPart ? cnIntToNumber(intPart) : 0;
let decStr = "";
for (const ch of decPart) {
if (CN_DIGIT[ch] !== void 0)
decStr += CN_DIGIT[ch];
}
if (!decStr)
return intVal;
return Number(`${intVal}.${decStr}`);
}
return cnIntToNumber(seq);
}
function hasCnDigit(seq) {
for (const ch of seq) {
if (CN_DIGIT[ch] !== void 0 || CN_UNIT[ch] !== void 0)
return true;
}
return false;
}
function normalizeNumbers(text) {
const raw = String(text || "");
return raw.replace(/[零〇一壹幺二贰两三叁四肆五伍六陆七柒八捌九玖十拾百佰千仟万亿点]+/g, (m) => {
if (!hasCnDigit(m))
return m;
const n = cnSeqToNumber(m);
return Number.isFinite(n) ? String(n) : m;
});
}
function numAfter(text, keys) {
for (const k of keys) {
const i = text.indexOf(k);
if (i >= 0) {
const rest = text.slice(i + k.length);
const m = rest.match(/-?\d+(?:\.\d+)?/);
if (m)
return m[0];
}
}
return "";
}
function textAfter(text, keys, stopKeys = []) {
for (const k of keys) {
const i = text.indexOf(k);
if (i >= 0) {
let rest = text.slice(i + k.length);
rest = rest.replace(/^[是为吃了喝了吃的喝的有打了用了::,,、。\s]+/, "");
let end = rest.length;
const mStop = rest.match(/[,。.;!?\n]/);
if (mStop && mStop.index < end)
end = mStop.index;
for (const sk of stopKeys) {
const si = rest.indexOf(sk);
if (si >= 0 && si < end)
end = si;
}
const seg = rest.slice(0, end).trim();
if (seg)
return seg;
}
}
return "";
}
function parseGlucose(raw) {
const t = normalizeNumbers(raw);
const result = {};
const fasting = numAfter(t, ["空腹"]);
const post = numAfter(t, ["餐后", "饭后"]);
const other = numAfter(t, ["其他", "随机", "睡前", "凌晨", "夜间", "晚上"]);
if (fasting)
result.fasting_blood_sugar = fasting;
if (post)
result.postprandial_blood_sugar = post;
if (other)
result.other_blood_sugar = other;
if (!fasting && !post && !other) {
const nums = t.match(/\d+(?:\.\d+)?/g);
if (nums && nums.length === 1)
result.other_blood_sugar = nums[0];
}
return result;
}
function parseBloodPressure(raw) {
const t = normalizeNumbers(raw);
const result = {};
let sys = numAfter(t, ["高压", "收缩压", "收缩"]);
let dia = numAfter(t, ["低压", "舒张压", "舒张"]);
if (!sys || !dia) {
const nums = (t.match(/\d{2,3}/g) || []).map(Number).filter((n) => n >= 30 && n <= 300);
if (!sys && !dia && nums.length >= 2) {
sys = String(nums[0]);
dia = String(nums[1]);
} else if (!sys && nums.length === 1 && nums[0] >= 90) {
sys = String(nums[0]);
}
}
if (sys && dia && Number(sys) < Number(dia)) {
const tmp = sys;
sys = dia;
dia = tmp;
}
if (sys)
result.systolic_pressure = sys;
if (dia)
result.diastolic_pressure = dia;
const insulin = textAfter(raw, ["胰岛素"], ["高压", "低压", "血压", "西药", "备注"]);
if (insulin)
result.insulin = insulin;
const western = textAfter(raw, ["西药", "降糖药", "口服药"], ["胰岛素", "高压", "低压", "血压", "备注"]);
if (western)
result.western_medicine = western;
return result;
}
const DIET_MARKERS = [
{ keys: ["早餐", "早饭", "早上", "早点", "早晨"], field: "breakfast_foods" },
{ keys: ["午餐", "午饭", "中午"], field: "lunch_foods" },
{ keys: ["晚餐", "晚饭", "晚上", "夜宵", "夜里"], field: "dinner_foods" }
];
function cleanFoodSeg(seg) {
return String(seg || "").replace(/^[是为吃了吃的喝了喝的有::,,、。\s]+/, "").replace(/[。.\s]+$/, "").trim();
}
function parseDiet(raw) {
const t = String(raw || "").trim();
const result = {};
const hits = [];
DIET_MARKERS.forEach((m) => {
for (const k of m.keys) {
const idx = t.indexOf(k);
if (idx >= 0) {
hits.push({ idx, field: m.field, klen: k.length });
break;
}
}
});
if (!hits.length) {
if (t)
result.note = t;
return result;
}
hits.sort((a, b) => a.idx - b.idx);
hits.forEach((h, i) => {
const start = h.idx + h.klen;
const end = i + 1 < hits.length ? hits[i + 1].idx : t.length;
const seg = cleanFoodSeg(t.slice(start, end));
if (seg && !result[h.field])
result[h.field] = seg;
});
const head = cleanFoodSeg(t.slice(0, hits[0].idx));
if (head)
result.note = head;
return result;
}
const EXERCISE_FILLERS = [
"今天",
"我",
"做了",
"做",
"进行了",
"进行",
"运动了",
"运动",
"锻炼了",
"锻炼",
"了",
"大概",
"左右",
"持续",
"差不多",
"一共",
"总共",
"的",
"走了",
"打了",
"跑了"
];
function parseExercise(raw) {
const t = normalizeNumbers(raw);
const result = {};
const durMatch = t.match(/(\d+(?:\.\d+)?)\s*(个小时|小时|钟头|时|分钟|分)/);
if (durMatch) {
const val = parseFloat(durMatch[1]);
const isHour = /个小时|小时|钟头|时/.test(durMatch[2]);
result.duration = String(Math.round(isHour ? val * 60 : val));
}
if (/高强度|剧烈|很累|大汗|气喘/.test(t))
result.intensity = 3;
else if (/中强度|中等强度|中等|有点累|微微出汗|微汗/.test(t))
result.intensity = 2;
else if (/低强度|轻松|轻微|溜达|缓慢/.test(t))
result.intensity = 1;
let type = t;
if (durMatch)
type = type.replace(durMatch[0], "");
type = type.replace(/高强度|中强度|中等强度|低强度|剧烈|很累|大汗|气喘|有点累|微微出汗|微汗|轻松|轻微|缓慢/g, "").replace(/\d+(?:\.\d+)?/g, "").replace(/[,。.;、\s]+/g, "");
EXERCISE_FILLERS.forEach((f) => {
type = type.split(f).join("");
});
type = type.trim();
if (type && type.length <= 20)
result.exercise_type = type;
return result;
}
const FIELD_LABELS = {
fasting_blood_sugar: "空腹",
postprandial_blood_sugar: "餐后",
other_blood_sugar: "其他血糖",
systolic_pressure: "高压",
diastolic_pressure: "低压",
western_medicine: "西药",
insulin: "胰岛素",
breakfast_foods: "早餐",
lunch_foods: "午餐",
dinner_foods: "晚餐",
note: "备注",
exercise_type: "运动",
duration: "时长",
intensity: "强度"
};
const INTENSITY_LABELS = { 1: "低强度", 2: "中强度", 3: "高强度" };
function summarizeParsed(parsed) {
const parts = [];
Object.keys(parsed).forEach((k) => {
const label = FIELD_LABELS[k] || k;
let val = parsed[k];
if (k === "intensity")
val = INTENSITY_LABELS[val] || val;
parts.push(`${label}${val}`);
});
return parts.join(" · ");
}
exports.normalizeNumbers = normalizeNumbers;
exports.parseBloodPressure = parseBloodPressure;
exports.parseDiet = parseDiet;
exports.parseExercise = parseExercise;
exports.parseGlucose = parseGlucose;
exports.summarizeParsed = summarizeParsed;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/utils/voiceParse.js.map