fix: prevent double self-introduction in reschedule MSG 2
- reminder-graph: add stripIntroFromFallback() to remove 'Hi! My name is Lisa...' from any saved rescheduleGreeting template before using it as the MSG 2 fallback - reminder-graph: add explicit 'Do NOT introduce yourself' to rescheduleNode Gemini prompt so the AI never adds its own intro to MSG 2 - ai-chat-templates-card: add hasIntroPattern() warning on the Reschedule Patients template field — shows an amber alert if the saved template starts with a self-introduction, guiding users to remove it Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -111,10 +111,25 @@ async function confirmNode(state: GraphStateType, config: any) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip any leading self-introduction ("Hi! My name is Lisa...") from a
|
||||
* reschedule greeting template so it can be used as MSG 2 without duplicating
|
||||
* the intro that was already sent as MSG 1.
|
||||
*/
|
||||
function stripIntroFromFallback(text: string): string {
|
||||
// Matches patterns like: "Hi! My name is Lisa, ... . " or "Hi, I'm Lisa ... . "
|
||||
const stripped = text
|
||||
.replace(/^(Hi[!,]?\s*)?(My name is|I'?m|I am)\s+[^.!?]+[.!?]\s*/i, "")
|
||||
.trim();
|
||||
return stripped || text;
|
||||
}
|
||||
|
||||
async function rescheduleNode(state: GraphStateType, config: any) {
|
||||
const apiKey: string | undefined = config?.configurable?.apiKey;
|
||||
const lang = state.language || "English";
|
||||
const fallback = state.rescheduleGreeting || (RESCHEDULE_FALLBACKS[lang] ?? RESCHEDULE_FALLBACKS["English"]!);
|
||||
// Strip any self-intro from the saved template — intro is already sent as MSG 1
|
||||
const rawFallback = state.rescheduleGreeting || (RESCHEDULE_FALLBACKS[lang] ?? RESCHEDULE_FALLBACKS["English"]!);
|
||||
const fallback = stripIntroFromFallback(rawFallback);
|
||||
|
||||
if (!apiKey) return { reply: fallback };
|
||||
|
||||
@@ -124,7 +139,7 @@ async function rescheduleNode(state: GraphStateType, config: any) {
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
`You are a friendly dental office assistant. The patient cannot make their appointment. Write a short, empathetic SMS reply (1-2 sentences max) acknowledging they can't make it and asking when they would like to reschedule. You MUST reply in ${lang}. Do not add any formatting or extra text.`,
|
||||
`You are a friendly dental office assistant. The patient cannot make their appointment. Write a short, empathetic SMS reply (1-2 sentences max) acknowledging they can't make it and asking when they would like to reschedule. Do NOT introduce yourself or say your name — the introduction has already been sent in a separate message. You MUST reply in ${lang}. No formatting, no extra text.`,
|
||||
},
|
||||
{ role: "user", content: `Patient replied: "${state.message}"` },
|
||||
]);
|
||||
|
||||
@@ -48,6 +48,11 @@ function unsupportedVars(text: string): string[] {
|
||||
);
|
||||
}
|
||||
|
||||
/** True if text starts with a self-introduction ("Hi! My name is Lisa…"). */
|
||||
function hasIntroPattern(text: string): boolean {
|
||||
return /^(Hi[!,]?\s*)?(My name is|I'?m|I am)\s+/i.test(text.trim());
|
||||
}
|
||||
|
||||
export function AiChatTemplatesCard() {
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -203,7 +208,9 @@ export function AiChatTemplatesCard() {
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{templates_list.map((t) => {
|
||||
const badVars = t.key === "reminderSms" ? unsupportedVars(t.value) : [];
|
||||
const badVars = t.key === "reminderSms" ? unsupportedVars(t.value) : [];
|
||||
const hasIntro = t.key === "reschedule" && hasIntroPattern(t.value);
|
||||
const hasWarn = badVars.length > 0 || hasIntro;
|
||||
return (
|
||||
<div key={t.key} className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -216,7 +223,7 @@ export function AiChatTemplatesCard() {
|
||||
onChange={(e) => t.onChange(e.target.value)}
|
||||
placeholder={t.placeholder}
|
||||
rows={3}
|
||||
className={`text-sm resize-none ${badVars.length ? "border-amber-400 focus-visible:ring-amber-400" : ""}`}
|
||||
className={`text-sm resize-none ${hasWarn ? "border-amber-400 focus-visible:ring-amber-400" : ""}`}
|
||||
/>
|
||||
{/* Unsupported variable warning */}
|
||||
{badVars.length > 0 && (
|
||||
@@ -229,6 +236,18 @@ export function AiChatTemplatesCard() {
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Self-intro warning for reschedule greeting */}
|
||||
{hasIntro && (
|
||||
<div className="flex items-start gap-2 rounded-md bg-amber-50 border border-amber-300 px-3 py-2 text-xs text-amber-800">
|
||||
<span className="mt-0.5">⚠️</span>
|
||||
<span>
|
||||
This template starts with a self-introduction ("Hi! My name is Lisa…").
|
||||
The AI introduction is already sent as a <strong>separate first message</strong> —
|
||||
this template is only used as the <strong>second message</strong> (the intent response).
|
||||
Remove the intro to avoid the patient receiving "My name is Lisa…" twice.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Live preview */}
|
||||
{officeName && t.value.includes("{officeName}") && (
|
||||
<p className="text-xs text-muted-foreground italic pl-1">
|
||||
|
||||
Reference in New Issue
Block a user