New frontend and tools

This commit is contained in:
2024-10-01 19:31:57 -04:00
parent b56619a2e6
commit 9d25dd7d94
15 changed files with 937 additions and 191 deletions

View File

@ -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 */

View File

@ -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>
);
)
}

View File

@ -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>
);
)
}

View 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>
)
}

View 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;

View 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;

View 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;

View 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;

View 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
View 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
View File

@ -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",

View File

@ -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"
}
}