Skip to main content

UI Integration Guide

Complete guide for integrating user interfaces with Pixell agents.

Note: This documentation is generated by AI based on the source code, and therefore it may have some incorrect knowledge of the project. In that case, please contact engineering@pixell.global

Prerequisites

Before integrating with user interfaces, ensure you have:

  • PAR Runtime - PAR Installation running with your agent
  • Frontend Framework - React, Vue, Angular, or vanilla JavaScript
  • HTTP Client - Axios, Fetch API, or similar for API calls
  • WebSocket Support - For real-time communication (optional)
  • Agent Endpoints - REST API endpoints configured in your agent

Architecture Overview

The integration follows this flow:

  1. User Interaction → Frontend captures user input
  2. API Request → Frontend sends HTTP request to PAR
  3. Agent Processing → PAR forwards to agent via gRPC
  4. Response → Agent processes and returns result
  5. UI Update → Frontend updates interface with response

Step 1: Setup Frontend Project

1.1 Create React Project

# Create new React app
npx create-react-app pixell-agent-ui
cd pixell-agent-ui

# Install additional dependencies
npm install axios socket.io-client
npm install @types/axios --save-dev

1.2 Create Vue Project

# Create new Vue app
npm create vue@latest pixell-agent-ui
cd pixell-agent-ui

# Install dependencies
npm install axios socket.io-client

1.3 Project Structure

pixell-agent-ui/
├── src/
│ ├── components/ # UI components
│ │ ├── AgentChat.jsx # Chat interface
│ │ ├── AgentStatus.jsx # Status display
│ │ └── AgentConfig.jsx # Configuration
│ ├── services/ # API services
│ │ ├── agentApi.js # Agent API client
│ │ └── websocket.js # WebSocket client
│ ├── hooks/ # Custom hooks (React)
│ │ └── useAgent.js # Agent state management
│ ├── store/ # State management
│ │ └── agentStore.js # Agent state store
│ └── utils/ # Utility functions
│ └── helpers.js # Helper functions

Step 2: Connect to PAR API

2.1 Create API Client

React/JavaScript:

// src/services/agentApi.js
import axios from 'axios';

class AgentAPI {
constructor(baseURL = 'http://localhost:8080') {
this.client = axios.create({
baseURL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
}
});

// Add request interceptor for authentication
this.client.interceptors.request.use(
(config) => {
const token = localStorage.getItem('agent_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);

// Add response interceptor for error handling
this.client.interceptors.response.use(
(response) => response,
(error) => {
console.error('API Error:', error.response?.data || error.message);
return Promise.reject(error);
}
);
}

// Health check
async healthCheck() {
const response = await this.client.get('/health');
return response.data;
}

// Get agent capabilities
async getCapabilities() {
const response = await this.client.get('/capabilities');
return response.data;
}

// Send message to agent
async sendMessage(message, context = {}) {
const response = await this.client.post('/process', {
type: 'user_message',
data: {
message,
context
}
});
return response.data;
}

// Get agent status
async getStatus() {
const response = await this.client.get('/status');
return response.data;
}

// Upload file to agent
async uploadFile(file, metadata = {}) {
const formData = new FormData();
formData.append('file', file);
formData.append('metadata', JSON.stringify(metadata));

const response = await this.client.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
}
});
return response.data;
}
}

export default new AgentAPI();

Vue/JavaScript:

// src/services/agentApi.js
import axios from 'axios';

export const useAgentAPI = () => {
const client = axios.create({
baseURL: 'http://localhost:8080',
timeout: 30000,
headers: {
'Content-Type': 'application/json',
}
});

const healthCheck = async () => {
const response = await client.get('/health');
return response.data;
};

const sendMessage = async (message, context = {}) => {
const response = await client.post('/process', {
type: 'user_message',
data: { message, context }
});
return response.data;
};

const getCapabilities = async () => {
const response = await client.get('/capabilities');
return response.data;
};

return {
healthCheck,
sendMessage,
getCapabilities
};
};

2.2 TypeScript Support

// src/types/agent.ts
export interface AgentResponse {
success: boolean;
result?: any;
error?: string;
metadata?: {
agent: string;
version: string;
timestamp: string;
};
}

export interface AgentMessage {
type: string;
data: {
message: string;
context?: Record<string, any>;
};
}

export interface AgentCapabilities {
capabilities: string[];
description: string;
version: string;
}

