New frontend and tools
This commit is contained in:
		| @ -3,25 +3,62 @@ | ||||
| @tailwind utilities; | ||||
|  | ||||
| :root { | ||||
|   --background: #ffffff; | ||||
|   --foreground: #171717; | ||||
| } | ||||
|  | ||||
| @media (prefers-color-scheme: dark) { | ||||
|   :root { | ||||
|     --background: #0a0a0a; | ||||
|     --foreground: #ededed; | ||||
|   } | ||||
|   --foreground-rgb: 255, 255, 255; | ||||
|   --background-start-rgb: 0, 0, 0; | ||||
|   --background-end-rgb: 0, 0, 0; | ||||
| } | ||||
|  | ||||
| body { | ||||
|   color: var(--foreground); | ||||
|   background: var(--background); | ||||
|   font-family: Arial, Helvetica, sans-serif; | ||||
|   color: rgb(var(--foreground-rgb)); | ||||
|   background: linear-gradient( | ||||
|       to bottom, | ||||
|       transparent, | ||||
|       rgb(var(--background-end-rgb)) | ||||
|     ) | ||||
|     rgb(var(--background-start-rgb)); | ||||
|   font-family: 'Noto Sans Mono', monospace; | ||||
| } | ||||
|  | ||||
| @layer utilities { | ||||
|   .text-balance { | ||||
|     text-wrap: balance; | ||||
|   } | ||||
| /* Custom styles from the original index.html */ | ||||
| .thinking-section { | ||||
|   margin-bottom: 20px; | ||||
|   border-left: 2px solid #444; | ||||
|   padding-left: 10px; | ||||
| } | ||||
|  | ||||
| .thought-summary { | ||||
|   font-weight: bold; | ||||
|   margin-bottom: 5px; | ||||
|   padding: 5px; | ||||
|   border-radius: 3px; | ||||
| } | ||||
|  | ||||
| .thought-summary.plan { background-color: #2c3e50; } | ||||
| .thought-summary.decision { background-color: #34495e; } | ||||
| .thought-summary.tool_call { background-color: #16a085; } | ||||
| .thought-summary.tool_result { background-color: #27ae60; } | ||||
| .thought-summary.think_more { background-color: #2980b9; } | ||||
| .thought-summary.answer { background-color: #8e44ad; } | ||||
|  | ||||
| .thought-details { | ||||
|   display: none; | ||||
|   margin-left: 20px; | ||||
|   border-left: 2px solid #444; | ||||
|   padding-left: 10px; | ||||
|   margin-bottom: 10px; | ||||
|   white-space: pre-wrap; | ||||
|   font-family: 'Noto Sans Mono', monospace; | ||||
|   background-color: #222; | ||||
| } | ||||
|  | ||||
| .collapsible::before { | ||||
|   content: '▶ '; | ||||
|   display: inline-block; | ||||
|   transition: transform 0.3s; | ||||
| } | ||||
|  | ||||
| .collapsible.open::before { | ||||
|   transform: rotate(90deg); | ||||
| } | ||||
|  | ||||
| /* Add any other custom styles from the original index.html here */ | ||||
|  | ||||
| @ -1,35 +1,31 @@ | ||||
| import type { Metadata } from "next"; | ||||
| import localFont from "next/font/local"; | ||||
| import "./globals.css"; | ||||
| import './globals.css' | ||||
| import { Inter } from 'next/font/google' | ||||
|  | ||||
| const geistSans = localFont({ | ||||
|   src: "./fonts/GeistVF.woff", | ||||
|   variable: "--font-geist-sans", | ||||
|   weight: "100 900", | ||||
| }); | ||||
| const geistMono = localFont({ | ||||
|   src: "./fonts/GeistMonoVF.woff", | ||||
|   variable: "--font-geist-mono", | ||||
|   weight: "100 900", | ||||
| }); | ||||
| const inter = Inter({ subsets: ['latin'] }) | ||||
|  | ||||
| export const metadata: Metadata = { | ||||
|   title: "Create Next App", | ||||
|   description: "Generated by create next app", | ||||
| }; | ||||
| export const metadata = { | ||||
|   title: 'DWS Intelligence', | ||||
|   description: 'AI-powered chat application', | ||||
| } | ||||
|  | ||||
| export default function RootLayout({ | ||||
|   children, | ||||
| }: Readonly<{ | ||||
|   children: React.ReactNode; | ||||
| }>) { | ||||
| }: { | ||||
|   children: React.ReactNode | ||||
| }) { | ||||
|   return ( | ||||
|     <html lang="en"> | ||||
|       <body | ||||
|         className={`${geistSans.variable} ${geistMono.variable} antialiased`} | ||||
|       > | ||||
|         {children} | ||||
|       </body> | ||||
|       <head> | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script> | ||||
|         <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | ||||
|         <script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script> | ||||
|         <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | ||||
|         <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@1.0.1/dist/chartjs-adapter-moment.min.js"></script> | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script> | ||||
|         <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css" /> | ||||
|         <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Mono:wght@400;700&display=swap" rel="stylesheet" /> | ||||
|       </head> | ||||
|       <body className={inter.className}>{children}</body> | ||||
|     </html> | ||||
|   ); | ||||
|   ) | ||||
| } | ||||
|  | ||||
| @ -1,101 +1,44 @@ | ||||
| import Image from "next/image"; | ||||
| 'use client' | ||||
|  | ||||
| import { useState, useEffect } from 'react' | ||||
| import ChatArea from '../components/ChatArea' | ||||
| import Sidebar from '../components/Sidebar' | ||||
| import useSocket from '../hooks/useSocket' | ||||
| import useLocalStorage from '../hooks/useLocalStorage' | ||||
|  | ||||
| export default function Home() { | ||||
|   return ( | ||||
|     <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]"> | ||||
|       <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start"> | ||||
|         <Image | ||||
|           className="dark:invert" | ||||
|           src="https://nextjs.org/icons/next.svg" | ||||
|           alt="Next.js logo" | ||||
|           width={180} | ||||
|           height={38} | ||||
|           priority | ||||
|         /> | ||||
|         <ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]"> | ||||
|           <li className="mb-2"> | ||||
|             Get started by editing{" "} | ||||
|             <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> | ||||
|               app/page.tsx | ||||
|             </code> | ||||
|             . | ||||
|           </li> | ||||
|           <li>Save and see your changes instantly.</li> | ||||
|         </ol> | ||||
|   const [currentChatId, setCurrentChatId] = useState<string | null>(null) | ||||
|   const [chats, setChats] = useLocalStorage('chats', {}) | ||||
|   const socket = useSocket() | ||||
|  | ||||
|         <div className="flex gap-4 items-center flex-col sm:flex-row"> | ||||
|           <a | ||||
|             className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5" | ||||
|             href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||||
|             target="_blank" | ||||
|             rel="noopener noreferrer" | ||||
|           > | ||||
|             <Image | ||||
|               className="dark:invert" | ||||
|               src="https://nextjs.org/icons/vercel.svg" | ||||
|               alt="Vercel logomark" | ||||
|               width={20} | ||||
|               height={20} | ||||
|             /> | ||||
|             Deploy now | ||||
|           </a> | ||||
|           <a | ||||
|             className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44" | ||||
|             href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||||
|             target="_blank" | ||||
|             rel="noopener noreferrer" | ||||
|           > | ||||
|             Read our docs | ||||
|           </a> | ||||
|         </div> | ||||
|       </main> | ||||
|       <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center"> | ||||
|         <a | ||||
|           className="flex items-center gap-2 hover:underline hover:underline-offset-4" | ||||
|           href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||||
|           target="_blank" | ||||
|           rel="noopener noreferrer" | ||||
|         > | ||||
|           <Image | ||||
|             aria-hidden | ||||
|             src="https://nextjs.org/icons/file.svg" | ||||
|             alt="File icon" | ||||
|             width={16} | ||||
|             height={16} | ||||
|           /> | ||||
|           Learn | ||||
|         </a> | ||||
|         <a | ||||
|           className="flex items-center gap-2 hover:underline hover:underline-offset-4" | ||||
|           href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||||
|           target="_blank" | ||||
|           rel="noopener noreferrer" | ||||
|         > | ||||
|           <Image | ||||
|             aria-hidden | ||||
|             src="https://nextjs.org/icons/window.svg" | ||||
|             alt="Window icon" | ||||
|             width={16} | ||||
|             height={16} | ||||
|           /> | ||||
|           Examples | ||||
|         </a> | ||||
|         <a | ||||
|           className="flex items-center gap-2 hover:underline hover:underline-offset-4" | ||||
|           href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||||
|           target="_blank" | ||||
|           rel="noopener noreferrer" | ||||
|         > | ||||
|           <Image | ||||
|             aria-hidden | ||||
|             src="https://nextjs.org/icons/globe.svg" | ||||
|             alt="Globe icon" | ||||
|             width={16} | ||||
|             height={16} | ||||
|           /> | ||||
|           Go to nextjs.org → | ||||
|         </a> | ||||
|       </footer> | ||||
|   useEffect(() => { | ||||
|     if (Object.keys(chats).length === 0) { | ||||
|       createNewChat() | ||||
|     } else { | ||||
|       setCurrentChatId(Object.keys(chats)[0]) | ||||
|     } | ||||
|   }, []) | ||||
|  | ||||
|   const createNewChat = () => { | ||||
|     const chatId = Date.now().toString() | ||||
|     setChats(prevChats => ({ | ||||
|       ...prevChats, | ||||
|       [chatId]: { messages: [], thinkingSections: [] } | ||||
|     })) | ||||
|     setCurrentChatId(chatId) | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <div className="flex h-screen"> | ||||
|       <ChatArea | ||||
|         currentChatId={currentChatId} | ||||
|         setCurrentChatId={setCurrentChatId} | ||||
|         chats={chats} | ||||
|         setChats={setChats} | ||||
|         createNewChat={createNewChat} | ||||
|         socket={socket} | ||||
|       /> | ||||
|       <Sidebar socket={socket} /> | ||||
|     </div> | ||||
|   ); | ||||
|   ) | ||||
| } | ||||
|  | ||||
							
								
								
									
										133
									
								
								dewey/components/ChatArea.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								dewey/components/ChatArea.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,133 @@ | ||||
| import { useState, useEffect } from 'react' | ||||
| import ChatTabs from './ChatTabs' | ||||
| import ChatContainer from './ChatContainer' | ||||
| import UserInput from './UserInput' | ||||
|  | ||||
| export default function ChatArea({ currentChatId, setCurrentChatId, chats, setChats, createNewChat, socket }) { | ||||
|   const [userInput, setUserInput] = useState('') | ||||
|  | ||||
|   const sendMessage = () => { | ||||
|     if (userInput.trim() && currentChatId) { | ||||
|       const newMessage = { content: userInput, isUser: true } | ||||
|       setChats(prevChats => ({ | ||||
|         ...prevChats, | ||||
|         [currentChatId]: { | ||||
|           ...prevChats[currentChatId], | ||||
|           messages: [...prevChats[currentChatId].messages, newMessage], | ||||
|           thinkingSections: [...prevChats[currentChatId].thinkingSections, { thoughts: [] }] | ||||
|         } | ||||
|       })) | ||||
|       socket.emit('chat_request', {  | ||||
|         message: userInput, | ||||
|         conversation_history: chats[currentChatId].messages | ||||
|           .filter(m => !m.isUser).map(m => ({ role: 'assistant', content: m.content })) | ||||
|           .concat(chats[currentChatId].messages.filter(m => m.isUser).map(m => ({ role: 'user', content: m.content }))) | ||||
|       }) | ||||
|       setUserInput('') | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const switchToChat = (chatId: string) => { | ||||
|     setCurrentChatId(chatId); | ||||
|   } | ||||
|  | ||||
|   const closeChat = (chatId: string) => { | ||||
|     if (window.confirm('Are you sure you want to close this chat?')) { | ||||
|       setChats(prevChats => { | ||||
|         const newChats = { ...prevChats }; | ||||
|         delete newChats[chatId]; | ||||
|         return newChats; | ||||
|       }); | ||||
|       if (currentChatId === chatId) { | ||||
|         const remainingChatIds = Object.keys(chats).filter(id => id !== chatId); | ||||
|         if (remainingChatIds.length > 0) { | ||||
|           switchToChat(remainingChatIds[0]); | ||||
|         } else { | ||||
|           createNewChat(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (socket) { | ||||
|       socket.on('thinking', (data) => { | ||||
|         // Handle thinking event | ||||
|         setChats(prevChats => ({ | ||||
|           ...prevChats, | ||||
|           [currentChatId]: { | ||||
|             ...prevChats[currentChatId], | ||||
|             thinkingSections: [ | ||||
|               ...prevChats[currentChatId].thinkingSections, | ||||
|               { thoughts: [{ type: 'thinking', content: data.step }] } | ||||
|             ] | ||||
|           } | ||||
|         })); | ||||
|       }); | ||||
|  | ||||
|       socket.on('thought', (data) => { | ||||
|         // Handle thought event | ||||
|         setChats(prevChats => ({ | ||||
|           ...prevChats, | ||||
|           [currentChatId]: { | ||||
|             ...prevChats[currentChatId], | ||||
|             thinkingSections: prevChats[currentChatId].thinkingSections.map((section, index) =>  | ||||
|               index === prevChats[currentChatId].thinkingSections.length - 1 | ||||
|                 ? { ...section, thoughts: [...section.thoughts, data] } | ||||
|                 : section | ||||
|             ) | ||||
|           } | ||||
|         })); | ||||
|       }); | ||||
|  | ||||
|       socket.on('chat_response', (data) => { | ||||
|         // Handle chat response event | ||||
|         setChats(prevChats => ({ | ||||
|           ...prevChats, | ||||
|           [currentChatId]: { | ||||
|             ...prevChats[currentChatId], | ||||
|             messages: [...prevChats[currentChatId].messages, { content: data.response, isUser: false }] | ||||
|           } | ||||
|         })); | ||||
|       }); | ||||
|  | ||||
|       socket.on('error', (data) => { | ||||
|         // Handle error event | ||||
|         console.error('Error:', data.message); | ||||
|         // You might want to display this error to the user | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     return () => { | ||||
|       if (socket) { | ||||
|         socket.off('thinking'); | ||||
|         socket.off('thought'); | ||||
|         socket.off('chat_response'); | ||||
|         socket.off('error'); | ||||
|       } | ||||
|     }; | ||||
|   }, [socket, currentChatId, setChats]); | ||||
|  | ||||
|   return ( | ||||
|     <div className="flex flex-col flex-1"> | ||||
|       <ChatTabs | ||||
|         chats={chats} | ||||
|         currentChatId={currentChatId} | ||||
|         createNewChat={createNewChat} | ||||
|         switchToChat={switchToChat} | ||||
|         closeChat={closeChat} | ||||
|       /> | ||||
|       {currentChatId && ( | ||||
|         <ChatContainer | ||||
|           currentChat={chats[currentChatId]} | ||||
|           socket={socket} | ||||
|         /> | ||||
|       )} | ||||
|       <UserInput | ||||
|         value={userInput} | ||||
|         onChange={setUserInput} | ||||
|         onSend={sendMessage} | ||||
|       /> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
							
								
								
									
										85
									
								
								dewey/components/ChatContainer.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								dewey/components/ChatContainer.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| import React, { useEffect, useRef } from 'react'; | ||||
| import { marked } from 'marked'; | ||||
|  | ||||
| interface ChatContainerProps { | ||||
|   currentChat: { | ||||
|     messages: Array<{ content: string; isUser: boolean }>; | ||||
|     thinkingSections: Array<{ thoughts: Array<{ type: string; content: string; details?: string }> }>; | ||||
|   } | null; | ||||
|   socket: any; | ||||
| } | ||||
|  | ||||
| const ChatContainer: React.FC<ChatContainerProps> = ({ currentChat, socket }) => { | ||||
|   const chatContainerRef = useRef<HTMLDivElement>(null); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (chatContainerRef.current) { | ||||
|       chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; | ||||
|     } | ||||
|   }, [currentChat]); | ||||
|  | ||||
|   if (!currentChat) return null; | ||||
|  | ||||
|   return ( | ||||
|     <div ref={chatContainerRef} className="flex-1 overflow-y-auto p-4 bg-gray-900"> | ||||
|       {currentChat.messages.map((message, index) => ( | ||||
|         <div | ||||
|           key={index} | ||||
|           className={`mb-4 ${ | ||||
|             message.isUser ? 'text-right text-cyan-300' : 'text-left text-white' | ||||
|           }`} | ||||
|         > | ||||
|           <div | ||||
|             className={`inline-block p-2 rounded-lg ${ | ||||
|               message.isUser ? 'bg-cyan-800' : 'bg-gray-700' | ||||
|             }`} | ||||
|           > | ||||
|             {message.isUser ? ( | ||||
|               message.content | ||||
|             ) : ( | ||||
|               <div dangerouslySetInnerHTML={{ __html: marked(message.content) }} /> | ||||
|             )} | ||||
|           </div> | ||||
|         </div> | ||||
|       ))} | ||||
|       {currentChat.thinkingSections.map((section, sectionIndex) => ( | ||||
|         <div key={sectionIndex} className="mb-4 border-l-2 border-gray-600 pl-4"> | ||||
|           {section.thoughts.map((thought, thoughtIndex) => ( | ||||
|             <div key={thoughtIndex} className="mb-2"> | ||||
|               <div className={`font-bold ${getThoughtColor(thought.type)}`}> | ||||
|                 {thought.type}: | ||||
|               </div> | ||||
|               <div dangerouslySetInnerHTML={{ __html: marked(thought.content) }} /> | ||||
|               {thought.details && ( | ||||
|                 <pre className="mt-2 p-2 bg-gray-800 rounded"> | ||||
|                   {thought.details} | ||||
|                 </pre> | ||||
|               )} | ||||
|             </div> | ||||
|           ))} | ||||
|         </div> | ||||
|       ))} | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| function getThoughtColor(type: string): string { | ||||
|   switch (type.toLowerCase()) { | ||||
|     case 'plan': | ||||
|       return 'text-blue-400'; | ||||
|     case 'decision': | ||||
|       return 'text-green-400'; | ||||
|     case 'tool_call': | ||||
|       return 'text-yellow-400'; | ||||
|     case 'tool_result': | ||||
|       return 'text-purple-400'; | ||||
|     case 'think_more': | ||||
|       return 'text-pink-400'; | ||||
|     case 'answer': | ||||
|       return 'text-red-400'; | ||||
|     default: | ||||
|       return 'text-gray-400'; | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default ChatContainer; | ||||
							
								
								
									
										48
									
								
								dewey/components/ChatTabs.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								dewey/components/ChatTabs.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| import React from 'react'; | ||||
|  | ||||
| interface ChatTabsProps { | ||||
|   chats: Record<string, any>; | ||||
|   currentChatId: string | null; | ||||
|   createNewChat: () => void; | ||||
|   switchToChat: (chatId: string) => void; | ||||
|   closeChat: (chatId: string) => void; | ||||
| } | ||||
|  | ||||
| const ChatTabs: React.FC<ChatTabsProps> = ({ chats, currentChatId, createNewChat, switchToChat, closeChat }) => { | ||||
|   return ( | ||||
|     <div className="flex bg-gray-800 p-2"> | ||||
|       {Object.keys(chats).map((chatId) => ( | ||||
|         <div | ||||
|           key={chatId} | ||||
|           className={`px-4 py-2 mr-2 rounded-t-lg flex items-center ${ | ||||
|             chatId === currentChatId ? 'bg-gray-600' : 'bg-gray-700' | ||||
|           }`} | ||||
|         > | ||||
|           <button | ||||
|             onClick={() => switchToChat(chatId)} | ||||
|             className="flex-grow text-left" | ||||
|           > | ||||
|             Chat {chatId} | ||||
|           </button> | ||||
|           <button  | ||||
|             className="ml-2 text-red-500 hover:text-red-700" | ||||
|             onClick={(e) => { | ||||
|               e.stopPropagation(); | ||||
|               closeChat(chatId); | ||||
|             }} | ||||
|           > | ||||
|             × | ||||
|           </button> | ||||
|         </div> | ||||
|       ))} | ||||
|       <button | ||||
|         className="px-4 py-2 bg-green-600 rounded-t-lg" | ||||
|         onClick={createNewChat} | ||||
|       > | ||||
|         + New Chat | ||||
|       </button> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default ChatTabs; | ||||
							
								
								
									
										129
									
								
								dewey/components/Sidebar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								dewey/components/Sidebar.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,129 @@ | ||||
| import React, { useEffect, useRef, useState } from 'react'; | ||||
| import dynamic from 'next/dynamic'; | ||||
|  | ||||
| const Chart = dynamic(() => import('chart.js/auto').then((mod) => mod.Chart), { | ||||
|   ssr: false, | ||||
| }); | ||||
|  | ||||
| interface SidebarProps { | ||||
|   socket: any; | ||||
| } | ||||
|  | ||||
| const Sidebar: React.FC<SidebarProps> = ({ socket }) => { | ||||
|   const [isCollapsed, setIsCollapsed] = useState(false); | ||||
|   const chartRefs = useRef<{ [key: string]: any }>({ | ||||
|     cpu: null, | ||||
|     memory: null, | ||||
|     disk: null, | ||||
|     gpu: null, | ||||
|     gpuMemory: null, | ||||
|   }); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (socket) { | ||||
|       socket.on('system_resources', (data: any) => { | ||||
|         updateCharts(data); | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     return () => { | ||||
|       if (socket) { | ||||
|         socket.off('system_resources'); | ||||
|       } | ||||
|     }; | ||||
|   }, [socket]); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     const initCharts = async () => { | ||||
|       const ChartJS = await Chart; | ||||
|       initializeCharts(ChartJS); | ||||
|     }; | ||||
|     initCharts(); | ||||
|  | ||||
|     return () => { | ||||
|       Object.values(chartRefs.current).forEach(chart => chart?.destroy()); | ||||
|     }; | ||||
|   }, []); | ||||
|  | ||||
|   const initializeCharts = (ChartJS: any) => { | ||||
|     const chartConfig = { | ||||
|       type: 'line', | ||||
|       options: { | ||||
|         responsive: true, | ||||
|         maintainAspectRatio: false, | ||||
|         scales: { | ||||
|           x: { | ||||
|             type: 'time', | ||||
|             time: { | ||||
|               unit: 'second', | ||||
|             }, | ||||
|           }, | ||||
|           y: { | ||||
|             beginAtZero: true, | ||||
|             max: 100, | ||||
|           }, | ||||
|         }, | ||||
|         animation: false, | ||||
|       }, | ||||
|       data: { | ||||
|         datasets: [{ | ||||
|           data: [], | ||||
|           borderColor: 'rgb(75, 192, 192)', | ||||
|           tension: 0.1, | ||||
|         }], | ||||
|       }, | ||||
|     }; | ||||
|  | ||||
|     ['cpu', 'memory', 'disk', 'gpu', 'gpuMemory'].forEach(chartName => { | ||||
|       const ctx = document.getElementById(`${chartName}Chart`) as HTMLCanvasElement; | ||||
|       if (ctx) { | ||||
|         chartRefs.current[chartName] = new ChartJS(ctx, chartConfig); | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   const updateCharts = (data: any) => { | ||||
|     const now = new Date(); | ||||
|     Object.entries(data).forEach(([key, value]) => { | ||||
|       const chartName = key.replace('_', '').toLowerCase(); | ||||
|       const chart = chartRefs.current[chartName]; | ||||
|       if (chart) { | ||||
|         chart.data.datasets[0].data.push({x: now, y: value}); | ||||
|         chart.update('none'); | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <div className={`w-80 bg-gray-800 p-4 ${isCollapsed ? 'hidden' : ''}`}> | ||||
|       <button | ||||
|         onClick={() => setIsCollapsed(!isCollapsed)} | ||||
|         className="mb-4 px-4 py-2 bg-gray-700 text-white rounded-lg" | ||||
|       > | ||||
|         {isCollapsed ? 'Show Charts' : 'Hide Charts'} | ||||
|       </button> | ||||
|       <div className="mb-4"> | ||||
|         <h3 className="text-white mb-2">CPU Load</h3> | ||||
|         <canvas id="cpuChart"></canvas> | ||||
|       </div> | ||||
|       <div className="mb-4"> | ||||
|         <h3 className="text-white mb-2">Memory Usage</h3> | ||||
|         <canvas id="memoryChart"></canvas> | ||||
|       </div> | ||||
|       <div className="mb-4"> | ||||
|         <h3 className="text-white mb-2">Disk I/O</h3> | ||||
|         <canvas id="diskChart"></canvas> | ||||
|       </div> | ||||
|       <div className="mb-4"> | ||||
|         <h3 className="text-white mb-2">GPU Load</h3> | ||||
|         <canvas id="gpuChart"></canvas> | ||||
|       </div> | ||||
|       <div className="mb-4"> | ||||
|         <h3 className="text-white mb-2">GPU Memory</h3> | ||||
|         <canvas id="gpuMemoryChart"></canvas> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default Sidebar; | ||||
							
								
								
									
										37
									
								
								dewey/components/UserInput.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								dewey/components/UserInput.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| import React from 'react'; | ||||
|  | ||||
| interface UserInputProps { | ||||
|   value: string; | ||||
|   onChange: (value: string) => void; | ||||
|   onSend: () => void; | ||||
| } | ||||
|  | ||||
| const UserInput: React.FC<UserInputProps> = ({ value, onChange, onSend }) => { | ||||
|   const handleKeyPress = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { | ||||
|     if (e.key === 'Enter' && !e.shiftKey) { | ||||
|       e.preventDefault(); | ||||
|       onSend(); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <div className="p-4 bg-gray-800"> | ||||
|       <textarea | ||||
|         value={value} | ||||
|         onChange={(e) => onChange(e.target.value)} | ||||
|         onKeyPress={handleKeyPress} | ||||
|         className="w-full p-2 bg-gray-700 text-white rounded-lg resize-none" | ||||
|         rows={3} | ||||
|         placeholder="Type your message here..." | ||||
|       /> | ||||
|       <button | ||||
|         onClick={onSend} | ||||
|         className="mt-2 px-4 py-2 bg-blue-600 text-white rounded-lg" | ||||
|       > | ||||
|         Send | ||||
|       </button> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default UserInput; | ||||
							
								
								
									
										23
									
								
								dewey/hooks/useLocalStorage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								dewey/hooks/useLocalStorage.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| import { useState, useEffect } from 'react' | ||||
|  | ||||
| export default function useLocalStorage(key, initialValue) { | ||||
|   const [storedValue, setStoredValue] = useState(() => { | ||||
|     try { | ||||
|       const item = window.localStorage.getItem(key) | ||||
|       return item ? JSON.parse(item) : initialValue | ||||
|     } catch (error) { | ||||
|       console.log(error) | ||||
|       return initialValue | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   useEffect(() => { | ||||
|     try { | ||||
|       window.localStorage.setItem(key, JSON.stringify(storedValue)) | ||||
|     } catch (error) { | ||||
|       console.log(error) | ||||
|     } | ||||
|   }, [key, storedValue]) | ||||
|  | ||||
|   return [storedValue, setStoredValue] | ||||
| } | ||||
							
								
								
									
										14
									
								
								dewey/hooks/useSocket.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								dewey/hooks/useSocket.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| import { useEffect, useState } from 'react' | ||||
| import io from 'socket.io-client' | ||||
|  | ||||
| export default function useSocket() { | ||||
|   const [socket, setSocket] = useState<any>(null) | ||||
|  | ||||
|   useEffect(() => { | ||||
|     const newSocket = io('http://localhost:5001') | ||||
|     setSocket(newSocket) | ||||
|     return () => newSocket.close() | ||||
|   }, []) | ||||
|  | ||||
|   return socket | ||||
| } | ||||
							
								
								
									
										292
									
								
								dewey/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										292
									
								
								dewey/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -8,19 +8,24 @@ | ||||
|       "name": "dewey", | ||||
|       "version": "0.1.0", | ||||
|       "dependencies": { | ||||
|         "next": "14.2.13", | ||||
|         "react": "^18", | ||||
|         "react-dom": "^18" | ||||
|         "autoprefixer": "^10.4.20", | ||||
|         "chart.js": "^3.9.1", | ||||
|         "chartjs-adapter-moment": "^1.0.1", | ||||
|         "marked": "^4.3.0", | ||||
|         "next": "^14.2.13", | ||||
|         "react": "^18.3.1", | ||||
|         "react-dom": "^18.3.1", | ||||
|         "socket.io-client": "^4.8.0" | ||||
|       }, | ||||
|       "devDependencies": { | ||||
|         "@types/node": "^20", | ||||
|         "@types/react": "^18", | ||||
|         "@types/react-dom": "^18", | ||||
|         "@types/node": "^20.16.10", | ||||
|         "@types/react": "^18.3.10", | ||||
|         "@types/react-dom": "^18.3.0", | ||||
|         "eslint": "^8", | ||||
|         "eslint-config-next": "14.2.13", | ||||
|         "postcss": "^8", | ||||
|         "tailwindcss": "^3.4.1", | ||||
|         "typescript": "^5" | ||||
|         "postcss": "^8.4.47", | ||||
|         "tailwindcss": "^3.4.13", | ||||
|         "typescript": "^5.6.2" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@alloc/quick-lru": { | ||||
| @ -467,6 +472,12 @@ | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@socket.io/component-emitter": { | ||||
|       "version": "3.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", | ||||
|       "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@swc/counter": { | ||||
|       "version": "0.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", | ||||
| @ -1027,6 +1038,43 @@ | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/autoprefixer": { | ||||
|       "version": "10.4.20", | ||||
|       "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", | ||||
|       "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", | ||||
|       "funding": [ | ||||
|         { | ||||
|           "type": "opencollective", | ||||
|           "url": "https://opencollective.com/postcss/" | ||||
|         }, | ||||
|         { | ||||
|           "type": "tidelift", | ||||
|           "url": "https://tidelift.com/funding/github/npm/autoprefixer" | ||||
|         }, | ||||
|         { | ||||
|           "type": "github", | ||||
|           "url": "https://github.com/sponsors/ai" | ||||
|         } | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "browserslist": "^4.23.3", | ||||
|         "caniuse-lite": "^1.0.30001646", | ||||
|         "fraction.js": "^4.3.7", | ||||
|         "normalize-range": "^0.1.2", | ||||
|         "picocolors": "^1.0.1", | ||||
|         "postcss-value-parser": "^4.2.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "autoprefixer": "bin/autoprefixer" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": "^10 || ^12 || >=14" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "postcss": "^8.1.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/available-typed-arrays": { | ||||
|       "version": "1.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", | ||||
| @ -1107,6 +1155,38 @@ | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/browserslist": { | ||||
|       "version": "4.24.0", | ||||
|       "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", | ||||
|       "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", | ||||
|       "funding": [ | ||||
|         { | ||||
|           "type": "opencollective", | ||||
|           "url": "https://opencollective.com/browserslist" | ||||
|         }, | ||||
|         { | ||||
|           "type": "tidelift", | ||||
|           "url": "https://tidelift.com/funding/github/npm/browserslist" | ||||
|         }, | ||||
|         { | ||||
|           "type": "github", | ||||
|           "url": "https://github.com/sponsors/ai" | ||||
|         } | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "caniuse-lite": "^1.0.30001663", | ||||
|         "electron-to-chromium": "^1.5.28", | ||||
|         "node-releases": "^2.0.18", | ||||
|         "update-browserslist-db": "^1.1.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "browserslist": "cli.js" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/busboy": { | ||||
|       "version": "1.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", | ||||
| @ -1195,6 +1275,22 @@ | ||||
|         "url": "https://github.com/chalk/chalk?sponsor=1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/chart.js": { | ||||
|       "version": "3.9.1", | ||||
|       "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz", | ||||
|       "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/chartjs-adapter-moment": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.1.tgz", | ||||
|       "integrity": "sha512-Uz+nTX/GxocuqXpGylxK19YG4R3OSVf8326D+HwSTsNw1LgzyIGRo+Qujwro1wy6X+soNSnfj5t2vZ+r6EaDmA==", | ||||
|       "license": "MIT", | ||||
|       "peerDependencies": { | ||||
|         "chart.js": ">=3.0.0", | ||||
|         "moment": "^2.10.2" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/chokidar": { | ||||
|       "version": "3.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", | ||||
| @ -1376,7 +1472,6 @@ | ||||
|       "version": "4.3.7", | ||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", | ||||
|       "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "ms": "^2.1.3" | ||||
| @ -1500,6 +1595,12 @@ | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/electron-to-chromium": { | ||||
|       "version": "1.5.29", | ||||
|       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz", | ||||
|       "integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==", | ||||
|       "license": "ISC" | ||||
|     }, | ||||
|     "node_modules/emoji-regex": { | ||||
|       "version": "9.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", | ||||
| @ -1507,6 +1608,28 @@ | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/engine.io-client": { | ||||
|       "version": "6.6.1", | ||||
|       "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.1.tgz", | ||||
|       "integrity": "sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@socket.io/component-emitter": "~3.1.0", | ||||
|         "debug": "~4.3.1", | ||||
|         "engine.io-parser": "~5.2.1", | ||||
|         "ws": "~8.17.1", | ||||
|         "xmlhttprequest-ssl": "~2.1.1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/engine.io-parser": { | ||||
|       "version": "5.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", | ||||
|       "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=10.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/enhanced-resolve": { | ||||
|       "version": "5.17.1", | ||||
|       "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", | ||||
| @ -1708,6 +1831,15 @@ | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/escalade": { | ||||
|       "version": "3.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", | ||||
|       "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/escape-string-regexp": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", | ||||
| @ -2322,6 +2454,19 @@ | ||||
|         "url": "https://github.com/sponsors/isaacs" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/fraction.js": { | ||||
|       "version": "4.3.7", | ||||
|       "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", | ||||
|       "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": "*" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "type": "patreon", | ||||
|         "url": "https://github.com/sponsors/rawify" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/fs.realpath": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", | ||||
| @ -3368,6 +3513,18 @@ | ||||
|       "dev": true, | ||||
|       "license": "ISC" | ||||
|     }, | ||||
|     "node_modules/marked": { | ||||
|       "version": "4.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", | ||||
|       "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", | ||||
|       "license": "MIT", | ||||
|       "bin": { | ||||
|         "marked": "bin/marked.js" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 12" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/merge2": { | ||||
|       "version": "1.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", | ||||
| @ -3425,11 +3582,20 @@ | ||||
|         "node": ">=16 || 14 >=14.17" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/moment": { | ||||
|       "version": "2.30.1", | ||||
|       "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", | ||||
|       "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", | ||||
|       "license": "MIT", | ||||
|       "peer": true, | ||||
|       "engines": { | ||||
|         "node": "*" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ms": { | ||||
|       "version": "2.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", | ||||
|       "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/mz": { | ||||
| @ -3547,6 +3713,12 @@ | ||||
|         "node": "^10 || ^12 || >=14" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/node-releases": { | ||||
|       "version": "2.0.18", | ||||
|       "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", | ||||
|       "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/normalize-path": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", | ||||
| @ -3557,6 +3729,15 @@ | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/normalize-range": { | ||||
|       "version": "0.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", | ||||
|       "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/object-assign": { | ||||
|       "version": "4.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", | ||||
| @ -3883,7 +4064,6 @@ | ||||
|       "version": "8.4.47", | ||||
|       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", | ||||
|       "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", | ||||
|       "dev": true, | ||||
|       "funding": [ | ||||
|         { | ||||
|           "type": "opencollective", | ||||
| @ -4039,7 +4219,6 @@ | ||||
|       "version": "4.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", | ||||
|       "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/prelude-ls": { | ||||
| @ -4451,6 +4630,34 @@ | ||||
|         "url": "https://github.com/sponsors/isaacs" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/socket.io-client": { | ||||
|       "version": "4.8.0", | ||||
|       "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.0.tgz", | ||||
|       "integrity": "sha512-C0jdhD5yQahMws9alf/yvtsMGTaIDBnZ8Rb5HU56svyq0l5LIrGzIDZZD5pHQlmzxLuU91Gz+VpQMKgCTNYtkw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@socket.io/component-emitter": "~3.1.0", | ||||
|         "debug": "~4.3.2", | ||||
|         "engine.io-client": "~6.6.1", | ||||
|         "socket.io-parser": "~4.2.4" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=10.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/socket.io-parser": { | ||||
|       "version": "4.2.4", | ||||
|       "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", | ||||
|       "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@socket.io/component-emitter": "~3.1.0", | ||||
|         "debug": "~4.3.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=10.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/source-map-js": { | ||||
|       "version": "1.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", | ||||
| @ -5044,6 +5251,36 @@ | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/update-browserslist-db": { | ||||
|       "version": "1.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", | ||||
|       "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", | ||||
|       "funding": [ | ||||
|         { | ||||
|           "type": "opencollective", | ||||
|           "url": "https://opencollective.com/browserslist" | ||||
|         }, | ||||
|         { | ||||
|           "type": "tidelift", | ||||
|           "url": "https://tidelift.com/funding/github/npm/browserslist" | ||||
|         }, | ||||
|         { | ||||
|           "type": "github", | ||||
|           "url": "https://github.com/sponsors/ai" | ||||
|         } | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "escalade": "^3.2.0", | ||||
|         "picocolors": "^1.1.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "update-browserslist-db": "cli.js" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "browserslist": ">= 4.21.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/uri-js": { | ||||
|       "version": "4.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", | ||||
| @ -5278,6 +5515,35 @@ | ||||
|       "dev": true, | ||||
|       "license": "ISC" | ||||
|     }, | ||||
|     "node_modules/ws": { | ||||
|       "version": "8.17.1", | ||||
|       "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", | ||||
|       "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=10.0.0" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "bufferutil": "^4.0.1", | ||||
|         "utf-8-validate": ">=5.0.2" | ||||
|       }, | ||||
|       "peerDependenciesMeta": { | ||||
|         "bufferutil": { | ||||
|           "optional": true | ||||
|         }, | ||||
|         "utf-8-validate": { | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/xmlhttprequest-ssl": { | ||||
|       "version": "2.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz", | ||||
|       "integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==", | ||||
|       "engines": { | ||||
|         "node": ">=0.4.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/yaml": { | ||||
|       "version": "2.5.1", | ||||
|       "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", | ||||
|  | ||||
| @ -9,18 +9,23 @@ | ||||
|     "lint": "next lint" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "react": "^18", | ||||
|     "react-dom": "^18", | ||||
|     "next": "14.2.13" | ||||
|     "autoprefixer": "^10.4.20", | ||||
|     "chart.js": "^3.9.1", | ||||
|     "chartjs-adapter-moment": "^1.0.1", | ||||
|     "marked": "^4.3.0", | ||||
|     "next": "^14.2.13", | ||||
|     "react": "^18.3.1", | ||||
|     "react-dom": "^18.3.1", | ||||
|     "socket.io-client": "^4.8.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "typescript": "^5", | ||||
|     "@types/node": "^20", | ||||
|     "@types/react": "^18", | ||||
|     "@types/react-dom": "^18", | ||||
|     "postcss": "^8", | ||||
|     "tailwindcss": "^3.4.1", | ||||
|     "@types/node": "^20.16.10", | ||||
|     "@types/react": "^18.3.10", | ||||
|     "@types/react-dom": "^18.3.0", | ||||
|     "eslint": "^8", | ||||
|     "eslint-config-next": "14.2.13" | ||||
|     "eslint-config-next": "14.2.13", | ||||
|     "postcss": "^8.4.47", | ||||
|     "tailwindcss": "^3.4.13", | ||||
|     "typescript": "^5.6.2" | ||||
|   } | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user