├── .gitignore ├── README.md ├── chat.css ├── chat.js ├── cloud-proxy ├── .dockerignore ├── Dockerfile ├── build.sh ├── deploy.sh ├── main.py ├── requirements.txt └── update_token.sh ├── commits.md ├── index.html ├── prompt.txt ├── screenshot.gif └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | .BC.* 2 | *~ 3 | OLD* 4 | .svn 5 | tmp* 6 | *.pyc 7 | .DS_Store 8 | .venv 9 | .env 10 | token.js 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easy Chat 2 | 3 | [Easy Chat](https://paul-gauthier.github.io/easy-chat/) 4 | is a user interface for ChatGPT, designed specifically for young readers. 5 | Almost all the code for Easy Chat was written by ChatGPT. 6 | 7 | ## Features 8 | 9 | - Typography, layout and text spacing which is helpful for young, early or struggling readers. 10 | - Large, easy-to-read text in the Open Dyslexic font. 11 | - No incremental "live typing" of the ChatGPT response, which is distracting when trying to read. Responses display when they are fully loaded. 12 | - Text-to-speech of individual words by clicking on any word. 13 | - Text-to-speech of entire passages with Immersive Reading (highlights each word as it is read aloud at a moderate reading speed). Click on the speaker icons. 14 | - A system prompt that tells ChatGPT it is speaking to a child and to use common words, short sentences and short paragraphs. 15 | 16 | ![Screenshot of Easy Chat](screenshot.gif) 17 | 18 | ## Usage 19 | 20 | Try it out here: [Easy Chat](https://paul-gauthier.github.io/easy-chat/) 21 | 22 | ## Created by ChatGPT 23 | 24 | Almost all the code in this repository was written by ChatGPT, using what I think is a novel workflow. 25 | 26 | I started by asking it to create the html for a simple chat app, with embedded css and js. After that, I just asked for changes, bug fixes, new features and improvements in plain English. 27 | 28 | For each change, I passed ChaptGPT the change request and a copy of the **entire codebase**. 29 | It returned a new copy of the codebase, with the requested changes. 30 | ChatGPT figured out how to make the requested changes, what parts of the code needed to be modified and what modifications to make. 31 | I reviewed the diffs it generated, and either accepted or rejected the proposed changes. If the changes weren't acceptable, I discarded them and improved my request to be more specific or explicit -- and tried again. 32 | 33 | In essence, I worked with ChatGPT like it was a junior web developer. 34 | 35 | ### All code in, all code out 36 | 37 | I have starting thinking about this as an "all code in, all code out" pattern: 38 | 39 | - Send all the (relevant) code to GPT along with a change request 40 | - Have it reply with all the code, modified to include the requested change 41 | - Automatically replace the original files with the GPT edited versions 42 | - Use git diff, etc to review and either accept/reject the changes. 43 | 44 | GPT is **significantly** better at modifying code when following this "all code in, all code out" pattern. This pattern has downsides: you can quickly exhaust the context window, it's slow waiting for GPT to re-type your code (most of which it hasn't modified) and of course you're running up token costs. But the ability of GPT to understand and execute high level changes to the code is far superior with this approach. 45 | 46 | I have tried quite a large number of alternative workflows. Outside the "all code in/out" pattern, GPT gets confused, makes mistakes, implements the requested change in different ways in different sections of the code, or just plain fails. 47 | 48 | If you're asking for self contained modifications to a single function, that's all the code that needs to go in/out. 49 | On the other side of the spectrum, GPT built this entire webapp using this pattern by repeatedly feeding it all the html/css/js along with a series of feature requests. Many feature requests required coordinated changes across html/css/js. 50 | 51 | ### Example prompts 52 | 53 | Most of my prompts were basically feature requests, like you might file in a JIRA ticket. 54 | Here's an example prompt: 55 | 56 | > Use text-to-voice to speak the highlighted word. 57 | 58 | Which resulted in 59 | [these changes](commits.md#user-content-62e0862ce0cf1017082e30ec7fa4034cfaf80137) to the code: 60 | 61 | ``` 62 | diff --git a/chat.html b/chat.html 63 | index 4ed5668..7d3d563 100644 64 | --- a/chat.html 65 | +++ b/chat.html 66 | @@ -206,8 +206,17 @@ 67 | const highlightedWords = chatBox.querySelectorAll('.highlight'); 68 | highlightedWords.forEach(word => word.classList.remove('highlight')); 69 | event.target.classList.add('highlight'); 70 | + const textToSpeak = event.target.textContent; 71 | + const speech = new SpeechSynthesisUtterance(textToSpeak); 72 | + speechSynthesis.speak(speech); 73 | } 74 | }); 75 | + 76 | + /* New code for text-to-speech */ 77 | + const speak = (text) => { 78 | + const speech = new SpeechSynthesisUtterance(text); 79 | + speechSynthesis.speak(speech); 80 | + }; 81 | 82 | 83 | 84 | ``` 85 | 86 | Here is another prompt, this time describing a bug and asking for a bug fix: 87 | 88 | > The text-to-speech is saying "speaker outputting high volume" at the end of every message. 89 | > I think it is reading the unicode speaker icon aloud. 90 | > It should not. 91 | > Fix this bug. 92 | 93 | Which produced 94 | [this simple fix](commits.md#user-content-2e73c58dccc4336f53264dd6b9b5093cf88b0d20): 95 | 96 | ``` 97 | diff --git a/chat.html b/chat.html 98 | index b9f8ba3..8f6de60 100644 99 | --- a/chat.html 100 | +++ b/chat.html 101 | @@ -207,7 +207,7 @@ 102 | 103 | /* New code for word highlighting */ 104 | chatBox.addEventListener('click', (event) => { 105 | - if (event.target.tagName === 'SPAN') { 106 | + if (event.target.tagName === 'SPAN' && !event.target.classList.contains('speaker')) { 107 | const highlightedWords = chatBox.querySelectorAll('.highlight'); 108 | highlightedWords.forEach(word => word.classList.remove('highlight')); 109 | event.target.classList.add('highlight'); 110 | ``` 111 | 112 | One of the most impressive changes was when I asked it to take what was currently a non-functional chat UI and 113 | [wire it up to the OpenAI chat completions API](commits.md#user-content-61326c036fa7888e58231f4bcb4f13d0f889ea0c). 114 | I don't have a record of the exact prompt I used, but I basically said "wire it up like this" and pasted 115 | a dozen lines of the `curl` example from the [API reference docs](https://platform.openai.com/docs/api-reference/chat). 116 | 117 | Another shocking change occured when I asked ChatGPT to add a speaker button beside each chat message bubble. 118 | I had previously asked it to use text-to-speech to speak individual words when clicked. 119 | When I asked it to 120 | [add a speaker button on every chat bubble](commits.md#user-content-cbae63b904561671b9df467584b3687a61939355) 121 | , it wired them up to speak all the text in the bubble without me needing to ask. 122 | 123 | I am missing the actual text of many of the earliest prompts I used. 124 | I didn't start recording them until I was a couple of dozen commits into the process. 125 | When I started, I didn't think I was going to make much progress using this style of "coding". 126 | I am suprised at how much I was able to accomplish by treating ChatGPT as a junior web developer. 127 | 128 | Regardless, you can review this 129 | [curated commit history of this repo](commits.md) 130 | to see many of the prompts I used. 131 | Any commit that starts with "PROMPT" was coded by ChatGPT from that specific prompt. 132 | If the commit starts with "asked for ...", that also means ChatGPT did the coding, but I didn't record the exact prompt. 133 | 134 | ### Workflow 135 | 136 | My workflow for each change was dead simple: 137 | 138 | - I wrote my plain English change request in a file called `prompt.txt`. 139 | 140 | - I fed the prompt and the entire codebase to `gpt-3.5-turbo` via the [aichat](https://github.com/sigoden/aichat) tool. 141 | - `cat prompt.txt chat.html | aichat -r webdev > tmp.html && cp tmp.html chat.html` 142 | 143 | - Each time, ChatGPT returned a modified version of the codebase, implementing my requested changes. 144 | 145 | - I did a `git diff` to review what ChatGPT had changed and tried out the resulting system. 146 | - If it was good, `git commit -F prompt.txt` 147 | - If it wasn't right, I would use `git stash` to discard the changes, adjust the prompt and try again. 148 | 149 | I used the roles feature of [aichat](https://github.com/sigoden/aichat) to set up a `webdev` role with a system prompt like this: 150 | 151 | > I want you to act as a web development expert. 152 | > I want you to answer only with code. 153 | > Make the requested change to the provided code and output the changed code. 154 | > MAKE NO OTHER CHANGES! 155 | > Do not provide explanations! 156 | 157 | ### Limitations 158 | 159 | My workflow broke down when the size of the prompt and codebase exceeded the context window for the `gpt-3.5-turbo` model. 160 | I experimented with numerous other workflows to try and work around this limit, but with little consistent success. 161 | 162 | Ultimately I refactored the js and css into their own files, and began feeding ChatGPT excerpts from the code that were relevant to each change I needed. 163 | This wasn't always successful, as it often wanted to (re)write the code that I wasn't showing it. 164 | 165 | I look forward to GPT-4 API access, where I can take advantage of the 32k token context window. 166 | 167 | -------------------------------------------------------------------------------- /chat.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: 'Open-Dyslexic', sans-serif; 3 | font-size: 24px; 4 | } 5 | 6 | body { 7 | margin: 0; 8 | padding: 0; 9 | background-color: #f2f2f2; 10 | height: 100vh; 11 | overflow: hidden; 12 | } 13 | 14 | .top-nav { 15 | background-color: #4CAF50; 16 | color: #fff; 17 | display: flex; 18 | justify-content: space-between; 19 | padding: 10px 20px; 20 | position: fixed; /* Added */ 21 | top: 0; /* Added */ 22 | left: 0; /* Added */ 23 | right: 0; /* Added */ 24 | z-index: 1; /* Added */ 25 | } 26 | 27 | .top-nav a { 28 | color: #fff; 29 | text-decoration: none; 30 | } 31 | 32 | .nav-left { 33 | float: left; 34 | } 35 | 36 | .nav-right { 37 | float: right; 38 | } 39 | 40 | .nav-right a { 41 | margin: 0 10px; 42 | } 43 | 44 | .container { 45 | max-width: 800px; 46 | margin: 100px auto 0 auto; /* Changed */ 47 | padding: 20px; 48 | height: 100%; 49 | display: flex; 50 | flex-direction: column; 51 | } 52 | 53 | .chat-box { 54 | flex: 1; 55 | overflow-y: scroll; 56 | padding: 20px; 57 | background-color: #fff; 58 | border-radius: 10px; 59 | margin-bottom: 20px; 60 | } 61 | 62 | .chat-box p { 63 | margin: 20px 0; 64 | padding: 10px; 65 | border-radius: 10px; 66 | line-height: 1.5; 67 | opacity: 0; 68 | animation: reveal 0.5s ease forwards; 69 | } 70 | 71 | .chat-box .assistant { 72 | background-color: #e6e6e6; 73 | text-align: left; 74 | } 75 | 76 | .chat-box .user { 77 | background-color: #d9edf7; 78 | text-align: right; 79 | } 80 | 81 | .chat-box span { 82 | cursor: pointer; 83 | } 84 | 85 | .chat-box .fa { 86 | display: inline-block; 87 | margin-right: 10px; 88 | font-size: 20px; 89 | cursor: pointer; 90 | } 91 | 92 | .chat-box .fa-volume-up { 93 | color: #4CAF50; 94 | } 95 | 96 | .input-box { 97 | display: flex; 98 | align-items: center; 99 | position: sticky; 100 | bottom: 0; 101 | background-color: #fff; 102 | padding: 20px; 103 | border-radius: 10px; 104 | } 105 | 106 | .input-box textarea { 107 | flex: 1; 108 | padding: 10px; 109 | border-radius: 5px; 110 | border: none; 111 | outline: none; 112 | resize: vertical; 113 | min-height: 60px; 114 | transform: translateZ(0); 115 | } 116 | 117 | .input-box button { 118 | background-color: #4CAF50; 119 | color: #fff; 120 | border: none; 121 | padding: 10px 20px; 122 | border-radius: 5px; 123 | cursor: pointer; 124 | margin-left: 10px; 125 | } 126 | 127 | .input-box+div { 128 | height: 20px; 129 | } 130 | 131 | @media only screen and (max-width: 600px) { 132 | * { 133 | font-size: 14px; 134 | } 135 | } 136 | 137 | .spinner { 138 | border: 16px solid #f3f3f3; 139 | border-top: 16px solid #4CAF50; 140 | border-radius: 50%; 141 | width: 30px; 142 | height: 30px; 143 | animation: spin 2s linear infinite; 144 | margin-left: 10px; 145 | display: none; 146 | } 147 | 148 | @keyframes spin { 149 | 0% { 150 | transform: rotate(0deg); 151 | } 152 | 153 | 100% { 154 | transform: rotate(360deg); 155 | } 156 | } 157 | 158 | @keyframes reveal { 159 | 0% { 160 | opacity: 0; 161 | transform: translateY(10px); 162 | } 163 | 164 | 100% { 165 | opacity: 1; 166 | transform: translateY(0); 167 | } 168 | } 169 | 170 | .highlight { 171 | background-color: yellow; 172 | } 173 | -------------------------------------------------------------------------------- /chat.js: -------------------------------------------------------------------------------- 1 | const markdownToHtmlWithWordSpans = (markdown) => { 2 | const html = marked.parse(markdown); 3 | const parser = new DOMParser(); 4 | const doc = parser.parseFromString(html, 'text/html'); 5 | const elements = doc.querySelectorAll('p, li'); 6 | elements.forEach(element => { 7 | const words = element.textContent.split(' '); 8 | element.innerHTML = words.map(word => `${word}`).join(' '); 9 | }); 10 | return doc.body.innerHTML; 11 | }; 12 | 13 | const chatBox = document.querySelector('.chat-box'); 14 | const inputBox = document.querySelector('.input-box textarea'); 15 | const sendButton = document.querySelector('.input-box button'); 16 | const spinner = document.querySelector('.spinner'); 17 | 18 | const sendMessage = () => { 19 | if (inputBox.value !== '') { 20 | spinner.style.display = 'inline-block'; 21 | sendButton.style.display = 'none'; 22 | const message = document.createElement('p'); 23 | message.innerHTML = inputBox.value.split(' ').map(word => `${word}`).join(' '); 24 | message.classList.add('user'); 25 | chatBox.insertBefore(message, document.getElementById('bottom')); 26 | addUserSpeakerIcon(message); 27 | inputBox.value = ''; 28 | document.getElementById('bottom').scrollIntoView(); 29 | 30 | const systemPrompt = "You are a helpful assistant. You are speaking to a child. Use common words. Use short sentences. Use short paragraphs. Start by asking if they want you to tell a story." 31 | const messages = []; 32 | messages.push({ role: 'system', content: systemPrompt }); 33 | const chatMessages = chatBox.querySelectorAll('.user, .assistant, .assistant'); 34 | for (let i = 0; i < chatMessages.length; i++) { 35 | const role = chatMessages[i].classList.contains('user') ? 'user' : 'assistant'; 36 | const content = chatMessages[i].textContent; 37 | messages.push({ role, content }); 38 | } 39 | 40 | const requestOptions = { 41 | method: 'POST', 42 | headers: { 43 | 'Content-Type': 'application/json', 44 | }, 45 | body: JSON.stringify({ 46 | model: 'gpt-3.5-turbo', 47 | messages: messages 48 | }) 49 | }; 50 | 51 | fetch('https://cloud-proxy-35h462xo6a-uw.a.run.app/v1/chat/completions', requestOptions) 52 | .then(response => response.json()) 53 | .then(data => { 54 | const assistantMessage = document.createElement('p'); 55 | assistantMessage.innerHTML = markdownToHtmlWithWordSpans(data.choices[0].message.content) 56 | assistantMessage.classList.add('assistant'); 57 | chatBox.insertBefore(assistantMessage, document.getElementById('bottom')); 58 | document.getElementById('bottom').scrollIntoView(); 59 | inputBox.blur(); 60 | spinner.style.display = 'none'; 61 | sendButton.style.display = 'inline-block'; 62 | inputBox.focus(); 63 | addUserSpeakerIcon(assistantMessage); 64 | }) 65 | .catch(error => console.log(error)); 66 | } 67 | }; 68 | 69 | sendButton.addEventListener('click', sendMessage); 70 | 71 | inputBox.addEventListener('keydown', (event) => { 72 | if (event.key === 'Enter' && inputBox.value !== '') { 73 | sendMessage(); 74 | event.preventDefault(); 75 | } 76 | }); 77 | 78 | chatBox.addEventListener('click', (event) => { 79 | if (event.target.tagName === 'SPAN' && !event.target.classList.contains('fa')) { 80 | const highlightedWords = chatBox.querySelectorAll('.highlight'); 81 | highlightedWords.forEach(word => word.classList.remove('highlight')); 82 | event.target.classList.add('highlight'); 83 | const textToSpeak = event.target.textContent; 84 | const speech = new SpeechSynthesisUtterance(textToSpeak); 85 | speechSynthesis.speak(speech); 86 | } 87 | }); 88 | 89 | const highlight = (element) => { 90 | element.classList.add('highlight'); 91 | }; 92 | 93 | const removeHighlight = (element) => { 94 | element.classList.remove('highlight'); 95 | }; 96 | 97 | const speak = (element) => { 98 | if ('speechSynthesis' in window) { 99 | const textElements = element.querySelectorAll('span'); 100 | const textContent = Array.from(textElements).map(el => el.textContent).join(' '); 101 | 102 | const utterance = new SpeechSynthesisUtterance(textContent); 103 | utterance.rate = 0.75; 104 | utterance.onboundary = (event) => { 105 | if (event.name === 'word') { 106 | const charIndex = event.charIndex; 107 | const wordIndex = getWordIndexByCharIndex(textElements, charIndex); 108 | if (wordIndex !== -1) { 109 | removeHighlights(textElements); 110 | highlight(textElements[wordIndex]); 111 | } 112 | } 113 | }; 114 | utterance.onend = () => { 115 | removeHighlights(textElements); 116 | }; 117 | 118 | window.speechSynthesis.speak(utterance); 119 | } else { 120 | alert('Speech Synthesis not supported in your browser'); 121 | } 122 | }; 123 | 124 | const removeHighlights = (elements) => { 125 | elements.forEach(removeHighlight); 126 | }; 127 | 128 | const getWordIndexByCharIndex = (textElements, charIndex) => { 129 | let currentCharIndex = 0; 130 | for (let i = 0; i < textElements.length; i++) { 131 | currentCharIndex += textElements[i].textContent.length + 1; // Add 1 for the space between words 132 | if (currentCharIndex > charIndex) { 133 | return i; 134 | } 135 | } 136 | return -1; 137 | }; 138 | 139 | const addUserSpeakerIcon = (message) => { 140 | const speakerIcon = document.createElement('span'); 141 | speakerIcon.classList.add('fa', 'fa-volume-up'); 142 | speakerIcon.onclick = () => speak(message); 143 | message.insertBefore(speakerIcon, message.firstChild); 144 | }; -------------------------------------------------------------------------------- /cloud-proxy/.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | *.pyo 4 | *.pyd 5 | .env 6 | *.log 7 | -------------------------------------------------------------------------------- /cloud-proxy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-slim 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt requirements.txt 6 | RUN pip install -r requirements.txt 7 | 8 | COPY . . 9 | 10 | CMD [ "python", "main.py" ] 11 | -------------------------------------------------------------------------------- /cloud-proxy/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # exit when any command fails 4 | set -e 5 | 6 | docker build -t gcr.io/${PROJECT_ID}/cloud-proxy:latest . 7 | docker push gcr.io/${PROJECT_ID}/cloud-proxy:latest 8 | -------------------------------------------------------------------------------- /cloud-proxy/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # exit when any command fails 4 | set -e 5 | 6 | gcloud run deploy cloud-proxy \ 7 | --image gcr.io/${PROJECT_ID}/cloud-proxy:latest \ 8 | --platform managed \ 9 | --region us-west1 \ 10 | --allow-unauthenticated \ 11 | --set-env-vars SECRET_PROJECT_ID=${PROJECT_ID},SECRET_NAME=openai-api-key,SECRET_VERSION=latest 12 | -------------------------------------------------------------------------------- /cloud-proxy/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | import os 4 | import requests 5 | from flask import Flask, request, make_response, Response 6 | from werkzeug.datastructures import Headers 7 | 8 | from google.cloud import secretmanager 9 | secret_project_id = os.environ['SECRET_PROJECT_ID'] 10 | secret_name = os.environ['SECRET_NAME'] 11 | secret_version = os.environ.get('SECRET_VERSION', 'latest') 12 | 13 | def get_bearer_token(): 14 | client = secretmanager.SecretManagerServiceClient() 15 | secret_version_path = client.secret_version_path(secret_project_id, secret_name, secret_version) 16 | response = client.access_secret_version(request=secretmanager.AccessSecretVersionRequest(name=secret_version_path)) 17 | 18 | return response.payload.data.decode('UTF-8') 19 | 20 | app = Flask(__name__) 21 | 22 | @app.route('/', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS']) 23 | @app.route('/', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS']) 24 | def proxy_function(path): 25 | if request.method == 'OPTIONS': 26 | return handle_cors_preflight_request() 27 | 28 | host = 'api.openai.com' 29 | base_url = 'https://' + host 30 | target_url = f'{base_url}/{path}' 31 | 32 | headers = dict(request.headers) 33 | headers['Authorization'] = f"Bearer {get_bearer_token()}" 34 | headers['Host'] = host 35 | 36 | try: 37 | response = requests.request( 38 | method=request.method, 39 | url=target_url, 40 | headers=headers, 41 | data=request.get_data() 42 | ) 43 | 44 | headers = {key: value for key, value in response.headers.items()} 45 | headers.pop('Transfer-Encoding', None) 46 | headers.pop('Content-Encoding', None) 47 | 48 | return Response(response.content, response.status_code, headers) 49 | 50 | except requests.exceptions.RequestException as e: 51 | return make_response(json.dumps({'error': str(e)}), 500) 52 | 53 | def handle_cors_preflight_request(): 54 | headers = { 55 | 'Access-Control-Allow-Origin': '*', 56 | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', 57 | 'Access-Control-Allow-Headers': 'Content-Type, Authorization, Host' 58 | } 59 | return Response('', 204, headers) 60 | 61 | def lambda_handler(event, context): 62 | return app(event, context) 63 | 64 | if __name__ == "__main__": 65 | app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 8080)), threaded=True) 66 | -------------------------------------------------------------------------------- /cloud-proxy/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | google-cloud-secret-manager 3 | requests 4 | -------------------------------------------------------------------------------- /cloud-proxy/update_token.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # exit when any command fails 4 | set -e 5 | 6 | gcloud config set project $PROJECT_ID 7 | echo -n $OPENAI_API_KEY | gcloud secrets versions add openai-api-key --data-file=- 8 | -------------------------------------------------------------------------------- /commits.md: -------------------------------------------------------------------------------- 1 | 2 | ### [aac1a743062cea7f5fa806da3de843c2ac531238](https://github.com/paul-gauthier/easy-chat/commit/aac1a743062cea7f5fa806da3de843c2ac531238) 3 | 4 | > asked for a simple html chat page with embedded css/js 5 | ``` 6 | 7 | diff --git a/chat.html b/chat.html 8 | new file mode 100644 9 | index 0000000..64d1f74 10 | --- /dev/null 11 | +++ b/chat.html 12 | @@ -0,0 +1,112 @@ 13 | + 14 | + 15 | + 16 | + Chat UI 17 | + 78 | + 79 | + 80 | +
81 | +
82 | +

Hi, how are you?

83 | +

I'm good, thanks. How about you?

84 | +

I'm doing well, thanks for asking.

85 | +

That's great to hear.

86 | +

What have you been up to lately?

87 | +

Not much, just working and hanging out with friends. How about you?

88 | +

Same here, just trying to stay busy.

89 | +

Anyway, I have to go now. Talk to you later!

90 | +
91 | +
92 | + 93 | + 94 | +
95 | +
96 | + 123 | + 124 | + 125 | 126 | ``` 127 | ### [37068c5d32504b2f4b6446f758bfc46172e50c0c](https://github.com/paul-gauthier/easy-chat/commit/37068c5d32504b2f4b6446f758bfc46172e50c0c) 128 | 129 | > asked for some more whitespace 130 | ``` 131 | 132 | diff --git a/chat.html b/chat.html 133 | index 64d1f74..a2e0659 100644 134 | --- a/chat.html 135 | +++ b/chat.html 136 | @@ -22,7 +22,7 @@ 137 | margin-bottom: 20px; 138 | } 139 | .chat-box p { 140 | - margin: 0; 141 | + margin: 10px 0; 142 | padding: 10px; 143 | border-radius: 10px; 144 | font-size: 16px; 145 | 146 | ``` 147 | ### [41a28041ca4ffc5ce6b7682fb86e5449b5f78d58](https://github.com/paul-gauthier/easy-chat/commit/41a28041ca4ffc5ce6b7682fb86e5449b5f78d58) 148 | 149 | > asked to move the send button to the right of the input box 150 | ``` 151 | 152 | diff --git a/chat.html b/chat.html 153 | index a2e0659..02f2ea6 100644 154 | --- a/chat.html 155 | +++ b/chat.html 156 | @@ -37,6 +37,8 @@ 157 | text-align: right; 158 | } 159 | .input-box { 160 | + display: flex; 161 | + align-items: center; 162 | position: sticky; 163 | bottom: 0; 164 | background-color: #fff; 165 | @@ -45,7 +47,7 @@ 166 | margin-bottom: 20px; 167 | } 168 | .input-box input[type="text"] { 169 | - width: 100%; 170 | + flex: 1; 171 | padding: 10px; 172 | border-radius: 5px; 173 | border: none; 174 | 175 | ``` 176 | ### [8e4d75fbfcc87869677189b4113d33cfd584fc02](https://github.com/paul-gauthier/easy-chat/commit/8e4d75fbfcc87869677189b4113d33cfd584fc02) 177 | 178 | > asked for Open Dyslexic font 179 | ``` 180 | 181 | diff --git a/chat.html b/chat.html 182 | index 02f2ea6..3ba5de4 100644 183 | --- a/chat.html 184 | +++ b/chat.html 185 | @@ -7,7 +7,9 @@ 186 | margin: 0; 187 | padding: 0; 188 | background-color: #f2f2f2; 189 | + font-family: 'Open Dyslexic', sans-serif; 190 | } 191 | + @import url('https://fonts.googleapis.com/css2?family=Open+Sans&display=swap'); 192 | .container { 193 | max-width: 800px; 194 | margin: 0 auto; 195 | 196 | ``` 197 | ### [9e4d958a34798f725c40adcb82d68b9dca9e5173](https://github.com/paul-gauthier/easy-chat/commit/9e4d958a34798f725c40adcb82d68b9dca9e5173) 198 | 199 | > asked for a bigger font size 200 | ``` 201 | 202 | diff --git a/chat.html b/chat.html 203 | index 3ba5de4..bfef0bf 100644 204 | --- a/chat.html 205 | +++ b/chat.html 206 | @@ -23,11 +23,11 @@ 207 | border-radius: 10px; 208 | margin-bottom: 20px; 209 | } 210 | - .chat-box p { 211 | + .chat-box p, .chat-box .user1, .chat-box .user2 { 212 | margin: 10px 0; 213 | padding: 10px; 214 | border-radius: 10px; 215 | - font-size: 16px; 216 | + font-size: 24px; 217 | line-height: 1.5; 218 | } 219 | .chat-box .user1 { 220 | 221 | ``` 222 | ### [7276cd799a5983adc28c66b1807620e820c87c01](https://github.com/paul-gauthier/easy-chat/commit/7276cd799a5983adc28c66b1807620e820c87c01) 223 | 224 | > asked for more whitespace around chat text 225 | ``` 226 | 227 | diff --git a/chat.html b/chat.html 228 | index bfef0bf..6549952 100644 229 | --- a/chat.html 230 | +++ b/chat.html 231 | @@ -15,16 +15,16 @@ 232 | margin: 0 auto; 233 | padding: 20px; 234 | } 235 | - .chat-box { 236 | + .chat-box { 237 | height: calc(100vh - 100px); 238 | overflow-y: scroll; 239 | padding: 20px; 240 | background-color: #fff; 241 | border-radius: 10px; 242 | - margin-bottom: 20px; 243 | + margin-bottom: 30px; 244 | } 245 | .chat-box p, .chat-box .user1, .chat-box .user2 { 246 | - margin: 10px 0; 247 | + margin: 20px 0; 248 | padding: 10px; 249 | border-radius: 10px; 250 | font-size: 24px; 251 | 252 | ``` 253 | ### [3a11dfaf8383472c448912877cebc9daae6802d7](https://github.com/paul-gauthier/easy-chat/commit/3a11dfaf8383472c448912877cebc9daae6802d7) 254 | 255 | > tweaking font 256 | ``` 257 | 258 | diff --git a/chat.html b/chat.html 259 | index 6549952..3802093 100644 260 | --- a/chat.html 261 | +++ b/chat.html 262 | @@ -7,9 +7,9 @@ 263 | margin: 0; 264 | padding: 0; 265 | background-color: #f2f2f2; 266 | - font-family: 'Open Dyslexic', sans-serif; 267 | + font-family: 'Dyslexie', sans-serif; 268 | } 269 | - @import url('https://fonts.googleapis.com/css2?family=Open+Sans&display=swap'); 270 | + @import url('https://fonts.googleapis.com/css2?family=Dyslexie&display=swap'); 271 | .container { 272 | max-width: 800px; 273 | margin: 0 auto; 274 | 275 | ``` 276 | ### [2353314992673372e50ec271410be60d7a54f6a4](https://github.com/paul-gauthier/easy-chat/commit/2353314992673372e50ec271410be60d7a54f6a4) 277 | 278 | > fixed Open-Dyslexic font 279 | ``` 280 | 281 | diff --git a/chat.html b/chat.html 282 | index 3802093..f74a11e 100644 283 | --- a/chat.html 284 | +++ b/chat.html 285 | @@ -7,9 +7,8 @@ 286 | margin: 0; 287 | padding: 0; 288 | background-color: #f2f2f2; 289 | - font-family: 'Dyslexie', sans-serif; 290 | + font-family: 'Open-Dyslexic', sans-serif; 291 | } 292 | - @import url('https://fonts.googleapis.com/css2?family=Dyslexie&display=swap'); 293 | .container { 294 | max-width: 800px; 295 | margin: 0 auto; 296 | @@ -67,6 +66,7 @@ 297 | margin-left: 10px; 298 | } 299 | 300 | + 301 | 302 | 303 |
304 | 305 | ``` 306 | ### [02c0ed88fec200ea03289cd96e3e5ed3a711f814](https://github.com/paul-gauthier/easy-chat/commit/02c0ed88fec200ea03289cd96e3e5ed3a711f814) 307 | 308 | > asked to change the labels from user1/user2 to machine/human 309 | ``` 310 | 311 | diff --git a/chat.html b/chat.html 312 | index f74a11e..6fd2ba3 100644 313 | --- a/chat.html 314 | +++ b/chat.html 315 | @@ -22,18 +22,18 @@ 316 | border-radius: 10px; 317 | margin-bottom: 30px; 318 | } 319 | - .chat-box p, .chat-box .user1, .chat-box .user2 { 320 | + .chat-box p, .chat-box .machine-user, .chat-box .human-user { 321 | margin: 20px 0; 322 | padding: 10px; 323 | border-radius: 10px; 324 | font-size: 24px; 325 | line-height: 1.5; 326 | } 327 | - .chat-box .user1 { 328 | + .chat-box .machine-user { 329 | background-color: #e6e6e6; 330 | text-align: left; 331 | } 332 | - .chat-box .user2 { 333 | + .chat-box .human-user { 334 | background-color: #d9edf7; 335 | text-align: right; 336 | } 337 | @@ -71,14 +71,14 @@ 338 | 339 |
340 |
341 | -

Hi, how are you?

342 | -

I'm good, thanks. How about you?

343 | -

I'm doing well, thanks for asking.

344 | -

That's great to hear.

345 | -

What have you been up to lately?

346 | -

Not much, just working and hanging out with friends. How about you?

347 | -

Same here, just trying to stay busy.

348 | -

Anyway, I have to go now. Talk to you later!

349 | +

Hi, how are you?

350 | +

I'm good, thanks. How about you?

351 | +

I'm doing well, thanks for asking.

352 | +

That's great to hear.

353 | +

What have you been up to lately?

354 | +

Not much, just working and hanging out with friends. How about you?

355 | +

Same here, just trying to stay busy.

356 | +

Anyway, I have to go now. Talk to you later!

357 |
358 |
359 | 360 | @@ -94,7 +94,7 @@ 361 | if (inputBox.value !== '') { 362 | const message = document.createElement('p'); 363 | message.textContent = inputBox.value; 364 | - message.classList.add('user2'); 365 | + message.classList.add('human-user'); 366 | chatBox.appendChild(message); 367 | inputBox.value = ''; 368 | chatBox.scrollTop = chatBox.scrollHeight; 369 | @@ -105,7 +105,7 @@ 370 | if (event.key === 'Enter' && inputBox.value !== '') { 371 | const message = document.createElement('p'); 372 | message.textContent = inputBox.value; 373 | - message.classList.add('user2'); 374 | + message.classList.add('human-user'); 375 | chatBox.appendChild(message); 376 | inputBox.value = ''; 377 | chatBox.scrollTop = chatBox.scrollHeight; 378 | 379 | ``` 380 | ### [1681b4fc47f43ce81bf9fa2f611d85149b2cf8d6](https://github.com/paul-gauthier/easy-chat/commit/1681b4fc47f43ce81bf9fa2f611d85149b2cf8d6) 381 | 382 | > asked for a bigger, multiline text area 383 | ``` 384 | 385 | diff --git a/chat.html b/chat.html 386 | index 6fd2ba3..b510dae 100644 387 | --- a/chat.html 388 | +++ b/chat.html 389 | @@ -47,13 +47,15 @@ 390 | border-radius: 10px; 391 | margin-bottom: 20px; 392 | } 393 | - .input-box input[type="text"] { 394 | + .input-box textarea { 395 | flex: 1; 396 | padding: 10px; 397 | border-radius: 5px; 398 | border: none; 399 | font-size: 16px; 400 | outline: none; 401 | + resize: vertical; 402 | + min-height: 60px; 403 | } 404 | .input-box button { 405 | background-color: #4CAF50; 406 | @@ -81,13 +83,13 @@ 407 |

Anyway, I have to go now. Talk to you later!

408 |
409 |
410 | - 411 | + 412 | 413 |
414 |
415 | 496 | 497 | ``` 498 | ### [8f6d8afbe3107a0d2b39dedb9299ccf9af095bb6](https://github.com/paul-gauthier/easy-chat/commit/8f6d8afbe3107a0d2b39dedb9299ccf9af095bb6) 499 | 500 | > asked to fix layout 501 | ``` 502 | 503 | diff --git a/chat.html b/chat.html 504 | index 723d296..dea735c 100644 505 | --- a/chat.html 506 | +++ b/chat.html 507 | @@ -9,14 +9,19 @@ 508 | padding: 0; 509 | background-color: #f2f2f2; 510 | font-family: 'Open-Dyslexic', sans-serif; 511 | + height: 100vh; 512 | + overflow: hidden; 513 | } 514 | .container { 515 | max-width: 800px; 516 | margin: 0 auto; 517 | padding: 20px; 518 | + height: 100%; 519 | + display: flex; 520 | + flex-direction: column; 521 | } 522 | .chat-box { 523 | - height: calc(100vh - 100px); 524 | + flex: 1; 525 | overflow-y: scroll; 526 | padding: 20px; 527 | background-color: #fff; 528 | 529 | ``` 530 | ### [7cd078580141ad85bbdf9c6d4771cc6cdc99b711](https://github.com/paul-gauthier/easy-chat/commit/7cd078580141ad85bbdf9c6d4771cc6cdc99b711) 531 | 532 | > asked to fix layout 533 | ``` 534 | 535 | diff --git a/chat.html b/chat.html 536 | index dea735c..142cd09 100644 537 | --- a/chat.html 538 | +++ b/chat.html 539 | @@ -73,6 +73,9 @@ 540 | cursor: pointer; 541 | margin-left: 10px; 542 | } 543 | + .input-box + div { 544 | + height: 20px; 545 | + } 546 | 547 | 548 | 549 | @@ -93,6 +96,7 @@ 550 | 551 | 552 |
553 | +
554 | 555 | 727 | 728 | ``` 729 | ### [9b39d74b1bca84dd1d61bb10ea856b878b04ac01](https://github.com/paul-gauthier/easy-chat/commit/9b39d74b1bca84dd1d61bb10ea856b878b04ac01) 730 | 731 | > asked to rename human-user to user 732 | ``` 733 | 734 | diff --git a/chat.html b/chat.html 735 | index 60d0bd3..24d5763 100644 736 | --- a/chat.html 737 | +++ b/chat.html 738 | @@ -31,17 +31,17 @@ 739 | border-radius: 10px; 740 | margin-bottom: 30px; 741 | } 742 | - .chat-box p, .chat-box .machine-user, .chat-box .human-user { 743 | + .chat-box p, .chat-box .assistant, .chat-box .user { 744 | margin: 20px 0; 745 | padding: 10px; 746 | border-radius: 10px; 747 | line-height: 1.5; 748 | } 749 | - .chat-box .machine-user { 750 | + .chat-box .assistant { 751 | background-color: #e6e6e6; 752 | text-align: left; 753 | } 754 | - .chat-box .human-user { 755 | + .chat-box .user { 756 | background-color: #d9edf7; 757 | text-align: right; 758 | } 759 | @@ -90,14 +90,14 @@ 760 | 761 |
762 |
763 | -

Hi, how are you?

764 | -

I'm good, thanks. How about you?

765 | -

I'm doing well, thanks for asking.

766 | -

That's great to hear.

767 | -

What have you been up to lately?

768 | -

Not much, just working and hanging out with friends. How about you?

769 | -

Same here, just trying to stay busy.

770 | -

Anyway, I have to go now. Talk to you later!

771 | +

Hi, how are you?

772 | +

I'm good, thanks. How about you?

773 | +

I'm doing well, thanks for asking.

774 | +

That's great to hear.

775 | +

What have you been up to lately?

776 | +

Not much, just working and hanging out with friends. How about you?

777 | +

Same here, just trying to stay busy.

778 | +

Anyway, I have to go now. Talk to you later!

779 |
780 |
781 |
782 | @@ -115,7 +115,7 @@ 783 | if (inputBox.value !== '') { 784 | const message = document.createElement('p'); 785 | message.textContent = inputBox.value; 786 | - message.classList.add('human-user'); 787 | + message.classList.add('user'); 788 | chatBox.appendChild(message); 789 | inputBox.value = ''; 790 | document.getElementById('bottom').scrollIntoView(); 791 | 792 | ``` 793 | ### [61326c036fa7888e58231f4bcb4f13d0f889ea0c](https://github.com/paul-gauthier/easy-chat/commit/61326c036fa7888e58231f4bcb4f13d0f889ea0c) 794 | 795 | > asked to make live api calls to openai chatgpt-turbo, with example calls from docs 796 | ``` 797 | 798 | diff --git a/chat.html b/chat.html 799 | index 24d5763..92ef4dd 100644 800 | --- a/chat.html 801 | +++ b/chat.html 802 | @@ -31,7 +31,7 @@ 803 | border-radius: 10px; 804 | margin-bottom: 30px; 805 | } 806 | - .chat-box p, .chat-box .assistant, .chat-box .user { 807 | + .chat-box p, .chat-box .assistant, .chat-box .user, .chat-box .machine-user { 808 | margin: 20px 0; 809 | padding: 10px; 810 | border-radius: 10px; 811 | @@ -45,6 +45,10 @@ 812 | background-color: #d9edf7; 813 | text-align: right; 814 | } 815 | + .chat-box .machine-user { 816 | + background-color: #f0f0f0; 817 | + text-align: left; 818 | + } 819 | .input-box { 820 | display: flex; 821 | align-items: center; 822 | @@ -119,6 +123,37 @@ 823 | chatBox.appendChild(message); 824 | inputBox.value = ''; 825 | document.getElementById('bottom').scrollIntoView(); 826 | + 827 | + const messages = []; 828 | + const chatMessages = chatBox.querySelectorAll('p'); 829 | + for (let i = 0; i < chatMessages.length; i++) { 830 | + const role = chatMessages[i].classList.contains('user') ? 'user' : 'assistant'; 831 | + const content = chatMessages[i].textContent; 832 | + messages.push({ role, content }); 833 | + } 834 | + 835 | + const requestOptions = { 836 | + method: 'POST', 837 | + headers: { 838 | + 'Content-Type': 'application/json', 839 | + 'Authorization': 'Bearer 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' 840 | + }, 841 | + body: JSON.stringify({ 842 | + model: 'gpt-3.5-turbo', 843 | + messages: messages 844 | + }) 845 | + }; 846 | + 847 | + fetch('https://api.openai.com/v1/chat/completions', requestOptions) 848 | + .then(response => response.json()) 849 | + .then(data => { 850 | + const machineUserMessage = document.createElement('p'); 851 | + machineUserMessage.textContent = data.choices[0].message.content; 852 | + machineUserMessage.classList.add('machine-user'); 853 | + chatBox.appendChild(machineUserMessage); 854 | + document.getElementById('bottom').scrollIntoView(); 855 | + }) 856 | + .catch(error => console.log(error)); 857 | } 858 | }; 859 | 860 | 861 | ``` 862 | ### [fd937e0b235b8235c7af61746d1c8002e4865686](https://github.com/paul-gauthier/easy-chat/commit/fd937e0b235b8235c7af61746d1c8002e4865686) 863 | 864 | > asked to add a progress spinner 865 | ``` 866 | 867 | diff --git a/chat.html b/chat.html 868 | index 92ef4dd..2c3a767 100644 869 | --- a/chat.html 870 | +++ b/chat.html 871 | @@ -88,6 +88,23 @@ 872 | font-size: 14px; 873 | } 874 | } 875 | + 876 | + /* New code for progress spinner */ 877 | + .spinner { 878 | + border: 16px solid #f3f3f3; 879 | + border-top: 16px solid #4CAF50; 880 | + border-radius: 50%; 881 | + width: 30px; 882 | + height: 30px; 883 | + animation: spin 2s linear infinite; 884 | + margin-left: 10px; 885 | + display: none; 886 | + } 887 | + 888 | + @keyframes spin { 889 | + 0% { transform: rotate(0deg); } 890 | + 100% { transform: rotate(360deg); } 891 | + } 892 | 893 | 894 | 895 | @@ -107,6 +124,7 @@ 896 |
897 | 898 | 899 | +
900 |
901 |
902 |
903 | @@ -114,9 +132,11 @@ 904 | const chatBox = document.querySelector('.chat-box'); 905 | const inputBox = document.querySelector('.input-box textarea'); 906 | const sendButton = document.querySelector('.input-box button'); 907 | + const spinner = document.querySelector('.spinner'); 908 | 909 | const sendMessage = () => { 910 | if (inputBox.value !== '') { 911 | + spinner.style.display = 'inline-block'; 912 | const message = document.createElement('p'); 913 | message.textContent = inputBox.value; 914 | message.classList.add('user'); 915 | @@ -152,6 +172,7 @@ 916 | machineUserMessage.classList.add('machine-user'); 917 | chatBox.appendChild(machineUserMessage); 918 | document.getElementById('bottom').scrollIntoView(); 919 | + spinner.style.display = 'none'; 920 | }) 921 | .catch(error => console.log(error)); 922 | } 923 | 924 | ``` 925 | ### [62401b126591a021b450e33cad49a38e1374417f](https://github.com/paul-gauthier/easy-chat/commit/62401b126591a021b450e33cad49a38e1374417f) 926 | 927 | > PROMPT Hide the SEND button while waiting for API response. 928 | ``` 929 | 930 | diff --git a/chat.html b/chat.html 931 | index 2c3a767..50434fc 100644 932 | --- a/chat.html 933 | +++ b/chat.html 934 | @@ -137,6 +137,7 @@ 935 | const sendMessage = () => { 936 | if (inputBox.value !== '') { 937 | spinner.style.display = 'inline-block'; 938 | + sendButton.style.display = 'none'; 939 | const message = document.createElement('p'); 940 | message.textContent = inputBox.value; 941 | message.classList.add('user'); 942 | @@ -173,6 +174,7 @@ 943 | chatBox.appendChild(machineUserMessage); 944 | document.getElementById('bottom').scrollIntoView(); 945 | spinner.style.display = 'none'; 946 | + sendButton.style.display = 'inline-block'; 947 | }) 948 | .catch(error => console.log(error)); 949 | } 950 | 951 | ``` 952 | ### [2a1c5b33f30e714920dfe787283335406374ddcd](https://github.com/paul-gauthier/easy-chat/commit/2a1c5b33f30e714920dfe787283335406374ddcd) 953 | 954 | > PROMPT Add an animation to reveal each new message as it is added to the conversation. 955 | ``` 956 | 957 | diff --git a/chat.html b/chat.html 958 | index 50434fc..ab439d0 100644 959 | --- a/chat.html 960 | +++ b/chat.html 961 | @@ -36,6 +36,8 @@ 962 | padding: 10px; 963 | border-radius: 10px; 964 | line-height: 1.5; 965 | + opacity: 0; 966 | + animation: reveal 0.5s ease forwards; 967 | } 968 | .chat-box .assistant { 969 | background-color: #e6e6e6; 970 | @@ -105,6 +107,12 @@ 971 | 0% { transform: rotate(0deg); } 972 | 100% { transform: rotate(360deg); } 973 | } 974 | + 975 | + /* New code for message reveal animation */ 976 | + @keyframes reveal { 977 | + 0% { opacity: 0; transform: translateY(10px); } 978 | + 100% { opacity: 1; transform: translateY(0); } 979 | + } 980 | 981 | 982 | 983 | 984 | ``` 985 | ### [ec5ad8daf2145afcde2b8804bb7a7c88b137deb3](https://github.com/paul-gauthier/easy-chat/commit/ec5ad8daf2145afcde2b8804bb7a7c88b137deb3) 986 | 987 | > PROMPT Start with keyboard focus on the input box. Put focus back there after sending each messages. 988 | ``` 989 | 990 | diff --git a/chat.html b/chat.html 991 | index 95c0ab8..96f909d 100644 992 | --- a/chat.html 993 | +++ b/chat.html 994 | @@ -125,7 +125,7 @@ 995 |
996 |
997 |
998 | - 999 | + 1000 | 1001 |
1002 |
1003 | @@ -178,6 +178,7 @@ 1004 | document.getElementById('bottom').scrollIntoView(); 1005 | spinner.style.display = 'none'; 1006 | sendButton.style.display = 'inline-block'; 1007 | + inputBox.focus(); 1008 | }) 1009 | .catch(error => console.log(error)); 1010 | } 1011 | 1012 | ``` 1013 | ### [f5bee3eea29680a9fc77ea3ce21f020217dc115b](https://github.com/paul-gauthier/easy-chat/commit/f5bee3eea29680a9fc77ea3ce21f020217dc115b) 1014 | 1015 | > PROMPT Put SPANs around every individual word in all the messages by default. This way they can be clicked on and styled independently. When the user clicks on any word in the chat history, highlight *just* the word that they clicked on. Do not highlight the whole message. Only require a SINGLE CLICK not a double click. 1016 | ``` 1017 | 1018 | diff --git a/chat.html b/chat.html 1019 | index 96f909d..4ed5668 100644 1020 | --- a/chat.html 1021 | +++ b/chat.html 1022 | @@ -51,6 +51,9 @@ 1023 | background-color: #f0f0f0; 1024 | text-align: left; 1025 | } 1026 | + .chat-box span { 1027 | + cursor: pointer; 1028 | + } 1029 | .input-box { 1030 | display: flex; 1031 | align-items: center; 1032 | @@ -113,14 +116,19 @@ 1033 | 0% { opacity: 0; transform: translateY(10px); } 1034 | 100% { opacity: 1; transform: translateY(0); } 1035 | } 1036 | + 1037 | + /* New code for word highlighting */ 1038 | + .highlight { 1039 | + background-color: yellow; 1040 | + } 1041 | 1042 | 1043 | 1044 | 1045 |
1046 |
1047 | -

Hello!

1048 | -

How can I help you?

1049 | +

Hello!

1050 | +

How can I help you?

1051 |

1052 |
1053 |
1054 | @@ -142,7 +150,7 @@ 1055 | spinner.style.display = 'inline-block'; 1056 | sendButton.style.display = 'none'; 1057 | const message = document.createElement('p'); 1058 | - message.textContent = inputBox.value; 1059 | + message.innerHTML = inputBox.value.split(' ').map(word => `${word}`).join(' '); 1060 | message.classList.add('user'); 1061 | chatBox.appendChild(message); 1062 | inputBox.value = ''; 1063 | @@ -172,7 +180,7 @@ 1064 | .then(response => response.json()) 1065 | .then(data => { 1066 | const machineUserMessage = document.createElement('p'); 1067 | - machineUserMessage.textContent = data.choices[0].message.content; 1068 | + machineUserMessage.innerHTML = data.choices[0].message.content.split(' ').map(word => `${word}`).join(' '); 1069 | machineUserMessage.classList.add('machine-user'); 1070 | chatBox.appendChild(machineUserMessage); 1071 | document.getElementById('bottom').scrollIntoView(); 1072 | @@ -191,6 +199,15 @@ 1073 | sendMessage(); 1074 | } 1075 | }); 1076 | + 1077 | + /* New code for word highlighting */ 1078 | + chatBox.addEventListener('click', (event) => { 1079 | + if (event.target.tagName === 'SPAN') { 1080 | + const highlightedWords = chatBox.querySelectorAll('.highlight'); 1081 | + highlightedWords.forEach(word => word.classList.remove('highlight')); 1082 | + event.target.classList.add('highlight'); 1083 | + } 1084 | + }); 1085 | 1086 | 1087 | 1088 | 1089 | ``` 1090 | ### [62e0862ce0cf1017082e30ec7fa4034cfaf80137](https://github.com/paul-gauthier/easy-chat/commit/62e0862ce0cf1017082e30ec7fa4034cfaf80137) 1091 | 1092 | > PROMPT Use text-to-voice to speak the highlighted word. 1093 | ``` 1094 | 1095 | diff --git a/chat.html b/chat.html 1096 | index 4ed5668..7d3d563 100644 1097 | --- a/chat.html 1098 | +++ b/chat.html 1099 | @@ -206,8 +206,17 @@ 1100 | const highlightedWords = chatBox.querySelectorAll('.highlight'); 1101 | highlightedWords.forEach(word => word.classList.remove('highlight')); 1102 | event.target.classList.add('highlight'); 1103 | + const textToSpeak = event.target.textContent; 1104 | + const speech = new SpeechSynthesisUtterance(textToSpeak); 1105 | + speechSynthesis.speak(speech); 1106 | } 1107 | }); 1108 | + 1109 | + /* New code for text-to-speech */ 1110 | + const speak = (text) => { 1111 | + const speech = new SpeechSynthesisUtterance(text); 1112 | + speechSynthesis.speak(speech); 1113 | + }; 1114 | 1115 | 1116 | 1117 | 1118 | ``` 1119 | ### [353439bb77181912172d4b63962be5d8634e1262](https://github.com/paul-gauthier/easy-chat/commit/353439bb77181912172d4b63962be5d8634e1262) 1120 | 1121 | > PROMPT When new messages are added it is not scrolling to the bottom to show the newest message. They are being added AFTER the bottom div. New messages need to be added BEFORE the bottom div. Fix this bug. 1122 | ``` 1123 | 1124 | diff --git a/chat.html b/chat.html 1125 | index 7d3d563..ca07526 100644 1126 | --- a/chat.html 1127 | +++ b/chat.html 1128 | @@ -129,7 +129,6 @@ 1129 |
1130 |

Hello!

1131 |

How can I help you?

1132 | -

1133 |
1134 |
1135 |
1136 | @@ -152,7 +151,7 @@ 1137 | const message = document.createElement('p'); 1138 | message.innerHTML = inputBox.value.split(' ').map(word => `${word}`).join(' '); 1139 | message.classList.add('user'); 1140 | - chatBox.appendChild(message); 1141 | + chatBox.insertBefore(message, document.getElementById('bottom')); 1142 | inputBox.value = ''; 1143 | document.getElementById('bottom').scrollIntoView(); 1144 | 1145 | @@ -182,7 +181,7 @@ 1146 | const machineUserMessage = document.createElement('p'); 1147 | machineUserMessage.innerHTML = data.choices[0].message.content.split(' ').map(word => `${word}`).join(' '); 1148 | machineUserMessage.classList.add('machine-user'); 1149 | - chatBox.appendChild(machineUserMessage); 1150 | + chatBox.insertBefore(machineUserMessage, document.getElementById('bottom')); 1151 | document.getElementById('bottom').scrollIntoView(); 1152 | spinner.style.display = 'none'; 1153 | sendButton.style.display = 'inline-block'; 1154 | 1155 | ``` 1156 | ### [cbae63b904561671b9df467584b3687a61939355](https://github.com/paul-gauthier/easy-chat/commit/cbae63b904561671b9df467584b3687a61939355) 1157 | 1158 | > PROMPT Add a small speaker button beside each chat message. Including the initial few messages that are already on the page. Use a unicode speaker symbol for the icon. 1159 | ``` 1160 | 1161 | diff --git a/chat.html b/chat.html 1162 | index ca07526..b9f8ba3 100644 1163 | --- a/chat.html 1164 | +++ b/chat.html 1165 | @@ -54,6 +54,12 @@ 1166 | .chat-box span { 1167 | cursor: pointer; 1168 | } 1169 | + .chat-box .speaker { 1170 | + display: inline-block; 1171 | + margin-left: 10px; 1172 | + font-size: 20px; 1173 | + cursor: pointer; 1174 | + } 1175 | .input-box { 1176 | display: flex; 1177 | align-items: center; 1178 | @@ -127,8 +133,8 @@ 1179 | 1180 |
1181 |
1182 | -

Hello!

1183 | -

How can I help you?

1184 | +

Hello!🔊

1185 | +

How can I help you?🔊

1186 |
1187 |
1188 |
1189 | 1190 | ``` 1191 | ### [2e73c58dccc4336f53264dd6b9b5093cf88b0d20](https://github.com/paul-gauthier/easy-chat/commit/2e73c58dccc4336f53264dd6b9b5093cf88b0d20) 1192 | 1193 | > PROMPT The text-to-speech is saying "speaker outputting high volume" at the end of every message. I think it is reading the unicode speaker icon aloud. It should not. Fix this bug. 1194 | ``` 1195 | 1196 | diff --git a/chat.html b/chat.html 1197 | index b9f8ba3..8f6de60 100644 1198 | --- a/chat.html 1199 | +++ b/chat.html 1200 | @@ -207,7 +207,7 @@ 1201 | 1202 | /* New code for word highlighting */ 1203 | chatBox.addEventListener('click', (event) => { 1204 | - if (event.target.tagName === 'SPAN') { 1205 | + if (event.target.tagName === 'SPAN' && !event.target.classList.contains('speaker')) { 1206 | const highlightedWords = chatBox.querySelectorAll('.highlight'); 1207 | highlightedWords.forEach(word => word.classList.remove('highlight')); 1208 | event.target.classList.add('highlight'); 1209 | 1210 | ``` 1211 | ### [1037bd9206eb29eb522090bac3b838a279d125ad](https://github.com/paul-gauthier/easy-chat/commit/1037bd9206eb29eb522090bac3b838a279d125ad) 1212 | 1213 | > PROMPT Change all the speaker icons to use font-awesome. 1214 | ``` 1215 | 1216 | diff --git a/chat.html b/chat.html 1217 | index 8f6de60..fa09253 100644 1218 | --- a/chat.html 1219 | +++ b/chat.html 1220 | @@ -54,7 +54,7 @@ 1221 | .chat-box span { 1222 | cursor: pointer; 1223 | } 1224 | - .chat-box .speaker { 1225 | + .chat-box .fa { 1226 | display: inline-block; 1227 | margin-left: 10px; 1228 | font-size: 20px; 1229 | @@ -128,13 +128,13 @@ 1230 | background-color: yellow; 1231 | } 1232 | 1233 | - 1234 | + 1235 | 1236 | 1237 |
1238 |
1239 | -

Hello!🔊

1240 | -

How can I help you?🔊

1241 | +

Hello!

1242 | +

How can I help you?

1243 |
1244 |
1245 |
1246 | @@ -207,7 +207,7 @@ 1247 | 1248 | /* New code for word highlighting */ 1249 | chatBox.addEventListener('click', (event) => { 1250 | - if (event.target.tagName === 'SPAN' && !event.target.classList.contains('speaker')) { 1251 | + if (event.target.tagName === 'SPAN' && !event.target.classList.contains('fa')) { 1252 | const highlightedWords = chatBox.querySelectorAll('.highlight'); 1253 | highlightedWords.forEach(word => word.classList.remove('highlight')); 1254 | event.target.classList.add('highlight'); 1255 | 1256 | ``` 1257 | ### [0b9b4793cec7754f39e70f67240d262d2f4dca7e](https://github.com/paul-gauthier/easy-chat/commit/0b9b4793cec7754f39e70f67240d262d2f4dca7e) 1258 | 1259 | > PROMPT Add speaker icons with calls to speak() for new messages from user and assistant. 1260 | ``` 1261 | 1262 | diff --git a/chat.html b/chat.html 1263 | index fa09253..2cb7e08 100644 1264 | --- a/chat.html 1265 | +++ b/chat.html 1266 | @@ -60,6 +60,9 @@ 1267 | font-size: 20px; 1268 | cursor: pointer; 1269 | } 1270 | + .chat-box .fa-volume-up { 1271 | + color: #4CAF50; 1272 | + } 1273 | .input-box { 1274 | display: flex; 1275 | align-items: center; 1276 | @@ -192,6 +195,10 @@ 1277 | spinner.style.display = 'none'; 1278 | sendButton.style.display = 'inline-block'; 1279 | inputBox.focus(); 1280 | + const speakButton = document.createElement('span'); 1281 | + speakButton.classList.add('fa', 'fa-volume-up'); 1282 | + speakButton.onclick = () => speak(machineUserMessage.textContent); 1283 | + machineUserMessage.appendChild(speakButton); 1284 | }) 1285 | .catch(error => console.log(error)); 1286 | } 1287 | 1288 | ``` 1289 | ### [5a277084d4892227067922ea5127d5c0a625a02c](https://github.com/paul-gauthier/easy-chat/commit/5a277084d4892227067922ea5127d5c0a625a02c) 1290 | 1291 | > PROMPT When the user adds a new message, add a speaker icon which will read the user's message aloud. 1292 | ``` 1293 | 1294 | diff --git a/chat.html b/chat.html 1295 | index 2cb7e08..7e07c16 100644 1296 | --- a/chat.html 1297 | +++ b/chat.html 1298 | @@ -229,6 +229,20 @@ 1299 | const speech = new SpeechSynthesisUtterance(text); 1300 | speechSynthesis.speak(speech); 1301 | }; 1302 | + 1303 | + /* New code for adding speaker icon to user message */ 1304 | + const addUserSpeakerIcon = (message) => { 1305 | + const speakerIcon = document.createElement('span'); 1306 | + speakerIcon.classList.add('fa', 'fa-volume-up'); 1307 | + speakerIcon.onclick = () => speak(message.textContent); 1308 | + message.appendChild(speakerIcon); 1309 | + }; 1310 | + 1311 | + chatBox.addEventListener('DOMNodeInserted', (event) => { 1312 | + if (event.target.classList.contains('user')) { 1313 | + addUserSpeakerIcon(event.target); 1314 | + } 1315 | + }); 1316 | 1317 | 1318 | 1319 | 1320 | ``` 1321 | ### [7e70cf97297444ab845e1422b544778f56d08c15](https://github.com/paul-gauthier/easy-chat/commit/7e70cf97297444ab845e1422b544778f56d08c15) 1322 | 1323 | > PROMPT Call addUserSpeakerIcon() from inside sendMessage, right after the user message is inserted. Don't use chatBox.addEventListener to trigger it. 1324 | ``` 1325 | 1326 | diff --git a/chat.html b/chat.html 1327 | index 7e07c16..e49b3e8 100644 1328 | --- a/chat.html 1329 | +++ b/chat.html 1330 | @@ -161,6 +161,7 @@ 1331 | message.innerHTML = inputBox.value.split(' ').map(word => `${word}`).join(' '); 1332 | message.classList.add('user'); 1333 | chatBox.insertBefore(message, document.getElementById('bottom')); 1334 | + addUserSpeakerIcon(message); 1335 | inputBox.value = ''; 1336 | document.getElementById('bottom').scrollIntoView(); 1337 | 1338 | @@ -237,12 +238,8 @@ 1339 | speakerIcon.onclick = () => speak(message.textContent); 1340 | message.appendChild(speakerIcon); 1341 | }; 1342 | - 1343 | - chatBox.addEventListener('DOMNodeInserted', (event) => { 1344 | - if (event.target.classList.contains('user')) { 1345 | - addUserSpeakerIcon(event.target); 1346 | - } 1347 | - }); 1348 | - 1349 | + 1350 | + 1351 | 1352 | 1353 | + 1354 | 1355 | ``` 1356 | ### [32976cb5d17b258b7cbf0774b9e186f3eefff065](https://github.com/paul-gauthier/easy-chat/commit/32976cb5d17b258b7cbf0774b9e186f3eefff065) 1357 | 1358 | > asked to generate an immersive reading function in another file, imported 1359 | ``` 1360 | 1361 | diff --git a/chat.html b/chat.html 1362 | index e49b3e8..83df70c 100644 1363 | --- a/chat.html 1364 | +++ b/chat.html 1365 | @@ -136,8 +136,8 @@ 1366 | 1367 |
1368 |
1369 | -

Hello!

1370 | -

How can I help you?

1371 | +

Hello!

1372 | +

How can I help you?

1373 |
1374 |
1375 |
1376 | @@ -198,7 +198,7 @@ 1377 | inputBox.focus(); 1378 | const speakButton = document.createElement('span'); 1379 | speakButton.classList.add('fa', 'fa-volume-up'); 1380 | - speakButton.onclick = () => speak(machineUserMessage.textContent); 1381 | + speakButton.onclick = () => speak(machineUserMessage); 1382 | machineUserMessage.appendChild(speakButton); 1383 | }) 1384 | .catch(error => console.log(error)); 1385 | @@ -225,17 +225,29 @@ 1386 | } 1387 | }); 1388 | 1389 | - /* New code for text-to-speech */ 1390 | - const speak = (text) => { 1391 | - const speech = new SpeechSynthesisUtterance(text); 1392 | - speechSynthesis.speak(speech); 1393 | - }; 1394 | + const speak = (element) => { 1395 | + var text = element.innerText; 1396 | + var words = text.split(' '); 1397 | + var utterance = new SpeechSynthesisUtterance(text); 1398 | + var currentIndex = 0; 1399 | + 1400 | + utterance.addEventListener('boundary', function(event) { 1401 | + if (event.name === 'word') { 1402 | + var word = words[currentIndex]; 1403 | + element.innerHTML = text; 1404 | + element.innerHTML = element.innerHTML.replace(word, '' + word + ''); 1405 | + currentIndex++; 1406 | + } 1407 | + }); 1408 | + 1409 | + speechSynthesis.speak(utterance); 1410 | + } 1411 | 1412 | /* New code for adding speaker icon to user message */ 1413 | const addUserSpeakerIcon = (message) => { 1414 | const speakerIcon = document.createElement('span'); 1415 | speakerIcon.classList.add('fa', 'fa-volume-up'); 1416 | - speakerIcon.onclick = () => speak(message.textContent); 1417 | + speakerIcon.onclick = () => speak(message); 1418 | message.appendChild(speakerIcon); 1419 | }; 1420 | 1421 | 1422 | ``` 1423 | ### [2e4f463d60307daaf79f4dceb802bf989387487b](https://github.com/paul-gauthier/easy-chat/commit/2e4f463d60307daaf79f4dceb802bf989387487b) 1424 | 1425 | > restore the original text when speaking ends 1426 | ``` 1427 | 1428 | diff --git a/chat.html b/chat.html 1429 | index 83df70c..c637df5 100644 1430 | --- a/chat.html 1431 | +++ b/chat.html 1432 | @@ -241,8 +241,11 @@ 1433 | }); 1434 | 1435 | speechSynthesis.speak(utterance); 1436 | + utterance.onend = function() { 1437 | + element.innerHTML = text; 1438 | + }; 1439 | } 1440 | - 1441 | + 1442 | /* New code for adding speaker icon to user message */ 1443 | const addUserSpeakerIcon = (message) => { 1444 | const speakerIcon = document.createElement('span'); 1445 | 1446 | ``` 1447 | ### [9f0df30a797034b28ec5978ee035d18d6c3914a1](https://github.com/paul-gauthier/easy-chat/commit/9f0df30a797034b28ec5978ee035d18d6c3914a1) 1448 | 1449 | > adopt newest speak() 1450 | ``` 1451 | 1452 | diff --git a/chat.html b/chat.html 1453 | index c637df5..c3d6a22 100644 1454 | --- a/chat.html 1455 | +++ b/chat.html 1456 | @@ -224,9 +224,11 @@ 1457 | speechSynthesis.speak(speech); 1458 | } 1459 | }); 1460 | - 1461 | + 1462 | const speak = (element) => { 1463 | - var text = element.innerText; 1464 | + var text = element.innerText; 1465 | + var html = element.innerHTML; 1466 | + 1467 | var words = text.split(' '); 1468 | var utterance = new SpeechSynthesisUtterance(text); 1469 | var currentIndex = 0; 1470 | @@ -234,7 +236,7 @@ 1471 | utterance.addEventListener('boundary', function(event) { 1472 | if (event.name === 'word') { 1473 | var word = words[currentIndex]; 1474 | - element.innerHTML = text; 1475 | + element.innerHTML = html; 1476 | element.innerHTML = element.innerHTML.replace(word, '' + word + ''); 1477 | currentIndex++; 1478 | } 1479 | @@ -242,7 +244,7 @@ 1480 | 1481 | speechSynthesis.speak(utterance); 1482 | utterance.onend = function() { 1483 | - element.innerHTML = text; 1484 | + element.innerHTML = html; 1485 | }; 1486 | } 1487 | 1488 | 1489 | ``` 1490 | ### [c3c61572a4aeb6dbbc64b400bf170077a4407dca](https://github.com/paul-gauthier/easy-chat/commit/c3c61572a4aeb6dbbc64b400bf170077a4407dca) 1491 | 1492 | > PROMPT with tweaks: keep the speaker button working on newly added messages after playing 1493 | ``` 1494 | 1495 | diff --git a/chat.html b/chat.html 1496 | index c3d6a22..83ea475 100644 1497 | --- a/chat.html 1498 | +++ b/chat.html 1499 | @@ -245,8 +245,10 @@ 1500 | speechSynthesis.speak(utterance); 1501 | utterance.onend = function() { 1502 | element.innerHTML = html; 1503 | + const speakerIcon = element.lastChild; 1504 | + speakerIcon.onclick = () => speak(element); 1505 | }; 1506 | - } 1507 | + }; 1508 | 1509 | /* New code for adding speaker icon to user message */ 1510 | const addUserSpeakerIcon = (message) => { 1511 | 1512 | ``` 1513 | ### [360e61ac4fa10e5bd553f7024fe1a130952e59f9](https://github.com/paul-gauthier/easy-chat/commit/360e61ac4fa10e5bd553f7024fe1a130952e59f9) 1514 | 1515 | > asked gpt4 to generate a new speak() in another file, imported 1516 | ``` 1517 | 1518 | diff --git a/chat.html b/chat.html 1519 | index 83ea475..c2190de 100644 1520 | --- a/chat.html 1521 | +++ b/chat.html 1522 | @@ -224,32 +224,55 @@ 1523 | speechSynthesis.speak(speech); 1524 | } 1525 | }); 1526 | - 1527 | - const speak = (element) => { 1528 | - var text = element.innerText; 1529 | - var html = element.innerHTML; 1530 | - 1531 | - var words = text.split(' '); 1532 | - var utterance = new SpeechSynthesisUtterance(text); 1533 | - var currentIndex = 0; 1534 | + 1535 | + function highlight(element) { 1536 | + element.classList.add('highlight'); 1537 | + } 1538 | 1539 | - utterance.addEventListener('boundary', function(event) { 1540 | - if (event.name === 'word') { 1541 | - var word = words[currentIndex]; 1542 | - element.innerHTML = html; 1543 | - element.innerHTML = element.innerHTML.replace(word, '' + word + ''); 1544 | - currentIndex++; 1545 | - } 1546 | - }); 1547 | + function removeHighlight(element) { 1548 | + element.classList.remove('highlight'); 1549 | + } 1550 | 1551 | - speechSynthesis.speak(utterance); 1552 | - utterance.onend = function() { 1553 | - element.innerHTML = html; 1554 | - const speakerIcon = element.lastChild; 1555 | - speakerIcon.onclick = () => speak(element); 1556 | - }; 1557 | - }; 1558 | - 1559 | + function speak(element) { 1560 | + if ('speechSynthesis' in window) { 1561 | + const textElements = element.querySelectorAll('span'); 1562 | + const textContent = Array.from(textElements).map(el => el.textContent).join(' '); 1563 | + 1564 | + const utterance = new SpeechSynthesisUtterance(textContent); 1565 | + utterance.onboundary = (event) => { 1566 | + if (event.name === 'word') { 1567 | + const charIndex = event.charIndex; 1568 | + const wordIndex = getWordIndexByCharIndex(textElements, charIndex); 1569 | + if (wordIndex !== -1) { 1570 | + removeHighlights(textElements); 1571 | + highlight(textElements[wordIndex]); 1572 | + } 1573 | + } 1574 | + }; 1575 | + utterance.onend = () => { 1576 | + removeHighlights(textElements); 1577 | + }; 1578 | + 1579 | + window.speechSynthesis.speak(utterance); 1580 | + } else { 1581 | + alert('Speech Synthesis not supported in your browser'); 1582 | + } 1583 | + } 1584 | + 1585 | + function removeHighlights(elements) { 1586 | + elements.forEach(removeHighlight); 1587 | + } 1588 | + 1589 | + function getWordIndexByCharIndex(textElements, charIndex) { 1590 | + let currentCharIndex = 0; 1591 | + for (let i = 0; i < textElements.length; i++) { 1592 | + currentCharIndex += textElements[i].textContent.length + 1; // Add 1 for the space between words 1593 | + if (currentCharIndex > charIndex) { 1594 | + return i; 1595 | + } 1596 | + } 1597 | + return -1; 1598 | + } 1599 | /* New code for adding speaker icon to user message */ 1600 | const addUserSpeakerIcon = (message) => { 1601 | const speakerIcon = document.createElement('span'); 1602 | 1603 | ``` 1604 | ### [37a4e74f68b15b8cf4a752fb22e8d7ceb2117393](https://github.com/paul-gauthier/easy-chat/commit/37a4e74f68b15b8cf4a752fb22e8d7ceb2117393) 1605 | 1606 | > bugfix: put back dyslexic font 1607 | ``` 1608 | 1609 | diff --git a/chat.html b/chat.html 1610 | index c2190de..9fac1ab 100644 1611 | --- a/chat.html 1612 | +++ b/chat.html 1613 | @@ -131,6 +131,7 @@ 1614 | background-color: yellow; 1615 | } 1616 | 1617 | + 1618 | 1619 | 1620 | 1621 | 1622 | ``` 1623 | ### [e80ee9ceba2d5922d0b528bec7093f0ef9a68a4d](https://github.com/paul-gauthier/easy-chat/commit/e80ee9ceba2d5922d0b528bec7093f0ef9a68a4d) 1624 | 1625 | > PROMPT Right now on ipad the keyboard closes when the user hits the SEND button, but it stays open if the user types the ENTER key to send the message. It should close when they type ENTER to send the message. Fix this bug. 1626 | ``` 1627 | 1628 | diff --git a/chat.html b/chat.html 1629 | index 9fac1ab..3078bb3 100644 1630 | --- a/chat.html 1631 | +++ b/chat.html 1632 | @@ -167,7 +167,7 @@ 1633 | document.getElementById('bottom').scrollIntoView(); 1634 | 1635 | const messages = []; 1636 | - const chatMessages = chatBox.querySelectorAll('p'); 1637 | + const chatMessages = chatBox.querySelectorAll('.user, .assistant, .machine-user'); 1638 | for (let i = 0; i < chatMessages.length; i++) { 1639 | const role = chatMessages[i].classList.contains('user') ? 'user' : 'assistant'; 1640 | const content = chatMessages[i].textContent; 1641 | @@ -194,6 +194,7 @@ 1642 | machineUserMessage.classList.add('machine-user'); 1643 | chatBox.insertBefore(machineUserMessage, document.getElementById('bottom')); 1644 | document.getElementById('bottom').scrollIntoView(); 1645 | + inputBox.blur(); 1646 | spinner.style.display = 'none'; 1647 | sendButton.style.display = 'inline-block'; 1648 | inputBox.focus(); 1649 | 1650 | ``` 1651 | ### [a65014b09661b9e756c704def74956959648d76a](https://github.com/paul-gauthier/easy-chat/commit/a65014b09661b9e756c704def74956959648d76a) 1652 | 1653 | > PROMPT inputBox.addEventListener('keydown')... need so suppress the event. Don't let the enter key be input to the textarea. 1654 | ``` 1655 | 1656 | diff --git a/chat.html b/chat.html 1657 | index 3078bb3..347643e 100644 1658 | --- a/chat.html 1659 | +++ b/chat.html 1660 | @@ -211,7 +211,8 @@ 1661 | 1662 | inputBox.addEventListener('keydown', (event) => { 1663 | if (event.key === 'Enter' && inputBox.value !== '') { 1664 | - sendMessage(); 1665 | + sendMessage(); 1666 | + event.preventDefault(); 1667 | } 1668 | }); 1669 | 1670 | 1671 | ``` 1672 | ### [53f53fee541ca8ed0e09f0f30582a11049320682](https://github.com/paul-gauthier/easy-chat/commit/53f53fee541ca8ed0e09f0f30582a11049320682) 1673 | 1674 | > PROMPT Write a js function suitable for embedding in an html page which takes markdown text and turns it into html, but each word in the text should be contained in its own span with class="word". Be sure not to wrap html tags in spans. Only wrap words which are made up of alpha characters. You can use the marked library, like in this examples: 1675 | ``` 1676 | 1677 | diff --git a/chat.html b/chat.html 1678 | index 347643e..9a2a451 100644 1679 | --- a/chat.html 1680 | +++ b/chat.html 1681 | @@ -148,7 +148,28 @@ 1682 |
1683 |
1684 |
1685 | - 1687 | + 1966 | + 1967 | 1968 | 1969 | 1970 | 1971 | ``` 1972 | ### [fa3485473473c2f9903b46b0181a29a50b3395d0](https://github.com/paul-gauthier/easy-chat/commit/fa3485473473c2f9903b46b0181a29a50b3395d0) 1973 | 1974 | > PROMPT Obfuscate the value of token in the source file. 1975 | ``` 1976 | 1977 | diff --git a/chat.js b/chat.js 1978 | index bfb4c52..e3d9a40 100644 1979 | --- a/chat.js 1980 | +++ b/chat.js 1981 | @@ -36,6 +36,9 @@ const inputBox = document.querySelector('.input-box textarea'); 1982 | const sendButton = document.querySelector('.input-box button'); 1983 | const spinner = document.querySelector('.spinner'); 1984 | 1985 | +// stop github from reporting to openai 1986 | +const token = atob('c2stNFBDcmhTSjF0d2tITWVjSHZNQUVUM0JsbmtGSkZCQzNrZEdqTkhqTUFid3g3UERL'); 1987 | + 1988 | const sendMessage = () => { 1989 | if (inputBox.value !== '') { 1990 | spinner.style.display = 'inline-block'; 1991 | diff --git a/index.html b/index.html 1992 | index 5977483..d1ab034 100644 1993 | --- a/index.html 1994 | +++ b/index.html 1995 | @@ -28,7 +28,6 @@ 1996 |
1997 |
1998 | 1999 | - 2000 | 2001 | 2002 | 2003 | 2004 | ``` 2005 | ### [65ecf6d815e7b19d7f3fbe5ef739e0d2ab6744fc](https://github.com/paul-gauthier/easy-chat/commit/65ecf6d815e7b19d7f3fbe5ef739e0d2ab6744fc) 2006 | 2007 | > bugfix: gpt didn't correctly perform the btoa() on the original token, did it manually 2008 | ``` 2009 | 2010 | diff --git a/chat.js b/chat.js 2011 | index e3d9a40..cd763f0 100644 2012 | --- a/chat.js 2013 | +++ b/chat.js 2014 | @@ -37,7 +37,7 @@ const sendButton = document.querySelector('.input-box button'); 2015 | const spinner = document.querySelector('.spinner'); 2016 | 2017 | // stop github from reporting to openai 2018 | -const token = atob('c2stNFBDcmhTSjF0d2tITWVjSHZNQUVUM0JsbmtGSkZCQzNrZEdqTkhqTUFid3g3UERL'); 2019 | +const token = atob("c2stNFBDcmhTSjF0d2tITUVjSHZNQUVUM0JsYmtGSkZCQzNrZEdqTkhqTUFid3g3UERL") 2020 | 2021 | const sendMessage = () => { 2022 | if (inputBox.value !== '') { 2023 | 2024 | ``` 2025 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Easy Chat 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 23 |
24 |
25 |

Hello! 26 |

27 |

How can I help 28 | you?

29 |
30 |
31 |
32 | 33 | 34 |
35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /prompt.txt: -------------------------------------------------------------------------------- 1 | 2 | Can you make this proxy respond properly to CORS preflight requests? 3 | 4 | ### 5 | -------------------------------------------------------------------------------- /screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paul-gauthier/easy-chat/c09b7c094cd00e0a295706502c10d7c21c2c92cd/screenshot.gif -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paul-gauthier/easy-chat/c09b7c094cd00e0a295706502c10d7c21c2c92cd/screenshot.png --------------------------------------------------------------------------------