From b5099693b5810bdf1570fc5ce3470836e5e6aebe Mon Sep 17 00:00:00 2001 From: Muhammed Date: Sun, 2 Aug 2026 23:07:37 +0100 Subject: [PATCH 1/3] Add script for validating documentation drift --- package.json | 1 + scripts/validate-docs-drift.mjs | 161 ++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 scripts/validate-docs-drift.mjs diff --git a/package.json b/package.json index f4584308..ab33a9c0 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "ci:testing": "npm run test:coverage && npm run test:coverage:check && npm run ci:workflows", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", + "docs:validate-drift": "node scripts/validate-docs-drift.mjs", "docs:api:generate": "node scripts/generate-api-docs.mjs", "api:start": "node api/server.js" }, diff --git a/scripts/validate-docs-drift.mjs b/scripts/validate-docs-drift.mjs new file mode 100644 index 00000000..b6946916 --- /dev/null +++ b/scripts/validate-docs-drift.mjs @@ -0,0 +1,161 @@ +import fs from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const repoRoot = path.resolve(__dirname, '..'); +const docsRoot = path.join(repoRoot, 'docs'); +const rootPackage = path.join(repoRoot, 'package.json'); + +const externalLinkPattern = /^(?:[a-zA-Z][a-zA-Z0-9+.-]*:|\/\/)/; +const markdownLinkPattern = /!?\[[^\]]*\]\(\s*([^\s)]+)(?:\s+"[^"]*")?\s*\)/g; +const npmRunPattern = /npm run\s+([\w:-]+)/g; + +async function collectMarkdownFiles(dir) { + const entries = await fs.readdir(dir, { withFileTypes: true }); + const files = []; + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + files.push(...await collectMarkdownFiles(fullPath)); + } else if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdx'))) { + files.push(fullPath); + } + } + return files; +} + +function parseMarkdownLinks(content) { + const links = []; + let match; + while ((match = markdownLinkPattern.exec(content)) !== null) { + links.push(match[1]); + } + return links; +} + +function parseNpmRunCommands(content) { + const scripts = []; + let match; + while ((match = npmRunPattern.exec(content)) !== null) { + scripts.push(match[1]); + } + return scripts; +} + +function isExternalLink(href) { + return externalLinkPattern.test(href) || href.startsWith('mailto:') || href.startsWith('tel:'); +} + +function normalizeLinkHref(href) { + return href.replace(/^\.+\//, '').replace(/#.*$/, ''); +} + +async function fileExists(filePath) { + try { + const stat = await fs.stat(filePath); + return stat.isFile(); + } catch { + return false; + } +} + +async function resolveLink(sourceFile, rawHref) { + const href = rawHref.split('#', 1)[0]; + if (!href || href.startsWith('#')) { + return null; + } + + const candidates = []; + const isAbsoluteDocs = href.startsWith('docs/') || href.startsWith('/docs/') || href.startsWith('./docs/') || href.startsWith('../docs/'); + + const sourceDir = path.dirname(sourceFile); + const referenced = path.normalize(href); + + if (!path.isAbsolute(referenced)) { + candidates.push(path.join(sourceDir, referenced)); + if (isAbsoluteDocs) { + const relativeToRoot = path.join(repoRoot, referenced.replace(/^\/*/, '')); + candidates.push(relativeToRoot); + } + } else { + candidates.push(path.join(repoRoot, referenced)); + } + + for (const candidate of candidates) { + const normalized = path.normalize(candidate); + if (await fileExists(normalized)) return normalized; + const withMd = `${normalized}.md`; + const withMdx = `${normalized}.mdx`; + const indexMd = path.join(normalized, 'index.md'); + const readmeMd = path.join(normalized, 'README.md'); + if (await fileExists(withMd)) return withMd; + if (await fileExists(withMdx)) return withMdx; + if (await fileExists(indexMd)) return indexMd; + if (await fileExists(readmeMd)) return readmeMd; + } + + return null; +} + +async function loadPackageScripts() { + try { + const pkgJson = JSON.parse(await fs.readFile(rootPackage, 'utf8')); + return new Set(Object.keys(pkgJson.scripts || {})); + } catch (error) { + console.error('Unable to read package.json for npm script validation:', error.message); + return new Set(); + } +} + +async function main() { + const files = await collectMarkdownFiles(docsRoot); + const packageScripts = await loadPackageScripts(); + const errors = []; + + for (const file of files) { + const content = await fs.readFile(file, 'utf8'); + const links = parseMarkdownLinks(content); + for (const link of links) { + if (isExternalLink(link) || link.startsWith('#')) continue; + const resolved = await resolveLink(file, link); + if (!resolved) { + errors.push({ type: 'missing-file', file, link, resolved: path.join(path.dirname(file), link) }); + } + } + + const scripts = parseNpmRunCommands(content); + for (const script of scripts) { + if (!packageScripts.has(script)) { + errors.push({ type: 'missing-script', file, script }); + } + } + } + + if (errors.length === 0) { + console.log('=== Documentation Drift Detection ==='); + console.log(`Scanned ${files.length} documentation files.`); + console.log('No missing docs links or npm scripts were found.'); + process.exit(0); + } + + console.log('=== Documentation Drift Detection ==='); + console.log(`Scanned ${files.length} documentation files.`); + console.log(); + for (const error of errors) { + if (error.type === 'missing-file') { + console.log(`ERROR: File not found: "${error.link}" (resolved: ${error.resolved}) — referenced from ${path.relative(repoRoot, error.file)}`); + } else if (error.type === 'missing-script') { + console.log(`ERROR: Script not found: "npm run ${error.script}" — referenced from ${path.relative(repoRoot, error.file)}`); + } + } + console.log(); + console.log(`Errors: ${errors.length}`); + process.exit(1); +} + +main().catch((error) => { + console.error('Unexpected error during docs drift validation:', error); + process.exit(1); +}); From 4405a8c7790ff53b84edc7afbe520523b382b01f Mon Sep 17 00:00:00 2001 From: Muhammed Date: Mon, 3 Aug 2026 00:22:09 +0100 Subject: [PATCH 2/3] Update dependencies and remove unused packages in package.json and package-lock.json --- package-lock.json | 164 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 13 ++-- 2 files changed, 162 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1263d1f6..e3529fc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,11 @@ "version": "0.1.0", "dependencies": { "@axe-core/react": "^4.11.3", + "@sentry/browser": "8.54.0", + "@sentry/react": "8.54.0", "@stellar/stellar-sdk": "^12.3.0", "@tensorflow/tfjs": "^4.22.0", - "@tensorflow/tfjs-node": "^4.22.0", + "@tensorflow/tfjs-node": "4.22.0", "d3-array": "^3.2.4", "d3-force-3d": "^3.0.6", "d3-scale": "^4.0.2", @@ -35,6 +37,7 @@ "recharts": "^2.12.7", "swr": "^2.2.0", "uuid": "^9.0.1", + "ws": "^8.16.0", "zustand": "^4.5.4" }, "devDependencies": { @@ -78,8 +81,7 @@ }, "optionalDependencies": { "@ledgerhq/hw-transport-webhid": "^6.29.4", - "@ledgerhq/hw-transport-webusb": "^6.29.4", - "@stellar/ledger": "^1.0.0" + "@ledgerhq/hw-transport-webusb": "^6.29.4" } }, "node_modules/@adobe/css-tools": { @@ -5437,6 +5439,117 @@ "dev": true, "license": "MIT" }, + "node_modules/@sentry-internal/browser-utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.54.0.tgz", + "integrity": "sha512-DKWCqb4YQosKn6aD45fhKyzhkdG7N6goGFDeyTaJFREJDFVDXiNDsYZu30nJ6BxMM7uQIaARhPAC5BXfoED3pQ==", + "license": "MIT", + "dependencies": { + "@sentry/core": "8.54.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/browser-utils/node_modules/@sentry/core": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.54.0.tgz", + "integrity": "sha512-03bWf+D1j28unOocY/5FDB6bUHtYlm6m6ollVejhg45ZmK9iPjdtxNWbrLsjT1WRym0Tjzowu+A3p+eebYEv0Q==", + "license": "MIT", + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.54.0.tgz", + "integrity": "sha512-nQqRacOXoElpE0L0ADxUUII0I3A94niqG9Z4Fmsw6057QvyrV/LvTiMQBop6r5qLjwMqK+T33iR4/NQI5RhsXQ==", + "license": "MIT", + "dependencies": { + "@sentry/core": "8.54.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/feedback/node_modules/@sentry/core": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.54.0.tgz", + "integrity": "sha512-03bWf+D1j28unOocY/5FDB6bUHtYlm6m6ollVejhg45ZmK9iPjdtxNWbrLsjT1WRym0Tjzowu+A3p+eebYEv0Q==", + "license": "MIT", + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.54.0.tgz", + "integrity": "sha512-8xuBe06IaYIGJec53wUC12tY2q4z2Z0RPS2s1sLtbA00EvK1YDGuXp96IDD+HB9mnDMrQ/jW5f97g9TvPsPQUg==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "8.54.0", + "@sentry/core": "8.54.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.54.0.tgz", + "integrity": "sha512-K/On3OAUBeq/TV2n+1EvObKC+WMV9npVXpVyJqCCyn8HYMm8FUGzuxeajzm0mlW4wDTPCQor6mK9/IgOquUzCw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/replay": "8.54.0", + "@sentry/core": "8.54.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/core": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.54.0.tgz", + "integrity": "sha512-03bWf+D1j28unOocY/5FDB6bUHtYlm6m6ollVejhg45ZmK9iPjdtxNWbrLsjT1WRym0Tjzowu+A3p+eebYEv0Q==", + "license": "MIT", + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/replay/node_modules/@sentry/core": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.54.0.tgz", + "integrity": "sha512-03bWf+D1j28unOocY/5FDB6bUHtYlm6m6ollVejhg45ZmK9iPjdtxNWbrLsjT1WRym0Tjzowu+A3p+eebYEv0Q==", + "license": "MIT", + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/browser": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.54.0.tgz", + "integrity": "sha512-BgUtvxFHin0fS0CmJVKTLXXZcke0Av729IVfi+2fJ4COX8HO7/HAP02RKaSQGmL2HmvWYTfNZ7529AnUtrM4Rg==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "8.54.0", + "@sentry-internal/feedback": "8.54.0", + "@sentry-internal/replay": "8.54.0", + "@sentry-internal/replay-canvas": "8.54.0", + "@sentry/core": "8.54.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/browser/node_modules/@sentry/core": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.54.0.tgz", + "integrity": "sha512-03bWf+D1j28unOocY/5FDB6bUHtYlm6m6ollVejhg45ZmK9iPjdtxNWbrLsjT1WRym0Tjzowu+A3p+eebYEv0Q==", + "license": "MIT", + "engines": { + "node": ">=14.18" + } + }, "node_modules/@sentry/core": { "version": "6.19.7", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.19.7.tgz", @@ -5542,6 +5655,32 @@ "dev": true, "license": "0BSD" }, + "node_modules/@sentry/react": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-8.54.0.tgz", + "integrity": "sha512-42T/fp8snYN19Fy/2P0Mwotu4gcdy+1Lx+uYCNcYP1o7wNGigJ7qb27sW7W34GyCCHjoCCfQgeOqDQsyY8LC9w==", + "license": "MIT", + "dependencies": { + "@sentry/browser": "8.54.0", + "@sentry/core": "8.54.0", + "hoist-non-react-statics": "^3.3.2" + }, + "engines": { + "node": ">=14.18" + }, + "peerDependencies": { + "react": "^16.14.0 || 17.x || 18.x || 19.x" + } + }, + "node_modules/@sentry/react/node_modules/@sentry/core": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.54.0.tgz", + "integrity": "sha512-03bWf+D1j28unOocY/5FDB6bUHtYlm6m6ollVejhg45ZmK9iPjdtxNWbrLsjT1WRym0Tjzowu+A3p+eebYEv0Q==", + "license": "MIT", + "engines": { + "node": ">=14.18" + } + }, "node_modules/@sentry/types": { "version": "6.19.7", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.19.7.tgz", @@ -5598,9 +5737,6 @@ "integrity": "sha512-VVolPL5goVEIsvuGqDc5uiKxV03lzfWdvYg1KikvwheDmTBO68CKDji3bAZ/kppZrx5iTA8z3Ld5yuytcvhvOQ==", "license": "Apache-2.0" }, - "node_modules/@stellar/ledger": { - "optional": true - }, "node_modules/@stellar/stellar-base": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-12.1.1.tgz", @@ -11491,6 +11627,21 @@ "hermes-estree": "0.25.1" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/html-encoding-sniffer": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", @@ -18165,7 +18316,6 @@ "version": "8.20.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index ab33a9c0..3429b017 100644 --- a/package.json +++ b/package.json @@ -44,15 +44,12 @@ "api:start": "node api/server.js" }, "dependencies": { - "@tensorflow/tfjs": "^4.22.0", - "@tensorflow/tfjs-node": "^5.0.0", + "@axe-core/react": "^4.11.3", "@sentry/browser": "8.54.0", "@sentry/react": "8.54.0", - "@tensorflow/tfjs-node": "4.22.0", - "express": "^4.18.2", - "@axe-core/react": "^4.11.3", "@stellar/stellar-sdk": "^12.3.0", - "date-fns": "^3.6.0", + "@tensorflow/tfjs": "^4.22.0", + "@tensorflow/tfjs-node": "4.22.0", "d3-array": "^3.2.4", "d3-force-3d": "^3.0.6", "d3-scale": "^4.0.2", @@ -61,6 +58,7 @@ "d3-shape": "^3.2.0", "d3-time-format": "^4.1.0", "d3-zoom": "^3.0.0", + "date-fns": "^3.6.0", "express": "^4.18.2", "i18next": "^23.15.1", "i18next-browser-languagedetector": "^8.0.0", @@ -80,8 +78,7 @@ }, "optionalDependencies": { "@ledgerhq/hw-transport-webhid": "^6.29.4", - "@ledgerhq/hw-transport-webusb": "^6.29.4", - "@stellar/ledger": "^1.0.0" + "@ledgerhq/hw-transport-webusb": "^6.29.4" }, "devDependencies": { "@axe-core/playwright": "^4.10.0", From c1d598829a9435aadfec7d452525858edba6e96f Mon Sep 17 00:00:00 2001 From: Muhammed Date: Tue, 4 Aug 2026 19:43:49 +0100 Subject: [PATCH 3/3] Add ConversationalSearch component and integrate natural language query handling in AdvancedSearch --- src/components/dashboard/AdvancedSearch.tsx | 119 ++++++++--- .../search/ConversationalSearch.tsx | 200 ++++++++++++++++++ 2 files changed, 286 insertions(+), 33 deletions(-) create mode 100644 src/components/search/ConversationalSearch.tsx diff --git a/src/components/dashboard/AdvancedSearch.tsx b/src/components/dashboard/AdvancedSearch.tsx index e2a7d745..d49cbe2a 100644 --- a/src/components/dashboard/AdvancedSearch.tsx +++ b/src/components/dashboard/AdvancedSearch.tsx @@ -30,6 +30,7 @@ import { import advancedSearchService from '../../lib/advancedSearch.js'; import auditTrail from '../../lib/auditTrail.js'; import { format } from 'date-fns'; +import ConversationalSearch from '../search/ConversationalSearch'; import SemanticSearchPanel from '../search/SemanticSearchPanel'; export default function AdvancedSearch() { @@ -50,6 +51,8 @@ export default function AdvancedSearch() { const [alertCron, setAlertCron] = useState('0 * * * *'); const [shareToken, setShareToken] = useState(''); const [semanticMode, setSemanticMode] = useState(false); + const [conversationalSearchText, setConversationalSearchText] = useState(''); + const [intentSummary, setIntentSummary] = useState(null); const [filters, setFilters] = useState({ dateRange: { start: '', end: '' }, @@ -167,70 +170,109 @@ export default function AdvancedSearch() { setSearchAnalytics(advancedSearchService.getSearchAnalytics()); }; - const handleSearch = async () => { - if (!searchQuery.trim() && !hasActiveFilters()) { + const handleSearch = async ( + overrideQuery?: string, + overrideFilters?: any, + overrideTypes?: string[] + ) => { + const queryText = (overrideQuery ?? searchQuery).trim(); + const activeFilters = overrideFilters ?? filters; + + if (!queryText && !hasActiveFilters(activeFilters)) { return; } setLoading(true); try { const query = { - text: searchQuery.trim(), - types: selectedTypes, - filters: buildFilters(), + text: queryText, + types: overrideTypes ?? selectedTypes, + filters: buildFilters(activeFilters), sort, page: pagination.page, - limit: pagination.limit + limit: pagination.limit, }; const results = advancedSearchService.search(query); setSearchResults(results); auditTrail.logUserAction('Performed advanced search', { - query: searchQuery.trim(), - types: selectedTypes, + query: queryText, + types: overrideTypes ?? selectedTypes, resultCount: results.total, - searchTime: results.searchTime + searchTime: results.searchTime, }); loadData(); } catch (error) { - auditTrail.logError(error, { operation: 'advancedSearch', query: searchQuery }); + auditTrail.logError(error, { operation: 'advancedSearch', query: queryText }); console.error('Search failed:', error); } finally { setLoading(false); } }; - const buildFilters = () => { - const builtFilters = {}; - - if (filters.dateRange.start) { - builtFilters.dateRange = { ...builtFilters.dateRange, start: new Date(filters.dateRange.start).getTime() }; + const handleConversationalQuery = async (query: string, parsed: { filters: any; searchTerms: string[]; intent: any }) => { + setConversationalSearchText(query); + setIntentSummary(`Detected ${parsed.intent.type} intent`); + setSearchQuery(query); + + const newFilters = { + ...filters, + assetType: parsed.filters.assets?.[0] ?? filters.assetType, + addressFilter: parsed.filters.addresses?.[0] ?? filters.addressFilter, + amountRange: { + min: parsed.filters.amounts?.min ?? filters.amountRange.min, + max: parsed.filters.amounts?.max ?? filters.amountRange.max, + }, + memoFilter: filters.memoFilter, + statusFilter: filters.statusFilter, + networkFilter: filters.networkFilter, + dateRange: { + start: parsed.filters.dateRange?.start ? parsed.filters.dateRange.start.toISOString().slice(0, 10) : filters.dateRange.start, + end: parsed.filters.dateRange?.end ? parsed.filters.dateRange.end.toISOString().slice(0, 10) : filters.dateRange.end, + }, + }; + + setFilters(newFilters); + await handleSearch(query, newFilters, selectedTypes); + }; + + const buildFilters = (sourceFilters = filters) => { + const builtFilters: any = {}; + + if (sourceFilters.dateRange?.start) { + const start = typeof sourceFilters.dateRange.start === 'string' + ? new Date(sourceFilters.dateRange.start) + : sourceFilters.dateRange.start; + builtFilters.dateRange = { ...builtFilters.dateRange, start: start.getTime() }; } - if (filters.dateRange.end) { - builtFilters.dateRange = { ...builtFilters.dateRange, end: new Date(filters.dateRange.end).getTime() }; + if (sourceFilters.dateRange?.end) { + const end = typeof sourceFilters.dateRange.end === 'string' + ? new Date(sourceFilters.dateRange.end) + : sourceFilters.dateRange.end; + builtFilters.dateRange = { ...builtFilters.dateRange, end: end.getTime() }; } - - if (filters.assetType) builtFilters.assetType = filters.assetType; - if (filters.operationType) builtFilters.operationType = filters.operationType; - if (filters.addressFilter) builtFilters.addressFilter = filters.addressFilter; - if (filters.memoFilter) builtFilters.memoFilter = filters.memoFilter; - if (filters.statusFilter) builtFilters.statusFilter = filters.statusFilter; - if (filters.networkFilter) builtFilters.networkFilter = filters.networkFilter; - - if (filters.amountRange.min) { - builtFilters.amountRange = { ...builtFilters.amountRange, min: parseFloat(filters.amountRange.min) }; + + if (sourceFilters.assetType) builtFilters.assetType = sourceFilters.assetType; + if (sourceFilters.operationType) builtFilters.operationType = sourceFilters.operationType; + if (sourceFilters.addressFilter) builtFilters.addressFilter = sourceFilters.addressFilter; + if (sourceFilters.memoFilter) builtFilters.memoFilter = sourceFilters.memoFilter; + if (sourceFilters.statusFilter) builtFilters.statusFilter = sourceFilters.statusFilter; + if (sourceFilters.networkFilter) builtFilters.networkFilter = sourceFilters.networkFilter; + + if (sourceFilters.amountRange?.min) { + builtFilters.amountRange = { ...builtFilters.amountRange, min: parseFloat(sourceFilters.amountRange.min) }; } - if (filters.amountRange.max) { - builtFilters.amountRange = { ...builtFilters.amountRange, max: parseFloat(filters.amountRange.max) }; + if (sourceFilters.amountRange?.max) { + builtFilters.amountRange = { ...builtFilters.amountRange, max: parseFloat(sourceFilters.amountRange.max) }; } - + return builtFilters; }; - const hasActiveFilters = () => { - return Object.values(filters).some(value => { + const hasActiveFilters = (sourceFilters = filters) => { + return Object.values(sourceFilters).some(value => { if (typeof value === 'object' && value !== null) { return Object.values(value).some(v => v !== ''); } @@ -344,6 +386,18 @@ export default function AdvancedSearch() { +
+ + {intentSummary && ( +
+ {intentSummary} +
+ )} +
+ {/* Semantic mode toggle */}
+
+ + {intent && ( +
+
+ + Detected intent +
+
+
Type: {intent.type}
+ {intent.entities.addresses?.length > 0 &&
Addresses: {intent.entities.addresses.join(', ')}
} + {intent.entities.amounts?.length > 0 &&
Amounts: {intent.entities.amounts.join(', ')}
} + {intent.entities.assets?.length > 0 &&
Assets: {intent.entities.assets.join(', ')}
} + {intent.entities.dateRanges?.length > 0 &&
Date: {intent.entities.dateRanges.map((dr) => dr.start ? format(dr.start, 'PPP') : 'recent').join(', ')}
} +
+
+ )} + + {intent && suggestions.length > 0 && ( +
+
Refine your query:
+
+ {suggestions.map((suggestion) => ( + + ))} +
+
+ )} + + + {showFeedback && ( +
+
+ + Conversation mode +
+
+ This interface converts plain English into structured Stellar query filters. You can refine the results using follow-up questions or export the final results once they are loaded. +
+
+ )} + + {parsedIntent && ( +
+
Parsed query
+
+
Intent: {parsedIntent.intent.type}
+
Terms: {parsedIntent.searchTerms.join(', ') || 'None'}
+
Filters: {Object.entries(parsedIntent.filters) + .filter(([, value]) => { + if (Array.isArray(value)) return value.length > 0; + return value !== undefined && value !== null && value !== ''; + }) + .map(([key, value]) => `${key}: ${JSON.stringify(value)}`) + .join('; ') || 'None'} +
+
+
+ )} + + {history.length > 0 && ( +
+
Recent conversational queries
+
    + {history.map((item, idx) => ( +
  • + {item.query} + {item.timestamp} +
  • + ))} +
+
+ )} + + ); +}