Files
signal-platform/frontend/src/components/admin/TickerUniverseBootstrap.tsx
T
dennisthiessen 126c3b3c17
Deploy / lint (push) Successful in 5s
Deploy / test (push) Successful in 32s
Deploy / deploy (push) Successful in 22s
Add DeepSeek/xAI/OpenAI-compatible sentiment providers; custom dark dropdown
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>
2026-06-13 12:42:04 +02:00

92 lines
3.1 KiB
TypeScript

import { useEffect, useState } from 'react';
import {
useBootstrapTickers,
useTickerUniverseSetting,
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' },
{ value: 'nasdaq100', label: 'NASDAQ 100' },
{ value: 'nasdaq_all', label: 'NASDAQ All' },
];
export function TickerUniverseBootstrap() {
const { data, isLoading, isError, error } = useTickerUniverseSetting();
const updateDefault = useUpdateTickerUniverseSetting();
const bootstrap = useBootstrapTickers();
const [universe, setUniverse] = useState<TickerUniverse>('sp500');
const [pruneMissing, setPruneMissing] = useState(false);
useEffect(() => {
if (data?.universe) {
setUniverse(data.universe);
}
}, [data]);
const onSaveDefault = () => {
updateDefault.mutate(universe);
};
const onBootstrap = () => {
bootstrap.mutate({ universe, pruneMissing });
};
return (
<div className="glass p-5 space-y-4">
<h3 className="text-sm font-semibold text-gray-200">Ticker Universe Discovery</h3>
<p className="text-xs text-gray-500">
Auto-discover tickers from a predefined universe and keep your registry updated.
</p>
{isError && (
<p className="text-sm text-red-400">
{(error as Error)?.message || 'Failed to load ticker universe setting'}
</p>
)}
<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>
<Dropdown
value={universe}
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">
<input
type="checkbox"
checked={pruneMissing}
onChange={(e) => setPruneMissing(e.target.checked)}
disabled={bootstrap.isPending}
className="h-4 w-4 rounded border-white/20 bg-transparent"
/>
<span className="text-xs text-gray-400">Prune removed symbols</span>
</label>
</div>
<div className="flex flex-wrap gap-2">
<button
className="btn-primary px-4 py-2 text-sm disabled:opacity-50"
onClick={onSaveDefault}
disabled={isLoading || updateDefault.isPending || bootstrap.isPending}
>
{updateDefault.isPending ? 'Saving…' : 'Save Default Universe'}
</button>
<button
className="px-4 py-2 text-sm rounded border border-white/[0.1] text-gray-300 hover:text-white disabled:opacity-50"
onClick={onBootstrap}
disabled={isLoading || updateDefault.isPending || bootstrap.isPending}
>
{bootstrap.isPending ? 'Bootstrapping…' : 'Bootstrap Now'}
</button>
</div>
</div>
);
}