export interface AgentStatus {
status: 'healthy' | 'unhealthy' | 'degraded';
uptime: string;
version: string;
capabilities: string[];
}
// src/services/agentApi.ts
import axios, { AxiosInstance, AxiosResponse } from 'axios';
import { AgentResponse, AgentMessage, AgentCapabilities, AgentStatus } from '../types/agent';

class AgentAPI {
private client: AxiosInstance;

constructor(baseURL: string = 'http://localhost:8080') {
this.client = axios.create({
baseURL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
}
});
}

async healthCheck(): Promise<AgentStatus> {
const response: AxiosResponse<AgentStatus> = await this.client.get('/health');
return response.data;
}

async getCapabilities(): Promise<AgentCapabilities> {
const response: AxiosResponse<AgentCapabilities> = await this.client.get('/capabilities');
return response.data;
}

async sendMessage(message: string, context: Record<string, any> = {}): Promise<AgentResponse> {
const payload: AgentMessage = {
type: 'user_message',
data: { message, context }
};

const response: AxiosResponse<AgentResponse> = await this.client.post('/process', payload);
return response.data;
}
}

export default new AgentAPI();

Step 3: Handle Agent Responses

3.1 React Hook for Agent State

// src/hooks/useAgent.js
import { useState, useEffect, useCallback } from 'react';
import agentAPI from '../services/agentApi';

export const useAgent = () => {
const [status, setStatus] = useState('disconnected');
const [capabilities, setCapabilities] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const [messages, setMessages] = useState([]);

// Initialize agent connection
useEffect(() => {
initializeAgent();
}, []);

const initializeAgent = async () => {
try {
setIsLoading(true);
setError(null);

// Check health
const health = await agentAPI.healthCheck();
setStatus(health.status);

// Get capabilities
const caps = await agentAPI.getCapabilities();
setCapabilities(caps.capabilities);

} catch (err) {
setError(err.message);
setStatus('error');
} finally {
setIsLoading(false);
}
};

const sendMessage = useCallback(async (message, context = {}) => {
try {
setIsLoading(true);
setError(null);

// Add user message to chat
const userMessage = {
id: Date.now(),
type: 'user',
content: message,
timestamp: new Date().toISOString()
};
setMessages(prev => [...prev, userMessage]);

// Send to agent
const response = await agentAPI.sendMessage(message, context);

// Add agent response to chat
const agentMessage = {
id: Date.now() + 1,
type: 'agent',
content: response.result || response.error,
timestamp: new Date().toISOString(),
success: response.success
};
setMessages(prev => [...prev, agentMessage]);

return response;

} catch (err) {
setError(err.message);

// Add error message to chat
const errorMessage = {
id: Date.now(),
type: 'error',
content: err.message,
timestamp: new Date().toISOString()
};
setMessages(prev => [...prev, errorMessage]);

} finally {
setIsLoading(false);
}
}, []);

const clearMessages = useCallback(() => {
setMessages([]);
}, []);

return {
status,
capabilities,
isLoading,
error,
messages,
sendMessage,
clearMessages,
initializeAgent
};
};

3.2 Vue Composable for Agent State

// src/composables/useAgent.js
import { ref, onMounted } from 'vue';
import { useAgentAPI } from '../services/agentApi';

export const useAgent = () => {
const status = ref('disconnected');
const capabilities = ref([]);
const isLoading = ref(false);
const error = ref(null);
const messages = ref([]);

const agentAPI = useAgentAPI();

const initializeAgent = async () => {
try {
isLoading.value = true;
error.value = null;

const health = await agentAPI.healthCheck();
status.value = health.status;

const caps = await agentAPI.getCapabilities();
capabilities.value = caps.capabilities;

} catch (err) {
error.value = err.message;
status.value = 'error';
} finally {
isLoading.value = false;
}
};

const sendMessage = async (message, context = {}) => {
try {
isLoading.value = true;
error.value = null;

// Add user message
const userMessage = {
id: Date.now(),
type: 'user',
content: message,
timestamp: new Date().toISOString()
};
messages.value.push(userMessage);

// Send to agent
const response = await agentAPI.sendMessage(message, context);

// Add agent response
const agentMessage = {
id: Date.now() + 1,
type: 'agent',
content: response.result || response.error,
timestamp: new Date().toISOString(),
success: response.success
};
messages.value.push(agentMessage);

return response;

} catch (err) {
error.value = err.message;

const errorMessage = {
id: Date.now(),
type: 'error',
content: err.message,
timestamp: new Date().toISOString()
};
messages.value.push(errorMessage);

} finally {
isLoading.value = false;
}
};

const clearMessages = () => {
messages.value = [];
};

onMounted(() => {
initializeAgent();
});

return {
status,
capabilities,
isLoading,
error,
messages,
sendMessage,
clearMessages,
initializeAgent
};
};

