mirror of
https://github.com/thecyberlearn/supa-tid.git
synced 2026-08-18 07:53:01 +00:00
Update index.html
This commit is contained in:
parent
ca1fbaba57
commit
95f70b8454
253
index.html
253
index.html
@ -6,7 +6,6 @@
|
||||
<title>Trade Intelligence - AI Trade Management</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
// Configure Tailwind for proper styling
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
@ -85,14 +84,14 @@
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-lg font-bold text-white">Trade Intelligence</h1>
|
||||
<p class="text-sm text-white">Victor AI Agent</p>
|
||||
<p class="text-sm text-white">AI Trade Intelligence</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden md:flex items-center gap-6">
|
||||
<div class="text-right">
|
||||
<p class="text-sm text-white">Last updated</p>
|
||||
<p class="font-semibold text-white">6:08:40 AM</p>
|
||||
<p class="font-semibold text-white" id="lastUpdated"></p>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
@ -444,8 +443,8 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// n8n Authentication System
|
||||
const N8N_AUTH_WEBHOOK = 'https://m8taq6tk.rpcld.cc/webhook/login';
|
||||
// Authentication System
|
||||
const AUTH_WEBHOOK = 'https://m8taq6tk.rpcld.cc/webhook/login';
|
||||
|
||||
function checkAuthentication() {
|
||||
const isAuthenticated = localStorage.getItem('authenticated');
|
||||
@ -474,9 +473,7 @@ async function handleLogin(event) {
|
||||
submitButton.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Signing In...';
|
||||
|
||||
try {
|
||||
console.log('Attempting login with n8n webhook...');
|
||||
|
||||
const response = await fetch(N8N_AUTH_WEBHOOK, {
|
||||
const response = await fetch(AUTH_WEBHOOK, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -493,7 +490,6 @@ async function handleLogin(event) {
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('Authentication response:', result);
|
||||
|
||||
if (result.success === true) {
|
||||
// Successful login
|
||||
@ -514,7 +510,6 @@ async function handleLogin(event) {
|
||||
document.getElementById('password').value = '';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
errorDiv.textContent = 'Login failed. Please check your connection and try again.';
|
||||
errorDiv.classList.remove('hidden');
|
||||
document.getElementById('password').value = '';
|
||||
@ -533,12 +528,10 @@ function logout() {
|
||||
|
||||
// Check if required dependencies are loaded
|
||||
if (typeof supabase === 'undefined') {
|
||||
console.error('Supabase library not loaded');
|
||||
alert('Failed to load Supabase. Please refresh the page.');
|
||||
}
|
||||
|
||||
if (typeof APP_CONFIG === 'undefined') {
|
||||
console.error('APP_CONFIG not loaded');
|
||||
alert('Configuration not loaded. Please check config.js');
|
||||
}
|
||||
|
||||
@ -549,6 +542,99 @@ const tableFields = {
|
||||
gulfood: ["company_name", "address", "phone_number", "mail_id", "website"]
|
||||
};
|
||||
|
||||
// Validate and normalize AI-generated filters
|
||||
function validateAndNormalizeFilters(filters) {
|
||||
if (!filters || !Array.isArray(filters)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return filters.map(filter => {
|
||||
// Ensure filter has required properties
|
||||
if (!filter || typeof filter !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Normalize filter structure
|
||||
const normalizedFilter = {
|
||||
field: filter.field || 'all',
|
||||
operator: filter.operator || 'ilike',
|
||||
value: filter.value || ''
|
||||
};
|
||||
|
||||
// If the value doesn't have % wildcards, add them
|
||||
if (normalizedFilter.operator === 'ilike' && normalizedFilter.value &&
|
||||
!normalizedFilter.value.startsWith('%') && !normalizedFilter.value.endsWith('%')) {
|
||||
normalizedFilter.value = `%${normalizedFilter.value}%`;
|
||||
}
|
||||
|
||||
return normalizedFilter;
|
||||
}).filter(filter => filter !== null); // Remove invalid filters
|
||||
}
|
||||
|
||||
// Smart filter function for cross-table compatibility
|
||||
function filterForTable(filters, tableName) {
|
||||
const validFields = tableFields[tableName];
|
||||
|
||||
return filters.filter(filter => {
|
||||
// Always include fields that exist in this table
|
||||
if (validFields.includes(filter.field)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle cross-table company name searches
|
||||
if (filter.field === 'buyer' && tableName === 'seller') {
|
||||
// Map buyer field to seller field
|
||||
filter.field = 'seller';
|
||||
return true;
|
||||
} else if (filter.field === 'seller' && tableName === 'buyer') {
|
||||
// Map seller field to buyer field
|
||||
filter.field = 'buyer';
|
||||
return true;
|
||||
} else if ((filter.field === 'buyer' || filter.field === 'seller') && tableName === 'gulfood') {
|
||||
// Map buyer/seller field to company_name for gulfood
|
||||
filter.field = 'company_name';
|
||||
return true;
|
||||
} else if (filter.field === 'company_name' && (tableName === 'buyer' || tableName === 'seller')) {
|
||||
// Map company_name to appropriate field for buyer/seller tables
|
||||
filter.field = tableName;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle generic company searches - create appropriate company name filter for each table
|
||||
const companySearchFields = ['company', 'name', 'business_name', 'firm', 'entity', 'all'];
|
||||
if (companySearchFields.includes(filter.field)) {
|
||||
if (tableName === 'buyer') {
|
||||
filter.field = 'buyer';
|
||||
return true;
|
||||
} else if (tableName === 'seller') {
|
||||
filter.field = 'seller';
|
||||
return true;
|
||||
} else if (tableName === 'gulfood') {
|
||||
filter.field = 'company_name';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL: Handle when AI generates 'all' field for specific company searches
|
||||
// This ensures we search ALL tables when looking for a specific company
|
||||
if (filter.field === 'all') {
|
||||
if (tableName === 'buyer') {
|
||||
filter.field = 'buyer';
|
||||
return true;
|
||||
} else if (tableName === 'seller') {
|
||||
filter.field = 'seller';
|
||||
return true;
|
||||
} else if (tableName === 'gulfood') {
|
||||
filter.field = 'company_name';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude incompatible fields
|
||||
return false;
|
||||
}).map(filter => ({...filter})); // Clone to avoid modifying original
|
||||
}
|
||||
|
||||
let editContext = { id: null, table: null };
|
||||
|
||||
// Simple cache implementation
|
||||
@ -740,7 +826,6 @@ async function submitModalEdit() {
|
||||
|
||||
const { error } = await client.from(editContext.table).update(update).eq("id", editContext.id);
|
||||
if (error) {
|
||||
console.error('Update error:', error);
|
||||
showToast("Update failed: " + error.message, 'error');
|
||||
return;
|
||||
}
|
||||
@ -754,15 +839,10 @@ async function submitModalEdit() {
|
||||
performSearch();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Submit edit error:', error);
|
||||
showToast("Update failed: " + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Event listener moved to DOMContentLoaded
|
||||
|
||||
|
||||
function renderRecord(r) {
|
||||
const fields = tableFields[r.__table];
|
||||
const tableColors = {
|
||||
@ -866,20 +946,17 @@ function renderRecord(r) {
|
||||
}
|
||||
|
||||
async function parseQueryWithAI(naturalQuery) {
|
||||
// Check if n8n is configured
|
||||
if (!APP_CONFIG.useN8n || APP_CONFIG.n8nWebhookUrl === 'YOUR_N8N_WEBHOOK_URL_HERE') {
|
||||
console.log('🔄 n8n not configured, using fallback parsing');
|
||||
// Check if webhook is configured
|
||||
if (!APP_CONFIG.useWebhook || APP_CONFIG.webhookUrl === 'YOUR_WEBHOOK_URL_HERE') {
|
||||
return parseQueryFallback(naturalQuery);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('🤖 Parsing query with n8n AI:', naturalQuery);
|
||||
|
||||
// Add timeout and better error handling
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15 second timeout for n8n
|
||||
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15 second timeout
|
||||
|
||||
const response = await fetch(APP_CONFIG.n8nWebhookUrl, {
|
||||
const response = await fetch(APP_CONFIG.webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -904,33 +981,27 @@ async function parseQueryWithAI(naturalQuery) {
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// Handle n8n response format
|
||||
// Handle webhook response format
|
||||
if (result.success) {
|
||||
console.log('✅ n8n AI parsing successful:', result);
|
||||
if (result.fallback) {
|
||||
console.log('ℹ️ Used fallback logic in n8n');
|
||||
}
|
||||
return {
|
||||
table: result.table,
|
||||
filters: result.filters
|
||||
};
|
||||
} else {
|
||||
throw new Error(result.error || 'n8n workflow failed');
|
||||
throw new Error(result.error || 'webhook failed');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (error.name === 'AbortError') {
|
||||
console.warn('⚠️ n8n AI parsing timeout, using local fallback');
|
||||
// Timeout occurred
|
||||
} else if (error.message.includes('Failed to fetch')) {
|
||||
console.warn('⚠️ Cannot reach n8n webhook, using local fallback. Check your n8n workflow is active.');
|
||||
// Network error
|
||||
} else {
|
||||
console.warn('⚠️ n8n AI parsing failed, using local fallback:', error.message);
|
||||
// Other error
|
||||
}
|
||||
|
||||
// Fallback to simple keyword-based parsing
|
||||
const fallbackResult = parseQueryFallback(naturalQuery);
|
||||
console.log('🔄 Local fallback parsing result:', fallbackResult);
|
||||
console.log('🔄 Fallback filters:', fallbackResult.filters);
|
||||
return fallbackResult;
|
||||
}
|
||||
}
|
||||
@ -951,6 +1022,15 @@ function parseQueryFallback(query) {
|
||||
// Extract potential filters
|
||||
const filters = [];
|
||||
|
||||
// Extract company names from "find" queries
|
||||
if (lowerQuery.includes("find")) {
|
||||
const companyNameMatch = query.match(/find\s+(.+)/i);
|
||||
if (companyNameMatch) {
|
||||
const companyName = companyNameMatch[1].trim();
|
||||
filters.push({ field: "all", operator: "ilike", value: `%${companyName}%` });
|
||||
}
|
||||
}
|
||||
|
||||
// Country filters
|
||||
const countries = ["india", "uk", "united kingdom", "usa", "united states", "uae", "china", "germany", "france", "italy"];
|
||||
for (const country of countries) {
|
||||
@ -984,7 +1064,6 @@ async function fetchFrom(table, filters, page = 0, pageSize = 100) {
|
||||
// Check cache first
|
||||
const cachedResult = getCachedResult(cacheKey);
|
||||
if (cachedResult) {
|
||||
console.log('Using cached result for:', table);
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
@ -1016,7 +1095,6 @@ async function fetchFrom(table, filters, page = 0, pageSize = 100) {
|
||||
const { data, error, count } = await q;
|
||||
|
||||
if (error) {
|
||||
console.error('Supabase error:', error);
|
||||
throw new Error(`Database query failed: ${error.message}`);
|
||||
}
|
||||
|
||||
@ -1031,7 +1109,6 @@ async function fetchFrom(table, filters, page = 0, pageSize = 100) {
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error in fetchFrom:', error);
|
||||
throw new Error(`Failed to fetch ${table} data: ${error.message}`);
|
||||
}
|
||||
}
|
||||
@ -1081,34 +1158,68 @@ async function performSearch(loadMore = false) {
|
||||
|
||||
try {
|
||||
const ai = await parseQueryWithAI(query);
|
||||
console.log('🔍 Search query:', query);
|
||||
console.log('🔍 AI parsing result:', ai);
|
||||
|
||||
// CRITICAL: Validate and normalize filters before processing
|
||||
const validatedFilters = validateAndNormalizeFilters(ai.filters);
|
||||
|
||||
// BULLETPROOF: Detect specific company name searches and ensure comprehensive search
|
||||
const isSpecificCompanySearch = query.toLowerCase().includes('find') &&
|
||||
validatedFilters.some(f => f.field === 'all' || f.field === 'company_name' || f.field === 'buyer' || f.field === 'seller' ||
|
||||
(f.value && f.value.length > 10 && /[A-Z]/.test(f.value))); // Long value with capitals likely a company name
|
||||
|
||||
if (isSpecificCompanySearch && ai.table === 'all') {
|
||||
// Find the company search value
|
||||
const companyFilter = validatedFilters.find(f =>
|
||||
f.field === 'all' || f.field === 'company_name' || f.field === 'buyer' || f.field === 'seller' ||
|
||||
(f.value && f.value.length > 10)
|
||||
);
|
||||
|
||||
if (companyFilter) {
|
||||
const companyValue = companyFilter.value;
|
||||
|
||||
// Remove the original filter and add comprehensive filters for all tables
|
||||
const otherFilters = validatedFilters.filter(f => f !== companyFilter);
|
||||
|
||||
// Add filters for all company name fields
|
||||
validatedFilters.length = 0; // Clear array
|
||||
validatedFilters.push(
|
||||
...otherFilters,
|
||||
{ field: 'buyer', operator: 'ilike', value: companyValue },
|
||||
{ field: 'seller', operator: 'ilike', value: companyValue },
|
||||
{ field: 'company_name', operator: 'ilike', value: companyValue }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let newResults = [];
|
||||
let totalCount = 0;
|
||||
|
||||
if (ai.table === "all") {
|
||||
// Smart filter distribution for cross-table search
|
||||
const buyerFilters = filterForTable(validatedFilters, "buyer");
|
||||
const sellerFilters = filterForTable(validatedFilters, "seller");
|
||||
const gulfoodFilters = filterForTable(validatedFilters, "gulfood");
|
||||
|
||||
const [buyerRes, sellerRes, gulfoodRes] = await Promise.all([
|
||||
fetchFrom("buyer", ai.filters, searchState.page),
|
||||
fetchFrom("seller", ai.filters, searchState.page),
|
||||
fetchFrom("gulfood", [], searchState.page) // No filters for gulfood since it has different fields
|
||||
fetchFrom("buyer", buyerFilters, searchState.page),
|
||||
fetchFrom("seller", sellerFilters, searchState.page),
|
||||
fetchFrom("gulfood", gulfoodFilters, searchState.page)
|
||||
]);
|
||||
|
||||
newResults = [...buyerRes.data, ...sellerRes.data, ...gulfoodRes.data];
|
||||
totalCount = buyerRes.count + sellerRes.count + gulfoodRes.count;
|
||||
searchState.hasMore = buyerRes.hasMore || sellerRes.hasMore || gulfoodRes.hasMore;
|
||||
} else if (ai.table === "gulfood") {
|
||||
// For gulfood table, don't apply country/brand/category filters
|
||||
const gulfoodFilters = ai.filters.filter(f =>
|
||||
["company_name", "address", "phone_number", "mail_id", "website"].includes(f.field)
|
||||
);
|
||||
console.log('🔍 Gulfood filters:', gulfoodFilters);
|
||||
// For gulfood table, filter compatible fields only
|
||||
const gulfoodFilters = filterForTable(validatedFilters, "gulfood");
|
||||
const result = await fetchFrom(ai.table, gulfoodFilters, searchState.page);
|
||||
newResults = result.data;
|
||||
totalCount = result.count;
|
||||
searchState.hasMore = result.hasMore;
|
||||
} else {
|
||||
const result = await fetchFrom(ai.table, ai.filters, searchState.page);
|
||||
// For buyer/seller tables, filter compatible fields only
|
||||
const tableFilters = filterForTable(validatedFilters, ai.table);
|
||||
const result = await fetchFrom(ai.table, tableFilters, searchState.page);
|
||||
newResults = result.data;
|
||||
totalCount = result.count;
|
||||
searchState.hasMore = result.hasMore;
|
||||
@ -1152,7 +1263,6 @@ async function performSearch(loadMore = false) {
|
||||
searchState.page++;
|
||||
|
||||
} catch (e) {
|
||||
console.error('Search error:', e);
|
||||
showErrorMessage(`Search failed: ${e.message}`);
|
||||
} finally {
|
||||
// Hide loading state on search button
|
||||
@ -1254,9 +1364,7 @@ async function loadAllCountries() {
|
||||
});
|
||||
|
||||
allCountries = Array.from(countrySet).sort();
|
||||
console.log('Loaded countries:', allCountries);
|
||||
} catch (error) {
|
||||
console.error('Error loading countries:', error);
|
||||
// Fallback to common countries
|
||||
allCountries = [
|
||||
'India', 'United Kingdom', 'United States', 'UAE', 'China',
|
||||
@ -1390,7 +1498,6 @@ async function performProductMatching() {
|
||||
count: count || 0
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Buyer fetch error:', error);
|
||||
throw new Error(`Failed to fetch buyers: ${error.message}`);
|
||||
}
|
||||
};
|
||||
@ -1418,7 +1525,6 @@ async function performProductMatching() {
|
||||
count: count || 0
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Seller fetch error:', error);
|
||||
throw new Error(`Failed to fetch sellers: ${error.message}`);
|
||||
}
|
||||
};
|
||||
@ -1463,7 +1569,6 @@ async function performProductMatching() {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Product mapping error:', error);
|
||||
const errorMessage = error.message || 'Unknown error occurred';
|
||||
|
||||
document.getElementById("buyersList").innerHTML = `
|
||||
@ -1638,7 +1743,6 @@ function exportResults() {
|
||||
|
||||
showToast(`Exported ${searchState.results.length} records successfully`, 'success');
|
||||
} catch (error) {
|
||||
console.error('Export error:', error);
|
||||
showToast('Export failed: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@ -1648,8 +1752,6 @@ function quickSearch(type) {
|
||||
if (searchInput) {
|
||||
searchInput.value = type;
|
||||
performSearch();
|
||||
} else {
|
||||
console.error('Search input not found');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1681,7 +1783,6 @@ async function deleteRecord(btn) {
|
||||
try {
|
||||
const card = btn.closest("[data-id]");
|
||||
if (!card) {
|
||||
console.error('Could not find card element');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1689,7 +1790,6 @@ async function deleteRecord(btn) {
|
||||
const id = card.dataset.id;
|
||||
|
||||
if (!table || !id) {
|
||||
console.error('Missing table or id data');
|
||||
showToast("Could not delete: missing record information", 'error');
|
||||
return;
|
||||
}
|
||||
@ -1698,7 +1798,6 @@ async function deleteRecord(btn) {
|
||||
|
||||
const { error } = await client.from(table).delete().eq("id", id);
|
||||
if (error) {
|
||||
console.error('Delete error:', error);
|
||||
showToast("Delete failed: " + error.message, 'error');
|
||||
return;
|
||||
}
|
||||
@ -1711,7 +1810,6 @@ async function deleteRecord(btn) {
|
||||
performSearch();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Delete record error:', error);
|
||||
showToast("Delete failed: " + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@ -1792,17 +1890,12 @@ async function handleCreateSubmit(event) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Attempting to insert into table:', table);
|
||||
console.log('Data to insert:', data);
|
||||
|
||||
const { data: result, error } = await client.from(table).insert([data]);
|
||||
if (error) {
|
||||
console.error('Create error details:', error);
|
||||
showToast("Create failed: " + (error.message || JSON.stringify(error)), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Insert successful:', result);
|
||||
showToast("Record created successfully!", 'success');
|
||||
clearCache();
|
||||
closeCreateModal();
|
||||
@ -1812,20 +1905,30 @@ async function handleCreateSubmit(event) {
|
||||
performSearch();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Create record error:', error);
|
||||
showToast("Create failed: " + (error.message || JSON.stringify(error)), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function updateLastUpdated() {
|
||||
const now = new Date();
|
||||
const timeString = now.toLocaleTimeString('en-US', {
|
||||
hour12: true,
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
const lastUpdatedElement = document.getElementById('lastUpdated');
|
||||
if (lastUpdatedElement) {
|
||||
lastUpdatedElement.textContent = timeString;
|
||||
}
|
||||
}
|
||||
|
||||
function initializeDashboard() {
|
||||
try {
|
||||
console.log('Initializing dashboard...');
|
||||
|
||||
// Check if required elements exist
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
|
||||
if (!searchInput) {
|
||||
console.error('Search input not found');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1839,10 +1942,11 @@ function initializeDashboard() {
|
||||
// Load countries for suggestions
|
||||
loadAllCountries();
|
||||
|
||||
console.log('Dashboard initialized successfully');
|
||||
// Update last updated time
|
||||
updateLastUpdated();
|
||||
setInterval(updateLastUpdated, 1000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Initialization error:', error);
|
||||
alert('Failed to initialize dashboard. Please refresh the page.');
|
||||
}
|
||||
}
|
||||
@ -1891,6 +1995,5 @@ input:focus, select:focus, button:focus {
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user