chat: Add Gemini support
This commit is contained in:
@@ -149,6 +149,24 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--bg-user-message);
|
border-color: var(--bg-user-message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.submit {
|
||||||
|
anchor-name: --submit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-selector {
|
||||||
|
position: absolute;
|
||||||
|
position-anchor: --submit;
|
||||||
|
bottom: anchor(top);
|
||||||
|
right: anchor(right);
|
||||||
|
}
|
||||||
|
.model-opt__label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
details {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -157,17 +175,45 @@
|
|||||||
<div id="messages" class="messages"></div>
|
<div id="messages" class="messages"></div>
|
||||||
<textarea rows="5" id="prompt" class="prompt" placeholder="Type your message..."></textarea>
|
<textarea rows="5" id="prompt" class="prompt" placeholder="Type your message..."></textarea>
|
||||||
<button id="submit" class="submit">Send</button>
|
<button id="submit" class="submit">Send</button>
|
||||||
|
<details class="model-selector">
|
||||||
|
<summary>Model</summary>
|
||||||
|
<label class="model-opt__label">
|
||||||
|
<input type="radio" name="model" value="gpt-4o" checked />
|
||||||
|
GPT-4o
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="model-opt__label">
|
||||||
|
<input type="radio" name="model" value="gpt-4o-mini" />
|
||||||
|
GPT-4o mini
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="model-opt__label">
|
||||||
|
<input type="radio" name="model" value="claude-3-5-sonnet-20241022" />
|
||||||
|
Claude 3.5 Sonnet
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="model-opt__label">
|
||||||
|
<input type="radio" name="model" value="gemini-1.5-flash-8b-latest" />
|
||||||
|
Gemini 1.5 Flash 8b
|
||||||
|
</label>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
window.env ??= {};
|
window.env ??= {};
|
||||||
let abortController = null;
|
let abortController = null;
|
||||||
const messages = [{ role: "system", content: "You are a helpful AI assistant." }];
|
const messages = [{ role: "system", content: "You are a helpful AI assistant. Do not preach about implications." }];
|
||||||
|
|
||||||
const $messages = document.getElementById("messages");
|
const $messages = document.getElementById("messages");
|
||||||
const $prompt = document.getElementById("prompt");
|
const $prompt = document.getElementById("prompt");
|
||||||
const $submit = document.getElementById("submit");
|
const $submit = document.getElementById("submit");
|
||||||
|
|
||||||
|
document.querySelectorAll("[name=model]").forEach((modelOpt) => {
|
||||||
|
if (modelOpt.value === window.env.MODEL) {
|
||||||
|
modelOpt.checked = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$submit.addEventListener("click", () => {
|
$submit.addEventListener("click", () => {
|
||||||
if (abortController) {
|
if (abortController) {
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
@@ -213,16 +259,27 @@
|
|||||||
$submit.textContent = "Cancel";
|
$submit.textContent = "Cancel";
|
||||||
abortController = new AbortController();
|
abortController = new AbortController();
|
||||||
|
|
||||||
|
const pickedModel = document.querySelector("[name=model]:checked")?.value ?? window.env.MODEL;
|
||||||
|
|
||||||
const chat = {
|
const chat = {
|
||||||
chatgpt: (params) =>
|
chatgpt: (params) => chatWithOpenAI({ ...params, apiKey: window.env.OPENAI_API_KEY, model: "gpt-4o" }),
|
||||||
chatWithOpenAI({ ...params, apiKey: window.env.OPENAI_API_KEY, model: "gpt-4o" }),
|
|
||||||
claude: (params) =>
|
claude: (params) =>
|
||||||
chatWithClaude({
|
chatWithClaude({
|
||||||
...params,
|
...params,
|
||||||
apiKey: window.env.ANTHROPIC_API_KEY,
|
apiKey: window.env.ANTHROPIC_API_KEY,
|
||||||
model: "claude-3-5-sonnet-20241022",
|
model: "claude-3-5-sonnet-20241022",
|
||||||
}),
|
}),
|
||||||
}[window.env.MODEL ?? "claude"];
|
"gpt-4o": (params) => chatWithOpenAI({ ...params, apiKey: window.env.OPENAI_API_KEY, model: "gpt-4o" }),
|
||||||
|
"gpt-4o-mini": (params) => chatWithOpenAI({ ...params, apiKey: window.env.OPENAI_API_KEY, model: "gpt-4o-mini" }),
|
||||||
|
"gemini-1.5-flash-8b-latest": (params) =>
|
||||||
|
chatWithGemini({ ...params, apiKey: window.env.GEMINI_API_KEY, model: "gemini-1.5-flash-8b-latest" }),
|
||||||
|
"claude-3-5-sonnet-20241022": (params) =>
|
||||||
|
chatWithClaude({
|
||||||
|
...params,
|
||||||
|
apiKey: window.env.ANTHROPIC_API_KEY,
|
||||||
|
model: "claude-3-5-sonnet-20241022",
|
||||||
|
}),
|
||||||
|
}[pickedModel];
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
console.error(`Unknown model: ${window.env.MODEL}`);
|
console.error(`Unknown model: ${window.env.MODEL}`);
|
||||||
return;
|
return;
|
||||||
@@ -254,18 +311,18 @@
|
|||||||
function createMessageElement(content, isUser) {
|
function createMessageElement(content, isUser) {
|
||||||
const message = document.createElement("div");
|
const message = document.createElement("div");
|
||||||
message.className = `message ${isUser ? "user-message" : "system-message"}`;
|
message.className = `message ${isUser ? "user-message" : "system-message"}`;
|
||||||
message.innerHTML = marked.parse(content);
|
message.innerHTML = marked.parse(content, { sanitize: true });
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateMessageContent(element, content) {
|
||||||
|
element.innerHTML = marked.parse(content, { sanitize: true });
|
||||||
|
}
|
||||||
|
|
||||||
function scrollToBottom(element) {
|
function scrollToBottom(element) {
|
||||||
element.scrollTop = element.scrollHeight;
|
element.scrollTop = element.scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMessageContent(element, content) {
|
|
||||||
element.innerHTML = marked.parse(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function chatWithOpenAI({ onContent, messages, signal, apiKey, model }) {
|
async function chatWithOpenAI({ onContent, messages, signal, apiKey, model }) {
|
||||||
const payload = {
|
const payload = {
|
||||||
model,
|
model,
|
||||||
@@ -363,6 +420,63 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function chatWithGemini({ onContent, messages, signal, apiKey, model = "gemini-1.5-flash-8b" }) {
|
||||||
|
const payload = {
|
||||||
|
contents: messages.map((msg) => ({
|
||||||
|
role: msg.role === "system" ? "user" : msg.role,
|
||||||
|
parts: [{ text: msg.content }],
|
||||||
|
})),
|
||||||
|
generationConfig: {
|
||||||
|
temperature: 0,
|
||||||
|
},
|
||||||
|
safetySettings: [
|
||||||
|
{ category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
|
||||||
|
{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
|
||||||
|
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
|
||||||
|
{ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"x-goog-api-key": apiKey,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
|
||||||
|
const text = decoder.decode(value, { stream: true });
|
||||||
|
const lines = text
|
||||||
|
.split("\n")
|
||||||
|
.filter((line) => line.trim())
|
||||||
|
.map((line) => line.replace("data: ", ""));
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line === "") continue;
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(line);
|
||||||
|
if (parsed.candidates?.[0]?.content?.parts?.[0]?.text) {
|
||||||
|
const content = parsed.candidates[0].content.parts[0].text;
|
||||||
|
if (content) {
|
||||||
|
await onContent(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error parsing line:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user