https://blog.csdn.net/weixin_40582941/article/details/155031787?spm=1011.2124.3001.6209

构建后端

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>AG-UI IM Client</title>

<style>

* {

margin: 0;

padding: 0;

box-sizing: border-box;

}

body {

font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;

background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);

min-height: 100vh;

padding: 20px;

}

.container {

max-width: 800px;

margin: 0 auto;

background: white;

border-radius: 15px;

box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);

overflow: hidden;

}

.header {

background: #2c3e50;

color: white;

padding: 20px;

text-align: center;

}

.header h1 {

font-size: 24px;

margin-bottom: 5px;

}

.header p {

opacity: 0.8;

font-size: 14px;

}

.chat-container {

display: flex;

flex-direction: column;

height: 600px;

}

.chat-messages {

flex: 1;

padding: 20px;

overflow-y: auto;

background: #f8f9fa;

}

.message {

margin-bottom: 15px;

display: flex;

align-items: flex-start;

}

.message.user {

justify-content: flex-end;

}

.message-content {

max-width: 70%;

padding: 12px 16px;

border-radius: 18px;

position: relative;

word-wrap: break-word;

}

.message.user .message-content {

background: #007bff;

color: white;

border-bottom-right-radius: 4px;

}

.message.assistant .message-content {

background: white;

color: #333;

border: 1px solid #e0e0e0;

border-bottom-left-radius: 4px;

}

.message-role {

font-size: 12px;

font-weight: bold;

margin-bottom: 4px;

opacity: 0.7;

}

.message-time {

font-size: 11px;

text-align: right;

margin-top: 5px;

opacity: 0.6;

}

.typing-indicator {

display: none;

padding: 12px 16px;

background: white;

border: 1px solid #e0e0e0;

border-radius: 18px;

border-bottom-left-radius: 4px;

max-width: 70%;

margin-bottom: 15px;

}

.typing-dots {

display: flex;

gap: 4px;

}

.typing-dot {

width: 8px;

height: 8px;

background: #999;

border-radius: 50%;

animation: typing 1.4s infinite ease-in-out;

}

.typing-dot:nth-child(1) { animation-delay: -0.32s; }

.typing-dot:nth-child(2) { animation-delay: -0.16s; }

@keyframes typing {

0%, 80%, 100% { transform: scale(0.8); opacity: 0.5; }

40% { transform: scale(1); opacity: 1; }

}

.input-area {

padding: 20px;

background: white;

border-top: 1px solid #e0e0e0;

display: flex;

gap: 10px;

}

.message-input {

flex: 1;

padding: 12px 16px;

border: 1px solid #ddd;

border-radius: 25px;

outline: none;

font-size: 14px;

transition: border-color 0.3s;

}

.message-input:focus {

border-color: #007bff;

}

.send-button {

padding: 12px 24px;

background: #007bff;

color: white;

border: none;

border-radius: 25px;

cursor: pointer;

font-size: 14px;

font-weight: bold;

transition: background 0.3s;

}

.send-button:hover {

background: #0056b3;

}

.send-button:disabled {

background: #6c757d;

cursor: not-allowed;

}

.session-info {

padding: 10px 20px;

background: #e9ecef;

border-bottom: 1px solid #dee2e6;

display: flex;

justify-content: space-between;

align-items: center;

font-size: 12px;

}

.session-id {

font-family: monospace;

background: #2c3e50;

color: white;

padding: 4px 8px;

border-radius: 4px;

}

.controls {

display: flex;

gap: 10px;

}

.control-button {

padding: 6px 12px;

background: #6c757d;

color: white;

border: none;

border-radius: 4px;

cursor: pointer;

font-size: 12px;

}

.control-button:hover {

background: #5a6268;

}

.event-log {

background: #2c3e50;

color: #00ff00;

font-family: monospace;

padding: 10px;

max-height: 150px;

overflow-y: auto;

font-size: 12px;

border-top: 1px solid #34495e;

display: none;

}

.toggle-log {

background: #34495e;

color: white;

border: none;

padding: 5px 10px;

font-size: 12px;

cursor: pointer;

border-radius: 4px;

}

.status {

padding: 8px 20px;

background: #fff3cd;

border-bottom: 1px solid #ffeaa7;

font-size: 12px;

display: flex;

justify-content: space-between;

align-items: center;

}

.connection-status {

display: flex;

align-items: center;

gap: 8px;

}

.status-dot {

width: 8px;

height: 8px;

border-radius: 50%;

background: #6c757d;

}

.status-dot.connected {

background: #28a745;

animation: pulse 2s infinite;

}

@keyframes pulse {

0% { opacity: 1; }

50% { opacity: 0.5; }

100% { opacity: 1; }

}

