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
@@ -5,6 +5,7 @@ import {
useUpdateTickerUniverseSetting,
} from '../../hooks/useAdmin';
import type { TickerUniverse } from '../../lib/types';
import { Dropdown } from '../ui/Dropdown';
const UNIVERSE_OPTIONS: Array<{ value: TickerUniverse; label: string }> = [
{ value: 'sp500', label: 'S&P 500' },
@@ -50,18 +51,11 @@ export function TickerUniverseBootstrap() {
<div className="grid gap-4 md:grid-cols-3">
<label className="block space-y-1 md:col-span-2">
<span className="text-xs text-gray-400">Default Universe</span>
<select
<Dropdown
value={universe}
onChange={(e) => setUniverse(e.target.value as TickerUniverse)}
className="w-full input-glass px-3 py-2 text-sm"
disabled={isLoading || updateDefault.isPending || bootstrap.isPending}
>
{UNIVERSE_OPTIONS.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
onChange={(v) => setUniverse(v as TickerUniverse)}
options={UNIVERSE_OPTIONS.map((o) => ({ value: o.value, label: o.label }))}
/>
</label>
<label className="flex items-end gap-2 pb-2">