wip: 优化调度逻辑,增加了工具管理

This commit is contained in:
朝夕 2026-04-20 20:00:15 +08:00
parent fe49340106
commit 1a8277ad88
26 changed files with 175 additions and 3880 deletions

24
frontend/.gitignore vendored
View File

@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -1,16 +0,0 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
## React Compiler
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
## Expanding the ESLint configuration
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.

View File

@ -1,29 +0,0 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])

View File

@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>frontend</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +0,0 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"lucide-react": "^1.8.0",
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
"devDependencies": {
"@eslint/js": "^9.39.4",
"@tailwindcss/vite": "^4.2.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.5.0",
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"postcss": "^8.5.10",
"tailwindcss": "^4.2.2",
"vite": "^8.0.4"
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -1,24 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="bluesky-icon" viewBox="0 0 16 17">
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
</symbol>
<symbol id="discord-icon" viewBox="0 0 20 19">
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
</symbol>
<symbol id="documentation-icon" viewBox="0 0 21 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
</symbol>
<symbol id="github-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
</symbol>
<symbol id="social-icon" viewBox="0 0 20 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
</symbol>
<symbol id="x-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -1,592 +0,0 @@
import React, { useState } from 'react';
import {
Settings, Cpu, HardDrive, Network, List, MessageSquare, Terminal, ChevronRight,
Activity, Zap, Server, Box, MessageCircle, Users, Key, Sliders, Plus, Edit2,
Trash2, Globe, Save, MonitorPlay
} from 'lucide-react';
function UsersSettings() {
return (
<div className="max-w-4xl mx-auto">
<div className="flex justify-between items-center mb-6">
<div>
<h3 className="text-xl font-semibold text-slate-800">User Management</h3>
<p className="text-sm text-slate-500 mt-1">Manage system users and their roles.</p>
</div>
<button className="flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm font-medium transition-colors shadow-sm">
<Plus size={16} className="mr-2" />
Add User
</button>
</div>
<div className="bg-white border border-slate-200 rounded-xl shadow-sm overflow-hidden">
<table className="w-full text-left text-sm">
<thead className="bg-slate-50 border-b border-slate-200 text-slate-500">
<tr>
<th className="px-6 py-4 font-medium">Username</th>
<th className="px-6 py-4 font-medium">Role</th>
<th className="px-6 py-4 font-medium">Status</th>
<th className="px-6 py-4 font-medium text-right">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{[
{ name: 'admin', role: 'Administrator', status: 'Active' },
{ name: 'zhaoxi', role: 'User', status: 'Active' },
{ name: 'guest', role: 'Viewer', status: 'Inactive' },
].map((user, i) => (
<tr key={i} className="hover:bg-slate-50 transition-colors">
<td className="px-6 py-4 font-medium text-slate-800">{user.name}</td>
<td className="px-6 py-4 text-slate-600">{user.role}</td>
<td className="px-6 py-4">
<span className={`px-2.5 py-1 rounded-full text-xs font-medium ${user.status === 'Active' ? 'bg-green-100 text-green-700 border border-green-200' : 'bg-slate-100 text-slate-600 border border-slate-200'}`}>
{user.status}
</span>
</td>
<td className="px-6 py-4 text-right">
<button className="text-slate-400 hover:text-blue-600 mr-3 transition-colors" title="Edit"><Edit2 size={16} /></button>
<button className="text-slate-400 hover:text-red-600 transition-colors" title="Delete"><Trash2 size={16} /></button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
function ProvidersSettings() {
return (
<div className="max-w-4xl mx-auto">
<div className="flex justify-between items-center mb-6">
<div>
<h3 className="text-xl font-semibold text-slate-800">Provider Management</h3>
<p className="text-sm text-slate-500 mt-1">Configure external AI model providers and API keys.</p>
</div>
<button className="flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm font-medium transition-colors shadow-sm">
<Plus size={16} className="mr-2" />
Add Provider
</button>
</div>
<div className="grid grid-cols-2 gap-6">
{[
{ name: 'OpenAI', type: 'API', status: 'Connected', model: 'gpt-4o' },
{ name: 'Local vLLM', type: 'Local', status: 'Connected', model: 'llama-3-8b-instruct' },
{ name: 'Anthropic', type: 'API', status: 'Not Configured', model: 'claude-3-5-sonnet' },
].map((provider, i) => (
<div key={i} className="bg-white border border-slate-200 p-5 rounded-xl shadow-sm hover:border-blue-200 transition-colors">
<div className="flex justify-between items-start mb-4">
<div className="flex items-center">
<div className="w-10 h-10 rounded-lg bg-slate-50 border border-slate-100 flex items-center justify-center mr-3">
<Box size={20} className="text-slate-600" />
</div>
<div>
<h4 className="font-semibold text-slate-800">{provider.name}</h4>
<span className="text-xs text-slate-500 font-mono">{provider.type}</span>
</div>
</div>
<span className={`flex items-center text-xs font-medium px-2 py-1 rounded-md border ${provider.status === 'Connected' ? 'bg-green-50 text-green-700 border-green-200' : 'bg-slate-50 text-slate-500 border-slate-200'}`}>
{provider.status === 'Connected' && <span className="w-1.5 h-1.5 rounded-full bg-green-500 mr-1.5"></span>}
{provider.status}
</span>
</div>
<div className="mb-4">
<p className="text-sm text-slate-600 mb-1">Default Model:</p>
<div className="bg-slate-50 border border-slate-100 rounded text-sm px-3 py-1.5 font-mono text-slate-700">{provider.model}</div>
</div>
<div className="flex justify-end space-x-2">
<button className="px-3 py-1.5 text-sm font-medium text-slate-600 bg-white border border-slate-200 rounded hover:bg-slate-50 transition-colors">Configure</button>
</div>
</div>
))}
</div>
</div>
);
}
function SystemSettings() {
return (
<div className="max-w-4xl mx-auto">
<div className="mb-6">
<h3 className="text-xl font-semibold text-slate-800">System Settings</h3>
<p className="text-sm text-slate-500 mt-1">Global platform configurations.</p>
</div>
<div className="space-y-6">
<div className="bg-white border border-slate-200 rounded-xl shadow-sm p-6">
<h4 className="text-sm font-semibold text-slate-800 mb-4 flex items-center">
<Globe size={16} className="mr-2 text-slate-500" />
General
</h4>
<div className="space-y-4 max-w-md">
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">System Language</label>
<select className="w-full bg-slate-50 border border-slate-200 text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all">
<option>English</option>
<option>简体中文</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Theme</label>
<select className="w-full bg-slate-50 border border-slate-200 text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all">
<option>Light</option>
<option>Dark</option>
<option>System Default</option>
</select>
</div>
</div>
</div>
<div className="bg-white border border-slate-200 rounded-xl shadow-sm p-6">
<h4 className="text-sm font-semibold text-slate-800 mb-4 flex items-center">
<Server size={16} className="mr-2 text-slate-500" />
Cluster & Runtime
</h4>
<div className="space-y-4 max-w-md">
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Max Concurrent Workflows</label>
<input type="number" defaultValue={10} className="w-full bg-slate-50 border border-slate-200 text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all" />
</div>
<div className="flex items-center mt-4">
<input type="checkbox" id="debug_mode" defaultChecked className="w-4 h-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500" />
<label htmlFor="debug_mode" className="ml-2 block text-sm text-slate-700">
Enable debug logging
</label>
</div>
</div>
</div>
<div className="flex justify-end">
<button className="flex items-center px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm font-medium transition-colors shadow-sm">
<Save size={16} className="mr-2" />
Save Changes
</button>
</div>
</div>
</div>
);
}
function MonitoringDashboard() {
return (
<div className="flex-1 flex flex-col bg-slate-50 overflow-hidden">
<div className="p-6 border-b border-slate-200 bg-white shadow-sm z-10 flex items-center justify-between">
<div>
<h2 className="text-xl font-semibold text-slate-800">Ray Cluster Monitoring</h2>
<p className="text-sm text-slate-500 mt-1">Real-time resource utilization across all nodes.</p>
</div>
<div className="flex items-center space-x-2">
<span className="flex h-2.5 w-2.5 rounded-full bg-green-500 animate-pulse"></span>
<span className="text-sm font-medium text-slate-600">Connected</span>
</div>
</div>
<div className="flex-1 overflow-y-auto p-6 space-y-6">
{/* Cluster Global Metrics */}
<div className="grid grid-cols-4 gap-4">
<div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm flex items-center">
<div className="w-12 h-12 rounded-lg bg-blue-50 flex items-center justify-center mr-4">
<Server size={24} className="text-blue-600" />
</div>
<div>
<p className="text-xs text-slate-500 font-medium">TOTAL NODES</p>
<p className="text-2xl font-bold text-slate-800">4</p>
</div>
</div>
<div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm flex items-center">
<div className="w-12 h-12 rounded-lg bg-indigo-50 flex items-center justify-center mr-4">
<Cpu size={24} className="text-indigo-600" />
</div>
<div>
<p className="text-xs text-slate-500 font-medium">TOTAL CPU CORES</p>
<p className="text-2xl font-bold text-slate-800">128</p>
</div>
</div>
<div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm flex items-center">
<div className="w-12 h-12 rounded-lg bg-green-50 flex items-center justify-center mr-4">
<HardDrive size={24} className="text-green-600" />
</div>
<div>
<p className="text-xs text-slate-500 font-medium">TOTAL RAM</p>
<p className="text-2xl font-bold text-slate-800">512 GB</p>
</div>
</div>
<div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm flex items-center">
<div className="w-12 h-12 rounded-lg bg-purple-50 flex items-center justify-center mr-4">
<Box size={24} className="text-purple-600" />
</div>
<div>
<p className="text-xs text-slate-500 font-medium">TOTAL GPUS</p>
<p className="text-2xl font-bold text-slate-800">8</p>
</div>
</div>
</div>
{/* Node List */}
<div className="bg-white border border-slate-200 rounded-xl shadow-sm overflow-hidden">
<table className="w-full text-left text-sm">
<thead className="bg-slate-50 border-b border-slate-200 text-slate-500">
<tr>
<th className="px-6 py-4 font-medium">Node IP</th>
<th className="px-6 py-4 font-medium">Role</th>
<th className="px-6 py-4 font-medium">CPU</th>
<th className="px-6 py-4 font-medium">RAM</th>
<th className="px-6 py-4 font-medium">GPU</th>
<th className="px-6 py-4 font-medium">VRAM</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{[
{ ip: '192.168.0.2', role: 'Head', cpu: '24%', ram: '16GB / 64GB', gpu: 'N/A', vram: 'N/A', status: 'healthy' },
{ ip: '192.168.0.3', role: 'Worker', cpu: '85%', ram: '112GB / 128GB', gpu: '98%', vram: '22GB / 24GB', status: 'busy' },
{ ip: '192.168.0.4', role: 'Worker', cpu: '12%', ram: '32GB / 128GB', gpu: '0%', vram: '0GB / 24GB', status: 'healthy' },
{ ip: '192.168.0.5', role: 'Worker', cpu: '45%', ram: '64GB / 128GB', gpu: '40%', vram: '12GB / 24GB', status: 'healthy' },
].map((node, i) => (
<tr key={i} className="hover:bg-slate-50 transition-colors">
<td className="px-6 py-4 font-medium text-slate-800 flex items-center">
<span className={`w-2 h-2 rounded-full mr-2 ${node.status === 'busy' ? 'bg-orange-500' : 'bg-green-500'}`}></span>
{node.ip}
</td>
<td className="px-6 py-4 text-slate-600">
<span className={`px-2 py-1 rounded text-xs font-medium ${node.role === 'Head' ? 'bg-blue-100 text-blue-700' : 'bg-slate-100 text-slate-700'}`}>
{node.role}
</span>
</td>
<td className="px-6 py-4">
<div className="flex items-center">
<span className="w-10 text-right mr-2">{node.cpu}</span>
<div className="w-16 bg-slate-100 rounded-full h-1.5"><div className={`h-1.5 rounded-full ${parseInt(node.cpu) > 80 ? 'bg-red-500' : 'bg-indigo-500'}`} style={{ width: node.cpu }}></div></div>
</div>
</td>
<td className="px-6 py-4 text-slate-600 font-mono text-xs">{node.ram}</td>
<td className="px-6 py-4">
{node.gpu !== 'N/A' ? (
<div className="flex items-center">
<span className="w-10 text-right mr-2">{node.gpu}</span>
<div className="w-16 bg-slate-100 rounded-full h-1.5"><div className={`h-1.5 rounded-full ${parseInt(node.gpu) > 80 ? 'bg-red-500' : 'bg-purple-500'}`} style={{ width: node.gpu }}></div></div>
</div>
) : (
<span className="text-slate-400 italic">No GPU</span>
)}
</td>
<td className="px-6 py-4 text-slate-600 font-mono text-xs">{node.vram}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}
function App() {
const [activeTab, setActiveTab] = useState('workflows');
const [currentView, setCurrentView] = useState('dashboard'); // 'dashboard', 'settings', or 'monitoring'
const [settingsTab, setSettingsTab] = useState('users'); // 'users', 'providers', 'system'
return (
<div className="flex h-screen w-screen bg-slate-50 text-slate-800 font-sans overflow-hidden">
{/* 1. Sidebar (Leftmost) - Plugins */}
<div className="w-12 bg-white border-r border-slate-200 flex flex-col items-center py-4 space-y-6 shadow-sm z-10 shrink-0">
<div
className="w-8 h-8 bg-blue-600 rounded-xl flex items-center justify-center text-white shadow-md shadow-blue-200 cursor-pointer hover:bg-blue-700 transition-colors"
onClick={() => setCurrentView('dashboard')}
>
<Activity size={18} />
</div>
<div className="flex flex-col space-y-4 flex-1 mt-8">
<button
onClick={() => setCurrentView('dashboard')}
className={`p-1.5 rounded-lg transition-colors ${currentView === 'dashboard' ? 'text-blue-600 bg-blue-50' : 'text-slate-400 hover:text-blue-500 hover:bg-blue-50'}`}
title="Dashboard"
>
<MessageSquare size={18} />
</button>
<button
onClick={() => setCurrentView('monitoring')}
className={`p-1.5 rounded-lg transition-colors ${currentView === 'monitoring' ? 'text-blue-600 bg-blue-50' : 'text-slate-400 hover:text-blue-500 hover:bg-blue-50'}`}
title="Monitoring"
>
<MonitorPlay size={18} />
</button>
<button
onClick={() => setCurrentView('settings')}
className={`p-1.5 rounded-lg transition-colors ${currentView === 'settings' ? 'text-blue-600 bg-blue-50' : 'text-slate-400 hover:text-blue-500 hover:bg-blue-50'}`}
title="Settings"
>
<Settings size={18} />
</button>
</div>
</div>
{currentView === 'monitoring' ? (
<MonitoringDashboard />
) : currentView === 'dashboard' ? (
<>
{/* 2. Left Panel - Cluster Status & Workflows/Chats */}
<div className="w-72 bg-white border-r border-slate-200 flex flex-col z-0 shrink-0">
{/* Top: Cluster Status */}
<div className="h-1/3 p-4 border-b border-slate-100 flex flex-col">
<h2 className="text-sm font-semibold text-slate-500 uppercase tracking-wider mb-4 flex items-center">
<Server size={16} className="mr-2" />
Cluster Status
</h2>
<div className="space-y-4 flex-1">
<div className="flex items-center justify-between">
<div className="flex items-center text-slate-600">
<Box size={16} className="mr-2 text-blue-500" />
<span className="text-sm">Active Nodes</span>
</div>
<span className="text-sm font-medium text-slate-800">3 / 4</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center text-slate-600">
<Cpu size={16} className="mr-2 text-indigo-500" />
<span className="text-sm">Cluster CPU</span>
</div>
<span className="text-sm font-medium text-slate-800">45%</span>
</div>
<div className="w-full bg-slate-100 rounded-full h-1.5">
<div className="bg-indigo-500 h-1.5 rounded-full" style={{ width: '45%' }}></div>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center text-slate-600">
<HardDrive size={16} className="mr-2 text-green-500" />
<span className="text-sm">Cluster RAM</span>
</div>
<span className="text-sm font-medium text-slate-800">12.4 GB</span>
</div>
<div className="w-full bg-slate-100 rounded-full h-1.5">
<div className="bg-green-500 h-1.5 rounded-full" style={{ width: '60%' }}></div>
</div>
</div>
</div>
{/* Bottom: Tabs for Workflows & Basic Chats */}
<div className="flex-1 flex flex-col overflow-hidden">
<div className="flex border-b border-slate-100">
<button
onClick={() => setActiveTab('workflows')}
className={`flex-1 py-3 text-xs font-medium text-center uppercase tracking-wider transition-colors ${activeTab === 'workflows' ? 'text-blue-600 border-b-2 border-blue-600 bg-blue-50/50' : 'text-slate-500 hover:bg-slate-50'}`}
>
<List size={14} className="inline mr-1.5 -mt-0.5" />
Workflows
</button>
<button
onClick={() => setActiveTab('chats')}
className={`flex-1 py-3 text-xs font-medium text-center uppercase tracking-wider transition-colors ${activeTab === 'chats' ? 'text-blue-600 border-b-2 border-blue-600 bg-blue-50/50' : 'text-slate-500 hover:bg-slate-50'}`}
>
<MessageCircle size={14} className="inline mr-1.5 -mt-0.5" />
Chats
</button>
</div>
<div className="flex-1 p-4 overflow-y-auto">
{activeTab === 'workflows' && (
<div className="space-y-2">
{[1, 2, 3, 4].map((i) => (
<div key={i} className={`p-3 rounded-lg border cursor-pointer transition-all ${i === 1 ? 'border-blue-200 bg-blue-50 shadow-sm' : 'border-slate-100 hover:border-blue-200 hover:bg-slate-50'}`}>
<div className="flex justify-between items-center mb-1">
<span className={`font-medium text-sm ${i === 1 ? 'text-blue-700' : 'text-slate-700'}`}>Data Processing {i}</span>
<span className={`flex h-2 w-2 rounded-full ${i === 1 ? 'bg-green-400' : 'bg-slate-300'}`}></span>
</div>
<p className="text-xs text-slate-400 line-clamp-1">Processing raw user logs...</p>
</div>
))}
</div>
)}
{activeTab === 'chats' && (
<div className="space-y-2">
<div className="p-3 rounded-lg border border-slate-100 hover:border-blue-200 hover:bg-slate-50 cursor-pointer transition-all">
<div className="font-medium text-sm text-slate-700 mb-1">System Architecture</div>
<p className="text-xs text-slate-400 line-clamp-1">Can you explain the MoE model...</p>
</div>
<div className="p-3 rounded-lg border border-slate-100 hover:border-blue-200 hover:bg-slate-50 cursor-pointer transition-all">
<div className="font-medium text-sm text-slate-700 mb-1">Log Analysis Helper</div>
<p className="text-xs text-slate-400 line-clamp-1">Show me the errors from yesterday.</p>
</div>
</div>
)}
</div>
</div>
</div>
{/* 3. Middle Panel - AI Chat */}
<div className="flex-1 flex flex-col bg-slate-50">
<div className="h-14 border-b border-slate-200 bg-white flex items-center px-6 shadow-sm z-10">
<MessageSquare size={18} className="text-blue-600 mr-3" />
<h1 className="font-semibold text-slate-800">Pretor Assistant</h1>
</div>
{/* Chat History */}
<div className="flex-1 p-6 overflow-y-auto space-y-6">
{/* System Message */}
<div className="flex justify-center">
<span className="text-xs text-slate-400 bg-slate-200/50 px-3 py-1 rounded-full">Today 10:42 AM</span>
</div>
{/* User Message */}
<div className="flex justify-end">
<div className="bg-blue-600 text-white p-4 rounded-2xl rounded-tr-sm max-w-[80%] shadow-sm">
<p className="text-sm leading-relaxed">Start the data processing workflow for yesterday's logs.</p>
</div>
</div>
{/* AI Message */}
<div className="flex justify-start">
<div className="w-8 h-8 rounded-full bg-white border border-blue-100 flex items-center justify-center mr-3 mt-1 shadow-sm flex-shrink-0">
<Activity size={16} className="text-blue-600" />
</div>
<div className="bg-white border border-slate-100 text-slate-700 p-4 rounded-2xl rounded-tl-sm max-w-[80%] shadow-sm">
<p className="text-sm leading-relaxed mb-3">I've initiated the <strong>Data Processing 1</strong> workflow. It is currently gathering logs.</p>
<div className="bg-slate-50 border border-slate-100 rounded-lg p-3 flex items-center text-sm">
<Terminal size={16} className="text-slate-400 mr-2" />
<span className="font-mono text-slate-600 text-xs">Task ID: flow_x829j_log</span>
</div>
</div>
</div>
</div>
{/* Chat Input */}
<div className="p-4 bg-white border-t border-slate-200">
<div className="relative flex items-center">
<input
type="text"
placeholder="Ask Pretor to do something..."
className="w-full bg-slate-50 border border-slate-200 text-sm rounded-xl pl-4 pr-12 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-inner"
/>
<button className="absolute right-2 p-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors shadow-sm">
<ChevronRight size={18} />
</button>
</div>
<div className="flex mt-2 space-x-3 px-2">
<span className="text-[10px] text-slate-400 cursor-pointer hover:text-blue-500">Run diagnostics</span>
<span className="text-[10px] text-slate-400 cursor-pointer hover:text-blue-500">View recent errors</span>
</div>
</div>
</div>
{/* 4. Right Panel - Workflow Execution Status */}
<div className="w-80 bg-white border-l border-slate-200 flex flex-col z-0">
<div className="h-14 border-b border-slate-100 flex items-center px-4 justify-between bg-slate-50/50">
<h2 className="font-semibold text-slate-800 text-sm flex items-center">
<Terminal size={16} className="mr-2 text-slate-500" />
Execution Trace
</h2>
<span className="px-2 py-1 bg-green-100 text-green-700 text-xs rounded-md font-medium border border-green-200">Running</span>
</div>
<div className="flex-1 p-4 overflow-y-auto">
<div className="mb-6">
<h3 className="text-lg font-bold text-slate-800 mb-1">Data Processing 1</h3>
<p className="text-xs text-slate-500 mb-4 font-mono">ID: flow_x829j_log</p>
<div className="bg-slate-50 rounded-lg p-3 border border-slate-100 text-xs text-slate-600 space-y-2">
<div className="flex justify-between">
<span className="text-slate-400">Started:</span>
<span>2 mins ago</span>
</div>
<div className="flex justify-between">
<span className="text-slate-400">Agent:</span>
<span className="font-medium text-blue-600">Supervisor Node</span>
</div>
</div>
</div>
{/* Vertical Timeline Container */}
<div className="relative border-l-2 border-slate-200 ml-3 pl-6 space-y-6">
{/* Step 1 */}
<div className="relative">
{/* Dot */}
<div className="absolute -left-[31px] top-1 flex items-center justify-center w-6 h-6 rounded-full border-2 border-white bg-green-500 text-white shadow-sm">
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="3" d="M5 13l4 4L19 7"></path></svg>
</div>
{/* Card */}
<div className="p-3 rounded-lg border border-slate-100 bg-white shadow-sm">
<h4 className="font-semibold text-slate-800 text-xs mb-1">1. Initialize Context</h4>
<p className="text-[11px] text-slate-500">Loaded environment variables and db connection.</p>
</div>
</div>
{/* Step 2 */}
<div className="relative">
{/* Dot (Active) */}
<div className="absolute -left-[31px] top-1 flex items-center justify-center w-6 h-6 rounded-full border-2 border-white bg-blue-500 shadow-sm">
<span className="h-2 w-2 bg-white rounded-full animate-pulse"></span>
</div>
{/* Card */}
<div className="p-3 rounded-lg border border-blue-200 bg-blue-50 shadow-sm">
<h4 className="font-semibold text-blue-800 text-xs mb-1">2. Consciousness Node</h4>
<p className="text-[11px] text-blue-600">Planning data extraction steps...</p>
</div>
</div>
{/* Step 3 */}
<div className="relative opacity-60">
{/* Dot (Pending) */}
<div className="absolute -left-[31px] top-1 flex items-center justify-center w-6 h-6 rounded-full border-2 border-white bg-slate-200 shadow-sm"></div>
{/* Card */}
<div className="p-3 rounded-lg border border-slate-100 bg-white shadow-sm">
<h4 className="font-semibold text-slate-600 text-xs mb-1">3. Control Node Exec</h4>
<p className="text-[11px] text-slate-500">Waiting to execute tasks.</p>
</div>
</div>
</div>
</div>
</div>
</>
) : (
/* Settings View */
<div className="flex-1 flex bg-slate-50 overflow-hidden">
{/* Settings Inner Sidebar */}
<div className="w-64 bg-white border-r border-slate-200 flex flex-col z-0">
<div className="p-6 border-b border-slate-100">
<h2 className="text-lg font-semibold text-slate-800">Settings</h2>
</div>
<div className="flex-1 p-4 space-y-2 overflow-y-auto">
<button
onClick={() => setSettingsTab('users')}
className={`w-full flex items-center px-4 py-3 text-sm font-medium rounded-xl transition-all ${settingsTab === 'users' ? 'bg-blue-50 text-blue-600' : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'}`}
>
<Users size={18} className="mr-3" />
User Management
</button>
<button
onClick={() => setSettingsTab('providers')}
className={`w-full flex items-center px-4 py-3 text-sm font-medium rounded-xl transition-all ${settingsTab === 'providers' ? 'bg-blue-50 text-blue-600' : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'}`}
>
<Key size={18} className="mr-3" />
Provider Management
</button>
<button
onClick={() => setSettingsTab('system')}
className={`w-full flex items-center px-4 py-3 text-sm font-medium rounded-xl transition-all ${settingsTab === 'system' ? 'bg-blue-50 text-blue-600' : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'}`}
>
<Sliders size={18} className="mr-3" />
System Settings
</button>
</div>
</div>
{/* Settings Main Content */}
<div className="flex-1 overflow-y-auto p-8">
{settingsTab === 'users' && <UsersSettings />}
{settingsTab === 'providers' && <ProvidersSettings />}
{settingsTab === 'system' && <SystemSettings />}
</div>
</div>
)}
</div>
);
}
export default App;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -1,15 +0,0 @@
@import "tailwindcss";
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -1,10 +0,0 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)

View File

@ -1,8 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
})

View File

@ -15,7 +15,7 @@
import datetime import datetime
from pydantic import BaseModel, Field, ConfigDict from pydantic import BaseModel, Field, ConfigDict
from ulid import ULID from ulid import ULID
from typing import Dict, List from typing import Any, Dict
from pretor.core.workflow.workflow import PretorWorkflow from pretor.core.workflow.workflow import PretorWorkflow
import asyncio import asyncio
@ -30,6 +30,7 @@ class PretorEvent(BaseModel):
message: str = Field(description="用户发来的消息") message: str = Field(description="用户发来的消息")
attachment: Dict[str, str] | None = Field(default=None,description="附件") attachment: Dict[str, str] | None = Field(default=None,description="附件")
#-------------------------------------------------------------------------------------------------------------- #--------------------------------------------------------------------------------------------------------------
context: Dict[str, Any] = Field(default_factory=dict, description="事件上下文内容,可包含工作流模板等信息")
workflow: PretorWorkflow | None = Field(default=None,description="工作流") workflow: PretorWorkflow | None = Field(default=None,description="工作流")
pending_queue: asyncio.Queue[str] | None= Field(default=None,description="待处理队列") pending_queue: asyncio.Queue[str] | None= Field(default=None,description="待处理队列")
receive_queue: asyncio.Queue[str] | None = Field(default=None,description="待接收队列") receive_queue: asyncio.Queue[str] | None = Field(default=None,description="待接收队列")

View File

@ -26,7 +26,7 @@ class GlobalToolManager:
def __init__(self): def __init__(self):
self.tool_mapper = defaultdict(dict) self.tool_mapper = defaultdict(dict)
tool_plugin_dir = pathlib.Path(__file__).parent.parent.parent / "plugin.tool_plugin" tool_plugin_dir = pathlib.Path(__file__).parent.parent.parent / "plugin" / "tool_plugin"
if not tool_plugin_dir.exists() or not tool_plugin_dir.is_dir(): if not tool_plugin_dir.exists() or not tool_plugin_dir.is_dir():
return return

View File

@ -23,6 +23,8 @@ from pretor.core.global_state_machine.model_provider.base_provider import Provid
from pretor.adapter.model_adapter.agent_factory import AgentFactory from pretor.adapter.model_adapter.agent_factory import AgentFactory
from loguru import logger from loguru import logger
from pretor.utils.get_tool import get_tool
@ray.remote @ray.remote
class ConsciousnessNode: class ConsciousnessNode:
@ -130,6 +132,7 @@ class ConsciousnessNode:
async def _run(self, payload: Union[ForSupervisoryInput, ForWorkflowInput, ForWorkflowEngineInput]) -> Union[ForSupervisoryNode, ForWorkflow, ForWorkflowEngine]: async def _run(self, payload: Union[ForSupervisoryInput, ForWorkflowInput, ForWorkflowEngineInput]) -> Union[ForSupervisoryNode, ForWorkflow, ForWorkflowEngine]:
try: try:
self.agent.retries = 3 self.agent.retries = 3
tool = get_tool("control_node")
if isinstance(payload, ForWorkflowEngineInput): if isinstance(payload, ForWorkflowEngineInput):
deps = ConsciousnessNodeDeps( deps = ConsciousnessNodeDeps(
original_command=payload.original_command, original_command=payload.original_command,
@ -139,7 +142,8 @@ class ConsciousnessNode:
logger.debug("ConsciousnessNode: 开始生成工作流 (原生重试开启)") logger.debug("ConsciousnessNode: 开始生成工作流 (原生重试开启)")
result = await self.agent.run( result = await self.agent.run(
"根据original_command制定严密的可执行workflow可以学习并参考workflow_template的设计理念", "根据original_command制定严密的可执行workflow可以学习并参考workflow_template的设计理念",
deps=deps) deps=deps,
tools=tool)
return result.output return result.output
elif isinstance(payload, ForWorkflowInput): elif isinstance(payload, ForWorkflowInput):
@ -149,7 +153,8 @@ class ConsciousnessNode:
) )
logger.debug("ConsciousnessNode: 开始处理工作流节点任务 (原生重试开启)") logger.debug("ConsciousnessNode: 开始处理工作流节点任务 (原生重试开启)")
result = await self.agent.run(f"处理此工作流步骤信息:\n{payload.workflow_step.model_dump_json()}", result = await self.agent.run(f"处理此工作流步骤信息:\n{payload.workflow_step.model_dump_json()}",
deps=deps) deps=deps,
tools=tool)
return result.output return result.output
elif isinstance(payload, ForSupervisoryInput): elif isinstance(payload, ForSupervisoryInput):
@ -158,9 +163,9 @@ class ConsciousnessNode:
command="对于工作流整体执行结果进行检查,并且生成一份专业的技术性总结报告" command="对于工作流整体执行结果进行检查,并且生成一份专业的技术性总结报告"
) )
logger.debug("ConsciousnessNode: 开始生成技术总结报告 (原生重试开启)") logger.debug("ConsciousnessNode: 开始生成技术总结报告 (原生重试开启)")
result = await self.agent.run( result = await self.agent.run(f"基于以下工作流的执行记录,生成技术报告:\n{payload.workflow.model_dump_json()}",
f"基于以下工作流的执行记录,生成技术报告:\n{payload.workflow.model_dump_json()}", deps=deps,
deps=deps) tools=tool)
return result.output return result.output
except Exception as e: except Exception as e:
logger.exception(f"ConsciousnessNode 模型生成最终失败: {str(e)}") logger.exception(f"ConsciousnessNode 模型生成最终失败: {str(e)}")

View File

@ -19,6 +19,8 @@ from pretor.core.global_state_machine.global_state_machine import GlobalStateMac
from pretor.core.global_state_machine.model_provider.base_provider import Provider from pretor.core.global_state_machine.model_provider.base_provider import Provider
from pretor.adapter.model_adapter.agent_factory import AgentFactory from pretor.adapter.model_adapter.agent_factory import AgentFactory
from pretor.core.individual.control_node.template import ForWorkflow, ForWorkflowInput, ControlNodeDeps from pretor.core.individual.control_node.template import ForWorkflow, ForWorkflowInput, ControlNodeDeps
from pretor.utils.get_tool import get_tool
@ray.remote @ray.remote
class ControlNode: class ControlNode:
@ -85,9 +87,12 @@ class ControlNode:
) )
logger.debug(f"ControlNode: 开始执行工作流节点 [{payload.workflow_step.name}] (原生重试开启)") logger.debug(f"ControlNode: 开始执行工作流节点 [{payload.workflow_step.name}] (原生重试开启)")
tool = get_tool("control_node")
result = await self.agent.run( result = await self.agent.run(
f"请根据提供的 workflow_step 上下文,执行此步骤并输出结果。\n详细指令或附加数据:{payload.workflow_step.model_dump_json()}", f"请根据提供的 workflow_step 上下文,执行此步骤并输出结果。\n详细指令或附加数据:{payload.workflow_step.model_dump_json()}",
deps=deps deps=deps,
tools=tool
) )
return result.output return result.output
except Exception as e: except Exception as e:

View File

@ -23,6 +23,7 @@ from pretor.core.individual.supervisory_node.template import ForConsciousnessNod
from pydantic_ai import RunContext, Agent from pydantic_ai import RunContext, Agent
from loguru import logger from loguru import logger
from pretor.utils.ray_hook import ray_actor_hook from pretor.utils.ray_hook import ray_actor_hook
from pretor.utils.get_tool import get_tool
@ray.remote @ray.remote
class SupervisoryNode: class SupervisoryNode:
@ -93,7 +94,15 @@ class SupervisoryNode:
try: try:
result = await self._run(payload) result = await self._run(payload)
if isinstance(result, ForConsciousnessNode): if isinstance(result, ForConsciousnessNode):
logger.info(f"SupervisoryNode: 任务已分配给意识节点,选用模板 [{result.workflow_template}]") logger.info(f"SupervisoryNode: 任务已分配给工作流引擎处理,选用模板 [{result.workflow_template}]")
if isinstance(payload, PretorEvent):
payload.context["workflow_template"] = result.workflow_template
try:
workflow_running_engine = ray_actor_hook("workflow_running_engine")
await workflow_running_engine.put_event.remote(payload)
except Exception as e:
logger.error(f"SupervisoryNode: 无法将事件放入 WorkflowRunningEngine: {e}")
return "抱歉,任务提交失败,系统内部错误。"
return f"任务已创建,准备创建工作流。原因:{result.reasoning}" return f"任务已创建,准备创建工作流。原因:{result.reasoning}"
elif isinstance(result, ForUser): elif isinstance(result, ForUser):
logger.info("SupervisoryNode: 直接向用户返回简单回复。") logger.info("SupervisoryNode: 直接向用户返回简单回复。")
@ -160,7 +169,10 @@ class SupervisoryNode:
if isinstance(payload, TerminationMessage): if isinstance(payload, TerminationMessage):
prompt_message = f"【工作流执行结束报告】\n请将以下技术报告转化为对用户的友好回复:\n{message}" prompt_message = f"【工作流执行结束报告】\n请将以下技术报告转化为对用户的友好回复:\n{message}"
self.agent.retries = 3 self.agent.retries = 3
result = await self.agent.run(prompt_message, deps=deps) tool = get_tool("supervisory_node")
result = await self.agent.run(prompt_message,
deps=deps,
tools=tool)
return result.output return result.output
except Exception as e: except Exception as e:
logger.exception(f"SupervisoryNode 模型生成或解析最终失败: {str(e)}") logger.exception(f"SupervisoryNode 模型生成或解析最终失败: {str(e)}")

View File

@ -12,16 +12,24 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from pretor.utils.ray_hook import ray_actor_hook
import ray import ray
import asyncio import asyncio
from pretor.core.workflow.workflow import PretorWorkflow, WorkStep from pretor.core.workflow.workflow import PretorWorkflow, WorkStep
from loguru import logger from loguru import logger
from typing import Optional, Dict, Union, Any, List from typing import Optional, Dict, Union, Any, List
from pretor.utils.error import WorkflowError, WorkflowExit from pretor.utils.error import WorkflowError, WorkflowExit
from pretor.api.platform.event import PretorEvent
from pretor.core.individual.control_node.template import ForWorkflowInput as ControlForWorkflowInput, \ from pretor.core.individual.control_node.template import ForWorkflowInput as ControlForWorkflowInput, \
ForWorkflow as ControlForWorkflow ForWorkflow as ControlForWorkflow
from pretor.core.individual.consciousness_node.template import ForWorkflowInput as ConsciousnessForWorkflowInput, \ from pretor.core.individual.consciousness_node.template import (
ForSupervisoryInput, ForSupervisoryNode, ForWorkflow as ConsciousnessForWorkflow ForWorkflowInput as ConsciousnessForWorkflowInput,
ForSupervisoryInput,
ForSupervisoryNode,
ForWorkflow as ConsciousnessForWorkflow,
ForWorkflowEngineInput,
ForWorkflowEngine
)
from pretor.core.individual.supervisory_node.template import TerminationMessage from pretor.core.individual.supervisory_node.template import TerminationMessage
@ -225,7 +233,7 @@ class WorkflowEngine:
class WorkflowRunningEngine: class WorkflowRunningEngine:
def __init__(self, consciousness_node=None, control_node=None, supervisory_node=None): def __init__(self, consciousness_node=None, control_node=None, supervisory_node=None):
self.runner_engine = {} self.runner_engine = {}
self.workflow_queue: asyncio.Queue[PretorWorkflow] = None self.workflow_queue: asyncio.Queue[PretorEvent] = None
self.consciousness_node = consciousness_node self.consciousness_node = consciousness_node
self.control_node = control_node self.control_node = control_node
self.supervisory_node = supervisory_node self.supervisory_node = supervisory_node
@ -237,8 +245,8 @@ class WorkflowRunningEngine:
} }
self.workflow_queue = asyncio.Queue() self.workflow_queue = asyncio.Queue()
async def put_workflow(self, workflow: PretorWorkflow) -> None: async def put_event(self, event: PretorEvent) -> None:
await self.workflow_queue.put(workflow) await self.workflow_queue.put(event)
async def runner(self, i: int) -> None: async def runner(self, i: int) -> None:
""" """
@ -249,14 +257,38 @@ class WorkflowRunningEngine:
""" """
while True: while True:
try: try:
workflow = await self.workflow_queue.get() event = await self.workflow_queue.get()
logger.info(f"WorkflowRunningEngine: runner_{i}接收工作流{workflow.trace_id}:{workflow.title}") logger.info(f"WorkflowRunningEngine: runner_{i} 接收到事件 {event.event_id} 准备生成工作流。")
workflow_engine = WorkflowEngine(workflow,
self.consciousness_node, if not self.consciousness_node:
self.control_node, raise WorkflowError("未配置 consciousness_node无法生成工作流")
self.supervisory_node)
await workflow_engine.run() workflow_template = event.context.get("workflow_template", "")
pass
payload = ForWorkflowEngineInput(
original_command=event.message,
workflow_template=workflow_template
)
result_obj = await self.consciousness_node.working.remote(payload)
if isinstance(result_obj, ForWorkflowEngine):
workflow = result_obj.workflow
logger.info(
f"WorkflowRunningEngine: runner_{i} 成功生成工作流 {workflow.trace_id}:{workflow.title}")
workflow.event_info = event
global_state_machine = ray_actor_hook("global_state_machine")
await global_state_machine.update_workflow.remote(event.event_id, workflow)
workflow_engine = WorkflowEngine(workflow,
self.consciousness_node,
self.control_node,
self.supervisory_node)
await workflow_engine.run()
else:
logger.error(f"WorkflowRunningEngine: runner_{i} 无法生成工作流,返回类型为 {type(result_obj)}")
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info(f"WorkflowRunningEngine: runner_{i} 被取消。") logger.info(f"WorkflowRunningEngine: runner_{i} 被取消。")
raise raise

65
pretor/utils/get_tool.py Normal file
View File

@ -0,0 +1,65 @@
# Copyright 2026 zhaoxi826
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import importlib
from typing import Callable, Dict, List
import pathlib
from loguru import logger
from functools import lru_cache
from pretor.utils.ray_hook import ray_actor_hook
_tool_cache: Dict[str, Callable] = {}
def _get_tool_func(tool_name: str) -> Callable | None:
func = _tool_cache.get(tool_name, None)
if func:
return func
tool_plugin_dir = pathlib.Path(__file__).parent.parent.parent / "plugin" / "tool_plugin" / tool_name
if not tool_plugin_dir.exists() or not tool_plugin_dir.is_dir():
return None
module_name = f"pretor.plugin.tool_plugin.{tool_name}"
try:
module = importlib.import_module(module_name)
func = getattr(module, tool_name)
if not callable(func):
return None
_tool_cache[tool_name] = func
return func
except ModuleNotFoundError:
logger.error(f"Module {module_name} not found")
return None
def del_tool_cache(tool_name: str) -> None:
if tool_name in _tool_cache:
del _tool_cache[tool_name]
refresh_agent_tools()
@lru_cache(maxsize=1)
async def get_tool(agent_name: str) -> List[Callable]:
global_state_machine = ray_actor_hook("global_state_machine")
_tool_list = await global_state_machine.get_tool_list.remote(agent_name)
tool_list = []
for tool_name in _tool_list.keys():
tool_func = _get_tool_func(tool_name)
if tool_func:
tool_list.append(tool_func)
else:
continue
return tool_list
def refresh_agent_tools() -> None:
get_tool.cache_clear()

View File

@ -1,25 +0,0 @@
# Copyright 2026 zhaoxi826
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pretor.utils.error import DemandError
def inspector(variety: str, name: str):
def decorator(func):
def wrapper(*args, **kwargs):
demand = args[1].status.demand
if demand.variety != variety and demand.name != name:
raise DemandError("需求目标对象错误或名称错误!")
return func(*args, **kwargs)
return wrapper
return decorator

View File

@ -0,0 +1,14 @@
worker_individual
---
**worker_individual**是pretor中的基础工作对象主要分为三类:**skill_individual**,**ordinary_individual**和**special_individual**,庞大的**worker_individual**将负责具体的生产工作。
---
## worker_individual分类
### skill_individual专家子个体
**skill_individual专家子个体** 是拥有专业**skill**的agent通常使用MoE混合专家模型或者大参数的专家模型来作为agent的模型。通过装配专业化的知识从而实现完成复杂任务。
### ordinary_individual普通子个体
**ordinary_individual普通子个体** 是普通的agent通常使用小参数微调专家模型来作为agent的模型。通过专业化数据的微调在一定程度上实现比大参数MoE模型在单一方面上的能力。
### special_individual特殊子个体
**special_individual特殊子个体** 是特殊的agent这类agent一般不承担普通的生成任务更多是实现一些特殊的任务比如生成语音生成视频等。

View File

@ -31,4 +31,5 @@ gpu = [
[dependency-groups] [dependency-groups]
dev = [ dev = [
"pytest>=9.0.3", "pytest>=9.0.3",
"pytest-asyncio>=1.3.0",
] ]

18
uv.lock
View File

@ -3089,6 +3089,7 @@ gpu = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "pytest" }, { name = "pytest" },
{ name = "pytest-asyncio" },
] ]
[package.metadata] [package.metadata]
@ -3114,7 +3115,10 @@ requires-dist = [
provides-extras = ["gpu"] provides-extras = ["gpu"]
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [{ name = "pytest", specifier = ">=9.0.3" }] dev = [
{ name = "pytest", specifier = ">=9.0.3" },
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
]
[[package]] [[package]]
name = "pretor-viceroy" name = "pretor-viceroy"
@ -3954,6 +3958,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
] ]
[[package]]
name = "pytest-asyncio"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" },
]
[[package]] [[package]]
name = "python-dateutil" name = "python-dateutil"
version = "2.9.0.post0" version = "2.9.0.post0"