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);
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill-rule="evenodd" clip-rule="evenodd" d="M168.64 23.253c4.608 1.814 8.768 4.8 12.544 8.747 6.293 6.528 11.605 15.872 15.659 26.944 4.074 11.136 6.72 23.467 7.722 35.84a107.824 107.824 0 0143.712-13.568l1.088-.085c18.56-1.494 36.907 1.856 52.907 10.112a103.091 103.091 0 016.336 3.626c1.067-12.138 3.669-24.192 7.68-35.072 4.053-11.093 9.365-20.416 15.637-26.965a35.628 35.628 0 0112.566-8.747c5.482-2.133 11.306-2.517 16.981-.896 8.555 2.432 15.893 7.851 21.675 15.723 5.29 7.19 9.258 16.405 11.968 27.456 4.906 19.925 5.76 46.144 2.453 77.76l1.131.853.554.406c16.15 12.288 27.392 29.802 33.344 50.133 9.28 31.723 4.608 67.307-11.392 87.211l-.384.448.043.064c8.896 16.256 14.293 33.429 15.445 51.2l.043.64c1.365 22.72-4.267 45.589-17.365 68.053l-.15.213.214.512c10.069 24.683 13.226 49.536 9.344 74.368l-.128.832a13.888 13.888 0 01-15.936 11.435 13.83 13.83 0 01-11.31-10.43 13.828 13.828 0 01-.21-5.399c3.562-22.038.213-44.139-10.24-66.624a13.713 13.713 0 01.853-13.163l.085-.128c12.886-19.712 18.219-39.04 17.067-58.027-.981-16.618-6.933-32.938-17.067-48.49a13.737 13.737 0 013.84-18.902l.192-.128c5.184-3.392 9.963-12.053 12.374-23.893a90.218 90.218 0 00-2.027-42.112c-4.373-14.933-12.373-27.392-23.573-35.904-12.694-9.685-29.504-14.357-50.774-13.013a13.93 13.93 0 01-13.482-7.915c-6.699-14.187-16.47-24.341-28.651-30.635a70.145 70.145 0 00-37.803-7.082c-26.56 2.112-49.984 17.088-56.96 35.968a13.91 13.91 0 01-13.013 9.066c-22.763.043-40.384 5.376-53.269 14.998-11.136 8.32-18.731 19.946-22.742 33.877a86.824 86.824 0 00-1.45 40.235c2.389 11.904 7.061 21.76 12.416 27.072l.17.149c4.523 4.416 5.483 11.307 2.326 16.747-7.68 13.269-13.419 33.045-14.358 52.053-1.066 21.717 3.968 40.576 15.339 54.101l.341.406a13.711 13.711 0 012.027 14.72c-12.288 26.368-16.064 48.042-11.989 65.109a13.91 13.91 0 01-27.072 6.357c-5.184-21.717-1.664-46.592 10.09-74.624l.299-.746-.17-.256a92.574 92.574 0 01-12.758-27.926l-.107-.405a122.965 122.965 0 01-3.776-38.08c.939-19.413 5.931-39.296 13.27-55.253l.256-.555-.043-.043c-6.25-8.917-10.88-20.33-13.44-32.96l-.107-.512a114.176 114.176 0 011.984-53.12c5.59-19.52 16.576-36.288 32.768-48.405 1.28-.96 2.624-1.92 3.968-2.816-3.392-31.851-2.538-58.24 2.39-78.293 2.709-11.051 6.698-20.267 11.989-27.456 5.76-7.851 13.099-13.27 21.653-15.723 5.675-1.621 11.52-1.259 17.003.896v.021zm87.808 193.92c19.968 0 38.4 6.678 52.181 18.24 13.44 11.243 21.44 26.347 21.44 41.387 0 18.944-8.661 33.707-24.17 43.136-13.227 8-30.955 11.883-51.264 11.883-21.526 0-39.915-5.526-53.184-15.659-13.163-10.027-20.544-24.107-20.544-39.36 0-15.083 8.49-30.229 22.528-41.515 14.25-11.456 33.066-18.112 53.013-18.112zm0 19.115a65.498 65.498 0 00-40.875 13.867c-9.834 7.893-15.402 17.813-15.402 26.666 0 9.131 4.48 17.686 13.013 24.192 9.707 7.403 23.979 11.691 41.451 11.691 17.045 0 31.424-3.136 41.216-9.088 9.877-5.973 14.933-14.635 14.933-26.816 0-9.024-5.248-18.987-14.571-26.795-10.325-8.64-24.32-13.717-39.765-13.717zm14.123 25.813l.085.086a7.431 7.431 0 01-1.195 10.453l-6.229 4.907v9.514a7.999 7.999 0 01-8.021 7.958 8.004 8.004 0 01-8.022-7.958v-9.813l-5.781-4.651a7.4 7.4 0 01-1.109-10.453 7.53 7.53 0 0110.538-1.088l4.587 3.669 4.693-3.712a7.533 7.533 0 0110.454 1.088zm-107.52-40.938c10.197 0 18.496 8.32 18.496 18.581a18.564 18.564 0 01-18.518 18.581 18.559 18.559 0 01-18.496-18.56 18.565 18.565 0 015.399-13.129 18.609 18.609 0 0113.119-5.473zm185.728 0c10.24 0 18.517 8.32 18.517 18.581a18.559 18.559 0 01-18.517 18.581 18.56 18.56 0 01-18.496-18.56 18.56 18.56 0 0118.496-18.602zM158.72 49.067l-.064.042a14.06 14.06 0 00-6.08 5.078l-.107.128c-2.944 4.032-5.504 9.962-7.424 17.749-3.626 14.763-4.608 34.795-2.645 59.349 9.173-2.73 19.179-4.437 29.952-5.056l.213-.021.406-.725a69.41 69.41 0 013.157-5.099c2.624-16.448.469-36.096-5.397-52.139-2.859-7.765-6.336-13.866-9.664-17.344a13.403 13.403 0 00-2.283-1.92l-.064-.042zm195.712.853l-.043.021a13.396 13.396 0 00-2.282 1.92c-3.328 3.478-6.827 9.6-9.664 17.366-6.187 16.938-8.256 37.888-4.907 54.869l1.237 2.069.171.299h.64a110.599 110.599 0 0131.275 4.523c1.834-23.979.81-43.584-2.731-58.07-1.92-7.786-4.48-13.717-7.445-17.749l-.086-.128a14.054 14.054 0 00-6.08-5.099h-.085v-.021z" fill="#000"/></svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
+83
-12
@@ -4,6 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TraderAI</title>
|
||||
<link rel="icon" href="/static/art/LBC_Logo.ico" sizes="any">
|
||||
<link rel="stylesheet" href="/static/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
@@ -36,23 +37,93 @@
|
||||
<h2>Pending Approval</h2>
|
||||
<div id="pending-actions" class="pending-empty">No pending actions</div>
|
||||
</section>
|
||||
<section class="side-section">
|
||||
<div class="section-title-row">
|
||||
<h2>Memory</h2>
|
||||
<button class="secondary small-button" id="memory-refresh" type="button">Refresh</button>
|
||||
<section class="side-section sidebar-tools">
|
||||
<div class="sidebar-tool-buttons" role="tablist" aria-label="Sidebar panels">
|
||||
<button class="sidebar-tool-button" id="settings-toggle" type="button" aria-expanded="false" aria-controls="settings-panel" title="Settings">
|
||||
<i data-lucide="settings" aria-hidden="true"></i>
|
||||
<span>Settings</span>
|
||||
</button>
|
||||
<button class="sidebar-tool-button" id="memory-toggle" type="button" aria-expanded="false" aria-controls="memory-panel" title="Memory">
|
||||
<i data-lucide="brain" aria-hidden="true"></i>
|
||||
<span>Memory</span>
|
||||
</button>
|
||||
<button class="sidebar-tool-button" id="ollama-toggle" type="button" aria-expanded="false" aria-controls="ollama-panel" title="Ollama">
|
||||
<img class="sidebar-tool-image" src="/static/art/ollama-icon.svg" alt="" onerror="this.remove();">
|
||||
<i data-lucide="bot" aria-hidden="true"></i>
|
||||
<span>Ollama</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="memory-controls">
|
||||
<label><input type="checkbox" id="clear-memories" checked> Memories</label>
|
||||
<label><input type="checkbox" id="clear-conversations" checked> Chat</label>
|
||||
<label><input type="checkbox" id="clear-outbox" checked> Notices</label>
|
||||
<label><input type="checkbox" id="clear-profile"> Profile</label>
|
||||
<label><input type="checkbox" id="clear-jobs"> Jobs</label>
|
||||
<div class="sidebar-panel" id="settings-panel" hidden>
|
||||
<div class="section-title-row">
|
||||
<h2>Config</h2>
|
||||
<button class="secondary small-button" id="config-refresh" type="button">Refresh</button>
|
||||
</div>
|
||||
<form class="config-form" id="config-form">
|
||||
<label>Ollama URL<input id="config-ollama-base-url" name="ollama_base_url" type="text"></label>
|
||||
<label>Ollama Model<input id="config-ollama-model" name="ollama_model" type="text"></label>
|
||||
<label>Context Tokens<input id="config-ollama-num-ctx" name="ollama_num_ctx" type="number" min="1024" step="1024"></label>
|
||||
<label>UEX API URL<input id="config-uex-base-url" name="uex_base_url" type="text"></label>
|
||||
<label>UEX Secret Key<input id="config-uex-secret-key" name="uex_secret_key" type="password" autocomplete="off"></label>
|
||||
<label>UEX Bearer Token<input id="config-uex-bearer-token" name="uex_bearer_token" type="password" autocomplete="off"></label>
|
||||
<label>UEX Username<input id="config-traderai-user-name" name="traderai_user_name" type="text"></label>
|
||||
<label>Memory DB Path<input id="config-traderai-memory-path" name="traderai_memory_path" type="text"></label>
|
||||
<label>Notification Poll Seconds<input id="config-uex-notification-poll-seconds" name="uex_notification_poll_seconds" type="number" min="15" step="15"></label>
|
||||
<label class="config-check"><input id="config-require-write-approval" name="require_write_approval" type="checkbox"> Require write approval</label>
|
||||
<div class="config-paths" id="config-paths"></div>
|
||||
<button type="submit">Save Config</button>
|
||||
<div class="config-status" id="config-status"></div>
|
||||
</form>
|
||||
<div class="update-box">
|
||||
<div class="section-title-row">
|
||||
<h2>Updates</h2>
|
||||
<button class="secondary small-button" id="update-check" type="button">Check</button>
|
||||
</div>
|
||||
<div class="update-status" id="update-status"></div>
|
||||
<div class="update-actions">
|
||||
<button class="secondary small-button" id="update-open-releases" type="button">Releases</button>
|
||||
<button class="small-button" id="update-install" type="button">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-panel" id="memory-panel" hidden>
|
||||
<div class="section-title-row">
|
||||
<h2>Memory</h2>
|
||||
<button class="secondary small-button" id="memory-refresh" type="button">Refresh</button>
|
||||
</div>
|
||||
<div class="memory-controls">
|
||||
<label><input type="checkbox" id="clear-memories" checked> Memories</label>
|
||||
<label><input type="checkbox" id="clear-conversations" checked> Chat</label>
|
||||
<label><input type="checkbox" id="clear-outbox" checked> Notices</label>
|
||||
<label><input type="checkbox" id="clear-profile"> Profile</label>
|
||||
<label><input type="checkbox" id="clear-jobs"> Jobs</label>
|
||||
</div>
|
||||
<button class="danger-button" id="memory-clear" type="button">Clear Selected</button>
|
||||
<div id="memory-inspector" class="memory-inspector"></div>
|
||||
</div>
|
||||
<div class="sidebar-panel" id="ollama-panel" hidden>
|
||||
<div class="section-title-row">
|
||||
<h2>Ollama</h2>
|
||||
<button class="secondary small-button" id="ollama-refresh" type="button">Refresh</button>
|
||||
</div>
|
||||
<form class="config-form" id="ollama-config-form">
|
||||
<label>Ollama URL<input id="ollama-base-url" name="ollama_base_url" type="text"></label>
|
||||
<label>Model<input id="ollama-model" name="ollama_model" type="text"></label>
|
||||
<label>Context Tokens<input id="ollama-num-ctx" name="ollama_num_ctx" type="number" min="1024" step="1024"></label>
|
||||
<button type="submit">Save Ollama Config</button>
|
||||
</form>
|
||||
<div class="ollama-status" id="ollama-status"></div>
|
||||
<div class="ollama-actions">
|
||||
<button class="secondary small-button" id="ollama-download" type="button">Download</button>
|
||||
<button class="secondary small-button" id="ollama-install" type="button">Auto Install</button>
|
||||
<button class="secondary small-button" id="ollama-launch" type="button">Launch</button>
|
||||
<button class="small-button" id="ollama-pull" type="button">Install Model</button>
|
||||
</div>
|
||||
<div class="config-status" id="ollama-message"></div>
|
||||
</div>
|
||||
<button class="danger-button" id="memory-clear" type="button">Clear Selected</button>
|
||||
<div id="memory-inspector" class="memory-inspector"></div>
|
||||
</section>
|
||||
</aside>
|
||||
</main>
|
||||
<script src="https://unpkg.com/lucide@0.562.0/dist/umd/lucide.min.js"></script>
|
||||
<script src="/static/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+222
-1
@@ -205,7 +205,7 @@ h2 {
|
||||
.messages {
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
padding: 28px;
|
||||
padding: 28px 28px 18px;
|
||||
background:
|
||||
radial-gradient(circle at 50% 12%, rgba(212, 175, 55, 0.1), transparent 34%),
|
||||
linear-gradient(180deg, rgba(247, 241, 220, 0.58), rgba(255, 250, 240, 0.64));
|
||||
@@ -360,8 +360,12 @@ h2 {
|
||||
}
|
||||
|
||||
.composer-wrap {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 5;
|
||||
border-top: 1px solid var(--line);
|
||||
background: rgba(255, 250, 240, 0.88);
|
||||
box-shadow: 0 -16px 34px rgba(38, 58, 27, 0.12);
|
||||
}
|
||||
|
||||
.message-activity {
|
||||
@@ -543,6 +547,29 @@ textarea:disabled {
|
||||
opacity: 0.66;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="number"] {
|
||||
width: 100%;
|
||||
min-height: 38px;
|
||||
padding: 9px 11px;
|
||||
border: 1px solid var(--line-strong);
|
||||
border-radius: 10px;
|
||||
outline: none;
|
||||
background: #fffdf7;
|
||||
color: var(--brown);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
input[type="password"]:focus,
|
||||
input[type="number"]:focus {
|
||||
border-color: var(--gold);
|
||||
box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.18);
|
||||
}
|
||||
|
||||
button {
|
||||
min-width: 104px;
|
||||
border: 0;
|
||||
@@ -605,6 +632,200 @@ button.secondary {
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.sidebar-tools {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.sidebar-tool-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.sidebar-tool-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
min-height: 46px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--line-strong);
|
||||
border-radius: 14px;
|
||||
background: #fff9e9;
|
||||
color: var(--forest);
|
||||
font-family: Inter, "Segoe UI", Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
box-shadow: 0 10px 22px rgba(38, 58, 27, 0.08);
|
||||
}
|
||||
|
||||
.sidebar-tool-button svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
stroke-width: 2.3;
|
||||
}
|
||||
|
||||
.sidebar-tool-image {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.sidebar-tool-image + svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-tool-button.active {
|
||||
border-color: rgba(212, 175, 55, 0.72);
|
||||
background: linear-gradient(180deg, #345326, #1f3416);
|
||||
color: var(--ivory);
|
||||
}
|
||||
|
||||
.sidebar-panel {
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.config-form {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.config-form label {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.config-check {
|
||||
display: flex !important;
|
||||
grid-template-columns: none;
|
||||
align-items: center;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
.config-form button {
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.config-paths,
|
||||
.config-status {
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.config-status {
|
||||
color: var(--forest);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.ollama-status {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin: 14px 0;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 250, 240, 0.78);
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.ollama-status-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ollama-status-item {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ollama-status-item strong {
|
||||
color: var(--forest);
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.ollama-status-item span {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
min-height: 24px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid rgba(52, 83, 38, 0.28);
|
||||
border-radius: 999px;
|
||||
background: #edf3df;
|
||||
color: var(--forest);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.status-pill.warning {
|
||||
border-color: rgba(159, 60, 50, 0.26);
|
||||
background: #fff2e5;
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.ollama-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.ollama-actions button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.update-box {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.update-box h2 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.update-status {
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 250, 240, 0.78);
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.update-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.update-actions button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.section-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user