Integrate Bzzz P2P task coordination and enhance project management

🔗 Bzzz Integration:
- Added comprehensive Bzzz integration documentation and todos
- Implemented N8N chat workflow architecture for task coordination
- Enhanced project management with Bzzz-specific features
- Added GitHub service for seamless issue synchronization
- Created BzzzIntegration component for frontend management

🎯 Project Management Enhancements:
- Improved project listing and filtering capabilities
- Enhanced authentication and authorization flows
- Added unified coordinator for better task orchestration
- Streamlined project activation and configuration
- Updated API endpoints for Bzzz compatibility

📊 Technical Improvements:
- Updated Docker Swarm configuration for local registry
- Enhanced frontend build with updated assets
- Improved WebSocket connections for real-time updates
- Added comprehensive error handling and logging
- Updated environment configurations for production

 System Integration:
- Successfully tested with Bzzz v1.2 task execution workflow
- Validated GitHub issue discovery and claiming functionality
- Confirmed sandbox-based task execution compatibility
- Verified Docker registry integration

This release enables seamless integration between Hive project management and Bzzz P2P task coordination, creating a complete distributed development ecosystem.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-07-14 20:56:01 +10:00
parent e89f2f4b7b
commit 3f3eec7f5d
38 changed files with 2591 additions and 932 deletions

View File

