Update to v0.9.1 #1

Merged
serversdown merged 44 commits from dev into main 2026-01-18 02:46:25 -05:00
6 changed files with 359 additions and 37 deletions
Showing only changes of commit 01d4811717 - Show all commits

View File

@@ -46,21 +46,29 @@ async function postJSON(url, data) {
// ----------------------------------------------------- // -----------------------------------------------------
// The unified chat handler // The unified chat handler
// ----------------------------------------------------- // -----------------------------------------------------
async function handleChatRequest(session_id, user_msg, mode = "cortex") { async function handleChatRequest(session_id, user_msg, mode = "cortex", backend = null) {
let reason; let reason;
// Determine which endpoint to use based on mode // Determine which endpoint to use based on mode
const endpoint = mode === "standard" ? CORTEX_SIMPLE : CORTEX_REASON; const endpoint = mode === "standard" ? CORTEX_SIMPLE : CORTEX_REASON;
const modeName = mode === "standard" ? "simple" : "reason"; const modeName = mode === "standard" ? "simple" : "reason";
console.log(`Relay → routing to Cortex.${modeName} (mode: ${mode})`); console.log(`Relay → routing to Cortex.${modeName} (mode: ${mode}${backend ? `, backend: ${backend}` : ''})`);
// Build request payload
const payload = {
session_id,
user_prompt: user_msg
};
// Add backend parameter if provided (only for standard mode)
if (backend && mode === "standard") {
payload.backend = backend;
}
// Call appropriate Cortex endpoint // Call appropriate Cortex endpoint
try { try {
reason = await postJSON(endpoint, { reason = await postJSON(endpoint, payload);
session_id,
user_prompt: user_msg
});
} catch (e) { } catch (e) {
console.error(`Relay → Cortex.${modeName} error:`, e.message); console.error(`Relay → Cortex.${modeName} error:`, e.message);
throw new Error(`cortex_${modeName}_failed: ${e.message}`); throw new Error(`cortex_${modeName}_failed: ${e.message}`);
@@ -96,14 +104,15 @@ app.post("/v1/chat/completions", async (req, res) => {
const lastMessage = messages[messages.length - 1]; const lastMessage = messages[messages.length - 1];
const user_msg = lastMessage?.content || ""; const user_msg = lastMessage?.content || "";
const mode = req.body.mode || "cortex"; // Get mode from request, default to cortex const mode = req.body.mode || "cortex"; // Get mode from request, default to cortex
const backend = req.body.backend || null; // Get backend preference
if (!user_msg) { if (!user_msg) {
return res.status(400).json({ error: "No message content provided" }); return res.status(400).json({ error: "No message content provided" });
} }
console.log(`Relay (v1) → received: "${user_msg}" [mode: ${mode}]`); console.log(`Relay (v1) → received: "${user_msg}" [mode: ${mode}${backend ? `, backend: ${backend}` : ''}]`);
const result = await handleChatRequest(session_id, user_msg, mode); const result = await handleChatRequest(session_id, user_msg, mode, backend);
res.json({ res.json({
id: `chatcmpl-${Date.now()}`, id: `chatcmpl-${Date.now()}`,
@@ -145,10 +154,11 @@ app.post("/chat", async (req, res) => {
const session_id = req.body.session_id || "default"; const session_id = req.body.session_id || "default";
const user_msg = req.body.message || ""; const user_msg = req.body.message || "";
const mode = req.body.mode || "cortex"; // Get mode from request, default to cortex const mode = req.body.mode || "cortex"; // Get mode from request, default to cortex
const backend = req.body.backend || null; // Get backend preference
console.log(`Relay → received: "${user_msg}" [mode: ${mode}]`); console.log(`Relay → received: "${user_msg}" [mode: ${mode}${backend ? `, backend: ${backend}` : ''}]`);
const result = await handleChatRequest(session_id, user_msg, mode); const result = await handleChatRequest(session_id, user_msg, mode, backend);
res.json(result); res.json(result);
} catch (err) { } catch (err) {

View File

@@ -14,18 +14,14 @@
</head> </head>
<body> <body>
<div id="chat"> <div id="chat">
<!-- Model selector --> <!-- Mode selector -->
<div id="model-select"> <div id="model-select">
<label for="model">Model:</label> <label for="mode">Mode:</label>
<select id="model">
<option value="gpt-4o-mini">GPT-4o-mini (OpenAI)</option>
<option value="ollama:nollama/mythomax-l2-13b:Q5_K_S">Ollama MythoMax (3090)</option>
</select>
<label for="mode" style="margin-left: 20px;">Mode:</label>
<select id="mode"> <select id="mode">
<option value="standard">Standard</option> <option value="standard">Standard</option>
<option value="cortex">Cortex</option> <option value="cortex">Cortex</option>
</select> </select>
<button id="settingsBtn" style="margin-left: auto;">⚙ Settings</button>
<div id="theme-toggle"> <div id="theme-toggle">
<button id="toggleThemeBtn">🌙 Dark Mode</button> <button id="toggleThemeBtn">🌙 Dark Mode</button>
</div> </div>
@@ -55,6 +51,44 @@
</div> </div>
</div> </div>
<!-- Settings Modal (outside chat container) -->
<div id="settingsModal" class="modal">
<div class="modal-overlay"></div>
<div class="modal-content">
<div class="modal-header">
<h3>Settings</h3>
<button id="closeModalBtn" class="close-btn"></button>
</div>
<div class="modal-body">
<div class="settings-section">
<h4>Standard Mode Backend</h4>
<p class="settings-desc">Select which LLM backend to use for Standard Mode:</p>
<div class="radio-group">
<label class="radio-label">
<input type="radio" name="backend" value="SECONDARY" checked>
<span>SECONDARY - Ollama/Qwen (3090)</span>
<small>Fast, local, good for general chat</small>
</label>
<label class="radio-label">
<input type="radio" name="backend" value="OPENAI">
<span>OPENAI - GPT-4o-mini</span>
<small>Cloud-based, high quality (costs money)</small>
</label>
<label class="radio-label">
<input type="radio" name="backend" value="custom">
<span>Custom Backend</span>
<input type="text" id="customBackend" placeholder="e.g., PRIMARY, FALLBACK" />
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button id="saveSettingsBtn" class="primary-btn">Save</button>
<button id="cancelSettingsBtn">Cancel</button>
</div>
</div>
</div>
<script> <script>
const RELAY_BASE = "http://10.0.0.41:7078"; const RELAY_BASE = "http://10.0.0.41:7078";
const API_URL = `${RELAY_BASE}/v1/chat/completions`; const API_URL = `${RELAY_BASE}/v1/chat/completions`;
@@ -128,7 +162,6 @@
await saveSession(); // ✅ persist both user + assistant messages await saveSession(); // ✅ persist both user + assistant messages
const model = document.getElementById("model").value;
const mode = document.getElementById("mode").value; const mode = document.getElementById("mode").value;
// make sure we always include a stable user_id // make sure we always include a stable user_id
@@ -137,13 +170,24 @@
userId = "brian"; // use whatever ID you seeded Mem0 with userId = "brian"; // use whatever ID you seeded Mem0 with
localStorage.setItem("userId", userId); localStorage.setItem("userId", userId);
} }
// Get backend preference for Standard Mode
let backend = null;
if (mode === "standard") {
backend = localStorage.getItem("standardModeBackend") || "SECONDARY";
}
const body = { const body = {
model: model,
mode: mode, mode: mode,
messages: history, messages: history,
sessionId: currentSession sessionId: currentSession
}; };
// Only add backend if in standard mode
if (backend) {
body.backend = backend;
}
try { try {
const resp = await fetch(API_URL, { const resp = await fetch(API_URL, {
method: "POST", method: "POST",
@@ -194,18 +238,25 @@
} }
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
// Dark mode toggle // Dark mode toggle - defaults to dark
const btn = document.getElementById("toggleThemeBtn"); const btn = document.getElementById("toggleThemeBtn");
// Set dark mode by default if no preference saved
const savedTheme = localStorage.getItem("theme");
if (!savedTheme || savedTheme === "dark") {
document.body.classList.add("dark");
btn.textContent = "☀️ Light Mode";
localStorage.setItem("theme", "dark");
} else {
btn.textContent = "🌙 Dark Mode";
}
btn.addEventListener("click", () => { btn.addEventListener("click", () => {
document.body.classList.toggle("dark"); document.body.classList.toggle("dark");
const isDark = document.body.classList.contains("dark"); const isDark = document.body.classList.contains("dark");
btn.textContent = isDark ? "☀️ Light Mode" : "🌙 Dark Mode"; btn.textContent = isDark ? "☀️ Light Mode" : "🌙 Dark Mode";
localStorage.setItem("theme", isDark ? "dark" : "light"); localStorage.setItem("theme", isDark ? "dark" : "light");
}); });
if (localStorage.getItem("theme") === "dark") {
document.body.classList.add("dark");
btn.textContent = "☀️ Light Mode";
}
// Sessions // Sessions
// Populate dropdown initially // Populate dropdown initially
@@ -262,6 +313,69 @@
// Settings Modal
const settingsModal = document.getElementById("settingsModal");
const settingsBtn = document.getElementById("settingsBtn");
const closeModalBtn = document.getElementById("closeModalBtn");
const saveSettingsBtn = document.getElementById("saveSettingsBtn");
const cancelSettingsBtn = document.getElementById("cancelSettingsBtn");
const modalOverlay = document.querySelector(".modal-overlay");
// Load saved backend preference
const savedBackend = localStorage.getItem("standardModeBackend") || "SECONDARY";
// Set initial radio button state
const backendRadios = document.querySelectorAll('input[name="backend"]');
let isCustomBackend = !["SECONDARY", "OPENAI"].includes(savedBackend);
if (isCustomBackend) {
document.querySelector('input[name="backend"][value="custom"]').checked = true;
document.getElementById("customBackend").value = savedBackend;
} else {
document.querySelector(`input[name="backend"][value="${savedBackend}"]`).checked = true;
}
// Show modal
settingsBtn.addEventListener("click", () => {
settingsModal.classList.add("show");
});
// Hide modal functions
const hideModal = () => {
settingsModal.classList.remove("show");
};
closeModalBtn.addEventListener("click", hideModal);
cancelSettingsBtn.addEventListener("click", hideModal);
modalOverlay.addEventListener("click", hideModal);
// ESC key to close
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && settingsModal.classList.contains("show")) {
hideModal();
}
});
// Save settings
saveSettingsBtn.addEventListener("click", () => {
const selectedRadio = document.querySelector('input[name="backend"]:checked');
let backendValue;
if (selectedRadio.value === "custom") {
backendValue = document.getElementById("customBackend").value.trim().toUpperCase();
if (!backendValue) {
alert("Please enter a custom backend name");
return;
}
} else {
backendValue = selectedRadio.value;
}
localStorage.setItem("standardModeBackend", backendValue);
addMessage("system", `Backend changed to: ${backendValue}`);
hideModal();
});
// Health check // Health check
checkHealth(); checkHealth();
setInterval(checkHealth, 10000); setInterval(checkHealth, 10000);

View File

@@ -8,6 +8,26 @@
--font-console: "IBM Plex Mono", monospace; --font-console: "IBM Plex Mono", monospace;
} }
/* Light mode variables */
body {
--bg-dark: #f5f5f5;
--bg-panel: rgba(255, 115, 0, 0.05);
--accent: #ff6600;
--accent-glow: 0 0 12px #ff6600cc;
--text-main: #1a1a1a;
--text-fade: #666;
}
/* Dark mode variables */
body.dark {
--bg-dark: #0a0a0a;
--bg-panel: rgba(255, 115, 0, 0.1);
--accent: #ff6600;
--accent-glow: 0 0 12px #ff6600cc;
--text-main: #e6e6e6;
--text-fade: #999;
}
body { body {
margin: 0; margin: 0;
background: var(--bg-dark); background: var(--bg-dark);
@@ -28,7 +48,7 @@ body {
border: 1px solid var(--accent); border: 1px solid var(--accent);
border-radius: 10px; border-radius: 10px;
box-shadow: var(--accent-glow); box-shadow: var(--accent-glow);
background: linear-gradient(180deg, rgba(255,102,0,0.05) 0%, rgba(0,0,0,0.9) 100%); background: var(--bg-dark);
overflow: hidden; overflow: hidden;
} }
@@ -153,8 +173,8 @@ button:hover, select:hover {
/* Dropdown (session selector) styling */ /* Dropdown (session selector) styling */
select { select {
background-color: #1a1a1a; background-color: var(--bg-dark);
color: #f5f5f5; color: var(--text-main);
border: 1px solid #b84a12; border: 1px solid #b84a12;
border-radius: 6px; border-radius: 6px;
padding: 4px 6px; padding: 4px 6px;
@@ -162,8 +182,8 @@ select {
} }
select option { select option {
background-color: #1a1a1a; background-color: var(--bg-dark);
color: #f5f5f5; color: var(--text-main);
} }
/* Hover/focus for better visibility */ /* Hover/focus for better visibility */
@@ -171,5 +191,174 @@ select:focus,
select:hover { select:hover {
outline: none; outline: none;
border-color: #ff7a33; border-color: #ff7a33;
background-color: #222; background-color: var(--bg-panel);
}
/* Settings Modal */
.modal {
display: none !important;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
}
.modal.show {
display: block !important;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(4px);
z-index: 999;
}
.modal-content {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(180deg, rgba(255,102,0,0.1) 0%, rgba(10,10,10,0.95) 100%);
border: 2px solid var(--accent);
border-radius: 12px;
box-shadow: var(--accent-glow), 0 0 40px rgba(255,102,0,0.3);
min-width: 400px;
max-width: 600px;
max-height: 80vh;
overflow-y: auto;
z-index: 1001;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--accent);
background: rgba(255,102,0,0.1);
}
.modal-header h3 {
margin: 0;
font-size: 1.2rem;
color: var(--accent);
}
.close-btn {
background: transparent;
border: none;
color: var(--accent);
font-size: 1.5rem;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.close-btn:hover {
background: rgba(255,102,0,0.2);
box-shadow: 0 0 8px var(--accent);
}
.modal-body {
padding: 20px;
}
.settings-section h4 {
margin: 0 0 8px 0;
color: var(--accent);
font-size: 1rem;
}
.settings-desc {
margin: 0 0 16px 0;
color: var(--text-fade);
font-size: 0.85rem;
}
.radio-group {
display: flex;
flex-direction: column;
gap: 12px;
}
.radio-label {
display: flex;
flex-direction: column;
padding: 12px;
border: 1px solid rgba(255,102,0,0.3);
border-radius: 6px;
background: rgba(255,102,0,0.05);
cursor: pointer;
transition: all 0.2s;
}
.radio-label:hover {
border-color: var(--accent);
background: rgba(255,102,0,0.1);
box-shadow: 0 0 8px rgba(255,102,0,0.3);
}
.radio-label input[type="radio"] {
margin-right: 8px;
accent-color: var(--accent);
}
.radio-label span {
font-weight: 500;
margin-bottom: 4px;
}
.radio-label small {
color: var(--text-fade);
font-size: 0.8rem;
margin-left: 24px;
}
.radio-label input[type="text"] {
margin-top: 8px;
margin-left: 24px;
padding: 6px;
background: rgba(0,0,0,0.3);
border: 1px solid rgba(255,102,0,0.5);
border-radius: 4px;
color: var(--text-main);
font-family: var(--font-console);
}
.radio-label input[type="text"]:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 8px rgba(255,102,0,0.3);
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 16px 20px;
border-top: 1px solid var(--accent);
background: rgba(255,102,0,0.05);
}
.primary-btn {
background: var(--accent);
color: #000;
font-weight: bold;
}
.primary-btn:hover {
background: #ff7a33;
box-shadow: var(--accent-glow);
} }

View File

@@ -4,8 +4,8 @@
"focus": "conversation", "focus": "conversation",
"confidence": 0.7, "confidence": 0.7,
"curiosity": 1.0, "curiosity": 1.0,
"last_updated": "2025-12-20T09:08:41.342756", "last_updated": "2025-12-21T18:50:41.582043",
"interaction_count": 25, "interaction_count": 26,
"learning_queue": [], "learning_queue": [],
"active_goals": [], "active_goals": [],
"preferences": { "preferences": {

View File

@@ -141,11 +141,16 @@ async def call_llm(
"Authorization": f"Bearer {cfg['api_key']}", "Authorization": f"Bearer {cfg['api_key']}",
"Content-Type": "application/json" "Content-Type": "application/json"
} }
# Use messages array if provided, otherwise convert prompt to single user message
if messages:
chat_messages = messages
else:
chat_messages = [{"role": "user", "content": prompt}]
payload = { payload = {
"model": model, "model": model,
"messages": [ "messages": chat_messages,
{"role": "user", "content": prompt}
],
"temperature": temperature, "temperature": temperature,
"max_tokens": max_tokens, "max_tokens": max_tokens,
} }

View File

@@ -44,6 +44,7 @@ class ReasonRequest(BaseModel):
session_id: str session_id: str
user_prompt: str user_prompt: str
temperature: float | None = None temperature: float | None = None
backend: str | None = None
# ------------------------------------------------------------------- # -------------------------------------------------------------------
@@ -388,8 +389,11 @@ async def run_simple(req: ReasonRequest):
logger.info(f"📨 Total messages being sent to LLM: {len(messages)} (including system message)") logger.info(f"📨 Total messages being sent to LLM: {len(messages)} (including system message)")
# Get backend from env (default to OPENAI for standard mode) # Get backend from request, otherwise fall back to env variable
backend = os.getenv("STANDARD_MODE_LLM", "OPENAI") backend = req.backend if req.backend else os.getenv("STANDARD_MODE_LLM", "SECONDARY")
backend = backend.upper() # Normalize to uppercase
logger.info(f"🔧 Using backend: {backend}")
temperature = req.temperature if req.temperature is not None else 0.7 temperature = req.temperature if req.temperature is not None else 0.7
# Direct LLM call with messages (works for Ollama/OpenAI chat APIs) # Direct LLM call with messages (works for Ollama/OpenAI chat APIs)