feat: chat
This commit is contained in:
+305
-10
@@ -15,6 +15,7 @@ const configPathsEl = document.getElementById("config-paths");
|
||||
const settingsToggle = document.getElementById("settings-toggle");
|
||||
const memoryToggle = document.getElementById("memory-toggle");
|
||||
const plansToggle = document.getElementById("plans-toggle");
|
||||
const negotiationsToggle = document.getElementById("negotiations-toggle");
|
||||
const ollamaToggle = document.getElementById("ollama-toggle");
|
||||
const settingsPanel = document.getElementById("settings-panel");
|
||||
const memoryPanel = document.getElementById("memory-panel");
|
||||
@@ -48,6 +49,32 @@ const negotiationForm = document.getElementById("negotiation-form");
|
||||
const negotiationInput = document.getElementById("negotiation-input");
|
||||
const negotiationStatusEl = document.getElementById("negotiation-status");
|
||||
const negotiationCloseButton = document.getElementById("negotiation-close");
|
||||
const negotiationListEl = document.getElementById("negotiation-list");
|
||||
const negotiationsRefreshAllButton = document.getElementById("negotiations-refresh-all");
|
||||
const negotiationPanelListEl = document.getElementById("negotiation-panel-list");
|
||||
const negotiationSearchEl = document.getElementById("negotiation-search");
|
||||
const negotiationFilterEl = document.getElementById("negotiation-filter");
|
||||
const negotiationThreadHeaderEl = document.getElementById("negotiation-thread-header");
|
||||
const negotiationMetaCardEl = document.getElementById("negotiation-meta-card");
|
||||
const negotiationUserCardEl = document.getElementById("negotiation-user-card");
|
||||
const negotiationRefreshButton = document.getElementById("negotiation-refresh-button");
|
||||
const negotiationDraftButton = document.getElementById("negotiation-draft-button");
|
||||
const negotiationOpenChatButton = document.getElementById("negotiation-open-chat");
|
||||
const negotiationEndDealButton = document.getElementById("negotiation-end-deal");
|
||||
const negotiationSyncPillEl = document.getElementById("negotiation-sync-pill");
|
||||
const negotiationCloseModal = document.getElementById("negotiation-close-modal");
|
||||
const negotiationCloseModalClose = document.getElementById("negotiation-close-modal-close");
|
||||
const negotiationCloseForm = document.getElementById("negotiation-close-form");
|
||||
const negotiationCloseStatusEl = document.getElementById("negotiation-close-status");
|
||||
const closeDealClosedEl = document.getElementById("close-deal-closed");
|
||||
const closeDealValueEl = document.getElementById("close-deal-value");
|
||||
const closeCurrencyEl = document.getElementById("close-currency");
|
||||
const closeClarityEl = document.getElementById("close-clarity");
|
||||
const closeSpeedEl = document.getElementById("close-speed");
|
||||
const closeRespectEl = document.getElementById("close-respect");
|
||||
const closeFairnessEl = document.getElementById("close-fairness");
|
||||
const closeCommentEl = document.getElementById("close-comment");
|
||||
const closeDraftButton = document.getElementById("close-draft-button");
|
||||
const updateModal = document.getElementById("update-modal");
|
||||
const updateModalCopy = document.getElementById("update-modal-copy");
|
||||
const updateModalClose = document.getElementById("update-modal-close");
|
||||
@@ -66,6 +93,7 @@ let ollamaOnline = true;
|
||||
let latestUpdate = null;
|
||||
let currentThreadId = "default";
|
||||
let currentNegotiationId = null;
|
||||
let negotiationRows = [];
|
||||
let latestOllamaStatus = null;
|
||||
let composerImages = [];
|
||||
const clickedOllamaActions = new Set();
|
||||
@@ -1182,16 +1210,114 @@ async function deleteInboxItem(id) {
|
||||
await refreshInbox();
|
||||
}
|
||||
|
||||
async function refreshNegotiations(preserveCurrent = true) {
|
||||
const status = negotiationFilterEl?.value || "open";
|
||||
const search = negotiationSearchEl?.value?.trim() || "";
|
||||
try {
|
||||
const response = await fetch(`/api/negotiations?status=${encodeURIComponent(status)}&search=${encodeURIComponent(search)}&limit=100`);
|
||||
const result = await response.json();
|
||||
negotiationRows = result.negotiations || [];
|
||||
renderNegotiationLists(negotiationRows);
|
||||
if (!preserveCurrent) return;
|
||||
if (!currentNegotiationId && negotiationRows.length) currentNegotiationId = negotiationRows[0].hash;
|
||||
if (currentNegotiationId && negotiationRows.some((item) => item.hash === currentNegotiationId) && !negotiationPanel.hidden) {
|
||||
await loadNegotiationDetail(currentNegotiationId, false);
|
||||
}
|
||||
} catch (error) {
|
||||
const message = `Negotiations failed: ${fetchErrorMessage(error)}`;
|
||||
if (negotiationListEl) negotiationListEl.textContent = message;
|
||||
if (negotiationPanelListEl) negotiationPanelListEl.textContent = message;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAllNegotiations() {
|
||||
const previous = negotiationsRefreshAllButton?.disabled;
|
||||
if (negotiationsRefreshAllButton) negotiationsRefreshAllButton.disabled = true;
|
||||
if (negotiationStatusEl) negotiationStatusEl.textContent = "Refreshing all negotiations";
|
||||
try {
|
||||
const response = await fetch("/api/negotiations/refresh-all", { method: "POST" });
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.detail || `HTTP ${response.status}`);
|
||||
await refreshNegotiations(true);
|
||||
if (negotiationStatusEl) {
|
||||
negotiationStatusEl.textContent = `Refreshed ${result.count || 0} negotiations`;
|
||||
}
|
||||
} catch (error) {
|
||||
if (negotiationStatusEl) negotiationStatusEl.textContent = `Refresh all failed: ${fetchErrorMessage(error)}`;
|
||||
} finally {
|
||||
if (negotiationsRefreshAllButton) negotiationsRefreshAllButton.disabled = Boolean(previous);
|
||||
}
|
||||
}
|
||||
|
||||
function renderNegotiationLists(items) {
|
||||
renderNegotiationListInto(negotiationListEl, items.slice(0, 8));
|
||||
renderNegotiationListInto(negotiationPanelListEl, items);
|
||||
}
|
||||
|
||||
function renderNegotiationListInto(container, items) {
|
||||
if (!container) return;
|
||||
container.innerHTML = "";
|
||||
if (!items.length) {
|
||||
container.innerHTML = '<div class="pending-empty">No negotiations</div>';
|
||||
return;
|
||||
}
|
||||
for (const item of items) {
|
||||
const row = document.createElement("button");
|
||||
row.type = "button";
|
||||
row.className = `negotiation-row${item.hash === currentNegotiationId ? " active" : ""}`;
|
||||
row.addEventListener("click", () => openNegotiationPanel(item.hash));
|
||||
const top = document.createElement("div");
|
||||
top.className = "negotiation-row-top";
|
||||
const title = document.createElement("div");
|
||||
title.className = "negotiation-row-title";
|
||||
title.textContent = item.title || item.counterparty_username || item.hash;
|
||||
const badge = document.createElement("span");
|
||||
badge.className = `negotiation-row-badge ${item.status === "closed" ? "closed" : ""}`;
|
||||
badge.textContent = item.status || "open";
|
||||
top.append(title, badge);
|
||||
const meta = document.createElement("div");
|
||||
meta.className = "negotiation-row-meta";
|
||||
meta.textContent = [
|
||||
item.counterparty_username || "Unknown user",
|
||||
item.last_message_at ? formatShortDate(item.last_message_at) : "No messages",
|
||||
].join(" • ");
|
||||
row.append(top, meta);
|
||||
if (Number(item.unread_count || 0) > 0) {
|
||||
const unread = document.createElement("span");
|
||||
unread.className = "negotiation-row-unread";
|
||||
unread.textContent = String(item.unread_count);
|
||||
row.appendChild(unread);
|
||||
}
|
||||
container.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
async function openNegotiationPanel(identifier) {
|
||||
if (!identifier) {
|
||||
negotiationPanel.hidden = false;
|
||||
negotiationsToggle?.setAttribute("aria-expanded", "true");
|
||||
return;
|
||||
}
|
||||
currentNegotiationId = identifier;
|
||||
negotiationPanel.hidden = false;
|
||||
negotiationTitle.textContent = `Negotiation ${identifier}`;
|
||||
negotiationsToggle?.setAttribute("aria-expanded", "true");
|
||||
negotiationStatusEl.textContent = "";
|
||||
negotiationSyncPillEl.textContent = "Local sync";
|
||||
await loadNegotiationDetail(identifier, true);
|
||||
}
|
||||
|
||||
async function loadNegotiationDetail(identifier, refreshList = true) {
|
||||
negotiationTitle.textContent = `Negotiation ${identifier}`;
|
||||
negotiationMessagesEl.textContent = "Loading";
|
||||
negotiationThreadHeaderEl.innerHTML = '<div class="muted">Loading local thread...</div>';
|
||||
negotiationMetaCardEl.innerHTML = "<h3>Deal</h3><div class='muted'>Loading</div>";
|
||||
negotiationUserCardEl.innerHTML = "<h3>User</h3><div class='muted'>Loading</div>";
|
||||
try {
|
||||
const response = await fetch(`/api/negotiations/${encodeURIComponent(identifier)}/messages`);
|
||||
const response = await fetch(`/api/negotiations/${encodeURIComponent(identifier)}`);
|
||||
const result = await response.json();
|
||||
renderNegotiationMessages(result.data || result.messages || result.notifications || []);
|
||||
if (!response.ok) throw new Error(result.detail || `HTTP ${response.status}`);
|
||||
renderNegotiationDetail(result.negotiation);
|
||||
if (refreshList) await refreshNegotiations(false);
|
||||
} catch (error) {
|
||||
negotiationMessagesEl.textContent = `Could not load negotiation: ${fetchErrorMessage(error)}`;
|
||||
}
|
||||
@@ -1202,6 +1328,7 @@ function closeNegotiationPanel() {
|
||||
currentNegotiationId = null;
|
||||
negotiationInput.value = "";
|
||||
negotiationStatusEl.textContent = "";
|
||||
negotiationsToggle?.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
function openPlansPanel(openPlanId = null) {
|
||||
@@ -1217,6 +1344,37 @@ function closePlansPanel() {
|
||||
plansToggle?.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
function renderNegotiationDetail(negotiation) {
|
||||
if (!negotiation) return;
|
||||
negotiationTitle.textContent = negotiation.title || negotiation.counterparty_username || negotiation.hash;
|
||||
negotiationSyncPillEl.textContent = negotiation.last_synced_at ? `Synced ${formatShortDate(negotiation.last_synced_at)}` : "Local sync";
|
||||
negotiationThreadHeaderEl.innerHTML = `
|
||||
<div><strong>${escapeHtml(negotiation.title || "Negotiation")}</strong></div>
|
||||
<div class="muted">${escapeHtml(negotiation.counterparty_username || "Unknown user")} • ${escapeHtml(negotiation.status || "open")} • ${escapeHtml(negotiation.hash || "")}</div>
|
||||
`;
|
||||
renderNegotiationMessages(negotiation.messages || []);
|
||||
const raw = negotiation.metadata?.raw || {};
|
||||
negotiationMetaCardEl.innerHTML = `
|
||||
<h3>Deal</h3>
|
||||
<div class="negotiation-detail-kv">
|
||||
<div><strong>Listing</strong> ${escapeHtml(negotiation.title || raw.listing_title || negotiation.hash)}</div>
|
||||
<div><strong>Status</strong> ${escapeHtml(negotiation.status || "open")}</div>
|
||||
<div><strong>Slug</strong> ${escapeHtml(negotiation.listing_slug || raw.listing_slug || "Unknown")}</div>
|
||||
<div><strong>Price</strong> ${escapeHtml(String(raw.price || raw.deal_value || "Unknown"))} ${escapeHtml(String(raw.currency || raw.deal_value_currency || ""))}</div>
|
||||
<div><strong>Last message</strong> ${escapeHtml(negotiation.last_message_at ? formatShortDate(negotiation.last_message_at) : "Unknown")}</div>
|
||||
</div>
|
||||
`;
|
||||
negotiationUserCardEl.innerHTML = `
|
||||
<h3>User</h3>
|
||||
<div class="negotiation-detail-kv">
|
||||
<div><strong>Counterparty</strong> ${escapeHtml(negotiation.counterparty_username || raw.client_username || raw.advertiser_username || "Unknown")}</div>
|
||||
<div><strong>Advertiser</strong> ${escapeHtml(String(raw.advertiser_username || raw.advertiser_name || "Unknown"))}</div>
|
||||
<div><strong>Client</strong> ${escapeHtml(String(raw.client_username || raw.client_name || "Unknown"))}</div>
|
||||
<div><strong>Unread</strong> ${escapeHtml(String(negotiation.unread_count || 0))}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderNegotiationMessages(data) {
|
||||
negotiationMessagesEl.innerHTML = "";
|
||||
const items = Array.isArray(data) ? data : [data].filter(Boolean);
|
||||
@@ -1226,10 +1384,15 @@ function renderNegotiationMessages(data) {
|
||||
}
|
||||
for (const item of items) {
|
||||
const card = document.createElement("div");
|
||||
card.className = "negotiation-message";
|
||||
const author = item.user_username || item.username || item.author || item.sender || "UEX";
|
||||
const body = item.message || item.content || item.text || JSON.stringify(item, null, 2);
|
||||
card.innerHTML = `<strong>${escapeHtml(String(author))}</strong><br>${inlineMarkdown(String(body))}`;
|
||||
card.className = `negotiation-message${item.is_me ? " self" : ""}`;
|
||||
const author = item.author_username || item.user_username || item.username || item.author || item.sender || "UEX";
|
||||
const body = item.body || item.message || item.content || item.text || JSON.stringify(item, null, 2);
|
||||
const meta = document.createElement("div");
|
||||
meta.className = "negotiation-message-meta";
|
||||
meta.innerHTML = `<strong>${escapeHtml(String(author))}</strong><span>${escapeHtml(item.sent_at ? formatShortDate(item.sent_at) : "")}</span>`;
|
||||
const text = document.createElement("div");
|
||||
text.innerHTML = inlineMarkdown(String(body));
|
||||
card.append(meta, text);
|
||||
negotiationMessagesEl.appendChild(card);
|
||||
}
|
||||
negotiationMessagesEl.scrollTop = negotiationMessagesEl.scrollHeight;
|
||||
@@ -1241,7 +1404,7 @@ async function submitNegotiationMessage(event) {
|
||||
if (!text || !currentNegotiationId) return;
|
||||
negotiationStatusEl.textContent = "Sending";
|
||||
try {
|
||||
const response = await fetch(`/api/negotiations/${encodeURIComponent(currentNegotiationId)}/messages`, {
|
||||
const response = await fetch(`/api/negotiations/${encodeURIComponent(currentNegotiationId)}/messages/manual`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ message: text }),
|
||||
@@ -1250,12 +1413,125 @@ async function submitNegotiationMessage(event) {
|
||||
if (!response.ok) throw new Error(result.detail || `HTTP ${response.status}`);
|
||||
negotiationInput.value = "";
|
||||
negotiationStatusEl.textContent = result.message || "Sent";
|
||||
await openNegotiationPanel(currentNegotiationId);
|
||||
if (result.negotiation) renderNegotiationDetail(result.negotiation);
|
||||
await refreshNegotiations(false);
|
||||
} catch (error) {
|
||||
negotiationStatusEl.textContent = `Send failed: ${fetchErrorMessage(error)}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function draftNegotiationMessage() {
|
||||
const text = negotiationInput.value.trim();
|
||||
if (!text || !currentNegotiationId) return;
|
||||
negotiationStatusEl.textContent = "Drafting";
|
||||
try {
|
||||
const response = await fetch(`/api/negotiations/${encodeURIComponent(currentNegotiationId)}/messages/draft`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ message: text }),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.detail || `HTTP ${response.status}`);
|
||||
negotiationStatusEl.textContent = "Draft ready for approval";
|
||||
await refreshPending();
|
||||
} catch (error) {
|
||||
negotiationStatusEl.textContent = `Draft failed: ${fetchErrorMessage(error)}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshActiveNegotiation() {
|
||||
if (!currentNegotiationId) return;
|
||||
negotiationStatusEl.textContent = "Refreshing";
|
||||
try {
|
||||
const response = await fetch(`/api/negotiations/${encodeURIComponent(currentNegotiationId)}/refresh`, { method: "POST" });
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.detail || `HTTP ${response.status}`);
|
||||
if (result.negotiation) renderNegotiationDetail(result.negotiation);
|
||||
negotiationStatusEl.textContent = "Refreshed";
|
||||
await refreshNegotiations(false);
|
||||
} catch (error) {
|
||||
negotiationStatusEl.textContent = `Refresh failed: ${fetchErrorMessage(error)}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function openNegotiationInChat() {
|
||||
if (!currentNegotiationId) return;
|
||||
try {
|
||||
const response = await fetch(`/api/negotiations/${encodeURIComponent(currentNegotiationId)}/open-chat`, { method: "POST" });
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.detail || `HTTP ${response.status}`);
|
||||
if (result.chat?.id) {
|
||||
currentThreadId = result.chat.id;
|
||||
await loadChatMessages(currentThreadId);
|
||||
await refreshChats();
|
||||
}
|
||||
} catch (error) {
|
||||
negotiationStatusEl.textContent = `Open chat failed: ${fetchErrorMessage(error)}`;
|
||||
}
|
||||
}
|
||||
|
||||
function openNegotiationCloseModal() {
|
||||
if (!currentNegotiationId) return;
|
||||
negotiationCloseStatusEl.textContent = "";
|
||||
negotiationCloseModal.hidden = false;
|
||||
}
|
||||
|
||||
function closeNegotiationCloseModal() {
|
||||
negotiationCloseModal.hidden = true;
|
||||
}
|
||||
|
||||
function negotiationClosePayload() {
|
||||
return {
|
||||
deal_closed: closeDealClosedEl.value !== "false",
|
||||
deal_value: closeDealValueEl.value ? Number(closeDealValueEl.value) : null,
|
||||
currency: closeCurrencyEl.value.trim() || null,
|
||||
clarity_rating: closeClarityEl.value ? Number(closeClarityEl.value) : null,
|
||||
speed_rating: closeSpeedEl.value ? Number(closeSpeedEl.value) : null,
|
||||
respect_rating: closeRespectEl.value ? Number(closeRespectEl.value) : null,
|
||||
fairness_rating: closeFairnessEl.value ? Number(closeFairnessEl.value) : null,
|
||||
comment: closeCommentEl.value.trim() || null,
|
||||
};
|
||||
}
|
||||
|
||||
async function submitNegotiationClose(event) {
|
||||
event.preventDefault();
|
||||
if (!currentNegotiationId) return;
|
||||
negotiationCloseStatusEl.textContent = "Submitting";
|
||||
try {
|
||||
const response = await fetch(`/api/negotiations/${encodeURIComponent(currentNegotiationId)}/close/manual`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(negotiationClosePayload()),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.detail || `HTTP ${response.status}`);
|
||||
negotiationCloseStatusEl.textContent = result.message || "Submitted";
|
||||
if (result.negotiation) renderNegotiationDetail(result.negotiation);
|
||||
await refreshNegotiations(false);
|
||||
closeNegotiationCloseModal();
|
||||
} catch (error) {
|
||||
negotiationCloseStatusEl.textContent = `Close failed: ${fetchErrorMessage(error)}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function draftNegotiationClose() {
|
||||
if (!currentNegotiationId) return;
|
||||
negotiationCloseStatusEl.textContent = "Drafting";
|
||||
try {
|
||||
const response = await fetch(`/api/negotiations/${encodeURIComponent(currentNegotiationId)}/close/draft`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(negotiationClosePayload()),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.detail || `HTTP ${response.status}`);
|
||||
negotiationCloseStatusEl.textContent = "Draft ready for approval";
|
||||
await refreshPending();
|
||||
} catch (error) {
|
||||
negotiationCloseStatusEl.textContent = `Draft failed: ${fetchErrorMessage(error)}`;
|
||||
}
|
||||
}
|
||||
|
||||
function parsePlanItems(text) {
|
||||
return text
|
||||
.split(/\r?\n/)
|
||||
@@ -1854,7 +2130,10 @@ async function pollNotifications() {
|
||||
try {
|
||||
const response = await fetch("/api/notifications");
|
||||
const result = await response.json();
|
||||
if ((result.notifications || []).length) await refreshInbox();
|
||||
if ((result.notifications || []).length) {
|
||||
await refreshInbox();
|
||||
await refreshNegotiations(true);
|
||||
}
|
||||
} catch {
|
||||
// Notification polling should never interrupt chat.
|
||||
}
|
||||
@@ -1899,6 +2178,11 @@ plansToggle?.addEventListener("click", () => {
|
||||
if (plansPanel?.hidden) openPlansPanel();
|
||||
else closePlansPanel();
|
||||
});
|
||||
negotiationsToggle?.addEventListener("click", () => {
|
||||
if (negotiationPanel?.hidden) openNegotiationPanel(currentNegotiationId || negotiationRows[0]?.hash || "");
|
||||
else closeNegotiationPanel();
|
||||
});
|
||||
negotiationsRefreshAllButton?.addEventListener("click", refreshAllNegotiations);
|
||||
ollamaToggle?.addEventListener("click", () => toggleSidebarPanel("ollama"));
|
||||
plansRefreshButton?.addEventListener("click", () => refreshPlans());
|
||||
plansCloseButton?.addEventListener("click", closePlansPanel);
|
||||
@@ -1935,6 +2219,15 @@ chatSidebarToggle?.addEventListener("click", toggleChatRail);
|
||||
newChatButton?.addEventListener("click", () => createChat(true));
|
||||
negotiationCloseButton?.addEventListener("click", closeNegotiationPanel);
|
||||
negotiationForm?.addEventListener("submit", submitNegotiationMessage);
|
||||
negotiationDraftButton?.addEventListener("click", draftNegotiationMessage);
|
||||
negotiationRefreshButton?.addEventListener("click", refreshActiveNegotiation);
|
||||
negotiationOpenChatButton?.addEventListener("click", openNegotiationInChat);
|
||||
negotiationEndDealButton?.addEventListener("click", openNegotiationCloseModal);
|
||||
negotiationSearchEl?.addEventListener("input", () => refreshNegotiations(false));
|
||||
negotiationFilterEl?.addEventListener("change", () => refreshNegotiations(false));
|
||||
negotiationCloseModalClose?.addEventListener("click", closeNegotiationCloseModal);
|
||||
negotiationCloseForm?.addEventListener("submit", submitNegotiationClose);
|
||||
closeDraftButton?.addEventListener("click", draftNegotiationClose);
|
||||
updateModalClose?.addEventListener("click", closeUpdatePrompt);
|
||||
updateModalReleases?.addEventListener("click", openReleasesPage);
|
||||
updateModalInstall?.addEventListener("click", installUpdate);
|
||||
@@ -2038,8 +2331,10 @@ refreshConfig();
|
||||
refreshOllamaStatus();
|
||||
refreshChats().then(() => loadChatMessages(currentThreadId));
|
||||
refreshInbox();
|
||||
refreshNegotiations(false);
|
||||
checkForUpdate(true);
|
||||
pollNotifications();
|
||||
checkHealth();
|
||||
setInterval(checkHealth, 30000);
|
||||
setInterval(pollNotifications, 15000);
|
||||
setInterval(() => refreshNegotiations(true), 15000);
|
||||
|
||||
Reference in New Issue
Block a user