ux: ollama flow
This commit is contained in:
+57
-11
@@ -52,6 +52,8 @@ let ollamaOnline = true;
|
||||
let latestUpdate = null;
|
||||
let currentThreadId = "default";
|
||||
let currentNegotiationId = null;
|
||||
let latestOllamaStatus = null;
|
||||
const clickedOllamaActions = new Set();
|
||||
|
||||
if (window.lucide) {
|
||||
window.lucide.createIcons();
|
||||
@@ -585,6 +587,7 @@ async function refreshOllamaStatus() {
|
||||
|
||||
function renderOllamaStatus(status) {
|
||||
if (!ollamaStatusEl) return;
|
||||
latestOllamaStatus = status;
|
||||
const models = status.models?.length ? status.models.join(", ") : "None detected";
|
||||
const pillClass = status.installed && status.running && status.model_available ? "status-pill" : "status-pill warning";
|
||||
ollamaStatusEl.innerHTML = `
|
||||
@@ -595,14 +598,18 @@ function renderOllamaStatus(status) {
|
||||
${ollamaStatusItem("Model", status.configured_model || "")}
|
||||
${ollamaStatusItem("Pulled", status.model_available ? "Yes" : "No")}
|
||||
${ollamaStatusItem("URL", status.base_url || "")}
|
||||
${ollamaStatusItem("Auto Install", status.can_auto_install ? "Available" : "Unavailable")}
|
||||
${status.can_auto_install ? ollamaStatusItem("Auto Install", "Available") : ""}
|
||||
</div>
|
||||
${ollamaStatusItem("Installed Models", models)}
|
||||
${status.detail ? ollamaStatusItem("Detail", status.detail) : ""}
|
||||
`;
|
||||
if (ollamaInstallButton) ollamaInstallButton.disabled = Boolean(status.installed);
|
||||
if (ollamaInstallButton) {
|
||||
ollamaInstallButton.hidden = !status.can_auto_install;
|
||||
ollamaInstallButton.disabled = Boolean(status.installed) || !status.can_auto_install;
|
||||
}
|
||||
if (ollamaLaunchButton) ollamaLaunchButton.disabled = !status.installed || Boolean(status.running);
|
||||
if (ollamaPullButton) ollamaPullButton.disabled = !status.running || Boolean(status.model_available);
|
||||
updateOllamaAttention(status);
|
||||
}
|
||||
|
||||
function ollamaStatusItem(label, value) {
|
||||
@@ -630,6 +637,29 @@ async function postOllamaAction(endpoint, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function markOllamaActionClicked(action) {
|
||||
if (action) clickedOllamaActions.add(action);
|
||||
updateOllamaAttention();
|
||||
}
|
||||
|
||||
function setOllamaButtonAttention(button, action, active) {
|
||||
if (!button) return;
|
||||
const shouldPulse = active && !clickedOllamaActions.has(action) && !button.disabled && !button.hidden;
|
||||
button.classList.toggle("attention-pulse", shouldPulse);
|
||||
}
|
||||
|
||||
function updateOllamaAttention(status = null) {
|
||||
const currentStatus = status || latestOllamaStatus;
|
||||
if (!currentStatus) return;
|
||||
const ready = Boolean(currentStatus.installed && currentStatus.running && currentStatus.model_available);
|
||||
ollamaToggle?.classList.toggle("attention-pulse", !ready);
|
||||
setOllamaButtonAttention(ollamaDownloadButton, "download", !currentStatus.installed);
|
||||
setOllamaButtonAttention(ollamaInstallButton, "install", !currentStatus.installed && currentStatus.can_auto_install);
|
||||
setOllamaButtonAttention(ollamaLaunchButton, "launch", currentStatus.installed && !currentStatus.running);
|
||||
setOllamaButtonAttention(ollamaPullButton, "pull", currentStatus.running && !currentStatus.model_available);
|
||||
if (ready) clickedOllamaActions.clear();
|
||||
}
|
||||
|
||||
function configuredOllamaModel() {
|
||||
return document.getElementById("ollama-model")?.value || "";
|
||||
}
|
||||
@@ -980,20 +1010,24 @@ async function checkHealth() {
|
||||
ollamaOnline = Boolean(health.online);
|
||||
if (!ollamaOnline) {
|
||||
statusEl.textContent = "Offline";
|
||||
setWarning(health.message || "Ollama is offline. Start Ollama before chatting.");
|
||||
setWarning("Ollama needs attention. Open the Ollama tab and use the pulsing action button.");
|
||||
ollamaToggle?.classList.add("attention-pulse");
|
||||
return false;
|
||||
}
|
||||
if (health.model_available === false) {
|
||||
setWarning(`Ollama is online, but model "${health.model}" is not pulled. Run: ollama pull ${health.model}`);
|
||||
setWarning(`Ollama needs the configured model "${health.model}". Open the Ollama tab and use Install Model.`);
|
||||
ollamaToggle?.classList.add("attention-pulse");
|
||||
} else {
|
||||
setWarning("");
|
||||
ollamaToggle?.classList.remove("attention-pulse");
|
||||
}
|
||||
statusEl.textContent = "Ready";
|
||||
return true;
|
||||
} catch (error) {
|
||||
ollamaOnline = false;
|
||||
statusEl.textContent = "Offline";
|
||||
setWarning(`Could not check Ollama health: ${error.message}`);
|
||||
setWarning("Could not check Ollama health. Open the Ollama tab and use the pulsing action button.");
|
||||
ollamaToggle?.classList.add("attention-pulse");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1174,10 +1208,22 @@ memoryToggle?.addEventListener("click", () => toggleSidebarPanel("memory"));
|
||||
ollamaToggle?.addEventListener("click", () => toggleSidebarPanel("ollama"));
|
||||
ollamaForm?.addEventListener("submit", saveOllamaConfig);
|
||||
ollamaRefreshButton?.addEventListener("click", refreshOllamaStatus);
|
||||
ollamaDownloadButton?.addEventListener("click", () => postOllamaAction("/api/ollama/download"));
|
||||
ollamaInstallButton?.addEventListener("click", () => postOllamaAction("/api/ollama/install"));
|
||||
ollamaLaunchButton?.addEventListener("click", () => postOllamaAction("/api/ollama/launch"));
|
||||
ollamaPullButton?.addEventListener("click", () => postOllamaAction("/api/ollama/pull", { body: { model: configuredOllamaModel() } }));
|
||||
ollamaDownloadButton?.addEventListener("click", () => {
|
||||
markOllamaActionClicked("download");
|
||||
postOllamaAction("/api/ollama/download");
|
||||
});
|
||||
ollamaInstallButton?.addEventListener("click", () => {
|
||||
markOllamaActionClicked("install");
|
||||
postOllamaAction("/api/ollama/install");
|
||||
});
|
||||
ollamaLaunchButton?.addEventListener("click", () => {
|
||||
markOllamaActionClicked("launch");
|
||||
postOllamaAction("/api/ollama/launch");
|
||||
});
|
||||
ollamaPullButton?.addEventListener("click", () => {
|
||||
markOllamaActionClicked("pull");
|
||||
postOllamaAction("/api/ollama/pull", { body: { model: configuredOllamaModel() } });
|
||||
});
|
||||
updateCheckButton?.addEventListener("click", checkForUpdate);
|
||||
updateInstallButton?.addEventListener("click", installUpdate);
|
||||
updateOpenReleasesButton?.addEventListener("click", openReleasesPage);
|
||||
@@ -1194,7 +1240,7 @@ async function sendMessage() {
|
||||
if (!message || input.disabled) return;
|
||||
const healthy = await checkHealth();
|
||||
if (!healthy) {
|
||||
addMessage("assistant warning-message", "Ollama is offline. Start Ollama, then try again.");
|
||||
addMessage("assistant warning-message", "Ollama needs attention before chat can continue. Open the Ollama tab and press the pulsing action button, then try again.");
|
||||
return;
|
||||
}
|
||||
input.value = "";
|
||||
@@ -1257,7 +1303,7 @@ async function sendMessage() {
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error.message.includes("503")
|
||||
? "Ollama is offline or unreachable. Start Ollama, then try again."
|
||||
? "Ollama needs attention before chat can continue. Open the Ollama tab and press the pulsing action button, then try again."
|
||||
: `Chat failed: ${error.message}`;
|
||||
setWarning(message);
|
||||
setMessageMarkdown(assistantNode, message);
|
||||
|
||||
Reference in New Issue
Block a user