dsh-im-ops/src/channels/shared/editable-message-stream.mjs
2026-08-16 14:47:16 +08:00

81 lines
2.3 KiB
JavaScript

export function splitMessageText(value, limit) {
const text = typeof value === 'string' ? value.trim() : '';
if (!text) return [];
const chunks = [];
let remaining = text;
while (remaining.length > limit) {
let cut = remaining.lastIndexOf('\n', limit);
if (cut < Math.floor(limit * 0.55)) cut = remaining.lastIndexOf(' ', limit);
if (cut < Math.floor(limit * 0.55)) cut = limit;
chunks.push(remaining.slice(0, cut).trim());
remaining = remaining.slice(cut).trimStart();
}
if (remaining) chunks.push(remaining);
return chunks;
}
export function createEditableMessageStream({
initialText = '正在处理…',
limit,
updateIntervalMs = 800,
create,
edit,
sendRemainder,
logger = console,
}) {
let messageId;
let pending = null;
let timer = null;
let inFlight = null;
let closed = false;
let lastSent = initialText;
const schedule = () => {
if (closed || timer !== null || inFlight || !pending) return;
timer = setTimeout(() => {
timer = null;
const text = pending;
pending = null;
const next = splitMessageText(text, limit)[0] ?? initialText;
inFlight = Promise.resolve(next === lastSent ? undefined : edit(messageId, next))
.then(() => { lastSent = next; })
.catch((error) => logger.warn?.('[dsh-im] streamed message update failed:', error))
.finally(() => {
inFlight = null;
schedule();
});
}, updateIntervalMs);
timer?.unref?.();
};
return {
async start() {
messageId = await create(initialText);
return this;
},
update(text) {
if (closed || typeof text !== 'string' || !text.trim()) return;
pending = text;
schedule();
},
async finish(text) {
if (closed) throw new Error('Message stream is already closed');
closed = true;
if (timer !== null) clearTimeout(timer);
timer = null;
pending = null;
await inFlight?.catch(() => undefined);
const chunks = splitMessageText(text, limit);
const first = chunks[0] ?? '处理完成。';
if (first !== lastSent) await edit(messageId, first);
lastSent = first;
for (const chunk of chunks.slice(1)) await sendRemainder(chunk);
},
cancel() {
closed = true;
pending = null;
if (timer !== null) clearTimeout(timer);
timer = null;
},
};
}