New frontend and tools

This commit is contained in:
Tanishq Dubey 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"
}
}

25
main.py
View File

@ -26,7 +26,7 @@ tool_manager = DefaultToolManager()
@app.route('/')
def index():
logger.info("Serving index.html")
logger.info("Serving index.html")
return send_from_directory('.', 'index.html')
class ChatRequest(BaseModel):
@ -99,7 +99,7 @@ def answer_question_tools(user_input: str, conversation_history: List[dict], max
emit('conversation_history', {'history': conversation_history})
emit('thought', {'type': 'tool_result', 'content': tool_response})
reflection_prompt = "Reflect on the tool results. If there were any errors, propose multiple alternative approaches to solve the problem. If successful, consider if the result fully answers the user's query or if additional steps are needed."
reflection_prompt = "Reflect on the tool results. If there were any errors, propose multiple alternative approaches to solve the problem. If successful, consider if the result fully answers the user's query or if additional steps are needed. If input is needed, ask the user for clarification with <answer></answer>."
conversation_history.append({
"role": "assistant",
"content": reflection_prompt
@ -112,9 +112,20 @@ def answer_question_tools(user_input: str, conversation_history: List[dict], max
final_answer = answer_content.group(1).strip()
emit('thought', {'type': 'answer', 'content': final_answer})
return final_answer
elif "<clarification>" in assistant_message['content'].lower():
clarification_content = re.search(r'<clarification>(.*?)</clarification>', assistant_message['content'], re.DOTALL)
if clarification_content:
clarification_answer = clarification_content.group(1).strip()
emit('thought', {'type': 'clarification', 'content': clarification_answer})
return clarification_answer
else:
emit('thought', {'type': 'decision', 'content': "Think/Plan/Decision/Action\n\n" + assistant_message['content']})
reflection_prompt = "Your last response didn't provide a final answer. Please reflect on your current understanding of the problem and consider if you need to use any tools or if you can now provide a final answer. If you're ready to give a final answer, put your response in tags <answer></answer>"
if assistant_message['content'].strip():
emit('thought', {'type': 'think_more', 'content': "Think/Plan/Decision/Action\n\n" + assistant_message['content']})
reflection_prompt = "Your last response didn't provide a final answer or was incorrectly formatted. Please reflect on your current understanding of the problem and consider if you need to use any tools or if you can now provide a final answer. If you're ready to give a final answer, or need more input from the user, put your response in tags <answer></answer>."
else:
emit('thought', {'type': 'think_more', 'content': "Previous step failed. Retrying..."})
reflection_prompt = "The previous step failed to produce any content. Please retry the previous step, considering alternative approaches or tools that might help solve the problem. If you're ready to give a final answer, or need more input from the user, put your response in tags <answer></answer>."
conversation_history.append({"role": "assistant", "content": reflection_prompt})
emit('conversation_history', {'history': conversation_history})
@ -133,6 +144,7 @@ When addressing a query, follow these steps:
2. Plan: Develop a plan of action, considering whether you need to use any tools or if you can answer directly.
3. Execute: If you need to use a tool, call it as you would a function. If not, proceed with your reasoning.
- Analyse the given prompt and decided whether or not it can be answered by a tool. If it can, use the following functions to respond with a JSON for a function call with its proper arguments that best answers the given prompt. Respond in the format \"name\": function name, \"parameters\": dictionary of argument name and its value. Do not use variables.
4. Reflect: After each step or tool use, reflect on the results:
- If successful, consider if the result fully answers the user's query or if additional steps are needed.
@ -147,12 +159,13 @@ When addressing a query, follow these steps:
6. Conclude: When you believe you have a comprehensive answer to the user's query, provide your final answer.
Always explain your thought process, including your reasoning for each decision and how you arrived at your conclusions. If you're providing a final answer, put your response in tags <answer></answer>.
Always explain your thought process, including your reasoning for each decision and how you arrived at your conclusions. If you're providing a final answer, or need more input from the user, put your response in tags <answer></answer>.
Remember, complex problems often require multiple steps and iterations. Don't hesitate to break down the problem, use tools multiple times, or explore different approaches to arrive at the best solution.
Before approaching a problem, come up with a few ways you might solve it, and then choose the most promising approach. Repeat this on each iteration.
"""
PRIMARY_MODEL = "llama3.1:8b"
PRIMARY_MODEL = "qwen2.5:14b"
UPDATE_INTERVAL = 0.1 # 100ms, configurable

13
next.config.mjs Normal file
View File

@ -0,0 +1,13 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
webpack: (config) => {
config.externals.push({
'utf-8-validate': 'commonjs utf-8-validate',
'bufferutil': 'commonjs bufferutil',
})
return config
},
}
export default nextConfig;

View File

@ -2,10 +2,10 @@ import duckduckgo_search
import requests
from readability.readability import Document
from markdownify import markdownify as md
import sys
import time
import io
import subprocess
import time
import tempfile
class Tool:
def __init__(self, name: str, description: str, arguments: dict, returns: str):
self.name = name
@ -80,33 +80,37 @@ class CalculatorTool(Tool):
super().__init__("calculator", "Perform a calculation", {'type': 'object', 'properties': {'expression': {'type': 'string', 'description': 'The mathematical expression to evaluate, should be a python mathematical expression'}}}, "result:string")
def execute(self, arg: dict) -> str:
try:
return str(exec(arg["expression"]))
except Exception as e:
return f"Error executing code: {str(e)}"
p = PythonCodeTool()
return p.execute({'code': arg['expression']})
class PythonCodeTool(Tool):
def __init__(self):
super().__init__("python_code", "Execute python code", {'type': 'object', 'properties': {'code': {'type': 'string', 'description': 'The python code to execute, should be a single line of valid python'}}}, "result:string")
super().__init__("python_code", "Execute python code",
{'type': 'object', 'properties': {'code': {'type': 'string', 'description': 'The python code to execute, can be multiline'}}},
"result:string")
def execute(self, arg: dict) -> str:
try:
start_time = time.time()
process = subprocess.Popen(['python', '-c', arg['code']],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
stdout, stderr = process.communicate(timeout=10) # 10 second timeout
end_time = time.time()
execution_time = end_time - start_time
result = {
'stdout': stdout,
'stderr': stderr,
'return_value': process.returncode,
'execution_time': execution_time
}
with tempfile.NamedTemporaryFile(suffix=".py", mode="w", delete=False) as temp_file:
temp_file.write(arg['code'])
temp_file.flush()
start_time = time.time()
process = subprocess.Popen(['python', temp_file.name],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
stdout, stderr = process.communicate(timeout=10) # 10 second timeout
end_time = time.time()
execution_time = end_time - start_time
result = {
'stdout': stdout,
'stderr': stderr,
'return_value': process.returncode,
'execution_time': execution_time
}
except subprocess.TimeoutExpired:
process.kill()
return "Error: Code execution timed out after 10 seconds"