dsh-metaphysics 0.56.0 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +103 -1
- package/README.md +26 -9
- package/client.almanac.js +66 -61
- package/client.bazi.js +98 -91
- package/client.chenggu.js +68 -63
- package/client.convert.js +68 -63
- package/client.daliuren.js +104 -97
- package/client.fengshui.js +65 -60
- package/client.hepan.js +99 -92
- package/client.js +392 -267
- package/client.liuyao.js +101 -94
- package/client.mbti.js +66 -61
- package/client.name.js +68 -63
- package/client.numerology.js +66 -61
- package/client.qimen.js +104 -97
- package/client.star.js +66 -61
- package/client.tarot.js +99 -94
- package/client.trend.js +104 -97
- package/client.xiaoliuren.js +66 -61
- package/client.zeri.js +66 -61
- package/client.ziwei.js +98 -91
- package/engine/bridge.js +211 -0
- package/engine/citations.js +603 -320
- package/engine/diff.js +59 -0
- package/engine/fengshui.js +718 -718
- package/engine/geju.js +16 -0
- package/engine/handle.js +192 -0
- package/engine/hepan.js +43 -0
- package/engine/index.js +168 -51
- package/engine/interpret-knowledge.js +3 -2
- package/engine/interpret-plain.js +4 -1
- package/engine/interpret.js +7 -3
- package/engine/qimen-ganying.js +134 -134
- package/engine/qimen-interpret.js +233 -233
- package/engine/revise.js +162 -0
- package/engine/trace.js +493 -456
- package/engine/zeri.js +89 -13
- package/index.js +59 -6
- package/package.json +6 -3
- package//345/221/275/347/220/206/346/216/222/347/233/230.html +3548 -1943
package/engine/bridge.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// C3 对话桥替代方案:深链 / 复制 / 结构化参数。
|
|
2
|
+
//
|
|
3
|
+
// 背景:面板原本靠会话槽位 conversation.input.right 拿 inputActions,再把提问写进输入框。
|
|
4
|
+
// 那个槽位不稳定(宿主改过名、也可能根本没声明),桥一断用户就没有路把盘送进对话。
|
|
5
|
+
// 这里给三条不依赖宿主的通道,桥能用时仍然优先用桥:
|
|
6
|
+
//
|
|
7
|
+
// 1. 深链:把「排这张盘用的参数」编码进 URL 片段(#mp=<handle>&q=<问题>)。
|
|
8
|
+
// 打开链接的人(换台机器、发给朋友、自己收藏)能还原同一张盘,不必重报出生信息。
|
|
9
|
+
// 2. 复制:两档现成文本——完整提问(含盘面摘要与回答骨架)与一句话精简版。
|
|
10
|
+
// 3. 结构化参数:一段 JSON,助手可以直接拿 args 去调 compute,不必解析自然语言。
|
|
11
|
+
//
|
|
12
|
+
// 约定:
|
|
13
|
+
// * 深链里用的是 C1 句柄(mc1.<body>.<校验位>),所以截断/改字会明确报错,不会静默排成另一张盘;
|
|
14
|
+
// * 链接只带「排盘参数 + 一句问题」,不带摘要、不带解析——摘要可能有好几 KB,URL 装不下;
|
|
15
|
+
// * 这里只做编解码与文案拼装,不排盘、不调用模型,node 与浏览器都能跑(不依赖 Buffer / btoa)。
|
|
16
|
+
import { encodeHandle, decodeHandle, HANDLE_PREFIX } from './handle.js';
|
|
17
|
+
import { askPrompt } from './followup.js';
|
|
18
|
+
|
|
19
|
+
/** 问题进链接前截断的长度:再长就交给「复制完整提问」那一档 */
|
|
20
|
+
export const LINK_MAX_QUESTION = 200;
|
|
21
|
+
/** 精简版里问题的截断长度 */
|
|
22
|
+
const BRIEF_MAX_QUESTION = 60;
|
|
23
|
+
|
|
24
|
+
const SYSTEM_LABEL = {
|
|
25
|
+
bazi: '八字', ziwei: '紫微', liuyao: '六爻', meihua: '梅花', daliuren: '大六壬',
|
|
26
|
+
qimen: '奇门', hepan: '合盘', tarot: '塔罗', almanac: '黄历', zeri: '择日',
|
|
27
|
+
chenggu: '称骨', xiaoliuren: '小六壬', star: '星座', numerology: '生命数字',
|
|
28
|
+
mbti: '人格', trend: '人生K线', fengshui: '风水', name: '起名', convert: '农历',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const labelOf = (system) => SYSTEM_LABEL[system] || system || '未知体系';
|
|
32
|
+
|
|
33
|
+
/** 只保留 URL 片段能用的一段:去掉已有 hash,其余原样 */
|
|
34
|
+
function baseOf(base) {
|
|
35
|
+
const s = String(base || '');
|
|
36
|
+
const i = s.indexOf('#');
|
|
37
|
+
return i < 0 ? s : s.slice(0, i);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function encodeQuestion(q) {
|
|
41
|
+
try { return encodeURIComponent(q); } catch (e) { return ''; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function decodeQuestion(s) {
|
|
45
|
+
try { return decodeURIComponent(s); } catch (e) { return String(s || ''); }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 参数 → 深链。没有可编码的参数(或参数里只有输出开关)时返回 ''。
|
|
50
|
+
*
|
|
51
|
+
* askLink({ system: 'bazi', datetime: '1990-05-12T14:30', gender: 'female' }, { question: '2027 年事业', base: url })
|
|
52
|
+
* → '<url>#mp=mc1.xxx.yyy&q=2027%20%E5%B9%B4%E4%BA%8B%E4%B8%9A'
|
|
53
|
+
*
|
|
54
|
+
* 也接受现成句柄:askLink({ handle: r.handle }, { question, base })。
|
|
55
|
+
*/
|
|
56
|
+
export function askLink(args = {}, opts = {}) {
|
|
57
|
+
const o = opts || {};
|
|
58
|
+
const raw = String(o.handle || (args && args.handle) || '').trim();
|
|
59
|
+
const handle = raw || encodeHandle(args || {});
|
|
60
|
+
if (!handle || !handle.startsWith(HANDLE_PREFIX)) return '';
|
|
61
|
+
const q = String(o.question || '').trim().slice(0, LINK_MAX_QUESTION);
|
|
62
|
+
const parts = [`mp=${handle}`];
|
|
63
|
+
if (q) parts.push(`q=${encodeQuestion(q)}`);
|
|
64
|
+
return `${baseOf(o.base)}#${parts.join('&')}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 深链 → 参数。任何不合法都不抛异常,返回 { ok:false, code, message },由调用方决定是
|
|
69
|
+
* 静默忽略还是提示一句——分享出去的老链接、被聊天软件截断的链接都会走到这里。
|
|
70
|
+
*
|
|
71
|
+
* 认这几种写法:完整 URL、只有片段(#mp=…)、只有查询串(?mp=…)、裸的 mp=…
|
|
72
|
+
*/
|
|
73
|
+
export function parseAskLink(text) {
|
|
74
|
+
const raw = String(text || '').trim();
|
|
75
|
+
if (!raw) return { ok: false, code: 'E_LINK', message: '没有链接内容:请粘贴带 #mp= 的完整链接' };
|
|
76
|
+
|
|
77
|
+
// 片段的优先级高于查询串:链接里同时有时,以片段为准(那是本插件自己写的)
|
|
78
|
+
let body = '';
|
|
79
|
+
const hashAt = raw.indexOf('#');
|
|
80
|
+
if (hashAt >= 0 && raw.slice(hashAt + 1).trim()) body = raw.slice(hashAt + 1).trim();
|
|
81
|
+
else {
|
|
82
|
+
const qAt = raw.indexOf('?');
|
|
83
|
+
if (qAt >= 0 && raw.slice(qAt + 1).trim()) body = raw.slice(qAt + 1).trim();
|
|
84
|
+
else if (/^mp=/.test(raw)) body = raw;
|
|
85
|
+
}
|
|
86
|
+
if (!body) return { ok: false, code: 'E_LINK', message: '链接里没有 #mp= 片段:这不是本插件生成的深链' };
|
|
87
|
+
|
|
88
|
+
const fields = {};
|
|
89
|
+
for (const seg of body.split('&')) {
|
|
90
|
+
if (!seg) continue;
|
|
91
|
+
const eq = seg.indexOf('=');
|
|
92
|
+
if (eq <= 0) continue;
|
|
93
|
+
const k = seg.slice(0, eq);
|
|
94
|
+
const v = seg.slice(eq + 1);
|
|
95
|
+
if (k === 'mp') fields.mp = v;
|
|
96
|
+
else if (k === 'q') fields.q = v;
|
|
97
|
+
}
|
|
98
|
+
if (!fields.mp) return { ok: false, code: 'E_LINK', message: '链接里没有 mp 参数(只有问题没有盘):无法还原盘面' };
|
|
99
|
+
|
|
100
|
+
let args;
|
|
101
|
+
try {
|
|
102
|
+
args = decodeHandle(fields.mp);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
return { ok: false, code: (e && e.code) || 'E_HANDLE', message: (e && e.message) || '句柄无法解析' };
|
|
105
|
+
}
|
|
106
|
+
const question = fields.q ? decodeQuestion(fields.q).slice(0, LINK_MAX_QUESTION) : '';
|
|
107
|
+
return {
|
|
108
|
+
ok: true,
|
|
109
|
+
handle: fields.mp,
|
|
110
|
+
args,
|
|
111
|
+
question,
|
|
112
|
+
system: args.system || '',
|
|
113
|
+
label: labelOf(args.system),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const splitDateTime = (dt) => {
|
|
118
|
+
const m = /^(\d{4}-\d{1,2}-\d{1,2})[T ](\d{1,2}:\d{2})/.exec(String(dt || '').trim());
|
|
119
|
+
if (!m) return { date: '', time: '' };
|
|
120
|
+
const [y, mo, d] = m[1].split('-');
|
|
121
|
+
return { date: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, time: m[2].padStart(5, '0') };
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 这些参数能原样填回表单;其余(partner / candidates / answers / mode / upper …)
|
|
126
|
+
* 由调用方提示「未还原」——只报真的填回去的,不假装全还原了。
|
|
127
|
+
*/
|
|
128
|
+
const FORM_KEYS = ['gender', 'address', 'trueSolar', 'lateZi', 'lines'];
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 深链 → 表单初值。面板与独立页共用一份,避免两边各自猜字段名。
|
|
132
|
+
* 返回 { tab, date, time, ... , question, restored[], rest{} };rest 是没还原的参数,用于如实提示。
|
|
133
|
+
*/
|
|
134
|
+
export function formOf(parsed) {
|
|
135
|
+
const p = parsed && parsed.ok ? parsed : null;
|
|
136
|
+
const args = (p && p.args) || {};
|
|
137
|
+
const dt = splitDateTime(args.datetime);
|
|
138
|
+
const out = {
|
|
139
|
+
tab: args.system || '',
|
|
140
|
+
date: dt.date,
|
|
141
|
+
time: dt.time,
|
|
142
|
+
question: (p && p.question) || '',
|
|
143
|
+
restored: [],
|
|
144
|
+
rest: {},
|
|
145
|
+
};
|
|
146
|
+
if (out.tab) out.restored.push('system');
|
|
147
|
+
if (dt.date) out.restored.push('datetime');
|
|
148
|
+
for (const k of FORM_KEYS) {
|
|
149
|
+
if (args[k] === undefined || args[k] === null) continue;
|
|
150
|
+
out[k] = args[k];
|
|
151
|
+
out.restored.push(k);
|
|
152
|
+
}
|
|
153
|
+
// 问题不是盘面参数,但它跟着链接走,单独记一笔
|
|
154
|
+
if (out.question) out.restored.push('question');
|
|
155
|
+
for (const [k, v] of Object.entries(args)) {
|
|
156
|
+
if (k === 'system' || k === 'datetime' || FORM_KEYS.includes(k)) continue;
|
|
157
|
+
out.rest[k] = v;
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** 未还原参数的提示文案:空对象返回 ''(不打扰) */
|
|
163
|
+
export function restNote(form) {
|
|
164
|
+
const keys = Object.keys((form && form.rest) || {});
|
|
165
|
+
if (!keys.length) return '';
|
|
166
|
+
return `链接里还有 ${keys.length} 项没填回表单(${keys.join('、')}):这些只在引擎调用里生效,页面上按默认值显示。`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 一张盘 → 三档复制文本 + 深链。
|
|
171
|
+
*
|
|
172
|
+
* full 完整提问(与桥发出去的是同一份,含盘面摘要、层次与回答骨架)
|
|
173
|
+
* brief 一句话版:体系 + 句柄 + 问题,够助手认出是哪张盘
|
|
174
|
+
* structured 结构化参数(JSON),助手可直接照 args 调 compute
|
|
175
|
+
* link 深链(没有句柄时为空)
|
|
176
|
+
*/
|
|
177
|
+
export function askPackages(result, opts = {}) {
|
|
178
|
+
const o = opts || {};
|
|
179
|
+
const r = result || {};
|
|
180
|
+
const system = r.system || '';
|
|
181
|
+
const label = labelOf(system);
|
|
182
|
+
const question = String(o.question || '').trim();
|
|
183
|
+
const handle = String(r.handle || '').trim();
|
|
184
|
+
|
|
185
|
+
let full = '';
|
|
186
|
+
try {
|
|
187
|
+
const p = askPrompt(r, { question, interpret: o.interpret });
|
|
188
|
+
full = (p && p.text) || String(r.summary || '');
|
|
189
|
+
} catch (e) {
|
|
190
|
+
full = String(r.summary || '');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const briefQ = question
|
|
194
|
+
? (question.length > BRIEF_MAX_QUESTION ? `${question.slice(0, BRIEF_MAX_QUESTION)}…` : question)
|
|
195
|
+
: '(未填问题:按摘要讲主线)';
|
|
196
|
+
const brief = handle
|
|
197
|
+
? `【命理追问】${label}|${handle}|问:${briefQ}`
|
|
198
|
+
: `【命理追问】${label}|问:${briefQ}`;
|
|
199
|
+
|
|
200
|
+
const args = {};
|
|
201
|
+
if (handle) args.handle = handle;
|
|
202
|
+
if (system) args.system = system;
|
|
203
|
+
if (question) args.question = question;
|
|
204
|
+
const structured = JSON.stringify({ tool: 'metaphysics', v: 1, args }, null, 2);
|
|
205
|
+
|
|
206
|
+
const link = handle ? askLink({ handle }, { question, base: o.base }) : '';
|
|
207
|
+
|
|
208
|
+
return { system, label, handle, question, full, brief, structured, link };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export default askPackages;
|