PageSourceSearch

https://www.payoneer.com/developers-docs/js/ask-ai-sidebar.js

js payoneer.com collected 2026-09-24 06:57:03 UTC 28,385 bytes, 555 lines download raw bytes

1/**
2 * Ask AI — sidebar helper for Payoneer developer topics.
3 * Uses a local curated KB (no third-party AI, no API keys in the page).
4 * Optional: set window.PAYO_ASK_AI_ENDPOINT to a same-origin HTTPS endpoint that
5 * accepts POST JSON { "question": "..." } and returns JSON { "answer": "...", "sources": [{ "label", "href" }] }.
6 */
7(function () {
8	"use strict";
9
10	/** Curated Payoneer developer knowledge (expand as needed). */
11	var KB = [
12		{
13			id: "overview",
14			title: "Payoneer developer platform",
15			keywords: ["platform", "developer", "api", "integrate", "overview", "start", "begin"],
16			answer:
17				"The Payoneer Platform exposes REST APIs so you can embed financial flows (balances, payouts, billing, cards, etc.) in your product. Use the official API reference for endpoints, request bodies, and auth. Sandbox base URLs let you test before production.",
18			sources: [
19				{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" },
20				{ label: "Platform (this site)", href: "./developers-docs.html" },
21			],
22		},
23		{
24			id: "auth",
25			title: "Authentication and tokens",
26			keywords: ["oauth", "token", "bearer", "authorization", "auth", "client", "secret", "credential"],
27			answer:
28				"API calls use HTTPS and typically a Bearer access token in the Authorization header. Obtain tokens through Payoneer’s OAuth / consent flows as documented; never expose client secrets or tokens in frontend bundles or public repos — keep them on your server.",
29			sources: [{ label: "API reference — auth", href: "https://developer.payoneer.com/docs/payoneer-api-reference" }],
30		},
31		{
32			id: "sandbox",
33			title: "Sandbox vs production",
34			keywords: ["sandbox", "production", "test", "api.sandbox", "environment"],
35			answer:
36				"Sandbox hostnames (e.g. api.sandbox.payoneer.com) are for integration testing. Use production hosts only when your integration is complete and approved. Always confirm exact base URLs in the current API reference.",
37			sources: [{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" }],
38		},
39		{
40			id: "eligibility",
41			title: "Payment eligibility",
42			keywords: ["eligibility", "eligible", "minimum", "maximum", "amount", "balance", "limits"],
43			answer:
44				"Before paying another Payoneer account, call the eligibility API to confirm the user may send that payment and to read min/max amounts per balance. Do not hardcode limits — use the API response.",
45			sources: [{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" }],
46		},
47		{
48			id: "charge-account",
49			title: "POST .../precalc (accountholder charge)",
50			keywords: ["charge", "precalc", "debit", "fee", "customer", "service fee", "subscription", "collect", "platform", "balance", "mfa", "commit"],
51			answer:
52				"Accountholder-initiated charge: retrieve balances, user selects balance, POST precalc on /v4/accounts/{account_id}/balances/{balance_id}/precalc, user approves fees, PUT commit. Commit may return challenge_required with a challenge URL and session for MFA. See the Pay with Payoneer (charge account) guide and Global financial services on banking portals for details.",
53			sources: [
54				{ label: "Pay with Payoneer (charge account)", href: "./ap-charge-account.html#charge-user-balance-flow" },
55				{ label: "Official — banking portals", href: "https://developer.payoneer.com/docs/global-financial-services-on-banking-portals.html#/dmgnep147rctz-accountholder-initiated-charge-debit" },
56			],
57		},
58		{
59			id: "send-bank",
60			title: "POST payments/send (bank account)",
61			keywords: ["bank", "wire", "swift", "iban", "routing", "ach", "local bank", "bank account", "recipient bank"],
62			answer:
63				"For payouts to a saved recipient bank account, use the same POST /v4/accounts/{account_id}/balances/{balance_id}/payments/send path. Identify the destination with to.type = bank_account and to.id = the Payoneer bank-account identifier (per your program’s registration flow). Then consent, PUT commit with commit_id, and handle challenge_required like other send flows.",
64			sources: [
65				{ label: "Bank payout guide", href: "./ap-payments-to-recipient-bank-account.html#step-b-send" },
66				{ label: "Official docs", href: "https://developer.payoneer.com/docs/diverse-financial-services-on-digital-platforms.html#/9e161fb566531-making-payments-to-recipient-s-bank-accounts" },
67			],
68		},
69		{
70			id: "send",
71			title: "POST payments/pay_to_payoneer_account",
72			keywords: ["send", "post", "payment", "pay", "balance", "email", "recipient", "payee", "pay_to_payoneer_account", "purpose_of_payment"],
73			answer:
74				"Initiate a payment to another Payoneer account with POST /v4/accounts/{account_id}/balances/{balance_id}/payments/pay_to_payoneer_account. Put the recipient in to.type = email and to.id = their email; include purpose_of_payment when required. The response includes commit_id, fees, and amounts — use them for confirmation UI and the commit step.",
75			sources: [
76				{ label: "This guide", href: "./ap-payments-between-payoneer-accounts.html#step-b-send" },
77				{ label: "Official docs", href: "https://developer.payoneer.com/docs/diverse-financial-services-on-digital-platforms.html#/73e805060ae51-making-payments-between-payoneer-accounts" },
78			],
79		},
80		{
81			id: "commit",
82			title: "Commit payment (PUT)",
83			keywords: ["commit", "put", "confirm", "approve", "charge"],
84			answer:
85				"After the user approves fees and details, confirm with PUT /v4/accounts/{account_id}/payments/{commit_id} using the commit_id from the send response. Without commit, the payment is not finalized.",
86			sources: [{ label: "Commit step", href: "./ap-payments-between-payoneer-accounts.html#step-d-commit" }],
87		},
88		{
89			id: "consent",
90			title: "User confirmation",
91			keywords: ["consent", "confirm", "approve", "fees", "user", "customer", "ui"],
92			answer:
93				"You must show the user transaction details and fees from the send response and obtain confirmation before calling commit. The user must confirm agreement to charging their Payoneer balance.",
94			sources: [{ label: "Confirmation step", href: "./ap-payments-between-payoneer-accounts.html#step-c-consent" }],
95		},
96		{
97			id: "mfa",
98			title: "challenge_required and MFA",
99			keywords: ["403", "challenge", "challenge_reference", "mfa", "forbidden", "authentication", "auth.payoneer", "session"],
100			answer:
101				"If commit returns 403 with error challenge_required, the user must complete MFA. The JSON includes challenge.url, session_id, and challenge_reference — send the user to the URL, then retry commit after authentication. Handle expiry via challenge.expires_at.",
102			sources: [{ label: "Challenge section", href: "./ap-payments-between-payoneer-accounts.html#challenge-required" }],
103		},
104		{
105			id: "accounts-payable",
106			title: "Accounts Payable",
107			keywords: ["payable", "supplier", "pay", "accounts payable", "ap"],
108			answer:
109				"Accounts Payable covers outbound flows such as paying other Payoneer accounts, bank payouts, and charging balances, depending on your program. Pick the product that matches your use case in the Platform overview.",
110			sources: [{ label: "Accounts Payable", href: "./accounts-payable.html" }],
111		},
112		{
113			id: "accounts-receivable",
114			title: "Accounts Receivable",
115			keywords: ["receivable", "receive", "incoming", "withdraw", "ar", "billing"],
116			answer:
117				"Accounts Receivable focuses on receiving funds and options like withdrawing to bank. See Billing services and Withdraw to bank guides on the Accounts Receivable page, plus the API reference.",
118			sources: [{ label: "Accounts Receivable", href: "./accounts-receivable.html" }],
119		},
120		{
121			id: "withdraw-to-bank",
122			title: "Withdraw to bank (balance → bank account)",
123			keywords: ["withdraw", "withdrawal", "eligibility", "bank_id", "bank account", "balance", "commit", "mfa", "ar", "receivable"],
124			answer:
125				"Withdraw to bank: GET eligibility/withdraw for payout methods and bank_id, GET balances for balance IDs, then POST .../payments/withdraw with to.type = bank_id and the bank id from eligibility. Show fees/FX for user confirmation, PUT commit with commit_id, and handle challenge_required on commit if needed. See the API reference for exact schemas.",
126			sources: [
127				{ label: "Withdraw to bank guide", href: "./withdraw-to-bank.html#overview" },
128				{ label: "Official — withdraw from balance", href: "https://developer.payoneer.com/docs/diverse-financial-services-on-digital-platforms.html#/2987be6d6b91b-withdraw-funds-from-user-s-account-balance" },
129			],
130		},
131		{
132			id: "billing-services",
133			title: "Billing services (opt-in + payment requests)",
134			keywords: ["billing", "payment request", "opt-in", "invoice", "request payment", "billing services", "opa", "core country", "optin"],
135			answer:
136				"Use payment_request/optin to start opt-in and check eligibility; surface statuses (e.g. NOT_STARTED, IN_PROCESS, COMPLETED, NOT_ELIGIBLE) on your customer. After Payoneer completes review, use Create Payment Request for an OPA link; payer fields can be pre-filled in the API body. Non–Core countries may require $5k volume—see the guide.",
137			sources: [
138				{ label: "Billing services guide", href: "./ar-billing-services.html#opt-in-billing-services" },
139				{ label: "Opt-in (official)", href: "https://developer.payoneer.com/docs/diverse-financial-services-on-digital-platforms.html#/f5ol55uv2y8vn-opt-in-to-billing-services" },
140				{ label: "Payment requests (official)", href: "https://developer.payoneer.com/docs/diverse-financial-services-on-digital-platforms.html#/65p4dh2ff8wk3-create-payment-requests" },
141			],
142		},
143		{
144			id: "gca",
145			title: "Receiving Accounts",
146			keywords: ["gca", "currency", "balance", "receiving", "iban", "account"],
147			answer:
148				"Global Currency Accounts let account holders hold and manage balances in multiple currencies. APIs can expose balances, transactions, and receiving accounts — see the product page and API reference.",
149			sources: [
150				{ label: "Receiving Accounts", href: "./receiving-accounts.html" },
151				{ label: "Create receiving accounts", href: "./receiving-accounts.html#create" },
152				{ label: "Get receiving accounts", href: "./receiving-accounts.html#get" },
153			],
154		},
155		{
156			id: "gca-create-receiving",
157			title: "Create receiving accounts (POST receiving_account)",
158			keywords: ["receiving account", "receiving_account", "create receiving", "iban", "local bank", "currency account", "wire", "bic"],
159			answer:
160				"POST /v4/accounts/{account_id}/receiving_account with a JSON body such as {\"currency\":\"EUR\"} (see API reference for supported fields). Use the bearer token and account_id from your consent flow. The response includes result.id, currency, status, and a details array of name/value pairs for bank instructions.",
161			sources: [
162				{ label: "Create receiving accounts guide", href: "./receiving-accounts.html#create" },
163				{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" },
164			],
165		},
166		{
167			id: "gca-get-receiving",
168			title: "Get receiving accounts (GET receiving_accounts)",
169			keywords: ["get receiving", "receiving_accounts", "list receiving", "all receiving accounts", "account_id"],
170			answer:
171				"GET /v4/accounts/{account_id}/receiving_accounts returns result.items (each with id, currency, status, details.items name/value pairs) and result.total. Use the bearer token from your consent flow. See the API reference for pagination or filters if your program supports them.",
172			sources: [
173				{ label: "Get receiving accounts guide", href: "./receiving-accounts.html#get" },
174				{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" },
175			],
176		},
177		{
178			id: "mass-payout",
179			title: "Mass Payout",
180			keywords: ["mass", "payout", "batch", "bulk"],
181			answer:
182				"Mass Payout is for programmatic payouts at scale. Use the documented batch or payout APIs for your integration; details vary by program — confirm in the API reference.",
183			sources: [
184				{ label: "Mass Payout", href: "./mass-payout.html" },
185				{ label: "Payee onboarding", href: "./mass-payee-onboarding.html#overview" },
186				{ label: "Submit mass payout", href: "./mass-submit-payout.html#overview" },
187				{ label: "China SAFE", href: "./mass-payout-china-safe.html#overview" },
188			],
189		},
190		{
191			id: "mass-china-safe",
192			title: "Mass Payout with China SAFE (API & SFTP)",
193			keywords: ["china safe", "SAFE", "外汇", "mass payout china", "sftp", "order reporting", "v4-china-safe", "orders_report", "local_ftp", "safe-data"],
194			answer:
195				"POST .../masspayouts with Payments including orders_report (merchant/store; orders.type local_ftp + path to SFTP JSON, or use POST .../payouts/{client_reference_id}/safe-data with orders.type data and items[]). Sandbox: api.sandbox.payoneer.com. Full spec: v4-china-safe guide.",
196			sources: [
197				{ label: "China SAFE guide (local)", href: "./mass-payout-china-safe.html#overview" },
198				{ label: "Official v4-china-safe", href: "https://developer.payoneer.com/docs/v4-china-safe.html#/8e768e6831f6c-overview" },
199				{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" },
200			],
201		},
202		{
203			id: "mass-submit-payout",
204			title: "Submit mass payout (API & Admin Console)",
205			keywords: ["mass payout", "masspayouts", "submit mass", "500 payouts", "payments array", "admin console", "mass payments", "csv", "upload"],
206			answer:
207				"POST /v4/programs/{program_id}/masspayouts with a Payments array (payee_id, amount, currency, description, client_reference_id). Deposit funds first; insufficient balance means payouts won't run. Or use Admin Console Mass Payments to upload CSV/XLS/XLSX/XML. Transfers are asynchronous; dual approval may require Integration team setup.",
208			sources: [
209				{ label: "Submit mass payout guide", href: "./mass-submit-payout.html#overview" },
210				{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" },
211			],
212		},
213		{
214			id: "mass-payee-onboarding",
215			title: "Payee onboarding (Create Registration Link)",
216			keywords: ["payee", "onboarding", "registration link", "registration-link", "payee_id", "already_have_an_account", "pre-populate", "prepopulate", "lock_type"],
217			answer:
218				"POST /v4/programs/{program_id}/payees/registration-link with your payee_id and optional payee profile to pre-fill registration. The response includes a redirect URL. Use already_have_an_account=true for existing Payoneer users (that URL must not be loaded in an iframe). For Active payees, use webhooks (Admin Console URL) and/or GET .../payees/{ClientPayeeID}/status with your app token. See the guide and API reference.",
219			sources: [
220				{ label: "Payee onboarding guide", href: "./mass-payee-onboarding.html#overview" },
221				{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" },
222			],
223		},
224		{
225			id: "white-label",
226			title: "White Label",
227			keywords: ["white", "label", "brand", "customize"],
228			answer:
229				"White Label solutions let partners present Payoneer capabilities under their own brand. Integration patterns are described 
229in the White Label product documentation and API reference.",
230			sources: [
231				{ label: "White Label", href: "./white-label.html" },
232				{ label: "Registration & Payouts", href: "./white-label-registration-payouts.html#overview" },
233				{ label: "Register payee with token", href: "./white-label-register-payee-with-token.html#overview" },
234				{ label: "White Label Payouts", href: "./white-label-payouts.html#overview" },
235			],
236		},
237		{
238			id: "white-label-payouts",
239			title: "White Label Payouts",
240			keywords: ["white label payouts", "one step payments", "single step payments", "masspayouts", "500 payouts", "banking registration fields", "registration-fields", "payout_method_details", "Account Funded webhook", "webhook notifications", "cancel payment webhook", "Payment Loaded webhook", "KYC webhook", "Soft No Go", "batch payout", "wl payout v4"],
241			answer:
242				"One Step / Single Step Payments: one API call with beneficiary KYC + bank details; optional China SAFE orders report. masspayouts accepts up to 500 instructions; fund the program account first; Account Funded webhook; deferred validation; separate webhooks per client_reference_id; subscribe to cancel/failure webhooks (Callback Notifications). Query registration-fields / Banking registration fields before bank details. Local page: batch interfaces schematic, example JSON, webhook flowchart.",
243			sources: [
244				{ label: "White Label Payouts guide", href: "./white-label-payouts.html#overview" },
245				{ label: "Batch payout instruction", href: "./white-label-payouts.html#batch-payout-instruction" },
246				{ label: "Official getting started", href: "https://developer.payoneer.com/docs/white-label-payout-getting-started-v4.html" },
247				{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" },
248			],
249		},
250		{
251			id: "white-label-registration-payouts",
252			title: "White Label Registration & Payouts",
253			keywords: ["white label registration", "register payee", "payout funding", "payout instructions", "wl v4", "getting started"],
254			answer:
255				"Flow: get register-payee format from Payoneer → collect payee bank details on your site → register payee with Payoneer → payout funding → payout instructions → Payoneer credits payees. See getting started on developer.payoneer.com (white-label-registration-payouts-v4-getting-started) and the local flowchart page.",
256			sources: [
257				{ label: "Registration & Payouts guide", href: "./white-label-registration-payouts.html#overview" },
258				{ label: "Official getting started", href: "https://developer.payoneer.com/docs/white-label-registration-payouts-v4-getting-started.html" },
259				{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" },
260			],
261		},
262		{
263			id: "white-label-register-payee-with-token",
264			title: "Register payee with token",
265			keywords: [
266				"register payee with token",
267				"registration with tokens",
268				"register-payee-with-token",
269				"payee_id",
270				"wl token",
271				"white label token",
272				"kyc",
273				"bank transfer registration",
274			],
275			answer:
276				"POST /v4/programs/{program_id}/payees/register-payee-with-token: pass your payee_id and payee/payout_method payload for KYC and bank-transfer routing. Response includes a token to redirect the user to Payoneer's page to complete bank details. Full schemas and redirect handling: getting started (white-label-registration-with-tokens-v4) and API reference.",
277			sources: [
278				{ label: "Local guide", href: "./white-label-register-payee-with-token.html#overview" },
279				{ label: "Official getting started", href: "https://developer.payoneer.com/docs/white-label-registration-with-tokens-v4-getting-started.html" },
280				{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" },
281			],
282		},
283		{
284			id: "cards",
285			title: "Cards",
286			keywords: [
287				"card",
288				"cards",
289				"plastic",
290				"virtual",
291				"order cards",
292				"primary card",
293				"linked card",
294				"order primary card",
295				"get order primary card eligibility",
296				"order linked card",
297				"activate card",
298				"get card information",
299				"get card details",
300				"list all cards",
301				"get sensitive card data",
302				"view pin",
303				"get card transactions",
304				"update card info",
305				"update card status",
306				"lost stolen cancelled",
307				"update pin",
308				"spend control",
309				"reset spend control",
310				"card nickname",
311			],
312			answer:
313				"Order a primary card (eligibility first: get order primary card eligibility for currency, card plans, shipping). Order linked cards only after a primary card exists in that currency: Get Card Details, Activate Card if needed, Order linked card eligibility, then Order linked card for virtual linked cards. For read APIs: Get Card Details (non-sensitive), List All Cards (filterable), Get Sensitive Card Data (full PAN/CVV), View PIN, Get Card Transactions in a time range. For updates: Update Card Status (Lost, Stolen, or Cancelled only), Update PIN, Update Card Information (nickname only), Set Spend Control, Reset Spend Control. See local pages and the API reference.",
314			sources: [
315				{ label: "Order cards", href: "./order-cards.html#overview" },
316				{ label: "Get card information", href: "./get-card-information.html#overview" },
317				{ label: "Update card info", href: "./update-card-info.html#overview" },
318				{ label: "Cards hub", href: "./cards.html" },
319				{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" },
320			],
321		},
322		{
323			id: "capital-advance",
324			title: "Capital Advance",
325			keywords: [
326				"capital advance",
327				"get capital advance offers",
328				"capital advance offers",
329				"early payment",
330				"working capital",
331			],
332			answer:
333				"GET /v4/accounts/{account_id}/capital-advance/offers returns available offers (items with marketplace, store, amount, fee, settlement_plan, url, etc.). Present offers on your platform and use each item’s url for the customer to claim. See the local Get capital advance offers page and the API reference.",
334			sources: [
335				{ label: "Capital Advance", href: "./get-capital-advance-offers.html#overview" },
336				{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" },
337			],
338		},
339		{
340			id: "webhooks",
341			title: "Callbacks and status",
342			keywords: ["webhook", "callback", "status", "notification", "event"],
343			answer:
344				"For async operations, use the mechanisms documented for your program (webhooks, polling, or status APIs). Always verify signatures and treat payloads as untrusted until validated server-side.",
345			sources: [{ label: "API reference", href: "https://developer.payoneer.com/docs/payoneer-api-reference" }],
346		},
347	];
348
349	function tokenize(q) {
350		return String(q || "")
351			.toLowerCase()
352			.replace(/[^a-z0-9\s]/g, " ")
353			.split(/\s+/)
354			.filter(function (w) {
355				return w.length > 1;
356			});
357	}
358
359	function scoreEntry(words, entry) {
360		if (!words.length) return 0;
361		var title = (entry.title || "").toLowerCase();
362		var ans = (entry.answer || "").toLowerCase();
363		var kws = entry.keywords || [];
364		var s = 0;
365		for (var i = 0; i < words.length; i++) {
366			var w = words[i];
367			if (kws.indexOf(w) !== -1) s += 5;
368			else {
369				for (var j = 0; j < kws.length; j++) {
370					if (kws[j].indexOf(w) !== -1 || w.indexOf(kws[j]) !== -1) {
371						s += 3;
372						break;
373					}
374				}
375			}
376			if (title.indexOf(w) !== -1) s += 2;
377			if (ans.indexOf(w) !== -1) s += 1;
378		}
379		return s;
380	}
381
382	function escapeHtml(s) {
383		return String(s)
384			.replace(/&/g, "&amp;")
385			.replace(/</g, "&lt;")
386			.replace(/>/g, "&gt;")
387			.replace(/"/g, "&quot;");
388	}
389
390	function renderSources(sources) {
391		if (!sources || !sources.length) return "";
392		var parts = ['<p class="ask-ai-sources-title mb-1">References</p><ul class="ask-ai-sources list-unstyled mb-0">'];
393		for (var i = 0; i < sources.length; i++) {
394			var src = sources[i];
395			parts.push(
396				'<li><a href="' +
397					escapeHtml(src.href) +
398					'" target="_blank" rel="noopener noreferrer">' +
399					escapeHtml(src.label) +
400					"</a></li>"
401			);
402		}
403		parts.push("</ul>");
404		return parts.join("");
405	}
406
407	function rankEntries(query) {
408		var words = tokenize(query);
409		var ranked = [];
410		for (var i = 0; i < KB.length; i++) {
411			var sc = scoreEntry(words, KB[i]);
412			if (sc > 0) ranked.push({ entry: KB[i], score: sc });
413		}
414		ranked.sort(function (a, b) {
415			return b.score - a.score;
416		});
417		return { words: words, ranked: ranked };
418	}
419
420	function renderLocalAnswer(query) {
421		var r = rankEntries(query);
422		if (!r.words.length) {
423			return {
424				html:
425					'<p class="ask-ai-muted mb-0">Type a question about Payoneer developer APIs, authentication, sandbox, or payments flows.</p>',
426			};
427		}
428		if (!r.ranked.length || r.ranked[0].score < 2) {
429			return {
430				html:
431					'<p class="mb-2">No strong match in this offline knowledge base. Try keywords like <strong>send</strong>, <strong>commit</strong>, <strong>eligibility</strong>, <strong>MFA</strong>, or open the full <a href="https://developer.payoneer.com/docs/payoneer-api-reference" target="_blank" rel="noopener">API reference</a>.</p>' +
432					'<p class="ask-ai-muted small mb-0">Tip: questions stay in your browser — nothing is sent to Payoneer unless you configure an optional backend endpoint.</p>',
433			};
434		}
435		var top = r.ranked[0].entry;
436		var html = [
437			'<p class="ask-ai-match-title mb-1">' + escapeHtml(top.title) + "</p>",
438			'<p class="mb-2">' + escapeHtml(top.answer) + "</p>",
439			renderSources(top.sources),
440		];
441		if (r.ranked.length > 1 && r.ranked[1].score >= 2) {
442			html.push('<p class="ask-ai-related-title mt-2 mb-1">Related</p><ul class="ask-ai-related small mb-0">');
443			for (var k = 1; k < Math.min(4, r.ranked.length); k++) {
444				var e = r.ranked[k].entry;
445				html.push("<li>" + escapeHtml(e.title) + "</li>");
446			}
447			html.push("</ul>");
448		}
449		return { html: html.join("") };
450	}
451
452	function tryRemote(question) {
453		var endpoint = typeof window !== "undefined" && window.PAYO_ASK_AI_ENDPOINT;
454		if (!endpoint || typeof fetch !== "function") return Promise.resolve(null);
455		return fetch(endpoint, {
456			method: "POST",
457			headers: { "Content-Type": "application/json" },
458			body: JSON.stringify({ question: question }),
459			credentials: "same-origin",
460		})
461			.then(function (res) {
462				if (!res.ok) return null;
463				return res.json();
464			})
465			.catch(function () {
466				return null;
467			});
468	}
469
470	function renderRemote(data) {
471		if (!data || !data.answer) return "";
472		var html = ['<p class="mb-2">' + escapeHtml(String(data.answer)) + "</p>"];
473		if (data.sources && data.sources.length) {
474			html.push(renderSources(data.sources));
475		}
476		return html.join("");
477	}
478
479	function hideAskAiResponse(root) {
480		var out = root.querySelector(".ask-ai-response");
481		if (!out || out.hidden) return;
482		out.hidden = true;
483		out.innerHTML = "";
484		out.setAttribute("aria-busy", "false");
485	}
486
487	function bindRoot(root) {
488		var form = root.querySelector(".ask-ai-form");
489		var input = root.querySelector(".ask-ai-input");
490		var out = root.querySelector(".ask-ai-response");
491		if (!form || !input || !out) return;
492
493		function setLoading(on) {
494			out.hidden = false;
495			out.innerHTML = on
496				? '<p class="ask-ai-muted mb-0">Searching…</p>'
497				: "";
498			out.setAttribute("aria-busy", on ? "true" : "false");
499		}
500
501		function showHtml(html) {
502			out.hidden = false;
503			out.innerHTML = html;
504			out.setAttribute("aria-busy", "false");
505		}
506
507		form.addEventListener("submit", function (e) {
508			e.preventDefault();
509			var q = (input.value || "").trim();
510			setLoading(true);
511
512			tryRemote(q).then(function (remote) {
513				if (remote && remote.answer) {
514					var rh = renderRemote(remote);
515					if (rh) {
516						showHtml('<div class="ask-ai-block ask-ai-block--remote">' + rh + "</div>");
517						return;
518					}
519				}
520				var local = renderLocalAnswer(q);
521				showHtml('<div class="ask-ai-block ask-ai-block--local">' + local.html + "</div>");
522			});
523		});
524	}
525
526	function init() {
527		var roots = document.querySelectorAll("[data-ask-ai-root]");
528		if (!roots.length) return;
529
530		document.addEventListener("click", function (e) {
531			for (var i = 0; i < roots.length; i++) {
532				if (!roots[i].contains(e.target)) {
533					hideAskAiResponse(roots[i]);
534				}
535			}
536		});
537
538		document.addEventListener("keydown", function (e) {
539			if (e.key !== "Escape") return;
540			for (var j = 0; j < roots.length; j++) {
541				hideAskAiResponse(roots[j]);
542			}
543		});
544
545		for (var k = 0; k < roots.length; k++) {
546			bindRoot(roots[k]);
547		}
548	}
549
550	if (document.readyState === "loading") {
551		document.addEventListener("DOMContentLoaded", init);
552	} else {
553		init();
554	}
555})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.