dsh-fish-soul 0.13.4 → 0.13.6
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/README.md +1 -1
- package/lib/client.behavior.js +6 -2
- package/lib/client.js +3 -2
- package/lib/client.persona.js +7 -4
- package/lib/client.soul.js +5 -1
- package/lib/routes-persona.js +15 -1
- package/lib/routes-v09.js +14 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/lib/client.behavior.js
CHANGED
|
@@ -125,7 +125,7 @@ window.__ModuleLoader__.load({
|
|
|
125
125
|
|
|
126
126
|
/** 每日日记 */
|
|
127
127
|
function Diary({ data, save, api, notify, load, relTime, ui }) {
|
|
128
|
-
const { h, Group, Row, Switch, Muted, Button, Empty, Tag } = ui;
|
|
128
|
+
const { h, Group, Row, Switch, Muted, Button, Empty, Tag, IconButton } = ui;
|
|
129
129
|
const d = data.diary;
|
|
130
130
|
const [busy, setBusy] = react.useState(false);
|
|
131
131
|
if (!d) return null;
|
|
@@ -145,7 +145,11 @@ window.__ModuleLoader__.load({
|
|
|
145
145
|
Row({ title: "发到 QQ 空间", desc: "默认关;SnowLuma 不支持的话会在动态里记一条失败" },
|
|
146
146
|
h(Switch, { label: "发到 QQ 空间", checked: c.qzone, onChange: (v) => save({ diary: { qzone: v } }) })),
|
|
147
147
|
topics.length === 0 ? null : h("div", { className: "fs-list" }, topics.map((x, i) => h("div", { className: "fs-item", key: `t${i}` },
|
|
148
|
-
Tag("想聊", "info"), h("span", { className: "fs-item-main" }, `${x.who ? `${x.who}:` : ""}${x.topic}`)
|
|
148
|
+
Tag("想聊", "info"), h("span", { className: "fs-item-main" }, `${x.who ? `${x.who}:` : ""}${x.topic}`),
|
|
149
|
+
h("span", { className: "fs-item-meta" }, IconButton("close", "删除", async () => {
|
|
150
|
+
const r = await api("POST", "/diary/topic-remove", { who: x.who ?? "", topic: x.topic });
|
|
151
|
+
if (r?.ok) { notify("删掉了"); load(); } else notify(r?.error ?? "删除失败");
|
|
152
|
+
}))))),
|
|
149
153
|
h("div", { className: "fs-list" }, (d.entries ?? []).length === 0 ? Empty("还没写过日记")
|
|
150
154
|
: d.entries.map((e) => h("div", { className: "fs-item", key: e.id, title: e.content },
|
|
151
155
|
h("span", { className: "fs-item-main" }, e.content.replace(/^\d{4}-\d{2}-\d{2} 的日记:/, "")),
|
package/lib/client.js
CHANGED
|
@@ -161,8 +161,9 @@ window.__ModuleLoader__.load({
|
|
|
161
161
|
h("select", { ...props, className: "fs-input" }, ...options), Icon("chevron", 12));
|
|
162
162
|
|
|
163
163
|
const Empty = (text) => h("div", { className: "fs-empty" }, text);
|
|
164
|
-
const Item = ({ key, lead, text, title, meta }) => h("div", { className: "fs-item", key },
|
|
165
|
-
lead ?? null, h("span", { className: "fs-item-main", title: title ?? text }, text), meta ? h("span", { className: "fs-item-meta" }, ...meta) : null
|
|
164
|
+
const Item = ({ key, lead, text, title, meta, onRemove }) => h("div", { className: "fs-item", key },
|
|
165
|
+
lead ?? null, h("span", { className: "fs-item-main", title: title ?? text }, text), meta ? h("span", { className: "fs-item-meta" }, ...meta) : null,
|
|
166
|
+
onRemove ? IconButton("close", "删除", onRemove) : null);
|
|
166
167
|
|
|
167
168
|
/** 表单:服务端数据到了才初始化一次,之后本地编辑、失焦保存。 */
|
|
168
169
|
function useForm(data, init) {
|
package/lib/client.persona.js
CHANGED
|
@@ -207,16 +207,19 @@ window.__ModuleLoader__.load({
|
|
|
207
207
|
const [data, setData] = useLoad(api, "/persona/state");
|
|
208
208
|
const [open, setOpen] = react.useState(null);
|
|
209
209
|
const [editing, setEditing] = react.useState(null);
|
|
210
|
-
const [fresh, setFresh] = react.useState(
|
|
210
|
+
const [fresh, setFresh] = react.useState(null);
|
|
211
211
|
const [busy, setBusy] = react.useState(false);
|
|
212
212
|
const fileRef = react.useRef(null);
|
|
213
213
|
if (data === null) return Empty(Dot("ongoing"));
|
|
214
214
|
if (data.error) return h("div", { className: "fs-notice", role: "alert" }, h("b", null, "人格读不到"), h("span", null, data.error));
|
|
215
215
|
const cur = data.current ?? {};
|
|
216
|
+
// 开关跟着人走(服务端 kv 持久化),进页面带回来;没到手的写入前不发请求
|
|
217
|
+
const freshOn = fresh ?? data.freshDefault === true;
|
|
218
|
+
const toggleFresh = (on) => { setFresh(on); api("POST", "/persona/fresh", { enabled: on === true }); };
|
|
216
219
|
|
|
217
220
|
async function post(path, body, okText) {
|
|
218
221
|
setBusy(true);
|
|
219
|
-
const r = await api("POST", path, { ...body, fresh });
|
|
222
|
+
const r = await api("POST", path, { ...body, fresh: freshOn });
|
|
220
223
|
setBusy(false);
|
|
221
224
|
if (r?.ok) { setData(r); setEditing(null); notify(okText(r)); } else notify(r?.error ?? "保存失败");
|
|
222
225
|
return r?.ok === true;
|
|
@@ -273,8 +276,8 @@ window.__ModuleLoader__.load({
|
|
|
273
276
|
Button({ onClick: () => fileRef.current?.click(), disabled: busy }, "导入角色卡"),
|
|
274
277
|
cur.card ? Button({ variant: "ghost", onClick: exportCard }, "导出") : null,
|
|
275
278
|
h("input", { ref: fileRef, type: "file", accept: ".json,.png,application/json,image/png", hidden: true, onChange: pickFile })),
|
|
276
|
-
|
|
277
|
-
|
|
279
|
+
Row({ title: "正在聊的也马上换", desc: freshOn ? "下一条消息起开新会话,旧聊天的口吻不带过来;记忆和关系都还在" : "沿用旧会话:新人格下一轮生效,但口吻可能要几轮才转过来" },
|
|
280
|
+
h(Switch, { label: "正在聊的也马上换", checked: freshOn, onChange: toggleFresh }))),
|
|
278
281
|
Group({ title: "人格模板", aside: Muted("都是 SillyTavern V2 卡,换上以后还能接着改") },
|
|
279
282
|
...(data.templates ?? []).map((t) => h(Template, {
|
|
280
283
|
key: t.id, t, h, ui, open: open === t.id, current: cur.template === t.id && cur.source === "persona.json",
|
package/lib/client.soul.js
CHANGED
|
@@ -350,6 +350,10 @@ window.__ModuleLoader__.load({
|
|
|
350
350
|
const [sel, setSel] = react.useState(() => new Set());
|
|
351
351
|
const qs = new URLSearchParams({ ...(owner !== "all" ? { ownerId: owner } : {}), ...(qApplied ? { q: qApplied } : {}), ...(source ? { source } : {}), ...(bulk ? { limit: "500" } : {}) }).toString();
|
|
352
352
|
const [data, load] = usePoll(api, `/inspector/overview${qs ? `?${qs}` : ""}`, 0);
|
|
353
|
+
async function dropPending(id) {
|
|
354
|
+
const r = await api("POST", "/inspector/forget", { targetType: "pending", id });
|
|
355
|
+
if (r?.ok) { notify("已删掉这条待办"); load(); } else notify(r?.error ?? "删除失败");
|
|
356
|
+
}
|
|
353
357
|
const [adding, setAdding] = react.useState("");
|
|
354
358
|
react.useEffect(() => { setSel(new Set()); }, [owner, qApplied, source, bulk]);
|
|
355
359
|
if (!data) return null;
|
|
@@ -409,7 +413,7 @@ window.__ModuleLoader__.load({
|
|
|
409
413
|
h("div", { className: "fs-list" }, memories.length === 0 ? Empty("还没有记忆")
|
|
410
414
|
: memories.map((m) => h(MemoryItem, { key: m.id, m, ui, api, notify, refresh: load, curGen: data.currentGenerationId ?? 1, selectable: bulk, selected: sel.has(m.id), onSelect })))),
|
|
411
415
|
pendings.length === 0 ? null : Group({ title: "待办", aside: Tag(String(pendings.length), "outline") },
|
|
412
|
-
h("div", { className: "fs-list" }, pendings.map((p) => Item({ key: p.id, lead: Muted(props.fmtDueAt(p.dueAt), { className: "fs-unit num" }), text: p.reason, meta: [p.channelId ?? ""] })))),
|
|
416
|
+
h("div", { className: "fs-list" }, pendings.map((p) => Item({ key: p.id, lead: Muted(props.fmtDueAt(p.dueAt), { className: "fs-unit num" }), text: p.reason, meta: [p.channelId ?? ""], onRemove: () => dropPending(p.id) })))),
|
|
413
417
|
h(ImportBackup, { ui, api, notify, relTime: props.relTime, onDone: load }));
|
|
414
418
|
}
|
|
415
419
|
|
package/lib/routes-persona.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 还有「她慢慢长出来的变化」(growth.js):看 / 确认 / 删 / 自己加 / 清空 / 开关。
|
|
5
5
|
* 换完立刻热加载(下一轮就用新人格);fresh = 正在聊的聊天下一条消息起开新会话(记忆在 soul.db,不受影响)。
|
|
6
6
|
*/
|
|
7
|
-
import { fishSoulDir, setKv } from './db.js';
|
|
7
|
+
import { fishSoulDir, getKv, setKv } from './db.js';
|
|
8
8
|
import { editGrowth, growthConfig, growthState } from './growth.js';
|
|
9
9
|
import { PERSONA_TEMPLATES, personaTemplate } from './persona-templates.js';
|
|
10
10
|
import { canUndoPersona, normalizeCard, parsePngCard, readPersonaCard, savePersonaCard, undoPersona } from './persona.js';
|
|
@@ -32,6 +32,7 @@ export async function handlePersonaRoute({ method, path, req, send, readBody, sa
|
|
|
32
32
|
},
|
|
33
33
|
templates: PERSONA_TEMPLATES.map(({ id, title, tagline, tags, card }) => ({ id, title, tagline, tags, card })),
|
|
34
34
|
canUndo: canUndoPersona(dir),
|
|
35
|
+
freshDefault: getKv(db, 'persona.freshDefault', false) === true,
|
|
35
36
|
growth: { config: growthConfig(getConfig().growth), items: growthState(db) }
|
|
36
37
|
};
|
|
37
38
|
};
|
|
@@ -44,6 +45,19 @@ export async function handlePersonaRoute({ method, path, req, send, readBody, sa
|
|
|
44
45
|
|
|
45
46
|
if (path === '/persona/state' && method === 'GET') { send(200, state()); return true; }
|
|
46
47
|
|
|
48
|
+
// 「正在聊的也马上换」开关:跟着人走(存 kv),不是跟着这次打开的页面走
|
|
49
|
+
if (path === '/persona/fresh' && method === 'POST') {
|
|
50
|
+
if (!guard()) return true;
|
|
51
|
+
try {
|
|
52
|
+
const b = await body();
|
|
53
|
+
setKv(db, 'persona.freshDefault', b.enabled === true);
|
|
54
|
+
send(200, state());
|
|
55
|
+
} catch (error) {
|
|
56
|
+
send(400, { ok: false, error: String(error instanceof Error ? error.message : error) });
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
if (path === '/persona/apply' && method === 'POST') {
|
|
48
62
|
if (!guard()) return true;
|
|
49
63
|
let card;
|
package/lib/routes-v09.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 习惯开关(心情影响行为 / 去 AI 味 / 合并转发 / 群里冷热 / 小互动 / 每轮判断心情 / 日记)、
|
|
4
4
|
* 每个群的群设定和冷热、按聊天选模型、记忆调试器、现在写一篇日记。
|
|
5
5
|
*/
|
|
6
|
-
import { listChatSessions } from './db.js';
|
|
6
|
+
import { listChatSessions, getKv, setKv } from './db.js';
|
|
7
7
|
import { behaviorConfig, chatModelsConfig, resolveChatModel, groupNotes, setGroupNote } from './behavior.js';
|
|
8
8
|
|
|
9
9
|
const BODY_MAX = 64 * 1024;
|
|
@@ -102,6 +102,19 @@ export async function handleV09Route({ method, path, req, send, readBody, sameOr
|
|
|
102
102
|
send(200, { ok: true, ...(await debugRecall({ ownerId, text, inGroup: b.inGroup === true })) });
|
|
103
103
|
return true;
|
|
104
104
|
}
|
|
105
|
+
// ── 契约 42:删掉一条日记里「想聊的」──
|
|
106
|
+
if (path === '/diary/topic-remove' && post) {
|
|
107
|
+
if (!guard()) return true;
|
|
108
|
+
const b = await body();
|
|
109
|
+
const t = getKv(db, 'diary.topics', null);
|
|
110
|
+
if (!t || !Array.isArray(t.items)) { send(200, { ok: false, error: '没有想聊的' }); return true; }
|
|
111
|
+
const idx = t.items.findIndex((x) => (x.who ?? '') === String(b.who ?? '') && x.topic === String(b.topic ?? ''));
|
|
112
|
+
if (idx === -1) { send(200, { ok: false, error: '这条不在了' }); return true; }
|
|
113
|
+
t.items.splice(idx, 1);
|
|
114
|
+
setKv(db, 'diary.topics', t);
|
|
115
|
+
send(200, { ok: true });
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
105
118
|
// ── 契约 41:现在写一篇日记 ──
|
|
106
119
|
if (path === '/diary/write-now' && post) {
|
|
107
120
|
if (!guard()) return true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-fish-soul",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.6",
|
|
4
4
|
"description": "鱼 · 人格、按人记忆(可纠正、有印象、能导入旧聊天记录)、日程自走与主动开口,直连 SnowLuma 收发 QQ(群里会自己插话),B站 / 抖音小号私信与自己刷视频;按功能选模型并统计用量花费,能听会说 QQ 语音、收藏表情包、学群里的梗,可选上网查;心情影响说话、去 AI 味、每个群的设定、按聊天选模型、每晚写日记(DSH 插件,单角色 feiyu)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|