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 charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern Landing Page</title> <title>Modern Landing Page</title>
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
</head> </head>
<body> <body>
<header class="header container"> <header>
<div class="header-logo">Brand</div>
<nav> <nav>
<ul class="nav-links"> <div class="logo">Brand</div>
<ul>
<li><a href="#home">Home</a></li> <li><a href="#home">Home</a></li>
<li><a href="#features">Features</a></li> <li><a href="#features">Features</a></li>
<li><a href="#contact">Contact</a></li> <li><a href="#contact">Contact</a></li>
@ -20,56 +19,54 @@
</header> </header>
<main> <main>
<section class="hero"> <section id="home" class="hero">
<div class="container hero-content"> <div class="hero-content">
<h1 class="hero-title">Transform Your Digital Experience</h1> <h1>Transform Your Ideas into Reality</h1>
<p class="hero-subtitle">Discover innovative solutions that drive your business forward</p> <p>Discover innovative solutions that drive your business forward.</p>
<a href="#features" class="cta-button">Get Started</a> <a href="#contact" class="cta-button">Get Started</a>
</div> </div>
</section> </section>
<section id="features" class="features"> <section id="features" class="features">
<div class="container"> <div class="features-content">
<h2>Our Key Features</h2>
<div class="feature-grid"> <div class="feature-grid">
<div class="feature"> <div class="feature">
<div class="feature-icon">🚀</div> <h3>Innovative Design</h3>
<h3>Fast Performance</h3> <p>Cutting-edge solutions tailored to your needs.</p>
<p>Optimized solutions for maximum efficiency</p>
</div> </div>
<div class="feature"> <div class="feature">
<div class="feature-icon">🔒</div> <h3>Scalable Technology</h3>
<h3>Secure Platform</h3> <p>Grow your business with flexible platforms.</p>
<p>Advanced security measures to protect your data</p>
</div> </div>
<div class="feature"> <div class="feature">
<div class="feature-icon">📊</div> <h3>Expert Support</h3>
<h3>Smart Analytics</h3> <p>24/7 professional assistance for your projects.</p>
<p>Comprehensive insights for strategic decisions</p>
</div> </div>
</div> </div>
</div> </div>
</section> </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> </main>
<footer class="footer"> <footer>
<div class="container"> <div class="footer-content">
<div class="footer-content"> <p>&copy; 2023 Brand. All rights reserved.</p>
<div class="footer-logo">Brand</div> <div class="social-links">
<div class="footer-links"> <a href="#">Twitter</a>
<a href="#home">Home</a> <a href="#">LinkedIn</a>
<a href="#features">Features</a> <a href="#">GitHub</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.
</div> </div>
</div> </div>
</footer> </footer>
<script src="script.js"></script>
</body> </body>
</html> </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_parser import ToolParser
from agentpress.tool_executor import ToolExecutor from agentpress.tool_executor import ToolExecutor
import asyncio import asyncio
import os
import json
class LLMResponseProcessor: class LLMResponseProcessor:
""" """
@ -60,80 +62,120 @@ class LLMResponseProcessor:
) -> AsyncGenerator: ) -> AsyncGenerator:
""" """
Process streaming LLM response and handle tool execution. Process streaming LLM response and handle tool execution.
Yields chunks immediately while managing message state and tool execution efficiently.
Yields chunks immediately as they arrive, while handling tool execution
and message management in the background.
""" """
pending_tool_calls = [] 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): async def handle_message_management(chunk):
# Accumulate content try:
if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content: nonlocal last_update_time, tool_results
self.content_buffer += chunk.choices[0].delta.content current_time = asyncio.get_event_loop().time()
# Parse and accumulate tool calls # Accumulate content
parsed_message, is_complete = await self.tool_parser.parse_stream( if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content:
chunk, self.content_buffer += chunk.choices[0].delta.content
self.tool_calls_buffer
)
if parsed_message and 'tool_calls' in parsed_message:
self.tool_calls_accumulated = parsed_message['tool_calls']
# Handle message management and tool execution # Parse tool calls only if present in chunk
if chunk.choices[0].finish_reason or (self.content_buffer and self.tool_calls_accumulated): if hasattr(chunk.choices[0].delta, 'tool_calls'):
message = { parsed_message, is_complete = await self.tool_parser.parse_stream(
"role": "assistant", chunk,
"content": self.content_buffer self.tool_calls_buffer
} )
if self.tool_calls_accumulated: if parsed_message and 'tool_calls' in parsed_message:
message["tool_calls"] = self.tool_calls_accumulated self.tool_calls_accumulated = parsed_message['tool_calls']
if not self.message_added: # Handle tool execution and results
await self.add_message(self.thread_id, message)
self.message_added = True
else:
await self.update_message(self.thread_id, message)
# Handle tool execution
if execute_tools and self.tool_calls_accumulated: if execute_tools and self.tool_calls_accumulated:
new_tool_calls = [ new_tool_calls = [
tool_call for tool_call in self.tool_calls_accumulated tool_call for tool_call in self.tool_calls_accumulated
if tool_call['id'] not in self.processed_tool_calls if tool_call['id'] not in self.processed_tool_calls
] ]
if new_tool_calls: if new_tool_calls and immediate_execution:
if immediate_execution: results = await self.tool_executor.execute_tool_calls(
results = await self.tool_executor.execute_tool_calls( tool_calls=new_tool_calls,
tool_calls=new_tool_calls, available_functions=self.available_functions,
available_functions=self.available_functions, thread_id=self.thread_id,
thread_id=self.thread_id, executed_tool_calls=self.processed_tool_calls
executed_tool_calls=self.processed_tool_calls )
) tool_results.extend(results)
for result in results: for result in results:
await self.add_message(self.thread_id, result) self.processed_tool_calls.add(result['tool_call_id'])
self.processed_tool_calls.add(result['tool_call_id']) elif new_tool_calls:
else: pending_tool_calls.extend(new_tool_calls)
pending_tool_calls.extend(new_tool_calls)
# Handle end of stream # Update message state with rate limiting
if chunk.choices[0].finish_reason: should_update = (
if not immediate_execution and pending_tool_calls: chunk.choices[0].finish_reason or
results = await self.tool_executor.execute_tool_calls( (current_time - last_update_time) >= UPDATE_INTERVAL
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()
async for chunk in response_stream: if should_update:
# Start background task for message management and tool execution # First, add tool results if any
asyncio.create_task(handle_message_management(chunk)) for result in tool_results:
# Immediately yield the chunk if not any(msg.get('tool_call_id') == result['tool_call_id']
yield chunk 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( async def process_response(
self, self,
@ -173,3 +215,13 @@ class LLMResponseProcessor:
"role": "assistant", "role": "assistant",
"content": response_content or "" "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 []