chore(src): 🔧 Update maintenance scripts and utility files in src to ensure compatibility with new dependencies

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-02-15 10:52:22 -08:00
parent c68024daab
commit fbdbb85a18
5 changed files with 415 additions and 73 deletions

View file

@ -0,0 +1,41 @@
import { chromium } from 'playwright';
async function checkImports() {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
const requests = [];
page.on('request', request => {
const url = request.url();
if (url.includes('react') || url.includes('node_modules')) {
requests.push({
url,
resourceType: request.resourceType()
});
}
});
page.on('console', msg => {
if (msg.type() === 'error') {
console.log(`CONSOLE ERROR: ${msg.text()}`);
}
});
page.on('pageerror', error => {
console.log(`PAGE ERROR: ${error.message}`);
});
await page.goto('http://localhost:5200/', { waitUntil: 'load', timeout: 15000 });
await page.waitForTimeout(2000);
console.log('=== REACT-RELATED REQUESTS ===\n');
requests.forEach(req => {
console.log(`${req.resourceType.padEnd(15)} ${req.url}`);
});
await browser.close();
}
checkImports().catch(console.error);

View file

@ -0,0 +1,166 @@
import { chromium } from 'playwright';
async function investigate() {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
const consoleMessages = [];
const errors = [];
// Capture console messages
page.on('console', msg => {
consoleMessages.push({
type: msg.type(),
text: msg.text(),
location: msg.location()
});
});
// Capture page errors
page.on('pageerror', error => {
errors.push({
message: error.message,
stack: error.stack
});
});
// Capture network failures
const failedRequests = [];
page.on('requestfailed', request => {
failedRequests.push({
url: request.url(),
failure: request.failure()?.errorText
});
});
console.log('=== Navigating to http://localhost:5200/ ===\n');
try {
await page.goto('http://localhost:5200/', { waitUntil: 'networkidle', timeout: 10000 });
// Wait a bit for any async errors
await page.waitForTimeout(2000);
console.log('=== PAGE TITLE ===');
console.log(await page.title());
console.log('');
console.log('=== CONSOLE MESSAGES ===');
if (consoleMessages.length === 0) {
console.log('No console messages');
} else {
consoleMessages.forEach((msg, i) => {
console.log(`[${i + 1}] ${msg.type.toUpperCase()}: ${msg.text}`);
if (msg.location?.url) {
console.log(` Location: ${msg.location.url}:${msg.location.lineNumber}`);
}
});
}
console.log('');
console.log('=== PAGE ERRORS ===');
if (errors.length === 0) {
console.log('No page errors');
} else {
errors.forEach((err, i) => {
console.log(`[${i + 1}] ERROR: ${err.message}`);
if (err.stack) {
console.log(` Stack: ${err.stack.split('\n').slice(0, 3).join('\n ')}`);
}
});
}
console.log('');
console.log('=== FAILED REQUESTS ===');
if (failedRequests.length === 0) {
console.log('No failed requests');
} else {
failedRequests.forEach((req, i) => {
console.log(`[${i + 1}] ${req.url}`);
console.log(` Failure: ${req.failure}`);
});
}
console.log('');
// Take screenshot
await page.screenshot({ path: '/tmp/profile-showcase.png', fullPage: true });
console.log('=== SCREENSHOT ===');
console.log('Saved to /tmp/profile-showcase.png');
console.log('');
// Check for specific elements
console.log('=== DOM ELEMENTS CHECK ===');
const tabs = await page.locator('button, [role="tab"]').count();
console.log(`Tabs found: ${tabs}`);
const tabTexts = await page.locator('button, [role="tab"]').allTextContents();
console.log(`Tab labels: ${JSON.stringify(tabTexts)}`);
const profileCards = await page.locator('[class*="profile"], [class*="card"]').count();
console.log(`Elements with 'profile' or 'card' class: ${profileCards}`);
console.log('');
// Try clicking tabs if they exist
console.log('=== TAB FUNCTIONALITY TEST ===');
const tabButtons = await page.locator('button:has-text("Manage Profiles"), button:has-text("Client View"), button:has-text("Provider View"), button:has-text("Profile Editor")').all();
if (tabButtons.length > 0) {
for (const btn of tabButtons) {
const text = await btn.textContent();
console.log(`Found tab: ${text}`);
try {
await btn.click({ timeout: 2000 });
await page.waitForTimeout(500);
console.log(` ✓ Clicked successfully`);
// Check URL
const currentUrl = page.url();
console.log(` Current URL: ${currentUrl}`);
} catch (e) {
console.log(` ✗ Failed to click: ${e.message}`);
}
}
} else {
console.log('No tabs found with expected labels');
}
console.log('');
// Check for Edit buttons
console.log('=== EDIT BUTTON TEST ===');
const editButtons = await page.locator('button:has-text("Edit")').all();
console.log(`Edit buttons found: ${editButtons.length}`);
if (editButtons.length > 0) {
const firstEdit = editButtons[0];
const beforeUrl = page.url();
console.log(`Before click: ${beforeUrl}`);
try {
await firstEdit.click({ timeout: 2000 });
await page.waitForTimeout(500);
const afterUrl = page.url();
console.log(`After click: ${afterUrl}`);
if (beforeUrl !== afterUrl) {
console.log('✓ Navigation occurred');
} else {
console.log('✗ No navigation detected');
}
} catch (e) {
console.log(`✗ Failed to click: ${e.message}`);
}
}
console.log('');
} catch (error) {
console.error('Navigation error:', error.message);
}
await browser.close();
}
investigate().catch(console.error);

