apps/web/src/lib/agendaListing.ts

1import type { AgendaSession } from '@local/ai/components/api/pineconeUpload'
2
3/** A person question is about announcement status; absence in a snapshot cannot settle it. */
4export function speakingPerson(message: string): string | undefined {
5	const name = message
6		.trim()
7		.match(
8			/^(?:is|will)\s+(.{1,80}?)\s+(?:be\s+)?(?:speaking|speak)(?:\s|[?.!]|$)/i,
9		)?.[1]
10		.trim()
11	return name && !/\b(?:anyone|anybody|someone|somebody|there|who)\b/i.test(name)
12		? name
13		: undefined
14}
15
16/** Complete stage listings keep source names, speakers and exact session destinations together. */
17export function stageListingReply(
18	message: string,
19	sessions: AgendaSession[],
20): string | undefined {
21	const number = message.match(
22		/\b(?:what(?:['’]s| is)?|show|list)\b.{0,55}\bstage\s+(\d+)\b/i,
23	)?.[1]
24	if (!number) return undefined
25	const stage = new RegExp(`^Stage\\s+${Number(number)}(?:\\s|$)`, 'i')
26	const matches = sessions
27		.filter((session) => stage.test(session.room))
28		.sort((a, b) => `${a.day} ${a.start}`.localeCompare(`${b.day} ${b.start}`))
29	if (!matches.length)
30		return `I don’t have sessions for Stage ${Number(number)} in the current agenda. I can’t confirm its programme from this information.`
31	return `Here are the currently listed sessions for ${matches[0].room}:\n\n${matches
32		.slice(0, 12)
33		.map(
34			(session) =>
35				`- **${session.dayLabel || session.day}, ${session.start}–${session.end}** — ${session.url ? `[${session.title}](${session.url})` : session.title}${session.speakers ? `; ${session.speakers}` : '; speakers not listed yet'}`,
36		)
37		.join(
38			'\n',
39		)}${matches.length > 12 ? `\n\nShowing the first 12 of ${matches.length} listed sessions.` : ''}`
40}
41