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:
- User Interaction → Frontend captures user input
 - API Request → Frontend sends HTTP request to PAR
 - Agent Processing → PAR forwards to agent via gRPC
 - Response → Agent processes and returns result
 - 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:
- Best Practices - Follow development best practices
 - Debugging Guide - Debug your UI integration
 - Full Deployment Guide - Deploy your complete system
 - 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
- Dynamic UI Concepts - UI architecture patterns
 - REST Endpoints - API reference
 - Best Practices - Development best practices