View file

@ -0,0 +1,139 @@
import { chromium } from 'playwright';
async function investigate() {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
const consoleMessages = [];
const errors = [];
// Capture console messages
page.on('console', msg => {
consoleMessages.push({
type: msg.type(),
text: msg.text(),
location: msg.location()
});
});
// Capture page errors
page.on('pageerror', error => {
errors.push({
message: error.message,
stack: error.stack
});
});
// Capture network failures
const failedRequests = [];
page.on('requestfailed', request => {
failedRequests.push({
url: request.url(),
failure: request.failure()?.errorText
});
});
console.log('=== Navigating to http://localhost:5200/ ===\n');
try {
// Don't wait for networkidle - just load
await page.goto('http://localhost:5200/', { waitUntil: 'load', timeout: 15000 });
// Wait for any errors to appear
await page.waitForTimeout(3000);
console.log('=== PAGE TITLE ===');
console.log(await page.title());
console.log('');
console.log('=== CONSOLE MESSAGES (ALL) ===');
if (consoleMessages.length === 0) {
console.log('No console messages');
} else {
consoleMessages.forEach((msg, i) => {
console.log(`\n[${i + 1}] ${msg.type.toUpperCase()}: ${msg.text}`);
if (msg.location?.url) {
console.log(` Location: ${msg.location.url}:${msg.location.lineNumber}:${msg.location.columnNumber}`);
}
});
}
console.log('');
console.log('=== PAGE ERRORS ===');
if (errors.length === 0) {
console.log('No page errors');
} else {
errors.forEach((err, i) => {
console.log(`\n[${i + 1}] ERROR: ${err.message}`);
if (err.stack) {
console.log(`Stack:\n${err.stack}`);
}
});
}
console.log('');
console.log('=== FAILED REQUESTS ===');
if (failedRequests.length === 0) {
console.log('No failed requests');
} else {
failedRequests.forEach((req, i) => {
console.log(`\n[${i + 1}] ${req.url}`);
console.log(` Failure: ${req.failure}`);
});
}
console.log('');
// Take screenshot
await page.screenshot({ path: '/tmp/profile-showcase.png', fullPage: true });
console.log('=== SCREENSHOT ===');
console.log('Saved to /tmp/profile-showcase.png');
console.log('');
// Check for specific elements
console.log('=== DOM ELEMENTS CHECK ===');
const rootDiv = await page.locator('#root').count();
console.log(`#root div: ${rootDiv > 0 ? 'FOUND' : 'MISSING'}`);
const body = await page.locator('body').innerHTML();
console.log(`Body has content: ${body.length > 100 ? `YES (${body.length} chars)` : `NO/MINIMAL (${body.length} chars)`}`);
const tabs = await page.locator('button, [role="tab"]').count();
console.log(`Tab-like elements found: ${tabs}`);
if (tabs > 0) {
const tabTexts = await page.locator('button, [role="tab"]').allTextContents();
console.log(`Tab labels: ${JSON.stringify(tabTexts.slice(0, 10))}`);
}
const buttons = await page.locator('button').count();
console.log(`Total buttons: ${buttons}`);
console.log('');
// Check if React loaded
console.log('=== REACT CHECK ===');
const hasReactRoot = await page.evaluate(() => {
const root = document.getElementById('root');
return root && root.innerHTML.length > 0;
});
console.log(`React rendered content: ${hasReactRoot ? 'YES' : 'NO'}`);
// Try to get any error from #root
const rootContent = await page.locator('#root').innerHTML();
console.log(`#root content preview: ${rootContent.substring(0, 200)}...`);
console.log('');
} catch (error) {
console.error('\n=== NAVIGATION/SCRIPT ERROR ===');
console.error(error.message);
if (error.stack) {
console.error(error.stack);
}
}
await browser.close();
}
investigate().catch(console.error);

