feat(chat): Add Youtube URLs as context
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -45,12 +45,20 @@
|
||||
font-size: 14px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
transition:
|
||||
background-color 0.2s,
|
||||
color 0.2s;
|
||||
}
|
||||
input {
|
||||
line-height: inherit;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
td + td,
|
||||
th + td {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
height: 100vh;
|
||||
display: grid;
|
||||
@@ -131,7 +139,9 @@
|
||||
font-size: inherit;
|
||||
background: var(--bg-input);
|
||||
color: var(--text-primary);
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
transition:
|
||||
background-color 0.2s,
|
||||
color 0.2s;
|
||||
}
|
||||
.prompt::placeholder {
|
||||
color: var(--text-secondary);
|
||||
@@ -145,7 +155,9 @@
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.4rem;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
transition:
|
||||
background-color 0.2s,
|
||||
color 0.2s;
|
||||
}
|
||||
.submit:hover {
|
||||
background: var(--hover-bg);
|
||||
@@ -173,7 +185,9 @@
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
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 {
|
||||
opacity: 0.7;
|
||||
@@ -318,12 +332,14 @@
|
||||
|
||||
window.env ??= {};
|
||||
const systemPrompt = `
|
||||
You are a helpful AI assistant.
|
||||
Do not preach about implications, do not praise excessively.
|
||||
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.
|
||||
`
|
||||
You are a helpful AI assistant.
|
||||
- Do not preach about implications, do not praise excessively.
|
||||
- Keep a critical mindset, do not assume blindly.
|
||||
- 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, "")
|
||||
.trim();
|
||||
|
||||
@@ -367,7 +383,7 @@
|
||||
},
|
||||
|
||||
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() {
|
||||
@@ -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" }),
|
||||
getExcerpt: (msgs) => msgs.find((m) => m.role === "user")?.content.slice(0, 30) || "Empty chat",
|
||||
|
||||
@@ -409,6 +414,7 @@
|
||||
|
||||
// Streaming setup
|
||||
const target = this.$refs.incomingMessage;
|
||||
this.$refs.incomingMessage.innerHTML = "";
|
||||
let fullResponse = "";
|
||||
|
||||
this.scrollToBottom();
|
||||
@@ -423,13 +429,11 @@
|
||||
onContent: (chunk) => {
|
||||
fullResponse += chunk;
|
||||
target.innerHTML = renderMarkdown(fullResponse);
|
||||
this.attachCopyButtons(target);
|
||||
this.scrollToBottom();
|
||||
},
|
||||
onError: (e) => {
|
||||
fullResponse += `\n\n[Error: ${e.message}]`;
|
||||
target.innerHTML = renderMarkdown(fullResponse);
|
||||
this.attachCopyButtons(target);
|
||||
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) {
|
||||
const $placeholder = document.createElement("template");
|
||||
const $placeholder = document.createElement("div");
|
||||
$placeholder.innerHTML = md.render(markdown);
|
||||
attachCopyButtons($placeholder);
|
||||
return $placeholder.innerHTML;
|
||||
}
|
||||
|
||||
@@ -514,10 +529,24 @@
|
||||
}
|
||||
|
||||
async function chatWithGemini({ onContent, onError, messages, signal, apiKey = window.env.GEMINI_API_KEY, model, ...rest }) {
|
||||
const contents = messages.map((msg) => ({
|
||||
role: msg.role === "assistant" ? "model" : "user",
|
||||
parts: [{ text: msg.content }],
|
||||
}));
|
||||
const contents = messages.map((msg, i, arr) => {
|
||||
const role = msg.role === "assistant" ? "model" : "user";
|
||||
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 {
|
||||
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?key=${apiKey}`, {
|
||||
|
||||
Reference in New Issue
Block a user