Skip to content

okwow123/stockanalysis_deploy

Repository files navigation

AI Stock Analyst

AI-driven equity research SaaS — enter a ticker, get a professional analyst-style report in Korean or English. 글로벌 주식 AI 분석 SaaS — 종목만 입력하면 한국어/영어로 전문 애널리스트 리포트를 받아보세요.

Next.js TypeScript Tailwind License: MIT


✨ Features

  • 🔍 Stock symbol search with normalized input (AAPL, NVDA, 005930, …)
  • 🤖 MiniMax LLM-powered equity analysis (investment score + 5-section report)
  • 🌍 Multi-language UI (🇰🇷 Korean / 🇺🇸 English) with next-intl
  • 💾 Supabase PostgreSQL cache — same (symbol, language) returns instantly without re-hitting the LLM
  • 🛡️ API-key safe — keys are server-only env vars, never exposed to the client
  • In-process dedup — concurrent identical requests share a single LLM call
  • 📱 Responsive — mobile-first, works from 320px to 4K
  • 🚀 Vercel-ready — deploy in under 2 minutes

🧱 Tech Stack

Layer Choice
Frontend Next.js 14 (App Router) · React 18 · TypeScript · Tailwind CSS · shadcn/ui
i18n next-intl (URL-based: /ko/... and /en/...)
AI MiniMax LLM API (MiniMax-M3 model) via direct fetch + JSON mode
Database Supabase (PostgreSQL) + service-role key for server-side writes
Hosting Vercel Free Plan

📁 Project Structure

app/
├─ app/                              # Next.js App Router
│  ├─ [locale]/
│  │  ├─ layout.tsx                  # Locale-aware root layout
│  │  ├─ page.tsx                    # Home: search + recent analyses
│  │  ├─ loading.tsx                 # Suspense fallback
│  │  └─ analysis/[symbol]/
│  │     └─ page.tsx                 # Report view (DB hit) or trigger
│  ├─ api/analyze/route.ts           # POST: DB check → LLM → save
│  ├─ globals.css                    # Tailwind + design tokens
│  └─ layout.tsx                     # Root (passes through to [locale])
│
├─ components/
│  ├─ ui/                            # shadcn primitives (inline, no CLI needed)
│  │  ├─ button.tsx
│  │  ├─ input.tsx
│  │  ├─ card.tsx
│  │  ├─ badge.tsx
│  │  └─ skeleton.tsx
│  ├─ StockSearch.tsx                # Search form (client)
│  ├─ ReportCard.tsx                 # 5-section report renderer
│  ├─ ScoreBadge.tsx                 # 0-100 color-coded badge
│  ├─ LanguageSwitcher.tsx           # URL-based locale switcher
│  ├─ RecentAnalyses.tsx             # Server component listing recent reports
│  ├─ AnalysisTrigger.tsx            # Client component that calls /api/analyze
│  └─ SiteHeader.tsx                 # Server component with nav + language
│
├─ lib/
│  ├─ MiniMax.ts                     # LLM client + prompt engineering
│  ├─ supabase.ts                    # Service-role client + CRUD
│  ├─ stock-lookup.ts                # Symbol → company name (top 50 tickers)
│  └─ utils.ts                       # cn(), normalize, format, clamp
│
├─ i18n/
│  ├─ config.ts                      # locales, labels, prompt-language map
│  └─ request.ts                     # next-intl request config
│
├─ messages/
│  ├─ ko.json                        # Korean UI strings
│  └─ en.json                        # English UI strings
│
├─ types/
│  └─ stock.ts                       # Domain types (AIReport, StockAnalysisRow, …)
│
├─ supabase/
│  └─ schema.sql                     # Table + indexes + RLS policy
│
├─ middleware.ts                     # next-intl URL-based locale routing
├─ next.config.mjs                   # Next config with next-intl plugin
├─ tailwind.config.ts
├─ tsconfig.json
├─ package.json
├─ .env.local.example                # Copy to .env.local and fill in
└─ README.md

🚀 Quick Start (Local)

1. Install

cd app
npm install

Node 18.17+ required (we test on Node 20 / 22).

2. Set up environment variables

cp .env.local.example .env.local

Edit .env.local and fill in:

# MiniMax (https://api.MiniMax.chat → API Keys)
MINIMAX_API_KEY=sk-...
MINIMAX_API_BASE=https://api.MiniMax.chat/v1
MINIMAX_MODEL=MiniMax-M3

# Supabase (Project Settings → API)
NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_ROLE_KEY=eyJ...

NEXT_PUBLIC_APP_URL=http://localhost:3000

3. Create the Supabase table

  1. Go to your Supabase projectSQL EditorNew query
  2. Paste the contents of supabase/schema.sql
  3. Click Run

You should see Success. No rows returned.

4. Run

npm run dev
# → http://localhost:3000  (redirects to /ko or /en based on browser language)

5. Build (production check)

