useChat.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. import { useState, useEffect, useCallback } from "react";
  2. import { notifications } from "@/lib/notifications";
  3. import { toast } from "sonner";
  4. import { generateReportFromMessages } from "@/utils/reports";
  5. import { Message, ChatState, ChatResponse, SuggestedPrompt, MedicalAlert } from "@/components/chatbot/types";
  6. export const MAX_MESSAGES = 25;
  7. interface UseChatProps {
  8. chatType: "medical" | "psychological";
  9. }
  10. export const useChat = ({ chatType }: UseChatProps) => {
  11. const [messages, setMessages] = useState<Message[]>([]);
  12. const [messageCount, setMessageCount] = useState(0);
  13. const [isLimitReached, setIsLimitReached] = useState(false);
  14. const [isGeneratingReport, setIsGeneratingReport] = useState(false);
  15. const [generatedReport, setGeneratedReport] = useState("");
  16. const [inputMessage, setInputMessage] = useState("");
  17. const [isLoading, setIsLoading] = useState(false);
  18. const [reportGenerated, setReportGenerated] = useState(false);
  19. const [showSuggestions, setShowSuggestions] = useState(true);
  20. const [showCompletedBanner, setShowCompletedBanner] = useState(false);
  21. const [currentSuggestions, setCurrentSuggestions] = useState<SuggestedPrompt[]>([]);
  22. const [showDynamicSuggestions, setShowDynamicSuggestions] = useState(false);
  23. const [inputDisabledForSuggestions, setInputDisabledForSuggestions] = useState(false);
  24. const [medicalAlertDetected, setMedicalAlertDetected] = useState<MedicalAlert | null>(null);
  25. const [isSchedulingFromAlert, setIsSchedulingFromAlert] = useState(false);
  26. const [showMedicalAlertBanner, setShowMedicalAlertBanner] = useState(false);
  27. const [isMedicalAlertMinimized, setIsMedicalAlertMinimized] = useState(false);
  28. const [crisisDetected, setCrisisDetected] = useState(false);
  29. const [showCrisisBanner, setShowCrisisBanner] = useState(false);
  30. const [sessionRecordId, setSessionRecordId] = useState<string | null>(null); // ID del reporte de la sesión actual
  31. const isPsychological = chatType === "psychological";
  32. const remainingMessages = isPsychological ? Infinity : Math.max(0, MAX_MESSAGES - messageCount);
  33. const isLastMessage = !isPsychological && remainingMessages === 1;
  34. // Cargar estado desde localStorage al iniciar
  35. useEffect(() => {
  36. const storageKey = `chatState_${chatType}`;
  37. const savedState = localStorage.getItem(storageKey);
  38. if (savedState) {
  39. try {
  40. const parsed: ChatState = JSON.parse(savedState);
  41. // Convertir timestamps de string a Date
  42. const messagesWithDates = (parsed.messages || []).map(msg => ({
  43. ...msg,
  44. timestamp: msg.timestamp ? new Date(msg.timestamp) : undefined
  45. }));
  46. setMessages(messagesWithDates);
  47. setMessageCount(parsed.messageCount || 0);
  48. setIsLimitReached(parsed.isLimitReached || false);
  49. setReportGenerated(parsed.reportGenerated || false);
  50. setCurrentSuggestions(parsed.currentSuggestions || []);
  51. // Si hay mensajes guardados, ocultar sugerencias iniciales
  52. if (messagesWithDates && messagesWithDates.length > 0) {
  53. setShowSuggestions(false);
  54. // Mostrar sugerencias dinámicas si el último mensaje es del asistente y no está en streaming
  55. const lastMessage = messagesWithDates[messagesWithDates.length - 1];
  56. if (lastMessage?.role === "assistant" && !lastMessage.isStreaming && lastMessage.suggestions) {
  57. setCurrentSuggestions(lastMessage.suggestions);
  58. setShowDynamicSuggestions(true);
  59. }
  60. } else {
  61. // Si no hay mensajes, asegurarse de que las sugerencias estén visibles
  62. setShowSuggestions(true);
  63. }
  64. } catch (error) {
  65. console.error("Error loading chat state:", error);
  66. // En caso de error, asegurar que las sugerencias estén visibles
  67. setShowSuggestions(true);
  68. }
  69. }
  70. }, [chatType]);
  71. // Guardar estado en localStorage cuando cambie
  72. useEffect(() => {
  73. const storageKey = `chatState_${chatType}`;
  74. const stateToSave: ChatState = {
  75. messages,
  76. messageCount,
  77. isLimitReached,
  78. reportGenerated,
  79. currentSuggestions,
  80. };
  81. localStorage.setItem(storageKey, JSON.stringify(stateToSave));
  82. }, [messages, messageCount, isLimitReached, reportGenerated, currentSuggestions, chatType]);
  83. // Función para generar reporte usando useCallback para evitar re-renderizados
  84. const generateMedicalReport = useCallback(async () => {
  85. if (isGeneratingReport || reportGenerated) return;
  86. setIsGeneratingReport(true);
  87. try {
  88. // Mostrar toast de generación
  89. notifications.records.generating();
  90. // Generar reporte
  91. const report = generateReportFromMessages(messages);
  92. setGeneratedReport(report);
  93. setReportGenerated(true);
  94. // Guardar en la base de datos
  95. const response = await fetch("/api/chat/report", {
  96. method: "POST",
  97. headers: {
  98. "Content-Type": "application/json",
  99. },
  100. body: JSON.stringify({
  101. content: report,
  102. messages: messages,
  103. chatType,
  104. }),
  105. });
  106. if (response.ok) {
  107. notifications.records.saved();
  108. } else {
  109. notifications.records.saveError();
  110. }
  111. } catch (error) {
  112. console.error("Error generando reporte:", error);
  113. notifications.records.generateError();
  114. } finally {
  115. setIsGeneratingReport(false);
  116. }
  117. }, [messages, isGeneratingReport, reportGenerated]);
  118. // Efecto para generar reporte cuando se alcanza el límite (solo para chat médico)
  119. useEffect(() => {
  120. if (
  121. !isPsychological &&
  122. messageCount >= MAX_MESSAGES &&
  123. !isLimitReached &&
  124. !reportGenerated &&
  125. !isLoading
  126. ) {
  127. // Solo generar reporte si el último mensaje es del asistente y no está en streaming
  128. if (
  129. messages.length > 0 &&
  130. messages[messages.length - 1].role === "assistant" &&
  131. !messages[messages.length - 1].isStreaming
  132. ) {
  133. // Esperar un poco antes de mostrar el banner para que el usuario vea la respuesta
  134. setTimeout(() => {
  135. setShowCompletedBanner(true);
  136. generateMedicalReport();
  137. }, 2000); // 2 segundos de delay
  138. }
  139. }
  140. }, [
  141. isPsychological,
  142. messageCount,
  143. isLimitReached,
  144. reportGenerated,
  145. generateMedicalReport,
  146. messages,
  147. isLoading,
  148. ]);
  149. const sendMessage = async (customMessage?: string, retryCount = 0) => {
  150. const messageToSend = customMessage || inputMessage.trim();
  151. if (!messageToSend || isLimitReached || isLoading) return;
  152. console.log("🚀 [CHAT] Iniciando envío de mensaje:", messageToSend);
  153. console.log("📊 [CHAT] Estado actual - Mensajes:", messageCount, "Límite alcanzado:", isLimitReached);
  154. const userMessage: Message = {
  155. role: "user",
  156. content: messageToSend,
  157. timestamp: new Date(),
  158. };
  159. setMessages((prev) => [...prev, userMessage]);
  160. setMessageCount((prev) => prev + 1);
  161. setInputMessage("");
  162. setIsLoading(true);
  163. setShowSuggestions(false); // Ocultar sugerencias iniciales después del primer mensaje
  164. setShowDynamicSuggestions(false); // Ocultar sugerencias dinámicas cuando el usuario envía un mensaje
  165. setInputDisabledForSuggestions(false); // Habilitar input inmediatamente cuando el usuario envía un mensaje
  166. const startTime = Date.now();
  167. console.log("⏱️ [CHAT] Iniciando petición a API en:", new Date().toISOString());
  168. // Configurar timeout de 15 segundos
  169. const controller = new AbortController();
  170. const timeoutId = setTimeout(() => {
  171. console.log("⏰ [CHAT] Timeout alcanzado, cancelando petición...");
  172. controller.abort();
  173. }, 15000); // 15 segundos
  174. try {
  175. // Llamada a OpenRouter API con STREAMING
  176. console.log("📡 [CHAT] Enviando petición a /api/chat...");
  177. const response = await fetch("/api/chat", {
  178. method: "POST",
  179. headers: {
  180. "Content-Type": "application/json",
  181. },
  182. body: JSON.stringify({
  183. message: messageToSend,
  184. messages: [...messages, userMessage],
  185. chatType,
  186. }),
  187. signal: controller.signal, // Agregar señal de cancelación
  188. });
  189. // Limpiar timeout si la petición fue exitosa
  190. clearTimeout(timeoutId);
  191. const responseTime = Date.now() - startTime;
  192. console.log("✅ [CHAT] Conexión establecida en:", responseTime, "ms");
  193. console.log("📋 [CHAT] Status de respuesta:", response.status, response.statusText);
  194. if (response.ok) {
  195. console.log("🔄 [CHAT] Procesando stream en tiempo real...");
  196. // Crear mensaje del asistente vacío para ir llenándolo
  197. const assistantMessage: Message = {
  198. role: "assistant",
  199. content: "",
  200. timestamp: new Date(),
  201. isStreaming: true,
  202. };
  203. setMessages((prev) => [...prev, assistantMessage]);
  204. console.log("🎬 [CHAT] Iniciando recepción de stream...");
  205. // Procesar el stream
  206. const reader = response.body?.getReader();
  207. const decoder = new TextDecoder();
  208. let buffer = "";
  209. let fullContent = "";
  210. let metadata: { medicalAlert?: string; suggestions?: SuggestedPrompt[]; crisisDetected?: boolean } = {};
  211. if (!reader) {
  212. throw new Error("No se pudo obtener el reader del stream");
  213. }
  214. try {
  215. while (true) {
  216. const { done, value } = await reader.read();
  217. if (done) {
  218. console.log("✅ [CHAT] Stream completado");
  219. break;
  220. }
  221. // Decodificar el chunk
  222. buffer += decoder.decode(value, { stream: true });
  223. // Procesar eventos SSE (formato: data: {json}\n\n)
  224. const lines = buffer.split('\n\n');
  225. buffer = lines.pop() || ""; // Guardar la última línea incompleta
  226. for (const line of lines) {
  227. if (line.startsWith('data: ')) {
  228. const data = line.slice(6); // Remover "data: "
  229. if (data === '[DONE]') {
  230. console.log("� [CHAT] Recibida señal de finalización");
  231. break;
  232. }
  233. try {
  234. const parsed = JSON.parse(data);
  235. if (parsed.type === 'content') {
  236. // Acumular contenido y actualizar en tiempo real
  237. fullContent += parsed.content;
  238. setMessages((prev) =>
  239. prev.map((msg, index) =>
  240. index === prev.length - 1 && msg.role === "assistant"
  241. ? { ...msg, content: fullContent }
  242. : msg
  243. )
  244. );
  245. } else if (parsed.type === 'metadata') {
  246. // Guardar metadatos
  247. metadata = {
  248. medicalAlert: parsed.medicalAlert,
  249. suggestions: parsed.suggestions,
  250. crisisDetected: parsed.crisisDetected
  251. };
  252. console.log("📦 [CHAT] Metadatos recibidos:", metadata);
  253. }
  254. } catch (parseError) {
  255. console.warn("⚠️ [CHAT] Error parseando evento SSE:", parseError);
  256. }
  257. }
  258. }
  259. }
  260. } finally {
  261. reader.releaseLock();
  262. }
  263. const totalTime = Date.now() - startTime;
  264. console.log("✅ [CHAT] Respuesta completa procesada en:", totalTime, "ms");
  265. // Detectar crisis en chat psicológico
  266. if (isPsychological && metadata.crisisDetected) {
  267. console.log("🚨 [CHAT] Crisis detectada en chat psicológico");
  268. setCrisisDetected(true);
  269. setShowCrisisBanner(true);
  270. }
  271. // Detectar alerta médica
  272. if (metadata.medicalAlert && metadata.medicalAlert !== "NO_AGENDAR" && !medicalAlertDetected) {
  273. console.log("🚨 [CHAT] Alerta médica detectada:", metadata.medicalAlert);
  274. setMedicalAlertDetected(metadata.medicalAlert as MedicalAlert);
  275. setShowMedicalAlertBanner(true);
  276. setIsMedicalAlertMinimized(false); // Expandir banner automáticamente cuando hay nueva alerta
  277. }
  278. // Marcar mensaje como completado y agregar metadatos
  279. setMessages((prev) =>
  280. prev.map((msg, index) =>
  281. index === prev.length - 1 && msg.role === "assistant"
  282. ? {
  283. ...msg,
  284. isStreaming: false,
  285. medicalAlert: metadata.medicalAlert as MedicalAlert | undefined,
  286. suggestions: metadata.suggestions
  287. }
  288. : msg
  289. )
  290. );
  291. // Mostrar sugerencias dinámicas después del streaming si no es el último mensaje
  292. // En chat psicológico no mostrar sugerencias dinámicas
  293. if (!isPsychological && messageCount + 1 < MAX_MESSAGES && metadata.suggestions) {
  294. setCurrentSuggestions(metadata.suggestions);
  295. setShowDynamicSuggestions(true);
  296. setInputDisabledForSuggestions(true);
  297. // Habilitar el input después de 3 segundos para dar tiempo a leer las sugerencias
  298. setTimeout(() => {
  299. setInputDisabledForSuggestions(false);
  300. }, 3000);
  301. }
  302. } else {
  303. console.log("❌ [CHAT] Error en respuesta de API - Status:", response.status);
  304. const errorText = await response.text();
  305. console.log("📄 [CHAT] Contenido del error:", errorText);
  306. // Respuesta de fallback si la API falla
  307. const fallbackMessage: Message = {
  308. role: "assistant",
  309. content: `Gracias por tu consulta sobre "${messageToSend}". Como asistente médico virtual, puedo ayudarte con información general sobre salud, pero recuerda que no puedo reemplazar la consulta con un profesional médico. ¿Hay algo específico que te gustaría saber?`,
  310. timestamp: new Date(),
  311. medicalAlert: "NO_AGENDAR",
  312. suggestions: [
  313. {
  314. title: "Síntomas de Gripe",
  315. emoji: "🤒",
  316. prompt: "¿Cuáles son los síntomas más comunes de la gripe y cómo diferenciarla de un resfriado?"
  317. },
  318. {
  319. title: "Fortalecer Inmunidad",
  320. emoji: "🛡️",
  321. prompt: "¿Qué alimentos y hábitos me ayudan a fortalecer mi sistema inmunológico?"
  322. },
  323. {
  324. title: "Cuándo Consultar",
  325. emoji: "🩺",
  326. prompt: "¿Cuándo debo consultar a un médico por síntomas de gripe o resfriado?"
  327. }
  328. ],
  329. };
  330. setMessages((prev) => [...prev, fallbackMessage]);
  331. // Mostrar sugerencias dinámicas si no es el último mensaje
  332. // En chat psicológico no mostrar sugerencias dinámicas
  333. if (!isPsychological && messageCount + 1 < MAX_MESSAGES) {
  334. setCurrentSuggestions(fallbackMessage.suggestions || []);
  335. setShowDynamicSuggestions(true);
  336. setInputDisabledForSuggestions(true);
  337. // Habilitar el input después de 3 segundos
  338. setTimeout(() => {
  339. setInputDisabledForSuggestions(false);
  340. }, 3000);
  341. }
  342. }
  343. } catch (error) {
  344. const totalTime = Date.now() - startTime;
  345. // Limpiar timeout
  346. clearTimeout(timeoutId);
  347. // Detectar si el error fue por timeout
  348. const isTimeout = error instanceof Error && error.name === 'AbortError';
  349. console.error("💥 [CHAT] Error enviando mensaje después de", totalTime, "ms:", error);
  350. console.error("🔍 [CHAT] Tipo de error:", error instanceof TypeError ? 'Network/Fetch Error' : isTimeout ? 'Timeout Error' : 'Other Error');
  351. console.error("📋 [CHAT] Detalles del error:", {
  352. name: error instanceof Error ? error.name : 'Unknown',
  353. message: error instanceof Error ? error.message : String(error),
  354. stack: error instanceof Error ? error.stack : 'No stack trace'
  355. });
  356. // Si fue timeout y es el primer intento, reintentar automáticamente
  357. if (isTimeout && retryCount === 0) {
  358. console.log("🔄 [CHAT] Timeout detectado, reintentando automáticamente...");
  359. // Remover el mensaje del usuario anterior antes de reintentar
  360. setMessages((prev) => prev.slice(0, -1));
  361. setMessageCount((prev) => prev - 1);
  362. setIsLoading(false);
  363. // Notificar al usuario
  364. toast.info("Reintentando...", {
  365. description: "La petición tardó demasiado, reintentando automáticamente.",
  366. });
  367. // Reintentar después de un pequeño delay
  368. setTimeout(() => {
  369. sendMessage(messageToSend, 1);
  370. }, 1000);
  371. return;
  372. }
  373. // Mensaje personalizado según el tipo de error
  374. let errorContent = "Lo siento, estoy teniendo problemas técnicos. Por favor, intenta de nuevo en unos momentos.";
  375. if (isTimeout) {
  376. errorContent = "La petición tardó demasiado tiempo y fue cancelada automáticamente. Por favor, intenta enviar tu mensaje nuevamente.";
  377. }
  378. // Respuesta de fallback en caso de error
  379. const fallbackMessage: Message = {
  380. role: "assistant",
  381. content: errorContent,
  382. timestamp: new Date(),
  383. medicalAlert: "NO_AGENDAR",
  384. suggestions: [
  385. {
  386. title: "Reintentar Consulta",
  387. emoji: "🔄",
  388. prompt: "¿Puedes intentar responder mi pregunta anterior sobre salud?"
  389. },
  390. {
  391. title: "Síntomas Comunes",
  392. emoji: "🩺",
  393. prompt: "¿Cuáles son los síntomas más comunes que debo vigilar en mi salud?"
  394. },
  395. {
  396. title: "Emergencias Médicas",
  397. emoji: "🚨",
  398. prompt: "¿Cuándo debo considerar que un síntoma es una emergencia médica?"
  399. }
  400. ],
  401. };
  402. setMessages((prev) => [...prev, fallbackMessage]);
  403. // Mostrar sugerencias dinámicas si no es el último mensaje
  404. // En chat psicológico no mostrar sugerencias dinámicas
  405. if (!isPsychological && messageCount + 1 < MAX_MESSAGES) {
  406. setCurrentSuggestions(fallbackMessage.suggestions || []);
  407. setShowDynamicSuggestions(true);
  408. setInputDisabledForSuggestions(true);
  409. // Habilitar el input después de 3 segundos
  410. setTimeout(() => {
  411. setInputDisabledForSuggestions(false);
  412. }, 3000);
  413. }
  414. } finally {
  415. const totalTime = Date.now() - startTime;
  416. console.log("🏁 [CHAT] Procesamiento completado en:", totalTime, "ms");
  417. setIsLoading(false);
  418. }
  419. };
  420. const resetChat = () => {
  421. setMessages([]);
  422. setMessageCount(0);
  423. setIsLimitReached(false);
  424. setIsGeneratingReport(false);
  425. setGeneratedReport("");
  426. setInputMessage("");
  427. setReportGenerated(false);
  428. setShowSuggestions(true); // Mostrar sugerencias iniciales al resetear
  429. setShowCompletedBanner(false);
  430. setCurrentSuggestions([]);
  431. setShowDynamicSuggestions(false);
  432. setInputDisabledForSuggestions(false);
  433. setMedicalAlertDetected(null);
  434. setShowMedicalAlertBanner(false);
  435. setIsSchedulingFromAlert(false);
  436. setCrisisDetected(false);
  437. setShowCrisisBanner(false);
  438. setSessionRecordId(null); // Limpiar el recordId de la sesión
  439. // Limpiar localStorage
  440. const storageKey = `chatState_${chatType}`;
  441. localStorage.removeItem(storageKey);
  442. notifications.chat.newConsultation();
  443. };
  444. const dismissCompletedBanner = () => {
  445. setShowCompletedBanner(false);
  446. };
  447. const handleResetWithReport = async () => {
  448. if (messages.length === 0) {
  449. resetChat();
  450. return;
  451. }
  452. try {
  453. // Generar reporte con la conversación actual
  454. const currentReport = generateReportFromMessages(messages);
  455. // Guardar el reporte en la base de datos
  456. const response = await fetch("/api/chat/report", {
  457. method: "POST",
  458. headers: {
  459. "Content-Type": "application/json",
  460. },
  461. body: JSON.stringify({
  462. content: currentReport,
  463. messages: messages,
  464. }),
  465. });
  466. if (response.ok) {
  467. notifications.records.savedNewConsultation();
  468. resetChat();
  469. } else {
  470. notifications.records.saveError();
  471. }
  472. } catch (error) {
  473. console.error("Error al generar reporte:", error);
  474. notifications.records.generateError();
  475. }
  476. };
  477. const handleScheduleFromAlert = async (onSuccess: (recordId: string) => void) => {
  478. console.log("🚀 [useChat] handleScheduleFromAlert iniciado");
  479. // Si ya existe un recordId de esta sesión, reutilizarlo
  480. if (sessionRecordId) {
  481. console.log("♻️ [useChat] Reutilizando recordId existente:", sessionRecordId);
  482. toast.info("Usando el reporte médico guardado previamente", {
  483. description: "El reporte de esta conversación ya fue creado"
  484. });
  485. onSuccess(sessionRecordId);
  486. return;
  487. }
  488. setIsSchedulingFromAlert(true);
  489. try {
  490. // Generar reporte con la conversación actual
  491. const currentReport = generateReportFromMessages(messages);
  492. console.log("📝 [useChat] Reporte generado, longitud:", currentReport.length);
  493. // Guardar el reporte en la base de datos
  494. const response = await fetch("/api/chat/report", {
  495. method: "POST",
  496. headers: {
  497. "Content-Type": "application/json",
  498. },
  499. body: JSON.stringify({
  500. content: currentReport,
  501. messages: messages,
  502. }),
  503. });
  504. if (response.ok) {
  505. const data = await response.json();
  506. const recordId = data.id; // ID del Record creado
  507. console.log("✅ [useChat] Record creado exitosamente, ID:", recordId);
  508. // Guardar el recordId en el estado de la sesión
  509. setSessionRecordId(recordId);
  510. // Callback con el recordId para abrir el modal de cita
  511. onSuccess(recordId);
  512. notifications.records.saved();
  513. } else {
  514. console.error("❌ [useChat] Error al guardar reporte:", response.status);
  515. notifications.records.saveError();
  516. }
  517. } catch (error) {
  518. console.error("❌ [useChat] Error al generar reporte para cita:", error);
  519. notifications.records.generateError();
  520. } finally {
  521. setIsSchedulingFromAlert(false);
  522. }
  523. };
  524. const dismissMedicalAlertBanner = () => {
  525. setIsMedicalAlertMinimized(true); // Minimizar en lugar de ocultar
  526. };
  527. const expandMedicalAlertBanner = () => {
  528. setIsMedicalAlertMinimized(false); // Expandir el banner
  529. };
  530. const dismissCrisisBanner = () => {
  531. setShowCrisisBanner(false);
  532. };
  533. return {
  534. messages,
  535. messageCount,
  536. isLimitReached,
  537. isGeneratingReport,
  538. generatedReport,
  539. inputMessage,
  540. setInputMessage,
  541. isLoading,
  542. reportGenerated,
  543. showSuggestions,
  544. showCompletedBanner,
  545. remainingMessages,
  546. isLastMessage,
  547. sendMessage,
  548. resetChat,
  549. handleResetWithReport,
  550. dismissCompletedBanner,
  551. setGeneratedReport,
  552. currentSuggestions,
  553. showDynamicSuggestions,
  554. setShowDynamicSuggestions,
  555. inputDisabledForSuggestions,
  556. setInputDisabledForSuggestions,
  557. medicalAlertDetected,
  558. showMedicalAlertBanner,
  559. isMedicalAlertMinimized,
  560. isSchedulingFromAlert,
  561. handleScheduleFromAlert,
  562. dismissMedicalAlertBanner,
  563. expandMedicalAlertBanner,
  564. crisisDetected,
  565. showCrisisBanner,
  566. dismissCrisisBanner,
  567. };
  568. };;