aboutsummaryrefslogtreecommitdiff
path: root/examples/server/public/index.html
diff options
context:
space:
mode:
Diffstat (limited to 'examples/server/public/index.html')
-rw-r--r--examples/server/public/index.html359
1 files changed, 359 insertions, 0 deletions
diff --git a/examples/server/public/index.html b/examples/server/public/index.html
new file mode 100644
index 0000000..6393e2e
--- /dev/null
+++ b/examples/server/public/index.html
@@ -0,0 +1,359 @@
+<html>
+
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
+ <title>llama.cpp - chat</title>
+
+ <style>
+
+ body {
+ background-color: #fff;
+ color: #000;
+ font-family: system-ui;
+ font-size: 90%;
+ }
+
+ #container {
+ margin: 0em auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 100%;
+ }
+
+ header, footer {
+ text-align: center;
+ }
+
+ main {
+ margin: 3px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ gap: 1em;
+
+ flex-grow: 1;
+ overflow-y: auto;
+
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ padding: 0.5em;
+ }
+
+ body {
+ max-width: 600px;
+ min-width: 300px;
+ line-height: 1.2;
+ margin: 0 auto;
+ padding: 0 0.5em;
+ }
+
+ p {
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ hyphens: auto;
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+ }
+
+ #write form {
+ margin: 1em 0 0 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5em;
+ align-items: stretch;
+ }
+
+ .right {
+ display: flex;
+ flex-direction: row;
+ gap: 0.5em;
+ justify-content: flex-end;
+ }
+
+ fieldset {
+ border: none;
+ padding: 0;
+ margin: 0;
+ }
+
+ textarea {
+ padding: 5px;
+ flex-grow: 1;
+ width: 100%;
+ }
+
+ pre code {
+ display: block;
+ background-color: #222;
+ color: #ddd;
+ }
+ code {
+ font-family: monospace;
+ padding: 0.1em 0.3em;
+ border-radius: 3px;
+ }
+
+ fieldset label {
+ margin: 0.5em 0;
+ display: block;
+ }
+ </style>
+
+ <script type="module">
+ import {
+ html, h, signal, effect, computed, render, useSignal, useEffect, useRef
+ } from '/index.js';
+
+ import { llamaComplete } from '/completion.js';
+
+ const session = signal({
+ prompt: "This is a conversation between user and llama, a friendly chatbot. respond in markdown.",
+ template: "{{prompt}}\n\n{{history}}\n{{char}}:",
+ historyTemplate: "{{name}}: {{message}}",
+ transcript: [],
+ type: "chat",
+ char: "llama",
+ user: "User",
+ })
+
+ const transcriptUpdate = (transcript) => {
+ session.value = {
+ ...session.value,
+ transcript
+ }
+ }
+
+ const chatStarted = computed(() => session.value.transcript.length > 0)
+
+ const params = signal({
+ n_predict: 400,
+ temperature: 0.7,
+ repeat_last_n: 256,
+ repeat_penalty: 1.18,
+ top_k: 40,
+ top_p: 0.5,
+ })
+
+ const controller = signal(null)
+ const generating = computed(() => controller.value == null )
+
+ // simple template replace
+ const template = (str, extraSettings) => {
+ let settings = session.value;
+ if (extraSettings) {
+ settings = { ...settings, ...extraSettings };
+ }
+ return String(str).replaceAll(/\{\{(.*?)\}\}/g, (_, key) => template(settings[key]));
+ }
+
+ // send message to server
+ const chat = async (msg) => {
+ if (controller.value) {
+ console.log('already running...');
+ return;
+ }
+ controller.value = new AbortController();
+
+ transcriptUpdate([...session.value.transcript, ["{{user}}", msg]])
+
+ const payload = template(session.value.template, {
+ message: msg,
+ history: session.value.transcript.flatMap(([name, message]) => template(session.value.historyTemplate, {name, message})).join("\n"),
+ });
+
+ let currentMessage = '';
+ const history = session.value.transcript
+
+ const llamaParams = {
+ ...params.value,
+ prompt: payload,
+ stop: ["</s>", template("{{char}}:"), template("{{user}}:")],
+ }
+
+ await llamaComplete(llamaParams, controller.value, (message) => {
+ const data = message.data;
+ currentMessage += data.content;
+ // remove leading whitespace
+ currentMessage = currentMessage.replace(/^\s+/, "")
+
+ transcriptUpdate([...history, ["{{char}}", currentMessage]])
+
+ if (data.stop) {
+ console.log("-->", data, ' response was:', currentMessage, 'transcript state:', session.value.transcript);
+ }
+ })
+
+ controller.value = null;
+ }
+
+ function MessageInput() {
+ const message = useSignal("")
+
+ const stop = (e) => {
+ e.preventDefault();
+ if (controller.value) {
+ controller.value.abort();
+ controller.value = null;
+ }
+ }
+
+ const reset = (e) => {
+ stop(e);
+ transcriptUpdate([]);
+ }
+
+ const submit = (e) => {
+ stop(e);
+ chat(message.value);
+ message.value = "";
+ }
+
+ const enterSubmits = (event) => {
+ if (event.which === 13 && !event.shiftKey) {
+ submit(event);
+ }
+ }
+
+ return html`
+ <form onsubmit=${submit}>
+ <div>
+ <textarea type="text" rows=2 onkeypress=${enterSubmits} value="${message}" oninput=${(e) => message.value = e.target.value} placeholder="Say something..."/>
+
+ </div>
+ <div class="right">
+ <button type="submit" disabled=${!generating.value} >Send</button>
+ <button onclick=${stop} disabled=${generating}>Stop</button>
+ <button onclick=${reset}>Reset</button>
+ </div>
+ </form>
+ `
+ }
+
+ const ChatLog = (props) => {
+ const messages = session.value.transcript;
+ const container = useRef(null)
+
+ useEffect(() => {
+ // scroll to bottom (if needed)
+ if (container.current && container.current.scrollHeight <= container.current.scrollTop + container.current.offsetHeight + 300) {
+ container.current.scrollTo(0, container.current.scrollHeight)
+ }
+ }, [messages])
+
+ const chatLine = ([user, msg]) => {
+ return html`<p key=${msg}><strong>${template(user)}:</strong> <${Markdown} text=${template(msg)} /></p>`
+ };
+
+ return html`
+ <section id="chat" ref=${container}>
+ ${messages.flatMap(chatLine)}
+ </section>`;
+ };
+
+ const ConfigForm = (props) => {
+ const updateSession = (el) => session.value = { ...session.value, [el.target.name]: el.target.value }
+ const updateParams = (el) => params.value = { ...params.value, [el.target.name]: el.target.value }
+ const updateParamsFloat = (el) => params.value = { ...params.value, [el.target.name]: parseFloat(el.target.value) }
+
+ return html`
+ <form>
+ <fieldset>
+ <div>
+ <label for="prompt">Prompt</label>
+ <textarea type="text" name="prompt" value="${session.value.prompt}" rows=4 oninput=${updateSession}/>
+ </div>
+
+ <div>
+ <label for="user">User name</label>
+ <input type="text" name="user" value="${session.value.user}" oninput=${updateSession} />
+ </div>
+
+ <div>
+ <label for="bot">Bot name</label>
+ <input type="text" name="char" value="${session.value.char}" oninput=${updateSession} />
+ </div>
+
+ <div>
+ <label for="template">Prompt template</label>
+ <textarea id="template" name="template" value="${session.value.template}" rows=4 oninput=${updateSession}/>
+ </div>
+
+ <div>
+ <label for="template">Chat history template</label>
+ <textarea id="template" name="historyTemplate" value="${session.value.historyTemplate}" rows=1 oninput=${updateSession}/>
+ </div>
+
+ <div>
+ <label for="temperature">Temperature</label>
+ <input type="range" id="temperature" min="0.0" max="1.0" step="0.01" name="temperature" value="${params.value.temperature}" oninput=${updateParamsFloat} />
+ <span>${params.value.temperature}</span>
+ </div>
+
+ <div>
+ <label for="nPredict">Predictions</label>
+ <input type="range" id="nPredict" min="1" max="2048" step="1" name="n_predict" value="${params.value.n_predict}" oninput=${updateParamsFloat} />
+ <span>${params.value.n_predict}</span>
+ </div>
+
+ <div>
+ <label for="repeat_penalty">Penalize repeat sequence</label>
+ <input type="range" id="repeat_penalty" min="0.0" max="2.0" step="0.01" name="repeat_penalty" value="${params.value.repeat_penalty}" oninput=${updateParamsFloat} />
+ <span>${params.value.repeat_penalty}</span>
+ </div>
+
+ <div>
+ <label for="repeat_last_n">Consider N tokens for penalize</label>
+ <input type="range" id="repeat_last_n" min="0.0" max="2048" name="repeat_last_n" value="${params.value.repeat_last_n}" oninput=${updateParamsFloat} />
+ <span>${params.value.repeat_last_n}</span>
+ </div>
+
+ </fieldset>
+ </form>
+ `
+ }
+const Markdown = (params) => {
+ const md = params.text
+ .replace(/^#{1,6} (.*)$/gim, '<h3>$1</h3>')
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
+ .replace(/__(.*?)__/g, '<strong>$1</strong>')
+ .replace(/\*(.*?)\*/g, '<em>$1</em>')
+ .replace(/_(.*?)_/g, '<em>$1</em>')
+ .replace(/```.*?\n([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
+ .replace(/`(.*?)`/g, '<code>$1</code>')
+ .replace(/\n/gim, '<br />');
+ return html`<span dangerouslySetInnerHTML=${{ __html: md }} />`;
+};
+
+ function App(props) {
+
+ return html`
+ <div id="container">
+ <header>
+ <h1>llama.cpp</h1>
+ </header>
+
+ <main id="content">
+ <${chatStarted.value ? ChatLog : ConfigForm} />
+ </main>
+
+ <footer id="write">
+ <${MessageInput} />
+ </footer>
+
+ <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>
+ </footer>
+ </div>
+ `;
+ }
+
+ render(h(App), document.body);
+ </script>
+</head>
+
+<body>
+</body>
+
+</html>