-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Now I let user to send messages Also I created input as in openAI
- Loading branch information
1 parent
6e4cbeb
commit 12d083c
Showing
17 changed files
with
4,334 additions
and
5,117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import axios, { AxiosError } from "axios" | ||
import moment from "moment-timezone" | ||
import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime" | ||
|
||
import { IMessage } from "@/interfaces/support/IMessage" | ||
import { TAPITelegram } from "@/api/telegram/route" | ||
import { TAPITicketsOpen } from "@/api/tickets/open/route" | ||
import { TAPIMessageSend } from "@/api/message/send/route" | ||
import { useMessagesStore } from "@/store/ui/useMessagesStore" | ||
import useUserStore from "@/store/user/userStore" | ||
import { getUserId } from "@/utils/getUserId" | ||
|
||
export async function sendChatMessageFn(inputValue: string, sender_id: string, router: AppRouterInstance) { | ||
// don't allow to send empty message | ||
if (inputValue.length === 0) { | ||
return null | ||
} | ||
|
||
const { messages, setMessages, ticketId: ticketIdState, setTicketId } = useMessagesStore.getState() | ||
|
||
const isFirstMessage = messages.length === 0 | ||
const ticketId = ticketIdState || getUserId() | ||
|
||
const message: IMessage = { | ||
id: crypto.randomUUID(), // to don't wait response from DB about generated id | ||
created_at: moment().tz("Europe/Berlin").format(), | ||
seen: false, | ||
body: inputValue, | ||
sender_id: sender_id, | ||
sender_username: sender_id, | ||
ticket_id: ticketId, | ||
} | ||
|
||
setMessages([...messages, message]) // optimistically set state | ||
|
||
if (isFirstMessage) { | ||
setTicketId(ticketId) | ||
console.log(38, "messages - ", messages) | ||
try { | ||
// 1. Send message in telegram | ||
await axios.post("/api/telegram", { message: message.body } as TAPITelegram) | ||
// 2. Insert row in table 'tickets' | ||
await axios.post("/api/tickets/open", { | ||
ticketId: message.ticket_id, | ||
ownerId: sender_id, | ||
ownerUsername: sender_id, | ||
messageBody: message.body, | ||
ownerAvatarUrl: null, // TODO - getAvatarUrl() - set avatar here based on isAuthenticated | ||
} as TAPITicketsOpen) | ||
} catch (error) { | ||
if (error instanceof AxiosError) { | ||
console.log(113, "error sending message - ", error.response) | ||
setMessages([]) // in case error delete message | ||
setTicketId("") | ||
} | ||
} finally { | ||
router.refresh() | ||
} | ||
} | ||
|
||
try { | ||
// 3. Insert message in table 'messages' | ||
await axios.post("/api/message/send", { | ||
id: message.id, | ||
ticketId: message.ticket_id, | ||
senderId: message.sender_id, | ||
senderUsername: message.sender_id, | ||
senderAvatarUrl: null, // TODO getAvatarUrl() | ||
messageBody: message.body, | ||
images: undefined, | ||
messageSender: "user", | ||
} as TAPIMessageSend) | ||
} catch (error) { | ||
console.log(74, "error inserting new message", error) | ||
setMessages(messages.slice(0, -1)) // delete last message and keep other | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,101 @@ | ||
"use client" | ||
|
||
import { UseFormRegister } from "react-hook-form" | ||
import { useRouter } from "next/navigation" | ||
import { useEffect, useRef, useState } from "react" | ||
import { twMerge } from "tailwind-merge" | ||
|
||
interface FormData { | ||
message: string | ||
} | ||
import { sendChatMessageFn } from "@/(site)/functions/sendChatMessageFn" | ||
import { getUserId } from "@/utils/getUserId" | ||
|
||
interface MessageInputProps { | ||
id: keyof FormData | ||
register: UseFormRegister<FormData> | ||
className?: string | ||
} | ||
|
||
export function MessageInput({ className, id, register }: MessageInputProps) { | ||
export function MessageInput({ className }: MessageInputProps) { | ||
const [value, setValue] = useState("") | ||
const [height, setHeight] = useState(52) // Initialize with the base height for one line | ||
const textareaRef = useRef<HTMLTextAreaElement>(null) | ||
const router = useRouter() | ||
const userId = getUserId() | ||
|
||
// shift+enter managed by ChatGPT-4 - copy paste all code to it if issues | ||
|
||
useEffect(() => { | ||
// Recalculate height every time the value changes | ||
const lineCount = value.split("\n").length | ||
|
||
setHeight(Math.max(42, 42 + (lineCount - 1) * 24)) // Adjust height based on line count, 24px per line | ||
}, [value]) | ||
|
||
const handleKeyDown = async (event: React.KeyboardEvent<HTMLTextAreaElement>) => { | ||
if (event.key === "Enter") { | ||
if (event.shiftKey) { | ||
event.preventDefault() // Prevent default behavior for Shift+Enter | ||
|
||
// Insert newline at the current cursor position | ||
const cursorPosition = event.currentTarget.selectionStart | ||
const beforeText = value.slice(0, cursorPosition) | ||
const afterText = value.slice(cursorPosition) | ||
const newValue = `${beforeText}\n${afterText}` | ||
setValue(newValue) // Update value to trigger height recalculation | ||
|
||
setTimeout(() => { | ||
if (textareaRef.current) { | ||
textareaRef.current.selectionStart = textareaRef.current.selectionEnd = cursorPosition + 1 | ||
// Adjust scrollTop to ensure the new line and cursor are visible | ||
ensureCursorVisibility(textareaRef.current) | ||
textareaRef.current.scrollTop = textareaRef.current.scrollHeight // scroll to bottom | ||
} | ||
}, 0) | ||
} else { | ||
event.preventDefault() // Prevent default form submission on Enter | ||
// Trim and check if the message is not just spaces or newlines | ||
if (value.trim().length > 0) { | ||
setValue("") // Clear the textarea after sending the message | ||
setHeight(36) // Reset height to initial value after message is sent | ||
await sendChatMessageFn(value.trim(), userId, router) | ||
} | ||
} | ||
} | ||
} | ||
|
||
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { | ||
setValue(event.target.value) | ||
} | ||
|
||
// Ensure the cursor is visible in the textarea, adjusting scroll if necessary | ||
function ensureCursorVisibility(textarea: HTMLTextAreaElement) { | ||
const lineHeight = 24 // Assuming line height is 24px | ||
const { scrollHeight, clientHeight, scrollTop } = textarea | ||
const cursorPosition = textarea.selectionStart | ||
const cursorLine = textarea.value.substring(0, cursorPosition).split("\n").length | ||
const topLineVisible = Math.ceil(scrollTop / lineHeight) + 1 | ||
const bottomLineVisible = topLineVisible + Math.floor(clientHeight / lineHeight) - 1 | ||
|
||
if (cursorLine < topLineVisible || cursorLine > bottomLineVisible) { | ||
// Align the cursor line to the bottom of the visible area | ||
const newScrollTop = (cursorLine - Math.floor(clientHeight / lineHeight)) * lineHeight | ||
textarea.scrollTop = newScrollTop | ||
} | ||
} | ||
|
||
return ( | ||
<div className="w-[calc(100%-2px)] bg-foreground-accent p-4"> | ||
<input | ||
<textarea | ||
ref={textareaRef} | ||
className={twMerge( | ||
`w-full rounded border border-solid bg-transparent px-4 py-2 mb-1 outline-none text-title`, | ||
`w-full !max-h-[61px] min-h-[36px] resize-none hide-scrollbar rounded border border-solid bg-transparent px-4 py-2 mb-1 outline-none text-title`, | ||
className, | ||
)} | ||
id={id} | ||
tabIndex={0} | ||
placeholder="Enter message..." | ||
{...register(id)} | ||
/> | ||
autoFocus | ||
value={value} | ||
onChange={handleChange} | ||
onKeyDown={handleKeyDown} | ||
style={{ | ||
overflowY: "auto", | ||
height: `${height}px`, // Use state to manage dynamic height | ||
}}></textarea> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.