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, | |
}); | |
}); | |