Skip to content

Commit 043599d

Browse files
authored
Simplify basic templates and sync (#23)
1 parent 8ac00d5 commit 043599d

File tree

2 files changed

+47
-214
lines changed

2 files changed

+47
-214
lines changed

src/app/workflow.py

Lines changed: 9 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,19 @@
1-
import asyncio
2-
import time
3-
41
from workflows import Context, Workflow, step
5-
from workflows.events import (
6-
HumanResponseEvent,
7-
InputRequiredEvent,
8-
StartEvent,
9-
StopEvent,
10-
Event,
11-
)
12-
import logging
13-
from datetime import datetime
14-
15-
logger = logging.getLogger(__name__)
16-
17-
18-
class PingEvent(StartEvent):
19-
message: str
20-
21-
22-
class PongEvent(Event):
23-
message: str
24-
25-
26-
class WorkflowCompletedEvent(StopEvent):
27-
timestamp: str
28-
29-
30-
class PauseEvent(InputRequiredEvent):
31-
timestamp: str
2+
from workflows.events import StartEvent, StopEvent
323

334

34-
class ResumeEvent(HumanResponseEvent):
35-
should_continue: bool
5+
class Start(StartEvent):
6+
pass
367

378

38-
class OkGoEvent(Event):
39-
message: str
40-
41-
42-
class DefaultWorkflow(Workflow):
9+
class BasicWorkflow(Workflow):
4310
@step
44-
async def start(self, event: PingEvent, context: Context) -> OkGoEvent:
45-
return OkGoEvent(message="OK GO")
46-
47-
@step
48-
async def loop(
49-
self, event: ResumeEvent | OkGoEvent, context: Context
50-
) -> PauseEvent | WorkflowCompletedEvent:
51-
if isinstance(event, ResumeEvent) and not event.should_continue:
52-
return WorkflowCompletedEvent(
53-
timestamp="workflow completed at "
54-
+ datetime.now().isoformat(timespec="seconds")
55-
)
56-
start = time.monotonic()
57-
logger.info(f"Received message!!!!!: {event}")
58-
for i in range(5):
59-
logger.info(f"Processing message: {event} {i}")
60-
elapsed = (time.monotonic() - start) * 1000
61-
context.write_event_to_stream(
62-
PongEvent(message=f"+{elapsed:.0f}ms PONG {i + 1}/5 ")
11+
async def hello(self, event: Start, context: Context) -> StopEvent:
12+
return StopEvent(
13+
result=(
14+
"Hello from the basic-ui backend. Edit src/app/workflow.py to get started."
6315
)
64-
await asyncio.sleep(0.2)
65-
66-
return PauseEvent(
67-
timestamp="workflow paused at "
68-
+ datetime.now().isoformat(timespec="seconds")
6916
)
7017

7118

72-
workflow = DefaultWorkflow(timeout=None)
73-
74-
if __name__ == "__main__":
75-
logging.basicConfig(level=logging.INFO)
76-
77-
async def main():
78-
print(await DefaultWorkflow().run(message="Hello!"))
79-
80-
asyncio.run(main())
19+
workflow = BasicWorkflow(timeout=None)

ui/src/pages/Home.tsx

Lines changed: 38 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1,155 +1,49 @@
11
import { useState } from "react";
2-
import {
3-
type WorkflowEvent,
4-
useWorkflowHandler,
5-
useWorkflowRun,
6-
} from "@llamaindex/ui";
2+
import { useWorkflowRun, useWorkflowHandler } from "@llamaindex/ui";
73

84
export default function Home() {
9-
const [taskId, setTaskId] = useState<string | null>(null);
10-
const createHandler = useWorkflowRun();
5+
const [handlerId, setHandlerId] = useState<string | null>(null);
6+
const run = useWorkflowRun();
7+
const handler = useWorkflowHandler(handlerId ?? "");
8+
9+
const result = handler.events.find((e) => e.type.endsWith(".StopEvent")) as
10+
| { type: string; data: { result?: string } }
11+
| undefined;
12+
1113
return (
12-
<div className="aurora-container relative min-h-screen overflow-hidden bg-background text-foreground">
13-
<main className="relative mx-auto flex min-h-screen max-w-2xl px-6 flex-col gap-4 items-center justify-center my-12">
14-
<div className="text-center mb-4 text-black/80 dark:text-white/80 text-lg">
15-
This is a basic starter app for LlamaDeploy. It's running a simple
16-
Human-in-the-loop workflow on the backend, and vite with react on the
17-
frontend with llama-ui to call the workflow. Customize this app with
18-
your own workflow and UI.
19-
</div>
20-
<div className="flex flex-row gap-4 items-start justify-center w-full">
21-
<HandlerOutput handlerId={taskId} />
22-
<RunButton
23-
disabled={createHandler.isCreating}
14+
<div className="relative min-h-screen flex items-center justify-center p-6">
15+
<div className="max-w-2xl text-center text-black/80 dark:text-white/80 flex flex-col gap-4">
16+
<p className="text-lg">
17+
This is a minimal UI starter. Click the button to run the backend
18+
workflow and display its result.
19+
</p>
20+
<div className="flex items-center justify-center gap-3">
21+
<button
22+
type="button"
23+
disabled={run.isCreating}
2424
onClick={() =>
25-
createHandler
26-
.runWorkflow("default", {
27-
message: `${new Date().toLocaleTimeString()} PING`,
28-
})
29-
.then((task) => setTaskId(task.handler_id))
25+
run
26+
.runWorkflow("default", {})
27+
.then((h) => setHandlerId(h.handler_id))
3028
}
29+
className="inline-flex items-center justify-center rounded-xl border px-6 py-3 text-sm font-semibold shadow-sm border-black/10 bg-black/5 text-black hover:bg-black/10 dark:border-white/10 dark:bg-white/10 dark:text-white"
3130
>
32-
<GreenDot />
33-
Run
34-
</RunButton>
31+
Run Workflow
32+
</button>
3533
</div>
36-
</main>
37-
</div>
38-
);
39-
}
40-
41-
const GreenDot = () => {
42-
return (
43-
<span className="mr-2 size-2 rounded-full bg-emerald-500/80 shadow-[0_0_20px_2px_rgba(16,185,129,0.35)] transition group-hover:bg-emerald-400/90"></span>
44-
);
45-
};
46-
47-
function RunButton({
48-
disabled,
49-
children,
50-
onClick,
51-
}: {
52-
disabled: boolean;
53-
children: React.ReactNode;
54-
onClick: () => void;
55-
}) {
56-
return (
57-
<button
58-
type="button"
59-
disabled={disabled}
60-
onClick={onClick}
61-
className="group inline-flex items-center justify-center rounded-xl border px-6 py-3 text-sm font-semibold shadow-sm backdrop-blur transition active:scale-[.99]
62-
border-black/10 bg-black/5 text-black hover:bg-black/10 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-black/20
63-
dark:border-white/10 dark:bg-white/10 dark:text-white dark:hover:bg-white/15 dark:focus:ring-white/30 cursor-pointer"
64-
>
65-
{children}
66-
</button>
67-
);
68-
}
69-
70-
type PongEvent = { type: `${string}.PongEvent`; data: { message: string } };
71-
type PauseEvent = { type: `${string}.PauseEvent`; data: { timestamp: string } };
72-
73-
function isPongEvent(event: WorkflowEvent): event is PongEvent {
74-
return event.type.endsWith(".PongEvent");
75-
}
76-
function isPauseEvent(event: WorkflowEvent): event is PauseEvent {
77-
return event.type.endsWith(".PauseEvent");
78-
}
79-
80-
function HandlerOutput({ handlerId }: { handlerId: string | null }) {
81-
// stream events and result from the workflow
82-
const handler = useWorkflowHandler(handlerId ?? "");
83-
84-
// read workflow events here
85-
const pongsOrResume = handler.events.filter(
86-
(event) => isPongEvent(event) || isPauseEvent(event),
87-
) as (PongEvent | PauseEvent)[];
88-
const completed = handler.events.find((event) =>
89-
event.type.endsWith(".WorkflowCompletedEvent"),
90-
) as { type: string; data: { timestamp: string } } | undefined;
91-
92-
return (
93-
<div className="flex flex-col gap-4 w-full min-h-60 items-center">
94-
<Output>{completed ? completed.data.timestamp : "Running... "}</Output>
95-
{pongsOrResume.map((pong, index) => (
96-
<span
97-
className="inline-flex items-center px-2 py-0.5 text-xs font-medium bg-black/3
98-
dark:bg-white/2 text-black/60 dark:text-white/60 rounded border border-black/5
99-
dark:border-white/5 backdrop-blur-sm"
100-
key={index}
101-
style={{
102-
animation: "fade-in-left 80ms ease-out both",
103-
willChange: "opacity, transform",
104-
}}
105-
>
106-
{isPongEvent(pong) ? pong.data.message : pong.data.timestamp}
107-
{isPauseEvent(pong) &&
108-
index === pongsOrResume.length - 1 &&
109-
!completed && (
110-
<button
111-
onClick={() =>
112-
handler.sendEvent({
113-
type: "app.workflow.ResumeEvent",
114-
data: { should_continue: true },
115-
})
116-
}
117-
className="ml-2 px-2 py-0.5 bg-black/10 hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20
118-
text-black/80 dark:text-white/80 text-xs rounded border border-black/10 dark:border-white/10"
119-
>
120-
Resume?
121-
</button>
122-
)}
123-
</span>
124-
))}
125-
{!completed && pongsOrResume.length > 0 && (
126-
<button
127-
onClick={() =>
128-
handler.sendEvent({
129-
type: "app.workflow.ResumeEvent",
130-
data: { should_continue: false },
131-
})
132-
}
133-
className="ml-2 px-2 py-0.5 bg-black/10 hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20
134-
text-black/80 dark:text-white/80 text-xs rounded border border-black/10 dark:border-white/10"
135-
>
136-
Stop
137-
</button>
138-
)}
139-
</div>
140-
);
141-
}
142-
143-
function Output({ children }: { children: React.ReactNode }) {
144-
return (
145-
<div
146-
aria-live="polite"
147-
className="w-full rounded-xl border bg-black/5 p-4 text-left shadow-[inset_0_1px_0_0_rgba(255,255,255,0.06)]
148-
border-black/10 dark:border-white/10 dark:bg-white/5"
149-
>
150-
<pre className="whitespace-pre-wrap break-words font-mono text-xs text-black/80 dark:text-white/80">
151-
{children}
152-
</pre>
34+
<div className="text-sm">
35+
{handlerId && (
36+
<div>
37+
Handler: <code>{handlerId}</code>
38+
</div>
39+
)}
40+
{result?.data?.result && (
41+
<div className="mt-2">
42+
Result: <code>{result.data.result}</code>
43+
</div>
44+
)}
45+
</div>
46+
</div>
15347
</div>
15448
);
15549
}

0 commit comments

Comments
 (0)