<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Artydev Coding Terminal</title>
<!-- External Stylesheets -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@webtui/css/dist/base.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@webtui/theme-catppuccin/dist/catppuccin.css">
<style>
body {
margin: 0;
background: #0b0f14;
font-family: monospace;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* --- FILLED BLOCK ASCII HEADER (CENTERED) --- */
.banner-header {
background: #0e121a;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
padding: 14px 16px 10px 16px;
user-select: none;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.ascii-title {
margin: 0;
font-family: monospace;
white-space: pre;
line-height: 1.1;
font-size: 11px;
color: #89b4fa; /* Catppuccin Blue */
text-shadow: 0 0 10px rgba(137, 180, 250, 0.25);
}
.sub-tagline {
margin-top: 6px;
font-size: 11px;
color: #585b70;
}
/* --- TMUX STYLE MULTIPLEXER BAR --- */
.session-bar {
background: #11151c;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
padding: 6px 12px;
display: flex;
gap: 15px;
font-size: 13px;
user-select: none;
align-items: center;
overflow-x: auto;
}
.session-wrapper {
display: inline-flex;
align-items: center;
gap: 4px;
color: #7f849c;
}
.session-tab {
cursor: pointer;
white-space: nowrap;
}
.session-tab.active {
color: #a6e3a1; /* Catppuccin Green */
font-weight: bold;
}
.session-tab.active::before { content: "["; color: #7aa2f7; }
.session-tab.active::after { content: "]*"; color: #7aa2f7; }
/* Inline Rename Editor Box Styling */
.rename-input {
background: #1e1e2e;
border: 1px solid #7aa2f7;
color: #a6e3a1;
font-family: monospace;
font-size: 13px;
padding: 0 4px;
width: 100px;
outline: none;
}
/* Subtle Delete Action Elements */
.delete-btn {
color: #585b70;
cursor: pointer;
font-size: 11px;
padding: 0 2px;
transition: color 0.15s ease;
display: none;
}
.session-wrapper:hover .delete-btn {
display: inline-block;
}
.delete-btn:hover {
color: #f38ba8 !important;
}
.bar-actions-group {
margin-left: auto;
display: flex;
gap: 16px;
align-items: center;
}
/* Unintrusive Model Selector UX */
.model-selector-wrapper {
display: flex;
align-items: center;
gap: 6px;
color: #94e2d5; /* Teal styling */
font-size: 12px;
}
.model-select-native {
background: #1e1e2e;
color: #94e2d5;
border: 1px solid rgba(148, 226, 213, 0.2);
font-family: monospace;
font-size: 11px;
padding: 2px 6px;
border-radius: 3px;
outline: none;
cursor: pointer;
}
.model-select-native:focus {
border-color: #94e2d5;
}
.new-session-btn {
color: #f5c2e7;
cursor: pointer;
font-weight: bold;
white-space: nowrap;
}
.wipe-workspace-btn {
color: #f38ba8; /* Warning Red */
cursor: pointer;
font-weight: normal;
white-space: nowrap;
opacity: 0.6;
transition: opacity 0.15s ease;
}
.wipe-workspace-btn:hover {
opacity: 1;
text-shadow: 0 0 8px rgba(243, 139, 168, 0.2);
}
/* --- PRIMARY TERMINAL AREA --- */
.terminal {
flex: 1;
display: flex;
flex-direction: column;
padding: 16px;
box-sizing: border-box;
overflow: hidden;
}
#log {
flex: 1;
overflow-y: auto;
padding-right: 8px;
}
.line {
white-space: pre-wrap;
margin: 2px 0;
}
.user { color: #a6e3a1; }
.ai { color: #f9e2af; }
.sys { color: #7aa2f7; }
.err { color: #f38ba8; }
.tool { color: #f5c2e7; }
.input-row {
display: flex;
gap: 10px;
align-items: center;
border-top: 1px solid rgba(255,255,255,0.08);
padding-top: 10px;
}
.prompt { color: #cdd6f4; white-space: nowrap; }
#input {
flex: 1;
background: transparent;
border: none;
outline: none;
color: #cdd6f4;
font-size: 14px;
font-family: monospace;
}
/* --- BOTTOM HELP LEGEND --- */
.footer-help {
background: #11151c;
border-top: 1px solid rgba(255, 255, 255, 0.05);
padding: 6px 12px;
font-size: 11px;
color: #585b70;
display: flex;
gap: 20px;
overflow-x: auto;
white-space: nowrap;
}
.footer-help span strong {
color: #cdd6f4;
}
</style>
</head>
<body data-webtui-theme="catppuccin-mocha">
<!-- Block Character Filled ASCII Header -->
<div class="banner-header">
<pre class="ascii-title">
█████╗ ██████╗ ████████╗██╗ ██╗██████╗ ███████╗██╗ ██╗ ██████╗ ██████╗ ██████╗ ██╗███╗ ██╗ ██████╗
██╔══██╗██╔══██╗╚══██╔══╝╚██╗ ██╔╝██╔══██╗██╔════╝██║ ██║ ██╔════╝██╔═══██╗██╔══██╗██║████╗ ██║██╔════╝
███████║██████╔╝ ██║ ╚████╔╝ ██║ ██║█████╗ ██║ ██║ ██║ ██║ ██║██║ ██║██║██╔██╗ ██║██║ ███╗
██╔══██║██╔══██╗ ██║ ╚██╔╝ ██║ ██║██╔══╝ ╚██╗ ██╔╝ ██║ ██║ ██║██║ ██║██║██║╚██╗██║██║ ██║
██║ ██║██║ ██║ ██║ ██║ ██████╔╝███████╗ ╚████╔╝ ╚██████╗╚██████╔╝██████╔╝██║██║ ╚████║╚██████╔╝
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ </pre>
<div class="sub-tagline">Local Agent Orchestration Terminal • Core Runtime: Qwen2.5 + Dynamic Toolkits</div>
</div>
<!-- Multiplexer Navigation Bar -->
<div class="session-bar" id="sessionBar"></div>
<!-- Primary Interface Window -->
<div class="terminal">
<div id="log"></div>
<div class="input-row">
<span class="prompt" id="terminalPrompt">user@qwen:[~]~$</span>
<input id="input" autocomplete="off" autofocus placeholder="Type a message or /command..." />
</div>
</div>
<!-- Footer Keyboard Legend -->
<div class="footer-help">
<span><strong>Double-Click Tab</strong> Rename</span>
<span><strong>Hover + [x]</strong> Delete single</span>
<span><strong>/model [name]</strong> Switch core model</span>
<span><strong>[Wipe All]</strong> Factory reset data</span>
<span><strong>/new [name]</strong> Create context</span>
<span><strong>/clear</strong> Clear view</span>
</div>
<script type="module">
import { get, set, del } from 'https://cdn.jsdelivr.net/npm/idb-keyval@6/+esm';
const log = document.getElementById("log");
const input = document.getElementById("input");
const sessionBar = document.getElementById("sessionBar");
const terminalPrompt = document.getElementById("terminalPrompt");
const OLLAMA_URL = "http://localhost:11434/api/chat";
const REGISTRY_KEY = "multiplexer_registry_config";
const SESSION_PREFIX = "multiplexer_session_";
// List of available target models on your Ollama layer
const POPULAR_MODELS = [
"qwen2.5:7b",
"qwen2.5:14b",
"qwen2.5:latest",
"llama3.1:latest",
"mistral:latest",
"phi3:latest"
];
/* ---------------------------
MEMORY SYSTEM STRUCTURES
----------------------------*/
const SYSTEM_PROMPT = {
role: "system",
content: `You are a tool-using assistant.
When you need to use a tool to look up information or perform calculations, you MUST respond ONLY with a raw valid JSON object matching this schema:
{
"tool": "tool_name",
"arguments": { "key": "value" }
}
Do not include conversational text when calling a tool.
Otherwise, respond normally to the user in plain text.
Available tools:
- get_time: Returns the current date and time. No arguments needed.
- calc: Calculates a mathematical expression. Requires argument "expression" (string).`
};
let registry = {
currentActiveId: null,
activeModel: "qwen2.5:7b", // Default fallback model
list: []
};
let activeMessages = [SYSTEM_PROMPT];
let fullPersistentHistory = [];
/* ---------------------------
CONTEXT PRUNING (Sliding Window)
----------------------------*/
function pruneActiveMemory() {
const systemPrompt = activeMessages[0];
let chatHistory = activeMessages.slice(1);
const MAX_ACTIVE_ITEMS = 14;
if (chatHistory.length > MAX_ACTIVE_ITEMS) {
chatHistory = chatHistory.slice(chatHistory.length - MAX_ACTIVE_ITEMS);
if (chatHistory[0].role === "user" && chatHistory[0].content.startsWith("TOOL RESULT")) {
chatHistory.shift();
}
}
activeMessages = [systemPrompt, ...chatHistory];
}
/* ---------------------------
PERSISTENCE LAYER (IndexedDB)
----------------------------*/
async function saveAllToBrowser() {
try {
await set(REGISTRY_KEY, registry);
if (registry.currentActiveId) {
await set(SESSION_PREFIX + registry.currentActiveId, fullPersistentHistory);
}
} catch (err) {
console.error("Storage save operation encountered errors:", err);
}
}
async function loadSessionData(sessionId) {
try {
const savedData = await get(SESSION_PREFIX + sessionId);
fullPersistentHistory = savedData || [];
activeMessages = [SYSTEM_PROMPT, ...fullPersistentHistory.filter(m => !m.isToolActivityLog)];
pruneActiveMemory();
renderTerminalScreen();
renderTopMultiplexerBar();
} catch (err) {
print("Database retrieval block failure: " + err.message, "err");
}
}
/* ---------------------------
UI RENDERING MANAGERS
----------------------------*/
function renderTopMultiplexerBar() {
sessionBar.innerHTML = "";
registry.list.forEach((session, index) => {
const wrapper = document.createElement("div");
wrapper.className = "session-wrapper";
const tab = document.createElement("span");
const isActive = session.id === registry.currentActiveId;
tab.className = `session-tab ${isActive ? 'active' : ''}`;
tab.textContent = `${index}: ${session.name}`;
tab.addEventListener("click", (e) => {
if (!isActive && tab.tagName === 'SPAN') switchSession(index);
});
tab.addEventListener("dblclick", (e) => {
e.stopPropagation();
const editorInput = document.createElement("input");
editorInput.type = "text";
editorInput.className = "rename-input";
editorInput.value = session.name;
const finishRename = async () => {
const freshName = editorInput.value.trim();
if (freshName && freshName !== session.name) {
session.name = freshName;
await saveAllToBrowser();
renderTerminalScreen();
}
renderTopMultiplexerBar();
};
editorInput.addEventListener("keydown", (ke) => {
if (ke.key === "Enter") finishRename();
if (ke.key === "Escape") renderTopMultiplexerBar();
});
editorInput.addEventListener("blur", finishRename);
wrapper.replaceChild(editorInput, tab);
editorInput.focus();
editorInput.select();
});
wrapper.appendChild(tab);
const delBtn = document.createElement("span");
delBtn.className = "delete-btn";
delBtn.textContent = "[x]";
delBtn.title = "Delete Session";
delBtn.addEventListener("click", (e) => {
e.stopPropagation();
handleDeleteSessionConfirmation(index);
});
wrapper.appendChild(delBtn);
sessionBar.appendChild(wrapper);
});
const actionsGroup = document.createElement("div");
actionsGroup.className = "bar-actions-group";
// Unintrusive Model Selection Element
const modelWrapper = document.createElement("div");
modelWrapper.className = "model-selector-wrapper";
modelWrapper.innerHTML = `<span>model:</span>`;
const modelSelect = document.createElement("select");
modelSelect.className = "model-select-native";
// Make sure the active model is always in the list options
const modelsToRender = [...new Set([registry.activeModel, ...POPULAR_MODELS])];
modelsToRender.forEach(modelName => {
const opt = document.createElement("option");
opt.value = modelName;
opt.textContent = modelName;
if (modelName === registry.activeModel) opt.selected = true;
modelSelect.appendChild(opt);
});
modelSelect.addEventListener("change", async (e) => {
await switchActiveModel(e.target.value);
});
modelWrapper.appendChild(modelSelect);
actionsGroup.appendChild(modelWrapper);
const createBtn = document.createElement("span");
createBtn.className = "new-session-btn";
createBtn.textContent = "+ [New]";
createBtn.addEventListener("click", () => handleCreateSession());
actionsGroup.appendChild(createBtn);
const wipeBtn = document.createElement("span");
wipeBtn.className = "wipe-workspace-btn";
wipeBtn.textContent = "[Wipe All]";
wipeBtn.title = "Irreversibly delete all database history records";
wipeBtn.addEventListener("click", () => handleWipeAllSessionsConfirmation());
actionsGroup.appendChild(wipeBtn);
sessionBar.appendChild(actionsGroup);
}
function renderTerminalScreen() {
log.innerHTML = "";
const currentSession = registry.list.find(s => s.id === registry.currentActiveId);
const sessionName = currentSession ? currentSession.name : "none";
terminalPrompt.textContent = `user@qwen:[${sessionName}]~$`;
if (!currentSession) {
print("No active environment available. Please click '+ [New]' or type /new.", "sys");
return;
}
if (fullPersistentHistory.length === 0) {
print(`Welcome to session [${sessionName}]. Current Core: ${registry.activeModel}. Type your message or run /help.`, "sys");
return;
}
for (const msg of fullPersistentHistory) {
if (msg.role === "user" && !msg.content.startsWith("TOOL RESULT")) {
print(`user@qwen:[${sessionName}]~$ ${msg.content}`, "user");
} else if (msg.role === "assistant") {
if (!msg.content.trim().startsWith("{")) {
print(msg.content, "ai");
}
} else if (msg.role === "system" && msg.isToolActivityLog) {
print(msg.content, "tool");
}
}
}
function print(text, cls="sys") {
const div = document.createElement("div");
div.className = "line " + cls;
div.textContent = text;
log.appendChild(div);
log.scrollTop = log.scrollHeight;
return div;
}
/* ---------------------------
MULTIPLEXER OPERATIONS
----------------------------*/
async function switchActiveModel(newModelName) {
if (!newModelName || !newModelName.trim()) return;
registry.activeModel = newModelName.trim();
await saveAllToBrowser();
print(`Engine target processing runtime hot-swapped to: ${registry.activeModel}`, "sys");
renderTopMultiplexerBar();
}
async function handleCreateSession(customName = null) {
const id = "s_" + Date.now();
const name = customName ? customName.trim() : `session-${registry.list.length}`;
registry.list.push({ id, name });
registry.currentActiveId = id;
fullPersistentHistory = [];
activeMessages = [SYSTEM_PROMPT];
await saveAllToBrowser();
renderTopMultiplexerBar();
renderTerminalScreen();
}
async function switchSession(index) {
const target = registry.list[index];
if (!target) {
print(`Error: Session index context [${index}] not tracked inside current workspace registry profiles.`, "err");
return;
}
registry.currentActiveId = target.id;
await saveAllToBrowser();
await loadSessionData(target.id);
}
async function handleDeleteSessionConfirmation(index) {
const targetSession = registry.list[index];
if (!targetSession) return;
if (confirm(`Are you sure you want to permanently delete session "${targetSession.name}"?`)) {
await del(SESSION_PREFIX + targetSession.id);
registry.list.splice(index, 1);
if (registry.currentActiveId === targetSession.id) {
if (registry.list.length > 0) {
const nextIndexFallback = Math.max(0, index - 1);
registry.currentActiveId = registry.list[nextIndexFallback].id;
await set(REGISTRY_KEY, registry);
await loadSessionData(registry.currentActiveId);
} else {
registry.currentActiveId = null;
fullPersistentHistory = [];
activeMessages = [SYSTEM_PROMPT];
await set(REGISTRY_KEY, registry);
renderTopMultiplexerBar();
renderTerminalScreen();
}
} else {
await set(REGISTRY_KEY, registry);
renderTopMultiplexerBar();
}
print(`Successfully removed terminal workspace tracking profile target container: [${targetSession.name}]`, "sys");
}
}
async function handleWipeAllSessionsConfirmation() {
const explicitPromptText = "⚠️ WARNING: This will permanently delete ALL sessions and history logs. This action cannot be undone.\n\nType 'WIPE' to confirm initialization:";
const confirmationInput = prompt(explicitPromptText);
if (confirmationInput === "WIPE") {
print("Initiating full environment memory database purge...", "err");
for (const session of registry.list) {
await del(SESSION_PREFIX + session.id);
}
await del(REGISTRY_KEY);
registry.list = [];
registry.currentActiveId = null;
registry.activeModel = "qwen2.5:7b";
fullPersistentHistory = [];
activeMessages = [SYSTEM_PROMPT];
print("Database scrub complete. Re-initializing factory general configuration environments.", "sys");
await handleCreateSession("general");
} else {
print("Workspace wipe sequence aborted by user action.", "sys");
}
}
/* ---------------------------
SLASH COMMAND INTERCEPTOR
----------------------------*/
async function handleSlashCommand(rawInput) {
const parts = rawInput.trim().split(" ");
const cmd = parts[0].toLowerCase();
const args = parts.slice(1).join(" ");
print(`user@qwen:[command]~$ ${rawInput}`, "user");
switch (cmd) {
case "/new":
await handleCreateSession(args || null);
break;
case "/model":
if (!args.trim()) {
print(`Current active context orchestration engine model target: ${registry.activeModel}`, "sys");
print(`Usage option: /model [ollama-model-tag] (e.g., /model llama3.1:latest)`, "sys");
} else {
await switchActiveModel(args);
}
break;
case "/switch":
const targetIndex = parseInt(args, 10);
if (isNaN(targetIndex)) {
print("Usage error syntax structural rule: /switch [numerical-index-id]", "err");
} else {
await switchSession(targetIndex);
}
break;
case "/rename":
if (!args.trim()) {
print("Usage error syntax: /rename [new-session-tag-name]", "err");
} else {
const activeItem = registry.list.find(s => s.id === registry.currentActiveId);
if (activeItem) {
activeItem.name = args.trim();
await saveAllToBrowser();
renderTopMultiplexerBar();
renderTerminalScreen();
}
}
break;
case "/delete":
const delIdx = parseInt(args, 10);
if (isNaN(delIdx)) {
print("Usage error syntax: /delete [numerical-index-id]", "err");
} else {
await handleDeleteSessionConfirmation(delIdx);
}
break;
case "/wipeall":
await handleWipeAllSessionsConfirmation();
break;
case "/ls":
print("--- Active Workspace Environments ---", "sys");
registry.list.forEach((s, idx) => {
const activeMarker = s.id === registry.currentActiveId ? " (active)" : "";
print(` [${idx}]: ${s.name}${activeMarker}`, "sys");
});
break;
case "/clear":
log.innerHTML = "";
break;
case "/help":
print("Available workspace commands:", "sys");
print(" /model [tag] - Hot-swap core backend model parsing engine dynamically", "sys");
print(" /new [name] - Create a brand new independent conversation container", "sys");
print(" /switch [index] - Hot-swap active screen output to alternate target history", "sys");
print(" /rename [name] - Changes character pointer tracking tag assigned to window row", "sys");
print(" /delete [index] - Deletes and drops tracked IndexedDB context elements from storage", "sys");
print(" /wipeall - Destroys and cleans entire memory workspace databases", "sys");
print(" /ls - Lists out all tracking logs captured via IndexedDB system paths", "sys");
print(" /clear - Standard display layer terminal clear operation layout reset", "sys");
break;
default:
print(`Unrecognized multiplexer control sequence pipeline call target '${cmd}'. Try calling /help.`, "err");
}
}
/* ---------------------------
LOCAL ENVIRONMENT TOOLS
----------------------------*/
function runTool(name, args) {
if (name === "get_time") {
return { time: new Date().toISOString() };
}
if (name === "calc") {
try {
const evaluate = new Function(`return (${args.expression})`);
return { result: evaluate() };
} catch {
return { error: "invalid expression" };
}
}
return { error: "unknown tool" };
}
function tryParseTool(text) {
try {
const obj = JSON.parse(text.trim());
if (obj.tool && obj.arguments) return obj;
} catch {}
return null;
}
/* ---------------------------
STREAM INTERFACE DRIVER
----------------------------*/
async function streamAI(res) {
const div = print("", "ai");
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let fullText = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
if (!line.trim()) continue;
try {
const json = JSON.parse(line);
const token = json.message?.content || "";
div.textContent += token;
fullText += token;
log.scrollTop = log.scrollHeight;
} catch {}
}
}
return fullText;
}
async function callModel() {
const res = await fetch(OLLAMA_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: registry.activeModel, // Dynamically injection based on selection config
stream: true,
messages: activeMessages
})
});
if (!res.ok || !res.body) throw new Error(`Ollama communication pipeline break during request using model ${registry.activeModel}`);
return res;
}
/* ---------------------------
AGENTIC EXECUTION RUNTIME ENGINE
----------------------------*/
async function askAI(prompt) {
const currentSession = registry.list.find(s => s.id === registry.currentActiveId);
const sessionLabel = currentSession ? currentSession.name : "~";
print(`user@qwen:[${sessionLabel}]~$ ${prompt}`, "user");
const userMsg = { role: "user", content: prompt };
activeMessages.push(userMsg);
fullPersistentHistory.push(userMsg);
let isAgentProcessing = true;
let executionDepthGuard = 5;
try {
while (isAgentProcessing && executionDepthGuard > 0) {
executionDepthGuard--;
const responseStream = await callModel();
const aiResponseText = await streamAI(responseStream);
const assistantMsg = { role: "assistant", content: aiResponseText };
activeMessages.push(assistantMsg);
fullPersistentHistory.push(assistantMsg);
const parsedToolRequest = tryParseTool(aiResponseText);
if (parsedToolRequest) {
const actionMessage = `🔧 Executing tool: ${parsedToolRequest.tool}...`;
print(actionMessage, "tool");
fullPersistentHistory.push({ role: "system", content: actionMessage, isToolActivityLog: true });
const toolOutcome = runTool(parsedToolRequest.tool, parsedToolRequest.arguments);
const outcomeString = `📦 Response: ${JSON.stringify(toolOutcome)}`;
print(outcomeString, "tool");
fullPersistentHistory.push({ role: "system", content: outcomeString, isToolActivityLog: true });
const toolResultFeedback = {
role: "user",
content: `TOOL RESULT for '${parsedToolRequest.tool}': ${JSON.stringify(toolOutcome)}`
};
activeMessages.push(toolResultFeedback);
fullPersistentHistory.push(toolResultFeedback);
} else {
isAgentProcessing = false;
}
}
await saveAllToBrowser();
pruneActiveMemory();
} catch (err) {
print("Error encountered inside tracking node loops: " + err.message, "err");
}
}
/* ---------------------------
DOM INTERFACES INTERCEPT
----------------------------*/
input.addEventListener("keydown", async (e) => {
if (e.key === "Enter") {
const value = input.value.trim();
if (!value) return;
input.value = "";
if (value.startsWith("/")) {
await handleSlashCommand(value);
} else {
if (!registry.currentActiveId) {
print("No active environment available. Please execute '/new' command to spawn a session first.", "err");
return;
}
await askAI(value);
}
}
});
/* ---------------------------
SYSTEM ENTRY BOOT STRAPPER
----------------------------*/
async function boot() {
try {
const savedRegistry = await get(REGISTRY_KEY);
if (savedRegistry && savedRegistry.list.length > 0) {
registry = savedRegistry;
// Ensure configuration object structure compatibility if updating from previous version
if (!registry.activeModel) registry.activeModel = "qwen2.5:7b";
renderTopMultiplexerBar();
await loadSessionData(registry.currentActiveId);
} else {
await handleCreateSession("general");
}
} catch (e) {
console.warn("Storage profile structure state clean initialization step required:", e);
await handleCreateSession("general");
}
}
boot();
</script>
</body>
</html>
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (2)
Nice, looks solid!
Hy Leob,
It is indeed, don't forget to set setx OLLAMA_ORIGINS "*" , resolves CORS problems :-)