diff --git a/examples/rag-playground/src/components/app/app.css b/examples/rag-playground/src/components/app/app.css index ee45f20..1269e3b 100644 --- a/examples/rag-playground/src/components/app/app.css +++ b/examples/rag-playground/src/components/app/app.css @@ -3,17 +3,154 @@ height: 100%; display: flex; - flex-direction: row; + flex-direction: column; + justify-content: flex-start; + align-items: center; +} + +* { + box-sizing: border-box; +} + +.svg-icon { + display: flex; justify-content: center; align-items: center; + width: 1em; + height: 1em; + + transition: transform 80ms linear; + transform-origin: center; + & svg { + width: 100%; + height: 100%; + } +} + +.main-app { + display: grid; + grid-template-columns: 1fr min-content 1fr; + grid-template-rows: 1fr min-content 1fr; + height: calc(100vh - 5px); + max-height: 820px; + width: 100vw; box-sizing: border-box; + overflow-x: hidden; + position: relative; + background-color: hsl(216, 77%, 27%); +} + +.text-left { + grid-row: 2/3; + grid-column: 1/2; + display: flex; + justify-content: flex-end; + margin-top: 50px; + padding-right: 12px; + color: #e0e0e0; } -.app-container { - width: min(1000px, 100vw - 50px); - height: min(650px, 100vw - 50px); - border-radius: 10px; - border: 1px solid var(--gray-400); - /* box-shadow: var(--shadow-border-light); */ +.app-wrapper { + grid-row: 2/3; + grid-column: 2/3; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + max-height: 950px; + + & mememo-playground { + border-radius: 10px; + /* border: 1px solid var(--gray-400); */ + background-color: white; + + width: min(1050px, 100vw - 50px); + height: min(650px, 100vw - 50px); + } + + .app-title { + color: white; + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-end; + padding: 0 10px 8px; + user-select: none; + -webkit-user-select: none; + line-height: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + .title-left { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + } + + .app-title { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + } + + .app-info { + display: flex; + align-items: baseline; + gap: 10px; + } + + .app-name { + font-size: 32px; + font-weight: 800; + } + + .app-tagline { + font-size: 26px; + font-weight: 200; + color: #fff; + display: flex; + flex-flow: row; + + & a { + color: unset; + font-style: unset; + text-decoration: unset; + } + + .mememo-logo { + margin-left: 9px; + display: flex; + flex-flow: row; + gap: 5px; + position: relative; + + &::after { + content: ''; + position: absolute; + top: 105%; + left: 50%; + transform: translate(-50%, 0); + width: 105%; + height: 2px; + background-color: hsla(0, 100%, 100%, 0.5); + } + } + } + } +} + +.text-right { + grid-row: 2/3; + grid-column: 3/4; + display: flex; + justify-content: flex-start; + padding-left: 12px; + margin-top: 80px; + color: #e0e0e0; } diff --git a/examples/rag-playground/src/components/app/app.ts b/examples/rag-playground/src/components/app/app.ts index 5507850..d03e379 100644 --- a/examples/rag-playground/src/components/app/app.ts +++ b/examples/rag-playground/src/components/app/app.ts @@ -4,6 +4,7 @@ import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import '../playground/playground'; import componentCSS from './app.css?inline'; +import logoIcon from '../../images/icon-logo.svg?raw'; /** * App element. @@ -47,8 +48,38 @@ export class MememoRagPlayground extends LitElement { render() { return html`
-
- +
+
+ +
+
+
+
+
+
RAG Playground
+
+ Prototype RAG applications in your browser! Powered + by + +
+
+
+
+ + +
+
+ +
`; diff --git a/examples/rag-playground/src/components/playground/playground.css b/examples/rag-playground/src/components/playground/playground.css index 580e871..23a84d1 100644 --- a/examples/rag-playground/src/components/playground/playground.css +++ b/examples/rag-playground/src/components/playground/playground.css @@ -24,6 +24,7 @@ minmax(300px, 400px); --arrow-color: var(--gray-300); + --arrow-color-activated: var(--blue-300); --border-radius: 5px; --block-shadow: 0px 0px 2px hsla(0, 0%, 0%, 0.1), 0px 0px 6px hsla(0, 0%, 0%, 0.05); @@ -118,6 +119,28 @@ } } +@keyframes animate-loader-left-right { + 0% { + left: 0; + transform: translateX(-100%); + } + 100% { + left: 100%; + transform: translateX(0%); + } +} + +@keyframes animate-loader-top-bottom { + 0% { + top: 0; + transform: translateY(-100%); + } + 100% { + top: 100%; + transform: translateY(0%); + } +} + .flow { display: flex; align-items: center; @@ -125,6 +148,14 @@ z-index: 1; position: relative; + &.running { + .background { + .line-loader { + display: inline-block; + } + } + } + &.input-text { grid-row: 1 / 2; grid-column: 2 / 5; @@ -153,6 +184,18 @@ align-items: center; width: 100%; + &.activated { + .background { + .start-rectangle { + background-color: var(--arrow-color-activated); + } + + .end-triangle { + border-left: 20px solid var(--arrow-color-activated); + } + } + } + .background { position: relative; width: 100%; @@ -166,13 +209,9 @@ width: 100%; height: 30px; - display: inline-block; + display: none; overflow: hidden; z-index: 2; - - &.hidden { - display: none; - } } .line-loader::after { @@ -192,18 +231,7 @@ top: 0; left: 0; box-sizing: border-box; - animation: animate-loader 1500ms ease-out infinite; - } - - @keyframes animate-loader { - 0% { - left: 0; - transform: translateX(-100%); - } - 100% { - left: 100%; - transform: translateX(0%); - } + animation: animate-loader-left-right 1500ms ease-out infinite; } .start-rectangle { @@ -241,6 +269,18 @@ align-items: center; height: 100%; + &.activated { + .background { + .start-rectangle { + background-color: var(--arrow-color-activated); + } + + .end-triangle { + border-top: 20px solid var(--arrow-color-activated); + } + } + } + .background { position: relative; height: 100%; @@ -254,21 +294,17 @@ height: 100%; width: 30px; - display: inline-block; + display: none; overflow: hidden; z-index: 2; - - &.hidden { - display: none; - } } .line-loader::after { content: ''; - height: 100px; - width: 100%; + width: 30px; + height: 100%; background: linear-gradient( - 90deg, + 180deg, hsla(0, 100%, 100%, 0) 0%, hsla(0, 100%, 100%, 0) 10%, hsla(0, 100%, 100%, 0.5) 50%, @@ -280,18 +316,7 @@ top: 0; left: 0; box-sizing: border-box; - animation: animate-loader 1500ms ease-out infinite; - } - - @keyframes animate-loader { - 0% { - left: 0; - transform: translateX(-100%); - } - 100% { - left: 100%; - transform: translateX(0%); - } + animation: animate-loader-top-bottom 1500ms ease-out infinite; } .start-rectangle { @@ -323,16 +348,43 @@ } } +.header { + font-size: var(--font-u1); + color: var(--gray-600); + line-height: 1.25; + font-weight: 800; +} + +.svg-icon { + display: flex; + justify-content: center; + align-items: center; + width: 1em; + height: 1em; + + transition: transform 80ms linear; + transform-origin: center; + + & svg { + width: 100%; + height: 100%; + } +} + .search-box { border-radius: var(--border-radius); box-shadow: var(--block-shadow); border: 1px solid hsla(0deg, 0%, 0%, 0.08); - background-color: white; + background-color: var(--gray-900); padding: 5px 10px; display: flex; align-items: center; justify-content: center; text-align: center; + + .header { + color: white; + } } .model-box { diff --git a/examples/rag-playground/src/components/playground/playground.ts b/examples/rag-playground/src/components/playground/playground.ts index aa23f80..223de48 100644 --- a/examples/rag-playground/src/components/playground/playground.ts +++ b/examples/rag-playground/src/components/playground/playground.ts @@ -30,6 +30,7 @@ import '../text-viewer/text-viewer'; import componentCSS from './playground.css?inline'; import EmbeddingWorkerInline from '../../workers/embedding?worker&inline'; import promptTemplatesJSON from '../../config/promptTemplates.json'; +import logoIcon from '../../images/icon-logo.svg?raw'; interface DatasetInfo { dataURL: string; @@ -42,6 +43,13 @@ enum Dataset { Arxiv = 'arxiv' } +enum Arrow { + Search = 'search', + Input = 'input', + Document = 'document', + Output = 'output' +} + const promptTemplate = promptTemplatesJSON as Record; const datasets: Record = { @@ -105,6 +113,13 @@ export class MememoPlayground extends LitElement { value: TextGenMessage | PromiseLike ) => {}; + arrowElements: Record = { + [Arrow.Document]: null, + [Arrow.Input]: null, + [Arrow.Search]: null, + [Arrow.Output]: null + }; + //==========================================================================|| // Lifecycle Methods || //==========================================================================|| @@ -184,6 +199,22 @@ export class MememoPlayground extends LitElement { resizeObserver.observe(userContainer); resizeObserver.observe(promptContainer); resizeObserver.observe(outputContainer); + + // Track the arrow elements + this.arrowElements = { + [Arrow.Document]: this.shadowRoot.querySelector( + '.flow.text-prompt' + ) as HTMLElement, + [Arrow.Input]: this.shadowRoot.querySelector( + '.flow.input-prompt' + ) as HTMLElement, + [Arrow.Search]: this.shadowRoot.querySelector( + '.flow.input-text' + ) as HTMLElement, + [Arrow.Output]: this.shadowRoot.querySelector( + '.flow.prompt-output' + ) as HTMLElement + }; } /** @@ -223,15 +254,10 @@ export class MememoPlayground extends LitElement { throw Error('textViewerComponent is not initialized.'); } - this.textViewerComponent.semanticSearch(embedding, this.topK, 0.5); - } + // Loading the arrow + this.arrowElements[Arrow.Search]?.classList.add('running'); - /** - * Augment the prompt using relevant documents - * @param relevantDocuments Documents that are relevant to the user query - */ - compilePrompt(relevantDocuments: string[]) { - console.log(relevantDocuments); + this.textViewerComponent.semanticSearch(embedding, this.topK, 0.5); } //==========================================================================|| @@ -246,6 +272,9 @@ export class MememoPlayground extends LitElement { // Extract embeddings for the user query this.getEmbedding([this.userQuery]); + + // Activate arrows + this.arrowElements[Arrow.Input]?.classList.add('activated'); } /** @@ -262,8 +291,10 @@ export class MememoPlayground extends LitElement { semanticSearchFinishedHandler(e: CustomEvent) { this.relevantDocuments = e.detail; - // Start to run the complied prompt - // Need to wait for the prompt component to update the prompt + // Activate arrows + this.arrowElements[Arrow.Search]?.classList.remove('running'); + this.arrowElements[Arrow.Search]?.classList.add('activated'); + this.arrowElements[Arrow.Document]?.classList.add('activated'); } embeddingWorkerMessageHandler(e: MessageEvent) { @@ -290,6 +321,12 @@ export class MememoPlayground extends LitElement { //==========================================================================|| // Private Helpers || //==========================================================================|| + _deactivateAllArrows() { + for (const key of Object.values(Arrow)) { + this.arrowElements[key]?.classList.remove('activated'); + } + } + /** * Run the given prompt using the preferred model * @returns A promise of the prompt inference @@ -297,6 +334,9 @@ export class MememoPlayground extends LitElement { _runPrompt(curPrompt: string, temperature = 0.2) { let runRequest: Promise; + // Show a loader + this.arrowElements[Arrow.Output]?.classList.add('running'); + switch (this.userConfig.preferredLLM) { case SupportedRemoteModel['gpt-3.5']: { runRequest = textGenGpt( @@ -378,7 +418,7 @@ export class MememoPlayground extends LitElement { } runRequest.then( - message => { + async message => { switch (message.command) { case 'finishTextGen': { // Success @@ -389,7 +429,15 @@ export class MememoPlayground extends LitElement { console.info(message.payload.result); } + await new Promise(resolve => { + setTimeout(resolve, 5000); + }); + this.llmOutput = message.payload.result; + + // Activate arrows + this.arrowElements[Arrow.Output]?.classList.remove('running'); + this.arrowElements[Arrow.Output]?.classList.add('activated'); break; } @@ -412,11 +460,14 @@ export class MememoPlayground extends LitElement { ) => this.userQueryRunClickHandler(e)} + @queryEdited=${() => this._deactivateAllArrows()} >
@@ -438,11 +489,14 @@ export class MememoPlayground extends LitElement { @runButtonClicked=${(e: CustomEvent) => { this.promptRunClickHandler(e); }} + @promptEdited=${() => this._deactivateAllArrows()} >
-
GPT 3.5
+
+
GPT 3.5
+
@@ -451,7 +505,7 @@ export class MememoPlayground extends LitElement {
- +
@@ -459,7 +513,7 @@ export class MememoPlayground extends LitElement {
- +
@@ -467,7 +521,7 @@ export class MememoPlayground extends LitElement {
- +
@@ -475,7 +529,7 @@ export class MememoPlayground extends LitElement {
- +
diff --git a/examples/rag-playground/src/components/prompt-box/prompt-box.ts b/examples/rag-playground/src/components/prompt-box/prompt-box.ts index 0905cf2..4c8e850 100644 --- a/examples/rag-playground/src/components/prompt-box/prompt-box.ts +++ b/examples/rag-playground/src/components/prompt-box/prompt-box.ts @@ -56,7 +56,9 @@ export class MememoPromptBox extends LitElement { // If the update is triggered by a relevant document update, we also // run the compiled prompt - this.runButtonClicked(); + if (this.relevantDocuments && this.relevantDocuments.length > 0) { + this.runButtonClicked(); + } } } @@ -100,7 +102,14 @@ export class MememoPromptBox extends LitElement { //==========================================================================|| textareaInput(e: InputEvent) { const textareaElement = e.currentTarget as HTMLTextAreaElement; - this.template = textareaElement.value; + this.prompt = textareaElement.value; + + // Notify the parent + const event = new Event('promptEdited', { + bubbles: true, + composed: true + }); + this.dispatchEvent(event); } runButtonClicked() { @@ -133,15 +142,15 @@ export class MememoPromptBox extends LitElement {
- - + +