cchat/static/js/index.js
2024-07-19 03:12:21 +02:00

211 lines
6.1 KiB
JavaScript

document.addEventListener("alpine:init", () => {
Alpine.data("state", () => ({
// current state
cstate: {
time: null,
messages: [],
},
// historical state
histories: JSON.parse(localStorage.getItem("histories")) || [],
home: 0,
generating: false,
endpoint: window.location.origin + "/v1",
model: "llama3-8b-8192", // This doesn't matter anymore as the backend handles it now
stopToken: "<|eot_id|>", // We may need this for some models
// performance tracking
time_till_first: 0,
tokens_per_second: 0,
total_tokens: 0,
// New property for error messages
errorMessage: null,
removeHistory(cstate) {
const index = this.histories.findIndex((state) => {
return state.time === cstate.time;
});
if (index !== -1) {
this.histories.splice(index, 1);
localStorage.setItem("histories", JSON.stringify(this.histories));
}
},
async handleSend() {
const el = document.getElementById("input-form");
const value = el.value.trim();
if (!value) return;
if (this.generating) return;
this.generating = true;
this.errorMessage = null; // Clear any previous error messages
if (this.home === 0) this.home = 1;
// ensure that going back in history will go back to home
window.history.pushState({}, "", "/");
// add message to list
this.cstate.messages.push({ role: "user", content: value });
// clear textarea
el.value = "";
el.style.height = "auto";
el.style.height = el.scrollHeight + "px";
// reset performance tracking
const prefill_start = Date.now();
let start_time = 0;
let tokens = 0;
this.tokens_per_second = 0;
try {
// start receiving server sent events
let gottenFirstChunk = false;
for await (const chunk of this.openaiChatCompletion(
this.cstate.messages,
)) {
if (!gottenFirstChunk) {
this.cstate.messages.push({ role: "assistant", content: "" });
gottenFirstChunk = true;
}
// add chunk to the last message
this.cstate.messages[this.cstate.messages.length - 1].content +=
chunk;
// calculate performance tracking
tokens += 1;
this.total_tokens += 1;
if (start_time === 0) {
start_time = Date.now();
this.time_till_first = start_time - prefill_start;
} else {
const diff = Date.now() - start_time;
if (diff > 0) {
this.tokens_per_second = tokens / (diff / 1000);
}
}
}
// update the state in histories or add it if it doesn't exist
const index = this.histories.findIndex((cstate) => {
return cstate.time === this.cstate.time;
});
this.cstate.time = Date.now();
if (index !== -1) {
// update the time
this.histories[index] = this.cstate;
} else {
this.histories.push(this.cstate);
}
// update in local storage
localStorage.setItem("histories", JSON.stringify(this.histories));
} catch (error) {
console.error("Error in handleSend:", error);
this.showError(
error.message || "An error occurred processing your request.",
);
} finally {
this.generating = false;
}
},
async handleEnter(event) {
// if shift is not pressed
if (!event.shiftKey) {
event.preventDefault();
await this.handleSend();
}
},
showError(message) {
this.errorMessage = message;
setTimeout(() => {
this.errorMessage = null;
}, 3000); // Hide after 5 seconds
},
updateTotalTokens(messages) {
fetch(`${window.location.origin}/v1/tokenizer/count`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages }),
})
.then((response) => {
if (!response.ok) {
throw new Error("Failed to count tokens");
}
return response.json();
})
.then((data) => {
this.total_tokens = data.token_count;
})
.catch((error) => {
console.error("Error updating total tokens:", error);
this.showError("Failed to update token count. Please try again.");
});
},
async *openaiChatCompletion(messages) {
try {
const response = await fetch(`${this.endpoint}/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${this.apiKey}`,
},
body: JSON.stringify({
model: this.model,
messages: messages,
stream: true,
stop: [this.stopToken],
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || "Failed to fetch");
}
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = line.slice(6);
if (data === "[DONE]") return;
try {
const json = JSON.parse(data);
if (json.choices && json.choices[0].delta.content) {
yield json.choices[0].delta.content;
}
} catch (error) {
console.error("Error parsing JSON:", error);
}
}
}
}
} catch (error) {
console.error("Error in openaiChatCompletion:", error);
this.showError(
error.message ||
"An error occurred while communicating with the server.",
);
throw error;
}
},
}));
});