import "../assets/home.less"; import "../assets/chat.less"; import { Input } from "../components/ui/input.tsx"; import { Toggle } from "../components/ui/toggle.tsx"; import { ChevronDown, ChevronRight, FolderKanban, Globe, LogIn, MessageSquare, Plus, RotateCw, Trash2, } from "lucide-react"; import { Button } from "../components/ui/button.tsx"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "../components/ui/tooltip.tsx"; import { useDispatch, useSelector } from "react-redux"; import type { RootState } from "../store"; import { selectAuthenticated } from "../store/auth.ts"; import { login } from "../conf.ts"; import { deleteConversation, toggleConversation, updateConversationList, } from "../conversation/history.ts"; import React, { useEffect, useRef, useState } from "react"; import { filterMessage, formatMessage, mobile, useAnimation, useEffectAsync, } from "../utils.ts"; import {toast, useToast} from "../components/ui/use-toast.ts"; import { ConversationInstance, Message } from "../conversation/types.ts"; import { selectCurrent, selectModel, selectHistory, selectMessages, selectWeb, setModel, setWeb, } from "../store/chat.ts"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "../components/ui/alert-dialog.tsx"; import { manager } from "../conversation/manager.ts"; import { useTranslation } from "react-i18next"; import MessageSegment from "../components/Message.tsx"; import { setMenu } from "../store/menu.ts"; import FileProvider, { FileObject } from "../components/FileProvider.tsx"; import router from "../router.ts"; import SelectGroup from "../components/SelectGroup.tsx"; function SideBar() { const { t } = useTranslation(); const dispatch = useDispatch(); const open = useSelector((state: RootState) => state.menu.open); const auth = useSelector(selectAuthenticated); const current = useSelector(selectCurrent); const [removeConversation, setRemoveConversation] = useState(null); const { toast } = useToast(); const history: ConversationInstance[] = useSelector(selectHistory); const refresh = useRef(null); useEffectAsync(async () => { await updateConversationList(dispatch); }, []); return (
{auth ? (
{history.length ? ( history.map((conversation, i) => (
{ const target = e.target as HTMLElement; if ( target.classList.contains("delete") || target.parentElement?.classList.contains("delete") ) return; await toggleConversation(dispatch, conversation.id); if (mobile) dispatch(setMenu(false)); }} >
{filterMessage(conversation.name)}
{conversation.id}
{ e.preventDefault(); e.stopPropagation(); setRemoveConversation(conversation); }} />
)) ) : (
{t("conversation.empty")}
)}
{ if (!open) setRemoveConversation(null); }} > {t("conversation.remove-title")} {t("conversation.remove-description")} {filterMessage(removeConversation?.name || "")} {t("end")} {t("conversation.cancel")} { e.preventDefault(); e.stopPropagation(); if ( await deleteConversation( dispatch, removeConversation?.id || -1, ) ) toast({ title: t("conversation.delete-success"), description: t("conversation.delete-success-prompt"), }); else toast({ title: t("conversation.delete-failed"), description: t("conversation.delete-failed-prompt"), }); setRemoveConversation(null); }} > {t("conversation.delete")}
) : ( )}
); } function ChatInterface() { const ref = useRef(null); const [scroll, setScroll] = useState(false); const messages: Message[] = useSelector(selectMessages); function listenScrolling() { if (!ref.current) return; const el = ref.current as HTMLDivElement; const offset = el.scrollHeight - el.scrollTop - el.clientHeight; setScroll(offset > 100); } useEffect( function () { if (!ref.current) return; const el = ref.current as HTMLDivElement; el.scrollTop = el.scrollHeight; listenScrolling(); }, [messages], ); useEffect(() => { if (!ref.current) return; const el = ref.current as HTMLDivElement; el.addEventListener("scroll", listenScrolling); }, [ref]); return ( <>
{messages.map((message, i) => ( ))}
); } function ChatWrapper() { const { t } = useTranslation(); const [file, setFile] = useState({ name: "", content: "", }); const [clearEvent, setClearEvent] = useState<() => void>(() => {}); const dispatch = useDispatch(); const auth = useSelector(selectAuthenticated); const model = useSelector(selectModel); const web = useSelector(selectWeb); const messages = useSelector(selectMessages); const target = useRef(null); manager.setDispatch(dispatch); useEffect(() => { if (auth && model === "GPT-3.5") dispatch(setModel("GPT-3.5-16k")); }, [auth]); function clearFile() { clearEvent?.(); } async function handleSend(auth: boolean, model: string, web: boolean) { // because of the function wrapper, we need to update the selector state using props. if (!target.current) return; const el = target.current as HTMLInputElement; const message: string = formatMessage(file, el.value); if (message.length > 0 && el.value.trim().length > 0) { if (await manager.send(t, auth, { message, web, model })) { clearFile(); el.value = ""; } } } window.addEventListener("load", () => { const el = document.getElementById("input"); if (el) el.focus(); }); return (
{ messages.length > 0 ? :
}
dispatch(setWeb(state)) } variant={`outline`} >

{t("chat.web")}

) => { if (e.key === "Enter") await handleSend(auth, model, web); }} /> {auth && ( )}
{ if (!auth && model !== "GPT-3.5") { toast({ title: t("login-require"), }) return; } dispatch(setModel(model)); }} />
); } function Home() { return (
); } export default Home;