This commit is contained in:
marko-kraemer 2024-11-13 14:28:28 +01:00
parent f8fc799726
commit 304fa1978c
5 changed files with 421 additions and 99 deletions

View File

@ -4,14 +4,13 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern Landing Page</title>
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header class="header container">
<div class="header-logo">Brand</div>
<header>
<nav>
<ul class="nav-links">
<div class="logo">Brand</div>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#features">Features</a></li>
<li><a href="#contact">Contact</a></li>
@ -20,56 +19,54 @@
</header>
<main>
<section class="hero">
<div class="container hero-content">
<h1 class="hero-title">Transform Your Digital Experience</h1>
<p class="hero-subtitle">Discover innovative solutions that drive your business forward</p>
<a href="#features" class="cta-button">Get Started</a>
<section id="home" class="hero">
<div class="hero-content">
<h1>Transform Your Ideas into Reality</h1>
<p>Discover innovative solutions that drive your business forward.</p>
<a href="#contact" class="cta-button">Get Started</a>
</div>
</section>
<section id="features" class="features">
<div class="container">
<div class="features-content">
<h2>Our Key Features</h2>
<div class="feature-grid">
<div class="feature">
<div class="feature-icon">🚀</div>
<h3>Fast Performance</h3>
<p>Optimized solutions for maximum efficiency</p>
<h3>Innovative Design</h3>
<p>Cutting-edge solutions tailored to your needs.</p>
</div>
<div class="feature">
<div class="feature-icon">🔒</div>
<h3>Secure Platform</h3>
<p>Advanced security measures to protect your data</p>
<h3>Scalable Technology</h3>
<p>Grow your business with flexible platforms.</p>
</div>
<div class="feature">
<div class="feature-icon">📊</div>
<h3>Smart Analytics</h3>
<p>Comprehensive insights for strategic decisions</p>
<h3>Expert Support</h3>
<p>24/7 professional assistance for your projects.</p>
</div>
</div>
</div>
</section>
<section id="contact" class="contact">
<div class="contact-content">
<h2>Ready to Get Started?</h2>
<p>Connect with our team and unlock your potential.</p>
<a href="#" class="cta-button">Contact Us</a>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-logo">Brand</div>
<div class="footer-links">
<a href="#home">Home</a>
<a href="#features">Features</a>
<a href="#contact">Contact</a>
</div>
<div class="footer-social">
<a href="#">Twitter</a>
<a href="#">LinkedIn</a>
<a href="#">GitHub</a>
</div>
</div>
<div class="footer-copyright">
&copy; 2023 Brand. All rights reserved.
<footer>
<div class="footer-content">
<p>&copy; 2023 Brand. All rights reserved.</p>
<div class="social-links">
<a href="#">Twitter</a>
<a href="#">LinkedIn</a>
<a href="#">GitHub</a>
</div>
</div>
</footer>
<script src="script.js"></script>
</body>
</html>

View File

@ -1 +0,0 @@
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}html{scroll-behavior:smooth}body{line-height:1.6;font-family:'-apple-system','BlinkMacSystemFont','Segoe UI','Roboto','Oxygen','Ubuntu','Cantarell','Open Sans','Helvetica Neue',sans-serif}img{max-width:100%;height:auto}a{text-decoration:none;color:inherit}

View File

@ -0,0 +1,48 @@
document.addEventListener('DOMContentLoaded', () => {
const ctaButtons = document.querySelectorAll('.cta-button');
const navLinks = document.querySelectorAll('nav ul li a');
ctaButtons.forEach(ctaButton => {
ctaButton.addEventListener('click', (e) => {
e.preventDefault();
ctaButton.classList.add('clicked');
setTimeout(() => {
ctaButton.classList.remove('clicked');
}, 300);
const modal = document.createElement('div');
modal.classList.add('modal');
modal.innerHTML = `
<div class="modal-content">
<h2>Thank You!</h2>
<p>We'll get back to you soon.</p>
<button class="close-modal">Close</button>
</div>
`;
document.body.appendChild(modal);
modal.querySelector('.close-modal').addEventListener('click', () => {
document.body.removeChild(modal);
});
});
});
navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = link.getAttribute('href').substring(1);
const targetSection = document.getElementById(targetId) || document.querySelector('main');
targetSection.scrollIntoView({ behavior: 'smooth' });
});
});
window.addEventListener('scroll', () => {
const features = document.querySelectorAll('.feature');
features.forEach(feature => {
const rect = feature.getBoundingClientRect();
if (rect.top < window.innerHeight * 0.8) {
feature.classList.add('visible');
}
});
});
});