.error-message {

background: #f8d7da;

color: #721c24;

padding: 10px;

margin: 10px 20px;

border-radius: 5px;

border: 1px solid #f5c6cb;

display: none;

}

</style>

</head>

<body>

<div class="container">

<div class="header">

<h1>AG-UI IM Client</h1>

<p>Real-time chat using AG-UI Protocol with FastAPI</p>

</div>

<div class="status">

<div class="connection-status">

<div class="status-dot" id="statusDot"></div>

<span id="statusText">Ready</span>

</div>

<button class="toggle-log" onclick="toggleEventLog()">Toggle Event Log</button>

</div>

<div class="error-message" id="errorMessage"></div>

<div class="session-info">

<div>Session: <span class="session-id" id="sessionId">loading...</span></div>

<div class="controls">

<button class="control-button" onclick="clearChat()">Clear Chat</button>

<button class="control-button" onclick="newSession()">New Session</button>

</div>

</div>

<div class="chat-container">

<div class="chat-messages" id="chatMessages">

<div class="message assistant">

<div class="message-content">

<div class="message-role">AG-UI Assistant</div>

Welcome to the AG-UI IM Client! Send a message to start chatting.

</div>

</div>

</div>

<div class="typing-indicator" id="typingIndicator">

<div class="typing-dots">

<div class="typing-dot"></div>

<div class="typing-dot"></div>

<div class="typing-dot"></div>

</div>

</div>

<div class="input-area">

<input type="text"

class="message-input"

id="messageInput"

placeholder="Type your message..."

onkeypress="handleKeyPress(event)">

<button class="send-button" id="sendButton" onclick="sendMessage()">Send</button>

</div>

</div>

<div class="event-log" id="eventLog">

<div>AG-UI Event Log:</div>

</div>

</div>

<script>

// Configuration

const API_BASE_URL = 'http://localhost:8000';

let currentSessionId = null;

let currentRunId = null;

let currentMessageId = null;

let isProcessing = false;

// DOM Elements

const chatMessages = document.getElementById('chatMessages');

const messageInput = document.getElementById('messageInput');

const sendButton = document.getElementById('sendButton');

const typingIndicator = document.getElementById('typingIndicator');

const sessionIdElement = document.getElementById('sessionId');

const eventLog = document.getElementById('eventLog');

const statusDot = document.getElementById('statusDot');

const statusText = document.getElementById('statusText');

const errorMessage = document.getElementById('errorMessage');

// Initialize

document.addEventListener('DOMContentLoaded', async () => {

await initializeSession();

messageInput.focus();

});

// Initialize or create session

async function initializeSession() {

try {

setStatus('Connecting...', 'connecting');

// Try to get existing session from localStorage

const savedSessionId = localStorage.getItem('agui_session_id');

if (savedSessionId) {

// Verify session exists

const response = await fetch(`${API_BASE_URL}/api/sessions/${savedSessionId}/messages`);

if (response.ok) {

currentSessionId = savedSessionId;

const data = await response.json();

loadChatHistory(data.messages);

} else {

await createNewSession();

}

} else {

await createNewSession();

}

setStatus('Connected', 'connected');

} catch (error) {

console.error('Failed to initialize session:', error);

setStatus('Connection Failed', 'error');

showError('Failed to connect to server. Please check if the FastAPI server is running.');

}

}

// Create new session

async function createNewSession() {

try {

const response = await fetch(`${API_BASE_URL}/api/sessions/new`);

const data = await response.json();

currentSessionId = data.session_id;

localStorage.setItem('agui_session_id', currentSessionId);

sessionIdElement.textContent = currentSessionId;

clearChatUI();

} catch (error) {

console.error('Failed to create session:', error);

throw error;

}

}

// Load chat history

function loadChatHistory(messages) {

clearChatUI();

messages.forEach(message => {

addMessageToChat(message.role, message.message, false);

});

sessionIdElement.textContent = currentSessionId;

}

// Send message to AG-UI API

