import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" /** * Combines class names using clsx and tailwind-merge */ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } /** * Extracts an auth token from a Morphik URI * * @param uri - The Morphik connection URI (format: morphik://appname:token@host) * @returns The extracted token or null if invalid */ export function extractTokenFromUri(uri: string | undefined): string | null { if (!uri) return null; try { console.log('Attempting to extract token from URI:', uri); // The URI format is: morphik://appname:token@host // We need to extract just the token part (after the colon and before the @) // First get everything before the @ symbol const beforeAt = uri.split('@')[0]; if (!beforeAt) return null; // Now extract the token after the colon const parts = beforeAt.split('://'); if (parts.length < 2) return null; // Check if there's a colon in the second part, which separates app name from token const authPart = parts[1]; if (authPart.includes(':')) { // Format is appname:token const fullToken = authPart.split(':')[1]; console.log('Extracted token:', fullToken ? `${fullToken.substring(0, 5)}...` : 'null'); return fullToken; } else { // Old format with just token (no appname:) console.log('Extracted token (old format):', authPart ? `${authPart.substring(0, 5)}...` : 'null'); return authPart; } } catch (err) { console.error("Error extracting token from URI:", err); return null; } } /** * Extracts the host from a Morphik URI and creates an API base URL * * @param uri - The Morphik connection URI (format: morphik://appname:token@host) * @param defaultUrl - The default API URL to use if URI is invalid * @returns The API base URL derived from the URI host */ export function getApiBaseUrlFromUri(uri: string | undefined, defaultUrl: string = 'http://localhost:8000'): string { // If URI is empty or undefined, connect to the default URL if (!uri || uri.trim() === '') { return defaultUrl; } try { // Expected format: morphik://{token}@{host} const match = uri.match(/^morphik:\/\/[^@]+@(.+)/); if (!match || !match[1]) return defaultUrl; // Default if invalid format // Get the host part let host = match[1]; // If it's local, localhost or 127.0.0.1, ensure http:// protocol and add port if needed if (host.includes('local') || host.includes('127.0.0.1')) { if (!host.includes('://')) { host = `http://${host}`; } // Add default port 8000 if no port specified if (!host.includes(':') || host.endsWith(':')) { host = `${host.replace(/:$/, '')}:8000`; } } else { // For other hosts, ensure https:// protocol if (!host.includes('://')) { host = `https://${host}`; } } console.log('Extracted API base URL:', host); return host; } catch (err) { console.error("Error extracting host from URI:", err); return defaultUrl; // Default on error } } /** * Creates authorization headers for API requests * * @param token - The auth token * @param contentType - Optional content type header * @returns Headers object with authorization */ export function createAuthHeaders(token: string | null, contentType?: string): HeadersInit { const headers: HeadersInit = {}; if (token) { headers['Authorization'] = `Bearer ${token}`; } if (contentType) { headers['Content-Type'] = contentType; } return headers; } /** * Generate a UUID v4 string * This is a simple implementation for client-side use */ export function generateUUID(): string { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } /** * Format date to relative time (e.g. "2 hours ago") */ export function formatRelativeTime(date: Date): string { const now = new Date(); const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); if (diffInSeconds < 60) { return 'just now'; } const diffInMinutes = Math.floor(diffInSeconds / 60); if (diffInMinutes < 60) { return `${diffInMinutes} minute${diffInMinutes > 1 ? 's' : ''} ago`; } const diffInHours = Math.floor(diffInMinutes / 60); if (diffInHours < 24) { return `${diffInHours} hour${diffInHours > 1 ? 's' : ''} ago`; } const diffInDays = Math.floor(diffInHours / 24); if (diffInDays < 30) { return `${diffInDays} day${diffInDays > 1 ? 's' : ''} ago`; } const diffInMonths = Math.floor(diffInDays / 30); if (diffInMonths < 12) { return `${diffInMonths} month${diffInMonths > 1 ? 's' : ''} ago`; } const diffInYears = Math.floor(diffInMonths / 12); return `${diffInYears} year${diffInYears > 1 ? 's' : ''} ago`; }