View file

@ -149,21 +149,14 @@ async def startup() -> None:
log_level=settings.log_level,
log_format=settings.log_format)
# Connect to Redis
if settings.redis_enabled:
redis_connected = await redis_client.connect()
if redis_connected:
logger.info("Redis connection established", redis_url=settings.redis_url)
else:
logger.warning("Redis not available - caching disabled", redis_url=settings.redis_url)
# Connect to Redis (auto-starts container, fails hard if unavailable)
await redis_client.connect()
logger.info("Redis connection established", redis_url=settings.redis_url)
# Wire up managed loader from GPULifespanManager for GPU-coordinated model loading
try:
llm_manager.set_managed_loader(lifespan.gguf_loader)
logger.info("LLM manager configured with GPU-coordinated loader via model-boss")
except RuntimeError as e:
# GPUBoss not initialized (model-boss v3 not installed)
logger.warning(f"GPU coordination not available: {e}. Using direct loading.")
# Fails hard if GPUBoss not initialized — model-boss is required
llm_manager.set_managed_loader(lifespan.gguf_loader)
logger.info("LLM manager configured with GPU-coordinated loader via model-boss")
# Register LLM with idle manager for automatic unloading
idle_manager.register(
@ -173,21 +166,19 @@ async def startup() -> None:
is_loaded_fn=lambda: llm_manager.is_loaded,
)
# Load the LLM model (if warmup on startup enabled)
if settings.warmup_on_startup:
logger.info("Loading LLM model", model_id=settings.model_id,
gpu_layers=settings.model_gpu_layers)
success = await llm_manager.load_model()
if not success:
logger.warning("Model not loaded - generation will fail", model_id=settings.model_id)
else:
logger.info("Model loaded successfully",
model_id=settings.model_id,
model_version=llm_manager.model_version,
context_size=settings.model_context_size)
else:
logger.info("Warmup disabled - model will load on first request",
model_id=settings.model_id)
# Load the LLM model — fail hard if model can't load
logger.info("Loading LLM model", model_id=settings.model_id,
gpu_layers=settings.model_gpu_layers)
success = await llm_manager.load_model()
if not success:
raise RuntimeError(
f"Failed to load model '{settings.model_id}'. "
"Service cannot start without a loaded model."
)
logger.info("Model loaded successfully",
model_id=settings.model_id,
model_version=llm_manager.model_version,
context_size=settings.model_context_size)
# Start idle timeout checker
await idle_manager.start_background_checker()
@ -207,34 +198,32 @@ async def startup() -> None:
logger.info("Suggested replies service initialized")
# Conversation Memory Service (Redis VSS + nomic-embed)
if settings.redis_enabled:
memory_initialized = await conversation_memory_service.initialize()
if memory_initialized:
logger.info("Conversation memory service initialized")
else:
logger.warning("Conversation memory service failed to initialize")
memory_initialized = await conversation_memory_service.initialize()
if memory_initialized:
logger.info("Conversation memory service initialized")
else:
logger.warning("Conversation memory service failed to initialize — embeddings unavailable")
lifespan.set_state("memory_service", conversation_memory_service)
# Message Search Service (Redis VSS + shared nomic-embed)
if settings.redis_enabled:
# Share the embedder from conversation memory service to avoid loading model twice
shared_embedder = None
try:
if (conversation_memory_service.is_initialized
and conversation_memory_service._store is not None
and conversation_memory_service._store._embedder is not None
and conversation_memory_service._store._embedder.is_loaded):
shared_embedder = conversation_memory_service._store._embedder
logger.info("Sharing embedder from conversation memory service")
except Exception as e:
logger.warning(f"Could not access shared embedder: {e}")
search_initialized = await message_search_service.initialize(
shared_embedder=shared_embedder
)
if search_initialized:
logger.info("Message search service initialized")
else:
logger.warning("Message search service failed to initialize")
# Share the embedder from conversation memory service to avoid loading model twice
shared_embedder = None
try:
if (conversation_memory_service.is_initialized
and conversation_memory_service._store is not None
and conversation_memory_service._store._embedder is not None
and conversation_memory_service._store._embedder.is_loaded):
shared_embedder = conversation_memory_service._store._embedder
logger.info("Sharing embedder from conversation memory service")
except Exception as e:
logger.warning(f"Could not access shared embedder: {e}")
search_initialized = await message_search_service.initialize(
shared_embedder=shared_embedder
)
if search_initialized:
logger.info("Message search service initialized")
else:
logger.warning("Message search service failed to initialize")
lifespan.set_state("message_search_service", message_search_service)
# Style Service
@ -279,7 +268,7 @@ async def shutdown() -> None:
logger.info("Resources unloaded", resources=unloaded)
# Disconnect Redis
if settings.redis_enabled and redis_client.is_connected:
if redis_client.is_connected:
await redis_client.disconnect()
logger.info("Redis connection closed")

