mirror of https://github.com/kortix-ai/suna.git
fe thread ux improvements
This commit is contained in:
parent
1b4f216c6a
commit
d65d1d2fc2
|
@ -3,172 +3,322 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Modern Web Application</title>
|
||||
<title>iWielder.ai - Random AI Stuff!</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="noise-overlay"></div>
|
||||
|
||||
<header>
|
||||
<nav class="navbar container">
|
||||
<a href="#" class="logo">WebApp</a>
|
||||
<div class="nav-links">
|
||||
<a href="#home">Home</a>
|
||||
<a href="#features">Features</a>
|
||||
<a href="#about">About</a>
|
||||
<a href="#contact">Contact</a>
|
||||
</div>
|
||||
<div class="nav-actions">
|
||||
<button id="darkModeToggle" class="dark-mode-toggle" aria-label="Toggle dark mode">
|
||||
<i class="fas fa-moon"></i>
|
||||
<i class="fas fa-sun"></i>
|
||||
</button>
|
||||
<button class="mobile-menu-btn">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="logo-container">
|
||||
<img src="https://placehold.co/100x100/FF5757/FFFFFF?text=W" alt="Wielder Logo" class="logo-img">
|
||||
<h1 class="logo">iWielder<span>.ai</span></h1>
|
||||
</div>
|
||||
<nav>
|
||||
<button class="random-button" id="randomStuffBtn">RANDOM STUFF!</button>
|
||||
<ul class="nav-links">
|
||||
<li><a href="#about">ABOUT</a></li>
|
||||
<li><a href="#videos">VIDEOS</a></li>
|
||||
<li><a href="#photos">PHOTOS</a></li>
|
||||
<li><a href="#blog">BLOG</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<!-- Hero Section -->
|
||||
<section id="home" class="hero">
|
||||
<div class="container">
|
||||
<div class="hero-content">
|
||||
<div class="hero-text">
|
||||
<h1>Build Amazing Web Applications</h1>
|
||||
<p>A modern, responsive web application template to kickstart your next project.</p>
|
||||
<div class="hero-buttons">
|
||||
<a href="#features" class="btn btn-primary">Get Started</a>
|
||||
<a href="#about" class="btn btn-secondary">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-image">
|
||||
<img src="https://images.unsplash.com/photo-1498050108023-c5249f4df085?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=600&q=80" alt="Web Development">
|
||||
</div>
|
||||
<section class="hero">
|
||||
<div class="hero-content">
|
||||
<h2 class="glitch-text" data-text="WELCOME TO iWIELDER!">WELCOME TO iWIELDER!</h2>
|
||||
<p>The totally random, completely awesome website about AI, tech, and whatever Adam feels like talking about!</p>
|
||||
<div class="hero-buttons">
|
||||
<button class="primary-button" id="subscribeBtn">SUBSCRIBE!</button>
|
||||
<button class="secondary-button" id="leaveCommentBtn">LEAVE A COMMENT!</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-image">
|
||||
<div class="polaroid">
|
||||
<img src="https://placehold.co/400x300/5271FF/FFFFFF?text=AI+STUFF" alt="AI Stuff">
|
||||
<p class="polaroid-caption">Adam's latest AI creation!</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features Section -->
|
||||
<section id="features" class="features">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2>Key Features</h2>
|
||||
<p>Everything you need to build modern web applications</p>
|
||||
</div>
|
||||
<div class="features-grid">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-mobile-alt"></i>
|
||||
</div>
|
||||
<h3>Responsive Design</h3>
|
||||
<p>Looks great on all devices, from mobile phones to desktop computers.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-bolt"></i>
|
||||
</div>
|
||||
<h3>Fast Performance</h3>
|
||||
<p>Optimized for speed and efficiency to provide the best user experience.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-code"></i>
|
||||
</div>
|
||||
<h3>Clean Code</h3>
|
||||
<p>Well-structured, maintainable code following best practices.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="separator">
|
||||
<div class="star"></div>
|
||||
<div class="star"></div>
|
||||
<div class="star"></div>
|
||||
</div>
|
||||
|
||||
<!-- About Section -->
|
||||
<section id="about" class="about">
|
||||
<div class="container">
|
||||
<div class="about-content">
|
||||
<div class="about-text">
|
||||
<h2>About Our Platform</h2>
|
||||
<p>Our web application platform is designed to help developers build amazing applications quickly and efficiently. With a focus on user experience and performance, we provide all the tools you need to succeed.</p>
|
||||
<p>Whether you're building a simple landing page or a complex web application, our platform has you covered with modern features and best practices built in.</p>
|
||||
<a href="#contact" class="btn btn-primary">Get in Touch</a>
|
||||
<h2 class="section-title">ABOUT iWIELDER</h2>
|
||||
<div class="about-content">
|
||||
<div class="about-text">
|
||||
<p class="speech-bubble">Hey there, people of the internet! Welcome to iWielder, where we talk about AI, machine learning, and all the cool tech stuff that's changing the world!</p>
|
||||
<p>iWielder is run by Adam, the AI enthusiast who's always tinkering with the latest models and creating awesome AI applications. This is where he shares his experiments, thoughts, and occasionally his lunch (just kidding... or are we?).</p>
|
||||
<div class="fun-fact">
|
||||
<h3>RANDOM FACT!</h3>
|
||||
<p>Adam once trained an AI to write poetry about tacos. It was surprisingly emotional.</p>
|
||||
</div>
|
||||
<div class="about-image">
|
||||
<img src="https://images.unsplash.com/photo-1522542550221-31fd19575a2d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=600&q=80" alt="Web Development Team">
|
||||
</div>
|
||||
<div class="about-image">
|
||||
<div class="tv-frame">
|
||||
<div class="tv-screen">
|
||||
<img src="https://placehold.co/400x300/57FFAD/000000?text=WIELDER.AI" alt="Wielder.ai">
|
||||
</div>
|
||||
<div class="tv-stand"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contact Section -->
|
||||
<section id="contact" class="contact">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2>Contact Us</h2>
|
||||
<p>Get in touch with our team</p>
|
||||
<div class="separator">
|
||||
<div class="star"></div>
|
||||
<div class="star"></div>
|
||||
<div class="star"></div>
|
||||
</div>
|
||||
|
||||
<section id="videos" class="videos">
|
||||
<h2 class="section-title">LATEST VIDEOS</h2>
|
||||
<div class="video-grid">
|
||||
<div class="video-card">
|
||||
<div class="video-thumbnail">
|
||||
<img src="https://placehold.co/300x200/FF9E57/FFFFFF?text=AI+Demo" alt="AI Demo">
|
||||
<div class="play-button">
|
||||
<i class="fas fa-play"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h3>How I Built an AI That Writes Dad Jokes</h3>
|
||||
<p class="video-stats">
|
||||
<span><i class="fas fa-eye"></i> 42,069 views</span>
|
||||
<span><i class="fas fa-thumbs-up"></i> 1,337 likes</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="contact-form">
|
||||
<form id="contactForm">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" name="name" required>
|
||||
<div class="video-card">
|
||||
<div class="video-thumbnail">
|
||||
<img src="https://placehold.co/300x200/57C7FF/FFFFFF?text=Tutorial" alt="Tutorial">
|
||||
<div class="play-button">
|
||||
<i class="fas fa-play"></i>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="message">Message</label>
|
||||
<textarea id="message" name="message" rows="5" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Send Message</button>
|
||||
</form>
|
||||
</div>
|
||||
<h3>Getting Started with Wielder.ai in 10 Minutes</h3>
|
||||
<p class="video-stats">
|
||||
<span><i class="fas fa-eye"></i> 31,415 views</span>
|
||||
<span><i class="fas fa-thumbs-up"></i> 926 likes</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="video-card">
|
||||
<div class="video-thumbnail">
|
||||
<img src="https://placehold.co/300x200/B157FF/FFFFFF?text=Experiment" alt="Experiment">
|
||||
<div class="play-button">
|
||||
<i class="fas fa-play"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h3>I Let an AI Control My Smart Home for 24 Hours</h3>
|
||||
<p class="video-stats">
|
||||
<span><i class="fas fa-eye"></i> 87,654 views</span>
|
||||
<span><i class="fas fa-thumbs-up"></i> 3,210 likes</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="more-button">SEE MORE VIDEOS!</button>
|
||||
</section>
|
||||
|
||||
<div class="separator">
|
||||
<div class="star"></div>
|
||||
<div class="star"></div>
|
||||
<div class="star"></div>
|
||||
</div>
|
||||
|
||||
<section id="photos" class="photos">
|
||||
<h2 class="section-title">RANDOM PHOTOS</h2>
|
||||
<div class="photo-gallery">
|
||||
<div class="photo-item">
|
||||
<img src="https://placehold.co/300x300/FF5757/FFFFFF?text=AI+Art" alt="AI Art">
|
||||
<div class="photo-caption">AI-generated masterpiece!</div>
|
||||
</div>
|
||||
<div class="photo-item">
|
||||
<img src="https://placehold.co/300x300/57FFAD/000000?text=Workspace" alt="Workspace">
|
||||
<div class="photo-caption">Where the magic happens!</div>
|
||||
</div>
|
||||
<div class="photo-item">
|
||||
<img src="https://placehold.co/300x300/5271FF/FFFFFF?text=Conference" alt="Conference">
|
||||
<div class="photo-caption">Speaking at AI Con 2023!</div>
|
||||
</div>
|
||||
<div class="photo-item">
|
||||
<img src="https://placehold.co/300x300/B157FF/FFFFFF?text=Robot" alt="Robot">
|
||||
<div class="photo-caption">My new robot friend!</div>
|
||||
</div>
|
||||
<div class="photo-item">
|
||||
<img src="https://placehold.co/300x300/FF9E57/FFFFFF?text=Code" alt="Code">
|
||||
<div class="photo-caption">Late night coding session!</div>
|
||||
</div>
|
||||
<div class="photo-item">
|
||||
<img src="https://placehold.co/300x300/57C7FF/FFFFFF?text=Hardware" alt="Hardware">
|
||||
<div class="photo-caption">New GPU day!</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="more-button">SEE MORE PHOTOS!</button>
|
||||
</section>
|
||||
|
||||
<div class="separator">
|
||||
<div class="star"></div>
|
||||
<div class="star"></div>
|
||||
<div class="star"></div>
|
||||
</div>
|
||||
|
||||
<section id="blog" class="blog">
|
||||
<h2 class="section-title">LATEST BLOG POSTS</h2>
|
||||
<div class="blog-grid">
|
||||
<article class="blog-card">
|
||||
<div class="blog-image">
|
||||
<img src="https://placehold.co/400x200/5271FF/FFFFFF?text=AI+Ethics" alt="AI Ethics">
|
||||
</div>
|
||||
<div class="blog-content">
|
||||
<h3>Why We Need to Talk About AI Ethics</h3>
|
||||
<p class="blog-excerpt">As AI becomes more powerful, we need to have serious conversations about ethics, bias, and the future of humanity...</p>
|
||||
<div class="blog-meta">
|
||||
<span class="blog-date"><i class="fas fa-calendar"></i> May 15, 2023</span>
|
||||
<span class="blog-comments"><i class="fas fa-comment"></i> 42 comments</span>
|
||||
</div>
|
||||
<a href="#" class="read-more">READ MORE</a>
|
||||
</div>
|
||||
</article>
|
||||
<article class="blog-card">
|
||||
<div class="blog-image">
|
||||
<img src="https://placehold.co/400x200/FF5757/FFFFFF?text=Tutorial" alt="Tutorial">
|
||||
</div>
|
||||
<div class="blog-content">
|
||||
<h3>Building Your First AI Assistant with Wielder.ai</h3>
|
||||
<p class="blog-excerpt">In this step-by-step tutorial, I'll show you how to create your very own AI assistant using Wielder.ai's powerful API...</p>
|
||||
<div class="blog-meta">
|
||||
<span class="blog-date"><i class="fas fa-calendar"></i> April 28, 2023</span>
|
||||
<span class="blog-comments"><i class="fas fa-comment"></i> 87 comments</span>
|
||||
</div>
|
||||
<a href="#" class="read-more">READ MORE</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<button class="more-button">READ MORE POSTS!</button>
|
||||
</section>
|
||||
|
||||
<div class="separator">
|
||||
<div class="star"></div>
|
||||
<div class="star"></div>
|
||||
<div class="star"></div>
|
||||
</div>
|
||||
|
||||
<section class="comments">
|
||||
<h2 class="section-title">COMMENTS</h2>
|
||||
<div class="comments-container">
|
||||
<div class="comment">
|
||||
<div class="comment-avatar">
|
||||
<img src="https://placehold.co/50x50/57FFAD/000000?text=S" alt="Sam">
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<h3 class="comment-author">Sam</h3>
|
||||
<p class="comment-text">This website is almost as cool as my butter sock! Almost.</p>
|
||||
<div class="comment-meta">
|
||||
<span class="comment-date">2 days ago</span>
|
||||
<span class="comment-reply">Reply</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment">
|
||||
<div class="comment-avatar">
|
||||
<img src="https://placehold.co/50x50/5271FF/FFFFFF?text=F" alt="Freddie">
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<h3 class="comment-author">Freddie</h3>
|
||||
<p class="comment-text">I've been trying to build my own AI model using your tutorial. It keeps generating pictures of tacos though...</p>
|
||||
<div class="comment-meta">
|
||||
<span class="comment-date">5 days ago</span>
|
||||
<span class="comment-reply">Reply</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment">
|
||||
<div class="comment-avatar">
|
||||
<img src="https://placehold.co/50x50/FF9E57/FFFFFF?text=S" alt="Spencer">
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<h3 class="comment-author">Spencer</h3>
|
||||
<p class="comment-text">WHAT IS THIS NEWFANGLED AI TECHNOLOGY?! In my day, we had to program computers with PUNCH CARDS! PUNCH CARDS!</p>
|
||||
<div class="comment-meta">
|
||||
<span class="comment-date">1 week ago</span>
|
||||
<span class="comment-reply">Reply</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-form">
|
||||
<h3>Leave a Comment</h3>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" placeholder="Your name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="comment">Comment</label>
|
||||
<textarea id="comment" placeholder="What's on your mind?"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="primary-button">POST COMMENT</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-logo">
|
||||
<a href="#">WebApp</a>
|
||||
<p>Building the future of web applications.</p>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<div class="footer-column">
|
||||
<h4>Product</h4>
|
||||
<a href="#">Features</a>
|
||||
<a href="#">Pricing</a>
|
||||
<a href="#">Documentation</a>
|
||||
</div>
|
||||
<div class="footer-column">
|
||||
<h4>Company</h4>
|
||||
<a href="#">About</a>
|
||||
<a href="#">Team</a>
|
||||
<a href="#">Contact</a>
|
||||
</div>
|
||||
<div class="footer-column">
|
||||
<h4>Resources</h4>
|
||||
<a href="#">Blog</a>
|
||||
<a href="#">Help Center</a>
|
||||
<a href="#">Community</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-content">
|
||||
<div class="footer-logo">
|
||||
<h2>iWielder<span>.ai</span></h2>
|
||||
<p>The totally random AI website!</p>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2023 WebApp. All rights reserved.</p>
|
||||
<div class="social-links">
|
||||
<a href="#"><i class="fab fa-twitter"></i></a>
|
||||
<a href="#"><i class="fab fa-facebook-f"></i></a>
|
||||
<a href="#"><i class="fab fa-instagram"></i></a>
|
||||
<a href="#"><i class="fab fa-github"></i></a>
|
||||
<div class="footer-links">
|
||||
<div class="footer-column">
|
||||
<h3>SITE LINKS</h3>
|
||||
<ul>
|
||||
<li><a href="#about">About</a></li>
|
||||
<li><a href="#videos">Videos</a></li>
|
||||
<li><a href="#photos">Photos</a></li>
|
||||
<li><a href="#blog">Blog</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-column">
|
||||
<h3>CONNECT</h3>
|
||||
<ul>
|
||||
<li><a href="#"><i class="fab fa-twitter"></i> Twitter</a></li>
|
||||
<li><a href="#"><i class="fab fa-instagram"></i> Instagram</a></li>
|
||||
<li><a href="#"><i class="fab fa-youtube"></i> YouTube</a></li>
|
||||
<li><a href="#"><i class="fab fa-github"></i> GitHub</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-column">
|
||||
<h3>LEGAL STUFF</h3>
|
||||
<ul>
|
||||
<li><a href="#">Privacy Policy</a></li>
|
||||
<li><a href="#">Terms of Service</a></li>
|
||||
<li><a href="#">Cookie Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2023 iWielder.ai | Created by Adam | No AI were harmed in the making of this website</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div class="random-popup" id="randomPopup">
|
||||
<div class="random-content">
|
||||
<h2>RANDOM STUFF!</h2>
|
||||
<p id="randomText">Did you know that AI can now generate images of cats wearing hats in space? What a time to be alive!</p>
|
||||
<button class="close-button" id="closePopup">CLOSE</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -204,4 +204,68 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
element.style.transform = 'translateY(0)';
|
||||
});
|
||||
});
|
||||
|
||||
// Testimonial slider functionality
|
||||
const testimonialTrack = document.querySelector('.testimonial-track');
|
||||
const testimonialCards = document.querySelectorAll('.testimonial-card');
|
||||
const dots = document.querySelectorAll('.dot');
|
||||
const prevBtn = document.querySelector('.prev-btn');
|
||||
const nextBtn = document.querySelector('.next-btn');
|
||||
|
||||
if (testimonialTrack && testimonialCards.length > 0) {
|
||||
let currentIndex = 0;
|
||||
const cardCount = testimonialCards.length;
|
||||
|
||||
// Function to update the slider position
|
||||
function updateSlider() {
|
||||
testimonialTrack.style.transform = `translateX(-${currentIndex * 100}%)`;
|
||||
|
||||
// Update active dot
|
||||
dots.forEach((dot, index) => {
|
||||
dot.classList.toggle('active', index === currentIndex);
|
||||
});
|
||||
}
|
||||
|
||||
// Event listeners for navigation buttons
|
||||
if (prevBtn) {
|
||||
prevBtn.addEventListener('click', function() {
|
||||
currentIndex = (currentIndex - 1 + cardCount) % cardCount;
|
||||
updateSlider();
|
||||
});
|
||||
}
|
||||
|
||||
if (nextBtn) {
|
||||
nextBtn.addEventListener('click', function() {
|
||||
currentIndex = (currentIndex + 1) % cardCount;
|
||||
updateSlider();
|
||||
});
|
||||
}
|
||||
|
||||
// Event listeners for dots
|
||||
dots.forEach((dot, index) => {
|
||||
dot.addEventListener('click', function() {
|
||||
currentIndex = index;
|
||||
updateSlider();
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-advance the slider every 5 seconds
|
||||
let sliderInterval = setInterval(function() {
|
||||
currentIndex = (currentIndex + 1) % cardCount;
|
||||
updateSlider();
|
||||
}, 5000);
|
||||
|
||||
// Pause auto-advance when hovering over the slider
|
||||
testimonialTrack.addEventListener('mouseenter', function() {
|
||||
clearInterval(sliderInterval);
|
||||
});
|
||||
|
||||
// Resume auto-advance when mouse leaves the slider
|
||||
testimonialTrack.addEventListener('mouseleave', function() {
|
||||
sliderInterval = setInterval(function() {
|
||||
currentIndex = (currentIndex + 1) % cardCount;
|
||||
updateSlider();
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -41,6 +41,7 @@ export default function ThreadPage({ params }: { params: ThreadParams }) {
|
|||
const [showScrollButton, setShowScrollButton] = useState(false);
|
||||
const [buttonOpacity, setButtonOpacity] = useState(0);
|
||||
const [isLatestMessageVisible, setIsLatestMessageVisible] = useState(true);
|
||||
const [userHasScrolled, setUserHasScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthLoading && !user) {
|
||||
|
@ -245,14 +246,16 @@ export default function ThreadPage({ params }: { params: ThreadParams }) {
|
|||
|
||||
console.log(`[PAGE] Setting up stream for agent run ${runId}`);
|
||||
|
||||
// Start streaming the agent's responses
|
||||
// Start streaming the agent's responses with improved implementation
|
||||
const cleanup = streamAgent(runId, {
|
||||
onMessage: (content: string) => {
|
||||
// Skip empty content chunks
|
||||
if (!content.trim()) return;
|
||||
|
||||
// Immediately append content as it arrives without forcing scroll
|
||||
setStreamContent(prev => prev + content);
|
||||
// Improved stream update with requestAnimationFrame for smoother UI updates
|
||||
window.requestAnimationFrame(() => {
|
||||
setStreamContent(prev => prev + content);
|
||||
});
|
||||
},
|
||||
onToolCall: (name: string, args: any) => {
|
||||
console.log('[PAGE] Tool call received:', name, args);
|
||||
|
@ -372,9 +375,47 @@ export default function ThreadPage({ params }: { params: ThreadParams }) {
|
|||
}
|
||||
};
|
||||
|
||||
// Scroll to bottom of messages
|
||||
// Check if user has scrolled up from bottom
|
||||
const handleScroll = () => {
|
||||
if (!messagesContainerRef.current) return;
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = messagesContainerRef.current;
|
||||
|
||||
// If we're scrolled up a significant amount and latest message isn't visible
|
||||
const isScrolledUp = scrollHeight - scrollTop - clientHeight > 100;
|
||||
|
||||
if (isScrolledUp !== showScrollButton) {
|
||||
setShowScrollButton(isScrolledUp);
|
||||
setButtonOpacity(isScrolledUp ? 1 : 0);
|
||||
}
|
||||
|
||||
// Track if user has manually scrolled
|
||||
if (isScrolledUp) {
|
||||
setUserHasScrolled(true);
|
||||
} else {
|
||||
// Reset user scroll state when they scroll back to bottom
|
||||
setUserHasScrolled(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-scroll when messages change
|
||||
useEffect(() => {
|
||||
// Only auto-scroll if:
|
||||
// 1. User hasn't manually scrolled up, or
|
||||
// 2. This is a new message sent by the user (but not during streaming)
|
||||
const isNewUserMessage = messages.length > 0 && messages[messages.length - 1]?.role === 'user';
|
||||
|
||||
if ((!userHasScrolled && isLatestMessageVisible) || (isNewUserMessage && !isStreaming)) {
|
||||
scrollToBottom();
|
||||
}
|
||||
}, [messages, streamContent, isLatestMessageVisible, userHasScrolled, isStreaming]);
|
||||
|
||||
// Scroll to bottom explicitly when user sends a message
|
||||
const scrollToBottom = (behavior: ScrollBehavior = 'smooth') => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior });
|
||||
if (behavior === 'instant' || behavior === 'auto') {
|
||||
setUserHasScrolled(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Setup intersection observer to detect if latest message is visible
|
||||
|
@ -412,48 +453,13 @@ export default function ThreadPage({ params }: { params: ThreadParams }) {
|
|||
setButtonOpacity(shouldShowButton ? 1 : 0);
|
||||
}, [isLatestMessageVisible]);
|
||||
|
||||
// Check if user has scrolled up from bottom
|
||||
const handleScroll = () => {
|
||||
if (!messagesContainerRef.current) return;
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = messagesContainerRef.current;
|
||||
|
||||
// If we're scrolled up a significant amount and latest message isn't visible
|
||||
const isScrolledUp = scrollHeight - scrollTop - clientHeight > 100;
|
||||
|
||||
if (isScrolledUp !== showScrollButton) {
|
||||
setShowScrollButton(isScrolledUp);
|
||||
setButtonOpacity(isScrolledUp ? 1 : 0);
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-scroll when messages change
|
||||
useEffect(() => {
|
||||
// Only auto-scroll if user hasn't manually scrolled up
|
||||
if (isLatestMessageVisible) {
|
||||
scrollToBottom();
|
||||
}
|
||||
}, [messages, streamContent, isLatestMessageVisible]);
|
||||
|
||||
// Initial scroll to bottom with animation
|
||||
useEffect(() => {
|
||||
if (!isLoading) {
|
||||
// Short delay to ensure content is rendered before scrolling
|
||||
const timer = setTimeout(() => {
|
||||
scrollToBottom('smooth');
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isLoading]);
|
||||
|
||||
// Update UI states when agent status changes
|
||||
useEffect(() => {
|
||||
// Scroll to bottom when agent starts responding
|
||||
if (agentStatus === 'running') {
|
||||
// Scroll to bottom when agent starts responding, but only if user hasn't scrolled up manually
|
||||
if (agentStatus === 'running' && !userHasScrolled) {
|
||||
scrollToBottom();
|
||||
}
|
||||
}, [agentStatus]);
|
||||
}, [agentStatus, userHasScrolled]);
|
||||
|
||||
// Only show a full-screen loader on the very first load
|
||||
if (isAuthLoading || (isLoading && !initialLoadCompleted.current)) {
|
||||
|
@ -483,6 +489,12 @@ export default function ThreadPage({ params }: { params: ThreadParams }) {
|
|||
|
||||
const projectName = project?.name || 'Loading...';
|
||||
|
||||
// Make sure clicking the scroll button resets user scroll state
|
||||
const handleScrollButtonClick = () => {
|
||||
scrollToBottom();
|
||||
setUserHasScrolled(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6 flex flex-col h-[calc(100vh-64px)]">
|
||||
<div className="mb-6">
|
||||
|
@ -533,7 +545,7 @@ export default function ThreadPage({ params }: { params: ThreadParams }) {
|
|||
</div>
|
||||
))}
|
||||
|
||||
{/* Streaming content */}
|
||||
{/* Streaming content with improved rendering */}
|
||||
{streamContent && (
|
||||
<div
|
||||
ref={latestMessageRef}
|
||||
|
@ -542,7 +554,23 @@ export default function ThreadPage({ params }: { params: ThreadParams }) {
|
|||
<div className="max-w-[80%] px-4 py-3 rounded-2xl shadow-sm bg-white border border-zinc-100 rounded-bl-none">
|
||||
<div className="whitespace-pre-wrap text-sm break-words overflow-hidden">
|
||||
{streamContent}
|
||||
{isStreaming && <span className="inline-block h-4 w-0.5 bg-zinc-800 ml-0.5 animate-pulse"></span>}
|
||||
{isStreaming && (
|
||||
<span className="inline-flex items-center ml-0.5">
|
||||
<span
|
||||
className="inline-block h-4 w-0.5 bg-zinc-800 mx-px"
|
||||
style={{
|
||||
opacity: 0.7,
|
||||
animation: 'cursorBlink 1s ease-in-out infinite',
|
||||
}}
|
||||
/>
|
||||
<style jsx global>{`
|
||||
@keyframes cursorBlink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
`}</style>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -568,7 +596,7 @@ export default function ThreadPage({ params }: { params: ThreadParams }) {
|
|||
<div
|
||||
className="transition-all duration-700 ease-in-out"
|
||||
style={{
|
||||
height: (isStreaming || agentStatus === 'running') ? '40px' : '20px',
|
||||
height: (isStreaming || agentStatus === 'running') ? '20px' : '20px',
|
||||
opacity: (isStreaming || agentStatus === 'running') ? 1 : 0.5
|
||||
}}
|
||||
/>
|
||||
|
@ -586,7 +614,7 @@ export default function ThreadPage({ params }: { params: ThreadParams }) {
|
|||
visibility: showScrollButton ? 'visible' : 'hidden'
|
||||
}}
|
||||
>
|
||||
<div className="bg-zinc-900/90 backdrop-blur-sm rounded-full shadow-lg p-2.5 flex items-center justify-center hover:bg-black transition-all duration-200 transform hover:scale-105 cursor-pointer pointer-events-auto" onClick={() => scrollToBottom()}>
|
||||
<div className="bg-zinc-900/90 backdrop-blur-sm rounded-full shadow-lg p-2.5 flex items-center justify-center hover:bg-black transition-all duration-200 transform hover:scale-105 cursor-pointer pointer-events-auto" onClick={handleScrollButtonClick}>
|
||||
<ArrowDown className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -625,12 +653,31 @@ export default function ThreadPage({ params }: { params: ThreadParams }) {
|
|||
</Button>
|
||||
</form>
|
||||
|
||||
{/* Fixed height container to prevent layout shifts */}
|
||||
<div className="h-5 mt-1">
|
||||
{/* Status indicator with improved spacing and styling */}
|
||||
<div className="h-6 mt-2">
|
||||
{agentStatus === 'running' && (
|
||||
<div className="text-xs text-gray-500 text-center">
|
||||
{isStreaming ? 'Agent is responding...' : 'Agent is thinking...'}
|
||||
{isStreaming && ' Click the stop button to interrupt.'}
|
||||
<div className="flex items-center justify-center gap-1.5">
|
||||
<div className="text-xs text-zinc-500 flex items-center gap-1.5">
|
||||
{isStreaming ? (
|
||||
<>
|
||||
<span className="inline-flex items-center">
|
||||
<span className="relative flex h-2 w-2 mr-1">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
|
||||
</span>
|
||||
Agent is responding
|
||||
</span>
|
||||
<span className="font-normal border-l border-zinc-200 pl-1.5 ml-0.5 text-zinc-400">
|
||||
Press <kbd className="inline-flex items-center justify-center p-0.5 bg-zinc-100 border border-zinc-200 rounded text-zinc-600"><Square className="h-2.5 w-2.5" /></kbd> to stop
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="inline-flex items-center">
|
||||
<Loader2 className="h-3 w-3 animate-spin mr-1" />
|
||||
Agent is thinking...
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue