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:
2026-03-03 00:44:51 +01:00
parent aefdcb38de
commit cd7a54f38b
10 changed files with 428 additions and 7 deletions

View 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
View 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
View 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))),
)
}

View 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));
}
});
}
}

View 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");
}
}
}

View 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");
}
}