diff --git a/dewey/app/globals.css b/dewey/app/globals.css index 13d40b8..8a87c98 100644 --- a/dewey/app/globals.css +++ b/dewey/app/globals.css @@ -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 */ diff --git a/dewey/app/layout.tsx b/dewey/app/layout.tsx index a36cde0..be4d3f0 100644 --- a/dewey/app/layout.tsx +++ b/dewey/app/layout.tsx @@ -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 ( - - {children} - + + + + + + + + + + + {children} - ); + ) } diff --git a/dewey/app/page.tsx b/dewey/app/page.tsx index 433c8aa..28c25aa 100644 --- a/dewey/app/page.tsx +++ b/dewey/app/page.tsx @@ -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 ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - app/page.tsx - - . -
  2. -
  3. Save and see your changes instantly.
  4. -
+ const [currentChatId, setCurrentChatId] = useState(null) + const [chats, setChats] = useLocalStorage('chats', {}) + const socket = useSocket() -
- - Vercel logomark - Deploy now - - - Read our docs - -
-
- + 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 ( +
+ +
- ); + ) } diff --git a/dewey/components/ChatArea.tsx b/dewey/components/ChatArea.tsx new file mode 100644 index 0000000..7c3742b --- /dev/null +++ b/dewey/components/ChatArea.tsx @@ -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 ( +
+ + {currentChatId && ( + + )} + +
+ ) +} \ No newline at end of file diff --git a/dewey/components/ChatContainer.tsx b/dewey/components/ChatContainer.tsx new file mode 100644 index 0000000..9c3b1a9 --- /dev/null +++ b/dewey/components/ChatContainer.tsx @@ -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 = ({ currentChat, socket }) => { + const chatContainerRef = useRef(null); + + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [currentChat]); + + if (!currentChat) return null; + + return ( +
+ {currentChat.messages.map((message, index) => ( +
+
+ {message.isUser ? ( + message.content + ) : ( +
+ )} +
+
+ ))} + {currentChat.thinkingSections.map((section, sectionIndex) => ( +
+ {section.thoughts.map((thought, thoughtIndex) => ( +
+
+ {thought.type}: +
+
+ {thought.details && ( +
+                  {thought.details}
+                
+ )} +
+ ))} +
+ ))} +
+ ); +}; + +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; \ No newline at end of file diff --git a/dewey/components/ChatTabs.tsx b/dewey/components/ChatTabs.tsx new file mode 100644 index 0000000..f42fa94 --- /dev/null +++ b/dewey/components/ChatTabs.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +interface ChatTabsProps { + chats: Record; + currentChatId: string | null; + createNewChat: () => void; + switchToChat: (chatId: string) => void; + closeChat: (chatId: string) => void; +} + +const ChatTabs: React.FC = ({ chats, currentChatId, createNewChat, switchToChat, closeChat }) => { + return ( +
+ {Object.keys(chats).map((chatId) => ( +
+ + +
+ ))} + +
+ ); +}; + +export default ChatTabs; \ No newline at end of file diff --git a/dewey/components/Sidebar.tsx b/dewey/components/Sidebar.tsx new file mode 100644 index 0000000..24d6356 --- /dev/null +++ b/dewey/components/Sidebar.tsx @@ -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 = ({ 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 ( +
+ +
+

CPU Load

+ +
+
+

Memory Usage

+ +
+
+

Disk I/O

+ +
+
+

GPU Load

+ +
+
+

GPU Memory

+ +
+
+ ); +}; + +export default Sidebar; \ No newline at end of file diff --git a/dewey/components/UserInput.tsx b/dewey/components/UserInput.tsx new file mode 100644 index 0000000..eaca268 --- /dev/null +++ b/dewey/components/UserInput.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +interface UserInputProps { + value: string; + onChange: (value: string) => void; + onSend: () => void; +} + +const UserInput: React.FC = ({ value, onChange, onSend }) => { + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + onSend(); + } + }; + + return ( +
+