docs: update README with new Rust frontend and fix TypeScript version
- Updated README.md to reflect the addition of a native Rust frontend using egui - Added setup instructions for the new rust-frontend directory - Modified frontend package.json to downgrade TypeScript version from 5.3.3 to 4.9.5 due to conflicts - Updated path references in documentation from ComfyUI-Rust/ to direct paths - Reorganized project structure documentation to distinguish between original and new frontends
This commit is contained in:
34
README.md
34
README.md
@@ -4,7 +4,8 @@
|
||||
This project implements an AI image/video generation tool inspired by the node-based workflow editor of Stable Diffusion Web UI. Built as a modern web application using:
|
||||
|
||||
- **Backend**: Pure Rust (Actix-web framework)
|
||||
- **Frontend**: React + TypeScript with Node graph visualization
|
||||
- **Frontend**: React + TypeScript with Node graph visualization (currently having TypeScript version conflict)
|
||||
- **New Frontend Alternative**: Native Rust frontend using egui
|
||||
- **GPU Acceleration**: ROCm integration for AMD RX 9070 XT GPU
|
||||
|
||||
### Key Features:
|
||||
@@ -50,7 +51,7 @@ rocminfo # Should show your GPU info including "gfx900" or similar architectu
|
||||
### Backend Setup (Rust):
|
||||
|
||||
```bash
|
||||
cd ComfyUI-Rust/backend
|
||||
cd backend
|
||||
cargo build --release # For production builds, use release mode for better performance on AMD GPUs
|
||||
|
||||
# Run the backend server:
|
||||
@@ -60,10 +61,10 @@ Backend runs at: http://localhost:[PORT]
|
||||
API endpoints available after starting.
|
||||
```
|
||||
|
||||
### Frontend Setup (React):
|
||||
### Frontend Setup (React - currently has TypeScript conflict):
|
||||
|
||||
```bash
|
||||
cd ComfyUI-Rust/frontend
|
||||
cd frontend
|
||||
npm install # Install dependencies
|
||||
|
||||
Run dev mode with hot reload and AMD GPU preview support:
|
||||
@@ -73,6 +74,16 @@ yarn start
|
||||
RUST_AMD_ROCM_PATH=/usr/local/AMDROCmlib yarn run prod-build && npm run serve
|
||||
```
|
||||
|
||||
### New Rust Frontend Setup (egui):
|
||||
|
||||
```bash
|
||||
cd rust-frontend
|
||||
cargo run # Run the egui-based frontend
|
||||
|
||||
# Build for release:
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
## Project Structure Overview:
|
||||
|
||||
### Rust Backend (`backend/src`):
|
||||
@@ -96,7 +107,7 @@ src/
|
||||
|
||||
```
|
||||
|
||||
### Frontend Web App (`frontend/src`):
|
||||
### Original Frontend Web App (`frontend/src`):
|
||||
```typescript/react
|
||||
src/
|
||||
├─ components/node-editor.tsx // Node-based workflow editor (graph canvas)
|
||||
@@ -109,6 +120,17 @@ src/
|
||||
|
||||
```
|
||||
|
||||
### New Rust Frontend (`rust-frontend/src`):
|
||||
```rust
|
||||
src/
|
||||
├── main.rs # Main application entry point
|
||||
├── node_editor.rs # Node-based workflow editor implementation
|
||||
├── node_panel.rs # Panel for selecting and managing nodes
|
||||
├── preview_pane.rs # Preview pane for image results
|
||||
└── api_client.rs # Backend API communication layer
|
||||
|
||||
```
|
||||
|
||||
## ROCm Integration Notes:
|
||||
|
||||
### Key Components:
|
||||
@@ -116,7 +138,7 @@ src/
|
||||
- Parallelism configured based on AMD GPU thread count (RX9070XT)
|
||||
|
||||
```rust
|
||||
// Example configuration from rust/backend/src/config.rs
|
||||
// Example configuration from backend/src/config.rs
|
||||
|
||||
pub struct Config {
|
||||
pub gpu_backend_config = RocmConfig { // ROCm detection for RX900 series GPUs
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/react": "^18.2.45",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
18
rust-frontend/Cargo.toml
Normal file
18
rust-frontend/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "comfyui-rust-frontend"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
eframe = "0.24"
|
||||
egui = "0.24"
|
||||
egui_extras = "0.24"
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
image = "0.24"
|
||||
thiserror = "1.0"
|
||||
|
||||
[dependencies.comfyui-backend]
|
||||
path = "../backend"
|
||||
59
rust-frontend/README.md
Normal file
59
rust-frontend/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# ComfyUI Rust Frontend
|
||||
|
||||
A native Rust frontend for ComfyUI-like AI image generation tool using egui.
|
||||
|
||||
## Features
|
||||
|
||||
- Node-based workflow editor
|
||||
- Real-time preview pane
|
||||
- Integration with backend API
|
||||
- ROCm support through Rust bindings
|
||||
|
||||
## Architecture
|
||||
|
||||
This frontend is built with:
|
||||
- `eframe` and `egui` for the UI framework
|
||||
- `reqwest` for HTTP communication with the backend
|
||||
- `serde` for JSON serialization/deserialization
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── main.rs # Main application entry point
|
||||
├── node_editor.rs # Node-based workflow editor implementation
|
||||
├── node_panel.rs # Panel for selecting and managing nodes
|
||||
├── preview_pane.rs # Preview pane for image results
|
||||
└── api_client.rs # Backend API communication layer
|
||||
```
|
||||
|
||||
## Running the Application
|
||||
|
||||
1. Make sure you have Rust installed (`rustup` or `curl https://sh.rustup.org/rustup.sh -sSf | sh`)
|
||||
2. Start the backend server:
|
||||
```bash
|
||||
cd ../backend && cargo run
|
||||
```
|
||||
3. In another terminal, run the frontend:
|
||||
```bash
|
||||
cd rust-frontend && cargo run
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To build for release:
|
||||
```bash
|
||||
cd rust-frontend && cargo build --release
|
||||
```
|
||||
|
||||
The executable will be located at `target/release/comfyui-rust-frontend`.
|
||||
|
||||
## Integration with Backend
|
||||
|
||||
This frontend communicates with the backend through HTTP API endpoints. The API client handles:
|
||||
|
||||
- Fetching available node types from `/nodes`
|
||||
- Executing workflows via POST to `/execute`
|
||||
- Getting execution results and preview images
|
||||
|
||||
The backend must be running on `http://localhost:8080` by default, but this can be configured in `src/main.rs`.
|
||||
74
rust-frontend/src/api_client.rs
Normal file
74
rust-frontend/src/api_client.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use reqwest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ApiClient {
|
||||
base_url: String,
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl ApiClient {
|
||||
pub fn new(base_url: String) -> Self {
|
||||
Self {
|
||||
base_url,
|
||||
client: reqwest::Client::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Example API call - in a real implementation, this would be connected to your backend endpoints
|
||||
pub async fn get_nodes(&self) -> Result<Vec<NodeInfo>, Box<dyn Error>> {
|
||||
let response = self.client
|
||||
.get(&format!("{}/nodes", self.base_url))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let nodes: Vec<NodeInfo> = response.json().await?;
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
pub async fn execute_workflow(&self, workflow: &Workflow) -> Result<ExecutionResult, Box<dyn Error>> {
|
||||
let response = self.client
|
||||
.post(&format!("{}/execute", self.base_url))
|
||||
.json(workflow)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let result: ExecutionResult = response.json().await?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct NodeInfo {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Workflow {
|
||||
pub nodes: Vec<WorkflowNode>,
|
||||
pub connections: Vec<Connection>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WorkflowNode {
|
||||
pub id: String,
|
||||
pub node_type: String,
|
||||
pub parameters: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Connection {
|
||||
pub from_node: String,
|
||||
pub from_output: String,
|
||||
pub to_node: String,
|
||||
pub to_input: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ExecutionResult {
|
||||
pub status: String,
|
||||
pub output: Option<serde_json::Value>,
|
||||
}
|
||||
12
rust-frontend/src/lib.rs
Normal file
12
rust-frontend/src/lib.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
//! A Rust frontend for ComfyUI using egui
|
||||
|
||||
pub mod node_editor;
|
||||
pub mod node_panel;
|
||||
pub mod preview_pane;
|
||||
pub mod api_client;
|
||||
|
||||
// Re-export key types for easier access
|
||||
pub use node_editor::{NodeEditor, Node};
|
||||
pub use node_panel::NodePanel;
|
||||
pub use preview_pane::PreviewPane;
|
||||
pub use api_client::{ApiClient, Workflow, ExecutionResult};
|
||||
69
rust-frontend/src/main.rs
Normal file
69
rust-frontend/src/main.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use eframe::egui;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod node_editor;
|
||||
mod node_panel;
|
||||
mod preview_pane;
|
||||
mod api_client;
|
||||
|
||||
use node_editor::NodeEditor;
|
||||
use node_panel::NodePanel;
|
||||
use preview_pane::PreviewPane;
|
||||
use api_client::ApiClient;
|
||||
|
||||
struct ComfyUIApp {
|
||||
node_editor: NodeEditor,
|
||||
node_panel: NodePanel,
|
||||
preview_pane: PreviewPane,
|
||||
api_client: ApiClient,
|
||||
}
|
||||
|
||||
impl ComfyUIApp {
|
||||
fn new(_cc: &eframe::CreationContext) -> Self {
|
||||
Self {
|
||||
node_editor: NodeEditor::new(),
|
||||
node_panel: NodePanel::new(),
|
||||
preview_pane: PreviewPane::new(),
|
||||
api_client: ApiClient::new("http://localhost:8080".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for ComfyUIApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
// Main layout with three panes
|
||||
ui.horizontal(|ui| {
|
||||
// Left panel - Node selection
|
||||
ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| {
|
||||
self.node_panel.ui(ui);
|
||||
});
|
||||
|
||||
// Center panel - Node editor
|
||||
ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| {
|
||||
self.node_editor.ui(ui, &self.api_client);
|
||||
});
|
||||
|
||||
// Right panel - Preview pane
|
||||
ui.with_layout(egui::Layout::top_down_justified(egui::Align::RIGHT), |ui| {
|
||||
self.preview_pane.ui(ui, &self.api_client);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
let native_options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_title("ComfyUI Rust Frontend")
|
||||
.with_inner_size([1200.0, 800.0]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"ComfyUI Rust Frontend",
|
||||
native_options,
|
||||
Box::new(|cc| Box::new(ComfyUIApp::new(cc))),
|
||||
)
|
||||
}
|
||||
67
rust-frontend/src/node_editor.rs
Normal file
67
rust-frontend/src/node_editor.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use eframe::egui;
|
||||
use crate::api_client::ApiClient;
|
||||
|
||||
pub struct NodeEditor {
|
||||
nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Node {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub inputs: Vec<Input>,
|
||||
pub outputs: Vec<Output>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Input {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub node_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Output {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub node_id: String,
|
||||
}
|
||||
|
||||
impl NodeEditor {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
nodes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, api_client: &ApiClient) {
|
||||
ui.heading("Node Editor");
|
||||
|
||||
// Create a scroll area for the node editor
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
// Placeholder for actual node rendering logic
|
||||
ui.label("This is where the node-based workflow would be rendered.");
|
||||
ui.label("Nodes can be dragged and connected here.");
|
||||
|
||||
// Simple example of a node
|
||||
if ui.button("Add Node").clicked() {
|
||||
let new_node = Node {
|
||||
id: format!("node_{}", self.nodes.len()),
|
||||
name: "New Node".to_string(),
|
||||
x: 100.0 + (self.nodes.len() as f32 * 50.0),
|
||||
y: 100.0,
|
||||
inputs: vec![],
|
||||
outputs: vec![],
|
||||
};
|
||||
self.nodes.push(new_node);
|
||||
}
|
||||
|
||||
// Display existing nodes
|
||||
for node in &self.nodes {
|
||||
ui.label(format!("Node: {} at ({}, {})", node.name, node.x, node.y));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
47
rust-frontend/src/node_panel.rs
Normal file
47
rust-frontend/src/node_panel.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use eframe::egui;
|
||||
|
||||
pub struct NodePanel {
|
||||
selected_node_type: Option<String>,
|
||||
}
|
||||
|
||||
impl NodePanel {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
selected_node_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.set_min_width(200.0);
|
||||
ui.heading("Node Panel");
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Available node types
|
||||
ui.label("Available Nodes:");
|
||||
|
||||
let node_types = vec![
|
||||
"Image Loader",
|
||||
"Text Input",
|
||||
"Image Generator",
|
||||
"Image Save",
|
||||
"Conditional Node",
|
||||
"Loop Node"
|
||||
];
|
||||
|
||||
for node_type in node_types {
|
||||
if ui.button(node_type).clicked() {
|
||||
self.selected_node_type = Some(node_type.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Selected node info
|
||||
if let Some(selected) = &self.selected_node_type {
|
||||
ui.label(format!("Selected: {}", selected));
|
||||
} else {
|
||||
ui.label("No node selected");
|
||||
}
|
||||
}
|
||||
}
|
||||
53
rust-frontend/src/preview_pane.rs
Normal file
53
rust-frontend/src/preview_pane.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use eframe::egui;
|
||||
use crate::api_client::ApiClient;
|
||||
|
||||
pub struct PreviewPane {
|
||||
image_data: Option<Vec<u8>>,
|
||||
image_name: String,
|
||||
}
|
||||
|
||||
impl PreviewPane {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
image_data: None,
|
||||
image_name: "No image".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, api_client: &ApiClient) {
|
||||
ui.set_min_width(300.0);
|
||||
ui.heading("Preview Pane");
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Display current preview info
|
||||
ui.label(format!("Image: {}", self.image_name));
|
||||
|
||||
if let Some(data) = &self.image_data {
|
||||
// Try to display the image (simplified)
|
||||
ui.label("Image would be displayed here");
|
||||
|
||||
// Placeholder for actual image display logic
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Load Sample Image").clicked() {
|
||||
self.image_name = "sample_output.png".to_string();
|
||||
// In a real implementation, this would load actual image data
|
||||
self.image_data = Some(vec![0; 1024]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ui.label("No preview available");
|
||||
|
||||
if ui.button("Generate Preview").clicked() {
|
||||
self.image_name = "generated_output.png".to_string();
|
||||
// In a real implementation, this would fetch actual image data from backend
|
||||
self.image_data = Some(vec![0; 1024]);
|
||||
}
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Status info
|
||||
ui.label("Status: Ready");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user