126c3b3c17
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>
92 lines
3.1 KiB
TypeScript
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>
|
|
);
|
|
}
|