Step 4: Real-time Updates

4.1 WebSocket Integration

// src/services/websocket.js
class AgentWebSocket {
constructor(url = 'ws://localhost:8080/ws') {
this.url = url;
this.socket = null;
this.listeners = new Map();
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1000;
}

connect() {
try {
this.socket = new WebSocket(this.url);

this.socket.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
this.emit('connected');
};

this.socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.emit('message', data);
} catch (error) {
console.error('Error parsing WebSocket message:', error);
}
};

this.socket.onclose = () => {
console.log('WebSocket disconnected');
this.emit('disconnected');
this.handleReconnect();
};

this.socket.onerror = (error) => {
console.error('WebSocket error:', error);
this.emit('error', error);
};

} catch (error) {
console.error('Error connecting WebSocket:', error);
this.handleReconnect();
}
}

handleReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);

setTimeout(() => {
console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
this.connect();
}, delay);
} else {
console.error('Max reconnection attempts reached');
this.emit('max_reconnect_attempts');
}
}

send(data) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(data));
} else {
console.warn('WebSocket not connected');
}
}

on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}

off(event, callback) {
if (this.listeners.has(event)) {
const callbacks = this.listeners.get(event);
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
}

emit(event, data) {
if (this.listeners.has(event)) {
this.listeners.get(event).forEach(callback => callback(data));
}
}

disconnect() {
if (this.socket) {
this.socket.close();
this.socket = null;
}
}
}

export default new AgentWebSocket();

4.2 React WebSocket Hook

// src/hooks/useWebSocket.js
import { useEffect, useRef } from 'react';
import websocket from '../services/websocket';

export const useWebSocket = (onMessage, onStatusChange) => {
const websocketRef = useRef(websocket);

useEffect(() => {
const ws = websocketRef.current;

const handleMessage = (data) => {
if (onMessage) {
onMessage(data);
}
};

const handleStatusChange = (status) => {
if (onStatusChange) {
onStatusChange(status);
}
};

ws.on('message', handleMessage);
ws.on('connected', () => handleStatusChange('connected'));
ws.on('disconnected', () => handleStatusChange('disconnected'));
ws.on('error', (error) => handleStatusChange('error'));

ws.connect();

return () => {
ws.off('message', handleMessage);
ws.off('connected', () => handleStatusChange('connected'));
ws.off('disconnected', () => handleStatusChange('disconnected'));
ws.off('error', (error) => handleStatusChange('error'));
ws.disconnect();
};
}, [onMessage, onStatusChange]);

const sendMessage = (data) => {
websocketRef.current.send(data);
};

return { sendMessage };
};

Step 5: Error Handling

5.1 Error Boundary Component

// src/components/ErrorBoundary.jsx
import React from 'react';

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}

static getDerivedStateFromError(error) {
return { hasError: true, error };
}

componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}

render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong</h2>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false, error: null })}>
Try again
</button>
</div>
);
}

return this.props.children;
}
}

export default ErrorBoundary;

5.2 Error Handling Utilities

// src/utils/errorHandler.js
export const handleAgentError = (error) => {
if (error.response) {
// Server responded with error status
const { status, data } = error.response;

switch (status) {
case 400:
return `Bad Request: ${data.message || 'Invalid request'}`;
case 401:
return 'Unauthorized: Please check your authentication';
case 403:
return 'Forbidden: You don\'t have permission to access this resource';
case 404:
return 'Not Found: Agent endpoint not found';
case 429:
return 'Rate Limited: Too many requests, please try again later';
case 500:
return 'Server Error: Internal server error occurred';
case 503:
return 'Service Unavailable: Agent is temporarily unavailable';
default:
return `Error ${status}: ${data.message || 'Unknown error'}`;
}
} else if (error.request) {
// Request was made but no response received
return 'Network Error: Unable to connect to agent';
} else {
// Something else happened
return `Error: ${error.message}`;
}
};

