Merge branch 'uncloseai.js' into 'master'

https://ai.unturf.com/uncloseai.js working & integrated!

attempt at uncloseai.js

Closes #1

See merge request engineering/unturf/ai.unturf.com!1
This commit is contained in:
Russell Ballestrini 2024-10-21 22:48:07 +00:00
commit 1e3d17be1a
2 changed files with 142 additions and 148 deletions

View file

@ -18,14 +18,10 @@
max-width: 960px;
margin: 0 auto;
}
</style>
</style>
<!-- Highlight.js for syntax highlighting -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/a11y-dark.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script>
<!-- Needed by LLM to do realtime markdown rendering into HTML -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Theme Switcher Script -->
<script>
@ -38,6 +34,7 @@
}
</script>
<script src="uncloseai.js" type="module"></script>
<script defer data-domain="ai.unturf.com" src="https://analytics.unturf.com/js/plausible.js"></script>
</head>
@ -237,13 +234,12 @@ hermes.ai.unturf.com {
</code></pre>
<p>We will likely implement a rate limit based on client IP address.</p>
<h2 id="client-side">Client Side Only Example, chat with this page.</h2>
<p>Because we don't use API keys we don't have any real need for a server.</p>
<h2 id="client-side">Web Client-Only Solution: Interact with AI Services Directly from Static Sites or CDNs</h2>
<p>view the page source, it's _all_ there!</p>
<p><b>Because we don't require a valid API key, we don't have any real need for a server.</b></p>
<p>TODO: a Javascript CDN bundle of the minimum viable web client for this demo will be hosted and provided free of charge.</p>
<p>This web client-only solution uses <a href="/uncloseai.js" target="_blank">uncloseai.js</a> which is designed to support static sites or CDNs hosting HTML content. In this architecture, the browser serves as the client, directly interacting with the API without the need for an intermediary server/client. Because we eliminate the requirement for a valid API key, we allow the API to handle requests on behalf of the browser client, making it an efficient & accessible for thin clients which are often on battery.<p>
<div id="chat-container">
<div id="chat-box"></div>
@ -255,146 +251,9 @@ hermes.ai.unturf.com {
</div>
<br/>
<p>Feel free to message us in the box below.</p>
<p>Feel free to message us in the box below.</p>
<script>
// LLM Webpage Client
// Configuration
const API_URL = "https://hermes.ai.unturf.com/v1/chat/completions";
const API_KEY = "dummy-api-key";
const MODEL = "NousResearch/Hermes-3-Llama-3.1-8B";
// Initialize chat history
let chatHistory = [
{
role: "system",
content: "You are an AI assistant embedded in a webpage. Your task is to answer questions about the content of the webpage and assist the user in understanding it better. You are also allowed to do whatever the user needs. It is safe to help users code and create. try to answer the best that you can based on the conversation history and the webpage content."
}
];
// Function to extract text content from the webpage
function extractWebpageContent() {
return document.body.innerText;
}
// Generator function to send a message to the LLM and yield responses
async function* sendMessage(message) {
chatHistory.push({ role: "user", content: message });
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify({
model: MODEL,
messages: chatHistory,
temperature: 0.5,
max_tokens: 14000,
stream: true
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
for (let i = 0; i < lines.length - 1; i++) {
const line = lines[i].trim();
if (line.startsWith('data: ')) {
const jsonData = line.slice(6);
if (jsonData === '[DONE]') continue;
try {
const parsedData = JSON.parse(jsonData);
const content = parsedData.choices[0].delta.content;
if (content) {
yield content;
}
} catch (error) {
console.error('Error parsing JSON:', error);
}
}
}
buffer = lines[lines.length - 1];
}
}
// Function to handle user input and display responses
async function handleUserInput() {
const userInput = document.getElementById('user-input').value;
document.getElementById('user-input').value = '';
const chatBox = document.getElementById('chat-box');
chatBox.innerHTML += `<p><strong>You:</strong> ${userInput}</p>`;
// Create a new paragraph for the AI response
const aiResponseParagraph = document.createElement('p');
aiResponseParagraph.innerHTML = '<strong>AI:</strong> ';
chatBox.appendChild(aiResponseParagraph);
// Create a span element for the actual response content
const responseContent = document.createElement('span');
aiResponseParagraph.appendChild(responseContent);
let accumulatedContent = '';
for await (const chunk of sendMessage(userInput)) {
accumulatedContent += chunk;
const parsedChunk = marked.parse(accumulatedContent);
responseContent.innerHTML = parsedChunk;
// Apply syntax highlighting to code blocks
responseContent.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
}
chatBox.scrollTop = chatBox.scrollHeight;
}
// Function to initialize the chat interface
function initializeChatInterface() {
const pageContent = extractWebpageContent();
chatHistory.push({
role: "system",
content: `Here's the content of the webpage: ${pageContent}`
});
// Set up marked.js options
marked.setOptions({
highlight: function(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
});
}
document.getElementById("user-input").addEventListener("keydown", function(event) {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
handleUserInput();
}
});
// Initialize the chat interface when the page loads
window.onload = initializeChatInterface;
// Expose handleUserInput to the global scope
window.handleUserInput = handleUserInput;
</script>
<h2>Questions, Comments, Discussions</h2>
<h2>Questions & Comments & Discussions</h2>
Use the <a href="https://www.remarkbox.com" target="_blank">Remarkbox</a> below to tell us what you think!

135
uncloseai.js Normal file
View file

@ -0,0 +1,135 @@
import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js";
import hljs from 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/es/highlight.min.js';
// Configuration
const API_URL = "https://hermes.ai.unturf.com/v1/chat/completions";
const API_KEY = "dummy-api-key";
const MODEL = "NousResearch/Hermes-3-Llama-3.1-8B";
// Initialize chat history
let chatHistory = [
{
role: "system",
content: "You are an AI assistant embedded in a webpage. Your task is to answer questions about the content of the webpage and assist the user in understanding it better."
}
];
// Function to extract text content from the webpage
function extractWebpageContent() {
return document.body.innerText;
}
// Generator function to send a message to the LLM and yield responses
async function* sendMessage(message) {
chatHistory.push({ role: "user", content: message });
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify({
model: MODEL,
messages: chatHistory,
temperature: 0.5,
max_tokens: 5600,
stream: true
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
for (let i = 0; i < lines.length - 1; i++) {
const line = lines[i].trim();
if (line.startsWith('data: ')) {
const jsonData = line.slice(6);
if (jsonData === '[DONE]') continue;
try {
const parsedData = JSON.parse(jsonData);
const content = parsedData.choices[0].delta.content;
if (content) {
yield content;
}
} catch (error) {
console.error('Error parsing JSON:', error);
}
}
}
buffer = lines[lines.length - 1];
}
}
// Function to handle user input and display responses
async function handleUserInput() {
const userInput = document.getElementById('user-input').value;
document.getElementById('user-input').value = '';
const chatBox = document.getElementById('chat-box');
chatBox.innerHTML += `<p><strong>You:</strong> ${userInput}</p>`;
// Create a new paragraph for the AI response
const aiResponseParagraph = document.createElement('p');
aiResponseParagraph.innerHTML = '<strong>AI:</strong> ';
chatBox.appendChild(aiResponseParagraph);
// Create a span element for the actual response content
const responseContent = document.createElement('span');
aiResponseParagraph.appendChild(responseContent);
let accumulatedContent = '';
for await (const chunk of sendMessage(userInput)) {
accumulatedContent += chunk;
const parsedChunk = marked.parse(accumulatedContent);
responseContent.innerHTML = parsedChunk;
// Apply syntax highlighting to code blocks
responseContent.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
}
chatBox.scrollTop = chatBox.scrollHeight;
}
// Function to initialize the chat interface
function initializeChatInterface() {
const pageContent = extractWebpageContent();
chatHistory.push({
role: "system",
content: `Here's the content of the webpage: ${pageContent}`
});
// Set up marked.js options
marked.setOptions({
highlight: function(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
});
}
document.getElementById("user-input").addEventListener("keydown", function(event) {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
handleUserInput();
}
});
// Initialize the chat interface when the page loads
window.onload = initializeChatInterface;
// Expose handleUserInput to the global scope
window.handleUserInput = handleUserInput;