Robinhood-Web / script.js
pumpfun's picture
Connect live SEC research service
f97e0c5 verified
Raw History Blame Contribute Delete
7.89 kB
const languageButtons = document.querySelectorAll('[data-lang]');
const translatedNodes = document.querySelectorAll('[data-en][data-zh]');
const translatedInputs = document.querySelectorAll('[data-en-placeholder][data-zh-placeholder]');
const form = document.querySelector('#research-form');
const queryInput = document.querySelector('#company-query');
const research = document.querySelector('#research');
const emptyState = document.querySelector('#research-empty');
const loadingState = document.querySelector('#research-loading');
const errorState = document.querySelector('#research-error');
const resultState = document.querySelector('#research-result');
let currentLanguage = 'en';
let currentResult = null;
const apiOrigin = window.location.hostname.endsWith('.hf.space') ? 'https://robinhood-web-pi.vercel.app' : '';
const copy = {
en: {
sector: 'Classification', fiscal: 'Fiscal year end', state: 'Incorporated',
facts: 'Latest reported facts', filings: 'Recent material filings', period: 'Period', filed: 'Filed',
source: 'Evidence source', limitations: 'Read before using', live: 'LIVE SEC RECORD',
noFacts: 'No matching standardized XBRL facts were found for this issuer.',
noFilings: 'No recent supported filing was found.',
error: 'The public record could not be opened. Try another U.S. ticker or try again shortly.',
},
zh: {
sector: '行业分类', fiscal: '财年截止', state: '注册地',
facts: '最近披露指标', filings: '近期重要申报', period: '数据期', filed: '提交日',
source: '证据来源', limitations: '使用前请阅读', live: 'SEC 实时公开记录',
noFacts: '未找到该公司的匹配标准化 XBRL 指标。',
noFilings: '未找到近期受支持的申报。',
error: '暂时无法打开公开记录。请尝试其他美股代码,或稍后重试。',
},
};
const element = (tag, className, text) => {
const node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = text;
return node;
};
const formatMoney = (value) => new Intl.NumberFormat(currentLanguage === 'zh' ? 'zh-CN' : 'en-US', {
style: 'currency', currency: 'USD', notation: 'compact', maximumFractionDigits: 2,
}).format(value);
const renderResult = (data) => {
const t = copy[currentLanguage];
const company = data.company;
resultState.replaceChildren();
const header = element('header', 'result-header');
const headingGroup = element('div');
headingGroup.append(element('p', 'section-label', t.live), element('h2', '', company.name));
const identity = element('div', 'company-identity');
identity.append(element('strong', '', company.ticker), element('span', '', company.exchange || '—'), element('span', '', `CIK ${company.cik}`));
header.append(headingGroup, identity);
const profile = element('div', 'company-profile');
[
[t.sector, company.sic_description || '—'],
[t.fiscal, company.fiscal_year_end || '—'],
[t.state, company.state || '—'],
].forEach(([label, value]) => {
const item = element('div');
item.append(element('span', '', label), element('strong', '', value));
profile.append(item);
});
const factsSection = element('section', 'result-block');
factsSection.append(element('h3', '', t.facts));
if (data.metrics.length) {
const grid = element('div', 'metric-grid');
data.metrics.forEach((metric) => {
const card = element('article', 'metric-card');
const period = metric.period_start ? `${metric.period_start}—${metric.period}` : (metric.period || '—');
card.append(
element('span', '', currentLanguage === 'zh' ? metric.label_zh : metric.label_en),
element('strong', '', formatMoney(metric.value)),
element('small', '', `${metric.form} · ${t.period} ${period} · ${t.filed} ${metric.filed}`),
);
grid.append(card);
});
factsSection.append(grid);
} else {
factsSection.append(element('p', 'empty-copy', t.noFacts));
}
const filingsSection = element('section', 'result-block filings-block');
filingsSection.append(element('h3', '', t.filings));
if (data.filings.length) {
const list = element('div', 'filing-list');
data.filings.forEach((filing) => {
const link = element('a', 'filing-row');
link.href = filing.url;
link.target = '_blank';
link.rel = 'noopener';
link.append(
element('strong', '', filing.form),
element('span', '', filing.description),
element('time', '', filing.filed),
element('b', '', '↗'),
);
list.append(link);
});
filingsSection.append(list);
} else {
filingsSection.append(element('p', 'empty-copy', t.noFilings));
}
const evidence = element('aside', 'evidence-note');
const evidenceLink = element('a', '', data.source);
evidenceLink.href = 'https://www.sec.gov/search-filings/edgar-application-programming-interfaces';
evidenceLink.target = '_blank';
evidenceLink.rel = 'noopener';
const retrieved = new Date(data.retrieved_at).toLocaleString(currentLanguage === 'zh' ? 'zh-CN' : 'en-US');
const sourceLine = element('p');
sourceLine.append(element('span', '', `${t.source}: `), evidenceLink, document.createTextNode(` · ${retrieved}`));
evidence.append(sourceLine, element('h3', '', t.limitations), element('p', '', data.limitations[currentLanguage]));
resultState.append(header, profile, factsSection, filingsSection, evidence);
};
const setLanguage = (language) => {
currentLanguage = language;
document.documentElement.lang = language === 'zh' ? 'zh-CN' : 'en';
translatedNodes.forEach((node) => { node.textContent = node.dataset[language]; });
translatedInputs.forEach((node) => { node.placeholder = node.dataset[`${language}Placeholder`]; });
languageButtons.forEach((button) => {
const active = button.dataset.lang === language;
button.classList.toggle('active', active);
button.setAttribute('aria-pressed', String(active));
});
if (currentResult) renderResult(currentResult);
};
const runResearch = async (query) => {
research.setAttribute('aria-busy', 'true');
emptyState.hidden = true;
errorState.hidden = true;
resultState.hidden = true;
loadingState.hidden = false;
try {
const response = await fetch(`${apiOrigin}/api/research?q=${encodeURIComponent(query)}`);
if (!response.ok) throw new Error(String(response.status));
currentResult = await response.json();
renderResult(currentResult);
resultState.hidden = false;
} catch (error) {
currentResult = null;
errorState.textContent = copy[currentLanguage].error;
errorState.hidden = false;
} finally {
loadingState.hidden = true;
research.setAttribute('aria-busy', 'false');
research.scrollIntoView({ behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth', block: 'start' });
}
};
languageButtons.forEach((button) => button.addEventListener('click', () => setLanguage(button.dataset.lang)));
document.querySelectorAll('[data-query]').forEach((button) => button.addEventListener('click', () => {
queryInput.value = button.dataset.query;
runResearch(button.dataset.query);
}));
form.addEventListener('submit', (event) => {
event.preventDefault();
const query = queryInput.value.trim();
if (query) runResearch(query);
});
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
document.documentElement.classList.add('motion-ready');
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
entry.target.classList.add('visible');
observer.unobserve(entry.target);
});
}, { rootMargin: '0px 0px -10% 0px', threshold: 0.12 });
document.querySelectorAll('.reveal').forEach((node) => observer.observe(node));
}