feat(chat): Add Youtube URLs as context
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
@@ -45,12 +45,20 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
transition: background-color 0.2s, color 0.2s;
|
transition:
|
||||||
|
background-color 0.2s,
|
||||||
|
color 0.2s;
|
||||||
}
|
}
|
||||||
input {
|
input {
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td + td,
|
||||||
|
th + td {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-container {
|
.chat-container {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -131,7 +139,9 @@
|
|||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
background: var(--bg-input);
|
background: var(--bg-input);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
transition: background-color 0.2s, color 0.2s;
|
transition:
|
||||||
|
background-color 0.2s,
|
||||||
|
color 0.2s;
|
||||||
}
|
}
|
||||||
.prompt::placeholder {
|
.prompt::placeholder {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
@@ -145,7 +155,9 @@
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
transition: background-color 0.2s, color 0.2s;
|
transition:
|
||||||
|
background-color 0.2s,
|
||||||
|
color 0.2s;
|
||||||
}
|
}
|
||||||
.submit:hover {
|
.submit:hover {
|
||||||
background: var(--hover-bg);
|
background: var(--hover-bg);
|
||||||
@@ -173,7 +185,9 @@
|
|||||||
background-color: var(--bg-secondary);
|
background-color: var(--bg-secondary);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
transition: opacity 0.2s, background-color 0.2s;
|
transition:
|
||||||
|
opacity 0.2s,
|
||||||
|
background-color 0.2s;
|
||||||
}
|
}
|
||||||
:is(#model-selector, #history-panel):hover {
|
:is(#model-selector, #history-panel):hover {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
@@ -319,10 +333,12 @@
|
|||||||
window.env ??= {};
|
window.env ??= {};
|
||||||
const systemPrompt = `
|
const systemPrompt = `
|
||||||
You are a helpful AI assistant.
|
You are a helpful AI assistant.
|
||||||
Do not preach about implications, do not praise excessively.
|
- Do not preach about implications, do not praise excessively.
|
||||||
Keep a critical mindset, do not assume blindly.
|
- Keep a critical mindset, do not assume blindly.
|
||||||
When I ask for code, keep in mind that I am a senior backend developer (but do not mention that explicitly), and do not explain excessively, but and challenge my statements with better alternatives if I don't sound right, especially in coding.
|
- Answer in valid markdown only, no exceptions.
|
||||||
Answer in valid markdown only, no exceptions.
|
|
||||||
|
- When I ask something about code, or software development, keep in mind that I am a senior backend developer (but do not mention that explicitly), and do not explain excessively, but and challenge my statements with better alternatives if I don't sound right, especially in coding. Do not bring it up otherwise.
|
||||||
|
- Do not give me code if the topic is not about programming or software development.
|
||||||
`
|
`
|
||||||
.replace(/^\s+/gm, "")
|
.replace(/^\s+/gm, "")
|
||||||
.trim();
|
.trim();
|
||||||
@@ -367,7 +383,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
get inputRows() {
|
get inputRows() {
|
||||||
return Math.min(10, Math.max(2, this.promptText.split("\n").length));
|
return Math.min(10, Math.max(2, this.promptText.split("\n").length)) + 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleScroll() {
|
handleScroll() {
|
||||||
@@ -383,17 +399,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
attachCopyButtons($el) {
|
|
||||||
$el.querySelectorAll("pre").forEach(($pre) => {
|
|
||||||
if (!$pre.querySelector(".copy-btn")) {
|
|
||||||
const $btn = document.createElement("button");
|
|
||||||
$btn.className = "copy-btn";
|
|
||||||
$btn.textContent = "Copy";
|
|
||||||
$pre.appendChild($btn);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
formatTime: (iso) => new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
|
formatTime: (iso) => new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
|
||||||
getExcerpt: (msgs) => msgs.find((m) => m.role === "user")?.content.slice(0, 30) || "Empty chat",
|
getExcerpt: (msgs) => msgs.find((m) => m.role === "user")?.content.slice(0, 30) || "Empty chat",
|
||||||
|
|
||||||
@@ -409,6 +414,7 @@
|
|||||||
|
|
||||||
// Streaming setup
|
// Streaming setup
|
||||||
const target = this.$refs.incomingMessage;
|
const target = this.$refs.incomingMessage;
|
||||||
|
this.$refs.incomingMessage.innerHTML = "";
|
||||||
let fullResponse = "";
|
let fullResponse = "";
|
||||||
|
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
@@ -423,13 +429,11 @@
|
|||||||
onContent: (chunk) => {
|
onContent: (chunk) => {
|
||||||
fullResponse += chunk;
|
fullResponse += chunk;
|
||||||
target.innerHTML = renderMarkdown(fullResponse);
|
target.innerHTML = renderMarkdown(fullResponse);
|
||||||
this.attachCopyButtons(target);
|
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
},
|
},
|
||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
fullResponse += `\n\n[Error: ${e.message}]`;
|
fullResponse += `\n\n[Error: ${e.message}]`;
|
||||||
target.innerHTML = renderMarkdown(fullResponse);
|
target.innerHTML = renderMarkdown(fullResponse);
|
||||||
this.attachCopyButtons(target);
|
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -460,11 +464,22 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const md = markdownit({ html: false });
|
function attachCopyButtons($el) {
|
||||||
|
$el.querySelectorAll("pre").forEach(($pre) => {
|
||||||
|
if (!$pre.querySelector(".copy-btn")) {
|
||||||
|
const $btn = document.createElement("button");
|
||||||
|
$btn.className = "copy-btn";
|
||||||
|
$btn.textContent = "Copy";
|
||||||
|
$pre.appendChild($btn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const md = markdownit({ html: false });
|
||||||
function renderMarkdown(markdown) {
|
function renderMarkdown(markdown) {
|
||||||
const $placeholder = document.createElement("template");
|
const $placeholder = document.createElement("div");
|
||||||
$placeholder.innerHTML = md.render(markdown);
|
$placeholder.innerHTML = md.render(markdown);
|
||||||
|
attachCopyButtons($placeholder);
|
||||||
return $placeholder.innerHTML;
|
return $placeholder.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,10 +529,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function chatWithGemini({ onContent, onError, messages, signal, apiKey = window.env.GEMINI_API_KEY, model, ...rest }) {
|
async function chatWithGemini({ onContent, onError, messages, signal, apiKey = window.env.GEMINI_API_KEY, model, ...rest }) {
|
||||||
const contents = messages.map((msg) => ({
|
const contents = messages.map((msg, i, arr) => {
|
||||||
role: msg.role === "assistant" ? "model" : "user",
|
const role = msg.role === "assistant" ? "model" : "user";
|
||||||
parts: [{ text: msg.content }],
|
let parts = [{ text: msg.content }];
|
||||||
}));
|
|
||||||
|
// Add YouTube URLs as file data for the final user message
|
||||||
|
const isLastMessage = i === arr.length - 1;
|
||||||
|
if (isLastMessage && role === "user") {
|
||||||
|
const youtubeUrlPattern = /https?:\/\/(www\.)?youtube\.com\/watch\?v=[\w-]+|https?:\/\/youtu\.be\/[\w-]+/g;
|
||||||
|
const youtubeUrls = msg.content.match(youtubeUrlPattern) || [];
|
||||||
|
const fileParts = youtubeUrls.map((url) => ({ file_data: { file_uri: url } }));
|
||||||
|
parts = [...parts, ...fileParts];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
role,
|
||||||
|
parts,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?key=${apiKey}`, {
|
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?key=${apiKey}`, {
|
||||||
|
|||||||
Reference in New Issue
Block a user