ui: rebase and resolve conflict

This commit is contained in:
Deepak Seth 2023-12-15 14:30:30 -08:00
parent 0e18b2e7d0
commit 8cf2f35223

View file

@ -10,6 +10,7 @@
body { body {
font-family: system-ui; font-family: system-ui;
font-size: 90%; font-size: 90%;
width: 100%;
} }
#container { #container {
@ -18,6 +19,7 @@
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
height: 100%; height: 100%;
width: 100%;
} }
main { main {
@ -29,18 +31,15 @@
flex-grow: 1; flex-grow: 1;
overflow-y: auto; overflow-y: auto;
border: 1px solid #ccc;
border-radius: 5px;
padding: 0.5em; padding: 0.5em;
} }
body { body {
max-width: 600px;
min-width: 300px; min-width: 300px;
line-height: 1.2; line-height: 1.2;
margin: 0 auto; margin: 0 auto;
padding: 0 0.5em; padding: 0 0.5em;
box-sizing: border-box;
} }
p { p {
@ -142,6 +141,81 @@
display: inline; display: inline;
} }
header {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.header_controls {
gap: 0.5rem;
display: flex;
}
.flexform {
display: flex;
max-height: 100%;
height: 100%;
flex-wrap: wrap;
}
.column {
flex-grow: 1;
padding: 1rem;
box-sizing: border-box;
}
.aside {
max-width: 300px;
overflow: auto;
background: rgba(220, 220, 220, 0.2);
}
@media (max-width:600px) {
.aside {
max-width: 100%;
}
}
.range_field {
border-bottom: 1px dashed;
padding-bottom: 0.5em;
}
.range_field:last-child {
border-bottom: none;
}
.range_field label {
display: flex;
justify-content: space-between;
}
.range_field input {
width: 100%;
}
.basecontainer {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
#content {
overflow: auto;
}
.response p {
padding: 0.5em;
margin: 0;
border-radius: 0.5em;
}
.response p:nth-child(even) {
background-color: rgba(242, 242, 242, 0.5);
}
header, header,
footer { footer {
text-align: center; text-align: center;
@ -150,8 +224,18 @@
footer { footer {
font-size: 80%; font-size: 80%;
color: #888; color: #888;
box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(229, 231, 235, 0.53) 0px -1px 0px 0px, rgba(0, 0, 0, 0.24) 0px 5px 20px -5px;
padding-top: 0.5em;
} }
@media (max-width:600px) {
.footernote {
display: block;
}
}
.mode-chat textarea[name=prompt] { .mode-chat textarea[name=prompt] {
height: 4.5em; height: 4.5em;
} }
@ -537,26 +621,26 @@
} }
return html` return html`
<form onsubmit=${submit}> <form onsubmit=${submit}>
<div> <div>
<textarea <textarea
className=${generating.value ? "loading" : null} className=${generating.value ? "loading" : null}
oninput=${(e) => message.value = e.target.value} oninput=${(e) => message.value = e.target.value}
onkeypress=${enterSubmits} onkeypress=${enterSubmits}
placeholder="Say something..." placeholder="Say something..."
rows=2 rows=2
type="text" type="text"
value="${message}" value="${message}"
/> />
</div> </div>
<div class="right"> <div class="right">
<button type="submit" disabled=${generating.value}>Send</button> <button type="submit" disabled=${generating.value}>Send</button>
<button onclick=${uploadImage}>Upload Image</button> <button onclick=${uploadImage}>Upload Image</button>
<button onclick=${stop} disabled=${!generating.value}>Stop</button> <button onclick=${stop} disabled=${!generating.value}>Stop</button>
<button onclick=${reset}>Reset</button> <button onclick=${reset}>Reset</button>
</div> </div>
</form> </form>
` `
} }
function CompletionControls() { function CompletionControls() {
@ -565,11 +649,11 @@
runCompletion(); runCompletion();
} }
return html` return html`
<div> <div>
<button onclick=${submit} type="button" disabled=${generating.value}>Start</button> <button onclick=${submit} type="button" disabled=${generating.value}>Start</button>
<button onclick=${stop} disabled=${!generating.value}>Stop</button> <button onclick=${stop} disabled=${!generating.value}>Stop</button>
<button onclick=${reset}>Reset</button> <button onclick=${reset}>Reset</button>
</div>`; </div>`;
} }
const ChatLog = (props) => { const ChatLog = (props) => {
@ -613,12 +697,12 @@
} }
return html` return html`
<div id="chat" ref=${container} key=${messages.length}> <div id="chat" ref=${container} key=${messages.length}>
<img style="width: 60%;${!session.value.image_selected ? `display: none;` : ``}" src="${session.value.image_selected}"/> <img style="width: 60%;${!session.value.image_selected ? `display: none;` : ``}" src="${session.value.image_selected}"/>
<span contenteditable=${isCompletionMode} ref=${container} oninput=${handleCompletionEdit}> <span class="response" contenteditable=${isCompletionMode} ref=${container} oninput=${handleCompletionEdit}>
${messages.flatMap(chatLine)} ${messages.flatMap(chatLine)}
</span> </span>
</div>`; </div>`;
}; };
const ConfigForm = (props) => { const ConfigForm = (props) => {
@ -649,22 +733,20 @@
const FloatField = ({ label, max, min, name, step, value }) => { const FloatField = ({ label, max, min, name, step, value }) => {
return html` return html`
<div> <div class="range_field">
<label for="${name}">${label}</label> <label for="${name}">${label} <span>${value}</span></label>
<input type="range" id="${name}" min="${min}" max="${max}" step="${step}" name="${name}" value="${value}" oninput=${updateParamsFloat} /> <input type="range" id="${name}" min="${min}" max="${max}" step="${step}" name="${name}" value="${value}" oninput=${updateParamsFloat} />
<span>${value}</span> </div>
</div> `
`
}; };
const IntField = ({ label, max, min, name, value }) => { const IntField = ({ label, max, min, name, value }) => {
return html` return html`
<div> <div class="range_field">
<label for="${name}">${label}</label> <label for="${name}">${label} <span>${value}</span></label>
<input type="range" id="${name}" min="${min}" max="${max}" name="${name}" value="${value}" oninput=${updateParamsInt} /> <input type="range" id="${name}" min="${min}" max="${max}" name="${name}" value="${value}" oninput=${updateParamsInt} />
<span>${value}</span> </div>
</div> `
`
}; };
const userTemplateReset = (e) => { const userTemplateReset = (e) => {
@ -675,129 +757,145 @@
const UserTemplateResetButton = () => { const UserTemplateResetButton = () => {
if (selectedUserTemplate.value.name == 'default') { if (selectedUserTemplate.value.name == 'default') {
return html` return html`
<button disabled>Using default template</button> <button disabled>Using default template</button>
` `
} }
return html` return html`
<button onclick=${userTemplateReset}>Reset all to default</button> <button onclick=${userTemplateReset}>Reset all to default</button>
` `
}; };
useEffect(() => { useEffect(() => {
// autosave template on every change // autosave template on every change
userTemplateAutosave() userTemplateAutosave()
}, [session.value, params.value]) }, [session.value])// removed params.value as it would re render the page.
const GrammarControl = () => ( const GrammarControl = () => (
html` html`
<div> <details>
<label for="template">Grammar</label> <summary>Grammer Configuration</summary>
<textarea id="grammar" name="grammar" placeholder="Use gbnf or JSON Schema+convert" value="${params.value.grammar}" rows=4 oninput=${updateParams}/> <label for="template">Grammar</label>
<input type="text" name="prop-order" placeholder="order: prop1,prop2,prop3" oninput=${updateGrammarJsonSchemaPropOrder} /> <textarea id="grammar" name="grammar" placeholder="Use gbnf or JSON Schema+convert" value="${params.value.grammar}" rows=4 oninput=${updateParams}/>
<button type="button" onclick=${convertJSONSchemaGrammar}>Convert JSON Schema</button> <input type="text" name="prop-order" placeholder="order: prop1,prop2,prop3" oninput=${updateGrammarJsonSchemaPropOrder} />
</div> <button type="button" onclick=${convertJSONSchemaGrammar}>Convert JSON Schema</button>
` </details>
`
); );
const PromptControlFieldSet = () => ( const PromptControlFieldSet = () => (
html` html`
<fieldset> <fieldset>
<div> <div>
<label htmlFor="prompt">Prompt</label> <label htmlFor="prompt">Prompt</label>
<textarea type="text" name="prompt" value="${session.value.prompt}" oninput=${updateSession}/> <textarea type="text" name="prompt" value="${session.value.prompt}" oninput=${updateSession}/>
</div> </div>
</fieldset> </fieldset>
` `
); );
const ChatConfigForm = () => ( const ChatConfigForm = () => (
html` html`
${PromptControlFieldSet()} ${PromptControlFieldSet()}
<fieldset class="two"> <fieldset class="two">
<div> <div>
<label for="user">User name</label> <label for="user">User name</label>
<input type="text" name="user" value="${session.value.user}" oninput=${updateSession} /> <input type="text" name="user" value="${session.value.user}" oninput=${updateSession} />
</div> </div>
<div> <div>
<label for="bot">Bot name</label> <label for="bot">Bot name</label>
<input type="text" name="char" value="${session.value.char}" oninput=${updateSession} /> <input type="text" name="char" value="${session.value.char}" oninput=${updateSession} />
</div> </div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<div> <div>
<label for="template">Prompt template</label> <label for="template">Prompt template</label>
<textarea id="template" name="template" value="${session.value.template}" rows=4 oninput=${updateSession}/> <textarea id="template" name="template" value="${session.value.template}" rows=4 oninput=${updateSession}/>
</div> </div>
<div> <div>
<label for="template">Chat history template</label> <label for="template">Chat history template</label>
<textarea id="template" name="historyTemplate" value="${session.value.historyTemplate}" rows=1 oninput=${updateSession}/> <textarea id="template" name="historyTemplate" value="${session.value.historyTemplate}" rows=1 oninput=${updateSession}/>
</div> </div>
${GrammarControl()} </fieldset>
</fieldset> `
`
); );
const CompletionConfigForm = () => ( const CompletionConfigForm = () => (
html` html`
${PromptControlFieldSet()} ${PromptControlFieldSet()}
<fieldset>${GrammarControl()}</fieldset> `
`
); );
return html`
<form>
<fieldset class="two">
<${UserTemplateResetButton}/>
<div>
<label class="slim"><input type="radio" name="type" value="chat" checked=${session.value.type === "chat"} oninput=${updateSession} /> Chat</label>
<label class="slim"><input type="radio" name="type" value="completion" checked=${session.value.type === "completion"} oninput=${updateSession} /> Completion</label>
</div>
</fieldset>
${session.value.type === 'chat' ? ChatConfigForm() : CompletionConfigForm()} const ModelOptions = () => (
html`
<fieldset class="two"> <details>
${IntField({ label: "Predictions", max: 2048, min: -1, name: "n_predict", value: params.value.n_predict })} <summary>Settings</summary>
${FloatField({ label: "Temperature", max: 2.0, min: 0.0, name: "temperature", step: 0.01, value: params.value.temperature })} <fieldset>
${FloatField({ label: "Penalize repeat sequence", max: 2.0, min: 0.0, name: "repeat_penalty", step: 0.01, value: params.value.repeat_penalty })} ${IntField({ label: "Predictions", max: 2048, min: -1, name: "n_predict", value: params.value.n_predict })}
${IntField({ label: "Consider N tokens for penalize", max: 2048, min: 0, name: "repeat_last_n", value: params.value.repeat_last_n })} ${FloatField({ label: "Temperature", max: 2.0, min: 0.0, name: "temperature", step: 0.01, value: params.value.temperature })}
${IntField({ label: "Top-K sampling", max: 100, min: -1, name: "top_k", value: params.value.top_k })} ${FloatField({ label: "Penalize repeat sequence", max: 2.0, min: 0.0, name: "repeat_penalty", step: 0.01, value: params.value.repeat_penalty })}
${FloatField({ label: "Top-P sampling", max: 1.0, min: 0.0, name: "top_p", step: 0.01, value: params.value.top_p })} ${IntField({ label: "Consider N tokens for penalize", max: 2048, min: 0, name: "repeat_last_n", value: params.value.repeat_last_n })}
${FloatField({ label: "Min-P sampling", max: 1.0, min: 0.0, name: "min_p", step: 0.01, value: params.value.min_p })} ${IntField({ label: "Top-K sampling", max: 100, min: -1, name: "top_k", value: params.value.top_k })}
</fieldset> ${FloatField({ label: "Top-P sampling", max: 1.0, min: 0.0, name: "top_p", step: 0.01, value: params.value.top_p })}
<details> ${FloatField({ label: "Min-P sampling", max: 1.0, min: 0.0, name: "min_p", step: 0.01, value: params.value.min_p })}
${IntField({ label: "Show Probabilities", max: 10, min: 0, name: "n_probs", value: params.value.n_probs })}
</fieldset>
</details>
${GrammarControl()}
<details>
<summary>More options</summary> <summary>More options</summary>
<fieldset class="two">
${FloatField({ label: "TFS-Z", max: 1.0, min: 0.0, name: "tfs_z", step: 0.01, value: params.value.tfs_z })}
${FloatField({ label: "Typical P", max: 1.0, min: 0.0, name: "typical_p", step: 0.01, value: params.value.typical_p })}
${FloatField({ label: "Presence penalty", max: 1.0, min: 0.0, name: "presence_penalty", step: 0.01, value: params.value.presence_penalty })}
${FloatField({ label: "Frequency penalty", max: 1.0, min: 0.0, name: "frequency_penalty", step: 0.01, value: params.value.frequency_penalty })}
</fieldset>
<hr />
<fieldset class="three">
<div>
<label><input type="radio" name="mirostat" value="0" checked=${params.value.mirostat == 0} oninput=${updateParamsInt} /> no Mirostat</label>
<label><input type="radio" name="mirostat" value="1" checked=${params.value.mirostat == 1} oninput=${updateParamsInt} /> Mirostat v1</label>
<label><input type="radio" name="mirostat" value="2" checked=${params.value.mirostat == 2} oninput=${updateParamsInt} /> Mirostat v2</label>
</div>
${FloatField({ label: "Mirostat tau", max: 10.0, min: 0.0, name: "mirostat_tau", step: 0.01, value: params.value.mirostat_tau })}
${FloatField({ label: "Mirostat eta", max: 1.0, min: 0.0, name: "mirostat_eta", step: 0.01, value: params.value.mirostat_eta })}
</fieldset>
<fieldset> <fieldset>
${IntField({ label: "Show Probabilities", max: 10, min: 0, name: "n_probs", value: params.value.n_probs })} ${FloatField({ label: "TFS-Z", max: 1.0, min: 0.0, name: "tfs_z", step: 0.01, value: params.value.tfs_z })}
${FloatField({ label: "Typical P", max: 1.0, min: 0.0, name: "typical_p", step: 0.01, value: params.value.typical_p })}
${FloatField({ label: "Presence penalty", max: 1.0, min: 0.0, name: "presence_penalty", step: 0.01, value: params.value.presence_penalty })}
${FloatField({ label: "Frequency penalty", max: 1.0, min: 0.0, name: "frequency_penalty", step: 0.01, value: params.value.frequency_penalty })}
</fieldset> </fieldset>
<fieldset> </details>
<label for="api_key">API Key</label> <details>
<input type="text" name="api_key" value="${params.value.api_key}" placeholder="Enter API key" oninput=${updateParams} /> <summary>Mirostat</summary>
</fieldset> <fieldset>
</details> <div>
</form> <label><input type="radio" name="mirostat" value="0" checked=${params.value.mirostat == 0} oninput=${updateParamsInt} /> no Mirostat</label>
<label><input type="radio" name="mirostat" value="1" checked=${params.value.mirostat == 1} oninput=${updateParamsInt} /> Mirostat v1</label>
<label><input type="radio" name="mirostat" value="2" checked=${params.value.mirostat == 2} oninput=${updateParamsInt} /> Mirostat v2</label>
</div>
${FloatField({ label: "Mirostat tau", max: 10.0, min: 0.0, name: "mirostat_tau", step: 0.01, value: params.value.mirostat_tau })}
${FloatField({ label: "Mirostat eta", max: 1.0, min: 0.0, name: "mirostat_eta", step: 0.01, value: params.value.mirostat_eta })}
</fieldset>
</details>
` `
);
const ModeSelector = () => (
html`
<div class="header_controls">
<label class="slim"><input type="radio" name="type" value="chat" checked=${session.value.type === "chat"} oninput=${updateSession} /> Chat</label>
<label class="slim"><input type="radio" name="type" value="completion" checked=${session.value.type === "completion"} oninput=${updateSession} /> Completion</label>
<${UserTemplateResetButton}/>
</div>
`
)
if (props?.header)
return html`<${ModeSelector} />`
return html`
<form class="flexform">
<div class="column">
${session.value.type === 'chat' ? ChatConfigForm() : CompletionConfigForm()}
</div>
<div class="column aside">
<${ModelOptions} />
</div>
</form>
`
} }
const probColor = (p) => { const probColor = (p) => {
@ -830,30 +928,30 @@
const pColor = found ? probColor(found.prob) : 'transparent' const pColor = found ? probColor(found.prob) : 'transparent'
const popoverChildren = html` const popoverChildren = html`
<div class="prob-set"> <div class="prob-set">
${probs.map((p, index) => { ${probs.map((p, index) => {
return html` return html`
<div <div
key=${index} key=${index}
title=${`prob: ${p.prob}`} title=${`prob: ${p.prob}`}
style=${{ style=${{
padding: '0.3em', padding: '0.3em',
backgroundColor: p.tok_str === content ? probColor(p.prob) : 'transparent' backgroundColor: p.tok_str === content ? probColor(p.prob) : 'transparent'
}} }}
> >
<span>${p.tok_str}: </span> <span>${p.tok_str}: </span>
<span>${Math.floor(p.prob * 100)}%</span> <span>${Math.floor(p.prob * 100)}%</span>
</div> </div>
` `
})} })}
</div> </div>
` `
return html` return html`
<${Popover} style=${{ backgroundColor: pColor }} popoverChildren=${popoverChildren}> <${Popover} style=${{ backgroundColor: pColor }} popoverChildren=${popoverChildren}>
${msg.content.match(/\n/gim) ? html`<br />` : msg.content} ${msg.content.match(/\n/gim) ? html`<br />` : msg.content}
</> </>
` `
}); });
} }
@ -879,10 +977,10 @@
return html`<span/>` return html`<span/>`
} }
return html` return html`
<span> <span>
${llamaStats.value.predicted_per_token_ms.toFixed()}ms per token, ${llamaStats.value.predicted_per_second.toFixed(2)} tokens per second ${llamaStats.value.predicted_per_token_ms.toFixed()}ms per token, ${llamaStats.value.predicted_per_second.toFixed(2)} tokens per second |
</span> </span>
` `
} }
// simple popover impl // simple popover impl
@ -917,22 +1015,22 @@
}, []); }, []);
return html` return html`
<span style=${props.style} ref=${buttonRef} onClick=${togglePopover}>${props.children}</span> <span style=${props.style} ref=${buttonRef} onClick=${togglePopover}>${props.children}</span>
${isOpen.value && html` ${isOpen.value && html`
<${Portal} into="#portal"> <${Portal} into="#portal">
<div <div
ref=${popoverRef} ref=${popoverRef}
class="popover-content" class="popover-content"
style=${{ style=${{
top: position.value.top, top: position.value.top,
left: position.value.left, left: position.value.left,
}} }}
> >
${props.popoverChildren} ${props.popoverChildren}
</div> </div>
</${Portal}> </${Portal}>
`} `}
`; `;
}; };
// Source: preact-portal (https://github.com/developit/preact-portal/blob/master/src/preact-portal.js) // Source: preact-portal (https://github.com/developit/preact-portal/blob/master/src/preact-portal.js)
@ -975,10 +1073,10 @@
} }
this.remote = render(html` this.remote = render(html`
<${PortalProxy} context=${this.context}> <${PortalProxy} context=${this.context}>
${show && this.props.children || null} ${show && this.props.children || null}
</${PortalProxy}> </${PortalProxy}>
`, this.into, this.remote); `, this.into, this.remote);
} }
render() { render() {
@ -999,29 +1097,29 @@
function App(props) { function App(props) {
return html` return html`
<div class="mode-${session.value.type}"> <div class="basecontainer mode-${session.value.type}">
<header> <header>
<h1>llama.cpp</h1> <h1>llama.cpp</h1>
</header> <${ConfigForm} header/>
</header>
<main id="content"> <main id="content">
<${chatStarted.value ? ChatLog : ConfigForm} /> <${chatStarted.value ? ChatLog : ConfigForm} />
</main> </main>
<section id="write"> <footer>
<${session.value.type === 'chat' ? MessageInput : CompletionControls} /> <section id="write">
</section> <${session.value.type === 'chat' ? MessageInput : CompletionControls} />
</section>
<footer> <p><${ModelGenerationInfo} /> <span class="footernote">Powered by <a href="https://github.com/ggerganov/llama.cpp">llama.cpp</a> and <a href="https://ggml.ai">ggml.ai</a></span></p>
<p><${ModelGenerationInfo} /></p> </footer>
<p>Powered by <a href="https://github.com/ggerganov/llama.cpp">llama.cpp</a> and <a href="https://ggml.ai">ggml.ai</a>.</p> </div>
</footer> `;
</div>
`;
} }
render(h(App), document.querySelector('#container')); render(h(App), document.querySelector('#container'));
</script> </script>
</head> </head>
<body> <body>
@ -1032,4 +1130,3 @@
</body> </body>
</html> </html>