NovaAI / index.html
Radmana's picture
Update index.html
09bea9f verified
Raw History Blame Contribute Delete
14.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nova AI</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
background: #0f1117;
color: #ffffff;
height: 100vh;
overflow: hidden;
}
.app {
display: flex;
height: 100vh;
}
.sidebar {
width: 260px;
background: #171a21;
border-right: 1px solid #292d36;
padding: 18px;
display: flex;
flex-direction: column;
}
.logo {
font-size: 21px;
font-weight: bold;
margin-bottom: 25px;
}
.new-chat {
border: 1px solid #363b46;
background: #222630;
color: white;
padding: 12px;
border-radius: 9px;
cursor: pointer;
font-size: 14px;
}
.new-chat:hover {
background: #2b303c;
}
.sidebar-bottom {
margin-top: auto;
color: #858b98;
font-size: 12px;
line-height: 1.5;
}
.main {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
.topbar {
height: 60px;
border-bottom: 1px solid #292d36;
display: flex;
align-items: center;
padding: 0 22px;
font-weight: bold;
}
.chat {
flex: 1;
overflow-y: auto;
padding: 30px 15%;
}
.welcome {
text-align: center;
margin-top: 15vh;
}
.welcome h1 {
font-size: 34px;
margin-bottom: 10px;
}
.welcome p {
color: #9ba1ad;
}
.message {
display: flex;
margin-bottom: 25px;
gap: 13px;
line-height: 1.6;
}
.avatar {
width: 34px;
height: 34px;
min-width: 34px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: #303642;
font-size: 15px;
}
.user .avatar {
background: #555b68;
}
.content {
max-width: 850px;
word-break: break-word;
}
.name {
font-size: 13px;
color: #9ba1ad;
margin-bottom: 3px;
}
.content strong {
font-weight: bold;
}
.content code {
background: #20242d;
padding: 2px 5px;
border-radius: 5px;
font-family: Consolas, monospace;
}
.content pre {
background: #171a21;
border: 1px solid #303540;
padding: 12px;
border-radius: 8px;
overflow-x: auto;
}
.input-area {
padding: 15px 15%;
border-top: 1px solid #292d36;
background: #0f1117;
}
.input-box {
display: flex;
align-items: flex-end;
gap: 8px;
background: #1d2129;
border: 1px solid #343944;
border-radius: 13px;
padding: 9px;
}
textarea {
flex: 1;
resize: none;
background: transparent;
border: none;
outline: none;
color: white;
font-size: 15px;
padding: 8px;
min-height: 25px;
max-height: 150px;
font-family: Arial, Helvetica, sans-serif;
}
textarea::placeholder {
color: #777d89;
}
#send {
width: 40px;
height: 40px;
border: none;
border-radius: 9px;
background: #ffffff;
color: #111111;
cursor: pointer;
font-size: 18px;
}
#send:disabled {
opacity: 0.4;
cursor: default;
}
.token-panel {
position: fixed;
inset: 0;
background: rgba(0,0,0,.75);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.token-card {
width: min(450px, 90%);
background: #1b1f27;
border: 1px solid #343944;
border-radius: 14px;
padding: 25px;
}
.token-card h2 {
margin-top: 0;
}
.token-card p {
color: #a4a9b4;
font-size: 14px;
line-height: 1.5;
}
.token-input {
width: 100%;
padding: 12px;
border-radius: 8px;
border: 1px solid #3a3f4b;
background: #10131a;
color: white;
margin: 10px 0;
outline: none;
}
.token-button {
width: 100%;
padding: 12px;
border: none;
border-radius: 8px;
background: white;
color: black;
cursor: pointer;
font-weight: bold;
}
.status {
color: #888f9b;
font-size: 12px;
margin-top: 8px;
text-align: center;
}
@media (max-width: 700px) {
.sidebar {
display: none;
}
.chat,
.input-area {
padding-left: 15px;
padding-right: 15px;
}
}
</style>
</head>
<body>
<div class="app">
<aside class="sidebar">
<div class="logo">
Nova AI
</div>
<button class="new-chat" onclick="newChat()">
+ New chat
</button>
<div class="sidebar-bottom">
Powered by Hugging Face<br>
AI responses may contain mistakes.
</div>
</aside>
<main class="main">
<div class="topbar">
Nova AI
</div>
<div class="chat" id="chat">
<div class="welcome" id="welcome">
<h1>How can I help?</h1>
<p>Ask me anything.</p>
</div>
</div>
<div class="input-area">
<div class="input-box">
<textarea
id="message"
placeholder="Message Nova AI..."
rows="1"
onkeydown="handleKey(event)"
></textarea>
<button id="send" onclick="sendMessage()">➤</button>
</div>
<div class="status" id="status">
Ready
</div>
</div>
</main>
</div>
<div class="token-panel" id="tokenPanel">
<div class="token-card">
<h2>Connect Nova AI</h2>
<p>
Enter your Hugging Face access token to connect
this chatbot to an AI model.
</p>
<input
class="token-input"
id="token"
type="password"
placeholder="hf_..."
>
<button class="token-button" onclick="saveToken()">
Connect
</button>
<p style="font-size:12px;">
Your token is kept in this browser and is not written
into the website code.
</p>
</div>
</div>
<script type="module">
import { InferenceClient }
from "https://esm.sh/@huggingface/inference";
let token = sessionStorage.getItem("nova_hf_token");
const MODEL = "openai/gpt-oss-120b:fastest";
const SYSTEM_PROMPT =
"You are Nova AI, a helpful, friendly and intelligent AI assistant. " +
"Give clear and accurate answers. " +
"Use normal text for simple math whenever possible. " +
"Do not use LaTeX commands such as \\\\(, \\\\), \\\\[ or \\\\]. " +
"Do not use commands like \\\\times, \\\\frac, \\\\sqrt or \\\\boxed. " +
"Instead write math in plain text, for example 8 × 8 = 64. " +
"Do not add your own name before answers. " +
"Do not claim to be ChatGPT or another specific AI.";
let messages = [
{
role: "system",
content: SYSTEM_PROMPT
}
];
const tokenPanel =
document.getElementById("tokenPanel");
if (token) {
tokenPanel.style.display = "none";
}
/* SAVE TOKEN */
window.saveToken = function() {
const value =
document
.getElementById("token")
.value
.trim();
if (!value) {
alert("Enter your Hugging Face token.");
return;
}
token = value;
sessionStorage.setItem(
"nova_hf_token",
token
);
tokenPanel.style.display = "none";
document.getElementById("status").textContent =
"Connected";
};
/* CLEAN MATH */
function cleanMath(text) {
return text
// Remove LaTeX brackets
.replace(/\\\(/g, "")
.replace(/\\\)/g, "")
.replace(/\\\[/g, "")
.replace(/\\\]/g, "")
// Common LaTeX commands
.replace(/\\times/g, "×")
.replace(/\\cdot/g, "·")
.replace(/\\div/g, "÷")
.replace(/\\pm/g, "±")
.replace(/\\leq/g, "≤")
.replace(/\\geq/g, "≥")
.replace(/\\neq/g, "≠")
.replace(/\\sqrt\{([^}]+)\}/g, "√($1)")
// Fractions
.replace(
/\\frac\{([^}]+)\}\{([^}]+)\}/g,
"($1/$2)"
)
// Remove boxed
.replace(
/\\boxed\{([^}]+)\}/g,
"$1"
)
// Remove remaining common commands
.replace(/\\text\{([^}]+)\}/g, "$1")
.replace(/\\mathrm\{([^}]+)\}/g, "$1")
// Remove stray backslashes
.replace(/\\([a-zA-Z]+)/g, "$1")
.trim();
}
/* SIMPLE SAFE MARKDOWN */
function formatText(text) {
text = cleanMath(text);
// Escape HTML first
text = text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
// Code blocks
text = text.replace(
/```([\s\S]*?)```/g,
"<pre><code>$1</code></pre>"
);
// Inline code
text = text.replace(
/`([^`]+)`/g,
"<code>$1</code>"
);
// Bold
text = text.replace(
/\*\*(.*?)\*\*/g,
"<strong>$1</strong>"
);
// Italic
text = text.replace(
/(?<!\*)\*([^*]+)\*(?!\*)/g,
"<em>$1</em>"
);
// Line breaks
text = text.replace(/\n/g, "<br>");
return text;
}
/* SEND MESSAGE */
window.sendMessage = async function() {
const input =
document.getElementById("message");
const text =
input.value.trim();
if (!text || !token) {
return;
}
document.getElementById("welcome").style.display =
"none";
addMessage("user", text);
input.value = "";
messages.push({
role: "user",
content: text
});
const sendButton =
document.getElementById("send");
sendButton.disabled = true;
document.getElementById("status").textContent =
"Thinking...";
try {
const client =
new InferenceClient(token);
const result =
await client.chatCompletion({
model: MODEL,
messages: messages,
max_tokens: 1000,
temperature: 0.7
});
let answer =
result.choices?.[0]?.message?.content ||
"I couldn't generate a response.";
// Remove accidental Nova AI prefixes
answer = answer
.replace(/^✦\s*Nova AI\s*/i, "")
.replace(/^Nova AI\s*:\s*/i, "")
.trim();
messages.push({
role: "assistant",
content: answer
});
addMessage(
"assistant",
answer
);
document.getElementById("status").textContent =
"Ready";
} catch (error) {
console.error(error);
addMessage(
"assistant",
"⚠️ I couldn't connect to the AI.\n\n" +
"Check your Hugging Face token and make sure " +
"the selected model has an available inference provider."
);
document.getElementById("status").textContent =
"Connection error";
} finally {
sendButton.disabled = false;
}
};
/* ADD MESSAGE */
function addMessage(role, text) {
const chat =
document.getElementById("chat");
const message =
document.createElement("div");
message.className =
"message " + role;
const avatar =
document.createElement("div");
avatar.className =
"avatar";
// Simple clean avatars
avatar.textContent =
role === "user" ? "U" : "N";
const wrapper =
document.createElement("div");
const name =
document.createElement("div");
name.className =
"name";
name.textContent =
role === "user"
? "You"
: "Nova AI";
const content =
document.createElement("div");
content.className =
"content";
if (role === "assistant") {
content.innerHTML =
formatText(text);
} else {
// User messages stay completely safe
content.textContent =
text;
}
wrapper.appendChild(name);
wrapper.appendChild(content);
message.appendChild(avatar);
message.appendChild(wrapper);
chat.appendChild(message);
chat.scrollTop =
chat.scrollHeight;
}
/* NEW CHAT */
window.newChat = function() {
messages = [
{
role: "system",
content: SYSTEM_PROMPT
}
];
const chat =
document.getElementById("chat");
chat.innerHTML = `
<div class="welcome" id="welcome">
<h1>How can I help?</h1>
<p>Ask me anything.</p>
</div>
`;
document.getElementById("status").textContent =
"Ready";
};
/* ENTER TO SEND */
window.handleKey = function(event) {
if (
event.key === "Enter" &&
!event.shiftKey
) {
event.preventDefault();
sendMessage();
}
};
</script>
</body>
</html>