Skip to content

Commit

Permalink
Refactor frontend (#55)
Browse files Browse the repository at this point in the history
* Refactor frontend

* Frontend fixes

* Fix addition

* Add pdf support
  • Loading branch information
jamakase authored Sep 12, 2024
1 parent 625f0cf commit 39fbc38
Show file tree
Hide file tree
Showing 26 changed files with 1,507 additions and 544 deletions.
646 changes: 630 additions & 16 deletions frontend/package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"framer-motion": "^11.5.2",
"lucide-react": "^0.439.0",
"next": "14.2.7",
"react": "^18",
"react-dom": "^18",
"react-query": "^3.39.3",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"uuid": "^10.0.0"
"uuid": "^10.0.0",
"vaul": "^0.9.2"
},
"devDependencies": {
"@types/node": "^20",
Expand Down
133 changes: 133 additions & 0 deletions frontend/src/app/(withSidebar)/conversations/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"use client";

import { Button } from "@/components/ui/button";
import { api } from "@/domain/api/api";
import { useConfig } from "@/domain/config/ConfigProvider";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useMutation, useQuery, useQueryClient } from "react-query";
import MessageList from "../../../_components/MessageList";
import { Send } from "lucide-react";

// Тип для хранения информации о чате
type Conversation = {
id: number;
name: string;
messages: Array<{ id: number; text: string; sender: string }>;
};

// Основная функция компонента
export default function ConversationPage({
params,
}: {
params: { id: string };
}) {
const [message, setMessage] = useState("");
const [isSidebarOpen, setIsSidebarOpen] = useState(false);

const config = useConfig();
const router = useRouter();
const queryClient = useQueryClient();

const { data: currentConversation } = useQuery(
["conversation", params.id],
() =>
api.getMessages(config.ENDPOINT, parseInt(params.id)).then((response) => {
if (
response &&
response.length > 0 &&
response[0].messages &&
response[0].messages.length > 0
) {
const conversationMessages =
response[0].messages[0][parseInt(params.id)];
if (conversationMessages && Array.isArray(conversationMessages)) {
return conversationMessages.map((msg: any) => ({
id: msg.id,
text: msg.text,
sender: msg.role === 1 ? "bot" : "user",
}));
}
}
return [];
}),
{ enabled: !!params.id }
);

const sendMessageMutation = useMutation(
(newMessage: string) =>
api.sendMessage(config.ENDPOINT, parseInt(params.id), newMessage),
{
onSuccess: (data) => {
queryClient.invalidateQueries(["conversation", params.id]);
},
}
);

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
handleSendMessage();
}
};

const handleSendMessage = async () => {
if (!message.trim()) return;
sendMessageMutation.mutate(message);
setMessage("");
};

return (
<div className="min-h-full w-full relative overflow-hidden hide-scrollbar">
{/* {!isSidebarOpen && (
<button
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
className="fixed top-4 left-4 z-50 md:hidden"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
)} */}

<main className="flex h-full">

<div className="flex-1 flex flex-col h-full ml-[0px] md:ml-0 overflow-y-auto">
<MessageList messages={currentConversation || []} />

<div className="p-4 bg-white border-t border-gray-300 fixed bottom-0 w-full md:relative">
<div className="flex items-stretch gap-3">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Введите ваш вопрос"
className="flex-grow p-2 focus:outline-none h-full"
style={{ color: "black" }}
/>
<Button
size="icon"
onClick={handleSendMessage}
className="rounded-full"
>
<Send className="w-5 h-5 text-white mt-px mr-px" />
</Button>
</div>
</div>
</div>
</main>
</div>
);
}
36 changes: 36 additions & 0 deletions frontend/src/app/(withSidebar)/conversations/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client";

import Sidebar from "@/app/_components/Sidebar";
import { api } from "@/domain/api/api";
import { useConfig } from "@/domain/config/ConfigProvider";
import { useRouter } from "next/navigation";
import { useMutation } from "react-query";

export default function RootLayout({
children,
params,
}: {
children: React.ReactNode;
params: { id?: string };
}) {
const conversationId = params.id;
const router = useRouter();
const config = useConfig();

const createConversationMutation = useMutation({
mutationFn: () => api.createConversation(config.ENDPOINT),
onSuccess: (response: any) => {
router.push(`/conversations/${response.data.id}`);
},
});

return (
<div className="flex w-full h-full">
<Sidebar
currentConversationId={conversationId ?? null}
onAddConversation={createConversationMutation.mutate}
/>
{children}
</div>
);
}
26 changes: 26 additions & 0 deletions frontend/src/app/(withSidebar)/conversations/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { api } from '@/domain/api/api';
import { useConfig } from '@/domain/config/ConfigProvider';
import { MessagesSquare } from 'lucide-react';
import { useQuery } from 'react-query';

export default function Home() {
const config = useConfig();

const { data: conversations = [] } = useQuery('conversations', () =>
api.get_messages__user_id_(config.ENDPOINT)
.then(response => response.data.map((id: number, index: number) => ({
id,
name: `Чат ${index + 1}`,
messages: []
})))
);

return (
<div className="text-md md:text-xl text-gray-500 font-bold h-full w-full flex flex-col md:flex-row items-center justify-center">
<MessagesSquare className="w-8 md:w-10 h-8 md:h-10 mr-2" />
Самое время начать общение!
</div>
);
}
15 changes: 15 additions & 0 deletions frontend/src/app/(withSidebar)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import { QueryClient, QueryClientProvider } from "react-query";
// import { MainLayout } from "./components/MainLayout";

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
}
5 changes: 5 additions & 0 deletions frontend/src/app/(withSidebar)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { redirect } from 'next/navigation';

