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