commit 3c5117c2c3451168095a38f45fd74f894b1cb1f6 Author: Michael Ikehi Date: Thu Nov 6 11:08:59 2025 +0100 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..941046a --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# Reason Flow - AI Engineering Reasoning System + +An intelligent AI system for complex engineering reasoning with continuous learning capabilities. + +## Architecture + +- **MODEL1**: Engineering reasoning model that creates step-by-step plans +- **QUERYMODEL**: Execution model that uses tools to carry out plans +- **RAG Pipeline**: Document search and retrieval system +- **6 Specialized Tools**: Query expansion, extraction, reports, web search, PDF search +- **Feedback Loop**: Continuous learning system + +## Tech Stack + +- **Backend**: Express.js, PostgreSQL, Sequelize +- **Frontend**: React, Socket.io +- **AI Models**: Kimi K2 (Groq), OpenAI +- **RAG**: LangChain, Vector Database +- **Fine-tuning**: SFT, DPO, Correction Memory + +## Setup Instructions + +1. Install dependencies: +```bash +npm run install-all +``` + +2. Set up environment variables: +```bash +cp env.example .env +# Edit .env with your API keys and database credentials +``` + +3. Set up database: +```bash +# Create PostgreSQL database +createdb reason_flow +``` + +4. Start development servers: +```bash +npm run dev-full +``` + +## Project Structure + +``` +reason_flow/ +├── server/ # Express.js backend +│ ├── controllers/ # Route controllers +│ ├── models/ # Database models +│ ├── routes/ # API routes +│ ├── services/ # Business logic +│ ├── middleware/ # Custom middleware +│ └── utils/ # Utility functions +├── client/ # React frontend +│ ├── src/ +│ │ ├── components/ # React components +│ │ ├── pages/ # Page components +│ │ ├── services/ # API services +│ │ └── utils/ # Utility functions +├── data/ # Training data and documents +├── tests/ # Test files +└── uploads/ # File uploads +``` + +## Features + +- **Intelligent Planning**: AI generates step-by-step engineering plans +- **Tool Execution**: Automated execution using specialized tools +- **Document Search**: RAG-powered document retrieval +- **Continuous Learning**: Feedback loop for model improvement +- **Real-time Chat**: WebSocket-based communication +- **File Upload**: Document processing and indexing + +## API Endpoints + +- `/api/health` - Health check +- `/api/auth/*` - Authentication +- `/api/chat/*` - Chat functionality +- `/api/models/*` - Model management +- `/api/documents/*` - Document management +- `/api/feedback/*` - Feedback collection +- `/api/tools/*` - Tool execution + +## Development + +Run tests: +```bash +npm test +``` + +Build for production: +```bash +npm run build +``` + +## License + +MIT diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..04dde65 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,204 @@ +# Reason Flow Setup Guide + +This guide will help you set up the Reason Flow AI engineering reasoning system. + +## Prerequisites + +- Node.js (v16 or higher) +- PostgreSQL (v12 or higher) +- Git + +## Quick Start + +### 1. Clone and Install Dependencies + +```bash +git clone +cd reason_flow +npm run install-all +``` + +### 2. Environment Configuration + +#### Option A: Interactive Setup (Recommended) +```bash +npm run env:setup +``` + +#### Option B: Manual Setup +```bash +cp env.example .env +# Edit .env with your configuration +``` + +### 3. Database Setup + +```bash +npm run db:setup +``` + +### 4. Validate Configuration + +```bash +npm run config:validate +``` + +### 5. Start Development Server + +```bash +npm run dev +``` + +## Detailed Setup + +### Environment Variables + +The system uses environment variables for configuration. Key variables include: + +#### Required Variables +- `GROQ_API_KEY` - Your Groq API key for Kimi K2 model +- `DB_PASSWORD` - PostgreSQL database password +- `JWT_SECRET` - Secret key for JWT tokens + +#### Optional Variables +- `OPENAI_API_KEY` - For additional AI capabilities +- `SERP_API_KEY` - For web search functionality +- `REDIS_URL` - For caching (optional) + +### Database Configuration + +The system supports multiple environments: + +- **Development**: `reason_flow_dev` +- **Test**: `reason_flow_test` +- **Production**: `reason_flow_prod` + +### API Keys Setup + +1. **Groq API Key**: Get from [Groq Console](https://console.groq.com/) +2. **OpenAI API Key**: Get from [OpenAI Platform](https://platform.openai.com/) +3. **SERP API Key**: Get from [SERP API](https://serpapi.com/) + +## Available Scripts + +### Environment Management +- `npm run env:setup` - Interactive environment setup +- `npm run env:validate` - Validate environment configuration +- `npm run env:show` - Show current configuration + +### Database Management +- `npm run db:setup` - Complete database setup +- `npm run db:init` - Initialize database +- `npm run db:reset` - Reset database +- `npm run db:migrate` - Run migrations +- `npm run db:seed` - Run seeders +- `npm run db:status` - Check database status +- `npm run db:info` - Show database information + +### Development +- `npm run dev` - Start development server +- `npm run dev-full` - Start both backend and frontend +- `npm run test:groq` - Test Groq API integration + +### Configuration +- `npm run config:validate` - Validate all configuration +- `npm run config:show` - Show current configuration + +## Environment-Specific Configuration + +### Development +- Debug mode enabled +- Verbose logging +- Relaxed rate limiting +- Hot reload enabled + +### Production +- Debug mode disabled +- Optimized logging +- Strict rate limiting +- SSL recommended +- Security headers enabled + +### Test +- Minimal logging +- Test database +- Mock external services + +## Troubleshooting + +### Common Issues + +1. **Database Connection Failed** + - Check PostgreSQL is running + - Verify database credentials + - Ensure database exists + +2. **Groq API Errors** + - Verify API key is correct + - Check API quota and limits + - Test connection: `npm run test:groq` + +3. **Configuration Errors** + - Run: `npm run config:validate` + - Check required variables are set + - Verify environment file exists + +### Logs + +- Application logs: `./logs/app.log` +- Error logs: `./logs/error.log` +- Combined logs: `./logs/combined.log` + +### Health Checks + +- API Health: `GET /api/health` +- Model Status: `GET /api/models/status` +- Database Status: `npm run db:status` + +## Security Considerations + +### Production Deployment + +1. **Change Default Secrets** + - Update JWT_SECRET + - Change admin password + - Use strong database passwords + +2. **Enable SSL** + - Set DB_SSL=true + - Use HTTPS in production + - Configure proper CORS origins + +3. **Environment Variables** + - Never commit .env files + - Use secure secret management + - Rotate keys regularly + +## Monitoring + +### Health Endpoints +- `/api/health` - Basic health check +- `/api/models/status` - Model status +- `/api/feedback/stats` - Feedback statistics + +### Logging +- Structured JSON logging +- Log rotation configured +- Error tracking enabled + +## Support + +For issues and questions: +1. Check the logs for errors +2. Validate configuration +3. Test individual components +4. Review the troubleshooting section + +## Next Steps + +After setup: +1. Test the API endpoints +2. Upload sample documents +3. Create test conversations +4. Verify model responses +5. Set up monitoring diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..e9b1f62 --- /dev/null +++ b/client/package.json @@ -0,0 +1,51 @@ +{ + "name": "reason-flow-client", + "version": "1.0.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^14.5.1", + "axios": "^1.6.2", + "date-fns": "^2.30.0", + "framer-motion": "^10.16.16", + "lucide-react": "^0.294.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", + "react-hook-form": "^7.48.2", + "react-markdown": "^9.0.1", + "react-query": "^3.39.3", + "react-router-dom": "^6.8.1", + "react-scripts": "^5.0.1", + "react-syntax-highlighter": "^5.8.0", + "react-toastify": "^9.1.3", + "socket.io-client": "^4.7.4", + "styled-components": "^6.1.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "proxy": "http://localhost:8000" +} diff --git a/client/public/index.html b/client/public/index.html new file mode 100644 index 0000000..a9977fd --- /dev/null +++ b/client/public/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + + Reason Flow - Engineering AI + + + +
+ + diff --git a/client/public/manifest.json b/client/public/manifest.json new file mode 100644 index 0000000..b6e972c --- /dev/null +++ b/client/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Reason Flow", + "name": "Reason Flow - Engineering AI", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#3b82f6", + "background_color": "#ffffff" +} diff --git a/client/src/App.jsx b/client/src/App.jsx new file mode 100644 index 0000000..b73ae72 --- /dev/null +++ b/client/src/App.jsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { Routes, Route, Navigate } from 'react-router-dom'; +import { AuthProvider, useAuth } from './contexts/AuthContext'; +import LoginPage from './pages/LoginPage'; +import Dashboard from './pages/Dashboard'; +import ChatPage from './pages/ChatPage'; +import DocumentsPage from './pages/DocumentsPage'; +import ToolsPage from './pages/ToolsPage'; +import AdminPage from './pages/AdminPage'; +import Layout from './components/Layout'; + +function ProtectedRoute({ children }) { + const { user, loading } = useAuth(); + + if (loading) { + return ( +
+
+

Loading...

+
+ ); + } + + if (!user) { + return ; + } + + return {children}; +} + +function App() { + return ( + + + } /> + + + + } /> + + + + } /> + + + + } /> + + + + } /> + + + + } /> + } /> + } /> + + + ); +} + +export default App; diff --git a/client/src/components/Layout.css b/client/src/components/Layout.css new file mode 100644 index 0000000..69a5d9d --- /dev/null +++ b/client/src/components/Layout.css @@ -0,0 +1,283 @@ +/* Layout Styles */ +.layout { + display: flex; + min-height: 100vh; + background: #f8fafc; +} + +/* Sidebar */ +.sidebar { + width: 280px; + background: linear-gradient(180deg, #1e293b 0%, #0f172a 100%); + color: white; + display: flex; + flex-direction: column; + position: fixed; + height: 100vh; + left: -280px; + transition: left 0.3s ease; + z-index: 1000; + box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1); +} + +.sidebar.open { + left: 0; +} + +.sidebar-header { + padding: 1.5rem; + border-bottom: 1px solid #334155; + display: flex; + align-items: center; + justify-content: space-between; +} + +.logo { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.logo-icon { + font-size: 1.5rem; +} + +.logo-text { + font-size: 1.25rem; + font-weight: 700; + background: linear-gradient(135deg, #60a5fa, #a78bfa); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.sidebar-toggle { + background: none; + border: none; + color: #94a3b8; + font-size: 1.25rem; + cursor: pointer; + padding: 0.5rem; + border-radius: 0.375rem; + transition: all 0.2s; +} + +.sidebar-toggle:hover { + background: #334155; + color: white; +} + +.sidebar-nav { + flex: 1; + padding: 1rem 0; +} + +.nav-item { + width: 100%; + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1.5rem; + background: none; + border: none; + color: #cbd5e1; + text-align: left; + cursor: pointer; + transition: all 0.2s; + font-size: 0.95rem; +} + +.nav-item:hover { + background: #334155; + color: white; +} + +.nav-item.active { + background: linear-gradient(135deg, #3b82f6, #8b5cf6); + color: white; + box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3); +} + +.nav-icon { + font-size: 1.125rem; + width: 1.5rem; + text-align: center; +} + +.sidebar-footer { + padding: 1.5rem; + border-top: 1px solid #334155; +} + +.user-info { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 1rem; +} + +.user-avatar { + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + background: linear-gradient(135deg, #3b82f6, #8b5cf6); + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 0.875rem; +} + +.user-name { + font-weight: 600; + font-size: 0.875rem; +} + +.user-role { + font-size: 0.75rem; + color: #94a3b8; + text-transform: capitalize; +} + +.logout-btn { + width: 100%; + padding: 0.75rem; + background: #dc2626; + color: white; + border: none; + border-radius: 0.5rem; + cursor: pointer; + font-weight: 500; + transition: all 0.2s; +} + +.logout-btn:hover { + background: #b91c1c; + transform: translateY(-1px); +} + +/* Main Content */ +.main-content { + flex: 1; + margin-left: 0; + display: flex; + flex-direction: column; + min-height: 100vh; +} + +/* Top Bar */ +.topbar { + background: white; + border-bottom: 1px solid #e2e8f0; + padding: 1rem 1.5rem; + display: flex; + align-items: center; + justify-content: space-between; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.menu-toggle { + display: none; + background: none; + border: none; + font-size: 1.25rem; + cursor: pointer; + padding: 0.5rem; + border-radius: 0.375rem; + transition: all 0.2s; +} + +.menu-toggle:hover { + background: #f1f5f9; +} + +.topbar-title { + font-size: 1.5rem; + font-weight: 700; + color: #1e293b; +} + +.topbar-actions { + display: flex; + align-items: center; + gap: 1rem; +} + +.status-indicator { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + color: #64748b; +} + +.status-dot { + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + background: #10b981; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +/* Page Content */ +.page-content { + flex: 1; + padding: 2rem; + max-width: 100%; + overflow-x: auto; +} + +/* Mobile Overlay */ +.mobile-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 999; + display: none; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .menu-toggle { + display: block; + } + + .mobile-overlay { + display: block; + } + + .page-content { + padding: 1rem; + } + + .topbar-title { + font-size: 1.25rem; + } +} + +@media (min-width: 769px) { + .sidebar { + position: static; + left: 0; + } + + .main-content { + margin-left: 0; + } + + .menu-toggle { + display: none; + } + + .mobile-overlay { + display: none; + } +} diff --git a/client/src/components/Layout.jsx b/client/src/components/Layout.jsx new file mode 100644 index 0000000..322cc0f --- /dev/null +++ b/client/src/components/Layout.jsx @@ -0,0 +1,114 @@ +import React, { useState } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { useAuth } from '../contexts/AuthContext'; +import './Layout.css'; + +const Layout = ({ children }) => { + const { user, logout } = useAuth(); + const navigate = useNavigate(); + const location = useLocation(); + const [sidebarOpen, setSidebarOpen] = useState(false); + + const handleLogout = () => { + logout(); + navigate('/login'); + }; + + const navigation = [ + { path: '/dashboard', label: 'Dashboard', icon: '🏠' }, + { path: '/chat', label: 'Engineering Chat', icon: '💬' }, + { path: '/documents', label: 'Knowledge Base', icon: '📚' }, + { path: '/tools', label: 'Tool Execution', icon: '🔧' }, + ...(user?.role === 'admin' ? [{ path: '/admin', label: 'Admin Panel', icon: '⚙️' }] : []) + ]; + + const isActive = (path) => location.pathname === path; + + return ( +
+ {/* Sidebar */} + + + {/* Main Content */} +
+ {/* Top Bar */} +
+ +
+ {navigation.find(item => item.path === location.pathname)?.label || 'Reason Flow'} +
+
+
+ + System Online +
+
+
+ + {/* Page Content */} +
+ {children} +
+
+ + {/* Mobile Overlay */} + {sidebarOpen && ( +
setSidebarOpen(false)} + /> + )} +
+ ); +}; + +export default Layout; diff --git a/client/src/contexts/AuthContext.jsx b/client/src/contexts/AuthContext.jsx new file mode 100644 index 0000000..6df384f --- /dev/null +++ b/client/src/contexts/AuthContext.jsx @@ -0,0 +1,94 @@ +import React, { createContext, useContext, useState, useEffect } from 'react'; +import axios from 'axios'; + +const AuthContext = createContext(); + +export const useAuth = () => { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +}; + +export const AuthProvider = ({ children }) => { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const token = localStorage.getItem('token'); + if (token) { + axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; + // Verify token and get user info + axios.get('/api/auth/profile') + .then(response => { + setUser(response.data.data.user); + }) + .catch(() => { + localStorage.removeItem('token'); + delete axios.defaults.headers.common['Authorization']; + }) + .finally(() => { + setLoading(false); + }); + } else { + setLoading(false); + } + }, []); + + const login = async (email, password) => { + try { + const response = await axios.post('/api/auth/login', { email, password }); + const { token, user } = response.data.data; + + localStorage.setItem('token', token); + axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; + setUser(user); + + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data?.error || 'Login failed' + }; + } + }; + + const register = async (userData) => { + try { + const response = await axios.post('/api/auth/register', userData); + const { token, user } = response.data.data; + + localStorage.setItem('token', token); + axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; + setUser(user); + + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data?.error || 'Registration failed' + }; + } + }; + + const logout = () => { + localStorage.removeItem('token'); + delete axios.defaults.headers.common['Authorization']; + setUser(null); + }; + + const value = { + user, + login, + register, + logout, + loading + }; + + return ( + + {children} + + ); +}; diff --git a/client/src/index.css b/client/src/index.css new file mode 100644 index 0000000..788d2b0 --- /dev/null +++ b/client/src/index.css @@ -0,0 +1,80 @@ +/* Global Styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background-color: #f8fafc; + color: #1e293b; + line-height: 1.6; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + +/* Reset and base styles */ +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + line-height: 1.25; +} + +button { + font-family: inherit; + cursor: pointer; +} + +input, textarea, select { + font-family: inherit; +} + +/* Utility classes */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* Focus styles */ +*:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; +} + +/* Scrollbar styles */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f5f9; +} + +::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} + +/* Animations */ +@keyframes spin { + to { transform: rotate(360deg); } +} diff --git a/client/src/index.js b/client/src/index.js new file mode 100644 index 0000000..3cecec4 --- /dev/null +++ b/client/src/index.js @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App'; +import './index.css'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + + + +); \ No newline at end of file diff --git a/client/src/pages/AdminPage.css b/client/src/pages/AdminPage.css new file mode 100644 index 0000000..b082571 --- /dev/null +++ b/client/src/pages/AdminPage.css @@ -0,0 +1,515 @@ +/* Admin Page Styles */ +.admin-page { + max-width: 1200px; + margin: 0 auto; + padding: 0; +} + +.page-header { + text-align: center; + margin-bottom: 2rem; +} + +.page-header h1 { + font-size: 2.5rem; + font-weight: 700; + background: linear-gradient(135deg, #1e293b, #3b82f6); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin: 0 0 0.5rem 0; +} + +.page-header p { + color: #64748b; + font-size: 1.125rem; + margin: 0; +} + +/* Message Styles */ +.message { + padding: 1rem; + border-radius: 0.75rem; + margin-bottom: 1.5rem; + font-weight: 500; +} + +.message.success { + background: #d1fae5; + border: 1px solid #a7f3d0; + color: #065f46; +} + +.message.error { + background: #fee2e2; + border: 1px solid #fecaca; + color: #991b1b; +} + +/* Admin Section */ +.admin-section { + background: white; + border-radius: 1rem; + padding: 2rem; + margin-bottom: 2rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + border: 1px solid #e2e8f0; +} + +.section-header { + margin-bottom: 1.5rem; +} + +.section-header h2 { + font-size: 1.5rem; + font-weight: 600; + color: #1e293b; + margin: 0 0 0.5rem 0; +} + +.section-header p { + color: #64748b; + margin: 0; +} + +/* Stats Grid */ +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1.5rem; +} + +.stat-card { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 0.75rem; + padding: 1.5rem; + display: flex; + align-items: center; + gap: 1rem; + transition: all 0.2s; +} + +.stat-card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); +} + +.stat-icon { + font-size: 2rem; + width: 3rem; + height: 3rem; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #f1f5f9, #e2e8f0); + border-radius: 0.75rem; +} + +.stat-content { + flex: 1; +} + +.stat-number { + font-size: 2rem; + font-weight: 700; + color: #1e293b; + line-height: 1; +} + +.stat-label { + color: #64748b; + font-size: 0.875rem; + margin-top: 0.25rem; +} + +/* Model Status */ +.model-status-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1.5rem; +} + +.model-card { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 0.75rem; + padding: 1.5rem; + transition: all 0.2s; +} + +.model-card:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transform: translateY(-1px); +} + +.model-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.75rem; +} + +.model-header h3 { + margin: 0; + color: #1e293b; + font-size: 1.125rem; + font-weight: 600; +} + +.status-indicator { + padding: 0.25rem 0.75rem; + border-radius: 0.375rem; + font-size: 0.75rem; + font-weight: 500; + text-transform: capitalize; +} + +.status-indicator.online { + background: #d1fae5; + color: #065f46; +} + +.status-indicator.offline { + background: #fee2e2; + color: #991b1b; +} + +.status-indicator.unknown { + background: #f3f4f6; + color: #6b7280; +} + +.model-card p { + color: #64748b; + font-size: 0.875rem; + margin: 0 0 1rem 0; +} + +.model-metrics { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.metric { + display: flex; + justify-content: space-between; + font-size: 0.875rem; +} + +.metric-label { + color: #64748b; + font-weight: 500; +} + +.metric-value { + color: #1e293b; + font-weight: 600; +} + +/* Feedback Management */ +.feedback-overview { + margin-bottom: 2rem; +} + +.feedback-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 1rem; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 0.75rem; + padding: 1.5rem; +} + +.stat-item { + display: flex; + flex-direction: column; + gap: 0.25rem; + text-align: center; +} + +.stat-label { + color: #64748b; + font-size: 0.75rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.stat-value { + font-size: 1.5rem; + font-weight: 700; + color: #1e293b; +} + +.stat-value.positive { + color: #10b981; +} + +.stat-value.negative { + color: #ef4444; +} + +.stat-value.correction { + color: #f59e0b; +} + +.stat-value.suggestion { + color: #8b5cf6; +} + +/* Recent Feedback */ +.recent-feedback h3 { + margin: 0 0 1rem 0; + color: #1e293b; + font-size: 1.125rem; + font-weight: 600; +} + +.feedback-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.feedback-item { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 0.75rem; + padding: 1.5rem; + transition: all 0.2s; +} + +.feedback-item:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transform: translateY(-1px); +} + +.feedback-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.feedback-type { + display: flex; + align-items: center; + gap: 1rem; +} + +.type-badge { + padding: 0.25rem 0.75rem; + border-radius: 0.375rem; + font-size: 0.75rem; + font-weight: 500; + text-transform: capitalize; +} + +.type-badge.positive { + background: #d1fae5; + color: #065f46; +} + +.type-badge.negative { + background: #fee2e2; + color: #991b1b; +} + +.type-badge.correction { + background: #fef3c7; + color: #92400e; +} + +.type-badge.suggestion { + background: #e9d5ff; + color: #7c3aed; +} + +.feedback-rating { + color: #64748b; + font-size: 0.875rem; +} + +.feedback-actions { + display: flex; + gap: 0.5rem; +} + +.process-btn { + background: #3b82f6; + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 0.375rem; + font-size: 0.75rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.process-btn:hover:not(:disabled) { + background: #2563eb; + transform: translateY(-1px); +} + +.process-btn:disabled { + background: #10b981; + cursor: not-allowed; + opacity: 0.8; +} + +.feedback-comment, +.feedback-correction { + background: white; + border: 1px solid #e2e8f0; + border-radius: 0.5rem; + padding: 0.75rem; + margin-bottom: 0.75rem; + font-size: 0.875rem; + line-height: 1.5; +} + +.feedback-meta { + display: flex; + gap: 1rem; + font-size: 0.75rem; + color: #64748b; + padding-top: 0.75rem; + border-top: 1px solid #e2e8f0; +} + +/* System Actions */ +.system-actions { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; +} + +.action-group h3 { + margin: 0 0 1rem 0; + color: #1e293b; + font-size: 1.125rem; + font-weight: 600; +} + +.action-buttons { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.action-btn { + padding: 0.75rem 1rem; + border: none; + border-radius: 0.5rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + text-align: left; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.action-btn.primary { + background: linear-gradient(135deg, #3b82f6, #8b5cf6); + color: white; +} + +.action-btn.primary:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); +} + +.action-btn.secondary { + background: #f1f5f9; + color: #64748b; + border: 1px solid #e2e8f0; +} + +.action-btn.secondary:hover { + background: #e2e8f0; + transform: translateY(-1px); +} + +/* Loading State */ +.loading-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 4rem; + color: #64748b; +} + +.loading-spinner { + width: 2rem; + height: 2rem; + border: 3px solid #e2e8f0; + border-top: 3px solid #3b82f6; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 1rem; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Responsive Design */ +@media (max-width: 768px) { + .stats-grid { + grid-template-columns: repeat(2, 1fr); + } + + .model-status-grid { + grid-template-columns: 1fr; + } + + .feedback-stats { + grid-template-columns: repeat(2, 1fr); + } + + .system-actions { + grid-template-columns: 1fr; + } + + .feedback-header { + flex-direction: column; + gap: 1rem; + align-items: flex-start; + } + + .feedback-actions { + align-self: flex-end; + } +} + +@media (max-width: 480px) { + .stats-grid { + grid-template-columns: 1fr; + } + + .feedback-stats { + grid-template-columns: 1fr; + } + + .page-header h1 { + font-size: 2rem; + } + + .page-header p { + font-size: 1rem; + } + + .section-header h2 { + font-size: 1.25rem; + } + + .admin-section { + padding: 1.5rem; + } +} diff --git a/client/src/pages/AdminPage.jsx b/client/src/pages/AdminPage.jsx new file mode 100644 index 0000000..eef1177 --- /dev/null +++ b/client/src/pages/AdminPage.jsx @@ -0,0 +1,338 @@ +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import './AdminPage.css'; + +const AdminPage = () => { + const [systemStats, setSystemStats] = useState({ + users: 0, + conversations: 0, + documents: 0, + feedback: 0, + toolExecutions: 0 + }); + const [modelStatus, setModelStatus] = useState({}); + const [feedbackStats, setFeedbackStats] = useState({}); + const [recentFeedback, setRecentFeedback] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + loadAdminData(); + }, []); + + const loadAdminData = async () => { + try { + setLoading(true); + + // Load system statistics + const [conversationsRes, documentsRes, feedbackRes, toolsRes, modelRes] = await Promise.all([ + axios.get('/api/chat/conversations?limit=1'), + axios.get('/api/documents?limit=1'), + axios.get('/api/feedback/stats'), + axios.get('/api/tools/executions?limit=1'), + axios.get('/api/models/status') + ]); + + setSystemStats({ + users: 0, // Would need a users endpoint + conversations: conversationsRes.data.data.pagination?.total || 0, + documents: documentsRes.data.data.pagination?.total || documentsRes.data.data.documents?.length || 0, + feedback: feedbackRes.data.data.total || 0, + toolExecutions: toolsRes.data.data.pagination?.total || toolsRes.data.data.executions?.length || 0 + }); + + setModelStatus(modelRes.data.data); + setFeedbackStats(feedbackRes.data.data); + + // Load recent feedback + const feedbackListRes = await axios.get('/api/feedback?limit=5'); + setRecentFeedback(feedbackListRes.data.data.feedback || []); + + } catch (error) { + console.error('Error loading admin data:', error); + } finally { + setLoading(false); + } + }; + + const processFeedback = async (feedbackId) => { + try { + await axios.put(`/api/feedback/${feedbackId}/process`); + setMessage('✅ Feedback processed successfully'); + loadAdminData(); + } catch (error) { + console.error('Error processing feedback:', error); + setMessage(`❌ Failed to process feedback: ${error.response?.data?.error || error.message}`); + } + }; + + const [message, setMessage] = useState(''); + + if (loading) { + return ( +
+
+
+

Loading admin dashboard...

+
+
+ ); + } + + return ( +
+
+

⚙️ Admin Panel

+

System administration and monitoring

+
+ + {message && ( +
+ {message} +
+ )} + + {/* System Overview */} +
+
+

📊 System Overview

+

Key system metrics and statistics

+
+ +
+
+
👥
+
+
{systemStats.users}
+
Total Users
+
+
+
+
💬
+
+
{systemStats.conversations}
+
Conversations
+
+
+
+
📚
+
+
{systemStats.documents}
+
Documents
+
+
+
+
+
+
{systemStats.feedback}
+
Feedback Items
+
+
+
+
🔧
+
+
{systemStats.toolExecutions}
+
Tool Executions
+
+
+
+
+ + {/* Model Status */} +
+
+

🤖 Model Status

+

AI model health and performance

+
+ +
+
+
+

🧠 MODEL1 (Planner)

+ + {modelStatus.model1 || 'Unknown'} + +
+

Engineering plan generation and reasoning

+
+
+ Status: + {modelStatus.model1 || 'Unknown'} +
+
+
+ +
+
+

⚡ QUERYMODEL (Executor)

+ + {modelStatus.queryModel || 'Unknown'} + +
+

Plan execution and tool orchestration

+
+
+ Status: + {modelStatus.queryModel || 'Unknown'} +
+
+
+ +
+
+

🔍 RAG System

+ + {modelStatus.rag || 'Unknown'} + +
+

Document search and knowledge retrieval

+
+
+ Status: + {modelStatus.rag || 'Unknown'} +
+
+
+
+
+ + {/* Feedback Management */} +
+
+

💬 Feedback Management

+

User feedback and system improvements

+
+ +
+
+
+ Total Feedback: + {feedbackStats.total || 0} +
+
+ Positive: + {feedbackStats.positive || 0} +
+
+ Negative: + {feedbackStats.negative || 0} +
+
+ Corrections: + {feedbackStats.correction || 0} +
+
+ Suggestions: + {feedbackStats.suggestion || 0} +
+
+
+ + {recentFeedback.length > 0 && ( +
+

Recent Feedback

+
+ {recentFeedback.map((feedback) => ( +
+
+
+ + {feedback.feedback_type} + + + {feedback.rating ? `⭐ ${feedback.rating}` : 'No rating'} + +
+
+ +
+
+ + {feedback.comment && ( +
+ Comment: {feedback.comment} +
+ )} + + {feedback.corrected_content && ( +
+ Correction: {feedback.corrected_content} +
+ )} + +
+ ID: {feedback.id} + Date: {new Date(feedback.created_at).toLocaleDateString()} + Status: {feedback.is_processed ? 'Processed' : 'Pending'} +
+
+ ))} +
+
+ )} +
+ + {/* System Actions */} +
+
+

🛠️ System Actions

+

Administrative operations and maintenance

+
+ +
+
+

Data Management

+
+ + + +
+
+ +
+

Model Management

+
+ + + +
+
+ +
+

System Maintenance

+
+ + + +
+
+
+
+
+ ); +}; + +export default AdminPage; diff --git a/client/src/pages/ChatPage.css b/client/src/pages/ChatPage.css new file mode 100644 index 0000000..48bac02 --- /dev/null +++ b/client/src/pages/ChatPage.css @@ -0,0 +1,631 @@ +/* Chat Page Styles */ +.chat-page { + height: 100vh; + display: flex; + flex-direction: column; + background: #f8fafc; +} + +.chat-container { + flex: 1; + display: flex; + flex-direction: column; + max-width: 1000px; + margin: 0 auto; + background: white; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); +} + +/* Chat Header */ +.chat-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; + background: linear-gradient(135deg, #1e293b, #334155); + color: white; + display: flex; + justify-content: space-between; + align-items: center; +} + +.chat-title h2 { + margin: 0 0 0.25rem 0; + font-size: 1.5rem; + font-weight: 700; +} + +.chat-title p { + margin: 0; + opacity: 0.8; + font-size: 0.875rem; +} + +.chat-actions { + display: flex; + gap: 0.75rem; +} + +.action-btn { + padding: 0.5rem 1rem; + border: 1px solid rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.1); + color: white; + border-radius: 0.5rem; + cursor: pointer; + font-size: 0.875rem; + transition: all 0.2s; +} + +.action-btn:hover { + background: rgba(255, 255, 255, 0.2); + transform: translateY(-1px); +} + +/* Pending Plans */ +.pending-plans { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; + background: #f8fafc; +} + +.pending-plans h3 { + margin: 0 0 1rem 0; + color: #1e293b; + font-size: 1.125rem; +} + +.plans-grid { + display: grid; + gap: 1rem; +} + +.plan-card { + background: white; + border: 1px solid #e2e8f0; + border-radius: 0.75rem; + padding: 1rem; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); +} + +.plan-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.75rem; +} + +.plan-header h4 { + margin: 0; + color: #1e293b; + font-size: 1rem; +} + +.plan-status { + background: #fbbf24; + color: #92400e; + padding: 0.25rem 0.5rem; + border-radius: 0.375rem; + font-size: 0.75rem; + font-weight: 500; + text-transform: capitalize; +} + +.plan-content { + margin-bottom: 1rem; +} + +.plan-content pre { + background: #f1f5f9; + padding: 0.75rem; + border-radius: 0.5rem; + font-size: 0.875rem; + color: #475569; + white-space: pre-wrap; + margin: 0; + max-height: 200px; + overflow-y: auto; +} + +.plan-actions { + display: flex; + gap: 0.5rem; +} + +.plan-actions .btn { + padding: 0.5rem 1rem; + border: none; + border-radius: 0.375rem; + cursor: pointer; + font-size: 0.875rem; + font-weight: 500; + transition: all 0.2s; +} + +.plan-actions .btn.approve { + background: #10b981; + color: white; +} + +.plan-actions .btn.reject { + background: #ef4444; + color: white; +} + +.plan-actions .btn.execute { + background: #3b82f6; + color: white; +} + +.plan-actions .btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +/* Messages Container */ +.messages-container { + flex: 1; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.messages { + flex: 1; + overflow-y: auto; + padding: 1rem; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.message { + display: flex; + gap: 0.75rem; + max-width: 80%; +} + +.message.user { + align-self: flex-end; + flex-direction: row-reverse; +} + +.message.assistant { + align-self: flex-start; +} + +.message-avatar { + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.125rem; + flex-shrink: 0; +} + +.message.user .message-avatar { + background: linear-gradient(135deg, #3b82f6, #8b5cf6); + color: white; +} + +.message.assistant .message-avatar { + background: linear-gradient(135deg, #10b981, #059669); + color: white; +} + +.message-content { + flex: 1; + min-width: 0; +} + +.message-header { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.25rem; +} + +.message-role { + font-weight: 600; + color: #1e293b; + font-size: 0.875rem; +} + +.message-time { + color: #64748b; + font-size: 0.75rem; +} + +.feedback-btn { + background: #f59e0b; + color: white; + border: none; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.75rem; + cursor: pointer; + transition: all 0.2s; +} + +.feedback-btn:hover { + background: #d97706; + transform: translateY(-1px); +} + +.message-text { + background: #f8fafc; + padding: 0.75rem 1rem; + border-radius: 1rem; + border: 1px solid #e2e8f0; +} + +.message.user .message-text { + background: linear-gradient(135deg, #3b82f6, #8b5cf6); + color: white; + border-color: transparent; +} + +.message-text pre { + margin: 0; + white-space: pre-wrap; + font-family: inherit; + font-size: 0.875rem; + line-height: 1.5; +} + +/* Message type specific styles */ +.message[data-message-type="system_prompt"] .message-content { + background: #f0f9ff; + border-left: 4px solid #0ea5e9; +} + +.message[data-message-type="system_prompt"] .message-role { + color: #0369a1; + font-weight: 600; +} + +.message[data-message-type="plan"] .message-content { + background: #fefce8; + border-left: 4px solid #eab308; +} + +.message[data-message-type="plan"] .message-role { + color: #a16207; + font-weight: 600; +} + +.message[data-message-type="execution_result"] .message-content { + background: #f0fdf4; + border-left: 4px solid #22c55e; +} + +.message[data-message-type="execution_result"] .message-role { + color: #15803d; + font-weight: 600; +} + +.message[data-message-type="error"] .message-content { + background: #fef2f2; + border-left: 4px solid #ef4444; +} + +.message[data-message-type="error"] .message-role { + color: #dc2626; + font-weight: 600; +} + +/* Plan Action Buttons */ +.plan-actions { + display: flex; + gap: 0.75rem; + margin-top: 1rem; + padding: 1rem; + background: #f8fafc; + border-radius: 0.5rem; + border: 1px solid #e2e8f0; +} + +.action-btn { + flex: 1; + padding: 0.75rem 1rem; + border: none; + border-radius: 0.5rem; + font-weight: 600; + font-size: 0.875rem; + cursor: pointer; + transition: all 0.2s; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.action-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.approve-btn { + background: linear-gradient(135deg, #10b981, #059669); + color: white; +} + +.approve-btn:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3); +} + +.reject-btn { + background: linear-gradient(135deg, #ef4444, #dc2626); + color: white; +} + +.reject-btn:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3); +} + +.modify-btn { + background: linear-gradient(135deg, #f59e0b, #d97706); + color: white; +} + +.modify-btn:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3); +} + +/* Typing Indicator */ +.typing-indicator { + display: flex; + gap: 0.25rem; + padding: 0.75rem 1rem; +} + +.typing-indicator span { + width: 0.5rem; + height: 0.5rem; + background: #64748b; + border-radius: 50%; + animation: typing 1.4s infinite ease-in-out; +} + +.typing-indicator span:nth-child(1) { animation-delay: -0.32s; } +.typing-indicator span:nth-child(2) { animation-delay: -0.16s; } + +@keyframes typing { + 0%, 80%, 100% { transform: scale(0.8); opacity: 0.5; } + 40% { transform: scale(1); opacity: 1; } +} + +/* Chat Input */ +.chat-input { + padding: 1rem; + border-top: 1px solid #e2e8f0; + background: white; +} + +.input-container { + display: flex; + gap: 0.75rem; + align-items: flex-end; +} + +.input-container textarea { + flex: 1; + padding: 0.75rem 1rem; + border: 1px solid #d1d5db; + border-radius: 0.75rem; + resize: none; + font-family: inherit; + font-size: 0.875rem; + line-height: 1.5; + transition: all 0.2s; + background: #f8fafc; +} + +.input-container textarea:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + background: white; +} + +.send-btn { + padding: 0.75rem 1.5rem; + background: linear-gradient(135deg, #3b82f6, #8b5cf6); + color: white; + border: none; + border-radius: 0.75rem; + cursor: pointer; + font-weight: 600; + transition: all 0.2s; + white-space: nowrap; +} + +.send-btn:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); +} + +.send-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; +} + +/* Modal */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal { + background: white; + border-radius: 1rem; + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25); + max-width: 500px; + width: 90%; + max-height: 80vh; + overflow: hidden; +} + +.modal-header { + padding: 1.5rem; + border-bottom: 1px solid #e2e8f0; + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal-header h3 { + margin: 0; + color: #1e293b; + font-size: 1.25rem; +} + +.close-btn { + background: none; + border: none; + font-size: 1.25rem; + cursor: pointer; + color: #64748b; + padding: 0.25rem; + border-radius: 0.25rem; + transition: all 0.2s; +} + +.close-btn:hover { + background: #f1f5f9; + color: #1e293b; +} + +.modal-content { + padding: 1.5rem; + max-height: 60vh; + overflow-y: auto; +} + +.form-group { + margin-bottom: 1rem; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: #374151; + font-size: 0.875rem; +} + +.form-group input, +.form-group select, +.form-group textarea { + width: 100%; + padding: 0.75rem; + border: 1px solid #d1d5db; + border-radius: 0.5rem; + font-size: 0.875rem; + transition: all 0.2s; + background: #fafafa; +} + +.form-group input:focus, +.form-group select:focus, +.form-group textarea:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + background: white; +} + +.modal-actions { + display: flex; + gap: 0.75rem; + justify-content: flex-end; + margin-top: 1.5rem; +} + +.modal-actions .btn { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 0.5rem; + cursor: pointer; + font-weight: 500; + transition: all 0.2s; +} + +.modal-actions .btn.secondary { + background: #f1f5f9; + color: #64748b; +} + +.modal-actions .btn.primary { + background: linear-gradient(135deg, #3b82f6, #8b5cf6); + color: white; +} + +.modal-actions .btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +/* Responsive Design */ +@media (max-width: 768px) { + .chat-container { + height: 100vh; + } + + .message { + max-width: 95%; + } + + .input-container { + flex-direction: column; + align-items: stretch; + } + + .send-btn { + align-self: flex-end; + width: auto; + } + + .plan-actions { + flex-wrap: wrap; + } + + .plan-actions .btn { + flex: 1; + min-width: 80px; + } +} + +@media (max-width: 480px) { + .chat-header { + padding: 1rem; + } + + .chat-title h2 { + font-size: 1.25rem; + } + + .chat-title p { + font-size: 0.75rem; + } + + .messages { + padding: 0.75rem; + } + + .chat-input { + padding: 0.75rem; + } +} diff --git a/client/src/pages/ChatPage.jsx b/client/src/pages/ChatPage.jsx new file mode 100644 index 0000000..a305ffe --- /dev/null +++ b/client/src/pages/ChatPage.jsx @@ -0,0 +1,571 @@ +import React, { useState, useEffect, useRef } from 'react'; +import axios from 'axios'; +import './ChatPage.css'; + +const ChatPage = () => { + const [conversation, setConversation] = useState(null); + const [messages, setMessages] = useState([]); + const [inputMessage, setInputMessage] = useState(''); + const [loading, setLoading] = useState(false); + const [pendingPlans, setPendingPlans] = useState([]); + const [showFeedback, setShowFeedback] = useState(null); + const [feedbackData, setFeedbackData] = useState({ + feedbackType: 'positive', + rating: 5, + comment: '', + correctedContent: '' + }); + + const messagesEndRef = useRef(null); + const inputRef = useRef(null); + + useEffect(() => { + createConversation(); + }, []); + + useEffect(() => { + if (conversation) { + loadConversationMessages(); + } + }, [conversation]); + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + const createConversation = async () => { + try { + const response = await axios.post('/api/chat/conversations', { + title: 'New Engineering Discussion' + }); + setConversation(response.data.data.conversation); + } catch (error) { + console.error('Error creating conversation:', error); + } + }; + + const loadConversationMessages = async () => { + if (!conversation) return; + + try { + const response = await axios.get(`/api/chat/conversations/${conversation.id}`); + const conv = response.data.data.conversation; + + // Load all messages from the conversation + const conversationMessages = conv.messages || []; + setMessages(conversationMessages.map(msg => ({ + role: msg.role, + content: msg.content, + timestamp: msg.created_at, + message_type: msg.message_type, + id: msg.id + }))); + + // Load pending plans + loadPendingPlans(); + } catch (error) { + console.error('Error loading conversation messages:', error); + } + }; + + const sendMessage = async () => { + if (!inputMessage.trim() || !conversation) return; + + const userMessage = { + role: 'user', + content: inputMessage, + timestamp: new Date().toISOString() + }; + + setMessages(prev => [...prev, userMessage]); + const currentInput = inputMessage.trim().toLowerCase(); + setInputMessage(''); + setLoading(true); + + // Do not locally infer approve/reject. Always send message to backend and let routing/LLM decide. + + try { + const response = await axios.post('/api/chat/message', { + conversationId: conversation.id, + content: inputMessage + }); + + const assistantMessage = response.data.data.assistantMessage; + setMessages(prev => [...prev, assistantMessage]); + + // Check if this is a plan message and show approval prompt + if (assistantMessage.message_type === 'plan') { + loadPendingPlans(); + + // Add a system message prompting for approval + setTimeout(() => { + setMessages(prev => [...prev, { + role: 'assistant', + content: `📋 **Engineering Plan Generated!** Please review the comprehensive plan above and choose your action:`, + timestamp: new Date().toISOString(), + message_type: 'system_prompt', + plan_id: assistantMessage.plan_id // Store the plan ID for the buttons + }]); + }, 1000); + } + } catch (error) { + console.error('Error sending message:', error); + setMessages(prev => [...prev, { + role: 'assistant', + content: 'Sorry, I encountered an error. Please try again.', + timestamp: new Date().toISOString() + }]); + } finally { + setLoading(false); + } + }; + + const handlePlanApproval = async () => { + if (pendingPlans.length === 0) { + setMessages(prev => [...prev, { + role: 'assistant', + content: '❌ No pending plans to approve. Please ask an engineering question first.', + timestamp: new Date().toISOString(), + message_type: 'error' + }]); + return; + } + + const latestPlan = pendingPlans[0]; + await approvePlan(latestPlan.id); + }; + + const handlePlanRejection = async () => { + if (pendingPlans.length === 0) { + setMessages(prev => [...prev, { + role: 'assistant', + content: '❌ No pending plans to reject. Please ask an engineering question first.', + timestamp: new Date().toISOString(), + message_type: 'error' + }]); + return; + } + + const latestPlan = pendingPlans[0]; + await rejectPlan(latestPlan.id); + + // Ask for a new plan + setTimeout(() => { + setMessages(prev => [...prev, { + role: 'assistant', + content: '🔄 **Plan Rejected!** Please provide more details or ask for a different approach to your engineering question.', + timestamp: new Date().toISOString(), + message_type: 'system_prompt' + }]); + }, 1000); + }; + + const loadPendingPlans = async () => { + if (!conversation) return; + + try { + const response = await axios.get(`/api/chat/conversations/${conversation.id}`); + const conv = response.data.data.conversation; + + const plans = conv.messages + .filter(m => m.message_type === 'plan' && m.plan_id) + .map(m => ({ + id: m.plan_id, + content: m.content, + status: m.plan?.status || 'pending_approval', + title: m.plan?.title || 'Generated Plan', + created_at: m.created_at + })) + .filter(p => p.status === 'pending_approval' || p.status === 'draft'); + + setPendingPlans(plans); + } catch (error) { + console.error('Error loading pending plans:', error); + } + }; + + const approvePlan = async (planId) => { + try { + await axios.put(`/api/models/model1/plan/${planId}`, { + status: 'approved', + approvalFeedback: 'Plan approved for execution' + }); + + setMessages(prev => [...prev, { + role: 'assistant', + content: `✅ **Plan Approved!** The plan has been approved and is now executing with engineering tools...`, + timestamp: new Date().toISOString(), + message_type: 'system' + }]); + + loadPendingPlans(); + + // Automatically execute the plan after approval + setTimeout(() => { + executePlan(planId); + }, 1500); + + } catch (error) { + console.error('Error approving plan:', error); + setMessages(prev => [...prev, { + role: 'assistant', + content: `❌ **Approval Failed:** ${error.response?.data?.error || error.message}`, + timestamp: new Date().toISOString(), + message_type: 'error' + }]); + } + }; + + const rejectPlan = async (planId) => { + try { + await axios.put(`/api/models/model1/plan/${planId}`, { + status: 'rejected', + approvalFeedback: 'Plan rejected by user' + }); + + setMessages(prev => [...prev, { + role: 'assistant', + content: `❌ **Plan Rejected!** The plan has been rejected. You can ask for a new plan or modify your request.`, + timestamp: new Date().toISOString(), + message_type: 'system' + }]); + + loadPendingPlans(); + } catch (error) { + console.error('Error rejecting plan:', error); + setMessages(prev => [...prev, { + role: 'assistant', + content: `❌ **Rejection Failed:** ${error.response?.data?.error || error.message}`, + timestamp: new Date().toISOString(), + message_type: 'error' + }]); + } + }; + + const executePlan = async (planId) => { + try { + setLoading(true); + const response = await axios.post('/api/models/querymodel/orchestrate', { + planId, + options: { topK: 5 } + }); + + const result = response.data.data.toolExecution; + + // Format the orchestration results nicely + let formattedContent = `🚀 **Plan Executed Successfully!**\n\n`; + + if (result.expandedQuery) { + formattedContent += `**📋 Expanded Query:**\n${result.expandedQuery.substring(0, 500)}...\n\n`; + } + + if (result.extraction && result.extraction.results) { + formattedContent += `**📚 Document Search Results:** ${result.extraction.results.length} documents found\n`; + formattedContent += `**Confidence Score:** ${(result.extraction.confidence * 100).toFixed(1)}%\n\n`; + } + + if (result.web) { + formattedContent += `**🌐 Web Search:** ${result.web.totalResults} results found\n`; + formattedContent += `**Answer:** ${result.web.answer}\n\n`; + } + + if (result.report && result.report.content) { + formattedContent += `**📊 Generated Report:**\n${result.report.content}\n\n`; + } + + // formattedContent += `**⚡ Processing Time:** ${result.report?.processingTime || 'N/A'}s\n`; + // formattedContent += `**🔢 Tokens Used:** ${result.report?.tokensUsed || 'N/A'}`; + + setMessages(prev => [...prev, { + role: 'assistant', + content: formattedContent, + timestamp: new Date().toISOString(), + message_type: 'execution_result' + }]); + + loadPendingPlans(); + } catch (error) { + console.error('Error executing plan:', error); + setMessages(prev => [...prev, { + role: 'assistant', + content: `❌ **Execution Failed:** ${error.response?.data?.error || error.message}`, + timestamp: new Date().toISOString(), + message_type: 'error' + }]); + } finally { + setLoading(false); + } + }; + + const submitFeedback = async (messageId) => { + try { + await axios.post('/api/feedback/submit', { + messageId, + feedbackType: feedbackData.feedbackType, + rating: feedbackData.rating, + comment: feedbackData.comment, + correctedContent: feedbackData.correctedContent + }); + + setShowFeedback(null); + setFeedbackData({ + feedbackType: 'positive', + rating: 5, + comment: '', + correctedContent: '' + }); + } catch (error) { + console.error('Error submitting feedback:', error); + } + }; + + const handleKeyPress = (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } + }; + + return ( +
+
+ {/* Chat Header */} +
+
+

🧠 Engineering Reasoning Chat

+

Advanced AI system for complex engineering problem solving

+
+
+ +
+
+ + {/* Pending Plans */} + {pendingPlans.length > 0 && ( +
+

📋 Pending Plans ({pendingPlans.length})

+
+ {pendingPlans.map((plan) => ( +
+
+

{plan.title}

+ {plan.status} +
+
+
{plan.content}
+
+
+ + + +
+
+ ))} +
+
+ )} + + {/* Messages */} +
+
+ {messages.map((message, index) => ( +
+
+ {message.role === 'user' ? '👤' : '🤖'} +
+
+
+ + {message.role === 'user' ? 'You' : 'Engineering AI'} + + + {new Date(message.timestamp).toLocaleTimeString()} + + {message.role === 'assistant' && ( + + )} +
+
+
{message.content}
+
+ + {/* Plan Action Buttons */} + {message.message_type === 'system_prompt' && message.content.includes('Engineering Plan Generated') && message.plan_id && ( +
+ + + +
+ )} +
+
+ ))} + {loading && ( +
+
🤖
+
+
+ + + +
+
+
+ )} +
+
+
+ + {/* Input */} +
+
+