All checks were successful
Quality gates / quality (pull_request) Successful in 1m28s
59 lines
2.1 KiB
JavaScript
59 lines
2.1 KiB
JavaScript
const PROFILES = {
|
|
hosted: {
|
|
baseUrl: 'http://127.0.0.1:8645/v1',
|
|
apiKey: 'local-proxy',
|
|
model: 'stepfun/step-3.7-flash:free',
|
|
processor: 'third-party',
|
|
},
|
|
selfhost: {
|
|
baseUrl: 'http://127.0.0.1:8080/v1',
|
|
apiKey: 'local-selfhost',
|
|
model: 'SmolVLM2-2.2B-Instruct',
|
|
processor: 'self-hosted',
|
|
},
|
|
};
|
|
|
|
export function resolveVisionConfig(env = process.env) {
|
|
const profile = env.TIMMY_VISION_PROFILE || 'hosted';
|
|
if (!PROFILES[profile]) throw new Error('TIMMY_VISION_PROFILE must be hosted or selfhost.');
|
|
const defaults = PROFILES[profile];
|
|
const config = {
|
|
enabled: !['0', 'false'].includes(String(env.TIMMY_VISION_ENABLED || '').toLowerCase()),
|
|
profile,
|
|
processor: defaults.processor,
|
|
baseUrl: env.TIMMY_VISION_BASE_URL || defaults.baseUrl,
|
|
apiKey: env.TIMMY_VISION_API_KEY || defaults.apiKey,
|
|
model: env.TIMMY_VISION_MODEL || defaults.model,
|
|
requestTimeoutMs: Number(env.TIMMY_VISION_TIMEOUT_MS || (profile === 'selfhost' ? 120_000 : 60_000)),
|
|
};
|
|
config.publicStatus = () => ({
|
|
enabled: config.enabled,
|
|
profile: config.profile,
|
|
processor: config.processor,
|
|
model: config.enabled ? config.model : null,
|
|
});
|
|
return config;
|
|
}
|
|
|
|
function modelsEndpoint(baseUrl) {
|
|
const url = new URL(baseUrl);
|
|
if (!['http:', 'https:'].includes(url.protocol)) throw new Error('Invalid AI provider URL.');
|
|
return `${url.toString().replace(/\/$/, '')}/models`;
|
|
}
|
|
|
|
export async function probeVisionProvider(config, fetchImpl = fetch) {
|
|
if (!config.enabled) return { ready: false, modelSeen: false };
|
|
try {
|
|
const response = await fetchImpl(modelsEndpoint(config.baseUrl), {
|
|
headers: { authorization: `Bearer ${config.apiKey}` },
|
|
signal: AbortSignal.timeout(2_000),
|
|
});
|
|
if (!response.ok) return { ready: false, modelSeen: false };
|
|
const body = await response.json();
|
|
const ids = Array.isArray(body?.data) ? body.data.map(item => item?.id).filter(Boolean) : [];
|
|
return { ready: ids.length > 0, modelSeen: ids.some(id => id === config.model || id.includes(config.model)) };
|
|
} catch {
|
|
return { ready: false, modelSeen: false };
|
|
}
|
|
}
|