npm run build
npm start

🧪 Testing the flow

# Action Expected
1 Open http://localhost:3000 Lands on /ko (or /en if browser language is English)
2 Type NVDA, click AI 분석 시작 Navigates to /ko/analysis/NVDA, shows loading skeleton, then full Korean report
3 Reload the same page Loads immediately from cache (no LLM call)
4 Click English in header Re-renders at /en/analysis/NVDA with a separate English report (different cache key)
5 Reload the English page Also instant from cache

Smoke test the API directly

curl -X POST http://localhost:3000/api/analyze \
  -H "Content-Type: application/json" \
  -d '{"symbol":"TSLA","language":"en"}'

Successful response:

{
  "ok": true,
  "fromCache": false,
  "data": {
    "id": 1,
    "symbol": "TSLA",
    "company_name": "Tesla, Inc.",
    "language": "en",
    "investment_score": 72,
    "ai_report": { "summary": "...", "...": "..." },
    "created_at": "2026-07-14T02:14:39.123Z"
  }
}

Run it a second time → "fromCache": true.


☁️ Deploying to Vercel (Free Plan)

Option A — Vercel Dashboard (recommended)

  1. Push this folder to a GitHub/GitLab/Bitbucket repo.
  2. Go to vercel.com/new → import the repo.
  3. Framework Preset auto-detects as Next.js. Leave defaults.
  4. Environment Variables — add every key from .env.local.example:
    • MINIMAX_API_KEY
    • MINIMAX_API_BASE
    • MINIMAX_MODEL
    • NEXT_PUBLIC_SUPABASE_URL
    • NEXT_PUBLIC_SUPABASE_ANON_KEY
    • SUPABASE_SERVICE_ROLE_KEY
    • NEXT_PUBLIC_APP_URL (use the Vercel preview URL, e.g. https://ai-stock-analyst.vercel.app)
  5. Click Deploy. First build takes ~60–90s.
  6. Done — every git push triggers a preview deploy.

Option B — Vercel CLI

npm i -g vercel
vercel login
vercel link
vercel env add MINIMAX_API_KEY
# … repeat for each var
vercel --prod

Vercel free-plan notes

  • ✅ 100 GB egress / month, 100 deployments / day — more than enough for an MVP.
  • ✅ Serverless functions on the free plan have a 10s execution limit on Hobby. The /api/analyze route makes one MiniMax call + two Supabase queries → typically 3–7s. If you hit a timeout, set export const maxDuration = 60; at the top of app/api/analyze/route.ts (Pro plan only — for free plan the 10s ceiling is enforced).
  • ⚠️ Edge runtime is not supported by this route (we use nodejs runtime to keep env vars + fetch stable).

🧠 How the cache works

User submits "NVDA" + "ko"
        │
        ▼
POST /api/analyze  ──► Supabase: SELECT … WHERE symbol='NVDA' AND language='ko'
        │
        ├─ Hit  → return cached row  (fromCache: true,  no LLM cost)
        │
        └─ Miss →  in-process dedup check  (Set<string> of "NVDA::ko")
                       │
                       └─ free → call MiniMax → parse JSON → INSERT → return  (fromCache: false)
  • symbol is normalized to UPPERCASE before lookup.
  • (symbol, language) is the cache key — NVDA/ko and NVDA/en are separate cache rows.
  • The most recent row is returned (ORDER BY created_at DESC LIMIT 1), so re-analyses just create a new row.
  • Concurrent identical requests share a single in-process inflight set to avoid duplicate LLM calls.

🛠️ Common Issues

Error Fix
Missing Supabase env vars Make sure .env.local has both NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.
MINIMAX_API_KEY is not set Add it to .env.local (or Vercel env vars). Restart next dev after editing.
401 Unauthorized from MiniMax Wrong or expired key. Regenerate on the MiniMax dashboard.
JSON object not in DB / API returns 502 The LLM returned malformed output. The route logs the raw content; the prompt asks for json_object.
permission denied for table stock_analysis You ran the API with the anon key. The service role key must do the writes (this is the default).
Hydration mismatch on the language switcher Refresh the page. We render the locale from useLocale(), which is hydrated after middleware.
Vercel build fails with Module not found Make sure the local npm install finished; commit package-lock.json.

🔒 Security notes

  • API key safetyMINIMAX_API_KEY and SUPABASE_SERVICE_ROLE_KEY are only read in server components, route handlers, and lib/* (no "use client" files import them). The Vercel deployment only ships NEXT_PUBLIC_* vars to the browser.
  • RLS — the stock_analysis table has Row Level Security enabled with a public read policy. Writes are blocked for anon/authenticated roles; only the service role bypasses RLS.
  • Input validation — the route validates symbol (1–20 alphanumeric, uppercased) and language (must be ko or en).
  • Disclaimer — every report and the site footer includes a "not investment advice" disclaimer.

📜 License

MIT — go build something cool.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages