dsh-liuying-voice 1.2.1 → 1.3.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/lib/index.js +33 -0
- package/lib/persona-page.html +123 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -384,6 +384,26 @@ function mountSettingsPage(ctx) {
|
|
|
384
384
|
});
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
+
function mountPersonaPage(ctx) {
|
|
388
|
+
const web = ctx.get("webServer");
|
|
389
|
+
if (!web?.register) return null;
|
|
390
|
+
return web.register({
|
|
391
|
+
kind: "exact",
|
|
392
|
+
path: "/liuying-voice/persona",
|
|
393
|
+
handler: async (req, res) => {
|
|
394
|
+
try {
|
|
395
|
+
const html = readFileSync(path.join(PLUGIN_ROOT, "lib", "persona-page.html"), "utf8");
|
|
396
|
+
res.statusCode = 200;
|
|
397
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
398
|
+
res.end(html);
|
|
399
|
+
} catch {
|
|
400
|
+
res.statusCode = 500;
|
|
401
|
+
res.end("persona-page.html 缺失");
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
387
407
|
export async function apply(ctx, config = {}) {
|
|
388
408
|
diag(`apply 开始;webServer=${ctx.get("webServer") ? "有" : "无"};tools=${ctx.get("tools") ? "有" : "无"}`);
|
|
389
409
|
// 一次性把可能影响 python 子进程的环境变量记下来(排查 edge-tts 用)
|
|
@@ -425,6 +445,19 @@ export async function apply(ctx, config = {}) {
|
|
|
425
445
|
} catch (error) {
|
|
426
446
|
log(`设置页面挂载失败:${error?.message ?? error}`);
|
|
427
447
|
}
|
|
448
|
+
try {
|
|
449
|
+
const dispose = mountPersonaPage(ctx);
|
|
450
|
+
if (typeof dispose === "function") {
|
|
451
|
+
const prev = disposeSettings;
|
|
452
|
+
disposeSettings = () => {
|
|
453
|
+
if (typeof prev === "function") prev();
|
|
454
|
+
dispose();
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
log("人格卡页面已挂载:/liuying-voice/persona");
|
|
458
|
+
} catch (error) {
|
|
459
|
+
log(`人格卡页面挂载失败:${error?.message ?? error}`);
|
|
460
|
+
}
|
|
428
461
|
try {
|
|
429
462
|
const dispose = mountMemory(ctx);
|
|
430
463
|
disposeMemory = typeof dispose === "function" ? dispose : null;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>流萤 · 人格卡</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: dark; }
|
|
9
|
+
* { box-sizing: border-box; }
|
|
10
|
+
body { margin: 0; font: 15px/1.75 "Segoe UI", "Microsoft YaHei", sans-serif; background: #0c1113; color: #d8e6e4; }
|
|
11
|
+
.wrap { display: flex; min-height: 100vh; }
|
|
12
|
+
aside { width: 220px; flex: 0 0 220px; border-right: 1px solid #1e2a2d; padding: 22px 14px; background: #0f1517; }
|
|
13
|
+
aside h1 { font-size: 15px; margin: 0 0 16px; color: #8fd8c8; letter-spacing: .08em; font-weight: 600; }
|
|
14
|
+
aside a { display: block; color: #9db5b9; text-decoration: none; font-size: 13px; padding: 5px 8px; border-radius: 5px; margin: 2px 0; }
|
|
15
|
+
aside a:hover { background: #16201f; color: #cfe3e6; }
|
|
16
|
+
aside a.on { background: #1f3a34; color: #b8f0e2; }
|
|
17
|
+
main { flex: 1; padding: 30px 40px 60px; max-width: 860px; }
|
|
18
|
+
main h2 { color: #8fd8c8; font-size: 18px; margin: 26px 0 10px; border-bottom: 1px solid #1e2a2d; padding-bottom: 6px; }
|
|
19
|
+
main h3 { color: #b8d8d0; font-size: 15px; margin: 20px 0 8px; }
|
|
20
|
+
main p, main li { color: #c4d6d4; }
|
|
21
|
+
main strong { color: #eafaf5; }
|
|
22
|
+
main ul { padding-left: 22px; }
|
|
23
|
+
main blockquote { margin: 12px 0; padding: 8px 14px; border-left: 3px solid #2f5a50; color: #9db5b9; background: #10191b; }
|
|
24
|
+
main code { background: #16201f; padding: 1px 5px; border-radius: 4px; font-size: 13px; }
|
|
25
|
+
.muted { color: #5f7478; font-size: 13px; }
|
|
26
|
+
.toolbar { margin-bottom: 18px; display: flex; gap: 10px; }
|
|
27
|
+
.toolbar a { color: #4db6a0; text-decoration: none; font-size: 13px; }
|
|
28
|
+
.loading { color: #5f7478; }
|
|
29
|
+
</style>
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<div class="wrap">
|
|
33
|
+
<aside>
|
|
34
|
+
<h1>流萤 · 人格卡</h1>
|
|
35
|
+
<div id="nav" class="muted">加载中…</div>
|
|
36
|
+
</aside>
|
|
37
|
+
<main>
|
|
38
|
+
<div class="toolbar"><a id="rawLink" href="#" target="_blank">查看原文 ↗</a></div>
|
|
39
|
+
<div id="content" class="loading">加载中…</div>
|
|
40
|
+
</main>
|
|
41
|
+
</div>
|
|
42
|
+
<script>
|
|
43
|
+
const API = "/liuying-voice/memory";
|
|
44
|
+
const $ = (id) => document.getElementById(id);
|
|
45
|
+
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
46
|
+
|
|
47
|
+
// 极简 Markdown 渲染:标题 / 列表 / 粗体 / 代码 / 引用
|
|
48
|
+
function render(md) {
|
|
49
|
+
const lines = md.split(/\r?\n/);
|
|
50
|
+
let out = "", inList = false, inQuote = false;
|
|
51
|
+
const inline = (t) =>
|
|
52
|
+
esc(t)
|
|
53
|
+
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
54
|
+
.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
|
|
55
|
+
.replace(/~~([^~]+)~~/g, "<s>$1</s>");
|
|
56
|
+
const closeList = () => { if (inList) { out += "</ul>"; inList = false; } };
|
|
57
|
+
for (const line of lines) {
|
|
58
|
+
const t = line.trim();
|
|
59
|
+
if (!t) { closeList(); inQuote = false; continue; }
|
|
60
|
+
if (t.startsWith(">")) {
|
|
61
|
+
closeList();
|
|
62
|
+
if (!inQuote) { out += "<blockquote>"; inQuote = true; }
|
|
63
|
+
out += inline(t.slice(1).trim()) + "<br>";
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (inQuote) { out += "</blockquote>"; inQuote = false; }
|
|
67
|
+
const h = t.match(/^(#{1,4})\s+(.*)$/);
|
|
68
|
+
if (h) { closeList(); out += `<h${h[1].length + 1}>${inline(h[2])}</h${h[1].length + 1}>`; continue; }
|
|
69
|
+
if (/^[-*]\s+/.test(t)) {
|
|
70
|
+
if (!inList) { out += "<ul>"; inList = true; }
|
|
71
|
+
out += `<li>${inline(t.replace(/^[-*]\s+/, ""))}</li>`;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
closeList();
|
|
75
|
+
out += `<p>${inline(t)}</p>`;
|
|
76
|
+
}
|
|
77
|
+
closeList();
|
|
78
|
+
if (inQuote) out += "</blockquote>";
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function show(file) {
|
|
83
|
+
$("content").className = "loading";
|
|
84
|
+
$("content").textContent = "加载中…";
|
|
85
|
+
fetch(`${API}?p=${encodeURIComponent(file)}`, { cache: "no-store" })
|
|
86
|
+
.then((r) => r.text())
|
|
87
|
+
.then((html) => {
|
|
88
|
+
const m = html.match(/<pre>([\s\S]*)<\/pre>/);
|
|
89
|
+
const body = m ? m[1] : "";
|
|
90
|
+
const text = body.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, '"');
|
|
91
|
+
$("content").className = "";
|
|
92
|
+
$("content").innerHTML = render(text);
|
|
93
|
+
$("rawLink").href = `${API}?p=${encodeURIComponent(file)}`;
|
|
94
|
+
document.querySelectorAll("aside a").forEach((a) => a.classList.toggle("on", a.dataset.f === file));
|
|
95
|
+
})
|
|
96
|
+
.catch((e) => { $("content").textContent = "加载失败:" + e.message; });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fetch(API, { cache: "no-store" })
|
|
100
|
+
.then((r) => r.text())
|
|
101
|
+
.then((html) => {
|
|
102
|
+
const links = [...html.matchAll(/<li><a href="[^"]*?p=([^"&]+)">([^<]+)<\/a><\/li>/g)].map((m) => ({
|
|
103
|
+
rel: decodeURIComponent(m[1]),
|
|
104
|
+
name: m[2],
|
|
105
|
+
}));
|
|
106
|
+
const order = ["firefly-lore.md", "firefly-stories.md", "penacony-2.0.md", "3.8-终将实现的-台词.md", "penacony-full.md", "penacony-tasks.md", "README.md"];
|
|
107
|
+
links.sort((a, b) => {
|
|
108
|
+
const ia = order.indexOf(a.rel), ib = order.indexOf(b.rel);
|
|
109
|
+
return (ia < 0 ? 99 : ia) - (ib < 0 ? 99 : ib) || a.rel.localeCompare(b.rel);
|
|
110
|
+
});
|
|
111
|
+
$("nav").innerHTML = links
|
|
112
|
+
.map((l) => `<a href="#" data-f="${esc(l.rel)}">${esc(l.rel)}</a>`)
|
|
113
|
+
.join("");
|
|
114
|
+
document.querySelectorAll("aside a").forEach((a) => {
|
|
115
|
+
a.addEventListener("click", (ev) => { ev.preventDefault(); show(a.dataset.f); });
|
|
116
|
+
});
|
|
117
|
+
if (links.length) show(links[0].rel);
|
|
118
|
+
else $("content").textContent = "memory 目录为空";
|
|
119
|
+
})
|
|
120
|
+
.catch((e) => { $("nav").textContent = ""; $("content").textContent = "加载失败:" + e.message; });
|
|
121
|
+
</script>
|
|
122
|
+
</body>
|
|
123
|
+
</html>
|
package/package.json
CHANGED