First commit
This commit is contained in:
commit
b60e72c88e
34
index.html
Normal file
34
index.html
Normal file
@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Super Stupidly Simple Ollama Client</title>
|
||||
<link rel="icon" type="image/png" href="favicon/128/favicon.png" sizes="any">
|
||||
<link rel="icon" type="image/png" href="favicon/16/favicon.png" sizes="16x16">
|
||||
<link rel="icon" type="image/png" href="favicon/32/favicon.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="favicon/64/favicon.png" sizes="64x64">
|
||||
<link rel="icon" type="image/png" href="favicon/128/favicon.png" sizes="128x128">
|
||||
</head>
|
||||
<body>
|
||||
<div id="chatbox"></div>
|
||||
<div id="statusbox"></div>
|
||||
<textarea id="prompt" placeholder="Type a message..." rows="2"></textarea>
|
||||
<div class="controlbox">
|
||||
<select id="model">
|
||||
<option value="deepseek-v2:16b">deepseek-v2:16b</option>
|
||||
<option value="deepseek-coder-v2:16b">deepseek-coder-v2:16b</option>
|
||||
</select>
|
||||
<select id="role">
|
||||
<option value="user">user</option>
|
||||
<option value="system">system</option>
|
||||
<option value="assistant">assistant</option>
|
||||
</select>
|
||||
<label>Response? <input type="checkbox" id="response" checked></label>
|
||||
<button onclick="sendPrompt()">Send</button>
|
||||
</div>
|
||||
<script src="marked.min.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
96
script.js
Normal file
96
script.js
Normal file
@ -0,0 +1,96 @@
|
||||
const messages = [];
|
||||
const statusEl = document.getElementById("statusbox" );
|
||||
const chatContainer = document.getElementById("chatbox" );
|
||||
|
||||
function setStatus(msg, stts) {
|
||||
if (stts == "idle" ) statusEl.className = "status-idle";
|
||||
if (stts == "responding" ) statusEl.className = "status-responding";
|
||||
if (stts == "completed" ) statusEl.className = "status-completed";
|
||||
if (stts == "failed" ) statusEl.className = "status-failed";
|
||||
statusEl.textContent = msg;
|
||||
}
|
||||
|
||||
async function sendPrompt() {
|
||||
const role = document.getElementById("role" ).value;
|
||||
const model = document.getElementById("model" ).value;
|
||||
const response = document.getElementById("response" ).checked;
|
||||
const promptBox = document.getElementById("prompt" );
|
||||
const prompt = promptBox.value.trim();
|
||||
|
||||
if (!prompt) return;
|
||||
|
||||
const bubUser = document.createElement("div" );
|
||||
const tagRole = document.createElement("span" );
|
||||
const msgUser = document.createElement("div" );
|
||||
|
||||
const bubBot = document.createElement("div" );
|
||||
const tagModel = document.createElement("span" );
|
||||
const msgBot = document.createElement("div" );
|
||||
|
||||
bubUser .className = "bub bub-r bub-primary";
|
||||
tagRole .className = "tag";
|
||||
msgUser .className = "message";
|
||||
|
||||
bubBot .className = "bub bub-l bub-secondary";
|
||||
tagModel .className = "tag";
|
||||
msgBot .className = "message";
|
||||
|
||||
tagRole .textContent = `Role: ${role}`;
|
||||
tagModel .textContent = `Model: ${model}`;
|
||||
|
||||
msgUser.innerHTML = marked.parse(prompt, { sanitize: true });
|
||||
|
||||
bubUser.appendChild(tagRole);
|
||||
bubUser.appendChild(msgUser);
|
||||
chatContainer.appendChild(bubUser);
|
||||
|
||||
bubBot.appendChild(tagModel);
|
||||
bubBot.appendChild(msgBot);
|
||||
if (response) chatContainer.appendChild(bubBot);
|
||||
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
messages.push({ role: role, content: prompt }); // Add prompt to history
|
||||
if (response) setStatus(`⏳ ${model} Loading…`, "responding");
|
||||
|
||||
if (response) try {
|
||||
const res = await fetch("http://localhost:11434/api/chat", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ model, messages, stream: true })
|
||||
});
|
||||
|
||||
const reader = res.body.getReader();
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
let botReply = "";
|
||||
|
||||
setStatus(`✏️ ${model} Typing…`, "responding");
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
const chunk = decoder.decode(value, { stream: true }).trim();
|
||||
for (const line of chunk.split("\n")) {
|
||||
if (!line) continue;
|
||||
try {
|
||||
const j = JSON.parse(line);
|
||||
if (j.message?.content) {
|
||||
botReply += j.message.content;
|
||||
msgBot.innerHTML = marked.parse(botReply, { sanitize: true });
|
||||
msgBot.prepend(tagModel);
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
messages.push({ role: "assistant", content: botReply });
|
||||
setStatus(`✅ Response completed by ${model}`, "completed");
|
||||
|
||||
} catch (e) {
|
||||
msgBot.innerHTML = "<em>[Failed to load answer]</em>";
|
||||
msgBot.prepend(tagModel);
|
||||
setStatus(`❌ Failed to get a response from ${model}`, "failed");
|
||||
}
|
||||
|
||||
promptBox.value = "";
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user