diff --git a/CHANGELOG.md b/CHANGELOG.md index a5fabed..1482c03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,13 +16,18 @@ - **Community Interest Targeting**: Community involvement question now only shown to users who provide contact information - **Anonymous Feedback Improvements**: Community interest field hidden from summary for anonymous submissions - **Focus Management**: Fixed keyboard focus jumping to previous questions; now properly targets current question only +- **Q&A Response System**: Replaced unreliable `streamMessage` with direct API calls using `injectMessage` for consistent response display ### Fixed +- **Critical Q&A Display Issue**: Fixed problem where Q&A responses stopped displaying after 3-4 questions due to react-chatbotify's `streamMessage` accumulating state +- **Query ID Generation**: Implemented UUID-based query ID generation directly in QA flow to ensure proper feedback tracking +- **Response Reliability**: Q&A responses now display consistently regardless of conversation length - Resolved form state persistence issues where previous session data would interfere with new submissions - Fixed multiple checkbox selection display in feedback summary (was showing only one selection) - Corrected timing issues with form context updates using chatState.userInput pattern - Fixed anonymous feedback showing previous contact information in summary - Improved keyboard accessibility for checkboxes with proper focus management +- Removed broken fallback loop flow that used deprecated `streamMessage` approach ## [2.0.0-rc.1] - 2025-05-28 diff --git a/README.md b/README.md index b6b8a70..ad081f8 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,6 @@ The bot supports several conversation flows: - Built-in feedback system with thumbs up/down options after each response - Automatic feedback tracking and analytics - Users can provide feedback or continue asking questions immediately -- Negative feedback offers direct path to support ticket creation - Requires user to be logged in ### 🎫 Support Ticket Flows @@ -238,7 +237,7 @@ REACT_APP_API_KEY=your-api-key-here The bot integrates with the ACCESS Q&A API and JSM (Jira Service Management) for ticket creation. Configure your backend endpoints to handle: -- Q&A queries with streaming responses +- Q&A queries with JSON responses - Support ticket creation with ProForma field mapping - File upload processing - User authentication and session management @@ -406,4 +405,4 @@ npm install @snf/access-qa-bot@beta #### 📚 Documentation - Updated README with comprehensive feature list and integration examples - Added detailed API documentation and configuration options -- Included changelog for version tracking \ No newline at end of file +- Included changelog for version tracking diff --git a/src/components/QABot.js b/src/components/QABot.js index 8bce8c1..10ba88f 100644 --- a/src/components/QABot.js +++ b/src/components/QABot.js @@ -7,7 +7,6 @@ import { v4 as uuidv4 } from 'uuid'; import BotController from './BotController'; import useThemeColors from '../hooks/useThemeColors'; import useChatBotSettings from '../hooks/useChatBotSettings'; -import useHandleAIQuery from '../hooks/useHandleAIQuery'; import useUpdateHeader from '../hooks/useUpdateHeader'; import useRingEffect from '../hooks/useRingEffect'; import useFocusableSendButton from '../hooks/useFocusableSendButton'; @@ -55,12 +54,11 @@ const QABotInternal = React.forwardRef((props, botRef) => { accessId } = props; - const finalApiKey = apiKey || ((typeof process !== 'undefined' && process.env) ? process.env.REACT_APP_API_KEY : null); + const finalApiKey = apiKey || process.env.REACT_APP_API_KEY || null; const [isBotLoggedIn, setIsBotLoggedIn] = useState(isLoggedIn !== undefined ? isLoggedIn : false); const sessionIdRef = useRef(getOrCreateSessionId()); const sessionId = sessionIdRef.current; - const [currentQueryId, setCurrentQueryId] = useState(null); // Use Form Context instead of local state const { ticketForm, feedbackForm, updateTicketForm, updateFeedbackForm, resetTicketForm, resetFeedbackForm } = useFormContext(); @@ -72,8 +70,6 @@ const QABotInternal = React.forwardRef((props, botRef) => { } }, [isLoggedIn]); - // Initialize currentQueryId as null - will be set by useHandleAIQuery - // when actual queries are processed // Listen for chat window toggle events from react-chatbotify useEffect(() => { @@ -105,7 +101,6 @@ const QABotInternal = React.forwardRef((props, botRef) => { loginUrl }); - const handleQuery = useHandleAIQuery(finalApiKey, sessionId, setCurrentQueryId); const formContext = useMemo(() => ({ ticketForm: ticketForm || {}, @@ -120,9 +115,7 @@ const QABotInternal = React.forwardRef((props, botRef) => { welcomeMessage, isBotLoggedIn, loginUrl, - handleQuery, sessionId, - currentQueryId, ticketForm, setTicketForm: updateTicketForm, feedbackForm, @@ -134,7 +127,7 @@ const QABotInternal = React.forwardRef((props, botRef) => { name: userName || null, accessId: accessId || null } - }), [welcomeMessage, isBotLoggedIn, loginUrl, handleQuery, sessionId, currentQueryId, ticketForm, feedbackForm, updateTicketForm, updateFeedbackForm, formContext, finalApiKey, userEmail, userName, accessId]); + }), [welcomeMessage, isBotLoggedIn, loginUrl, sessionId, ticketForm, feedbackForm, updateTicketForm, updateFeedbackForm, formContext, finalApiKey, userEmail, userName, accessId]); useUpdateHeader(isBotLoggedIn, containerRef); useRingEffect(ringEffect, containerRef); diff --git a/src/config/constants.js b/src/config/constants.js index e766699..8d29bc4 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -4,8 +4,8 @@ export const DEFAULT_CONFIG = { WELCOME_MESSAGE_LOGGED_OUT: 'To ask questions, please log in.', WELCOME_MESSAGE_LOGIN_TRANSITION: 'Welcome! You are now logged in. What can I help you with?', WELCOME_MESSAGE_LOGOUT_TRANSITION: 'You have been logged out.', - API_ENDPOINT: 'https://access-ai.ccs.uky.edu/api/query', - //API_ENDPOINT: 'https://access-ai.ccs.uky.edu:543/api/query', + API_ENDPOINT: 'https://access-ai-grace1-external.ccs.uky.edu/access/chat/api/', + RATING_ENDPOINT: 'https://access-ai-grace1-external.ccs.uky.edu/access/chat/rating/', // Netlify function URL - this should point to the Netlify functions endpoint for ticket submission // NOT the Q&A API endpoint @@ -32,6 +32,19 @@ export const DEFAULT_CONFIG = { } }; +// Runtime functions to get endpoints (handles env vars that may not be available at build time) +export const getApiEndpoint = () => { + return (typeof process !== 'undefined' && process.env?.REACT_APP_API_ENDPOINT) + ? process.env.REACT_APP_API_ENDPOINT + : DEFAULT_CONFIG.API_ENDPOINT; +}; + +export const getRatingEndpoint = () => { + return (typeof process !== 'undefined' && process.env?.REACT_APP_RATING_ENDPOINT) + ? process.env.REACT_APP_RATING_ENDPOINT + : DEFAULT_CONFIG.RATING_ENDPOINT; +}; + // Helper functions from strings.js export const buildWelcomeMessage = (isLoggedIn, welcomeMessage) => { if (isLoggedIn) { @@ -44,5 +57,6 @@ export const buildWelcomeMessage = (isLoggedIn, welcomeMessage) => { export const getApiKey = (providedApiKey) => { // Return provided API key if available, otherwise fall back to environment variable - return providedApiKey || ((typeof process !== 'undefined' && process.env) ? process.env.REACT_APP_API_KEY : null); + // Simplified for better Netlify compatibility + return providedApiKey || process.env.REACT_APP_API_KEY || null; }; \ No newline at end of file diff --git a/src/index.js b/src/index.js index bf5838b..cf90a74 100644 --- a/src/index.js +++ b/src/index.js @@ -109,7 +109,7 @@ function ExampleApp() { open={chatOpen} onOpenChange={setChatOpen} loginUrl="/login" - apiKey={(typeof process !== 'undefined' && process.env) ? process.env.REACT_APP_API_KEY : null} + apiKey={process.env.REACT_APP_API_KEY || null} welcome="What can I help you with?" userEmail={email || undefined} userName={name || undefined} diff --git a/src/utils/create-bot-flow.js b/src/utils/create-bot-flow.js index b590e2f..1bcd1ab 100644 --- a/src/utils/create-bot-flow.js +++ b/src/utils/create-bot-flow.js @@ -12,10 +12,7 @@ function createBotFlow({ welcomeMessage, isBotLoggedIn, loginUrl, - handleQuery, - hasQueryError, sessionId, - currentQueryId, ticketForm = {}, setTicketForm = () => {}, // feedbackForm = {}, @@ -38,9 +35,7 @@ function createBotFlow({ // Create Q&A flow (requires login) const qaFlow = isBotLoggedIn ? createQAFlow({ - fetchAndStreamResponse: handleQuery, sessionId, - currentQueryId, apiKey }) : { @@ -84,17 +79,7 @@ function createBotFlow({ ...(qaFlow || {}), ...(ticketFlow || {}), //...(feedbackFlow || {}), // TODO: add feedback flow back in - ...(securityFlow || {}), - // Add fallback loop for errors (only if logged in) - ...(isBotLoggedIn && { - loop: { - message: async (params) => { - await handleQuery(params); - }, - renderMarkdown: ["BOT"], - path: () => hasQueryError ? 'start' : 'loop' - } - }) + ...(securityFlow || {}) }; return flow; diff --git a/src/utils/flows/qa-flow.js b/src/utils/flows/qa-flow.js index 74fb889..31f6dcf 100644 --- a/src/utils/flows/qa-flow.js +++ b/src/utils/flows/qa-flow.js @@ -1,16 +1,19 @@ import { handleBotError } from '../error-handler'; -import { DEFAULT_CONFIG } from '../../config/constants'; +import { getApiEndpoint, getRatingEndpoint } from '../../config/constants'; +import { v4 as uuidv4 } from 'uuid'; /** * Creates the Q&A conversation flow * * @param {Object} params Configuration - * @param {Function} params.fetchAndStreamResponse Function to fetch and stream responses * @param {string} params.sessionId Current session ID - * @param {string} params.currentQueryId Current query ID + * @param {string} params.apiKey API key for requests * @returns {Object} Q&A flow configuration */ -export const createQAFlow = ({ fetchAndStreamResponse, sessionId, currentQueryId, apiKey }) => { +export const createQAFlow = ({ sessionId, apiKey }) => { + // Track the query ID for the most recent response that can receive feedback + let feedbackQueryId = null; + return { go_ahead_and_ask: { message: "Please type your question.", @@ -18,32 +21,27 @@ export const createQAFlow = ({ fetchAndStreamResponse, sessionId, currentQueryId }, qa_loop: { message: async (chatState) => { - try { - await fetchAndStreamResponse(chatState); - return "Was this helpful?"; - } catch (error) { - console.error('Error in bot flow:', error); - return handleBotError(error); - } - }, - renderMarkdown: ["BOT"], - options: ["👍 Yes", "👎 No"], - chatDisabled: false, - function: async (chatState) => { - if (chatState.userInput === "👍 Yes" || chatState.userInput === "👎 No") { - if (apiKey && sessionId) { - const isPositive = chatState.userInput === "👍 Yes"; + const { userInput } = chatState; + + // Handle feedback first if it's feedback + if (userInput === "👍 Helpful" || userInput === "👎 Not helpful") { + + // Send feedback using the captured query ID + if (apiKey && sessionId && feedbackQueryId) { + const isPositive = userInput === "👍 Helpful"; const headers = { 'Content-Type': 'application/json', 'X-Origin': 'access', 'X-API-KEY': apiKey, 'X-Session-ID': sessionId, - 'X-Query-ID': currentQueryId, + 'X-Query-ID': feedbackQueryId, 'X-Feedback': isPositive ? 1 : 0 }; + const endpoint = getRatingEndpoint(); + try { - await fetch(`${DEFAULT_CONFIG.API_ENDPOINT}/rating`, { + await fetch(endpoint, { method: 'POST', headers }); @@ -51,35 +49,51 @@ export const createQAFlow = ({ fetchAndStreamResponse, sessionId, currentQueryId console.error('Error sending feedback:', error); } } + return "Thanks for the feedback! Feel free to ask another question."; + } else { + // Process as a question - fetch response directly + try { + // Generate our own query ID since we're bypassing useHandleAIQuery + const queryId = uuidv4(); + feedbackQueryId = queryId; + + const headers = { + 'Content-Type': 'application/json', + 'X-Origin': 'access', + 'X-API-KEY': apiKey, + 'X-Session-ID': sessionId, + 'X-Query-ID': queryId + }; + + const response = await fetch(getApiEndpoint(), { + method: 'POST', + headers, + body: JSON.stringify({ + query: userInput + }) + }); + + const body = await response.json(); + const text = body.response; + + // Inject the response + await chatState.injectMessage(text); + return null; + } catch (error) { + console.error('Error in bot flow:', error); + return handleBotError(error); + } } }, - path: (chatState) => { - if (chatState.userInput === "👍 Yes") { - return "qa_positive_feedback"; - } else if (chatState.userInput === "👎 No") { - return "qa_negative_feedback"; - } - return "qa_loop"; - } - }, - qa_positive_feedback: { - message: "Thank you for your feedback! It helps us improve this tool.", - transition: { duration: 1000 }, - path: "go_ahead_and_ask" - }, - qa_negative_feedback: { - message: "Sorry that wasn't useful. Would you like to open a help ticket for assistance?", - options: ["Open a help ticket", "Ask another question"], - chatDisabled: true, - path: (chatState) => { - if (chatState.userInput === "Open a help ticket") { - return "help_ticket"; + renderMarkdown: ["BOT"], + options: (chatState) => { + // Only show feedback options if the input isn't already feedback + if (chatState.userInput === "👍 Helpful" || chatState.userInput === "👎 Not helpful") { + return []; // No options after feedback is given } - return "qa_continue"; - } - }, - qa_continue: { - message: "Ask another question, but remember that each question must stand alone.", + return ["👍 Helpful", "👎 Not helpful"]; + }, + chatDisabled: false, path: "qa_loop" } };