add chat gpt
This commit is contained in:
parent
bf77ae3050
commit
269e3b0907
|
@ -0,0 +1,76 @@
|
|||
#chat-window {
|
||||
height: calc(100vh - 334px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
textarea {
|
||||
resize: none;
|
||||
line-height: 1.2;
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
font-family: 'Palatino Linotype', Lora, serif;
|
||||
font-size: 1rem;
|
||||
transition: background-color 350ms, border-color 150ms;
|
||||
border: 1px solid;
|
||||
margin: 0 -5px;
|
||||
background-color: #eab77533;
|
||||
border-color: transparent;
|
||||
height: 24px; /* initial height */
|
||||
}
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
textarea:placeholder-shown:not(:focus) {
|
||||
background-color: #eab77555;
|
||||
border-color: #323a42;
|
||||
}
|
||||
.messages-scroller {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
padding-right: 8px;
|
||||
margin-right: -5px;
|
||||
overflow: auto;
|
||||
}
|
||||
.messages .message {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.messages .message.user {
|
||||
text-align: right;
|
||||
}
|
||||
.messages .message.user:not(:last-child, :nth-last-child(2)) {
|
||||
color: #323a42bb;
|
||||
}
|
||||
.error {
|
||||
color: #ad5f1e;
|
||||
}
|
||||
.poem {
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
.poem h3 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.byline {
|
||||
font-style: italic;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.poem-line:empty {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.prompt-help {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: #ad5f1e77;
|
||||
cursor: pointer;
|
||||
}
|
||||
.small-caps {
|
||||
font-variant-caps: small-caps;
|
||||
}
|
||||
ul.poem-list {
|
||||
padding: 0;
|
||||
}
|
||||
ul.poem-list li {
|
||||
list-style-type: none;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
|
@ -309,6 +309,10 @@ a.journal-link:hover, a.journal-link:focus {
|
|||
border-color: #6d747a;
|
||||
box-shadow: 2px 2px #6d747a;
|
||||
}
|
||||
a.journal-link:hover .line, a.journal-link:focus .line {
|
||||
/* maybe just make it all opacity 0.7 instead of all the working-around (same for .library-link) */
|
||||
opacity: 0.7;
|
||||
}
|
||||
a.journal-link:hover time, a.journal-link:focus time {
|
||||
color: #6d747a;
|
||||
}
|
||||
|
|
|
@ -108,6 +108,27 @@ sup, sub {
|
|||
.line.stop-6 {
|
||||
background-color: #426153;
|
||||
}
|
||||
.icon.stop-0 {
|
||||
color: #984624;
|
||||
}
|
||||
.icon.stop-1 {
|
||||
color: #ad5f1e;
|
||||
}
|
||||
.icon.stop-2 {
|
||||
color: #c59506;
|
||||
}
|
||||
.icon.stop-3 {
|
||||
color: #c8b500;
|
||||
}
|
||||
.icon.stop-4 {
|
||||
color: #8ea530;
|
||||
}
|
||||
.icon.stop-5 {
|
||||
color: #4b7b52;
|
||||
}
|
||||
.icon.stop-6 {
|
||||
color: #426153;
|
||||
}
|
||||
.socials p a {
|
||||
padding: 0 2px;
|
||||
margin: 0 -2px;
|
||||
|
@ -196,6 +217,9 @@ a.journal-link:hover, a.journal-link:focus {
|
|||
border-color: #6d747a;
|
||||
box-shadow: 2px 2px #6d747a;
|
||||
}
|
||||
a.journal-link:hover .line, a.journal-link:focus .line {
|
||||
opacity: 0.7;
|
||||
}
|
||||
a.journal-link h3 {
|
||||
margin: 0 0.5em 0 0;
|
||||
}
|
||||
|
@ -347,6 +371,58 @@ td {
|
|||
padding: 0.25em;
|
||||
border: 1px solid #323a4288;
|
||||
}
|
||||
.library-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
}
|
||||
a.library-link {
|
||||
display: grid;
|
||||
text-decoration: none;
|
||||
grid-template-columns: auto 1fr;
|
||||
margin: -0.25em;
|
||||
margin-right: 0;
|
||||
padding: 0.25em;
|
||||
margin-bottom: 4px;
|
||||
padding-right: 0;
|
||||
border: 1px solid #323a42;
|
||||
box-shadow: 1px 1px #323a42;
|
||||
border-radius: 3px;
|
||||
align-items: center;
|
||||
}
|
||||
a.library-link:hover, a.library-link:focus {
|
||||
outline: 0;
|
||||
border-color: #6d747a;
|
||||
box-shadow: 2px 2px #6d747a;
|
||||
}
|
||||
a.library-link:hover .icon, a.library-link:focus .icon {
|
||||
opacity: 0.7;
|
||||
}
|
||||
a.library-link h3 {
|
||||
font-family: 'Palatino Linotype', Lora, serif;
|
||||
grid-column: 2;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
margin: 0 0.5em 0.25em 0;
|
||||
}
|
||||
a.library-link p {
|
||||
grid-row: 3;
|
||||
font-size: 0.9em;
|
||||
grid-column: 1 / span 2;
|
||||
margin: 0.5em 5px 0.5em 0;
|
||||
}
|
||||
a.library-link .icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
a.library-link.coming-soon {
|
||||
opacity: 0.5;
|
||||
}
|
||||
a.library-link .coming-soon {
|
||||
color: #4b7b52;
|
||||
font-size: 0.8em;
|
||||
grid-row: 2;
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
@media only screen and (max-width: 700px) {
|
||||
a.journal-link {
|
||||
border-right: none;
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
</div>
|
||||
<nav>
|
||||
<a href='journal'><span class='link-title'>Journal</span> <span class='link-description'>a collection of articles, essays, and posts authored by me.</span></a>
|
||||
<a href='bookmarks'><span class='link-title'>Bookmarks</span> <span class='link-description'>web pages I want to keep track of and share with others.</span></a>
|
||||
<a href='library' class='new'><span class='link-title'>Library</span> <span class='link-description'>indexes, links, pages & projects</span></a>
|
||||
<a href='socials'><span class='link-title'>Socials</span> <span class='link-description'>other accounts of mine around the web.</span></a>
|
||||
</nav>
|
||||
</header>
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<title>gome — gome poem tome</title>
|
||||
<meta charset='utf-8'/>
|
||||
<meta name='theme-color' content='#efe5d7'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
|
||||
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
|
||||
<link rel='preconnect' href='https://fonts.googleapis.com'>
|
||||
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
|
||||
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
|
||||
<link rel='stylesheet' type='text/css' href='../css/style.css'/>
|
||||
<link rel='stylesheet' type='text/css' href='../css/chat-gpt.css'/>
|
||||
<script type='module' src='js/chat-ui.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<header id='header'>
|
||||
<nav>
|
||||
<a href='..'>back to gomepage</a>—<a href='.'>library</a>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<h1>Gome Poem Tome</h1>
|
||||
<noscript>
|
||||
<p>Sorry, this is just a Javascript thing… but here’s a poem:</p>
|
||||
<h3>Ozymandias</h3>
|
||||
<div class='byline'>by Horace Smith</div>
|
||||
<p>
|
||||
In Egypt’s sandy silence, all alone,</br>
|
||||
Stands a gigantic Leg, which far off throws</br>
|
||||
The only shadow that the Desert knows:—</br>
|
||||
“I am great <span class='small-caps'>Ozymandias</span>,” saith the stone,</br>
|
||||
“The King of Kings; this mighty City shows</br>
|
||||
The wonders of my hand.”— The City’s gone,—</br>
|
||||
Naught but the Leg remaining to disclose</br>
|
||||
The site of this forgotten Babylon.</br></br>
|
||||
We wonder — and some Hunter may express</br>
|
||||
Wonder like ours, when thro’ the wilderness</br>
|
||||
Where London stood, holding the Wolf in chace,</br>
|
||||
He meets some fragment huge, and stops to guess</br>
|
||||
What powerful but unrecorded race</br>
|
||||
Once dwelt in that annihilated place.
|
||||
</p>
|
||||
</noscript>
|
||||
<div id='chat-window'>
|
||||
<div class='messages-scroller'><div class='messages'></div></div>
|
||||
<textarea placeholder=' '></textarea>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<img src='../img/mushrooms_2.webp' alt='Toadstools' />
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<title>gome — library</title>
|
||||
<meta charset='utf-8'/>
|
||||
<meta name='theme-color' content='#efe5d7'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
|
||||
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
|
||||
<link rel='preconnect' href='https://fonts.googleapis.com'>
|
||||
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
|
||||
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
|
||||
<link rel='stylesheet' type='text/css' href='../css/style.css'/>
|
||||
</head>
|
||||
<body>
|
||||
<header id='header'>
|
||||
<nav>
|
||||
<a href='..'>back to gomepage</a>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<h1>Library</h1>
|
||||
<div class='library-links'>
|
||||
<a class='library-link' href='chat-gpt.html'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon stop-0"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
|
||||
<h3>Chat GPT (Gome Poem Tome)</h3>
|
||||
<p>It’s not intelligent, but the poems are good</p>
|
||||
</a>
|
||||
<a class='library-link coming-soon' href='journal-index.html'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon stop-1"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
|
||||
<h3>Journal Index</h3>
|
||||
<span class='coming-soon'>Coming soon</span>
|
||||
<p>A helpful guide to my posts</p>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<img src='../img/mushrooms_2.webp' alt='Toadstools' />
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<title>gome — bookmarks</title>
|
||||
<title>gome — library</title>
|
||||
<meta charset='utf-8'/>
|
||||
<meta name='theme-color' content='#efe5d7'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
|
||||
|
@ -14,11 +14,11 @@
|
|||
<body>
|
||||
<header id='header'>
|
||||
<nav>
|
||||
<a href='..'>back to gomepage</a>
|
||||
<a href='..'>back to gomepage</a>—<a href='.'>library</a>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<h1>Bookmarks</h1>
|
||||
<h1>Journal Index</h1>
|
||||
<p>Just a stub for now.</p>
|
||||
</main>
|
||||
<footer>
|
|
@ -0,0 +1,237 @@
|
|||
import gpt from './gpt.js';
|
||||
|
||||
const NBSP = '\x0a';
|
||||
|
||||
const chat_window = document.getElementById('chat-window');
|
||||
const textarea = chat_window.querySelector('textarea');
|
||||
const messages = chat_window.querySelector('.messages');
|
||||
const scroller = chat_window.querySelector('.messages-scroller');
|
||||
|
||||
textarea.message_history = JSON.parse(window.sessionStorage.getItem('message_history')) || [''];
|
||||
textarea.history_index = 0;
|
||||
|
||||
function resize_textarea() {
|
||||
textarea.style.height = 0;
|
||||
textarea.style.overflowY = 'hidden';
|
||||
textarea.style.height = (textarea.scrollHeight - 4) + 'px';
|
||||
textarea.style.overflowY = 'initial';
|
||||
}
|
||||
|
||||
textarea.addEventListener('input', resize_textarea);
|
||||
textarea.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') {
|
||||
if (textarea.value) {
|
||||
handle_message(textarea.value);
|
||||
textarea.history_index = 0;
|
||||
if (textarea.value !== textarea.message_history[1]) {
|
||||
textarea.message_history[0] = textarea.value;
|
||||
textarea.message_history.unshift('');
|
||||
window.sessionStorage.setItem('message_history', JSON.stringify(textarea.message_history));
|
||||
}
|
||||
textarea.value = '';
|
||||
resize_textarea();
|
||||
scroller.scrollTo(0, scroller.scrollHeight);
|
||||
}
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'ArrowUp' || e.key === 'Up') {
|
||||
if (
|
||||
(textarea.selectionStart === textarea.value.length || textarea.selectionStart === 0) &&
|
||||
textarea.history_index < textarea.message_history.length - 1
|
||||
) {
|
||||
if (textarea.history_index === 0) {
|
||||
textarea.message_history[0] = textarea.value;
|
||||
}
|
||||
textarea.history_index++;
|
||||
textarea.value = textarea.message_history[textarea.history_index];
|
||||
resize_textarea();
|
||||
}
|
||||
} else if (e.key === 'ArrowDown' || e.key === 'Down') {
|
||||
if (
|
||||
(textarea.selectionStart === textarea.value.length || textarea.selectionStart === 0) &&
|
||||
textarea.history_index > 0
|
||||
) {
|
||||
textarea.history_index--;
|
||||
textarea.value = textarea.message_history[textarea.history_index];
|
||||
resize_textarea();
|
||||
}
|
||||
} else if (e.key === 'c' && e.ctrlKey && textarea.selectionEnd - textarea.selectionStart <= 0) {
|
||||
textarea.history_index = 0;
|
||||
textarea.value = '';
|
||||
resize_textarea();
|
||||
}
|
||||
});
|
||||
async function handle_message(message) {
|
||||
add_message_block('user', message);
|
||||
await wait(300);
|
||||
const response_div = add_message_block('gpt');
|
||||
const ellipsis_p = add_text(response_div);
|
||||
const { cancel: cancel_typeout } = typeout('... ... ...', ellipsis_p, 150, false);
|
||||
const atLeastOneEllipsis = wait(525);
|
||||
const response = await gpt(message).catch(error => ({error}));
|
||||
await atLeastOneEllipsis;
|
||||
cancel_typeout();
|
||||
set_response(response_div, response);
|
||||
}
|
||||
function add_message_block(type, message) {
|
||||
const div = document.createElement('div');
|
||||
div.className = `message ${type}`;
|
||||
if (message) {
|
||||
add_text(div, message);
|
||||
}
|
||||
messages.appendChild(div);
|
||||
return div;
|
||||
}
|
||||
function add_text(node, text) {
|
||||
const p = document.createElement('p');
|
||||
if (text) {
|
||||
p.innerText = text;
|
||||
}
|
||||
node.appendChild(p);
|
||||
return p;
|
||||
}
|
||||
function add_error(node) {
|
||||
const p = document.createElement('p');
|
||||
p.className = 'error';
|
||||
node.appendChild(p);
|
||||
return p;
|
||||
}
|
||||
async function set_response(div, response) {
|
||||
if (typeof response === 'string') {
|
||||
typeout(response, div.firstChild);
|
||||
if (response.includes('poems')) {
|
||||
div.firstChild.addEventListener('click', prompt_helper_handler(`Show me the poems`));
|
||||
div.firstChild.classList += 'prompt-help';
|
||||
} else if (response.includes('“help”')) {
|
||||
div.firstChild.addEventListener('click', prompt_helper_handler(`Help me out`));
|
||||
div.firstChild.classList += 'prompt-help';
|
||||
}
|
||||
} else if (typeof response === 'object') {
|
||||
div.innerText = '';
|
||||
if (response.error) {
|
||||
const error_p = add_error(div);
|
||||
typeout(response.error, error_p);
|
||||
} else if (response.poem) {
|
||||
const poem_div = document.createElement('div');
|
||||
poem_div.className = 'poem';
|
||||
div.appendChild(poem_div);
|
||||
|
||||
const title = document.createElement('h3');
|
||||
poem_div.appendChild(title);
|
||||
await typeout(response.poem.title, title, 100).promise;
|
||||
|
||||
const byline = document.createElement('div');
|
||||
byline.className = 'byline';
|
||||
poem_div.appendChild(byline);
|
||||
await typeout('by '+response.poem.author, byline, 100).promise;
|
||||
|
||||
typeout_lines(response.poem.lines, poem_div);
|
||||
} else if (response.poem_list) {
|
||||
const title = document.createElement('h3');
|
||||
div.appendChild(title);
|
||||
await typeout('Available poems', title, 50).promise;
|
||||
|
||||
const list_ul = document.createElement('ul');
|
||||
list_ul.className = 'poem-list';
|
||||
div.appendChild(list_ul);
|
||||
|
||||
list_ul.append(...response.poem_list.map(({title, author}) => {
|
||||
const poem_li = document.createElement('li');
|
||||
poem_li.className = 'poem-list-entry prompt-help';
|
||||
poem_li.addEventListener('click', prompt_helper_handler(`Show me “${title}”`));
|
||||
wait(random(250))
|
||||
.then(() => typeout(`“${title}” by ${author}`, poem_li, 25));
|
||||
return poem_li;
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
function wait(delay) {
|
||||
return new Promise(resolve => window.setTimeout(() => resolve(), delay));
|
||||
}
|
||||
const NON_WS = /\S/;
|
||||
function typeout(message, node, interval = 10, speedup = true) {
|
||||
let cancel = false;
|
||||
const promise = new Promise(resolve => {
|
||||
function type_letter(index) {
|
||||
if (!cancel) {
|
||||
const substring = message.substring(0, index);
|
||||
if (node instanceof Text) {
|
||||
node.textContent = substring;
|
||||
} else {
|
||||
node.innerText = substring;
|
||||
}
|
||||
if (index + 1 <= message.length) {
|
||||
const extra_speed = speedup ? Math.min(Math.round(index / 128), 9) : 0;
|
||||
window.setTimeout(() => type_letter(index + 1 + extra_speed), interval);
|
||||
} else {
|
||||
window.setTimeout(resolve, interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
type_letter(message.search(NON_WS) + 1);
|
||||
});
|
||||
return { cancel: () => { node.innerText = NBSP; cancel = true; }, promise };
|
||||
}
|
||||
async function parsed_typeout(line, node, interval = 10, speedup = true) {
|
||||
const match = line.match(/(?:<sc>(.+?)<\/sc>)|(?:<i>(.+?)<\/i>)/);
|
||||
const append = child => {
|
||||
if (node instanceof Text) {
|
||||
node.parentElement.appendChild(child);
|
||||
} else {
|
||||
node.appendChild(child);
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
return typeout(line, node, interval, speedup).promise;
|
||||
} else {
|
||||
await typeout(line.substring(0, match.index), node, interval, speedup).promise;
|
||||
|
||||
if (match[1] !== undefined) {
|
||||
const smallCapSpan = document.createElement('span');
|
||||
smallCapSpan.className = 'small-caps';
|
||||
append(smallCapSpan);
|
||||
await typeout(match[1], smallCapSpan, interval, speedup).promise;
|
||||
} else if (match[2] !== undefined) {
|
||||
const italicSpan = document.createElement('i');
|
||||
append(italicSpan);
|
||||
await typeout(match[2], italicSpan, interval, speedup).promise;
|
||||
}
|
||||
|
||||
const afterText = document.createTextNode(' ');
|
||||
append(afterText);
|
||||
return parsed_typeout(line.substring(match.index + match[0].length), afterText, interval, speedup).promise;
|
||||
}
|
||||
}
|
||||
async function typeout_lines(lines, node, interval = 50, speedup = true) {
|
||||
const promises = [];
|
||||
for (const line of lines) {
|
||||
if (line === '') {
|
||||
await Promise.all(promises);
|
||||
const line_div = document.createElement('div');
|
||||
line_div.className = 'poem-line';
|
||||
node.append(line_div);
|
||||
} else {
|
||||
const line_div = document.createElement('div');
|
||||
line_div.className = 'poem-line';
|
||||
line_div.innerText = NBSP;
|
||||
node.append(line_div);
|
||||
promises.push(
|
||||
wait(random(500))
|
||||
.then(() => parsed_typeout(line, line_div, interval, speedup))
|
||||
);
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
function random(max) {
|
||||
return Math.floor((Math.random() * max));
|
||||
}
|
||||
|
||||
function prompt_helper_handler(message) {
|
||||
return () => {
|
||||
textarea.value = message;
|
||||
resize_textarea();
|
||||
textarea.focus();
|
||||
};
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
const patterns = {
|
||||
poems: /\bpoems?\b/,
|
||||
thanks: /\bthank(s| you)?\b/,
|
||||
hello: /\b((hi|hello|hey)|what((’|')s)? ?up)\b/,
|
||||
help: /\b(help(me)?|how do)\b/,
|
||||
wow: /\bwow\b/,
|
||||
};
|
||||
|
||||
let poem_list = [];
|
||||
const poem_list_loaded = fetch('./js/poem-list.json')
|
||||
.then(response => response.json())
|
||||
.then(list => poem_list = list);
|
||||
|
||||
let poem_patterns = [];
|
||||
fetch('./js/pattern-overrides.json')
|
||||
.then(response => response.json())
|
||||
.then(async list => {
|
||||
poem_patterns = (await poem_list_loaded).map(
|
||||
({filename}) => {
|
||||
const result = list.find(([name]) => name === filename);
|
||||
return [filename, new RegExp(`\\b${result ? result[1] : filename}\\b`)];
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
let fail_count = 0;
|
||||
|
||||
async function gpt(message) {
|
||||
const response = await chat(message);
|
||||
if (!response) {
|
||||
fail_count += 1;
|
||||
if (fail_count >= 3) {
|
||||
return 'Try typing “help”';
|
||||
}
|
||||
return `You said: “${message}”`;
|
||||
} else {
|
||||
fail_count = 0;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
function chat(message) {
|
||||
message = message.toLowerCase();
|
||||
if (patterns.poems.test(message)) {
|
||||
return { poem_list };
|
||||
} else if (patterns.thanks.test(message)) {
|
||||
return ['You’re welcome 😅', 'Sure thing!'].sample();
|
||||
} else if (patterns.hello.test(message)) {
|
||||
return ['Howdy! I can show you some poems.', 'Greetings! I can show you poems.'].sample();
|
||||
} else if (patterns.help.test(message)) {
|
||||
return 'Type “poems” to see a list of poems I can show you.';
|
||||
} else if (patterns.wow.test(message)) {
|
||||
return 'Yeah, wow! 🤯';
|
||||
} else {
|
||||
for (const [name, pattern] of poem_patterns) {
|
||||
if (pattern.test(message)) {
|
||||
return poem(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function poem(filename) {
|
||||
return fetch('./poems/' + filename)
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.text();
|
||||
} else {
|
||||
throw null;
|
||||
}
|
||||
})
|
||||
.then(file => file
|
||||
.replaceAll('\r\n', '\n')
|
||||
.replaceAll('\t', TAB)
|
||||
.split('\n')
|
||||
)
|
||||
.then(lines => {
|
||||
const title = lines[0];
|
||||
const author = lines[1];
|
||||
|
||||
const poem = {
|
||||
title,
|
||||
author,
|
||||
lines: lines.slice(2)
|
||||
};
|
||||
|
||||
return { poem };
|
||||
})
|
||||
.catch(() => ({ error: 'Failed to get poem.' }));
|
||||
}
|
||||
|
||||
const TAB = '\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0';
|
||||
|
||||
Array.prototype.sample = function(){
|
||||
return this[~~(Math.random() * this.length)];
|
||||
}
|
||||
|
||||
export default gpt;
|
|
@ -0,0 +1,4 @@
|
|||
[
|
||||
["fears", "(fears|cease)"],
|
||||
["snowy", "(snowy|stopping)"]
|
||||
]
|
|
@ -0,0 +1,32 @@
|
|||
[
|
||||
{
|
||||
"title": "Ozymandias",
|
||||
"author": "Horace Smith",
|
||||
"filename": "ozymandias"
|
||||
},
|
||||
{
|
||||
"title": "Sympathy",
|
||||
"author": "Paul Laurence Dunbar",
|
||||
"filename": "sympathy"
|
||||
},
|
||||
{
|
||||
"title": "Caged Bird",
|
||||
"author": "Maya Angelou",
|
||||
"filename": "caged bird"
|
||||
},
|
||||
{
|
||||
"title": "When I have Fears That I May Cease to Be",
|
||||
"author": "John Keats",
|
||||
"filename": "fears"
|
||||
},
|
||||
{
|
||||
"title": "Stopping by Woods on a Snowy Evening",
|
||||
"author": "Robert Frost",
|
||||
"filename": "snowy"
|
||||
},
|
||||
{
|
||||
"title": "After Apple-Picking",
|
||||
"author": "Robert Frost",
|
||||
"filename": "apple-picking"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,44 @@
|
|||
After Apple-Picking
|
||||
Robert Frost
|
||||
My long two-pointed ladder's sticking through a tree
|
||||
Toward heaven still,
|
||||
And there's a barrel that I didn't fill
|
||||
Beside it, and there may be two or three
|
||||
Apples I didn't pick upon some bough.
|
||||
But I am done with apple-picking now.
|
||||
Essence of winter sleep is on the night,
|
||||
The scent of apples: I am drowsing off.
|
||||
I cannot rub the strangeness from my sight
|
||||
I got from looking through a pane of glass
|
||||
I skimmed this morning from the drinking trough
|
||||
And held against the world of hoary grass.
|
||||
It melted, and I let it fall and break.
|
||||
But I was well
|
||||
Upon my way to sleep before it fell,
|
||||
And I could tell
|
||||
What form my dreaming was about to take.
|
||||
Magnified apples appear and disappear,
|
||||
Stem end and blossom end,
|
||||
And every fleck of russet showing clear.
|
||||
My instep arch not only keeps the ache,
|
||||
It keeps the pressure of a ladder-round.
|
||||
I feel the ladder sway as the boughs bend.
|
||||
And I keep hearing from the cellar bin
|
||||
The rumbling sound
|
||||
Of load on load of apples coming in.
|
||||
For I have had too much
|
||||
Of apple-picking: I am overtired
|
||||
Of the great harvest I myself desired.
|
||||
There were ten thousand thousand fruit to touch,
|
||||
Cherish in hand, lift down, and not let fall.
|
||||
For all
|
||||
That struck the earth,
|
||||
No matter if not bruised or spiked with stubble,
|
||||
Went surely to the cider-apple heap
|
||||
As of no worth.
|
||||
One can see what will trouble
|
||||
This sleep of mine, whatever sleep it is.
|
||||
Were he not gone,
|
||||
The woodchuck could say whether it's like his
|
||||
Long sleep, as I describe its coming on,
|
||||
Or just some human sleep.
|
|
@ -0,0 +1,45 @@
|
|||
Caged Bird
|
||||
Maya Angelou
|
||||
A free bird leaps
|
||||
on the back of the wind
|
||||
and floats downstream
|
||||
till the current ends
|
||||
and dips his wing
|
||||
in the orange sun rays
|
||||
and dares to claim the sky.
|
||||
|
||||
But a bird that stalks
|
||||
down his narrow cage
|
||||
can seldom see through
|
||||
his bars of rage
|
||||
his wings are clipped and
|
||||
his feet are tied
|
||||
so he opens his throat to sing.
|
||||
|
||||
The caged bird sings
|
||||
with a fearful trill
|
||||
of things unknown
|
||||
but longed for still
|
||||
and his tune is heard
|
||||
on the distant hill
|
||||
for the caged bird
|
||||
sings of freedom.
|
||||
|
||||
The free bird thinks of another breeze
|
||||
and the trade winds soft through the sighing trees
|
||||
and the fat worms waiting on a dawn bright lawn
|
||||
and he names the sky his own.
|
||||
|
||||
But a caged bird stands on the grave of dreams
|
||||
his shadow shouts on a nightmare scream
|
||||
his wings are clipped and his feet are tied
|
||||
so he opens his throat to sing.
|
||||
|
||||
The caged bird sings
|
||||
with a fearful trill
|
||||
of things unknown
|
||||
but longed for still
|
||||
and his tune is heard
|
||||
on the distant hill
|
||||
for the caged bird
|
||||
sings of freedom.
|
|
@ -0,0 +1,16 @@
|
|||
When I have Fears That I May Cease to Be
|
||||
John Keats
|
||||
When I have fears that I may cease to be
|
||||
Before my pen has gleaned my teeming brain,
|
||||
Before high-pilèd books, in charactery,
|
||||
Hold like rich garners the full ripened grain;
|
||||
When I behold, upon the night’s starred face,
|
||||
Huge cloudy symbols of a high romance,
|
||||
And think that I may never live to trace
|
||||
Their shadows with the magic hand of chance;
|
||||
And when I feel, fair creature of an hour,
|
||||
That I shall never look upon thee more,
|
||||
Never have relish in the faery power
|
||||
Of unreflecting love—then on the shore
|
||||
Of the wide world I stand alone, and think
|
||||
Till love and fame to nothingness do sink.
|
|
@ -0,0 +1,17 @@
|
|||
Ozymandias
|
||||
Horace Smith
|
||||
In Egypt’s sandy silence, all alone,
|
||||
Stands a gigantic Leg, which far off throws
|
||||
The only shadow that the Desert knows:—
|
||||
“I am great <sc>Ozymandias</sc>,” saith the stone,
|
||||
“The King of Kings; this mighty City shows
|
||||
The wonders of my hand.”— The City’s gone,—
|
||||
Naught but the Leg remaining to disclose
|
||||
The site of this forgotten Babylon.
|
||||
|
||||
We wonder — and some Hunter may express
|
||||
Wonder like ours, when thro’ the wilderness
|
||||
Where London stood, holding the Wolf in chace,
|
||||
He meets some fragment huge, and stops to guess
|
||||
What powerful but unrecorded race
|
||||
Once dwelt in that annihilated place.
|
|
@ -0,0 +1,21 @@
|
|||
Stopping by Woods on a Snowy Evening
|
||||
Robert Frost
|
||||
Whose woods these are I think I know.
|
||||
His house is in the village though;
|
||||
He will not see me stopping here
|
||||
To watch his woods fill up with snow.
|
||||
|
||||
My little horse must think it queer
|
||||
To stop without a farmhouse near
|
||||
Between the woods and frozen lake
|
||||
The darkest evening of the year.
|
||||
|
||||
He gives his harness bells a shake
|
||||
To ask if there is some mistake.
|
||||
The only other sound’s the sweep
|
||||
Of easy wind and downy flake.
|
||||
|
||||
The woods are lovely, dark and deep,
|
||||
But I have promises to keep,
|
||||
And miles to go before I sleep,
|
||||
And miles to go before I sleep.
|
|
@ -0,0 +1,25 @@
|
|||
Sympathy
|
||||
Paul Laurence Dunbar
|
||||
I know what the caged bird feels, alas!
|
||||
When the sun is bright on the upland slopes;
|
||||
When the wind stirs soft through the springing grass,
|
||||
And the river flows like a stream of glass;
|
||||
When the first bird sings and the first bud opes,
|
||||
And the faint perfume from its chalice steals—
|
||||
I know what the caged bird feels!
|
||||
|
||||
I know why the caged bird beats his wing
|
||||
Till its blood is red on the cruel bars;
|
||||
For he must fly back to his perch and cling
|
||||
When he fain would be on the bough a-swing;
|
||||
And a pain still throbs in the old, old scars
|
||||
And they pulse again with a keener sting—
|
||||
I know why he beats his wing!
|
||||
|
||||
I know why the caged bird sings, ah me,
|
||||
When his wing is bruised and his bosom sore,—
|
||||
When he beats his bars and he would be free;
|
||||
It is not a carol of joy or glee,
|
||||
But a prayer that he sends from his heart’s deep core,
|
||||
But a plea, that upward to Heaven he flings—
|
||||
I know why the caged bird sings!
|
Loading…
Reference in New Issue