Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
--foreground: #171717;
--message-card-background: #f0f0f0;
--icon-color: #555555;
--drawer-color: #f2f2f2;
--drawer-color: #ffffff;
}

[data-theme="dark"] {
Expand Down
14 changes: 11 additions & 3 deletions components/Chatbot/hooks/useColor.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { isColorLight } from "@/utils/themeUtility";
import { getPrimaryGradientBg, isColorLight } from "@/utils/themeUtility";
import { useTheme } from "@mui/material";

export const useColor = () => {
const theme = useTheme();
const backgroundColor = theme.palette.primary.main;
const textColor = isColorLight(backgroundColor) ? "black" : "white";
const isLight = isColorLight(backgroundColor);
const textColor = isLight ? "black" : "white";

return { backgroundColor, textColor }
return {
backgroundColor,
textColor,
primaryBgColor: backgroundColor,
primaryTextColor: textColor,
foregroundColor: textColor,
primaryGradientBg: getPrimaryGradientBg(backgroundColor),
}
}
60 changes: 45 additions & 15 deletions components/Chatbot/hooks/useHelloIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,18 @@ export const useFetchHelloPreviousHistory = () => {
const { setChatsLoading } = useChatActions();
const { setHelloMessages } = useHelloMessages();

const { uuid, currentChannelId } = useReduxStateManagement({
const { uuid, currentChannelId, companyId } = useReduxStateManagement({
chatSessionId,
tabSessionId: useHelloContext().tabSessionId
});

return useCallback((dynamicChannelId?: string) => {
const channelId = dynamicChannelId || currentChannelId;
// Backend regex: ^ch-comp-(\d+)\.([0-9a-f]{32})$ — must be 32 hex chars.
// Some code paths persist a 24-char ObjectId (k_clientId/a_clientId), so
// regenerate whenever the stored id does not match.
const storedChannel = (dynamicChannelId || currentChannelId) || '';
const isValid = /^[0-9a-f]{32}$/.test(String(storedChannel).split('.')[1] || '');
const channelId = isValid ? storedChannel : generateChannelId(companyId);
if (!channelId || !uuid) return;

setChatsLoading(true);
Expand All @@ -92,7 +97,7 @@ export const useFetchHelloPreviousHistory = () => {
.finally(() => {
setChatsLoading(false);
});
}, [currentChannelId, uuid, setChatsLoading, setHelloMessages, globalDispatch]);
}, [currentChannelId, uuid, companyId, setChatsLoading, setHelloMessages, globalDispatch]);
};

export const useGetMoreHelloChats = () => {
Expand All @@ -101,7 +106,7 @@ export const useGetMoreHelloChats = () => {
const { setChatsLoading } = useChatActions();
const { addHelloMessage } = useHelloMessages();

const { uuid, currentChannelId } = useReduxStateManagement({
const { uuid, currentChannelId, companyId } = useReduxStateManagement({
chatSessionId,
tabSessionId: useHelloContext().tabSessionId
});
Expand All @@ -112,10 +117,14 @@ export const useGetMoreHelloChats = () => {
}));

return useCallback(() => {
if (!currentChannelId || !uuid || !hasMoreMessages) return;
// Backend regex: ^ch-comp-(\d+)\.([0-9a-f]{32})$ — must be 32 hex chars.
const storedChannel = currentChannelId || '';
const isValid = /^[0-9a-f]{32}$/.test(String(storedChannel).split('.')[1] || '');
const channelId = isValid ? storedChannel : generateChannelId(companyId);
if (!channelId || !uuid || !hasMoreMessages) return;

setChatsLoading(true);
getHelloChatHistoryApi(currentChannelId, skip)
getHelloChatHistoryApi(channelId, skip)
.then((response) => {
const helloChats = response?.data?.data;
if (Array.isArray(helloChats) && helloChats.length > 0) {
Expand All @@ -136,7 +145,7 @@ export const useGetMoreHelloChats = () => {
.finally(() => {
setChatsLoading(false);
});
}, [currentChannelId, uuid, setChatsLoading, addHelloMessage, hasMoreMessages, skip, globalDispatch]);
}, [currentChannelId, uuid, companyId, setChatsLoading, addHelloMessage, hasMoreMessages, skip, globalDispatch]);
};

export const useFetchChannels = () => {
Expand Down Expand Up @@ -224,13 +233,20 @@ export const useOnSendHello = () => {

try {

const channelIdToUse = newChannelId || currentChannelId || overrideChannelId;
const chatIdToUse = overrideChatId || currentChatId;
const teamIdToUse = overrideTeamId || currentTeamId;
const channelIdToUse = newChannelId !== undefined ? newChannelId : (currentChannelId || overrideChannelId);
const chatIdToUse = overrideChatId !== undefined ? overrideChatId : currentChatId;
const teamIdToUse = overrideTeamId !== undefined ? overrideTeamId : currentTeamId;

let workingChannelId = channelIdToUse;
if (!chatIdToUse && !channelIdToUse) {
workingChannelId = generateChannelId(companyId);
// Backend regex: ^ch-comp-(\d+)\.([0-9a-f]{32})$ — must be 32 hex chars.
// Some code paths persist a 24-char ObjectId (k_clientId/a_clientId), so
// regenerate whenever the chosen channel id does not match.
const isChannelHexValid = (id: string) =>
/^[0-9a-f]{32}$/.test(String(id || '').split('.')[1] || '');

let workingChannelId = isChannelHexValid(channelIdToUse)
? channelIdToUse
: generateChannelId(companyId);
if (!chatIdToUse && (!channelIdToUse || !isChannelHexValid(channelIdToUse))) {
dispatch(setDataInAppInfoReducer({
subThreadId: workingChannelId
}));
Expand Down Expand Up @@ -303,10 +319,24 @@ export const useOnSendHello = () => {
}
const data = await sendMessageToHelloApi(message, attachments, channelDetail, chatIdToUse, helloVariables, voiceCall, demo_widget, widget_msg_id, repliedOn);
if (data && (!chatIdToUse || !channelIdToUse || demo_widget)) {
// Prefer the locally-generated 32-char channel id (matches backend regex
// ^ch-comp-(\d+)\.([0-9a-f]{32})$) over the backend's echoed channel,
// which may contain a 24-char ObjectId suffix and would be rejected by
// /get-history/. Fall back to the backend's echo only if it already has
// a 32-char hex suffix.
const backendChannel = data?.['channel'];
const channelToPersist = (() => {
if (workingChannelId) return workingChannelId;
if (backendChannel) {
const suffix = String(backendChannel).split('.')[1];
if (suffix && /^[0-9a-f]{32}$/.test(suffix)) return backendChannel;
}
return generateChannelId(companyId);
})();
dispatch(setDataInAppInfoReducer({
subThreadId: data?.['channel'],
subThreadId: channelToPersist,
currentChatId: data?.['id'],
currentChannelId: data?.['channel'],
currentChannelId: channelToPersist,
overrideChannelId: ""
}));
// no need to append user message again this time
Expand Down
69 changes: 48 additions & 21 deletions components/Chatbot/hooks/useReduxManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,52 @@ export const useReduxStateManagement = ({
currentChannelId,
currentTeamId,
isDefaultNavigateToChatScreen,
overrideChannelId
} = useCustomSelector((state) => ({
interfaceContextData: state.Interface?.[chatSessionId]?.interfaceContext?.variables,
isHelloUser: state.draftData?.isHelloUser || false,
uuid: state.Hello?.[chatSessionId]?.channelListData?.uuid,
unique_id: state.Hello?.[chatSessionId]?.channelListData?.unique_id,
presence_channel: state.Hello?.[chatSessionId]?.channelListData?.presence_channel,
team_id: state.Hello?.[chatSessionId]?.widgetInfo?.team?.[0]?.id,
isDefaultNavigateToChatScreen: isDefaultNavigateToChatScreenFn(state, chatSessionId),
chat_id: state.Hello?.[chatSessionId]?.Channel?.id,
channelId: state.Hello?.[chatSessionId]?.Channel?.channel || null,
mode: state.Hello?.[chatSessionId]?.mode || [],
selectedAiServiceAndModal: state.Interface?.[chatSessionId]?.selectedAiServiceAndModal || null,
unique_id_hello: state?.Hello?.[chatSessionId]?.helloConfig?.unique_id,
widgetToken: state?.Hello?.[chatSessionId]?.helloConfig?.widgetToken,
currentChatId: state?.appInfo?.[tabSessionId]?.currentChatId,
currentChannelId: state?.appInfo?.[tabSessionId]?.currentChannelId,
currentTeamId: state?.appInfo?.[tabSessionId]?.currentTeamId,
overrideChannelId: state?.appInfo?.[tabSessionId]?.overrideChannelId,
}));
overrideChannelId,
companyId
} = useCustomSelector((state) => {
const channels = state.Hello?.[chatSessionId]?.channelListData?.channels || [];
const fallbackChat = (() => {
if (!channels?.length) return null;
const openChats = channels
.filter((ch: any) => !ch.is_closed)
.sort((a: any, b: any) => (b.last_message?.timetoken || 0) - (a.last_message?.timetoken || 0));
return openChats[0] || channels[0];
})();

// Treat "" / null / undefined as a deliberate "start fresh" signal,
// not as a fallback trigger. Only fall back when explicitly cleared.
const isExplicitlyEmpty = (v: any) => v === '' || v === null || v === undefined;
const appInfoChannelId = state?.appInfo?.[tabSessionId]?.currentChannelId;
const appInfoChatId = state?.appInfo?.[tabSessionId]?.currentChatId;
const appInfoTeamId = state?.appInfo?.[tabSessionId]?.currentTeamId;

return {
interfaceContextData: state.Interface?.[chatSessionId]?.interfaceContext?.variables,
isHelloUser: state.draftData?.isHelloUser || false,
uuid: state.Hello?.[chatSessionId]?.channelListData?.uuid,
unique_id: state.Hello?.[chatSessionId]?.channelListData?.unique_id,
presence_channel: state.Hello?.[chatSessionId]?.channelListData?.presence_channel,
team_id: state.Hello?.[chatSessionId]?.widgetInfo?.team?.[0]?.id,
isDefaultNavigateToChatScreen: isDefaultNavigateToChatScreenFn(state, chatSessionId),
chat_id: state.Hello?.[chatSessionId]?.Channel?.id,
channelId: state.Hello?.[chatSessionId]?.Channel?.channel || null,
mode: state.Hello?.[chatSessionId]?.mode || [],
selectedAiServiceAndModal: state.Interface?.[chatSessionId]?.selectedAiServiceAndModal || null,
unique_id_hello: state?.Hello?.[chatSessionId]?.helloConfig?.unique_id,
widgetToken: state?.Hello?.[chatSessionId]?.helloConfig?.widgetToken,
currentChannelId: isExplicitlyEmpty(appInfoChannelId)
? ''
: (appInfoChannelId ?? fallbackChat?.channel ?? ''),
currentChatId: isExplicitlyEmpty(appInfoChatId)
? ''
: (appInfoChatId ?? fallbackChat?.id ?? ''),
currentTeamId: isExplicitlyEmpty(appInfoTeamId)
? ''
: (appInfoTeamId ?? fallbackChat?.team_id ?? ''),
overrideChannelId: state?.appInfo?.[tabSessionId]?.overrideChannelId,
companyId: state.Hello?.[chatSessionId]?.widgetInfo?.company_id || '',
};
});

return {
interfaceContextData,
Expand All @@ -71,6 +97,7 @@ export const useReduxStateManagement = ({
currentChatId,
currentChannelId,
currentTeamId,
overrideChannelId
overrideChannelId,
companyId
};
};
2 changes: 1 addition & 1 deletion components/FormComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ function FormComponent({ chatSessionId }: FormComponentProps) {
if (!open && !showWidgetForm) return null;
if (!open && showWidgetForm) return (
<div
className={`bg-white p-2 px-4 cursor-pointer z-[9999] hover:shadow-md transition-all mx-auto rounded-br-md rounded-bl-md ${isSmallScreen ? 'w-full' : 'w-1/2 max-w-lg'}`}
className={`bg-white p-2 px-4 cursor-pointer z-[9] hover:shadow-md transition-all mx-auto rounded-br-md rounded-bl-md ${isSmallScreen ? 'w-full' : 'w-1/2 max-w-lg'}`}
onClick={() => setOpen(true)}
style={{
background: `linear-gradient(to right, ${backgroundColor}, ${backgroundColor}CC)`,
Expand Down
Loading