chat: Add Gemini support
This commit is contained in:
@@ -149,6 +149,24 @@
|
||||
outline: none;
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
@@ -157,17 +175,45 @@
|
||||
<div id="messages" class="messages"></div>
|
||||
<textarea rows="5" id="prompt" class="prompt" placeholder="Type your message..."></textarea>
|
||||
<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>
|
||||
<script>
|
||||
window.env ??= {};
|
||||
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 $prompt = document.getElementById("prompt");
|
||||
const $submit = document.getElementById("submit");
|
||||
|
||||
document.querySelectorAll("[name=model]").forEach((modelOpt) => {
|
||||
if (modelOpt.value === window.env.MODEL) {
|
||||
modelOpt.checked = true;
|
||||
}
|
||||
});
|
||||
|
||||
$submit.addEventListener("click", () => {
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
@@ -213,16 +259,27 @@
|
||||
$submit.textContent = "Cancel";
|
||||
abortController = new AbortController();
|
||||
|
||||
const pickedModel = document.querySelector("[name=model]:checked")?.value ?? window.env.MODEL;
|
||||
|
||||
const chat = {
|
||||
chatgpt: (params) =>
|
||||
chatWithOpenAI({ ...params, apiKey: window.env.OPENAI_API_KEY, model: "gpt-4o" }),
|
||||
chatgpt: (params) => chatWithOpenAI({ ...params, apiKey: window.env.OPENAI_API_KEY, model: "gpt-4o" }),
|
||||
claude: (params) =>
|
||||
chatWithClaude({
|
||||
...params,
|
||||
apiKey: window.env.ANTHROPIC_API_KEY,
|
||||
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) {
|
||||
console.error(`Unknown model: ${window.env.MODEL}`);
|
||||
return;
|
||||
@@ -254,18 +311,18 @@
|
||||
function createMessageElement(content, isUser) {
|
||||
const message = document.createElement("div");
|
||||
message.className = `message ${isUser ? "user-message" : "system-message"}`;
|
||||
message.innerHTML = marked.parse(content);
|
||||
message.innerHTML = marked.parse(content, { sanitize: true });
|
||||
return message;
|
||||
}
|
||||
|
||||
function updateMessageContent(element, content) {
|
||||
element.innerHTML = marked.parse(content, { sanitize: true });
|
||||
}
|
||||
|
||||
function scrollToBottom(element) {
|
||||
element.scrollTop = element.scrollHeight;
|
||||
}
|
||||
|
||||
function updateMessageContent(element, content) {
|
||||
element.innerHTML = marked.parse(content);
|
||||
}
|
||||
|
||||
async function chatWithOpenAI({ onContent, messages, signal, apiKey, model }) {
|
||||
const payload = {
|
||||
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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user