Skip to content

Instantly share code, notes, and snippets.

@semanticpixel
Last active June 4, 2023 18:14
Show Gist options
  • Save semanticpixel/5e7dd962ab5b3575a29420c50f65c8c8 to your computer and use it in GitHub Desktop.
Save semanticpixel/5e7dd962ab5b3575a29420c50f65c8c8 to your computer and use it in GitHub Desktop.
Chat
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class extends Component {
@action
click() {
console.log('Clicked: ', this.args.text);
}
}
import Component from '@glimmer/component';
export default class extends Component {
}
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
// const DELTA = 38;
const DELTA = 24; // padding-top + padding-bottom
export default class extends Component {
@tracked height = 22;
@tracked overflow = 'hidden';
resetTextArea() {
this.height = 22;
this.overflow = 'hidden';
}
@action
input(e) {
console.log('value: ', e.target.value);
e.preventDefault();
if (e.target.value === '') {
this.resetTextArea();
} else {
// this.height = 0;
console.log('Height: ', e.target.scrollHeight - DELTA);
const height = e.target.scrollHeight - DELTA;
this.height = height / 22 > 5 ? 22 * 5 : height;
this.overflow = height / 22 > 5 ? 'auto' : 'hidden';
}
}
@action
keyDown(e) {
if (e.which === 13) {
event.preventDefault();
const text = e.target.value;
if (!text) {
return;
}
const newEvent = new Event("submit", {cancelable: true});
event.target.form.dispatchEvent(newEvent);
console.log('Event: ', e);
}
}
@action
submitForm(e) {
console.log('We are on submit');
e.preventDefault();
const input = e.target.elements.namedItem('message');
this.args.onSubmit(input.value);
e.target.reset();
this.resetTextArea();
}
}
import Component from '@glimmer/component';
export default class extends Component {
}
import Component from '@glimmer/component';
export default class extends Component {
}
import Component from '@glimmer/component';
export default class extends Component {
}
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default class extends Component {
@tracked isStreaming = false;
@tracked _text = '';
constructor() {
super(...arguments);
if (this.args.message.isSelfMessage) {
this._text = this.args.message.text;
return;
}
const stringArray = this.args.message.text.split(" ");
this.isStreaming = true;
let n = 0;
const interval = setInterval(() => {
this._text = `${this._text}${stringArray[n]} `;
n++;
if (n >= stringArray.length) {
clearInterval(interval);
this.isStreaming = false;
}
}, 100);
}
}
import Component from '@glimmer/component';
export default class extends Component {
}
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
const BOT_MESSAGES = [
'How do you do. Please tell me your problem.',
'How do you do. Please state your problem.',
'Hi. What seems to be your problem?',
'Please go on.',
'Can you elaborate on that?',
'Is it because you are tired that you came to me?',
];
export default class extends Component {
@tracked isLoading = false;
@tracked messageList = [];
@action
onSubmit(text) {
const messagesCopy = this.messageList;
messagesCopy.push({ isSelfMessage: true, text: text });
this.messageList = [...messagesCopy];
this.isLoading = true;
setTimeout(() => {
const rndInt = Math.floor(Math.random() * 6) + 1;
messagesCopy.push({ isSelfMessage: false, text: BOT_MESSAGES[rndInt - 1] });
this.messageList = [...messagesCopy];
this.isLoading = false;
}, 3000);
}
}
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
const BOT_MESSAGES = [
'How do you do. Please tell me your problem.',
'How do you do. Please state your problem.',
'Hi. What seems to be your problem?',
'Please go on.',
'Can you elaborate on that?',
'Is it because you are tired that you came to me?',
];
const messagesInResponse = 1;
export default class extends Component {
@tracked isLoading = false;
@tracked messageList = [];
currentUserMessage = 0;
scrollCurrentMessage() {
window.requestAnimationFrame(() => {
const currentMessage = document.querySelectorAll('article')[this.currentUserMessage - 1];
if (currentMessage) {
currentMessage.scrollIntoView({behavior: 'smooth'});
}
});
}
@action
onSubmit(text) {
const messagesCopy = this.messageList;
messagesCopy.push({ isSelfMessage: true, text: text });
this.messageList = [...messagesCopy];
this.isLoading = true;
// Scroll
this.currentUserMessage = messagesCopy.length;
this.scrollCurrentMessage();
setTimeout(() => {
messagesCopy.push({
isSelfMessage: false,
isStreaming: true,
text: ''
});
messagesCopy.push({
isSelfMessage: false,
isStreaming: false,
text: 'People you may know',
isAttachment: true
});
this.messageList = [...messagesCopy];
this.isLoading = false;
// Scroll
this.currentUserMessage = messagesCopy.length;
this.scrollCurrentMessage();
// Grab random text from bot messages above
const rndInt = Math.floor(Math.random() * 6) + 1;
const botMessage = BOT_MESSAGES[rndInt - 1];
// Break text into words
const chunks = botMessage.split(" ");
let n = 0;
// Mimic a streaming reponse
const interval = setInterval(() => {
let isStreaming = true;
if (n >= chunks.length - 1) {
clearInterval(interval);
isStreaming = false;
}
messagesCopy[messagesCopy.length - 2] = {
isSelfMessage: false,
text: `${messagesCopy[messagesCopy.length - 2].text}${chunks[n]} `,
isStreaming: isStreaming,
};
n++;
this.messageList = [...messagesCopy];
}, 100);
}, 3000);
}
}
import Component from '@glimmer/component';
export default class extends Component {
}
import Component from '@glimmer/component';
export default class extends Component {
}
import Controller from '@ember/controller';
export default class ApplicationController extends Controller {
appName = 'Ember Twiddle';
}
body {
margin: 0;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 62.5%;
padding: 0;
}
.panel {
display: flex;
flex-direction: column;
width: 32rem;
max-width: 32rem;
height: 100vh;
background-color: white;
border: 1px solid #ddd;
}
.header {
display: flex;
padding: 0.8rem;
align-items: center;
font-weight: bold;
color: blue;
background-color: white;
border-bottom: 1px solid #ddd;
}
.header__logo {
flex: 1;
display: flex;
align-items: center;
}
.header__title {
font-weight: bold;
color: blue;
margin-right: .4rem;
}
.visually-hidden {
display: none;
}
.input {
display: flex;
padding: 0.8rem;
border-top: 1px solid #ddd;
margin: 0;
}
.input__textbox {
color: black;
/* height: 4.8rem !important; */
padding: 12px 16px;
font-size: 22px !important;
line-height: 1;
background-color: #ddd !important;
border-radius: 1.2rem !important;
border: none !important;
box-shadow: none !important;
resize: none;
width: 100%;
}
.message-list {
flex: 1 1 auto;
overflow-y: hidden;
/* padding: 1.6rem; */
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.message-list__scroller {
/* display: flex;
flex-direction: column;
justify-content: flex-end; */
overflow-y: auto;
padding: 1.6rem;
display: flex;
flex-direction: column;
}
.message {
/* display: inline-block; */
width: fit-content;
margin: 0.4rem 0;
color: black;
background-color: #fab1a0;
border-radius: .4rem .4rem .4rem 0;
padding: 1.2rem 1.6rem;
font-size: 1.2rem;
word-wrap: break-word;
-webkit-font-smoothing: antialiased;
}
.message--user {
margin-left: 25%;
align-self: flex-end;
color: white;
background-color: #0984e3;
border-radius: .4rem .4rem 0 .4rem;
}
.message__bubble {
display: inline-block;
color: black;
background-color: #fab1a0;
border-radius: .4rem .4rem .4rem 0;
padding: 1.2rem 1.6rem;
font-size: 1.2rem;
word-wrap: break-word;
/* white-space: pre-line; */
-webkit-font-smoothing: antialiased;
}
.message--user .message__bubble {
color: white;
background-color: #0984e3;
border-radius: .4rem .4rem 0 .4rem;
}
.message__text {
margin: 0;
}
.message-v2 {
display: inline-block;
width: fit-content;
color: black;
background-color: gray;
border-radius: .4rem;;
padding: 1.2rem 1.6rem;
font-size: 1.2rem;
word-wrap: break-word;
-webkit-font-smoothing: antialiased;
margin: 0.8rem 0;
}
.message-v2--user {
text-align: right;
color: white;
background-color: blue;
border-radius: .4rem;
}
.message--is-streaming .message__text::after {
content: "▋";
vertical-align: baseline;
animation: blink 1s steps(5,start) infinite;
line-height: 1;
}
/* .message__text::after {
content: "▋";
vertical-align: baseline;
animation: blink 1s steps(5,start) infinite;
} */
@keyframes blink {
to {
visibility: hidden
/* opacity: 0; */
}
}
@keyframes type-caret {
75% {
border-color: transparent;
}
}
.attachment {
/* box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.08); */
/* transition: box-shadow 83ms; */
/* background-color: #fff; */
/* border-radius: 2px; */
/* overflow: hidden; */
position: relative;
/* border: none; */
/* padding: 1.2rem; */
}
.attachment__title {
font-size: 1.4rem;
margin: 0;
margin-bottom: 1.2rem;
}
.attachment__content {
/* display: flex;
flex-wrap: wrap;
justify-content: space-between; */
display: grid;
width: 100%;
grid-template-columns: auto auto;
grid-column-gap: 1rem;
grid-row-gap: 1rem;
}
.attachment__card {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
/* width: 40%; */
gap: 1.2rem;
/* margin-bottom: 12px; */
padding: 1.2rem;
/* box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.08);
transition: box-shadow 83ms; */
background-color: #fff;
border-radius: 2px;
overflow: hidden;
position: relative;
border: none;
}
.attachment__image {
width: 56px;
height: 56px;
border-radius: 50%;
background-color: #ddd;
}
.attachment__text {
width: 64px;
height: 1.2rem;
background-color: #ddd;
}
.loader {
display: flex;
justify-content: center;
align-items: center;
margin: 0 auto;
/* overflow: hidden; */
width: 100%;
margin-top: 0.4rem;
}
.loader__dots {
display: flex;
justify-content: center;
align-items: center;
width: 32px;
position: relative;
padding: 8px 4px;
/* margin: 0 -5%; */
overflow: hidden;
animation: coach-spin 3s infinite linear;
}
.loader__dots::before,
.loader__dots::after {
content: "";
width: 10px;
height: 10px;
border-radius: 5px;
background-color: #9880ff;
color: #9880ff;
}
.loader__dots::before {
transform: translatey(-5px);
}
.loader__dots::after {
background-color: #40e0d0;
color: #40e0d0;
transform: translatey(5px);
}
@keyframes coach-spin {
0% {
transform: rotateZ(0deg) translate3d(0, 0, 0);
}
100% {
transform: rotateZ(720deg) translate3d(0, 0, 0);
}
}
.actions {
border-top: 1px solid #ddd;
padding: 12px;
}
.action {
color: #0a66c2;
background-color: transparent;
border: none;
font-size: 16px;
height: 20px;
cursor: pointer;
}
.picker {
width: 100%;
height: 56px;
position: relative;
text-align: center;
overflow: hidden;
}
/* .picker-overlay {
position: absolute;
left: 0;
width: 100%;
height: 18px;
z-index: 100;
}
.picker-overlay--top {
top: 0px;
background-color: #fff;
opacity: 0.8;
}
.picker-overlay--bottom {
bottom: 0px;
background-color: #fff;
opacity: 0.8;
} */
.picker::before,
.picker::after {
content: '';
position: absolute;
left: 0;
width: 100%;
height: 18px;
z-index: 1000;
}
.picker::before {
top: 0px;
/* background-image: linear-gradient(to bottom, #fff, rgba(255,255,255, 0.8) 30%,transparent 100%); */
/* background-image: linear-gradient(to bottom,#fff,transparent 100%); */
background-image: linear-gradient(to bottom, #fff, rgba(255,255,255, 0.8) 10%, rgba(255,255,255, 0.4) 90%, transparent 100%);
/* background-color: #fff;
opacity: 0.8; */
}
.picker::after {
bottom: 0px;
/* background-image: linear-gradient(to top, #fff,transparent); */
background-image: linear-gradient(to top, #fff, rgba(255,255,255, 0.8) 10%, rgba(255,255,255, 0.4) 90%, transparent 100%);
/* background-color: #fff;
opacity: 0.8; */
}
.picker-list {
margin: 0;
padding: 0;
list-style: none;
scroll-snap-type: y mandatory;
/* scroll-snap-type: y proximity; */
overflow-y: scroll;
display: flex;
flex-direction: column;
height: 20px;
padding-top: 18px;
padding-bottom: 18px;
-ms-overflow-style: none;
scrollbar-width: none;
}
.picker-list::-webkit-scrollbar{
display: none;
}
.picker-item {
scroll-snap-align: center;
scroll-snap-stop: always;
}
<button class="action" {{on "click" this.click}}>
{{@text}}
</button>
<section class="attachment">
<h1 class="attachment__title">{{@title}}</h1>
<div class="attachment__content">
<div class="attachment__card">
<div class="attachment__image"></div>
<div class="attachment__text"></div>
</div>
<div class="attachment__card">
<div class="attachment__image"></div>
<div class="attachment__text"></div>
</div>
<div class="attachment__card">
<div class="attachment__image"></div>
<div class="attachment__text"></div>
</div>
<div class="attachment__card">
<div class="attachment__image"></div>
<div class="attachment__text"></div>
</div>
</div>
</section>
<form class="input" {{on "submit" this.submitForm}}>
<textarea
id="message"
rows="1"
class="input__textbox"
name="message"
placeholder="Start typing"
type="text"
style="height: {{this.height}}px; overflow: {{this.overflow}};"
{{on "keydown" this.keyDown}}
{{on "input" this.input}}
></textarea>
</form>
{{!-- <form class="input" {{on "submit" this.submitForm}}>
<div>
<textarea
id="message"
rows="1"
class="input__textbox"
name="message"
placeholder="Start typing"
type="text"
style="height: {{this.height}}px; overflow: {{this.overflow}};"
{{on "keydown" this.keyDown}}
{{on "input" this.input}}
></textarea>
</div>
</form> --}}
<header class="header">
<div class="header__logo">
<h1 class="header__title">{{@text}}</h1>
</div>
</header>
<div class="loader">
<div class="loader__dots"></div>
</div>
<section class="message-list">
<div class="message-list__scroller">
{{yield}}
</div>
</section>
<article class="message {{if @message.isSelfMessage 'message--user'}} {{if this.isStreaming 'message--is-streaming'}}">
<span class="message__bubble">
<span class="message__text">{{this._text}}</span>
</span>
</article>
{{!-- <article class="message {{if @message.isSelfMessage 'message--user'}} {{if @message.isStreaming 'message--is-streaming'}}" ...attributes>
{{#if @message.isAttachment}}
<Attachment @title={{@message.text}} />
{{else}}
<span class="message__bubble">
<span class="message__text">{{@message.text}}</span>
</span>
{{/if}}
</article> --}}
<article class="message {{if @message.isSelfMessage 'message--user'}} {{if @message.isStreaming 'message--is-streaming'}}" ...attributes>
{{#if @message.isAttachment}}
<Attachment @title={{@message.text}} />
{{else}}
<p class="message__text">{{@message.text}}</p>
{{/if}}
</article>
<div class="panel">
<Header @text="Chat"/>
<MessageList>
{{#each this.messageList as |message|}}
<MessageV2 @message={{message}} />
{{/each}}
{{#if isLoading}}
<Loader />
{{/if}}
</MessageList>
<Form @onSubmit={{this.onSubmit}} />
</div>
<div class="panel">
<Header @text="Chat"/>
<MessageList>
{{#each this.messageList as |message index|}}
<Message @message={{message}} id={{index}} />
{{/each}}
{{#if this.isLoading}}
<Loader />
{{/if}}
</MessageList>
<div class="actions">
<Picker as |picker|>
<picker.item>
<Action @text="Summarize this article" />
</picker.item>
<picker.item>
<Action @text="How can I get promoted?" />
</picker.item>
<picker.item>
<Action @text="Tell me more about LinkedIn" />
</picker.item>
<picker.item>
<Action @text="Send a message" />
</picker.item>
</Picker>
</div>
<Form @onSubmit={{this.onSubmit}} />
</div>
<li class="picker-item">
{{yield}}
</li>
<div class="picker">
{{!-- <div class="picker-overlay picker-overlay--top"></div> --}}
<ul class="picker-list">
{{yield
(hash
item=(component "picker-item")
)
}}
</ul>
{{!-- <div class="picker-overlay picker-overlay--bottom"></div> --}}
</div>
{
"version": "0.17.1",
"EmberENV": {
"FEATURES": {},
"_TEMPLATE_ONLY_GLIMMER_COMPONENTS": false,
"_APPLICATION_TEMPLATE_WRAPPER": true,
"_JQUERY_INTEGRATION": true
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.js",
"ember": "3.18.1",
"ember-template-compiler": "3.18.1",
"ember-testing": "3.18.1",
"@ember/render-modifiers": "2.0.4"
},
"addons": {
"@glimmer/component": "1.0.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment