Updated code
10
.dockerignore
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.git
|
||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
*.md
|
||||||
|
.env*
|
||||||
|
npm-debug.log*
|
||||||
|
README.md
|
||||||
|
.gitignore
|
||||||
|
.dockerignore
|
||||||
|
Dockerfile
|
2
.env.example
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
OPENAI_API_KEY=your_openai_api_key_here
|
||||||
|
OPENAI_ASSISTANT_ID=your_openai_assistant_id_here
|
3
.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": ["next/core-web-vitals", "next/typescript"]
|
||||||
|
}
|
37
.gitignore
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
.yarn/install-state.gz
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
41
Dockerfile
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
FROM node:18-alpine AS builder
|
||||||
|
RUN apk add --no-cache python3 py3-pip make g++
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
RUN python3 -m venv /opt/venv && \
|
||||||
|
. /opt/venv/bin/activate && \
|
||||||
|
pip3 install --no-cache-dir autogen python-dotenv openai
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
RUN ls -la .next/standalone
|
||||||
|
|
||||||
|
FROM node:18-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install Python and set up venv
|
||||||
|
RUN apk add --no-cache python3 py3-pip make g++ && \
|
||||||
|
python3 -m venv /opt/venv && \
|
||||||
|
. /opt/venv/bin/activate && \
|
||||||
|
pip3 install --no-cache-dir autogen python-dotenv openai
|
||||||
|
|
||||||
|
# Copy all Next.js build output
|
||||||
|
COPY --from=builder /app/.next/standalone ./
|
||||||
|
COPY --from=builder /app/.next/static ./.next/static
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
COPY --from=builder /app/app/api ./app/api
|
||||||
|
COPY --from=builder /opt/venv /opt/venv
|
||||||
|
|
||||||
|
RUN mkdir -p /app/temp && chmod 777 /app/temp
|
||||||
|
|
||||||
|
USER node
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
ENV PORT=3000
|
||||||
|
ENV PATH="/opt/venv/bin:/usr/local/bin:${PATH}"
|
||||||
|
ENV VIRTUAL_ENV=/opt/venv
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2025 Dotbaseai
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
OR OTHER DEALINGS IN THE SOFTWARE.
|
95
README.md
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# Dotbase
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|

