mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
Backend:
- Add smart provider fallback when selected model's provider isn't configured
- Automatically switch to a model from a configured provider instead of failing
- Log warning when fallback occurs for visibility
Frontend (AISettings.tsx):
- Add helper functions to check if model's provider is configured
- Group model dropdown: configured providers first, unconfigured marked with ⚠️
- Add inline warning when selecting model from unconfigured provider
- Validate on save that model's provider is configured (or being added)
- Warn before clearing last configured provider (would disable AI)
- Warn before clearing provider that current model uses
- Add patrol interval validation (must be 0 or >= 10 minutes)
- Show red border + inline error for invalid patrol intervals 1-9
- Update patrol interval hint: '(0=off, 10+ to enable)'
These changes prevent confusing '500 Internal Server Error' and
'AI is not enabled or configured' errors when model/provider mismatch.
70 lines
2.1 KiB
JavaScript
70 lines
2.1 KiB
JavaScript
import { spawn } from 'node:child_process';
|
|
|
|
const truthy = (value) => {
|
|
if (!value) return false;
|
|
return ['1', 'true', 'yes', 'on'].includes(String(value).trim().toLowerCase());
|
|
};
|
|
|
|
const shouldSkipDocker = truthy(process.env.PULSE_E2E_SKIP_DOCKER);
|
|
const shouldSkipPlaywrightInstall = truthy(process.env.PULSE_E2E_SKIP_PLAYWRIGHT_INSTALL);
|
|
|
|
const run = (command, args, options = {}) =>
|
|
new Promise((resolve, reject) => {
|
|
const child = spawn(command, args, { stdio: 'inherit', ...options });
|
|
child.on('error', reject);
|
|
child.on('close', (code) => {
|
|
if (code === 0) resolve();
|
|
else reject(new Error(`${command} ${args.join(' ')} exited with code ${code}`));
|
|
});
|
|
});
|
|
|
|
const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
|
|
const canRun = async (command, args) => {
|
|
try {
|
|
await run(command, args, { stdio: 'ignore' });
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const waitForHealth = async (healthURL, timeoutMs = 120_000) => {
|
|
const startedAt = Date.now();
|
|
while (Date.now() - startedAt < timeoutMs) {
|
|
try {
|
|
const res = await fetch(healthURL, { method: 'GET' });
|
|
if (res.ok) return;
|
|
} catch {
|
|
// ignore and retry
|
|
}
|
|
await new Promise((r) => setTimeout(r, 1000));
|
|
}
|
|
throw new Error(`Timed out waiting for ${healthURL}`);
|
|
};
|
|
|
|
if (truthy(process.env.PULSE_E2E_INSECURE_TLS)) {
|
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
}
|
|
|
|
if (!shouldSkipPlaywrightInstall) {
|
|
await run(npxCmd, ['playwright', 'install', 'chromium']);
|
|
}
|
|
|
|
if (shouldSkipDocker) {
|
|
console.log('[integration] PULSE_E2E_SKIP_DOCKER enabled, skipping docker compose up');
|
|
process.exit(0);
|
|
}
|
|
|
|
const composeArgs = ['compose', '-f', 'docker-compose.test.yml', 'up', '-d'];
|
|
const legacyComposeArgs = ['-f', 'docker-compose.test.yml', 'up', '-d'];
|
|
const useDockerCompose = !(await canRun('docker', ['compose', 'version']));
|
|
|
|
if (useDockerCompose) {
|
|
await run('docker-compose', legacyComposeArgs);
|
|
} else {
|
|
await run('docker', composeArgs);
|
|
}
|
|
|
|
const baseURL = (process.env.PULSE_BASE_URL || 'http://localhost:7655').replace(/\/+$/, '');
|
|
await waitForHealth(`${baseURL}/api/health`);
|