Add DeepSeek/xAI/OpenAI-compatible sentiment providers; custom dark dropdown
Deploy / lint (push) Successful in 5s
Deploy / test (push) Successful in 32s
Deploy / deploy (push) Successful in 22s

Providers (admin-switchable, no redeploy):
- DeepSeek and any OpenAI-compatible endpoint (OpenRouter, Together,
  Groq, local Ollama) via a generic Chat Completions adapter + base_url
- xAI Grok with Live Search (search_parameters web+X, citations) —
  grounded tier alongside OpenAI and Gemini
- DeepSeek / generic compatible endpoints are ungrounded (no web
  search); UI shows an amber warning and labels each provider's grounding
- Optional env fallbacks DEEPSEEK_API_KEY / XAI_API_KEY

UI: replace native <select> (unstyleable white popup on Windows) with a
custom dark Dropdown component everywhere — sentiment provider, scanner
filters, market sort, indicators, admin universe, user role.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 12:42:04 +02:00
parent d53ed972d1
commit 126c3b3c17
16 changed files with 521 additions and 98 deletions
+37 -1
View File
@@ -33,7 +33,8 @@ class TestGetConfig:
assert config["model"] == "gpt-4o-mini"
assert config["api_key_configured"] is False
assert config["api_key_source"] == "none"
assert set(config["valid_providers"]) == {"openai", "gemini"}
assert config["web_search"] is True
assert set(config["valid_providers"]) == {"openai", "gemini", "deepseek", "xai", "openai_compatible"}
async def test_never_returns_raw_key(self, session: AsyncSession):
await sps.update_sentiment_config(session, api_key="sk-secret-123")
@@ -92,3 +93,38 @@ class TestBuildProvider:
await sps.update_sentiment_config(session, provider="openai")
provider = await sps.build_sentiment_provider(session)
assert type(provider).__name__ == "OpenAISentimentProvider"
async def test_builds_deepseek_with_fixed_base_url(self, session: AsyncSession):
await sps.update_sentiment_config(session, provider="deepseek", api_key="ds-x")
provider = await sps.build_sentiment_provider(session)
assert type(provider).__name__ == "OpenAICompatibleSentimentProvider"
config = await sps.get_sentiment_config(session)
assert config["base_url"] == "https://api.deepseek.com"
assert config["web_search"] is False
async def test_builds_xai_with_live_search(self, session: AsyncSession):
await sps.update_sentiment_config(session, provider="xai", api_key="xai-x")
provider = await sps.build_sentiment_provider(session)
assert type(provider).__name__ == "OpenAICompatibleSentimentProvider"
# xAI is wired with Live Search enabled
assert provider._live_search is True
assert provider._extra_body == {"search_parameters": {"mode": "auto", "return_citations": True}}
config = await sps.get_sentiment_config(session)
assert config["base_url"] == "https://api.x.ai/v1"
assert config["web_search"] is True
async def test_openai_compatible_requires_base_url(self, session: AsyncSession):
await sps.update_sentiment_config(session, provider="openai_compatible", api_key="x")
with pytest.raises(ProviderError):
await sps.build_sentiment_provider(session)
async def test_openai_compatible_with_base_url(self, session: AsyncSession):
await sps.update_sentiment_config(
session,
provider="openai_compatible",
api_key="x",
model="llama-3.1-70b",
base_url="https://openrouter.ai/api/v1",
)
provider = await sps.build_sentiment_provider(session)
assert type(provider).__name__ == "OpenAICompatibleSentimentProvider"