feat: versioning, feat: in app configueration, feat: single exe, feat: reasoning, action: inital version, fix: config saving
Build Release EXE / build-windows-exe (release) Successful in 1m5s
Build Release EXE / build-windows-exe (release) Successful in 1m5s
This commit is contained in:
+295
-2
@@ -7,8 +7,35 @@ const warningEl = document.getElementById("warning");
|
||||
const memoryInspectorEl = document.getElementById("memory-inspector");
|
||||
const memoryRefreshButton = document.getElementById("memory-refresh");
|
||||
const memoryClearButton = document.getElementById("memory-clear");
|
||||
const configForm = document.getElementById("config-form");
|
||||
const configRefreshButton = document.getElementById("config-refresh");
|
||||
const configStatusEl = document.getElementById("config-status");
|
||||
const configPathsEl = document.getElementById("config-paths");
|
||||
const settingsToggle = document.getElementById("settings-toggle");
|
||||
const memoryToggle = document.getElementById("memory-toggle");
|
||||
const ollamaToggle = document.getElementById("ollama-toggle");
|
||||
const settingsPanel = document.getElementById("settings-panel");
|
||||
const memoryPanel = document.getElementById("memory-panel");
|
||||
const ollamaPanel = document.getElementById("ollama-panel");
|
||||
const ollamaForm = document.getElementById("ollama-config-form");
|
||||
const ollamaRefreshButton = document.getElementById("ollama-refresh");
|
||||
const ollamaDownloadButton = document.getElementById("ollama-download");
|
||||
const ollamaInstallButton = document.getElementById("ollama-install");
|
||||
const ollamaLaunchButton = document.getElementById("ollama-launch");
|
||||
const ollamaPullButton = document.getElementById("ollama-pull");
|
||||
const ollamaStatusEl = document.getElementById("ollama-status");
|
||||
const ollamaMessageEl = document.getElementById("ollama-message");
|
||||
const updateCheckButton = document.getElementById("update-check");
|
||||
const updateInstallButton = document.getElementById("update-install");
|
||||
const updateOpenReleasesButton = document.getElementById("update-open-releases");
|
||||
const updateStatusEl = document.getElementById("update-status");
|
||||
|
||||
let ollamaOnline = true;
|
||||
let latestUpdate = null;
|
||||
|
||||
if (window.lucide) {
|
||||
window.lucide.createIcons();
|
||||
}
|
||||
|
||||
function addMessage(role, text) {
|
||||
const node = document.createElement("div");
|
||||
@@ -381,6 +408,255 @@ function setWarning(text) {
|
||||
warningEl.textContent = text || "";
|
||||
}
|
||||
|
||||
function fetchErrorMessage(error) {
|
||||
if (error instanceof TypeError && /fetch/i.test(error.message)) {
|
||||
return "TraderAI backend is not reachable. Close this app window and launch TraderAI.exe again.";
|
||||
}
|
||||
return error.message;
|
||||
}
|
||||
|
||||
const configFieldIds = {
|
||||
ollama_base_url: "config-ollama-base-url",
|
||||
ollama_model: "config-ollama-model",
|
||||
ollama_num_ctx: "config-ollama-num-ctx",
|
||||
uex_base_url: "config-uex-base-url",
|
||||
uex_secret_key: "config-uex-secret-key",
|
||||
uex_bearer_token: "config-uex-bearer-token",
|
||||
traderai_user_name: "config-traderai-user-name",
|
||||
traderai_memory_path: "config-traderai-memory-path",
|
||||
uex_notification_poll_seconds: "config-uex-notification-poll-seconds",
|
||||
require_write_approval: "config-require-write-approval",
|
||||
};
|
||||
|
||||
const ollamaFieldIds = {
|
||||
ollama_base_url: "ollama-base-url",
|
||||
ollama_model: "ollama-model",
|
||||
ollama_num_ctx: "ollama-num-ctx",
|
||||
};
|
||||
|
||||
async function refreshConfig() {
|
||||
try {
|
||||
const response = await fetch("/api/config");
|
||||
const config = await response.json();
|
||||
renderConfig(config);
|
||||
} catch (error) {
|
||||
configStatusEl.textContent = `Config load failed: ${fetchErrorMessage(error)}`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderConfig(config) {
|
||||
const values = config.values || {};
|
||||
for (const [key, id] of Object.entries(configFieldIds)) {
|
||||
const field = document.getElementById(id);
|
||||
if (!field) continue;
|
||||
if (field.type === "checkbox") {
|
||||
field.checked = Boolean(values[key]);
|
||||
} else {
|
||||
field.value = values[key] ?? "";
|
||||
}
|
||||
}
|
||||
for (const [key, id] of Object.entries(ollamaFieldIds)) {
|
||||
const field = document.getElementById(id);
|
||||
if (!field) continue;
|
||||
field.value = values[key] ?? "";
|
||||
}
|
||||
configPathsEl.textContent = `App data: ${config.app_data_dir}\nConfig: ${config.config_path}\nLog: ${config.log_path}\nEdge profile: ${config.edge_profile_dir}`;
|
||||
configStatusEl.textContent = "";
|
||||
}
|
||||
|
||||
async function saveConfig(event) {
|
||||
event.preventDefault();
|
||||
const values = {};
|
||||
for (const [key, id] of Object.entries(configFieldIds)) {
|
||||
const field = document.getElementById(id);
|
||||
if (!field) continue;
|
||||
values[key] = field.type === "checkbox" ? field.checked : field.value;
|
||||
}
|
||||
configStatusEl.textContent = "Saving";
|
||||
try {
|
||||
const response = await fetch("/api/config", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ values }),
|
||||
});
|
||||
const result = await response.json();
|
||||
renderConfig(result);
|
||||
configStatusEl.textContent = result.message || "Saved";
|
||||
addMessage("assistant", "Config saved. Restart TraderAI for the new settings to fully apply.");
|
||||
} catch (error) {
|
||||
configStatusEl.textContent = `Config save failed: ${fetchErrorMessage(error)}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveOllamaConfig(event) {
|
||||
event.preventDefault();
|
||||
const values = {};
|
||||
for (const [key, id] of Object.entries(ollamaFieldIds)) {
|
||||
const field = document.getElementById(id);
|
||||
if (!field) continue;
|
||||
values[key] = field.value;
|
||||
}
|
||||
setOllamaMessage("Saving Ollama config");
|
||||
try {
|
||||
const response = await fetch("/api/config", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ values }),
|
||||
});
|
||||
const result = await response.json();
|
||||
renderConfig(result);
|
||||
setOllamaMessage(result.message || "Saved");
|
||||
await refreshOllamaStatus();
|
||||
} catch (error) {
|
||||
setOllamaMessage(`Ollama config save failed: ${fetchErrorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshOllamaStatus() {
|
||||
if (!ollamaStatusEl) return;
|
||||
ollamaStatusEl.textContent = "Checking Ollama";
|
||||
try {
|
||||
const response = await fetch("/api/ollama/status");
|
||||
const status = await response.json();
|
||||
renderOllamaStatus(status);
|
||||
} catch (error) {
|
||||
ollamaStatusEl.textContent = `Ollama status failed: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderOllamaStatus(status) {
|
||||
if (!ollamaStatusEl) return;
|
||||
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 = `
|
||||
<div class="${pillClass}">${escapeHtml(status.message || "Unknown")}</div>
|
||||
<div class="ollama-status-grid">
|
||||
${ollamaStatusItem("Installed", status.installed ? "Yes" : "No")}
|
||||
${ollamaStatusItem("Running", status.running ? "Yes" : "No")}
|
||||
${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")}
|
||||
</div>
|
||||
${ollamaStatusItem("Installed Models", models)}
|
||||
${status.detail ? ollamaStatusItem("Detail", status.detail) : ""}
|
||||
`;
|
||||
if (ollamaInstallButton) ollamaInstallButton.disabled = Boolean(status.installed);
|
||||
if (ollamaLaunchButton) ollamaLaunchButton.disabled = !status.installed || Boolean(status.running);
|
||||
if (ollamaPullButton) ollamaPullButton.disabled = !status.running || Boolean(status.model_available);
|
||||
}
|
||||
|
||||
function ollamaStatusItem(label, value) {
|
||||
return `<div class="ollama-status-item"><strong>${escapeHtml(label)}</strong><span>${escapeHtml(String(value ?? ""))}</span></div>`;
|
||||
}
|
||||
|
||||
function setOllamaMessage(message) {
|
||||
if (ollamaMessageEl) ollamaMessageEl.textContent = message || "";
|
||||
}
|
||||
|
||||
async function postOllamaAction(endpoint, options = {}) {
|
||||
setOllamaMessage("Working");
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: options.body ? { "Content-Type": "application/json" } : undefined,
|
||||
body: options.body ? JSON.stringify(options.body) : undefined,
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.detail || `HTTP ${response.status}`);
|
||||
setOllamaMessage(result.message || "Done");
|
||||
await refreshOllamaStatus();
|
||||
} catch (error) {
|
||||
setOllamaMessage(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function configuredOllamaModel() {
|
||||
return document.getElementById("ollama-model")?.value || document.getElementById("config-ollama-model")?.value || "";
|
||||
}
|
||||
|
||||
async function checkForUpdate() {
|
||||
if (!updateStatusEl) return;
|
||||
updateStatusEl.textContent = "Checking releases";
|
||||
try {
|
||||
const response = await fetch("/api/update/check");
|
||||
const result = await response.json();
|
||||
latestUpdate = result;
|
||||
renderUpdateStatus(result);
|
||||
} catch (error) {
|
||||
updateStatusEl.textContent = `Update check failed: ${error.message}`;
|
||||
if (updateInstallButton) updateInstallButton.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function renderUpdateStatus(update) {
|
||||
if (!updateStatusEl) return;
|
||||
const lines = [
|
||||
`Current: ${update.current_version || "unknown"}`,
|
||||
`Latest: ${update.latest_version || "unknown"}`,
|
||||
update.message || "",
|
||||
].filter(Boolean);
|
||||
if (update.available && !update.asset_download_url) {
|
||||
lines.push("The release needs a TraderAI.exe attachment before the app can self-update.");
|
||||
}
|
||||
if (update.available && update.asset_download_url && !update.packaged) {
|
||||
lines.push("Self-update runs from the packaged desktop exe.");
|
||||
}
|
||||
updateStatusEl.textContent = lines.join("\n");
|
||||
if (updateInstallButton) {
|
||||
updateInstallButton.disabled = !update.available || !update.asset_download_url || !update.packaged;
|
||||
}
|
||||
}
|
||||
|
||||
async function installUpdate() {
|
||||
if (!updateStatusEl) return;
|
||||
updateStatusEl.textContent = "Downloading update";
|
||||
try {
|
||||
const response = await fetch("/api/update/install", { method: "POST" });
|
||||
const result = await response.json();
|
||||
latestUpdate = result;
|
||||
renderUpdateStatus(result);
|
||||
} catch (error) {
|
||||
updateStatusEl.textContent = `Update failed: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
function openReleasesPage() {
|
||||
const url = latestUpdate?.release_url || "https://git.hudsonriggs.systems/LambdaBankingConglomerate/TraderAI/releases";
|
||||
window.open(url, "_blank", "noreferrer");
|
||||
}
|
||||
|
||||
function toggleSidebarPanel(panelName) {
|
||||
const panels = {
|
||||
settings: { panel: settingsPanel, button: settingsToggle },
|
||||
memory: { panel: memoryPanel, button: memoryToggle },
|
||||
ollama: { panel: ollamaPanel, button: ollamaToggle },
|
||||
};
|
||||
const target = panels[panelName];
|
||||
if (!target?.panel || !target?.button) return;
|
||||
const shouldOpen = target.panel.hidden;
|
||||
for (const item of Object.values(panels)) {
|
||||
if (!item.panel || !item.button) continue;
|
||||
item.panel.hidden = true;
|
||||
item.button.classList.remove("active");
|
||||
item.button.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
if (shouldOpen) {
|
||||
target.panel.hidden = false;
|
||||
target.button.classList.add("active");
|
||||
target.button.setAttribute("aria-expanded", "true");
|
||||
if (panelName === "settings") {
|
||||
refreshConfig();
|
||||
checkForUpdate();
|
||||
}
|
||||
if (panelName === "memory") refreshMemory();
|
||||
if (panelName === "ollama") {
|
||||
refreshConfig();
|
||||
refreshOllamaStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function checkHealth() {
|
||||
try {
|
||||
const response = await fetch("/api/health");
|
||||
@@ -576,8 +852,22 @@ input.addEventListener("keydown", async (event) => {
|
||||
}
|
||||
});
|
||||
|
||||
memoryRefreshButton.addEventListener("click", refreshMemory);
|
||||
memoryClearButton.addEventListener("click", clearMemory);
|
||||
memoryRefreshButton?.addEventListener("click", refreshMemory);
|
||||
memoryClearButton?.addEventListener("click", clearMemory);
|
||||
configRefreshButton?.addEventListener("click", refreshConfig);
|
||||
configForm?.addEventListener("submit", saveConfig);
|
||||
settingsToggle?.addEventListener("click", () => toggleSidebarPanel("settings"));
|
||||
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() } }));
|
||||
updateCheckButton?.addEventListener("click", checkForUpdate);
|
||||
updateInstallButton?.addEventListener("click", installUpdate);
|
||||
updateOpenReleasesButton?.addEventListener("click", openReleasesPage);
|
||||
|
||||
async function sendMessage() {
|
||||
const message = input.value.trim();
|
||||
@@ -663,6 +953,9 @@ async function sendMessage() {
|
||||
addMessage("assistant", "Tell me what to find or draft on UEX. I will ask for approval before sending anything.");
|
||||
refreshPending();
|
||||
refreshMemory();
|
||||
refreshConfig();
|
||||
refreshOllamaStatus();
|
||||
checkForUpdate();
|
||||
pollNotifications();
|
||||
checkHealth();
|
||||
setInterval(checkHealth, 30000);
|
||||
|
||||
Reference in New Issue
Block a user