Reject empty/whitespace chat messages before send (#52)
## Summary - Closes #51 - Trim prompt Yup validation; refuse empty/whitespace in submit handlers and `sendMessage` - Add WebSocketContext test for empty send rejection ## Test plan - [x] `WebSocketContext.test.js` empty/whitespace send cases - [ ] Manually confirm whitespace-only input cannot submit on desktop + mobile viewport - [ ] Confirm normal messages still send Related BE: https://git.aimloperations.com/ai_ml_operations/chat_backend/issues/31Reviewed-on: #52
This commit was merged in pull request #52.
This commit is contained in:
@@ -41,6 +41,7 @@ type AsyncChatProps = {
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
prompt: Yup.string()
|
||||
.trim()
|
||||
.min(1, "Need to have at least one character")
|
||||
.required("This is requried"),
|
||||
});
|
||||
@@ -206,20 +207,24 @@ const AsyncChat = ({
|
||||
{ resetForm }: any,
|
||||
): Promise<void> => {
|
||||
// send the prompt to be saved
|
||||
const trimmedPrompt = (prompt ?? "").trim();
|
||||
if (!trimmedPrompt) {
|
||||
return;
|
||||
}
|
||||
console.log(fileType);
|
||||
try {
|
||||
const tempConversations: ConversationPrompt[] = [
|
||||
...conversationDetails,
|
||||
new ConversationPrompt({ message: prompt, user_created: true }),
|
||||
new ConversationPrompt({ message: trimmedPrompt, user_created: true }),
|
||||
new ConversationPrompt({ message: "", user_created: false }),
|
||||
];
|
||||
conversationRef.current = tempConversations;
|
||||
setConversationDetails(tempConversations);
|
||||
// TODO: add the file here
|
||||
console.log(
|
||||
`Sending message. ${prompt} ${selectedConversation} ${fileType}`,
|
||||
`Sending message. ${trimmedPrompt} ${selectedConversation} ${fileType}`,
|
||||
);
|
||||
sendMessage(prompt, selectedConversation, file, fileType);
|
||||
sendMessage(trimmedPrompt, selectedConversation, file, fileType);
|
||||
resetForm();
|
||||
} catch (e) {
|
||||
console.log(`error ${e}`);
|
||||
|
||||
@@ -91,6 +91,13 @@ function WebSocketProvider({ children }) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const trimmedMessage =
|
||||
typeof message === "string" ? message.trim() : "";
|
||||
if (!trimmedMessage) {
|
||||
console.log("Error sending message. Message text is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
@@ -98,7 +105,7 @@ function WebSocketProvider({ children }) {
|
||||
if (base64File && isSocketOpen(ws.current)) {
|
||||
ws.current.send(
|
||||
JSON.stringify({
|
||||
message,
|
||||
message: trimmedMessage,
|
||||
conversation_id,
|
||||
email: accountRef.current?.email,
|
||||
file: base64File,
|
||||
@@ -112,7 +119,7 @@ function WebSocketProvider({ children }) {
|
||||
} else {
|
||||
current.send(
|
||||
JSON.stringify({
|
||||
message,
|
||||
message: trimmedMessage,
|
||||
conversation_id,
|
||||
email: accountRef.current?.email,
|
||||
file: null,
|
||||
|
||||
@@ -246,4 +246,35 @@ describe('WebSocketProvider', () => {
|
||||
expect(payload.message).toBe('hi');
|
||||
expect(payload.email).toBe('test@example.com');
|
||||
});
|
||||
|
||||
it('does not send empty or whitespace-only messages', async () => {
|
||||
let sendMessage;
|
||||
function Capture() {
|
||||
const ctx = useContext(WebSocketContext);
|
||||
sendMessage = ctx[3];
|
||||
return null;
|
||||
}
|
||||
|
||||
render(
|
||||
<AccountContext.Provider
|
||||
value={{ account: { email: 'test@example.com' }, setAccount: () => {} }}
|
||||
>
|
||||
<WebSocketProvider>
|
||||
<Capture />
|
||||
</WebSocketProvider>
|
||||
</AccountContext.Provider>,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(MockWebSocket.instances.length).toBe(1));
|
||||
await act(async () => {
|
||||
MockWebSocket.instances[0].open();
|
||||
});
|
||||
|
||||
const before = MockWebSocket.instances[0].sent.length;
|
||||
await act(async () => {
|
||||
expect(sendMessage(' ', 1, null, null, 'FAST')).toBe(false);
|
||||
expect(sendMessage('', 1, null, null, 'FAST')).toBe(false);
|
||||
});
|
||||
expect(MockWebSocket.instances[0].sent.length).toBe(before);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -273,6 +273,7 @@ const VisuallyHiddenInput = styled.input`
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
prompt: Yup.string()
|
||||
.trim()
|
||||
.min(1, "Need to have at least one character")
|
||||
.required("This is required"),
|
||||
});
|
||||
@@ -326,16 +327,20 @@ const AsyncDashboardInner = (): JSX.Element => {
|
||||
{ prompt, file, fileType, modelName }: PromptValues,
|
||||
{ resetForm }: any,
|
||||
): Promise<void> => {
|
||||
const trimmedPrompt = (prompt ?? "").trim();
|
||||
if (!trimmedPrompt) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const tempConversations: ConversationPrompt[] = [
|
||||
...conversationDetails,
|
||||
new ConversationPrompt({ message: prompt, user_created: true }),
|
||||
new ConversationPrompt({ message: trimmedPrompt, user_created: true }),
|
||||
new ConversationPrompt({ message: "", user_created: false }),
|
||||
];
|
||||
|
||||
conversationRef.current = tempConversations;
|
||||
setConversationDetails(tempConversations);
|
||||
sendMessage(prompt, selectedConversation, file, fileType, modelName);
|
||||
sendMessage(trimmedPrompt, selectedConversation, file, fileType, modelName);
|
||||
trackEvent(AnalyticsEvents.MESSAGE_SENT, {
|
||||
hasConversation: Boolean(selectedConversation),
|
||||
hasAttachment: Boolean(file),
|
||||
|
||||
Reference in New Issue
Block a user