export const isRetryableError = (error) => {
if (error.response) {
const status = error.response.status;
return status >= 500 || status === 429;
}
return true; // Network errors are retryable
};

export const retryRequest = async (requestFn, maxRetries = 3, delay = 1000) => {
for (let i = 0; i < maxRetries; i++) {
try {
return await requestFn();
} catch (error) {
if (i === maxRetries - 1 || !isRetryableError(error)) {
throw error;
}

await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
}
}
};

Complete Example

React Example

// src/components/AgentChat.jsx
import React, { useState, useRef, useEffect } from 'react';
import { useAgent } from '../hooks/useAgent';
import { useWebSocket } from '../hooks/useWebSocket';
import { handleAgentError } from '../utils/errorHandler';
import './AgentChat.css';

const AgentChat = () => {
const { status, capabilities, isLoading, error, messages, sendMessage, clearMessages } = useAgent();
const [inputMessage, setInputMessage] = useState('');
const messagesEndRef = useRef(null);

const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};

useEffect(() => {
scrollToBottom();
}, [messages]);

const handleWebSocketMessage = (data) => {
if (data.type === 'agent_response') {
// Handle real-time agent responses
console.log('Real-time response:', data);
}
};

const handleWebSocketStatusChange = (status) => {
console.log('WebSocket status:', status);
};

useWebSocket(handleWebSocketMessage, handleWebSocketStatusChange);

const handleSubmit = async (e) => {
e.preventDefault();
if (!inputMessage.trim() || isLoading) return;

try {
await sendMessage(inputMessage);
setInputMessage('');
} catch (error) {
console.error('Error sending message:', error);
}
};

const getStatusColor = () => {
switch (status) {
case 'healthy': return '#4caf50';
case 'unhealthy': return '#f44336';
case 'degraded': return '#ff9800';
default: return '#9e9e9e';
}
};

return (
<div className="agent-chat">
<div className="chat-header">
<h2>Agent Chat</h2>
<div className="status-indicator">
<span
className="status-dot"
style={{ backgroundColor: getStatusColor() }}
/>
<span className="status-text">{status}</span>
</div>
</div>

<div className="capabilities">
<h3>Capabilities:</h3>
<div className="capability-tags">
{capabilities.map((capability, index) => (
<span key={index} className="capability-tag">
{capability}
</span>
))}
</div>
</div>

<div className="messages-container">
{messages.map((message) => (
<div key={message.id} className={`message ${message.type}`}>
<div className="message-content">
{message.content}
</div>
<div className="message-timestamp">
{new Date(message.timestamp).toLocaleTimeString()}
</div>
</div>
))}
{isLoading && (
<div className="message agent">
<div className="typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>

{error && (
<div className="error-message">
{handleAgentError(error)}
</div>
)}

<form onSubmit={handleSubmit} className="message-form">
<input
type="text"
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
placeholder="Type your message..."
disabled={isLoading}
className="message-input"
/>
<button
type="submit"
disabled={!inputMessage.trim() || isLoading}
className="send-button"
>
Send
</button>
</form>

<div className="chat-controls">
<button onClick={clearMessages} className="clear-button">
Clear Chat
</button>
</div>
</div>
);
};

export default AgentChat;
/* src/components/AgentChat.css */
.agent-chat {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 800px;
margin: 0 auto;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}

.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: #f5f5f5;
border-bottom: 1px solid #ddd;
}

.status-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
}

.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}

.capabilities {
padding: 1rem;
background-color: #f9f9f9;
border-bottom: 1px solid #ddd;
}

.capability-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.5rem;
}

.capability-tag {
background-color: #e3f2fd;
color: #1976d2;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
}

.messages-container {
flex: 1;
overflow-y: auto;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
}

.message {
max-width: 70%;
padding: 0.75rem;
border-radius: 8px;
word-wrap: break-word;
}

.message.user {
align-self: flex-end;
background-color: #007bff;
color: white;
}

.message.agent {
align-self: flex-start;
background-color: #f1f1f1;
color: #333;
}