export default function Home() {
redirect('/conversations');
}
58 changes: 58 additions & 0 deletions frontend/src/app/_components/MainLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"use client"
import { Sheet, SheetContent, SheetTrigger, SheetClose } from "@/components/ui/sheet"
import MainSidebar from "./MainSidebar"
import { MessagesSquare, PanelLeft, Search } from "lucide-react"
import { Button } from "@/components/ui/button"
import Link from "next/link"
import { usePathname } from "next/navigation"

const sideBarData = [
{id: 1, name: 'Список чатов', icon: MessagesSquare, href: '/conversations'},
{id: 2, name: 'Поиск по документам', icon: Search, href: '/search'},
];

export function MainLayout({children}: {children?: any}) {
const pathname = usePathname();

return (
<div className="flex min-h-screen w-screen min-w-screen bg-white">
<MainSidebar data={sideBarData}/>
<div className="flex flex-col w-full h-screen md:pl-16">

<header className="sticky top-0 z-30 flex min-h-14 h-14 items-center gap-4 border-b bg-background px-4 md:static md:hidden md:border-0 md:bg-transparent md:px-6">
<Sheet>
<SheetTrigger asChild>
<Button size="icon" variant="outline" className="md:hidden">
<PanelLeft className="h-5 w-5" />
<span className="sr-only">Toggle Menu</span>
</Button>
</SheetTrigger>
<SheetContent side="left" className="sm:max-w-xs">
<nav className="grid gap-6 text-lg font-medium">
<h4 className="text-2xl font-bold text-black">AFANA</h4>
{sideBarData.map((item) => (
<Link
key={item.id}
href={item.href}
>
<SheetClose className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground">
<item.icon className="h-5 w-5" />
{item.name}
</SheetClose>
</Link>
))}
</nav>
</SheetContent>
</Sheet>
<div className="text-md font-bold ">
{pathname === '/search' ? "Поиск по документам" : 'Список чатов'}
</div>
</header>

<main className="h-full">
{children}
</main>
</div>
</div>
)
};
55 changes: 55 additions & 0 deletions frontend/src/app/_components/MainSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"use client"

import React from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation'

import {
Tooltip,
TooltipContent,
TooltipTrigger,
TooltipProvider
} from "@/components/ui/tooltip"

type Conversation = {
id: number;
name: string;
messages: Array<{ id: number; text: string; sender: string }>;
};

type SidebarProps = {
data: {id: number, name: string, icon: React.ElementType, href: string}[];
};

export default function MainSidebar({ data }: SidebarProps) {
const pathname = usePathname();

return (
<aside className="hidden py-4 px-1 flex-1 flex-col items-center min-h-0 bg-gray-200 overflow-hidden min-w-14 max-w-14 m-2 rounded-lg gap-2 fixed inset-y-0 left-0 z-10 w-14 border-r bg-background md:flex">
<h4 className="text-lg font-bold text-black text-center">AFN</h4>
<nav className="flex flex-col items-center gap-2 w-full sm:py-3">
{data.map((item) => (
<TooltipProvider key={item.id}>
<Tooltip>
<TooltipTrigger asChild>
<Link
href={item.href}
className={
`${pathname.includes(item.href) ? "bg-gray-900 text-white pointer-events-none" : ""}
flex justify-center items-center
w-full h-12 bg-transparent text-balack rounded-lg
transition-all duration-500
hover:bg-white/70 active:scale-90`
}
>
<item.icon className="h-6 w-6"/>
</Link>
</TooltipTrigger>
<TooltipContent side="right">{item.name}</TooltipContent>
</Tooltip>
</TooltipProvider>
))}
</nav>
</aside>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type MessageListProps = {

export default function MessageList({ messages }: MessageListProps) {
return (
<ScrollArea key={messages.length} className="flex-1 p-4 overflow-y-auto bg-white">
<ScrollArea key={messages.length} className="flex-1 px-4 pt-4 pb-16 md:pb-0 md:px-8 md:pt-8 overflow-y-auto bg-white">
<AnimatePresence>
{messages.map((msg) => (
msg.text && (
Expand All @@ -26,8 +26,8 @@ export default function MessageList({ messages }: MessageListProps) {
transition={{ duration: 0.5 }}
className={`mb-4 ${msg.sender === 'user' ? 'flex justify-end' : ''}`}
>
<div className={`inline-block p-2 rounded-lg max-w-[70%] break-words ${
msg.sender === 'user' ? 'bg-[#D958E4] text-white text-left' : 'bg-[#CDCED7] text-black text-justify'
<div className={`inline-block px-2 py-3 rounded-lg max-w-[70%] break-words ${
msg.sender === 'user' ? 'bg-[#5d76f7] text-white text-left' : 'bg-gray-300 text-black text-justify'
}`}>
{msg.text}
</div>
Expand Down
Loading

0 comments on commit 39fbc38

Please sign in to comment.