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:
parent
c68024daab
commit
fbdbb85a18
5 changed files with 415 additions and 73 deletions
41
e2e/smoke/check-imports.mjs
Normal file
41
e2e/smoke/check-imports.mjs
Normal 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);
|
||||
166
e2e/smoke/investigate-profile-showcase.mjs
Normal file
166
e2e/smoke/investigate-profile-showcase.mjs
Normal 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);
|
||||
139
e2e/smoke/investigate-profile.mjs
Normal file
139
e2e/smoke/investigate-profile.mjs
Normal 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);
|
||||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue