feat: show per-stage timing (듣기/LLM/TTS) in the transcript channel
The transcript channel only showed STT and LLM seconds. Add wall-clock start/end times and durations for listening, LLM and TTS so it's obvious what takes long; STT surfaces as the gap between listening end and LLM start. - bridge: emit llm_start_ms/llm_end_ms on meta and tts_*_ms on the end event - bot: capture the listening window, assemble full timing after the stream, and render a per-stage breakdown in the transcript message Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -34,12 +34,66 @@ async function loadSelfbot(): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
interface TurnInfo {
|
||||
export interface TurnInfo {
|
||||
transcript: string;
|
||||
reply: string;
|
||||
note?: string;
|
||||
sttSec?: number;
|
||||
thinkSec?: number;
|
||||
/** Wall-clock epoch-ms markers for each pipeline stage. STT is the gap
|
||||
* between listenEndMs and llmStartMs. */
|
||||
listenStartMs?: number;
|
||||
listenEndMs?: number;
|
||||
llmStartMs?: number;
|
||||
llmEndMs?: number;
|
||||
ttsStartMs?: number;
|
||||
ttsEndMs?: number;
|
||||
}
|
||||
|
||||
/** Local wall-clock HH:MM:SS for an epoch-ms instant. */
|
||||
function clock(ms?: number): string {
|
||||
if (ms == null) return "?";
|
||||
const d = new Date(ms);
|
||||
const p = (n: number) => String(n).padStart(2, "0");
|
||||
return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||
}
|
||||
|
||||
/** Seconds between two epoch-ms instants, 1 decimal, or null if either side is
|
||||
* missing or the span is negative (clock skew / out-of-order markers). */
|
||||
function durSec(a?: number, b?: number): string | null {
|
||||
if (a == null || b == null) return null;
|
||||
const s = (b - a) / 1000;
|
||||
if (s < 0) return null;
|
||||
return s.toFixed(1);
|
||||
}
|
||||
|
||||
/** Build the transcript-channel message: transcript + reply, plus a per-stage
|
||||
* timing breakdown (listening / LLM / TTS) with start→end wall-clock times and
|
||||
* durations, so it's obvious what took long. Pure + exported for testing. */
|
||||
export function formatTurnMessage(info: TurnInfo): string {
|
||||
const head = info.transcript
|
||||
? `🎤 들음 → 🗣️ "${info.transcript}"\n🤖 답변: ${(info.reply || "").trim() || "(무응답)"}`
|
||||
: `🎤 들음 → ❌ ${info.note || "무시됨"}`;
|
||||
|
||||
const lines: string[] = [];
|
||||
const listen = durSec(info.listenStartMs, info.listenEndMs);
|
||||
if (listen != null) {
|
||||
lines.push(` 👂 듣기 ${listen}초 ${clock(info.listenStartMs)} → ${clock(info.listenEndMs)}`);
|
||||
}
|
||||
const llm = durSec(info.llmStartMs, info.llmEndMs);
|
||||
if (llm != null) {
|
||||
const stt = durSec(info.listenEndMs, info.llmStartMs);
|
||||
const gap = stt != null && Number(stt) >= 0.1 ? ` (STT/전송 ${stt}초)` : "";
|
||||
lines.push(` 🧠 LLM ${llm}초 ${clock(info.llmStartMs)} → ${clock(info.llmEndMs)}${gap}`);
|
||||
}
|
||||
const tts = durSec(info.ttsStartMs, info.ttsEndMs);
|
||||
if (tts != null) {
|
||||
lines.push(` 🔊 TTS ${tts}초 ${clock(info.ttsStartMs)} → ${clock(info.ttsEndMs)}`);
|
||||
}
|
||||
|
||||
if (!lines.length) return head;
|
||||
const lastEnd = info.ttsEndMs ?? info.llmEndMs ?? info.listenEndMs;
|
||||
const total = durSec(info.listenStartMs, lastEnd);
|
||||
const totalNote = total != null ? ` (합계 ${total}초)` : "";
|
||||
return `${head}\n⏱️ 타이밍${totalNote}\n${lines.join("\n")}`;
|
||||
}
|
||||
|
||||
/** Mirror EVERY heard utterance (and why it did/didn't answer) to a text
|
||||
@@ -47,17 +101,7 @@ interface TurnInfo {
|
||||
async function postTranscript(client: AnyClient, info: TurnInfo): Promise<void> {
|
||||
const chId = config.transcriptChannelId;
|
||||
if (!chId) return;
|
||||
const timing =
|
||||
info.sttSec != null || info.thinkSec != null
|
||||
? ` ⏱️ stt ${info.sttSec ?? "?"}s · llm ${info.thinkSec ?? "?"}s`
|
||||
: "";
|
||||
let msg: string;
|
||||
if (info.transcript) {
|
||||
const r = (info.reply || "").trim();
|
||||
msg = `🎤 들음 → 🗣️ "${info.transcript}"\n🤖 답변: ${r || "(무응답)"}${timing}`;
|
||||
} else {
|
||||
msg = `🎤 들음 → ❌ ${info.note || "무시됨"}${timing}`;
|
||||
}
|
||||
const msg = formatTurnMessage(info);
|
||||
try {
|
||||
const ch: any = await client.channels.fetch(chId).catch(() => null);
|
||||
if (ch?.send) await ch.send(msg);
|
||||
|
||||
Reference in New Issue
Block a user