From 459860e65c7cc314cb6636b82c81d51b27021c2a Mon Sep 17 00:00:00 2001 From: Jay Wang Date: Tue, 6 Feb 2024 18:09:15 -0500 Subject: [PATCH] Add prompt box Signed-off-by: Jay Wang --- examples/rag-playground/package.json | 1 + .../src/components/playground/playground.css | 5 + .../src/components/playground/playground.ts | 24 ++- .../src/components/prompt-box/prompt-box.css | 135 ++++++++++++++++ .../src/components/prompt-box/prompt-box.ts | 152 ++++++++++++++++++ .../src/components/query-box/query-box.css | 5 +- .../components/text-viewer/text-viewer.css | 2 + .../src/components/text-viewer/text-viewer.ts | 10 +- .../src/config/promptTemplates.json | 3 + .../rag-playground/src/images/icon-expand.svg | 1 + .../rag-playground/src/images/icon-eye.svg | 7 + 11 files changed, 340 insertions(+), 5 deletions(-) create mode 100644 examples/rag-playground/src/components/prompt-box/prompt-box.css create mode 100644 examples/rag-playground/src/components/prompt-box/prompt-box.ts create mode 100644 examples/rag-playground/src/config/promptTemplates.json create mode 100644 examples/rag-playground/src/images/icon-expand.svg create mode 100644 examples/rag-playground/src/images/icon-eye.svg diff --git a/examples/rag-playground/package.json b/examples/rag-playground/package.json index 9effe8c..0116803 100644 --- a/examples/rag-playground/package.json +++ b/examples/rag-playground/package.json @@ -33,6 +33,7 @@ "eslint-plugin-wc": "^2.0.4", "flexsearch": "^0.7.43", "gh-pages": "^6.1.1", + "gpt-tokenizer": "^2.1.2", "lit": "^3.1.2", "prettier": "^3.2.4", "typescript": "^5.3.3", diff --git a/examples/rag-playground/src/components/playground/playground.css b/examples/rag-playground/src/components/playground/playground.css index 083f34c..47fdddd 100644 --- a/examples/rag-playground/src/components/playground/playground.css +++ b/examples/rag-playground/src/components/playground/playground.css @@ -89,6 +89,11 @@ border-radius: var(--border-radius); box-shadow: var(--block-shadow); border: 1px solid hsla(0deg, 0%, 0%, 0.08); + + & mememo-prompt-box { + width: 100%; + height: 100%; + } } &.container-model { diff --git a/examples/rag-playground/src/components/playground/playground.ts b/examples/rag-playground/src/components/playground/playground.ts index 92c1b15..bdb837f 100644 --- a/examples/rag-playground/src/components/playground/playground.ts +++ b/examples/rag-playground/src/components/playground/playground.ts @@ -6,10 +6,12 @@ import type { EmbeddingWorkerMessage } from '../../workers/embedding'; import type { MememoTextViewer } from '../text-viewer/text-viewer'; import '../query-box/query-box'; +import '../prompt-box/prompt-box'; import '../text-viewer/text-viewer'; import componentCSS from './playground.css?inline'; import EmbeddingWorkerInline from '../../workers/embedding?worker&inline'; +import promptTemplatesJSON from '../../config/promptTemplates.json'; interface DatasetInfo { dataURL: string; @@ -22,6 +24,8 @@ enum Dataset { Arxiv = 'arxiv' } +const promptTemplate = promptTemplatesJSON as Record; + const datasets: Record = { [Dataset.Arxiv]: { dataURL: '/data/ml-arxiv-papers-1000.ndjson.gzip', @@ -40,7 +44,12 @@ export class MememoPlayground extends LitElement { //==========================================================================|| // Class Properties || //==========================================================================|| + @state() userQuery = ''; + + @state() + relevantDocuments: string[] = []; + embeddingWorker: Worker; embeddingWorkerRequestCount = 0; get embeddingWorkerRequestID() { @@ -127,6 +136,10 @@ export class MememoPlayground extends LitElement { this.getEmbedding([this.userQuery]); } + semanticSearchFinishedHandler(e: CustomEvent) { + this.relevantDocuments = e.detail; + } + embeddingWorkerMessageHandler(e: MessageEvent) { switch (e.data.command) { case 'finishExtractEmbedding': { @@ -175,10 +188,19 @@ export class MememoPlayground extends LitElement { indexURL=${datasets['arxiv'].indexURL} datasetName=${datasets['arxiv'].datasetName} datasetNameDisplay=${datasets['arxiv'].datasetNameDisplay} + @semanticSearchFinished=${(e: CustomEvent) => + this.semanticSearchFinishedHandler(e)} > -
Prompt
+
+ ) => {}} + > +
GPT 3.5
diff --git a/examples/rag-playground/src/components/prompt-box/prompt-box.css b/examples/rag-playground/src/components/prompt-box/prompt-box.css new file mode 100644 index 0000000..0f96dc9 --- /dev/null +++ b/examples/rag-playground/src/components/prompt-box/prompt-box.css @@ -0,0 +1,135 @@ +.prompt-box { + width: 100%; + + display: flex; + box-sizing: border-box; + flex-direction: column; + align-items: center; + + padding: var(--box-padding-v) var(--box-padding-h); + gap: 5px; +} + +textarea { + border: 1px solid var(--gray-300); + border-radius: 5px; + padding: 5px 5px; + width: 100%; + height: auto; + box-sizing: border-box; + font-family: inherit; + font-size: var(--font-d2); + resize: vertical; + max-height: 300px; + + &:focus { + outline: 2px solid var(--focus-border-color); + border: 1px solid var(--focus-border-color); + } + + &::placeholder { + color: var(--gray-600); + } +} + +.header { + font-size: var(--font-u1); + color: var(--gray-600); + line-height: 1; + + width: 100%; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + gap: 12px; + + .text-group { + display: flex; + gap: 7px; + align-items: baseline; + overflow: hidden; + } + + .text { + font-weight: 800; + white-space: nowrap; + } + + .token-count { + font-size: var(--font-d2); + color: var(--gray-600); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &[is-oversized] { + color: var(--pink-500); + } + } +} + +.svg-icon { + display: flex; + justify-content: center; + align-items: center; + width: 1em; + height: 1em; + + color: currentColor; + transition: transform 80ms linear; + transform-origin: center; + + & svg { + fill: currentColor; + width: 100%; + height: 100%; + } +} + +.button-group { + display: flex; + flex-flow: row; + align-items: center; + gap: 8px; +} + +button { + all: unset; + + display: flex; + line-height: 1; + font-size: var(--font-d2); + padding: 4px 6px; + border-radius: 5px; + white-space: nowrap; + height: var(--font-d2); + + cursor: pointer; + user-select: none; + -webkit-user-select: none; + + background-color: color-mix(in lab, var(--gray-200), white 20%); + color: var(--gray-800); + display: flex; + flex-flow: row; + align-items: center; + font-size: var(--font-d1); + + &:hover { + background-color: color-mix(in lab, var(--gray-300), white 30%); + } + + &:active { + background-color: color-mix(in lab, var(--gray-300), white 20%); + } + + .svg-icon { + position: relative; + top: 1px; + margin-right: 3px; + color: var(--gray-700); + width: 12px; + height: 12px; + } +} diff --git a/examples/rag-playground/src/components/prompt-box/prompt-box.ts b/examples/rag-playground/src/components/prompt-box/prompt-box.ts new file mode 100644 index 0000000..acdad9f --- /dev/null +++ b/examples/rag-playground/src/components/prompt-box/prompt-box.ts @@ -0,0 +1,152 @@ +import { LitElement, css, unsafeCSS, html, PropertyValues } from 'lit'; +import { customElement, property, state, query } from 'lit/decorators.js'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; +import { encode } from 'gpt-tokenizer/model/gpt-3.5-turbo'; +import d3 from '../../utils/d3-import'; + +import componentCSS from './prompt-box.css?inline'; +import searchIcon from '../../images/icon-search.svg?raw'; +import expandIcon from '../../images/icon-expand.svg?raw'; +import playIcon from '../../images/icon-play.svg?raw'; + +const numberFormatter = d3.format(','); + +/** + * Prompt box element. + * + */ +@customElement('mememo-prompt-box') +export class MememoPromptBox extends LitElement { + //==========================================================================|| + // Class Properties || + //==========================================================================|| + @property({ type: String }) + template: string | undefined; + + @property({ type: String }) + userQuery: string | undefined; + + @property({ attribute: false }) + relevantDocuments: string[] | undefined; + + @state() + prompt = ''; + + @state() + tokenCount = 0; + + //==========================================================================|| + // Lifecycle Methods || + //==========================================================================|| + constructor() { + super(); + } + + /** + * This method is called before new DOM is updated and rendered + * @param changedProperties Property that has been changed + */ + willUpdate(changedProperties: PropertyValues) { + if ( + changedProperties.has('template') || + changedProperties.has('userQuery') || + changedProperties.has('relevantDocuments') + ) { + this.updatePrompt(); + } + } + + //==========================================================================|| + // Custom Methods || + //==========================================================================|| + async initData() {} + + /** + * Recompile the prompt using template and provided information. + */ + updatePrompt() { + if (this.template === undefined) return; + + let prompt = this.template; + + if (this.userQuery !== undefined) { + prompt = prompt.replace('{{user}}', this.userQuery); + } + + if (this.relevantDocuments !== undefined) { + const documents = this.relevantDocuments.join('\n'); + prompt = prompt.replace('{{context}}', documents); + } + + this.prompt = prompt; + this.tokenCount = encode(prompt).length; + } + + //==========================================================================|| + // Event Handlers || + //==========================================================================|| + textareaInput(e: InputEvent) { + const textareaElement = e.currentTarget as HTMLTextAreaElement; + this.template = textareaElement.value; + } + + runButtonClicked() { + // Notify the parent to run the user query + const event = new CustomEvent('runButtonClicked', { + bubbles: true, + composed: true, + detail: this.template + }); + this.dispatchEvent(event); + } + + //==========================================================================|| + // Private Helpers || + //==========================================================================|| + + //==========================================================================|| + // Templates and Styles || + //==========================================================================|| + render() { + return html` +
+
+
+ Retrieval Augmented Prompt + + 8000} + >${numberFormatter(this.tokenCount)} tokens +
+ +
+ + + +
+
+ +
+ `; + } + + static styles = [ + css` + ${unsafeCSS(componentCSS)} + ` + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'mememo-prompt-box': MememoPromptBox; + } +} diff --git a/examples/rag-playground/src/components/query-box/query-box.css b/examples/rag-playground/src/components/query-box/query-box.css index cf40554..14c40ac 100644 --- a/examples/rag-playground/src/components/query-box/query-box.css +++ b/examples/rag-playground/src/components/query-box/query-box.css @@ -40,10 +40,9 @@ textarea { width: 100%; display: flex; flex-direction: row; - justify-content: space-between; justify-content: center; - gap: 10px; align-items: center; + gap: 12px; .text { font-weight: 800; @@ -72,7 +71,7 @@ textarea { display: flex; flex-flow: row; align-items: center; - gap: 10px; + gap: 8px; } button { diff --git a/examples/rag-playground/src/components/text-viewer/text-viewer.css b/examples/rag-playground/src/components/text-viewer/text-viewer.css index dadd404..c43e43f 100644 --- a/examples/rag-playground/src/components/text-viewer/text-viewer.css +++ b/examples/rag-playground/src/components/text-viewer/text-viewer.css @@ -243,12 +243,14 @@ top: 0px; right: 0px; /* transform: translate(0, -50%); */ + /* float: right; */ font-size: var(--font-d3); font-variant-numeric: tabular-nums; padding: 2px 5px; border-bottom-left-radius: 4px; /* border-top-left-radius: 5px; */ + /* border-radius: 5px; */ text-align: right; background-color: color-mix(in lab, var(--blue-100) 70%, transparent 30%); diff --git a/examples/rag-playground/src/components/text-viewer/text-viewer.ts b/examples/rag-playground/src/components/text-viewer/text-viewer.ts index 66daabd..5bb109c 100644 --- a/examples/rag-playground/src/components/text-viewer/text-viewer.ts +++ b/examples/rag-playground/src/components/text-viewer/text-viewer.ts @@ -291,6 +291,14 @@ export class MememoTextViewer extends LitElement { 0, this.shownDocumentCap ); + + // Send the documents back to the parent + const event = new CustomEvent('semanticSearchFinished', { + bubbles: true, + composed: true, + detail: documents + }); + this.dispatchEvent(event); break; } @@ -370,7 +378,6 @@ export class MememoTextViewer extends LitElement { } }} > - ${itemText} + ${itemText}
`; } diff --git a/examples/rag-playground/src/config/promptTemplates.json b/examples/rag-playground/src/config/promptTemplates.json new file mode 100644 index 0000000..1e5d24c --- /dev/null +++ b/examples/rag-playground/src/config/promptTemplates.json @@ -0,0 +1,3 @@ +{ + "arxiv": "You are an expert in machine learning, and you are answering a user's questions about machine learning. The user's question is in . You have access to documents in . Your answer should be solely based on the provided documents. Provide cite the document source if possible. Answer your question in an tag.\n\n{{user}}\n\n{{context}}" +} diff --git a/examples/rag-playground/src/images/icon-expand.svg b/examples/rag-playground/src/images/icon-expand.svg new file mode 100644 index 0000000..72d0229 --- /dev/null +++ b/examples/rag-playground/src/images/icon-expand.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/rag-playground/src/images/icon-eye.svg b/examples/rag-playground/src/images/icon-eye.svg new file mode 100644 index 0000000..6b47363 --- /dev/null +++ b/examples/rag-playground/src/images/icon-eye.svg @@ -0,0 +1,7 @@ + + + + + + +