View File

@ -1 +1,227 @@
:root{--primary-color:#3498db;--secondary-color:#2ecc71;--text-color:#333;--background-color:#f4f4f4;--accent-color:#e74c3c}body{scroll-behavior:smooth;font-smooth:always;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:var(--background-color);color:var(--text-color)}.container{max-width:1200px;margin:0 auto;padding:0 15px}.header{display:flex;justify-content:space-between;align-items:center;padding:20px 0;background-color:white;box-shadow:0 2px 4px rgba(0,0,0,0.1)}.header-logo{font-size:24px;font-weight:bold;color:var(--primary-color)}.nav-links{display:flex;list-style:none;gap:20px}.nav-links a{color:var(--text-color);transition:color 0.3s ease}.nav-links a:hover{color:var(--primary-color)}.hero{display:flex;align-items:center;min-height:70vh;background:linear-gradient(135deg,var(--primary-color),var(--secondary-color));color:white;text-align:center}.hero-content{max-width:800px;margin:0 auto}.hero-title{font-size:48px;margin-bottom:20px;font-weight:bold}.hero-subtitle{font-size:24px;margin-bottom:30px}.cta-button{display:inline-block;background-color:white;color:var(--primary-color);padding:12px 30px;border-radius:5px;font-weight:bold;transition:transform 0.3s ease}.cta-button:hover{transform:scale(1.05)}.footer{background-color:#333;color:white;text-align:center;padding:20px 0}.features{padding:80px 0;background-color:white}.feature-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:30px}.feature{text-align:center;padding:30px;border-radius:10px;transition:all 0.3s ease;box-shadow:0 4px 6px rgba(0,0,0,0.1);background-color:white}.feature:hover{transform:translateY(-10px);box-shadow:0 8px 15px rgba(0,0,0,0.15);background-color:var(--background-color)}.feature-icon{font-size:48px;margin-bottom:20px}.feature h3{margin-bottom:15px;font-size:20px}.feature p{color:var(--text-color)}.footer{background-color:#1a1a1a;color:white;padding:40px 0}.footer-content{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.footer-logo{font-size:24px;font-weight:bold}.footer-links{display:flex;gap:20px}.footer-links a{color:white;transition:color 0.3s ease}.footer-links a:hover{color:var(--primary-color)}.footer-social{display:flex;gap:15px}.footer-social a{color:white;transition:color 0.3s ease}.footer-social a:hover{color:var(--secondary-color)}.footer-copyright{text-align:center;padding-top:20px;border-top:1px solid rgba(255,255,255,0.1)}@media(max-width:768px){.header{flex-direction:column;text-align:center}.header-logo{margin-bottom:15px}.nav-links{flex-direction:column;align-items:center}.hero{text-align:center}.hero-title{font-size:36px}.hero-subtitle{font-size:18px}.feature-grid{grid-template-columns:1fr}.features{padding:40px 0}.footer-content{flex-direction:column;text-align:center}.footer-links,.footer-social{margin-top:20px;flex-direction:column;align-items:center}}
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--text-color: #333;
--background-color: #f4f4f4;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
scroll-behavior: smooth;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--background-color);
}
header {
background-color: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
position: fixed;
width: 100%;
top: 0;
z-index: 1000;
}
nav {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
}
nav .logo {
font-size: 1.5rem;
font-weight: bold;
color: var(--primary-color);
}
nav ul {
display: flex;
list-style: none;
}
nav ul li {
margin-left: 1.5rem;
}
nav ul li a {
text-decoration: none;
color: var(--text-color);
transition: color 0.3s ease;
}
nav ul li a:hover {
color: var(--primary-color);
}
.hero {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
text-align: center;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
}
.hero-content {
max-width: 800px;
padding: 2rem;
animation: fadeInUp 1s ease-out;
}
.hero h1 {
font-size: 3rem;
margin-bottom: 1rem;
background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.hero p {
font-size: 1.2rem;
margin-bottom: 2rem;
color: rgba(255,255,255,0.9);
}
.cta-button {
display: inline-block;
background-color: white;
color: var(--primary-color);
padding: 0.75rem 1.5rem;
text-decoration: none;
border-radius: 5px;
transition: all 0.3s ease;
transform: translateY(0);
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.cta-button:hover {
background-color: #f4f4f4;
transform: translateY(-3px);
box-shadow: 0 6px 8px rgba(0,0,0,0.15);
}
footer {
background-color: #333;
color: white;
text-align: center;
padding: 2rem;
}
.footer-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.social-links a {
color: white;
margin: 0 0.5rem;
text-decoration: none;
}
@media screen and (max-width: 768px) {
nav {
flex-direction: column;
}
nav ul {
margin-top: 1rem;
flex-wrap: wrap;
justify-content: center;
}
nav ul li {
margin: 0.5rem;
}
.hero {
padding: 2rem;
}
.hero h1 {
font-size: 2rem;
}
.hero p {
font-size: 1rem;
}
.footer-content {
flex-direction: column;
}
.social-links {
margin-top: 1rem;
}
}
.cta-button.clicked {
transform: scale(0.95);
opacity: 0.8;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 2000;
}
.modal-content {
background: white;
padding: 2rem;
border-radius: 10px;
text-align: center;
max-width: 400px;
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
}
.close-modal {
margin-top: 1rem;
background-color: var(--primary-color);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 5px;
cursor: pointer;
}
.feature {
opacity: 0;
transform: translateY(20px);
transition: all 0.6s ease;
}
.feature.visible {
opacity: 1;
transform: translateY(0);
}

View File

@ -3,6 +3,8 @@ from typing import Dict, Any, AsyncGenerator, Callable
from agentpress.tool_parser import ToolParser
from agentpress.tool_executor import ToolExecutor
import asyncio
import os
import json
class LLMResponseProcessor:
"""
@ -60,80 +62,120 @@ class LLMResponseProcessor:
) -> AsyncGenerator:
"""
Process streaming LLM response and handle tool execution.
Yields chunks immediately as they arrive, while handling tool execution
and message management in the background.
Yields chunks immediately while managing message state and tool execution efficiently.
"""
pending_tool_calls = []
background_tasks = set()
last_update_time = 0
UPDATE_INTERVAL = 0.5 # Minimum seconds between message updates
tool_results = [] # Track tool results
async def handle_message_management(chunk):
# Accumulate content
if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content:
self.content_buffer += chunk.choices[0].delta.content
# Parse and accumulate tool calls
parsed_message, is_complete = await self.tool_parser.parse_stream(
chunk,
self.tool_calls_buffer
)
if parsed_message and 'tool_calls' in parsed_message:
self.tool_calls_accumulated = parsed_message['tool_calls']
try:
nonlocal last_update_time, tool_results
current_time = asyncio.get_event_loop().time()
# Handle message management and tool execution
if chunk.choices[0].finish_reason or (self.content_buffer and self.tool_calls_accumulated):
message = {
"role": "assistant",
"content": self.content_buffer
}
if self.tool_calls_accumulated:
message["tool_calls"] = self.tool_calls_accumulated
# Accumulate content
if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content:
self.content_buffer += chunk.choices[0].delta.content
# Parse tool calls only if present in chunk
if hasattr(chunk.choices[0].delta, 'tool_calls'):
parsed_message, is_complete = await self.tool_parser.parse_stream(
chunk,
self.tool_calls_buffer
)
if parsed_message and 'tool_calls' in parsed_message:
self.tool_calls_accumulated = parsed_message['tool_calls']
if not self.message_added:
await self.add_message(self.thread_id, message)
self.message_added = True
else:
await self.update_message(self.thread_id, message)
# Handle tool execution
# Handle tool execution and results
if execute_tools and self.tool_calls_accumulated:
new_tool_calls = [
tool_call for tool_call in self.tool_calls_accumulated
if tool_call['id'] not in self.processed_tool_calls
]
if new_tool_calls:
if immediate_execution:
results = await self.tool_executor.execute_tool_calls(
tool_calls=new_tool_calls,
available_functions=self.available_functions,
thread_id=self.thread_id,
executed_tool_calls=self.processed_tool_calls
)
for result in results:
await self.add_message(self.thread_id, result)
self.processed_tool_calls.add(result['tool_call_id'])
else:
pending_tool_calls.extend(new_tool_calls)
if new_tool_calls and immediate_execution:
results = await self.tool_executor.execute_tool_calls(
tool_calls=new_tool_calls,
available_functions=self.available_functions,
thread_id=self.thread_id,
executed_tool_calls=self.processed_tool_calls
)
tool_results.extend(results)
for result in results:
self.processed_tool_calls.add(result['tool_call_id'])
elif new_tool_calls:
pending_tool_calls.extend(new_tool_calls)
# Handle end of stream
if chunk.choices[0].finish_reason:
if not immediate_execution and pending_tool_calls:
results = await self.tool_executor.execute_tool_calls(
tool_calls=pending_tool_calls,
available_functions=self.available_functions,
thread_id=self.thread_id,
executed_tool_calls=self.processed_tool_calls
)
for result in results:
await self.add_message(self.thread_id, result)
self.processed_tool_calls.add(result['tool_call_id'])
pending_tool_calls.clear()
# Update message state with rate limiting
should_update = (
chunk.choices[0].finish_reason or
(current_time - last_update_time) >= UPDATE_INTERVAL
)
async for chunk in response_stream:
# Start background task for message management and tool execution
asyncio.create_task(handle_message_management(chunk))
# Immediately yield the chunk
yield chunk
if should_update:
# First, add tool results if any
for result in tool_results:
if not any(msg.get('tool_call_id') == result['tool_call_id']
for msg in await self.list_messages(self.thread_id)):
await self.add_message(self.thread_id, result)
tool_results = [] # Clear processed results
# Then add/update assistant message
message = {
"role": "assistant",
"content": self.content_buffer
}
if self.tool_calls_accumulated:
message["tool_calls"] = self.tool_calls_accumulated
if not self.message_added:
await self.add_message(self.thread_id, message)
self.message_added = True
else:
await self.update_message(self.thread_id, message)
last_update_time = current_time
# Handle stream completion
if chunk.choices[0].finish_reason:
if not immediate_execution and pending_tool_calls:
results = await self.tool_executor.execute_tool_calls(
tool_calls=pending_tool_calls,
available_functions=self.available_functions,
thread_id=self.thread_id,
executed_tool_calls=self.processed_tool_calls
)
for result in results:
await self.add_message(self.thread_id, result)
self.processed_tool_calls.add(result['tool_call_id'])
pending_tool_calls.clear()
except Exception as e:
logging.error(f"Error in background task: {e}")
return
try:
async for chunk in response_stream:
# Create and track background task
task = asyncio.create_task(handle_message_management(chunk))
background_tasks.add(task)
task.add_done_callback(background_tasks.discard)
# Immediately yield the chunk
yield chunk
# Wait for all background tasks to complete
if background_tasks:
await asyncio.gather(*background_tasks, return_exceptions=True)
except Exception as e:
logging.error(f"Error in stream processing: {e}")
# Clean up any remaining background tasks
for task in background_tasks:
if not task.done():
task.cancel()
raise
async def process_response(
self,
@ -172,4 +214,14 @@ class LLMResponseProcessor:
await self.add_message(self.thread_id, {
"role": "assistant",
"content": response_content or ""
})
})
async def list_messages(self, thread_id: str) -> list:
"""Helper method to get current messages in thread"""
thread_path = os.path.join(self.threads_dir, f"{thread_id}.json")
try:
with open(thread_path, 'r') as f:
thread_data = json.load(f)
return thread_data.get("messages", [])
except Exception:
return []