Skip to content

Commit 3de0eae

Browse files
committed
Add procurement agent ui
1 parent 19a2568 commit 3de0eae

40 files changed

Lines changed: 3688 additions & 66 deletions

agentex-ui/README.md

Lines changed: 172 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,163 @@ A modern web interface for building, testing, and monitoring intelligent agents.
4747
- **Error Handling** - User-friendly error messages with toast notifications
4848
- **Dark Mode** - WCAG-compliant dark mode with proper contrast ratios
4949

50+
## Extended Features
51+
52+
This UI includes several enhancements beyond the standard Agentex capabilities:
53+
54+
### Artifact Panel
55+
56+
An interactive side panel for viewing rich content generated by agents:
57+
58+
- **PDF Viewer** - In-app PDF document viewing with navigation controls
59+
- **Map Viewer** - Interactive Google Maps integration for location-based data
60+
- **Data Tables** - Native database query results displayed in sortable tables
61+
- **Split View** - Resizable panel that allows simultaneous viewing of artifacts and chat
62+
63+
The artifact panel automatically detects and displays procurement events, documents, and location data from agent responses.
64+
65+
### Task Creation Without Messages
66+
67+
Start agent tasks using structured parameters instead of requiring an initial message:
68+
69+
- **Project Form Interface** - Create construction project tasks with dedicated input fields
70+
- **Direct Parameter Passing** - Pass structured data directly to agent initialization
71+
- **No Chat Required** - Begin tasks without sending an initial message
72+
- **Flexible Params** - Support for any agent-specific parameters via the params object
73+
74+
### Human-in-the-Loop Support
75+
76+
Interactive agent workflows that pause for human input, decisions, or approvals:
77+
78+
- **wait_for_human Tool Detection** - Automatically detects when agents request human input
79+
- **Inline Response Forms** - Displays contextual forms directly in the message stream
80+
- **Recommended Actions** - Shows agent's suggested actions or questions to guide responses
81+
- **Optimistic UI** - Instant feedback when submitting responses
82+
- **Keyboard Shortcuts** - Submit responses with Cmd/Ctrl + Enter
83+
- **Response State Tracking** - Clearly indicates when responses have been received
84+
85+
When an agent calls the `wait_for_human` tool with a `recommended_action` parameter, the UI automatically renders an interactive form allowing the user to provide input. Once submitted, the response is sent back to the agent to continue execution.
86+
87+
### Native Data API
88+
89+
A Next.js API route for querying external databases directly from the UI:
90+
91+
- **SQLite Integration** - Query SQLite databases via `app/api/data/[taskId]/route.ts`
92+
- **Custom Tables** - Access agent-specific data tables (schedules, procurement items, etc.)
93+
- **Type-Safe** - Fully typed responses with TypeScript interfaces
94+
- **Performance** - Direct database access without backend roundtrips
95+
96+
This enables agents to maintain their own data stores that the UI can query independently of the main Agentex API.
97+
98+
## Demo: Procurement Agent
99+
100+
To see these features in action, try the procurement agent demo from the Scale Agentex Python SDK:
101+
102+
<img src="./docs/images/procurement-agent.gif" alt="Procurement Agent" width="1280"/>
103+
104+
### Setup Instructions
105+
106+
1. **Clone the SDK repository:**
107+
108+
```bash
109+
git clone https://github.com/scaleapi/scale-agentex-python.git
110+
cd scale-agentex-python
111+
```
112+
113+
2. **Navigate to the procurement agent:**
114+
115+
```bash
116+
cd examples/demos/procurement_agent
117+
```
118+
119+
3. **Run the agent:**
120+
121+
```bash
122+
# Install dependencies
123+
uv sync
124+
125+
# Run the agent
126+
export ENVIRONMENT=development && uv run agentex agents run --manifest manifest.yaml
127+
```
128+
129+
4. **Configure the UI:**
130+
131+
In your `agentex-ui/.env.development`:
132+
133+
```bash
134+
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=your_google_maps_api_key
135+
NEXT_PUBLIC_SQLITE_DB_PATH=/path/to/procurement_agent/construction_project.db
136+
```
137+
138+
5. **Start a project:**
139+
140+
Open the UI, select the procurement agent, and create a new construction project. The agent will:
141+
- Track procurement items and their status
142+
- Display interactive maps of delivery locations
143+
- Show project schedules in data tables
144+
- Render submitted documents in the PDF viewer
145+
146+
6. **Copy the task id**
147+
148+
Copy the task id in the top left of the UI
149+
150+
<img src="./docs/images/task-id.png" alt="Task Id" width="1280"/>
151+
152+
7. **Send Test Events**
153+
154+
From this directory (`scale-agentex-python/examples/demos/procurement_agent`):
155+
156+
```bash
157+
# Navigate to the scripts directory
158+
cd project/scripts
159+
160+
# Send events (you'll be prompted for the task ID)
161+
uv run send_test_events.py
162+
```
163+
164+
**Learn More:** [Agentex Procurement Agent](https://github.com/scaleapi/scale-agentex-python/tree/main/examples/demos/procurement_agent)
165+
166+
### Using Human-in-the-Loop in Your Agents
167+
168+
To request human input in your agent code, use the `wait_for_human` tool:
169+
170+
```python
171+
# In your agent's tool definitions
172+
from agentex import tool
173+
174+
@tool
175+
def wait_for_human(recommended_action: str) -> str:
176+
"""
177+
Pause execution and wait for human input.
178+
179+
Args:
180+
recommended_action: A message or question to display to the user
181+
182+
Returns:
183+
The user's response text
184+
"""
185+
pass # Implementation handled by Agentex framework
186+
187+
# Example usage in agent logic
188+
response = wait_for_human(
189+
recommended_action="Should we proceed with ordering 500 units of steel beams at $50,000?"
190+
)
191+
192+
if "yes" in response.lower() or "approve" in response.lower():
193+
# Continue with the order
194+
place_order(item="steel beams", quantity=500)
195+
else:
196+
# Cancel or adjust the order
197+
cancel_order()
198+
```
199+
200+
The UI will automatically:
201+
202+
1. Detect the `wait_for_human` tool call
203+
2. Render an interactive form with the `recommended_action` message
204+
3. Wait for the user to submit their response
205+
4. Send the response back to the agent to continue execution
206+
50207
## Stack
51208

52209
- **Framework**: Next.js 15 (App Router), React 19, TypeScript
@@ -85,10 +242,24 @@ cp example.env.development .env.development
85242
Edit `.env.development` with your configuration:
86243

87244
```bash
88-
# Backend API endpoint
245+
# Required: Backend API endpoint
89246
NEXT_PUBLIC_AGENTEX_API_BASE_URL=http://localhost:5003
247+
248+
# Optional: Google Maps API key for map visualization features
249+
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=your_google_maps_api_key_here
250+
251+
# Optional: SQLite database path for native data API (used by procurement agent)
252+
NEXT_PUBLIC_SQLITE_DB_PATH=/path/to/your/database.db
90253
```
91254

255+
**Environment Variable Details:**
256+
257+
| Variable | Required | Description |
258+
| ---------------------------------- | -------- | -------------------------------------------------------------------------- |
259+
| `NEXT_PUBLIC_AGENTEX_API_BASE_URL` | Yes | Base URL for the Agentex backend API (default: `http://localhost:5003`) |
260+
| `NEXT_PUBLIC_GOOGLE_MAPS_API_KEY` | No | Google Maps API key for rendering map visualizations in the artifact panel |
261+
| `NEXT_PUBLIC_SQLITE_DB_PATH` | No | Path to SQLite database for native data queries (used by procurement demo) |
262+
92263
### 3. Install Dependencies
93264

94265
```bash
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
3+
import Database from 'better-sqlite3';
4+
5+
import { ScheduleData, ProcurementItem, TaskDataTables } from '@/lib/types';
6+
7+
export async function GET(
8+
_request: NextRequest,
9+
{ params }: { params: Promise<{ taskId: string }> }
10+
): Promise<NextResponse<TaskDataTables | { error: string }>> {
11+
try {
12+
const { taskId } = await params;
13+
14+
const dbPath = process.env.NEXT_PUBLIC_SQLITE_DB_PATH;
15+
16+
if (!dbPath) {
17+
return NextResponse.json(
18+
{ error: 'NEXT_PUBLIC_SQLITE_DB_PATH environment variable not set' },
19+
{ status: 500 }
20+
);
21+
}
22+
23+
const db = new Database(dbPath, { readonly: true });
24+
25+
try {
26+
const scheduleQuery = db.prepare(`
27+
SELECT workflow_id, project_name, project_start_date, project_end_date,
28+
schedule_json, created_at, updated_at
29+
FROM master_construction_schedule
30+
WHERE workflow_id = ?
31+
`);
32+
const schedule = scheduleQuery.get(taskId) as ScheduleData | undefined;
33+
34+
const procurementQuery = db.prepare(`
35+
SELECT workflow_id, item, status, eta, date_arrived,
36+
purchase_order_id, created_at, updated_at
37+
FROM procurement_items
38+
WHERE workflow_id = ?
39+
ORDER BY created_at DESC
40+
`);
41+
const procurementItems = procurementQuery.all(
42+
taskId
43+
) as ProcurementItem[];
44+
45+
return NextResponse.json({
46+
schedule: schedule || null,
47+
procurement_items: procurementItems,
48+
});
49+
} finally {
50+
db.close();
51+
}
52+
} catch (error) {
53+
console.error('Error querying SQLite database:', error);
54+
return NextResponse.json(
55+
{ error: error instanceof Error ? error.message : 'Unknown error' },
56+
{ status: 500 }
57+
);
58+
}
59+
}

agentex-ui/app/globals.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@
5252
--ring: #19202f;
5353
--secondary-foreground: #ffffff;
5454
--secondary: #45464f;
55+
--toastify-icon-color-success: var(--color-green-700);
56+
--toastify-color-progress-success: var(--color-green-700);
57+
--toastify-icon-color-error: var(--color-red-700);
58+
--toastify-color-progress-error: var(--color-red-700);
59+
--toastify-icon-color-warning: var(--color-yellow-700);
60+
--toastify-color-progress-warning: var(--color-yellow-700);
61+
--toastify-icon-color-info: var(--color-blue-700);
62+
--toastify-color-progress-info: var(--color-blue-700);
63+
--toastify-icon-color-default: var(--color-gray-700);
64+
--toastify-color-progress-default: var(--color-gray-700);
5565
}
5666

5767
.dark {

agentex-ui/app/layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Geist, Geist_Mono } from 'next/font/google';
22

33
import { QueryProvider, ThemeProvider } from '@/components/providers';
4+
import { ArtifactPanelProvider } from '@/contexts/artifact-panel-context';
45

56
import type { Metadata } from 'next';
67
import '@/app/globals.css';
@@ -37,7 +38,7 @@ export default function RootLayout({
3738
enableSystem={false}
3839
disableTransitionOnChange
3940
>
40-
{children}
41+
<ArtifactPanelProvider>{children}</ArtifactPanelProvider>
4142
</ThemeProvider>
4243
</QueryProvider>
4344
</body>

agentex-ui/components/agentex-ui-root.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import { useCallback, useEffect, useState } from 'react';
44

55
import { ToastContainer } from 'react-toastify';
66

7+
import { ArtifactPanel } from '@/components/artifacts/artifact-panel';
78
import { PrimaryContent } from '@/components/primary-content/primary-content';
89
import { useAgentexClient } from '@/components/providers';
910
import { TaskSidebar } from '@/components/task-sidebar/task-sidebar';
1011
import { TracesSidebar } from '@/components/traces-sidebar/traces-sidebar';
12+
import { useArtifactPanel } from '@/contexts/artifact-panel-context';
1113
import { useAgents } from '@/hooks/use-agents';
1214
import { useLocalStorageState } from '@/hooks/use-local-storage-state';
1315
import {
@@ -20,6 +22,7 @@ export function AgentexUIRoot() {
2022
const [isTracesSidebarOpen, setIsTracesSidebarOpen] = useState(false);
2123
const { agentexClient } = useAgentexClient();
2224
const { data: agents = [], isLoading } = useAgents(agentexClient);
25+
const { isOpen: isArtifactPanelOpen, closeArtifact } = useArtifactPanel();
2326
const [localAgentName, setLocalAgentName] = useLocalStorageState<
2427
string | undefined
2528
>('lastSelectedAgent', undefined);
@@ -41,6 +44,21 @@ export function AgentexUIRoot() {
4144
// eslint-disable-next-line react-hooks/exhaustive-deps
4245
}, [isLoading]);
4346

47+
// Close artifact when task or agent changes
48+
useEffect(() => {
49+
if (isArtifactPanelOpen) {
50+
closeArtifact();
51+
}
52+
// eslint-disable-next-line react-hooks/exhaustive-deps
53+
}, [taskID, agentName]);
54+
55+
// Close traces sidebar when artifact opens
56+
useEffect(() => {
57+
if (isArtifactPanelOpen && isTracesSidebarOpen) {
58+
setIsTracesSidebarOpen(false);
59+
}
60+
}, [isArtifactPanelOpen, isTracesSidebarOpen]);
61+
4462
const handleSelectTask = useCallback(
4563
(taskId: string | null) => {
4664
updateParams({
@@ -82,8 +100,10 @@ export function AgentexUIRoot() {
82100
toggleTracesSidebar={() =>
83101
setIsTracesSidebarOpen(!isTracesSidebarOpen)
84102
}
103+
isArtifactPanelOpen={isArtifactPanelOpen}
85104
/>
86105
<TracesSidebar isOpen={isTracesSidebarOpen} />
106+
<ArtifactPanel />
87107
</div>
88108
<ToastContainer />
89109
</>

0 commit comments

Comments
 (0)