feat: add Chinese UI, Responses models, and mirrored Windows setup
Localize the web console and persist custom model and capability settings. Use OpenAI Responses exclusively and move runtime limits into environment configuration. Bootstrap uv and the virtual environment on Windows, support optional China mirrors, and package source distributions without environments, build output, or credentials. Update regression tests and repository quality checks for the new behavior.
This commit is contained in:
1 parent
371aa6df56
commit
2ddec53954
183 files changed
+3427
-11857
No files matched your search
@@ -16,7 +16,7 @@
|
||||
|
||||
import httpx
|
||||
|
||||
from artemis.llm.google.provider import is_google_family_provider
|
||||
from artemis.config.settings import settings
|
||||
|
||||
from artemis.utils.logger import get_logger
|
||||
|
||||
@@ -60,7 +60,7 @@ async def validate_api_key(
|
||||
provider: str,
|
||||
api_key: str,
|
||||
base_url: str | None = None,
|
||||
timeout: float = 12.0,
|
||||
timeout: float | None = None,
|
||||
) -> tuple[bool, str]:
|
||||
"""Tests if the provided API key is valid and usable with the corresponding provider.
|
||||
|
||||
@@ -90,19 +90,10 @@ async def validate_api_key(
|
||||
return False, "Please enter a real API key instead of the placeholder."
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=timeout) as client:
|
||||
if is_google_family_provider(clean_provider):
|
||||
# Test Google Gemini via generativelanguage API
|
||||
# The key travels in a header, never in the URL: httpx logs request
|
||||
# URLs at INFO level and those logs end up in files.
|
||||
url = "https://generativelanguage.googleapis.com/v1beta/models?pageSize=1"
|
||||
resp = await client.get(url, headers={"x-goog-api-key": clean_key})
|
||||
if resp.status_code == 200:
|
||||
return True, "Google Gemini API key verified successfully."
|
||||
err_msg = _extract_error_message(resp)
|
||||
return False, f"Gemini API verification failed ({resp.status_code}): {err_msg}"
|
||||
|
||||
elif clean_provider in ("ocr", "vision", "google_vision"):
|
||||
async with httpx.AsyncClient(
|
||||
timeout=timeout if timeout is not None else settings.ARTEMIS_CREDENTIAL_TIMEOUT_SECONDS
|
||||
) as client:
|
||||
if clean_provider in ("ocr", "vision", "google_vision"):
|
||||
# Test Google Cloud Vision OCR API
|
||||
url = "https://vision.googleapis.com/v1/images:annotate"
|
||||
payload = {
|
||||
@@ -124,68 +115,25 @@ async def validate_api_key(
|
||||
return False, f"Vision OCR API verification failed ({resp.status_code}): {err_msg}"
|
||||
|
||||
elif clean_provider == "openai":
|
||||
endpoint = (
|
||||
f"{base_url.rstrip('/')}/models"
|
||||
if base_url
|
||||
else "https://api.openai.com/v1/models"
|
||||
base = (base_url or settings.OPENAI_BASE_URL or "https://api.openai.com/v1").rstrip(
|
||||
"/"
|
||||
)
|
||||
resp = await client.get(endpoint, headers={"Authorization": f"Bearer {clean_key}"})
|
||||
if resp.status_code == 200:
|
||||
return True, "OpenAI API key verified successfully."
|
||||
err_msg = _extract_error_message(resp)
|
||||
return False, f"OpenAI API verification failed ({resp.status_code}): {err_msg}"
|
||||
|
||||
elif clean_provider in ("anthropic", "claude"):
|
||||
url = "https://api.anthropic.com/v1/models"
|
||||
resp = await client.get(
|
||||
url,
|
||||
headers={
|
||||
"x-api-key": clean_key,
|
||||
"anthropic-version": "2023-06-01",
|
||||
resp = await client.post(
|
||||
f"{base}/responses",
|
||||
headers={"Authorization": f"Bearer {clean_key}"},
|
||||
json={
|
||||
"model": settings.OPENAI_MODEL,
|
||||
"input": "Reply with OK.",
|
||||
"max_output_tokens": 16,
|
||||
"store": False,
|
||||
},
|
||||
)
|
||||
if resp.status_code == 200:
|
||||
return True, "Anthropic Claude API key verified successfully."
|
||||
err_msg = _extract_error_message(resp)
|
||||
return False, f"Anthropic API verification failed ({resp.status_code}): {err_msg}"
|
||||
|
||||
elif clean_provider == "openrouter":
|
||||
url = "https://openrouter.ai/api/v1/auth/key"
|
||||
resp = await client.get(url, headers={"Authorization": f"Bearer {clean_key}"})
|
||||
if resp.status_code == 200:
|
||||
return True, "OpenRouter API key verified successfully."
|
||||
err_msg = _extract_error_message(resp)
|
||||
return False, f"OpenRouter API verification failed ({resp.status_code}): {err_msg}"
|
||||
|
||||
elif clean_provider in ("xai", "grok"):
|
||||
url = "https://api.x.ai/v1/models"
|
||||
resp = await client.get(url, headers={"Authorization": f"Bearer {clean_key}"})
|
||||
if resp.status_code == 200:
|
||||
return True, "xAI API key verified successfully."
|
||||
err_msg = _extract_error_message(resp)
|
||||
return False, f"xAI API verification failed ({resp.status_code}): {err_msg}"
|
||||
|
||||
elif clean_provider in ("ollama", "vllm", "custom"):
|
||||
if base_url:
|
||||
endpoint = f"{base_url.rstrip('/')}/models"
|
||||
headers = (
|
||||
{"Authorization": f"Bearer {clean_key}"} if clean_key != "EMPTY" else {}
|
||||
)
|
||||
resp = await client.get(endpoint, headers=headers)
|
||||
if resp.status_code == 200:
|
||||
return True, f"Custom endpoint ({base_url}) verified successfully."
|
||||
err_msg = _extract_error_message(resp)
|
||||
return (
|
||||
False,
|
||||
f"Custom endpoint verification failed ({resp.status_code}): {err_msg}",
|
||||
)
|
||||
return True, "Custom endpoint configured."
|
||||
if resp.status_code == 200 and resp.json().get("output"):
|
||||
return True, "OpenAI Responses 连接验证成功。"
|
||||
return False, f"Responses 验证失败(HTTP {resp.status_code})"
|
||||
|
||||
else:
|
||||
# Unknown provider - fallback to basic length check
|
||||
if len(clean_key) >= 8:
|
||||
return True, f"Credentials for {provider} configured."
|
||||
return False, f"Invalid API key format for {provider}."
|
||||
return False, "仅支持 OpenAI Responses 和可选 OCR 凭据。"
|
||||
|
||||
except httpx.TimeoutException:
|
||||
logger.warning(f"Timeout verifying API key for {provider}")
|
||||
|
||||
Reference in new issue
Block a user