ux: ollama flow
This commit is contained in:
+1
-1
@@ -53,7 +53,7 @@ class OllamaAgent:
|
|||||||
"online": False,
|
"online": False,
|
||||||
"model": self.model,
|
"model": self.model,
|
||||||
"base_url": self.base_url,
|
"base_url": self.base_url,
|
||||||
"message": f"Ollama is offline or unreachable at {self.base_url}. Start Ollama and make sure the model is pulled.",
|
"message": f"Ollama is offline or unreachable at {self.base_url}. Open the Ollama tab and use the recommended action.",
|
||||||
"detail": str(exc),
|
"detail": str(exc),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+57
-11
@@ -52,6 +52,8 @@ let ollamaOnline = true;
|
|||||||
let latestUpdate = null;
|
let latestUpdate = null;
|
||||||
let currentThreadId = "default";
|
let currentThreadId = "default";
|
||||||
let currentNegotiationId = null;
|
let currentNegotiationId = null;
|
||||||
|
let latestOllamaStatus = null;
|
||||||
|
const clickedOllamaActions = new Set();
|
||||||
|
|
||||||
if (window.lucide) {
|
if (window.lucide) {
|
||||||
window.lucide.createIcons();
|
window.lucide.createIcons();
|
||||||
@@ -585,6 +587,7 @@ async function refreshOllamaStatus() {
|
|||||||
|
|
||||||
function renderOllamaStatus(status) {
|
function renderOllamaStatus(status) {
|
||||||
if (!ollamaStatusEl) return;
|
if (!ollamaStatusEl) return;
|
||||||
|
latestOllamaStatus = status;
|
||||||
const models = status.models?.length ? status.models.join(", ") : "None detected";
|
const models = status.models?.length ? status.models.join(", ") : "None detected";
|
||||||
const pillClass = status.installed && status.running && status.model_available ? "status-pill" : "status-pill warning";
|
const pillClass = status.installed && status.running && status.model_available ? "status-pill" : "status-pill warning";
|
||||||
ollamaStatusEl.innerHTML = `
|
ollamaStatusEl.innerHTML = `
|
||||||
@@ -595,14 +598,18 @@ function renderOllamaStatus(status) {
|
|||||||
${ollamaStatusItem("Model", status.configured_model || "")}
|
${ollamaStatusItem("Model", status.configured_model || "")}
|
||||||
${ollamaStatusItem("Pulled", status.model_available ? "Yes" : "No")}
|
${ollamaStatusItem("Pulled", status.model_available ? "Yes" : "No")}
|
||||||
${ollamaStatusItem("URL", status.base_url || "")}
|
${ollamaStatusItem("URL", status.base_url || "")}
|
||||||
${ollamaStatusItem("Auto Install", status.can_auto_install ? "Available" : "Unavailable")}
|
${status.can_auto_install ? ollamaStatusItem("Auto Install", "Available") : ""}
|
||||||
</div>
|
</div>
|
||||||
${ollamaStatusItem("Installed Models", models)}
|
${ollamaStatusItem("Installed Models", models)}
|
||||||
${status.detail ? ollamaStatusItem("Detail", status.detail) : ""}
|
${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 (ollamaLaunchButton) ollamaLaunchButton.disabled = !status.installed || Boolean(status.running);
|
||||||
if (ollamaPullButton) ollamaPullButton.disabled = !status.running || Boolean(status.model_available);
|
if (ollamaPullButton) ollamaPullButton.disabled = !status.running || Boolean(status.model_available);
|
||||||
|
updateOllamaAttention(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ollamaStatusItem(label, value) {
|
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() {
|
function configuredOllamaModel() {
|
||||||
return document.getElementById("ollama-model")?.value || "";
|
return document.getElementById("ollama-model")?.value || "";
|
||||||
}
|
}
|
||||||
@@ -980,20 +1010,24 @@ async function checkHealth() {
|
|||||||
ollamaOnline = Boolean(health.online);
|
ollamaOnline = Boolean(health.online);
|
||||||
if (!ollamaOnline) {
|
if (!ollamaOnline) {
|
||||||
statusEl.textContent = "Offline";
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (health.model_available === 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 {
|
} else {
|
||||||
setWarning("");
|
setWarning("");
|
||||||
|
ollamaToggle?.classList.remove("attention-pulse");
|
||||||
}
|
}
|
||||||
statusEl.textContent = "Ready";
|
statusEl.textContent = "Ready";
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ollamaOnline = false;
|
ollamaOnline = false;
|
||||||
statusEl.textContent = "Offline";
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1174,10 +1208,22 @@ memoryToggle?.addEventListener("click", () => toggleSidebarPanel("memory"));
|
|||||||
ollamaToggle?.addEventListener("click", () => toggleSidebarPanel("ollama"));
|
ollamaToggle?.addEventListener("click", () => toggleSidebarPanel("ollama"));
|
||||||
ollamaForm?.addEventListener("submit", saveOllamaConfig);
|
ollamaForm?.addEventListener("submit", saveOllamaConfig);
|
||||||
ollamaRefreshButton?.addEventListener("click", refreshOllamaStatus);
|
ollamaRefreshButton?.addEventListener("click", refreshOllamaStatus);
|
||||||
ollamaDownloadButton?.addEventListener("click", () => postOllamaAction("/api/ollama/download"));
|
ollamaDownloadButton?.addEventListener("click", () => {
|
||||||
ollamaInstallButton?.addEventListener("click", () => postOllamaAction("/api/ollama/install"));
|
markOllamaActionClicked("download");
|
||||||
ollamaLaunchButton?.addEventListener("click", () => postOllamaAction("/api/ollama/launch"));
|
postOllamaAction("/api/ollama/download");
|
||||||
ollamaPullButton?.addEventListener("click", () => postOllamaAction("/api/ollama/pull", { body: { model: configuredOllamaModel() } }));
|
});
|
||||||
|
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);
|
updateCheckButton?.addEventListener("click", checkForUpdate);
|
||||||
updateInstallButton?.addEventListener("click", installUpdate);
|
updateInstallButton?.addEventListener("click", installUpdate);
|
||||||
updateOpenReleasesButton?.addEventListener("click", openReleasesPage);
|
updateOpenReleasesButton?.addEventListener("click", openReleasesPage);
|
||||||
@@ -1194,7 +1240,7 @@ async function sendMessage() {
|
|||||||
if (!message || input.disabled) return;
|
if (!message || input.disabled) return;
|
||||||
const healthy = await checkHealth();
|
const healthy = await checkHealth();
|
||||||
if (!healthy) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
input.value = "";
|
input.value = "";
|
||||||
@@ -1257,7 +1303,7 @@ async function sendMessage() {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error.message.includes("503")
|
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}`;
|
: `Chat failed: ${error.message}`;
|
||||||
setWarning(message);
|
setWarning(message);
|
||||||
setMessageMarkdown(assistantNode, message);
|
setMessageMarkdown(assistantNode, message);
|
||||||
|
|||||||
@@ -1078,6 +1078,31 @@ button.secondary {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attention-pulse {
|
||||||
|
position: relative;
|
||||||
|
border-color: rgba(47, 125, 50, 0.72) !important;
|
||||||
|
box-shadow: 0 0 0 0 rgba(55, 148, 61, 0.54);
|
||||||
|
animation: green-attention-pulse 1.35s ease-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attention-pulse:disabled,
|
||||||
|
.attention-pulse[hidden] {
|
||||||
|
animation: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes green-attention-pulse {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(55, 148, 61, 0.58);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
box-shadow: 0 0 0 10px rgba(55, 148, 61, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(55, 148, 61, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.update-box {
|
.update-box {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|||||||
Reference in New Issue
Block a user