Skip to content

Commit

Permalink
clean up code and improve feedback button handling
Browse files Browse the repository at this point in the history
- dont use special method handleFeedbackClick for sending feedback buttons
- handle SSE close and error events
- show linebreaks in markdown
  • Loading branch information
devxpy committed Feb 19, 2025
1 parent 97d157d commit 2ab85b8
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 81 deletions.
26 changes: 22 additions & 4 deletions src/api/streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,35 @@ export const getDataFromStream = (sseUrl: string, setterFn: any) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
window.GooeyEventSource = evtSource;

evtSource.addEventListener("close", () => {
// close the event source
evtSource.close();
// set the state to null
setterFn(null);
});

evtSource.addEventListener("error", (event: MessageEvent) => {
// parse the error message as JSON
const { detail } = JSON.parse(event.data);
// display the error message
setterFn({
type: STREAM_MESSAGE_TYPES.MESSAGE_PART,
text: `<p className="text-gooeyDanger font_14_400">${detail}</p>`,
});
// close the event source
evtSource.close();
});

evtSource.onmessage = (event) => {
// parse the message as JSON
const data = JSON.parse(event.data);
// update the state with the streamed message
setterFn(data);
// check if the message is the final response
if (data.type === STREAM_MESSAGE_TYPES.FINAL_RESPONSE) {
// close the stream
setterFn(data);
evtSource.close();
} else {
// update the state with the streamed message
setterFn(data);
}
};
};
62 changes: 24 additions & 38 deletions src/contexts/MessagesContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const MessagesContextProvider = (props: any) => {
const conversationId = lastResponse?.conversation_id;
setIsSendingMessage(true);
const newQuery = createNewQuery(payload);
sendPrompt({
sendPayload({
...payload,
conversation_id: conversationId,
citation_style: CITATION_STYLE,
Expand Down Expand Up @@ -109,6 +109,21 @@ const MessagesContextProvider = (props: any) => {
const updateStreamedMessage = useCallback(
(payload: any) => {
setMessages((prev: any) => {
// stream close
if (!payload) {
const newMessages = new Map(prev);
const lastResponseId: any = Array.from(prev.keys()).pop(); // last message id
const prevMessage = prev.get(lastResponseId);
newMessages.set(lastResponseId, {
...prevMessage,
output_text: [prevMessage?.text ?? ""],
type: STREAM_MESSAGE_TYPES.FINAL_RESPONSE,
status: "completed",
});
setIsReceiving(false);
return newMessages;
}

// stream start
if (payload?.type === STREAM_MESSAGE_TYPES.CONVERSATION_START) {
setIsSendingMessage(false);
Expand Down Expand Up @@ -167,11 +182,16 @@ const MessagesContextProvider = (props: any) => {
const lastResponseId: any = Array.from(prev.keys()).pop(); // last messages id
const prevMessage = prev.get(lastResponseId);
const text = (prevMessage?.text || "") + (payload.text || "");
const buttons = [
...(prevMessage?.buttons || []),
...(payload.buttons || []),
];
newConversations.set(lastResponseId, {
...prevMessage,
...payload,
id: currentStreamRef.current,
text,
buttons,
});
return newConversations;
}
Expand All @@ -182,17 +202,15 @@ const MessagesContextProvider = (props: any) => {
[config?.integration_id, handleAddConversation, scrollToMessage]
);

const sendPrompt = async (payload: IncomingMsg) => {
const sendPayload = async (payload: IncomingMsg) => {
try {
let audioUrl = "";
if (payload?.input_audio) {
// upload audio file to gooey
const file = new File(
[payload.input_audio],
`gooey-widget-recording-${uuidv4()}.webm`
);
audioUrl = await uploadFileToGooey(file as File);
payload.input_audio = audioUrl;
payload.input_audio = await uploadFileToGooey(file as File);
}
payload = {
...config?.payload,
Expand All @@ -207,8 +225,7 @@ const MessagesContextProvider = (props: any) => {
);
getDataFromStream(streamUrl, updateStreamedMessage);
// setLoading false in updateStreamedMessage
} catch (err) {
console.error("Api Failed!", err);
} finally {
setIsSendingMessage(false);
}
};
Expand Down Expand Up @@ -276,35 +293,6 @@ const MessagesContextProvider = (props: any) => {
setIsSendingMessage(false);
}, [isReceiving, isSending, messages]);

const handleFeedbackClick = (button_id: string, context_msg_id: string) => {
createStreamApi(
{
button_pressed: {
button_id,
context_msg_id,
},
integration_id: config?.integration_id,
user_id: currentUserId,
},
apiSource.current
);
setMessages((prev: any) => {
const newConversations = new Map(prev);
const prevMessage = prev.get(context_msg_id);
const newButtons = prevMessage.buttons.map((button: any) => {
if (button.id === button_id) {
return { ...button, isPressed: true };
}
return undefined; // hide the other buttons
});
newConversations.set(context_msg_id, {
...prevMessage,
buttons: newButtons,
});
return newConversations;
});
};

const setActiveConversation = useCallback(
async (conversation: Conversation) => {
if (isSending || isReceiving) cancelApiCall();
Expand Down Expand Up @@ -348,7 +336,6 @@ const MessagesContextProvider = (props: any) => {
};

const valueMessages = {
sendPrompt,
messages,
isSending,
initializeQuery,
Expand All @@ -357,7 +344,6 @@ const MessagesContextProvider = (props: any) => {
scrollMessageContainer,
scrollContainerRef,
isReceiving,
handleFeedbackClick,
conversations,
setActiveConversation,
currentConversationId: currentConversation.current?.id || null,
Expand Down
9 changes: 7 additions & 2 deletions src/widgets/copilot/components/ChatInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,15 +179,20 @@ const ChatInput = () => {
onChange={handleInputChange}
onKeyDown={handlePressEnter}
className={clsx(
"br-large b-1 font_16_500 bg-white gpt-10 gpb-10 gpr-40 flex-1 gm-0", isLeftButtons ? "gpl-32" : "gpl-12"
"br-large b-1 font_16_500 bg-white gpt-10 gpb-10 gpr-40 flex-1 gm-0",
isLeftButtons ? "gpl-32" : "gpl-12"
)}
placeholder={`Message ${config.branding.name || ""}`}
></textarea>

{/* Left icons */}
{isLeftButtons && (
<div className="input-left-buttons">
<IconButton onClick={handleFileUpload} variant="text-alt" className="gp-4">
<IconButton
onClick={handleFileUpload}
variant="text-alt"
className="gp-4"
>
<IconPaperClip size={18} />
</IconButton>
</div>
Expand Down
104 changes: 75 additions & 29 deletions src/widgets/copilot/components/Messages/IncomingMsg.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useSystemContext } from "src/contexts/hooks";
import { useMessagesContext, useSystemContext } from "src/contexts/hooks";
import { STREAM_MESSAGE_TYPES } from "src/api/streaming";
import ResponseLoader from "../Loader";

Expand Down Expand Up @@ -37,28 +37,78 @@ export const BotMessageLayout = (props: Record<string, any>) => {
);
};

const FeedbackButtons = ({ data, onFeedbackClick }: any) => {
type ReplyButton = {
id: string;
title: string;
isPressed?: boolean;
};

const FeedbackButtons = ({
data,
}: {
data: {
buttons: ReplyButton[];
bot_message_id: string;
};
}) => {
const { buttons, bot_message_id } = data;
const { initializeQuery }: any = useMessagesContext();
if (!buttons) return null;
return (
<div className="d-flex gml-36">
{buttons.map(
(button: any) =>
!!button && (
<Button
key={button.id}
className="gmr-4 text-muted"
variant="text"
onClick={() =>
!button.isPressed && onFeedbackClick(button.id, bot_message_id)
}
>
{getFeedbackButtonIcon(button.id, button.isPressed)}
</Button>
),
)}
</div>
);
const children = buttons
.map(
(button) =>
button && (
<FeedbackButton
key={button.id}
button={button}
onClick={() => {
if (button.isPressed) return;
initializeQuery({
button_pressed: {
button_id: button.id,
context_msg_id: bot_message_id,
},
});
}}
/>
)
)
.filter(Boolean);
return <div className="d-flex gml-36">{children}</div>;
};

const FeedbackButton = ({
button,
onClick,
}: {
button: ReplyButton;
onClick: () => void;
}) => {
let icon = getFeedbackButtonIcon(button.id, button.isPressed || false);
if (icon) {
return (
<Button
key={button.id}
className="gmr-4 text-muted"
variant="text"
onClick={onClick}
>
{icon}
</Button>
);
} else {
return (
<Button
key={button.id}
className="gmr-4"
variant="text"
onClick={onClick}
hideOverflow={false}
>
{button.title}
</Button>
);
}
};

const IncomingMsg = memo(
Expand All @@ -68,7 +118,6 @@ const IncomingMsg = memo(
showSources: boolean;
linkColor: string;
autoPlay: boolean | undefined;
onFeedbackClick: (buttonId: string, botMessageId: string) => void;
}) => {
const {
output_audio = [],
Expand All @@ -85,7 +134,7 @@ const IncomingMsg = memo(
const parsedElements = formatTextResponse(
props.data,
props?.linkColor,
props?.showSources,
props?.showSources
);

if (!parsedElements) return <ResponseLoader show={true} />;
Expand All @@ -96,7 +145,7 @@ const IncomingMsg = memo(
<div
className={clsx(
"font_16_400 pos-relative gooey-output-text markdown text-reveal-container mw-100",
isStreaming && "response-streaming",
isStreaming && "response-streaming"
)}
id={props?.id}
>
Expand Down Expand Up @@ -125,18 +174,15 @@ const IncomingMsg = memo(
</div>
)}
{!isStreaming && props?.data?.buttons && (
<FeedbackButtons
onFeedbackClick={props?.onFeedbackClick}
data={props?.data}
/>
<FeedbackButtons data={props?.data} />
)}
</div>
{props.showSources && !!references.length && (
<SourcesSection {...props.data} />
)}
</div>
);
},
}
);

export default IncomingMsg;
10 changes: 5 additions & 5 deletions src/widgets/copilot/components/Messages/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const GOOEY_META_SCRAPPER_API = "https://metascraper.gooey.ai";
export const findSourceIcon = (
contentType: string,
url: string,
size: number = 12,
size: number = 12
): JSX.ElementType | null => {
const urlLower = url.toLowerCase();
// try to guess from url first
Expand Down Expand Up @@ -123,7 +123,7 @@ export function extractMainDomain(url: string) {
export const fetchUrlMeta = async (url: string): Promise<any> => {
try {
const response: any = await axios.get(
`${GOOEY_META_SCRAPPER_API}/fetchUrlMeta?url=${url}`,
`${GOOEY_META_SCRAPPER_API}/fetchUrlMeta?url=${url}`
);
return response?.data;
} catch (err) {
Expand Down Expand Up @@ -268,13 +268,13 @@ const customizedLinks = (reactNode: any, domNode: any, data: any) => {
export const formatTextResponse = (
data: any,
linkColor: string,
showSources: boolean,
showSources: boolean
) => {
const body = getOutputText(data);
if (!body) return "";
const rawHtml = marked.parse(body, {
async: false,
breaks: false,
breaks: true,
extensions: null,
gfm: true,
hooks: null,
Expand All @@ -285,7 +285,7 @@ export const formatTextResponse = (
});
const parsedElements = parse(
rawHtml as string,
getReactParserOptions({ ...data, showSources, linkColor }),
getReactParserOptions({ ...data, showSources, linkColor })
);
return parsedElements;
};
Expand Down
Loading

0 comments on commit 2ab85b8

Please sign in to comment.