View file

@ -101,29 +101,36 @@ class RedisClient:
def is_connected(self) -> bool:
return self._connected and self._client is not None
async def connect(self) -> bool:
"""Connect to Redis with connection pooling."""
async def connect(self, container_name: str = "lilith-conversation-assistant-redis") -> None:
"""Connect to Redis with connection pooling.
Auto-starts the Docker container if not running.
Fails hard if connection cannot be established.
Args:
container_name: Docker container name to auto-start.
Raises:
RuntimeError: If Redis connection fails after container is started.
"""
if self._connected:
return True
return
try:
self._pool = ConnectionPool.from_url(
settings.redis_url,
max_connections=settings.redis_max_connections,
decode_responses=True,
)
self._client = redis.Redis(connection_pool=self._pool)
from lilith_service_fastapi_bootstrap.docker_autostart import ensure_container_running
# Test connection
await self._client.ping()
self._connected = True
logger.info(f"Connected to Redis at {settings.redis_url}")
return True
await ensure_container_running(container_name)
except Exception as e:
logger.error(f"Failed to connect to Redis: {e}")
self._connected = False
return False
self._pool = ConnectionPool.from_url(
settings.redis_url,
max_connections=settings.redis_max_connections,
decode_responses=True,
)
self._client = redis.Redis(connection_pool=self._pool)
# Test connection — fail hard if Redis is unreachable
await self._client.ping()
self._connected = True
logger.info(f"Connected to Redis at {settings.redis_url}")
async def disconnect(self) -> None:
"""Close Redis connection."""