async function sendMessage() {

const message = messageInput.value.trim();

if (!message || isProcessing) return;

// Clear input and disable send button

messageInput.value = '';

sendButton.disabled = true;

isProcessing = true;

// Add user message to chat

addMessageToChat('user', message, true);

// Show typing indicator

typingIndicator.style.display = 'block';

scrollToBottom();

try {

setStatus('Processing...', 'processing');

// Prepare request data

const requestData = {

message: message

};

// Make POST request to stream endpoint

const response = await fetch(`${API_BASE_URL}/api/chat/${currentSessionId}/stream`, {

method: 'POST',

headers: {

'Content-Type': 'application/json',

},

body: JSON.stringify(requestData)

});

if (!response.ok) {

throw new Error(`HTTP error! status: ${response.status}`);

}

// Create a new assistant message element

const messageElement = createMessageElement('assistant');

chatMessages.appendChild(messageElement);

// Process the stream

const reader = response.body.getReader();

const decoder = new TextDecoder();

let assistantMessage = '';

while (true) {

const { done, value } = await reader.read();

if (done) break;

const chunk = decoder.decode(value);

const lines = chunk.split('\n');

for (const line of lines) {

if (line.trim()) {

try {

var data = line.trim();

if (data.startsWith('data: ')) {

data = data.substring(6).trim();

}

const event = JSON.parse(data);

logEvent(event);

switch (event.type) {

case 'RUN_STARTED':

currentRunId = event.run_id;

setStatus('AI Processing', 'processing');

break;

case 'TEXT_MESSAGE_CONTENT':

assistantMessage += event.delta;

messageElement.querySelector('.message-text').textContent = assistantMessage;

scrollToBottom();

break;

case 'RUN_FINISHED':

setStatus('Connected', 'connected');

break;

case 'RUN_ERROR':

showError(`AG-UI Error: ${event.message}`);

break;

}

} catch (e) {

console.error('Error parsing event:', e, line);

}

}

}

}

// Hide typing indicator

typingIndicator.style.display = 'none';

} catch (error) {

console.error('Error sending message:', error);

showError(`Failed to send message: ${error.message}`);

typingIndicator.style.display = 'none';

setStatus('Error', 'error');

} finally {

// Re-enable input

sendButton.disabled = false;

isProcessing = false;

messageInput.focus();

}

}

// Add message to chat UI

function addMessageToChat(role, text, scroll = true) {

const messageElement = createMessageElement(role);

messageElement.querySelector('.message-text').textContent = text;

chatMessages.appendChild(messageElement);

if (scroll) {

scrollToBottom();

}

}

// Create message element

function createMessageElement(role) {

const messageDiv = document.createElement('div');

messageDiv.className = `message ${role}`;

const messageContent = document.createElement('div');

messageContent.className = 'message-content';

const roleDiv = document.createElement('div');

roleDiv.className = 'message-role';

roleDiv.textContent = role === 'user' ? 'You' : 'AG-UI Assistant';

const textDiv = document.createElement('div');

textDiv.className = 'message-text';

const timeDiv = document.createElement('div');

timeDiv.className = 'message-time';

timeDiv.textContent = new Date().toLocaleTimeString();

messageContent.appendChild(roleDiv);

messageContent.appendChild(textDiv);

messageContent.appendChild(timeDiv);

messageDiv.appendChild(messageContent);

return messageDiv;

}

// Clear chat UI

function clearChatUI() {

chatMessages.innerHTML = `

<div class="message assistant">

<div class="message-content">

<div class="message-role">AG-UI Assistant</div>

Welcome to the AG-UI IM Client! Send a message to start chatting.

</div>

</div>

`;

}

// Clear chat (API call)

async function clearChat() {

try {

await fetch(`${API_BASE_URL}/api/sessions/${currentSessionId}/clear`, {

method: 'POST'

});

clearChatUI();

} catch (error) {

console.error('Error clearing chat:', error);

showError('Failed to clear chat history');

}

}

// Create new session

async function newSession() {

try {

await createNewSession();

showError(''); // Clear any previous errors

} catch (error) {

console.error('Error creating new session:', error);

showError('Failed to create new session');

}

}

// Handle Enter key press

function handleKeyPress(event) {

if (event.key === 'Enter') {

sendMessage();

}

}

// Scroll to bottom of chat

function scrollToBottom() {

chatMessages.scrollTop = chatMessages.scrollHeight;

}

// Set connection status

function setStatus(text, state) {

statusText.textContent = text;

statusDot.className = 'status-dot';

if (state) {

statusDot.classList.add(state);

}

}

// Show error message

function showError(message) {

if (message) {

errorMessage.textContent = message;

errorMessage.style.display = 'block';

} else {

errorMessage.style.display = 'none';

}

}

// Log AG-UI events

function logEvent(event) {

const logEntry = document.createElement('div');

logEntry.textContent = `[${new Date().toLocaleTimeString()}] ${event.type}: ${JSON.stringify(event)}`;

eventLog.appendChild(logEntry);

eventLog.scrollTop = eventLog.scrollHeight;

}

// Toggle event log visibility

function toggleEventLog() {

eventLog.style.display = eventLog.style.display === 'none' ? 'block' : 'none';

}

</script>

</body>

</html>

https://blog.csdn.net/weixin_40582941/article/details/155031787

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