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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  让一个 QQ 小号背后有"同一个人":固定人格、按人记记忆、有自己的生活节奏、会主动开口;同一个"她"同时出现在 QQ、B站、抖音。
4
4
 
5
- DSH 插件(单角色 `feiyu`)。版本 **0.13.4**。
5
+ DSH 插件(单角色 `feiyu`)。版本 **0.13.6**。
6
6
 
7
7
  ## 能力
8
8
 
@@ -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) {
@@ -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(true);
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
- Row({ title: "正在聊的也马上换", desc: fresh ? "下一条消息起开新会话,旧聊天的口吻不带过来;记忆和关系都还在" : "沿用旧会话:新人格下一轮生效,但口吻可能要几轮才转过来" },
277
- h(Switch, { label: "正在聊的也马上换", checked: fresh, onChange: setFresh }))),
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",
@@ -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
 
@@ -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.4",
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",