PageSourceSearch

https://www.chapter7.com/themes/default/js/ncms/universalForm/universalFormPrompt.js

js chapter7.com collected 2026-09-27 08:21:14 UTC 23,016 bytes, 578 lines download raw bytes

1/* For AI Prompt */
2// All the constants:
3window.chatPrompt = {
4	prompt: "Using the following Case description submitted by a potential customer, what follow up high level (maximum three listed in bullet points) most important questions would you ask in an SMS format in order to qualify the case and connect them to the right attorney? Note that we already have the zip code of the incident so we don't need the location\n    Name: {{name}}\n    Case Description: {{case_details}}",
5	context: "Act as a legal office intake assistant whose job is to qualify the legal case leads submitted on our legal content website for a free consultation.",
6	model: 'gpt-4'
7};
8
9function getCookie(name) {
10    const value = `; ${document.cookie}`;
11    const parts = value.split(`; ${name}=`);
12    if (parts.length === 2) return parts.pop().split(';').shift();
13}
14
15//server side API url:
16const CHAT_API_URL = "https://tools.nolo.com/public/form-prompt";
17const FORM_CACHE_API_URL = "https://tools.nolo.com/form-lead";
18const SPAM_CHAR_LIMIT = 1000;
19
20// Universal Form Version
21const ERROR_COMPLETION_LINE = "OpenAI completion failed";
22
23// user visiting history in session
24userTrackKey = window.userTrackKey || 'userWebJourney';
25
26const showModal = true;
27
28function submitToForm(parentWrapper, source) {
29
30	// send out Remove cache request
31	var reqHander = new Headers();
32	reqHander.append("Content-Type", "application/json");
33	reqHander.append("X-XSRF-TOKEN", decodeURIComponent(getCookie("XSRF-TOKEN")));
34
35	console.log("submitToForm -- parentWrapper: ", parentWrapper);
36
37	var user_name = parentWrapper.querySelector("[name=name]").value.replace(/\s+/g, ' ');
38	// if (!parentWrapper.querySelector("[name=name]").value.includes(' ')) {
39	// 	user_name += ' LNU'; //last name undefined 
40	// } else if (parentWrapper.querySelector("[name=name]").value.split(' ')[1].length <= 0) {
41	// 	user_name += 'LNU';
42	// }
43
44	var raw = {
45		// "_token": $("input[name=_token]")[0].value,
46		"name": user_name,
47		"email": parentWrapper.querySelector("[name=email]").value,
48		"phone": parentWrapper.querySelector("[name=phone]").value,
49		"zip": parentWrapper.querySelector("[name=zip]").value,
50		"description": parentWrapper.querySelector("[name=description]").value,
51		"loc": parentWrapper.querySelector("[name=loc]").value,
52		"lead_id": parentWrapper.querySelector("[name=lead_id]").value,
53		"WebsiteID": "15-103-212-197-7-216-247-236",
54		"referer": document.referrer,
55		"ai_case_id": window.ai_case_id,
56		"skip_prediction": true
57	};
58
59	if (NCMS.pageType != 'front') {
60		raw["form_type"] = parentWrapper.querySelector("[name=form_type]").value
61	}
62
63	raw['sms_optin_status'] = parentWrapper.querySelector("[name=sms_optin_status]").value;
64	raw['tcpa_consent_verbiage'] = parentWrapper.querySelector("[name=tcpa_consent_verbiage]").value;
65	raw['trusted_form_id'] = parentWrapper.querySelector("[name=trusted_form_id]").value;
66	
67	if (source == 'submit') {
68		raw['ai'] = [];
69		document.querySelectorAll('.ai-radio-label').forEach(element => {
70			var radioFor = element.getAttribute('data-q-id');
71			radioFor = radioFor.replace('[', '\\[');
72			radioFor = radioFor.replace(']', '\\]');
73
74			var radioAnswer, radioLabel = '', radioAnswer = '';
75			try {
76				radioLabel = document.querySelector('#' + formPrefix + "chat-content label[data-q-id=" + radioFor + "]").innerText;
77				radioAnswer = document.querySelector('#' + formPrefix + "chat-content input[name=" + radioFor + "]:checked").value;
78			} catch (error) {
79				radioAnswer = 'undefined';
80			}
81			raw['ai'].push({'question': radioLabel, 'answer': radioAnswer});
82		});
83	}
84
85	var requestOptions = {
86		method: 'POST', //'DELETE',
87		headers: reqHander,
88		body: JSON.stringify(raw),
89		redirect: 'follow'
90	};
91
92	window.cacheTimeStamp = "";
93
94	if (window.gptAbort == true) {
95		console.log("aborting GPT, no AI Qs");
96		// parentWrapper.querySelector(".submit-error").classList.remove('d-none'); 
97		// $('#uform-universalFormPromptModal').modal('hide');
98		
99		// if we failed uformMCQ, then directly submit
100		fetch('/uformSubmit', requestOptions)
101			.then(function (response) {
102				return response.text()
103			}).then(function (data) {
104				console.log('moving on to success')
105				window.promptSubmit.click();
106			}).catch(function (error) {
107				console.error(error)
108			});
109	} else {
110		fetch('/uformSubmit', requestOptions)
111			.then(function (response) {
112				return response.text()
113			}).then(function (data) {
114				console.log('moving on to success')
115				window.promptSubmit.click();
116			}).catch(function (error) {
117				console.error(error)
118			});
119	}
120
121	// hit the submit btn or close the window    
122	if (["x", "close"].includes(source)) {
123		// https://jira.internetbrands.com/browse/EHCS-3415
124		if (window.modelOpen && window.modelOpen == "uquiz") {
125			// close quiz model and show typ step
126			// show step-4
127			$('#uquiz-universalFormPromptModal').modal('hide');
128			document.querySelector(".modal-progress_bar").setAttribute("data-progress-step", "4");
129			// clear form
130			window.parentWrapper.querySelectorAll(".modal-body_main-step input").forEach(function (i) {
131				i.value = ""
132			})
133			window.parentWrapper.querySelectorAll(".modal-body_main-step textarea").forEach(function (i) {
134				i.value = ""
135			})
136			document.getElementById(quizPrefix + "loading").classList.remove("d-none");
137			document.getElementById(quizPrefix + "chat-wrapper").classList.add("d-none");
138		} else {
139			delete window.modelOpen
140			//redirect to typ
141			// window.location.replace("https://www.nolo.com/uform/success");
142		}
143		delete window.modelOpen
144	} else {
145		// $('#uform-universalFormPromptModal').modal('hide');
146		if (typeof promptModalQuiz !== 'undefined') {
147			// $('#uquiz-universalFormPromptModal').modal('hide');
148			displayLoadingForSubmit();
149		}
150		displayLoadingForSubmit();
151		// window.promptSubmit.click();
152	}
153}
154
155// Tracking function
156function triggerTrack(link_name, quiz = false) {
157	if (typeof gtag == 'undefined') {
158		function gtag() {
159			dataLayer.push(arguments);
160		}
161	}
162	var selector, journey_name;
163	if (quiz) {
164		journey_name = 'Form_Top';
165	} else {
166		var form = parentWrapper.querySelector("form");
167		var type = form.getAttribute("onsubmit").split(",")[1].replaceAll("'", "").replaceAll(" ", "");
168		var selector, journey_name;
169		if (type == 'desktop') {
170			selector = '.form-connect-with-attorneys.desktop-form';
171			journey_name = 'Form';
172		} else if (type == 'mobile') {
173			selector = '.form-connect-with-attorneys.mobile-form';
174			journey_name = 'Form';
175		} else if (type == 'top') {
176			selector = '.form-connect-with-attorneys.top-form';
177			journey_name = 'Form_Top';
178		} else if (type == 'bottom') {
179			selector = '.form-connect-with-attorneys.bottom-form';
180			journey_name = 'Form_Bottom';
181		}
182
183	}
184
185	const zipRegex = /^(00[67]\d{2}|009\d{2})(-\d{4})?$/;
186
187	var aop_id = document.getElementById('aop_abbreviation').value;
188	gtag('event', 'optimize_journey', {
189		'journey_name': aop_id + '_' + journey_name,
190		'step_name': aop_id + '_universalform',
191		'link_name': link_name,
192		'test_lead': zipRegex.test(document.querySelector(selector+ ' [name=zip]').value) ? "Yes" : "No",
193		'send_to': 'G-RJLCGB9QZ9'
194
195	});
196}
197
198// hide the real submit button
199var formDescriptions = document.querySelectorAll(".find-a-lawyer-widget textarea[name=description]");
200// hide the real click here button
201var formClickHeres = document.querySelectorAll(".find-a-lawyer-widget #universal-form-click-here");
202
203// hide the real submit button
204var formSubmitBtns = document.querySelectorAll(".find-a-lawyer-widget button[type=submit]");
205
206
207var promptModal;
208var formPrefix = "uform-";
209
210if (document.getElementById('aop_abbreviation') && document.getElementById('aop_abbreviation').value !== 'SSDI') {
211	startChat();
212}
213
214function startChat() {
215	// if (!document.querySelector(".find-a-lawyer-widget textarea[name=description]")) injectCaseField();
216	if (!document.querySelector("#submit-to-prompt")) {
217		formSubmitBtns.forEach(function (formSubmitBtn) {
218			injectCaseButton(formSubmitBtn);
219		});
220	}
221
222	if (!document.querySelector("#universal-form-click-here-ai")) formClickHeres.forEach(function (formClickHere) {
223		injectClickHere(formClickHeres)
224	})
225	// if (!document.getElementById(`${formPrefix}universalFormPromptModal`) && showModal) bindModalEvents();
226	// bind the modal to BS library:
227	bindModalEvents();
228	// promptModal = new bootstrap.Modal(document.getElementById(`${formPrefix}universalFormPromptModal`), {backdrop:'static'});
229}
230 
231
232// create a prompt casedetails textarea to collect the first hand case description from universal form which should look exact same as case description field:'
233function injectClickHere(formClickHeres) {
234	formClickHeres.forEach(function (formClickHere) {
235		if (formClickHere.closest(".universal-form-top") && formClickHere.closest(".universal-form-top").parentNode.querySelector('#universal-form-modal')) {
236			// using uquiz-injection
237		} else {
238			if (formClickHere.parentElement.parentElement.querySelectorAll("#universal-form-click-here-ai").length>=1) {
239				return;
240			} else {
241				var clickHereDiv = document.createElement("div");
242				clickHereDiv.innerText = "click here"
243				clickHereDiv.setAttribute("id", "universal-form-click-here-ai")
244				formClickHere.classList.add("d-none");
245				// Adding the prompt continue button to the page:
246				formClickHere.insertAdjacentElement("afterend", clickHereDiv);
247				clickHereDiv.addEventListener("mousedown", function () {
248					event.preventDefault();
249                    formClickHere.parentElement.parentElement
250					var parentWrapper = event.currentTarget.closest(".find-a-lawyer-widget");
251                    parentWrapper.querySelector('input[name="sms_optin_status"]').value = '0';
252					aiPromptSubmission(parentWrapper, formClickHere)
253				})
254			}
255		}
256	});
257}
258
259// create a prompt casedetails textarea to collect the first hand case description from universal form which should look exact same as case description field:'
260// function injectCaseField() {
261// 	formDescriptions.forEach(function (formDescription) {
262// 		if (formDescription.closest(".universal-form-top") && formDescription.closest(".universal-form-top").parentNode.querySelector('#universal-form-modal')) {
263// 			// using uquiz-injection
264// 		} else {
265// 			var promptCaseField = document.createElement("textarea");
266// 			promptCaseField.classList.add("form-control");
267// 			promptCaseField.setAttribute("style", "max-width:100%; flex-basis:100%;");
268// 			promptCaseField.setAttribute("row", "7");
269// 			promptCaseField.setAttribute("name", "casedetails");
270// 			promptCaseField.setAttribute("placeholder", "Please describe your legal situation in as much detail as possible.");
271// 			formDescription.classList.add("d-none");
272// 			// Adding the prompt continue button to the page:
273// 			formDescription.insertAdjacentElement("afterend", promptCaseField);
274// 			promptCaseField.addEventListener("input", function () {
275// 				promptCaseField.parentNode.classList.add("done");
276// 				formDescription.value = promptCaseField.value;
277// 				promptCaseField.classList.remove('error')
278// 			})
279// 		}
280// 	});
281// }
282
283// create prompt continue button which should look exact same as submit button:
284
285// button to replace "real" submission for AI prompt
286function injectCaseButton(formSubmitBtn) {
287	var promptContinueBtn = document.createElement("button");
288	promptContinueBtn.classList.add("btn");
289	promptContinueBtn.classList.add("btn-CTA");
290	promptContinueBtn.classList.add("find-a-lawyer-button");
291	promptContinueBtn.setAttribute("id", "submit-to-prompt");
292	promptContinueBtn.innerText = formSubmitBtn.innerText.replaceAll("\n", "");
293	promptContinueBtn.type = "button";
294	if(formSubmitBtn.getElementsByTagName('svg').length > 0) {
295		var btnSVG = formSubmitBtn.getElementsByTagName('svg')[0];
296		btnSVG.style.cssText = 'margin-left: auto; margin-top: 5px;float: right;';
297		promptContinueBtn.append(btnSVG);
298	}
299	// event of click: post the questionnaire to the API, get the answer from AI
300	promptContinueBtn.addEventListener("mousedown", async function (event) {
301		event.preventDefault();
302		var parentWrapper = event.currentTarget.closest(".find-a-lawyer-widget");
303		aiPromptSubmission(parentWrapper, formSubmitBtn)
304	})
305
306	formSubmitBtn.classList.add("d-none");
307	// Adding the prompt continue button to the page: Parent Node:.button-container
308	formSubmitBtn.parentNode.appendChild(promptContinueBtn);
309}
310
311async function aiPromptSubmission(parentWrapper, formSubmitBtn) {
312
313	// disable the button and wait for the result:
314	parentWrapper.querySelector("form #submit-to-prompt").disabled = true;
315	parentWrapper.querySelector("form #universal-form-click-here-ai").disabled = true;
316
317	//collect all answers:
318	// var name = parentWrapper.querySelector("[name=name]").value;
319	// var phone = parentWrapper.querySelector("[name=phone]").value;
320	// var email = parentWrapper.querySelector("[name=email]").value;
321	// var zip = parentWrapper.querySelector("[name=zip]").value;
322	// var caseDetails = parentWrapper.querySelector("[name=description]").value;
323
324	var form = parentWrapper.querySelector("form");
325	var type = form.getAttribute("onsubmit").split(",")[1].replaceAll("'", "").replaceAll(" ", "");
326
327	var validation = await validateForm(event, type, form);
328
329	if (validation == true) {
330		// Post information to the OpenAI
331		var reqHander = new Headers();
332		reqHander.append("Content-Type", "application/json");
333		reqHander.append("X-XSRF-TOKEN", decodeURIComponent(getCookie("XSRF-TOKEN")));
334
335		// Looking for session storage for the information
336		var curUserTracking = JSON.parse(sessionStorage.getItem(userTrackKey));
337
338		// handle the case where homepage and bottom uform can submit with just one name
339		var user_name = parentWrapper.querySelector("[name=name]").value.replace(/\s+/g, ' ');
340
341		// need to generate unique id ("ai_case_id")
342		window.ai_case_id = Math.floor(Math.random() * (1e13 + 1e12 +1)) + 1e12;
343
344		var raw_small = {
345			"zip": parentWrapper.querySelector("[name=zip]").value,
346			"description": parentWrapper.querySelector("[name=description]").value,
347			"prompt": "generic_questions",
348			"ai_case_id": window.ai_case_id,
349			"skip_prediction": true
350		}
351
352		var raw = {
353			"name": user_name,
354			"email": parentWrapper.querySelector("[name=email]").value,
355			"phone": parentWrapper.querySelector("[name=phone]").value,
356			"zip": parentWrapper.querySelector("[name=zip]").value,
357			"description": parentWrapper.querySelector("[name=description]").value,
358			"loc": parentWrapper.querySelector("[name=loc]").value,
359			"WebsiteID": "15-103-212-197-7-216-247-236",
360			"referer": document.referrer,
361			"prompt": "generic_questions",
362			'cache_lead': true,
363			"ai_case_id": window.ai_case_id,
364			"skip_prediction": true
365		};
366		
367		if (NCMS.pageType != 'front') {
368			raw["form_type"] = parentWrapper.querySelector("[name=form_type]").value
369		}
370
371		raw['sms_optin_status'] = parentWrapper.querySelector("[name=sms_optin_status]").value;
372		raw['tcpa_consent_verbiage'] = parentWrapper.querySelector("[name=tcpa_consent_verbiage]").value;
373		raw['trusted_form_id'] = parentWrapper.querySelector("[name=trusted_form_id]").value;
374
375		var body = JSON.stringify(raw);
376
377		window.promptSubmit = formSubmitBtn;
378
379		var requestOptions = {
380			method: 'POST',
381			headers: reqHander,
382			body: body,
383			redirect: 'follow'
384		};
385
386		var requestOptionsSmall = {
387			method: 'POST',
388			headers: reqHander,
389			body: JSON.stringify(raw_small),
390			redirect: 'follow'
391		};
392
393		if (parentWrapper.querySelector("[name=description]").value.length >= SPAM_CHAR_LIMIT ) {
394			var source;
395			if (parentWrapper.querySelector("[name=description]").value.length >= SPAM_CHAR_LIMIT) {
396				source = "long-desc";
397			} else source = "error"
398			// console.log("parentWrapper -- ", parentWrapper);
399			submitToForm(parentWrapper, source);
400		} else {
401			$('#uform-universalFormPromptModal').modal('show');
402			$('#bottom-right-black-bubble-invite-container').addClass("move-back");
403			$('.ngageMobileFloat').addClass("move-back");
404			document.getElementById(formPrefix + "prompt-form").setAttribute("disabled",true);
405
406			fetch('/uformMCQ', requestOptionsSmall)
407				.then(function (response) {
408					return response.json()
409				})
410				.then(function (data) {
411					if (data && data.status == 200) {
412						var mcqQuestions = data.responseBody.questions;
413						if (showModal) {
414							document.getElementById(formPrefix + "loading").classList.add("d-none");
415							// open modal with AI questions:
416
417							// create radio questions from tools GPT MCQ response
418							for (let i = 0; i < mcqQuestions.length; i++) {
419								// create single question container
420								var questionContainer = document.createElement('div');
421								questionContainer.classList.add("ai-question-container");
422
423								// create label and append ^
424								var questionLabel = document.createElement('label');
425								questionLabel.innerHTML = mcqQuestions[i].text;
426								questionLabel.classList.add('ai-radio-label');
427								questionLabel.setAttribute('data-q-id', 'aiQuestion['+i+']');
428								questionContainer.appendChild(questionLabel);
429
430								// create radioContainer (contains input + label)
431								var radioContainer = document.createElement('div');
432								radioContainer.classList.add('ai-radio-container');
433
434								// create radio buttons and append ^
435								for (let j = 0; j < mcqQuestions[i].options.length; j++) {
436									var radioSelection = document.createElement('div');
437									var radioInput = document.createElement('input');
438									radioInput.setAttribute('type', 'radio');
439									radioInput.setAttribute('class', 'radio-input');
440									radioInput.setAttribute('id', 'aiQuestion['+i+']_'+j);
441									radioInput.setAttribute('name', 'aiQuestion['+i+']');
442
443									// just use obj.answer
444									// try {
445										radioInput.setAttribute('value', mcqQuestions[i].options[j].answer);
446									// } catch (error) {
447									//     console.log("using .answer", error);
448									//     radioInput.setAttribute('value', mcqQuestions[i].options[j].answer);
449									// }
450									
451									//styling class
452									radioInput.addEventListener('change', function(event) {
453										if (event.target.checked) {
454											document.querySelector("#uform-prompt-form").classList.add("interacted");
455											document.querySelector("#uform-prompt-form").removeAttribute("disabled");
456										}
457									});
458									
459									//now the label
460									var radioLabel = document.createElement('label');
461									radioLabel.setAttribute('class', 'radio-label');
462									radioLabel.setAttribute('for', 'aiQuestion['+i+']_'+j);
463									radioLabel.innerHTML = mcqQuestions[i].options[j].text;
464
465									radioSelection.appendChild(radioInput);
466									radioSelection.appendChild(radioLabel);
467
468									// append 
469									radioContainer.appendChild(radioSelection);
470								}
471								questionContainer.appendChild(radioContainer);
472
473								//^^ append all that to (formPrefix + "chat-content")
474								document.getElementById(formPrefix + "chat-content").appendChild(questionContainer);
475							}
476
477							// document.getElementById(quizPrefix + "chat-content").innerText = completion;
478							document.getElementById(formPrefix + "loading").classList.add("d-none");
479							document.querySelector("#uform-universalFormPromptModal .subtitle").classList.remove("d-none");
480							document.getElementById(formPrefix + "chat-wrapper").classList.remove("d-none");
481							
482							window.parentWrapper = parentWrapper;
483							document.querySelector('.modal-backdrop').classList.add("darker")
484						} else {
485							// Show the question as the second step of form, hide all other answer:
486							showPromptStep(parentWrapper, completion);
487						}
488						window.modelOpen = "uform";
489						triggerTrack("first_form_submit", false)
490					} else {
491						console.error('return empty ai response, submit nGage now')
492						submitToForm(window.parentWrapper, "error");
493					}
494				})
495				.catch(function (error) {
496					console.error('failed getting gpt qs', error);
497					// $('#uform-universalFormPromptModal').modal('hide');
498					window.gptAbort = true;
499					submitToForm(parentWrapper, "error");
500				});
501
502			fetch('/uformQueue', requestOptions)
503				.then(function (response) {
504					return response.json()
505				}).then(function (data) {
506					parentWrapper.querySelector("[name=lead_id]").value = data.responseBody.lead_id;
507					requestOptions.body = JSON.stringify(raw);
508				}).catch(function (error) {
509					console.error(error)
510				});
511		}
512	} else {
513		parentWrapper.querySelector("form #submit-to-prompt").removeAttribute("disabled")
514	}
515}
516
517//Modal Events
518function bindModalEvents() {
519	// document.querySelector("#" + formPrefix + "chat-wrapper textarea").addEventListener("input", function (event) {
520	// 	event.preventDefault();
521	// 	if (event.target.value.length > 0 && (event.target.value.trim() !== '')) document.getElementById(formPrefix + "prompt-form").removeAttribute('disabled')
522	// 	else document.getElementById(formPrefix + "prompt-form").setAttribute('disabled', '')
523	// })
524
525	// bind the Modal prompt-form button 
526	document.getElementById(formPrefix + "prompt-form").addEventListener("mousedown", function (event) {
527		if (event.button == 0) {
528			event.preventDefault();		
529			// append the content to the form case description:
530			// document.querySelectorAll('.ai-radio-label').forEach(element => {
531			// 	var radioFor = element.getAttribute('for');
532			// 	radioFor = radioFor.replace('[', '\\[');
533			// 	radioFor = radioFor.replace(']', '\\]');
534
535			// 	var radioAnswer;
536			// 	try {
537			// 		radioAnswer = document.querySelector('#' + formPrefix + "chat-content input[name=" + radioFor + "]:checked").value;
538			// 	} catch (error) {
539			// 		radioAnswer = 'undefined';
540			// 	}
541			// 	window.parentWrapper.querySelector("[name=description]").value += " \n\n " + element.innerText + ' ' + radioAnswer;
542			// });
543			
544			// hit real submission button:
545			submitToForm(window.parentWrapper, "submit");
546			triggerTrack("ai_prompt_submit", false)
547			displayLoadingForSubmit();
548		}
549	});
550
551	// bind the Modal prompt-form-close button 
552	document.getElementById(formPrefix + "prompt-form-close").addEventListener("mousedown", function (event) {
553		event.preventDefault();
554		triggerTrack("ai_prompt_cancel_clicked", false)
555		// $('#uform-universalFormPromptModal').modal('hide');
556		displayLoadingForSubmit();
557
558		// hit real submission button:
559		submitToForm(window.parentWrapper, "close");
560	});
561
562	// bind the Modal close X icon button 
563	document.getElementById(formPrefix + "close-modal-icon").addEventListener("mousedown", function (event) {
564		event.preventDefault();
565		triggerTrack("ai_prompt_x_clicked", false)
566		// $('#uform-universalFormPromptModal').modal('hide');
567		displayLoadingForSubmit();
568		// hit real submission button:
569		submitToForm(window.parentWrapper, "x");
570	});
571}
572
573function displayLoadingForSubmit() {
574	document.querySelector("#uform-chat-wrapper").classList.add("d-none");
575	document.querySelector("#uform-loading").classList.remove("d-none");
576	document.querySelector("#uform-universalFormPromptModal .subtitle").classList.add("d-none");
577}
578//# sourceMappingURL=universalFormPrompt.js.map

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.