Files
household-bot/apps/bot/src/topic-history.ts

111 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { instantFromEpochSeconds, nowInstant, Temporal, type Instant } from '@household/domain'
import type { TopicMessageHistoryRecord, TopicMessageHistoryRepository } from '@household/ports'
export interface TopicHistoryTurn {
role: 'user' | 'assistant'
speaker: string
text: string
threadId: string | null
}
const MEMORY_LOOKUP_PATTERN =
/\b(?:do you remember|remember|what were we talking about|what did we say today)\b|(?:^|[^\p{L}])(?:помнишь|ты\s+помнишь|что\s+мы\s+сегодня\s+обсуждали|о\s+чем\s+мы\s+говорили)(?=$|[^\p{L}])/iu
export function shouldLoadExpandedChatHistory(text: string): boolean {
return MEMORY_LOOKUP_PATTERN.test(text.trim())
}
export function startOfCurrentDayInTimezone(
timezone: string,
referenceInstant = nowInstant()
): Instant {
const zoned = referenceInstant.toZonedDateTimeISO(timezone)
const startOfDay = Temporal.ZonedDateTime.from({
timeZone: timezone,
year: zoned.year,
month: zoned.month,
day: zoned.day,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
microsecond: 0,
nanosecond: 0
})
return startOfDay.toInstant()
}
export function historyRecordToTurn(record: TopicMessageHistoryRecord): TopicHistoryTurn {
return {
role: record.isBot ? 'assistant' : 'user',
speaker: record.senderDisplayName ?? (record.isBot ? 'Kojori Bot' : 'Unknown'),
text: record.rawText.trim(),
threadId: record.telegramThreadId
}
}
export function telegramMessageIdFromMessage(
message: { message_id?: number } | null | undefined
): string | null {
return typeof message?.message_id === 'number' ? message.message_id.toString() : null
}
export function telegramMessageSentAtFromMessage(
message: { date?: number } | null | undefined
): Instant | null {
return typeof message?.date === 'number' ? instantFromEpochSeconds(message.date) : null
}
export async function persistTopicHistoryMessage(input: {
repository: TopicMessageHistoryRepository | undefined
householdId: string
telegramChatId: string
telegramThreadId: string | null
telegramMessageId: string | null
telegramUpdateId: string | null
senderTelegramUserId: string | null
senderDisplayName: string | null
isBot: boolean
rawText: string
messageSentAt: Instant | null
}) {
const normalizedText = input.rawText.trim()
if (!input.repository || normalizedText.length === 0) {
return
}
await input.repository.saveMessage({
householdId: input.householdId,
telegramChatId: input.telegramChatId,
telegramThreadId: input.telegramThreadId,
telegramMessageId: input.telegramMessageId,
telegramUpdateId: input.telegramUpdateId,
senderTelegramUserId: input.senderTelegramUserId,
senderDisplayName: input.senderDisplayName,
isBot: input.isBot,
rawText: normalizedText,
messageSentAt: input.messageSentAt
})
}
export function formatThreadHistory(turns: readonly TopicHistoryTurn[]): string | null {
const lines = turns
.map((turn) => `${turn.speaker} (${turn.role}): ${turn.text}`)
.filter((line) => line.trim().length > 0)
return lines.length > 0 ? lines.join('\n') : null
}
export function formatSameDayChatHistory(turns: readonly TopicHistoryTurn[]): string | null {
const lines = turns
.map((turn) =>
turn.threadId
? `[thread ${turn.threadId}] ${turn.speaker} (${turn.role}): ${turn.text}`
: `${turn.speaker} (${turn.role}): ${turn.text}`
)
.filter((line) => line.trim().length > 0)
return lines.length > 0 ? lines.join('\n') : null
}