add chat gpt

This commit is contained in:
gome 2023-03-26 22:27:15 -05:00
parent bf77ae3050
commit 269e3b0907
17 changed files with 793 additions and 4 deletions

76
css/chat-gpt.css Normal file
View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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>

54
library/chat-gpt.html Normal file
View File

@ -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>&mdash;<a href='.'>library</a>
</nav>
</header>
<main>
<h1>Gome Poem Tome</h1>
<noscript>
<p>Sorry, this is just a Javascript thing&hellip; but heres a poem:</p>
<h3>Ozymandias</h3>
<div class='byline'>by Horace Smith</div>
<p>
In Egypts 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 Citys 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>

40
library/index.html Normal file
View File

@ -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>Its 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>

View File

@ -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>&mdash;<a href='.'>library</a>
</nav>
</header>
<main>
<h1>Bookmarks</h1>
<h1>Journal Index</h1>
<p>Just a stub for now.</p>
</main>
<footer>

237
library/js/chat-ui.js Normal file
View File

@ -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();
};
}

98
library/js/gpt.js Normal file
View File

@ -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 ['Youre 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;

View File

@ -0,0 +1,4 @@
[
["fears", "(fears|cease)"],
["snowy", "(snowy|stopping)"]
]

32
library/js/poem-list.json Normal file
View File

@ -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"
}
]

View File

@ -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.

45
library/poems/caged bird Normal file
View File

@ -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.

16
library/poems/fears Normal file
View File

@ -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 nights 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.

17
library/poems/ozymandias Normal file
View File

@ -0,0 +1,17 @@
Ozymandias
Horace Smith
In Egypts 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 Citys 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.

21
library/poems/snowy Normal file
View File

@ -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 sounds 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.

25
library/poems/sympathy Normal file
View File

@ -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 hearts deep core,
But a plea, that upward to Heaven he flings—
I know why the caged bird sings!