apps/web/src/lib/aiDisabledWorkflow.ts

1import {
2	confirmedHandoff,
3	emailFromMessage,
4	isHandoffCancellation,
5} from '@local/ai/components/workflows'
6import {
7	type AiCapabilities,
8	aiToolEnabled,
9} from '@local/config/ai-capabilities'
10import {
11	acceptedAgendaEmail,
12	acceptedSalesOffer,
13	needsAgendaInterests,
14	requestsTicketBooking,
15} from './aiFlows'
16import { supportEmail } from './aiResponsePolicy'
17
18type Turn = { role: 'assistant' | 'user'; text: string }
19
20/** Resolve a short confirmation from the latest relevant topic, not a generic offer alone. */
21function contactContext(message: string, history: Turn[]): string {
22	return (
23		[
24			message,
25			...history
26				.slice()
27				.reverse()
28				.map((turn) => turn.text),
29		].find((text) =>
30			/\b(?:sponsor(?:ship|ing)?|exhibit(?:or|ing)?|tickets?|passes|badges?|booking|agenda)\b/i.test(
31				text,
32			),
33		) || message
34	)
35}
36
37/** Disabled handoffs are ordinary chat replies, without collection, consent or a model call. */
38export function disabledWorkflowReply(
39	config: AiCapabilities,
40	message: string,
41	history: Turn[],
42): string | undefined {
43	// Cancellation is owned by the existing cancellation guard, regardless of selector mode.
44	if (isHandoffCancellation(history, message)) return undefined
45	// Reject entry into disabled workflows, rather than collecting details for
46	// an action the assistant cannot perform. Factual questions remain available.
47	if (!aiToolEnabled(config, 'submitLead')) {
48		if (requestsTicketBooking(message, history))
49			return 'For help buying tickets, please email [support@unleash.ai](mailto:support@unleash.ai). I can answer questions about the passes here, but I can’t submit a ticket enquiry through the chat.'
50		if (
51			/\b(?:(?:i|we)(?: would|['’]d)? (?:want|need|like) to|help (?:me|us)|(?:can|could) (?:i|we)) (?:sponsor|exhibit)\b|\b(?:put|connect) (?:me|us) (?:in touch|with).{0,30}(?:sponsor|exhibit)|\b(?:sponsorship|exhibitor) (?:enquiry|inquiry|request)\b/i.test(
52				message,
53			)
54		)
55			return 'For sponsorship or exhibiting, please email [customersuccess@unleash.ai](mailto:customersuccess@unleash.ai). I can answer questions about the opportunities here, but I can’t submit an enquiry through the chat.'
56	}
57	if (
58		!aiToolEnabled(config, 'buildAgenda') &&
59		(needsAgendaInterests(history, message, []) ||
60			/\b(?:build|create|make|plan|personalize|personalise)\b.{0,35}\b(?:agenda|schedule)\b/i.test(
61				message,
62			))
63	)
64		return 'I can’t build a personal agenda in this chat right now. You can browse the event’s agenda to choose your sessions.'
65	const previous =
66		history.findLast((turn) => turn.role === 'assistant')?.text || ''
67	const context = contactContext(message, history)
68	const chatOnly =
69		/\b(?:here|in (?:the )?chat|on screen)\b|\b(?:not|no|without)\s+(?:by\s+|an?\s+)?email\b/i.test(
70			message,
71		)
72	const agendaContext =
73		/email[^\n]*agenda|agenda[^\n]*email|email the selected sessions/i.test(
74			previous,
75		) ||
76		(/\b(?:name|email address)\b/i.test(previous) && /\bagenda\b/i.test(context))
77	const emailRequest =
78		!chatOnly &&
79		(acceptedAgendaEmail(history, message) ||
80			(agendaContext &&
81				(confirmedHandoff(history, message) ||
82					Boolean(emailFromMessage(message)))) ||
83			/\bemail\b.{0,35}\bagenda\b|\bagenda\b.{0,35}\bemail\b/i.test(message))
84	if (!aiToolEnabled(config, 'sendAgendaByEmail') && emailRequest) {
85		return 'Emailing agendas is currently unavailable. Nothing has been sent. You can keep or copy your selected agenda from this chat.'
86	}
87	if (
88		!aiToolEnabled(config, 'submitLead') &&
89		!agendaContext &&
90		(acceptedSalesOffer(history, message) ||
91			confirmedHandoff(history, message) ||
92			(/who should.*contact|(?:name|work email).*enquiry|name and work email/i.test(
93				previous,
94			) &&
95				Boolean(emailFromMessage(message))))
96	) {
97		return `Contact requests cannot currently be submitted through this chat. Nothing has been sent. Please contact ${supportEmail(context)} directly.`
98	}
99	return undefined
100}
101