mlstocks / frontend /src /App.vue
github-actions[bot]
Deploy to Hugging Face Space
abf702c
<script setup>
import Navbar from './components/Navbar.vue'
import TickerCard from './components/TickerCard.vue'
import Watchlist from './components/Watchlist.vue'
import SidebarAction from './components/SidebarAction.vue'
import SearchModal from './components/SearchModal.vue'
import DataHubModal from './components/DataHubModal.vue'
import AIBuilderClassic from './components/AIBuilderClassic.vue'
import AIBuilderNeural from './components/AIBuilderNeural.vue'
import OptionsAnalysisModal from './components/OptionsAnalysisModal.vue'
import Quickstart from './components/Quickstart.vue'
import {
Search, Brain, Activity, Sparkles, Zap, Palette,
Github, Twitter, MessageSquare, Shield, BookOpen, Terminal
} from 'lucide-vue-next'
import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
import { useAuth } from './services/auth'
import { api } from './services/api'
const { setToken, isAuthenticated, login, fetchProfile, user } = useAuth()
const isSearchOpen = ref(false)
const isDataHubOpen = ref(false)
const isAIBuilderOpen = ref(false)
const classicBuilderTab = ref('Data')
const isAIBuilderNeuralOpen = ref(false)
const isOptionsAnalysisOpen = ref(false)
const dataHubTab = ref('universe')
const selectedSymbolForAnalysis = ref('')
const watchlistRef = ref(null)
const currentTheme = ref('dark')
const switchTheme = (theme) => {
currentTheme.value = theme
document.documentElement.className = ''
if (theme !== 'dark') {
document.documentElement.classList.add(`theme-${theme}`)
}
}
const workflowStats = ref({
universeCount: 0,
readyFeatures: 0,
totalFeatures: 0,
modelCount: 0
})
const fetchWorkflowStats = async () => {
if (!isAuthenticated()) return
try {
const [univ, feat, mods] = await Promise.all([
api.get('/ai/universe'),
api.get('/ai/features-status'),
api.get('/ai/models')
])
workflowStats.value = {
universeCount: univ.length,
readyFeatures: feat.ready,
totalFeatures: feat.total,
modelCount: mods.length
}
} catch (err) {
console.error('Stats fetch failed:', err)
}
}
const marketData = ref([
{ symbol: 'S&P 500', loading: true },
{ symbol: 'NASDAQ', loading: true },
{ symbol: 'DOW', loading: true },
{ symbol: 'VIX', loading: true }
])
let marketInterval = null
const protectedAction = (callback) => {
return (...args) => {
if (!isAuthenticated()) {
login()
return
}
callback(...args)
}
}
const refreshWatchlist = () => {
watchlistRef.value?.fetchData(true)
fetchWorkflowStats()
}
const openOptionsAnalysis = (symbol) => {
selectedSymbolForAnalysis.value = symbol;
isOptionsAnalysisOpen.value = true;
}
const protectedOpenSearch = protectedAction(() => isSearchOpen.value = true);
const protectedOpenOptionsAnalysis = protectedAction(openOptionsAnalysis);
const protectedOpenDataHub = protectedAction((tab = 'universe') => {
dataHubTab.value = tab;
isDataHubOpen.value = true;
});
const protectedOpenAIBuilder = protectedAction((tab = 'Data') => {
classicBuilderTab.value = tab;
isAIBuilderOpen.value = true;
});
const protectedOpenAIBuilderNeural = protectedAction(() => isAIBuilderNeuralOpen.value = true);
const fetchMarketData = async () => {
try {
const data = await api.get('/market-indices')
if (data.results && data.results.length > 0) {
data.results.forEach(newItem => {
const index = marketData.value.findIndex(item => item.symbol === newItem.symbol)
if (index !== -1) {
marketData.value[index] = { ...newItem, loading: false }
}
})
}
} catch (error) {
console.error('Failed to fetch market data:', error)
}
}
const handleGlobalKeyDown = (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault()
protectedOpenSearch()
}
}
const globalEvents = ref([
{ t: 'Watchlist', m: 'Global market indices updated with 15ms latency.', c: 'text-blue-400' },
{ t: 'Security', m: 'Encrypted tunnel established with NEXUS nodes.', c: 'text-slate-400' }
])
const addGlobalEvent = (type, message, colorClass = 'text-slate-400') => {
globalEvents.value.unshift({ t: type, m: message, c: colorClass })
if (globalEvents.value.length > 3) globalEvents.value.pop()
}
onMounted(() => {
const params = new URLSearchParams(window.location.search)
const urlToken = params.get('token')
if (urlToken && window.location.pathname.includes('/auth/callback')) {
setToken(urlToken)
fetchProfile().then(() => {
fetchWorkflowStats()
addGlobalEvent('Auth', `Identity verified: ${user.value?.full_name?.split(' ')[0]}`, 'text-blue-400')
if (user.value?.theme) switchTheme(user.value.theme)
})
window.history.replaceState({}, document.title, "/")
} else {
if (isAuthenticated()) {
fetchProfile().then(() => {
if (user.value?.theme) switchTheme(user.value.theme)
});
}
}
window.addEventListener('keydown', handleGlobalKeyDown)
fetchMarketData()
if (isAuthenticated()) fetchWorkflowStats()
marketInterval = setInterval(() => {
fetchMarketData()
refreshWatchlist()
addGlobalEvent('Sync', 'Nexus heartbeats received.', 'text-emerald-500/80')
}, 60000)
})
onUnmounted(() => {
window.removeEventListener('keydown', handleGlobalKeyDown)
if (marketInterval) clearInterval(marketInterval)
})
</script>
<template>
<div class="min-h-screen font-sans bg-[#0b1120] text-slate-200 transition-colors duration-300 overflow-x-hidden">
<Navbar />
<main class="max-w-[1400px] mx-auto px-4 md:px-8 py-6 md:py-16">
<!-- HERO / INTRO -->
<section class="text-center mb-10 md:mb-20">
<template v-if="!isAuthenticated()">
<h1 class="text-3xl sm:text-4xl md:text-7xl font-black text-white mb-4 md:mb-8 tracking-tighter leading-tight">
Next-Gen Market <br/><span class="bg-gradient-to-r from-blue-400 to-indigo-600 bg-clip-text text-transparent">Intelligence.</span>
</h1>
<p class="text-slate-400 text-xs md:text-xl max-w-2xl mx-auto mb-8 md:mb-12 px-4 leading-relaxed">
Harness institutional-grade AI and multi-agent systems to master the market with quantitative precision.
</p>
</template>
<template v-else>
<div class="flex flex-col sm:flex-row sm:items-center justify-between mb-8 md:mb-12 gap-6 text-left">
<div>
<h2 class="text-2xl md:text-4xl font-bold text-white mb-2 tracking-tight">Intelligent Trade Model Builder</h2>
<p class="text-[10px] md:text-sm text-slate-500 uppercase font-black tracking-widest">
Operator: <span class="text-blue-400">{{ user?.full_name }}</span> | System: <span class="text-emerald-500">NEXUS Core Sync Active</span>
</p>
</div>
<div class="flex items-center gap-3">
<div class="bg-slate-900 border border-slate-800 px-4 py-2 rounded-2xl flex items-center gap-3 shadow-xl">
<div class="w-2 h-2 bg-emerald-500 rounded-full animate-pulse"></div>
<span class="text-[10px] font-black uppercase tracking-widest text-slate-400">Node Connected</span>
</div>
</div>
</div>
<Quickstart
v-bind="workflowStats"
@openUniverse="protectedOpenDataHub"
@openTraining="protectedOpenAIBuilder"
@openAgentic="protectedOpenSearch"
/>
</template>
<!-- Market Tickers -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-6 mt-10 md:mt-16">
<TickerCard
v-for="item in marketData"
:key="item.symbol"
v-bind="item"
/>
</div>
<!-- SEARCH BAR -->
<div class="max-w-3xl mx-auto mt-10 md:mt-20 px-2">
<div
@click="protectedOpenSearch"
class="bg-slate-900/40 border border-slate-800 p-1 rounded-2xl md:rounded-3xl group hover:border-blue-500/50 cursor-pointer transition-all active:scale-[0.98] shadow-2xl"
>
<div class="flex items-center gap-3 md:gap-5 p-3 md:p-5 text-left">
<div class="bg-blue-600/20 p-2 md:p-4 rounded-xl md:rounded-2xl">
<Search class="w-5 h-5 md:w-6 md:h-6 text-blue-500" />
</div>
<div class="flex flex-col flex-1 truncate">
<span class="text-sm md:text-xl font-black text-white tracking-tight">Search Assets & Strategies</span>
<span class="text-[10px] md:text-xs text-slate-500 uppercase tracking-widest font-bold">Options Architect Engine v3.2</span>
</div>
<div class="hidden md:flex items-center gap-2">
<kbd class="px-2 py-1 text-[10px] font-black text-slate-600 bg-slate-800 border border-slate-700 rounded-lg">⌘K</kbd>
</div>
</div>
</div>
</div>
</section>
<!-- DASHBOARD GRID -->
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6 md:gap-8">
<!-- Main Area -->
<div class="lg:col-span-8 xl:col-span-9 order-2 lg:order-1">
<Watchlist
ref="watchlistRef"
@openSearch="protectedOpenSearch"
@openOptionsAnalysis="protectedOpenOptionsAnalysis"
/>
</div>
<!-- Sidebar Area -->
<div class="lg:col-span-4 xl:col-span-3 order-1 lg:order-2 space-y-4 md:space-y-6">
<div class="flex items-center justify-between px-2">
<h3 class="text-[10px] font-black text-slate-500 uppercase tracking-widest italic">Intelligence Operations</h3>
<Activity class="w-3 h-3 text-slate-700" />
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-1 gap-3 md:gap-4">
<SidebarAction @click="protectedOpenAIBuilder" label="NEXUS Quadrant" color="bg-blue-600" iconColor="text-blue-500"><template #icon><Brain class="w-5 h-5" /></template></SidebarAction>
<SidebarAction @click="protectedOpenAIBuilderNeural" label="NEXUS Neural" color="bg-rose-600" iconColor="text-rose-500"><template #icon><Sparkles class="w-5 h-5" /></template></SidebarAction>
<SidebarAction @click="protectedOpenDataHub" label="Data Hub" color="bg-cyan-600" iconColor="text-cyan-500"><template #icon><Activity class="w-5 h-5" /></template></SidebarAction>
<div class="p-6 bg-slate-900 border border-slate-800 rounded-3xl relative overflow-hidden flex flex-col items-start gap-4 shadow-xl">
<Zap class="w-10 h-10 text-amber-500/20 absolute -right-2 -bottom-2 rotate-12" />
<h4 class="text-xs font-black text-white uppercase tracking-widest">Agentic Option Strategist</h4>
<p class="text-[10px] text-slate-500 leading-relaxed font-medium">Deploy multi-source web scrapers to build sentiment-aware strategies.</p>
<button @click="protectedOpenSearch" class="w-full py-2 bg-blue-600 text-[10px] font-black uppercase tracking-widest text-white rounded-xl shadow-lg shadow-blue-900/40">New Op</button>
</div>
</div>
</div>
</div>
</main>
<!-- FOOTER -->
<footer class="py-12 md:py-24 px-4 md:px-8 border-t border-slate-800/50 mt-20 md:mt-40 bg-slate-950/20">
<div class="max-w-[1400px] mx-auto grid grid-cols-1 md:grid-cols-12 gap-12">
<div class="md:col-span-5 lg:col-span-4">
<div class="flex items-center gap-2 mb-6">
<Activity class="text-blue-500 w-6 h-6" />
<span class="text-2xl font-black tracking-tighter text-white">k9t<span class="text-blue-500 italic">Trader</span></span>
</div>
<p class="text-sm text-slate-500 leading-relaxed mb-8 max-w-sm">
Institutional-grade market intelligence powered by multi-agent AI ecosystems. Build, verify, and master the quantitative strategy lifecycle.
</p>
<div class="flex gap-4">
<button class="w-10 h-10 rounded-xl bg-slate-900 border border-slate-800 flex items-center justify-center hover:border-blue-500 hover:text-blue-500 transition-all shadow-lg text-slate-500">
<Github class="w-5 h-5" />
</button>
<button class="w-10 h-10 rounded-xl bg-slate-900 border border-slate-800 flex items-center justify-center hover:border-sky-500 hover:text-sky-500 transition-all shadow-lg text-slate-500">
<Twitter class="w-5 h-5" />
</button>
<button class="w-10 h-10 rounded-xl bg-slate-900 border border-slate-800 flex items-center justify-center hover:border-indigo-500 hover:text-indigo-500 transition-all shadow-lg text-slate-500">
<MessageSquare class="w-5 h-5" />
</button>
</div>
</div>
<div class="md:col-span-7 lg:col-span-8 grid grid-cols-2 sm:grid-cols-3 gap-8">
<div>
<h4 class="text-[10px] font-black text-slate-600 uppercase tracking-widest mb-6">Nexus Pipeline</h4>
<ul class="space-y-4">
<li v-for="l in ['Trade Model Builder', 'Neural Quadrant', 'Data Hub Provisioning', 'Agentic Strategist']" :key="l" class="text-xs font-bold text-slate-500 hover:text-white transition-colors cursor-pointer flex items-center gap-2">
<div class="w-1 h-1 bg-blue-500 rounded-full"></div> {{ l }}
</li>
</ul>
</div>
<div>
<h4 class="text-[10px] font-black text-slate-600 uppercase tracking-widest mb-6">Terminal Resources</h4>
<ul class="space-y-4">
<li class="group flex items-center gap-3 cursor-pointer">
<div class="w-8 h-8 rounded-lg bg-slate-900 border border-slate-800 flex items-center justify-center text-slate-600 group-hover:border-emerald-500 group-hover:text-emerald-500 transition-all"><BookOpen class="w-4 h-4" /></div>
<span class="text-xs font-bold text-slate-500 group-hover:text-white transition-colors">Documentation</span>
</li>
<li class="group flex items-center gap-3 cursor-pointer">
<div class="w-8 h-8 rounded-lg bg-slate-900 border border-slate-800 flex items-center justify-center text-slate-600 group-hover:border-blue-500 group-hover:text-blue-500 transition-all"><Terminal class="w-4 h-4" /></div>
<span class="text-xs font-bold text-slate-500 group-hover:text-white transition-colors">API Console</span>
</li>
<li class="group flex items-center gap-3 cursor-pointer">
<div class="w-8 h-8 rounded-lg bg-slate-900 border border-slate-800 flex items-center justify-center text-slate-600 group-hover:border-rose-500 group-hover:text-rose-500 transition-all"><Shield class="w-4 h-4" /></div>
<span class="text-xs font-bold text-slate-500 group-hover:text-white transition-colors">Security Node</span>
</li>
</ul>
</div>
<div class="col-span-2 sm:col-span-1">
<div class="bg-indigo-600/5 border border-indigo-500/20 rounded-2xl p-6 relative overflow-hidden group">
<Sparkles class="w-16 h-16 text-indigo-500/5 absolute -right-2 -top-2" />
<h4 class="text-[9px] font-black text-indigo-400 uppercase tracking-widest mb-4 flex items-center gap-2">Live Node Feed</h4>
<div class="space-y-3">
<div v-for="(event, i) in globalEvents" :key="i" class="flex flex-col gap-1">
<span class="text-[8px] font-black text-slate-700 uppercase tracking-widest">{{ event.t }}</span>
<p class="text-[10px] leading-tight font-bold" :class="event.c">{{ event.m }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-20 pt-10 border-t border-slate-800/30 text-center">
<p class="text-[9px] md:text-[10px] text-slate-600 mb-2 font-bold italic uppercase tracking-widest">Quantitative Risk Disclosure: For education only.</p>
<p class="text-[10px] text-slate-700 font-bold">© {{ new Date().getFullYear() }} k9t Intelligence Systems | Refactor Complete</p>
</div>
</footer>
<!-- MODALS -->
<SearchModal :is-open="isSearchOpen" @close="isSearchOpen = false" @added="refreshWatchlist" @analyze="protectedOpenOptionsAnalysis" />
<DataHubModal :is-open="isDataHubOpen" :initial-tab="dataHubTab" @close="isDataHubOpen = false" @universeUpdated="fetchWorkflowStats" />
<AIBuilderClassic :is-open="isAIBuilderOpen" :initial-tab="classicBuilderTab" @close="isAIBuilderOpen = false" />
<AIBuilderNeural :is-open="isAIBuilderNeuralOpen" @close="isAIBuilderNeuralOpen = false" />
<OptionsAnalysisModal :is-open="isOptionsAnalysisOpen" :symbol="selectedSymbolForAnalysis" @close="isOptionsAnalysisOpen = false" />
</div>
</template>
<style>
body { background-color: #0b1120; margin: 0; padding: 0; }
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #1e293b; border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: #334155; }
</style>