first commit

This commit is contained in:
2025-11-06 11:08:59 +01:00
commit 3c5117c2c3
85 changed files with 13275 additions and 0 deletions
+100
View File
@@ -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
+204
View File
@@ -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 <repository-url>
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
+51
View File
@@ -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"
}
+20
View File
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#3b82f6" />
<meta
name="description"
content="Reason Flow - Advanced Engineering Reasoning System"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Reason Flow - Engineering AI</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
+15
View File
@@ -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"
}
+82
View File
@@ -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 (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
flexDirection: 'column',
gap: '1rem'
}}>
<div style={{
width: '2rem',
height: '2rem',
border: '3px solid #e2e8f0',
borderTop: '3px solid #3b82f6',
borderRadius: '50%',
animation: 'spin 1s linear infinite'
}}></div>
<p>Loading...</p>
</div>
);
}
if (!user) {
return <Navigate to="/login" replace />;
}
return <Layout>{children}</Layout>;
}
function App() {
return (
<AuthProvider>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
<Route path="/chat" element={
<ProtectedRoute>
<ChatPage />
</ProtectedRoute>
} />
<Route path="/documents" element={
<ProtectedRoute>
<DocumentsPage />
</ProtectedRoute>
} />
<Route path="/tools" element={
<ProtectedRoute>
<ToolsPage />
</ProtectedRoute>
} />
<Route path="/admin" element={
<ProtectedRoute>
<AdminPage />
</ProtectedRoute>
} />
<Route path="/" element={<Navigate to="/dashboard" replace />} />
<Route path="*" element={<Navigate to="/dashboard" replace />} />
</Routes>
</AuthProvider>
);
}
export default App;
+283
View File
@@ -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;
}
}
+114
View File
@@ -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 (
<div className="layout">
{/* Sidebar */}
<aside className={`sidebar ${sidebarOpen ? 'open' : ''}`}>
<div className="sidebar-header">
<div className="logo">
<span className="logo-icon">🧠</span>
<span className="logo-text">Reason Flow</span>
</div>
<button
className="sidebar-toggle"
onClick={() => setSidebarOpen(false)}
>
</button>
</div>
<nav className="sidebar-nav">
{navigation.map((item) => (
<button
key={item.path}
className={`nav-item ${isActive(item.path) ? 'active' : ''}`}
onClick={() => {
navigate(item.path);
setSidebarOpen(false);
}}
>
<span className="nav-icon">{item.icon}</span>
<span className="nav-label">{item.label}</span>
</button>
))}
</nav>
<div className="sidebar-footer">
<div className="user-info">
<div className="user-avatar">
{user?.firstName?.[0]}{user?.lastName?.[0]}
</div>
<div className="user-details">
<div className="user-name">{user?.firstName} {user?.lastName}</div>
<div className="user-role">{user?.role}</div>
</div>
</div>
<button className="logout-btn" onClick={handleLogout}>
🚪 Logout
</button>
</div>
</aside>
{/* Main Content */}
<div className="main-content">
{/* Top Bar */}
<header className="topbar">
<button
className="menu-toggle"
onClick={() => setSidebarOpen(true)}
>
</button>
<div className="topbar-title">
{navigation.find(item => item.path === location.pathname)?.label || 'Reason Flow'}
</div>
<div className="topbar-actions">
<div className="status-indicator">
<span className="status-dot"></span>
System Online
</div>
</div>
</header>
{/* Page Content */}
<main className="page-content">
{children}
</main>
</div>
{/* Mobile Overlay */}
{sidebarOpen && (
<div
className="mobile-overlay"
onClick={() => setSidebarOpen(false)}
/>
)}
</div>
);
};
export default Layout;
+94
View File
@@ -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 (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};
+80
View File
@@ -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); }
}
+14
View File
@@ -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(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
+515
View File
@@ -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;
}
}
+338
View File
@@ -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 (
<div className="admin-page">
<div className="loading-container">
<div className="loading-spinner"></div>
<p>Loading admin dashboard...</p>
</div>
</div>
);
}
return (
<div className="admin-page">
<div className="page-header">
<h1> Admin Panel</h1>
<p>System administration and monitoring</p>
</div>
{message && (
<div className={`message ${message.includes('✅') ? 'success' : 'error'}`}>
{message}
</div>
)}
{/* System Overview */}
<div className="admin-section">
<div className="section-header">
<h2>📊 System Overview</h2>
<p>Key system metrics and statistics</p>
</div>
<div className="stats-grid">
<div className="stat-card">
<div className="stat-icon">👥</div>
<div className="stat-content">
<div className="stat-number">{systemStats.users}</div>
<div className="stat-label">Total Users</div>
</div>
</div>
<div className="stat-card">
<div className="stat-icon">💬</div>
<div className="stat-content">
<div className="stat-number">{systemStats.conversations}</div>
<div className="stat-label">Conversations</div>
</div>
</div>
<div className="stat-card">
<div className="stat-icon">📚</div>
<div className="stat-content">
<div className="stat-number">{systemStats.documents}</div>
<div className="stat-label">Documents</div>
</div>
</div>
<div className="stat-card">
<div className="stat-icon"></div>
<div className="stat-content">
<div className="stat-number">{systemStats.feedback}</div>
<div className="stat-label">Feedback Items</div>
</div>
</div>
<div className="stat-card">
<div className="stat-icon">🔧</div>
<div className="stat-content">
<div className="stat-number">{systemStats.toolExecutions}</div>
<div className="stat-label">Tool Executions</div>
</div>
</div>
</div>
</div>
{/* Model Status */}
<div className="admin-section">
<div className="section-header">
<h2>🤖 Model Status</h2>
<p>AI model health and performance</p>
</div>
<div className="model-status-grid">
<div className="model-card">
<div className="model-header">
<h3>🧠 MODEL1 (Planner)</h3>
<span className={`status-indicator ${modelStatus.model1 || 'unknown'}`}>
{modelStatus.model1 || 'Unknown'}
</span>
</div>
<p>Engineering plan generation and reasoning</p>
<div className="model-metrics">
<div className="metric">
<span className="metric-label">Status:</span>
<span className="metric-value">{modelStatus.model1 || 'Unknown'}</span>
</div>
</div>
</div>
<div className="model-card">
<div className="model-header">
<h3> QUERYMODEL (Executor)</h3>
<span className={`status-indicator ${modelStatus.queryModel || 'unknown'}`}>
{modelStatus.queryModel || 'Unknown'}
</span>
</div>
<p>Plan execution and tool orchestration</p>
<div className="model-metrics">
<div className="metric">
<span className="metric-label">Status:</span>
<span className="metric-value">{modelStatus.queryModel || 'Unknown'}</span>
</div>
</div>
</div>
<div className="model-card">
<div className="model-header">
<h3>🔍 RAG System</h3>
<span className={`status-indicator ${modelStatus.rag || 'unknown'}`}>
{modelStatus.rag || 'Unknown'}
</span>
</div>
<p>Document search and knowledge retrieval</p>
<div className="model-metrics">
<div className="metric">
<span className="metric-label">Status:</span>
<span className="metric-value">{modelStatus.rag || 'Unknown'}</span>
</div>
</div>
</div>
</div>
</div>
{/* Feedback Management */}
<div className="admin-section">
<div className="section-header">
<h2>💬 Feedback Management</h2>
<p>User feedback and system improvements</p>
</div>
<div className="feedback-overview">
<div className="feedback-stats">
<div className="stat-item">
<span className="stat-label">Total Feedback:</span>
<span className="stat-value">{feedbackStats.total || 0}</span>
</div>
<div className="stat-item">
<span className="stat-label">Positive:</span>
<span className="stat-value positive">{feedbackStats.positive || 0}</span>
</div>
<div className="stat-item">
<span className="stat-label">Negative:</span>
<span className="stat-value negative">{feedbackStats.negative || 0}</span>
</div>
<div className="stat-item">
<span className="stat-label">Corrections:</span>
<span className="stat-value correction">{feedbackStats.correction || 0}</span>
</div>
<div className="stat-item">
<span className="stat-label">Suggestions:</span>
<span className="stat-value suggestion">{feedbackStats.suggestion || 0}</span>
</div>
</div>
</div>
{recentFeedback.length > 0 && (
<div className="recent-feedback">
<h3>Recent Feedback</h3>
<div className="feedback-list">
{recentFeedback.map((feedback) => (
<div key={feedback.id} className="feedback-item">
<div className="feedback-header">
<div className="feedback-type">
<span className={`type-badge ${feedback.feedback_type}`}>
{feedback.feedback_type}
</span>
<span className="feedback-rating">
{feedback.rating ? `${feedback.rating}` : 'No rating'}
</span>
</div>
<div className="feedback-actions">
<button
className="process-btn"
onClick={() => processFeedback(feedback.id)}
disabled={feedback.is_processed}
>
{feedback.is_processed ? '✅ Processed' : '🔄 Process'}
</button>
</div>
</div>
{feedback.comment && (
<div className="feedback-comment">
<strong>Comment:</strong> {feedback.comment}
</div>
)}
{feedback.corrected_content && (
<div className="feedback-correction">
<strong>Correction:</strong> {feedback.corrected_content}
</div>
)}
<div className="feedback-meta">
<span>ID: {feedback.id}</span>
<span>Date: {new Date(feedback.created_at).toLocaleDateString()}</span>
<span>Status: {feedback.is_processed ? 'Processed' : 'Pending'}</span>
</div>
</div>
))}
</div>
</div>
)}
</div>
{/* System Actions */}
<div className="admin-section">
<div className="section-header">
<h2>🛠 System Actions</h2>
<p>Administrative operations and maintenance</p>
</div>
<div className="system-actions">
<div className="action-group">
<h3>Data Management</h3>
<div className="action-buttons">
<button className="action-btn primary">
📊 Export Data
</button>
<button className="action-btn secondary">
🗑 Cleanup Old Data
</button>
<button className="action-btn secondary">
🔄 Refresh Cache
</button>
</div>
</div>
<div className="action-group">
<h3>Model Management</h3>
<div className="action-buttons">
<button className="action-btn primary">
🚀 Retrain Models
</button>
<button className="action-btn secondary">
📈 Update Metrics
</button>
<button className="action-btn secondary">
🔧 Test Models
</button>
</div>
</div>
<div className="action-group">
<h3>System Maintenance</h3>
<div className="action-buttons">
<button className="action-btn primary">
🔄 Restart Services
</button>
<button className="action-btn secondary">
📋 Generate Report
</button>
<button className="action-btn secondary">
🔍 Run Diagnostics
</button>
</div>
</div>
</div>
</div>
</div>
);
};
export default AdminPage;
+631
View File
@@ -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;
}
}
+571
View File
@@ -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 (
<div className="chat-page">
<div className="chat-container">
{/* Chat Header */}
<div className="chat-header">
<div className="chat-title">
<h2>🧠 Engineering Reasoning Chat</h2>
<p>Advanced AI system for complex engineering problem solving</p>
</div>
<div className="chat-actions">
<button
className="action-btn secondary"
onClick={createConversation}
>
🔄 New Conversation
</button>
</div>
</div>
{/* Pending Plans */}
{pendingPlans.length > 0 && (
<div className="pending-plans">
<h3>📋 Pending Plans ({pendingPlans.length})</h3>
<div className="plans-grid">
{pendingPlans.map((plan) => (
<div key={plan.id} className="plan-card">
<div className="plan-header">
<h4>{plan.title}</h4>
<span className="plan-status">{plan.status}</span>
</div>
<div className="plan-content">
<pre>{plan.content}</pre>
</div>
<div className="plan-actions">
<button
className="btn approve"
onClick={() => approvePlan(plan.id)}
>
Approve
</button>
<button
className="btn reject"
onClick={() => rejectPlan(plan.id)}
>
Reject
</button>
<button
className="btn execute"
onClick={() => executePlan(plan.id)}
>
🚀 Execute
</button>
</div>
</div>
))}
</div>
</div>
)}
{/* Messages */}
<div className="messages-container">
<div className="messages">
{messages.map((message, index) => (
<div key={index} className={`message ${message.role}`} data-message-type={message.message_type}>
<div className="message-avatar">
{message.role === 'user' ? '👤' : '🤖'}
</div>
<div className="message-content">
<div className="message-header">
<span className="message-role">
{message.role === 'user' ? 'You' : 'Engineering AI'}
</span>
<span className="message-time">
{new Date(message.timestamp).toLocaleTimeString()}
</span>
{message.role === 'assistant' && (
<button
className="feedback-btn"
onClick={() => setShowFeedback(message.id || index)}
>
💬 Feedback
</button>
)}
</div>
<div className="message-text">
<pre>{message.content}</pre>
</div>
{/* Plan Action Buttons */}
{message.message_type === 'system_prompt' && message.content.includes('Engineering Plan Generated') && message.plan_id && (
<div className="plan-actions">
<button
className="action-btn approve-btn"
onClick={() => approvePlan(message.plan_id)}
disabled={loading}
>
Approve Plan
</button>
<button
className="action-btn reject-btn"
onClick={() => rejectPlan(message.plan_id)}
disabled={loading}
>
Reject Plan
</button>
<button
className="action-btn modify-btn"
onClick={() => setInputMessage('Please modify the plan with the following changes: ')}
disabled={loading}
>
🔄 Modify Plan
</button>
</div>
)}
</div>
</div>
))}
{loading && (
<div className="message assistant">
<div className="message-avatar">🤖</div>
<div className="message-content">
<div className="typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
</div>
{/* Input */}
<div className="chat-input">
<div className="input-container">
<textarea
ref={inputRef}
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="Ask me about engineering problems, request analysis, or describe what you need help with..."
rows="3"
disabled={loading}
/>
<button
className="send-btn"
onClick={sendMessage}
disabled={!inputMessage.trim() || loading}
>
{loading ? '⏳' : '📤'} Send
</button>
</div>
</div>
</div>
{/* Feedback Modal */}
{showFeedback && (
<div className="modal-overlay">
<div className="modal">
<div className="modal-header">
<h3>💬 Provide Feedback</h3>
<button
className="close-btn"
onClick={() => setShowFeedback(null)}
>
</button>
</div>
<div className="modal-content">
<div className="form-group">
<label>Feedback Type</label>
<select
value={feedbackData.feedbackType}
onChange={(e) => setFeedbackData({...feedbackData, feedbackType: e.target.value})}
>
<option value="positive">👍 Positive</option>
<option value="negative">👎 Negative</option>
<option value="correction"> Correction</option>
<option value="suggestion">💡 Suggestion</option>
</select>
</div>
<div className="form-group">
<label>Rating (1-5)</label>
<input
type="number"
min="1"
max="5"
value={feedbackData.rating}
onChange={(e) => setFeedbackData({...feedbackData, rating: parseInt(e.target.value)})}
/>
</div>
<div className="form-group">
<label>Comment</label>
<textarea
value={feedbackData.comment}
onChange={(e) => setFeedbackData({...feedbackData, comment: e.target.value})}
placeholder="Share your thoughts about this response..."
rows="3"
/>
</div>
{feedbackData.feedbackType === 'correction' && (
<div className="form-group">
<label>Corrected Content</label>
<textarea
value={feedbackData.correctedContent}
onChange={(e) => setFeedbackData({...feedbackData, correctedContent: e.target.value})}
placeholder="Provide the corrected version..."
rows="3"
/>
</div>
)}
<div className="modal-actions">
<button
className="btn secondary"
onClick={() => setShowFeedback(null)}
>
Cancel
</button>
<button
className="btn primary"
onClick={() => submitFeedback(showFeedback)}
>
Submit Feedback
</button>
</div>
</div>
</div>
</div>
)}
</div>
);
};
export default ChatPage;
+412
View File
@@ -0,0 +1,412 @@
/* Dashboard Styles */
.dashboard {
max-width: 1200px;
margin: 0 auto;
padding: 0;
}
.dashboard-header {
text-align: center;
margin-bottom: 2rem;
}
.dashboard-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;
}
.dashboard-header p {
color: #64748b;
font-size: 1.125rem;
margin: 0;
}
/* Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
border-radius: 1rem;
padding: 1.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
border: 1px solid #e2e8f0;
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;
}
/* Dashboard Grid */
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 2rem;
}
.dashboard-card {
background: white;
border-radius: 1rem;
padding: 1.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
border: 1px solid #e2e8f0;
transition: all 0.2s;
}
.dashboard-card:hover {
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.card-header {
margin-bottom: 1.5rem;
}
.card-header h2 {
font-size: 1.25rem;
font-weight: 600;
color: #1e293b;
margin: 0 0 0.25rem 0;
}
.card-header p {
color: #64748b;
font-size: 0.875rem;
margin: 0;
}
/* Quick Actions */
.quick-actions {
display: grid;
gap: 1rem;
}
.action-btn {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
cursor: pointer;
transition: all 0.2s;
text-align: left;
}
.action-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.action-btn.blue:hover {
background: linear-gradient(135deg, #dbeafe, #bfdbfe);
border-color: #3b82f6;
}
.action-btn.green:hover {
background: linear-gradient(135deg, #dcfce7, #bbf7d0);
border-color: #10b981;
}
.action-btn.purple:hover {
background: linear-gradient(135deg, #f3e8ff, #e9d5ff);
border-color: #8b5cf6;
}
.action-btn.orange:hover {
background: linear-gradient(135deg, #fed7aa, #fdba74);
border-color: #f59e0b;
}
.action-icon {
font-size: 1.5rem;
width: 2.5rem;
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
background: white;
border-radius: 0.5rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.action-content {
flex: 1;
}
.action-title {
font-weight: 600;
color: #1e293b;
margin-bottom: 0.25rem;
}
.action-description {
color: #64748b;
font-size: 0.875rem;
}
/* System Status */
.system-status {
display: flex;
flex-direction: column;
gap: 1rem;
}
.status-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
background: #f8fafc;
border-radius: 0.75rem;
border: 1px solid #e2e8f0;
}
.status-info {
flex: 1;
}
.status-name {
font-weight: 600;
color: #1e293b;
margin-bottom: 0.25rem;
}
.status-description {
color: #64748b;
font-size: 0.875rem;
}
.status-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
text-transform: capitalize;
}
.status-indicator.online {
color: #10b981;
}
.status-indicator.offline {
color: #ef4444;
}
.status-dot {
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
background: currentColor;
}
/* Recent Activity */
.recent-activity {
display: flex;
flex-direction: column;
gap: 1rem;
}
.activity-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
background: #f8fafc;
border-radius: 0.75rem;
border: 1px solid #e2e8f0;
}
.activity-icon {
font-size: 1.25rem;
width: 2rem;
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
background: white;
border-radius: 0.5rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.activity-content {
flex: 1;
}
.activity-title {
font-weight: 600;
color: #1e293b;
margin-bottom: 0.25rem;
}
.activity-meta {
color: #64748b;
font-size: 0.875rem;
}
.empty-state {
text-align: center;
padding: 2rem;
color: #64748b;
}
.empty-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.start-btn {
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
margin-top: 1rem;
}
.start-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
/* System Overview */
.system-overview {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.overview-step {
display: flex;
align-items: flex-start;
gap: 1rem;
}
.step-number {
width: 2rem;
height: 2rem;
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.875rem;
flex-shrink: 0;
}
.step-content {
flex: 1;
}
.step-title {
font-weight: 600;
color: #1e293b;
margin-bottom: 0.25rem;
}
.step-description {
color: #64748b;
font-size: 0.875rem;
line-height: 1.5;
}
/* 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) {
.dashboard-grid {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.dashboard-header h1 {
font-size: 2rem;
}
.dashboard-header p {
font-size: 1rem;
}
}
@media (max-width: 480px) {
.stats-grid {
grid-template-columns: 1fr;
}
.dashboard-header h1 {
font-size: 1.75rem;
}
}
+267
View File
@@ -0,0 +1,267 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import './Dashboard.css';
const Dashboard = () => {
const [stats, setStats] = useState({
conversations: 0,
documents: 0,
plans: 0,
feedback: 0
});
const [recentActivity, setRecentActivity] = useState([]);
const [systemStatus, setSystemStatus] = useState({
model1: 'online',
queryModel: 'online',
rag: 'online'
});
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
useEffect(() => {
loadDashboardData();
}, []);
const loadDashboardData = async () => {
try {
setLoading(true);
// Load conversations
const conversationsRes = await axios.get('/api/chat/conversations?limit=5');
// Load documents
const documentsRes = await axios.get('/api/documents?limit=5');
// Load system status
const statusRes = await axios.get('/api/models/status');
setStats({
conversations: conversationsRes.data.data.pagination.total,
documents: documentsRes.data.data.pagination?.total || documentsRes.data.data.documents.length,
plans: 0, // Will be calculated from conversations
feedback: 0 // Will be loaded separately
});
setRecentActivity(conversationsRes.data.data.conversations.slice(0, 5));
setSystemStatus(statusRes.data.data);
} catch (error) {
console.error('Error loading dashboard data:', error);
} finally {
setLoading(false);
}
};
const quickActions = [
{
title: 'Start Engineering Chat',
description: 'Begin a new engineering conversation',
icon: '💬',
color: 'blue',
action: () => navigate('/chat')
},
{
title: 'Upload Documents',
description: 'Add knowledge to the system',
icon: '📚',
color: 'green',
action: () => navigate('/documents')
},
{
title: 'Execute Tools',
description: 'Run engineering tools and workflows',
icon: '🔧',
color: 'purple',
action: () => navigate('/tools')
},
{
title: 'View History',
description: 'Browse past conversations',
icon: '📖',
color: 'orange',
action: () => navigate('/chat')
}
];
const systemComponents = [
{ name: 'MODEL1 (Planner)', status: systemStatus.model1, description: 'Engineering plan generation' },
{ name: 'QUERYMODEL (Executor)', status: systemStatus.queryModel, description: 'Plan execution and tool orchestration' },
{ name: 'RAG System', status: systemStatus.rag, description: 'Document search and knowledge retrieval' }
];
if (loading) {
return (
<div className="dashboard">
<div className="loading-container">
<div className="loading-spinner"></div>
<p>Loading dashboard...</p>
</div>
</div>
);
}
return (
<div className="dashboard">
<div className="dashboard-header">
<h1>Engineering Reasoning Dashboard</h1>
<p>Advanced AI system for complex engineering problem solving</p>
</div>
{/* Stats Grid */}
<div className="stats-grid">
<div className="stat-card">
<div className="stat-icon">💬</div>
<div className="stat-content">
<div className="stat-number">{stats.conversations}</div>
<div className="stat-label">Conversations</div>
</div>
</div>
<div className="stat-card">
<div className="stat-icon">📚</div>
<div className="stat-content">
<div className="stat-number">{stats.documents}</div>
<div className="stat-label">Documents</div>
</div>
</div>
<div className="stat-card">
<div className="stat-icon">📋</div>
<div className="stat-content">
<div className="stat-number">{stats.plans}</div>
<div className="stat-label">Plans Generated</div>
</div>
</div>
<div className="stat-card">
<div className="stat-icon"></div>
<div className="stat-content">
<div className="stat-number">{stats.feedback}</div>
<div className="stat-label">Feedback Items</div>
</div>
</div>
</div>
{/* Main Content Grid */}
<div className="dashboard-grid">
{/* Quick Actions */}
<div className="dashboard-card">
<div className="card-header">
<h2>Quick Actions</h2>
<p>Start working with the system</p>
</div>
<div className="quick-actions">
{quickActions.map((action, index) => (
<button
key={index}
className={`action-btn ${action.color}`}
onClick={action.action}
>
<span className="action-icon">{action.icon}</span>
<div className="action-content">
<div className="action-title">{action.title}</div>
<div className="action-description">{action.description}</div>
</div>
</button>
))}
</div>
</div>
{/* System Status */}
<div className="dashboard-card">
<div className="card-header">
<h2>System Status</h2>
<p>Current system health</p>
</div>
<div className="system-status">
{systemComponents.map((component, index) => (
<div key={index} className="status-item">
<div className="status-info">
<div className="status-name">{component.name}</div>
<div className="status-description">{component.description}</div>
</div>
<div className={`status-indicator ${component.status}`}>
<span className="status-dot"></span>
{component.status}
</div>
</div>
))}
</div>
</div>
{/* Recent Activity */}
<div className="dashboard-card">
<div className="card-header">
<h2>Recent Activity</h2>
<p>Latest conversations and interactions</p>
</div>
<div className="recent-activity">
{recentActivity.length > 0 ? (
recentActivity.map((activity, index) => (
<div key={index} className="activity-item">
<div className="activity-icon">💬</div>
<div className="activity-content">
<div className="activity-title">{activity.title || 'Untitled Conversation'}</div>
<div className="activity-meta">
{new Date(activity.created_at).toLocaleDateString()} {activity.status}
</div>
</div>
</div>
))
) : (
<div className="empty-state">
<div className="empty-icon">📝</div>
<p>No recent activity</p>
<button
className="start-btn"
onClick={() => navigate('/chat')}
>
Start Your First Conversation
</button>
</div>
)}
</div>
</div>
{/* System Overview */}
<div className="dashboard-card">
<div className="card-header">
<h2>System Overview</h2>
<p>How the engineering reasoning system works</p>
</div>
<div className="system-overview">
<div className="overview-step">
<div className="step-number">1</div>
<div className="step-content">
<div className="step-title">MODEL1 Planning</div>
<div className="step-description">Analyzes your engineering query and creates a structured plan</div>
</div>
</div>
<div className="overview-step">
<div className="step-number">2</div>
<div className="step-content">
<div className="step-title">Plan Approval</div>
<div className="step-description">Review and approve the generated plan before execution</div>
</div>
</div>
<div className="overview-step">
<div className="step-number">3</div>
<div className="step-content">
<div className="step-title">QUERYMODEL Execution</div>
<div className="step-description">Executes the plan using specialized engineering tools</div>
</div>
</div>
<div className="overview-step">
<div className="step-number">4</div>
<div className="step-content">
<div className="step-title">Results & Feedback</div>
<div className="step-description">Review results and provide feedback for continuous improvement</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default Dashboard;
+538
View File
@@ -0,0 +1,538 @@
/* Documents Page Styles */
.documents-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;
}
/* Section Styles */
.upload-section,
.search-section,
.documents-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;
}
/* Upload Section */
.upload-area {
display: flex;
flex-direction: column;
gap: 1rem;
}
.file-input-container {
position: relative;
}
.file-input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.file-input-label {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem 2rem;
border: 2px dashed #d1d5db;
border-radius: 1rem;
background: #f9fafb;
cursor: pointer;
transition: all 0.2s;
text-align: center;
}
.file-input-label:hover {
border-color: #3b82f6;
background: #f0f9ff;
}
.upload-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.upload-text {
font-size: 1.125rem;
font-weight: 600;
color: #1e293b;
margin-bottom: 0.5rem;
}
.upload-hint {
color: #64748b;
font-size: 0.875rem;
}
.upload-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: #f8fafc;
border-radius: 0.75rem;
border: 1px solid #e2e8f0;
}
.file-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.file-info strong {
color: #1e293b;
font-size: 0.875rem;
}
.file-info span {
color: #64748b;
font-size: 0.75rem;
}
.upload-btn {
background: linear-gradient(135deg, #10b981, #059669);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 0.5rem;
min-width: 150px;
justify-content: center;
}
.upload-btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
}
.upload-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.progress-bar {
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 2px;
overflow: hidden;
margin-bottom: 0.5rem;
}
.progress-fill {
height: 100%;
background: white;
border-radius: 2px;
transition: width 0.3s ease;
}
/* Search Section */
.search-container {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.search-input-group {
display: flex;
gap: 1rem;
align-items: center;
}
.search-input {
flex: 1;
padding: 0.75rem 1rem;
border: 1px solid #d1d5db;
border-radius: 0.75rem;
font-size: 0.875rem;
transition: all 0.2s;
background: #f8fafc;
}
.search-input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
background: white;
}
.search-type-selector {
display: flex;
gap: 0.25rem;
background: #f1f5f9;
border-radius: 0.5rem;
padding: 0.25rem;
}
.search-type-btn {
padding: 0.5rem 1rem;
background: none;
border: none;
border-radius: 0.375rem;
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
transition: all 0.2s;
color: #64748b;
}
.search-type-btn.active {
background: white;
color: #3b82f6;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.search-btn {
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 0.75rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.search-btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.search-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Search Results */
.search-results {
margin-top: 1rem;
}
.search-results h3 {
font-size: 1.125rem;
font-weight: 600;
color: #1e293b;
margin: 0 0 1rem 0;
}
.results-grid {
display: grid;
gap: 1rem;
}
.result-card {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
padding: 1rem;
transition: all 0.2s;
}
.result-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-1px);
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.result-header h4 {
margin: 0;
color: #1e293b;
font-size: 1rem;
}
.relevance-score {
background: #dbeafe;
color: #1e40af;
padding: 0.25rem 0.5rem;
border-radius: 0.375rem;
font-size: 0.75rem;
font-weight: 500;
}
.result-snippet {
color: #475569;
font-size: 0.875rem;
line-height: 1.5;
margin-bottom: 0.75rem;
background: white;
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid #e2e8f0;
}
.result-meta {
display: flex;
gap: 1rem;
font-size: 0.75rem;
color: #64748b;
}
/* Documents Grid */
.documents-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
.document-card {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
padding: 1.5rem;
transition: all 0.2s;
}
.document-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.document-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
.document-header h4 {
margin: 0;
color: #1e293b;
font-size: 1rem;
flex: 1;
}
.document-actions {
display: flex;
gap: 0.5rem;
}
.action-btn {
padding: 0.25rem 0.5rem;
border: none;
border-radius: 0.375rem;
cursor: pointer;
font-size: 0.75rem;
font-weight: 500;
transition: all 0.2s;
}
.action-btn.delete {
background: #fee2e2;
color: #dc2626;
}
.action-btn.delete:hover {
background: #fecaca;
transform: translateY(-1px);
}
.document-meta {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
}
.meta-item {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
}
.meta-label {
color: #64748b;
font-weight: 500;
}
.status {
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
text-transform: capitalize;
}
.status.processed {
background: #d1fae5;
color: #065f46;
}
.status.processing {
background: #fef3c7;
color: #92400e;
}
.status.error {
background: #fee2e2;
color: #991b1b;
}
.document-preview {
border-top: 1px solid #e2e8f0;
padding-top: 1rem;
}
.preview-header {
font-size: 0.75rem;
font-weight: 600;
color: #64748b;
margin-bottom: 0.5rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.preview-text {
color: #475569;
font-size: 0.875rem;
line-height: 1.5;
background: white;
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid #e2e8f0;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 3rem;
color: #64748b;
}
.empty-icon {
font-size: 4rem;
margin-bottom: 1rem;
}
.empty-state h3 {
margin: 0 0 0.5rem 0;
color: #1e293b;
font-size: 1.25rem;
}
.empty-state p {
margin: 0;
font-size: 0.875rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.search-input-group {
flex-direction: column;
align-items: stretch;
}
.search-type-selector {
justify-content: center;
}
.documents-grid {
grid-template-columns: 1fr;
}
.upload-actions {
flex-direction: column;
gap: 1rem;
align-items: stretch;
}
.upload-btn {
width: 100%;
}
}
@media (max-width: 480px) {
.page-header h1 {
font-size: 2rem;
}
.page-header p {
font-size: 1rem;
}
.section-header h2 {
font-size: 1.25rem;
}
.upload-section,
.search-section,
.documents-section {
padding: 1.5rem;
}
}
+300
View File
@@ -0,0 +1,300 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './DocumentsPage.css';
const DocumentsPage = () => {
const [documents, setDocuments] = useState([]);
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [searchType, setSearchType] = useState('semantic');
const [uploading, setUploading] = useState(false);
const [searching, setSearching] = useState(false);
const [uploadFile, setUploadFile] = useState(null);
const [uploadProgress, setUploadProgress] = useState(0);
const [message, setMessage] = useState('');
useEffect(() => {
loadDocuments();
}, []);
const loadDocuments = async () => {
try {
const response = await axios.get('/api/documents');
setDocuments(response.data.data.documents || []);
} catch (error) {
console.error('Error loading documents:', error);
}
};
const handleFileUpload = async () => {
if (!uploadFile) return;
setUploading(true);
setUploadProgress(0);
setMessage('');
const formData = new FormData();
formData.append('document', uploadFile);
try {
const response = await axios.post('/api/documents/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
setUploadProgress(percentCompleted);
}
});
setMessage(`✅ Document uploaded successfully: ${response.data.data.document.original_filename}`);
setUploadFile(null);
setUploadProgress(0);
loadDocuments();
} catch (error) {
console.error('Upload error:', error);
setMessage(`❌ Upload failed: ${error.response?.data?.error || error.message}`);
} finally {
setUploading(false);
}
};
const handleSearch = async () => {
if (!searchQuery.trim()) return;
setSearching(true);
setSearchResults([]);
try {
const endpoint = searchType === 'semantic' ? '/api/documents/search' : '/api/documents/graph-search';
const response = await axios.get(endpoint, {
params: { query: searchQuery }
});
setSearchResults(response.data.data.results || []);
} catch (error) {
console.error('Search error:', error);
setMessage(`❌ Search failed: ${error.response?.data?.error || error.message}`);
} finally {
setSearching(false);
}
};
const deleteDocument = async (documentId) => {
if (!window.confirm('Are you sure you want to delete this document?')) return;
try {
await axios.delete(`/api/documents/${documentId}`);
setMessage('✅ Document deleted successfully');
loadDocuments();
} catch (error) {
console.error('Delete error:', error);
setMessage(`❌ Delete failed: ${error.response?.data?.error || error.message}`);
}
};
const formatFileSize = (bytes) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
return (
<div className="documents-page">
<div className="page-header">
<h1>📚 Knowledge Base</h1>
<p>Upload and search engineering documents with advanced AI</p>
</div>
{message && (
<div className={`message ${message.includes('✅') ? 'success' : 'error'}`}>
{message}
</div>
)}
{/* Upload Section */}
<div className="upload-section">
<div className="section-header">
<h2>📤 Upload Documents</h2>
<p>Add new documents to the knowledge base</p>
</div>
<div className="upload-area">
<div className="file-input-container">
<input
type="file"
id="file-upload"
accept=".pdf,.txt,.doc,.docx"
onChange={(e) => setUploadFile(e.target.files?.[0])}
className="file-input"
/>
<label htmlFor="file-upload" className="file-input-label">
<div className="upload-icon">📁</div>
<div className="upload-text">
{uploadFile ? uploadFile.name : 'Choose a file to upload'}
</div>
<div className="upload-hint">PDF, TXT, DOC, DOCX files supported</div>
</label>
</div>
{uploadFile && (
<div className="upload-actions">
<div className="file-info">
<strong>{uploadFile.name}</strong>
<span>{formatFileSize(uploadFile.size)}</span>
</div>
<button
className="upload-btn"
onClick={handleFileUpload}
disabled={uploading}
>
{uploading ? (
<>
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${uploadProgress}%` }}
></div>
</div>
<span>Uploading... {uploadProgress}%</span>
</>
) : (
'📤 Upload Document'
)}
</button>
</div>
)}
</div>
</div>
{/* Search Section */}
<div className="search-section">
<div className="section-header">
<h2>🔍 Document Search</h2>
<p>Find relevant information using AI-powered search</p>
</div>
<div className="search-container">
<div className="search-input-group">
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search for engineering concepts, specifications, or procedures..."
className="search-input"
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
/>
<div className="search-type-selector">
<button
className={`search-type-btn ${searchType === 'semantic' ? 'active' : ''}`}
onClick={() => setSearchType('semantic')}
>
🔍 Semantic
</button>
<button
className={`search-type-btn ${searchType === 'graph' ? 'active' : ''}`}
onClick={() => setSearchType('graph')}
>
🕸 Graph RAG
</button>
</div>
<button
className="search-btn"
onClick={handleSearch}
disabled={!searchQuery.trim() || searching}
>
{searching ? '⏳' : '🔍'} Search
</button>
</div>
{searchResults.length > 0 && (
<div className="search-results">
<h3>Search Results ({searchResults.length})</h3>
<div className="results-grid">
{searchResults.map((result, index) => (
<div key={index} className="result-card">
<div className="result-header">
<h4>📄 {result.original_filename}</h4>
<span className="relevance-score">
{result.score?.toFixed(3)} relevance
</span>
</div>
<div className="result-snippet">
{result.snippet}
</div>
<div className="result-meta">
<span>ID: {result.id}</span>
<span>Size: {formatFileSize(result.file_size || 0)}</span>
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
{/* Documents List */}
<div className="documents-section">
<div className="section-header">
<h2>📋 Document Library</h2>
<p>Manage your uploaded documents</p>
</div>
<div className="documents-grid">
{documents.length > 0 ? (
documents.map((doc) => (
<div key={doc.id} className="document-card">
<div className="document-header">
<h4>📄 {doc.original_filename}</h4>
<div className="document-actions">
<button
className="action-btn delete"
onClick={() => deleteDocument(doc.id)}
>
🗑 Delete
</button>
</div>
</div>
<div className="document-meta">
<div className="meta-item">
<span className="meta-label">Size:</span>
<span>{formatFileSize(doc.file_size)}</span>
</div>
<div className="meta-item">
<span className="meta-label">Uploaded:</span>
<span>{new Date(doc.created_at).toLocaleDateString()}</span>
</div>
<div className="meta-item">
<span className="meta-label">Status:</span>
<span className={`status ${doc.status}`}>{doc.status}</span>
</div>
</div>
{doc.extracted_text && (
<div className="document-preview">
<div className="preview-header">Content Preview:</div>
<div className="preview-text">
{doc.extracted_text.substring(0, 200)}...
</div>
</div>
)}
</div>
))
) : (
<div className="empty-state">
<div className="empty-icon">📚</div>
<h3>No documents yet</h3>
<p>Upload your first document to start building your knowledge base</p>
</div>
)}
</div>
</div>
</div>
);
};
export default DocumentsPage;
+226
View File
@@ -0,0 +1,226 @@
/* Login Page Styles */
.login-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
overflow: hidden;
}
.login-container {
background: white;
border-radius: 1rem;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
padding: 2rem;
width: 100%;
max-width: 400px;
position: relative;
z-index: 10;
}
.login-header {
text-align: center;
margin-bottom: 2rem;
}
.logo {
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
margin-bottom: 0.5rem;
}
.logo-icon {
font-size: 2rem;
}
.logo-text {
font-size: 1.75rem;
font-weight: 700;
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.login-subtitle {
color: #64748b;
font-size: 0.95rem;
margin: 0;
}
.form-tabs {
display: flex;
background: #f1f5f9;
border-radius: 0.5rem;
padding: 0.25rem;
margin-bottom: 1.5rem;
}
.tab {
flex: 1;
padding: 0.75rem 1rem;
background: none;
border: none;
border-radius: 0.375rem;
cursor: pointer;
font-weight: 500;
transition: all 0.2s;
color: #64748b;
}
.tab.active {
background: white;
color: #3b82f6;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.login-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-group label {
font-weight: 500;
color: #374151;
font-size: 0.875rem;
}
.form-group input {
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 0.95rem;
transition: all 0.2s;
background: #fafafa;
}
.form-group input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
background: white;
}
.error-message {
background: #fef2f2;
border: 1px solid #fecaca;
color: #dc2626;
padding: 0.75rem;
border-radius: 0.5rem;
font-size: 0.875rem;
text-align: center;
}
.submit-btn {
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
color: white;
border: none;
padding: 0.875rem 1.5rem;
border-radius: 0.5rem;
font-weight: 600;
font-size: 0.95rem;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
margin-top: 0.5rem;
}
.submit-btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3);
}
.submit-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.loading-spinner {
width: 1rem;
height: 1rem;
border: 2px solid transparent;
border-top: 2px solid currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.login-footer {
text-align: center;
margin-top: 1.5rem;
color: #64748b;
font-size: 0.875rem;
}
.switch-btn {
background: none;
border: none;
color: #3b82f6;
cursor: pointer;
font-weight: 500;
margin-left: 0.5rem;
text-decoration: underline;
}
.switch-btn:hover {
color: #1d4ed8;
}
.login-background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
.bg-pattern {
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background:
radial-gradient(circle at 25% 25%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 75% 75%, rgba(255, 255, 255, 0.1) 0%, transparent 50%);
animation: float 20s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-20px) rotate(180deg); }
}
/* Responsive Design */
@media (max-width: 480px) {
.login-container {
margin: 1rem;
padding: 1.5rem;
}
.logo-text {
font-size: 1.5rem;
}
.login-subtitle {
font-size: 0.875rem;
}
}
+182
View File
@@ -0,0 +1,182 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import './LoginPage.css';
const LoginPage = () => {
const [isLogin, setIsLogin] = useState(true);
const [formData, setFormData] = useState({
email: '',
password: '',
firstName: '',
lastName: ''
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const { login, register, user } = useAuth();
const navigate = useNavigate();
useEffect(() => {
if (user) {
navigate('/dashboard');
}
}, [user, navigate]);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError('');
try {
const result = isLogin
? await login(formData.email, formData.password)
: await register(formData);
if (result.success) {
navigate('/dashboard');
} else {
setError(result.error);
}
} catch (err) {
setError('An unexpected error occurred');
} finally {
setLoading(false);
}
};
const handleChange = (e) => {
setFormData(prev => ({
...prev,
[e.target.name]: e.target.value
}));
};
return (
<div className="login-page">
<div className="login-container">
<div className="login-header">
<div className="logo">
<span className="logo-icon">🧠</span>
<span className="logo-text">Reason Flow</span>
</div>
<p className="login-subtitle">
Advanced Engineering Reasoning System
</p>
</div>
<div className="login-form-container">
<div className="form-tabs">
<button
className={`tab ${isLogin ? 'active' : ''}`}
onClick={() => setIsLogin(true)}
>
Login
</button>
<button
className={`tab ${!isLogin ? 'active' : ''}`}
onClick={() => setIsLogin(false)}
>
Register
</button>
</div>
<form onSubmit={handleSubmit} className="login-form">
{!isLogin && (
<>
<div className="form-group">
<label htmlFor="firstName">First Name</label>
<input
type="text"
id="firstName"
name="firstName"
value={formData.firstName}
onChange={handleChange}
required={!isLogin}
placeholder="Enter your first name"
/>
</div>
<div className="form-group">
<label htmlFor="lastName">Last Name</label>
<input
type="text"
id="lastName"
name="lastName"
value={formData.lastName}
onChange={handleChange}
required={!isLogin}
placeholder="Enter your last name"
/>
</div>
</>
)}
<div className="form-group">
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required
placeholder="Enter your email"
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
name="password"
value={formData.password}
onChange={handleChange}
required
placeholder="Enter your password"
minLength="6"
/>
</div>
{error && (
<div className="error-message">
{error}
</div>
)}
<button
type="submit"
className="submit-btn"
disabled={loading}
>
{loading ? (
<span className="loading-spinner"></span>
) : (
isLogin ? 'Sign In' : 'Create Account'
)}
</button>
</form>
<div className="login-footer">
<p>
{isLogin ? "Don't have an account?" : "Already have an account?"}
<button
type="button"
className="switch-btn"
onClick={() => setIsLogin(!isLogin)}
>
{isLogin ? 'Register' : 'Login'}
</button>
</p>
</div>
</div>
</div>
<div className="login-background">
<div className="bg-pattern"></div>
</div>
</div>
);
};
export default LoginPage;
+525
View File
@@ -0,0 +1,525 @@
/* Tools Page Styles */
.tools-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;
}
.message.warning {
background: #fef3c7;
border: 1px solid #fde68a;
color: #92400e;
}
/* Section Styles */
.tool-selection,
.execution-history {
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;
}
/* Tool Grid */
.tool-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.tool-card {
display: flex;
align-items: center;
gap: 1rem;
padding: 1.5rem;
border: 2px solid #e2e8f0;
border-radius: 0.75rem;
background: #f8fafc;
cursor: pointer;
transition: all 0.2s;
text-align: left;
}
.tool-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.tool-card.selected {
border-color: #3b82f6;
background: #f0f9ff;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
}
.tool-card.blue.selected {
border-color: #3b82f6;
background: #dbeafe;
}
.tool-card.green.selected {
border-color: #10b981;
background: #d1fae5;
}
.tool-card.purple.selected {
border-color: #8b5cf6;
background: #e9d5ff;
}
.tool-card.orange.selected {
border-color: #f59e0b;
background: #fed7aa;
}
.tool-card.cyan.selected {
border-color: #06b6d4;
background: #cffafe;
}
.tool-card.red.selected {
border-color: #ef4444;
background: #fee2e2;
}
.tool-icon {
font-size: 2rem;
width: 3rem;
height: 3rem;
display: flex;
align-items: center;
justify-content: center;
background: white;
border-radius: 0.75rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
flex-shrink: 0;
}
.tool-info {
flex: 1;
}
.tool-info h3 {
margin: 0 0 0.5rem 0;
color: #1e293b;
font-size: 1.125rem;
font-weight: 600;
}
.tool-info p {
margin: 0;
color: #64748b;
font-size: 0.875rem;
line-height: 1.4;
}
/* Execution Panel */
.execution-panel {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
overflow: hidden;
}
.panel-header {
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
color: white;
padding: 1rem 1.5rem;
}
.panel-header h3 {
margin: 0;
font-size: 1.125rem;
font-weight: 600;
}
.panel-content {
padding: 1.5rem;
}
.input-group {
margin-bottom: 1.5rem;
}
.input-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #374151;
font-size: 0.875rem;
}
.input-group textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 0.875rem;
font-family: inherit;
resize: vertical;
transition: all 0.2s;
background: white;
}
.input-group textarea:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.execution-actions {
display: flex;
justify-content: flex-end;
}
.execute-btn {
background: linear-gradient(135deg, #10b981, #059669);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 0.5rem;
}
.execute-btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
}
.execute-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.execute-btn.disabled {
background: linear-gradient(135deg, #6b7280, #4b5563);
opacity: 0.8;
cursor: not-allowed;
}
.execute-btn.disabled:hover {
transform: none;
box-shadow: none;
}
.loading-spinner {
width: 1rem;
height: 1rem;
border: 2px solid transparent;
border-top: 2px solid currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Execution History */
.history-container {
max-height: 600px;
overflow-y: auto;
}
.executions-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.execution-card {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
padding: 1.5rem;
transition: all 0.2s;
}
.execution-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-1px);
}
.execution-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid #e2e8f0;
}
.execution-info h4 {
margin: 0 0 0.5rem 0;
color: #1e293b;
font-size: 1.125rem;
font-weight: 600;
}
.execution-meta {
display: flex;
gap: 1rem;
font-size: 0.75rem;
color: #64748b;
}
.execution-status {
display: flex;
align-items: center;
gap: 0.75rem;
}
.status-badge {
padding: 0.25rem 0.75rem;
border-radius: 0.375rem;
font-size: 0.75rem;
font-weight: 500;
text-transform: capitalize;
}
.status-badge.success {
background: #d1fae5;
color: #065f46;
}
.status-badge.error {
background: #fee2e2;
color: #991b1b;
}
.status-badge.warning {
background: #fef3c7;
color: #92400e;
}
.status-badge.info {
background: #dbeafe;
color: #1e40af;
}
.retry-btn {
background: #f59e0b;
color: white;
border: none;
padding: 0.25rem 0.75rem;
border-radius: 0.375rem;
font-size: 0.75rem;
cursor: pointer;
transition: all 0.2s;
}
.retry-btn:hover {
background: #d97706;
transform: translateY(-1px);
}
/* Execution Details */
.execution-details {
display: flex;
flex-direction: column;
gap: 1rem;
}
.detail-section {
background: white;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
padding: 1rem;
}
.detail-section.error {
border-color: #fecaca;
background: #fef2f2;
}
.detail-section h5 {
margin: 0 0 0.75rem 0;
color: #1e293b;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.code-block,
.error-block {
background: #f1f5f9;
padding: 0.75rem;
border-radius: 0.375rem;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.75rem;
line-height: 1.5;
overflow-x: auto;
margin: 0;
white-space: pre-wrap;
}
.error-block {
background: #fee2e2;
color: #991b1b;
border: 1px solid #fecaca;
}
.execution-metrics {
display: flex;
gap: 2rem;
padding-top: 1rem;
border-top: 1px solid #e2e8f0;
}
.metric {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.metric-label {
font-size: 0.75rem;
color: #64748b;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.metric-value {
font-size: 0.875rem;
color: #1e293b;
font-weight: 600;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 3rem;
color: #64748b;
}
.empty-icon {
font-size: 4rem;
margin-bottom: 1rem;
}
.empty-state h3 {
margin: 0 0 0.5rem 0;
color: #1e293b;
font-size: 1.25rem;
}
.empty-state p {
margin: 0;
font-size: 0.875rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.tool-grid {
grid-template-columns: 1fr;
}
.execution-header {
flex-direction: column;
gap: 1rem;
align-items: flex-start;
}
.execution-status {
align-self: flex-end;
}
.execution-metrics {
flex-direction: column;
gap: 1rem;
}
}
@media (max-width: 480px) {
.page-header h1 {
font-size: 2rem;
}
.page-header p {
font-size: 1rem;
}
.section-header h2 {
font-size: 1.25rem;
}
.tool-selection,
.execution-history {
padding: 1.5rem;
}
.tool-card {
flex-direction: column;
text-align: center;
}
.tool-info {
text-align: center;
}
}
+261
View File
@@ -0,0 +1,261 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './ToolsPage.css';
const ToolsPage = () => {
const [toolExecutions, setToolExecutions] = useState([]);
const [selectedTool, setSelectedTool] = useState('');
const [toolInput, setToolInput] = useState('');
const [executing, setExecuting] = useState(false);
const [message, setMessage] = useState('');
const tools = [
{
id: 'query_expander',
name: 'Query Expander',
description: 'Expands and refines engineering queries for better understanding',
icon: '🔍',
color: 'blue'
},
{
id: 'extraction',
name: 'Document Extraction',
description: 'Extracts relevant information from uploaded documents',
icon: '📄',
color: 'green'
},
{
id: 'report1',
name: 'Report Generator',
description: 'Generates structured engineering reports',
icon: '📊',
color: 'purple'
},
{
id: 'report2',
name: 'File Generator',
description: 'Creates downloadable engineering files',
icon: '📁',
color: 'orange'
},
{
id: 'web_search',
name: 'Web Search',
description: 'Searches the web for current engineering information',
icon: '🌐',
color: 'cyan'
},
{
id: 'encyclopedia_pdf',
name: 'Encyclopedia Search',
description: 'Searches internal PDF knowledge base',
icon: '📚',
color: 'red'
}
];
useEffect(() => {
loadToolExecutions();
}, []);
const loadToolExecutions = async () => {
try {
const response = await axios.get('/api/tools/executions');
setToolExecutions(response.data.data.executions || []);
} catch (error) {
console.error('Error loading tool executions:', error);
}
};
const executeTool = async () => {
setMessage('⚠️ **Manual tool execution is disabled.** Tools can only be executed as part of an approved engineering plan. Please use the Chat page to create and execute plans.');
setSelectedTool('');
setToolInput('');
};
const retryExecution = async (executionId) => {
try {
await axios.post(`/api/tools/executions/${executionId}/retry`);
setMessage('✅ Tool execution retried');
loadToolExecutions();
} catch (error) {
console.error('Retry error:', error);
setMessage(`❌ Retry failed: ${error.response?.data?.error || error.message}`);
}
};
const getStatusColor = (status) => {
switch (status) {
case 'completed': return 'success';
case 'failed': return 'error';
case 'running': return 'warning';
default: return 'info';
}
};
const formatDate = (dateString) => {
return new Date(dateString).toLocaleString();
};
return (
<div className="tools-page">
<div className="page-header">
<h1>🔧 Engineering Tools</h1>
<p>View tool execution history and results from approved engineering plans</p>
</div>
{message && (
<div className={`message ${message.includes('✅') ? 'success' : message.includes('⚠️') ? 'warning' : 'error'}`}>
{message}
</div>
)}
{/* Tool Selection */}
<div className="tool-selection">
<div className="section-header">
<h2>🛠 Tool Information</h2>
<p>Available engineering tools (execution requires approved plan)</p>
</div>
<div className="tool-selector">
<div className="tool-grid">
{tools.map((tool) => (
<button
key={tool.id}
className={`tool-card ${selectedTool === tool.id ? 'selected' : ''} ${tool.color}`}
onClick={() => setSelectedTool(tool.id)}
>
<div className="tool-icon">{tool.icon}</div>
<div className="tool-info">
<h3>{tool.name}</h3>
<p>{tool.description}</p>
</div>
</button>
))}
</div>
</div>
{selectedTool && (
<div className="execution-panel">
<div className="panel-header">
<h3>{tools.find(t => t.id === selectedTool)?.name} Information</h3>
</div>
<div className="panel-content">
<div className="tool-info">
<p><strong>Description:</strong> {tools.find(t => t.id === selectedTool)?.description}</p>
<p><strong>Usage:</strong> This tool is automatically executed as part of approved engineering plans.</p>
<p><strong>To use this tool:</strong></p>
<ol>
<li>Go to the Chat page</li>
<li>Ask an engineering question</li>
<li>Review the generated plan</li>
<li>Approve the plan</li>
<li>The system will automatically execute the appropriate tools</li>
</ol>
</div>
<div className="execution-actions">
<button
className="execute-btn disabled"
onClick={executeTool}
disabled={true}
>
🔒 Manual Execution Disabled
</button>
</div>
</div>
</div>
)}
</div>
{/* Execution History */}
<div className="execution-history">
<div className="section-header">
<h2>📋 Execution History</h2>
<p>View past tool executions and their results</p>
</div>
<div className="history-container">
{toolExecutions.length > 0 ? (
<div className="executions-list">
{toolExecutions.map((execution) => (
<div key={execution.id} className="execution-card">
<div className="execution-header">
<div className="execution-info">
<h4>
{tools.find(t => t.id === execution.tool_name)?.icon}
{tools.find(t => t.id === execution.tool_name)?.name || execution.tool_name}
</h4>
<div className="execution-meta">
<span className="execution-id">ID: {execution.id}</span>
<span className="execution-time">{formatDate(execution.created_at)}</span>
</div>
</div>
<div className="execution-status">
<span className={`status-badge ${getStatusColor(execution.status)}`}>
{execution.status}
</span>
{execution.status === 'failed' && (
<button
className="retry-btn"
onClick={() => retryExecution(execution.id)}
>
🔄 Retry
</button>
)}
</div>
</div>
<div className="execution-details">
<div className="detail-section">
<h5>Input Parameters</h5>
<pre className="code-block">
{JSON.stringify(execution.input_parameters, null, 2)}
</pre>
</div>
{execution.output_result && (
<div className="detail-section">
<h5>Output Result</h5>
<pre className="code-block">
{JSON.stringify(execution.output_result, null, 2)}
</pre>
</div>
)}
{execution.error_message && (
<div className="detail-section error">
<h5>Error Message</h5>
<pre className="error-block">
{execution.error_message}
</pre>
</div>
)}
<div className="execution-metrics">
<div className="metric">
<span className="metric-label">Duration:</span>
<span className="metric-value">{execution.duration || 'N/A'}ms</span>
</div>
<div className="metric">
<span className="metric-label">Tokens Used:</span>
<span className="metric-value">{execution.tokens_used || 'N/A'}</span>
</div>
</div>
</div>
</div>
))}
</div>
) : (
<div className="empty-state">
<div className="empty-icon">🔧</div>
<h3>No tool executions yet</h3>
<p>Execute your first tool to see the results here</p>
</div>
)}
</div>
</div>
</div>
);
};
export default ToolsPage;
+93
View File
@@ -0,0 +1,93 @@
# ===========================================
# REASON FLOW - ENVIRONMENT CONFIGURATION
# ===========================================
# Server Configuration
PORT=8000
NODE_ENV=development
HOST=localhost
# Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_NAME=reason_flow
DB_USER=postgres
DB_PASSWORD=your_password_here
DB_SSL=false
# Groq API Configuration
GROQ_API_KEY=your_groq_api_key_here
GROQ_MODEL=moonshotai/kimi-k2-instruct-0905
GROQ_BASE_URL=https://api.groq.com
# OpenAI API (for embeddings and additional models)
OPENAI_API_KEY=your_openai_api_key_here
OPENAI_BASE_URL=https://api.openai.com/v1
# JWT Configuration
JWT_SECRET=your_jwt_secret_here_make_it_long_and_secure
JWT_EXPIRES_IN=7d
# Admin User Configuration
ADMIN_EMAIL=admin@reasonflow.com
ADMIN_PASSWORD=admin123
ADMIN_FIRST_NAME=Admin
ADMIN_LAST_NAME=User
# File Upload Configuration
MAX_FILE_SIZE=50MB
UPLOAD_PATH=./uploads
ALLOWED_FILE_TYPES=pdf,txt,doc,docx
# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
# Logging Configuration
LOG_LEVEL=info
LOG_FILE=./logs/app.log
LOG_MAX_SIZE=10MB
LOG_MAX_FILES=5
# Vector Database Configuration
VECTOR_DB_URL=your_vector_db_url_here
VECTOR_DB_API_KEY=your_vector_db_api_key_here
# Web Search API Configuration
SERP_API_KEY=your_serp_api_key_here
SERP_ENGINE=google
# Redis Configuration (for caching and sessions)
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=your_redis_password_here
# Model Configuration
MODEL1_TEMPERATURE=0.3
MODEL1_MAX_TOKENS=3000
QUERYMODEL_TEMPERATURE=0.5
QUERYMODEL_MAX_TOKENS=4000
# Fine-tuning Configuration
FINE_TUNING_ENABLED=true
FINE_TUNING_SCHEDULE=weekly
FINE_TUNING_BATCH_SIZE=10
# Feedback Configuration
FEEDBACK_PROCESSING_ENABLED=true
FEEDBACK_BATCH_SIZE=50
FEEDBACK_PROCESSING_SCHEDULE=daily
# Security Configuration
CORS_ORIGIN=http://localhost:3000
HELMET_ENABLED=true
RATE_LIMIT_ENABLED=true
# Monitoring Configuration
HEALTH_CHECK_ENABLED=true
METRICS_ENABLED=true
PERFORMANCE_MONITORING=true
# Development Configuration
DEBUG_MODE=true
VERBOSE_LOGGING=true
HOT_RELOAD=true
+71
View File
@@ -0,0 +1,71 @@
{
"name": "reason-flow",
"version": "1.0.0",
"description": "AI-powered engineering reasoning system with continuous learning",
"main": "server/index.js",
"scripts": {
"start": "node server/index.js",
"dev": "nodemon server/index.js",
"build": "cd client && npm run build",
"install-all": "npm install && cd client && npm install",
"dev-full": "concurrently \"npm run dev\" \"cd client && npm start\"",
"db:setup": "node setup-database.js",
"db:init": "node server/utils/databaseManager.js init",
"db:reset": "node server/utils/databaseManager.js reset",
"db:migrate": "node server/utils/databaseManager.js migrate",
"db:seed": "node server/utils/databaseManager.js seed",
"db:status": "node server/utils/databaseManager.js status",
"db:info": "node server/utils/databaseManager.js info",
"test:groq": "node test-groq.js",
"env:setup": "node setup-env.js",
"env:validate": "node server/utils/validateConfig.js validate",
"env:show": "node server/utils/validateConfig.js show",
"config:validate": "node server/utils/validateConfig.js validate",
"config:show": "node server/utils/validateConfig.js show"
},
"dependencies": {
"@langchain/community": "^0.0.20",
"@langchain/openai": "^0.0.14",
"@xenova/transformers": "^2.17.2",
"axios": "^1.12.2",
"bcryptjs": "^2.4.3",
"cheerio": "^1.0.0-rc.12",
"compression": "^1.7.4",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"groq-sdk": "^0.7.0",
"helmet": "^7.1.0",
"joi": "^17.11.0",
"jsonwebtoken": "^9.0.2",
"langchain": "^0.1.0",
"ml-distance": "^4.0.1",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"openai": "^4.20.1",
"pdf-parse": "^1.1.1",
"pg": "^8.11.3",
"rate-limiter-flexible": "^3.0.6",
"sequelize": "^6.35.0",
"socket.io": "^4.7.4",
"uuid": "^9.0.1",
"winston": "^3.11.0"
},
"devDependencies": {
"@types/node": "^20.10.0",
"concurrently": "^8.2.2",
"jest": "^29.7.0",
"nodemon": "^3.0.2",
"supertest": "^6.3.3"
},
"keywords": [
"ai",
"engineering",
"reasoning",
"rag",
"fine-tuning",
"machine-learning"
],
"author": "Reason Flow Team",
"license": "MIT"
}
+224
View File
@@ -0,0 +1,224 @@
const path = require('path');
const logger = require('../utils/logger');
// Load environment variables
require('dotenv').config();
const appConfig = {
// Server Configuration
server: {
port: parseInt(process.env.PORT) || 8000,
host: process.env.HOST || 'localhost',
env: process.env.NODE_ENV || 'development',
corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:3000'
},
// Database Configuration
database: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432,
name: process.env.DB_NAME || 'reason_flow',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD,
ssl: process.env.DB_SSL === 'true'
},
// API Configuration
apis: {
groq: {
apiKey: process.env.GROQ_API_KEY,
model: process.env.GROQ_MODEL || 'moonshotai/kimi-k2-instruct-0905',
baseUrl: process.env.GROQ_BASE_URL || 'https://api.groq.com'
},
openai: {
apiKey: process.env.OPENAI_API_KEY,
baseUrl: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1'
},
serp: {
apiKey: process.env.SERP_API_KEY,
engine: process.env.SERP_ENGINE || 'google'
}
},
// Authentication Configuration
auth: {
jwtSecret: process.env.JWT_SECRET,
jwtExpiresIn: process.env.JWT_EXPIRES_IN || '7d',
adminEmail: process.env.ADMIN_EMAIL || 'admin@reasonflow.com',
adminPassword: process.env.ADMIN_PASSWORD || 'admin123',
adminFirstName: process.env.ADMIN_FIRST_NAME || 'Admin',
adminLastName: process.env.ADMIN_LAST_NAME || 'User'
},
// File Upload Configuration
upload: {
maxFileSize: process.env.MAX_FILE_SIZE || '50MB',
uploadPath: process.env.UPLOAD_PATH || './uploads',
allowedFileTypes: (process.env.ALLOWED_FILE_TYPES || 'pdf,txt,doc,docx').split(',')
},
// Rate Limiting Configuration
rateLimit: {
enabled: process.env.RATE_LIMIT_ENABLED !== 'false',
maxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 100,
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 900000
},
// Logging Configuration
logging: {
level: process.env.LOG_LEVEL || 'info',
file: process.env.LOG_FILE || './logs/app.log',
maxSize: process.env.LOG_MAX_SIZE || '10MB',
maxFiles: parseInt(process.env.LOG_MAX_FILES) || 5
},
// Model Configuration
models: {
model1: {
temperature: parseFloat(process.env.MODEL1_TEMPERATURE) || 0.3,
maxTokens: parseInt(process.env.MODEL1_MAX_TOKENS) || 3000
},
queryModel: {
temperature: parseFloat(process.env.QUERYMODEL_TEMPERATURE) || 0.5,
maxTokens: parseInt(process.env.QUERYMODEL_MAX_TOKENS) || 4000
}
},
// Fine-tuning Configuration
fineTuning: {
enabled: process.env.FINE_TUNING_ENABLED === 'true',
schedule: process.env.FINE_TUNING_SCHEDULE || 'weekly',
batchSize: parseInt(process.env.FINE_TUNING_BATCH_SIZE) || 10
},
// Feedback Configuration
feedback: {
processingEnabled: process.env.FEEDBACK_PROCESSING_ENABLED === 'true',
batchSize: parseInt(process.env.FEEDBACK_BATCH_SIZE) || 50,
schedule: process.env.FEEDBACK_PROCESSING_SCHEDULE || 'daily'
},
// Security Configuration
security: {
helmetEnabled: process.env.HELMET_ENABLED !== 'false',
corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:3000'
},
// Monitoring Configuration
monitoring: {
healthCheckEnabled: process.env.HEALTH_CHECK_ENABLED !== 'false',
metricsEnabled: process.env.METRICS_ENABLED === 'true',
performanceMonitoring: process.env.PERFORMANCE_MONITORING === 'true'
},
// Development Configuration
development: {
debugMode: process.env.DEBUG_MODE === 'true',
verboseLogging: process.env.VERBOSE_LOGGING === 'true',
hotReload: process.env.HOT_RELOAD === 'true'
},
// Redis Configuration
redis: {
url: process.env.REDIS_URL || 'redis://localhost:6379',
password: process.env.REDIS_PASSWORD
},
// Vector Database Configuration
vectorDb: {
url: process.env.VECTOR_DB_URL,
apiKey: process.env.VECTOR_DB_API_KEY
}
};
// Validation function
const validateConfig = () => {
const errors = [];
const warnings = [];
// Required fields validation
if (!appConfig.apis.groq.apiKey) {
errors.push('GROQ_API_KEY is required');
}
if (!appConfig.auth.jwtSecret) {
errors.push('JWT_SECRET is required');
}
if (!appConfig.database.password) {
errors.push('DB_PASSWORD is required');
}
// Production-specific validations
if (appConfig.server.env === 'production') {
if (appConfig.auth.jwtSecret === 'dev_secret_key_change_in_production') {
errors.push('JWT_SECRET must be changed for production');
}
if (!appConfig.apis.openai.apiKey) {
warnings.push('OPENAI_API_KEY is recommended for production');
}
if (appConfig.database.ssl === false) {
warnings.push('Database SSL is recommended for production');
}
}
// Development warnings
if (appConfig.server.env === 'development') {
if (appConfig.auth.jwtSecret === 'dev_secret_key_change_in_production') {
warnings.push('Using default JWT secret for development');
}
}
// Log warnings
warnings.forEach(warning => {
logger.warn(`Configuration warning: ${warning}`);
});
// Throw errors
if (errors.length > 0) {
const errorMessage = `Configuration errors: ${errors.join(', ')}`;
logger.error(errorMessage);
throw new Error(errorMessage);
}
logger.info('Configuration validated successfully');
return true;
};
// Get configuration for specific environment
const getConfigForEnv = (env) => {
const envConfig = { ...appConfig };
switch (env) {
case 'production':
envConfig.server.port = parseInt(process.env.PORT) || 8000;
envConfig.server.host = '0.0.0.0';
envConfig.development.debugMode = false;
envConfig.development.verboseLogging = false;
envConfig.logging.level = 'info';
break;
case 'test':
envConfig.server.port = parseInt(process.env.PORT) || 8001;
envConfig.database.name = process.env.DB_NAME || 'reason_flow_test';
envConfig.development.debugMode = false;
envConfig.logging.level = 'error';
break;
case 'development':
default:
envConfig.development.debugMode = true;
envConfig.logging.level = 'debug';
break;
}
return envConfig;
};
module.exports = {
appConfig,
validateConfig,
getConfigForEnv
};
+61
View File
@@ -0,0 +1,61 @@
const { Sequelize } = require('sequelize');
const logger = require('../utils/logger');
const { getConfig } = require('./databaseConfig');
const sequelize = new Sequelize(getConfig());
// Test database connection
const testConnection = async () => {
try {
await sequelize.authenticate();
logger.info('Database connection established successfully');
return true;
} catch (error) {
logger.error('Unable to connect to the database:', error);
return false;
}
};
// Sync database (create tables)
const syncDatabase = async (force = false) => {
try {
await sequelize.sync({ force, alter: !force });
logger.info(`Database synchronized successfully (force: ${force})`);
return true;
} catch (error) {
logger.error('Database synchronization failed:', error);
return false;
}
};
// Drop all tables
const dropDatabase = async () => {
try {
await sequelize.drop();
logger.info('Database dropped successfully');
return true;
} catch (error) {
logger.error('Database drop failed:', error);
return false;
}
};
// Close database connection
const closeConnection = async () => {
try {
await sequelize.close();
logger.info('Database connection closed');
return true;
} catch (error) {
logger.error('Error closing database connection:', error);
return false;
}
};
module.exports = {
sequelize,
testConnection,
syncDatabase,
dropDatabase,
closeConnection
};
+119
View File
@@ -0,0 +1,119 @@
const logger = require('../utils/logger');
const databaseConfig = {
development: {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
database: process.env.DB_NAME || 'reason_flow_dev',
username: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'password',
dialect: 'postgres',
logging: (msg) => logger.debug(msg),
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
define: {
timestamps: true,
underscored: true,
freezeTableName: true
}
},
test: {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
database: process.env.DB_NAME || 'reason_flow_test',
username: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'password',
dialect: 'postgres',
logging: false,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
define: {
timestamps: true,
underscored: true,
freezeTableName: true
}
},
production: {
host: process.env.DB_HOST,
port: process.env.DB_PORT || 5432,
database: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
dialect: 'postgres',
logging: false,
pool: {
max: 20,
min: 5,
acquire: 60000,
idle: 10000
},
define: {
timestamps: true,
underscored: true,
freezeTableName: true
},
dialectOptions: {
ssl: process.env.DB_SSL === 'true' ? {
require: true,
rejectUnauthorized: false
} : false
}
}
};
const getConfig = () => {
const env = process.env.NODE_ENV || 'development';
const config = databaseConfig[env];
if (!config) {
throw new Error(`Database configuration not found for environment: ${env}`);
}
// Validate required fields for production
if (env === 'production') {
const requiredFields = ['host', 'database', 'username', 'password'];
const missingFields = requiredFields.filter(field => !config[field]);
if (missingFields.length > 0) {
throw new Error(`Missing required database configuration: ${missingFields.join(', ')}`);
}
}
logger.info(`Using database configuration for environment: ${env}`);
return config;
};
const validateConnection = async (sequelize) => {
try {
await sequelize.authenticate();
logger.info('Database connection validated successfully');
return true;
} catch (error) {
logger.error('Database connection validation failed:', error);
return false;
}
};
const getDatabaseUrl = () => {
const config = getConfig();
const { host, port, database, username, password } = config;
return `postgresql://${username}:${password}@${host}:${port}/${database}`;
};
module.exports = {
databaseConfig,
getConfig,
validateConnection,
getDatabaseUrl
};
+83
View File
@@ -0,0 +1,83 @@
const logger = require('../utils/logger');
const groqConfig = {
// API Configuration
apiKey: process.env.GROQ_API_KEY,
model: process.env.GROQ_MODEL || 'moonshotai/kimi-k2-instruct-0905',
baseURL: process.env.GROQ_BASE_URL || 'https://api.groq.com',
// Model-specific configurations
models: {
MODEL1: {
model: 'moonshotai/kimi-k2-instruct-0905',
temperature: 0.3,
maxTokens: 300,
topP: 0.9,
systemPrompt: `You are MODEL1, an expert engineering reasoning system. Your primary function is to analyze complex engineering problems and create detailed, step-by-step plans to solve them.`
},
QUERYMODEL: {
model: 'moonshotai/kimi-k2-instruct-0905',
temperature: 0.5,
maxTokens: 400,
topP: 0.9,
systemPrompt: `You are QUERYMODEL, an expert engineering execution system. Your primary function is to execute engineering plans using various tools and resources.`
}
},
// Rate limiting
rateLimit: {
requestsPerMinute: 60,
requestsPerHour: 1000,
burstLimit: 10
},
// Retry configuration
retry: {
maxRetries: 3,
retryDelay: 1000,
backoffMultiplier: 2
},
// Timeout configuration
timeout: {
request: 30000, // 30 seconds
connection: 10000 // 10 seconds
},
// Logging configuration
logging: {
logRequests: process.env.NODE_ENV === 'development',
logResponses: process.env.NODE_ENV === 'development',
logErrors: true
}
};
const validateConfig = () => {
const errors = [];
if (!groqConfig.apiKey) {
errors.push('GROQ_API_KEY is required');
}
if (!groqConfig.model) {
errors.push('GROQ_MODEL is required');
}
if (errors.length > 0) {
logger.error('Groq configuration errors:', errors);
throw new Error(`Groq configuration invalid: ${errors.join(', ')}`);
}
logger.info('Groq configuration validated successfully');
return true;
};
const getModelConfig = (modelType) => {
return groqConfig.models[modelType] || groqConfig.models.MODEL1;
};
module.exports = {
groqConfig,
validateConfig,
getModelConfig
};
+214
View File
@@ -0,0 +1,214 @@
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const { User } = require('../models');
const logger = require('../utils/logger');
const generateToken = (userId) => {
return jwt.sign({ userId }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN || '7d'
});
};
const register = async (req, res) => {
try {
const { email, password, firstName, lastName, role = 'user' } = req.body;
// Validate input
if (!email || !password || !firstName || !lastName) {
return res.status(400).json({
success: false,
error: 'All fields are required'
});
}
// Check if user already exists
const existingUser = await User.findOne({ where: { email } });
if (existingUser) {
return res.status(400).json({
success: false,
error: 'User already exists with this email'
});
}
// Hash password
const saltRounds = 12;
const passwordHash = await bcrypt.hash(password, saltRounds);
// Create user
const user = await User.create({
email,
password_hash: passwordHash,
first_name: firstName,
last_name: lastName,
role
});
// Generate token
const token = generateToken(user.id);
logger.info(`New user registered: ${email}`);
res.status(201).json({
success: true,
data: {
user: {
id: user.id,
email: user.email,
firstName: user.first_name,
lastName: user.last_name,
role: user.role
},
token
}
});
} catch (error) {
logger.error('Registration error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const login = async (req, res) => {
try {
const { email, password } = req.body;
// Validate input
if (!email || !password) {
return res.status(400).json({
success: false,
error: 'Email and password are required'
});
}
// Find user
const user = await User.findOne({ where: { email } });
if (!user) {
return res.status(401).json({
success: false,
error: 'Invalid credentials'
});
}
// Check if user is active
if (!user.is_active) {
return res.status(401).json({
success: false,
error: 'Account is deactivated'
});
}
// Verify password
const isValidPassword = await bcrypt.compare(password, user.password_hash);
if (!isValidPassword) {
return res.status(401).json({
success: false,
error: 'Invalid credentials'
});
}
// Update last login
await user.update({ last_login: new Date() });
// Generate token
const token = generateToken(user.id);
logger.info(`User logged in: ${email}`);
res.json({
success: true,
data: {
user: {
id: user.id,
email: user.email,
firstName: user.first_name,
lastName: user.last_name,
role: user.role
},
token
}
});
} catch (error) {
logger.error('Login error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getProfile = async (req, res) => {
try {
const user = await User.findByPk(req.user.userId, {
attributes: { exclude: ['password_hash'] }
});
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
res.json({
success: true,
data: { user }
});
} catch (error) {
logger.error('Get profile error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const updateProfile = async (req, res) => {
try {
const { firstName, lastName, preferences } = req.body;
const user = await User.findByPk(req.user.userId);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
// Update user
const updateData = {};
if (firstName) updateData.first_name = firstName;
if (lastName) updateData.last_name = lastName;
if (preferences) updateData.preferences = { ...user.preferences, ...preferences };
await user.update(updateData);
res.json({
success: true,
data: {
user: {
id: user.id,
email: user.email,
firstName: user.first_name,
lastName: user.last_name,
role: user.role,
preferences: user.preferences
}
}
});
} catch (error) {
logger.error('Update profile error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
module.exports = {
register,
login,
getProfile,
updateProfile
};
+356
View File
@@ -0,0 +1,356 @@
const { Conversation, Message, Plan, User } = require('../models');
const logger = require('../utils/logger');
const model1Service = require('../services/model1Service');
const chatRouter = require('../services/chatRouter');
const createConversation = async (req, res) => {
try {
const { title } = req.body;
const userId = req.user.userId;
const conversation = await Conversation.create({
user_id: userId,
title: title || 'New Conversation',
status: 'active'
});
logger.info(`New conversation created: ${conversation.id}`);
res.status(201).json({
success: true,
data: { conversation }
});
} catch (error) {
logger.error('Create conversation error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getConversations = async (req, res) => {
try {
const userId = req.user.userId;
const { page = 1, limit = 10, status } = req.query;
const whereClause = { user_id: userId };
if (status) whereClause.status = status;
const conversations = await Conversation.findAndCountAll({
where: whereClause,
include: [
{
model: Message,
limit: 1,
order: [['created_at', 'DESC']]
}
],
order: [['created_at', 'DESC']],
limit: parseInt(limit),
offset: (parseInt(page) - 1) * parseInt(limit)
});
res.json({
success: true,
data: {
conversations: conversations.rows,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: conversations.count,
pages: Math.ceil(conversations.count / limit)
}
}
});
} catch (error) {
logger.error('Get conversations error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getConversation = async (req, res) => {
try {
const { conversationId } = req.params;
const userId = req.user.userId;
const conversation = await Conversation.findOne({
where: {
id: conversationId,
user_id: userId
},
include: [
{
model: Message,
order: [['created_at', 'ASC']]
},
{
model: Plan,
order: [['created_at', 'DESC']]
}
]
});
if (!conversation) {
return res.status(404).json({
success: false,
error: 'Conversation not found'
});
}
res.json({
success: true,
data: { conversation }
});
} catch (error) {
logger.error('Get conversation error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const sendMessage = async (req, res) => {
try {
const { conversationId, content, messageType = 'text' } = req.body;
const userId = req.user.userId;
// Verify conversation belongs to user
const conversation = await Conversation.findOne({
where: {
id: conversationId,
user_id: userId
}
});
if (!conversation) {
return res.status(404).json({
success: false,
error: 'Conversation not found'
});
}
// Create user message
const userMessage = await Message.create({
conversation_id: conversationId,
role: 'user',
content,
message_type: messageType
});
// Get conversation history for context
const conversationHistory = await Message.findAll({
where: { conversation_id: conversationId },
order: [['created_at', 'DESC']],
limit: 20 // Last 20 messages for context
});
// Create properly ordered conversation history for context
const orderedHistory = conversationHistory.reverse().map(msg => ({
role: msg.role,
content: msg.content,
message_type: msg.message_type,
timestamp: msg.created_at
}));
// Route the message to determine response type
const routingDecision = await chatRouter.routeMessage(content, {
conversationId,
userId,
timestamp: new Date().toISOString(),
conversationHistory: orderedHistory
});
let assistantMessage;
if (routingDecision.responseType === 'plan_needed' && routingDecision.isEngineeringQuestion) {
// Generate engineering plan using MODEL1
try {
const planData = await model1Service.generatePlan(content, {
conversationId,
userId,
timestamp: new Date().toISOString()
});
// Save the plan to database
const plan = await Plan.create({
conversation_id: conversationId,
title: planData.title,
description: planData.description,
steps: planData.steps,
status: 'draft',
tools_required: planData.toolsRequired || [],
estimated_duration: planData.estimatedDuration,
complexity_score: planData.complexityScore,
metadata: {
safetyConsiderations: planData.safetyConsiderations,
qualityChecks: planData.qualityChecks,
processingTime: planData.processingTime,
tokensUsed: planData.tokensUsed,
model: planData.model
}
});
// Create assistant message with plan
assistantMessage = await Message.create({
conversation_id: conversationId,
plan_id: plan.id,
role: 'assistant',
content: planData.rawContent || `# ${planData.title}\n\n${planData.description}\n\n## Steps:\n${planData.steps.map((step, index) => `${index + 1}. ${step}`).join('\n')}`,
message_type: 'plan',
metadata: {
modelType: 'MODEL1',
processingTime: planData.processingTime,
tokensUsed: planData.tokensUsed,
routingDecision: routingDecision.reasoning
}
});
logger.info(`MODEL1 plan generated: ${plan.id} for conversation: ${conversationId}`);
} catch (error) {
logger.error('MODEL1 plan generation failed:', error);
// Fallback to simple response if plan generation fails
const fallbackResponse = await chatRouter.generateSimpleResponse(content, {
conversationHistory: orderedHistory
});
assistantMessage = await Message.create({
conversation_id: conversationId,
role: 'assistant',
content: fallbackResponse,
message_type: 'text',
metadata: {
error: error.message,
modelType: 'FALLBACK',
routingDecision: routingDecision.reasoning
}
});
}
} else {
// Generate simple conversational response
const responseContent = routingDecision.response || await chatRouter.generateSimpleResponse(content, {
conversationHistory: orderedHistory
});
assistantMessage = await Message.create({
conversation_id: conversationId,
role: 'assistant',
content: responseContent,
message_type: 'text',
metadata: {
modelType: 'REASONAI',
routingDecision: routingDecision.reasoning,
responseType: routingDecision.responseType
}
});
logger.info(`Simple response generated for conversation: ${conversationId}`, {
responseType: routingDecision.responseType,
reasoning: routingDecision.reasoning
});
}
logger.info(`Message sent in conversation: ${conversationId}`);
res.json({
success: true,
data: { userMessage, assistantMessage }
});
} catch (error) {
logger.error('Send message error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const updateConversation = async (req, res) => {
try {
const { conversationId } = req.params;
const { title, status } = req.body;
const userId = req.user.userId;
const conversation = await Conversation.findOne({
where: {
id: conversationId,
user_id: userId
}
});
if (!conversation) {
return res.status(404).json({
success: false,
error: 'Conversation not found'
});
}
const updateData = {};
if (title) updateData.title = title;
if (status) updateData.status = status;
await conversation.update(updateData);
logger.info(`Conversation updated: ${conversationId}`);
res.json({
success: true,
data: { conversation }
});
} catch (error) {
logger.error('Update conversation error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const deleteConversation = async (req, res) => {
try {
const { conversationId } = req.params;
const userId = req.user.userId;
const conversation = await Conversation.findOne({
where: {
id: conversationId,
user_id: userId
}
});
if (!conversation) {
return res.status(404).json({
success: false,
error: 'Conversation not found'
});
}
await conversation.destroy();
logger.info(`Conversation deleted: ${conversationId}`);
res.json({
success: true,
message: 'Conversation deleted successfully'
});
} catch (error) {
logger.error('Delete conversation error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
module.exports = {
createConversation,
getConversations,
getConversation,
sendMessage,
updateConversation,
deleteConversation
};
+299
View File
@@ -0,0 +1,299 @@
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const pdf = require('pdf-parse');
const { Document, sequelize } = require('../models');
const { Op } = require('sequelize');
const logger = require('../utils/logger');
const embeddingService = require('../services/embeddingService');
const graphRagService = require('../services/graphRagService');
// Configure multer for file uploads
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadPath = path.join(__dirname, '../../uploads');
if (!fs.existsSync(uploadPath)) {
fs.mkdirSync(uploadPath, { recursive: true });
}
cb(null, uploadPath);
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
});
const upload = multer({
storage: storage,
limits: {
fileSize: 10 * 1024 * 1024, // 10MB for testing
fieldSize: 10 * 1024 * 1024, // 10MB for field values
fieldNameSize: 100, // 100 bytes for field names
files: 1 // Only 1 file at a time
},
fileFilter: (req, file, cb) => {
const allowedTypes = ['.pdf', '.txt', '.doc', '.docx'];
const ext = path.extname(file.originalname).toLowerCase();
if (allowedTypes.includes(ext)) {
cb(null, true);
} else {
cb(new Error('Invalid file type. Only PDF, TXT, DOC, DOCX files are allowed.'));
}
}
});
const uploadDocument = async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({
success: false,
error: 'No file uploaded'
});
}
const { category, tags } = req.body;
let extractedText = '';
// Extract text from PDF
if (req.file.mimetype === 'application/pdf') {
try {
const dataBuffer = fs.readFileSync(req.file.path);
const pdfData = await pdf(dataBuffer);
extractedText = pdfData.text;
} catch (error) {
logger.error('PDF extraction error:', error);
extractedText = 'Error extracting text from PDF';
}
} else if (req.file.mimetype === 'text/plain') {
extractedText = fs.readFileSync(req.file.path, 'utf8');
}
// Create document record
const document = await Document.create({
filename: req.file.filename,
original_filename: req.file.originalname,
file_path: req.file.path,
file_type: req.file.mimetype,
file_size: req.file.size,
content: extractedText,
extracted_text: extractedText,
category: category || 'general',
tags: tags ? tags.split(',').map(tag => tag.trim()) : [],
indexing_status: 'processing'
});
// Generate and store embeddings (if text available)
if (extractedText && extractedText.trim().length > 0) {
try {
const embedding = await embeddingService.embedText(extractedText.slice(0, 15000));
await document.update({ embeddings: embedding, is_indexed: true, indexing_status: 'completed' });
} catch (e) {
logger.error('Embedding generation failed:', e);
await document.update({ is_indexed: false, indexing_status: 'failed' });
}
} else {
await document.update({ is_indexed: false, indexing_status: 'failed' });
}
logger.info(`Document uploaded: ${document.id}`);
res.status(201).json({
success: true,
data: { document }
});
} catch (error) {
logger.error('Upload document error:', error);
// Clean up uploaded file if document creation failed
if (req.file && fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path);
}
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getDocuments = async (req, res) => {
try {
const { page = 1, limit = 10, category, search, isIndexed } = req.query;
const whereClause = {};
if (category) whereClause.category = category;
if (isIndexed !== undefined) whereClause.is_indexed = isIndexed === 'true';
if (search) {
whereClause[Op.or] = [
{ original_filename: { [Op.iLike]: `%${search}%` } },
{ extracted_text: { [Op.iLike]: `%${search}%` } }
];
}
const documents = await Document.findAndCountAll({
where: whereClause,
order: [['created_at', 'DESC']],
limit: parseInt(limit),
offset: (parseInt(page) - 1) * parseInt(limit)
});
res.json({
success: true,
data: {
documents: documents.rows,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: documents.count,
pages: Math.ceil(documents.count / parseInt(limit))
}
}
});
} catch (error) {
logger.error('Get documents error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getDocument = async (req, res) => {
try {
const { documentId } = req.params;
const document = await Document.findByPk(documentId);
if (!document) {
return res.status(404).json({
success: false,
error: 'Document not found'
});
}
res.json({
success: true,
data: { document }
});
} catch (error) {
logger.error('Get document error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const searchDocuments = async (req, res) => {
try {
const { query, category, limit = 10 } = req.query;
if (!query) {
return res.status(400).json({
success: false,
error: 'Search query is required'
});
}
const whereClause = {
is_indexed: true,
...(category ? { category } : {})
};
// Embed query and compute cosine similarity in JS for now
const queryEmbedding = await embeddingService.embedText(query);
const candidates = await Document.findAll({
where: whereClause,
attributes: ['id', 'original_filename', 'extracted_text', 'embeddings', 'category', 'created_at']
});
const scored = [];
for (const doc of candidates) {
const emb = doc.embeddings || [];
const score = embeddingService.cosineSimilarity(queryEmbedding, emb);
scored.push({
id: doc.id,
original_filename: doc.original_filename,
snippet: (doc.extracted_text || '').slice(0, 300),
category: doc.category,
created_at: doc.created_at,
score
});
}
scored.sort((a, b) => b.score - a.score);
const top = scored.slice(0, parseInt(limit));
res.json({ success: true, data: { results: top } });
} catch (error) {
logger.error('Search documents error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const graphSearchDocuments = async (req, res) => {
try {
const { query, category } = req.query;
if (!query) {
return res.status(400).json({ success: false, error: 'Search query is required' });
}
const result = await graphRagService.graphSearch({ query, category });
res.json({ success: true, data: result });
} catch (error) {
logger.error('Graph search error:', error);
res.status(500).json({ success: false, error: 'Internal server error' });
}
};
const deleteDocument = async (req, res) => {
try {
const { documentId } = req.params;
const document = await Document.findByPk(documentId);
if (!document) {
return res.status(404).json({
success: false,
error: 'Document not found'
});
}
// Delete physical file
if (fs.existsSync(document.file_path)) {
fs.unlinkSync(document.file_path);
}
// Delete database record
await document.destroy();
logger.info(`Document deleted: ${documentId}`);
res.json({
success: true,
message: 'Document deleted successfully'
});
} catch (error) {
logger.error('Delete document error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
module.exports = {
uploadDocument,
getDocuments,
getDocument,
searchDocuments,
graphSearchDocuments,
deleteDocument,
upload // Export multer middleware
};
+220
View File
@@ -0,0 +1,220 @@
const { Feedback, Message, User, sequelize } = require('../models');
const { Op } = require('sequelize');
const logger = require('../utils/logger');
const submitFeedback = async (req, res) => {
try {
const { messageId, feedbackType, rating, comment, correctedContent } = req.body;
const userId = req.user.userId;
// Validate feedback type
const validTypes = ['positive', 'negative', 'correction', 'suggestion'];
if (!validTypes.includes(feedbackType)) {
return res.status(400).json({
success: false,
error: 'Invalid feedback type'
});
}
// Validate rating if provided
if (rating && (rating < 1 || rating > 5)) {
return res.status(400).json({
success: false,
error: 'Rating must be between 1 and 5'
});
}
// Verify message exists if messageId provided
if (messageId) {
const message = await Message.findByPk(messageId);
if (!message) {
return res.status(404).json({
success: false,
error: 'Message not found'
});
}
}
const feedback = await Feedback.create({
user_id: userId,
message_id: messageId,
feedback_type: feedbackType,
rating,
comment,
corrected_content: correctedContent,
is_processed: false
});
logger.info(`Feedback submitted: ${feedback.id} by user ${userId}`);
res.status(201).json({
success: true,
data: { feedback }
});
} catch (error) {
logger.error('Submit feedback error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getFeedback = async (req, res) => {
try {
const { feedbackId } = req.params;
const feedback = await Feedback.findByPk(feedbackId, {
include: [
{
model: User,
attributes: ['id', 'email', 'first_name', 'last_name']
},
{
model: Message,
attributes: ['id', 'content', 'role']
}
]
});
if (!feedback) {
return res.status(404).json({
success: false,
error: 'Feedback not found'
});
}
res.json({
success: true,
data: { feedback }
});
} catch (error) {
logger.error('Get feedback error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getFeedbackList = async (req, res) => {
try {
const { page = 1, limit = 10, feedbackType, isProcessed, userId } = req.query;
const whereClause = {};
if (feedbackType) whereClause.feedback_type = feedbackType;
if (isProcessed !== undefined) whereClause.is_processed = isProcessed === 'true';
if (userId) whereClause.user_id = userId;
const feedback = await Feedback.findAndCountAll({
where: whereClause,
include: [
{
model: User,
attributes: ['id', 'email', 'first_name', 'last_name']
},
{
model: Message,
attributes: ['id', 'content', 'role']
}
],
order: [['created_at', 'DESC']],
limit: parseInt(limit),
offset: (parseInt(page) - 1) * parseInt(limit)
});
res.json({
success: true,
data: {
feedback: feedback.rows,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: feedback.count,
pages: Math.ceil(feedback.count / parseInt(limit))
}
}
});
} catch (error) {
logger.error('Get feedback list error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const processFeedback = async (req, res) => {
try {
const { feedbackId } = req.params;
const { processingNotes } = req.body;
const feedback = await Feedback.findByPk(feedbackId);
if (!feedback) {
return res.status(404).json({
success: false,
error: 'Feedback not found'
});
}
await feedback.update({
is_processed: true,
processing_notes: processingNotes
});
logger.info(`Feedback processed: ${feedbackId}`);
res.json({
success: true,
data: { feedback }
});
} catch (error) {
logger.error('Process feedback error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getFeedbackStats = async (req, res) => {
try {
const stats = await Feedback.findAll({
attributes: [
'feedback_type',
[sequelize.fn('COUNT', sequelize.col('id')), 'count'],
[sequelize.fn('AVG', sequelize.col('rating')), 'avg_rating']
],
group: ['feedback_type'],
raw: true
});
const totalFeedback = await Feedback.count();
const processedFeedback = await Feedback.count({ where: { is_processed: true } });
res.json({
success: true,
data: {
stats,
total: totalFeedback,
processed: processedFeedback,
unprocessed: totalFeedback - processedFeedback
}
});
} catch (error) {
logger.error('Get feedback stats error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
module.exports = {
submitFeedback,
getFeedback,
getFeedbackList,
processFeedback,
getFeedbackStats
};
+198
View File
@@ -0,0 +1,198 @@
const model1Service = require('../services/model1Service');
const { Plan, Conversation } = require('../models');
const logger = require('../utils/logger');
const generatePlan = async (req, res) => {
try {
const { query, conversationId, context = {} } = req.body;
const userId = req.user.userId;
if (!query) {
return res.status(400).json({
success: false,
error: 'Query is required'
});
}
// Verify conversation belongs to user
if (conversationId) {
const conversation = await Conversation.findOne({
where: {
id: conversationId,
user_id: userId
}
});
if (!conversation) {
return res.status(404).json({
success: false,
error: 'Conversation not found'
});
}
}
// Generate plan using MODEL1
const planData = await model1Service.generatePlan(query, context);
// Save plan if conversationId provided
let savedPlan = null;
if (conversationId) {
savedPlan = await model1Service.savePlan(planData, conversationId, userId);
}
logger.info(`MODEL1 plan generated for user: ${userId}`);
res.json({
success: true,
data: {
plan: savedPlan || planData,
processingTime: planData.processingTime,
tokensUsed: planData.tokensUsed,
model: planData.model
}
});
} catch (error) {
logger.error('MODEL1 plan generation error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
};
const validatePlan = async (req, res) => {
try {
const { planId } = req.params;
const { feedback = [] } = req.body;
const result = await model1Service.validatePlan(planId, feedback);
res.json({
success: true,
data: result
});
} catch (error) {
logger.error('MODEL1 plan validation error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
};
const getPlan = async (req, res) => {
try {
const { planId } = req.params;
const userId = req.user.userId;
const plan = await Plan.findOne({
where: {
id: planId
},
include: [
{
model: Conversation,
where: {
user_id: userId
},
attributes: ['id', 'title', 'user_id']
}
]
});
if (!plan) {
return res.status(404).json({
success: false,
error: 'Plan not found'
});
}
res.json({
success: true,
data: { plan }
});
} catch (error) {
logger.error('Get plan error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const updatePlan = async (req, res) => {
try {
const { planId } = req.params;
const { title, description, steps, status, approvalFeedback } = req.body;
const userId = req.user.userId;
const plan = await Plan.findOne({
where: {
id: planId
},
include: [
{
model: Conversation,
where: {
user_id: userId
},
attributes: ['id', 'title', 'user_id']
}
]
});
if (!plan) {
return res.status(404).json({
success: false,
error: 'Plan not found'
});
}
const updateData = {};
if (title) updateData.title = title;
if (description) updateData.description = description;
if (steps) updateData.steps = steps;
if (status) updateData.status = status;
if (approvalFeedback) updateData.approval_feedback = approvalFeedback;
await plan.update(updateData);
logger.info(`Plan updated: ${planId}, status: ${status}`);
res.json({
success: true,
data: { plan }
});
} catch (error) {
logger.error('Update plan error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getModelStatus = async (req, res) => {
try {
const status = await model1Service.getModelStatus();
res.json({
success: true,
data: status
});
} catch (error) {
logger.error('MODEL1 status error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
};
module.exports = {
generatePlan,
validatePlan,
getPlan,
updatePlan,
getModelStatus
};
+265
View File
@@ -0,0 +1,265 @@
const { ModelVersion, TrainingData } = require('../models');
const logger = require('../utils/logger');
const getModelStatus = async (req, res) => {
try {
const activeModels = await ModelVersion.findAll({
where: { is_active: true },
order: [['created_at', 'DESC']]
});
const modelStatus = {
MODEL1: null,
QUERYMODEL: null
};
activeModels.forEach(model => {
if (model.model_type === 'MODEL1') {
modelStatus.MODEL1 = {
id: model.id,
version: model.version,
deployment_status: model.deployment_status,
performance_metrics: model.performance_metrics,
last_updated: model.updated_at
};
} else if (model.model_type === 'QUERYMODEL') {
modelStatus.QUERYMODEL = {
id: model.id,
version: model.version,
deployment_status: model.deployment_status,
performance_metrics: model.performance_metrics,
last_updated: model.updated_at
};
}
});
res.json({
success: true,
data: { modelStatus }
});
} catch (error) {
logger.error('Get model status error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getModelVersions = async (req, res) => {
try {
const { modelType, page = 1, limit = 10 } = req.query;
const whereClause = {};
if (modelType) whereClause.model_type = modelType;
const models = await ModelVersion.findAndCountAll({
where: whereClause,
order: [['created_at', 'DESC']],
limit: parseInt(limit),
offset: (parseInt(page) - 1) * parseInt(limit)
});
res.json({
success: true,
data: {
models: models.rows,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: models.count,
pages: Math.ceil(models.count / parseInt(limit))
}
}
});
} catch (error) {
logger.error('Get model versions error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const createModelVersion = async (req, res) => {
try {
const {
modelName,
modelType,
baseModel,
fineTuningMethod,
hyperparameters = {}
} = req.body;
if (!modelName || !modelType || !baseModel) {
return res.status(400).json({
success: false,
error: 'Model name, type, and base model are required'
});
}
const validModelTypes = ['MODEL1', 'QUERYMODEL'];
if (!validModelTypes.includes(modelType)) {
return res.status(400).json({
success: false,
error: 'Invalid model type'
});
}
// Deactivate current active model of same type
await ModelVersion.update(
{ is_active: false },
{ where: { model_type: modelType, is_active: true } }
);
const model = await ModelVersion.create({
model_name: modelName,
version: `v${Date.now()}`,
model_type: modelType,
base_model: baseModel,
fine_tuning_method: fineTuningMethod,
hyperparameters,
deployment_status: 'training'
});
logger.info(`New model version created: ${model.id}`);
res.status(201).json({
success: true,
data: { model }
});
} catch (error) {
logger.error('Create model version error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const updateModelVersion = async (req, res) => {
try {
const { modelId } = req.params;
const {
deploymentStatus,
performanceMetrics,
modelPath,
trainingLog
} = req.body;
const model = await ModelVersion.findByPk(modelId);
if (!model) {
return res.status(404).json({
success: false,
error: 'Model version not found'
});
}
const updateData = {};
if (deploymentStatus) updateData.deployment_status = deploymentStatus;
if (performanceMetrics) updateData.performance_metrics = performanceMetrics;
if (modelPath) updateData.model_path = modelPath;
if (trainingLog) updateData.training_log = trainingLog;
await model.update(updateData);
logger.info(`Model version updated: ${modelId}`);
res.json({
success: true,
data: { model }
});
} catch (error) {
logger.error('Update model version error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const activateModel = async (req, res) => {
try {
const { modelId } = req.params;
const model = await ModelVersion.findByPk(modelId);
if (!model) {
return res.status(404).json({
success: false,
error: 'Model version not found'
});
}
// Deactivate other models of same type
await ModelVersion.update(
{ is_active: false },
{ where: { model_type: model.model_type, is_active: true } }
);
// Activate this model
await model.update({
is_active: true,
deployment_status: 'deployed'
});
logger.info(`Model activated: ${modelId}`);
res.json({
success: true,
data: { model }
});
} catch (error) {
logger.error('Activate model error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getTrainingData = async (req, res) => {
try {
const { modelVersionId, dataType, page = 1, limit = 10 } = req.query;
const whereClause = {};
if (modelVersionId) whereClause.model_version_id = modelVersionId;
if (dataType) whereClause.data_type = dataType;
const trainingData = await TrainingData.findAndCountAll({
where: whereClause,
order: [['created_at', 'DESC']],
limit: parseInt(limit),
offset: (parseInt(page) - 1) * parseInt(limit)
});
res.json({
success: true,
data: {
trainingData: trainingData.rows,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: trainingData.count,
pages: Math.ceil(trainingData.count / parseInt(limit))
}
}
});
} catch (error) {
logger.error('Get training data error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
module.exports = {
getModelStatus,
getModelVersions,
createModelVersion,
updateModelVersion,
activateModel,
getTrainingData
};
+350
View File
@@ -0,0 +1,350 @@
const queryModelService = require('../services/queryModelService');
const { Plan, ToolExecution, Conversation, Message } = require('../models');
const logger = require('../utils/logger');
const executePlan = async (req, res) => {
try {
const { planId, options = {} } = req.body;
const userId = req.user.userId;
if (!planId) {
return res.status(400).json({
success: false,
error: 'Plan ID is required'
});
}
// Verify plan belongs to user
const plan = await Plan.findOne({
where: {
id: planId
},
include: [
{
model: Conversation,
where: {
user_id: userId
},
attributes: ['id', 'title', 'user_id']
}
]
});
if (!plan) {
return res.status(404).json({
success: false,
error: 'Plan not found'
});
}
if (plan.status !== 'approved') {
return res.status(400).json({
success: false,
error: 'Plan must be approved before execution'
});
}
// Execute plan using QUERYMODEL
const result = await queryModelService.executePlan(planId, options);
logger.info(`QUERYMODEL execution completed for plan: ${planId}`);
res.json({
success: true,
data: result
});
} catch (error) {
logger.error('QUERYMODEL execution error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
};
const executeTool = async (req, res) => {
try {
const { planId, toolName, toolType, inputParameters } = req.body;
const userId = req.user.userId;
if (!planId || !toolName || !toolType || !inputParameters) {
return res.status(400).json({
success: false,
error: 'Plan ID, tool name, type, and input parameters are required'
});
}
// Verify plan belongs to user
const plan = await Plan.findOne({
where: {
id: planId
},
include: [
{
model: Conversation,
where: {
user_id: userId
},
attributes: ['id', 'title', 'user_id']
}
]
});
if (!plan) {
return res.status(404).json({
success: false,
error: 'Plan not found'
});
}
// Execute tool using QUERYMODEL
const result = await queryModelService.executeTool(toolName, toolType, inputParameters, planId);
logger.info(`Tool executed: ${toolName} for plan: ${planId}`);
res.json({
success: true,
data: { toolExecution: result }
});
} catch (error) {
logger.error('Execute tool error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const orchestratePlan = async (req, res) => {
try {
const { planId, options = {} } = req.body;
const userId = req.user.userId;
if (!planId) {
return res.status(400).json({
success: false,
error: 'Plan ID is required'
});
}
// Verify plan belongs to user
const plan = await Plan.findOne({
where: {
id: planId
},
include: [
{
model: Conversation,
where: {
user_id: userId
},
attributes: ['id', 'title', 'user_id']
}
]
});
if (!plan) {
return res.status(404).json({
success: false,
error: 'Plan not found'
});
}
if (plan.status !== 'approved') {
return res.status(400).json({
success: false,
error: 'Plan must be approved before orchestration'
});
}
// Get the original user query from the conversation
const originalMessage = await Message.findOne({
where: {
conversation_id: plan.conversation_id,
role: 'user'
},
order: [['created_at', 'DESC']]
});
const originalQuery = originalMessage ? originalMessage.content : plan.title;
// Execute orchestration using QUERYMODEL
const result = await queryModelService.executeOrchestrate({
query: originalQuery,
category: 'engineering',
topK: options.topK || 5,
generateReport: true
});
logger.info(`QUERYMODEL orchestration completed for plan: ${planId}`);
res.json({
success: true,
data: { toolExecution: result }
});
} catch (error) {
logger.error('QUERYMODEL orchestration error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
};
const getExecutionStatus = async (req, res) => {
try {
const { planId } = req.params;
const userId = req.user.userId;
const plan = await Plan.findOne({
where: {
id: planId
},
include: [
{
model: Conversation,
where: {
user_id: userId
},
attributes: ['id', 'title', 'user_id']
}
]
});
if (!plan) {
return res.status(404).json({
success: false,
error: 'Plan not found'
});
}
// Get tool executions for this plan
const toolExecutions = await ToolExecution.findAll({
where: {
plan_id: planId
},
order: [['created_at', 'DESC']]
});
res.json({
success: true,
data: {
plan,
toolExecutions
}
});
} catch (error) {
logger.error('Get execution status error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getToolExecutions = async (req, res) => {
try {
const { planId, toolType, status, page = 1, limit = 10 } = req.query;
const userId = req.user.userId;
const whereClause = {};
if (planId) whereClause.plan_id = planId;
if (toolType) whereClause.tool_type = toolType;
if (status) whereClause.status = status;
// Verify plan belongs to user if planId provided
if (planId) {
const plan = await Plan.findOne({
where: {
id: planId
},
include: [
{
model: Conversation,
where: {
user_id: userId
},
attributes: ['id', 'title', 'user_id']
}
]
});
if (!plan) {
return res.status(404).json({
success: false,
error: 'Plan not found'
});
}
}
const offset = (page - 1) * limit;
const { count, rows: executions } = await ToolExecution.findAndCountAll({
where: whereClause,
include: [
{
model: Plan,
where: {
conversation: {
user_id: userId
}
},
include: [
{
model: Conversation,
attributes: ['id', 'title', 'user_id']
}
]
}
],
order: [['created_at', 'DESC']],
limit: parseInt(limit),
offset: parseInt(offset)
});
res.json({
success: true,
data: {
executions,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: count,
pages: Math.ceil(count / limit)
}
}
});
} catch (error) {
logger.error('Get tool executions error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getModelStatus = async (req, res) => {
try {
const status = await queryModelService.getModelStatus();
res.json({
success: true,
data: status
});
} catch (error) {
logger.error('QUERYMODEL status error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
};
module.exports = {
executePlan,
executeTool,
orchestratePlan,
getExecutionStatus,
getToolExecutions,
getModelStatus
};
+250
View File
@@ -0,0 +1,250 @@
const { ToolExecution, Plan, Document, sequelize } = require('../models');
const logger = require('../utils/logger');
const queryModelService = require('../services/queryModelService');
const executeTool = async (req, res) => {
try {
const { planId, toolName, toolType, inputParameters } = req.body;
if (!planId || !toolName || !toolType || !inputParameters) {
return res.status(400).json({
success: false,
error: 'Plan ID, tool name, type, and input parameters are required'
});
}
// Verify plan exists
const plan = await Plan.findByPk(planId);
if (!plan) {
return res.status(404).json({
success: false,
error: 'Plan not found'
});
}
// Execute tool using QUERYMODEL service
const toolExecution = await queryModelService.executeTool(
toolName,
toolType,
inputParameters,
planId
);
logger.info(`Tool executed: ${toolName} for plan ${planId}`);
res.json({
success: true,
data: { toolExecution }
});
} catch (error) {
logger.error('Execute tool error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getToolExecutions = async (req, res) => {
try {
const { planId, toolType, status, page = 1, limit = 10 } = req.query;
const whereClause = {};
if (planId) whereClause.plan_id = planId;
if (toolType) whereClause.tool_type = toolType;
if (status) whereClause.status = status;
const toolExecutions = await ToolExecution.findAndCountAll({
where: whereClause,
include: [
{
model: Plan,
attributes: ['id', 'title', 'status']
},
{
model: Document,
attributes: ['id', 'filename', 'original_filename']
}
],
order: [['created_at', 'DESC']],
limit: parseInt(limit),
offset: (parseInt(page) - 1) * parseInt(limit)
});
res.json({
success: true,
data: {
toolExecutions: toolExecutions.rows,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: toolExecutions.count,
pages: Math.ceil(toolExecutions.count / parseInt(limit))
}
}
});
} catch (error) {
logger.error('Get tool executions error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getToolExecution = async (req, res) => {
try {
const { executionId } = req.params;
const toolExecution = await ToolExecution.findByPk(executionId, {
include: [
{
model: Plan,
attributes: ['id', 'title', 'status']
},
{
model: Document,
attributes: ['id', 'filename', 'original_filename']
}
]
});
if (!toolExecution) {
return res.status(404).json({
success: false,
error: 'Tool execution not found'
});
}
res.json({
success: true,
data: { toolExecution }
});
} catch (error) {
logger.error('Get tool execution error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const getToolStats = async (req, res) => {
try {
const stats = await ToolExecution.findAll({
attributes: [
'tool_name',
'tool_type',
[sequelize.fn('COUNT', sequelize.col('id')), 'count'],
[sequelize.fn('AVG', sequelize.col('execution_time')), 'avg_execution_time'],
[sequelize.fn('SUM', sequelize.col('tokens_used')), 'total_tokens']
],
group: ['tool_name', 'tool_type'],
raw: true
});
const totalExecutions = await ToolExecution.count();
const successfulExecutions = await ToolExecution.count({ where: { status: 'completed' } });
const failedExecutions = await ToolExecution.count({ where: { status: 'failed' } });
res.json({
success: true,
data: {
stats,
summary: {
total: totalExecutions,
successful: successfulExecutions,
failed: failedExecutions,
successRate: totalExecutions > 0 ? (successfulExecutions / totalExecutions) * 100 : 0
}
}
});
} catch (error) {
logger.error('Get tool stats error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const retryToolExecution = async (req, res) => {
try {
const { executionId } = req.params;
const toolExecution = await ToolExecution.findByPk(executionId);
if (!toolExecution) {
return res.status(404).json({
success: false,
error: 'Tool execution not found'
});
}
if (toolExecution.status === 'running') {
return res.status(400).json({
success: false,
error: 'Tool execution is already running'
});
}
// Reset execution status
await toolExecution.update({
status: 'pending',
output_result: null,
execution_time: null,
error_message: null
});
// Retry tool execution using QUERYMODEL service
try {
const retryExecution = await queryModelService.executeTool(
toolExecution.tool_name,
toolExecution.tool_type,
toolExecution.input_parameters,
toolExecution.plan_id
);
// Update the original execution record with retry results
await toolExecution.update({
output_result: retryExecution.output_result,
status: retryExecution.status,
execution_time: retryExecution.execution_time,
tokens_used: retryExecution.tokens_used,
error_message: null
});
logger.info(`Tool execution retried successfully: ${executionId}`);
} catch (retryError) {
logger.error(`Tool execution retry failed: ${executionId}`, retryError);
await toolExecution.update({
status: 'failed',
error_message: retryError.message
});
throw retryError;
}
logger.info(`Tool execution retried: ${executionId}`);
res.json({
success: true,
data: { toolExecution }
});
} catch (error) {
logger.error('Retry tool execution error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
module.exports = {
executeTool,
getToolExecutions,
getToolExecution,
getToolStats,
retryToolExecution
};
+110
View File
@@ -0,0 +1,110 @@
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const morgan = require('morgan');
const path = require('path');
require('dotenv').config();
const { sequelize } = require('./config/database');
const logger = require('./utils/logger');
//const rateLimiter = require('./middleware/rateLimiter');
const errorHandler = require('./middleware/errorHandler');
// Import routes
const authRoutes = require('./routes/auth');
const modelRoutes = require('./routes/models');
const chatRoutes = require('./routes/chat');
const documentRoutes = require('./routes/documents');
const feedbackRoutes = require('./routes/feedback');
const toolRoutes = require('./routes/tools');
const app = express();
const PORT = process.env.PORT || 8000;
// Security middleware
app.use(helmet());
app.use(cors({
origin: process.env.NODE_ENV === 'production'
? ['https://yourdomain.com']
: ['http://localhost:8000'],
credentials: true
}));
// Compression and logging
app.use(compression());
app.use(morgan('combined', { stream: { write: message => logger.info(message.trim()) } }));
// Body parsing
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
// Rate limiting
// app.use(rateLimiter);
// Static files
app.use('/uploads', express.static(path.join(__dirname, '../uploads')));
// API routes
app.use('/api/auth', authRoutes);
app.use('/api/models', modelRoutes);
app.use('/api/chat', chatRoutes);
app.use('/api/documents', documentRoutes);
app.use('/api/feedback', feedbackRoutes);
app.use('/api/tools', toolRoutes);
// Health check
app.get('/api/health', (req, res) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
version: '1.0.0'
});
});
// Error handling
app.use(errorHandler);
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({ error: 'Route not found' });
});
// Database connection and server start
const startServer = async () => {
try {
await sequelize.authenticate();
logger.info('Database connection established successfully');
// Sync database in development
if (process.env.NODE_ENV === 'development') {
await sequelize.sync({ alter: true });
logger.info('Database synchronized');
}
app.listen(PORT, () => {
logger.info(`Server running on port ${PORT}`);
logger.info(`Environment: ${process.env.NODE_ENV}`);
});
} catch (error) {
logger.error('Failed to start server:', error);
process.exit(1);
}
};
// Graceful shutdown
process.on('SIGTERM', async () => {
logger.info('SIGTERM received, shutting down gracefully');
await sequelize.close();
process.exit(0);
});
process.on('SIGINT', async () => {
logger.info('SIGINT received, shutting down gracefully');
await sequelize.close();
process.exit(0);
});
startServer();
module.exports = app;
+73
View File
@@ -0,0 +1,73 @@
const jwt = require('jsonwebtoken');
const { User } = require('../models');
const logger = require('../utils/logger');
const authenticate = async (req, res, next) => {
try {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({
success: false,
error: 'Access denied. No token provided.'
});
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByPk(decoded.userId);
if (!user || !user.is_active) {
return res.status(401).json({
success: false,
error: 'Invalid token or user not found'
});
}
req.user = { userId: user.id, role: user.role };
next();
} catch (error) {
if (error.name === 'JsonWebTokenError') {
return res.status(401).json({
success: false,
error: 'Invalid token'
});
}
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
success: false,
error: 'Token expired'
});
}
logger.error('Authentication error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
};
const authorize = (...roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({
success: false,
error: 'Access denied. Authentication required.'
});
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({
success: false,
error: 'Access denied. Insufficient permissions.'
});
}
next();
};
};
module.exports = {
authenticate,
authorize
};
+58
View File
@@ -0,0 +1,58 @@
const logger = require('../utils/logger');
const errorHandler = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
// Log error
logger.error(err);
// Mongoose bad ObjectId
if (err.name === 'CastError') {
const message = 'Resource not found';
error = { message, statusCode: 404 };
}
// Mongoose duplicate key
if (err.code === 11000) {
const message = 'Duplicate field value entered';
error = { message, statusCode: 400 };
}
// Mongoose validation error
if (err.name === 'ValidationError') {
const message = Object.values(err.errors).map(val => val.message).join(', ');
error = { message, statusCode: 400 };
}
// Sequelize validation error
if (err.name === 'SequelizeValidationError') {
const message = err.errors.map(e => e.message).join(', ');
error = { message, statusCode: 400 };
}
// Sequelize unique constraint error
if (err.name === 'SequelizeUniqueConstraintError') {
const message = 'Duplicate field value entered';
error = { message, statusCode: 400 };
}
// JWT errors
if (err.name === 'JsonWebTokenError') {
const message = 'Invalid token';
error = { message, statusCode: 401 };
}
if (err.name === 'TokenExpiredError') {
const message = 'Token expired';
error = { message, statusCode: 401 };
}
res.status(error.statusCode || 500).json({
success: false,
error: error.message || 'Server Error',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
};
module.exports = errorHandler;
+24
View File
@@ -0,0 +1,24 @@
const { RateLimiterMemory } = require('rate-limiter-flexible');
const rateLimiter = new RateLimiterMemory({
keyPrefix: 'middleware',
points: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 100,
duration: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 900, // 15 minutes
});
const rateLimiterMiddleware = async (req, res, next) => {
try {
const key = req.ip;
await rateLimiter.consume(key);
next();
} catch (rejRes) {
const secs = Math.round(rejRes.msBeforeNext / 1000) || 1;
res.set('Retry-After', String(secs));
res.status(429).json({
error: 'Too Many Requests',
retryAfter: secs
});
}
};
module.exports = rateLimiterMiddleware;
+87
View File
@@ -0,0 +1,87 @@
const Joi = require('joi');
const validate = (schema) => {
return (req, res, next) => {
const { error } = schema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
error: error.details[0].message
});
}
next();
};
};
// Validation schemas
const schemas = {
register: Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
firstName: Joi.string().min(2).required(),
lastName: Joi.string().min(2).required(),
role: Joi.string().valid('user', 'admin', 'expert').optional()
}),
login: Joi.object({
email: Joi.string().email().required(),
password: Joi.string().required()
}),
updateProfile: Joi.object({
firstName: Joi.string().min(2).optional(),
lastName: Joi.string().min(2).optional(),
preferences: Joi.object().optional()
}),
createConversation: Joi.object({
title: Joi.string().min(1).optional()
}),
sendMessage: Joi.object({
conversationId: Joi.string().uuid().required(),
content: Joi.string().min(1).required(),
messageType: Joi.string().valid('text', 'plan', 'execution', 'feedback').optional()
}),
submitFeedback: Joi.object({
messageId: Joi.string().uuid().optional(),
feedbackType: Joi.string().valid('positive', 'negative', 'correction', 'suggestion').required(),
rating: Joi.number().min(1).max(5).optional(),
comment: Joi.string().optional(),
correctedContent: Joi.string().optional()
}),
createModelVersion: Joi.object({
modelName: Joi.string().min(1).required(),
modelType: Joi.string().valid('MODEL1', 'QUERYMODEL').required(),
baseModel: Joi.string().min(1).required(),
fineTuningMethod: Joi.string().valid('SFT', 'DPO', 'PPO', 'LoRA', 'QLoRA').optional(),
hyperparameters: Joi.object().optional()
}),
executeTool: Joi.object({
planId: Joi.string().uuid().required(),
toolName: Joi.string().min(1).required(),
toolType: Joi.string().valid('query_expander', 'extraction', 'report1', 'report2', 'web_search', 'encyclopedia_pdf').required(),
inputParameters: Joi.object().required()
}),
generatePlan: Joi.object({
query: Joi.string().min(1).required(),
conversationId: Joi.string().uuid().optional(),
context: Joi.object().optional()
}),
executePlan: Joi.object({
planId: Joi.string().uuid().required(),
options: Joi.object().optional()
})
};
module.exports = {
validate,
schemas
};
+70
View File
@@ -0,0 +1,70 @@
const { DataTypes } = require('sequelize');
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('users', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password_hash: {
type: DataTypes.STRING,
allowNull: false
},
first_name: {
type: DataTypes.STRING,
allowNull: false
},
last_name: {
type: DataTypes.STRING,
allowNull: false
},
role: {
type: DataTypes.ENUM('user', 'admin', 'expert'),
defaultValue: 'user',
allowNull: false
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true,
allowNull: false
},
last_login: {
type: DataTypes.DATE,
allowNull: true
},
preferences: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
});
// Create indexes
await queryInterface.addIndex('users', ['email']);
await queryInterface.addIndex('users', ['role']);
await queryInterface.addIndex('users', ['is_active']);
await queryInterface.addIndex('users', ['created_at']);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('users');
}
};
@@ -0,0 +1,62 @@
const { DataTypes } = require('sequelize');
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('conversations', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
},
user_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
title: {
type: DataTypes.STRING,
allowNull: false
},
status: {
type: DataTypes.ENUM('active', 'completed', 'archived'),
defaultValue: 'active',
allowNull: false
},
context: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
});
// Create indexes
await queryInterface.addIndex('conversations', ['user_id']);
await queryInterface.addIndex('conversations', ['status']);
await queryInterface.addIndex('conversations', ['created_at']);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('conversations');
}
};
+88
View File
@@ -0,0 +1,88 @@
const { DataTypes } = require('sequelize');
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('plans', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
},
conversation_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'conversations',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
title: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: false
},
steps: {
type: DataTypes.JSONB,
allowNull: false
},
status: {
type: DataTypes.ENUM('draft', 'pending_approval', 'approved', 'rejected', 'executing', 'completed', 'failed'),
defaultValue: 'draft',
allowNull: false
},
approval_feedback: {
type: DataTypes.TEXT,
allowNull: true
},
execution_result: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
tools_required: {
type: DataTypes.ARRAY(DataTypes.STRING),
defaultValue: [],
allowNull: false
},
estimated_duration: {
type: DataTypes.INTEGER,
allowNull: true
},
complexity_score: {
type: DataTypes.FLOAT,
defaultValue: 0,
allowNull: false
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
});
// Create indexes
await queryInterface.addIndex('plans', ['conversation_id']);
await queryInterface.addIndex('plans', ['status']);
await queryInterface.addIndex('plans', ['created_at']);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('plans');
}
};
+83
View File
@@ -0,0 +1,83 @@
const { DataTypes } = require('sequelize');
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('messages', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
},
conversation_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'conversations',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
plan_id: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'plans',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
role: {
type: DataTypes.ENUM('user', 'assistant', 'system'),
allowNull: false
},
content: {
type: DataTypes.TEXT,
allowNull: false
},
message_type: {
type: DataTypes.ENUM('text', 'plan', 'execution', 'feedback'),
defaultValue: 'text',
allowNull: false
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
tokens_used: {
type: DataTypes.INTEGER,
defaultValue: 0,
allowNull: false
},
processing_time: {
type: DataTypes.FLOAT,
defaultValue: 0,
allowNull: false
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
});
// Create indexes
await queryInterface.addIndex('messages', ['conversation_id']);
await queryInterface.addIndex('messages', ['plan_id']);
await queryInterface.addIndex('messages', ['role']);
await queryInterface.addIndex('messages', ['message_type']);
await queryInterface.addIndex('messages', ['created_at']);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('messages');
}
};
+92
View File
@@ -0,0 +1,92 @@
const { DataTypes } = require('sequelize');
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('documents', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
},
filename: {
type: DataTypes.STRING,
allowNull: false
},
original_filename: {
type: DataTypes.STRING,
allowNull: false
},
file_path: {
type: DataTypes.STRING,
allowNull: false
},
file_type: {
type: DataTypes.STRING,
allowNull: false
},
file_size: {
type: DataTypes.BIGINT,
allowNull: false
},
content: {
type: DataTypes.TEXT,
allowNull: true
},
extracted_text: {
type: DataTypes.TEXT,
allowNull: true
},
embeddings: {
type: DataTypes.JSONB,
allowNull: true
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
is_indexed: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false
},
indexing_status: {
type: DataTypes.ENUM('pending', 'processing', 'completed', 'failed'),
defaultValue: 'pending',
allowNull: false
},
tags: {
type: DataTypes.ARRAY(DataTypes.STRING),
defaultValue: [],
allowNull: false
},
category: {
type: DataTypes.STRING,
allowNull: true
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
});
// Create indexes
await queryInterface.addIndex('documents', ['file_type']);
await queryInterface.addIndex('documents', ['is_indexed']);
await queryInterface.addIndex('documents', ['indexing_status']);
await queryInterface.addIndex('documents', ['category']);
await queryInterface.addIndex('documents', ['tags']);
await queryInterface.addIndex('documents', ['created_at']);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('documents');
}
};
+89
View File
@@ -0,0 +1,89 @@
const { DataTypes } = require('sequelize');
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('feedback', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
},
user_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
message_id: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'messages',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
feedback_type: {
type: DataTypes.ENUM('positive', 'negative', 'correction', 'suggestion'),
allowNull: false
},
rating: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {
min: 1,
max: 5
}
},
comment: {
type: DataTypes.TEXT,
allowNull: true
},
corrected_content: {
type: DataTypes.TEXT,
allowNull: true
},
is_processed: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false
},
processing_notes: {
type: DataTypes.TEXT,
allowNull: true
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
});
// Create indexes
await queryInterface.addIndex('feedback', ['user_id']);
await queryInterface.addIndex('feedback', ['message_id']);
await queryInterface.addIndex('feedback', ['feedback_type']);
await queryInterface.addIndex('feedback', ['is_processed']);
await queryInterface.addIndex('feedback', ['created_at']);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('feedback');
}
};
@@ -0,0 +1,94 @@
const { DataTypes } = require('sequelize');
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('model_versions', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
},
model_name: {
type: DataTypes.STRING,
allowNull: false
},
version: {
type: DataTypes.STRING,
allowNull: false
},
model_type: {
type: DataTypes.ENUM('MODEL1', 'QUERYMODEL'),
allowNull: false
},
base_model: {
type: DataTypes.STRING,
allowNull: false
},
fine_tuning_method: {
type: DataTypes.ENUM('SFT', 'DPO', 'PPO', 'LoRA', 'QLoRA'),
allowNull: true
},
training_data_count: {
type: DataTypes.INTEGER,
defaultValue: 0,
allowNull: false
},
performance_metrics: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false
},
deployment_status: {
type: DataTypes.ENUM('training', 'testing', 'deployed', 'deprecated'),
defaultValue: 'training',
allowNull: false
},
model_path: {
type: DataTypes.STRING,
allowNull: true
},
hyperparameters: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
training_log: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
});
// Create indexes
await queryInterface.addIndex('model_versions', ['model_name']);
await queryInterface.addIndex('model_versions', ['model_type']);
await queryInterface.addIndex('model_versions', ['is_active']);
await queryInterface.addIndex('model_versions', ['deployment_status']);
await queryInterface.addIndex('model_versions', ['created_at']);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('model_versions');
}
};
@@ -0,0 +1,85 @@
const { DataTypes } = require('sequelize');
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('training_data', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
},
model_version_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'model_versions',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
data_type: {
type: DataTypes.ENUM('qa_pair', 'plan_pair', 'feedback', 'preference'),
allowNull: false
},
input_text: {
type: DataTypes.TEXT,
allowNull: false
},
output_text: {
type: DataTypes.TEXT,
allowNull: false
},
context: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
quality_score: {
type: DataTypes.FLOAT,
allowNull: true
},
is_validated: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false
},
validation_notes: {
type: DataTypes.TEXT,
allowNull: true
},
source: {
type: DataTypes.ENUM('expert', 'user', 'generated', 'feedback'),
allowNull: false
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
});
// Create indexes
await queryInterface.addIndex('training_data', ['model_version_id']);
await queryInterface.addIndex('training_data', ['data_type']);
await queryInterface.addIndex('training_data', ['is_validated']);
await queryInterface.addIndex('training_data', ['source']);
await queryInterface.addIndex('training_data', ['quality_score']);
await queryInterface.addIndex('training_data', ['created_at']);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('training_data');
}
};
@@ -0,0 +1,95 @@
const { DataTypes } = require('sequelize');
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('tool_executions', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
},
plan_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'plans',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
document_id: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'documents',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
tool_name: {
type: DataTypes.STRING,
allowNull: false
},
tool_type: {
type: DataTypes.ENUM('query_expander', 'extraction', 'report1', 'report2', 'web_search', 'encyclopedia_pdf'),
allowNull: false
},
input_parameters: {
type: DataTypes.JSONB,
allowNull: false
},
output_result: {
type: DataTypes.JSONB,
allowNull: true
},
status: {
type: DataTypes.ENUM('pending', 'running', 'completed', 'failed'),
defaultValue: 'pending',
allowNull: false
},
execution_time: {
type: DataTypes.FLOAT,
allowNull: true
},
error_message: {
type: DataTypes.TEXT,
allowNull: true
},
tokens_used: {
type: DataTypes.INTEGER,
defaultValue: 0,
allowNull: false
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {},
allowNull: false
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
});
// Create indexes
await queryInterface.addIndex('tool_executions', ['plan_id']);
await queryInterface.addIndex('tool_executions', ['document_id']);
await queryInterface.addIndex('tool_executions', ['tool_name']);
await queryInterface.addIndex('tool_executions', ['tool_type']);
await queryInterface.addIndex('tool_executions', ['status']);
await queryInterface.addIndex('tool_executions', ['created_at']);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('tool_executions');
}
};
+42
View File
@@ -0,0 +1,42 @@
module.exports = (sequelize, DataTypes) => {
const Conversation = sequelize.define('Conversation', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
user_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
},
title: {
type: DataTypes.STRING,
allowNull: false
},
status: {
type: DataTypes.ENUM('active', 'completed', 'archived'),
defaultValue: 'active'
},
context: {
type: DataTypes.JSONB,
defaultValue: {}
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {}
}
}, {
tableName: 'conversations',
indexes: [
{ fields: ['user_id'] },
{ fields: ['status'] },
{ fields: ['created_at'] }
]
});
return Conversation;
};
+73
View File
@@ -0,0 +1,73 @@
module.exports = (sequelize, DataTypes) => {
const Document = sequelize.define('Document', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
filename: {
type: DataTypes.STRING,
allowNull: false
},
original_filename: {
type: DataTypes.STRING,
allowNull: false
},
file_path: {
type: DataTypes.STRING,
allowNull: false
},
file_type: {
type: DataTypes.STRING,
allowNull: false
},
file_size: {
type: DataTypes.BIGINT,
allowNull: false
},
content: {
type: DataTypes.TEXT,
allowNull: true
},
extracted_text: {
type: DataTypes.TEXT,
allowNull: true
},
embeddings: {
type: DataTypes.JSONB,
allowNull: true
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {}
},
is_indexed: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
indexing_status: {
type: DataTypes.ENUM('pending', 'processing', 'completed', 'failed'),
defaultValue: 'pending'
},
tags: {
type: DataTypes.ARRAY(DataTypes.STRING),
defaultValue: []
},
category: {
type: DataTypes.STRING,
allowNull: true
}
}, {
tableName: 'documents',
indexes: [
{ fields: ['file_type'] },
{ fields: ['is_indexed'] },
{ fields: ['indexing_status'] },
{ fields: ['category'] },
{ fields: ['tags'] },
{ fields: ['created_at'] }
]
});
return Document;
};
+68
View File
@@ -0,0 +1,68 @@
module.exports = (sequelize, DataTypes) => {
const Feedback = sequelize.define('Feedback', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
user_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
},
message_id: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'messages',
key: 'id'
}
},
feedback_type: {
type: DataTypes.ENUM('positive', 'negative', 'correction', 'suggestion'),
allowNull: false
},
rating: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {
min: 1,
max: 5
}
},
comment: {
type: DataTypes.TEXT,
allowNull: true
},
corrected_content: {
type: DataTypes.TEXT,
allowNull: true
},
is_processed: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
processing_notes: {
type: DataTypes.TEXT,
allowNull: true
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {}
}
}, {
tableName: 'feedback',
indexes: [
{ fields: ['user_id'] },
{ fields: ['message_id'] },
{ fields: ['feedback_type'] },
{ fields: ['is_processed'] },
{ fields: ['created_at'] }
]
});
return Feedback;
};
+60
View File
@@ -0,0 +1,60 @@
module.exports = (sequelize, DataTypes) => {
const Message = sequelize.define('Message', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
conversation_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'conversations',
key: 'id'
}
},
plan_id: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'plans',
key: 'id'
}
},
role: {
type: DataTypes.ENUM('user', 'assistant', 'system'),
allowNull: false
},
content: {
type: DataTypes.TEXT,
allowNull: false
},
message_type: {
type: DataTypes.ENUM('text', 'plan', 'execution', 'feedback'),
defaultValue: 'text'
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {}
},
tokens_used: {
type: DataTypes.INTEGER,
defaultValue: 0
},
processing_time: {
type: DataTypes.FLOAT,
defaultValue: 0
}
}, {
tableName: 'messages',
indexes: [
{ fields: ['conversation_id'] },
{ fields: ['plan_id'] },
{ fields: ['role'] },
{ fields: ['message_type'] },
{ fields: ['created_at'] }
]
});
return Message;
};
+72
View File
@@ -0,0 +1,72 @@
module.exports = (sequelize, DataTypes) => {
const ModelVersion = sequelize.define('ModelVersion', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
model_name: {
type: DataTypes.STRING,
allowNull: false
},
version: {
type: DataTypes.STRING,
allowNull: false
},
model_type: {
type: DataTypes.ENUM('MODEL1', 'QUERYMODEL'),
allowNull: false
},
base_model: {
type: DataTypes.STRING,
allowNull: false
},
fine_tuning_method: {
type: DataTypes.ENUM('SFT', 'DPO', 'PPO', 'LoRA', 'QLoRA'),
allowNull: true
},
training_data_count: {
type: DataTypes.INTEGER,
defaultValue: 0
},
performance_metrics: {
type: DataTypes.JSONB,
defaultValue: {}
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
deployment_status: {
type: DataTypes.ENUM('training', 'testing', 'deployed', 'deprecated'),
defaultValue: 'training'
},
model_path: {
type: DataTypes.STRING,
allowNull: true
},
hyperparameters: {
type: DataTypes.JSONB,
defaultValue: {}
},
training_log: {
type: DataTypes.JSONB,
defaultValue: {}
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {}
}
}, {
tableName: 'model_versions',
indexes: [
{ fields: ['model_name'] },
{ fields: ['model_type'] },
{ fields: ['is_active'] },
{ fields: ['deployment_status'] },
{ fields: ['created_at'] }
]
});
return ModelVersion;
};
+66
View File
@@ -0,0 +1,66 @@
module.exports = (sequelize, DataTypes) => {
const Plan = sequelize.define('Plan', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
conversation_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'conversations',
key: 'id'
}
},
title: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: false
},
steps: {
type: DataTypes.JSONB,
allowNull: false
},
status: {
type: DataTypes.ENUM('draft', 'pending_approval', 'approved', 'rejected', 'executing', 'completed', 'failed'),
defaultValue: 'draft'
},
approval_feedback: {
type: DataTypes.TEXT,
allowNull: true
},
execution_result: {
type: DataTypes.JSONB,
defaultValue: {}
},
tools_required: {
type: DataTypes.ARRAY(DataTypes.STRING),
defaultValue: []
},
estimated_duration: {
type: DataTypes.INTEGER, // in minutes
allowNull: true
},
complexity_score: {
type: DataTypes.FLOAT,
defaultValue: 0
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {}
}
}, {
tableName: 'plans',
indexes: [
{ fields: ['conversation_id'] },
{ fields: ['status'] },
{ fields: ['created_at'] }
]
});
return Plan;
};
+73
View File
@@ -0,0 +1,73 @@
module.exports = (sequelize, DataTypes) => {
const ToolExecution = sequelize.define('ToolExecution', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
plan_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'plans',
key: 'id'
}
},
document_id: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'documents',
key: 'id'
}
},
tool_name: {
type: DataTypes.STRING,
allowNull: false
},
tool_type: {
type: DataTypes.ENUM('query_expander', 'extraction', 'report1', 'report2', 'web_search', 'encyclopedia_pdf'),
allowNull: false
},
input_parameters: {
type: DataTypes.JSONB,
allowNull: false
},
output_result: {
type: DataTypes.JSONB,
allowNull: true
},
status: {
type: DataTypes.ENUM('pending', 'running', 'completed', 'failed'),
defaultValue: 'pending'
},
execution_time: {
type: DataTypes.FLOAT,
allowNull: true
},
error_message: {
type: DataTypes.TEXT,
allowNull: true
},
tokens_used: {
type: DataTypes.INTEGER,
defaultValue: 0
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {}
}
}, {
tableName: 'tool_executions',
indexes: [
{ fields: ['plan_id'] },
{ fields: ['document_id'] },
{ fields: ['tool_name'] },
{ fields: ['tool_type'] },
{ fields: ['status'] },
{ fields: ['created_at'] }
]
});
return ToolExecution;
};
+64
View File
@@ -0,0 +1,64 @@
module.exports = (sequelize, DataTypes) => {
const TrainingData = sequelize.define('TrainingData', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
model_version_id: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'model_versions',
key: 'id'
}
},
data_type: {
type: DataTypes.ENUM('qa_pair', 'plan_pair', 'feedback', 'preference'),
allowNull: false
},
input_text: {
type: DataTypes.TEXT,
allowNull: false
},
output_text: {
type: DataTypes.TEXT,
allowNull: false
},
context: {
type: DataTypes.JSONB,
defaultValue: {}
},
quality_score: {
type: DataTypes.FLOAT,
allowNull: true
},
is_validated: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
validation_notes: {
type: DataTypes.TEXT,
allowNull: true
},
source: {
type: DataTypes.ENUM('expert', 'user', 'generated', 'feedback'),
allowNull: false
},
metadata: {
type: DataTypes.JSONB,
defaultValue: {}
}
}, {
tableName: 'training_data',
indexes: [
{ fields: ['model_version_id'] },
{ fields: ['data_type'] },
{ fields: ['is_validated'] },
{ fields: ['source'] },
{ fields: ['quality_score'] }
]
});
return TrainingData;
};
+53
View File
@@ -0,0 +1,53 @@
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password_hash: {
type: DataTypes.STRING,
allowNull: false
},
first_name: {
type: DataTypes.STRING,
allowNull: false
},
last_name: {
type: DataTypes.STRING,
allowNull: false
},
role: {
type: DataTypes.ENUM('user', 'admin', 'expert'),
defaultValue: 'user'
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
last_login: {
type: DataTypes.DATE
},
preferences: {
type: DataTypes.JSONB,
defaultValue: {}
}
}, {
tableName: 'users',
indexes: [
{ fields: ['email'] },
{ fields: ['role'] },
{ fields: ['is_active'] }
]
});
return User;
};
+69
View File
@@ -0,0 +1,69 @@
const { sequelize } = require('../config/database');
const { DataTypes } = require('sequelize');
// Import all models
const User = require('./User')(sequelize, DataTypes);
const Conversation = require('./Conversation')(sequelize, DataTypes);
const Message = require('./Message')(sequelize, DataTypes);
const Plan = require('./Plan')(sequelize, DataTypes);
const Feedback = require('./Feedback')(sequelize, DataTypes);
const Document = require('./Document')(sequelize, DataTypes);
const TrainingData = require('./TrainingData')(sequelize, DataTypes);
const ModelVersion = require('./ModelVersion')(sequelize, DataTypes);
const ToolExecution = require('./ToolExecution')(sequelize, DataTypes);
// Define associations
const defineAssociations = () => {
// User associations
User.hasMany(Conversation, { foreignKey: 'user_id' });
User.hasMany(Feedback, { foreignKey: 'user_id' });
// Conversation associations
Conversation.belongsTo(User, { foreignKey: 'user_id' });
Conversation.hasMany(Message, { foreignKey: 'conversation_id' });
Conversation.hasMany(Plan, { foreignKey: 'conversation_id' });
// Message associations
Message.belongsTo(Conversation, { foreignKey: 'conversation_id' });
Message.belongsTo(Plan, { foreignKey: 'plan_id' });
// Plan associations
Plan.belongsTo(Conversation, { foreignKey: 'conversation_id' });
Plan.hasMany(Message, { foreignKey: 'plan_id' });
Plan.hasMany(ToolExecution, { foreignKey: 'plan_id' });
// Feedback associations
Feedback.belongsTo(User, { foreignKey: 'user_id' });
Feedback.belongsTo(Message, { foreignKey: 'message_id' });
// Document associations
Document.hasMany(ToolExecution, { foreignKey: 'document_id' });
// TrainingData associations
TrainingData.belongsTo(ModelVersion, { foreignKey: 'model_version_id' });
// ModelVersion associations
ModelVersion.hasMany(TrainingData, { foreignKey: 'model_version_id' });
// ToolExecution associations
ToolExecution.belongsTo(Plan, { foreignKey: 'plan_id' });
ToolExecution.belongsTo(Document, { foreignKey: 'document_id' });
};
// Initialize associations
defineAssociations();
const models = {
User,
Conversation,
Message,
Plan,
Feedback,
Document,
TrainingData,
ModelVersion,
ToolExecution,
sequelize
};
module.exports = models;
+15
View File
@@ -0,0 +1,15 @@
const express = require('express');
const router = express.Router();
const { register, login, getProfile, updateProfile } = require('../controllers/authController');
const { authenticate } = require('../middleware/auth');
const { validate, schemas } = require('../middleware/validation');
// Public routes
router.post('/register', validate(schemas.register), register);
router.post('/login', validate(schemas.login), login);
// Protected routes
router.get('/profile', authenticate, getProfile);
router.put('/profile', authenticate, validate(schemas.updateProfile), updateProfile);
module.exports = router;
+27
View File
@@ -0,0 +1,27 @@
const express = require('express');
const router = express.Router();
const {
createConversation,
getConversations,
getConversation,
sendMessage,
updateConversation,
deleteConversation
} = require('../controllers/chatController');
const { authenticate } = require('../middleware/auth');
const { validate, schemas } = require('../middleware/validation');
// All chat routes require authentication
router.use(authenticate);
// Conversation management
router.post('/conversations', validate(schemas.createConversation), createConversation);
router.get('/conversations', getConversations);
router.get('/conversations/:conversationId', getConversation);
router.put('/conversations/:conversationId', updateConversation);
router.delete('/conversations/:conversationId', deleteConversation);
// Messaging
router.post('/message', validate(schemas.sendMessage), sendMessage);
module.exports = router;
+25
View File
@@ -0,0 +1,25 @@
const express = require('express');
const router = express.Router();
const {
uploadDocument,
getDocuments,
getDocument,
searchDocuments,
graphSearchDocuments,
deleteDocument,
upload
} = require('../controllers/documentController');
const { authenticate } = require('../middleware/auth');
// All document routes require authentication
router.use(authenticate);
// Document management
router.post('/upload', upload.single('document'), uploadDocument);
router.get('/', getDocuments);
router.get('/search', searchDocuments);
router.get('/graph-search', graphSearchDocuments);
router.get('/:documentId', getDocument);
router.delete('/:documentId', deleteDocument);
module.exports = router;
+25
View File
@@ -0,0 +1,25 @@
const express = require('express');
const router = express.Router();
const {
submitFeedback,
getFeedback,
getFeedbackList,
processFeedback,
getFeedbackStats
} = require('../controllers/feedbackController');
const { authenticate, authorize } = require('../middleware/auth');
const { validate, schemas } = require('../middleware/validation');
// All feedback routes require authentication
router.use(authenticate);
// Feedback submission (public to authenticated users)
router.post('/submit', validate(schemas.submitFeedback), submitFeedback);
// Feedback management (admin only)
router.get('/stats', authorize('admin'), getFeedbackStats);
router.get('/', authorize('admin'), getFeedbackList);
router.get('/:feedbackId', authorize('admin'), getFeedback);
router.put('/:feedbackId/process', authorize('admin'), processFeedback);
module.exports = router;
+57
View File
@@ -0,0 +1,57 @@
const express = require('express');
const router = express.Router();
const {
getModelStatus,
getModelVersions,
createModelVersion,
updateModelVersion,
activateModel,
getTrainingData
} = require('../controllers/modelController');
const {
generatePlan,
validatePlan,
getPlan,
updatePlan,
getModelStatus: getModel1Status
} = require('../controllers/model1Controller');
const {
executePlan,
executeTool,
getExecutionStatus,
getToolExecutions,
getModelStatus: getQueryModelStatus,
orchestratePlan
} = require('../controllers/queryModelController');
const { authenticate, authorize } = require('../middleware/auth');
const { validate, schemas } = require('../middleware/validation');
// All model routes require authentication
router.use(authenticate);
// Public model status
router.get('/status', getModelStatus);
// MODEL1 routes
router.post('/model1/generate-plan', validate(schemas.generatePlan), generatePlan);
router.post('/model1/validate-plan/:planId', validatePlan);
router.get('/model1/plan/:planId', getPlan);
router.put('/model1/plan/:planId', updatePlan);
router.get('/model1/status', getModel1Status);
// QUERYMODEL routes
router.post('/querymodel/execute-plan', executePlan);
router.post('/querymodel/execute-tool', executeTool);
router.post('/querymodel/orchestrate', orchestratePlan);
router.get('/querymodel/execution-status/:planId', getExecutionStatus);
router.get('/querymodel/tool-executions', getToolExecutions);
router.get('/querymodel/status', getQueryModelStatus);
// Model management (admin only)
router.get('/versions', authorize('admin'), getModelVersions);
router.post('/versions', authorize('admin'), validate(schemas.createModelVersion), createModelVersion);
router.put('/versions/:modelId', authorize('admin'), updateModelVersion);
router.put('/versions/:modelId/activate', authorize('admin'), activateModel);
router.get('/training-data', authorize('admin'), getTrainingData);
module.exports = router;
+25
View File
@@ -0,0 +1,25 @@
const express = require('express');
const router = express.Router();
const {
executeTool,
getToolExecutions,
getToolExecution,
getToolStats,
retryToolExecution
} = require('../controllers/toolController');
const { authenticate, authorize } = require('../middleware/auth');
const { validate, schemas } = require('../middleware/validation');
// All tool routes require authentication
router.use(authenticate);
// Tool execution
router.post('/execute', validate(schemas.executeTool), executeTool);
// Tool management
router.get('/executions', getToolExecutions);
router.get('/executions/:executionId', getToolExecution);
router.get('/stats', authorize('admin'), getToolStats);
router.post('/executions/:executionId/retry', retryToolExecution);
module.exports = router;
+45
View File
@@ -0,0 +1,45 @@
const bcrypt = require('bcryptjs');
const { User } = require('../models');
module.exports = {
up: async (queryInterface, Sequelize) => {
try {
// Create admin user
const adminPassword = process.env.ADMIN_PASSWORD || 'admin123';
const saltRounds = 12;
const passwordHash = await bcrypt.hash(adminPassword, saltRounds);
const adminUser = await User.create({
email: 'admin@reasonflow.com',
password_hash: passwordHash,
first_name: 'Admin',
last_name: 'User',
role: 'admin',
is_active: true,
preferences: {
theme: 'light',
notifications: true,
language: 'en'
}
});
console.log('✅ Admin user created:', adminUser.email);
return adminUser;
} catch (error) {
console.error('❌ Error creating admin user:', error);
throw error;
}
},
down: async (queryInterface, Sequelize) => {
try {
await User.destroy({
where: { email: 'admin@reasonflow.com' }
});
console.log('✅ Admin user removed');
} catch (error) {
console.error('❌ Error removing admin user:', error);
throw error;
}
}
};
@@ -0,0 +1,123 @@
const { TrainingData, ModelVersion } = require('../models');
module.exports = {
up: async (queryInterface, Sequelize) => {
try {
// Create sample model versions
const model1Version = await ModelVersion.create({
model_name: 'MODEL1-v1.0',
version: 'v1.0',
model_type: 'MODEL1',
base_model: 'moonshotai/kimi-k2-instruct-0905',
fine_tuning_method: 'SFT',
training_data_count: 0,
performance_metrics: {
accuracy: 0.85,
precision: 0.82,
recall: 0.88,
f1_score: 0.85
},
is_active: true,
deployment_status: 'deployed',
hyperparameters: {
learning_rate: 0.0001,
batch_size: 16,
epochs: 10,
temperature: 0.3
},
training_log: {
start_time: new Date(),
end_time: new Date(),
total_epochs: 10,
final_loss: 0.15
}
});
const queryModelVersion = await ModelVersion.create({
model_name: 'QUERYMODEL-v1.0',
version: 'v1.0',
model_type: 'QUERYMODEL',
base_model: 'moonshotai/kimi-k2-instruct-0905',
fine_tuning_method: 'SFT',
training_data_count: 0,
performance_metrics: {
accuracy: 0.88,
precision: 0.85,
recall: 0.90,
f1_score: 0.87
},
is_active: true,
deployment_status: 'deployed',
hyperparameters: {
learning_rate: 0.0001,
batch_size: 16,
epochs: 10,
temperature: 0.5
},
training_log: {
start_time: new Date(),
end_time: new Date(),
total_epochs: 10,
final_loss: 0.12
}
});
// Create sample training data
const sampleTrainingData = [
{
model_version_id: model1Version.id,
data_type: 'qa_pair',
input_text: 'How do I design a bridge for heavy traffic loads?',
output_text: 'To design a bridge for heavy traffic loads, follow these steps: 1. Calculate the maximum load requirements based on traffic volume and vehicle types. 2. Determine the bridge span and select appropriate structural system (beam, arch, or suspension). 3. Design the superstructure with adequate load capacity and safety factors. 4. Design the substructure including piers and foundations. 5. Consider environmental factors like wind, seismic loads, and temperature variations. 6. Perform structural analysis and design checks according to relevant codes (AASHTO, ACI, etc.). 7. Ensure adequate clearance and geometric requirements. 8. Plan for maintenance and inspection access.',
context: { domain: 'civil_engineering', complexity: 'high' },
quality_score: 0.95,
is_validated: true,
source: 'expert'
},
{
model_version_id: model1Version.id,
data_type: 'qa_pair',
input_text: 'What are the key considerations for electrical system design in a commercial building?',
output_text: 'Key considerations for electrical system design in commercial buildings include: 1. Load calculation and demand analysis. 2. Power distribution system design (main panels, subpanels, feeders). 3. Lighting design and control systems. 4. Emergency and backup power systems. 5. Grounding and bonding systems. 6. Electrical room and equipment space planning. 7. Code compliance (NEC, local codes). 8. Energy efficiency and sustainability. 9. Fire safety and protection systems. 10. Coordination with other building systems.',
context: { domain: 'electrical_engineering', complexity: 'medium' },
quality_score: 0.92,
is_validated: true,
source: 'expert'
},
{
model_version_id: queryModelVersion.id,
data_type: 'plan_pair',
input_text: 'Execute a structural analysis for a steel frame building',
output_text: 'To execute structural analysis for a steel frame building: 1. Gather building plans and load information. 2. Model the structure in analysis software (ETABS, SAP2000, or similar). 3. Apply dead loads, live loads, and environmental loads. 4. Run linear static analysis. 5. Check member stresses against allowable limits. 6. Verify deflection limits are met. 7. Perform stability analysis for lateral loads. 8. Generate analysis reports and drawings. 9. Review results with design team. 10. Document findings and recommendations.',
context: { domain: 'structural_engineering', complexity: 'high' },
quality_score: 0.90,
is_validated: true,
source: 'expert'
}
];
for (const data of sampleTrainingData) {
await TrainingData.create(data);
}
console.log('✅ Sample training data created');
console.log(` - MODEL1 version: ${model1Version.id}`);
console.log(` - QUERYMODEL version: ${queryModelVersion.id}`);
console.log(` - Training data entries: ${sampleTrainingData.length}`);
} catch (error) {
console.error('❌ Error creating sample training data:', error);
throw error;
}
},
down: async (queryInterface, Sequelize) => {
try {
await TrainingData.destroy({ where: {} });
await ModelVersion.destroy({ where: {} });
console.log('✅ Sample training data removed');
} catch (error) {
console.error('❌ Error removing sample training data:', error);
throw error;
}
}
};
+155
View File
@@ -0,0 +1,155 @@
const groqService = require('./groqService');
const model1Service = require('./model1Service');
const logger = require('../utils/logger');
class ChatRouter {
constructor() {
this.systemPrompt = `You are ReasonAI, a specialized engineering assistant. Your role is to:
1. **Identify Engineering Questions**: Determine if a user's message requires an engineering plan or is a simple question/conversation.
2. **Respond Appropriately**:
- For engineering questions: Indicate that a plan should be generated
- For simple questions/greetings: Provide helpful responses within your engineering scope
- For conversation context: Use conversation history to provide relevant responses
- For out-of-scope questions: Politely redirect to engineering topics
3. **Scope Boundaries**:
- ✅ Engineering: Civil, mechanical, electrical, chemical, environmental, software engineering
- ✅ Technical: Calculations, design, analysis, standards, codes, materials
- ✅ Simple: Greetings, basic engineering concepts, "what is 2+2"
- ✅ Conversation: Questions about previous messages, context, follow-ups
- ❌ Out-of-scope: Sports, entertainment, politics, general knowledge outside engineering
4. **Context Awareness**: Use conversation history to provide relevant responses. If user asks about previous messages or context, respond helpfully.
5. Some questiosn might be engineering questions but not every questions requires a plan, if it can be answered straightforwardly, then it doesnt need a plan
6. **Response Format (STRICT)**: Respond ONLY with a single, minified JSON object. No prose, no code fences, no backticks, no explanations. Use DOUBLE QUOTES for all keys and string values. If unsure, default to simple_response. The JSON schema is:
{
"isEngineeringQuestion": boolean,
"responseType": "plan_needed" | "simple_response" | "out_of_scope",
"response": "Your response to the user",
"reasoning": "Why you classified it this way"
}`;
}
async routeMessage(content, context = {}) {
try {
const messages = [
{
role: 'system',
content: this.systemPrompt
}
];
// Add conversation history for context
if (context.conversationHistory && context.conversationHistory.length > 0) {
context.conversationHistory.forEach(msg => {
messages.push({
role: msg.role,
content: msg.content
});
});
}
// Add current message
messages.push({
role: 'user',
content: `Current user message: "${content}"\n\nContext: ${JSON.stringify(context)}\n\nConversation History: ${context.conversationHistory ? JSON.stringify(context.conversationHistory) : 'None'}`
});
const response = await groqService.generateResponse(messages, {
temperature: 0.3,
max_tokens: 4000
});
// Parse the JSON response
let routingDecision;
try {
// Prefer fenced json block if present
const fence = response.content.match(/```json\s*([\s\S]*?)```/i);
const raw = fence ? fence[1] : (response.content.match(/\{[\s\S]*\}/) || [null])[0];
if (!raw) throw new Error('No JSON found in response');
// Trim whitespace and attempt strict parse
const candidate = raw.trim();
routingDecision = JSON.parse(candidate);
} catch (parseError) {
// Downgrade to warn to avoid noisy error logs for non-strict JSON
logger.warn('Routing JSON parse failed; falling back to heuristic classification');
// Smart fallback based on content analysis
const lowerContent = content.toLowerCase();
const engineeringKeywords = ['design', 'calculate', 'analyze', 'beam', 'steel', 'concrete', 'structure', 'load', 'stress', 'engineering', 'technical', 'specification', 'code', 'standard'];
const isEngineering = engineeringKeywords.some(keyword => lowerContent.includes(keyword));
routingDecision = {
isEngineeringQuestion: isEngineering,
responseType: isEngineering ? 'plan_needed' : 'simple_response',
response: isEngineering ?
"I'll generate an engineering plan for your request." :
"I'm ReasonAI, your engineering assistant. I can help with engineering questions, calculations, and technical problems. How can I assist you today?",
reasoning: "Fallback routing due to parse error"
};
}
logger.info(`Message routed as: ${routingDecision.responseType}`, {
content: content.substring(0, 100),
reasoning: routingDecision.reasoning,
conversationHistoryLength: context.conversationHistory ? context.conversationHistory.length : 0
});
return routingDecision;
} catch (error) {
logger.error('Chat routing error:', error);
// Fallback response
return {
isEngineeringQuestion: false,
responseType: 'simple_response',
response: "I'm ReasonAI, your engineering assistant. I'm here to help with engineering questions and technical problems. How can I assist you today?",
reasoning: "Routing service error"
};
}
}
async generateSimpleResponse(content, context = {}) {
try {
const messages = [
{
role: 'system',
content: `You are ReasonAI, a friendly engineering assistant. Respond helpfully to the user's message while staying within your engineering scope. Be concise and professional. Consider the conversation history for context.`
}
];
// Add conversation history for context
if (context.conversationHistory && context.conversationHistory.length > 0) {
context.conversationHistory.forEach(msg => {
messages.push({
role: msg.role,
content: msg.content
});
});
}
// Add current message
messages.push({
role: 'user',
content: content
});
const response = await groqService.generateResponse(messages, {
temperature: 0.7,
max_tokens: 300
});
return response.content;
} catch (error) {
logger.error('Simple response generation error:', error);
return "I'm ReasonAI, your engineering assistant. I can help with engineering questions, calculations, and technical problems. How can I assist you today?";
}
}
}
module.exports = new ChatRouter();
+55
View File
@@ -0,0 +1,55 @@
const logger = require('../utils/logger');
let pipeline;
try {
// Lazy import to avoid startup cost when unused
({ pipeline } = require('@xenova/transformers'));
} catch (e) {
logger.warn('Embedding pipeline not available. Did you install @xenova/transformers?');
}
class EmbeddingService {
constructor() {
this.initialized = false;
this.extractor = null;
this.modelName = process.env.EMBEDDING_MODEL || 'Xenova/all-MiniLM-L6-v2';
}
async initIfNeeded() {
if (this.initialized) return;
if (!pipeline) {
throw new Error('Transformers pipeline not available');
}
this.extractor = await pipeline('feature-extraction', this.modelName);
this.initialized = true;
logger.info(`Embedding model loaded: ${this.modelName}`);
}
async embedText(text) {
if (!text || !text.trim()) return [];
await this.initIfNeeded();
const output = await this.extractor(text, { pooling: 'mean', normalize: true });
// output is a Tensor; convert to plain JS array
// Depending on version, .data or .tolist()
const vector = Array.isArray(output) ? output : (output?.data ? Array.from(output.data) : output.tolist());
return vector;
}
cosineSimilarity(a, b) {
if (!a || !b || a.length !== b.length || a.length === 0) return 0;
let dot = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < a.length; i++) {
const va = a[i] || 0;
const vb = b[i] || 0;
dot += va * vb;
normA += va * va;
normB += vb * vb;
}
const denom = Math.sqrt(normA) * Math.sqrt(normB);
return denom ? dot / denom : 0;
}
}
module.exports = new EmbeddingService();
+144
View File
@@ -0,0 +1,144 @@
const { Document } = require('../models');
const embeddingService = require('./embeddingService');
const logger = require('../utils/logger');
class GraphRagService {
constructor() {
this.similarityThreshold = parseFloat(process.env.GRAPH_RAG_SIM_THRESHOLD || '0.2');
this.maxNeighbors = parseInt(process.env.GRAPH_RAG_MAX_NEIGHBORS || '10');
this.maxResults = parseInt(process.env.GRAPH_RAG_MAX_RESULTS || '10');
}
scoreSimilarity(a, b) {
return embeddingService.cosineSimilarity(a, b);
}
tagOverlap(tagsA = [], tagsB = []) {
const setA = new Set((tagsA || []).map((t) => (t || '').toLowerCase()));
const setB = new Set((tagsB || []).map((t) => (t || '').toLowerCase()));
let overlap = 0;
setA.forEach((t) => {
if (setB.has(t)) overlap += 1;
});
return overlap;
}
buildGraph(nodes) {
// nodes: [{ id, embedding, tags }]
const edges = new Map(); // id -> [{ id, score, reason }]
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
const ni = nodes[i];
const nj = nodes[j];
const sim = this.scoreSimilarity(ni.embedding, nj.embedding);
const tagScore = this.tagOverlap(ni.tags, nj.tags);
const hybrid = sim + Math.min(tagScore, 3) * 0.05; // light tag bonus
if (hybrid >= this.similarityThreshold) {
if (!edges.has(ni.id)) edges.set(ni.id, []);
if (!edges.has(nj.id)) edges.set(nj.id, []);
edges.get(ni.id).push({ id: nj.id, score: hybrid, reason: { sim, tagScore } });
edges.get(nj.id).push({ id: ni.id, score: hybrid, reason: { sim, tagScore } });
}
}
}
// Trim neighbors
edges.forEach((arr, k) => {
arr.sort((a, b) => b.score - a.score);
edges.set(k, arr.slice(0, this.maxNeighbors));
});
return edges;
}
async graphSearch({ query, category }) {
const queryEmbedding = await embeddingService.embedText(query);
// Load candidate docs
const where = { is_indexed: true };
if (category) where.category = category;
const docs = await Document.findAll({
where,
attributes: ['id', 'original_filename', 'extracted_text', 'embeddings', 'tags', 'category', 'created_at']
});
const nodes = docs
.filter((d) => Array.isArray(d.embeddings) && d.embeddings.length > 0)
.map((d) => ({ id: d.id, embedding: d.embeddings, tags: d.tags || [], ref: d }));
if (nodes.length === 0) {
return { results: [] };
}
// Seed scores by query similarity
const seedScores = nodes.map((n) => ({
id: n.id,
score: this.scoreSimilarity(queryEmbedding, n.embedding)
}));
// Log similarity scores for debugging
logger.info('Similarity scores:', seedScores.map(s => ({ id: s.id, score: s.score.toFixed(4) })));
seedScores.sort((a, b) => b.score - a.score);
const seeds = seedScores.slice(0, Math.min(5, seedScores.length)).map((s) => s.id);
const graph = this.buildGraph(nodes);
// Expand neighborhoods from seeds
const visited = new Set();
const scored = new Map();
const pushScore = (id, add, meta) => {
const prev = scored.get(id) || { score: 0, hops: Infinity, reasons: [] };
const combined = {
score: Math.max(prev.score, add),
hops: Math.min(prev.hops, meta.hops),
reasons: prev.reasons.length < 3 ? [...prev.reasons, meta] : prev.reasons
};
scored.set(id, combined);
};
const queue = [];
seeds.forEach((id) => queue.push({ id, hops: 0, via: null }));
while (queue.length > 0 && scored.size < 200) {
const { id, hops, via } = queue.shift();
if (visited.has(id) || hops > 2) continue;
visited.add(id);
// Base score: similarity to query
const node = nodes.find((n) => n.id === id);
const base = this.scoreSimilarity(queryEmbedding, node.embedding);
pushScore(id, base, { type: 'seed', hops });
const neighbors = graph.get(id) || [];
neighbors.forEach((nbr) => {
const pathScore = (base + nbr.score) / 2;
pushScore(nbr.id, pathScore, { type: 'edge', hops: hops + 1, via: id, edgeScore: nbr.score });
if (!visited.has(nbr.id)) {
queue.push({ id: nbr.id, hops: hops + 1, via: id });
}
});
}
// Format results
const ranked = Array.from(scored.entries())
.map(([id, info]) => {
const ref = nodes.find((n) => n.id === id)?.ref;
return {
id,
original_filename: ref?.original_filename,
snippet: (ref?.extracted_text || '').slice(0, 400),
category: ref?.category,
created_at: ref?.created_at,
score: Number(info.score.toFixed(4)),
hops: info.hops,
reasons: info.reasons
};
})
.sort((a, b) => b.score - a.score)
.slice(0, this.maxResults);
return { results: ranked };
}
}
module.exports = new GraphRagService();
+248
View File
@@ -0,0 +1,248 @@
const Groq = require('groq-sdk');
const logger = require('../utils/logger');
const { groqConfig, validateConfig, getModelConfig } = require('../config/groq');
class GroqService {
constructor() {
// Validate configuration
validateConfig();
this.groq = new Groq({
apiKey: groqConfig.apiKey,
baseURL: groqConfig.baseURL
});
this.model = groqConfig.model;
this.config = groqConfig;
}
async generateResponse(messages, options = {}) {
try {
const startTime = Date.now();
const response = await this.groq.chat.completions.create({
model: this.model,
messages: messages,
temperature: options.temperature || 0.7,
max_tokens: options.maxTokens || 3000,
top_p: options.topP || 0.9,
stream: options.stream || false
});
const endTime = Date.now();
const processingTime = (endTime - startTime) / 1000;
const result = {
content: response.choices[0].message.content,
usage: response.usage,
processingTime,
model: this.model,
finishReason: response.choices[0].finish_reason
};
logger.info(`Groq API call completed in ${processingTime}s, tokens: ${result.usage.total_tokens}`);
return result;
} catch (error) {
logger.error('Groq API error:', error);
throw new Error(`Groq API error: ${error.message}`);
}
}
async generateEngineeringPlan(query, context = {}) {
const systemPrompt = `You are an expert engineering consultant with deep knowledge in structural engineering, mechanical engineering, electrical engineering, and civil engineering.
Your task is to analyze engineering problems and create detailed, step-by-step plans to solve them.
Guidelines:
1. Break down complex problems into clear, actionable steps
2. Consider safety, feasibility, and best practices
3. Include relevant calculations, standards, and regulations
4. Suggest appropriate tools and resources
5. Provide time estimates for each step
6. Consider potential challenges and mitigation strategies
Context: ${JSON.stringify(context)}
Create a comprehensive plan for the following engineering question:`;
const messages = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: query }
];
return await this.generateResponse(messages, {
temperature: 0.3, // Lower temperature for more focused, technical responses
maxTokens: 3000
});
}
async executePlan(plan, tools = []) {
const systemPrompt = `You are an engineering execution specialist. You have access to various tools to help execute engineering plans.
Available tools:
${tools.map(tool => `- ${tool.name}: ${tool.description}`).join('\n')}
Your task is to execute the given plan step by step, using the appropriate tools when needed.
Guidelines:
1. Execute each step of the plan systematically
2. Use tools when necessary to gather information or perform calculations
3. Provide detailed results for each step
4. Document any issues or deviations from the plan
5. Ensure all safety and quality standards are met
Execute the following plan:`;
const messages = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: plan }
];
return await this.generateResponse(messages, {
temperature: 0.5,
maxTokens: 4000
});
}
async expandQuery(query, context = {}) {
const systemPrompt = `You are a query expansion specialist for engineering problems. Your task is to take a user's engineering question and expand it into a more comprehensive, detailed query that will help find the most relevant information.
Guidelines:
1. Identify key engineering concepts and terminology
2. Suggest related questions and considerations
3. Include relevant standards, codes, and regulations
4. Consider different engineering disciplines that might be relevant
5. Add context about project scope, constraints, and requirements
Context: ${JSON.stringify(context)}
Expand the following engineering query:`;
const messages = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: query }
];
return await this.generateResponse(messages, {
temperature: 0.4,
maxTokens: 1500
});
}
async generateReport(data, reportType = 'general') {
const systemPrompts = {
general: `You are an engineering report generator. Create a comprehensive, professional engineering report based on the provided data.`,
technical: `You are a technical engineering report generator. Create a detailed technical report with calculations, analysis, and recommendations.`,
summary: `You are an engineering summary generator. Create a concise executive summary of the engineering analysis and findings.`
};
const systemPrompt = systemPrompts[reportType] || systemPrompts.general;
const messages = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: `Generate a ${reportType} report based on this data: ${JSON.stringify(data)}` }
];
return await this.generateResponse(messages, {
temperature: 0.3,
maxTokens: 2500
});
}
async searchAndAnalyze(query, searchResults = []) {
const systemPrompt = `You are an engineering analysis specialist. Analyze the provided search results and provide a comprehensive analysis of the engineering question.
Guidelines:
1. Synthesize information from multiple sources
2. Identify key findings and insights
3. Highlight important calculations, formulas, or methodologies
4. Note any conflicting information or gaps
5. Provide recommendations based on the analysis
6. Cite relevant sources and standards
Search results: ${JSON.stringify(searchResults)}
Analyze the following engineering question:`;
const messages = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: query }
];
return await this.generateResponse(messages, {
temperature: 0.4,
maxTokens: 3000
});
}
async validatePlan(plan, feedback = []) {
const systemPrompt = `You are an engineering plan validator. Review the provided plan and feedback to determine if the plan is valid, complete, and follows engineering best practices.
Guidelines:
1. Check for completeness and logical flow
2. Verify technical accuracy
3. Ensure safety considerations are addressed
4. Validate against engineering standards
5. Consider the provided feedback
6. Suggest improvements if needed
Feedback: ${JSON.stringify(feedback)}
Validate the following plan:`;
const messages = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: plan }
];
return await this.generateResponse(messages, {
temperature: 0.2,
maxTokens: 2000
});
}
async getModelInfo() {
try {
// Get available models
const models = await this.groq.models.list();
return {
currentModel: this.model,
availableModels: models.data,
apiStatus: 'connected'
};
} catch (error) {
logger.error('Error getting model info:', error);
return {
currentModel: this.model,
availableModels: [],
apiStatus: 'error',
error: error.message
};
}
}
async testConnection() {
try {
const testResponse = await this.generateResponse([
{ role: 'user', content: 'Hello, this is a test message.' }
], {
maxTokens: 10
});
return {
success: true,
response: testResponse,
model: this.model
};
} catch (error) {
logger.error('Groq connection test failed:', error);
return {
success: false,
error: error.message
};
}
}
}
module.exports = new GroqService();
+290
View File
@@ -0,0 +1,290 @@
const groqService = require('./groqService');
const { Plan, Message, Conversation } = require('../models');
const logger = require('../utils/logger');
class Model1Service {
constructor() {
this.modelType = 'MODEL1';
this.systemPrompt = `You are MODEL1, an expert engineering reasoning system. Your primary function is to analyze complex engineering problems and create detailed, step-by-step plans to solve them.
Your capabilities include:
- Structural engineering analysis
- Mechanical engineering design
- Electrical engineering systems
- Civil engineering projects
- Computer Enginnering
- Safety and compliance considerations
- Cost estimation and feasibility analysis
- Risk assessment and mitigation
- All other engineering related courses
When creating plans, always:
1. Break down complex problems into clear, actionable steps
2. Consider safety, feasibility, and best practices
3. Include relevant calculations, standards, and regulations
4. Suggest appropriate tools and resources
5. Provide time estimates for each step
6. Consider potential challenges and mitigation strategies
7. DO NOT give the answer, your job is to plan
8. Only give plan when the user ask about actionable questions
Format your response as a structured plan with:
- Title: Clear, descriptive title
- Description: Brief overview of the problem and approach
- Steps: Numbered list of detailed steps
- Tools Required: List of tools needed
- Estimated Duration: Total time estimate
- Complexity Score: 1-10 scale
- Safety Considerations: Key safety points
- Quality Checks: Verification steps`;
}
async generatePlan(query, context = {}) {
try {
const startTime = Date.now();
// Prepare context for the model
const enhancedContext = {
...context,
timestamp: new Date().toISOString(),
modelType: this.modelType
};
// Generate the plan using Groq
const response = await groqService.generateEngineeringPlan(query, enhancedContext);
const endTime = Date.now();
const processingTime = (endTime - startTime) / 1000;
// Parse the response to extract structured plan data
const planData = this.parsePlanResponse(response.content);
logger.info(`MODEL1 plan generated in ${processingTime}s for query: ${query.substring(0, 100)}...`);
return {
...planData,
processingTime,
tokensUsed: response.usage.total_tokens,
model: response.model,
finishReason: response.finishReason
};
} catch (error) {
logger.error('MODEL1 plan generation error:', error);
throw new Error(`MODEL1 plan generation failed: ${error.message}`);
}
}
parsePlanResponse(content) {
try {
// Extract structured data from the response
const lines = content.split('\n');
let title = '';
let description = '';
let steps = [];
let toolsRequired = [];
let estimatedDuration = 0;
let complexityScore = 5;
let safetyConsiderations = [];
let qualityChecks = [];
let currentSection = '';
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine.toLowerCase().includes('title:')) {
title = trimmedLine.split(':')[1]?.trim() || 'Engineering Plan';
} else if (trimmedLine.toLowerCase().includes('description:')) {
description = trimmedLine.split(':')[1]?.trim() || '';
} else if (trimmedLine.toLowerCase().includes('steps:')) {
currentSection = 'steps';
} else if (trimmedLine.toLowerCase().includes('tools required:')) {
currentSection = 'tools';
} else if (trimmedLine.toLowerCase().includes('estimated duration:')) {
const duration = trimmedLine.split(':')[1]?.trim();
estimatedDuration = this.parseDuration(duration);
} else if (trimmedLine.toLowerCase().includes('complexity score:')) {
const score = trimmedLine.split(':')[1]?.trim();
complexityScore = parseInt(score) || 5;
} else if (trimmedLine.toLowerCase().includes('safety considerations:')) {
currentSection = 'safety';
} else if (trimmedLine.toLowerCase().includes('quality checks:')) {
currentSection = 'quality';
} else if (trimmedLine.match(/^\d+\./)) {
if (currentSection === 'steps') {
steps.push(trimmedLine);
}
} else if (trimmedLine.startsWith('-')) {
if (currentSection === 'tools') {
toolsRequired.push(trimmedLine.substring(1).trim());
} else if (currentSection === 'safety') {
safetyConsiderations.push(trimmedLine.substring(1).trim());
} else if (currentSection === 'quality') {
qualityChecks.push(trimmedLine.substring(1).trim());
}
}
}
// If no structured data found, create a basic plan
if (!title) {
title = 'Engineering Plan';
description = content.substring(0, 200) + '...';
steps = [content];
}
return {
title,
description,
steps,
toolsRequired,
estimatedDuration,
complexityScore,
safetyConsiderations,
qualityChecks,
rawContent: content
};
} catch (error) {
logger.error('Error parsing plan response:', error);
return {
title: 'Engineering Plan',
description: content,
steps: [content],
toolsRequired: [],
estimatedDuration: 60,
complexityScore: 5,
safetyConsiderations: [],
qualityChecks: [],
rawContent: content
};
}
}
parseDuration(duration) {
if (!duration) return 60;
const match = duration.match(/(\d+)\s*(hour|hr|minute|min|day|d)/i);
if (match) {
const value = parseInt(match[1]);
const unit = match[2].toLowerCase();
switch (unit) {
case 'hour':
case 'hr':
return value * 60;
case 'minute':
case 'min':
return value;
case 'day':
case 'd':
return value * 24 * 60;
default:
return 60;
}
}
return 60; // Default to 60 minutes
}
async savePlan(planData, conversationId, userId) {
try {
const plan = await Plan.create({
conversation_id: conversationId,
title: planData.title,
description: planData.description,
steps: planData.steps,
status: 'draft',
tools_required: planData.toolsRequired,
estimated_duration: planData.estimatedDuration,
complexity_score: planData.complexityScore,
metadata: {
safetyConsiderations: planData.safetyConsiderations,
qualityChecks: planData.qualityChecks,
processingTime: planData.processingTime,
tokensUsed: planData.tokensUsed,
model: planData.model
}
});
// Create a message record for the plan
await Message.create({
conversation_id: conversationId,
plan_id: plan.id,
role: 'assistant',
content: planData.rawContent,
message_type: 'plan',
metadata: {
modelType: this.modelType,
processingTime: planData.processingTime,
tokensUsed: planData.tokensUsed
}
});
logger.info(`Plan saved: ${plan.id} for conversation: ${conversationId}`);
return plan;
} catch (error) {
logger.error('Error saving plan:', error);
throw new Error(`Failed to save plan: ${error.message}`);
}
}
async validatePlan(planId, feedback = []) {
try {
const plan = await Plan.findByPk(planId);
if (!plan) {
throw new Error('Plan not found');
}
const validationResponse = await groqService.validatePlan(plan.description, feedback);
// Update plan with validation results
await plan.update({
status: validationResponse.content.includes('valid') ? 'approved' : 'rejected',
approval_feedback: validationResponse.content,
metadata: {
...plan.metadata,
validation: {
response: validationResponse.content,
tokensUsed: validationResponse.usage.total_tokens,
processingTime: validationResponse.processingTime
}
}
});
logger.info(`Plan validated: ${planId}, status: ${plan.status}`);
return {
plan,
validation: validationResponse
};
} catch (error) {
logger.error('Plan validation error:', error);
throw new Error(`Plan validation failed: ${error.message}`);
}
}
async getModelStatus() {
try {
const groqInfo = await groqService.getModelInfo();
const connectionTest = await groqService.testConnection();
return {
modelType: this.modelType,
status: connectionTest.success ? 'active' : 'error',
groqInfo,
connectionTest,
lastChecked: new Date().toISOString()
};
} catch (error) {
logger.error('MODEL1 status check error:', error);
return {
modelType: this.modelType,
status: 'error',
error: error.message,
lastChecked: new Date().toISOString()
};
}
}
}
module.exports = new Model1Service();
+500
View File
@@ -0,0 +1,500 @@
const groqService = require('./groqService');
const { Plan, ToolExecution, Document } = require('../models');
const logger = require('../utils/logger');
const graphRagService = require('./graphRagService');
const embeddingService = require('./embeddingService');
const axios = require('axios');
class QueryModelService {
constructor() {
this.modelType = 'QUERYMODEL';
this.systemPrompt = `You are QUERYMODEL, an expert engineering execution system. Your primary function is to execute engineering plans using various tools and resources.
Your capabilities include:
- Executing step-by-step engineering plans
- Using specialized tools for calculations, analysis, and reporting
- Coordinating with external resources and databases
- Generating detailed execution reports
- Handling complex engineering workflows
- Ensuring quality and safety standards
Available tools:
- Query Expander: Enhance and clarify engineering queries
- Extraction: Search and extract information from documents
- Report1: Generate formatted engineering reports
- Report2: Create detailed engineering files and documents
- Web Search: Find current engineering information and standards
- Encyclopedia PDF: Search specialized engineering documents
When executing plans, always:
1. Follow the plan steps systematically
2. Use appropriate tools for each step
3. Document all results and findings
4. Ensure quality and safety standards
5. Provide detailed progress updates
6. Handle errors and deviations gracefully`;
}
async executePlan(planId, options = {}) {
try {
const startTime = Date.now();
// Get the plan
const plan = await Plan.findByPk(planId);
if (!plan) {
throw new Error('Plan not found');
}
if (plan.status !== 'approved') {
throw new Error('Plan must be approved before execution');
}
// Update plan status to executing
await plan.update({ status: 'executing' });
// Prepare execution context
const executionContext = {
planId,
planTitle: plan.title,
planSteps: plan.steps,
toolsRequired: plan.tools_required,
estimatedDuration: plan.estimated_duration,
complexityScore: plan.complexity_score,
...options
};
// Execute the plan using Groq
const response = await groqService.executePlan(plan.description, plan.tools_required);
const endTime = Date.now();
const processingTime = (endTime - startTime) / 1000;
// Parse execution results
const executionResults = this.parseExecutionResponse(response.content);
// Update plan with execution results
await plan.update({
status: 'completed',
execution_result: executionResults,
metadata: {
...plan.metadata,
execution: {
processingTime,
tokensUsed: response.usage.total_tokens,
model: response.model,
finishReason: response.finishReason
}
}
});
logger.info(`QUERYMODEL execution completed in ${processingTime}s for plan: ${planId}`);
return {
plan,
executionResults,
processingTime,
tokensUsed: response.usage.total_tokens,
model: response.model
};
} catch (error) {
logger.error('QUERYMODEL execution error:', error);
// Update plan status to failed
if (plan) {
await plan.update({
status: 'failed',
execution_result: { error: error.message }
});
}
throw new Error(`QUERYMODEL execution failed: ${error.message}`);
}
}
parseExecutionResponse(content) {
try {
const lines = content.split('\n');
let stepsCompleted = [];
let results = [];
let toolsUsed = [];
let issues = [];
let recommendations = [];
let currentSection = '';
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine.toLowerCase().includes('steps completed:')) {
currentSection = 'steps';
} else if (trimmedLine.toLowerCase().includes('results:')) {
currentSection = 'results';
} else if (trimmedLine.toLowerCase().includes('tools used:')) {
currentSection = 'tools';
} else if (trimmedLine.toLowerCase().includes('issues:')) {
currentSection = 'issues';
} else if (trimmedLine.toLowerCase().includes('recommendations:')) {
currentSection = 'recommendations';
} else if (trimmedLine.match(/^\d+\./)) {
if (currentSection === 'steps') {
stepsCompleted.push(trimmedLine);
}
} else if (trimmedLine.startsWith('-')) {
if (currentSection === 'results') {
results.push(trimmedLine.substring(1).trim());
} else if (trimmedLine === 'tools') {
toolsUsed.push(trimmedLine.substring(1).trim());
} else if (currentSection === 'issues') {
issues.push(trimmedLine.substring(1).trim());
} else if (currentSection === 'recommendations') {
recommendations.push(trimmedLine.substring(1).trim());
}
}
}
return {
stepsCompleted,
results,
toolsUsed,
issues,
recommendations,
rawContent: content,
executionStatus: issues.length > 0 ? 'completed_with_issues' : 'completed_successfully'
};
} catch (error) {
logger.error('Error parsing execution response:', error);
return {
stepsCompleted: [],
results: [content],
toolsUsed: [],
issues: [],
recommendations: [],
rawContent: content,
executionStatus: 'completed'
};
}
}
async executeTool(toolName, toolType, inputParameters, planId) {
try {
const startTime = Date.now();
// Create tool execution record
const toolExecution = await ToolExecution.create({
plan_id: planId,
tool_name: toolName,
tool_type: toolType,
input_parameters: inputParameters,
status: 'running'
});
let result;
// Execute the specific tool
switch (toolType) {
case 'query_expander':
result = await this.executeQueryExpander(inputParameters);
break;
case 'extraction':
result = await this.executeExtraction(inputParameters);
break;
case 'report1':
result = await this.executeReport1(inputParameters);
break;
case 'report2':
result = await this.executeReport2(inputParameters);
break;
case 'web_search':
result = await this.executeWebSearch(inputParameters);
break;
case 'encyclopedia_pdf':
result = await this.executeEncyclopediaPdf(inputParameters);
break;
case 'orchestrate':
result = await this.executeOrchestrate(inputParameters);
break;
default:
throw new Error(`Unknown tool type: ${toolType}`);
}
const endTime = Date.now();
const executionTime = (endTime - startTime) / 1000;
// Update tool execution record
await toolExecution.update({
output_result: result,
status: 'completed',
execution_time: executionTime,
tokens_used: result.tokensUsed || 0
});
logger.info(`Tool executed: ${toolName} in ${executionTime}s`);
return toolExecution;
} catch (error) {
logger.error(`Tool execution error: ${toolName}`, error);
// Update tool execution record with error
if (toolExecution) {
await toolExecution.update({
status: 'failed',
error_message: error.message
});
}
throw new Error(`Tool execution failed: ${error.message}`);
}
}
async executeQueryExpander(inputParameters) {
const { query, context } = inputParameters;
const response = await groqService.expandQuery(query, context);
return {
expandedQuery: response.content,
tokensUsed: response.usage.total_tokens,
processingTime: response.processingTime
};
}
async executeOrchestrate(inputParameters) {
const { query, category, topK = 5, generateReport = true } = inputParameters;
// 1) Expand query
const expanded = await this.executeQueryExpander({ query, context: { category } });
const expandedQuery = (expanded.expandedQuery || '').trim() || query;
// 2) Extract from RAG using original query (use 'general' category for now)
const extraction = await this.executeExtraction({ query: query, category: 'general', topK });
// 3) If low confidence, augment with web search using original query
let web = null;
if (extraction.confidence < 0.7) {
try {
web = await this.executeWebSearch({ query: query, maxResults: 5, searchDepth: 'basic', includeAnswer: true });
} catch (e) {
// continue without web
}
}
// 4) Optionally generate a brief report
let report = null;
if (generateReport) {
// Get full document content for better report generation
const documentDetails = await Promise.all(
extraction.results.slice(0, 3).map(async (result) => {
try {
const doc = await Document.findByPk(result.id, {
attributes: ['id', 'original_filename', 'extracted_text', 'category']
});
return {
filename: result.original_filename,
content: doc?.extracted_text || result.snippet,
score: result.score,
category: result.category
};
} catch (error) {
return {
filename: result.original_filename,
content: result.snippet,
score: result.score,
category: result.category
};
}
})
);
const reportData = {
query,
expandedQuery,
relevantDocuments: documentDetails,
webAnswer: web?.answer || null,
webCount: web?.totalResults || 0
};
const reportResp = await groqService.generateReport(reportData, 'summary');
report = {
content: reportResp.content,
tokensUsed: reportResp.usage?.total_tokens,
processingTime: reportResp.processingTime
};
}
return {
query,
expandedQuery,
extraction,
web,
report,
decision: {
usedWeb: !!web,
confidence: extraction.confidence
}
};
}
async executeExtraction(inputParameters) {
const { query, topK = 5, category } = inputParameters;
// 1) Try Graph RAG first
const graph = await graphRagService.graphSearch({ query, category });
let results = graph.results.map((r) => ({
id: r.id,
original_filename: r.original_filename,
snippet: r.snippet,
category: r.category,
score: r.score,
source: 'graph'
}));
// 2) If not enough, fallback to semantic search
if (results.length < topK) {
const queryEmbedding = await embeddingService.embedText(query);
const where = { is_indexed: true };
if (category) where.category = category;
const docs = await Document.findAll({
where,
attributes: ['id', 'original_filename', 'extracted_text', 'embeddings', 'category']
});
const scored = [];
for (const d of docs) {
const emb = d.embeddings || [];
if (!Array.isArray(emb) || emb.length === 0) continue;
const score = embeddingService.cosineSimilarity(queryEmbedding, emb);
scored.push({
id: d.id,
original_filename: d.original_filename,
snippet: (d.extracted_text || '').slice(0, 400),
category: d.category,
score,
source: 'semantic'
});
}
scored.sort((a, b) => b.score - a.score);
const need = topK - results.length;
results = results.concat(scored.slice(0, Math.max(0, need)));
}
// Trim to topK and return
results.sort((a, b) => b.score - a.score);
results = results.slice(0, topK);
// Confidence heuristic
const confidence = results.length > 0 ? Math.min(0.99, Math.max(0.5, results[0].score)) : 0;
return {
query,
topK,
results,
confidence
};
}
async executeReport1(inputParameters) {
const { data, format, context } = inputParameters;
const response = await groqService.generateReport(data, 'technical');
return {
report: response.content,
format: format || 'technical',
tokensUsed: response.usage.total_tokens,
processingTime: response.processingTime
};
}
async executeReport2(inputParameters) {
const { data, format, filename } = inputParameters;
const response = await groqService.generateReport(data, format);
return {
report: response.content,
filename: filename || `report_${Date.now()}.txt`,
format: format || 'general',
tokensUsed: response.usage.total_tokens,
processingTime: response.processingTime
};
}
async executeWebSearch(inputParameters) {
const { query, maxResults = 5, searchDepth = 'basic', includeAnswer = true } = inputParameters;
const apiKey = process.env.TAVILY_API_KEY;
if (!apiKey) {
throw new Error('TAVILY_API_KEY is not set');
}
const url = 'https://api.tavily.com/search';
const payload = {
query,
search_depth: searchDepth,
include_answer: includeAnswer,
max_results: Math.min(10, Math.max(1, maxResults))
};
try {
const resp = await axios.post(url, payload, {
headers: { Authorization: `Bearer ${apiKey}` }, // ✅ Correct way to pass API key
timeout: 15000,
});
const data = resp.data || {};
const results = (data.results || []).map((r) => ({
title: r.title,
url: r.url,
snippet: r.content || r.snippet || '',
score: r.score ?? undefined,
published: r.published_date || undefined,
}));
return {
query,
answer: data.answer || null,
results,
totalResults: results.length,
source: 'tavily',
};
} catch (error) {
logger.error('Tavily web search error:', error?.response?.data || error.message);
throw new Error('Web search failed');
}
}
async executeEncyclopediaPdf(inputParameters) {
const { query, documents, context } = inputParameters;
// Use our RAG for offline PDF search (category filter could be 'encyclopedia')
const graph = await graphRagService.graphSearch({ query, category: 'encyclopedia' });
return {
query,
results: graph.results,
totalResults: graph.results.length,
source: 'offline_rag'
};
}
async getModelStatus() {
try {
const groqInfo = await groqService.getModelInfo();
const connectionTest = await groqService.testConnection();
return {
modelType: this.modelType,
status: connectionTest.success ? 'active' : 'error',
groqInfo,
connectionTest,
lastChecked: new Date().toISOString()
};
} catch (error) {
logger.error('QUERYMODEL status check error:', error);
return {
modelType: this.modelType,
status: 'error',
error: error.message,
lastChecked: new Date().toISOString()
};
}
}
}
module.exports = new QueryModelService();
+208
View File
@@ -0,0 +1,208 @@
const path = require('path');
const fs = require('fs');
const logger = require('./logger');
class ConfigLoader {
constructor() {
this.config = {};
this.env = process.env.NODE_ENV || 'development';
this.loadConfig();
}
loadConfig() {
try {
// Load base configuration
this.loadBaseConfig();
// Load environment-specific configuration
this.loadEnvConfig();
// Validate configuration
this.validateConfig();
logger.info(`Configuration loaded for environment: ${this.env}`);
} catch (error) {
logger.error('Configuration loading failed:', error);
throw error;
}
}
loadBaseConfig() {
// Load from appConfig.js
const { appConfig } = require('../config/appConfig');
this.config = { ...appConfig };
}
loadEnvConfig() {
const envFile = path.join(__dirname, '../../', `env.${this.env}`);
if (fs.existsSync(envFile)) {
logger.info(`Loading environment configuration from: ${envFile}`);
// Parse environment file
const envContent = fs.readFileSync(envFile, 'utf8');
const envVars = this.parseEnvFile(envContent);
// Override configuration with environment-specific values
Object.assign(process.env, envVars);
// Reload configuration with new environment variables
const { appConfig } = require('../config/appConfig');
this.config = { ...appConfig };
} else {
logger.warn(`Environment configuration file not found: ${envFile}`);
}
}
parseEnvFile(content) {
const envVars = {};
const lines = content.split('\n');
for (const line of lines) {
const trimmedLine = line.trim();
// Skip empty lines and comments
if (!trimmedLine || trimmedLine.startsWith('#')) {
continue;
}
// Parse KEY=VALUE format
const equalIndex = trimmedLine.indexOf('=');
if (equalIndex > 0) {
const key = trimmedLine.substring(0, equalIndex).trim();
const value = trimmedLine.substring(equalIndex + 1).trim();
// Remove quotes if present
const cleanValue = value.replace(/^["']|["']$/g, '');
envVars[key] = cleanValue;
}
}
return envVars;
}
validateConfig() {
const errors = [];
const warnings = [];
// Required fields validation
if (!this.config.apis.groq.apiKey) {
errors.push('GROQ_API_KEY is required');
}
if (!this.config.auth.jwtSecret) {
errors.push('JWT_SECRET is required');
}
if (!this.config.database.password) {
errors.push('DB_PASSWORD is required');
}
// Environment-specific validations
if (this.env === 'production') {
if (this.config.auth.jwtSecret === 'dev_secret_key_change_in_production') {
errors.push('JWT_SECRET must be changed for production');
}
if (!this.config.database.ssl) {
warnings.push('Database SSL is recommended for production');
}
}
// Log warnings
warnings.forEach(warning => {
logger.warn(`Configuration warning: ${warning}`);
});
// Throw errors
if (errors.length > 0) {
const errorMessage = `Configuration errors: ${errors.join(', ')}`;
logger.error(errorMessage);
throw new Error(errorMessage);
}
}
get(key, defaultValue = null) {
const keys = key.split('.');
let value = this.config;
for (const k of keys) {
if (value && typeof value === 'object' && k in value) {
value = value[k];
} else {
return defaultValue;
}
}
return value;
}
getAll() {
return this.config;
}
getServerConfig() {
return this.config.server;
}
getDatabaseConfig() {
return this.config.database;
}
getApiConfig() {
return this.config.apis;
}
getAuthConfig() {
return this.config.auth;
}
getUploadConfig() {
return this.config.upload;
}
getRateLimitConfig() {
return this.config.rateLimit;
}
getLoggingConfig() {
return this.config.logging;
}
getModelConfig() {
return this.config.models;
}
getSecurityConfig() {
return this.config.security;
}
getMonitoringConfig() {
return this.config.monitoring;
}
getDevelopmentConfig() {
return this.config.development;
}
isDevelopment() {
return this.env === 'development';
}
isProduction() {
return this.env === 'production';
}
isTest() {
return this.env === 'test';
}
getEnvironment() {
return this.env;
}
}
// Create singleton instance
const configLoader = new ConfigLoader();
module.exports = configLoader;
+110
View File
@@ -0,0 +1,110 @@
const { testConnection, syncDatabase, dropDatabase } = require('../config/database');
const logger = require('./logger');
const initializeDatabase = async (force = false) => {
try {
console.log('🚀 Initializing database...');
// Test connection
console.log('📡 Testing database connection...');
const connected = await testConnection();
if (!connected) {
throw new Error('Database connection failed');
}
console.log('✅ Database connection successful');
// Sync database (create tables)
console.log('🔧 Synchronizing database schema...');
const synced = await syncDatabase(force);
if (!synced) {
throw new Error('Database synchronization failed');
}
console.log('✅ Database schema synchronized');
console.log('🎉 Database initialization completed successfully!');
return true;
} catch (error) {
console.error('❌ Database initialization failed:', error.message);
logger.error('Database initialization error:', error);
return false;
}
};
const resetDatabase = async () => {
try {
console.log('🔄 Resetting database...');
// Drop all tables
console.log('🗑️ Dropping all tables...');
const dropped = await dropDatabase();
if (!dropped) {
throw new Error('Database drop failed');
}
console.log('✅ All tables dropped');
// Reinitialize
console.log('🔧 Reinitializing database...');
const initialized = await initializeDatabase(true);
if (!initialized) {
throw new Error('Database reinitialization failed');
}
console.log('✅ Database reset completed');
return true;
} catch (error) {
console.error('❌ Database reset failed:', error.message);
logger.error('Database reset error:', error);
return false;
}
};
const checkDatabaseStatus = async () => {
try {
console.log('🔍 Checking database status...');
const connected = await testConnection();
if (!connected) {
console.log('❌ Database connection failed');
return false;
}
console.log('✅ Database is connected and ready');
return true;
} catch (error) {
console.error('❌ Database status check failed:', error.message);
return false;
}
};
// Run initialization if this file is executed directly
if (require.main === module) {
const args = process.argv.slice(2);
const command = args[0] || 'init';
switch (command) {
case 'init':
initializeDatabase().then(success => {
process.exit(success ? 0 : 1);
});
break;
case 'reset':
resetDatabase().then(success => {
process.exit(success ? 0 : 1);
});
break;
case 'status':
checkDatabaseStatus().then(success => {
process.exit(success ? 0 : 1);
});
break;
default:
console.log('Usage: node databaseInit.js [init|reset|status]');
process.exit(1);
}
}
module.exports = {
initializeDatabase,
resetDatabase,
checkDatabaseStatus
};
+212
View File
@@ -0,0 +1,212 @@
const { initializeDatabase, resetDatabase, checkDatabaseStatus } = require('./databaseInit');
const { sequelize } = require('../config/database');
const logger = require('./logger');
// Import seeders
const createAdminUser = require('../seeders/001_create_admin_user');
const createSampleTrainingData = require('../seeders/002_create_sample_training_data');
const runMigrations = async () => {
try {
console.log('🔄 Running database migrations...');
// Import all migration files
const migrations = [
require('../migrations/001_create_users'),
require('../migrations/002_create_conversations'),
require('../migrations/003_create_plans'),
require('../migrations/004_create_messages'),
require('../migrations/005_create_documents'),
require('../migrations/006_create_feedback'),
require('../migrations/007_create_model_versions'),
require('../migrations/008_create_training_data'),
require('../migrations/009_create_tool_executions')
];
for (const migration of migrations) {
console.log(` Running migration: ${migration.up.name || 'unnamed'}`);
await migration.up(sequelize.getQueryInterface(), sequelize.constructor);
}
console.log('✅ All migrations completed successfully');
return true;
} catch (error) {
console.error('❌ Migration failed:', error);
logger.error('Migration error:', error);
return false;
}
};
const runSeeders = async () => {
try {
console.log('🌱 Running database seeders...');
// Run seeders
await createAdminUser.up(sequelize.getQueryInterface(), sequelize.constructor);
await createSampleTrainingData.up(sequelize.getQueryInterface(), sequelize.constructor);
console.log('✅ All seeders completed successfully');
return true;
} catch (error) {
console.error('❌ Seeding failed:', error);
logger.error('Seeding error:', error);
return false;
}
};
const setupDatabase = async (options = {}) => {
try {
console.log('🚀 Setting up database...');
const {
force = false,
seed = true,
migrations = true
} = options;
// Check database status
const status = await checkDatabaseStatus();
if (!status) {
throw new Error('Database connection failed');
}
// Run migrations if requested
if (migrations) {
const migrated = await runMigrations();
if (!migrated) {
throw new Error('Migrations failed');
}
}
// Run seeders if requested
if (seed) {
const seeded = await runSeeders();
if (!seeded) {
throw new Error('Seeding failed');
}
}
console.log('🎉 Database setup completed successfully!');
return true;
} catch (error) {
console.error('❌ Database setup failed:', error.message);
logger.error('Database setup error:', error);
return false;
}
};
const getDatabaseInfo = async () => {
try {
console.log('📊 Database Information:');
// Get table information
const [results] = await sequelize.query(`
SELECT
schemaname,
tablename,
tableowner
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY tablename;
`);
console.log(` Tables: ${results.length}`);
results.forEach(table => {
console.log(` - ${table.tablename}`);
});
// Get database size
const [sizeResult] = await sequelize.query(`
SELECT pg_size_pretty(pg_database_size(current_database())) as size;
`);
console.log(` Database size: ${sizeResult[0].size}`);
return {
tables: results,
size: sizeResult[0].size
};
} catch (error) {
console.error('❌ Error getting database info:', error);
return null;
}
};
const backupDatabase = async (backupPath) => {
try {
console.log('💾 Creating database backup...');
// This would typically use pg_dump in a real implementation
console.log(` Backup path: ${backupPath}`);
console.log(' Note: Implement actual backup logic with pg_dump');
return true;
} catch (error) {
console.error('❌ Backup failed:', error);
return false;
}
};
// Command line interface
if (require.main === module) {
const args = process.argv.slice(2);
const command = args[0] || 'help';
const runCommand = async () => {
switch (command) {
case 'init':
await setupDatabase({ force: false, seed: true, migrations: true });
break;
case 'reset':
await resetDatabase();
break;
case 'migrate':
await runMigrations();
break;
case 'seed':
await runSeeders();
break;
case 'status':
await checkDatabaseStatus();
break;
case 'info':
await getDatabaseInfo();
break;
case 'backup':
const backupPath = args[1] || './backup.sql';
await backupDatabase(backupPath);
break;
case 'help':
default:
console.log(`
Database Manager Commands:
init - Initialize database with migrations and seeders
reset - Reset database (drop and recreate)
migrate - Run migrations only
seed - Run seeders only
status - Check database connection status
info - Show database information
backup - Create database backup
help - Show this help message
Usage: node databaseManager.js [command]
`);
break;
}
};
runCommand().then(() => {
process.exit(0);
}).catch(error => {
console.error('Command failed:', error);
process.exit(1);
});
}
module.exports = {
runMigrations,
runSeeders,
setupDatabase,
getDatabaseInfo,
backupDatabase
};
+40
View File
@@ -0,0 +1,40 @@
const winston = require('winston');
const path = require('path');
// Create logs directory if it doesn't exist
const fs = require('fs');
const logsDir = path.join(__dirname, '../../logs');
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true });
}
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'reason-flow' },
transports: [
new winston.transports.File({
filename: path.join(logsDir, 'error.log'),
level: 'error'
}),
new winston.transports.File({
filename: path.join(logsDir, 'combined.log')
})
]
});
// Add console transport for development
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
}
module.exports = logger;
+134
View File
@@ -0,0 +1,134 @@
const groqService = require('../services/groqService');
const model1Service = require('../services/model1Service');
const queryModelService = require('../services/queryModelService');
const logger = require('./logger');
const testGroqConnection = async () => {
console.log('🧪 Testing Groq API Connection...');
try {
const connectionTest = await groqService.testConnection();
if (connectionTest.success) {
console.log('✅ Groq API connection successful');
console.log(`Model: ${connectionTest.model}`);
console.log(`Response: ${connectionTest.response.content.substring(0, 100)}...`);
} else {
console.log('❌ Groq API connection failed');
console.log(`Error: ${connectionTest.error}`);
}
return connectionTest;
} catch (error) {
console.log('❌ Groq API test failed:', error.message);
return { success: false, error: error.message };
}
};
const testModel1 = async () => {
console.log('\n🧪 Testing MODEL1 (Engineering Plan Generation)...');
try {
const testQuery = "How do I design a bridge that can handle heavy traffic loads?";
const planData = await model1Service.generatePlan(testQuery, {
context: { test: true }
});
console.log('✅ MODEL1 plan generation successful');
console.log(`Title: ${planData.title}`);
console.log(`Description: ${planData.description.substring(0, 100)}...`);
console.log(`Steps: ${planData.steps.length}`);
console.log(`Tools Required: ${planData.toolsRequired.length}`);
console.log(`Processing Time: ${planData.processingTime}s`);
console.log(`Tokens Used: ${planData.tokensUsed}`);
return planData;
} catch (error) {
console.log('❌ MODEL1 test failed:', error.message);
return { success: false, error: error.message };
}
};
const testQueryModel = async () => {
console.log('\n🧪 Testing QUERYMODEL (Plan Execution)...');
try {
const testPlan = "Execute the following engineering plan: Design a bridge for heavy traffic loads. Steps: 1. Calculate load requirements 2. Design structural elements 3. Check safety factors";
const executionResult = await queryModelService.executePlan(testPlan, {
test: true
});
console.log('✅ QUERYMODEL execution successful');
console.log(`Execution Status: ${executionResult.executionResults.executionStatus}`);
console.log(`Steps Completed: ${executionResult.executionResults.stepsCompleted.length}`);
console.log(`Results: ${executionResult.executionResults.results.length}`);
console.log(`Processing Time: ${executionResult.processingTime}s`);
console.log(`Tokens Used: ${executionResult.tokensUsed}`);
return executionResult;
} catch (error) {
console.log('❌ QUERYMODEL test failed:', error.message);
return { success: false, error: error.message };
}
};
const testToolExecution = async () => {
console.log('\n🧪 Testing Tool Execution...');
try {
const toolResult = await queryModelService.executeTool(
'query_expander',
'query_expander',
{ query: 'bridge design', context: { test: true } },
'test-plan-id'
);
console.log('✅ Tool execution successful');
console.log(`Tool: ${toolResult.tool_name}`);
console.log(`Status: ${toolResult.status}`);
console.log(`Execution Time: ${toolResult.execution_time}s`);
return toolResult;
} catch (error) {
console.log('❌ Tool execution test failed:', error.message);
return { success: false, error: error.message };
}
};
const runAllTests = async () => {
console.log('🚀 Starting Groq Integration Tests...\n');
const results = {
connection: await testGroqConnection(),
model1: await testModel1(),
queryModel: await testQueryModel(),
toolExecution: await testToolExecution()
};
console.log('\n📊 Test Results Summary:');
console.log(`Connection: ${results.connection.success ? '✅' : '❌'}`);
console.log(`MODEL1: ${results.model1.success !== false ? '✅' : '❌'}`);
console.log(`QUERYMODEL: ${results.queryModel.success !== false ? '✅' : '❌'}`);
console.log(`Tool Execution: ${results.toolExecution.success !== false ? '✅' : '❌'}`);
const allPassed = Object.values(results).every(result =>
result.success !== false
);
console.log(`\n${allPassed ? '🎉 All tests passed!' : '⚠️ Some tests failed'}`);
return results;
};
// Run tests if this file is executed directly
if (require.main === module) {
runAllTests().catch(console.error);
}
module.exports = {
testGroqConnection,
testModel1,
testQueryModel,
testToolExecution,
runAllTests
};
+196
View File
@@ -0,0 +1,196 @@
const configLoader = require('./configLoader');
const logger = require('./logger');
const validateConfiguration = () => {
try {
console.log('🔍 Validating Reason Flow Configuration...\n');
const config = configLoader.getAll();
const env = configLoader.getEnvironment();
console.log(`Environment: ${env}`);
console.log(`Server: ${config.server.host}:${config.server.port}`);
console.log(`Database: ${config.database.host}:${config.database.port}/${config.database.name}`);
console.log(`CORS Origin: ${config.server.corsOrigin}\n`);
// Check required configurations
const checks = [
{
name: 'Groq API Key',
value: config.apis.groq.apiKey,
required: true,
valid: config.apis.groq.apiKey && config.apis.groq.apiKey !== 'your_groq_api_key_here'
},
{
name: 'JWT Secret',
value: config.auth.jwtSecret,
required: true,
valid: config.auth.jwtSecret && config.auth.jwtSecret !== 'your_jwt_secret_here_make_it_long_and_secure'
},
{
name: 'Database Password',
value: config.database.password,
required: true,
valid: config.database.password && config.database.password !== 'your_password_here'
},
{
name: 'OpenAI API Key',
value: config.apis.openai.apiKey,
required: false,
valid: config.apis.openai.apiKey && config.apis.openai.apiKey !== 'your_openai_api_key_here'
},
{
name: 'SERP API Key',
value: config.apis.serp.apiKey,
required: false,
valid: config.apis.serp.apiKey && config.apis.serp.apiKey !== 'your_serp_api_key_here'
}
];
console.log('Configuration Checks:');
console.log('====================');
let allValid = true;
let hasWarnings = false;
checks.forEach(check => {
const status = check.valid ? '✅' : (check.required ? '❌' : '⚠️');
const required = check.required ? '(Required)' : '(Optional)';
console.log(`${status} ${check.name} ${required}`);
if (!check.valid && check.required) {
allValid = false;
} else if (!check.valid && !check.required) {
hasWarnings = true;
}
});
console.log('\n');
// Environment-specific checks
if (env === 'production') {
console.log('Production Environment Checks:');
console.log('==============================');
const prodChecks = [
{
name: 'JWT Secret Security',
valid: config.auth.jwtSecret !== 'dev_secret_key_change_in_production',
message: 'JWT secret should be changed for production'
},
{
name: 'Database SSL',
valid: config.database.ssl,
message: 'Database SSL is recommended for production'
},
{
name: 'Debug Mode',
valid: !config.development.debugMode,
message: 'Debug mode should be disabled in production'
},
{
name: 'Verbose Logging',
valid: !config.development.verboseLogging,
message: 'Verbose logging should be disabled in production'
}
];
prodChecks.forEach(check => {
const status = check.valid ? '✅' : '⚠️';
console.log(`${status} ${check.name}: ${check.message}`);
if (!check.valid) {
hasWarnings = true;
}
});
}
console.log('\n');
// Summary
if (allValid) {
console.log('🎉 Configuration validation passed!');
if (hasWarnings) {
console.log('⚠️ Some optional configurations are missing or need attention.');
}
return true;
} else {
console.log('❌ Configuration validation failed!');
console.log('Please fix the required configuration issues before starting the application.');
return false;
}
} catch (error) {
console.error('❌ Configuration validation error:', error.message);
logger.error('Configuration validation error:', error);
return false;
}
};
const showConfiguration = () => {
try {
console.log('📋 Current Configuration:');
console.log('========================\n');
const config = configLoader.getAll();
console.log('Server Configuration:');
console.log(` Port: ${config.server.port}`);
console.log(` Host: ${config.server.host}`);
console.log(` Environment: ${config.server.env}`);
console.log(` CORS Origin: ${config.server.corsOrigin}\n`);
console.log('Database Configuration:');
console.log(` Host: ${config.database.host}`);
console.log(` Port: ${config.database.port}`);
console.log(` Name: ${config.database.name}`);
console.log(` User: ${config.database.user}`);
console.log(` SSL: ${config.database.ssl}\n`);
console.log('API Configuration:');
console.log(` Groq Model: ${config.apis.groq.model}`);
console.log(` Groq API Key: ${config.apis.groq.apiKey ? 'Set' : 'Not Set'}`);
console.log(` OpenAI API Key: ${config.apis.openai.apiKey ? 'Set' : 'Not Set'}`);
console.log(` SERP API Key: ${config.apis.serp.apiKey ? 'Set' : 'Not Set'}\n`);
console.log('Security Configuration:');
console.log(` JWT Secret: ${config.auth.jwtSecret ? 'Set' : 'Not Set'}`);
console.log(` JWT Expires In: ${config.auth.jwtExpiresIn}`);
console.log(` Helmet Enabled: ${config.security.helmetEnabled}\n`);
console.log('Development Configuration:');
console.log(` Debug Mode: ${config.development.debugMode}`);
console.log(` Verbose Logging: ${config.development.verboseLogging}`);
console.log(` Hot Reload: ${config.development.hotReload}\n`);
} catch (error) {
console.error('❌ Error showing configuration:', error.message);
}
};
// Run validation if this file is executed directly
if (require.main === module) {
const args = process.argv.slice(2);
const command = args[0] || 'validate';
switch (command) {
case 'validate':
const isValid = validateConfiguration();
process.exit(isValid ? 0 : 1);
break;
case 'show':
showConfiguration();
break;
default:
console.log('Usage: node validateConfig.js [validate|show]');
process.exit(1);
}
}
module.exports = {
validateConfiguration,
showConfiguration
};
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env node
// Database setup script for Reason Flow
require('dotenv').config();
const { setupDatabase, getDatabaseInfo } = require('./server/utils/databaseManager');
const { checkDatabaseStatus } = require('./server/utils/databaseInit');
const main = async () => {
console.log('🚀 Reason Flow Database Setup');
console.log('==============================\n');
try {
// Check if database is accessible
console.log('1. Checking database connection...');
const connected = await checkDatabaseStatus();
if (!connected) {
console.error('❌ Database connection failed. Please check your database configuration.');
console.error(' Make sure PostgreSQL is running and your environment variables are set.');
process.exit(1);
}
console.log('✅ Database connection successful\n');
// Setup database
console.log('2. Setting up database schema...');
const setup = await setupDatabase({
force: false,
seed: true,
migrations: true
});
if (!setup) {
console.error('❌ Database setup failed');
process.exit(1);
}
console.log('✅ Database setup completed\n');
// Show database info
console.log('3. Database information:');
await getDatabaseInfo();
console.log('\n🎉 Database setup completed successfully!');
console.log('\nNext steps:');
console.log('1. Start the server: npm run dev');
console.log('2. Test the API: curl http://localhost:8000/api/health');
console.log('3. Check model status: curl http://localhost:8000/api/models/status');
} catch (error) {
console.error('\n❌ Setup failed:', error.message);
process.exit(1);
}
};
main();
Executable
+178
View File
@@ -0,0 +1,178 @@
#!/usr/bin/env node
// Environment setup script for Reason Flow
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const question = (query) => {
return new Promise((resolve) => {
rl.question(query, resolve);
});
};
const main = async () => {
console.log('🚀 Reason Flow Environment Setup');
console.log('================================\n');
try {
// Check if .env already exists
const envPath = path.join(__dirname, '.env');
if (fs.existsSync(envPath)) {
const overwrite = await question('⚠️ .env file already exists. Overwrite? (y/N): ');
if (overwrite.toLowerCase() !== 'y' && overwrite.toLowerCase() !== 'yes') {
console.log('Setup cancelled.');
process.exit(0);
}
}
console.log('Please provide the following configuration values:\n');
// Collect configuration
const config = {};
// Server Configuration
console.log('📡 Server Configuration:');
config.PORT = await question('Port (default: 8000): ') || '8000';
config.NODE_ENV = await question('Environment (development/production/test, default: development): ') || 'development';
// Database Configuration
console.log('\n🗄️ Database Configuration:');
config.DB_HOST = await question('Database Host (default: localhost): ') || 'localhost';
config.DB_PORT = await question('Database Port (default: 5432): ') || '5432';
config.DB_NAME = await question('Database Name (default: reason_flow): ') || 'reason_flow';
config.DB_USER = await question('Database User (default: postgres): ') || 'postgres';
config.DB_PASSWORD = await question('Database Password: ');
// API Keys
console.log('\n🔑 API Keys:');
config.GROQ_API_KEY = await question('Groq API Key (required): ');
config.OPENAI_API_KEY = await question('OpenAI API Key (optional): ');
config.SERP_API_KEY = await question('SERP API Key (optional): ');
// JWT Configuration
console.log('\n🔐 Security Configuration:');
config.JWT_SECRET = await question('JWT Secret (or press Enter for auto-generated): ');
if (!config.JWT_SECRET) {
config.JWT_SECRET = require('crypto').randomBytes(64).toString('hex');
console.log(`Auto-generated JWT Secret: ${config.JWT_SECRET}`);
}
// Admin User
console.log('\n👤 Admin User Configuration:');
config.ADMIN_EMAIL = await question('Admin Email (default: admin@reasonflow.com): ') || 'admin@reasonflow.com';
config.ADMIN_PASSWORD = await question('Admin Password (default: admin123): ') || 'admin123';
// Generate .env file
const envContent = generateEnvContent(config);
fs.writeFileSync(envPath, envContent);
console.log('\n✅ Environment configuration created successfully!');
console.log(`📁 Configuration saved to: ${envPath}`);
console.log('\nNext steps:');
console.log('1. Review your configuration: node server/utils/validateConfig.js show');
console.log('2. Validate configuration: node server/utils/validateConfig.js validate');
console.log('3. Setup database: npm run db:setup');
console.log('4. Start the server: npm run dev');
} catch (error) {
console.error('\n❌ Setup failed:', error.message);
process.exit(1);
} finally {
rl.close();
}
};
const generateEnvContent = (config) => {
return `# Reason Flow Environment Configuration
# Generated on ${new Date().toISOString()}
# Server Configuration
PORT=${config.PORT}
NODE_ENV=${config.NODE_ENV}
HOST=localhost
# Database Configuration
DB_HOST=${config.DB_HOST}
DB_PORT=${config.DB_PORT}
DB_NAME=${config.DB_NAME}
DB_USER=${config.DB_USER}
DB_PASSWORD=${config.DB_PASSWORD}
DB_SSL=false
# Groq API Configuration
GROQ_API_KEY=${config.GROQ_API_KEY}
GROQ_MODEL=moonshotai/kimi-k2-instruct-0905
GROQ_BASE_URL=https://api.groq.com
# OpenAI API Configuration
OPENAI_API_KEY=${config.OPENAI_API_KEY}
OPENAI_BASE_URL=https://api.openai.com/v1
# JWT Configuration
JWT_SECRET=${config.JWT_SECRET}
JWT_EXPIRES_IN=7d
# Admin User Configuration
ADMIN_EMAIL=${config.ADMIN_EMAIL}
ADMIN_PASSWORD=${config.ADMIN_PASSWORD}
ADMIN_FIRST_NAME=Admin
ADMIN_LAST_NAME=User
# File Upload Configuration
MAX_FILE_SIZE=50MB
UPLOAD_PATH=./uploads
ALLOWED_FILE_TYPES=pdf,txt,doc,docx
# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
# Logging Configuration
LOG_LEVEL=info
LOG_FILE=./logs/app.log
LOG_MAX_SIZE=10MB
LOG_MAX_FILES=5
# CORS Configuration
CORS_ORIGIN=http://localhost:3000
# Security Configuration
HELMET_ENABLED=true
RATE_LIMIT_ENABLED=true
# Development Configuration
DEBUG_MODE=true
VERBOSE_LOGGING=true
HOT_RELOAD=true
# Monitoring Configuration
HEALTH_CHECK_ENABLED=true
METRICS_ENABLED=true
PERFORMANCE_MONITORING=true
# Model Configuration
MODEL1_TEMPERATURE=0.3
MODEL1_MAX_TOKENS=3000
QUERYMODEL_TEMPERATURE=0.5
QUERYMODEL_MAX_TOKENS=4000
# Fine-tuning Configuration
FINE_TUNING_ENABLED=true
FINE_TUNING_SCHEDULE=weekly
FINE_TUNING_BATCH_SIZE=10
# Feedback Configuration
FEEDBACK_PROCESSING_ENABLED=true
FEEDBACK_BATCH_SIZE=50
FEEDBACK_PROCESSING_SCHEDULE=daily
`;
};
main();
Executable
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env node
// Simple test script to verify Groq integration
require('dotenv').config();
const { runAllTests } = require('./server/utils/testGroq');
console.log('🚀 Starting Groq Integration Test...\n');
runAllTests()
.then(results => {
console.log('\n✅ Test completed successfully!');
process.exit(0);
})
.catch(error => {
console.error('\n❌ Test failed:', error.message);
process.exit(1);
});
+15
View File
@@ -0,0 +1,15 @@
import axios from "axios";
import 'dotenv/config';
const API_KEY = process.env.TAVILY_API_KEY;
async function webSearch(query) {
const response = await axios.post("https://api.tavily.com/search",
{ query },
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
console.log(response.data.results);
}
webSearch("who is the current president of united states");