let authClient = null; // Function to initialize auth client function initializeAuthClient() { if (typeof PropelAuth !== 'undefined') { try { authClient = PropelAuth.createClient({ authUrl: "https://auth.frac.tl", enableBackgroundTokenRefresh: true, }); return true; } catch (e) { console.error("Failed to initialize auth client:", e); return false; } } return false; } // Try to initialize immediately if PropelAuth is already loaded initializeAuthClient(); // Also set up a periodic check to initialize if PropelAuth loads later const authClientInitInterval = setInterval(() => { if (authClient !== null) { clearInterval(authClientInitInterval); } else if (initializeAuthClient()) { clearInterval(authClientInitInterval); console.log("Auth client initialized successfully after waiting"); } }, 100); // Check every 100ms // Theme management functions function setTheme(theme) { document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('preferred-theme', theme); } function getTheme() { return localStorage.getItem('preferred-theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); } // Get UUID from cookie function getUuidFromCookie() { //console.log("all cookies:",document.cookie); const match = document.cookie.match(/agents_user_uuid=([^;]+)/); return match ? match[1] : null; } // Update token in store function function updateTokenInStore(token) { const uuid = getUuidFromCookie(); if (!uuid) { console.error('No UUID found in cookie'); return; } fetch('https://agents.frac.tl/update-token', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ uuid: uuid, token: token }) }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } //console.log('Token updated in store successfully'); return response.json(); }) .catch(error => { //console.error('Error updating token in store:', error); }); } // Logout function window.logout = function() { if (!authClient) { console.error('Auth client is not initialized yet'); return; } //console.log("Logging out..."); authClient.logout().then(() => { //console.log("Logout successful. Redirecting..."); window.location.href = 'https://agents.frac.tl/api/auth/logout'; }).catch((error) => { //console.error("Logout failed:", error); }); }; // Login function window.login = function() { if (!authClient) { console.error('Auth client is not initialized yet'); return; } // Use the current URL as the redirect destination after login authClient.redirectToLoginPage({ postLoginRedirectUrl: window.location.href }); }; document.addEventListener("DOMContentLoaded", function() { // If authClient initialization failed earlier, retry now if (!authClient && window.retryAuthInitialization) { window.retryAuthInitialization(); } function updateTokenCount(tokenCount) { let sidebarProfile = document.getElementById("sidebar-profile"); if (!sidebarProfile) return; // Exit if sidebar element doesn't exist // Check if the token display div already exists let tokenDisplay = document.getElementById("token-count-display"); if (tokenCount > 0) { if (!tokenDisplay) { // Create a new div if it doesn't exist tokenDisplay = document.createElement("div"); tokenDisplay.id = "token-count-display"; tokenDisplay.style.fontSize = "14px"; tokenDisplay.style.fontWeight = "bold"; tokenDisplay.style.marginBottom = "10px"; tokenDisplay.style.padding = "10px"; tokenDisplay.style.backgroundColor = "#f0f0f5"; tokenDisplay.style.borderRadius = "5px"; tokenDisplay.style.textAlign = "center"; tokenDisplay.style.color = "#333"; tokenDisplay.innerText = `Token Count: ${tokenCount}`; // Insert it before the sidebar-profile div sidebarProfile.parentNode.insertBefore(tokenDisplay, sidebarProfile); } else { // Update the existing token display tokenDisplay.innerText = `Token Count: ${tokenCount}`; } } else { // If tokenCount is 0 or undefined, remove the display if it exists if (tokenDisplay) { tokenDisplay.remove(); } } } // Listen for messages from the iframe window.addEventListener("message", function (event) { if (event.data && event.data.tokenCount !== undefined) { updateTokenCount(event.data.tokenCount); } }); // Initialize theme immediately setTheme(getTheme()); /** * Checks authentication info and (optionally) redirects if logged out. * Always tries to refresh the token by passing `true` to getAuthenticationInfoOrNull(). * @param {boolean} redirectIfLoggedOut - Whether to redirect if no valid auth info. * @returns {Promise} The auth info object or null on error/no token. */ function checkAuthInfo(redirectIfLoggedOut = true) { return authClient.getAuthenticationInfoOrNull(true) // <-- `true` ensures token is refreshed if needed .then(authInfo => { if (authInfo && authInfo.accessToken) { // Set the __pa_at cookie every time document.cookie = `__pa_at=${authInfo.accessToken}; Path=/; Secure; SameSite=Lax`; // Update token in store updateTokenInStore(authInfo.accessToken); //console.log("access token to set should be:",authInfo.accessToken); //console.log("Access token set in cookie:", document.cookie); // Debugging } else { //console.log('you are logged out'); if (redirectIfLoggedOut) { authClient.redirectToLoginPage({ postLoginRedirectUrl: window.location.href }); } } return authInfo; }) .catch(error => { //console.error("Error fetching authentication info:", error); return null; }); } // 1) Call checkAuthInfo() once on load checkAuthInfo(); // 2) Set an interval to always refresh the token & cookie (e.g., every 4 minutes) const REFRESH_INTERVAL = 4 * 60 * 1000; // 4 minutes setInterval(() => { checkAuthInfo(); }, REFRESH_INTERVAL); function addUserProfile(authInfo) { const sidebar = document.querySelector('[data-testid="stSidebarContent"]'); if (sidebar && authInfo) { // Remove existing profile section if it already exists const existingProfile = document.getElementById('sidebar-profile'); if (existingProfile) existingProfile.remove(); // Add padding to the bottom of the sidebar content const sidebarNav = sidebar.querySelector('[data-testid="stSidebarNav"]'); if (sidebarNav) { sidebarNav.style.paddingBottom = '80px'; } // Create profile container const profileContainer = document.createElement('div'); profileContainer.id = 'sidebar-profile'; profileContainer.style.position = 'fixed'; profileContainer.style.bottom = '0'; profileContainer.style.left = '0'; profileContainer.style.width = sidebar.offsetWidth + 'px'; profileContainer.style.padding = '15px'; profileContainer.style.display = 'flex'; profileContainer.style.alignItems = 'center'; profileContainer.style.justifyContent = 'space-between'; profileContainer.style.backgroundColor = 'rgba(42, 42, 42, 0.95)'; profileContainer.style.backdropFilter = 'blur(5px)'; profileContainer.style.borderTop = '1px solid rgba(255, 255, 255, 0.1)'; profileContainer.style.zIndex = '1000'; // Create user details (left side) const userDetails = document.createElement('div'); userDetails.style.display = 'flex'; userDetails.style.alignItems = 'center'; userDetails.style.gap = '10px'; userDetails.style.flex = '1'; userDetails.style.minWidth = '0'; userDetails.style.cursor = 'pointer'; userDetails.onclick = function(e) { e.preventDefault(); e.stopPropagation(); window.location.href = 'https://auth.frac.tl/account/settings'; }; // Create avatar const avatar = document.createElement('img'); avatar.src = authInfo.user.pictureUrl || 'https://via.placeholder.com/40'; avatar.style.width = '40px'; avatar.style.height = '40px'; avatar.style.borderRadius = '50%'; avatar.style.border = '2px solid #ffffff'; avatar.style.flexShrink = '0'; // Create name with truncation const firstName = authInfo?.user?.firstName || "User"; const lastName = authInfo?.user?.lastName || ""; const name = document.createElement('span'); name.textContent = `${firstName} ${lastName}`.trim(); name.style.fontWeight = '400'; name.style.fontSize = '0.93em'; name.style.color = 'white'; name.style.overflow = 'hidden'; name.style.textOverflow = 'ellipsis'; name.style.whiteSpace = 'nowrap'; userDetails.appendChild(avatar); userDetails.appendChild(name); // Create logout icon using SVG const logoutButton = document.createElement('div'); logoutButton.style.cursor = 'pointer'; logoutButton.style.width = '20px'; logoutButton.style.height = '20px'; logoutButton.style.flexShrink = '0'; logoutButton.style.marginLeft = '10px'; logoutButton.title = 'Logout'; logoutButton.innerHTML = ` `; logoutButton.onclick = function(e) { e.preventDefault(); e.stopPropagation(); // console.log("Logout clicked"); window.logout(); }; profileContainer.appendChild(userDetails); profileContainer.appendChild(logoutButton); sidebar.appendChild(profileContainer); const resizeObserver = new ResizeObserver(entries => { for (let entry of entries) { profileContainer.style.width = entry.contentRect.width + 'px'; } }); resizeObserver.observe(sidebar); } } const observer = new MutationObserver(function(mutations, me) { const sidebar = document.querySelector('[data-testid="stSidebarContent"]'); if (sidebar) { // Call checkAuthInfo with "false" to skip redirect if not logged in, // but we still pass `true` to getAuthenticationInfoOrNull so the token is refreshed. checkAuthInfo(false).then(authInfo => { addUserProfile(authInfo); }); me.disconnect(); } const welcomeName = document.querySelector(".welcome-name"); if (welcomeName) { // If needed, you can update welcomeName with user info here. // e.g. welcomeName.innerText = `${authInfo.user.firstName} ${authInfo.user.lastName}`; } }); observer.observe(document, { childList: true, subtree: true }); // --------------------------------------------------------------------- // New header shadow functionality on scroll: // --------------------------------------------------------------------- const headerElem = document.querySelector("header"); const mainContent = document.querySelector(".stMain"); if (headerElem && mainContent) { mainContent.addEventListener("scroll", function() { if (mainContent.scrollTop > 10) { headerElem.classList.add("header-shadow"); } else { headerElem.classList.remove("header-shadow"); } }); } }); // Add FontAwesome CSS to the document head const fontAwesomeLink = document.createElement('link'); fontAwesomeLink.rel = 'stylesheet'; fontAwesomeLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css'; document.head.appendChild(fontAwesomeLink); // Desired image URL and label text for the status widget const newImageSrc = 'https://agents.frac.tl/app/static/ai_orange_loading.gif'; const newLabelText = 'WORKING...'; // Function to add social links, Hire Fractl link, and theme toggle to header function addSocialLinks() { const header = document.querySelector('[data-testid="stHeader"]'); if (!header || document.querySelector('#social-links-container')) return; const container = document.createElement('div'); container.id = 'social-links-container'; container.style.cssText = ` position: absolute; right: 20px; top: 50%; transform: translateY(-50%); display: flex; gap: 15px; align-items: center; z-index: 1; `; // Function to get the current theme-based text color function getThemeTextColor() { return getTheme() === 'dark' ? '#FFFFFF' : '#000000'; } // Create Signup button (Subscription) const signupButton = document.createElement('button'); signupButton.innerHTML = ' Subscription'; signupButton.onclick = () => { // Use the streamlitNavigate function instead of directly manipulating history if (typeof streamlitNavigate === 'function') { streamlitNavigate('https://agents.frac.tl#Subscription'); } else { // Fallback if streamlitNavigate is not available //console.log("streamlitNavigate function not found, using fallback"); window.history.pushState({}, "", 'https://agents.frac.tl#Subscription'); // Dispatch popstate event to mimic streamlitNavigate behavior const navEvent = new PopStateEvent('popstate', { state: {} }); window.dispatchEvent(navEvent); selectTabFromHash(); } }; // Set initial button styles function updateButtonStyles() { const textColor = getThemeTextColor(); signupButton.style.cssText = ` font-family: "Open Sans", serif; font-optical-sizing: auto; background-color: #40c4a8; color: ${textColor}; border: none; border-radius: 5px; padding: 4px 6px; font-size: 14px; font-weight: 600; cursor: pointer; transition: background-color 0.3s ease; margin-right: 8px; `; hireButton.style.cssText = ` font-family: "Open Sans", serif; font-optical-sizing: auto; background-color: #40c4a8; color: ${textColor}; border: none; border-radius: 5px; padding: 4px 6px; font-size: 14px; font-weight: 600; cursor: pointer; transition: background-color 0.3s ease; `; } signupButton.addEventListener('mouseover', () => { signupButton.style.backgroundColor = '#f49076'; }); signupButton.addEventListener('mouseout', () => { signupButton.style.backgroundColor = '#40c4a8'; }); // Create Hire Us button const hireButton = document.createElement('button'); hireButton.innerHTML = ' Hire Us!'; hireButton.onclick = () => { window.open('https://www.frac.tl', '_blank', 'noopener noreferrer'); }; hireButton.addEventListener('mouseover', () => { hireButton.style.backgroundColor = '#f49076'; }); hireButton.addEventListener('mouseout', () => { hireButton.style.backgroundColor = '#40c4a8'; }); // Set initial button styles updateButtonStyles(); // Append buttons to container container.appendChild(signupButton); container.appendChild(hireButton); // Create theme toggle element const themeToggle = document.createElement('a'); themeToggle.href = '#'; themeToggle.style.cursor = 'pointer'; themeToggle.style.marginRight = '10px'; function updateThemeIcon() { const currentTheme = getTheme(); themeToggle.innerHTML = ``; } updateThemeIcon(); themeToggle.onclick = function(e) { e.preventDefault(); const newTheme = getTheme() === 'dark' ? 'light' : 'dark'; setTheme(newTheme); updateThemeIcon(); }; const separator = document.createElement('div'); separator.style.cssText = ` width: 2px; height: 24px; background-color: ${getTheme() === 'dark' ? '#40c4a8' : '#40c4a8'}; margin-right:12px; display: inline-block; `; // Observer to update button colors when theme changes const themeObserver = new MutationObserver(() => { separator.style.backgroundColor = getTheme() === 'dark' ? '#40c4a8' : '#40c4a8'; updateButtonStyles(); // Update button colors when theme changes }); themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] }); container.appendChild(themeToggle); container.appendChild(separator); // Add social icons [ { icon: 'fa-brands fa-linkedin', url: 'https://www.linkedin.com/company/fractl/' }, { icon: 'fa-brands fa-bluesky', url: 'https://bsky.app/profile/fractlagents.bsky.social' } ].forEach(({ icon, url }) => { const link = document.createElement('a'); link.href = url; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.innerHTML = ``; container.appendChild(link); }); header.appendChild(container); } // Function to update the image, label, and style of the status widget function updateElements() { const statusWidget = document.querySelector('[data-testid="stStatusWidget"]'); if (!statusWidget) return; // Try to add social links if not already present addSocialLinks(); const imgElement = statusWidget.querySelector('img'); const labelElement = statusWidget.querySelector('label'); if (imgElement && labelElement) { if (imgElement.src !== newImageSrc) { imgElement.src = newImageSrc; imgElement.style.display = 'block'; } if (labelElement.textContent !== newLabelText) { labelElement.textContent = newLabelText; labelElement.style.margin = '0'; const htmlElement = document.documentElement; const isDarkMode = htmlElement.getAttribute('data-theme') === 'dark'; labelElement.style.color = isDarkMode ? '#FFFFFF' : '#000000'; labelElement.style.fontWeight = 'bold'; labelElement.style.opacity = isDarkMode ? '0.8' : '1'; } const statusContent = statusWidget.querySelector('div'); if (statusContent) { const htmlElement = document.documentElement; const isDarkMode = htmlElement.getAttribute('data-theme') === 'dark'; statusContent.style.backgroundColor = isDarkMode ? '#292929' : '#FFFFFF'; statusContent.style.zIndex = '100'; statusContent.style.position = 'relative'; } } } // Observe changes to update status elements const statusObserver = new MutationObserver(() => { updateElements(); }); statusObserver.observe(document.body, { childList: true, subtree: true }); updateElements(); window.addEventListener('message', function(event) { if (event.data.type === 'streamlit:redirect') { const redirectUrl = event.data.url; if (redirectUrl) { window.location.href = redirectUrl; } } }); // Function to check the URL hash and trigger tab selection function selectTabFromHash() { if (window.location.hash === "#Subscription") { //console.log("Attempting to select Subscription tab"); // Clear any existing intervals first if (window.selectTabInterval) clearInterval(window.selectTabInterval); window.selectTabInterval = setInterval(() => { const tabs = document.querySelectorAll('.stTabs [role="tab"]'); //console.log("Found tabs:", tabs.length); if (tabs.length > 0) { clearInterval(window.selectTabInterval); let found = false; tabs.forEach(tab => { if (tab.innerText.includes("Subscription")) { //console.log("Found Subscription tab, clicking"); tab.click(); found = true; } }); if (!found) { // console.log("Subscription tab not found"); } } }, 100); } } selectTabFromHash(); window.onUsersnapLoad = function(api) { api.init(); }; var script = document.createElement('script'); script.defer = 1; script.src = 'https://widget.usersnap.com/global/load/a12a05b9-98f5-4b33-9100-648d43a53b5a?onload=onUsersnapLoad'; document.getElementsByTagName('head')[0].appendChild(script);