const App = { persona: { slug: "", text: "", }, messages: [] as Message[], /// HTMLElements messagesRoot: document.querySelector('div.messages') as HTMLDivElement, divPersona: document.querySelector('div.persona') as HTMLDivElement, linkSuggest: document.querySelector('.chat-suggestion .js-suggestion') as HTMLLinkElement, sliders: Array.from( document.querySelectorAll('.decoder-settings input.slider') ) as HTMLInputElement[], }; document.addEventListener('DOMContentLoaded', () => { /** * Persona * - in simple mode, we just re-format the text slightly to make it prettier. * - in attention mode, we tokenize and add spans around every token. */ const simplePersona = () => { const text = App.persona.text; const html = text.split('.').map(x => Utils.capitalize(x)).join(`.
`); App.divPersona.innerHTML = html; }; const tokenizePersona = () => { const text = App.persona.text; const tokens = text.split(/\b/).filter(x => ! /\s/.test(x)); let html = ``; for (let [i, tok] of tokens.entries()) { if (i === 0) { html += `${ Utils.capitalize(tok) }`; } else if (i > 0 && tokens[i-1] === '.') { html += `
${ Utils.capitalize(tok) }`; } else if (/[.,']/.test(tok)) { html += `${ tok }`; } else { html += ` ${ tok }`; } } App.divPersona.innerHTML = html; }; App.persona = (window).PERSONA_ATLOAD; // ^^ tokenizePersona(); simplePersona(); document.querySelector('.js-shuffle')!.addEventListener('click', async (evt) => { evt.preventDefault(); App.persona = await Api.shared.getShuffle(); tokenizePersona(); history.replaceState(null, "", `/persona/${App.persona.slug}`); /// Also reset messages and reload suggestion. App.messages = []; App.messagesRoot.innerHTML = ""; loadSuggestion(); }); document.querySelector('.js-share')!.addEventListener('click', async (evt) => { evt.preventDefault(); history.replaceState(null, "", `/persona/${App.persona.slug}`); const text = `Chat with me: ${ App.divPersona.innerText.replace(/\n/g, " ") }`; window.open(`https://twitter.com/share?url=${ encodeURIComponent(window.location.href) }&text=${ encodeURIComponent(text) }`); }); /** * Settings */ const handleSliderChange = (slider: HTMLInputElement) => { const div = slider.parentNode as HTMLDivElement; const spanVal = div.querySelector('.js-val') as HTMLSpanElement; const value = Number.isInteger(slider.valueAsNumber) ? slider.valueAsNumber : Number(slider.valueAsNumber.toFixed(2)) ; spanVal.innerText = value.toString(); const min = Number(slider.getAttribute('min')); const max = Number(slider.getAttribute('max')); if (value < min + (max - min) / 3) { spanVal.className = "js-val green"; } else if (value < min + 2 * (max - min) / 3) { spanVal.className = "js-val orange"; } else { spanVal.className = "js-val red"; } const isInverted = slider.classList.contains('js-inverted'); if (isInverted) { if (spanVal.classList.contains('green')) { spanVal.classList.remove('green'); spanVal.classList.add('red'); } else if (spanVal.classList.contains('red')) { spanVal.classList.remove('red'); spanVal.classList.add('green'); } } }; for (const slider of App.sliders) { handleSliderChange(slider); slider.addEventListener('input', () => { handleSliderChange(slider); }); } const gauge = document.querySelector('div.gauge') as HTMLDivElement; gauge.addEventListener('click', () => { gauge.classList.add('active'); }); const gaugeEls = Array.from( document.querySelectorAll('.gauge .gauge-el-wrapper') ); for (const gaugeEl of gaugeEls) { gaugeEl.addEventListener('click', () => { const i = gaugeEls.indexOf(gaugeEl); if (i === 0) { App.sliders[0].value = `180`; App.sliders[1].value = `0.1`; App.sliders[2].value = `1.9`; } else if (i === 1) { App.sliders[0].value = `70`; App.sliders[1].value = `0.5`; App.sliders[2].value = `1.2`; } else { App.sliders[0].value = `0`; App.sliders[1].value = `0.9`; App.sliders[2].value = `0.6`; } for (const slider of App.sliders) { handleSliderChange(slider); } }); } /** * Chat input */ const form = document.querySelector('form.js-form') as HTMLFormElement; const input = document.querySelector('input.input-message') as HTMLInputElement; form.addEventListener('submit', async (evt) => { evt.preventDefault(); document.querySelector('.placeholder-start')!.style.display = 'none'; const content = input.value; if (content.trim() === "") { c.debug(`Empty input`); return ; } input.value = ""; const um: Message = { incoming: false, content: content, }; Markup.append(um); const o = await Api.shared.postWithSettings({ text: content }, { context: App.messages, persona: App.persona.text, }); c.log(o); // c.log(o.attention[0]); // c.log(o.attention[1]); // c.log(o.attention[2]); App.messages.push(um); /// ^^ not before the API call because it shouldn't be included in context. /** * Visualize Attention */ // for (const [idx, [token, att]] of o.attention[0].entries()) { // if (att === 0) { // continue; // } // const span = document.querySelector(`.persona span[data-idx="${idx}"]`); // if (!span) { // c.error(`span not found`); // return ; // } // span.className = `attention-level level${ Markup.attentionThreshold(att) }`; // } ////// const m: Message = { incoming: true, content: o.text, }; App.messages.push(m); Markup.append(m); /// Finally, launch an auto-complete: loadSuggestion(); }); /** * Suggestion box */ const loadSuggestion = async () => { const spanLoading = document.querySelector('.chat-suggestion .js-loading') as HTMLSpanElement; spanLoading.classList.remove('hide'); App.linkSuggest.classList.add('hide'); const o = await Api.shared.postWithSettings({ completion: "" }, { context: App.messages, persona: App.persona.text, }); c.log(o); App.linkSuggest.innerText = o.text; spanLoading.classList.add('hide'); App.linkSuggest.classList.remove('hide'); }; loadSuggestion(); App.linkSuggest.addEventListener('click', async (evt) => { evt.preventDefault(); App.linkSuggest.classList.add('hide'); input.value = App.linkSuggest.innerText; await Utils.delay(500); const sendBtn = form.querySelector('button.input-button') as HTMLButtonElement; sendBtn.click(); /// ^^ do not do `form.submit()` as we want to trigger our handler. }); });