.message.error {
align-self: center;
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

.message-timestamp {
font-size: 0.7rem;
opacity: 0.7;
margin-top: 0.25rem;
}

.typing-indicator {
display: flex;
gap: 0.25rem;
}

.typing-indicator span {
width: 6px;
height: 6px;
background-color: #999;
border-radius: 50%;
animation: typing 1.4s infinite ease-in-out;
}

.typing-indicator span:nth-child(2) {
animation-delay: 0.2s;
}

.typing-indicator span:nth-child(3) {
animation-delay: 0.4s;
}

@keyframes typing {
0%, 60%, 100% {
transform: translateY(0);
}
30% {
transform: translateY(-10px);
}
}

.error-message {
background-color: #f8d7da;
color: #721c24;
padding: 0.75rem;
margin: 0.5rem 1rem;
border-radius: 4px;
border: 1px solid #f5c6cb;
}

.message-form {
display: flex;
padding: 1rem;
gap: 0.5rem;
border-top: 1px solid #ddd;
}

.message-input {
flex: 1;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
outline: none;
}

.message-input:focus {
border-color: #007bff;
}

.send-button {
padding: 0.75rem 1.5rem;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}

.send-button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}

.chat-controls {
padding: 0.5rem 1rem;
border-top: 1px solid #ddd;
background-color: #f5f5f5;
}

.clear-button {
padding: 0.5rem 1rem;
background-color: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}

Vue Example

<!-- src/components/AgentChat.vue -->
<template>
<div class="agent-chat">
<div class="chat-header">
<h2>Agent Chat</h2>
<div class="status-indicator">
<span
class="status-dot"
:style="{ backgroundColor: getStatusColor() }"
/>
<span class="status-text">{{ status }}</span>
</div>
</div>

<div class="capabilities">
<h3>Capabilities:</h3>
<div class="capability-tags">
<span
v-for="(capability, index) in capabilities"
:key="index"
class="capability-tag"
>
{{ capability }}
</span>
</div>
</div>

<div class="messages-container" ref="messagesContainer">
<div
v-for="message in messages"
:key="message.id"
:class="['message', message.type]"
>
<div class="message-content">
{{ message.content }}
</div>
<div class="message-timestamp">
{{ formatTime(message.timestamp) }}
</div>
</div>

<div v-if="isLoading" class="message agent">
<div class="typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>

<div v-if="error" class="error-message">
{{ handleAgentError(error) }}
</div>

<form @submit.prevent="handleSubmit" class="message-form">
<input
v-model="inputMessage"
type="text"
placeholder="Type your message..."
:disabled="isLoading"
class="message-input"
/>
<button
type="submit"
:disabled="!inputMessage.trim() || isLoading"
class="send-button"
>
Send
</button>
</form>

<div class="chat-controls">
<button @click="clearMessages" class="clear-button">
Clear Chat
</button>
</div>
</div>
</template>

<script>
import { ref, onMounted, nextTick } from 'vue';
import { useAgent } from '../composables/useAgent';
import { handleAgentError } from '../utils/errorHandler';

export default {
name: 'AgentChat',
setup() {
const {
status,
capabilities,
isLoading,
error,
messages,
sendMessage,
clearMessages
} = useAgent();

const inputMessage = ref('');
const messagesContainer = ref(null);

const scrollToBottom = async () => {
await nextTick();
if (messagesContainer.value) {
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
}
};

const handleSubmit = async () => {
if (!inputMessage.value.trim() || isLoading.value) return;

try {
await sendMessage(inputMessage.value);
inputMessage.value = '';
} catch (error) {
console.error('Error sending message:', error);
}
};

const getStatusColor = () => {
switch (status.value) {
case 'healthy': return '#4caf50';
case 'unhealthy': return '#f44336';
case 'degraded': return '#ff9800';
default: return '#9e9e9e';
}
};

const formatTime = (timestamp) => {
return new Date(timestamp).toLocaleTimeString();
};

onMounted(() => {
// Watch for new messages and scroll to bottom
// This would be implemented with a watcher in a real app
});

return {
status,
capabilities,
isLoading,
error,
messages,
inputMessage,
messagesContainer,
sendMessage,
clearMessages,
handleSubmit,
getStatusColor,
formatTime,
handleAgentError
};
}
};
</script>

<style scoped>
/* Same CSS as React example */
.agent-chat {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 800px;
margin: 0 auto;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}

.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: #f5f5f5;
border-bottom: 1px solid #ddd;
}

.status-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
}

.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}

.capabilities {
padding: 1rem;
background-color: #f9f9f9;
border-bottom: 1px solid #ddd;
}

.capability-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.5rem;
}

.capability-tag {
background-color: #e3f2fd;
color: #1976d2;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
}

.messages-container {
flex: 1;
overflow-y: auto;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
}

