dsh-liuying-voice 1.2.0 → 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/client.js +11 -1
- package/lib/index.js +33 -0
- package/lib/persona-page.html +123 -0
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
2
|
* dsh-liuying-voice client:设置页。
|
|
3
3
|
* 运行时格式与 DSH client 插件一致(__ModuleLoader__.load + require("react"))。
|
|
4
4
|
*/
|
|
@@ -113,6 +113,16 @@ window.__ModuleLoader__.load({
|
|
|
113
113
|
return h(
|
|
114
114
|
"div",
|
|
115
115
|
{ style: { display: "grid", gap: 12, padding: 4 } },
|
|
116
|
+
h(
|
|
117
|
+
"a",
|
|
118
|
+
{
|
|
119
|
+
href: "/liuying-voice",
|
|
120
|
+
target: "_blank",
|
|
121
|
+
rel: "noreferrer",
|
|
122
|
+
style: { fontSize: 13, color: "var(--dsh-accent, #4db6a0)", textDecoration: "none" },
|
|
123
|
+
},
|
|
124
|
+
"在浏览器打开独立设置页 ↗",
|
|
125
|
+
),
|
|
116
126
|
h(
|
|
117
127
|
"label",
|
|
118
128
|
{ style: { display: "flex", alignItems: "center", gap: 8 } },
|
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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-liuying-voice",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "流萤语音插件 for DeepSeek Harness:本地 edge-tts + RVC / GPT-SoVITS
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "流萤语音插件 for DeepSeek Harness:本地 edge-tts + RVC / GPT-SoVITS 双引擎,自带流萤参考音频、一键环境安装脚本、独立设置页面与 DSH 设置入口。开源 MIT。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"dsh",
|