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:
|
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)
|
- **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
|
- **GPU Acceleration**: ROCm integration for AMD RX 9070 XT GPU
|
||||||
|
|
||||||
### Key Features:
|
### Key Features:
|
||||||
@@ -50,7 +51,7 @@ rocminfo # Should show your GPU info including "gfx900" or similar architectu
|
|||||||
### Backend Setup (Rust):
|
### Backend Setup (Rust):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ComfyUI-Rust/backend
|
cd backend
|
||||||
cargo build --release # For production builds, use release mode for better performance on AMD GPUs
|
cargo build --release # For production builds, use release mode for better performance on AMD GPUs
|
||||||
|
|
||||||
# Run the backend server:
|
# Run the backend server:
|
||||||
@@ -60,10 +61,10 @@ Backend runs at: http://localhost:[PORT]
|
|||||||
API endpoints available after starting.
|
API endpoints available after starting.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Frontend Setup (React):
|
### Frontend Setup (React - currently has TypeScript conflict):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ComfyUI-Rust/frontend
|
cd frontend
|
||||||
npm install # Install dependencies
|
npm install # Install dependencies
|
||||||
|
|
||||||
Run dev mode with hot reload and AMD GPU preview support:
|
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
|
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:
|
## Project Structure Overview:
|
||||||
|
|
||||||
### Rust Backend (`backend/src`):
|
### Rust Backend (`backend/src`):
|
||||||
@@ -96,7 +107,7 @@ src/
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Frontend Web App (`frontend/src`):
|
### Original Frontend Web App (`frontend/src`):
|
||||||
```typescript/react
|
```typescript/react
|
||||||
src/
|
src/
|
||||||
├─ components/node-editor.tsx // Node-based workflow editor (graph canvas)
|
├─ 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:
|
## ROCm Integration Notes:
|
||||||
|
|
||||||
### Key Components:
|
### Key Components:
|
||||||
@@ -116,7 +138,7 @@ src/
|
|||||||
- Parallelism configured based on AMD GPU thread count (RX9070XT)
|
- Parallelism configured based on AMD GPU thread count (RX9070XT)
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Example configuration from rust/backend/src/config.rs
|
// Example configuration from backend/src/config.rs
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub gpu_backend_config = RocmConfig { // ROCm detection for RX900 series GPUs
|
pub gpu_backend_config = RocmConfig { // ROCm detection for RX900 series GPUs
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"@types/node": "^20.11.0",
|
"@types/node": "^20.11.0",
|
||||||
"@types/react": "^18.2.45",
|
"@types/react": "^18.2.45",
|
||||||
"@types/react-dom": "^18.2.18",
|
"@types/react-dom": "^18.2.18",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"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