-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3f6d9c9
commit bbfcc02
Showing
3 changed files
with
257 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>LLM</title> | ||
<style> | ||
html { | ||
height: 100%; | ||
} | ||
|
||
body { | ||
min-height: 100%; | ||
position: relative; | ||
margin: 0; | ||
} | ||
|
||
event-propagator { | ||
display: block; | ||
position: absolute; | ||
inset: 0 0 0 0; | ||
pointer-events: none; | ||
} | ||
|
||
fc-geometry { | ||
border: 1px solid black; | ||
border-radius: 4px; | ||
} | ||
|
||
folk-llm { | ||
display: block; | ||
width: 100%; | ||
height: 100%; | ||
min-height: 1lh; | ||
} | ||
|
||
folk-timer { | ||
padding: 0.5rem; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<fc-geometry x="100" y="100" width="250"> | ||
<ul id="recipe" class="wprm-recipe-ingredients"> | ||
<li class="wprm-recipe-ingredient" style="list-style-type: disc" data-uid="0"> | ||
<span class="wprm-recipe-ingredient-amount">500</span> | ||
<span class="wprm-recipe-ingredient-unit">g</span> | ||
<span class="wprm-recipe-ingredient-name">all purpose flour King Arthur brand is recommended</span> | ||
<span class="wprm-recipe-ingredient-notes wprm-recipe-ingredient-notes-faded" | ||
>about 3 1/2 cups, using 'scoop and swipe' method</span | ||
> | ||
</li> | ||
<li class="wprm-recipe-ingredient" style="list-style-type: disc" data-uid="1"> | ||
<span class="wprm-recipe-ingredient-amount">360</span> | ||
<span class="wprm-recipe-ingredient-unit">g</span> | ||
<span class="wprm-recipe-ingredient-name">water</span> | ||
<span class="wprm-recipe-ingredient-notes wprm-recipe-ingredient-notes-faded">about 1 1/2 cups + 1 Tbsp</span> | ||
</li> | ||
<li class="wprm-recipe-ingredient" style="list-style-type: disc" data-uid="2"> | ||
<span class="wprm-recipe-ingredient-amount">10</span> <span class="wprm-recipe-ingredient-unit">g</span> | ||
<span class="wprm-recipe-ingredient-name">salt</span> | ||
<span class="wprm-recipe-ingredient-notes wprm-recipe-ingredient-notes-faded">about 2 tsp</span> | ||
</li> | ||
<li class="wprm-recipe-ingredient" style="list-style-type: disc" data-uid="3"> | ||
<span class="wprm-recipe-ingredient-amount">3</span> <span class="wprm-recipe-ingredient-unit">g</span> | ||
<span class="wprm-recipe-ingredient-name">instant yeast</span> | ||
<span class="wprm-recipe-ingredient-notes wprm-recipe-ingredient-notes-faded" | ||
>about 1 tsp; also known as Quick Rise or Rapid Rise yeast</span | ||
> | ||
</li> | ||
<li class="wprm-recipe-ingredient" style="list-style-type: disc" data-uid="4"> | ||
<span class="wprm-recipe-ingredient-amount">25</span> <span class="wprm-recipe-ingredient-unit">g</span> | ||
<span class="wprm-recipe-ingredient-name">honey</span> | ||
<span class="wprm-recipe-ingredient-notes wprm-recipe-ingredient-notes-faded">about 1 Tbsp</span> | ||
</li> | ||
</ul> | ||
</fc-geometry> | ||
|
||
<fc-geometry x="400" y="100" width="250"> | ||
<folk-llm system-prompt="Output text as HTML, no backticks"></folk-llm> | ||
</fc-geometry> | ||
|
||
<fc-geometry x="700" y="200"> | ||
<folk-timer></folk-timer> | ||
</fc-geometry> | ||
|
||
<event-propagator | ||
source="#recipe" | ||
target="folk-llm" | ||
triggers="click" | ||
expression="$target.prompt = `double this list of ingredients '${$source.innerHTML}'`" | ||
></event-propagator> | ||
|
||
<event-propagator | ||
source="folk-llm" | ||
target="folk-timer" | ||
triggers="started" | ||
expression="$target.reset(); $target.start()" | ||
> | ||
</event-propagator> | ||
|
||
<event-propagator | ||
source="folk-llm" | ||
target="folk-timer" | ||
triggers="finished" | ||
expression="$target.stop();" | ||
></event-propagator> | ||
|
||
<script type="module"> | ||
import { FolkGeometry } from '../src/canvas/fc-geometry.ts'; | ||
import { FolkLLM } from '../src/folk-llm.ts'; | ||
import { EventPropagator } from '../src/arrows/event-propagator.ts'; | ||
|
||
FolkGeometry.register(); | ||
FolkLLM.register(); | ||
|
||
customElements.define( | ||
'folk-timer', | ||
class Timer extends HTMLElement { | ||
#timeMs = 0; | ||
#intervalMs = 100; | ||
#timeoutId = -1; | ||
|
||
connectedCallback() { | ||
this.#updateTime(0); | ||
} | ||
|
||
start() { | ||
this.#timeoutId = setInterval(this.#updateTime, this.#intervalMs); | ||
} | ||
|
||
stop() { | ||
clearInterval(this.#timeoutId); | ||
this.#timeoutId = -1; | ||
} | ||
|
||
reset() { | ||
this.stop(); | ||
this.#updateTime(0); | ||
} | ||
|
||
#updateTime = (time = this.#timeMs + this.#intervalMs) => { | ||
this.#timeMs = time; | ||
this.textContent = (time / 1000).toFixed(1); | ||
}; | ||
} | ||
); | ||
|
||
EventPropagator.register(); | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
export type RolePrompt = { | ||
role: string; | ||
content: string; | ||
}; | ||
|
||
export type Prompt = string | RolePrompt[]; | ||
|
||
declare global { | ||
interface HTMLElementTagNameMap { | ||
'folk-llm': FolkLLM; | ||
} | ||
} | ||
|
||
export class FolkLLM extends HTMLElement { | ||
static tagName = 'folk-llm'; | ||
|
||
static register() { | ||
customElements.define(this.tagName, this); | ||
} | ||
|
||
#shadow = this.attachShadow({ mode: 'open' }); | ||
|
||
connectedCallback() { | ||
this.#update(new Set(['systemPrompt', 'prompt'])); | ||
} | ||
|
||
#session; | ||
|
||
#isModelReady = window?.ai.languageModel.capabilities().then((capabilities) => capabilities.available === 'readily'); | ||
|
||
#systemPrompt: Prompt = this.getAttribute('system-prompt') || ''; | ||
get systemPrompt() { | ||
return this.#systemPrompt; | ||
} | ||
set systemPrompt(systemPrompt) { | ||
this.#systemPrompt = systemPrompt; | ||
this.#requestUpdate('systemPrompt'); | ||
} | ||
|
||
#prompt: Prompt = this.getAttribute('prompt') || ''; | ||
get prompt() { | ||
return this.#prompt; | ||
} | ||
set prompt(prompt) { | ||
this.#prompt = prompt; | ||
this.#requestUpdate('prompt'); | ||
} | ||
|
||
#updatedProperties = new Set<string>(); | ||
#isUpdating = false; | ||
|
||
async #requestUpdate(property: string) { | ||
this.#updatedProperties.add(property); | ||
|
||
if (this.#isUpdating) return; | ||
|
||
this.#isUpdating = true; | ||
await true; | ||
this.#isUpdating = false; | ||
this.#update(this.#updatedProperties); | ||
this.#updatedProperties.clear(); | ||
} | ||
|
||
async #update(updatedProperties: Set<string>) { | ||
if (updatedProperties.has('systemPrompt')) { | ||
this.#session?.destroy(); | ||
|
||
const initialPrompt = | ||
typeof this.#systemPrompt === 'string' | ||
? { systemPrompt: this.#systemPrompt } | ||
: { initialPrompts: this.systemPrompt }; | ||
this.#session = await window.ai.languageModel.create(initialPrompt); | ||
this.#runPrompt(); | ||
} else if (updatedProperties.has('prompt') && this.#session !== undefined) { | ||
const oldSession = this.#session; | ||
this.#session = await oldSession.clone(); | ||
oldSession.destroy(); | ||
this.#runPrompt(); | ||
} | ||
} | ||
|
||
async #runPrompt() { | ||
if (this.prompt.length === 0 || this.#session === undefined) return; | ||
|
||
this.#shadow.textContent = ''; | ||
|
||
this.dispatchEvent(new Event('started')); | ||
const stream = await this.#session.promptStreaming(this.prompt); | ||
|
||
for await (const chunk of stream) { | ||
this.#shadow.innerHTML = chunk; | ||
} | ||
|
||
this.dispatchEvent(new Event('finished')); | ||
} | ||
} | ||
|
||
declare global { | ||
interface Window { | ||
ai: any; | ||
} | ||
} |