feat: per-call LLM timing, speaker ID, cancel captures on leave

- llm.py: log each Ollama call's caller + total/load/prompt/gen durations
  so a slow voice turn is attributable to a specific internal call
  (router/enrichment/digest/main); a RELOAD marker flags cold reloads.
- voice.ts: track in-flight Opus captures and abort them on session
  destroy(); drop any utterance that finishes after the user left, so no
  trailing post-leave VAD turns are reported.
- userbot.ts: show the speaker's Discord user ID on each transcript line
  (answered and dropped) so it's clear whose audio produced the turn.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
javis-bot
2026-06-14 00:38:26 +09:00
parent 5c11c5f7e8
commit 44ebfeafa8
4 changed files with 83 additions and 3 deletions

View File

@@ -35,6 +35,9 @@ async function loadSelfbot(): Promise<any> {
}
export interface TurnInfo {
/** Discord user ID of the speaker, so the transcript shows whose audio
* produced each turn (and which user a dropped/VAD turn belongs to). */
user?: string;
transcript: string;
reply: string;
note?: string;
@@ -69,9 +72,10 @@ function durSec(a?: number, b?: number): string | null {
* 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 who = info.user ? `👤 ${info.user} ` : "";
const head = info.transcript
? `🎤 들음 → 🗣️ "${info.transcript}"\n🤖 답변: ${(info.reply || "").trim() || "(무응답)"}`
: `🎤 들음 → ❌ ${info.note || "무시됨"}`;
? `${who}🎤 들음 → 🗣️ "${info.transcript}"\n🤖 답변: ${(info.reply || "").trim() || "(무응답)"}`
: `${who}🎤 들음 → ❌ ${info.note || "무시됨"}`;
const lines: string[] = [];
const listen = durSec(info.listenStartMs, info.listenEndMs);
@@ -120,7 +124,7 @@ async function joinAndListen(client: AnyClient, channelId: string): Promise<void
// joinVoiceChannel (it exposes id, guild.id and guild.voiceAdapterCreator).
const session = await joinChannel(channel as unknown as VoiceBasedChannel);
session.onTurn = (info) => {
console.log(`🗣️ ${info.transcript || "(" + (info.note || "empty") + ")"}\n🤖 ${info.reply}`);
console.log(`👤 ${info.user || "?"} 🗣️ ${info.transcript || "(" + (info.note || "empty") + ")"}\n🤖 ${info.reply}`);
// Mirror every heard utterance (and the reply / drop reason) to a text
// channel so you can see what the bot understood even when it doesn't answer.
void postTranscript(client, info);