.message {
max-width: 70%;
padding: 0.75rem;
border-radius: 8px;
word-wrap: break-word;
}

.message.user {
align-self: flex-end;
background-color: #007bff;
color: white;
}

.message.agent {
align-self: flex-start;
background-color: #f1f1f1;
color: #333;
}

.message.error {
align-self: center;
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

.message-timestamp {
font-size: 0.7rem;
opacity: 0.7;
margin-top: 0.25rem;
}

.typing-indicator {
display: flex;
gap: 0.25rem;
}

.typing-indicator span {
width: 6px;
height: 6px;
background-color: #999;
border-radius: 50%;
animation: typing 1.4s infinite ease-in-out;
}

.typing-indicator span:nth-child(2) {
animation-delay: 0.2s;
}

.typing-indicator span:nth-child(3) {
animation-delay: 0.4s;
}

@keyframes typing {
0%, 60%, 100% {
transform: translateY(0);
}
30% {
transform: translateY(-10px);
}
}

.error-message {
background-color: #f8d7da;
color: #721c24;
padding: 0.75rem;
margin: 0.5rem 1rem;
border-radius: 4px;
border: 1px solid #f5c6cb;
}

.message-form {
display: flex;
padding: 1rem;
gap: 0.5rem;
border-top: 1px solid #ddd;
}

.message-input {
flex: 1;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
outline: none;
}

.message-input:focus {
border-color: #007bff;
}

.send-button {
padding: 0.75rem 1.5rem;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}

.send-button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}

.chat-controls {
padding: 0.5rem 1rem;
border-top: 1px solid #ddd;
background-color: #f5f5f5;
}

.clear-button {
padding: 0.5rem 1rem;
background-color: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>

Production Considerations

1. Security

// src/utils/security.js
export const sanitizeInput = (input) => {
// Remove potentially dangerous characters
return input.replace(/[<>]/g, '');
};

export const validateMessage = (message) => {
if (!message || typeof message !== 'string') {
throw new Error('Message must be a non-empty string');
}

if (message.length > 1000) {
throw new Error('Message too long');
}

return sanitizeInput(message);
};

export const addCSRFToken = (config) => {
const token = document.querySelector('meta[name="csrf-token"]')?.content;
if (token) {
config.headers['X-CSRF-Token'] = token;
}
return config;
};

2. Performance Optimization

// src/utils/performance.js
export const debounce = (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};

export const throttle = (func, limit) => {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
};

export const memoize = (fn) => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
};

3. Monitoring and Analytics

// src/utils/analytics.js
export const trackEvent = (eventName, properties = {}) => {
// Send to analytics service
if (window.gtag) {
window.gtag('event', eventName, properties);
}

// Send to custom analytics
fetch('/api/analytics', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
event: eventName,
properties,
timestamp: new Date().toISOString(),
}),
});
};

export const trackAgentInteraction = (action, details = {}) => {
trackEvent('agent_interaction', {
action,
...details,
});
};

Troubleshooting

Common Issues

1. Connection Issues

Problem: Cannot connect to agent Solutions:

  • Check PAR runtime is running
  • Verify agent endpoints are configured
  • Check network connectivity
  • Verify CORS settings

2. Authentication Issues

Problem: 401 Unauthorized errors Solutions:

  • Check API key configuration
  • Verify JWT token validity
  • Ensure proper headers are sent

3. WebSocket Connection Issues

Problem: WebSocket fails to connect Solutions:

  • Check WebSocket URL
  • Verify PAR supports WebSocket
  • Check firewall settings
  • Implement reconnection logic

4. Performance Issues

Problem: Slow response times Solutions:

  • Implement request caching
  • Use connection pooling
  • Optimize message size
  • Add loading states

Debug Tools

// src/utils/debug.js
export const enableDebugMode = () => {
window.agentDebug = {
logLevel: 'debug',
showNetworkRequests: true,
showWebSocketMessages: true,
};
};

export const debugLog = (message, data = null) => {
if (window.agentDebug?.logLevel === 'debug') {
console.log(`[Agent Debug] ${message}`, data);
}
};

Next Steps

After integrating with user interfaces:

  1. Best Practices - Follow development best practices
  2. Debugging Guide - Debug your UI integration
  3. Full Deployment Guide - Deploy your complete system
  4. Agent-to-Agent Communication - Build multi-agent systems

Ready to build your agent UI? Check out Best Practices to learn development best practices!

Next Steps