victor-dashboard/index.html
2025-06-02 18:54:48 +05:30

1322 lines
52 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Victor AI Agent Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Plus Jakarta Sans', sans-serif;
}
</style>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.glass-effect {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.animate-pulse-soft {
animation: pulse-soft 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse-soft {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.card-hover {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.search-input:focus {
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
@media (max-width: 640px) {
.mobile-stack {
flex-direction: column;
}
.mobile-full {
width: 100%;
}
}
.gradient-text {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.loading-skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* === Custom Enhancements === */
.glassy-card {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(12px);
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
html {
font-size: 15px;
}
@media (min-width: 1024px) {
html {
font-size: 16px;
}
}
.text-shadow {
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-up {
animation: fadeUp 0.4s ease-out;
}
.tooltip:hover::after {
content: attr(data-tooltip);
position: absolute;
background: #333;
color: white;
padding: 4px 8px;
font-size: 12px;
border-radius: 4px;
top: -32px;
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
z-index: 100;
}
.tooltip {
position: relative;
cursor: pointer;
}
</style>
<style>
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeLine {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-slide-up {
animation: slideUp 0.4s ease-out;
}
.animate-fade {
animation: fadeLine 0.6s ease-in;
}
</style>
</head>
<body class="bg-gray-50">
<!-- Mobile Header -->
<div class="lg:hidden bg-white shadow-sm border-b sticky top-0 z-50">
<div class="px-4 py-3 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-gradient-to-br from-blue-600 to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
<i class="fas fa-robot text-white text-lg"></i>
</div>
<div>
<h1 class="text-lg font-bold text-gray-800">Victor AI</h1>
<p class="text-xs text-gray-500">Trade Intelligence</p>
</div>
</div>
<button id="mobileMenuBtn" class="p-2 rounded-lg hover:bg-gray-100">
<i class="fas fa-bars text-gray-600"></i>
</button>
</div>
</div>
<div class="min-h-screen flex">
<!-- Sidebar -->
<div id="sidebar" class="hidden lg:flex fixed lg:relative z-40 w-64 bg-white shadow-xl border-r border-gray-200 flex-col">
<div class="p-6 border-b border-gray-100">
<div class="flex items-center gap-3">
<div class="w-12 h-12 bg-gradient-to-br from-blue-600 to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
<i class="fas fa-robot text-white text-xl"></i>
</div>
<div>
<h1 class="text-xl font-bold gradient-text">Victor AI</h1>
<p class="text-sm text-gray-500">Trade Intelligence Dashboard</p>
</div>
</div>
</div>
<nav class="flex-1 p-4 space-y-2">
<a href="#" class="flex items-center gap-3 px-4 py-3 bg-blue-50 text-blue-700 rounded-xl font-medium">
<i class="fas fa-search w-4"></i>
<span>Search Database</span>
</a>
<a href="#" class="flex items-center gap-3 px-4 py-3 text-gray-600 hover:bg-gray-50 rounded-xl transition-colors">
<i class="fas fa-chart-bar w-4"></i>
<span>Analytics</span>
</a>
<a href="#" class="flex items-center gap-3 px-4 py-3 text-gray-600 hover:bg-gray-50 rounded-xl transition-colors">
<i class="fas fa-bookmark w-4"></i>
<span>Saved Searches</span>
</a>
<a href="#" class="flex items-center gap-3 px-4 py-3 text-gray-600 hover:bg-gray-50 rounded-xl transition-colors">
<i class="fas fa-cog w-4"></i>
<span>Settings</span>
</a>
</nav>
<div class="p-4 border-t border-gray-100">
<div class="bg-gradient-to-r from-blue-50 to-purple-50 p-4 rounded-xl">
<h3 class="font-semibold text-gray-800 mb-2">Need Help?</h3>
<p class="text-sm text-gray-600 mb-3">Get support with your searches</p>
<button class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
Contact Support
</button>
</div>
</div>
</div>
<!-- Mobile Sidebar Overlay -->
<div id="sidebarOverlay" class="hidden fixed inset-0 bg-black bg-opacity-50 z-30 lg:hidden"></div>
<!-- Main Content -->
<div class="flex-1 lg:ml-0">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 lg:py-8">
<!-- Desktop Header -->
<div class="hidden lg:block mb-8">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold text-gray-800 mb-2">Trade Intelligence Search</h1>
<p class="text-white">Find exporters, importers, and trade data from around the world</p>
</div>
<div class="text-right">
<p class="text-sm text-white">Last updated</p>
<p class="font-semibold text-gray-800" id="lastUpdated">Just now</p>
</div>
</div>
</div>
<!-- Search Section -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-4 lg:p-6 mb-6 lg:mb-8">
<div class="flex flex-col sm:flex-row gap-3">
<div class="flex-1">
<input
id="query"
type="text"
placeholder="find exporter from india, find importer from uk, find gulfood data"
value=""
class="w-full px-4 py-3 rounded-xl border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition-all search-input text-sm lg:text-base"
/>
</div>
<button
onclick="window.callWorkflow()"
class="w-full sm:w-auto bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-xl font-semibold transition-all flex items-center justify-center gap-2 min-w-[120px]"
id="searchBtn"
>
<i class="fas fa-search"></i>
<span>Search</span>
</button>
</div>
<!-- Quick Search Suggestions -->
<div class="mt-4 flex flex-wrap gap-2">
<span class="text-sm text-gray-500 mr-2">Quick searches:</span>
<button onclick="window.setQuickSearch('find exporter from india')" class="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-full transition-colors">
Exporters from India
</button>
<button onclick="window.setQuickSearch('find importer from uk')" class="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-full transition-colors">
UK Importers
</button>
<button onclick="window.setQuickSearch('find gulfood data')" class="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-full transition-colors">
Gulfood Exhibition
</button>
</div>
</div>
<!-- CRUD Command Section -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-4 lg:p-6 mb-6 fade-up">
<h2 class="text-lg font-bold text-gray-800 mb-3">🔧 Create / Update / Delete</h2>
<div class="flex flex-col sm:flex-row gap-3">
<input
id="crudCommand"
type="text"
placeholder="e.g., Create importer Indi Tech from India email info@inditech.in"
class="w-full px-4 py-3 rounded-xl border border-gray-300 focus:border-purple-500 focus:ring-2 focus:ring-purple-200 outline-none transition-all search-input text-sm lg:text-base"
/>
<button
onclick="window.callCrudOperation()"
class="w-full sm:w-auto bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-xl font-semibold transition-all flex items-center justify-center gap-2 min-w-[120px]"
id="crudBtn"
>
<i class="fas fa-terminal"></i>
<span>Execute</span>
</button>
</div>
</div>
<!-- CRUD Result Section -->
<div id="crudResultSection" class="hidden mb-6 fade-up">
<div class="bg-white rounded-xl border border-gray-200 p-4">
<h3 class="text-sm font-semibold text-gray-700 mb-3">📝 Operation Result</h3>
<div id="crudResultHtml" class="text-sm text-gray-800 leading-relaxed"></div>
</div>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 lg:gap-6 mb-6 lg:mb-8">
<div class="bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-gray-200">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500">Total Searches</p>
<p class="text-xl lg:text-2xl font-bold text-gray-800" id="totalSearches">0</p>
</div>
<div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
<i class="fas fa-search text-blue-600"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-gray-200">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500">Results Found</p>
<p class="text-xl lg:text-2xl font-bold text-gray-800" id="totalResults">0</p>
</div>
<div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
<i class="fas fa-chart-line text-green-600"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-gray-200">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500">Countries</p>
<p class="text-xl lg:text-2xl font-bold text-gray-800">150+</p>
</div>
<div class="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
<i class="fas fa-globe text-purple-600"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-4 lg:p-6 shadow-sm border border-gray-200">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500">Response Time</p>
<p class="text-xl lg:text-2xl font-bold text-gray-800" id="responseTime">0ms</p>
</div>
<div class="w-10 h-10 bg-orange-100 rounded-lg flex items-center justify-center">
<i class="fas fa-bolt text-orange-600"></i>
</div>
</div>
</div>
</div>
<!-- Results Section -->
<div id="resultsSection" class="hidden">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-4 lg:mb-6">
<h2 class="text-xl lg:text-2xl font-bold text-gray-800 mb-2 sm:mb-0">Search Results</h2>
<div class="flex items-center gap-4">
<p id="resultsCount" class="text-sm text-white"></p>
<div class="flex items-center gap-2">
<button onclick="window.exportResults()" class="text-sm bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-2 rounded-lg transition-colors flex items-center gap-2">
<i class="fas fa-download text-xs"></i>
Export
</button>
<button onclick="window.shareResults()" class="text-sm bg-blue-100 hover:bg-blue-200 text-blue-700 px-3 py-2 rounded-lg transition-colors flex items-center gap-2">
<i class="fas fa-share text-xs"></i>
Share
</button>
</div>
</div>
</div>
<div id="results" class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 lg:gap-6"></div>
<!-- Load More Button -->
<div id="loadMoreSection" class="hidden mt-8 text-center">
<button onclick="window.loadMoreResults()" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-6 py-3 rounded-xl font-medium transition-colors">
Load More Results
</button>
</div>
</div>
<!-- Empty State -->
<div id="emptyState" class="text-center py-12 lg:py-16">
<div class="max-w-md mx-auto">
<div class="w-20 h-20 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-6">
<i class="fas fa-search text-blue-600 text-2xl"></i>
</div>
<h3 class="text-xl font-semibold text-gray-800 mb-3">Start Your Search</h3>
<p class="text-white mb-6">Enter your search query above to find exporters, importers, and trade data from our comprehensive database.</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
<div class="bg-white p-4 rounded-lg border border-gray-200">
<h4 class="font-medium text-gray-800 mb-2">Find Exporters</h4>
<p class="text-gray-600">Search by country, product, or company name</p>
</div>
<div class="bg-white p-4 rounded-lg border border-gray-200">
<h4 class="font-medium text-gray-800 mb-2">Find Importers</h4>
<p class="text-gray-600">Discover potential buyers worldwide</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Declare all variables in global scope first
let totalSearchCount = 0;
let totalResultCount = 0;
let currentSearchData = [];
// Enhanced search function with better error handling
async function callWorkflow() {
const query = document.getElementById('query').value.trim();
if (!query) {
alert('Please enter a search query');
return;
}
const searchBtn = document.getElementById('searchBtn');
const results = document.getElementById('results');
const resultsSection = document.getElementById('resultsSection');
const resultsCount = document.getElementById('resultsCount');
const emptyState = document.getElementById('emptyState');
// Show loading state and clear previous results
searchBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Searching...';
searchBtn.disabled = true;
resultsSection.classList.add('hidden');
emptyState.classList.add('hidden');
// Show loading skeleton
results.innerHTML = Array(6).fill().map(() => `
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
<div class="animate-pulse">
<div class="flex items-center gap-3 mb-4">
<div class="w-8 h-6 bg-gray-200 rounded"></div>
<div class="h-6 bg-gray-200 rounded flex-1"></div>
</div>
<div class="space-y-3">
<div class="h-4 bg-gray-200 rounded w-3/4"></div>
<div class="h-4 bg-gray-200 rounded w-1/2"></div>
<div class="h-4 bg-gray-200 rounded w-2/3"></div>
</div>
</div>
</div>
`).join('');
resultsSection.classList.remove('hidden');
const startTime = Date.now();
try {
console.log('Making API request with query:', query);
const response = await fetch(`https://thecyberlearn.app.n8n.cloud/webhook/search-airtable?query=${encodeURIComponent(query)}`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
console.log('Response status:', response.status);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
console.log('Full API Response:', data);
const responseTime = Date.now() - startTime;
// Process the API response
let cards = '';
let resultCount = 0;
// Handle the API response format (your API returns an array)
if (data && Array.isArray(data) && data.length > 0) {
const apiData = data[0];
console.log('Processing API data:', apiData);
if (apiData.success && apiData.content) {
// Try originalMessage first, then fallback to message
const messageContent = apiData.content.originalMessage || apiData.content.message;
console.log('Message content:', messageContent);
if (messageContent) {
const companies = parseApiResponse(messageContent);
resultCount = companies.length;
currentSearchData = companies;
companies.forEach((company, index) => {
cards += generateCompanyCardFromData(company, index + 1);
});
} else {
console.log('No message content found in API response');
}
} else {
console.log('API response indicates failure or no content');
}
} else if (data && data.success && data.content) {
// Handle single object response (fallback)
const messageContent = data.content.originalMessage || data.content.message;
if (messageContent) {
const companies = parseApiResponse(messageContent);
resultCount = companies.length;
currentSearchData = companies;
companies.forEach((company, index) => {
cards += generateCompanyCardFromData(company, index + 1);
});
}
} else if (data && data.output) {
// Fallback for older API format
const companies = parseApiResponse(data.output);
resultCount = companies.length;
currentSearchData = companies;
companies.forEach((company, index) => {
cards += generateCompanyCardFromData(company, index + 1);
});
} else {
console.log('Unexpected API response format:', data);
}
// Update UI
updateStats(resultCount, responseTime);
resultsCount.textContent = `Found ${resultCount} ${data[0]?.tableType || 'company'}(s) matching "${query}"`;
if (cards) {
results.innerHTML = cards;
resultsSection.classList.remove('hidden');
// Show load more if there are many results
if (resultCount > 20) {
document.getElementById('loadMoreSection').classList.remove('hidden');
}
} else {
results.innerHTML = `
<div class="col-span-full bg-white rounded-xl p-8 text-center">
<div class="text-4xl mb-3">😔</div>
<p class="text-gray-600 font-semibold">No results found</p>
<p class="text-gray-500 text-sm mt-2">Try a different search term</p>
</div>`;
resultsSection.classList.remove('hidden');
}
} catch (error) {
console.error('Search error:', error);
let errorMessage = 'Search failed';
let errorDetail = 'Please try again or contact support';
if (error.message.includes('401')) {
errorMessage = 'Authentication Error';
errorDetail = 'The API key or endpoint URL needs to be updated. Please contact the administrator.';
} else if (error.message.includes('404')) {
errorMessage = 'Service Not Found';
errorDetail = 'The search service is currently unavailable.';
} else if (error.message.includes('Failed to fetch')) {
errorMessage = 'Connection Error';
errorDetail = 'Please check your internet connection and try again.';
}
results.innerHTML = `
<div class="col-span-full bg-red-50 border border-red-200 rounded-xl p-6 text-center">
<div class="text-red-600 text-2xl mb-3">⚠️</div>
<p class="text-red-700 font-semibold">${errorMessage}</p>
<p class="text-red-600 text-sm mt-2">${errorDetail}</p>
<div class="mt-4 p-3 bg-red-100 rounded-lg text-left">
<p class="text-xs text-red-700 font-mono">${error.message}</p>
</div>
<button onclick="location.reload()" class="mt-4 bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg text-sm transition-colors">
Refresh Page
</button>
</div>`;
resultsSection.classList.remove('hidden');
} finally {
searchBtn.innerHTML = '<i class="fas fa-search"></i> <span>Search</span>';
searchBtn.disabled = false;
}
}
// Enhanced parsing function - handles your exact API format
function parseApiResponse(responseText) {
let companies = [];
try {
// Clean up the text and remove markdown/HTML formatting
let cleanText = responseText
.replace(/<[^>]*>/g, '') // Remove HTML tags
.replace(/\*\*/g, '') // Remove bold markdown
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Convert markdown links to plain text
.replace(/`([^`]+)`/g, '$1') // Remove code formatting
.replace(/🔍.*?🚢/g, '') // Remove search header with ship emoji
.replace(/🔍.*?📦/g, '') // Remove search header with package emoji
.replace(/📊.*?────+/g, '') // Remove stats line
.replace(/💡.*$/gm, '') // Remove suggestions
.trim();
console.log('Cleaned text:', cleanText.substring(0, 500)); // Debug log
// Split by numbered company entries (1. 2. 3. etc.)
const splitPattern = /\b(\d+)\.\s+/;
const parts = cleanText.split(splitPattern);
// Extract company blocks (every 3rd element after split)
const companyBlocks = [];
for (let i = 2; i < parts.length; i += 2) {
if (parts[i] && parts[i].trim()) {
companyBlocks.push(parts[i].trim());
}
}
console.log('Company blocks found:', companyBlocks.length); // Debug log
companyBlocks.forEach((block, index) => {
if (!block || block.trim().length === 0) return;
console.log(`Processing block ${index + 1}:`, block.substring(0, 200)); // Debug log
const lines = block.split('\n').map(line => line.trim()).filter(line => line.length > 0);
// Extract company name (first line)
const companyName = lines[0] || '';
// Extract phone numbers (look for ☎️ emoji followed by numbers)
const phoneMatches = block.match(/☎️\s*([\d\s\-\(\)\.E\+]+)/g) || [];
const phone = phoneMatches[0] ? phoneMatches[0].replace(/☎️\s*/, '').trim() : '';
// Extract emails (look for 📧 emoji followed by email)
const emailMatches = block.match(/📧\s*([^\s\n]+@[^\s\n]+)/g) || [];
const email = emailMatches[0] ? emailMatches[0].replace(/📧\s*/, '').trim() : '';
// Extract websites (look for 🔗 emoji followed by URL)
const websiteMatches = block.match(/🔗\s*(https?:\/\/[^\s\n]+)/g) || [];
const website = websiteMatches[0] ? websiteMatches[0].replace(/🔗\s*/, '').replace(/\/$/, '') : '';
// Extract location (look for 📍 emoji followed by location)
const locationMatches = block.match(/📍\s*([^\n🔗📧☎📦🏷]+)/g) || [];
let location = 'Unknown';
if (locationMatches.length > 0) {
location = locationMatches[0].replace(/📍\s*/, '').trim();
}
// Fallback location detection
if (location === 'Unknown' || location.length < 2) {
if (block.includes('United Kingdom')) location = 'United Kingdom';
else if (block.includes('India')) location = 'India';
else if (block.includes('USA') || block.includes('United States')) location = 'USA';
else if (block.includes('UAE') || block.includes('Dubai')) location = 'UAE';
else if (block.includes('China')) location = 'China';
}
// Extract products (look for 📦 emoji)
const productMatches = block.match(/📦\s*([^\n🏷]+)/g) || [];
const products = [];
productMatches.forEach(match => {
const productText = match.replace(/📦\s*/, '').trim();
const productList = productText.split(/[,&]/).map(p => p.trim()).filter(p => p.length > 0);
products.push(...productList);
});
// Extract brands (look for 🏷️ emoji)
const brandMatches = block.match(/🏷️\s*([^\n]+)/g) || [];
const brands = [];
brandMatches.forEach(match => {
const brandText = match.replace(/🏷️\s*/, '').trim();
const brandList = brandText.split(/[,&]/).map(b => b.trim()).filter(b => b.length > 0);
brands.push(...brandList);
});
// Combine products and brands
const allProducts = [...products, ...brands].slice(0, 8); // Limit to 8 items
if (companyName && companyName.length > 0) {
companies.push({
name: companyName,
phone: phone,
email: email,
website: website,
address: location,
products: allProducts
});
}
});
console.log('Final parsed companies:', companies); // Debug log
} catch (error) {
console.error('Error parsing API response:', error);
console.log('Raw response text:', responseText);
}
return companies;
}
// Enhanced card generation
function generateCompanyCardFromData(company, index) {
const location = company.address?.toLowerCase() || '';
let flagHtml = '';
if (location.includes('india')) {
flagHtml = '<div class="w-8 h-6 bg-gradient-to-b from-orange-400 via-white to-green-500 rounded border border-gray-300 relative"><div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-4 h-4 border-2 border-blue-800 rounded-full"></div></div>';
} else if (location.includes('united kingdom') || location.includes('uk')) {
flagHtml = '<div class="w-8 h-6 bg-blue-800 rounded border border-gray-300 relative"><div class="absolute inset-0 bg-gradient-to-br from-red-600 via-white to-red-600" style="clip-path: polygon(0 0, 100% 0, 0 100%)"></div><div class="absolute inset-0 bg-gradient-to-bl from-red-600 via-white to-red-600" style="clip-path: polygon(100% 0, 100% 100%, 0 100%)"></div><div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-1 h-full bg-red-600"></div><div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-full h-1 bg-red-600"></div></div>';
} else if (location.includes('usa') || location.includes('united states')) {
flagHtml = '<div class="w-8 h-6 bg-red-600 rounded border border-gray-300 relative"><div class="absolute top-0 left-0 w-3 h-3 bg-blue-800"></div><div class="absolute top-1 left-0 w-full h-0.5 bg-white"></div><div class="absolute top-2 left-0 w-full h-0.5 bg-white"></div></div>';
} else if (location.includes('uae') || location.includes('dubai')) {
flagHtml = '<div class="w-8 h-6 bg-gradient-to-b from-green-600 via-white via-black to-red-600 rounded border border-gray-300"></div>';
} else if (location.includes('china')) {
flagHtml = '<div class="w-8 h-6 bg-red-600 rounded border border-gray-300 relative"><div class="absolute top-1 left-1 text-yellow-400 text-xs">⭐</div></div>';
} else {
flagHtml = '<div class="w-8 h-6 bg-gray-300 rounded border border-gray-300 flex items-center justify-center text-xs">🌍</div>';
}
return `
<div class="bg-white rounded-xl shadow-sm border border-gray-200 hover:shadow-lg transition-all duration-300 card-hover">
<div class="p-4 lg:p-6">
<div class="flex items-center gap-3 mb-4">
${flagHtml}
<h3 class="text-base lg:text-lg font-bold text-blue-700 hover:text-blue-800 cursor-pointer line-clamp-2">${company.name}</h3>
</div>
<div class="space-y-3 text-sm text-gray-700">
${company.phone ? `
<div class="flex items-center gap-2">
<i class="fas fa-phone text-green-600 w-4"></i>
<span class="break-all">${company.phone}</span>
</div>
` : ''}
${company.email ? `
<div class="flex items-center gap-2">
<i class="fas fa-envelope text-blue-600 w-4"></i>
<a href="mailto:${company.email}" class="text-blue-600 hover:text-blue-800 break-all">${company.email}</a>
</div>
` : ''}
${company.website ? `
<div class="flex items-center gap-2">
<i class="fas fa-globe text-purple-600 w-4"></i>
<a href="${company.website}" class="text-blue-600 hover:text-blue-800 underline break-all" target="_blank" rel="noopener">${company.website}</a>
</div>
` : ''}
${company.address ? `
<div class="flex items-start gap-2">
<i class="fas fa-map-marker-alt text-red-600 w-4 mt-0.5"></i>
<span class="text-gray-600">${company.address}</span>
</div>
` : ''}
</div>
${company.products && company.products.length > 0 ? `
<div class="mt-4 flex flex-wrap gap-2">
${company.products.slice(0, 4).map(product => `
<span class="bg-blue-50 text-blue-700 px-2 py-1 rounded-full text-xs font-medium">${product}</span>
`).join('')}
${company.products.length > 4 ? `
<span class="bg-gray-100 text-gray-600 px-2 py-1 rounded-full text-xs font-medium">+${company.products.length - 4} more</span>
` : ''}
</div>
` : ''}
<div class="mt-4 pt-4 border-t border-gray-100 flex gap-2">
</div>
</div>
</div>
`;
}
// Update stats
function updateStats(resultCount, responseTime) {
totalSearchCount++;
totalResultCount += resultCount;
document.getElementById('totalSearches').textContent = totalSearchCount;
document.getElementById('totalResults').textContent = totalResultCount;
document.getElementById('responseTime').textContent = responseTime + 'ms';
updateLastUpdated();
}
// Update last updated time
function updateLastUpdated() {
document.getElementById('lastUpdated').textContent = new Date().toLocaleTimeString();
}
// Quick search functionality
function setQuickSearch(query) {
document.getElementById('query').value = query;
window.callWorkflow();
}
// Export functionality
function exportResults() {
if (currentSearchData.length === 0) return;
const headers = ['Company Name', 'Phone', 'Email', 'Website', 'Address', 'Products'];
const csvContent = [
headers.join(','),
...currentSearchData.map(company => [
`"${company.name}"`,
`"${company.phone || ''}"`,
`"${company.email || ''}"`,
`"${company.website || ''}"`,
`"${company.address || ''}"`,
`"${(company.products || []).join('; ')}"`
].join(','))
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `search_results_${new Date().toISOString().split('T')[0]}.csv`;
a.click();
window.URL.revokeObjectURL(url);
}
// Share functionality
function shareResults() {
if (navigator.share && currentSearchData.length > 0) {
navigator.share({
title: 'Trade Search Results',
text: `Found ${currentSearchData.length} companies in search results`,
url: window.location.href
});
} else {
navigator.clipboard.writeText(window.location.href).then(() => {
alert('Link copied to clipboard!');
});
}
}
// Load more functionality
function loadMoreResults() {
document.getElementById('loadMoreSection').classList.add('hidden');
}
// Additional functionality
function viewCompanyDetails(companyName) {
alert(`Viewing details for: ${companyName}`);
}
function saveCompany(companyName) {
alert(`Saved: ${companyName}`);
}
function shareCompany(companyName) {
if (navigator.share) {
navigator.share({
title: companyName,
text: `Check out this company: ${companyName}`,
url: window.location.href
});
} else {
navigator.clipboard.writeText(`${companyName} - ${window.location.href}`).then(() => {
alert('Company info copied to clipboard!');
});
}
}
async function callCrudOperation() {
const query = document.getElementById('crudCommand').value.trim();
if (!query) {
alert('Please enter a CRUD command');
return;
}
const crudBtn = document.getElementById('crudBtn');
crudBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Executing...';
crudBtn.disabled = true;
try {
const response = await fetch(`https://thecyberlearn.app.n8n.cloud/webhook/search-airtable?query=${encodeURIComponent(query)}`, {
method: 'GET',
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
alert(result.message || 'Operation completed');
} catch (error) {
console.error('CRUD error:', error);
alert('Failed to execute command: ' + error.message);
} finally {
crudBtn.innerHTML = '<i class="fas fa-terminal"></i> <span>Execute</span>';
crudBtn.disabled = false;
}
}
async function callCrudOperation() {
const query = document.getElementById('crudCommand').value.trim();
if (!query) {
alert('Please enter a CRUD command');
return;
}
const crudBtn = document.getElementById('crudBtn');
crudBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Executing...';
crudBtn.disabled = true;
try {
const response = await fetch(`https://thecyberlearn.app.n8n.cloud/webhook/search-airtable?query=${encodeURIComponent(query)}`, {
method: 'GET',
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
const htmlContent = result?.content?.message || '✅ Operation completed.';
document.getElementById('crudResultHtml').innerHTML = htmlContent;
document.getElementById('crudResultSection').classList.remove('hidden');
} catch (error) {
console.error('CRUD error:', error);
document.getElementById('crudResultHtml').innerHTML = '<div class="text-red-600">❌ Failed to execute: ' + error.message + '</div>';
document.getElementById('crudResultSection').classList.remove('hidden');
} finally {
crudBtn.innerHTML = '<i class="fas fa-terminal"></i> <span>Execute</span>';
crudBtn.disabled = false;
}
}
async function callCrudOperation() {
const query = document.getElementById('crudCommand').value.trim();
if (!query) {
alert('Please enter a CRUD command');
return;
}
const crudBtn = document.getElementById('crudBtn');
crudBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Executing...';
crudBtn.disabled = true;
try {
const response = await fetch(`https://thecyberlearn.app.n8n.cloud/webhook/search-airtable?query=${encodeURIComponent(query)}`, {
method: 'GET',
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
const raw = result?.content?.originalMessage || result?.content?.message || '✅ Operation completed.';
const clean = raw.replace(/<[^>]*>/g, '').replace(/\*\*/g, '');
document.getElementById('crudResultHtml').innerHTML = `
<div class="fade-up space-y-4 text-sm leading-relaxed text-gray-800">
<div class="bg-white border border-gray-200 shadow-md p-4 rounded-xl">
<div class="text-green-600 font-semibold text-base mb-2 flex items-center gap-2">
<i class="fas fa-check-circle"></i> Operation Successful
</div>
<pre class="whitespace-pre-wrap text-gray-700">${clean}</pre>
</div>
</div>
`;
document.getElementById('crudResultSection').classList.remove('hidden');
} catch (error) {
console.error('CRUD error:', error);
document.getElementById('crudResultHtml').innerHTML = `
<div class="bg-red-100 border border-red-300 text-red-800 px-4 py-3 rounded-lg">
❌ Failed to execute: ${error.message}
</div>`;
document.getElementById('crudResultSection').classList.remove('hidden');
} finally {
crudBtn.innerHTML = '<i class="fas fa-terminal"></i> <span>Execute</span>';
crudBtn.disabled = false;
}
}
async function callCrudOperation() {
const query = document.getElementById('crudCommand').value.trim();
if (!query) {
alert('Please enter a CRUD command');
return;
}
const crudBtn = document.getElementById('crudBtn');
crudBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Executing...';
crudBtn.disabled = true;
try {
const response = await fetch(`https://thecyberlearn.app.n8n.cloud/webhook/search-airtable?query=${encodeURIComponent(query)}`, {
method: 'GET',
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
const raw = result?.content?.originalMessage || result?.content?.message || '✅ Operation completed.';
const clean = raw.replace(/<[^>]*>/g, '').replace(/\*\*/g, '');
const operation = result?.operation || 'info';
const type = result?.content?.messageType || 'success';
let bg = 'bg-emerald-50', border = 'border-emerald-200', text = 'text-emerald-800', icon = 'fa-check-circle', title = 'Success!';
if (operation === 'delete' || type === 'error') {
bg = 'bg-red-50'; border = 'border-red-200'; text = 'text-red-800'; icon = 'fa-times-circle'; title = 'Error!';
} else if (operation === 'update') {
bg = 'bg-amber-50'; border = 'border-amber-200'; text = 'text-amber-800'; icon = 'fa-exclamation-circle'; title = 'Updated!';
} else if (operation === 'read') {
bg = 'bg-blue-50'; border = 'border-blue-200'; text = 'text-blue-800'; icon = 'fa-info-circle'; title = 'Fetched!';
}
document.getElementById('crudResultHtml').innerHTML = `
<div class="fade-up flex items-start gap-3 p-4 rounded-xl border shadow-sm text-sm leading-6 ${bg} ${border} ${text}">
<i class="fas ${icon} mt-1 text-lg"></i>
<div>
<div class="font-semibold">${title}</div>
<div class="whitespace-pre-wrap">${clean}</div>
</div>
</div>
`;
document.getElementById('crudResultSection').classList.remove('hidden');
} catch (error) {
console.error('CRUD error:', error);
document.getElementById('crudResultHtml').innerHTML = `
<div class="bg-red-100 border border-red-300 text-red-800 px-4 py-3 rounded-lg">
❌ Failed to execute: ${error.message}
</div>`;
document.getElementById('crudResultSection').classList.remove('hidden');
} finally {
crudBtn.innerHTML = '<i class="fas fa-terminal"></i> <span>Execute</span>';
crudBtn.disabled = false;
}
}
async function callCrudOperation() {
const query = document.getElementById('crudCommand').value.trim();
if (!query) {
alert('Please enter a CRUD command');
return;
}
const crudBtn = document.getElementById('crudBtn');
crudBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Executing...';
crudBtn.disabled = true;
try {
const response = await fetch(`https://thecyberlearn.app.n8n.cloud/webhook/search-airtable?query=${encodeURIComponent(query)}`, {
method: 'GET',
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
const raw = result?.content?.originalMessage || result?.content?.message || '✅ Operation completed.';
const clean = raw
.replace(/<[^>]*>/g, '')
.replace(/\*\*/g, '')
.replace(/Complete dataset returned.*included/gi, '')
.trim();
const operation = result?.operation || 'info';
const type = result?.content?.messageType || 'success';
let bg = 'bg-emerald-50', border = 'border-emerald-200', text = 'text-emerald-800', icon = 'fa-check-circle', title = 'Success!';
if (operation === 'delete' || type === 'error') {
bg = 'bg-red-50'; border = 'border-red-200'; text = 'text-red-800'; icon = 'fa-times-circle'; title = 'Error!';
} else if (operation === 'update') {
bg = 'bg-amber-50'; border = 'border-amber-200'; text = 'text-amber-800'; icon = 'fa-exclamation-circle'; title = 'Updated!';
} else if (operation === 'read') {
bg = 'bg-blue-50'; border = 'border-blue-200'; text = 'text-blue-800'; icon = 'fa-info-circle'; title = 'Fetched!';
}
// Build toast element
const toast = document.createElement('div');
toast.className = `fade-up fixed bottom-6 right-6 max-w-sm w-full z-50 shadow-xl rounded-xl border px-4 py-3 ${bg} ${border} ${text} animate-slide-up`;
toast.innerHTML = `
<div class="flex justify-between items-start gap-3">
<div class="flex items-start gap-2">
<i class="fas ${icon} mt-1 text-lg"></i>
<div>
<div class="font-semibold">${title}</div>
<div class="mt-1 space-y-1 text-sm leading-snug">${clean.split('\n').map(line => `<div class="animate-fade">${line}</div>`).join('')}</div>
</div>
</div>
<button onclick="this.parentElement.parentElement.remove()" class="text-xl font-bold hover:text-black/50 transition">×</button>
</div>
`;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 8000); // Auto-dismiss after 8 seconds
} catch (error) {
console.error('CRUD error:', error);
alert('❌ ' + error.message);
} finally {
crudBtn.innerHTML = '<i class="fas fa-terminal"></i> <span>Execute</span>';
crudBtn.disabled = false;
}
}
async function callCrudOperation() {
const queryInput = document.getElementById('crudCommand');
const query = queryInput.value.trim();
if (!query) {
alert('Please enter a CRUD command');
return;
}
const crudBtn = document.getElementById('crudBtn');
crudBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Executing...';
crudBtn.disabled = true;
try {
const response = await fetch(`https://thecyberlearn.app.n8n.cloud/webhook/search-airtable?query=${encodeURIComponent(query)}`, {
method: 'GET',
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
const raw = result?.content?.originalMessage || result?.content?.message || '✅ Operation completed.';
const clean = raw
.replace(/<[^>]*>/g, '')
.replace(/\*\*/g, '')
.replace(/Complete dataset returned.*included/gi, '')
.trim();
const operation = result?.operation || 'info';
const type = result?.content?.messageType || 'success';
const success = result?.success;
let bg = 'bg-emerald-100', border = 'border-emerald-300', text = 'text-emerald-900', icon = 'fa-check-circle', title = 'Success!';
if (!success && (operation === 'delete' || type === 'error')) {
bg = 'bg-red-100'; border = 'border-red-300'; text = 'text-red-800'; icon = 'fa-times-circle'; title = 'Error!';
} else if (operation === 'delete' && success) {
bg = 'bg-rose-100'; border = 'border-rose-300'; text = 'text-rose-800'; icon = 'fa-trash-alt'; title = 'Deleted!';
} else if (operation === 'update') {
bg = 'bg-amber-100'; border = 'border-amber-300'; text = 'text-amber-900'; icon = 'fa-pen'; title = 'Updated!';
} else if (operation === 'read') {
bg = 'bg-blue-100'; border = 'border-blue-300'; text = 'text-blue-900'; icon = 'fa-info-circle'; title = 'Fetched!';
}
// Build toast element
const toast = document.createElement('div');
toast.className = `fade-up fixed bottom-6 right-6 max-w-sm w-full z-50 shadow-xl rounded-xl border px-4 py-3 ${bg} ${border} ${text} animate-slide-up`;
toast.innerHTML = `
<div class="flex justify-between items-start gap-3">
<div class="flex items-start gap-2">
<i class="fas ${icon} mt-1 text-lg"></i>
<div>
<div class="font-semibold">${title}</div>
<div class="mt-1 space-y-1 text-sm leading-snug">${clean.split('\n').map(line => `<div class="animate-fade">${line}</div>`).join('')}</div>
</div>
</div>
<button onclick="this.parentElement.parentElement.remove()" class="text-xl font-bold hover:text-black/50 transition">×</button>
</div>
`;
document.body.appendChild(toast);
queryInput.value = '';
setTimeout(() => toast.remove(), 8000);
} catch (error) {
console.error('CRUD error:', error);
alert('❌ ' + error.message);
} finally {
crudBtn.innerHTML = '<i class="fas fa-terminal"></i> <span>Execute</span>';
crudBtn.disabled = false;
}
}
async function callCrudOperation() {
const queryInput = document.getElementById('crudCommand');
const query = queryInput.value.trim();
if (!query) {
alert('Please enter a CRUD command');
return;
}
const crudBtn = document.getElementById('crudBtn');
crudBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Executing...';
crudBtn.disabled = true;
try {
const response = await fetch(`https://thecyberlearn.app.n8n.cloud/webhook/search-airtable?query=${encodeURIComponent(query)}`, {
method: 'GET',
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
const raw = result?.content?.originalMessage || result?.content?.message || '✅ Operation completed.';
const clean = raw
.replace(/<[^>]*>/g, '')
.replace(/\*\*/g, '')
.replace(/Complete dataset returned.*included/gi, '')
.trim();
const operation = result?.operation || 'info';
const type = result?.content?.messageType || 'success';
const success = result?.success;
// Determine toast style
let bg = 'bg-emerald-100', border = 'border-emerald-300', text = 'text-emerald-900', icon = 'fa-check-circle', title = 'Success!';
if (/cannot create/i.test(clean)) {
bg = 'bg-yellow-50'; border = 'border-yellow-200'; text = 'text-yellow-800'; icon = 'fa-clone'; title = 'Duplicate Found';
} else if (!success && (operation === 'delete' || type === 'error')) {
bg = 'bg-red-100'; border = 'border-red-300'; text = 'text-red-800'; icon = 'fa-times-circle'; title = 'Error!';
} else if (operation === 'delete' && success) {
bg = 'bg-rose-100'; border = 'border-rose-300'; text = 'text-rose-800'; icon = 'fa-trash-alt'; title = 'Deleted!';
} else if (operation === 'update') {
bg = 'bg-amber-100'; border = 'border-amber-300'; text = 'text-amber-900'; icon = 'fa-pen'; title = 'Updated!';
} else if (operation === 'read') {
bg = 'bg-blue-100'; border = 'border-blue-300'; text = 'text-blue-900'; icon = 'fa-info-circle'; title = 'Fetched!';
}
// Build toast element
const toast = document.createElement('div');
toast.className = `fade-up fixed bottom-6 right-6 max-w-sm w-full z-50 shadow-xl rounded-xl border px-4 py-3 ${bg} ${border} ${text} animate-slide-up`;
toast.innerHTML = `
<div class="flex justify-between items-start gap-3">
<div class="flex items-start gap-2">
<i class="fas ${icon} mt-1 text-lg"></i>
<div>
<div class="font-semibold">${title}</div>
<div class="mt-1 space-y-1 text-sm leading-snug">${clean.split('\n').map(line => `<div class="animate-fade">${line}</div>`).join('')}</div>
</div>
</div>
<button onclick="this.parentElement.parentElement.remove()" class="text-xl font-bold hover:text-black/50 transition">×</button>
</div>
`;
document.body.appendChild(toast);
queryInput.value = '';
setTimeout(() => toast.remove(), 8000);
} catch (error) {
console.error('CRUD error:', error);
alert('❌ ' + error.message);
} finally {
crudBtn.innerHTML = '<i class="fas fa-terminal"></i> <span>Execute</span>';
crudBtn.disabled = false;
}
}
// Make functions available globally
window.callWorkflow = callWorkflow;
window.setQuickSearch = setQuickSearch;
window.exportResults = exportResults;
window.shareResults = shareResults;
window.loadMoreResults = loadMoreResults;
window.saveCompany = saveCompany;
window.callCrudOperation = callCrudOperation;
window.shareCompany = shareCompany;
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
// Mobile menu functionality
document.getElementById('mobileMenuBtn').addEventListener('click', function() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.toggle('hidden');
overlay.classList.toggle('hidden');
});
document.getElementById('sidebarOverlay').addEventListener('click', function() {
document.getElementById('sidebar').classList.add('hidden');
document.getElementById('sidebarOverlay').classList.add('hidden');
});
// Enter key support for search
document.getElementById('query').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
window.callWorkflow();
}
});
// Initialize
updateLastUpdated();
});
</script>
</body>
</html>