Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
| import { Api } from './Api'; | |
| import { Mention } from './Mention'; | |
| import { c } from './lib/Log'; | |
| import { Utils } from './lib/Utils'; | |
| import { VanillaTilt } from './vanilla-tilt'; | |
| import { ShareScreenshotModal, SavePublishModal } from './modals'; | |
| /// We experimented with a couple of different build systems | |
| /// to integrate Quill (for instance module-then-postprocessing | |
| /// like in `web3d`) but none worked really well so we just | |
| /// hotlink the js and basically copy/paste the @types/quill | |
| /// declaration here. | |
| /// Update: we now use rollup (for html2canvas), but quill is | |
| /// still a pain so it's still not in the same bundle. | |
| const DEBUG = false; | |
| /// ^^ when debugging the quill integration, add the quill.snow.css to layout.hbs | |
| /// <link href="/front/node_modules/quill/dist/quill.snow.css" rel="stylesheet"> | |
| /// <link href="/front/node_modules/quill/dist/quill.core.css" rel="stylesheet"> | |
| /// We tried doing it programmatically here but it's a bit slow. | |
| if (DEBUG) { | |
| document.head.insertAdjacentHTML( | |
| 'beforeend', | |
| `<link href="/front/node_modules/quill/dist/quill.snow.css" rel="stylesheet">` | |
| ); | |
| /// ^^ add css to debug. Do it as early as possible. | |
| } | |
| enum Page { | |
| app, landing, model | |
| } | |
| const App = { | |
| page: | |
| (document.body.classList.contains('app')) ? Page.app | |
| : (document.body.classList.contains('landing')) ? Page.landing | |
| : Page.model | |
| , | |
| editable: document.body.dataset.editable === 'true', | |
| header: { | |
| shuffleBtn: document.querySelector('header .js-shuffle') as HTMLAnchorElement, | |
| triggerBtn: document.querySelector('header .js-trigger') as HTMLAnchorElement, | |
| mainInfoBtn: document.querySelector('header .title .info') as HTMLImageElement, | |
| shareBtn: document.querySelector<HTMLAnchorElement>('header .js-share'), | |
| saveBtn: document.querySelector<HTMLAnchorElement>('header .js-save'), | |
| duplicateBtn: document.querySelector<HTMLAnchorElement>('header .js-duplicate'), | |
| }, | |
| shareScreenBtn: document.querySelector('.page-container .js-share') as HTMLAnchorElement, | |
| loaderEditor: document.querySelector('.page-container .js-loader') as HTMLImageElement, | |
| sliders: Array.from( | |
| document.querySelectorAll('.decoder-settings input.slider') | |
| ) as HTMLInputElement[], | |
| INITIAL_CONTENT: {} as Delta, | |
| /** | |
| * Helper function to more cleanly route different page types. | |
| */ | |
| onLoad: (p: Page, callback: () => void) => { | |
| if (p === App.page) { | |
| document.addEventListener('DOMContentLoaded', () => { | |
| callback(); | |
| }); | |
| } | |
| }, | |
| }; | |
| const PROMPTS = [ | |
| `Before boarding your rocket to Mars, remember to pack these items`, | |
| `In a shocking finding, scientist discovered a herd of unicorns living in a remote, previously unexplored valley, in the Andes Mountains. Even more surprising to the researchers was the fact that the unicorns spoke perfect English.`, | |
| `Legolas and Gimli advanced on the orcs, raising their weapons with a harrowing war cry.`, | |
| `Today, scientists confirmed the worst possible outcome: the massive asteroid will collide with Earth`, | |
| ` | |
| Thor: The Tesseract belongs on Asgard, no human is a match for it. | |
| Tony turns to leave, but Steve stops him. | |
| Steve: You're not going alone! | |
| Tony: You gonna stop me? | |
| `.replace(/\t/g, "").trim().concat("\n"), | |
| ]; | |
| App.onLoad(Page.app, () => { | |
| const modalScreenshot = new ShareScreenshotModal; | |
| const opts: QuillOptionsStatic = DEBUG | |
| ? { | |
| theme: 'snow', | |
| modules: { | |
| mention: {}, | |
| }, | |
| } | |
| : { | |
| theme: undefined, | |
| // formats: [], | |
| modules: { | |
| toolbar: [], | |
| mention: {}, | |
| }, | |
| } | |
| ; | |
| if (! App.editable) { | |
| opts.readOnly = true; | |
| } | |
| const quill = new Quill('div.editor', opts); | |
| const mention = quill.getModule('mention') as Mention; | |
| (<any>window).quill = quill; | |
| const QUILL_C = (<any>window).QUILL_C; | |
| if (QUILL_C) { | |
| quill.setContents(QUILL_C); | |
| } | |
| quill.container.appendChild(App.loaderEditor); | |
| quill.container.appendChild(App.shareScreenBtn); | |
| // | |
| // div.editor .ql-container <-- quill.container | |
| // +--------------------------------+ | |
| // | div.ql-editor contenteditable | <-- quill.root | |
| // | +----------------------------+ | | |
| // | | | | | |
| // | | | | | |
| // | +----------------------------+ | | |
| // +--------------------------------+ | |
| // | |
| quill.keyboard.addBinding({ key: Mention.Keys.TAB }, () => { | |
| triggerAutocomplete(); | |
| }); | |
| quill.keyboard.bindings[Mention.Keys.TAB].unshift( | |
| quill.keyboard.bindings[Mention.Keys.TAB].pop() | |
| ); | |
| /// ^^ important. | |
| /// ^^ place it at beginning of bindings. | |
| const triggerAutocomplete = async () => { | |
| /// vv position loader | |
| mention.setCursorPos(); | |
| const cursorBbox = quill.getBounds(mention.getCursorPos()); | |
| App.loaderEditor.style.top = `${cursorBbox.top - 4}px`; | |
| App.loaderEditor.style.left = `${cursorBbox.left + 4}px`; | |
| App.loaderEditor.classList.remove('hide'); | |
| /// vv Launch api request. | |
| const text = quill.getText(0, mention.getCursorPos()); | |
| // ^^ That is so much simpler that what we used to do | |
| // when we were embbedding objects like in `quill-mention`. | |
| c.debug( | |
| `%c[About to launch autocomplete for]`, | |
| `color: green;`, | |
| text, | |
| ); | |
| const o = await Api.shared.postWithSettings({ context: text }); | |
| App.loaderEditor.classList.add('hide'); | |
| /// vv Trigger mention module. | |
| for (const x of o.sentences) { | |
| c.log(x.value); | |
| } | |
| mention.trigger( | |
| o.sentences.map(x => x.value) | |
| ); | |
| }; | |
| App.header.duplicateBtn?.addEventListener('click', async (e) => { | |
| e.preventDefault(); | |
| const url = await Api.shared.postDuplicate(); | |
| window.location.href = url; | |
| }); | |
| if (! App.editable) { | |
| return ; | |
| } | |
| /** | |
| * vv Below is only in editable mode. | |
| */ | |
| const modalSave = new SavePublishModal(quill); | |
| App.header.shuffleBtn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| quill.setText( | |
| Utils.randomItem(PROMPTS) | |
| ); | |
| quill.setSelection(quill.getLength(), 0); | |
| /// ^^ github.com/quilljs/quill/issues/2635 | |
| triggerAutocomplete(); | |
| }); | |
| App.header.triggerBtn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| triggerAutocomplete(); | |
| }); | |
| App.header.shareBtn?.addEventListener('click', async (e) => { | |
| e.preventDefault(); | |
| const text = `Write With Transformer via @huggingface`; | |
| window.open(`https://twitter.com/share?url=${ encodeURIComponent(window.location.href) }&text=${ encodeURIComponent(text) }`); | |
| }); | |
| App.header.saveBtn?.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| mention.hideMentionList(); | |
| modalSave.show(); | |
| }); | |
| App.shareScreenBtn.addEventListener('click', async (e) => { | |
| e.preventDefault(); | |
| mention.hideMentionList(); | |
| modalScreenshot.show(); | |
| }); | |
| quill.on('text-change', () => { | |
| App.shareScreenBtn.classList.remove('hide'); /// <- we use a fadeout effect. | |
| const hasTextFromAI = quill.getContents() | |
| .ops | |
| .some(op => op.attributes && op.attributes.bold === true) | |
| ; | |
| App.shareScreenBtn.classList.toggle('fadeout', ! hasTextFromAI); | |
| }); | |
| document.addEventListener('click', (e) => { | |
| /// Handle clicks on links inside the editor. | |
| if (! ( | |
| e.target instanceof HTMLAnchorElement | |
| && e.target.closest('div.ql-editor') !== null | |
| )) { | |
| return ; | |
| } | |
| /// Ok, let's do this. | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| const href = e.target.getAttribute('href'); /// <- caution, get the original string. | |
| c.debug(`[click]`, href); | |
| if (href === '#js-shuffle') { | |
| App.header.shuffleBtn.click(); | |
| } else { | |
| window.open(e.target.href); | |
| } | |
| }); | |
| document.addEventListener("scroll", e => { | |
| const trigger = document.getElementsByClassName("js-trigger")[0] as HTMLAnchorElement; | |
| if (scrollY > 100) { | |
| trigger.style.position = "fixed"; | |
| trigger.style.top = "10px"; | |
| trigger.style.border = "1px solid blue"; | |
| trigger.style.backgroundColor = "white"; | |
| trigger.style.borderRadius = "100px"; | |
| trigger.style.padding = "5px"; | |
| trigger.style.zIndex = "1"; | |
| trigger.style.left = "50%"; | |
| trigger.style.transform = "translateX(-50%)"; | |
| } else { | |
| trigger.style.position = "relative"; | |
| trigger.style.top = "auto"; | |
| trigger.style.border = "none"; | |
| trigger.style.backgroundColor = "white"; | |
| trigger.style.borderRadius = "0"; | |
| trigger.style.padding = "0"; | |
| trigger.style.zIndex = "1"; | |
| trigger.style.left = "auto" | |
| } | |
| }); | |
| /** | |
| * 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)) | |
| ; | |
| const valueKey = `value-${value}`; | |
| if (slider.dataset[valueKey]) { | |
| spanVal.innerText = slider.dataset[valueKey]!; | |
| } else { | |
| 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); | |
| }); | |
| } | |
| }); | |
| App.onLoad(Page.landing, () => { | |
| /** | |
| * VanillaTilt | |
| */ | |
| VanillaTilt.init(document.querySelectorAll("[data-tilt]"), { | |
| glare: true, | |
| scale: 1.06, | |
| 'max-glare': 0.3, | |
| speed: 400, | |
| }); | |
| }); | |