|
||||||
|
**Create agent-based workforces with drag-and-drop simplicity**
|
||||||
|
|
||||||
|
[Website](https://dotbase.ai) • [Documentation](https://docs.dotbase.ai) • [Examples](https://dotbase.ai/examples)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Overview
|
||||||
|
|
||||||
|
Dotbase is a powerful low-code platform that transforms how you build AI agent workforces. With our intuitive drag-and-drop interface, create sophisticated agent networks and export them as production-ready Python code.
|
||||||
|
|
||||||
|
## ✨ Key Features
|
||||||
|
|
||||||
|
- 🎯 **Visual Agent Design**: Drag-and-drop interface for workforce creation
|
||||||
|
- 🔄 **Autogen Integration**: Built on Microsoft's Autogen framework
|
||||||
|
- 🐍 **Python Export**: Generate production-ready Python code
|
||||||
|
- 🛠️ **Custom Functions**: Add custom tools and capabilities
|
||||||
|
- 🤝 **Multi-Agent Support**: Create collaborative agent networks
|
||||||
|
|
||||||
|
## 🏗️ Architecture
|
||||||
|
|
||||||
|
### Agents
|
||||||
|
|
||||||
|
Our Autogen-based system supports four core components:
|
||||||
|
|
||||||
|
| Agent Type | Description |
|
||||||
|
|------------|-------------|
|
||||||
|
| `AssistantAgent` | Configurable AI agents with custom system prompts |
|
||||||
|
| `GPTAssistantAgent` | OpenAI Assistant API integration with custom function support |
|
||||||
|
| `UserProxy` | Human-agent interaction interface |
|
||||||
|
| `GroupChat` | Multi-agent collaboration hub |
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
|
||||||
|
`CustomFunction`
|
||||||
|
- Integrate Python functions
|
||||||
|
- Connect OpenAI Assistant functions
|
||||||
|
- Add custom capabilities
|
||||||
|
|
||||||
|
## 💻 Workstation
|
||||||
|
|
||||||
|
### Node Operations
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
A[Library Panel] -->|Drag & Drop| B[Workstation]
|
||||||
|
B -->|Connect| C[Agents]
|
||||||
|
B -->|Configure| D[Settings]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Add**: Drag components from Library Panel
|
||||||
|
- **Connect**: Green handles indicate compatible connections
|
||||||
|
- **Delete**: Right-click for context menu
|
||||||
|
|
||||||
|
## 🚀 Deployment
|
||||||
|
|
||||||
|
### Local Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
pip install pyautogen
|
||||||
|
|
||||||
|
# Run exported script
|
||||||
|
python your_workforce.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Replit Deployment
|
||||||
|
|
||||||
|
1. Create Python project
|
||||||
|
2. Add to `pyproject.toml`:
|
||||||
|
```toml
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
pyautogen = "0.2.7"
|
||||||
|
```
|
||||||
|
3. Run your exported script
|
||||||
|
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
[MIT License](LICENSE)
|
||||||
|
|
||||||
|
## 🆘 Support
|
||||||
|
|
||||||
|
- Documentation: [docs.dotbase.ai](https://docs.dotbase.ai)
|
||||||
|
- Site: enterprise@dotbase.ai
|
||||||
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
Made with ❤️ by Dotbase Team
|
||||||
|
</div>
|
148
app/api/python/route.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// app/api/python/route.ts
|
||||||
|
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
dotenv.config({ path: '.env.local' });
|
||||||
|
|
||||||
|
type DebugData = {
|
||||||
|
tempDir?: string;
|
||||||
|
tempFile?: string;
|
||||||
|
codeLength?: number;
|
||||||
|
} | string | Error | number | null | undefined;
|
||||||
|
|
||||||
|
function debugLog(message: string, data?: DebugData): void {
|
||||||
|
console.log(`[Python Execute Debug] ${message}`, data || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executePython(code: string): Promise<string> {
|
||||||
|
const tempDir = path.join(process.cwd(), 'temp');
|
||||||
|
const tempFile = path.join(tempDir, `script_${Date.now()}.py`);
|
||||||
|
|
||||||
|
debugLog('Execution started', {
|
||||||
|
tempDir,
|
||||||
|
tempFile,
|
||||||
|
codeLength: code.length
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create temp directory if it doesn't exist
|
||||||
|
await fs.mkdir(tempDir, { recursive: true });
|
||||||
|
debugLog('Temp directory created/verified');
|
||||||
|
|
||||||
|
// Write Python code to temp file
|
||||||
|
await fs.writeFile(tempFile, code, 'utf8');
|
||||||
|
debugLog('Code written to temp file');
|
||||||
|
|
||||||
|
// Create a .env file in the temp directory
|
||||||
|
const envFile = path.join(tempDir, '.env');
|
||||||
|
const envContent = `OPENAI_API_KEY=${process.env.OPENAI_API_KEY}\nOPENAI_ASSISTANT_ID=${process.env.OPENAI_ASSISTANT_ID}`;
|
||||||
|
await fs.writeFile(envFile, envContent, 'utf8');
|
||||||
|
debugLog('.env file created');
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// Spawn Python process
|
||||||
|
const pythonProcess = spawn('python3', [tempFile], {
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PYTHONPATH: tempDir // Add temp directory to Python path
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let output = '';
|
||||||
|
let error = '';
|
||||||
|
|
||||||
|
// Handle stdout
|
||||||
|
pythonProcess.stdout.on('data', (data) => {
|
||||||
|
const chunk = data.toString();
|
||||||
|
output += chunk;
|
||||||
|
debugLog('Received stdout chunk:', chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle stderr
|
||||||
|
pythonProcess.stderr.on('data', (data) => {
|
||||||
|
const chunk = data.toString();
|
||||||
|
error += chunk;
|
||||||
|
debugLog('Received stderr chunk:', chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle process errors
|
||||||
|
pythonProcess.on('error', (err) => {
|
||||||
|
debugLog('Process error:', err.message);
|
||||||
|
resolve(output || err.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle process completion
|
||||||
|
pythonProcess.on('close', async (code) => {
|
||||||
|
debugLog('Process closed with code:', code);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Clean up temporary files
|
||||||
|
await fs.unlink(tempFile);
|
||||||
|
await fs.unlink(envFile);
|
||||||
|
debugLog('Temp files cleaned up');
|
||||||
|
} catch (err) {
|
||||||
|
debugLog('Failed to delete temp files:', err as Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(output || error || 'No output received');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set execution timeout
|
||||||
|
const timeoutDuration = 60000; // 1 minute timeout
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
debugLog('Execution timed out, returning partial output');
|
||||||
|
pythonProcess.kill();
|
||||||
|
resolve(output || error || 'Execution timed out - Partial output');
|
||||||
|
}, timeoutDuration);
|
||||||
|
|
||||||
|
pythonProcess.on('close', () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
debugLog('File system error:', err as Error);
|
||||||
|
throw err instanceof Error ? err : new Error('File system error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
try {
|
||||||
|
const { code } = await request.json();
|
||||||
|
debugLog('Received code:', code);
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
debugLog('Error: No code provided');
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'No code provided' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = await executePython(code);
|
||||||
|
debugLog('Execution completed successfully:', output);
|
||||||
|
return NextResponse.json({ output });
|
||||||
|
|
||||||
|
} catch (error: unknown) {
|
||||||
|
let errorMessage = 'An unknown error occurred';
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
errorMessage = error.message;
|
||||||
|
} else if (typeof error === 'string') {
|
||||||
|
errorMessage = error;
|
||||||
|
} else if (error && typeof error === 'object' && 'message' in error) {
|
||||||
|
errorMessage = String(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
debugLog('Route error:', errorMessage);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ output: errorMessage },
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
259
app/dashboard/page.tsx
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import ReactFlow, {
|
||||||
|
Background,
|
||||||
|
// Controls,
|
||||||
|
BackgroundVariant,
|
||||||
|
ReactFlowProvider,
|
||||||
|
Node,
|
||||||
|
ReactFlowInstance,
|
||||||
|
Edge,
|
||||||
|
useKeyPress,
|
||||||
|
SelectionMode
|
||||||
|
} from 'reactflow';
|
||||||
|
import 'reactflow/dist/style.css';
|
||||||
|
import { CUSTOM_DOTBASE_NODES } from '@/components/dashboard/nodes/types/nodeTypes';
|
||||||
|
import UtilBar from '@/components/dashboard/main/UtilBar';
|
||||||
|
import Workspace from '@/components/dashboard/main/Workspace';
|
||||||
|
import useDnDStore from '@/stores/useDnDStore';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import projectLogo from '@/public/assets/logo/full_wlogo.png';
|
||||||
|
|
||||||
|
import { X } from 'lucide-react';
|
||||||
|
|
||||||
|
|
||||||
|
const Dashboard = () => {
|
||||||
|
const {
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
addNode,
|
||||||
|
addEdge,
|
||||||
|
onNodesChange,
|
||||||
|
onEdgesChange,
|
||||||
|
setInstance,
|
||||||
|
deleteElements,
|
||||||
|
duplicateSelection,
|
||||||
|
setSelectedElements,
|
||||||
|
undo,
|
||||||
|
redo
|
||||||
|
} = useDnDStore();
|
||||||
|
|
||||||
|
// Keyboard shortcuts
|
||||||
|
const deletePressed = useKeyPress(['Backspace', 'Delete']);
|
||||||
|
const ctrlPressed = useKeyPress(['Control', 'Meta']);
|
||||||
|
const shiftPressed = useKeyPress('Shift');
|
||||||
|
const cPressed = useKeyPress('c');
|
||||||
|
// const vPressed = useKeyPress('v');
|
||||||
|
const zPressed = useKeyPress('z');
|
||||||
|
const yPressed = useKeyPress('y');
|
||||||
|
const [showTutorial, setShowTutorial] = React.useState(false);
|
||||||
|
const videoRef = React.useRef<HTMLVideoElement>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const tutorialWatched = localStorage.getItem('tutorialWatched');
|
||||||
|
if (!tutorialWatched) {
|
||||||
|
setShowTutorial(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Auto-play video when modal opens
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (showTutorial && videoRef.current) {
|
||||||
|
videoRef.current.play().catch(error => {
|
||||||
|
console.log('Video autoplay failed:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [showTutorial]);
|
||||||
|
|
||||||
|
// Rest of your existing keyboard shortcuts and handlers...
|
||||||
|
|
||||||
|
const handleCloseTutorial = () => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
videoRef.current.pause();
|
||||||
|
}
|
||||||
|
setShowTutorial(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDontShowAgain = () => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
videoRef.current.pause();
|
||||||
|
}
|
||||||
|
localStorage.setItem('tutorialWatched', 'true');
|
||||||
|
setShowTutorial(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (deletePressed) {
|
||||||
|
deleteElements();
|
||||||
|
}
|
||||||
|
}, [deletePressed, deleteElements]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (ctrlPressed && cPressed) {
|
||||||
|
duplicateSelection();
|
||||||
|
}
|
||||||
|
if (ctrlPressed && zPressed) {
|
||||||
|
if (shiftPressed || yPressed) {
|
||||||
|
redo();
|
||||||
|
} else {
|
||||||
|
undo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [ctrlPressed, cPressed, zPressed, yPressed, shiftPressed, duplicateSelection, undo, redo]);
|
||||||
|
|
||||||
|
const onInit = React.useCallback((instance: ReactFlowInstance) => {
|
||||||
|
setInstance(instance);
|
||||||
|
}, [setInstance]);
|
||||||
|
|
||||||
|
const onDragOver = React.useCallback((event: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.dataTransfer.dropEffect = 'move';
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onDrop = React.useCallback((event: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const reactFlowBounds = event.currentTarget.getBoundingClientRect();
|
||||||
|
|
||||||
|
const position = {
|
||||||
|
x: event.clientX - reactFlowBounds.left,
|
||||||
|
y: event.clientY - reactFlowBounds.top
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const nodeData = JSON.parse(event.dataTransfer.getData('application/reactflow'));
|
||||||
|
const newNode: Node = {
|
||||||
|
...nodeData,
|
||||||
|
position,
|
||||||
|
data: { ...nodeData.data, label: nodeData.data?.label || nodeData.type },
|
||||||
|
};
|
||||||
|
addNode(newNode);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding new node:', error);
|
||||||
|
}
|
||||||
|
}, [addNode]);
|
||||||
|
|
||||||
|
const onSelectionChange = React.useCallback(
|
||||||
|
(params: { nodes: Node[]; edges: Edge[] }) => {
|
||||||
|
setSelectedElements(params.nodes, params.edges);
|
||||||
|
},
|
||||||
|
[setSelectedElements]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full h-screen bg-[#1A1A1A]">
|
||||||
|
{/* Logo */}
|
||||||
|
<div className="absolute top-3 left-4 z-20 flex items-center gap-2">
|
||||||
|
<Image
|
||||||
|
src={projectLogo}
|
||||||
|
alt="Project Logo"
|
||||||
|
width={120}
|
||||||
|
height={40}
|
||||||
|
className="object-fill"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<UtilBar onTutorialClick={() => setShowTutorial(true)} />
|
||||||
|
<ReactFlowProvider>
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
onInit={onInit}
|
||||||
|
onConnect={addEdge}
|
||||||
|
onDrop={onDrop}
|
||||||
|
onDragOver={onDragOver}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
|
onSelectionChange={onSelectionChange}
|
||||||
|
nodeTypes={CUSTOM_DOTBASE_NODES}
|
||||||
|
defaultViewport={{ x: 2, y: 2, zoom: 1 }}
|
||||||
|
minZoom={0.6}
|
||||||
|
maxZoom={1.2}
|
||||||
|
className="h-full bg-[#1A1A1A]"
|
||||||
|
snapToGrid={true}
|
||||||
|
snapGrid={[15, 15]}
|
||||||
|
fitView
|
||||||
|
nodesDraggable={true}
|
||||||
|
nodesConnectable={true}
|
||||||
|
selectNodesOnDrag={false}
|
||||||
|
selectionMode={SelectionMode.Partial}
|
||||||
|
selectionOnDrag={true}
|
||||||
|
multiSelectionKeyCode={['Control', 'Meta']}
|
||||||
|
deleteKeyCode={['Backspace', 'Delete']}
|
||||||
|
>
|
||||||
|
<Background color="#333333" gap={15} size={1} variant={BackgroundVariant.Dots} />
|
||||||
|
{/* <Controls
|
||||||
|
className="!bg-[#252525] !border !border-[#333333] !rounded-md overflow-hidden"
|
||||||
|
showInteractive={false}
|
||||||
|
/> */}
|
||||||
|
<Workspace />
|
||||||
|
</ReactFlow>
|
||||||
|
</ReactFlowProvider>
|
||||||
|
{showTutorial && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||||
|
<div className="bg-[#252525] rounded-lg p-6 max-w-[800px] w-full mx-4 shadow-xl">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h2 className="text-white text-xl font-semibold">Getting Started with DotBase</h2>
|
||||||
|
<button
|
||||||
|
onClick={handleCloseTutorial}
|
||||||
|
className="text-gray-400 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
<X className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="aspect-video w-full bg-black rounded-md overflow-hidden mb-4 ">
|
||||||
|
<video
|
||||||
|
ref={videoRef}
|
||||||
|
className="w-full h-full object-contain"
|
||||||
|
controls
|
||||||
|
playsInline
|
||||||
|
src="/assets/images/demo.mp4"
|
||||||
|
>
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 mb-4 p-4 bg-[#1A1A1A] rounded-md">
|
||||||
|
<h3 className="text-white text-lg font-medium mb-2">Quick Tips</h3>
|
||||||
|
<ul className="text-gray-300 space-y-2 text-sm">
|
||||||
|
<li className="flex items-center">
|
||||||
|
<span className="bg-[#333333] px-2 py-0.5 rounded text-sm mr-2">Backspace</span>
|
||||||
|
Delete selected nodes
|
||||||
|
</li>
|
||||||
|
<li className="flex items-center">
|
||||||
|
<span className="bg-[#333333] px-2 py-0.5 rounded text-sm mr-2">Ctrl + Click</span>
|
||||||
|
Select Multiple nodes
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between mt-4">
|
||||||
|
<label className="flex items-center space-x-2 text-sm text-gray-300 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="w-4 h-4 rounded border-gray-600 bg-[#333333]"
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
handleDontShowAgain();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p>Don't forget to save</p>
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
onClick={handleCloseTutorial}
|
||||||
|
className="px-4 py-2 bg-[#410e66] text-white rounded-md hover:bg-blue-700 transition-colors"
|
||||||
|
>
|
||||||
|
Got it!
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dashboard;
|
52
app/layout.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Inter } from 'next/font/google';
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import { Providers } from '@/providers/privy-provider';
|
||||||
|
import '@/styles/globals.css';
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ['latin'] });
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "dotbase",
|
||||||
|
description:
|
||||||
|
"The Visual Model Forge. Craft, Combine, and Deploy AI Models with Drag-and-Drop Simplicity.",
|
||||||
|
openGraph: {
|
||||||
|
type: "website",
|
||||||
|
url: "https://dotbase.ai/",
|
||||||
|
title: "dotbase",
|
||||||
|
description:
|
||||||
|
"The Visual Model Forge. Craft, Combine, and Deploy AI Models with Drag-and-Drop Simplicity.",
|
||||||
|
images: `https://i.imgur.com/UgGWBt5.png`,
|
||||||
|
},
|
||||||
|
icons: {
|
||||||
|
icon: [
|
||||||
|
{
|
||||||
|
url: '/favicon.ico',
|
||||||
|
sizes: 'any',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/icon.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '32x32',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
apple: {
|
||||||
|
url: '/apple-icon.png',
|
||||||
|
sizes: '180x180',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={inter.className}>
|
||||||
|
<Providers>
|
||||||
|
{children}
|
||||||
|
</Providers>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);}
|
22
app/page.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import Header from "../components/home/Header";
|
||||||
|
import Footer from "../components/home/Footer";
|
||||||
|
import HeroSection from "@/components/home/HeroSection";
|
||||||
|
import FeaturesSection from "@/components/home/FeaturesSection";
|
||||||
|
import AgentsSection from "@/components/home/AgentsSection";
|
||||||
|
import TokenomicsSection from "@/components/home/TokenomicsSection";
|
||||||
|
import { Providers } from "@/providers/privy-provider";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<Providers>
|
||||||
|
<main className="bg-black">
|
||||||
|
<Header />
|
||||||
|
<HeroSection />
|
||||||
|
<AgentsSection />
|
||||||
|
<FeaturesSection />
|
||||||
|
<TokenomicsSection />
|
||||||
|
<Footer />
|
||||||
|
</main>
|
||||||
|
</Providers>
|
||||||
|
);
|
||||||
|
}
|
21
components.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.ts",
|
||||||
|
"css": "app/globals.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide"
|
||||||
|
}
|
33
components/dashboard/library/Branch.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import Node from '@/components/dashboard/library/Node';
|
||||||
|
import { TreeProps } from '@/components/dashboard/library/types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type BranchProps<T> = {
|
||||||
|
item: TreeProps<T>['data'][number];
|
||||||
|
level: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Branch<T>({ item, level }: BranchProps<T>): React.JSX.Element {
|
||||||
|
const [expanded, setExpanded] = React.useState(item.initiallyExpanded ?? false);
|
||||||
|
const hasChild = item?.children?.length;
|
||||||
|
|
||||||
|
const renderSubBranches = () => {
|
||||||
|
if (hasChild) {
|
||||||
|
const newLevel = level + 1;
|
||||||
|
return item?.children?.map((el) => <Branch key={el.id} item={el} level={newLevel} />);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onExpand = () => {
|
||||||
|
setExpanded((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-2">
|
||||||
|
<Node item={item} onExpand={onExpand} isExpanded={expanded} />
|
||||||
|
{expanded && renderSubBranches()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Branch;
|
32
components/dashboard/library/Node.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { TreeProps } from '@/components/dashboard/library/types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type NodeProps<T> = {
|
||||||
|
item: TreeProps<T>['data'][number];
|
||||||
|
onExpand: () => void;
|
||||||
|
isExpanded?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Node<T>(props: NodeProps<T>): React.JSX.Element {
|
||||||
|
const hasChild = props.item.children;
|
||||||
|
const hasComponent = props.item.jsxElement ? true : false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={props.item.children?.length ? props.onExpand : undefined}
|
||||||
|
draggable={props.item.draggable ?? true}
|
||||||
|
onDragStart={props.item.onDrag}
|
||||||
|
>
|
||||||
|
{hasComponent ? (
|
||||||
|
props.item.jsxElement
|
||||||
|
) : (
|
||||||
|
<p className="font-bold uppercase cursor-pointer">
|
||||||
|
<span className="pr-2">{hasChild ? (props.isExpanded ? '▾ ' : '▸ ') : ''}</span>
|
||||||
|
{props.item.name}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Node;
|
6
components/dashboard/library/Tree.tsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Branch from './Branch';
|
||||||
|
import { TreeProps } from './types';
|
||||||
|
|
||||||
|
export default function Tree<T>(props: TreeProps<T>) {
|
||||||
|
return props.data?.map((n) => <Branch key={n.id} item={n} level={0} />);
|
||||||
|
}
|
14
components/dashboard/library/types.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type DefaultDataProps<T> = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
initiallyExpanded?: boolean;
|
||||||
|
jsxElement?: React.JSX.Element;
|
||||||
|
draggable?: boolean;
|
||||||
|
onDrag?: (event: React.DragEvent) => void;
|
||||||
|
children?: DefaultDataProps<T>[];
|
||||||
|
} & T;
|
||||||
|
export type TreeProps<T> = {
|
||||||
|
data: (DefaultDataProps<T> & T)[];
|
||||||
|
};
|
202
components/dashboard/main/SlidingModal.tsx
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { X, AlertCircle } from 'lucide-react';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import LoadingDots from '@/components/ui/animations/LoadingDots';
|
||||||
|
|
||||||
|
interface SlidingModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
pythonCode?: string;
|
||||||
|
output?: string;
|
||||||
|
isExecuting: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type for managing output state
|
||||||
|
type OutputState = {
|
||||||
|
isLoading: boolean;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ClientOnlyModal = dynamic(() => Promise.resolve(({ children }: { children: React.ReactNode }) => <>{children}</>), {
|
||||||
|
ssr: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const SlidingModal: React.FC<SlidingModalProps> = ({ isOpen, onClose, pythonCode = '', output = '', isExecuting }) => {
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [outputState, setOutputState] = useState<OutputState>({
|
||||||
|
isLoading: isExecuting,
|
||||||
|
content: output
|
||||||
|
});
|
||||||
|
|
||||||
|
// Effect for handling mounting
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Effect for handling output changes
|
||||||
|
useEffect(() => {
|
||||||
|
setOutputState({
|
||||||
|
isLoading: isExecuting,
|
||||||
|
content: output
|
||||||
|
});
|
||||||
|
}, [output, isExecuting]);
|
||||||
|
|
||||||
|
// Function to mask sensitive information
|
||||||
|
const maskSensitiveInfo = (code: string): string => {
|
||||||
|
return code
|
||||||
|
.replace(/sk-proj-[a-zA-Z0-9-]+/g, 'YOUR_OPENAI_API_KEY')
|
||||||
|
.replace(/asst_[a-zA-Z0-9]+/g, 'YOUR_ASSISTANT_ID');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to determine output message
|
||||||
|
const getOutputMessage = (): JSX.Element | string => {
|
||||||
|
if (!outputState.content && !outputState.isLoading) {
|
||||||
|
return '> No output available';
|
||||||
|
}
|
||||||
|
if (outputState.isLoading) {
|
||||||
|
return (
|
||||||
|
<span className="flex items-center">
|
||||||
|
{'> Executing'}
|
||||||
|
<LoadingDots />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return outputState.content;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Don't render anything if not mounted
|
||||||
|
if (!mounted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClientOnlyModal>
|
||||||
|
<style jsx global>{`
|
||||||
|
/* Custom scrollbar styles - same as before */
|
||||||
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-track {
|
||||||
|
background: #1E1E1E;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background: #424242;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #4F4F4F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-corner {
|
||||||
|
background: #1E1E1E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #424242 #1E1E1E;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{isOpen && (
|
||||||
|
<>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
onClick={onClose}
|
||||||
|
className="fixed inset-0 bg-black/20 backdrop-blur-sm z-40"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ x: '100%' }}
|
||||||
|
animate={{ x: 0 }}
|
||||||
|
exit={{ x: '100%' }}
|
||||||
|
transition={{ type: 'spring', damping: 20, stiffness: 100 }}
|
||||||
|
className="fixed right-0 top-0 h-screen w-3/4 max-w-4xl bg-[#1A1A1A] border-l border-white/10 shadow-2xl z-50 rounded-l-2xl"
|
||||||
|
>
|
||||||
|
<div className="h-full flex flex-col">
|
||||||
|
<div className="flex items-center justify-between px-4 py-3 border-b border-white/10">
|
||||||
|
<h2 className="text-white text-lg font-medium font-offbit">Code Output</h2>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="p-1 rounded-md text-gray-400 hover:text-white hover:bg-white/10 transition-colors"
|
||||||
|
>
|
||||||
|
<X size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-auto p-4 space-y-4 custom-scrollbar">
|
||||||
|
<div className="mb-6">
|
||||||
|
<h3 className="text-white text-sm font-medium mb-2 font-offbit">Generated Python Code:</h3>
|
||||||
|
<div className="bg-[#1E1E1E] rounded-lg overflow-hidden border border-white/5">
|
||||||
|
<div className="flex items-center justify-between px-4 py-2 bg-[#252525] border-b border-white/5">
|
||||||
|
<span className="text-xs text-gray-400">script.py</span>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#FF5F56]"></div>
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#FFBD2E]"></div>
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#27C93F]"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 font-mono text-sm leading-relaxed overflow-auto custom-scrollbar">
|
||||||
|
<pre className="text-[#D4D4D4]">
|
||||||
|
{maskSensitiveInfo(pythonCode).split('\n').map((line, i) => (
|
||||||
|
<div key={i} className="flex">
|
||||||
|
<span className="w-8 inline-block text-gray-500 select-none">{i + 1}</span>
|
||||||
|
<span className="flex-1">{line || ' '}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<h3 className="text-white text-sm font-medium mb-2 font-offbit">Output:</h3>
|
||||||
|
<div className="bg-black rounded-lg overflow-hidden border border-white/5">
|
||||||
|
<div className="flex items-center justify-between px-4 py-2 bg-[#2D2D2D] border-b border-white/5">
|
||||||
|
<span className="text-xs text-gray-400">Terminal</span>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#FF5F56]"></div>
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#FFBD2E]"></div>
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#27C93F]"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 custom-scrollbar overflow-auto max-h-[400px]">
|
||||||
|
<pre className="text-green-400 font-mono text-sm whitespace-pre-wrap font-medium">
|
||||||
|
{getOutputMessage()}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 bg-blue-500/10 border border-blue-500/20 rounded-lg p-4">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<AlertCircle className="text-blue-400 mt-0.5" size={20} />
|
||||||
|
<div className="text-sm text-blue-200">
|
||||||
|
<p className="font-medium mb-1">Continue your chat locally</p>
|
||||||
|
<p className="text-blue-300/80">
|
||||||
|
To continue interacting with the AI assistant, please run this code in your local Python IDE where you can provide real-time responses and engage in a full conversation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</ClientOnlyModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SlidingModal;
|
268
components/dashboard/main/UtilBar.tsx
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { LogOut, FileCode, FilePlus2, SquareTerminal, TvMinimalPlay } from 'lucide-react';
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
import { usePrivy, useWallets } from '@privy-io/react-auth';
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from "@/components/ui/common/alert-dialog";
|
||||||
|
import useDnDStore from '@/stores/useDnDStore';
|
||||||
|
import Avatar, { genConfig } from 'react-nice-avatar';
|
||||||
|
import { generateDisplayCode, generateExecutionCode } from '@/utils/exportUtils';
|
||||||
|
import { NICKNAMES } from '@/utils/nicknames';
|
||||||
|
import SlidingModal from './SlidingModal';
|
||||||
|
|
||||||
|
interface UtilBarProps {
|
||||||
|
onTutorialClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UtilBar = ({ onTutorialClick }: UtilBarProps) => {
|
||||||
|
// State management for UI elements
|
||||||
|
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||||
|
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||||
|
const [showNewFileDialog, setShowNewFileDialog] = useState(false);
|
||||||
|
const [userName, setUserName] = useState('User');
|
||||||
|
const [avatarConfig, setAvatarConfig] = useState(() => genConfig());
|
||||||
|
|
||||||
|
// State management for code execution
|
||||||
|
const [showEditor, setShowEditor] = useState(false);
|
||||||
|
const [output, setOutput] = useState<string>('');
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [isExecuting, setIsExecuting] = useState(false);
|
||||||
|
|
||||||
|
// Global state and authentication hooks
|
||||||
|
const { logout: privyLogout } = usePrivy();
|
||||||
|
const { wallets } = useWallets();
|
||||||
|
const { nodes, edges, clearNodes } = useDnDStore();
|
||||||
|
|
||||||
|
// Initialize random username and avatar on component mount
|
||||||
|
useEffect(() => {
|
||||||
|
const randomIndex = Math.floor(Math.random() * NICKNAMES.length);
|
||||||
|
const selectedName = NICKNAMES[randomIndex];
|
||||||
|
setUserName(selectedName);
|
||||||
|
setAvatarConfig(genConfig(selectedName));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// File operations handlers
|
||||||
|
const handleNewFile = useCallback(() => {
|
||||||
|
clearNodes();
|
||||||
|
setShowNewFileDialog(false);
|
||||||
|
}, [clearNodes]);
|
||||||
|
|
||||||
|
const handleDownload = useCallback(() => {
|
||||||
|
// Generate masked version of code for download
|
||||||
|
const pythonCode = generateDisplayCode(nodes, edges);
|
||||||
|
const blob = new Blob([pythonCode], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = 'dotflow.py';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}, [nodes, edges]);
|
||||||
|
|
||||||
|
// Python code execution handler
|
||||||
|
const handleRunPython = useCallback(async () => {
|
||||||
|
setIsExecuting(true);
|
||||||
|
setError(null);
|
||||||
|
setShowEditor(true);
|
||||||
|
setOutput('Executing...'); // Set initial executing state
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Generate code with real credentials for execution
|
||||||
|
const executionCode = generateExecutionCode(nodes, edges);
|
||||||
|
const response = await fetch('/api/python', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ code: executionCode }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: { output: string } = await response.json();
|
||||||
|
|
||||||
|
// Sanitize the output to remove sensitive information
|
||||||
|
const sanitizedOutput = data.output
|
||||||
|
.replace(/sk-proj-[a-zA-Z0-9-]+/g, '[API_KEY]')
|
||||||
|
.replace(/asst_[a-zA-Z0-9]+/g, '[ASSISTANT_ID]');
|
||||||
|
|
||||||
|
setOutput(sanitizedOutput);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred';
|
||||||
|
setError(errorMessage);
|
||||||
|
setOutput(errorMessage);
|
||||||
|
console.error('Error running Python code:', err);
|
||||||
|
} finally {
|
||||||
|
setIsExecuting(false);
|
||||||
|
}
|
||||||
|
}, [nodes, edges]);
|
||||||
|
|
||||||
|
// Authentication handlers
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
// Disconnect all connected wallets
|
||||||
|
for (const wallet of wallets) {
|
||||||
|
try {
|
||||||
|
await wallet.disconnect();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error disconnecting wallet:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear all authentication related cookies and storage
|
||||||
|
Cookies.remove('privy-authenticated', { path: '/' });
|
||||||
|
localStorage.removeItem('useremail');
|
||||||
|
localStorage.removeItem('privy:embedded-wallet:iframe-ready');
|
||||||
|
localStorage.removeItem('privy:embedded-wallet:ready');
|
||||||
|
await privyLogout();
|
||||||
|
window.location.href = '/';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Logout error:', error);
|
||||||
|
window.location.href = '/';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tooltip component for action buttons
|
||||||
|
const IconWithTooltip: React.FC<{ children: React.ReactNode; tooltip: string }> = ({ children, tooltip }) => (
|
||||||
|
<div className="group relative">
|
||||||
|
{children}
|
||||||
|
<div className="absolute -bottom-8 left-1/2 transform -translate-x-1/2 hidden group-hover:block bg-[#252525] text-white text-xs px-2 py-1 rounded whitespace-nowrap border border-white/10">
|
||||||
|
{tooltip}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Action buttons configuration
|
||||||
|
const actions = [
|
||||||
|
{ onClick: () => setShowNewFileDialog(true), tooltip: "New File", icon: <FilePlus2 size={19} /> },
|
||||||
|
{ onClick: handleDownload, tooltip: "Export as Python", icon: <FileCode size={19} /> },
|
||||||
|
{ onClick: handleRunPython, tooltip: "Run", icon: <SquareTerminal size={19} /> },
|
||||||
|
{ onClick: onTutorialClick, tooltip: "Tutorial", icon: <TvMinimalPlay size={19} /> },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="absolute top-3 right-3 left-0 z-50 p-2 flex justify-end items-center gap-2">
|
||||||
|
{/* Action buttons */}
|
||||||
|
<div className="flex items-center gap-3 bg-[#252525]/80 backdrop-blur-md px-4 py-2 rounded-full border border-white/5">
|
||||||
|
{actions.map((action, index) => (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
<button
|
||||||
|
onClick={action.onClick}
|
||||||
|
className="flex items-center gap-1 text-gray-300 hover:text-white text-sm transition-transform duration-200 hover:scale-105"
|
||||||
|
disabled={action.tooltip === "Run" && isExecuting}
|
||||||
|
>
|
||||||
|
<IconWithTooltip tooltip={action.tooltip}>{action.icon}</IconWithTooltip>
|
||||||
|
</button>
|
||||||
|
{index < actions.length - 1 && <div className="w-px h-4 bg-gray-600/50" />}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Profile menu */}
|
||||||
|
<div className="relative">
|
||||||
|
<button
|
||||||
|
className="p-1.5 bg-[#252525]/80 backdrop-blur-md rounded-full transition-all duration-200 hover:scale-105 border border-white/5 flex items-center gap-1 pr-1.5 pl-3"
|
||||||
|
onMouseEnter={() => setShowProfileMenu(true)}
|
||||||
|
onMouseLeave={() => setShowProfileMenu(false)}
|
||||||
|
>
|
||||||
|
<div className='text-sm text-gray-300 mr-2 font-offbit'>
|
||||||
|
{userName}
|
||||||
|
</div>
|
||||||
|
<Avatar style={{ width: '24px', height: '24px' }} {...avatarConfig} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showProfileMenu && (
|
||||||
|
<div
|
||||||
|
className="absolute right-0 -mt-1 w-35 rounded-xl bg-[#252525]/90 backdrop-blur-md shadow-2xl py-2 border border-white/10 transform transition-all duration-200 ease-out hover:bg-gradient-to-r from-red-500/10 via-red-500/5 to-transparent"
|
||||||
|
onMouseEnter={() => setShowProfileMenu(true)}
|
||||||
|
onMouseLeave={() => setShowProfileMenu(false)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowLogoutDialog(true)}
|
||||||
|
className="flex items-center w-full px-4 py-1 text-sm text-gray-300 hover:text-white transition-all duration-200"
|
||||||
|
>
|
||||||
|
<LogOut className="h-4 w-4 mr-3" />
|
||||||
|
<span className="font-medium">Logout</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Logout confirmation dialog */}
|
||||||
|
<AlertDialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
||||||
|
<AlertDialogContent className="bg-[#252525]/95 backdrop-blur-xl border border-white/10 rounded-2xl shadow-2xl">
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle className="text-xl font-semibold bg-gradient-to-r from-white via-white to-gray-300 bg-clip-text text-transparent">
|
||||||
|
Ready to leave?
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription className="text-gray-400 mt-2">
|
||||||
|
Logging out will disconnect your wallets and end your current session. You can always log back in later.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter className="mt-6">
|
||||||
|
<AlertDialogCancel className="bg-[#333333]/50 text-white hover:bg-[#404040]/50 border border-white/5 px-5 rounded-xl transition-all duration-200 hover:scale-105">
|
||||||
|
Stay
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={handleLogout}
|
||||||
|
className="bg-gradient-to-r from-red-500 to-red-600 text-white hover:from-red-600 hover:to-red-700 border-none px-5 rounded-xl transition-all duration-200 hover:scale-105 shadow-lg hover:shadow-red-500/25"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
|
||||||
|
{/* New file confirmation dialog */}
|
||||||
|
<AlertDialog open={showNewFileDialog} onOpenChange={setShowNewFileDialog}>
|
||||||
|
<AlertDialogContent className="bg-[#252525]/95 backdrop-blur-xl border border-white/10 rounded-2xl shadow-2xl">
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle className="text-xl font-semibold bg-gradient-to-r from-white via-white to-gray-300 bg-clip-text text-transparent">
|
||||||
|
Create New File?
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription className="text-gray-400 mt-2">
|
||||||
|
This will clear your current work. Are you sure you want to continue?
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter className="mt-6">
|
||||||
|
<AlertDialogCancel className="bg-[#333333]/50 text-white hover:bg-[#404040]/50 border border-white/5 px-5 rounded-xl transition-all duration-200 hover:scale-105">
|
||||||
|
Cancel
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={handleNewFile}
|
||||||
|
className="bg-gradient-to-r from-blue-500 to-blue-600 text-white hover:from-blue-600 hover:to-blue-700 border-none px-5 rounded-xl transition-all duration-200 hover:scale-105 shadow-lg hover:shadow-blue-500/25"
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
|
||||||
|
{/* Code editor modal */}
|
||||||
|
<SlidingModal
|
||||||
|
isOpen={showEditor}
|
||||||
|
onClose={() => setShowEditor(false)}
|
||||||
|
pythonCode={generateDisplayCode(nodes, edges)}
|
||||||
|
output={error || output}
|
||||||
|
isExecuting={isExecuting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UtilBar;
|
98
components/dashboard/main/Workspace.tsx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { DotbaseNodesEnum, DOTBASE_NODES } from '@/components/dashboard/nodes/types/nodeTypes';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import '@/styles/styles.css';
|
||||||
|
|
||||||
|
const Workspace = () => {
|
||||||
|
const onDragStart = (event: React.DragEvent, nodeType: DotbaseNodesEnum) => {
|
||||||
|
const newNode = {
|
||||||
|
...DOTBASE_NODES[nodeType],
|
||||||
|
id: `${nodeType}__${uuidv4()}`
|
||||||
|
};
|
||||||
|
event.dataTransfer.setData('application/reactflow', JSON.stringify(newNode));
|
||||||
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
|
};
|
||||||
|
|
||||||
|
const agents = [
|
||||||
|
{
|
||||||
|
name: "HUB",
|
||||||
|
description: "Enables agent collaboration",
|
||||||
|
nodeType: DotbaseNodesEnum.HUB
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "BRIDGE",
|
||||||
|
description: "Mediates between agents and users",
|
||||||
|
nodeType: DotbaseNodesEnum.BRIDGE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NEXUS",
|
||||||
|
description: "System message-configured agent",
|
||||||
|
nodeType: DotbaseNodesEnum.NEXUS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "LUMINA",
|
||||||
|
description: "OpenAI Assistant API integration",
|
||||||
|
nodeType: DotbaseNodesEnum.LUMINA
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const tools = [
|
||||||
|
{
|
||||||
|
name: "SPARK",
|
||||||
|
description: "Custom function support for agents",
|
||||||
|
nodeType: DotbaseNodesEnum.SPARK
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="absolute bottom-10 left-0 right-0 z-10 flex justify-center">
|
||||||
|
<div className="w-[69%] bg-slate-500/10 backdrop-blur-sm rounded-xl p-6">
|
||||||
|
<div className="flex justify-evenly">
|
||||||
|
{/* Agents Section */}
|
||||||
|
<div className="w-[77%] mr-6">
|
||||||
|
<h2 className="text-white text-sm font-medium mb-4 font-offbit">AGENTS</h2>
|
||||||
|
<div className="flex gap-3 overflow-x-auto custom-scrollbar">
|
||||||
|
{agents.map((agent, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
draggable
|
||||||
|
onDragStart={(e) => onDragStart(e, agent.nodeType)}
|
||||||
|
className="w-[185px] flex-shrink-0 bg-[#1c1c1c] p-3 rounded-lg cursor-move
|
||||||
|
hover:bg-[#252525] transition-colors border border-[#333333] hover:border-[#444444]"
|
||||||
|
>
|
||||||
|
<h3 className="text-white text-sm font-medium mb-1 font-offbit">{agent.name}</h3>
|
||||||
|
<p className="text-gray-400 text-xs leading-relaxed">{agent.description}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-[2px] bg-slate-500/20 bottom-4" />
|
||||||
|
|
||||||
|
|
||||||
|
{/* Tools Section */}
|
||||||
|
<div className="w-[22%] ml-6">
|
||||||
|
<h2 className="text-white text-sm font-medium mb-4 font-offbit">TOOLS</h2>
|
||||||
|
<div className="flex flex-col ">
|
||||||
|
{tools.map((tool, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
draggable
|
||||||
|
onDragStart={(e) => onDragStart(e, tool.nodeType)}
|
||||||
|
className="w-[185px] bg-[#1c1c1c] rounded-lg p-3 cursor-move
|
||||||
|
hover:bg-[#252525] transition-colors border border-[#333333] hover:border-[#444444] min-h-[85px]"
|
||||||
|
>
|
||||||
|
<h3 className="text-white text-sm font-medium mb-1 font-offbit">{tool.name}</h3>
|
||||||
|
<p className="text-gray-400 text-xs leading-relaxed">{tool.description}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Workspace;
|
BIN
components/dashboard/main/nlogo.png
Normal file
After Width: | Height: | Size: 120 KiB |
91
components/dashboard/nodes/autogen/Bridge.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import FieldSchema, { InputField } from '@/components/dashboard/nodes/common/Fields';
|
||||||
|
import { ClsHeaderSkeleton, DefaultContent, ToolbarSkeleton } from '@/components/dashboard/nodes/common/ToolbarSkeleton';
|
||||||
|
import { ValidatorContext } from '@/contexts/ValidatorContext';
|
||||||
|
import useDnDStore from '@/stores/useDnDStore';
|
||||||
|
import { InformationCircleIcon } from '@heroicons/react/24/outline';
|
||||||
|
import React from 'react';
|
||||||
|
import { Handle, NodeToolbar, Position, NodeProps as ReactFlowNodeProps, useReactFlow } from 'reactflow';
|
||||||
|
import { DotbaseNodesEnum } from '../types/nodeTypes';
|
||||||
|
|
||||||
|
const Bridge: React.FC<ReactFlowNodeProps> = (props) => {
|
||||||
|
const { errors } = React.useContext(ValidatorContext);
|
||||||
|
const { updateNode } = useDnDStore();
|
||||||
|
const { getNode } = useReactFlow();
|
||||||
|
const [toolbarVisible, setToolbarVisible] = React.useState(false);
|
||||||
|
|
||||||
|
const data = getNode(props.id)?.data;
|
||||||
|
const onVarNameChange = React.useCallback(
|
||||||
|
(evt: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const val = evt.target.value.trim();
|
||||||
|
updateNode(props.id, { variableName: val });
|
||||||
|
},
|
||||||
|
[updateNode, props.id],
|
||||||
|
);
|
||||||
|
const onPromptChange = React.useCallback(
|
||||||
|
(evt: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const val = evt.target.value;
|
||||||
|
updateNode(props.id, { initialPrompt: val });
|
||||||
|
},
|
||||||
|
[updateNode, props.id],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-sm bg-slate-500/10 text-white w-[266px] border-none">
|
||||||
|
<div className={`${DotbaseNodesEnum.BRIDGE} flex justify-between items-center py-2`}>
|
||||||
|
<div className="font-bold ml-2">BRIDGE</div>
|
||||||
|
<InformationCircleIcon
|
||||||
|
width={20}
|
||||||
|
className="text-gray-300 mr-2"
|
||||||
|
onMouseEnter={() => setToolbarVisible(true)}
|
||||||
|
onMouseLeave={() => setToolbarVisible(false)}
|
||||||
|
/>
|
||||||
|
<NodeToolbar isVisible={toolbarVisible} position={Position.Top}>
|
||||||
|
<ToolbarSkeleton
|
||||||
|
header={<ClsHeaderSkeleton name="Bridge" />}
|
||||||
|
content={
|
||||||
|
<DefaultContent
|
||||||
|
name="Bridge"
|
||||||
|
description="is a proxy agent for the user, that can execute
|
||||||
|
code and provide feedback to the other agents. By default, the agent will prompt for human input every
|
||||||
|
time a message is received. Code execution is enabled by default. LLM-based auto reply is disabled by default."
|
||||||
|
docTeaser="Agent Name: Name of the agent."
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</NodeToolbar>
|
||||||
|
</div>
|
||||||
|
<div className="pb-2 px-2 bg-slate-500/10 text-sm">
|
||||||
|
<FieldSchema
|
||||||
|
field={
|
||||||
|
<InputField
|
||||||
|
label="Agent Name"
|
||||||
|
required
|
||||||
|
onChange={onVarNameChange}
|
||||||
|
value={data?.variableName}
|
||||||
|
type="text"
|
||||||
|
placeholder="dotbase_agent"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
errors={errors?.[props.id]?.variableName}
|
||||||
|
/>
|
||||||
|
<FieldSchema
|
||||||
|
field={
|
||||||
|
<InputField
|
||||||
|
label="Initial Prompt"
|
||||||
|
placeholder="Do a research about how..."
|
||||||
|
required
|
||||||
|
onChange={onPromptChange}
|
||||||
|
value={data?.initialPrompt}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
errors={errors?.[props.id]?.initialPrompt}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Handle type="source" position={Position.Bottom} className="rounded-none border-none w-16" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Bridge;
|
112
components/dashboard/nodes/autogen/Hub.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import FieldSchema, { InputField, SelectField } from '@/components/dashboard/nodes/common/Fields';
|
||||||
|
import { DotbaseNodesEnum } from '@/components/dashboard/nodes/types/nodeTypes';
|
||||||
|
import { ClsHeaderSkeleton, DefaultContent, ToolbarSkeleton } from '@/components/dashboard/nodes/common/ToolbarSkeleton';
|
||||||
|
import { ValidatorContext } from '@/contexts/ValidatorContext';
|
||||||
|
import useDnDStore from '@/stores/useDnDStore';
|
||||||
|
import { AgentSelectionStrategyEnum, OAIModelsEnum } from '@/utils/enum';
|
||||||
|
import { InformationCircleIcon } from '@heroicons/react/24/outline';
|
||||||
|
import React, { memo } from 'react';
|
||||||
|
import { Handle, NodeToolbar, Position, NodeProps as ReactFlowNodeProps, useReactFlow } from 'reactflow';
|
||||||
|
|
||||||
|
const Hub: React.FC<ReactFlowNodeProps> = (props) => {
|
||||||
|
const { errors } = React.useContext(ValidatorContext);
|
||||||
|
const { updateNode } = useDnDStore();
|
||||||
|
const { getNode } = useReactFlow();
|
||||||
|
const [toolbarVisible, setToolbarVisible] = React.useState(false);
|
||||||
|
const data = getNode(props.id)?.data;
|
||||||
|
|
||||||
|
const onAgentNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const val = e.target.value.trim();
|
||||||
|
updateNode(props.id, { variableName: val });
|
||||||
|
};
|
||||||
|
const onMaxRoundsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const val = e.target.value.trim();
|
||||||
|
updateNode(props.id, { maxRounds: parseInt(val) });
|
||||||
|
};
|
||||||
|
const onSpeakerSelectionChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
updateNode(props.id, { agentSelection: val });
|
||||||
|
};
|
||||||
|
const onLLMChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
updateNode(props.id, { selectedModel: val });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-sm bg-slate-500/10 text-white w-[266px] border-none">
|
||||||
|
<div className={`${DotbaseNodesEnum.HUB} flex justify-between items-center py-2`}>
|
||||||
|
<div className="font-bold ml-2">HUB</div>
|
||||||
|
<InformationCircleIcon
|
||||||
|
width={20}
|
||||||
|
className="text-gray-300 mr-2"
|
||||||
|
onMouseEnter={() => setToolbarVisible(true)}
|
||||||
|
onMouseLeave={() => setToolbarVisible(false)}
|
||||||
|
/>
|
||||||
|
<NodeToolbar isVisible={toolbarVisible} position={Position.Top}>
|
||||||
|
<ToolbarSkeleton
|
||||||
|
header={<ClsHeaderSkeleton name="Hub" />}
|
||||||
|
content={
|
||||||
|
<DefaultContent
|
||||||
|
name="Hub"
|
||||||
|
description="is designed to enable agents to collaborate with each other. All agents should be included in a Hub to facilitate their communication and teamwork."
|
||||||
|
docTeaser={`Group Name: Name of the group. (ex: HUB, my_dotbase_group, etc.)\n\nMax Rounds: The maximum rounds that the agents will iterate (default: 15).\n\nAgent Selection: the method for selecting the next speaker (default: "auto").\n• "auto": the next speaker is selected automatically by LLM.\n• "manual": the next speaker is selected manually by user input.\n• "random": the next speaker is selected randomly.\n• "round_robin": the next speaker is selected in a round robin fashion, i.e., iterating in the same order as provided in agents. LLM: Any large language model provided by OpenAI for the HubManager to consume.`}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</NodeToolbar>
|
||||||
|
</div>
|
||||||
|
<div className="pb-2 px-2 bg-slate-500/10 text-sm">
|
||||||
|
<FieldSchema
|
||||||
|
field={
|
||||||
|
<InputField
|
||||||
|
label="Group Name"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
onChange={onAgentNameChange}
|
||||||
|
value={data?.variableName}
|
||||||
|
placeholder="my_dotbase_grp"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
errors={errors?.[props.id]?.variableName}
|
||||||
|
/>
|
||||||
|
<FieldSchema
|
||||||
|
field={
|
||||||
|
<InputField
|
||||||
|
label="Max Rounds"
|
||||||
|
type="number"
|
||||||
|
onChange={onMaxRoundsChange}
|
||||||
|
value={data?.maxRounds}
|
||||||
|
placeholder="15"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
errors={errors?.[props.id]?.maxRounds}
|
||||||
|
/>
|
||||||
|
<FieldSchema
|
||||||
|
field={
|
||||||
|
<SelectField
|
||||||
|
label="Agent Selection"
|
||||||
|
onChange={onSpeakerSelectionChange}
|
||||||
|
selected={data?.agentSelection}
|
||||||
|
options={Object.values(AgentSelectionStrategyEnum)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
errors={errors?.[props.id]?.agentSelection}
|
||||||
|
/>
|
||||||
|
<FieldSchema
|
||||||
|
field={
|
||||||
|
<SelectField
|
||||||
|
label="LLM"
|
||||||
|
onChange={onLLMChange}
|
||||||
|
selected={data?.selectedModel}
|
||||||
|
options={Object.values(OAIModelsEnum)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
errors={errors?.[props.id]?.selectedModel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Handle type="target" position={Position.Top} className="rounded-none border-none w-16 h-1" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Hub);
|
118
components/dashboard/nodes/autogen/Lumina.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import FieldSchema, { SelectField , InputField } from '@/components/dashboard/nodes/common/Fields';
|
||||||
|
import { ClsHeaderSkeleton, DefaultContent, ToolbarSkeleton } from '@/components/dashboard/nodes/common/ToolbarSkeleton';
|
||||||
|
import { ValidatorContext } from '@/contexts/ValidatorContext';
|
||||||
|
import useDnDStore from '@/stores/useDnDStore';
|
||||||
|
import { OAIModelsEnum } from '@/utils/enum';
|
||||||
|
import { InformationCircleIcon } from '@heroicons/react/24/outline';
|
||||||
|
import React from 'react';
|
||||||
|
import { Handle, NodeToolbar, Position, NodeProps as ReactFlowNodeProps, useReactFlow } from 'reactflow';
|
||||||
|
import { DotbaseNodesEnum } from '../types/nodeTypes';
|
||||||
|
|
||||||
|
const Lumina: React.FC<ReactFlowNodeProps> = (props) => {
|
||||||
|
const { errors } = React.useContext(ValidatorContext);
|
||||||
|
const { updateNode } = useDnDStore();
|
||||||
|
const { getNode } = useReactFlow();
|
||||||
|
|
||||||
|
const data = getNode(props.id)?.data;
|
||||||
|
const [toolbarVisible, setToolbarVisible] = React.useState(false);
|
||||||
|
|
||||||
|
const onAgentNameChange = React.useCallback(
|
||||||
|
(evt: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const val = evt.target.value.trim();
|
||||||
|
updateNode(props.id, { variableName: val });
|
||||||
|
},
|
||||||
|
[updateNode, props.id],
|
||||||
|
);
|
||||||
|
// const onOAIIdChange = React.useCallback(
|
||||||
|
// (evt: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
// const val = evt.target.value.trim();
|
||||||
|
// updateNode(props.id, { OAIId: val });
|
||||||
|
// },
|
||||||
|
// [updateNode, props.id],
|
||||||
|
// );
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-sm bg-slate-500/10 text-white w-[240px] border-none">
|
||||||
|
<div
|
||||||
|
className={`${DotbaseNodesEnum.LUMINA} flex justify-between items-center py-2`}
|
||||||
|
>
|
||||||
|
<div className="font-bold ml-2">LUMINA</div>
|
||||||
|
<InformationCircleIcon
|
||||||
|
width={20}
|
||||||
|
className="text-gray-300 mr-2"
|
||||||
|
onMouseEnter={() => setToolbarVisible(true)}
|
||||||
|
onMouseLeave={() => setToolbarVisible(false)}
|
||||||
|
/>
|
||||||
|
<NodeToolbar isVisible={toolbarVisible} position={Position.Top}>
|
||||||
|
<ToolbarSkeleton
|
||||||
|
header={<ClsHeaderSkeleton name="Lumina" />}
|
||||||
|
content={
|
||||||
|
<DefaultContent
|
||||||
|
name="Lumina"
|
||||||
|
description="is an agent that leverages the OpenAI Assistant API for conversational capabilities."
|
||||||
|
docTeaser={`LLM: Any large language model provided by OpenAI for the agent to consume.`}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</NodeToolbar>
|
||||||
|
</div>
|
||||||
|
<div className="pb-2 px-2 bg-slate-500/10 text-sm">
|
||||||
|
{/* <FieldSchema
|
||||||
|
field={
|
||||||
|
<InputField
|
||||||
|
label="Agent Name"
|
||||||
|
required
|
||||||
|
onChange={onAgentNameChange}
|
||||||
|
value={data?.variableName}
|
||||||
|
type="text"
|
||||||
|
placeholder="my_agent"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
errors={errors?.[props.id]?.variableName}
|
||||||
|
/> */}
|
||||||
|
{/* <FieldSchema
|
||||||
|
field={
|
||||||
|
<InputField
|
||||||
|
label="OpenAI ID"
|
||||||
|
required
|
||||||
|
onChange={onOAIIdChange}
|
||||||
|
value={data?.OAIId}
|
||||||
|
type="text"
|
||||||
|
placeholder="asst-****"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
errors={errors?.[props.id]?.OAIId}
|
||||||
|
/> */}
|
||||||
|
<FieldSchema
|
||||||
|
field={
|
||||||
|
<InputField
|
||||||
|
label="Agent Name"
|
||||||
|
required
|
||||||
|
onChange={onAgentNameChange}
|
||||||
|
value={data?.variableName}
|
||||||
|
type="text"
|
||||||
|
placeholder="gpt_agent"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
errors={errors?.[props.id]?.variableName}
|
||||||
|
/>
|
||||||
|
<FieldSchema
|
||||||
|
field={
|
||||||
|
<SelectField
|
||||||
|
label="LLM"
|
||||||
|
selected={data?.selectedModel || OAIModelsEnum.GPT_4o}
|
||||||
|
onChange={(e) => updateNode(props.id, { selectedModel: e.target.value })}
|
||||||
|
options={Object.values(OAIModelsEnum)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
errors={errors?.[props.id]?.selectedModel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Handle type="target" position={Position.Top} className="rounded-none border-none w-16" />
|
||||||
|
<Handle type="source" position={Position.Bottom} className="rounded-none border-none w-16" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Lumina;
|
99
components/dashboard/nodes/autogen/Nexus.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import FieldSchema, { InputField, SelectField } from '@/components/dashboard/nodes/common/Fields';
|
||||||
|
import { ClsHeaderSkeleton, DefaultContent, ToolbarSkeleton } from '@/components/dashboard/nodes/common/ToolbarSkeleton';
|
||||||
|
import { ValidatorContext } from '@/contexts/ValidatorContext';
|
||||||
|
import useDnDStore from '@/stores/useDnDStore';
|
||||||
|
import { OAIModelsEnum } from '@/utils/enum';
|
||||||
|
import { InformationCircleIcon } from '@heroicons/react/24/outline';
|
||||||
|
import React from 'react';
|
||||||
|
import { Handle, NodeToolbar, Position, NodeProps as ReactFlowNodeProps, useReactFlow } from 'reactflow';
|
||||||
|
import { DotbaseNodesEnum } from '../types/nodeTypes';
|
||||||
|
|
||||||
|
const Nexus: React.FC<ReactFlowNodeProps> = (props) => {
|
||||||
|
const { errors } = React.useContext(ValidatorContext);
|
||||||
|
const { updateNode } = useDnDStore();
|
||||||
|
const { getNode } = useReactFlow();
|
||||||
|
|
||||||
|
const data = getNode(props.id)?.data;
|
||||||
|
const [toolbarVisible, setToolbarVisible] = React.useState(false);
|
||||||
|
|
||||||
|
const onAgentNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const val = e.target.value.trim();
|
||||||
|
updateNode(props.id, { variableName: val });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSystemPromptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
updateNode(props.id, { systemPrompt: val });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-sm bg-slate-500/10 text-white w-[280px] border-none">
|
||||||
|
<div
|
||||||
|
className={`${DotbaseNodesEnum.NEXUS} flex justify-between items-center border-b border-gray-200 py-2`}
|
||||||
|
>
|
||||||
|
<div className="font-bold ml-2">NEXUS</div>
|
||||||
|
<InformationCircleIcon
|
||||||
|
width={20}
|
||||||
|
className="text-gray-300 mr-2"
|
||||||
|
onMouseEnter={() => setToolbarVisible(true)}
|
||||||
|
onMouseLeave={() => setToolbarVisible(false)}
|
||||||
|
/>
|
||||||
|
<NodeToolbar isVisible={toolbarVisible} position={Position.Top}>
|
||||||
|
<ToolbarSkeleton
|
||||||
|
header={<ClsHeaderSkeleton name="Nexus" />}
|
||||||
|
content={
|
||||||
|
<DefaultContent
|
||||||
|
name="Nexus"
|
||||||
|
description="is a subclass of ConversableAgent configured with a default system message. The default system message is designed to solve a task with LLM, including suggesting python code blocks and debugging."
|
||||||
|
docTeaser={`Agent Name: Name of the agent. (ex: my_nexus_1)\n\nSystem Message: A message for the ChatCompletion inference. LLM: Any large language model provided by OpenAI for the agent to consume.`}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</NodeToolbar>
|
||||||
|
</div>
|
||||||
|
<div className="pb-2 px-2 bg-slate-500/10 text-sm">
|
||||||
|
<FieldSchema
|
||||||
|
field={
|
||||||
|
<InputField
|
||||||
|
label="Agent Name"
|
||||||
|
placeholder="my_agent"
|
||||||
|
required
|
||||||
|
onChange={onAgentNameChange}
|
||||||
|
type="text"
|
||||||
|
value={data?.variableName}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
errors={errors?.[props.id]?.variableName}
|
||||||
|
/>
|
||||||
|
<FieldSchema
|
||||||
|
field={
|
||||||
|
<InputField
|
||||||
|
label="System Message"
|
||||||
|
onChange={onSystemPromptChange}
|
||||||
|
type="text"
|
||||||
|
value={data?.systemPrompt}
|
||||||
|
placeholder='solve task xyz with LLM"'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
errors={errors?.[props.id]?.systemPrompt}
|
||||||
|
/>
|
||||||
|
<FieldSchema
|
||||||
|
field={
|
||||||
|
<SelectField
|
||||||
|
label="LLM"
|
||||||
|
selected={data?.selectedModel || OAIModelsEnum.GPT_3_5_TURBO}
|
||||||
|
onChange={(e) => updateNode(props.id, { selectedModel: e.target.value })}
|
||||||
|
options={Object.values(OAIModelsEnum)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
errors={errors?.[props.id]?.selectedModel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Handle type="target" position={Position.Top} className="rounded-none border-none w-16" />
|
||||||
|
<Handle type="source" position={Position.Bottom} className="rounded-none border-none w-16" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Nexus;
|
73
components/dashboard/nodes/autogen/Spark.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { DotbaseNodesEnum } from '@/components/dashboard/nodes/types/nodeTypes';
|
||||||
|
import { DefaultContent, MethodHeaderSkeleton, ToolbarSkeleton } from '@/components/dashboard/nodes/common/ToolbarSkeleton';
|
||||||
|
import { ValidatorContext } from '@/contexts/ValidatorContext';
|
||||||
|
import useDnDStore from '@/stores/useDnDStore';
|
||||||
|
import { InformationCircleIcon } from '@heroicons/react/24/outline';
|
||||||
|
import React from 'react';
|
||||||
|
import { Handle, NodeToolbar, Position, NodeProps as ReactFlowNodeProps } from 'reactflow';
|
||||||
|
|
||||||
|
import { highlight, languages } from 'prismjs';
|
||||||
|
import 'prismjs/components/prism-clike';
|
||||||
|
import 'prismjs/components/prism-python';
|
||||||
|
import 'prismjs/themes/prism.css';
|
||||||
|
import Editor from 'react-simple-code-editor';
|
||||||
|
|
||||||
|
const Spark: React.FC<ReactFlowNodeProps> = (props) => {
|
||||||
|
const { errors } = React.useContext(ValidatorContext);
|
||||||
|
const { updateNode } = useDnDStore();
|
||||||
|
const [toolbarVisible, setToolbarVisible] = React.useState(false);
|
||||||
|
const [code, setCode] = React.useState('');
|
||||||
|
|
||||||
|
const onCustomFuncChange = (code: string) => {
|
||||||
|
updateNode(props.id, { func: code });
|
||||||
|
setCode(code);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="nowheel rounded-sm bg-slate-500/10 text-white w-[266px] border-none">
|
||||||
|
<div
|
||||||
|
className={`${DotbaseNodesEnum.SPARK} flex justify-between items-center py-2`}
|
||||||
|
>
|
||||||
|
<div className="font-bold ml-2">SPARK</div>
|
||||||
|
<InformationCircleIcon
|
||||||
|
width={20}
|
||||||
|
className="text-gray-300 mr-2"
|
||||||
|
onMouseEnter={() => setToolbarVisible(true)}
|
||||||
|
onMouseLeave={() => setToolbarVisible(false)}
|
||||||
|
/>
|
||||||
|
<NodeToolbar isVisible={toolbarVisible} position={Position.Top}>
|
||||||
|
<ToolbarSkeleton
|
||||||
|
header={<MethodHeaderSkeleton name="Spark" />}
|
||||||
|
content={
|
||||||
|
<DefaultContent
|
||||||
|
name="Spark"
|
||||||
|
description="is a method for your agents to consume when it's necessary."
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</NodeToolbar>
|
||||||
|
</div>
|
||||||
|
<div className="pb-2 px-2 bg-slate-500/10 text-sm">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<Editor
|
||||||
|
value={code}
|
||||||
|
onValueChange={onCustomFuncChange}
|
||||||
|
placeholder="def my_action_cen(arg1, arg2):"
|
||||||
|
highlight={(code) => highlight(code || '', languages.python!, 'py')}
|
||||||
|
padding={10}
|
||||||
|
className="max-w-96 max-h-96 min-h-16 overflow-y-auto bg-[#1a1a1a] text-gray-400 w-full rounded-sm mt-2"
|
||||||
|
style={{
|
||||||
|
fontFamily: '"Fira code", "Fira Mono", monospace',
|
||||||
|
fontSize: 12,
|
||||||
|
}}
|
||||||
|
textareaClassName="outline-none w-80"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{errors?.[props.id]?.func && <span className="text-red-500 text-xs">{errors?.[props.id]?.func}</span>}
|
||||||
|
</div>
|
||||||
|
<Handle type="source" position={Position.Bottom} className="rounded-none border-none w-16 h-1" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Spark;
|
75
components/dashboard/nodes/common/Fields.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type FieldSchemaType = {
|
||||||
|
field: React.ReactElement;
|
||||||
|
errors?: string;
|
||||||
|
};
|
||||||
|
const FieldSchema: React.FC<FieldSchemaType> = ({ field, errors }) => {
|
||||||
|
return (
|
||||||
|
<div className="pt-2">
|
||||||
|
{field}
|
||||||
|
<span className="text-red-500 text-xs">{errors}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type InputFieldProps = {
|
||||||
|
label: string;
|
||||||
|
type: React.HTMLInputTypeAttribute;
|
||||||
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
value?: string;
|
||||||
|
required?: boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
};
|
||||||
|
export const InputField: React.FC<InputFieldProps> = ({ label, type, onChange, value, required, placeholder }) => {
|
||||||
|
const requiredString = required ? '*' : '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-2">
|
||||||
|
<p>
|
||||||
|
{label}
|
||||||
|
{requiredString}
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChange={onChange}
|
||||||
|
value={value}
|
||||||
|
className="px-1 bg-[#1a1a1a] rounded-sm text-gray-400 border border-[#1a1a1a] placeholder:text-gray-400 focus:outline-none focus:bg-black focus:border-[#4e1183] w-full text-sm "
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: string;
|
||||||
|
selected: string;
|
||||||
|
options: string[];
|
||||||
|
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||||
|
required?: boolean;
|
||||||
|
};
|
||||||
|
export const SelectField: React.FC<Props> = ({ selected, onChange, options, label, required }) => {
|
||||||
|
const requiredString = required ? '*' : '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-2">
|
||||||
|
<p>
|
||||||
|
{label}
|
||||||
|
{requiredString}
|
||||||
|
</p>
|
||||||
|
<select
|
||||||
|
value={selected}
|
||||||
|
onChange={onChange}
|
||||||
|
className="bg-[#1a1a1a] text-gray-400 border border-[#1a1a1a] text-sm rounded-sm focus:border-teal-500 outline-none"
|
||||||
|
>
|
||||||
|
{options.map((model) => (
|
||||||
|
<option key={model} value={model}>
|
||||||
|
{model}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FieldSchema;
|
17
components/dashboard/nodes/common/NodeSkeleton.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type TreeNodeProps = {
|
||||||
|
name: string;
|
||||||
|
content: React.JSX.Element;
|
||||||
|
};
|
||||||
|
const NodeSkeleton: React.FC<TreeNodeProps> = ({ name, content }: TreeNodeProps) => {
|
||||||
|
return (
|
||||||
|
<div className="bg-[#0a1a2f] border border-black-200 rounded-sm min-w-60">
|
||||||
|
<div className="bg-[#1a1a1a] border-b border-b-black-200">
|
||||||
|
<p className="px-2 py-1 font-bold text text-white">{name}</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-[#1a1a1a] p-2 text-white">{content}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default NodeSkeleton;
|
55
components/dashboard/nodes/common/ToolbarSkeleton.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
header: React.JSX.Element;
|
||||||
|
content: React.JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ClsHeaderSkeletonProps = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
export const ClsHeaderSkeleton: React.FC<ClsHeaderSkeletonProps> = ({ name }) => {
|
||||||
|
return (
|
||||||
|
<p className="text-xs">
|
||||||
|
<span className="text-red-500">class</span>
|
||||||
|
<span className="text-purple-400 pl-1">{name}</span>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type MethodHeaderSkeletonProps = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
export const MethodHeaderSkeleton: React.FC<MethodHeaderSkeletonProps> = ({ name }) => {
|
||||||
|
return (
|
||||||
|
<p className="text-xs">
|
||||||
|
<span className="text-red-500">def</span>
|
||||||
|
<span className="text-sky-400 pl-1">{name}</span>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
type DefaultContentProps = {
|
||||||
|
name?: string;
|
||||||
|
description: string;
|
||||||
|
docTeaser?: string;
|
||||||
|
};
|
||||||
|
export const DefaultContent: React.FC<DefaultContentProps> = ({ name, description, docTeaser }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className="text-xs">
|
||||||
|
{name && <span className="bg-gray-500 px-1 rounded-[2px] text-gray-100">{name}</span>}
|
||||||
|
<span className="text-gray-400 pl-1">{description}</span>
|
||||||
|
</p>
|
||||||
|
<p className="text-gray-500 text-xs mt-4 whitespace-pre-line">{docTeaser}</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ToolbarSkeleton: React.FC<Props> = ({ header, content }) => {
|
||||||
|
return (
|
||||||
|
<div className="bg-slate-500/10 border rounded max-w-96">
|
||||||
|
<div className="bg-slate-500/10 border-b-[0.5px] px-2 py-2">{header}</div>
|
||||||
|
<div className="px-2 py-4">{content}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
96
components/dashboard/nodes/types/nodeTypes.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// components/dashboard/nodes/types/nodeTypes.ts
|
||||||
|
import Nexus from '@/components/dashboard/nodes/autogen/Nexus';
|
||||||
|
import Spark from '@/components/dashboard/nodes/autogen/Spark';
|
||||||
|
import Lumina from '@/components/dashboard/nodes/autogen/Lumina';
|
||||||
|
import Hub from '@/components/dashboard/nodes/autogen/Hub';
|
||||||
|
import Bridge from '@/components/dashboard/nodes/autogen/Bridge';
|
||||||
|
import React from 'react';
|
||||||
|
import { NodeProps, Node as ReactFlowNode } from 'reactflow';
|
||||||
|
|
||||||
|
// Enum defining all available node types
|
||||||
|
export enum DotbaseNodesEnum {
|
||||||
|
BRIDGE = 'BRIDGE',
|
||||||
|
HUB = 'HUB',
|
||||||
|
LUMINA = 'LUMINA',
|
||||||
|
SPARK = 'SPARK',
|
||||||
|
NEXUS = 'NEXUS',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type definition for node data
|
||||||
|
export type DotbaseNodeDataType = {
|
||||||
|
connectivity: {
|
||||||
|
input: DotbaseNodesEnum[] | null;
|
||||||
|
output: DotbaseNodesEnum[] | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Type definition for our custom node type
|
||||||
|
export type DotbaseNodeType = Omit<ReactFlowNode<DotbaseNodeDataType>, 'position'>;
|
||||||
|
|
||||||
|
// Default configuration for each node type
|
||||||
|
export const DOTBASE_NODES: { [k in DotbaseNodesEnum]: DotbaseNodeType } = {
|
||||||
|
HUB: {
|
||||||
|
id: DotbaseNodesEnum.HUB,
|
||||||
|
type: DotbaseNodesEnum.HUB,
|
||||||
|
dragHandle: `.${DotbaseNodesEnum.HUB}`,
|
||||||
|
data: {
|
||||||
|
connectivity: {
|
||||||
|
input: [DotbaseNodesEnum.BRIDGE, DotbaseNodesEnum.LUMINA, DotbaseNodesEnum.NEXUS],
|
||||||
|
output: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BRIDGE: {
|
||||||
|
id: DotbaseNodesEnum.BRIDGE,
|
||||||
|
type: DotbaseNodesEnum.BRIDGE,
|
||||||
|
dragHandle: `.${DotbaseNodesEnum.BRIDGE}`,
|
||||||
|
data: {
|
||||||
|
connectivity: {
|
||||||
|
input: null,
|
||||||
|
output: [DotbaseNodesEnum.HUB],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LUMINA: {
|
||||||
|
id: DotbaseNodesEnum.LUMINA,
|
||||||
|
type: DotbaseNodesEnum.LUMINA,
|
||||||
|
dragHandle: `.${DotbaseNodesEnum.LUMINA}`,
|
||||||
|
data: {
|
||||||
|
connectivity: {
|
||||||
|
input: [DotbaseNodesEnum.SPARK],
|
||||||
|
output: [DotbaseNodesEnum.HUB],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SPARK: {
|
||||||
|
id: DotbaseNodesEnum.SPARK,
|
||||||
|
type: DotbaseNodesEnum.SPARK,
|
||||||
|
dragHandle: `.${DotbaseNodesEnum.SPARK}`,
|
||||||
|
data: {
|
||||||
|
connectivity: {
|
||||||
|
input: null,
|
||||||
|
output: [DotbaseNodesEnum.LUMINA],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NEXUS: {
|
||||||
|
id: DotbaseNodesEnum.NEXUS,
|
||||||
|
type: DotbaseNodesEnum.NEXUS,
|
||||||
|
dragHandle: `.${DotbaseNodesEnum.NEXUS}`,
|
||||||
|
data: {
|
||||||
|
connectivity: {
|
||||||
|
input: null,
|
||||||
|
output: [DotbaseNodesEnum.HUB],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map node types to their respective React components
|
||||||
|
export const CUSTOM_DOTBASE_NODES: { [_ in DotbaseNodesEnum]: React.ComponentType<NodeProps> } = {
|
||||||
|
BRIDGE: Bridge,
|
||||||
|
HUB: Hub,
|
||||||
|
LUMINA: Lumina,
|
||||||
|
SPARK: Spark,
|
||||||
|
NEXUS: Nexus,
|
||||||
|
};
|
275
components/home/AgentsSection.tsx
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
"use client";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { JSX } from "react/jsx-runtime";
|
||||||
|
import Image from "next/image";
|
||||||
|
import img1 from "@/public/assets/logo/logo.png"
|
||||||
|
import { TypingAnimation } from "@/components/ui/animations/typing-animation";
|
||||||
|
import { AnimatedBeam } from "@/components/ui/animations/animated-beam";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
interface IAgent {
|
||||||
|
icon: JSX.Element;
|
||||||
|
color: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AgentsSection() {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const logoRef = useRef<HTMLDivElement>(null);
|
||||||
|
const div1Ref = useRef<HTMLDivElement>(null);
|
||||||
|
const div2Ref = useRef<HTMLDivElement>(null);
|
||||||
|
const div3Ref = useRef<HTMLDivElement>(null);
|
||||||
|
const div4Ref = useRef<HTMLDivElement>(null);
|
||||||
|
const div5Ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const agents: IAgent[] = [
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg width="36" height="36" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
|
||||||
|
<circle cx="12" cy="18" r="5" fill="none" stroke="#FFFFFF" stroke-width="1.5"/>
|
||||||
|
<circle cx="28" cy="10" r="3" fill="none" stroke="#FFFFFF" stroke-width="1.5"/>
|
||||||
|
<circle cx="28" cy="26" r="3" fill="none" stroke="#FFFFFF" stroke-width="1.5"/>
|
||||||
|
<line x1="16.5" y1="16" x2="25.5" y2="11" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<line x1="16.5" y1="20" x2="25.5" y2="25" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<circle cx="12" cy="18" r="2" fill="#FFFFFF"/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
color: "#30A5FF",
|
||||||
|
title: "Nexus",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
|
||||||
|
<path d="M11 10c-2 0-3.5 1.5-3.5 3.5 0 1 .5 2 1.2 2.5-1 .5-1.7 1.5-1.7 2.7 0 1.3.8 2.4 2 2.8-.5.4-.8 1-.8 1.7 0 1.2 1 2.3 2.3 2.3.3 0 .6-.1.9-.2.1 1.4 1.2 2.4 2.6 2.4"
|
||||||
|
fill="none"
|
||||||
|
stroke="#FFFFFF"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<path d="M14 28c1.4 0 2.5-1 2.6-2.4.3.1.6.2.9.2 1.3 0 2.3-1 2.3-2.3"
|
||||||
|
fill="none"
|
||||||
|
stroke="#FFFFFF"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<path d="M25 10c2 0 3.5 1.5 3.5 3.5 0 1-.5 2-1.2 2.5 1 .5 1.7 1.5 1.7 2.7 0 1.3-.8 2.4-2 2.8.5.4.8 1 .8 1.7 0 1.2-1 2.3-2.3 2.3-.3 0-.6-.1-.9-.2-.1 1.4-1.2 2.4-2.6 2.4"
|
||||||
|
fill="none"
|
||||||
|
stroke="#FFFFFF"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<path d="M22 28c-1.4 0-2.5-1-2.6-2.4-.3.1-.6.2-.9.2-1.3 0-2.3-1-2.3-2.3"
|
||||||
|
fill="none"
|
||||||
|
stroke="#FFFFFF"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<path d="M18 8c-1.5 0-2.7 1-3.2 2.3M18 8c1.5 0 2.7 1 3.2 2.3"
|
||||||
|
fill="none"
|
||||||
|
stroke="#FFFFFF"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<path d="M13 14c-.5.5-.8 1.2-.8 2M23 14c.5.5.8 1.2.8 2M14 19c-.3.4-.5.9-.5 1.5M22 19c.3.4.5.9.5 1.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="#FFFFFF"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
color: "#30A5FF",
|
||||||
|
title: "Lumina",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
|
||||||
|
<circle cx="7" cy="18" r="3" fill="none" stroke="#FFFFFF" stroke-width="1.5"/>
|
||||||
|
<circle cx="29" cy="18" r="3" fill="none" stroke="#FFFFFF" stroke-width="1.5"/>
|
||||||
|
<path d="M10 18 C 15 18, 15 12, 18 12 C 21 12, 21 18, 26 18" stroke="#FFFFFF" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
||||||
|
<path d="M10 18 C 15 18, 15 24, 18 24 C 21 24, 21 18, 26 18" stroke="#FFFFFF" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
color: "#30A5FF",
|
||||||
|
title: "Bridge",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg width="36" height="36" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
|
||||||
|
<circle cx="18" cy="12" r="4.5" fill="none" stroke="#FFFFFF" stroke-width="1.5"/>
|
||||||
|
<path d="M10.5 25v-1c0-2.5 3-5 7.5-5s7.5 2.5 7.5 5v1" fill="none" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<circle cx="8" cy="14" r="3.5" fill="none" stroke="#FFFFFF" stroke-width="1.5"/>
|
||||||
|
<path d="M2 28v-1c0-2 2.5-4 6-4 1 0 2 .2 2.8.6" fill="none" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<circle cx="28" cy="14" r="3.5" fill="none" stroke="#FFFFFF" stroke-width="1.5"/>
|
||||||
|
<path d="M34 28v-1c0-2-2.5-4-6-4-1 0-2 .2-2.8.6" fill="none" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
color: "#FFFFFF",
|
||||||
|
title: "Hub",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg
|
||||||
|
width="36"
|
||||||
|
height="36"
|
||||||
|
viewBox="0 0 36 36"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M19.5 16.5L27 9" stroke="#FFF" strokeWidth="1.5" />
|
||||||
|
<path
|
||||||
|
d="M28.5 10.5L25.5 7.5L29.25 5.25L30.75 6.75L28.5 10.5Z"
|
||||||
|
stroke="#FFF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M6.03769 13.4623C4.52124 11.9459 4.12642 9.73254 4.85322 7.85322L6.98622 9.98622H9.98622V6.98622L7.85322 4.85322C9.73254 4.12642 11.9459 4.52124 13.4623 6.03769C14.9793 7.5547 15.3739 9.76911 14.646 11.6489L24.3511 21.354C26.2309 20.626 28.4452 21.0207 29.9623 22.5376C31.4787 24.0541 31.8736 26.2674 31.1467 28.1467L29.0137 26.0137H26.0137V29.0137L28.1467 31.1467C26.2674 31.8736 24.0541 31.4787 22.5376 29.9623C21.0217 28.4464 20.6266 26.2342 21.3523 24.3553L11.6447 14.6476C9.76579 15.3733 7.55356 14.9782 6.03769 13.4623Z"
|
||||||
|
stroke="#FFF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M18.3045 21.75L9.89845 30.1561C9.10672 30.9478 7.8231 30.9478 7.03137 30.1561L5.84379 28.9686C5.05207 28.1769 5.05207 26.8932 5.84379 26.1015L14.2499 17.6953"
|
||||||
|
stroke="#FFF"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
color: "#FF5C3D",
|
||||||
|
title: "Spark",
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="pt-36">
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: -100, opacity: 0 }}
|
||||||
|
whileInView={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5, delay: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="mx-auto container max-w-[1128px] px-8 flex flex-col items-center"
|
||||||
|
>
|
||||||
|
<p className="font-seven-seg text-lg uppercase text-center text-yellow-600">
|
||||||
|
Agents
|
||||||
|
</p>
|
||||||
|
<h2 className="mb-8 mt-2 text-4xl text-center uppercase text-[#FFF]">
|
||||||
|
<TypingAnimation startOnView={false}> 5 AGENTS POWERING DOTBASE</TypingAnimation>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/* <p className="mb-12 font-medium text-center text-[#001A2C]">
|
||||||
|
<span className="text-base">Meet the core agents driving ALMAZE</span>
|
||||||
|
<br />
|
||||||
|
</p> */}
|
||||||
|
|
||||||
|
<div ref={containerRef} className="relative flex flex-col items-center">
|
||||||
|
<div ref={logoRef} className="relative z-10 mb-56 flex bg-gradient-to-br from-[#1c1a3e] to-[#34181d] rounded-full px-1 py-1">
|
||||||
|
<Image src={img1} alt="" className="w-[56px]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AnimatedBeam
|
||||||
|
containerRef={containerRef}
|
||||||
|
fromRef={div1Ref}
|
||||||
|
toRef={logoRef}
|
||||||
|
duration={3}
|
||||||
|
curvature={100}
|
||||||
|
reverse
|
||||||
|
pathColor="#1c1a3e"
|
||||||
|
gradientStartColor="#410e66"
|
||||||
|
gradientStopColor="#410e66"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AnimatedBeam
|
||||||
|
containerRef={containerRef}
|
||||||
|
fromRef={div2Ref}
|
||||||
|
toRef={logoRef}
|
||||||
|
duration={3}
|
||||||
|
reverse
|
||||||
|
curvature={100}
|
||||||
|
pathColor="#1c1a3e"
|
||||||
|
gradientStartColor="#410e66"
|
||||||
|
gradientStopColor="#410e66"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AnimatedBeam
|
||||||
|
containerRef={containerRef}
|
||||||
|
fromRef={div3Ref}
|
||||||
|
toRef={logoRef}
|
||||||
|
duration={3}
|
||||||
|
reverse
|
||||||
|
curvature={100}
|
||||||
|
pathColor="#1c1a3e"
|
||||||
|
gradientStartColor="#410e66"
|
||||||
|
gradientStopColor="#410e66"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AnimatedBeam
|
||||||
|
containerRef={containerRef}
|
||||||
|
fromRef={div4Ref}
|
||||||
|
toRef={logoRef}
|
||||||
|
duration={3}
|
||||||
|
curvature={100}
|
||||||
|
pathColor="#1c1a3e"
|
||||||
|
gradientStartColor="#410e66"
|
||||||
|
gradientStopColor="#410e66"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AnimatedBeam
|
||||||
|
containerRef={containerRef}
|
||||||
|
fromRef={div5Ref}
|
||||||
|
toRef={logoRef}
|
||||||
|
duration={3}
|
||||||
|
curvature={100}
|
||||||
|
pathColor="#1c1a3e"
|
||||||
|
gradientStartColor="#410e66"
|
||||||
|
gradientStopColor="#410e66"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="relative z-10 grid grid-cols-5 gap-4">
|
||||||
|
{agents.map((agent, index) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
ref={
|
||||||
|
index === 0
|
||||||
|
? div1Ref
|
||||||
|
: index === 1
|
||||||
|
? div2Ref
|
||||||
|
: index === 2
|
||||||
|
? div3Ref
|
||||||
|
: index === 3
|
||||||
|
? div4Ref
|
||||||
|
: div5Ref
|
||||||
|
}
|
||||||
|
className="p-6 rounded-xl flex flex-col items-center gap-2.5"
|
||||||
|
>
|
||||||
|
<div className=" bg-gradient-to-br from-[#1c1a3e] to-[#34181d] rounded-full px-4 py-4">
|
||||||
|
{agent.icon}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3
|
||||||
|
className="text-lg font-semibold text-white text-center"
|
||||||
|
>
|
||||||
|
{agent.title}
|
||||||
|
</h3>
|
||||||
|
{/*
|
||||||
|
<p className="text-sm text-center text-[#001A2C]/75">
|
||||||
|
{agent.description}
|
||||||
|
</p> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
352
components/home/FeaturesSection.tsx
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
"use client";
|
||||||
|
import { JSX } from "react/jsx-runtime";
|
||||||
|
import { TypingAnimation } from "@/components/ui/animations/typing-animation";
|
||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
interface IFeature {
|
||||||
|
icon: JSX.Element;
|
||||||
|
title: string;
|
||||||
|
description: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FeaturesSection() {
|
||||||
|
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const features: IFeature[] = [
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg
|
||||||
|
width="36"
|
||||||
|
height="36"
|
||||||
|
viewBox="0 0 36 36"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M21.2559 31.3359C27.5304 30.9189 32.5284 25.8499 32.9397 19.4865C33.0201 18.2412 33.0201 16.9515 32.9397 15.7062C32.5284 9.34269 27.5304 4.27382 21.2559 3.85673C19.1152 3.71444 16.8804 3.71474 14.7441 3.85673C8.46958 4.27382 3.47161 9.34269 3.06036 15.7062C2.97988 16.9515 2.97988 18.2412 3.06036 19.4865C3.21015 21.8041 4.23514 23.95 5.44186 25.762C6.14251 27.0306 5.68011 28.6138 4.95031 29.9968C4.42411 30.994 4.16101 31.4926 4.37226 31.8528C4.58352 32.2129 5.05539 32.2245 5.99914 32.2474C7.8655 32.2929 9.12402 31.7637 10.123 31.027C10.6896 30.6093 10.9729 30.4003 11.1682 30.3763C11.3634 30.3523 11.7477 30.5106 12.516 30.8271C13.2066 31.1115 14.0084 31.287 14.7441 31.3359C16.8804 31.4779 19.1152 31.4782 21.2559 31.3359Z"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15.7305 10.5016V13.4701M10.5 15.7522H13.578M22.5346 15.7522H25.6125M22.5346 20.212H25.6125M10.5 20.212H13.578M15.7305 22.5304V25.4989M20.2687 22.5304V25.4989M20.2526 10.5016V13.4701M15.078 22.4529H21.0346C21.8629 22.4529 22.5346 21.7813 22.5346 20.9529V14.9701C22.5346 14.1417 21.8629 13.4701 21.0346 13.4701H15.078C14.2495 13.4701 13.578 14.1417 13.578 14.9701V20.9529C13.578 21.7813 14.2495 22.4529 15.078 22.4529Z"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
title: "Visual Agent Builder",
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
• Drag-and-drop interface for agent creation<br />
|
||||||
|
• Real-time agent configuration<br />
|
||||||
|
• Intuitive workflow design
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg
|
||||||
|
width="36"
|
||||||
|
height="36"
|
||||||
|
viewBox="0 0 36 36"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M11.25 6.75024C8.76473 6.75024 6.75 8.76497 6.75 11.2502C6.75 12.1032 6.98733 12.9008 7.39956 13.5805C5.32091 13.9781 3.75 15.8057 3.75 18.0002C3.75 20.1947 5.32091 22.0225 7.39956 22.42M11.25 6.75024C11.25 4.67918 12.9289 3.00024 15 3.00024C17.071 3.00024 18.75 4.67918 18.75 6.75024V9.00024M11.25 6.75024C11.25 7.97711 11.8392 9.06636 12.75 9.75053M7.39956 22.42C6.98733 23.0998 6.75 23.8973 6.75 24.7502C6.75 27.2356 8.76473 29.2502 11.25 29.2502C11.25 31.3213 12.9289 33.0002 15 33.0002C17.071 33.0002 18.75 31.3213 18.75 29.2502V27.0002M7.39956 22.42C7.93422 21.5384 8.7631 20.8552 9.75 20.5063"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M26.25 13.5001H23.25C21.8358 13.5001 21.1287 13.5001 20.6894 13.9395C20.25 14.3788 20.25 15.0859 20.25 16.5001V19.5001C20.25 20.9143 20.25 21.6214 20.6894 22.0608C21.1287 22.5001 21.8358 22.5001 23.25 22.5001H26.25C27.6642 22.5001 28.3713 22.5001 28.8106 22.0608C29.25 21.6214 29.25 20.9143 29.25 19.5001V16.5001C29.25 15.0859 29.25 14.3788 28.8106 13.9395C28.3713 13.5001 27.6642 13.5001 26.25 13.5001Z"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M22.5 22.5001V25.5001M27 22.5001V25.5001M22.5 10.5001V13.5001M27 10.5001V13.5001M20.25 15.7501H17.25M20.25 20.2501H17.25M32.25 15.7501H29.25M32.25 20.2501H29.25"
|
||||||
|
stroke="#1DA2FF"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
title: "Workflow Automation",
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
• Process automation pipelines<br />
|
||||||
|
• Data analysis workflows<br />
|
||||||
|
• Interactive support systems
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg
|
||||||
|
width="36"
|
||||||
|
height="36"
|
||||||
|
viewBox="0 0 36 36"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M28.5 18.0001C28.5 16.7575 29.5074 15.7501 30.75 15.7501C31.9926 15.7501 33 16.7575 33 18.0001C33 19.2427 31.9926 20.2501 30.75 20.2501C29.5074 20.2501 28.5 19.2427 28.5 18.0001Z"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M3 18.0001C3 16.7575 4.00736 15.7501 5.25 15.7501C6.49264 15.7501 7.5 16.7575 7.5 18.0001C7.5 19.2427 6.49264 20.2501 5.25 20.2501C4.00736 20.2501 3 19.2427 3 18.0001Z"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M20.5606 15.4395C20.1213 15.0001 19.4142 15.0001 18 15.0001C16.5858 15.0001 15.8787 15.0001 15.4394 15.4395M20.5606 15.4395C21 15.8788 21 16.5859 21 18.0001C21 19.4143 21 20.1214 20.5606 20.5608M15.4394 15.4395C15 15.8788 15 16.5859 15 18.0001C15 19.4143 15 20.1214 15.4394 20.5608M15.4394 20.5608C15.8787 21.0001 16.5858 21.0001 18 21.0001C19.4142 21.0001 20.1213 21.0001 20.5606 20.5608"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M31.0606 3.43946C30.6213 3.00012 29.9142 3.00012 28.5 3.00012C27.0858 3.00012 26.3787 3.00012 25.9394 3.43946M31.0606 3.43946C31.5 3.87881 31.5 4.58591 31.5 6.00012C31.5 7.41434 31.5 8.12144 31.0606 8.56079M25.9394 3.43946C25.5 3.87881 25.5 4.58591 25.5 6.00012C25.5 7.41434 25.5 8.12144 25.9394 8.56079M25.9394 8.56079C26.3787 9.00012 27.0858 9.00012 28.5 9.00012C29.9142 9.00012 30.6213 9.00012 31.0606 8.56079"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M10.0607 27.4395C9.62131 27.0001 8.91421 27.0001 7.5 27.0001C6.08579 27.0001 5.37869 27.0001 4.93934 27.4395M10.0607 27.4395C10.5 27.8788 10.5 28.5859 10.5 30.0001C10.5 31.4143 10.5 32.1214 10.0607 32.5608M4.93934 27.4395C4.5 27.8788 4.5 28.5859 4.5 30.0001C4.5 31.4143 4.5 32.1214 4.93934 32.5608M4.93934 32.5608C5.37869 33.0001 6.08579 33.0001 7.5 33.0001C8.91421 33.0001 9.62131 33.0001 10.0607 32.5608"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7.5 18.0001H15"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M21 18.0001H28.5"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M18 15.0001C18 12.0001 19.25 6.00012 25.5 6.00012"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M18 21.0001C18 24.0001 16.75 30.0001 10.5 30.0001"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
title: "Multi-Agent Orchestration",
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
• Nexus and Lumina support<br />
|
||||||
|
• Bridge integration<br />
|
||||||
|
• Hub coordination
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg
|
||||||
|
width="36"
|
||||||
|
height="36"
|
||||||
|
viewBox="0 0 36 36"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M31.9762 10.7122L31.2358 9.42731C30.6759 8.45557 30.396 7.9697 29.9196 7.77595C29.4432 7.58221 28.9044 7.73509 27.8269 8.04085L25.9966 8.5564C25.3087 8.71504 24.5869 8.62504 23.9589 8.30231L23.4535 8.01076C22.9149 7.66577 22.5006 7.15712 22.2712 6.55924L21.7704 5.06317C21.441 4.07314 21.2763 3.57812 20.8842 3.29498C20.4922 3.01184 19.9714 3.01184 18.9298 3.01184H17.2576C16.2162 3.01184 15.6954 3.01184 15.3033 3.29498C14.9113 3.57812 14.7466 4.07314 14.4172 5.06317L13.9163 6.55924C13.687 7.15712 13.2727 7.66577 12.7341 8.01076L12.2287 8.30231C11.6006 8.62504 10.8789 8.71504 10.191 8.5564L8.36062 8.04085C7.28311 7.73509 6.74437 7.58221 6.268 7.77595C5.79163 7.9697 5.51167 8.45557 4.95172 9.42731L4.21135 10.7122C3.68649 11.6231 3.42405 12.0785 3.47499 12.5634C3.52591 13.0482 3.87724 13.4389 4.57989 14.2203L6.12645 15.9493C6.50445 16.4278 6.77281 17.2618 6.77281 18.0117C6.77281 18.7618 6.50454 19.5955 6.12649 20.0742L4.57989 21.8032C3.87724 22.5847 3.52593 22.9753 3.47499 23.4603C3.42405 23.9451 3.68649 24.4005 4.21135 25.3113L4.95171 26.5962C5.51164 27.5679 5.79163 28.0539 6.268 28.2475C6.74437 28.4413 7.28313 28.2885 8.36065 27.9826L10.1909 27.4671C10.8789 27.3084 11.6008 27.3985 12.229 27.7213L12.7342 28.0129C13.2728 28.3579 13.687 28.8664 13.9162 29.4643L14.4172 30.9606C14.7466 31.9506 14.9113 32.4456 15.3033 32.7288C15.6954 33.0118 16.2162 33.0118 17.2576 33.0118H18.9298C19.9714 33.0118 20.4922 33.0118 20.8842 32.7288C21.2763 32.4456 21.441 31.9506 21.7704 30.9606L22.2714 29.4643C22.5006 28.8664 22.9147 28.3579 23.4534 28.0129L23.9586 27.7213C24.5868 27.3985 25.3086 27.3084 25.9966 27.4671L27.8269 27.9826C28.9044 28.2885 29.4432 28.4413 29.9196 28.2475C30.396 28.0539 30.6759 27.5679 31.2358 26.5962L31.9762 25.3113C32.5011 24.4005 32.7634 23.9451 32.7126 23.4603C32.6616 22.9753 32.3103 22.5847 31.6077 21.8032L30.061 20.0742C29.683 19.5955 29.4147 18.7618 29.4147 18.0117C29.4147 17.2618 29.6832 16.4278 30.061 15.9493L31.6077 14.2203C32.3103 13.4389 32.6616 13.0482 32.7126 12.5634C32.7634 12.0785 32.5011 11.6231 31.9762 10.7122Z"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M12.75 24.0001C13.7979 22.1885 15.7566 20.9696 18 20.9696C20.2433 20.9696 22.202 22.1885 23.25 24.0001M21 14.2501C21 15.907 19.6569 17.2501 18 17.2501C16.3431 17.2501 15 15.907 15 14.2501C15 12.5933 16.3431 11.2501 18 11.2501C19.6569 11.2501 21 12.5933 21 14.2501Z"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
title: "Tool Integration",
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
• Custom Python function support<br />
|
||||||
|
• Novamind Assistant integration<br />
|
||||||
|
• Extensible tool framework
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg
|
||||||
|
width="36"
|
||||||
|
height="36"
|
||||||
|
viewBox="0 0 36 36"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7.98257 14.5245C11.6031 8.1181 13.4133 4.91488 15.8975 4.09033C17.264 3.63672 18.7361 3.63672 20.1026 4.09033C22.5867 4.91488 24.3969 8.1181 28.0175 14.5245C31.638 20.9309 33.4482 24.1341 32.9052 26.7441C32.6064 28.1801 31.8704 29.4824 30.8025 30.4647C28.8615 32.2502 25.2411 32.2502 18 32.2502C10.759 32.2502 7.13846 32.2502 5.19745 30.4647C4.12963 29.4824 3.39359 28.1801 3.09484 26.7441C2.55179 24.1341 4.36205 20.9309 7.98257 14.5245Z"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M18.3633 25.5001V19.5001C18.3633 18.793 18.3633 18.4395 18.1435 18.2197C17.9239 18.0001 17.5704 18.0001 16.8633 18.0001"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M17.988 13.5001H18.0015"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="3"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
title: "Development Environment",
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
• Real-time testing and debugging<br />
|
||||||
|
• Production code export<br />
|
||||||
|
• Performance monitoring
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg
|
||||||
|
width="36"
|
||||||
|
height="36"
|
||||||
|
viewBox="0 0 36 36"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19.5 4.50359C18.7949 4.50012 18.0461 4.50012 17.25 4.50012C10.5325 4.50012 7.17373 4.50012 5.08686 6.58698C3 8.67386 3 12.0326 3 18.7501C3 25.4676 3 28.8264 5.08686 30.9133C7.17373 33.0001 10.5325 33.0001 17.25 33.0001C23.9675 33.0001 27.3263 33.0001 29.4132 30.9133C31.5 28.8264 31.5 25.4676 31.5 18.7501C31.5 17.9541 31.5 17.2053 31.4965 16.5001"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M27.75 3.00012L28.1369 4.04567C28.6442 5.41665 28.8978 6.10215 29.3979 6.60221C29.898 7.10228 30.5835 7.35593 31.9545 7.86324L33 8.25012L31.9545 8.637C30.5835 9.14432 29.898 9.39798 29.3979 9.89804C28.8978 10.3981 28.6442 11.0836 28.1369 12.4546L27.75 13.5001L27.3631 12.4546C26.8558 11.0836 26.6022 10.3981 26.1021 9.89804C25.602 9.39798 24.9165 9.14432 23.5455 8.637L22.5 8.25012L23.5455 7.86324C24.9165 7.35593 25.602 7.10228 26.1021 6.60221C26.6022 6.10215 26.8558 5.41665 27.3631 4.04567L27.75 3.00012Z"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M10.5 26.2501C13.9976 22.5868 20.4648 22.4143 24 26.2501M20.9927 15.0001C20.9927 17.0712 19.3113 18.7501 17.2372 18.7501C15.1633 18.7501 13.482 17.0712 13.482 15.0001C13.482 12.9291 15.1633 11.2501 17.2372 11.2501C19.3113 11.2501 20.9927 12.9291 20.9927 15.0001Z"
|
||||||
|
stroke="#fdd835"
|
||||||
|
strokeWidth="2.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
title: "Enterprise Ready",
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
• Reliable security protocols<br />
|
||||||
|
• Scalable architecture<br />
|
||||||
|
• Modern React/FastAPI stack
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="features" className="pt-36">
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: -100, opacity: 0 }}
|
||||||
|
whileInView={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5, delay: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="mx-auto container max-w-[1128px] px-8 flex flex-col items-center"
|
||||||
|
>
|
||||||
|
<p className="text-lg uppercase text-center text-yellow-600 font-seven-seg">
|
||||||
|
Features
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="mt-1 mb-2 max-w-[900px] text-3xl text-center uppercase text-[#FFF]">
|
||||||
|
<TypingAnimation startOnView={false}>
|
||||||
|
UNLEASH THE POWER OF DOTBASE
|
||||||
|
</TypingAnimation>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="mb-12 font-medium text-center text-[#b5b5b5]">
|
||||||
|
Reimagine what's possible with Dotbase, your ultimate platform for intelligent workflow creation. <br />
|
||||||
|
From effortless automation to dynamic agent collaboration, empowering to streamline, innovate, scale like never before.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{features.map((feature, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="relative group block p-2 h-full w-full"
|
||||||
|
onMouseEnter={() => setHoveredIndex(idx)}
|
||||||
|
onMouseLeave={() => setHoveredIndex(null)}
|
||||||
|
>
|
||||||
|
<AnimatePresence>
|
||||||
|
{hoveredIndex === idx && (
|
||||||
|
<motion.span
|
||||||
|
className="absolute inset-0 h-full w-full bg-yellow-600/10 block rounded-xl"
|
||||||
|
layoutId="hoverBackground"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
transition: { duration: 0.15 },
|
||||||
|
}}
|
||||||
|
exit={{
|
||||||
|
opacity: 0,
|
||||||
|
transition: { duration: 0.15, delay: 0.2 },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="p-8 border-2 text-[#FFF] border-gray-100 bg-gray-950 rounded-2xl flex flex-col gap-4 relative z-20 h-full transition-all duration-300 group-hover:border-yellow-600/60 group-hover:shadow-lg group-hover:text-yellow-600"
|
||||||
|
>
|
||||||
|
<div className="p-3 bg-gray-800 rounded-xl w-fit">
|
||||||
|
{feature.icon}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h3 className="text-xl font-seven-seg tracking-wide">
|
||||||
|
{feature.title}
|
||||||
|
</h3>
|
||||||
|
<div className="h-px w-12 bg-[#FFF]"></div>
|
||||||
|
<p className="text-sm leading-relaxed text-[#FFF]/75 font-medium">
|
||||||
|
{feature.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
84
components/home/Footer.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion, HTMLMotionProps } from 'framer-motion';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const AnimatedIcon: React.FC<HTMLMotionProps<'div'>> = ({ children, ...props }) => {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="w-[42px] h-8 rounded-md flex justify-center items-center cursor-pointer text-white hover:text-white/80"
|
||||||
|
whileHover={{ scale: 1.1 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
transition={{ type: "spring", stiffness: 400, damping: 17 }}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<footer id="footer" className="bg-black">
|
||||||
|
<div className="relative mx-auto container max-w-[1128px] min-h-[100px] px-8 flex flex-col justify-around items-center gap-2 pt-4">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-center mb-4 gap-4">
|
||||||
|
<a href="https://github.com/Dotbaseai" target="_blank" rel="noopener noreferrer">
|
||||||
|
<AnimatedIcon>
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M9.99999 0.526611C4.76578 0.526611 0.526306 4.76609 0.526306 10.0003C0.526306 14.1924 3.23815 17.7332 7.00394 18.9885C7.47762 19.0713 7.65525 18.7871 7.65525 18.5385C7.65525 18.3135 7.64341 17.5675 7.64341 16.774C5.26315 17.2122 4.64736 16.1938 4.45789 15.6608C4.35131 15.3885 3.88946 14.5477 3.48683 14.3227C3.15525 14.145 2.68157 13.7069 3.47499 13.695C4.22104 13.6832 4.75394 14.3819 4.93157 14.6661C5.7842 16.099 7.14604 15.6964 7.69078 15.4477C7.77367 14.8319 8.02236 14.4175 8.29473 14.1806C6.18683 13.9438 3.9842 13.1266 3.9842 9.50293C3.9842 8.47266 4.35131 7.62003 4.95525 6.95687C4.86052 6.72003 4.52894 5.74898 5.04999 4.44635C5.04999 4.44635 5.84341 4.19766 7.65525 5.4174C8.41315 5.20424 9.21841 5.09766 10.0237 5.09766C10.8289 5.09766 11.6342 5.20424 12.3921 5.4174C14.204 4.18582 14.9974 4.44635 14.9974 4.44635C15.5184 5.74898 15.1868 6.72003 15.0921 6.95687C15.6961 7.62003 16.0631 8.46082 16.0631 9.50293C16.0631 13.1385 13.8487 13.9438 11.7408 14.1806C12.0842 14.4766 12.3803 15.045 12.3803 15.9332C12.3803 17.2003 12.3684 18.2187 12.3684 18.5385C12.3684 18.7871 12.5461 19.0832 13.0198 18.9885C16.7619 17.7332 19.4737 14.1806 19.4737 10.0003C19.4737 4.76609 15.2342 0.526611 9.99999 0.526611Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</AnimatedIcon>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://dotbase.gitbook.io/dotbase" target="_blank" rel="noopener noreferrer">
|
||||||
|
<AnimatedIcon>
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.4297 10.5294C9.3925 11.085 9.87388 11.3629 10.4026 11.3634C10.9313 11.3638 11.4131 11.0868 12.3769 10.5328L18.5205 7.00132C18.7978 6.84191 18.9688 6.5464 18.9688 6.22649C18.9688 5.90659 18.7978 5.61108 18.5205 5.45166L12.3747 1.9189C11.412 1.36549 10.9306 1.08878 10.4024 1.08899C9.87425 1.0892 9.39311 1.36628 8.43081 1.92044L3.14751 4.96293C3.10835 4.98548 3.08878 4.99674 3.07052 5.00742C1.26579 6.06391 0.150771 7.99271 0.135862 10.0839C0.135712 10.105 0.135712 10.1276 0.135712 10.1728C0.135712 10.2179 0.135712 10.2405 0.135862 10.2616C0.150738 12.3504 1.26327 14.2774 3.06479 15.3347C3.08299 15.3454 3.10253 15.3567 3.14164 15.3793L6.45108 17.2901C8.37948 18.4035 9.34367 18.9602 10.4025 18.9605C11.4614 18.9609 12.4259 18.4048 14.3551 17.2927L17.8487 15.2788C18.8146 14.7219 19.2976 14.4435 19.5628 13.9845C19.828 13.5255 19.828 12.968 19.828 11.853V9.69911C19.828 9.38979 19.6605 9.10474 19.3903 8.95422C19.1287 8.80859 18.81 8.81074 18.5505 8.95991L11.3869 13.0778C10.9063 13.3541 10.666 13.4922 10.4023 13.4923C10.1386 13.4923 9.89816 13.3543 9.41736 13.0784L4.56887 10.2952C4.32601 10.1558 4.20456 10.0861 4.10702 10.0735C3.88465 10.0448 3.67084 10.1694 3.58613 10.377C3.54899 10.4681 3.54973 10.6081 3.55124 10.8881C3.55235 11.0943 3.5529 11.1974 3.57216 11.2922C3.61533 11.5045 3.72702 11.6968 3.89013 11.8394C3.96296 11.9031 4.05222 11.9546 4.23078 12.0577L9.41462 15.0496C9.89668 15.3278 10.1377 15.4669 10.4023 15.467C10.667 15.467 10.9081 15.3281 11.3903 15.0501L17.7442 11.3875C17.9089 11.2926 17.9912 11.2451 18.053 11.2808C18.1147 11.3165 18.1147 11.4115 18.1147 11.6016V12.5786C18.1147 12.8574 18.1147 12.9967 18.0484 13.1115C17.9821 13.2262 17.8614 13.2958 17.6199 13.435L12.3792 16.456C11.4144 17.0121 10.932 17.2902 10.4025 17.2899C9.87293 17.2897 9.39078 17.0112 8.4265 16.4542L3.52339 13.6218C3.50782 13.6128 3.50004 13.6083 3.49278 13.6041C2.46472 13.0034 1.83037 11.9043 1.82442 10.7136C1.82438 10.7052 1.82438 10.6962 1.82438 10.6782V9.78145C1.82438 9.12412 2.17446 8.51659 2.74315 8.18696C3.24567 7.89566 3.86551 7.89508 4.36856 8.18545L8.4297 10.5294Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</AnimatedIcon>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://x.com/dotbase_ai" target="_blank" rel="noopener noreferrer">
|
||||||
|
<AnimatedIcon>
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.2719 1.58659H18.0832L11.9415 8.60617L19.1667 18.1582H13.5094L9.0784 12.365L4.00833 18.1582H1.1954L7.76457 10.65L0.833374 1.58659H6.6343L10.6395 6.88187L15.2719 1.58659ZM14.2853 16.4756H15.843L5.78787 3.18087H4.11626L14.2853 16.4756Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</AnimatedIcon>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="text-white/30 text-xs">Copyright Reserved Dotbase @2025</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
265
components/home/Header.tsx
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
"use client";
|
||||||
|
import Image from "next/image";
|
||||||
|
import logoImg from "../../public/assets/logo/full_wlogo.png";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { usePrivy } from "@privy-io/react-auth";
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
// import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
// import { HoverBorderGradient } from "@/components/ui/animations/hover-border-gradient";
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
const privy = usePrivy();
|
||||||
|
const router = useRouter();
|
||||||
|
const [activeSection, setActiveSection] = useState("");
|
||||||
|
const [isCopied, setIsCopied] = useState<boolean>(false);
|
||||||
|
// const [CA, setCA] = useState("");
|
||||||
|
|
||||||
|
// const handleLogin = useCallback(() => privy.login(), [privy]);
|
||||||
|
// useEffect(() => {
|
||||||
|
// axios.get("https://catools.dev3vds1.link/get/almaze-labs")
|
||||||
|
// .then(response => {
|
||||||
|
// const data = response.data
|
||||||
|
// if (data) {
|
||||||
|
// // console.log(`this is the data addr : ${data.address}`)
|
||||||
|
// setCA(data.address);
|
||||||
|
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .catch(error => {
|
||||||
|
// console.error("Error fetching CA:", error);
|
||||||
|
// });
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (privy?.ready) {
|
||||||
|
if (privy.authenticated) {
|
||||||
|
localStorage.setItem('useremail', privy.user?.email?.address ?? "Guest");
|
||||||
|
Cookies.set('privy-authenticated', 'true', { path: '/', expires: 1 });
|
||||||
|
router.push('/dashboard');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [privy.ready, privy.authenticated, router]);
|
||||||
|
|
||||||
|
// Smooth scroll function
|
||||||
|
const scrollToSection = (sectionId: string) => {
|
||||||
|
const element = document.getElementById(sectionId);
|
||||||
|
if (element) {
|
||||||
|
setActiveSection(sectionId);
|
||||||
|
element.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "start",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isCopied) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsCopied(false);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Intersection Observer to detect active section
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setActiveSection(entry.target.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ threshold: 0.5 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const sections = ["features", "tokenomics"];
|
||||||
|
sections.forEach((section) => {
|
||||||
|
const element = document.getElementById(section);
|
||||||
|
if (element) observer.observe(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ name: "Features", href: "features", isExternal: false },
|
||||||
|
{ name: "Tokenomics", href: "tokenomics", isExternal: false },
|
||||||
|
{ name: "Socials", href: "footer", isExternal: false },
|
||||||
|
{ name: "Docs", href: "https://dotbase.gitbook.io/dotbase", isExternal: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.nav
|
||||||
|
initial={{ y: -50, opacity: 0 }}
|
||||||
|
whileInView={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="fixed z-[1000] top-0 left-0 w-full"
|
||||||
|
>
|
||||||
|
{/* Dark gradient background with glow */}
|
||||||
|
<div className="mx-auto container max-w-[1128px] pt-8 px-8">
|
||||||
|
<div className="relative">
|
||||||
|
{/* Glow effect layer */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-blue-500/60 via-purple-500/60 to-red-500/60 blur-xl" />
|
||||||
|
<div className="relative py-1 px-2 bg-gray-950 backdrop-blur-lg rounded-full flex justify-between items-center">
|
||||||
|
<Link href={"/"} className="flex">
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
transition={{ type: "spring", stiffness: 400, damping: 10 }}
|
||||||
|
>
|
||||||
|
<Image src={logoImg} alt="dotbase-logo" className="w-[116px]" />
|
||||||
|
</motion.div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<ul className="absolute top-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2 flex items-center gap-4">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<li key={item.href}>
|
||||||
|
{item.isExternal ? (
|
||||||
|
<motion.a
|
||||||
|
href={item.href}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<motion.button
|
||||||
|
className={`relative py-1.5 px-2 rounded-md text-sm font-medium text-white transition-colors duration-200 ${
|
||||||
|
activeSection === item.href ? "text-purple-700" : ""
|
||||||
|
}`}
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
transition={{ type: "spring", stiffness: 400, damping: 10 }}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
{activeSection === item.href && (
|
||||||
|
<motion.div
|
||||||
|
className="absolute bottom-0 left-0 h-0.5 w-full bg-purple-700"
|
||||||
|
layoutId="underline"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</motion.button>
|
||||||
|
</motion.a>
|
||||||
|
) : (
|
||||||
|
|
||||||
|
<motion.button
|
||||||
|
onClick={() => scrollToSection(item.href)}
|
||||||
|
className={`relative py-1.5 px-2 rounded-md text-sm font-medium text-white transition-colors duration-200 ${
|
||||||
|
activeSection === item.href ? "text-purple-700" : ""
|
||||||
|
}`}
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
transition={{ type: "spring", stiffness: 400, damping: 10 }}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
{activeSection === item.href && (
|
||||||
|
<motion.div
|
||||||
|
className="absolute bottom-0 left-0 h-0.5 w-full bg-purple-700"
|
||||||
|
layoutId="underline"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</motion.button>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1 pr-4">
|
||||||
|
<motion.a
|
||||||
|
href="https://github.com/Dotbaseai"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
transition={{ type: "spring", stiffness: 400, damping: 10 }}
|
||||||
|
className="w-[42px] h-[42px] rounded-md flex justify-center items-center hover:bg-white/5"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M9.99999 0.526611C4.76578 0.526611 0.526306 4.76609 0.526306 10.0003C0.526306 14.1924 3.23815 17.7332 7.00394 18.9885C7.47762 19.0713 7.65525 18.7871 7.65525 18.5385C7.65525 18.3135 7.64341 17.5675 7.64341 16.774C5.26315 17.2122 4.64736 16.1938 4.45789 15.6608C4.35131 15.3885 3.88946 14.5477 3.48683 14.3227C3.15525 14.145 2.68157 13.7069 3.47499 13.695C4.22104 13.6832 4.75394 14.3819 4.93157 14.6661C5.7842 16.099 7.14604 15.6964 7.69078 15.4477C7.77367 14.8319 8.02236 14.4175 8.29473 14.1806C6.18683 13.9438 3.9842 13.1266 3.9842 9.50293C3.9842 8.47266 4.35131 7.62003 4.95525 6.95687C4.86052 6.72003 4.52894 5.74898 5.04999 4.44635C5.04999 4.44635 5.84341 4.19766 7.65525 5.4174C8.41315 5.20424 9.21841 5.09766 10.0237 5.09766C10.8289 5.09766 11.6342 5.20424 12.3921 5.4174C14.204 4.18582 14.9974 4.44635 14.9974 4.44635C15.5184 5.74898 15.1868 6.72003 15.0921 6.95687C15.6961 7.62003 16.0631 8.46082 16.0631 9.50293C16.0631 13.1385 13.8487 13.9438 11.7408 14.1806C12.0842 14.4766 12.3803 15.045 12.3803 15.9332C12.3803 17.2003 12.3684 18.2187 12.3684 18.5385C12.3684 18.7871 12.5461 19.0832 13.0198 18.9885C16.7619 17.7332 19.4737 14.1806 19.4737 10.0003C19.4737 4.76609 15.2342 0.526611 9.99999 0.526611Z"
|
||||||
|
fill="#FFFFFF"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</motion.a>
|
||||||
|
|
||||||
|
<motion.a
|
||||||
|
href="https://x.com/dotbase_ai"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
transition={{ type: "spring", stiffness: 400, damping: 10 }}
|
||||||
|
className="w-[42px] h-[42px] rounded-md flex justify-center items-center hover:bg-white/5"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.2719 1.58659H18.0832L11.9415 8.60617L19.1667 18.1582H13.5094L9.0784 12.365L4.00833 18.1582H1.1954L7.76457 10.65L0.833374 1.58659H6.6343L10.6395 6.88187L15.2719 1.58659ZM14.2853 16.4756H15.843L5.78787 3.18087H4.11626L14.2853 16.4756Z"
|
||||||
|
fill="#FFFFFF"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</motion.a>
|
||||||
|
{/* <div className="bg-gray-950 text-white flex items-center space-x-2 py-1.5 text-[13px] font-normal mr-6">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(CA);
|
||||||
|
setIsCopied(true);
|
||||||
|
}}
|
||||||
|
className="py-1.5"
|
||||||
|
>
|
||||||
|
CA :{" "}
|
||||||
|
{isCopied
|
||||||
|
? "Copied!"
|
||||||
|
: CA}
|
||||||
|
</button>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="17"
|
||||||
|
viewBox="0 0 16 17"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g clipPath="url(#clip0_1_771)">
|
||||||
|
<path
|
||||||
|
d="M5.68437 5.91912V4.13412C5.68437 3.42005 5.68437 3.06301 5.82334 2.79028C5.94558 2.55037 6.14063 2.35532 6.38054 2.23308C6.65327 2.09412 7.01031 2.09412 7.72437 2.09412H12.5694C13.2834 2.09412 13.6405 2.09412 13.9132 2.23308C14.1531 2.35532 14.3482 2.55037 14.4704 2.79028C14.6094 3.06301 14.6094 3.42005 14.6094 4.13412V8.97912C14.6094 9.69318 14.6094 10.0502 14.4704 10.323C14.3482 10.5629 14.1531 10.7579 13.9132 10.8801C13.6405 11.0191 13.2834 11.0191 12.5694 11.0191H10.7844M3.89937 14.8441H8.74437C9.45844 14.8441 9.8155 14.8441 10.0882 14.7051C10.3281 14.5829 10.5232 14.3879 10.6454 14.148C10.7844 13.8752 10.7844 13.5182 10.7844 12.8041V7.95912C10.7844 7.24505 10.7844 6.88801 10.6454 6.61528C10.5232 6.37537 10.3281 6.18032 10.0882 6.05808C9.8155 5.91912 9.45844 5.91912 8.74437 5.91912H3.89937C3.18531 5.91912 2.82827 5.91912 2.55554 6.05808C2.31563 6.18032 2.12058 6.37537 1.99834 6.61528C1.85937 6.88801 1.85938 7.24505 1.85938 7.95912V12.8041C1.85938 13.5182 1.85937 13.8752 1.99834 14.148C2.12058 14.3879 2.31563 14.5829 2.55554 14.7051C2.82827 14.8441 3.1853 14.8441 3.89937 14.8441Z"
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth="1.275"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1_771">
|
||||||
|
<rect
|
||||||
|
width="15"
|
||||||
|
height="15"
|
||||||
|
fill="white"
|
||||||
|
transform="translate(0.584351 0.81897)"
|
||||||
|
/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.nav>
|
||||||
|
);
|
||||||
|
}
|
210
components/home/HeroSection.tsx
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
"use client";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { usePrivy } from "@privy-io/react-auth";
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import img1 from "@/public/assets/images/Dashboard.png";
|
||||||
|
import { FlipWords } from "@/components/ui/animations/flip-words";
|
||||||
|
import { ContainerScroll } from "@/components/ui/animations/container-scroll-animation";
|
||||||
|
import { HoverBorderGradient } from "@/components/ui/animations/hover-border-gradient";
|
||||||
|
import axios from "axios";
|
||||||
|
// import { cn } from "@/lib/utils";
|
||||||
|
// import { TypingAnimation } from "@/components/ui/animations/typing-animation";
|
||||||
|
// import { InteractiveGridPattern } from "@/components/ui/animations/interactive-grid-pattern";
|
||||||
|
// import bgImg from "@/assets/images/hero-section-bg.png";
|
||||||
|
|
||||||
|
export default function HeroSection() {
|
||||||
|
const privy = usePrivy();
|
||||||
|
const router = useRouter();
|
||||||
|
const handleLogin = useCallback(() => privy.login(), [privy]);
|
||||||
|
const words = ["Smarter Workflows", "Seamless Processes", "Optimized Systems"];
|
||||||
|
|
||||||
|
|
||||||
|
const [CA, setCA] = useState("");
|
||||||
|
const [isCopied, setIsCopied] = useState<boolean>(false);
|
||||||
|
useEffect(() => {
|
||||||
|
axios.get("https://catools.dev3vds1.link/get/almaze-labs")
|
||||||
|
.then(response => {
|
||||||
|
const data = response.data
|
||||||
|
if (data) {
|
||||||
|
// console.log(`this is the data addr : ${data.address}`)
|
||||||
|
setCA(data.address);
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Error fetching CA:", error);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
if (isCopied) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsCopied(false);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (privy?.ready) {
|
||||||
|
if (privy.authenticated) {
|
||||||
|
localStorage.setItem('useremail', privy.user?.email?.address ?? "Guest");
|
||||||
|
Cookies.set('privy-authenticated', 'true', { path: '/', expires: 1 });
|
||||||
|
router.push('/dashboard');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [privy.ready, privy.authenticated, router]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="relative pt-56">
|
||||||
|
<div className="min-h-screen bg-black p-8 flex items-center justify-center fixed left-4">
|
||||||
|
<div className="relative w-8 h-48 blur-md">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-red-500/50 via-purple-600/50 to-cyan-500/50 blur-3xl" />
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-red-500/20 via-purple-600/20 to-cyan-500/20 blur-2xl" />
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-red-500/30 via-purple-600/30 to-cyan-500/30" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="min-h-screen bg-black p-8 flex items-center justify-center fixed right-4 max-2xl:right-16 max-2xl:top-2 top-4">
|
||||||
|
<div className="relative w-8 h-48 blur-md">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-red-500/50 via-purple-600/50 to-cyan-500/50 blur-3xl" />
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-red-500/20 via-purple-600/20 to-cyan-500/20 blur-2xl" />
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-red-500/30 via-purple-600/30 to-cyan-500/30" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative z-10 mx-auto container max-w-[1128px] px-8 flex flex-col items-center">
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: -50, opacity: 0 }}
|
||||||
|
whileInView={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.25 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="mb-6 max-2xl:mb-24 h-[42px] px-1.5 rounded-md flex items-center"
|
||||||
|
>
|
||||||
|
<HoverBorderGradient
|
||||||
|
containerClassName="rounded-full"
|
||||||
|
as="button"
|
||||||
|
className="bg-black text-white flex items-center space-x-2 py-1.5 text-[13px] font-normal "
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(CA);
|
||||||
|
setIsCopied(true);
|
||||||
|
}}
|
||||||
|
className="py-1.5"
|
||||||
|
>
|
||||||
|
CA :{" "}
|
||||||
|
{isCopied
|
||||||
|
? "Copied!"
|
||||||
|
: CA}
|
||||||
|
</button>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="17"
|
||||||
|
viewBox="0 0 16 17"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g clipPath="url(#clip0_1_771)">
|
||||||
|
<path
|
||||||
|
d="M5.68437 5.91912V4.13412C5.68437 3.42005 5.68437 3.06301 5.82334 2.79028C5.94558 2.55037 6.14063 2.35532 6.38054 2.23308C6.65327 2.09412 7.01031 2.09412 7.72437 2.09412H12.5694C13.2834 2.09412 13.6405 2.09412 13.9132 2.23308C14.1531 2.35532 14.3482 2.55037 14.4704 2.79028C14.6094 3.06301 14.6094 3.42005 14.6094 4.13412V8.97912C14.6094 9.69318 14.6094 10.0502 14.4704 10.323C14.3482 10.5629 14.1531 10.7579 13.9132 10.8801C13.6405 11.0191 13.2834 11.0191 12.5694 11.0191H10.7844M3.89937 14.8441H8.74437C9.45844 14.8441 9.8155 14.8441 10.0882 14.7051C10.3281 14.5829 10.5232 14.3879 10.6454 14.148C10.7844 13.8752 10.7844 13.5182 10.7844 12.8041V7.95912C10.7844 7.24505 10.7844 6.88801 10.6454 6.61528C10.5232 6.37537 10.3281 6.18032 10.0882 6.05808C9.8155 5.91912 9.45844 5.91912 8.74437 5.91912H3.89937C3.18531 5.91912 2.82827 5.91912 2.55554 6.05808C2.31563 6.18032 2.12058 6.37537 1.99834 6.61528C1.85937 6.88801 1.85938 7.24505 1.85938 7.95912V12.8041C1.85938 13.5182 1.85937 13.8752 1.99834 14.148C2.12058 14.3879 2.31563 14.5829 2.55554 14.7051C2.82827 14.8441 3.1853 14.8441 3.89937 14.8441Z"
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth="1.275"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1_771">
|
||||||
|
<rect
|
||||||
|
width="15"
|
||||||
|
height="15"
|
||||||
|
fill="white"
|
||||||
|
transform="translate(0.584351 0.81897)"
|
||||||
|
/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
</HoverBorderGradient>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<ContainerScroll
|
||||||
|
titleComponent={
|
||||||
|
<>
|
||||||
|
<div className="flex items-center flex-col justify-center max-2xl:max-h-[150px] max-2xl:mt-24">
|
||||||
|
<motion.h2
|
||||||
|
initial={{ y: -50, opacity: 0 }}
|
||||||
|
whileInView={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.5 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className=" max-w-[1050px] text-5xl text-center text-[#FFF] mb-4"
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* <TypingAnimation startOnView = {false}> */}
|
||||||
|
Build <b><FlipWords words={words} /></b>with AI Agents
|
||||||
|
{/* </TypingAnimation> */}
|
||||||
|
</motion.h2>
|
||||||
|
|
||||||
|
<motion.p
|
||||||
|
initial={{ y: -50, opacity: 0 }}
|
||||||
|
whileInView={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.75 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="mb-6 text-lg font-medium text-center text-[#b5b5b5]"
|
||||||
|
>
|
||||||
|
Dotbase: The Visual Model Forge. Craft, Combine, and Deploy AI Models with Drag-and-Drop Simplicity.
|
||||||
|
</motion.p>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: -50, opacity: 0 }}
|
||||||
|
whileInView={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5, delay: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="mb-20 mt-2 flex items-center gap-4 max-xl:mb-2"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<HoverBorderGradient
|
||||||
|
containerClassName="rounded-full"
|
||||||
|
as="button"
|
||||||
|
className="bg-black text-white flex items-center space-x-2"
|
||||||
|
onClick={handleLogin}
|
||||||
|
>
|
||||||
|
<span>Try Dotbase</span>
|
||||||
|
</HoverBorderGradient>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className=" flex items-center justify-center">
|
||||||
|
<div className="relative">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
whileInView={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5, delay: 1.25 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="absolute -inset-1 bg-gradient-to-r from-gray-600 to-gray-600 rounded-lg blur opacity-75 group-hover:opacity-100 transition duration-1000 group-hover:duration-200" />
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: 0, opacity: 0 }}
|
||||||
|
whileInView={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5, delay: 1.25 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="relative w-full"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={img1}
|
||||||
|
alt=""
|
||||||
|
className="w-full rounded-lg relative"
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ContainerScroll>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
173
components/home/TokenomicsSection.tsx
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
"use client";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { TypingAnimation } from "@/components/ui/animations/typing-animation";
|
||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
|
||||||
|
export default function TokenomicsSection() {
|
||||||
|
const [activeState, setActiveState] = useState<number>(1);
|
||||||
|
|
||||||
|
const handleStateChange = (state: number) => {
|
||||||
|
setActiveState(state);
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
color: "#1DA2FF",
|
||||||
|
title: "Circulating Supply",
|
||||||
|
percentage: "95%",
|
||||||
|
content: {
|
||||||
|
title: "Circulating Supply 95%",
|
||||||
|
text: "The circulating supply represents the total amount of tokens that are currently available in the market. This ensures liquidity and accessibility for all participants. Our commitment to transparency and fairness is reflected in the high percentage of tokens allocated for circulation.",
|
||||||
|
bullets: [
|
||||||
|
"Ensures liquidity and accessibility for all participants.",
|
||||||
|
"Reflects our commitment to transparency and fairness.",
|
||||||
|
"Supports the growth and sustainability of our community."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
color: "#FBBC05",
|
||||||
|
title: "Team Allocations",
|
||||||
|
percentage: "2%",
|
||||||
|
content: {
|
||||||
|
title: "Team Allocations 2%",
|
||||||
|
background: "tokenomics-section-bg-2.png",
|
||||||
|
text: "The team allocation represents a small portion of the total supply, dedicated to rewarding the team for their hard work and commitment. This ensures that the team remains motivated and aligned with the project's long-term goals.",
|
||||||
|
bullets: [
|
||||||
|
"Rewards the team for their dedication and hard work.",
|
||||||
|
"Aligns the team's interests with the project's success.",
|
||||||
|
"Supports the ongoing development and growth of the project."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
color: "#EA4335",
|
||||||
|
title: "Marketing Operations",
|
||||||
|
percentage: "1%",
|
||||||
|
content: {
|
||||||
|
title: "Marketing Operations 1%",
|
||||||
|
background: "tokenomics-section-bg-3.png",
|
||||||
|
text: "The marketing operations allocation is dedicated to promoting our project and expanding our reach. This ensures that we can effectively communicate our vision and attract new participants.",
|
||||||
|
bullets: [
|
||||||
|
"Supports promotional activities and campaigns.",
|
||||||
|
"Helps in expanding our community and user base.",
|
||||||
|
"Ensures effective communication of our project's vision and goals."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
color: "#2AD458",
|
||||||
|
title: "Treasury",
|
||||||
|
percentage: "2%",
|
||||||
|
content: {
|
||||||
|
title: "Treasury 2%",
|
||||||
|
background: "tokenomics-section-bg-4.png",
|
||||||
|
text: "The treasury allocation is reserved for the long-term sustainability and growth of the project. This fund will be used for future development, partnerships, and unforeseen expenses.",
|
||||||
|
bullets: [
|
||||||
|
"Ensures the project's long-term sustainability and growth.",
|
||||||
|
"Provides funds for future development and partnerships.",
|
||||||
|
"Covers unforeseen expenses and emergencies."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="tokenomics" className="pt-36">
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: -100, opacity: 0 }}
|
||||||
|
whileInView={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5, delay: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="mx-auto container max-w-[1128px] px-8 flex flex-col items-center"
|
||||||
|
>
|
||||||
|
<p className="font-seven-seg text-lg uppercase text-center text-yellow-600">
|
||||||
|
Tokenomics
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 className="mb-2 mt-1 max-w-[900px] text-3xl text-center uppercase text-[#FFF]">
|
||||||
|
<TypingAnimation startOnView = {false}>GREAT TOKENS SIMPLE TOKENOMICS</TypingAnimation>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="mb-12 font-medium text-center text-[#b5b5b5]">
|
||||||
|
Discover the transparent and fair distribution of our tokens, designed to ensure <br /> sustainability and growth for our community.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="w-full bg-black rounded-xl">
|
||||||
|
<div className="px-28 grid grid-cols-4">
|
||||||
|
{tabs.map((tab) => (
|
||||||
|
<div
|
||||||
|
key={tab.id}
|
||||||
|
onClick={() => handleStateChange(tab.id)}
|
||||||
|
className={`border-r border-[#eee] last:border-r-0 text-white py-2 px-1 cursor-pointer transition-all duration-300
|
||||||
|
${activeState === tab.id ? 'text-yellow-600' : 'hover:text-yellow-600'}`}
|
||||||
|
>
|
||||||
|
<div className="mb-1 flex items-center justify-center gap-1">
|
||||||
|
{/* <span
|
||||||
|
className="w-[15px] h-[15px] rounded block"
|
||||||
|
style={{ backgroundColor: tab.color }}
|
||||||
|
/> */}
|
||||||
|
<p className="text-sm font-medium">
|
||||||
|
{tab.title}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/* <h3
|
||||||
|
className="font-offbit text-5xl uppercase transition-all duration-300 "
|
||||||
|
style={{ color: tab.color }}
|
||||||
|
>
|
||||||
|
{tab.percentage}
|
||||||
|
</h3> */}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-5 flex justify-center mt-2">
|
||||||
|
<div className="relative h-[280px] w-[800px] overflow-hidden rounded-2xl border border-yellow-600">
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{tabs.map((tab) => (
|
||||||
|
activeState === tab.id && (
|
||||||
|
<motion.div
|
||||||
|
key={tab.id}
|
||||||
|
initial={{ x: 50, opacity: 0 }}
|
||||||
|
animate={{ x: 0, opacity: 1 }}
|
||||||
|
exit={{ x: -50, opacity: 0 }}
|
||||||
|
transition={{
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 100,
|
||||||
|
damping: 20,
|
||||||
|
duration: 0.3
|
||||||
|
}}
|
||||||
|
className="absolute inset-0 p-8 rounded-xl"
|
||||||
|
style={{
|
||||||
|
background: `url('/images/${tab.content.background}')`,
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
backgroundColor: "#000",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h4 className="mb-1.5 text-2xl text-yellow-600 font-semibold">
|
||||||
|
{tab.content.title}
|
||||||
|
</h4>
|
||||||
|
<div className="text-sm leading-[24px] text-white space-y-4">
|
||||||
|
<p>{tab.content.text}</p>
|
||||||
|
<ul className="space-y-1">
|
||||||
|
{tab.content.bullets.map((bullet, index) => (
|
||||||
|
<li key={index}>• {bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)
|
||||||
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
13
components/ui/animations/LoadingDots.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
const LoadingDots = () => {
|
||||||
|
return (
|
||||||
|
<span className="inline-flex items-center">
|
||||||
|
<span className="animate-[loadingDot_1s_infinite] opacity-0">.</span>
|
||||||
|
<span className="animate-[loadingDot_1s_0.2s_infinite] opacity-0">.</span>
|
||||||
|
<span className="animate-[loadingDot_1s_0.4s_infinite] opacity-0">.</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoadingDots;
|
189
components/ui/animations/animated-beam.tsx
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { RefObject, useEffect, useId, useState } from "react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
export interface AnimatedBeamProps {
|
||||||
|
className?: string;
|
||||||
|
containerRef: RefObject<HTMLElement | null>; // Container ref
|
||||||
|
fromRef: RefObject<HTMLElement | null>;
|
||||||
|
toRef: RefObject<HTMLElement | null>;
|
||||||
|
curvature?: number;
|
||||||
|
reverse?: boolean;
|
||||||
|
pathColor?: string;
|
||||||
|
pathWidth?: number;
|
||||||
|
pathOpacity?: number;
|
||||||
|
gradientStartColor?: string;
|
||||||
|
gradientStopColor?: string;
|
||||||
|
delay?: number;
|
||||||
|
duration?: number;
|
||||||
|
startXOffset?: number;
|
||||||
|
startYOffset?: number;
|
||||||
|
endXOffset?: number;
|
||||||
|
endYOffset?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({
|
||||||
|
className,
|
||||||
|
containerRef,
|
||||||
|
fromRef,
|
||||||
|
toRef,
|
||||||
|
curvature = 0,
|
||||||
|
reverse = false, // Include the reverse prop
|
||||||
|
duration = Math.random() * 3 + 4,
|
||||||
|
delay = 0,
|
||||||
|
pathColor = "gray",
|
||||||
|
pathWidth = 2,
|
||||||
|
pathOpacity = 0.7,
|
||||||
|
gradientStartColor = "#ffaa40",
|
||||||
|
gradientStopColor = "#9c40ff",
|
||||||
|
startXOffset = 0,
|
||||||
|
startYOffset = 0,
|
||||||
|
endXOffset = 0,
|
||||||
|
endYOffset = 0,
|
||||||
|
}) => {
|
||||||
|
const id = useId();
|
||||||
|
const [pathD, setPathD] = useState("");
|
||||||
|
const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
|
// Calculate the gradient coordinates based on the reverse prop
|
||||||
|
const gradientCoordinates = reverse
|
||||||
|
? {
|
||||||
|
x1: ["90%", "-10%"],
|
||||||
|
x2: ["100%", "0%"],
|
||||||
|
y1: ["0%", "0%"],
|
||||||
|
y2: ["0%", "0%"],
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
x1: ["10%", "110%"],
|
||||||
|
x2: ["0%", "100%"],
|
||||||
|
y1: ["0%", "0%"],
|
||||||
|
y2: ["0%", "0%"],
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updatePath = () => {
|
||||||
|
if (containerRef.current && fromRef.current && toRef.current) {
|
||||||
|
const containerRect = containerRef.current.getBoundingClientRect();
|
||||||
|
const rectA = fromRef.current.getBoundingClientRect();
|
||||||
|
const rectB = toRef.current.getBoundingClientRect();
|
||||||
|
|
||||||
|
const svgWidth = containerRect.width;
|
||||||
|
const svgHeight = containerRect.height;
|
||||||
|
setSvgDimensions({ width: svgWidth, height: svgHeight });
|
||||||
|
|
||||||
|
const startX =
|
||||||
|
rectA.left - containerRect.left + rectA.width / 2 + startXOffset;
|
||||||
|
const startY =
|
||||||
|
rectA.top - containerRect.top + rectA.height / 2 + startYOffset;
|
||||||
|
const endX =
|
||||||
|
rectB.left - containerRect.left + rectB.width / 2 + endXOffset;
|
||||||
|
const endY =
|
||||||
|
rectB.top - containerRect.top + rectB.height / 2 + endYOffset;
|
||||||
|
|
||||||
|
const controlY = startY - curvature;
|
||||||
|
const d = `M ${startX},${startY} Q ${
|
||||||
|
(startX + endX) / 2
|
||||||
|
},${controlY} ${endX},${endY}`;
|
||||||
|
setPathD(d);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize ResizeObserver
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
// For all entries, recalculate the path
|
||||||
|
for (const entry of entries) {
|
||||||
|
console.log(entry);
|
||||||
|
updatePath();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Observe the container element
|
||||||
|
if (containerRef.current) {
|
||||||
|
resizeObserver.observe(containerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the updatePath initially to set the initial path
|
||||||
|
updatePath();
|
||||||
|
|
||||||
|
// Clean up the observer on component unmount
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
containerRef,
|
||||||
|
fromRef,
|
||||||
|
toRef,
|
||||||
|
curvature,
|
||||||
|
startXOffset,
|
||||||
|
startYOffset,
|
||||||
|
endXOffset,
|
||||||
|
endYOffset,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
width={svgDimensions.width}
|
||||||
|
height={svgDimensions.height}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-none absolute left-0 top-0 transform-gpu stroke-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d={pathD}
|
||||||
|
stroke={pathColor}
|
||||||
|
strokeWidth={pathWidth}
|
||||||
|
strokeOpacity={pathOpacity}
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d={pathD}
|
||||||
|
strokeWidth={pathWidth}
|
||||||
|
stroke={`url(#${id})`}
|
||||||
|
strokeOpacity="1"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
<defs>
|
||||||
|
<motion.linearGradient
|
||||||
|
className="transform-gpu"
|
||||||
|
id={id}
|
||||||
|
gradientUnits={"userSpaceOnUse"}
|
||||||
|
initial={{
|
||||||
|
x1: "0%",
|
||||||
|
x2: "0%",
|
||||||
|
y1: "0%",
|
||||||
|
y2: "0%",
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
x1: gradientCoordinates.x1,
|
||||||
|
x2: gradientCoordinates.x2,
|
||||||
|
y1: gradientCoordinates.y1,
|
||||||
|
y2: gradientCoordinates.y2,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
delay,
|
||||||
|
duration,
|
||||||
|
ease: [0.16, 1, 0.3, 1], // https://easings.net/#easeOutExpo
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatDelay: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<stop stopColor={gradientStartColor} stopOpacity="0"></stop>
|
||||||
|
<stop stopColor={gradientStartColor}></stop>
|
||||||
|
<stop offset="32.5%" stopColor={gradientStopColor}></stop>
|
||||||
|
<stop
|
||||||
|
offset="100%"
|
||||||
|
stopColor={gradientStopColor}
|
||||||
|
stopOpacity="0"
|
||||||
|
></stop>
|
||||||
|
</motion.linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
100
components/ui/animations/container-scroll-animation.tsx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { useRef } from "react";
|
||||||
|
import { useScroll, useTransform, motion, MotionValue } from "framer-motion";
|
||||||
|
|
||||||
|
interface ContainerScrollProps {
|
||||||
|
titleComponent: string | React.ReactNode;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HeaderProps {
|
||||||
|
translate: MotionValue<number>;
|
||||||
|
titleComponent: string | React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CardProps {
|
||||||
|
rotate: MotionValue<number>;
|
||||||
|
scale: MotionValue<number>;
|
||||||
|
translate: MotionValue<number>;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContainerScroll = ({
|
||||||
|
titleComponent,
|
||||||
|
children,
|
||||||
|
}: ContainerScrollProps) => {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const { scrollYProgress } = useScroll({
|
||||||
|
target: containerRef,
|
||||||
|
});
|
||||||
|
const [isMobile, setIsMobile] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const checkMobile = () => {
|
||||||
|
setIsMobile(window.innerWidth <= 768);
|
||||||
|
};
|
||||||
|
checkMobile();
|
||||||
|
window.addEventListener("resize", checkMobile);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", checkMobile);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const scaleDimensions = () => {
|
||||||
|
return isMobile ? [0.7, 0.9] : [1.05, 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
const rotate = useTransform(scrollYProgress, [0, 1], [20, 0]);
|
||||||
|
const scale = useTransform(scrollYProgress, [0, 1], scaleDimensions());
|
||||||
|
const translate = useTransform(scrollYProgress, [0, 1], [0, -100]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-center relative"
|
||||||
|
ref={containerRef}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="w-full relative"
|
||||||
|
style={{
|
||||||
|
perspective: "1000px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Header translate={translate} titleComponent={titleComponent} />
|
||||||
|
<Card rotate={rotate} translate={translate} scale={scale}>
|
||||||
|
{children}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Header = ({ translate, titleComponent }: HeaderProps) => {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
style={{
|
||||||
|
translateY: translate,
|
||||||
|
}}
|
||||||
|
className="div max-w-5xl mx-auto text-center"
|
||||||
|
>
|
||||||
|
{titleComponent}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Card = ({ rotate, scale, children }: CardProps) => {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
style={{
|
||||||
|
rotateX: rotate,
|
||||||
|
scale,
|
||||||
|
boxShadow:
|
||||||
|
"0 0 #0000004d, 0 9px 20px #0000004a, 0 37px 37px #00000042, 0 84px 50px #00000026, 0 149px 60px #0000000a, 0 233px 65px #00000003",
|
||||||
|
}}
|
||||||
|
className="max-w-5xl -mt-12 mx-auto w-full border-4 border-[#6C6C6C] p-2 md:p-6 bg-[#222222] rounded-[30px] shadow-2xl"
|
||||||
|
>
|
||||||
|
<div className="h-full w-full overflow-hidden rounded-2xl bg-gray-100 dark:bg-zinc-900 md:rounded-2xl">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
};
|
98
components/ui/animations/flip-words.tsx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
export const FlipWords = ({
|
||||||
|
words,
|
||||||
|
duration = 3000,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
words: string[];
|
||||||
|
duration?: number;
|
||||||
|
className?: string;
|
||||||
|
}) => {
|
||||||
|
const [currentWord, setCurrentWord] = useState(words[0]);
|
||||||
|
const [isAnimating, setIsAnimating] = useState<boolean>(false);
|
||||||
|
|
||||||
|
// thanks for the fix Julian - https://github.com/Julian-AT
|
||||||
|
const startAnimation = useCallback(() => {
|
||||||
|
const word = words[words.indexOf(currentWord) + 1] || words[0];
|
||||||
|
setCurrentWord(word);
|
||||||
|
setIsAnimating(true);
|
||||||
|
}, [currentWord, words]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAnimating)
|
||||||
|
setTimeout(() => {
|
||||||
|
startAnimation();
|
||||||
|
}, duration);
|
||||||
|
}, [isAnimating, duration, startAnimation]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence
|
||||||
|
onExitComplete={() => {
|
||||||
|
setIsAnimating(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
initial={{
|
||||||
|
opacity: 0,
|
||||||
|
y: 10,
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 100,
|
||||||
|
damping: 10,
|
||||||
|
}}
|
||||||
|
exit={{
|
||||||
|
opacity: 0,
|
||||||
|
y: -40,
|
||||||
|
x: 40,
|
||||||
|
filter: "blur(8px)",
|
||||||
|
scale: 2,
|
||||||
|
position: "absolute",
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
"z-10 inline-block relative text-left text-[#f5f5f5] dark:text-neutral-100 px-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
key={currentWord}
|
||||||
|
>
|
||||||
|
{/* edit suggested by Sajal: https://x.com/DewanganSajal */}
|
||||||
|
{currentWord.split(" ").map((word, wordIndex) => (
|
||||||
|
<motion.span
|
||||||
|
key={word + wordIndex}
|
||||||
|
initial={{ opacity: 0, y: 10, filter: "blur(8px)" }}
|
||||||
|
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
|
||||||
|
transition={{
|
||||||
|
delay: wordIndex * 0.3,
|
||||||
|
duration: 0.3,
|
||||||
|
}}
|
||||||
|
className="inline-block whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{word.split("").map((letter, letterIndex) => (
|
||||||
|
<motion.span
|
||||||
|
key={word + letterIndex}
|
||||||
|
initial={{ opacity: 0, y: 10, filter: "blur(8px)" }}
|
||||||
|
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
|
||||||
|
transition={{
|
||||||
|
delay: wordIndex * 0.3 + letterIndex * 0.05,
|
||||||
|
duration: 0.2,
|
||||||
|
}}
|
||||||
|
className="inline-block"
|
||||||
|
>
|
||||||
|
{letter}
|
||||||
|
</motion.span>
|
||||||
|
))}
|
||||||
|
<span className="inline-block"> </span>
|
||||||
|
</motion.span>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
};
|
116
components/ui/animations/hover-border-gradient.tsx
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
"use client"
|
||||||
|
import type React from "react"
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import { motion } from "framer-motion"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
type Direction = "TOP" | "LEFT" | "BOTTOM" | "RIGHT"
|
||||||
|
|
||||||
|
export function HoverBorderGradient({
|
||||||
|
children,
|
||||||
|
containerClassName,
|
||||||
|
className,
|
||||||
|
as: Tag = "button",
|
||||||
|
duration = 2,
|
||||||
|
clockwise = true,
|
||||||
|
...props
|
||||||
|
}: React.PropsWithChildren<{
|
||||||
|
as?: React.ElementType
|
||||||
|
containerClassName?: string
|
||||||
|
className?: string
|
||||||
|
duration?: number
|
||||||
|
clockwise?: boolean
|
||||||
|
} & React.HTMLAttributes<HTMLElement>
|
||||||
|
>) {
|
||||||
|
const [hovered, setHovered] = useState<boolean>(false)
|
||||||
|
const [direction, setDirection] = useState<Direction>("TOP")
|
||||||
|
|
||||||
|
const rotateDirection = (currentDirection: Direction): Direction => {
|
||||||
|
const directions: Direction[] = ["TOP", "RIGHT", "BOTTOM", "LEFT"]
|
||||||
|
const currentIndex = directions.indexOf(currentDirection)
|
||||||
|
const nextIndex = clockwise
|
||||||
|
? (currentIndex + 1) % directions.length
|
||||||
|
: (currentIndex - 1 + directions.length) % directions.length
|
||||||
|
return directions[nextIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced gradient maps matching the nav gradient
|
||||||
|
const movingMap: Record<Direction, string> = {
|
||||||
|
TOP: "radial-gradient(60% 150% at 50% 0%, rgba(37, 99, 235, 0.5) 0%, rgba(147, 51, 234, 0.3) 50%, rgba(239, 68, 68, 0.4) 100%)",
|
||||||
|
RIGHT: "radial-gradient(150% 60% at 100% 50%, rgba(37, 99, 235, 0.5) 0%, rgba(147, 51, 234, 0.3) 50%, rgba(239, 68, 68, 0.4) 100%)",
|
||||||
|
BOTTOM: "radial-gradient(60% 150% at 50% 100%, rgba(37, 99, 235, 0.5) 0%, rgba(147, 51, 234, 0.3) 50%, rgba(239, 68, 68, 0.4) 100%)",
|
||||||
|
LEFT: "radial-gradient(150% 60% at 0% 50%, rgba(37, 99, 235, 0.5) 0%, rgba(147, 51, 234, 0.3) 50%, rgba(239, 68, 68, 0.4) 100%)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hover highlight gradient
|
||||||
|
const highlight = "radial-gradient(100% 200% at 50% 50%, rgba(37, 99, 235, 0.7) 0%, rgba(147, 51, 234, 0.5) 50%, rgba(239, 68, 68, 0.6) 100%)"
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!hovered) {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setDirection((prevState) => rotateDirection(prevState))
|
||||||
|
}, duration * 250) // Faster rotation for smoother animation
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}
|
||||||
|
}, [hovered, duration])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
className={cn(
|
||||||
|
"relative flex rounded-full content-center transition duration-500 items-center flex-col flex-nowrap gap-10 h-min justify-center overflow-visible p-[2px] decoration-clone w-fit hover:scale-[1.02]",
|
||||||
|
containerClassName
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"w-auto text-white z-10 bg-black px-8 py-4 rounded-[inherit] font-medium text-[15px]",
|
||||||
|
"transition-all duration-300 hover:bg-black/90",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Primary glow layer */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute inset-0 z-0 rounded-[inherit] overflow-hidden"
|
||||||
|
style={{
|
||||||
|
filter: "blur(12px)",
|
||||||
|
}}
|
||||||
|
initial={{ background: movingMap[direction], opacity: 0.6 }}
|
||||||
|
animate={{
|
||||||
|
background: hovered ? highlight : movingMap[direction],
|
||||||
|
opacity: hovered ? 0.8 : 0.6,
|
||||||
|
scale: hovered ? 1.05 : 1,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
ease: "linear",
|
||||||
|
duration: duration * 0.25,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Secondary glow layer for depth */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute inset-0 z-0 rounded-[inherit] overflow-hidden"
|
||||||
|
style={{
|
||||||
|
filter: "blur(8px)",
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
background: hovered ? highlight : movingMap[direction],
|
||||||
|
scale: hovered ? 1.1 : 1,
|
||||||
|
opacity: hovered ? 1 : 0.7,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
ease: "linear",
|
||||||
|
duration: duration * 0.25,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Inner blur layer */}
|
||||||
|
<div className="bg-black/5 absolute z-[1] inset-[1px] rounded-[inherit] backdrop-blur-sm" />
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
}
|
83
components/ui/animations/typing-animation.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { motion, MotionProps } from "framer-motion";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
interface TypingAnimationProps extends MotionProps {
|
||||||
|
children: string;
|
||||||
|
className?: string;
|
||||||
|
duration?: number;
|
||||||
|
delay?: number;
|
||||||
|
as?: React.ElementType;
|
||||||
|
startOnView?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TypingAnimation({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
duration = 50,
|
||||||
|
delay = 0,
|
||||||
|
as: Component = "div",
|
||||||
|
startOnView = true,
|
||||||
|
...props
|
||||||
|
}: TypingAnimationProps) {
|
||||||
|
const MotionComponent = motion.create(Component, {
|
||||||
|
forwardMotionProps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [displayedText, setDisplayedText] = useState<string>("");
|
||||||
|
const [started, setStarted] = useState(false);
|
||||||
|
const elementRef = useRef<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!startOnView) {
|
||||||
|
const startTimeout = setTimeout(() => {
|
||||||
|
setStarted(true);
|
||||||
|
}, delay);
|
||||||
|
return () => clearTimeout(startTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setStarted(true);
|
||||||
|
}, delay);
|
||||||
|
observer.disconnect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.1 }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (elementRef.current) {
|
||||||
|
observer.observe(elementRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, [delay, startOnView]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!started) return;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
const typingEffect = setInterval(() => {
|
||||||
|
if (i < children.length) {
|
||||||
|
setDisplayedText(children.substring(0, i + 1));
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
clearInterval(typingEffect);
|
||||||
|
}
|
||||||
|
}, duration);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(typingEffect);
|
||||||
|
};
|
||||||
|
}, [children, duration, started]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MotionComponent ref={elementRef} className={cn("", className)} {...props}>
|
||||||
|
{displayedText}
|
||||||
|
</MotionComponent>
|
||||||
|
);
|
||||||
|
}
|
137
components/ui/common/alert-dialog.tsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const AlertDialog = AlertDialogPrimitive.Root
|
||||||
|
|
||||||
|
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
||||||
|
|
||||||
|
const AlertDialogPortal = AlertDialogPrimitive.Portal
|
||||||
|
|
||||||
|
const AlertDialogOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Overlay
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const AlertDialogContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPortal>
|
||||||
|
<AlertDialogOverlay />
|
||||||
|
<AlertDialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</AlertDialogPortal>
|
||||||
|
))
|
||||||
|
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const AlertDialogHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-2 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
AlertDialogHeader.displayName = "AlertDialogHeader"
|
||||||
|
|
||||||
|
const AlertDialogFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
AlertDialogFooter.displayName = "AlertDialogFooter"
|
||||||
|
|
||||||
|
const AlertDialogTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-lg font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const AlertDialogDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
|
||||||
|
|
||||||
|
const AlertDialogAction = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Action
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex h-10 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground ring-offset-background transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
||||||
|
|
||||||
|
const AlertDialogCancel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Cancel
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"mt-2 inline-flex h-10 items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-semibold ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 sm:mt-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
}
|
57
components/ui/common/button.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-9 px-4 py-2",
|
||||||
|
sm: "h-8 rounded-md px-3 text-xs",
|
||||||
|
lg: "h-10 rounded-md px-8",
|
||||||
|
icon: "h-9 w-9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : "button"
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Button.displayName = "Button"
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
76
components/ui/common/card.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Card = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"rounded-xl border bg-card text-card-foreground shadow",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Card.displayName = "Card"
|
||||||
|
|
||||||
|
const CardHeader = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardHeader.displayName = "CardHeader"
|
||||||
|
|
||||||
|
const CardTitle = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("font-semibold leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardTitle.displayName = "CardTitle"
|
||||||
|
|
||||||
|
const CardDescription = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardDescription.displayName = "CardDescription"
|
||||||
|
|
||||||
|
const CardContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||||
|
))
|
||||||
|
CardContent.displayName = "CardContent"
|
||||||
|
|
||||||
|
const CardFooter = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex items-center p-6 pt-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardFooter.displayName = "CardFooter"
|
||||||
|
|
||||||
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
22
components/ui/common/input.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
||||||
|
({ className, type, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
className={cn(
|
||||||
|
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Input.displayName = "Input"
|
||||||
|
|
||||||
|
export { Input }
|
84
contexts/ValidatorContext.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
'use client';
|
||||||
|
import { DnDFlowValidationSchema } from '@/utils/zod';
|
||||||
|
import useDnDStore from '@/stores/useDnDStore';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import React from 'react';
|
||||||
|
import { Node } from 'reactflow';
|
||||||
|
|
||||||
|
type ErrorObjType = {
|
||||||
|
[key: string]: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type ValidatorContextType = {
|
||||||
|
errors: ErrorObjType;
|
||||||
|
validate: (nds: Node[]) => boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ValidatorContext = React.createContext<ValidatorContextType>({
|
||||||
|
errors: {},
|
||||||
|
validate: () => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
type ModalContextProviderProps = {
|
||||||
|
children: React.JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValidatorContextProvider = ({ children }: ModalContextProviderProps) => {
|
||||||
|
const [errors, setErrors] = React.useState<ErrorObjType>({});
|
||||||
|
const [prev, setPrev] = React.useState<{ [x: string]: string }[]>();
|
||||||
|
const [nds, setNds] = React.useState<Node[]>([]);
|
||||||
|
const { nodes } = useDnDStore();
|
||||||
|
|
||||||
|
const _getZodData = React.useCallback((nds: Node[]) => {
|
||||||
|
return nds.map(({ type, data }) => {
|
||||||
|
return { [type as string]: data };
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validate = React.useCallback(
|
||||||
|
(nds: Node[]): boolean => {
|
||||||
|
setNds(nds);
|
||||||
|
try {
|
||||||
|
setErrors({});
|
||||||
|
const zodData = _getZodData(nds);
|
||||||
|
setPrev(zodData);
|
||||||
|
const zodResults = DnDFlowValidationSchema.safeParse(zodData);
|
||||||
|
// publish errors to consumers
|
||||||
|
if (!zodResults.success) {
|
||||||
|
const issues = zodResults.error.issues;
|
||||||
|
const updatedErrors: ErrorObjType = {};
|
||||||
|
issues.forEach(({ path, message }) => {
|
||||||
|
const nodeIndex = path[0] as number;
|
||||||
|
const nodeId = nds[nodeIndex].id;
|
||||||
|
const fieldName = path[path.length - 1];
|
||||||
|
|
||||||
|
if (!updatedErrors[nodeId as string]) {
|
||||||
|
updatedErrors[nodeId as string] = {};
|
||||||
|
}
|
||||||
|
updatedErrors[nodeId as string][fieldName as string] = message;
|
||||||
|
});
|
||||||
|
setErrors(updatedErrors);
|
||||||
|
}
|
||||||
|
return zodResults.success;
|
||||||
|
} catch (err) {
|
||||||
|
// toast msg
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[_getZodData],
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const filteredNodes = nodes.filter((node) => nds?.find((n) => n.id === node.id));
|
||||||
|
if (prev && !_.isEqual(_getZodData(filteredNodes), prev)) {
|
||||||
|
// after first submission, now validate the nodes has been validated in onChange event.
|
||||||
|
validate(filteredNodes);
|
||||||
|
}
|
||||||
|
}, [_getZodData, nds, nodes, prev, validate]);
|
||||||
|
|
||||||
|
return <ValidatorContext.Provider value={{ errors, validate }}>{children}</ValidatorContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ValidatorContextProvider;
|
13
hooks/useModalContext.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type UseModalContextReturnType = {
|
||||||
|
modal: React.JSX.Element | null;
|
||||||
|
setModal: React.Dispatch<React.SetStateAction<React.JSX.Element | null>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function useModalContext(): UseModalContextReturnType {
|
||||||
|
const [modal, setModal] = React.useState<UseModalContextReturnType['modal']>(null);
|
||||||
|
return { modal, setModal };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useModalContext;
|
6
lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { clsx, type ClassValue } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
23
middleware.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export function middleware(request: NextRequest) {
|
||||||
|
const { pathname } = request.nextUrl;
|
||||||
|
|
||||||
|
if (!pathname.startsWith('/dashboard')) {
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAuthenticated = request.cookies.get("privy-authenticated")?.value === "true";
|
||||||
|
const isLoggingOut = request.headers.get("x-logout") === "true";
|
||||||
|
|
||||||
|
if (!isAuthenticated || isLoggingOut) {
|
||||||
|
const homeUrl = new URL('/', request.url);
|
||||||
|
return NextResponse.redirect(homeUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: ['/dashboard/:path*']
|
||||||
|
};
|
19
next.config.mjs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
output: 'standalone',
|
||||||
|
async headers() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: "/api/:path*",
|
||||||
|
headers: [
|
||||||
|
{ key: "Access-Control-Allow-Credentials", value: "true" },
|
||||||
|
{ key: "Access-Control-Allow-Origin", value: "*" },
|
||||||
|
{ key: "Access-Control-Allow-Methods", value: "GET,DELETE,PATCH,POST,PUT" },
|
||||||
|
{ key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
17129
package-lock.json
generated
Normal file
75
package.json
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"name": "proj1",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Merged project configuration",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@heroicons/react": "^2.0.18",
|
||||||
|
"@monaco-editor/react": "^4.6.0",
|
||||||
|
"@privy-io/react-auth": "^1.99.1",
|
||||||
|
"@privy-io/wagmi-connector": "^0.1.13",
|
||||||
|
"@radix-ui/react-alert-dialog": "^1.1.5",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||||
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
|
"@react-three/fiber": "^8.17.12",
|
||||||
|
"@svgr/webpack": "^8.1.0",
|
||||||
|
"@tabler/icons-react": "^3.28.1",
|
||||||
|
"@vercel/analytics": "^1.1.2",
|
||||||
|
"@xyflow/react": "^12.4.2",
|
||||||
|
"ace-builds": "^1.32.3",
|
||||||
|
"ai": "^4.0.36",
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"child_process": "^1.0.2",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"framer-motion": "^11.18.2",
|
||||||
|
"html-to-image": "^1.11.11",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"lucide-react": "^0.471.1",
|
||||||
|
"motion": "^12.0.6",
|
||||||
|
"next": "14.2.16",
|
||||||
|
"prismjs": "^1.29.0",
|
||||||
|
"react": "^18",
|
||||||
|
"react-ace": "^10.1.0",
|
||||||
|
"react-dnd": "^16.0.1",
|
||||||
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
|
"react-dom": "^18",
|
||||||
|
"react-element-to-jsx-string": "^15.0.0",
|
||||||
|
"react-icons": "^5.4.0",
|
||||||
|
"react-markdown": "^9.0.3",
|
||||||
|
"react-nice-avatar": "^1.5.0",
|
||||||
|
"react-router-dom": "^7.1.1",
|
||||||
|
"react-simple-code-editor": "^0.13.1",
|
||||||
|
"reactflow": "^11.11.4",
|
||||||
|
"tailwind-merge": "^2.6.0",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"three": "^0.172.0",
|
||||||
|
"uuid": "^11.0.5",
|
||||||
|
"zod": "^3.22.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
|
"@types/lodash": "^4.14.202",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/prismjs": "^1.26.3",
|
||||||
|
"@types/react": "^18",
|
||||||
|
"@types/react-dom": "^18",
|
||||||
|
"@types/three": "^0.172.0",
|
||||||
|
"@types/uuid": "^10.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
||||||
|
"@typescript-eslint/parser": "^6.18.1",
|
||||||
|
"autoprefixer": "^10.0.1",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "14.2.16",
|
||||||
|
"postcss": "^8",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
8
postcss.config.mjs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/** @type {import('postcss-load-config').Config} */
|
||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
43
providers/privy-provider.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
'use client'; // Add this at the top to mark as Client Component
|
||||||
|
|
||||||
|
import { PrivyProvider } from '@privy-io/react-auth';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { toSolanaWalletConnectors } from '@privy-io/react-auth/solana';
|
||||||
|
|
||||||
|
export function Providers({ children }: { children: ReactNode }) {
|
||||||
|
const solanaConnectors = toSolanaWalletConnectors({
|
||||||
|
shouldAutoConnect: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PrivyProvider
|
||||||
|
appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID || ''}
|
||||||
|
config={{
|
||||||
|
appearance: {
|
||||||
|
accentColor: "#6A6FF5",
|
||||||
|
theme: "#FFFFFF",
|
||||||
|
showWalletLoginFirst: false,
|
||||||
|
logo: "https://auth.privy.io/logos/privy-logo.png",
|
||||||
|
walletChainType: 'solana-only',
|
||||||
|
walletList: ['phantom']
|
||||||
|
},
|
||||||
|
externalWallets: {
|
||||||
|
solana: {
|
||||||
|
connectors: solanaConnectors
|
||||||
|
},
|
||||||
|
},
|
||||||
|
loginMethods: [
|
||||||
|
'email',
|
||||||
|
'wallet'
|
||||||
|
],
|
||||||
|
embeddedWallets: {
|
||||||
|
createOnLogin: 'all-users',
|
||||||
|
requireUserPasswordOnCreate: false,
|
||||||
|
showWalletUIs: true
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</PrivyProvider>
|
||||||
|
);
|
||||||
|
}
|
BIN
public/apple-icon.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
public/assets/images/Dashboard.png
Normal file
After Width: | Height: | Size: 122 KiB |
15
public/assets/images/brand/logo.svg
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public/assets/images/demo.mp4
Normal file
13
public/assets/images/phantom.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 25 25" style="enable-background:new 0 0 25 25;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#AB9FF2;}
|
||||||
|
.st1{fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M22.5,0h-20C1.1,0,0,1.1,0,2.5v20C0,23.9,1.1,25,2.5,25h20c1.4,0,2.5-1.1,2.5-2.5v-20C25,1.1,23.9,0,22.5,0z"/>
|
||||||
|
<path class="st1" d="M19.6,8.8c-0.2-0.3-0.5-0.5-0.9-0.5H6.3c-0.7,0-1.1,0.8-0.7,1.4l2.8,3.9c0.2,0.3,0.5,0.5,0.9,0.5h12.4
|
||||||
|
c0.7,0,1.1-0.8,0.7-1.4L19.6,8.8z"/>
|
||||||
|
<path class="st1" d="M12.3,15.5c-0.2-0.3-0.5-0.5-0.9-0.5H3.7c-0.7,0-1.1,0.8-0.7,1.4l2.8,3.9c0.2,0.3,0.5,0.5,0.9,0.5h7.7
|
||||||
|
c0.7,0,1.1-0.8,0.7-1.4L12.3,15.5z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 782 B |
BIN
public/assets/logo/full_wlogo.png
Normal file
After Width: | Height: | Size: 120 KiB |
BIN
public/assets/logo/logo.png
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
public/fonts/OffBit-DotBold.ttf
Normal file
BIN
public/fonts/Seven-Segment.ttf
Normal file
BIN
public/icon.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
27
stores/useAppStore.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { createJSONStorage, persist } from 'zustand/middleware';
|
||||||
|
|
||||||
|
const DOTBASE_APP_STORE = 'DOTBASE_APP_STORE';
|
||||||
|
type StoreType = {
|
||||||
|
oaiKey: string;
|
||||||
|
setOAIKey: (key: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useAppStore = create<StoreType>()(
|
||||||
|
persist(
|
||||||
|
(set) => ({
|
||||||
|
oaiKey: '',
|
||||||
|
setOAIKey: (key: string) => set({ oaiKey: key }),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: DOTBASE_APP_STORE,
|
||||||
|
storage: createJSONStorage(() => localStorage),
|
||||||
|
partialize: (state) => {
|
||||||
|
state.oaiKey = '';
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
export default useAppStore;
|
194
stores/useDnDStore.ts
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import {
|
||||||
|
Node,
|
||||||
|
Edge,
|
||||||
|
Connection,
|
||||||
|
addEdge as rfAddEdge,
|
||||||
|
ReactFlowInstance,
|
||||||
|
NodeChange,
|
||||||
|
EdgeChange,
|
||||||
|
applyEdgeChanges,
|
||||||
|
applyNodeChanges
|
||||||
|
} from 'reactflow';
|
||||||
|
|
||||||
|
type DnDStore = {
|
||||||
|
nodes: Node[];
|
||||||
|
edges: Edge[];
|
||||||
|
instance: ReactFlowInstance | null;
|
||||||
|
selectedNodes: Node[];
|
||||||
|
selectedEdges: Edge[];
|
||||||
|
rfInstance: ReactFlowInstance | null;
|
||||||
|
clearNodes: () => void;
|
||||||
|
setNodes: (updater: ((nodes: Node[]) => Node[]) | Node[]) => void;
|
||||||
|
setEdges: (updater: ((edges: Edge[]) => Edge[]) | Edge[]) => void;
|
||||||
|
addNode: (node: Node) => void;
|
||||||
|
updateNode: (id: string, data: any) => void; // Added updateNode
|
||||||
|
addEdge: (connection: Connection) => void;
|
||||||
|
onNodesChange: (changes: NodeChange[]) => void;
|
||||||
|
onEdgesChange: (changes: EdgeChange[]) => void;
|
||||||
|
setInstance: (instance: ReactFlowInstance) => void;
|
||||||
|
setRfInstance: (instance: ReactFlowInstance) => void;
|
||||||
|
deleteElements: () => void;
|
||||||
|
duplicateSelection: () => void;
|
||||||
|
setSelectedElements: (nodes: Node[], edges: Edge[]) => void;
|
||||||
|
undoHistory: Array<{ nodes: Node[]; edges: Edge[]; }>;
|
||||||
|
redoHistory: Array<{ nodes: Node[]; edges: Edge[]; }>;
|
||||||
|
undo: () => void;
|
||||||
|
redo: () => void;
|
||||||
|
saveCurrentState: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useDnDStore = create<DnDStore>((set, get) => ({
|
||||||
|
nodes: [],
|
||||||
|
edges: [],
|
||||||
|
instance: null,
|
||||||
|
selectedNodes: [],
|
||||||
|
selectedEdges: [],
|
||||||
|
rfInstance: null,
|
||||||
|
undoHistory: [],
|
||||||
|
redoHistory: [],
|
||||||
|
|
||||||
|
setRfInstance: (instance) => set({ rfInstance: instance }),
|
||||||
|
|
||||||
|
clearNodes: () => set({ nodes: [], edges: [] }),
|
||||||
|
|
||||||
|
setNodes: (updater) => set((state) => ({
|
||||||
|
nodes: typeof updater === 'function' ? updater(state.nodes) : updater
|
||||||
|
})),
|
||||||
|
|
||||||
|
setEdges: (updater) => set((state) => ({
|
||||||
|
edges: typeof updater === 'function' ? updater(state.edges) : updater
|
||||||
|
})),
|
||||||
|
|
||||||
|
setInstance: (instance) => {
|
||||||
|
set({
|
||||||
|
instance,
|
||||||
|
rfInstance: instance
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addNode: (node) => {
|
||||||
|
const state = get();
|
||||||
|
state.saveCurrentState();
|
||||||
|
set((state) => ({ nodes: [...state.nodes, node] }));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateNode: (id, data) => {
|
||||||
|
const state = get();
|
||||||
|
state.saveCurrentState();
|
||||||
|
set((state) => ({
|
||||||
|
nodes: state.nodes.map((node) =>
|
||||||
|
node.id === id ? { ...node, data: { ...node.data, ...data } } : node
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
addEdge: (connection) => {
|
||||||
|
const state = get();
|
||||||
|
state.saveCurrentState();
|
||||||
|
set((state) => ({
|
||||||
|
edges: rfAddEdge(connection, state.edges)
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
onNodesChange: (changes: NodeChange[]) => {
|
||||||
|
set((state) => ({
|
||||||
|
nodes: applyNodeChanges(changes, state.nodes)
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
onEdgesChange: (changes: EdgeChange[]) => {
|
||||||
|
set((state) => ({
|
||||||
|
edges: applyEdgeChanges(changes, state.edges)
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
setSelectedElements: (nodes: Node[], edges: Edge[]) => {
|
||||||
|
set({ selectedNodes: nodes, selectedEdges: edges });
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteElements: () => {
|
||||||
|
const state = get();
|
||||||
|
state.saveCurrentState();
|
||||||
|
|
||||||
|
const selectedNodeIds = new Set(state.selectedNodes.map(node => node.id));
|
||||||
|
|
||||||
|
set((state) => ({
|
||||||
|
nodes: state.nodes.filter(node => !selectedNodeIds.has(node.id)),
|
||||||
|
edges: state.edges.filter(edge =>
|
||||||
|
!selectedNodeIds.has(edge.source) &&
|
||||||
|
!selectedNodeIds.has(edge.target) &&
|
||||||
|
!state.selectedEdges.find(e => e.id === edge.id)
|
||||||
|
),
|
||||||
|
selectedNodes: [],
|
||||||
|
selectedEdges: []
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
duplicateSelection: () => {
|
||||||
|
const state = get();
|
||||||
|
state.saveCurrentState();
|
||||||
|
|
||||||
|
const newNodes = state.selectedNodes.map(node => ({
|
||||||
|
...node,
|
||||||
|
id: `${node.id}-copy-${Date.now()}`,
|
||||||
|
position: {
|
||||||
|
x: node.position.x + 50,
|
||||||
|
y: node.position.y + 50
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
set((state) => ({
|
||||||
|
nodes: [...state.nodes, ...newNodes],
|
||||||
|
selectedNodes: newNodes
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
saveCurrentState: () => {
|
||||||
|
set((state) => ({
|
||||||
|
undoHistory: [...state.undoHistory, {
|
||||||
|
nodes: state.nodes,
|
||||||
|
edges: state.edges
|
||||||
|
}],
|
||||||
|
redoHistory: []
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
undo: () => {
|
||||||
|
set((state) => {
|
||||||
|
const prev = state.undoHistory[state.undoHistory.length - 1];
|
||||||
|
if (!prev) return state;
|
||||||
|
|
||||||
|
const newHistory = state.undoHistory.slice(0, -1);
|
||||||
|
return {
|
||||||
|
nodes: prev.nodes,
|
||||||
|
edges: prev.edges,
|
||||||
|
undoHistory: newHistory,
|
||||||
|
redoHistory: [
|
||||||
|
{ nodes: state.nodes, edges: state.edges },
|
||||||
|
...state.redoHistory
|
||||||
|
]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
redo: () => {
|
||||||
|
set((state) => {
|
||||||
|
const next = state.redoHistory[0];
|
||||||
|
if (!next) return state;
|
||||||
|
|
||||||
|
const newRedoHistory = state.redoHistory.slice(1);
|
||||||
|
return {
|
||||||
|
nodes: next.nodes,
|
||||||
|
edges: next.edges,
|
||||||
|
undoHistory: [
|
||||||
|
...state.undoHistory,
|
||||||
|
{ nodes: state.nodes, edges: state.edges }
|
||||||
|
],
|
||||||
|
redoHistory: newRedoHistory
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default useDnDStore;
|
151
styles/globals.css
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* Custom Fonts */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'OffBit';
|
||||||
|
src: url('../public/fonts/OffBit-DotBold.ttf') format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'SevenSeg';
|
||||||
|
src: url('../public/fonts/Seven-Segment.ttf') format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Global Body Styles */
|
||||||
|
body {
|
||||||
|
font-family: 'OffBit', Arial, Helvetica, sans-serif, 'SevenSeg';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utility for Balanced Text */
|
||||||
|
@layer utilities {
|
||||||
|
.text-balance {
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base Variables */
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 0 0% 3.9%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 0 0% 3.9%;
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 0 0% 3.9%;
|
||||||
|
--primary: 0 0% 9%;
|
||||||
|
--primary-foreground: 0 0% 98%;
|
||||||
|
--secondary: 0 0% 96.1%;
|
||||||
|
--secondary-foreground: 0 0% 9%;
|
||||||
|
--muted: 0 0% 96.1%;
|
||||||
|
--muted-foreground: 0 0% 45.1%;
|
||||||
|
--accent: 0 0% 96.1%;
|
||||||
|
--accent-foreground: 0 0% 9%;
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 0 0% 89.8%;
|
||||||
|
--input: 0 0% 89.8%;
|
||||||
|
--ring: 0 0% 3.9%;
|
||||||
|
--chart-1: 12 76% 61%;
|
||||||
|
--chart-2: 173 58% 39%;
|
||||||
|
--chart-3: 197 37% 24%;
|
||||||
|
--chart-4: 43 74% 66%;
|
||||||
|
--chart-5: 27 87% 67%;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
.dark {
|
||||||
|
--background: 0 0% 3.9%;
|
||||||
|
--foreground: 0 0% 98%;
|
||||||
|
--card: 0 0% 3.9%;
|
||||||
|
--card-foreground: 0 0% 98%;
|
||||||
|
--popover: 0 0% 3.9%;
|
||||||
|
--popover-foreground: 0 0% 98%;
|
||||||
|
--primary: 0 0% 98%;
|
||||||
|
--primary-foreground: 0 0% 9%;
|
||||||
|
--secondary: 0 0% 14.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
--muted: 0 0% 14.9%;
|
||||||
|
--muted-foreground: 0 0% 63.9%;
|
||||||
|
--accent: 0 0% 14.9%;
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 0 0% 14.9%;
|
||||||
|
--input: 0 0% 14.9%;
|
||||||
|
--ring: 0 0% 83.1%;
|
||||||
|
--chart-1: 220 70% 50%;
|
||||||
|
--chart-2: 160 60% 45%;
|
||||||
|
--chart-3: 30 80% 55%;
|
||||||
|
--chart-4: 280 65% 60%;
|
||||||
|
--chart-5: 340 75% 55%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply Global Styles */
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
input[type='number']::-webkit-inner-spin-button,
|
||||||
|
input[type='number']::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blob Animation */
|
||||||
|
@keyframes blob {
|
||||||
|
0% {
|
||||||
|
transform: translate(0px, 0px) scale(1);
|
||||||
|
}
|
||||||
|
33% {
|
||||||
|
transform: translate(30px, -50px) scale(1.1);
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
transform: translate(-20px, 20px) scale(0.9);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(0px, 0px) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-blob {
|
||||||
|
animation: blob 7s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-delay-2000 {
|
||||||
|
animation-delay: 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-delay-4000 {
|
||||||
|
animation-delay: 4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__handle {
|
||||||
|
background: #333333;
|
||||||
|
border: 2px solid #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__handle:hover {
|
||||||
|
background: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__edge-path {
|
||||||
|
stroke: #666666;
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__node {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #333333;
|
||||||
|
}
|
65
styles/reactflow.css
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
.react-flow__controls {
|
||||||
|
background: #252525;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__controls-button {
|
||||||
|
background: #252525;
|
||||||
|
border-bottom: 1px solid #333333;
|
||||||
|
fill: #666666;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__controls-button:hover {
|
||||||
|
background: #333333;
|
||||||
|
fill: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__controls-button:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__node {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #333333;
|
||||||
|
background: #252525;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__handle {
|
||||||
|
background: #333333;
|
||||||
|
border: 2px solid #666666;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__handle:hover {
|
||||||
|
background: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__edge-path {
|
||||||
|
stroke: #666666;
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__controls {
|
||||||
|
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__controls button {
|
||||||
|
background: #252525;
|
||||||
|
border-bottom: 1px solid #333333;
|
||||||
|
box-sizing: content-box;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__controls button:hover {
|
||||||
|
background: #333333;
|
||||||
|
}
|
67
styles/styles.css
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
Custom scrollbar styling that provides a refined, modern look
|
||||||
|
while maintaining consistency with the dark theme.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.custom-scrollbar {
|
||||||
|
/* Enable smooth scrolling behavior for a better user experience */
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
/* Hide default scrollbar in Firefox */
|
||||||
|
scrollbar-width: thin;
|
||||||
|
/* Set initial scrollbar colors for Firefox */
|
||||||
|
scrollbar-color: rgba(75, 75, 75, 0.5) rgba(28, 28, 28, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main scrollbar styling for Webkit browsers */
|
||||||
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
|
/* Keep the scrollbar height subtle but visible */
|
||||||
|
height: 6px;
|
||||||
|
/* Add some padding around the scrollbar */
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The track (background) of the scrollbar */
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-track {
|
||||||
|
/* Subtle, slightly transparent background */
|
||||||
|
background: rgba(28, 28, 28, 0.1);
|
||||||
|
/* Rounded edges for a modern look */
|
||||||
|
border-radius: 4px;
|
||||||
|
/* Add a slight border for definition */
|
||||||
|
border: 1px solid rgba(75, 75, 75, 0.1);
|
||||||
|
/* Inner shadow for depth */
|
||||||
|
box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The draggable scrolling element */
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
/* Semi-transparent thumb for a glass-like effect */
|
||||||
|
background: rgba(75, 75, 75, 0.5);
|
||||||
|
/* Rounded edges matching the track */
|
||||||
|
border-radius: 4px;
|
||||||
|
/* Subtle gradient for depth */
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(75, 75, 75, 0.6),
|
||||||
|
rgba(75, 75, 75, 0.4)
|
||||||
|
);
|
||||||
|
/* Smooth transition for hover effects */
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover state for the thumb */
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
|
/* Lighter color on hover for better feedback */
|
||||||
|
background: rgba(100, 100, 100, 0.6);
|
||||||
|
/* Enhanced gradient on hover */
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(100, 100, 100, 0.7),
|
||||||
|
rgba(100, 100, 100, 0.5)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active state when scrollbar is being dragged */
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb:active {
|
||||||
|
/* Darker color when active for pressed state feedback */
|
||||||
|
background: rgba(120, 120, 120, 0.7);
|
||||||
|
}
|
80
tailwind.config.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import type { Config } from "tailwindcss";
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
darkMode: ["class"],
|
||||||
|
content: [
|
||||||
|
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
offbit: ['OffBit', 'monospace'],
|
||||||
|
'seven-seg': ['SevenSeg', 'sans-serif'],
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
background: 'hsl(var(--background))',
|
||||||
|
foreground: 'hsl(var(--foreground))',
|
||||||
|
card: {
|
||||||
|
DEFAULT: 'hsl(var(--card))',
|
||||||
|
foreground: 'hsl(var(--card-foreground))',
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
|
foreground: 'hsl(var(--popover-foreground))',
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
|
foreground: 'hsl(var(--primary-foreground))',
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
|
foreground: 'hsl(var(--secondary-foreground))',
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
|
foreground: 'hsl(var(--muted-foreground))',
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
|
foreground: 'hsl(var(--accent-foreground))',
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
|
foreground: 'hsl(var(--destructive-foreground))',
|
||||||
|
},
|
||||||
|
border: 'hsl(var(--border))',
|
||||||
|
input: 'hsl(var(--input))',
|
||||||
|
ring: 'hsl(var(--ring))',
|
||||||
|
chart: {
|
||||||
|
'1': 'hsl(var(--chart-1))',
|
||||||
|
'2': 'hsl(var(--chart-2))',
|
||||||
|
'3': 'hsl(var(--chart-3))',
|
||||||
|
'4': 'hsl(var(--chart-4))',
|
||||||
|
'5': 'hsl(var(--chart-5))',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: 'var(--radius)',
|
||||||
|
md: 'calc(var(--radius) - 2px)',
|
||||||
|
sm: 'calc(var(--radius) - 4px)',
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
pulse: 'pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
||||||
|
'slow-spin': 'spin 20s linear infinite',
|
||||||
|
loadingDot: 'loadingDot 1s infinite', // Add this line
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
loadingDot: {
|
||||||
|
'0%': { opacity: '0' },
|
||||||
|
'50%': { opacity: '1' },
|
||||||
|
'100%': { opacity: '0' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [require("tailwindcss-animate")],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
38
tsconfig.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
},
|
||||||
|
"typeRoots": [
|
||||||
|
"./types",
|
||||||
|
"./node_modules/@types"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
"middleware.ts",
|
||||||
|
"app/api/chat/route.js"
|
||||||
|
],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
13
utils/enum.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export enum OAIModelsEnum {
|
||||||
|
GPT_4o = 'gpt-4o',
|
||||||
|
GPT_4_32K = 'gpt-4-32k',
|
||||||
|
GPT_3_5_TURBO = 'gpt-3.5-turbo',
|
||||||
|
GPT_3_5_TURBO_16K = 'gpt-3.5-turbo-16k',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AgentSelectionStrategyEnum {
|
||||||
|
RR = 'round_robin',
|
||||||
|
AUTO = 'auto',
|
||||||
|
MANUAL = 'manual',
|
||||||
|
RANDOM = 'random',
|
||||||
|
}
|
316
utils/exportUtils.ts
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Node, Edge } from 'reactflow';
|
||||||
|
import { DotbaseNodesEnum } from '@/components/dashboard/nodes/types/nodeTypes';
|
||||||
|
import { OAIModelsEnum } from '@/utils/enum';
|
||||||
|
|
||||||
|
// Updated generateImports to include .env configuration
|
||||||
|
const generateImports = () => `import os
|
||||||
|
import autogen
|
||||||
|
from autogen.agentchat.contrib.gpt_assistant_agent import GPTAssistantAgent
|
||||||
|
from autogen import UserProxyAgent
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Load environment variables from .env file
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Get API keys from environment variables
|
||||||
|
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
|
||||||
|
OPENAI_ASSISTANT_ID = os.getenv('OPENAI_ASSISTANT_ID')
|
||||||
|
|
||||||
|
# ----------------- #
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
// Helper functions for code generation with masked credentials
|
||||||
|
const generateDisplayCode = (nodes: Node[], edges: Edge[]): string => {
|
||||||
|
const generateBridgeForDisplay = (node: Node) => {
|
||||||
|
const data = node.data;
|
||||||
|
return `${data.variableName} = UserProxyAgent(
|
||||||
|
name="${data.variableName}",
|
||||||
|
human_input_mode="NEVER",
|
||||||
|
max_consecutive_auto_reply=1,
|
||||||
|
code_execution_config={
|
||||||
|
"work_dir": "dotbase-execution-dir",
|
||||||
|
"use_docker": False,
|
||||||
|
},
|
||||||
|
system_message="I am a user proxy agent that helps with information gathering and research."
|
||||||
|
)`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateHubForDisplay = (node: Node, connectedAgents: string[]) => {
|
||||||
|
const data = node.data;
|
||||||
|
return `${data.variableName} = autogen.GroupChat(
|
||||||
|
agents=[${connectedAgents.join(',')}], # All connected agents must be included here
|
||||||
|
messages=[],
|
||||||
|
max_round=${data.maxRounds || 15},
|
||||||
|
speaker_selection_method="${data.agentSelection || 'round_robin'}"
|
||||||
|
)
|
||||||
|
${data.variableName}_manager = autogen.GroupChatManager(
|
||||||
|
groupchat=${data.variableName},
|
||||||
|
llm_config={
|
||||||
|
"config_list": [{
|
||||||
|
"model":"${data.selectedModel || OAIModelsEnum.GPT_4o}",
|
||||||
|
"api_key": "YOUR_OPENAI_API_KEY" #Placeholder for API key
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateNexusForDisplay = (node: Node) => {
|
||||||
|
const data = node.data;
|
||||||
|
let code = `${data.variableName} = autogen.AssistantAgent(
|
||||||
|
name="${data.variableName}",`;
|
||||||
|
|
||||||
|
if (data.systemPrompt) {
|
||||||
|
code += `\n system_message="${data.systemPrompt}",`;
|
||||||
|
}
|
||||||
|
|
||||||
|
code += `\n description="I am an AI assistant that helps with research and provides detailed information and if given system_message I work accordingly.",
|
||||||
|
llm_config={
|
||||||
|
"config_list": [{
|
||||||
|
"model":"${data.selectedModel || OAIModelsEnum.GPT_4o}",
|
||||||
|
"api_key": "YOUR_OPENAI_API_KEY" #Placeholder for API key
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)`;
|
||||||
|
return code;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateLuminaForDisplay = (node: Node) => {
|
||||||
|
const data = node.data;
|
||||||
|
return `${data.variableName} = GPTAssistantAgent(
|
||||||
|
name="${data.variableName}",
|
||||||
|
description="I am an AI assistant that helps with research and provides detailed information.",
|
||||||
|
llm_config={
|
||||||
|
"config_list": [{
|
||||||
|
"model":"${data.selectedModel || OAIModelsEnum.GPT_4o}",
|
||||||
|
"api_key": "YOUR_OPENAI_API_KEY" #Placeholder for API key
|
||||||
|
}],
|
||||||
|
"assistant_id": "YOUR_ASSISTANT_ID" #Placeholder for assistant ID, check: https://platform.openai.com/assistants
|
||||||
|
}
|
||||||
|
)`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rest of the display code generation logic remains the same
|
||||||
|
const generateSpark = (node: Node) => {
|
||||||
|
const data = node.data;
|
||||||
|
return data.func || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateInitiateChat = (sourceNode: Node, targetNode: Node) => {
|
||||||
|
return `${sourceNode.data.variableName}.initiate_chat(${targetNode.data.variableName}_manager, message="${sourceNode.data.initialPrompt}")`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Combine all code blocks
|
||||||
|
let code = generateImports();
|
||||||
|
const codeBlocks: string[] = [];
|
||||||
|
const initiateChatBlocks: string[] = [];
|
||||||
|
|
||||||
|
// Generate Spark functions first
|
||||||
|
nodes.forEach(node => {
|
||||||
|
if (node.type === DotbaseNodesEnum.SPARK) {
|
||||||
|
const funcCode = generateSpark(node);
|
||||||
|
if (funcCode) {
|
||||||
|
codeBlocks.push(funcCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate agent code
|
||||||
|
nodes.forEach(node => {
|
||||||
|
let agentCode = '';
|
||||||
|
switch (node.type) {
|
||||||
|
case DotbaseNodesEnum.BRIDGE:
|
||||||
|
agentCode = generateBridgeForDisplay(node);
|
||||||
|
break;
|
||||||
|
case DotbaseNodesEnum.NEXUS:
|
||||||
|
agentCode = generateNexusForDisplay(node);
|
||||||
|
break;
|
||||||
|
case DotbaseNodesEnum.LUMINA:
|
||||||
|
agentCode = generateLuminaForDisplay(node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (agentCode) {
|
||||||
|
codeBlocks.push(agentCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate Hub code
|
||||||
|
nodes.forEach(node => {
|
||||||
|
if (node.type === DotbaseNodesEnum.HUB) {
|
||||||
|
const connectedAgents = edges
|
||||||
|
.filter(edge => edge.target === node.id)
|
||||||
|
.map(edge => {
|
||||||
|
const sourceNode = nodes.find(n => n.id === edge.source);
|
||||||
|
return sourceNode?.data.variableName;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (connectedAgents.length > 0) {
|
||||||
|
codeBlocks.push(generateHubForDisplay(node, connectedAgents));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate chat initiation code
|
||||||
|
edges.forEach(edge => {
|
||||||
|
const sourceNode = nodes.find(node => node.id === edge.source);
|
||||||
|
const targetNode = nodes.find(node => node.id === edge.target);
|
||||||
|
|
||||||
|
if (sourceNode?.type === DotbaseNodesEnum.BRIDGE &&
|
||||||
|
targetNode?.type === DotbaseNodesEnum.HUB) {
|
||||||
|
initiateChatBlocks.push(generateInitiateChat(sourceNode, targetNode));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return code + codeBlocks.join('\n\n') + '\n\n' + initiateChatBlocks.join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Helper functions for code generation with real credentials
|
||||||
|
const generateExecutionCode = (nodes: Node[], edges: Edge[]): string => {
|
||||||
|
const generateBridgeForDisplay = (node: Node) => {
|
||||||
|
const data = node.data;
|
||||||
|
return `${data.variableName} = UserProxyAgent(
|
||||||
|
name="${data.variableName}",
|
||||||
|
human_input_mode="ALWAYS",
|
||||||
|
max_consecutive_auto_reply=1,
|
||||||
|
code_execution_config={
|
||||||
|
"work_dir": "dotbase-execution-dir",
|
||||||
|
"use_docker": False,
|
||||||
|
},
|
||||||
|
system_message="I am a user proxy agent that helps with information gathering and research."
|
||||||
|
)`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateHubForDisplay = (node: Node, connectedAgents: string[]) => {
|
||||||
|
const data = node.data;
|
||||||
|
return `${data.variableName} = autogen.GroupChat(
|
||||||
|
agents=[${connectedAgents.join(',')}], # All connected agents must be included here
|
||||||
|
messages=[],
|
||||||
|
max_round=${connectedAgents.length},
|
||||||
|
speaker_selection_method="${data.agentSelection || 'round_robin'}"
|
||||||
|
)
|
||||||
|
${data.variableName}_manager = autogen.GroupChatManager(
|
||||||
|
groupchat=${data.variableName},
|
||||||
|
llm_config={
|
||||||
|
"config_list": [{
|
||||||
|
"model":"${data.selectedModel || OAIModelsEnum.GPT_4o}",
|
||||||
|
"api_key": OPENAI_API_KEY
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateNexusForDisplay = (node: Node) => {
|
||||||
|
const data = node.data;
|
||||||
|
let code = `${data.variableName} = autogen.AssistantAgent(
|
||||||
|
name="${data.variableName}",`;
|
||||||
|
|
||||||
|
if (data.systemPrompt) {
|
||||||
|
code += `\n system_message="${data.systemPrompt}",`;
|
||||||
|
}
|
||||||
|
|
||||||
|
code += `\n description="I am an AI assistant that helps with research and provides detailed information and if given system_message I work accordingly.",
|
||||||
|
llm_config={
|
||||||
|
"config_list": [{
|
||||||
|
"model":"${data.selectedModel || OAIModelsEnum.GPT_4o}",
|
||||||
|
"api_key": OPENAI_API_KEY
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)`;
|
||||||
|
return code;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateLuminaForDisplay = (node: Node) => {
|
||||||
|
const data = node.data;
|
||||||
|
return `${data.variableName} = GPTAssistantAgent(
|
||||||
|
name="${data.variableName}",
|
||||||
|
description="I am an AI assistant that helps with research and provides detailed information.",
|
||||||
|
llm_config={
|
||||||
|
"config_list": [{
|
||||||
|
"model":"${data.selectedModel || OAIModelsEnum.GPT_4o}",
|
||||||
|
"api_key": OPENAI_API_KEY
|
||||||
|
}],
|
||||||
|
"assistant_id": OPENAI_ASSISTANT_ID
|
||||||
|
}
|
||||||
|
)`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rest of the display code generation logic remains the same
|
||||||
|
const generateSpark = (node: Node) => {
|
||||||
|
const data = node.data;
|
||||||
|
return data.func || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateInitiateChat = (sourceNode: Node, targetNode: Node) => {
|
||||||
|
return `${sourceNode.data.variableName}.initiate_chat(${targetNode.data.variableName}_manager, message="${sourceNode.data.initialPrompt}")`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Combine all code blocks
|
||||||
|
let code = generateImports();
|
||||||
|
const codeBlocks: string[] = [];
|
||||||
|
const initiateChatBlocks: string[] = [];
|
||||||
|
|
||||||
|
// Generate Spark functions first
|
||||||
|
nodes.forEach(node => {
|
||||||
|
if (node.type === DotbaseNodesEnum.SPARK) {
|
||||||
|
const funcCode = generateSpark(node);
|
||||||
|
if (funcCode) {
|
||||||
|
codeBlocks.push(funcCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate agent code
|
||||||
|
nodes.forEach(node => {
|
||||||
|
let agentCode = '';
|
||||||
|
switch (node.type) {
|
||||||
|
case DotbaseNodesEnum.BRIDGE:
|
||||||
|
agentCode = generateBridgeForDisplay(node);
|
||||||
|
break;
|
||||||
|
case DotbaseNodesEnum.NEXUS:
|
||||||
|
agentCode = generateNexusForDisplay(node);
|
||||||
|
break;
|
||||||
|
case DotbaseNodesEnum.LUMINA:
|
||||||
|
agentCode = generateLuminaForDisplay(node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (agentCode) {
|
||||||
|
codeBlocks.push(agentCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate Hub code
|
||||||
|
nodes.forEach(node => {
|
||||||
|
if (node.type === DotbaseNodesEnum.HUB) {
|
||||||
|
const connectedAgents = edges
|
||||||
|
.filter(edge => edge.target === node.id)
|
||||||
|
.map(edge => {
|
||||||
|
const sourceNode = nodes.find(n => n.id === edge.source);
|
||||||
|
return sourceNode?.data.variableName;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (connectedAgents.length > 0) {
|
||||||
|
codeBlocks.push(generateHubForDisplay(node, connectedAgents));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate chat initiation code
|
||||||
|
edges.forEach(edge => {
|
||||||
|
const sourceNode = nodes.find(node => node.id === edge.source);
|
||||||
|
const targetNode = nodes.find(node => node.id === edge.target);
|
||||||
|
|
||||||
|
if (sourceNode?.type === DotbaseNodesEnum.BRIDGE &&
|
||||||
|
targetNode?.type === DotbaseNodesEnum.HUB) {
|
||||||
|
initiateChatBlocks.push(generateInitiateChat(sourceNode, targetNode));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return code + codeBlocks.join('\n\n') + '\n\n' + initiateChatBlocks.join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
export { generateDisplayCode, generateExecutionCode };
|
24
utils/nicknames.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export const NICKNAMES = [
|
||||||
|
'ECHO',
|
||||||
|
'SAGE',
|
||||||
|
'SKY',
|
||||||
|
'WAVE',
|
||||||
|
'DUSK',
|
||||||
|
'DAWN',
|
||||||
|
'MIST',
|
||||||
|
'SNOW',
|
||||||
|
'RAIN',
|
||||||
|
'ASH',
|
||||||
|
'STAR',
|
||||||
|
'LEAF',
|
||||||
|
'ZEN',
|
||||||
|
'AURA',
|
||||||
|
'REEF',
|
||||||
|
'VALE',
|
||||||
|
'MOON',
|
||||||
|
'PINE',
|
||||||
|
'EAST',
|
||||||
|
'PEAK',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type Nickname = typeof NICKNAMES[number];
|
6
utils/types.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export type PositionType = { top?: number; right?: number; bottom?: number; left?: number };
|
||||||
|
|
||||||
|
export type ContextMenuItemType = React.HTMLProps<HTMLDivElement> & {
|
||||||
|
item: React.JSX.Element | string;
|
||||||
|
subs?: ContextMenuItemType[];
|
||||||
|
};
|
63
utils/zod.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { AgentSelectionStrategyEnum, OAIModelsEnum } from '@/utils/enum';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const VARIABLE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
||||||
|
const VariableName = z
|
||||||
|
.string({
|
||||||
|
required_error:
|
||||||
|
'Variable name must start with a letter or underscore, and can only contain letters, numbers, and underscores.',
|
||||||
|
})
|
||||||
|
.regex(VARIABLE_NAME_REGEX, {
|
||||||
|
message:
|
||||||
|
'Variable name must start with a letter or underscore, and can only contain letters, numbers, and underscores.',
|
||||||
|
});
|
||||||
|
|
||||||
|
const LLMEnum = z.nativeEnum(OAIModelsEnum);
|
||||||
|
const AgentSelectionEnum = z.nativeEnum(AgentSelectionStrategyEnum);
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
const UserProxy = z.object({
|
||||||
|
variableName: VariableName,
|
||||||
|
initialPrompt: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Initial Prompt is required. Your workforce will take this prompt to start the conversation.',
|
||||||
|
})
|
||||||
|
.min(1, { message: 'Initial Prompt is required. Your workforce will take this prompt to start the conversation.' }),
|
||||||
|
});
|
||||||
|
const Hub = z.object({
|
||||||
|
variableName: VariableName,
|
||||||
|
maxRounds: z.number().optional(),
|
||||||
|
agentSelection: AgentSelectionEnum.default(AgentSelectionStrategyEnum.AUTO),
|
||||||
|
});
|
||||||
|
const GPTAssistantAgent = z.object({
|
||||||
|
variableName: VariableName,
|
||||||
|
OAIId: z
|
||||||
|
.string({ required_error: 'The OpenAI Id of the assistant agent is required.' })
|
||||||
|
.min(1, { message: 'The OpenAI Id of the assistant agent is required.' }),
|
||||||
|
});
|
||||||
|
const AssistantAgent = z.object({
|
||||||
|
variableName: VariableName,
|
||||||
|
systemMessage: z.string().optional(),
|
||||||
|
});
|
||||||
|
const CustomFunction = z.object({
|
||||||
|
func: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const LLMOpenAI = z.object({
|
||||||
|
model: LLMEnum.default(OAIModelsEnum.GPT_3_5_TURBO),
|
||||||
|
apiKey: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DnDFlowValidationSchema = z.array(
|
||||||
|
z.object({
|
||||||
|
BRIDGE: z.optional(UserProxy),
|
||||||
|
HUB: z.optional(Hub),
|
||||||
|
LUMINA: z.optional(GPTAssistantAgent),
|
||||||
|
NEXUS: z.optional(AssistantAgent),
|
||||||
|
SPARK: z.optional(CustomFunction),
|
||||||
|
LLM_OPENAI: z.optional(LLMOpenAI),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export type DnDFlowValidationSchemaType = z.infer<typeof DnDFlowValidationSchema>;
|