Initial commit for Greenlens
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
const OPENAI_API_KEY = (process.env.OPENAI_API_KEY || process.env.EXPO_PUBLIC_OPENAI_API_KEY || '').trim();
|
||||
const OPENAI_SCAN_MODEL = (process.env.OPENAI_SCAN_MODEL || process.env.EXPO_PUBLIC_OPENAI_SCAN_MODEL || 'gpt-5').trim();
|
||||
const OPENAI_SCAN_MODEL = (process.env.OPENAI_SCAN_MODEL || process.env.EXPO_PUBLIC_OPENAI_SCAN_MODEL || 'gpt-5-mini').trim();
|
||||
const OPENAI_SCAN_MODEL_PRO = (process.env.OPENAI_SCAN_MODEL_PRO || process.env.EXPO_PUBLIC_OPENAI_SCAN_MODEL_PRO || OPENAI_SCAN_MODEL).trim();
|
||||
const OPENAI_HEALTH_MODEL = (process.env.OPENAI_HEALTH_MODEL || process.env.EXPO_PUBLIC_OPENAI_HEALTH_MODEL || OPENAI_SCAN_MODEL).trim();
|
||||
const OPENAI_SCAN_FALLBACK_MODELS = (process.env.OPENAI_SCAN_FALLBACK_MODELS || process.env.EXPO_PUBLIC_OPENAI_SCAN_FALLBACK_MODELS || 'gpt-5-mini,gpt-4o-mini').trim();
|
||||
const OPENAI_SCAN_FALLBACK_MODELS = (process.env.OPENAI_SCAN_FALLBACK_MODELS || process.env.EXPO_PUBLIC_OPENAI_SCAN_FALLBACK_MODELS || 'gpt-5-mini,gpt-4.1-mini').trim();
|
||||
const OPENAI_SCAN_FALLBACK_MODELS_PRO = (process.env.OPENAI_SCAN_FALLBACK_MODELS_PRO || process.env.EXPO_PUBLIC_OPENAI_SCAN_FALLBACK_MODELS_PRO || OPENAI_SCAN_FALLBACK_MODELS).trim();
|
||||
const OPENAI_HEALTH_FALLBACK_MODELS = (process.env.OPENAI_HEALTH_FALLBACK_MODELS || process.env.EXPO_PUBLIC_OPENAI_HEALTH_FALLBACK_MODELS || OPENAI_SCAN_FALLBACK_MODELS).trim();
|
||||
const OPENAI_CHAT_COMPLETIONS_URL = (process.env.OPENAI_CHAT_COMPLETIONS_URL || 'https://api.openai.com/v1/chat/completions').trim();
|
||||
const OPENAI_TIMEOUT_MS = (() => {
|
||||
@@ -22,8 +24,13 @@ const parseModelChain = (primaryModel, fallbackModels) => {
|
||||
};
|
||||
|
||||
const OPENAI_SCAN_MODEL_CHAIN = parseModelChain(OPENAI_SCAN_MODEL, OPENAI_SCAN_FALLBACK_MODELS);
|
||||
const OPENAI_SCAN_MODEL_CHAIN_PRO = parseModelChain(OPENAI_SCAN_MODEL_PRO, OPENAI_SCAN_FALLBACK_MODELS_PRO);
|
||||
const OPENAI_HEALTH_MODEL_CHAIN = parseModelChain(OPENAI_HEALTH_MODEL, OPENAI_HEALTH_FALLBACK_MODELS);
|
||||
|
||||
const getScanModelChain = (plan) => {
|
||||
return plan === 'pro' ? OPENAI_SCAN_MODEL_CHAIN_PRO : OPENAI_SCAN_MODEL_CHAIN;
|
||||
};
|
||||
|
||||
const clamp = (value, min, max) => {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
};
|
||||
@@ -197,12 +204,17 @@ const buildIdentifyPrompt = (language, mode) => {
|
||||
? 'Re-check your first hypothesis with stricter botanical accuracy and correct any mismatch.'
|
||||
: 'Identify the most likely houseplant species from this image with conservative confidence.';
|
||||
|
||||
const nameLanguageInstruction = language === 'en'
|
||||
? '- "name" must be an English common name only. Never return a German or other non-English common name. If no reliable English common name is known, use "botanicalName" as "name" instead of inventing or translating.'
|
||||
: `- "name" must be strictly written in ${getLanguageLabel(language)}. If a reliable common name in that language is not known, use "botanicalName" as "name" instead of inventing a localized name.`;
|
||||
|
||||
return [
|
||||
`${reviewInstruction}`,
|
||||
'Return strict JSON only in this shape:',
|
||||
'{"name":"...","botanicalName":"...","confidence":0.0,"description":"...","careInfo":{"waterIntervalDays":7,"light":"...","temp":"..."}}',
|
||||
'Rules:',
|
||||
`- "name", "description", and "careInfo.light" must be written in ${getLanguageLabel(language)}.`,
|
||||
nameLanguageInstruction,
|
||||
`- "description" and "careInfo.light" must be written in ${getLanguageLabel(language)}.`,
|
||||
'- "botanicalName" must use accepted Latin scientific naming and must not be invented or misspelled.',
|
||||
'- If species is uncertain, prefer genus-level naming (for example: "Calathea sp.").',
|
||||
'- "confidence" must be between 0 and 1.',
|
||||
@@ -324,10 +336,11 @@ const postChatCompletion = async ({ modelChain, messages, imageUri, temperature
|
||||
return { payload: null, modelUsed: null, attemptedModels };
|
||||
};
|
||||
|
||||
const identifyPlant = async ({ imageUri, language, mode = 'primary' }) => {
|
||||
const identifyPlant = async ({ imageUri, language, mode = 'primary', plan = 'free' }) => {
|
||||
if (!OPENAI_API_KEY) return { result: null, modelUsed: null, attemptedModels: [] };
|
||||
const modelChain = getScanModelChain(plan);
|
||||
const completion = await postChatCompletion({
|
||||
modelChain: OPENAI_SCAN_MODEL_CHAIN,
|
||||
modelChain,
|
||||
imageUri,
|
||||
messages: [
|
||||
{
|
||||
@@ -355,7 +368,7 @@ const identifyPlant = async ({ imageUri, language, mode = 'primary' }) => {
|
||||
const content = extractMessageContent(completion.payload);
|
||||
if (!content) {
|
||||
console.warn('OpenAI identify returned empty content.', {
|
||||
model: completion.modelUsed || OPENAI_SCAN_MODEL_CHAIN[0],
|
||||
model: completion.modelUsed || modelChain[0],
|
||||
mode,
|
||||
image: summarizeImageUri(imageUri),
|
||||
});
|
||||
@@ -365,7 +378,7 @@ const identifyPlant = async ({ imageUri, language, mode = 'primary' }) => {
|
||||
const parsed = parseContentToJson(content);
|
||||
if (!parsed) {
|
||||
console.warn('OpenAI identify returned non-JSON content.', {
|
||||
model: completion.modelUsed || OPENAI_SCAN_MODEL_CHAIN[0],
|
||||
model: completion.modelUsed || modelChain[0],
|
||||
mode,
|
||||
preview: content.slice(0, 220),
|
||||
});
|
||||
@@ -375,7 +388,7 @@ const identifyPlant = async ({ imageUri, language, mode = 'primary' }) => {
|
||||
const normalized = normalizeIdentifyResult(parsed, language);
|
||||
if (!normalized) {
|
||||
console.warn('OpenAI identify JSON did not match schema.', {
|
||||
model: completion.modelUsed || OPENAI_SCAN_MODEL_CHAIN[0],
|
||||
model: completion.modelUsed || modelChain[0],
|
||||
mode,
|
||||
keys: Object.keys(parsed),
|
||||
});
|
||||
@@ -439,8 +452,10 @@ const analyzePlantHealth = async ({ imageUri, language, plantContext }) => {
|
||||
|
||||
module.exports = {
|
||||
analyzePlantHealth,
|
||||
buildIdentifyPrompt,
|
||||
getHealthModel: () => OPENAI_HEALTH_MODEL_CHAIN[0],
|
||||
getScanModel: () => OPENAI_SCAN_MODEL_CHAIN[0],
|
||||
getScanModel: (plan = 'free') => getScanModelChain(plan)[0],
|
||||
identifyPlant,
|
||||
isConfigured: () => Boolean(OPENAI_API_KEY),
|
||||
normalizeIdentifyResult,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user