modificari mari
This commit is contained in:
@@ -73,8 +73,18 @@ def extract_label_info(image_bytes: bytes, mode: str = "item"):
|
||||
# 3. Final fallback to hardcoded default
|
||||
prompt = "Extract technical specs. Return JSON with name, category, description, connector, size, color, part_number, ocr_text, quantity."
|
||||
|
||||
# 1. Try Gemini
|
||||
result = gemini.extract(image_bytes, prompt)
|
||||
# 0. Get Active Provider from DB
|
||||
provider_setting = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_provider").first()
|
||||
active_provider = provider_setting.value if provider_setting else "gemini"
|
||||
|
||||
# 1. Execute extraction based on selection
|
||||
result = None
|
||||
if active_provider == "claude":
|
||||
print(f"📡 Using Anthropic Claude for extraction...")
|
||||
result = claude.extract(image_bytes, prompt)
|
||||
else:
|
||||
print(f"📡 Using Google Gemini for extraction...")
|
||||
result = gemini.extract(image_bytes, prompt)
|
||||
|
||||
if result:
|
||||
# Check if AI returned a list of items or a singular object
|
||||
|
||||
90
backend/config_manager.py
Normal file
90
backend/config_manager.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
class ConfigManager:
|
||||
"""Safely manages multi-line .env files without corrupting other content."""
|
||||
|
||||
@staticmethod
|
||||
def get_root_env_path():
|
||||
# backend/config_manager.py -> backend/ -> /
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(base_dir)
|
||||
return os.path.join(project_root, "inventory.env")
|
||||
|
||||
@staticmethod
|
||||
def update_keys(updates: dict):
|
||||
"""
|
||||
Updates specific keys in inventory.env.
|
||||
Preserves comments and order where possible.
|
||||
Appends new keys at the end if not found.
|
||||
"""
|
||||
env_path = ConfigManager.get_root_env_path()
|
||||
|
||||
if not os.path.exists(env_path):
|
||||
# Create a basic file if it doesn't exist (unlikely in this project)
|
||||
with open(env_path, 'w', encoding='utf-8') as f:
|
||||
f.write("# TFM aInventory — Generated Configuration\n")
|
||||
|
||||
with open(env_path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
new_lines = []
|
||||
keys_to_process = set(updates.keys())
|
||||
processed_keys = set()
|
||||
|
||||
for line in lines:
|
||||
trimmed = line.strip()
|
||||
# Skip empty lines or comments when matching
|
||||
if not trimmed or trimmed.startswith('#'):
|
||||
new_lines.append(line)
|
||||
continue
|
||||
|
||||
# Check if this line is a key assignment we want to update
|
||||
found_match = False
|
||||
for key in keys_to_process:
|
||||
if trimmed.startswith(f"{key}="):
|
||||
new_lines.append(f"{key}={updates[key]}\n")
|
||||
processed_keys.add(key)
|
||||
found_match = True
|
||||
break
|
||||
|
||||
if not found_match:
|
||||
new_lines.append(line)
|
||||
|
||||
# Append keys that weren't found in the file
|
||||
missing_keys = keys_to_process - processed_keys
|
||||
if missing_keys:
|
||||
if new_lines and not new_lines[-1].endswith('\n'):
|
||||
new_lines.append('\n')
|
||||
if new_lines and not new_lines[-1].strip() == '':
|
||||
new_lines.append('\n')
|
||||
|
||||
new_lines.append("# --- Automatically Added Keys ---\n")
|
||||
for key in missing_keys:
|
||||
new_lines.append(f"{key}={updates[key]}\n")
|
||||
|
||||
with open(env_path, 'w', encoding='utf-8') as f:
|
||||
f.writelines(new_lines)
|
||||
|
||||
# Force reload environment variables for the current process
|
||||
load_dotenv(env_path, override=True)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def get_masked_key(key_name: str):
|
||||
"""Returns a masked version of the environment variable."""
|
||||
val = os.environ.get(key_name)
|
||||
if not val:
|
||||
return None
|
||||
|
||||
# Determine prefix based on key type
|
||||
prefix = ""
|
||||
if key_name == "GEMINI_API_KEY":
|
||||
prefix = "G-"
|
||||
elif key_name == "CLAUDE_API_KEY":
|
||||
prefix = "sk-"
|
||||
|
||||
if len(val) <= 8:
|
||||
return f"{prefix}****"
|
||||
|
||||
return f"{prefix}****{val[-4:]}"
|
||||
@@ -145,7 +145,8 @@ def startup_event():
|
||||
defaults = {
|
||||
"backup_retention_count": "10",
|
||||
"backup_schedule_hour": "3",
|
||||
"backup_schedule_freq_days": "1"
|
||||
"backup_schedule_freq_days": "1",
|
||||
"ai_provider": "gemini"
|
||||
}
|
||||
for key, val in defaults.items():
|
||||
if not db.query(models.SystemSetting).filter(models.SystemSetting.key == key).first():
|
||||
|
||||
@@ -189,3 +189,126 @@ def update_ai_prompt(
|
||||
|
||||
db.commit()
|
||||
return {"status": "success", "file_updated": os.path.exists(PROMPT_FILE_PATH)}
|
||||
|
||||
from ..config_manager import ConfigManager
|
||||
|
||||
@router.get("/settings/ai")
|
||||
def get_ai_config(
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: auth.TokenData = Depends(auth.get_current_admin)
|
||||
):
|
||||
"""Check AI provider status and active provider."""
|
||||
gemini_key = os.environ.get("GEMINI_API_KEY")
|
||||
claude_key = os.environ.get("CLAUDE_API_KEY")
|
||||
|
||||
# Get active provider from DB
|
||||
provider_setting = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_provider").first()
|
||||
active_provider = provider_setting.value if provider_setting else "gemini"
|
||||
|
||||
return {
|
||||
"active_provider": active_provider,
|
||||
"providers": [
|
||||
{
|
||||
"id": "gemini",
|
||||
"name": "Google Gemini 2.0",
|
||||
"configured": bool(gemini_key),
|
||||
"active": active_provider == "gemini",
|
||||
"masked_key": ConfigManager.get_masked_key("GEMINI_API_KEY")
|
||||
},
|
||||
{
|
||||
"id": "claude",
|
||||
"name": "Anthropic Claude 3.5",
|
||||
"configured": bool(claude_key),
|
||||
"active": active_provider == "claude",
|
||||
"masked_key": ConfigManager.get_masked_key("CLAUDE_API_KEY")
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@router.post("/settings/ai-keys")
|
||||
def update_ai_keys(
|
||||
payload: dict,
|
||||
current_admin: auth.TokenData = Depends(auth.get_current_admin)
|
||||
):
|
||||
"""Update AI API keys in inventory.env."""
|
||||
gemini_key = payload.get("gemini_api_key")
|
||||
claude_key = payload.get("claude_api_key")
|
||||
|
||||
updates = {}
|
||||
if gemini_key:
|
||||
updates["GEMINI_API_KEY"] = gemini_key
|
||||
if claude_key:
|
||||
updates["CLAUDE_API_KEY"] = claude_key
|
||||
|
||||
if not updates:
|
||||
return {"status": "no_change"}
|
||||
|
||||
ConfigManager.update_keys(updates)
|
||||
return {
|
||||
"status": "success",
|
||||
"gemini_configured": bool(os.environ.get("GEMINI_API_KEY")),
|
||||
"claude_configured": bool(os.environ.get("CLAUDE_API_KEY"))
|
||||
}
|
||||
|
||||
@router.post("/settings/test-ai-key")
|
||||
def test_ai_key(
|
||||
payload: dict,
|
||||
current_admin: auth.TokenData = Depends(auth.get_current_admin)
|
||||
):
|
||||
"""Test AI API key connectivity."""
|
||||
provider = payload.get("provider")
|
||||
key = payload.get("key")
|
||||
|
||||
if not provider or provider not in ["gemini", "claude"]:
|
||||
raise HTTPException(status_code=400, detail="Invalid provider")
|
||||
|
||||
# If key is masked or empty, use the one from environment
|
||||
if not key or "****" in key:
|
||||
key = os.environ.get("GEMINI_API_KEY" if provider == "gemini" else "CLAUDE_API_KEY")
|
||||
|
||||
if not key:
|
||||
raise HTTPException(status_code=400, detail="No API key provided or configured")
|
||||
|
||||
try:
|
||||
if provider == "gemini":
|
||||
from google import genai
|
||||
client = genai.Client(api_key=key, http_options={'api_version': 'v1beta'})
|
||||
# Listing models is the standard way to check key validity
|
||||
client.models.list()
|
||||
return {"status": "success", "message": "Google Gemini API connection verified!"}
|
||||
|
||||
elif provider == "claude":
|
||||
import anthropic
|
||||
client = anthropic.Anthropic(api_key=key)
|
||||
# Claude also supports model listing for key verification
|
||||
client.models.list(limit=1)
|
||||
return {"status": "success", "message": "Anthropic Claude API connection verified!"}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if "API_KEY_INVALID" in error_msg or "invalid_api_key" in error_msg:
|
||||
error_msg = "The API key provided is invalid."
|
||||
elif "PERMISSION_DENIED" in error_msg:
|
||||
error_msg = "Permission denied. Check if the key has correct permissions."
|
||||
|
||||
raise HTTPException(status_code=400, detail=f"{provider.capitalize()} Test Failed: {error_msg}")
|
||||
|
||||
@router.post("/settings/ai")
|
||||
def update_ai_provider(
|
||||
payload: dict,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: auth.TokenData = Depends(auth.get_current_admin)
|
||||
):
|
||||
"""Update the active AI provider."""
|
||||
provider = payload.get("provider")
|
||||
if provider not in ["gemini", "claude"]:
|
||||
raise HTTPException(status_code=400, detail="Invalid provider. Must be 'gemini' or 'claude'.")
|
||||
|
||||
existing = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_provider").first()
|
||||
if existing:
|
||||
existing.value = provider
|
||||
else:
|
||||
db.add(models.SystemSetting(key="ai_provider", value=provider))
|
||||
|
||||
db.commit()
|
||||
return {"status": "success", "active_provider": provider}
|
||||
|
||||
Reference in New Issue
Block a user