KOL count: 29 → 25 across marketing/SEO copy

Backend KOL_FEEDS trimmed from 29 to 25 (dead feeds removed).
Sync all hardcoded count mentions:
- layout.tsx JSON-LD, page.tsx (metric + comparison + copy)
- kol/page.tsx, KolPageClient.tsx ("and 26 more" → "and 22 more")
- glossary/page.tsx, opengraph-image.tsx
- public/llms.txt, llms-full.txt
- drop removed KOLs (Dragonfly Capital, Nic Carter) from named lists

Bundles other in-flight frontend work already in the working tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
k
2026-06-09 22:55:27 +08:00
parent 9e0f6554cb
commit 4c3c8c6f87
57 changed files with 3464 additions and 1855 deletions
+73 -5
View File
@@ -5,11 +5,18 @@ import type {
} from '@/types'
import type { SignedEnvelope } from './signedRequest'
const BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'
function getServerApiBase() {
return (
process.env.API_BASE_URL ||
process.env.INTERNAL_API_URL ||
process.env.NEXT_PUBLIC_API_URL ||
'http://localhost:8000'
)
}
function getApiOrigin() {
return typeof window === 'undefined'
? `${BASE_URL}/api`
? `${getServerApiBase()}/api`
: '/api/proxy/api'
}
@@ -29,6 +36,53 @@ async function fetchJson<T>(path: string, init?: RequestInit): Promise<T> {
return res.json() as Promise<T>
}
export interface PostListResponse {
items: TrumpPost[]
total: number
page: number
limit: number
counts: {
all: number
actionable: number
buy: number
short: number
off_topic: number
}
source_counts: {
source: string
count: number
latest: string | null
}[]
}
export async function getPostsPage(
limit = 20,
page = 1,
source?: string,
filters?: {
sourceIn?: string[]
sourceNotIn?: string[]
archiveOnly?: boolean
sentiment?: 'bullish' | 'bearish' | 'neutral'
signal?: 'buy' | 'short' | 'actionable'
aiScoredOnly?: boolean
},
): Promise<PostListResponse> {
const params = new URLSearchParams({
limit: String(limit),
page: String(page),
})
if (source) params.set('source', source)
if (filters?.sourceIn?.length) params.set('source_in', filters.sourceIn.join(','))
if (filters?.sourceNotIn?.length) params.set('source_not_in', filters.sourceNotIn.join(','))
if (filters?.archiveOnly) params.set('archive_only', 'true')
if (filters?.sentiment) params.set('sentiment', filters.sentiment)
if (filters?.signal) params.set('signal', filters.signal)
if (filters?.aiScoredOnly) params.set('ai_scored_only', 'true')
const q = `/posts-paged?${params.toString()}`
return fetchJson<PostListResponse>(q)
}
export async function getPosts(limit = 20, page = 1, source?: string): Promise<TrumpPost[]> {
const q = `/posts?limit=${limit}&page=${page}` + (source ? `&source=${encodeURIComponent(source)}` : '')
return fetchJson<TrumpPost[]>(q)
@@ -139,6 +193,11 @@ export interface UserPublic {
circuit_breaker_reason?: string | null
/** Master Auto-Trade gate. false (default) = signals shown, not traded. */
auto_trade?: boolean
/** Per-system enable flags. bot_engine gates System-1 (Trump) on
* trump_enabled and System-2 (Macro) on macro_enabled. Auto-Trade ON
* with trump_enabled=false still does NOT open on a Trump signal. */
trump_enabled?: boolean
macro_enabled?: boolean
}
export interface SignalSource {
@@ -403,11 +462,14 @@ export async function setManualWindow(
// ── KOL module ────────────────────────────────────────────────────
export async function getKolPosts(opts: {
handle?: string; source?: string; limit?: number; page?: number
} = {}): Promise<{ items: KolPostSummary[]; page: number; limit: number }> {
handle?: string; source?: string; signalsOnly?: boolean; ticker?: string; days?: number; limit?: number; page?: number
} = {}): Promise<{ items: KolPostSummary[]; page: number; limit: number; total: number }> {
const p = new URLSearchParams()
if (opts.handle) p.set('handle', opts.handle)
if (opts.source) p.set('source', opts.source)
if (opts.signalsOnly) p.set('signals_only', 'true')
if (opts.ticker) p.set('ticker', opts.ticker)
if (opts.days) p.set('days', String(opts.days))
p.set('limit', String(opts.limit ?? 50))
p.set('page', String(opts.page ?? 1))
return fetchJson(`/kol/posts?${p.toString()}`)
@@ -520,7 +582,13 @@ export interface TelegramStatus {
export interface TelegramInitResp {
code: string; deep_link: string; expires_in_seconds: number
}
export async function getTelegramStatus(wallet: string): Promise<TelegramStatus> {
export async function getTelegramStatus(wallet: string, env?: SignedEnvelope): Promise<TelegramStatus> {
// Pass signature when available so the backend returns full binding details.
// Without it the endpoint returns only `bound: boolean` to prevent third-party de-anonymisation.
if (env) {
const qs = new URLSearchParams({ timestamp: String(env.timestamp), signature: env.signature })
return fetchJson<TelegramStatus>(`/telegram/${wallet}/status?${qs}`)
}
return fetchJson<TelegramStatus>(`/telegram/${wallet}/status`)
}
// SignedEnvelope is already imported at the top of this file. Below we