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, Copy, FolderKanban, Globe, LogIn, Plus, RotateCw, } 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, selectInit } from "../store/auth.ts"; import { login } from "../conf.ts"; import { deleteConversation, toggleConversation, updateConversationList, } from "../conversation/history.ts"; import { shareConversation } from "../conversation/sharing.ts"; import React, { useEffect, useRef, useState } from "react"; import { filterMessage, extractMessage, formatMessage, mobile, useAnimation, useEffectAsync, copyClipboard, } from "../utils.ts"; import { useToast } from "../components/ui/use-toast.ts"; import { ConversationInstance, Message } from "../conversation/types.ts"; import { selectCurrent, selectModel, selectHistory, selectMessages, selectWeb, 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 EditorProvider from "../components/EditorProvider.tsx"; import ConversationSegment from "../components/home/ConversationSegment.tsx"; import { connectionEvent } from "../events/connection.ts"; import ModelSelector from "../components/home/ModelSelector.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 [operateConversation, setOperateConversation] = useState<{ target: ConversationInstance | null; type: string; }>({ target: null, type: "" }); const { toast } = useToast(); const history: ConversationInstance[] = useSelector(selectHistory); const refresh = useRef(null); const [shared, setShared] = useState(""); useEffectAsync(async () => { await updateConversationList(dispatch); }, []); // @ts-ignore return (
{auth ? (
{history.length ? ( history.map((conversation, i) => ( )) ) : (
{t("conversation.empty")}
)}
{ if (!open) setOperateConversation({ target: null, type: "" }); }} > {t("conversation.remove-title")} {t("conversation.remove-description")} {extractMessage( filterMessage(operateConversation?.target?.name || ""), )} {t("end")} {t("conversation.cancel")} { e.preventDefault(); e.stopPropagation(); if ( await deleteConversation( dispatch, operateConversation?.target?.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"), }); setOperateConversation({ target: null, type: "" }); }} > {t("conversation.delete")} { if (!open) setOperateConversation({ target: null, type: "" }); }} > {t("share.title")} {t("share.description")} {extractMessage( filterMessage(operateConversation?.target?.name || ""), )} {t("end")} {t("conversation.cancel")} { e.preventDefault(); e.stopPropagation(); const resp = await shareConversation( operateConversation?.target?.id || -1, ); if (resp.status) setShared(`${location.origin}/share/${resp.data}`); else toast({ title: t("share.failed"), description: resp.message, }); setOperateConversation({ target: null, type: "" }); }} > {t("share.title")} 0} onOpenChange={(open) => { if (!open) { setShared(""); setOperateConversation({ target: null, type: "" }); } }} > {t("share.success")}
{t("close")} { e.preventDefault(); e.stopPropagation(); window.open(shared, "_blank"); }} > {t("share.view")}
) : ( )}
); } function ChatInterface() { const ref = useRef(null); const [scroll, setScroll] = useState(false); const messages: Message[] = useSelector(selectMessages); const current: number = useSelector(selectCurrent); 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) => ( { connectionEvent.emit({ id: current, event: e, }); }} key={i} /> ))}
); } function ChatWrapper() { const { t } = useTranslation(); const [file, setFile] = useState({ name: "", content: "", }); const [clearEvent, setClearEvent] = useState<() => void>(() => {}); const [input, setInput] = useState(""); const dispatch = useDispatch(); const init = useSelector(selectInit); const auth = useSelector(selectAuthenticated); const model = useSelector(selectModel); const web = useSelector(selectWeb); const messages = useSelector(selectMessages); const target = useRef(null); manager.setDispatch(dispatch); function clearFile() { clearEvent?.(); } async function processSend( data: string, auth: boolean, model: string, web: boolean, ): Promise { const message: string = formatMessage(file, data); if (message.length > 0 && data.trim().length > 0) { if (await manager.send(t, auth, { message, web, model, type: "chat" })) { clearFile(); return true; } } return false; } async function handleSend(auth: boolean, model: string, web: boolean) { // because of the function wrapper, we need to update the selector state using props. if (await processSend(input, auth, model, web)) { setInput(""); } } window.addEventListener("load", () => { const el = document.getElementById("input"); if (el) el.focus(); }); useEffect(() => { if (!init) return; const search = new URLSearchParams(window.location.search); const query = (search.get("q") || "").trim(); if (query.length > 0) processSend(query, auth, model, web).then(); window.history.replaceState({}, "", "/"); }, [init]); return (
{messages.length > 0 ? ( ) : (
)}
dispatch(setWeb(state)) } variant={`outline`} >

{t("chat.web")}

{auth && ( )} ) => setInput(e.target.value) } placeholder={t("chat.placeholder")} onKeyDown={async (e: React.KeyboardEvent) => { if (e.key === "Enter") await handleSend(auth, model, web); }} />
); } function Home() { return (
); } export default Home;