compeleted ai
This commit is contained in:
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
import { useGetAbout } from "@/app/[name]/(Main)/about/hooks/useAboutData";
|
import { useGetAbout } from "@/app/[name]/(Main)/about/hooks/useAboutData";
|
||||||
import { useGetProfile } from "@/app/[name]/(Profile)/profile/hooks/userProfileData";
|
import { useGetProfile } from "@/app/[name]/(Profile)/profile/hooks/userProfileData";
|
||||||
|
import { toast } from "@/components/Toast";
|
||||||
|
import { extractErrorMessage } from "@/lib/func";
|
||||||
import { glassSurfaceFlat } from "@/lib/styles/glassSurface";
|
import { glassSurfaceFlat } from "@/lib/styles/glassSurface";
|
||||||
import { Send2 } from "iconsax-react";
|
import { Send2 } from "iconsax-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { FormEvent, useState } from "react";
|
import { FormEvent, useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useChat } from "./hooks/useAiData";
|
||||||
import { AI_SUGGESTIONS } from "./suggestions";
|
import { AI_SUGGESTIONS } from "./suggestions";
|
||||||
|
|
||||||
type Message = {
|
type Message = {
|
||||||
@@ -27,21 +30,46 @@ function AiPage() {
|
|||||||
const { t } = useTranslation("common", { keyPrefix: "AI" });
|
const { t } = useTranslation("common", { keyPrefix: "AI" });
|
||||||
const { data: about } = useGetAbout();
|
const { data: about } = useGetAbout();
|
||||||
const { data: profile, isSuccess } = useGetProfile();
|
const { data: profile, isSuccess } = useGetProfile();
|
||||||
|
const { mutate: chat, isPending } = useChat();
|
||||||
|
|
||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
const [messages, setMessages] = useState<Message[]>([]);
|
const [messages, setMessages] = useState<Message[]>([]);
|
||||||
|
const listRef = useRef<HTMLUListElement>(null);
|
||||||
|
|
||||||
const restaurant = about?.data;
|
const restaurant = about?.data;
|
||||||
const brandName = restaurant?.name || t("BrandFallback");
|
const brandName = restaurant?.name || t("BrandFallback");
|
||||||
const logoUrl = restaurant?.logo || "/assets/images/danak-logo.png";
|
|
||||||
const userName = isSuccess && profile?.data?.firstName ? profile.data.firstName : t("GuestName");
|
const userName = isSuccess && profile?.data?.firstName ? profile.data.firstName : t("GuestName");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
listRef.current?.scrollTo({ top: listRef.current.scrollHeight, behavior: "smooth" });
|
||||||
|
}, [messages, isPending]);
|
||||||
|
|
||||||
const sendMessage = (text: string) => {
|
const sendMessage = (text: string) => {
|
||||||
const content = text.trim();
|
const content = text.trim();
|
||||||
if (!content) return;
|
if (!content || isPending) return;
|
||||||
|
|
||||||
setMessages((prev) => [...prev, { id: `u-${Date.now()}`, role: "user", content }, { id: `a-${Date.now()}`, role: "assistant", content: t("MockReply") }]);
|
const userMessage: Message = { id: `u-${Date.now()}`, role: "user", content };
|
||||||
|
setMessages((prev) => [...prev, userMessage]);
|
||||||
setInput("");
|
setInput("");
|
||||||
|
|
||||||
|
chat(
|
||||||
|
{ question: content },
|
||||||
|
{
|
||||||
|
onSuccess: (res) => {
|
||||||
|
setMessages((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
id: `a-${Date.now()}`,
|
||||||
|
role: "assistant",
|
||||||
|
content: res.data.answer,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast(extractErrorMessage(error, t("ErrorFallback")), "error");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
|
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||||
@@ -55,15 +83,30 @@ function AiPage() {
|
|||||||
|
|
||||||
<div className="mt-6 flex min-h-0 flex-1 flex-col">
|
<div className="mt-6 flex min-h-0 flex-1 flex-col">
|
||||||
{messages.length > 0 ? (
|
{messages.length > 0 ? (
|
||||||
<ul className="noscrollbar flex-1 space-y-3 overflow-y-auto pb-4">
|
<ul ref={listRef} className="noscrollbar flex-1 space-y-3 overflow-y-auto pb-4">
|
||||||
{messages.map((message) => (
|
{messages.map((message) => {
|
||||||
<li
|
const isUser = message.role === "user";
|
||||||
key={message.id}
|
return (
|
||||||
className={message.role === "user" ? "rounded-[20px] rounded-tr-none bg-[#F6F7FA] p-4 dark:bg-gray-700" : "rounded-[20px] rounded-tl-none bg-[#EBEDF5] p-4 dark:bg-gray-600"}
|
<li key={message.id} className={`flex ${isUser ? "justify-end" : "justify-start"}`}>
|
||||||
>
|
<div
|
||||||
<p className="text-xs2 leading-5">{message.content}</p>
|
className={
|
||||||
|
isUser
|
||||||
|
? "max-w-[80%] rounded-[20px] rounded-br-none bg-primary/15 px-4 py-3 dark:bg-primary/25"
|
||||||
|
: "max-w-[80%] rounded-[20px] rounded-bl-none bg-[#EBEDF5] px-4 py-3 dark:bg-gray-600"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p className="text-xs2 leading-5 whitespace-pre-wrap">{message.content}</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{isPending && (
|
||||||
|
<li className="flex justify-start">
|
||||||
|
<div className="max-w-[80%] rounded-[20px] rounded-bl-none bg-[#EBEDF5] px-4 py-3 dark:bg-gray-600">
|
||||||
|
<p className="animate-pulse text-xs2 leading-5 text-disabled-text">{t("Thinking")}</p>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-1 flex-col items-center justify-center gap-8">
|
<div className="flex flex-1 flex-col items-center justify-center gap-8">
|
||||||
@@ -79,8 +122,9 @@ function AiPage() {
|
|||||||
<button
|
<button
|
||||||
key={labelKey}
|
key={labelKey}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => sendMessage(t(labelKey))}
|
disabled={isPending}
|
||||||
className={glassSurfaceFlat("inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 transition-[filter] active:brightness-95")}
|
onClick={() => setInput(t(labelKey))}
|
||||||
|
className={glassSurfaceFlat("inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 transition-[filter] active:brightness-95 disabled:opacity-50")}
|
||||||
>
|
>
|
||||||
<Icon size={15} className="stroke-foreground" variant="Bold" />
|
<Icon size={15} className="stroke-foreground" variant="Bold" />
|
||||||
<span className="text-xs2">{t(labelKey)}</span>
|
<span className="text-xs2">{t(labelKey)}</span>
|
||||||
@@ -97,10 +141,11 @@ function AiPage() {
|
|||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
placeholder={t("InputPlaceholder")}
|
placeholder={t("InputPlaceholder")}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
className="h-full w-full bg-transparent text-[11px] outline-none placeholder:text-disabled-text"
|
disabled={isPending}
|
||||||
|
className="h-full w-full bg-transparent text-[11px] outline-none placeholder:text-disabled-text disabled:opacity-60"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button type="submit" disabled={!input.trim()} className="rounded-full p-1.5 disabled:opacity-40">
|
<button type="submit" disabled={!input.trim() || isPending} className="rounded-full p-1.5 disabled:opacity-40">
|
||||||
<Send2 size={20} variant="Bold" className="fill-primary text-primary dark:fill-foreground dark:text-foreground" />
|
<Send2 size={20} variant="Bold" className="fill-primary text-primary dark:fill-foreground dark:text-foreground" />
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
import * as api from "../service/AiService";
|
||||||
|
|
||||||
|
export const useChat = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: api.chat,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { api } from "@/config/axios";
|
||||||
|
import { ChatRequest, ChatResponse } from "../types/Types";
|
||||||
|
|
||||||
|
export const chat = async (params: ChatRequest) => {
|
||||||
|
const { data } = await api.post<ChatResponse>("/public/ai/chat", params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { BaseResponse } from "@/app/[name]/(Main)/types/Types";
|
||||||
|
|
||||||
|
export type ChatRequest = {
|
||||||
|
question: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ChatData = {
|
||||||
|
answer: string;
|
||||||
|
consumedTokens: number;
|
||||||
|
cost: number;
|
||||||
|
promptTokens: number;
|
||||||
|
completionTokens: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ChatResponse = BaseResponse<ChatData>;
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
// export const API_BASE_URL = "https://dmenuplus-api-production.dev.danakcorp.com";
|
// export const API_BASE_URL = "https://dmenuplus-api-production.dev.danakcorp.com";
|
||||||
// export const API_BASE_URL = "http://192.168.1.107:2000";
|
export const API_BASE_URL = "http://192.168.99.131:2000";
|
||||||
export const API_BASE_URL = "https://dmenu-api.danakcorp.com";
|
// export const API_BASE_URL = "https://dmenu-api.danakcorp.com";
|
||||||
process.env.NEXT_PUBLIC_API_URL ?? (process.env.NODE_ENV === "development" ? "http://192.168.99.131:2000" : "https://dmenu-api.danakcorp.com");
|
process.env.NEXT_PUBLIC_API_URL ?? (process.env.NODE_ENV === "development" ? "http://192.168.99.131:2000" : "https://dmenu-api.danakcorp.com");
|
||||||
export const TOKEN_NAME = "dmenu-t";
|
export const TOKEN_NAME = "dmenu-t";
|
||||||
export const REFRESH_TOKEN_NAME = "dmenu-rt";
|
export const REFRESH_TOKEN_NAME = "dmenu-rt";
|
||||||
|
|||||||
@@ -83,6 +83,7 @@
|
|||||||
"dessert": "پیشنهادت برای دسر چیه؟",
|
"dessert": "پیشنهادت برای دسر چیه؟",
|
||||||
"group": "برای ۴ نفر چی سفارش بدیم"
|
"group": "برای ۴ نفر چی سفارش بدیم"
|
||||||
},
|
},
|
||||||
"MockReply": "این نسخه فقط رابط کاربری است؛ بهزودی میتونم براتون از منو پیشنهاد واقعی بدم."
|
"Thinking": "در حال فکر کردن…",
|
||||||
|
"ErrorFallback": "متأسفانه نتونستم جواب بدم. دوباره امتحان کن."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user