@@ -21,156 +21,167 @@ import WorkflowDashboard from './components/workflows/WorkflowDashboard'
import ClusterNodes from './components/cluster/ClusterNodes'
function App() {
// Check for connection issues and provide fallback
const socketIOEnabled = import.meta.env.VITE_DISABLE_SOCKETIO !== 'true';
const AppContent = () => (
<Routes>
{/* Public routes */}
<Route path="/login" element={<Login />} />
{/* Protected routes */}
<Route path="/" element={
<ProtectedRoute>
<Layout>
<Dashboard />
</Layout>
</ProtectedRoute>
} />
{/* Projects */}
<Route path="/projects" element={
<ProtectedRoute>
<Layout>
<ProjectList />
</Layout>
</ProtectedRoute>
} />
<Route path="/projects/new" element={
<ProtectedRoute>
<Layout>
<ProjectForm mode="create" />
</Layout>
</ProtectedRoute>
} />
<Route path="/projects/:id" element={
<ProtectedRoute>
<Layout>
<ProjectDetail />
</Layout>
</ProtectedRoute>
} />
<Route path="/projects/:id/edit" element={
<ProtectedRoute>
<Layout>
<ProjectForm mode="edit" />
</Layout>
</ProtectedRoute>
} />
{/* Workflows */}
<Route path="/workflows" element={
<ProtectedRoute>
<Layout>
<WorkflowDashboard />
</Layout>
</ProtectedRoute>
} />
<Route path="/workflows/new" element={
<ProtectedRoute>
<Layout>
<WorkflowEditor />
</Layout>
</ProtectedRoute>
} />
<Route path="/workflows/:id" element={
<ProtectedRoute>
<Layout>
<WorkflowEditor />
</Layout>
</ProtectedRoute>
} />
<Route path="/workflows/:id/edit" element={
<ProtectedRoute>
<Layout>
<WorkflowEditor />
</Layout>
</ProtectedRoute>
} />
<Route path="/workflows/templates" element={
<ProtectedRoute>
<Layout>
<WorkflowTemplates />
</Layout>
</ProtectedRoute>
} />
{/* Cluster */}
<Route path="/cluster" element={
<ProtectedRoute>
<Layout>
<ClusterNodes />
</Layout>
</ProtectedRoute>
} />
<Route path="/cluster/nodes" element={
<ProtectedRoute>
<Layout>
<ClusterNodes />
</Layout>
</ProtectedRoute>
} />
{/* Agents */}
<Route path="/agents" element={
<ProtectedRoute>
<Layout>
<Agents />
</Layout>
</ProtectedRoute>
} />
{/* Executions */}
<Route path="/executions" element={
<ProtectedRoute>
<Layout>
<Executions />
</Layout>
</ProtectedRoute>
} />
{/* Analytics */}
<Route path="/analytics" element={
<ProtectedRoute>
<Layout>
<Analytics />
</Layout>
</ProtectedRoute>
} />
{/* User Profile */}
<Route path="/profile" element={
<ProtectedRoute>
<Layout>
<UserProfile />
</Layout>
</ProtectedRoute>
} />
{/* Settings */}
<Route path="/settings" element={
<ProtectedRoute>
<Layout>
<Settings />
</Layout>
</ProtectedRoute>
} />
{/* Redirect unknown routes to dashboard */}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
);
return (
<Router>
<AuthProvider>
<ReactFlowProvider>
<SocketIOProvider>
<Routes>
{/* Public routes */}
<Route path="/login" element={<Login />} />
{/* Protected routes */}
<Route path="/" element={
<ProtectedRoute>
<Layout>
<Dashboard />
</Layout>
</ProtectedRoute>
} />
{/* Projects */}
<Route path="/projects" element={
<ProtectedRoute>
<Layout>
<ProjectList />
</Layout>
</ProtectedRoute>
} />
<Route path="/projects/new" element={
<ProtectedRoute>
<Layout>
<ProjectForm mode="create" />
</Layout>
</ProtectedRoute>
} />
<Route path="/projects/:id" element={
<ProtectedRoute>
<Layout>
<ProjectDetail />
</Layout>
</ProtectedRoute>
} />
<Route path="/projects/:id/edit" element={
<ProtectedRoute>
<Layout>
<ProjectForm mode="edit" />
</Layout>
</ProtectedRoute>
} />
{/* Workflows */}
<Route path="/workflows" element={
<ProtectedRoute>
<Layout>
<WorkflowDashboard />
</Layout>
</ProtectedRoute>
} />
<Route path="/workflows/new" element={
<ProtectedRoute>
<Layout>
<WorkflowEditor />
</Layout>
</ProtectedRoute>
} />
<Route path="/workflows/:id" element={
<ProtectedRoute>
<Layout>
<WorkflowEditor />
</Layout>
</ProtectedRoute>
} />
<Route path="/workflows/:id/edit" element={
<ProtectedRoute>
<Layout>
<WorkflowEditor />
</Layout>
</ProtectedRoute>
} />
<Route path="/workflows/templates" element={
<ProtectedRoute>
<Layout>
<WorkflowTemplates />
</Layout>
</ProtectedRoute>
} />
{/* Cluster */}
<Route path="/cluster" element={
<ProtectedRoute>
<Layout>
<ClusterNodes />
</Layout>
</ProtectedRoute>
} />
<Route path="/cluster/nodes" element={
<ProtectedRoute>
<Layout>
<ClusterNodes />
</Layout>
</ProtectedRoute>
} />
{/* Agents */}
<Route path="/agents" element={
<ProtectedRoute>
<Layout>
<Agents />
</Layout>
</ProtectedRoute>
} />
{/* Executions */}
<Route path="/executions" element={
<ProtectedRoute>
<Layout>
<Executions />
</Layout>
</ProtectedRoute>
} />
{/* Analytics */}
<Route path="/analytics" element={
<ProtectedRoute>
<Layout>
<Analytics />
</Layout>
</ProtectedRoute>
} />
{/* User Profile */}
<Route path="/profile" element={
<ProtectedRoute>
<Layout>
<UserProfile />
</Layout>
</ProtectedRoute>
} />
{/* Settings */}
<Route path="/settings" element={
<ProtectedRoute>
<Layout>
<Settings />
</Layout>
</ProtectedRoute>
} />
{/* Redirect unknown routes to dashboard */}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</SocketIOProvider>
{socketIOEnabled ? (
<SocketIOProvider>
<AppContent />
</SocketIOProvider>
) : (
<AppContent />
)}
</ReactFlowProvider>
</AuthProvider>
</Router>

View File

@@ -117,7 +117,7 @@ export interface APIError {
// Unified API configuration
export const API_CONFIG = {
BASE_URL: process.env.VITE_API_BASE_URL || 'http://localhost:8087',
BASE_URL: process.env.VITE_API_BASE_URL || 'https://hive.home.deepblack.cloud',
TIMEOUT: 30000,
RETRY_ATTEMPTS: 3,
RETRY_DELAY: 1000,

View File

@@ -87,7 +87,7 @@ export class WebSocketService {
return;
}
const baseURL = process.env.REACT_APP_SOCKETIO_URL || 'https://hive.home.deepblack.cloud';
const baseURL = import.meta.env.VITE_WS_BASE_URL || 'https://hive.home.deepblack.cloud';
this.socket = io(baseURL, {
auth: {

View File

@@ -0,0 +1,264 @@
import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
InformationCircleIcon,
ExclamationTriangleIcon,
CheckCircleIcon,
XMarkIcon,
EyeIcon,
LinkIcon
} from '@heroicons/react/24/outline';
import toast from 'react-hot-toast';
import { BzzzTask, BzzzRepository, Project } from '../../types/project';
interface BzzzIntegrationProps {
project: Project;
}
export default function BzzzIntegration({ project }: BzzzIntegrationProps) {
const queryClient = useQueryClient();
const [showAllTasks, setShowAllTasks] = useState(false);
// Fetch Bzzz tasks for this project
const { data: bzzzTasks = [], isLoading: tasksLoading } = useQuery({
queryKey: ['bzzz-tasks', project.id],
queryFn: async (): Promise<BzzzTask[]> => {
const response = await fetch(`/api/bzzz/projects/${project.id}/tasks`);
if (!response.ok) throw new Error('Failed to fetch Bzzz tasks');
return response.json();
},
enabled: !!project.bzzz_config?.bzzz_enabled
});
// Fetch active repositories to check if this project is discoverable
const { data: activeRepos = [] } = useQuery({
queryKey: ['bzzz-active-repos'],
queryFn: async (): Promise<{ repositories: BzzzRepository[] }> => {
const response = await fetch('/api/bzzz/active-repos');
if (!response.ok) throw new Error('Failed to fetch active repositories');
return response.json();
}
});
// Toggle project activation for Bzzz
const toggleActivationMutation = useMutation({
mutationFn: async (ready: boolean) => {
const response = await fetch(`/api/projects/${project.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
bzzz_config: {
...project.bzzz_config,
ready_to_claim: ready
}
})
});
if (!response.ok) throw new Error('Failed to update project');
return response.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['project', project.id] });
queryClient.invalidateQueries({ queryKey: ['bzzz-active-repos'] });
toast.success('Project Bzzz status updated!');
},
onError: () => {
toast.error('Failed to update project status');
}
});
if (!project.bzzz_config?.bzzz_enabled) {
return (
<div className="bg-gray-50 rounded-lg p-6">
<div className="text-center">
<InformationCircleIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">Bzzz Integration Disabled</h3>
<p className="text-gray-500 mb-4">
This project is not configured for Bzzz P2P task coordination.
</p>
<p className="text-sm text-gray-400">
Enable Bzzz integration in project settings to allow distributed AI agents to discover and work on tasks.
</p>
</div>
</div>
);
}
const isDiscoverable = activeRepos.repositories.some(repo => repo.name === project.name);
const readyToClaim = project.bzzz_config?.ready_to_claim || false;
const hasGitConfig = project.bzzz_config?.git_url;
const displayTasks = showAllTasks ? bzzzTasks : bzzzTasks.slice(0, 5);
return (
<div className="space-y-6">
{/* Status Overview */}
<div className="bg-white rounded-lg border p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-medium text-gray-900">🐝 Bzzz Integration Status</h2>
{isDiscoverable ? (
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
<CheckCircleIcon className="h-4 w-4 mr-1" />
Discoverable
</span>
) : (
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-yellow-100 text-yellow-800">
<ExclamationTriangleIcon className="h-4 w-4 mr-1" />
Not Discoverable
</span>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Git Repository */}
<div className="text-center p-4 bg-gray-50 rounded-lg">
<LinkIcon className="h-8 w-8 text-gray-400 mx-auto mb-2" />
<p className="text-sm font-medium text-gray-900">Git Repository</p>
<p className="text-xs text-gray-500 mt-1">
{hasGitConfig ? (
<a
href={project.bzzz_config.git_url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800"
>
{project.bzzz_config.git_owner}/{project.bzzz_config.git_repository}
</a>
) : (
'Not configured'
)}
</p>
</div>
{/* Available Tasks */}
<div className="text-center p-4 bg-gray-50 rounded-lg">
<div className="text-2xl font-bold text-gray-900">{bzzzTasks.length}</div>
<p className="text-sm font-medium text-gray-900">Available Tasks</p>
<p className="text-xs text-gray-500">With bzzz-task label</p>
</div>
{/* Claim Status */}
<div className="text-center p-4 bg-gray-50 rounded-lg">
<div className="flex items-center justify-center mb-2">
{readyToClaim ? (
<CheckCircleIcon className="h-8 w-8 text-green-500" />
) : (
<XMarkIcon className="h-8 w-8 text-red-500" />
)}
</div>
<p className="text-sm font-medium text-gray-900">Ready to Claim</p>
<button
onClick={() => toggleActivationMutation.mutate(!readyToClaim)}
disabled={toggleActivationMutation.isPending}
className={`text-xs px-2 py-1 rounded mt-1 ${
readyToClaim
? 'bg-red-100 text-red-700 hover:bg-red-200'
: 'bg-green-100 text-green-700 hover:bg-green-200'
}`}
>
{toggleActivationMutation.isPending
? 'Updating...'
: (readyToClaim ? 'Deactivate' : 'Activate')
}
</button>
</div>
</div>
</div>
{/* GitHub Tasks */}
{hasGitConfig && (
<div className="bg-white rounded-lg border">
<div className="px-6 py-4 border-b border-gray-200">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-gray-900">GitHub Issues (bzzz-task)</h3>
{bzzzTasks.length > 5 && (
<button
onClick={() => setShowAllTasks(!showAllTasks)}
className="text-sm text-blue-600 hover:text-blue-800"
>
<EyeIcon className="h-4 w-4 inline mr-1" />
{showAllTasks ? 'Show Less' : `Show All (${bzzzTasks.length})`}
</button>
)}
</div>
</div>
<div className="divide-y divide-gray-200">
{tasksLoading ? (
<div className="p-6 text-center text-gray-500">Loading tasks...</div>
) : displayTasks.length === 0 ? (
<div className="p-6 text-center">
<p className="text-gray-500">No issues found with 'bzzz-task' label.</p>
<p className="text-sm text-gray-400 mt-1">
Create GitHub issues and add the 'bzzz-task' label for agents to discover them.
</p>
</div>
) : (
displayTasks.map((task) => (
<div key={task.number} className="p-6">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-2">
<h4 className="text-sm font-medium text-gray-900">
#{task.number}: {task.title}
</h4>
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
task.is_claimed
? 'bg-blue-100 text-blue-800'
: 'bg-green-100 text-green-800'
}`}>
{task.is_claimed ? 'Claimed' : 'Available'}
</span>
<span className="inline-flex items-center px-2 py-1 rounded text-xs bg-gray-100 text-gray-600">
{task.task_type}
</span>
</div>
<p className="text-sm text-gray-600 mb-2 line-clamp-2">
{task.description || 'No description provided.'}
</p>
<div className="flex items-center space-x-4 text-xs text-gray-500">
<span>State: {task.state}</span>
{task.assignees.length > 0 && (
<span>Assigned to: {task.assignees.join(', ')}</span>
)}
<span>Labels: {task.labels.join(', ')}</span>
</div>
</div>
<a
href={task.html_url}
target="_blank"
rel="noopener noreferrer"
className="text-sm text-blue-600 hover:text-blue-800 ml-4"
>
View on GitHub
</a>
</div>
</div>
))
)}
</div>
</div>
)}
{/* Integration Help */}
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<div className="flex">
<InformationCircleIcon className="h-5 w-5 text-yellow-400" />
<div className="ml-3">
<h3 className="text-sm font-medium text-yellow-800">How to Use Bzzz Integration</h3>
<div className="mt-2 text-sm text-yellow-700">
<ol className="list-decimal list-inside space-y-1">
<li>Ensure your GitHub repository has issues labeled with 'bzzz-task'</li>
<li>Activate the project using the "Ready to Claim" toggle above</li>
<li>Bzzz agents will discover and coordinate to work on available tasks</li>
<li>Monitor progress through GitHub issue updates and agent coordination</li>
</ol>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -20,6 +20,16 @@ const projectSchema = z.object({
owner: z.string().optional(),
department: z.string().optional(),
priority: z.enum(['low', 'medium', 'high']).optional()
}).optional(),
bzzz_config: z.object({
git_url: z.string().url('Must be a valid Git URL').optional().or(z.literal('')),
git_owner: z.string().optional(),
git_repository: z.string().optional(),
git_branch: z.string().optional(),
bzzz_enabled: z.boolean().optional(),
ready_to_claim: z.boolean().optional(),
private_repo: z.boolean().optional(),
github_token_required: z.boolean().optional()
}).optional()
});
@@ -52,11 +62,46 @@ export default function ProjectForm({ mode, initialData, projectId }: ProjectFor
owner: initialData?.metadata?.owner || '',
department: initialData?.metadata?.department || '',
priority: initialData?.metadata?.priority || 'medium'
},
bzzz_config: {
git_url: initialData?.bzzz_config?.git_url || '',
git_owner: initialData?.bzzz_config?.git_owner || '',
git_repository: initialData?.bzzz_config?.git_repository || '',
git_branch: initialData?.bzzz_config?.git_branch || 'main',
bzzz_enabled: initialData?.bzzz_config?.bzzz_enabled || false,
ready_to_claim: initialData?.bzzz_config?.ready_to_claim || false,
private_repo: initialData?.bzzz_config?.private_repo || false,
github_token_required: initialData?.bzzz_config?.github_token_required || false
}
}
});
const currentTags = watch('tags') || [];
const gitUrl = watch('bzzz_config.git_url') || '';
const bzzzEnabled = watch('bzzz_config.bzzz_enabled') || false;
// Auto-parse Git URL to extract owner and repository
const parseGitUrl = (url: string) => {
if (!url) return;
try {
// Handle GitHub URLs like https://github.com/owner/repo or git@github.com:owner/repo.git
const githubMatch = url.match(/github\.com[/:]([\w-]+)\/([\w-]+)(?:\.git)?$/);
if (githubMatch) {
const [, owner, repo] = githubMatch;
setValue('bzzz_config.git_owner', owner);
setValue('bzzz_config.git_repository', repo);
}
} catch (error) {
console.log('Could not parse Git URL:', error);
}
};
// Watch for Git URL changes and auto-parse
const handleGitUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const url = e.target.value;
parseGitUrl(url);
};
const createProjectMutation = useMutation({
mutationFn: async (data: ProjectFormData) => {
@@ -314,6 +359,181 @@ export default function ProjectForm({ mode, initialData, projectId }: ProjectFor
</div>
</div>
{/* Bzzz Integration Configuration */}
<div className="bg-white shadow-sm rounded-lg">
<div className="px-6 py-4 border-b border-gray-200">
<div className="flex items-center space-x-2">
<h2 className="text-lg font-medium text-gray-900">🐝 Bzzz P2P Integration</h2>
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
Beta
</span>
</div>
<p className="text-sm text-gray-500 mt-1">
Configure this project for distributed AI task coordination via the Bzzz P2P network.
</p>
</div>
<div className="px-6 py-4 space-y-6">
{/* Enable Bzzz Integration */}
<div>
<div className="flex items-center space-x-3">
<input
type="checkbox"
id="bzzz_enabled"
{...register('bzzz_config.bzzz_enabled')}
className="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<label htmlFor="bzzz_enabled" className="text-sm font-medium text-gray-700">
Enable Bzzz P2P coordination for this project
</label>
</div>
<p className="text-sm text-gray-500 mt-1 ml-7">
Allow Bzzz agents to discover and work on tasks from this project's GitHub repository.
</p>
</div>
{/* Git Repository Configuration - Only show if Bzzz is enabled */}
{bzzzEnabled && (
<>
{/* Git Repository URL */}
<div>
<label htmlFor="git_url" className="block text-sm font-medium text-gray-700 mb-2">
Git Repository URL *
</label>
<input
type="url"
id="git_url"
{...register('bzzz_config.git_url')}
onChange={handleGitUrlChange}
className="block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="https://github.com/owner/repository"
/>
<p className="mt-1 text-sm text-gray-500">
GitHub repository URL where Bzzz will look for issues labeled with 'bzzz-task'.
</p>
{errors.bzzz_config?.git_url && (
<p className="mt-1 text-sm text-red-600">{errors.bzzz_config.git_url.message}</p>
)}
</div>
{/* Auto-parsed Git Info */}
<div className="grid grid-cols-2 gap-4">
<div>
<label htmlFor="git_owner" className="block text-sm font-medium text-gray-700 mb-2">
Repository Owner
</label>
<input
type="text"
id="git_owner"
{...register('bzzz_config.git_owner')}
className="block w-full border border-gray-300 rounded-md px-3 py-2 bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Auto-detected from URL"
readOnly
/>
</div>
<div>
<label htmlFor="git_repository" className="block text-sm font-medium text-gray-700 mb-2">
Repository Name
</label>
<input
type="text"
id="git_repository"
{...register('bzzz_config.git_repository')}
className="block w-full border border-gray-300 rounded-md px-3 py-2 bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Auto-detected from URL"
readOnly
/>
</div>
</div>
{/* Git Branch */}
<div>
<label htmlFor="git_branch" className="block text-sm font-medium text-gray-700 mb-2">
Default Branch
</label>
<input
type="text"
id="git_branch"
{...register('bzzz_config.git_branch')}
className="block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="main"
/>
</div>
{/* Repository Configuration */}
<div className="space-y-3">
<h3 className="text-sm font-medium text-gray-700">Repository Configuration</h3>
<div className="space-y-2">
{/* Ready to Claim */}
<div className="flex items-center space-x-3">
<input
type="checkbox"
id="ready_to_claim"
{...register('bzzz_config.ready_to_claim')}
className="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<label htmlFor="ready_to_claim" className="text-sm text-gray-700">
Ready for task claims (agents can start working immediately)
</label>
</div>
{/* Private Repository */}
<div className="flex items-center space-x-3">
<input
type="checkbox"
id="private_repo"
{...register('bzzz_config.private_repo')}
className="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<label htmlFor="private_repo" className="text-sm text-gray-700">
Private repository (requires authentication)
</label>
</div>
{/* GitHub Token Required */}
<div className="flex items-center space-x-3">
<input
type="checkbox"
id="github_token_required"
{...register('bzzz_config.github_token_required')}
className="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<label htmlFor="github_token_required" className="text-sm text-gray-700">
Requires GitHub token for API access
</label>
</div>
</div>
</div>
{/* Bzzz Integration Info */}
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<div className="flex">
<InformationCircleIcon className="h-5 w-5 text-yellow-400" />
<div className="ml-3">
<h3 className="text-sm font-medium text-yellow-800">
How Bzzz Integration Works
</h3>
<div className="mt-2 text-sm text-yellow-700">
<p>When enabled, Bzzz agents will:</p>
<ul className="list-disc list-inside mt-1 space-y-1">
<li>Monitor GitHub issues labeled with 'bzzz-task'</li>
<li>Coordinate P2P to assign tasks based on agent capabilities</li>
<li>Execute tasks using distributed AI reasoning</li>
<li>Report progress and escalate when needed</li>
</ul>
<p className="mt-2 font-medium">
Make sure your repository has issues labeled with 'bzzz-task' for agents to discover.
</p>
</div>
</div>
</div>
</div>
</>
)}
</div>
</div>
{/* Help Text */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div className="flex">

View File

@@ -22,6 +22,7 @@ import { projectApi } from '../../services/api';
export default function ProjectList() {
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'inactive' | 'archived'>('all');
const [bzzzFilter, setBzzzFilter] = useState<'all' | 'enabled' | 'disabled'>('all');
// Fetch real projects from API
const { data: projects = [], isLoading, error } = useQuery({
@@ -35,7 +36,13 @@ export default function ProjectList() {
const matchesSearch = project.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
project.description?.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus = statusFilter === 'all' || project.status === statusFilter;
return matchesSearch && matchesStatus;
const bzzzEnabled = (project as any).bzzz_config?.bzzz_enabled || false;
const matchesBzzz = bzzzFilter === 'all' ||
(bzzzFilter === 'enabled' && bzzzEnabled) ||
(bzzzFilter === 'disabled' && !bzzzEnabled);
return matchesSearch && matchesStatus && matchesBzzz;
});
const getStatusBadge = (status: string) => {
@@ -134,6 +141,19 @@ export default function ProjectList() {
<option value="archived">Archived</option>
</select>
</div>
<div className="flex items-center space-x-2">
<span className="text-sm text-gray-500">🐝</span>
<select
value={bzzzFilter}
onChange={(e) => setBzzzFilter(e.target.value as any)}
className="border border-gray-300 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
>
<option value="all">All Projects</option>
<option value="enabled">Bzzz Enabled</option>
<option value="disabled">Bzzz Disabled</option>
</select>
</div>
</div>
</div>
@@ -213,6 +233,16 @@ export default function ProjectList() {
</Link>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<Link
to={`/projects/${project.id}/bzzz`}
className={`${active ? 'bg-gray-100' : ''} block px-4 py-2 text-sm text-gray-700`}
>
🐝 Bzzz Integration
</Link>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button
@@ -233,9 +263,20 @@ export default function ProjectList() {
{/* Status and Tags */}
<div className="flex items-center justify-between mt-4">
<span className={getStatusBadge(project.status)}>
{project.status}
</span>
<div className="flex items-center space-x-2">
<span className={getStatusBadge(project.status)}>
{project.status}
</span>
{/* Bzzz Integration Status */}
{(project as any).bzzz_config?.bzzz_enabled && (
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
🐝 Bzzz
{(project as any).bzzz_config?.ready_to_claim && (
<span className="ml-1 inline-block w-2 h-2 bg-green-400 rounded-full"></span>
)}
</span>
)}
</div>
<div className="flex items-center space-x-1">
{project.tags?.slice(0, 2).map((tag) => (
<span key={tag} className="inline-flex items-center px-2 py-1 rounded text-xs bg-gray-100 text-gray-600">
@@ -248,6 +289,21 @@ export default function ProjectList() {
)}
</div>
</div>
{/* GitHub Repository Info for Bzzz-enabled projects */}
{(project as any).bzzz_config?.bzzz_enabled && (project as any).bzzz_config?.git_url && (
<div className="mt-3 text-xs text-gray-500">
<div className="flex items-center space-x-1">
<svg className="h-3 w-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z" clipRule="evenodd" />
</svg>
<span>{(project as any).bzzz_config.git_owner}/{(project as any).bzzz_config.git_repository}</span>
{(project as any).bzzz_config.ready_to_claim && (
<span className="text-green-600"> Ready for tasks</span>
)}
</div>
</div>
)}
</div>
{/* Metrics */}

View File

@@ -45,7 +45,7 @@ interface AuthProviderProps {
children: ReactNode;
}
const API_BASE_URL = process.env.REACT_APP_API_URL || '/api';
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL + '/api' || '/api';
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);

View File

@@ -21,8 +21,30 @@ interface SocketIOProviderProps {
export const SocketIOProvider: React.FC<SocketIOProviderProps> = ({
children,
url = process.env.REACT_APP_SOCKETIO_URL || 'https://hive.home.deepblack.cloud'
url = import.meta.env.VITE_WS_BASE_URL || 'https://hive.home.deepblack.cloud'
}) => {
// Allow disabling SocketIO completely via environment variable
const socketIODisabled = import.meta.env.VITE_DISABLE_SOCKETIO === 'true';
if (socketIODisabled) {
console.log('Socket.IO disabled via environment variable');
const contextValue: SocketIOContextType = {
isConnected: false,
connectionState: 'disconnected',
sendMessage: () => console.warn('Socket.IO is disabled'),
joinRoom: () => console.warn('Socket.IO is disabled'),
leaveRoom: () => console.warn('Socket.IO is disabled'),
lastMessage: null,
subscribe: () => () => {},
reconnect: () => console.warn('Socket.IO is disabled')
};
return (
<SocketIOContext.Provider value={contextValue}>
{children}
</SocketIOContext.Provider>
);
}
const [subscriptions, setSubscriptions] = useState<Map<string, Set<(data: any) => void>>>(new Map());
const {
@@ -50,7 +72,7 @@ export const SocketIOProvider: React.FC<SocketIOProviderProps> = ({
}
},
onConnect: () => {
console.log('Socket.IO connected to Hive backend');
console.log('Socket.IO connected to Hive backend');
// Join general room and subscribe to common events
if (socket) {
@@ -62,10 +84,11 @@ export const SocketIOProvider: React.FC<SocketIOProviderProps> = ({
}
},
onDisconnect: () => {
console.log('Socket.IO disconnected from Hive backend');
console.log('🔌 Socket.IO disconnected from Hive backend');
},
onError: (error) => {
console.error('Socket.IO error:', error);
// Errors are already logged in the hook, don't duplicate
// console.error('Socket.IO error:', error);
}
});

View File

@@ -19,7 +19,7 @@ interface WebSocketProviderProps {
export const WebSocketProvider: React.FC<WebSocketProviderProps> = ({
children,
url = process.env.REACT_APP_WS_URL || 'wss://hive.home.deepblack.cloud/socket.io/general'
url = import.meta.env.VITE_WS_BASE_URL || 'wss://hive.home.deepblack.cloud'
}) => {
const [subscriptions, setSubscriptions] = useState<Map<string, Set<(data: any) => void>>>(new Map());

View File

@@ -36,8 +36,8 @@ export const useSocketIO = (options: SocketIOHookOptions): SocketIOHookReturn =>
const {
url,
autoConnect = true,
reconnectionAttempts = 5,
reconnectionDelay = 1000,
reconnectionAttempts = 3,
reconnectionDelay = 5000,
onMessage,
onConnect,
onDisconnect,
@@ -70,7 +70,8 @@ export const useSocketIO = (options: SocketIOHookOptions): SocketIOHookReturn =>
reconnectionAttempts,
reconnectionDelay,
timeout: 20000,
forceNew: false
forceNew: false,
path: '/socket.io/'
});
socketInstance.on('connect', () => {
@@ -89,15 +90,17 @@ export const useSocketIO = (options: SocketIOHookOptions): SocketIOHookReturn =>
});
socketInstance.on('connect_error', (error) => {
console.error('Socket.IO connection error:', error);
console.warn('Socket.IO connection error (backend may be offline):', error.message);
setConnectionState('error');
onError?.(error);
// Don't call onError for connection errors to reduce noise
// onError?.(error);
});
socketInstance.on('reconnect_error', (error) => {
console.error('Socket.IO reconnection error:', error);
console.warn('Socket.IO reconnection error (backend may be offline):', error.message);
setConnectionState('error');
onError?.(error);
// Don't call onError for reconnection errors to reduce noise
// onError?.(error);
});
socketInstance.on('reconnect', (attemptNumber) => {
@@ -109,9 +112,10 @@ export const useSocketIO = (options: SocketIOHookOptions): SocketIOHookReturn =>
});
socketInstance.on('reconnect_failed', () => {
console.error('Socket.IO reconnection failed');
console.warn('Socket.IO reconnection failed (backend may be offline)');
setConnectionState('error');
onError?.(new Error('Reconnection failed'));
// Don't call onError for reconnection failures to reduce noise
// onError?.(new Error('Reconnection failed'));
});
// Listen for connection confirmation

View File

@@ -4,7 +4,7 @@ import { Workflow, WorkflowExecution } from '../types/workflow';
// Create axios instance with base configuration
const api = axios.create({
baseURL: process.env.VITE_API_BASE_URL || 'http://localhost:8087',
baseURL: process.env.VITE_API_BASE_URL || 'https://hive.home.deepblack.cloud',
headers: {
'Content-Type': 'application/json',
},

View File

@@ -1,3 +1,14 @@
export interface BzzzConfig {
git_url?: string;
git_owner?: string;
git_repository?: string;
git_branch?: string;
bzzz_enabled?: boolean;
ready_to_claim?: boolean;
private_repo?: boolean;
github_token_required?: boolean;
}
export interface Project {
id: string;
name: string;
@@ -8,6 +19,14 @@ export interface Project {
metadata?: Record<string, any>;
workflows?: string[]; // workflow IDs
tags?: string[];
bzzz_config?: BzzzConfig;
// Additional fields from filesystem analysis
github_repo?: string;
workflow_count?: number;
file_count?: number;
has_project_plan?: boolean;
has_todos?: boolean;
}
export interface ProjectWorkflow {
@@ -33,6 +52,7 @@ export interface CreateProjectRequest {
description?: string;
tags?: string[];
metadata?: Record<string, any>;
bzzz_config?: BzzzConfig;
}
export interface UpdateProjectRequest {
@@ -41,4 +61,32 @@ export interface UpdateProjectRequest {
status?: 'active' | 'inactive' | 'archived';
tags?: string[];
metadata?: Record<string, any>;
bzzz_config?: BzzzConfig;
}
export interface BzzzTask {
number: number;
title: string;
description: string;
state: 'open' | 'closed';
labels: string[];
created_at: string;
updated_at: string;
html_url: string;
is_claimed: boolean;
assignees: string[];
task_type: string;
}
export interface BzzzRepository {
project_id: number;
name: string;
git_url: string;
owner: string;
repository: string;
branch: string;
bzzz_enabled: boolean;
ready_to_claim: boolean;
private_repo: boolean;
github_token_required: boolean;
}