diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts
index 19ef713..16f09d4 100644
--- a/frontend/src/menuAside.ts
+++ b/frontend/src/menuAside.ts
@@ -7,6 +7,11 @@ const menuAside: MenuAsideItem[] = [
icon: icon.mdiViewDashboardOutline,
label: 'Dashboard',
},
+ {
+ href: '/insights',
+ icon: icon.mdiRobot,
+ label: 'AI Insights',
+ },
{
href: '/users/users-list',
@@ -136,4 +141,4 @@ const menuAside: MenuAsideItem[] = [
},
]
-export default menuAside
+export default menuAside
\ No newline at end of file
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index d27d82e..2b29ba2 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -1,166 +1,130 @@
-
import React, { useEffect, useState } from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import Link from 'next/link';
import BaseButton from '../components/BaseButton';
-import CardBox from '../components/CardBox';
import SectionFullScreen from '../components/SectionFullScreen';
import LayoutGuest from '../layouts/Guest';
-import BaseDivider from '../components/BaseDivider';
-import BaseButtons from '../components/BaseButtons';
import { getPageTitle } from '../config';
import { useAppSelector } from '../stores/hooks';
-import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
-import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
+import { getPexelsImage } from '../helpers/pexels';
+import * as icon from '@mdi/js';
+import BaseIcon from '../components/BaseIcon';
-
-export default function Starter() {
- const [illustrationImage, setIllustrationImage] = useState({
- src: undefined,
- photographer: undefined,
- photographer_url: undefined,
- })
- const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
- const [contentType, setContentType] = useState('video');
- const [contentPosition, setContentPosition] = useState('left');
+export default function LandingPage() {
+ const [bgImage, setBgImage] = useState('');
const textColor = useAppSelector((state) => state.style.linkColor);
- const title = 'App Draft'
-
- // Fetch Pexels image/video
useEffect(() => {
- async function fetchData() {
- const image = await getPexelsImage();
- const video = await getPexelsVideo();
- setIllustrationImage(image);
- setIllustrationVideo(video);
+ async function fetchBg() {
+ const image = await getPexelsImage('basketball court dark');
+ if (image?.src?.large2x) {
+ setBgImage(image.src.large2x);
+ }
}
- fetchData();
+ fetchBg();
}, []);
- const imageBlock = (image) => (
-
-
-
- Photo by {image?.photographer} on Pexels
-
+ return (
+
+
+
{getPageTitle('SportScalp AI - Professional Sports Analytics')}
+
+
+ {/* Hero Section */}
+
+ {/* Background Overlay */}
+
+
+
+
+
+
+ POWERED BY AI
+
+
+ UNCOVER THE WINNING EDGE
+
+
+ The world's most advanced sports data scalper. Ask questions, analyze betting lines, and get real-time insights for NBA, NFL, and NHL.
+
+
+
+
+
+
+
+ {/* Features Section */}
+
+
+
+
+
+
+
Natural Language Query
+
+ "Show me players with 70% 3PT accuracy in their last 15 games." Just ask, and our AI does the math.
+
+
+
+
+
+
+
+
Multi-Source Scalping
+
+ Real-time data from StatMuse, NBA, NFL, and NHL official sources, and major betting platforms.
+
+
+
+
+
+
+
+
Line Relevance Engine
+
+ Automatically cross-reference player performance with betting lines to find high-value opportunities.
+
+
+
+
+
+ {/* CTA Section */}
+
+
+
READY TO DOMINATE THE DATA?
+
+ GET STARTED NOW
+
+
+
+
+ {/* Footer */}
+
);
-
- const videoBlock = (video) => {
- if (video?.video_files?.length > 0) {
- return (
-
-
-
-
)
- }
- };
-
- return (
-
-
-
{getPageTitle('Starter Page')}
-
-
-
-
- {contentType === 'image' && contentPosition !== 'background'
- ? imageBlock(illustrationImage)
- : null}
- {contentType === 'video' && contentPosition !== 'background'
- ? videoBlock(illustrationVideo)
- : null}
-
-
-
-
-
© 2026 {title}. All rights reserved
-
- Privacy Policy
-
-
-
-
- );
}
-Starter.getLayout = function getLayout(page: ReactElement) {
- return
{page};
-};
-
+LandingPage.getLayout = function getLayout(page: ReactElement) {
+ return
{page};
+};
\ No newline at end of file
diff --git a/frontend/src/pages/insights.tsx b/frontend/src/pages/insights.tsx
new file mode 100644
index 0000000..55e3d74
--- /dev/null
+++ b/frontend/src/pages/insights.tsx
@@ -0,0 +1,228 @@
+import * as icon from '@mdi/js';
+import Head from 'next/head';
+import React, { useState } from 'react';
+import type { ReactElement } from 'react';
+import LayoutAuthenticated from '../layouts/Authenticated';
+import SectionMain from '../components/SectionMain';
+import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
+import CardBox from '../components/CardBox';
+import BaseButton from '../components/BaseButton';
+import FormField from '../components/FormField';
+import { getPageTitle } from '../config';
+import { useAppDispatch, useAppSelector } from '../stores/hooks';
+import { aiResponse } from '../stores/openAiSlice';
+import axios from 'axios';
+import BaseIcon from '../components/BaseIcon';
+
+const InsightsPage = () => {
+ const dispatch = useAppDispatch();
+ const { isAskingResponse } = useAppSelector((state) => state.openAi);
+ const [question, setQuestion] = useState('');
+ const [chatHistory, setChatHistory] = useState
>([]);
+ const [isExecutingSql, setIsExecutingSql] = useState(false);
+
+ const extractText = (payload: any) => {
+ if (!payload || typeof payload !== 'object') return '';
+ const data = payload.data || payload;
+
+ if (Array.isArray(data.output)) {
+ let combined = '';
+ for (const item of data.output) {
+ if (!item || !Array.isArray(item.content)) continue;
+ for (const block of item.content) {
+ if (block?.type === 'output_text' && typeof block.text === 'string') {
+ combined += block.text;
+ }
+ }
+ }
+ if (combined) return combined;
+ }
+
+ if (data.choices?.[0]?.message?.content) {
+ return data.choices[0].message.content;
+ }
+
+ return '';
+ };
+
+ const handleAsk = async (e?: React.FormEvent) => {
+ if (e) e.preventDefault();
+ if (!question.trim()) return;
+
+ const userQuestion = question;
+ setQuestion('');
+ setChatHistory(prev => [...prev, { role: 'user', content: userQuestion }]);
+
+ try {
+ // Step 1: Generate SQL
+ const systemPrompt = `You are a sports data assistant. You have access to a PostgreSQL database with:
+- players: id, name, position, teamId
+- player_stats: id, playerId, game_date, points, three_pointers_made, three_point_attempts, rebounds, assists, minutes
+- betting_lines: id, line_name, event_date, spread, moneyline_home, moneyline_away, total
+
+Translate the user question to a single SELECT SQL statement.
+Use window functions for 'last N games' (e.g. ROW_NUMBER() OVER (PARTITION BY ps.playerId ORDER BY ps.game_date DESC)).
+Return ONLY a JSON object: { "sql": "SELECT ..." }.`;
+
+ const aiGenSqlResp = await dispatch(aiResponse({
+ input: [
+ { role: 'system', content: systemPrompt },
+ { role: 'user', content: userQuestion }
+ ]
+ })).unwrap();
+
+ const aiText = extractText(aiGenSqlResp);
+ let sql = '';
+ try {
+ const cleanText = aiText.replace(/```json|```/g, '').trim();
+ const parsed = JSON.parse(cleanText);
+ sql = parsed.sql;
+ } catch (err) {
+ const match = aiText.match(/SELECT[\s\S]+/i);
+ if (match) sql = match[0];
+ }
+
+ if (!sql) {
+ setChatHistory(prev => [...prev, { role: 'assistant', content: "I couldn't generate a valid query for that question. Could you try rephrasing it?" }]);
+ return;
+ }
+
+ // Step 2: Execute SQL
+ setIsExecutingSql(true);
+ const sqlResult = await axios.post('/sql', { sql });
+ setIsExecutingSql(false);
+
+ const rows = sqlResult.data.rows;
+
+ if (!rows || rows.length === 0) {
+ setChatHistory(prev => [...prev, { role: 'assistant', content: "I found no data matching your query." }]);
+ return;
+ }
+
+ // Step 3: Summarize Results
+ const summaryPrompt = `Based on the following data from the database, answer the user's question: "${userQuestion}"
+
+Data: ${JSON.stringify(rows.slice(0, 10))}
+
+Provide a concise, professional summary. Mention specific names and numbers.`;
+
+ const aiSummaryResp = await dispatch(aiResponse({
+ input: [
+ { role: 'system', content: 'You are a sports analyst.' },
+ { role: 'user', content: summaryPrompt }
+ ]
+ })).unwrap();
+
+ const summary = extractText(aiSummaryResp);
+
+ setChatHistory(prev => [...prev, {
+ role: 'assistant',
+ content: summary,
+ type: 'table',
+ data: rows
+ }]);
+
+ } catch (error) {
+ console.error(error);
+ setIsExecutingSql(false);
+ setChatHistory(prev => [...prev, { role: 'assistant', content: "Sorry, I encountered an error while processing your request." }]);
+ }
+ };
+
+ return (
+ <>
+
+ {getPageTitle('AI Sports Insights')}
+
+
+
+ setChatHistory([])}
+ />
+
+
+
+
+ {chatHistory.length === 0 && (
+
+
+
Ask me anything about NBA, NFL, or NHL stats!
+
Example: "Who are the top 5 scorers in their last 10 games?"
+
+ )}
+ {chatHistory.map((msg, i) => (
+
+
+
{msg.content}
+ {msg.type === 'table' && msg.data && (
+
+
+
+
+ {Object.keys(msg.data[0] || {}).map(k => (
+ | {k} |
+ ))}
+
+
+
+ {msg.data.slice(0, 5).map((row, ri) => (
+
+ {Object.values(row).map((v: any, vi) => (
+ | {String(v)} |
+ ))}
+
+ ))}
+
+
+ {msg.data.length > 5 &&
Showing 5 of {msg.data.length} results
}
+
+ )}
+
+
+ ))}
+ {(isAskingResponse || isExecutingSql) && (
+
+ )}
+
+
+
+
+
+ >
+ );
+};
+
+InsightsPage.getLayout = function getLayout(page: ReactElement) {
+ return {page};
+};
+
+export default InsightsPage;
\ No newline at end of file