Adding Building Progress Bar with DaisyUI and Radial Progress (#522)

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-06-22 00:20:01 +00:00 committed by GitHub
commit 05bd7b415a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 2378 additions and 1471 deletions

View file

@ -1,4 +1,3 @@
import json
from fastapi import (
APIRouter,
HTTPException,
@ -7,7 +6,7 @@ from fastapi import (
status,
)
from fastapi.responses import StreamingResponse
from langflow.api.v1.schemas import BuiltResponse, InitResponse
from langflow.api.v1.schemas import BuiltResponse, InitResponse, StreamData
from langflow.chat.manager import ChatManager
from langflow.graph.graph.base import Graph
@ -71,30 +70,37 @@ async def stream_build(flow_id: str):
"""Stream the build process based on stored flow data."""
async def event_stream(flow_id):
final_response = json.dumps({"end_of_stream": True})
final_response = {"end_of_stream": True}
try:
if flow_id not in flow_data_store:
error_message = "Invalid session ID"
yield f"data: {json.dumps({'error': error_message})}\n\n"
yield str(StreamData(event="error", data={"error": error_message}))
return
graph_data = flow_data_store[flow_id].get("data")
if not graph_data:
error_message = "No data provided"
yield f"data: {json.dumps({'error': error_message})}\n\n"
yield str(StreamData(event="error", data={"error": error_message}))
return
logger.debug("Building langchain object")
graph = Graph.from_payload(graph_data)
try:
# Some error could happen when building the graph
graph = Graph.from_payload(graph_data)
except Exception as exc:
logger.exception(exc)
error_message = str(exc)
yield str(StreamData(event="error", data={"error": error_message}))
return
number_of_nodes = len(graph.nodes)
for i, vertex in enumerate(graph.generator_build(), 1):
try:
log_dict = {
"log": f"Building node {vertex.vertex_type}",
"progress": round(i / number_of_nodes, 2),
}
yield f"data: {json.dumps(log_dict)}\n\n"
yield str(StreamData(event="log", data=log_dict))
vertex.build()
params = vertex._built_object_repr()
valid = True
@ -105,21 +111,21 @@ async def stream_build(flow_id: str):
params = str(exc)
valid = False
response = json.dumps(
{
"valid": valid,
"params": params,
"id": vertex.id,
}
)
yield f"data: {response}\n\n"
response = {
"valid": valid,
"params": params,
"id": vertex.id,
"progress": round(i / number_of_nodes, 2),
}
yield str(StreamData(event="message", data=response))
chat_manager.set_cache(flow_id, graph.build())
except Exception as exc:
logger.error("Error while building the flow: %s", exc)
yield f"error: {json.dumps({'error': str(exc)})}\n\n"
yield str(StreamData(event="error", data={"error": str(exc)}))
finally:
yield f"data: {final_response}\n\n"
yield str(StreamData(event="message", data=final_response))
try:
return StreamingResponse(event_stream(flow_id), media_type="text/event-stream")

View file

@ -1,6 +1,7 @@
from typing import Any, Dict, List, Optional, Union
from langflow.database.models.flow import FlowCreate, FlowRead
from pydantic import BaseModel, Field, validator
import json
class GraphData(BaseModel):
@ -101,3 +102,11 @@ class InitResponse(BaseModel):
class BuiltResponse(BaseModel):
built: bool
class StreamData(BaseModel):
event: str
data: dict
def __str__(self) -> str:
return f"event: {self.event}\ndata: {json.dumps(self.data)}\n\n"

File diff suppressed because it is too large Load diff

View file

@ -8,15 +8,16 @@
"@headlessui/react": "^1.7.10",
"@heroicons/react": "^2.0.15",
"@mui/material": "^5.11.9",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-menubar": "^1.0.3",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-menubar": "^1.0.3",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.6",
"@tabler/icons-react": "^2.18.0",
"@tailwindcss/forms": "^0.5.3",
@ -98,6 +99,7 @@
"@types/uuid": "^9.0.1",
"@vitejs/plugin-react-swc": "^3.0.0",
"autoprefixer": "^10.4.14",
"daisyui": "^3.1.1",
"postcss": "^8.4.23",
"tailwindcss": "^3.3.2",
"typescript": "^5.0.2",

View file

@ -0,0 +1,21 @@
import { ReactElement, useContext, useEffect, useRef, useState } from "react";
import { ProgressBarType } from "../../types/components";
import { Progress } from "../../components/ui/progress";
import { progressContext } from "../../contexts/ProgressContext";
import { setInterval } from "timers/promises";
export default function ProgressBarComponent({
value,
children,
}: ProgressBarType) {
const ref = useRef(0);
const reff = useRef();
const { progress } = useContext(progressContext);
useEffect(() => {
ref.current = progress * 100;
console.log(progress);
}, [progress]);
return <Progress className="h-2.5" value={ref.current} />;
}

View file

@ -0,0 +1,19 @@
import { useContext, useEffect, useRef, useState } from "react";
import { RadialProgressType } from "../../types/components";
export default function RadialProgressComponent({
value,
color,
}: RadialProgressType) {
const style = {
"--value": value * 100,
"--size": "1.5rem",
"--thickness": "2px",
} as React.CSSProperties;
return (
<div className={"radial-progress " + color} style={style}>
<strong className="text-[8px]">{value * 100}%</strong>
</div>
);
}

View file

@ -1,4 +1,4 @@
import { useContext } from "react";
import { useContext, useState } from "react";
import { Transition } from "@headlessui/react";
import { Zap } from "lucide-react";
import { validateNodes } from "../../../utils";
@ -9,6 +9,8 @@ import { typesContext } from "../../../contexts/typesContext";
import { alertContext } from "../../../contexts/alertContext";
import { postBuildInit } from "../../../controllers/API";
import RadialProgressComponent from "../../RadialProgress";
export default function BuildTrigger({
open,
flow,
@ -20,9 +22,12 @@ export default function BuildTrigger({
setIsBuilt: any;
isBuilt: boolean;
}) {
const { updateSSEData, isBuilding, setIsBuilding } = useSSE();
const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE();
const { reactFlowInstance } = useContext(typesContext);
const { setErrorData, setSuccessData } = useContext(alertContext);
const [isIconTouched, setIsIconTouched] = useState(false);
const eventClick = isBuilding ? "pointer-events-none" : "";
const [progress, setProgress] = useState(0);
async function handleBuild(flow: FlowType) {
try {
@ -58,12 +63,10 @@ export default function BuildTrigger({
setIsBuilding(false);
}
}
async function streamNodeData(flow: FlowType) {
// Step 1: Make a POST request to send the flow data and receive a unique session ID
const response = await postBuildInit(flow);
const { flowId } = response.data;
// Step 2: Use the session ID to establish an SSE connection using EventSource
let validationResults = [];
let finished = false;
@ -83,19 +86,23 @@ export default function BuildTrigger({
return;
} else if (parsedData.log) {
// If the event is a log, log it
// TODO: implement the progress
setSuccessData({ title: parsedData.log });
setSuccessData({ title: parsedData.progress });
} else {
// Otherwise, process the data
const isValid = processStreamResult(parsedData);
setProgress(parsedData.progress);
validationResults.push(isValid);
}
};
eventSource.onerror = (error) => {
eventSource.onerror = (error: any) => {
console.error("EventSource failed:", error);
eventSource.close();
if (error.data) {
const parsedData = JSON.parse(error.data);
setErrorData({ title: parsedData.error });
setIsBuilding(false);
}
};
// Step 3: Wait for the stream to finish
while (!finished) {
@ -129,6 +136,14 @@ export default function BuildTrigger({
}
}
const handleMouseEnter = () => {
setIsIconTouched(true);
};
const handleMouseLeave = () => {
setIsIconTouched(false);
};
return (
<Transition
show={!open}
@ -142,17 +157,23 @@ export default function BuildTrigger({
>
<div className={`fixed right-4` + (isBuilt ? " bottom-20" : " bottom-4")}>
<div
className="flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full shadow-md shadow-[#0000002a] hover:shadow-[#00000032]
bg-[#E2E7EE] dark:border-gray-600 cursor-pointer"
className={`${eventClick} flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full shadow-md shadow-[#0000002a] hover:shadow-[#00000032] bg-[#E2E7EE] dark:border-gray-600 cursor-pointer`}
onClick={() => {
handleBuild(flow);
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<button>
<div className="flex gap-3 items-center">
{isBuilding ? (
{isBuilding && progress < 1 ? (
// Render your loading animation here when isBuilding is true
<Loading strokeWidth={1.5} style={{ color: "white" }} />
<RadialProgressComponent
color={"text-orange-400"}
value={progress}
></RadialProgressComponent>
) : isBuilding ? (
<Loading strokeWidth={1.5} style={{ color: "#fb923c" }} />
) : (
<Zap className="sh-6 w-6 fill-orange-400 stroke-1 stroke-orange-400" />
)}

View file

@ -70,35 +70,35 @@ export default function Header() {
</div>
<div className="flex justify-end px-2 w-96">
<div className="ml-auto mr-2 flex gap-5 items-center">
<a
href="https://github.com/logspace-ai/langflow"
target="_blank"
rel="noreferrer"
className="inline-flex shadow-sm items-center justify-center text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background text-gray-600 dark:text-gray-300 border border-input hover:bg-accent hover:text-accent-foreground h-9 px-3 pr-0 rounded-md"
>
<FaGithub className="h-5 w-5 mr-2" />
Star
<div className="ml-2 flex text-sm bg-background rounded-md rounded-l-none border px-2 h-9 -mr-px items-center justify-center">
{stars}
</div>
</a>
<a
href="https://twitter.com/logspace_ai"
target="_blank"
rel="noreferrer"
className="text-muted-foreground"
>
<FaTwitter className="h-5 w-5" />
</a>
<a
href="https://discord.gg/EqksyE2EX9"
target="_blank"
rel="noreferrer"
className="text-muted-foreground"
>
<FaDiscord className="h-5 w-5" />
</a>
<Separator orientation="vertical" />
<a
href="https://github.com/logspace-ai/langflow"
target="_blank"
rel="noreferrer"
className="inline-flex shadow-sm items-center justify-center text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background text-gray-600 dark:text-gray-300 border border-input hover:bg-accent hover:text-accent-foreground h-9 px-3 pr-0 rounded-md"
>
<FaGithub className="h-5 w-5 mr-2" />
Star
<div className="ml-2 flex text-sm bg-background rounded-md rounded-l-none border px-2 h-9 -mr-px items-center justify-center">
{stars}
</div>
</a>
<a
href="https://twitter.com/logspace_ai"
target="_blank"
rel="noreferrer"
className="text-muted-foreground"
>
<FaTwitter className="h-5 w-5" />
</a>
<a
href="https://discord.gg/EqksyE2EX9"
target="_blank"
rel="noreferrer"
className="text-muted-foreground"
>
<FaDiscord className="h-5 w-5" />
</a>
{/* <Separator orientation="vertical" />
<button
className="text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200"
onClick={() => {
@ -110,7 +110,7 @@ export default function Header() {
) : (
<MoonIcon className="h-5 w-5" />
)}
</button>
</button> */}
<button
className="text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200 relative"
onClick={(event: React.MouseEvent<HTMLElement>) => {

View file

@ -0,0 +1,27 @@
"use client";
import * as React from "react";
import * as ProgressPrimitive from "@radix-ui/react-progress";
import { cn } from "../../utils";
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
));
Progress.displayName = ProgressPrimitive.Root.displayName;
export { Progress };

View file

@ -22,7 +22,9 @@ const GITHUB_API_URL = "https://api.github.com";
export async function getRepoStars(owner, repo) {
try {
const response = await axios.get(`${GITHUB_API_URL}/repos/${owner}/${repo}`);
const response = await axios.get(
`${GITHUB_API_URL}/repos/${owner}/${repo}`
);
return response.data.stargazers_count;
} catch (error) {
console.error("Error fetching repository data:", error);

View file

@ -98,3 +98,14 @@ export type TooltipComponentType = {
| "top-start"
| "top";
};
export type ProgressBarType = {
children?: ReactElement;
value?: number;
max?: number;
};
export type RadialProgressType = {
value?: number;
color?: string;
};

View file

@ -241,5 +241,6 @@ module.exports = {
});
}),
require("@tailwindcss/typography"),
require("daisyui"),
],
};