move new ui to "/public" due to otherwise problematic CORS behaviour
This commit is contained in:
parent
8b937a1a71
commit
505d0a3346
18 changed files with 2 additions and 306 deletions
|
@ -9,7 +9,7 @@
|
||||||
<title>llama.cpp - chat</title>
|
<title>llama.cpp - chat</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
<link rel="stylesheet" href="css-styles/style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import {
|
import {
|
||||||
|
@ -1067,7 +1067,7 @@ return html`
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button class="dropbtn"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" stroke-width="2"/></svg></button>
|
<button class="dropbtn"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" stroke-width="2"/></svg></button>
|
||||||
<div class="dropdown-content" id="theme-selector">
|
<div class="dropdown-content" id="theme-selector">
|
||||||
<!-- <a href="index.html">Old UI</a> -->
|
<a href="index.html">Old UI</a>
|
||||||
<a href="#" data-theme="default">Snow Storm</a>
|
<a href="#" data-theme="default">Snow Storm</a>
|
||||||
<a href="#" data-theme="polarnight">Polar Night</a>
|
<a href="#" data-theme="polarnight">Polar Night</a>
|
||||||
<a href="#" data-theme="ketivah">Ketivah</a>
|
<a href="#" data-theme="ketivah">Ketivah</a>
|
|
@ -1,191 +0,0 @@
|
||||||
const paramDefaults = {
|
|
||||||
stream: true,
|
|
||||||
n_predict: 500,
|
|
||||||
temperature: 0.2,
|
|
||||||
stop: ["</s>"]
|
|
||||||
};
|
|
||||||
|
|
||||||
let generation_settings = null;
|
|
||||||
|
|
||||||
|
|
||||||
// Completes the prompt as a generator. Recommended for most use cases.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// import { llama } from '/completion.js'
|
|
||||||
//
|
|
||||||
// const request = llama("Tell me a joke", {n_predict: 800})
|
|
||||||
// for await (const chunk of request) {
|
|
||||||
// document.write(chunk.data.content)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
export async function* llama(prompt, params = {}, config = {}) {
|
|
||||||
let controller = config.controller;
|
|
||||||
|
|
||||||
if (!controller) {
|
|
||||||
controller = new AbortController();
|
|
||||||
}
|
|
||||||
|
|
||||||
const completionParams = { ...paramDefaults, ...params, prompt };
|
|
||||||
|
|
||||||
const response = await fetch("/completion", {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(completionParams),
|
|
||||||
headers: {
|
|
||||||
'Connection': 'keep-alive',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'text/event-stream'
|
|
||||||
},
|
|
||||||
signal: controller.signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
|
|
||||||
let content = "";
|
|
||||||
let leftover = ""; // Buffer for partially read lines
|
|
||||||
|
|
||||||
try {
|
|
||||||
let cont = true;
|
|
||||||
|
|
||||||
while (cont) {
|
|
||||||
const result = await reader.read();
|
|
||||||
if (result.done) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add any leftover data to the current chunk of data
|
|
||||||
const text = leftover + decoder.decode(result.value);
|
|
||||||
|
|
||||||
// Check if the last character is a line break
|
|
||||||
const endsWithLineBreak = text.endsWith('\n');
|
|
||||||
|
|
||||||
// Split the text into lines
|
|
||||||
let lines = text.split('\n');
|
|
||||||
|
|
||||||
// If the text doesn't end with a line break, then the last line is incomplete
|
|
||||||
// Store it in leftover to be added to the next chunk of data
|
|
||||||
if (!endsWithLineBreak) {
|
|
||||||
leftover = lines.pop();
|
|
||||||
} else {
|
|
||||||
leftover = ""; // Reset leftover if we have a line break at the end
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse all sse events and add them to result
|
|
||||||
const regex = /^(\S+):\s(.*)$/gm;
|
|
||||||
for (const line of lines) {
|
|
||||||
const match = regex.exec(line);
|
|
||||||
if (match) {
|
|
||||||
result[match[1]] = match[2]
|
|
||||||
// since we know this is llama.cpp, let's just decode the json in data
|
|
||||||
if (result.data) {
|
|
||||||
result.data = JSON.parse(result.data);
|
|
||||||
content += result.data.content;
|
|
||||||
|
|
||||||
// yield
|
|
||||||
yield result;
|
|
||||||
|
|
||||||
// if we got a stop token from server, we will break here
|
|
||||||
if (result.data.stop) {
|
|
||||||
if (result.data.generation_settings) {
|
|
||||||
generation_settings = result.data.generation_settings;
|
|
||||||
}
|
|
||||||
cont = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result.error) {
|
|
||||||
result.error = JSON.parse(result.error);
|
|
||||||
console.error(`llama.cpp error: ${result.error.content}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e.name !== 'AbortError') {
|
|
||||||
console.error("llama error: ", e);
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
controller.abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call llama, return an event target that you can subcribe to
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// import { llamaEventTarget } from '/completion.js'
|
|
||||||
//
|
|
||||||
// const conn = llamaEventTarget(prompt)
|
|
||||||
// conn.addEventListener("message", (chunk) => {
|
|
||||||
// document.write(chunk.detail.content)
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
export const llamaEventTarget = (prompt, params = {}, config = {}) => {
|
|
||||||
const eventTarget = new EventTarget();
|
|
||||||
(async () => {
|
|
||||||
let content = "";
|
|
||||||
for await (const chunk of llama(prompt, params, config)) {
|
|
||||||
if (chunk.data) {
|
|
||||||
content += chunk.data.content;
|
|
||||||
eventTarget.dispatchEvent(new CustomEvent("message", { detail: chunk.data }));
|
|
||||||
}
|
|
||||||
if (chunk.data.generation_settings) {
|
|
||||||
eventTarget.dispatchEvent(new CustomEvent("generation_settings", { detail: chunk.data.generation_settings }));
|
|
||||||
}
|
|
||||||
if (chunk.data.timings) {
|
|
||||||
eventTarget.dispatchEvent(new CustomEvent("timings", { detail: chunk.data.timings }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
eventTarget.dispatchEvent(new CustomEvent("done", { detail: { content } }));
|
|
||||||
})();
|
|
||||||
return eventTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call llama, return a promise that resolves to the completed text. This does not support streaming
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// llamaPromise(prompt).then((content) => {
|
|
||||||
// document.write(content)
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// or
|
|
||||||
//
|
|
||||||
// const content = await llamaPromise(prompt)
|
|
||||||
// document.write(content)
|
|
||||||
//
|
|
||||||
export const llamaPromise = (prompt, params = {}, config = {}) => {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
let content = "";
|
|
||||||
try {
|
|
||||||
for await (const chunk of llama(prompt, params, config)) {
|
|
||||||
content += chunk.data.content;
|
|
||||||
}
|
|
||||||
resolve(content);
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (deprecated)
|
|
||||||
*/
|
|
||||||
export const llamaComplete = async (params, controller, callback) => {
|
|
||||||
for await (const chunk of llama(params.prompt, params, { controller })) {
|
|
||||||
callback(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the model info from the server. This is useful for getting the context window and so on.
|
|
||||||
export const llamaModelInfo = async () => {
|
|
||||||
if (!generation_settings) {
|
|
||||||
generation_settings = await fetch("/model.json").then(r => r.json());
|
|
||||||
}
|
|
||||||
return generation_settings;
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 34 KiB |
File diff suppressed because one or more lines are too long
|
@ -1,112 +0,0 @@
|
||||||
const SPACE_RULE = '" "?';
|
|
||||||
|
|
||||||
const PRIMITIVE_RULES = {
|
|
||||||
boolean: '("true" | "false") space',
|
|
||||||
number: '("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space',
|
|
||||||
integer: '("-"? ([0-9] | [1-9] [0-9]*)) space',
|
|
||||||
string: ` "\\"" (
|
|
||||||
[^"\\\\] |
|
|
||||||
"\\\\" (["\\\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
|
|
||||||
)* "\\"" space`,
|
|
||||||
null: '"null" space',
|
|
||||||
};
|
|
||||||
|
|
||||||
const INVALID_RULE_CHARS_RE = /[^\dA-Za-z-]+/g;
|
|
||||||
const GRAMMAR_LITERAL_ESCAPE_RE = /[\n\r"]/g;
|
|
||||||
const GRAMMAR_LITERAL_ESCAPES = {'\r': '\\r', '\n': '\\n', '"': '\\"'};
|
|
||||||
|
|
||||||
export class SchemaConverter {
|
|
||||||
constructor(propOrder) {
|
|
||||||
this._propOrder = propOrder || {};
|
|
||||||
this._rules = new Map();
|
|
||||||
this._rules.set('space', SPACE_RULE);
|
|
||||||
}
|
|
||||||
|
|
||||||
_formatLiteral(literal) {
|
|
||||||
const escaped = JSON.stringify(literal).replace(
|
|
||||||
GRAMMAR_LITERAL_ESCAPE_RE,
|
|
||||||
m => GRAMMAR_LITERAL_ESCAPES[m]
|
|
||||||
);
|
|
||||||
return `"${escaped}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_addRule(name, rule) {
|
|
||||||
let escName = name.replace(INVALID_RULE_CHARS_RE, '-');
|
|
||||||
let key = escName;
|
|
||||||
|
|
||||||
if (this._rules.has(escName)) {
|
|
||||||
if (this._rules.get(escName) === rule) {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
while (this._rules.has(`${escName}${i}`)) {
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
key = `${escName}${i}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._rules.set(key, rule);
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
visit(schema, name) {
|
|
||||||
const schemaType = schema.type;
|
|
||||||
const ruleName = name || 'root';
|
|
||||||
|
|
||||||
if (schema.oneOf || schema.anyOf) {
|
|
||||||
const rule = (schema.oneOf || schema.anyOf).map((altSchema, i) =>
|
|
||||||
this.visit(altSchema, `${name}${name ? "-" : ""}${i}`)
|
|
||||||
).join(' | ');
|
|
||||||
|
|
||||||
return this._addRule(ruleName, rule);
|
|
||||||
} else if ('const' in schema) {
|
|
||||||
return this._addRule(ruleName, this._formatLiteral(schema.const));
|
|
||||||
} else if ('enum' in schema) {
|
|
||||||
const rule = schema.enum.map(v => this._formatLiteral(v)).join(' | ');
|
|
||||||
return this._addRule(ruleName, rule);
|
|
||||||
} else if (schemaType === 'object' && 'properties' in schema) {
|
|
||||||
// TODO: `required` keyword (from python implementation)
|
|
||||||
const propOrder = this._propOrder;
|
|
||||||
const propPairs = Object.entries(schema.properties).sort((a, b) => {
|
|
||||||
// sort by position in prop_order (if specified) then by key
|
|
||||||
const orderA = typeof propOrder[a[0]] === 'number' ? propOrder[a[0]] : Infinity;
|
|
||||||
const orderB = typeof propOrder[b[0]] === 'number' ? propOrder[b[0]] : Infinity;
|
|
||||||
return orderA - orderB || a[0].localeCompare(b[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
let rule = '"{" space';
|
|
||||||
propPairs.forEach(([propName, propSchema], i) => {
|
|
||||||
const propRuleName = this.visit(propSchema, `${name}${name ? "-" : ""}${propName}`);
|
|
||||||
if (i > 0) {
|
|
||||||
rule += ' "," space';
|
|
||||||
}
|
|
||||||
rule += ` ${this._formatLiteral(propName)} space ":" space ${propRuleName}`;
|
|
||||||
});
|
|
||||||
rule += ' "}" space';
|
|
||||||
|
|
||||||
return this._addRule(ruleName, rule);
|
|
||||||
} else if (schemaType === 'array' && 'items' in schema) {
|
|
||||||
// TODO `prefixItems` keyword (from python implementation)
|
|
||||||
const itemRuleName = this.visit(schema.items, `${name}${name ? "-" : ""}item`);
|
|
||||||
const rule = `"[" space (${itemRuleName} ("," space ${itemRuleName})*)? "]" space`;
|
|
||||||
return this._addRule(ruleName, rule);
|
|
||||||
} else {
|
|
||||||
if (!PRIMITIVE_RULES[schemaType]) {
|
|
||||||
throw new Error(`Unrecognized schema: ${JSON.stringify(schema)}`);
|
|
||||||
}
|
|
||||||
return this._addRule(
|
|
||||||
ruleName === 'root' ? 'root' : schemaType,
|
|
||||||
PRIMITIVE_RULES[schemaType]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
formatGrammar() {
|
|
||||||
let grammar = '';
|
|
||||||
this._rules.forEach((rule, name) => {
|
|
||||||
grammar += `${name} ::= ${rule}\n`;
|
|
||||||
});
|
|
||||||
return grammar;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue