Skip to content

Commit 29b2f58

Browse files
committed
chat(p0): 拆分session.mjs — DAG镜像迁入dagSync.mjs,RPC分发迁入rpcDispatcher.mjs
1 parent 08ee642 commit 29b2f58

3 files changed

Lines changed: 254 additions & 237 deletions

File tree

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { appendEvent, ensureChat, getDefaultChannelId, isValidChannelId, listChannelMessages } from './dag.mjs'
2+
3+
/**
4+
* 将日志条目的 content 字段规范为可写入 DAG 的纯文本。
5+
* @param {object} entry 聊天条目
6+
* @returns {string} 扁平文本
7+
*/
8+
function entryContentToMirrorText(entry) {
9+
const c = entry.content
10+
if (typeof c === 'string') return c
11+
if (c && typeof c === 'object') return JSON.stringify(c)
12+
return ''
13+
}
14+
15+
/**
16+
* 将非问候类聊天条目镜像为 DAG message 事件。
17+
* @param {string} groupId 聊天 ID
18+
* @param {object} entry 条目
19+
* @param {string} username 所有者
20+
* @returns {Promise<void>}
21+
*/
22+
export async function syncChatLogEntryToDag(groupId, entry, username) {
23+
try {
24+
if (entry.is_generating) return
25+
if (entry.timeSlice?.greeting_type) return
26+
if (!username) return
27+
const text = entryContentToMirrorText(entry)
28+
const hasFiles = Array.isArray(entry.files) && entry.files.length > 0
29+
if (!text.trim() && !hasFiles) return
30+
await ensureChat(username, groupId)
31+
let ts = entry.time_stamp ? +new Date(entry.time_stamp) : Date.now()
32+
if (!Number.isFinite(ts)) ts = Date.now()
33+
let sender = 'user'
34+
if (entry.role === 'char')
35+
sender = entry.name || entry.timeSlice?.charname || 'char'
36+
else if (entry.role === 'user')
37+
sender = 'user'
38+
else
39+
sender = entry.name || entry.role || 'system'
40+
const content = {
41+
text: text.slice(0, 200_000),
42+
chatLogEntryId: entry.id,
43+
role: entry.role,
44+
}
45+
if (hasFiles) content.fileCount = entry.files.length
46+
if (entry.visibility) content.visibility = entry.visibility
47+
if (entry.charVisibility?.length) content.charVisibility = entry.charVisibility
48+
const groupCh = entry.extension?.groupChannelId
49+
const channelIdForDag = isValidChannelId(groupCh)
50+
? groupCh
51+
: await getDefaultChannelId(username, groupId)
52+
try {
53+
const recentMessages = await listChannelMessages(username, groupId, channelIdForDag, { limit: 1 })
54+
const prevMessageEventId = recentMessages[recentMessages.length - 1]?.eventId
55+
if (prevMessageEventId) content.prevMessageEventId = prevMessageEventId
56+
} catch { /* ignore,prevMessageEventId 缺失时退化为旧行为 */ }
57+
await appendEvent(username, groupId, {
58+
type: 'message',
59+
channelId: channelIdForDag,
60+
sender,
61+
timestamp: ts,
62+
charId: entry.timeSlice?.charname,
63+
content,
64+
})
65+
}
66+
catch (e) {
67+
console.error(e)
68+
}
69+
}
70+
71+
/**
72+
* 将删除操作镜像为 DAG message_delete 事件。
73+
* @param {string} groupId 聊天 ID
74+
* @param {object} deletedEntry 被删条目
75+
* @param {string} username 所有者
76+
* @returns {Promise<void>}
77+
*/
78+
export async function mirrorDeleteToDag(groupId, deletedEntry, username) {
79+
try {
80+
if (!deletedEntry?.id || !username) return
81+
if (deletedEntry.timeSlice?.greeting_type) return
82+
await ensureChat(username, groupId)
83+
await appendEvent(username, groupId, {
84+
type: 'message_delete',
85+
channelId: await getDefaultChannelId(username, groupId),
86+
sender: 'local',
87+
timestamp: Date.now(),
88+
content: { chatLogEntryId: deletedEntry.id },
89+
})
90+
}
91+
catch (e) {
92+
console.error(e)
93+
}
94+
}
95+
96+
/**
97+
* 将编辑结果镜像为 DAG message_edit 事件。
98+
* @param {string} groupId 聊天 ID
99+
* @param {string} originalEntryId 原条目 UUID
100+
* @param {object} entry 编辑后的条目
101+
* @param {string} username 所有者
102+
* @returns {Promise<void>}
103+
*/
104+
export async function mirrorEditToDag(groupId, originalEntryId, entry, username) {
105+
try {
106+
if (!originalEntryId || !username) return
107+
if (entry.timeSlice?.greeting_type) return
108+
const text = entryContentToMirrorText(entry)
109+
const hasFiles = Array.isArray(entry.files) && entry.files.length > 0
110+
await ensureChat(username, groupId)
111+
const content = {
112+
chatLogEntryId: originalEntryId,
113+
text: text.slice(0, 200_000),
114+
}
115+
if (hasFiles) content.fileCount = entry.files.length
116+
await appendEvent(username, groupId, {
117+
type: 'message_edit',
118+
channelId: await getDefaultChannelId(username, groupId),
119+
sender: 'local',
120+
timestamp: Date.now(),
121+
content,
122+
})
123+
}
124+
catch (e) {
125+
console.error(e)
126+
}
127+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { isValidChannelId } from './dag.mjs'
2+
3+
/**
4+
* 创建本地 Char RPC 分发器。
5+
* @param {Function} loadChat 加载聊天元数据的函数
6+
* @param {Function} getChatRequest 构造 chatReplyRequest 的函数
7+
* @returns {Function} tryInvokeLocalCharRpc
8+
*/
9+
export function createCharRpcDispatcher(loadChat, getChatRequest) {
10+
/**
11+
* 尝试在本节点群会话上调用指定 `memberId` 对应角色的 Char 方法(用于 WS RPC)。
12+
*
13+
* @param {string} groupId 群组 id
14+
* @param {string} memberId `username:charname` 或纯 `charname`(后者视为群主用户下的角色)
15+
* @param {string} method 方法名(如 `GetReply`、`GetPrompt`)
16+
* @param {unknown[]} [args] 已 JSON 反序列化的参数表
17+
* @returns {Promise<{ kind: 'result', value: unknown } | { kind: 'not_local' } | { kind: 'method_not_found' } | { kind: 'error', message: string }>} 本机可调用时返回 `result`;非群主成员角色返回 `not_local`;无对应方法返回 `method_not_found`;执行异常返回 `error`
18+
*/
19+
return async function tryInvokeLocalCharRpc(groupId, memberId, method, args = []) {
20+
const list = Array.isArray(args) ? args : []
21+
const chatMetadata = await loadChat(groupId)
22+
if (!chatMetadata) return { kind: 'not_local' }
23+
24+
const owner = chatMetadata.username
25+
let charname = memberId
26+
if (typeof memberId === 'string' && memberId.includes(':')) {
27+
const idx = memberId.indexOf(':')
28+
const u = memberId.slice(0, idx)
29+
const cn = memberId.slice(idx + 1)
30+
if (u !== owner) return { kind: 'not_local' }
31+
charname = cn
32+
}
33+
34+
const char = chatMetadata.LastTimeSlice.chars[charname]
35+
if (!char) return { kind: 'not_local' }
36+
37+
/** @returns {string | null} 从 RPC 参数推断频道 id */
38+
const inferChannelId = () => {
39+
const ext = list[0]?.extension
40+
const ch = ext?.channelId
41+
if (isValidChannelId(ch)) return ch
42+
const ev = list[0]?.chatReplyRequest?.extension
43+
const ch2 = ev?.channelId
44+
if (isValidChannelId(ch2)) return ch2
45+
return null
46+
}
47+
48+
try {
49+
switch (method) {
50+
case 'UpdateInfo': {
51+
const fn = char.interfaces?.info?.UpdateInfo
52+
if (typeof fn !== 'function') return { kind: 'method_not_found' }
53+
return { kind: 'result', value: await fn(list[0] ?? []) }
54+
}
55+
case 'GetData': {
56+
const fn = char.interfaces?.config?.GetData
57+
if (typeof fn !== 'function') return { kind: 'method_not_found' }
58+
return { kind: 'result', value: await fn() }
59+
}
60+
case 'SetData': {
61+
const fn = char.interfaces?.config?.SetData
62+
if (typeof fn !== 'function') return { kind: 'method_not_found' }
63+
await fn(list[0])
64+
return { kind: 'result', value: null }
65+
}
66+
case 'GetGreeting':
67+
case 'GetGroupGreeting': {
68+
const fn = method === 'GetGreeting'
69+
? char.interfaces?.chat?.GetGreeting
70+
: char.interfaces?.chat?.GetGroupGreeting
71+
if (typeof fn !== 'function') return { kind: 'method_not_found' }
72+
const request = await getChatRequest(groupId, charname, inferChannelId())
73+
return { kind: 'result', value: await fn(request, Number(list[1]) || 0) }
74+
}
75+
case 'GetPrompt':
76+
case 'GetPromptForOther': {
77+
const fn = method === 'GetPrompt'
78+
? char.interfaces?.chat?.GetPrompt
79+
: char.interfaces?.chat?.GetPromptForOther
80+
if (typeof fn !== 'function') return { kind: 'method_not_found' }
81+
const request = await getChatRequest(groupId, charname, inferChannelId())
82+
return { kind: 'result', value: await fn(request) }
83+
}
84+
case 'TweakPrompt':
85+
case 'TweakPromptForOther': {
86+
const fn = method === 'TweakPrompt'
87+
? char.interfaces?.chat?.TweakPrompt
88+
: char.interfaces?.chat?.TweakPromptForOther
89+
if (typeof fn !== 'function') return { kind: 'result', value: null }
90+
const request = await getChatRequest(groupId, charname, inferChannelId())
91+
await fn(request, list[1], list[2], Number(list[3]) || 0)
92+
return { kind: 'result', value: null }
93+
}
94+
case 'GetReply': {
95+
const fn = char.interfaces?.chat?.GetReply
96+
if (typeof fn !== 'function') return { kind: 'method_not_found' }
97+
const request = await getChatRequest(groupId, charname, inferChannelId())
98+
return { kind: 'result', value: await fn(request) }
99+
}
100+
case 'onMessage': {
101+
const fn = char.interfaces?.chat?.onMessage
102+
if (typeof fn !== 'function') return { kind: 'result', value: false }
103+
const ev = list[0] || {}
104+
const onlineCount = Number(ev.onlineCount) || 1
105+
const rid = typeof ev.chatReplyRequest?.char_id === 'string' ? ev.chatReplyRequest.char_id : charname
106+
const request = await getChatRequest(groupId, rid, inferChannelId())
107+
return { kind: 'result', value: await fn({ chatReplyRequest: request, onlineCount }) }
108+
}
109+
case 'MessageEdit':
110+
case 'MessageEditing':
111+
case 'MessageDelete': {
112+
const fn = char.interfaces?.chat?.[method]
113+
if (typeof fn !== 'function') return { kind: 'method_not_found' }
114+
return { kind: 'result', value: await fn(list[0]) }
115+
}
116+
default:
117+
return { kind: 'method_not_found' }
118+
}
119+
}
120+
catch (e) {
121+
return { kind: 'error', message: String(e?.message || e) }
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)