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:
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