mirror of
https://github.com/coalaura/whiskr.git
synced 2025-09-09 09:19:54 +00:00
json & web search
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
$temperature = document.getElementById("temperature"),
|
||||
$reasoningEffort = document.getElementById("reasoning-effort"),
|
||||
$reasoningTokens = document.getElementById("reasoning-tokens"),
|
||||
$json = document.getElementById("json"),
|
||||
$search = document.getElementById("search"),
|
||||
$add = document.getElementById("add"),
|
||||
$send = document.getElementById("send"),
|
||||
$scrolling = document.getElementById("scrolling"),
|
||||
@@ -18,6 +20,8 @@
|
||||
models = {};
|
||||
|
||||
let autoScrolling = false,
|
||||
jsonMode = false,
|
||||
searchTool = false,
|
||||
interacted = false;
|
||||
|
||||
function scroll(force = false) {
|
||||
@@ -27,7 +31,7 @@
|
||||
|
||||
setTimeout(() => {
|
||||
$messages.scroll({
|
||||
top: $messages.scrollHeight + 200,
|
||||
top: $messages.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}, 0);
|
||||
@@ -39,6 +43,9 @@
|
||||
#reasoning;
|
||||
#text;
|
||||
|
||||
#tags = [];
|
||||
#error = false;
|
||||
|
||||
#editing = false;
|
||||
#expanded = false;
|
||||
#state = false;
|
||||
@@ -48,7 +55,7 @@
|
||||
#patching = {};
|
||||
|
||||
#_message;
|
||||
#_role;
|
||||
#_tags;
|
||||
#_reasoning;
|
||||
#_text;
|
||||
#_edit;
|
||||
@@ -75,10 +82,22 @@
|
||||
// main message div
|
||||
this.#_message = make("div", "message", this.#role);
|
||||
|
||||
// message role
|
||||
this.#_role = make("div", "role", this.#role);
|
||||
// message role (wrapper)
|
||||
const _wrapper = make("div", "role", this.#role);
|
||||
|
||||
this.#_message.appendChild(this.#_role);
|
||||
this.#_message.appendChild(_wrapper);
|
||||
|
||||
// message role
|
||||
const _role = make("div");
|
||||
|
||||
_role.textContent = this.#role;
|
||||
|
||||
_wrapper.appendChild(_role);
|
||||
|
||||
// message tags
|
||||
this.#_tags = make("div", "tags");
|
||||
|
||||
_wrapper.appendChild(this.#_tags);
|
||||
|
||||
// message reasoning (wrapper)
|
||||
const _reasoning = make("div", "reasoning");
|
||||
@@ -233,8 +252,17 @@
|
||||
}
|
||||
|
||||
#render(only = false, noScroll = false) {
|
||||
if (!only || only === "role") {
|
||||
this.#_role.textContent = this.#role;
|
||||
if (!only || only === "tags") {
|
||||
console.log(this.#tags);
|
||||
this.#_tags.innerHTML = this.#tags
|
||||
.map((tag) => `<div class="tag-${tag}" title="${tag}"></div>`)
|
||||
.join("");
|
||||
|
||||
this.#_message.classList.toggle("has-tags", this.#tags.length > 0);
|
||||
}
|
||||
|
||||
if (this.#error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!only || only === "reasoning") {
|
||||
@@ -251,7 +279,13 @@
|
||||
}
|
||||
|
||||
if (!only || only === "text") {
|
||||
this.#patch("text", this.#_text, this.#text, () => {
|
||||
let text = this.#text;
|
||||
|
||||
if (this.#tags.includes("json")) {
|
||||
text = `\`\`\`json\n${text}\n\`\`\``;
|
||||
}
|
||||
|
||||
this.#patch("text", this.#_text, text, () => {
|
||||
noScroll || scroll();
|
||||
});
|
||||
|
||||
@@ -262,23 +296,47 @@
|
||||
#save() {
|
||||
storeValue(
|
||||
"messages",
|
||||
messages.map((message) => message.getData(true)),
|
||||
messages.map((message) => message.getData(true)).filter(Boolean),
|
||||
);
|
||||
}
|
||||
|
||||
getData(includeReasoning = false) {
|
||||
getData(full = false) {
|
||||
const data = {
|
||||
role: this.#role,
|
||||
text: this.#text,
|
||||
};
|
||||
|
||||
if (this.#reasoning && includeReasoning) {
|
||||
if (this.#reasoning && full) {
|
||||
data.reasoning = this.#reasoning;
|
||||
}
|
||||
|
||||
if (this.#error && full) {
|
||||
data.error = this.#error;
|
||||
}
|
||||
|
||||
if (this.#tags.length && full) {
|
||||
data.tags = this.#tags;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
addTag(tag) {
|
||||
if (this.#tags.includes(tag)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#tags.push(tag);
|
||||
|
||||
this.#render("tags");
|
||||
|
||||
if (tag === "json") {
|
||||
this.#render("text");
|
||||
}
|
||||
|
||||
this.#save();
|
||||
}
|
||||
|
||||
setState(state) {
|
||||
if (this.#state === state) {
|
||||
return;
|
||||
@@ -309,6 +367,20 @@
|
||||
this.#save();
|
||||
}
|
||||
|
||||
showError(error) {
|
||||
this.#error = error;
|
||||
|
||||
this.#_message.classList.add("errored");
|
||||
|
||||
const _err = make("div", "error");
|
||||
|
||||
_err.textContent = this.#error;
|
||||
|
||||
this.#_text.appendChild(_err);
|
||||
|
||||
this.#save();
|
||||
}
|
||||
|
||||
stopEdit() {
|
||||
if (!this.#editing) {
|
||||
return;
|
||||
@@ -379,7 +451,9 @@
|
||||
const response = await fetch(url, options);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
const err = await response.json();
|
||||
|
||||
throw new Error(err?.error || response.statusText);
|
||||
}
|
||||
|
||||
const reader = response.body.getReader(),
|
||||
@@ -461,7 +535,7 @@
|
||||
models[model.id] = model;
|
||||
}
|
||||
|
||||
dropdown($model);
|
||||
dropdown($model, 4);
|
||||
|
||||
return modelList;
|
||||
}
|
||||
@@ -474,13 +548,29 @@
|
||||
$reasoningEffort.value = loadValue("reasoning-effort", "medium");
|
||||
$reasoningTokens.value = loadValue("reasoning-tokens", 1024);
|
||||
|
||||
if (loadValue("json")) {
|
||||
$json.click();
|
||||
}
|
||||
|
||||
if (loadValue("search")) {
|
||||
$search.click();
|
||||
}
|
||||
|
||||
if (loadValue("scrolling")) {
|
||||
$scrolling.click();
|
||||
}
|
||||
|
||||
loadValue("messages", []).forEach(
|
||||
(message) => new Message(message.role, message.reasoning, message.text),
|
||||
);
|
||||
loadValue("messages", []).forEach((message) => {
|
||||
const obj = new Message(message.role, message.reasoning, message.text);
|
||||
|
||||
if (message.error) {
|
||||
obj.showError(message.error);
|
||||
}
|
||||
|
||||
if (message.tags) {
|
||||
message.tags.forEach((tag) => obj.addTag(tag));
|
||||
}
|
||||
});
|
||||
|
||||
scroll(true);
|
||||
|
||||
@@ -537,6 +627,8 @@
|
||||
$reasoningEffort.parentNode.classList.add("none");
|
||||
$reasoningTokens.parentNode.classList.add("none");
|
||||
}
|
||||
|
||||
$json.classList.toggle("none", !data?.tags.includes("json"));
|
||||
});
|
||||
|
||||
$prompt.addEventListener("change", () => {
|
||||
@@ -544,7 +636,15 @@
|
||||
});
|
||||
|
||||
$temperature.addEventListener("input", () => {
|
||||
storeValue("temperature", $temperature.value);
|
||||
const value = $temperature.value,
|
||||
temperature = parseFloat(value);
|
||||
|
||||
storeValue("temperature", value);
|
||||
|
||||
$temperature.classList.toggle(
|
||||
"invalid",
|
||||
Number.isNaN(temperature) || temperature < 0 || temperature > 2,
|
||||
);
|
||||
});
|
||||
|
||||
$reasoningEffort.addEventListener("change", () => {
|
||||
@@ -555,8 +655,32 @@
|
||||
$reasoningTokens.parentNode.classList.toggle("none", !!effort);
|
||||
});
|
||||
|
||||
$reasoningTokens.addEventListener("change", () => {
|
||||
storeValue("reasoning-tokens", $reasoningTokens.value);
|
||||
$reasoningTokens.addEventListener("input", () => {
|
||||
const value = $reasoningTokens.value,
|
||||
tokens = parseInt(value);
|
||||
|
||||
storeValue("reasoning-tokens", value);
|
||||
|
||||
$reasoningTokens.classList.toggle(
|
||||
"invalid",
|
||||
Number.isNaN(tokens) || tokens <= 0 || tokens > 1024 * 1024,
|
||||
);
|
||||
});
|
||||
|
||||
$json.addEventListener("click", () => {
|
||||
jsonMode = !jsonMode;
|
||||
|
||||
storeValue("json", jsonMode);
|
||||
|
||||
$json.classList.toggle("on", jsonMode);
|
||||
});
|
||||
|
||||
$search.addEventListener("click", () => {
|
||||
searchTool = !searchTool;
|
||||
|
||||
storeValue("search", searchTool);
|
||||
|
||||
$search.classList.toggle("on", searchTool);
|
||||
});
|
||||
|
||||
$message.addEventListener("input", () => {
|
||||
@@ -589,6 +713,8 @@
|
||||
if (autoScrolling) {
|
||||
$scrolling.title = "Turn off auto-scrolling";
|
||||
$scrolling.classList.add("on");
|
||||
|
||||
scroll();
|
||||
} else {
|
||||
$scrolling.title = "Turn on auto-scrolling";
|
||||
$scrolling.classList.remove("on");
|
||||
@@ -612,7 +738,7 @@
|
||||
|
||||
const temperature = parseFloat($temperature.value);
|
||||
|
||||
if (Number.isNaN(temperature) || temperature < 0 || temperature > 1) {
|
||||
if (Number.isNaN(temperature) || temperature < 0 || temperature > 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -640,6 +766,8 @@
|
||||
effort: effort,
|
||||
tokens: tokens || 0,
|
||||
},
|
||||
json: jsonMode,
|
||||
search: searchTool,
|
||||
messages: messages.map((message) => message.getData()),
|
||||
};
|
||||
|
||||
@@ -647,6 +775,14 @@
|
||||
|
||||
message.setState("waiting");
|
||||
|
||||
if (jsonMode) {
|
||||
message.addTag("json");
|
||||
}
|
||||
|
||||
if (searchTool) {
|
||||
message.addTag("search");
|
||||
}
|
||||
|
||||
stream(
|
||||
"/-/chat",
|
||||
{
|
||||
@@ -678,6 +814,10 @@
|
||||
message.setState("receiving");
|
||||
message.addText(chunk.text);
|
||||
|
||||
break;
|
||||
case "error":
|
||||
message.showError(chunk.text);
|
||||
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
@@ -5,13 +5,15 @@
|
||||
#_selected;
|
||||
#_search;
|
||||
|
||||
#maxTags = false;
|
||||
#search = false;
|
||||
#selected = false;
|
||||
#options = [];
|
||||
|
||||
constructor(el) {
|
||||
constructor(el, maxTags = false) {
|
||||
this.#_select = el;
|
||||
|
||||
this.#maxTags = maxTags;
|
||||
this.#search = "searchable" in el.dataset;
|
||||
|
||||
this.#_select.querySelectorAll("option").forEach((option) => {
|
||||
@@ -101,15 +103,23 @@
|
||||
_opt.appendChild(_label);
|
||||
|
||||
// option tags (optional)
|
||||
if (option.tags?.length) {
|
||||
const tags = option.tags;
|
||||
|
||||
if (option.tags.length) {
|
||||
const _tags = make("div", "tags");
|
||||
|
||||
for (const tag of option.tags) {
|
||||
const _tag = make("div", "tag", tag);
|
||||
_tags.title = `${this.#maxTags ? `${tags.length}/${this.#maxTags}: ` : ""}${tags.join(", ")}`;
|
||||
|
||||
_tag.title = tag;
|
||||
if (this.#maxTags && tags.length >= this.#maxTags) {
|
||||
const _all = make("div", "tag", "all");
|
||||
|
||||
_tags.appendChild(_tag);
|
||||
_tags.appendChild(_all);
|
||||
} else {
|
||||
for (const tag of tags) {
|
||||
const _tag = make("div", "tag", tag);
|
||||
|
||||
_tags.appendChild(_tag);
|
||||
}
|
||||
}
|
||||
|
||||
_opt.appendChild(_tags);
|
||||
@@ -220,5 +230,5 @@
|
||||
});
|
||||
});
|
||||
|
||||
window.dropdown = (el) => new Dropdown(el);
|
||||
window.dropdown = (el, maxTags = false) => new Dropdown(el, maxTags);
|
||||
})();
|
||||
|
@@ -1,5 +1,7 @@
|
||||
function storeValue(key, value) {
|
||||
if (!value) {
|
||||
/** biome-ignore-all lint/correctness/noUnusedVariables: utility */
|
||||
|
||||
function storeValue(key, value = false) {
|
||||
if (value === null || value === undefined || value === false) {
|
||||
localStorage.removeItem(key);
|
||||
|
||||
return;
|
||||
@@ -11,14 +13,14 @@ function storeValue(key, value) {
|
||||
function loadValue(key, fallback = false) {
|
||||
const raw = localStorage.getItem(key);
|
||||
|
||||
if (!raw) {
|
||||
if (raw === null) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
try {
|
||||
const value = JSON.parse(raw);
|
||||
|
||||
if (!value) {
|
||||
if (value === null) {
|
||||
throw new Error("no value");
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user