Spaces:
Sleeping
Sleeping
Commit
·
f6a1fd1
1
Parent(s):
590171e
first commit
Browse files- .eslintrc.json +3 -0
- .gitattributes copy +35 -0
- .gitignore +50 -0
- Dockerfile +57 -0
- README copy.md +9 -0
- app/components/ChatBot.tsx +234 -0
- app/components/ThemeProvider.tsx +46 -0
- app/favicon.ico +0 -0
- app/fonts/GeistMonoVF.woff +0 -0
- app/fonts/GeistVF.woff +0 -0
- app/globals.css +49 -0
- app/layout.tsx +31 -0
- app/page.tsx +216 -0
- backend/main.py +83 -0
- backend/requirements.txt +6 -0
- next.config.ts +15 -0
- package-lock.json +0 -0
- package.json +28 -0
- pnpm-lock.yaml +0 -0
- postcss.config.mjs +8 -0
- public/file.svg +1 -0
- public/globe.svg +1 -0
- public/next.svg +1 -0
- public/vercel.svg +1 -0
- public/window.svg +1 -0
- tailwind.config.ts +61 -0
- tsconfig.json +27 -0
.eslintrc.json
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"extends": ["next/core-web-vitals", "next/typescript"]
|
3 |
+
}
|
.gitattributes copy
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
2 |
+
|
3 |
+
# dependencies
|
4 |
+
/node_modules
|
5 |
+
/.pnp
|
6 |
+
.pnp.*
|
7 |
+
.yarn/*
|
8 |
+
!.yarn/patches
|
9 |
+
!.yarn/plugins
|
10 |
+
!.yarn/releases
|
11 |
+
!.yarn/versions
|
12 |
+
|
13 |
+
# testing
|
14 |
+
/coverage
|
15 |
+
|
16 |
+
# next.js
|
17 |
+
/.next/
|
18 |
+
/out/
|
19 |
+
|
20 |
+
# production
|
21 |
+
/build
|
22 |
+
|
23 |
+
# misc
|
24 |
+
.DS_Store
|
25 |
+
*.pem
|
26 |
+
|
27 |
+
# debug
|
28 |
+
npm-debug.log*
|
29 |
+
yarn-debug.log*
|
30 |
+
yarn-error.log*
|
31 |
+
|
32 |
+
# env files (can opt-in for commiting if needed)
|
33 |
+
.env*
|
34 |
+
|
35 |
+
# vercel
|
36 |
+
.vercel
|
37 |
+
|
38 |
+
# typescript
|
39 |
+
*.tsbuildinfo
|
40 |
+
next-env.d.ts
|
41 |
+
|
42 |
+
# Python
|
43 |
+
__pycache__/
|
44 |
+
*.py[cod]
|
45 |
+
*$py.class
|
46 |
+
.Python
|
47 |
+
env/
|
48 |
+
venv/
|
49 |
+
.env
|
50 |
+
node_modules
|
Dockerfile
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM node:20-alpine AS base
|
2 |
+
|
3 |
+
# Frontend build
|
4 |
+
FROM base AS frontend-deps
|
5 |
+
RUN apk add --no-cache libc6-compat
|
6 |
+
WORKDIR /app
|
7 |
+
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
8 |
+
RUN \
|
9 |
+
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
10 |
+
elif [ -f package-lock.json ]; then npm ci; \
|
11 |
+
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
|
12 |
+
else echo "Lockfile not found." && exit 1; \
|
13 |
+
fi
|
14 |
+
|
15 |
+
FROM base AS frontend-builder
|
16 |
+
WORKDIR /app
|
17 |
+
COPY --from=frontend-deps /app/node_modules ./node_modules
|
18 |
+
COPY . .
|
19 |
+
RUN npm run build
|
20 |
+
|
21 |
+
# Backend build
|
22 |
+
FROM python:3.9-slim AS backend
|
23 |
+
WORKDIR /backend
|
24 |
+
COPY backend/requirements.txt .
|
25 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
26 |
+
COPY backend .
|
27 |
+
|
28 |
+
# Final image
|
29 |
+
FROM python:3.9-slim AS runner
|
30 |
+
WORKDIR /app
|
31 |
+
|
32 |
+
ENV NODE_ENV production
|
33 |
+
|
34 |
+
# Install Node.js and wget
|
35 |
+
RUN apt-get update && apt-get install -y curl wget && \
|
36 |
+
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
37 |
+
apt-get install -y nodejs && \
|
38 |
+
apt-get clean && \
|
39 |
+
rm -rf /var/lib/apt/lists/*
|
40 |
+
|
41 |
+
RUN addgroup --system --gid 1001 nodejs && \
|
42 |
+
adduser --system --uid 1001 nextjs
|
43 |
+
|
44 |
+
COPY --from=frontend-builder /app/public ./frontend/public
|
45 |
+
COPY --from=frontend-builder --chown=nextjs:nodejs /app/.next/standalone ./frontend
|
46 |
+
COPY --from=frontend-builder --chown=nextjs:nodejs /app/.next/static ./frontend/.next/static
|
47 |
+
COPY --from=frontend-builder --chown=nextjs:nodejs /app/.next/cache ./frontend/.next/cache
|
48 |
+
|
49 |
+
COPY --from=backend /backend ./backend
|
50 |
+
COPY --from=backend /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
|
51 |
+
|
52 |
+
COPY start.sh .
|
53 |
+
RUN chmod +x start.sh
|
54 |
+
|
55 |
+
EXPOSE 3000 8000
|
56 |
+
|
57 |
+
CMD ["./start.sh"]
|
README copy.md
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: Playgo Next
|
3 |
+
emoji: 🐢
|
4 |
+
colorFrom: pink
|
5 |
+
colorTo: yellow
|
6 |
+
sdk: docker
|
7 |
+
app_port: 3000
|
8 |
+
pinned: false
|
9 |
+
---
|
app/components/ChatBot.tsx
ADDED
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'use client'
|
2 |
+
|
3 |
+
import { useState, useRef, useEffect } from 'react'
|
4 |
+
import { useChat } from 'ai/react'
|
5 |
+
|
6 |
+
type MessageWithLoading = {
|
7 |
+
content: string;
|
8 |
+
role: string;
|
9 |
+
isStreaming?: boolean;
|
10 |
+
}
|
11 |
+
|
12 |
+
const EXAMPLE_PROMPTS = [
|
13 |
+
{
|
14 |
+
title: "Personalized Learning",
|
15 |
+
description: "Get tailored explanations",
|
16 |
+
prompt: "Can you help me understand photosynthesis in a simple way?"
|
17 |
+
},
|
18 |
+
{
|
19 |
+
title: "24/7 Availability",
|
20 |
+
description: "Learn at your own pace",
|
21 |
+
prompt: "I need help solving this quadratic equation: x² + 5x + 6 = 0"
|
22 |
+
},
|
23 |
+
{
|
24 |
+
title: "Multiple Subjects",
|
25 |
+
description: "From math to literature",
|
26 |
+
prompt: "What are the main themes in Shakespeare's Macbeth?"
|
27 |
+
}
|
28 |
+
]
|
29 |
+
|
30 |
+
export default function LandingPageChatBot() {
|
31 |
+
const { messages: rawMessages, input, handleInputChange, handleSubmit, isLoading, append } = useChat({
|
32 |
+
api: '/api/landing_page_chat',
|
33 |
+
streamProtocol: 'data',
|
34 |
+
onError: (error) => {
|
35 |
+
console.error('Chat error:', error);
|
36 |
+
},
|
37 |
+
onFinish: (message) => {
|
38 |
+
console.log('Chat finished:', message);
|
39 |
+
setMessages(prev => prev.map(msg => ({...msg, isStreaming: false})))
|
40 |
+
},
|
41 |
+
keepLastMessageOnError: true,
|
42 |
+
})
|
43 |
+
|
44 |
+
const chatContainerRef = useRef<HTMLDivElement>(null)
|
45 |
+
const [hasInteracted, setHasInteracted] = useState(false)
|
46 |
+
const [messages, setMessages] = useState<MessageWithLoading[]>([])
|
47 |
+
|
48 |
+
useEffect(() => {
|
49 |
+
setMessages(rawMessages.map((msg, index) => ({
|
50 |
+
...msg,
|
51 |
+
isStreaming: isLoading && index === rawMessages.length - 1 && msg.role === 'assistant'
|
52 |
+
})))
|
53 |
+
}, [rawMessages, isLoading])
|
54 |
+
|
55 |
+
const scrollToBottom = () => {
|
56 |
+
if (chatContainerRef.current) {
|
57 |
+
const { scrollHeight, clientHeight } = chatContainerRef.current
|
58 |
+
chatContainerRef.current.scrollTop = scrollHeight - clientHeight
|
59 |
+
}
|
60 |
+
}
|
61 |
+
|
62 |
+
useEffect(() => {
|
63 |
+
scrollToBottom()
|
64 |
+
}, [messages])
|
65 |
+
|
66 |
+
const sendMessage = async (text: string) => {
|
67 |
+
setHasInteracted(true)
|
68 |
+
await append({
|
69 |
+
content: text,
|
70 |
+
role: 'user',
|
71 |
+
})
|
72 |
+
}
|
73 |
+
|
74 |
+
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
75 |
+
setHasInteracted(true)
|
76 |
+
handleSubmit(e)
|
77 |
+
}
|
78 |
+
|
79 |
+
return (
|
80 |
+
<section className="w-full h-[800px] bg-gradient-to-b from-background-secondary to-background-primary flex items-center justify-center px-4">
|
81 |
+
<div className="w-full max-w-5xl mx-auto h-full flex items-center">
|
82 |
+
{!hasInteracted && messages.length === 0 ? (
|
83 |
+
<div className="text-center space-y-8 w-full">
|
84 |
+
<div className="space-y-4">
|
85 |
+
<h2 className="text-5xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
|
86 |
+
AI Learning Assistant
|
87 |
+
</h2>
|
88 |
+
<p className="text-text-secondary text-xl max-w-2xl mx-auto leading-relaxed">
|
89 |
+
Explore new topics, get homework help, or discover learning resources
|
90 |
+
</p>
|
91 |
+
</div>
|
92 |
+
|
93 |
+
<form onSubmit={onSubmit} className="max-w-3xl mx-auto">
|
94 |
+
<div className="relative flex items-center">
|
95 |
+
<input
|
96 |
+
type="text"
|
97 |
+
value={input}
|
98 |
+
onChange={handleInputChange}
|
99 |
+
placeholder="Ask anything about learning..."
|
100 |
+
className="w-full p-6 pr-36 rounded-2xl border border-border/50 bg-background-primary/50
|
101 |
+
backdrop-blur-sm shadow-lg focus:outline-none focus:ring-2 focus:ring-primary/50
|
102 |
+
text-lg transition-all duration-200 placeholder:text-text-secondary/50"
|
103 |
+
/>
|
104 |
+
<button
|
105 |
+
type="submit"
|
106 |
+
disabled={isLoading}
|
107 |
+
className="absolute right-2 p-3 bg-primary text-white rounded-full
|
108 |
+
hover:bg-primary/90 transition-all duration-200
|
109 |
+
shadow-md hover:shadow-lg active:scale-95 disabled:opacity-50
|
110 |
+
disabled:hover:bg-primary disabled:cursor-not-allowed"
|
111 |
+
>
|
112 |
+
<svg
|
113 |
+
xmlns="http://www.w3.org/2000/svg"
|
114 |
+
fill="none"
|
115 |
+
viewBox="0 0 24 24"
|
116 |
+
strokeWidth="1.5"
|
117 |
+
stroke="currentColor"
|
118 |
+
className={`size-6 ${isLoading ? 'animate-pulse' : ''}`}
|
119 |
+
>
|
120 |
+
<path
|
121 |
+
strokeLinecap="round"
|
122 |
+
strokeLinejoin="round"
|
123 |
+
d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5"
|
124 |
+
/>
|
125 |
+
</svg>
|
126 |
+
</button>
|
127 |
+
</div>
|
128 |
+
</form>
|
129 |
+
|
130 |
+
<div className="mt-12">
|
131 |
+
<h3 className="text-xl font-semibold text-text-primary mb-6">Try these examples:</h3>
|
132 |
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
133 |
+
{EXAMPLE_PROMPTS.map((prompt, index) => (
|
134 |
+
<button
|
135 |
+
key={index}
|
136 |
+
onClick={() => sendMessage(prompt.prompt)}
|
137 |
+
className="group p-6 rounded-2xl border border-border/50 bg-background-primary/30
|
138 |
+
hover:bg-background-primary/50 backdrop-blur-sm transition-all duration-200
|
139 |
+
hover:border-primary/50 hover:shadow-lg text-left"
|
140 |
+
>
|
141 |
+
<div className="flex items-center justify-between mb-2">
|
142 |
+
<h4 className="font-semibold text-text-primary">{prompt.title}</h4>
|
143 |
+
<svg
|
144 |
+
className="w-5 h-5 text-primary opacity-0 group-hover:opacity-100 transition-opacity duration-200"
|
145 |
+
fill="none"
|
146 |
+
stroke="currentColor"
|
147 |
+
viewBox="0 0 24 24"
|
148 |
+
>
|
149 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
150 |
+
</svg>
|
151 |
+
</div>
|
152 |
+
<p className="text-text-secondary text-sm mb-2">{prompt.description}</p>
|
153 |
+
<p className="text-primary text-sm font-medium truncate">{prompt.prompt}</p>
|
154 |
+
</button>
|
155 |
+
))}
|
156 |
+
</div>
|
157 |
+
</div>
|
158 |
+
</div>
|
159 |
+
) : (
|
160 |
+
<div className="bg-background-primary/50 backdrop-blur-sm rounded-2xl shadow-lg border border-border/50 overflow-hidden w-full h-[700px] flex flex-col">
|
161 |
+
<div className="p-6 border-b border-border/50 bg-background-secondary/30">
|
162 |
+
<h2 className="text-xl font-semibold text-text-primary">AI Learning Assistant</h2>
|
163 |
+
</div>
|
164 |
+
|
165 |
+
<div
|
166 |
+
ref={chatContainerRef}
|
167 |
+
className="flex-1 overflow-y-auto p-6 space-y-6"
|
168 |
+
>
|
169 |
+
{messages.map((message, index) => (
|
170 |
+
<div
|
171 |
+
key={index}
|
172 |
+
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
|
173 |
+
>
|
174 |
+
<div
|
175 |
+
className={`max-w-[80%] p-4 rounded-2xl shadow-sm ${
|
176 |
+
message.role === 'user'
|
177 |
+
? 'bg-primary text-white rounded-br-none'
|
178 |
+
: 'bg-background-secondary/50 text-text-primary rounded-bl-none'
|
179 |
+
}`}
|
180 |
+
>
|
181 |
+
{message.content}
|
182 |
+
{message.isStreaming && (
|
183 |
+
<span className="inline-block w-2.5 h-2.5 ml-1.5 bg-primary rounded-full animate-pulse" />
|
184 |
+
)}
|
185 |
+
</div>
|
186 |
+
</div>
|
187 |
+
))}
|
188 |
+
</div>
|
189 |
+
|
190 |
+
<div className="p-6 border-t border-border/50 bg-background-secondary/30">
|
191 |
+
<form onSubmit={onSubmit}>
|
192 |
+
<div className="relative flex items-center">
|
193 |
+
<input
|
194 |
+
type="text"
|
195 |
+
value={input}
|
196 |
+
onChange={handleInputChange}
|
197 |
+
placeholder={isLoading ? "AI is thinking..." : "Type your message..."}
|
198 |
+
disabled={isLoading}
|
199 |
+
className="w-full p-4 pr-32 rounded-xl border border-border/50 bg-background-primary/50
|
200 |
+
backdrop-blur-sm focus:outline-none focus:ring-2 focus:ring-primary/50
|
201 |
+
text-base transition-all duration-200 disabled:opacity-50"
|
202 |
+
/>
|
203 |
+
<button
|
204 |
+
type="submit"
|
205 |
+
disabled={isLoading}
|
206 |
+
className="absolute right-2 p-2 bg-primary text-white rounded-full
|
207 |
+
hover:bg-primary/90 transition-all duration-200
|
208 |
+
shadow-md hover:shadow-lg active:scale-95 disabled:opacity-50
|
209 |
+
disabled:hover:bg-primary disabled:cursor-not-allowed"
|
210 |
+
>
|
211 |
+
<svg
|
212 |
+
xmlns="http://www.w3.org/2000/svg"
|
213 |
+
fill="none"
|
214 |
+
viewBox="0 0 24 24"
|
215 |
+
strokeWidth="1.5"
|
216 |
+
stroke="currentColor"
|
217 |
+
className={`size-5 ${isLoading ? 'animate-pulse' : ''}`}
|
218 |
+
>
|
219 |
+
<path
|
220 |
+
strokeLinecap="round"
|
221 |
+
strokeLinejoin="round"
|
222 |
+
d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5"
|
223 |
+
/>
|
224 |
+
</svg>
|
225 |
+
</button>
|
226 |
+
</div>
|
227 |
+
</form>
|
228 |
+
</div>
|
229 |
+
</div>
|
230 |
+
)}
|
231 |
+
</div>
|
232 |
+
</section>
|
233 |
+
)
|
234 |
+
}
|
app/components/ThemeProvider.tsx
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'use client'
|
2 |
+
|
3 |
+
import { createContext, useContext, useEffect, useState } from 'react'
|
4 |
+
|
5 |
+
type Theme = 'light' | 'dark'
|
6 |
+
|
7 |
+
type ThemeContextType = {
|
8 |
+
theme: Theme
|
9 |
+
toggleTheme: () => void
|
10 |
+
}
|
11 |
+
|
12 |
+
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
|
13 |
+
|
14 |
+
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
15 |
+
const [theme, setTheme] = useState<Theme>('light')
|
16 |
+
|
17 |
+
useEffect(() => {
|
18 |
+
// On mount, read theme from localStorage or default to light
|
19 |
+
const savedTheme = localStorage.getItem('theme') as Theme
|
20 |
+
if (savedTheme) {
|
21 |
+
setTheme(savedTheme)
|
22 |
+
document.documentElement.setAttribute('data-theme', savedTheme)
|
23 |
+
}
|
24 |
+
}, [])
|
25 |
+
|
26 |
+
const toggleTheme = () => {
|
27 |
+
const newTheme = theme === 'light' ? 'dark' : 'light'
|
28 |
+
setTheme(newTheme)
|
29 |
+
localStorage.setItem('theme', newTheme)
|
30 |
+
document.documentElement.setAttribute('data-theme', newTheme)
|
31 |
+
}
|
32 |
+
|
33 |
+
return (
|
34 |
+
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
35 |
+
{children}
|
36 |
+
</ThemeContext.Provider>
|
37 |
+
)
|
38 |
+
}
|
39 |
+
|
40 |
+
export function useTheme() {
|
41 |
+
const context = useContext(ThemeContext)
|
42 |
+
if (context === undefined) {
|
43 |
+
throw new Error('useTheme must be used within a ThemeProvider')
|
44 |
+
}
|
45 |
+
return context
|
46 |
+
}
|
app/favicon.ico
ADDED
|
app/fonts/GeistMonoVF.woff
ADDED
Binary file (67.9 kB). View file
|
|
app/fonts/GeistVF.woff
ADDED
Binary file (66.3 kB). View file
|
|
app/globals.css
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@tailwind base;
|
2 |
+
@tailwind components;
|
3 |
+
@tailwind utilities;
|
4 |
+
|
5 |
+
:root {
|
6 |
+
/* Light mode */
|
7 |
+
--background-primary: #ffffff;
|
8 |
+
--background-secondary: #f8f9fa;
|
9 |
+
--background-tertiary: #f1f3f5;
|
10 |
+
|
11 |
+
--text-primary: #171717;
|
12 |
+
--text-secondary: #4b5563;
|
13 |
+
--text-tertiary: #6b7280;
|
14 |
+
|
15 |
+
--border: #e5e7eb;
|
16 |
+
--border-secondary: #d1d5db;
|
17 |
+
|
18 |
+
--card-background: rgba(255, 255, 255, 0.8);
|
19 |
+
--card-border: rgba(229, 231, 235, 0.5);
|
20 |
+
--glass-background: rgba(255, 255, 255, 0.7);
|
21 |
+
}
|
22 |
+
|
23 |
+
[data-theme='dark'] {
|
24 |
+
/* Dark mode - updated for better contrast and aesthetics */
|
25 |
+
--background-primary: #0f172a; /* Darker blue-gray */
|
26 |
+
--background-secondary: #1e293b; /* Rich blue-gray */
|
27 |
+
--background-tertiary: #334155; /* Medium blue-gray */
|
28 |
+
|
29 |
+
--text-primary: #f8fafc; /* Very light blue-gray */
|
30 |
+
--text-secondary: #cbd5e1; /* Light blue-gray */
|
31 |
+
--text-tertiary: #94a3b8; /* Medium blue-gray */
|
32 |
+
|
33 |
+
--border: #334155; /* Medium blue-gray */
|
34 |
+
--border-secondary: #475569; /* Light blue-gray */
|
35 |
+
|
36 |
+
--card-background: rgba(30, 41, 59, 0.8); /* Transparent blue-gray */
|
37 |
+
--card-border: rgba(51, 65, 85, 0.5); /* Transparent border */
|
38 |
+
--glass-background: rgba(15, 23, 42, 0.7); /* Transparent dark blue-gray */
|
39 |
+
}
|
40 |
+
|
41 |
+
body {
|
42 |
+
color: var(--text-primary);
|
43 |
+
background: var(--background-primary);
|
44 |
+
}
|
45 |
+
|
46 |
+
/* Add smooth transitions for theme switching */
|
47 |
+
body * {
|
48 |
+
transition: background-color 0.3s ease, border-color 0.3s ease;
|
49 |
+
}
|
app/layout.tsx
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { Metadata } from "next";
|
2 |
+
import localFont from "next/font/local";
|
3 |
+
import "./globals.css";
|
4 |
+
import { ThemeProvider } from './components/ThemeProvider'
|
5 |
+
|
6 |
+
const geistSans = localFont({
|
7 |
+
src: "./fonts/GeistVF.woff",
|
8 |
+
variable: "--font-geist-sans",
|
9 |
+
weight: "100 900",
|
10 |
+
});
|
11 |
+
|
12 |
+
export const metadata: Metadata = {
|
13 |
+
title: "PlayGo AI - AI-Powered Learning Platform",
|
14 |
+
description: "Transform education with AI-powered interactive learning experiences",
|
15 |
+
};
|
16 |
+
|
17 |
+
export default function RootLayout({
|
18 |
+
children,
|
19 |
+
}: {
|
20 |
+
children: React.ReactNode
|
21 |
+
}) {
|
22 |
+
return (
|
23 |
+
<html lang="en" className={`${geistSans.variable}`}>
|
24 |
+
<body className="antialiased">
|
25 |
+
<ThemeProvider>
|
26 |
+
{children}
|
27 |
+
</ThemeProvider>
|
28 |
+
</body>
|
29 |
+
</html>
|
30 |
+
)
|
31 |
+
}
|
app/page.tsx
ADDED
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'use client'
|
2 |
+
|
3 |
+
import Link from "next/link"
|
4 |
+
import { useTheme } from './components/ThemeProvider'
|
5 |
+
import { useEffect, useState } from 'react'
|
6 |
+
import ChatBot from './components/ChatBot'
|
7 |
+
|
8 |
+
export default function LandingPage() {
|
9 |
+
const { theme, toggleTheme } = useTheme()
|
10 |
+
const [message, setMessage] = useState('')
|
11 |
+
|
12 |
+
useEffect(() => {
|
13 |
+
// Call FastAPI endpoint
|
14 |
+
fetch('/api/hello')
|
15 |
+
.then(res => res.json())
|
16 |
+
.then(data => {setMessage(data.message); console.log('Fetched message:', data.message);})
|
17 |
+
.catch(err => console.error('Error fetching:', err))
|
18 |
+
}, [])
|
19 |
+
|
20 |
+
return (
|
21 |
+
<div className="min-h-screen bg-background-primary">
|
22 |
+
{/* Modern Header */}
|
23 |
+
<header className="fixed w-full bg-background-secondary/80 backdrop-blur-sm border-b border-border z-50">
|
24 |
+
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
|
25 |
+
<Link href="/" className="text-3xl font-bold">
|
26 |
+
<span className="text-[#FF6B6B]">P</span>
|
27 |
+
<span className="text-[#4ECDC4]">l</span>
|
28 |
+
<span className="text-[#45B7D1]">a</span>
|
29 |
+
<span className="text-[#FDCB6E]">y</span>
|
30 |
+
<span className="text-[#FF6B6B]">G</span>
|
31 |
+
<span className="text-[#4ECDC4]">o</span>
|
32 |
+
<span className="ml-2 text-[#45B7D1]">A</span>
|
33 |
+
<span className="text-[#FDCB6E]">I</span>
|
34 |
+
</Link>
|
35 |
+
<nav className="flex items-center space-x-8">
|
36 |
+
<Link href="/for-teachers" className="text-text-secondary hover:text-primary">
|
37 |
+
For Teachers
|
38 |
+
</Link>
|
39 |
+
<Link href="/for-students" className="text-text-secondary hover:text-primary">
|
40 |
+
For Students
|
41 |
+
</Link>
|
42 |
+
<Link href="/community" className="text-text-secondary hover:text-primary">
|
43 |
+
Community
|
44 |
+
</Link>
|
45 |
+
<button
|
46 |
+
onClick={toggleTheme}
|
47 |
+
className="px-4 py-2 rounded-lg bg-primary text-white hover:bg-primary/90"
|
48 |
+
>
|
49 |
+
{theme === 'light' ? '🌙' : '☀️'}
|
50 |
+
</button>
|
51 |
+
</nav>
|
52 |
+
</div>
|
53 |
+
</header>
|
54 |
+
|
55 |
+
{/* Add ChatBot right after header */}
|
56 |
+
<div className="pt-20">
|
57 |
+
<ChatBot />
|
58 |
+
</div>
|
59 |
+
|
60 |
+
<main className="pt-4">
|
61 |
+
{/* Hero Section */}
|
62 |
+
<section className="container mx-auto px-4 py-20 text-center">
|
63 |
+
<h1 className="text-6xl font-bold mb-6 bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
|
64 |
+
Turn learning into an AI-powered adventure
|
65 |
+
</h1>
|
66 |
+
<p className="text-text-secondary text-xl mb-12 max-w-2xl mx-auto">
|
67 |
+
PlayGo AI is the leading educational platform for building engaging AI-powered learning experiences
|
68 |
+
</p>
|
69 |
+
<div className="flex justify-center gap-4">
|
70 |
+
<Link
|
71 |
+
href="/get-started"
|
72 |
+
className="px-6 py-3 rounded-lg bg-primary text-white hover:bg-primary/90 font-medium"
|
73 |
+
>
|
74 |
+
Get Started
|
75 |
+
</Link>
|
76 |
+
<Link
|
77 |
+
href="/demo"
|
78 |
+
className="px-6 py-3 rounded-lg border border-border hover:border-primary text-text-primary font-medium"
|
79 |
+
>
|
80 |
+
View Demo
|
81 |
+
</Link>
|
82 |
+
</div>
|
83 |
+
</section>
|
84 |
+
|
85 |
+
{/* Features Grid */}
|
86 |
+
<section className="bg-background-secondary py-20">
|
87 |
+
<div className="container mx-auto px-4">
|
88 |
+
<h2 className="text-3xl font-bold text-center mb-16">Build with our powerful learning tools</h2>
|
89 |
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
90 |
+
<div className="p-6 rounded-xl bg-background-primary">
|
91 |
+
<div className="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center mb-4">
|
92 |
+
<svg className="w-6 h-6 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
93 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
94 |
+
</svg>
|
95 |
+
</div>
|
96 |
+
<h3 className="text-xl font-semibold mb-2">Interactive Learning</h3>
|
97 |
+
<p className="text-text-secondary">Engage with AI-powered quizzes, projects, and virtual labs</p>
|
98 |
+
</div>
|
99 |
+
|
100 |
+
<div className="p-6 rounded-xl bg-background-primary">
|
101 |
+
<div className="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center mb-4">
|
102 |
+
<svg className="w-6 h-6 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
103 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
104 |
+
</svg>
|
105 |
+
</div>
|
106 |
+
<h3 className="text-xl font-semibold mb-2">Diverse Subjects</h3>
|
107 |
+
<p className="text-text-secondary">Explore topics from STEM to humanities with expert AI guidance</p>
|
108 |
+
</div>
|
109 |
+
|
110 |
+
<div className="p-6 rounded-xl bg-background-primary">
|
111 |
+
<div className="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center mb-4">
|
112 |
+
<svg className="w-6 h-6 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
113 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
|
114 |
+
</svg>
|
115 |
+
</div>
|
116 |
+
<h3 className="text-xl font-semibold mb-2">Personalized Path</h3>
|
117 |
+
<p className="text-text-secondary">Adaptive learning tailored to your unique pace and style</p>
|
118 |
+
</div>
|
119 |
+
|
120 |
+
<div className="p-6 rounded-xl bg-background-primary">
|
121 |
+
<div className="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center mb-4">
|
122 |
+
<svg className="w-6 h-6 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
123 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
124 |
+
</svg>
|
125 |
+
</div>
|
126 |
+
<h3 className="text-xl font-semibold mb-2">Progress Tracking</h3>
|
127 |
+
<p className="text-text-secondary">Monitor learning outcomes with detailed analytics</p>
|
128 |
+
</div>
|
129 |
+
</div>
|
130 |
+
</div>
|
131 |
+
</section>
|
132 |
+
|
133 |
+
{/* Stats Section */}
|
134 |
+
<section className="container mx-auto px-4 py-20">
|
135 |
+
<h2 className="text-3xl font-bold text-center mb-16">Trusted by educators worldwide</h2>
|
136 |
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 text-center">
|
137 |
+
<div>
|
138 |
+
<p className="text-4xl font-bold text-primary mb-2">500K+</p>
|
139 |
+
<p className="text-text-secondary">Active Students</p>
|
140 |
+
</div>
|
141 |
+
<div>
|
142 |
+
<p className="text-4xl font-bold text-primary mb-2">10K+</p>
|
143 |
+
<p className="text-text-secondary">Teachers</p>
|
144 |
+
</div>
|
145 |
+
<div>
|
146 |
+
<p className="text-4xl font-bold text-primary mb-2">1M+</p>
|
147 |
+
<p className="text-text-secondary">Learning Sessions</p>
|
148 |
+
</div>
|
149 |
+
<div>
|
150 |
+
<p className="text-4xl font-bold text-primary mb-2">50+</p>
|
151 |
+
<p className="text-text-secondary">Countries</p>
|
152 |
+
</div>
|
153 |
+
</div>
|
154 |
+
</section>
|
155 |
+
</main>
|
156 |
+
|
157 |
+
{/* Updated Educational Footer */}
|
158 |
+
<footer className="bg-background-secondary border-t border-border py-12">
|
159 |
+
<div className="container mx-auto px-4">
|
160 |
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
161 |
+
<div className="text-center md:text-left">
|
162 |
+
<h3 className="text-2xl font-bold mb-4">
|
163 |
+
<span className="text-[#FF6B6B]">P</span>
|
164 |
+
<span className="text-[#4ECDC4]">l</span>
|
165 |
+
<span className="text-[#45B7D1]">a</span>
|
166 |
+
<span className="text-[#FDCB6E]">y</span>
|
167 |
+
<span className="text-[#FF6B6B]">G</span>
|
168 |
+
<span className="text-[#4ECDC4]">o</span>
|
169 |
+
<span className="ml-2 text-[#45B7D1]">A</span>
|
170 |
+
<span className="text-[#FDCB6E]">I</span>
|
171 |
+
</h3>
|
172 |
+
<p className="text-text-secondary">Making learning fun and accessible for everyone</p>
|
173 |
+
</div>
|
174 |
+
|
175 |
+
<div className="text-center">
|
176 |
+
<h3 className="font-semibold mb-4">Learning Resources</h3>
|
177 |
+
<ul className="space-y-2">
|
178 |
+
<li><Link href="/library" className="text-text-secondary hover:text-primary">Learning Library</Link></li>
|
179 |
+
<li><Link href="/tutorials" className="text-text-secondary hover:text-primary">Tutorials</Link></li>
|
180 |
+
<li><Link href="/blog" className="text-text-secondary hover:text-primary">Educational Blog</Link></li>
|
181 |
+
</ul>
|
182 |
+
</div>
|
183 |
+
|
184 |
+
<div className="text-center md:text-right">
|
185 |
+
<h3 className="font-semibold mb-4">Connect With Us</h3>
|
186 |
+
<div className="flex justify-center md:justify-end space-x-4">
|
187 |
+
<a href="#" className="text-text-secondary hover:text-primary" aria-label="Discord">
|
188 |
+
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
189 |
+
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"/>
|
190 |
+
</svg>
|
191 |
+
</a>
|
192 |
+
<a href="#" className="text-text-secondary hover:text-primary" aria-label="Twitter">
|
193 |
+
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
194 |
+
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
|
195 |
+
</svg>
|
196 |
+
</a>
|
197 |
+
<a href="#" className="text-text-secondary hover:text-primary" aria-label="YouTube">
|
198 |
+
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
199 |
+
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/>
|
200 |
+
</svg>
|
201 |
+
</a>
|
202 |
+
</div>
|
203 |
+
<p className="mt-4 text-text-secondary">
|
204 |
+
Contact us: <a href="mailto:[email protected]" className="hover:text-primary">[email protected]</a>
|
205 |
+
</p>
|
206 |
+
</div>
|
207 |
+
</div>
|
208 |
+
<div className="mt-12 pt-8 border-t border-border/50 text-center text-text-secondary">
|
209 |
+
<p>PlayGo AI - A non-profit organization dedicated to making education accessible through AI</p>
|
210 |
+
<p className="mt-2">© {new Date().getFullYear()} PlayGo AI. All rights reserved.</p>
|
211 |
+
</div>
|
212 |
+
</div>
|
213 |
+
</footer>
|
214 |
+
</div>
|
215 |
+
)
|
216 |
+
}
|
backend/main.py
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
|
4 |
+
from fastapi import FastAPI, HTTPException, Query
|
5 |
+
from fastapi.middleware.cors import CORSMiddleware
|
6 |
+
from fastapi.responses import StreamingResponse
|
7 |
+
from pydantic import BaseModel
|
8 |
+
import openai
|
9 |
+
from dotenv import load_dotenv
|
10 |
+
from typing import List
|
11 |
+
import asyncio
|
12 |
+
|
13 |
+
# Load environment variables
|
14 |
+
load_dotenv()
|
15 |
+
|
16 |
+
# Initialize OpenAI client
|
17 |
+
client = openai.OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
|
18 |
+
|
19 |
+
app = FastAPI()
|
20 |
+
|
21 |
+
app.add_middleware(
|
22 |
+
CORSMiddleware,
|
23 |
+
allow_origins=["http://localhost:3000"],
|
24 |
+
allow_credentials=True,
|
25 |
+
allow_methods=["*"],
|
26 |
+
allow_headers=["*"],
|
27 |
+
)
|
28 |
+
|
29 |
+
class Message(BaseModel):
|
30 |
+
content: str
|
31 |
+
role: str
|
32 |
+
|
33 |
+
class ChatRequest(BaseModel):
|
34 |
+
messages: List[Message]
|
35 |
+
|
36 |
+
async def stream_text(messages: List[Message]):
|
37 |
+
try:
|
38 |
+
formatted_messages = [
|
39 |
+
{"role": "system", "content": """You are an AI learning assistant for PlayGo AI,
|
40 |
+
an educational platform. Your goal is to help students learn and understand various
|
41 |
+
subjects. Provide clear, concise, and accurate explanations."""},
|
42 |
+
] + [{"role": msg.role, "content": msg.content} for msg in messages]
|
43 |
+
|
44 |
+
stream = client.chat.completions.create(
|
45 |
+
model="gpt-3.5-turbo",
|
46 |
+
messages=formatted_messages,
|
47 |
+
temperature=0.7,
|
48 |
+
stream=True
|
49 |
+
)
|
50 |
+
|
51 |
+
for chunk in stream:
|
52 |
+
for choice in chunk.choices:
|
53 |
+
if choice.finish_reason == "stop":
|
54 |
+
continue
|
55 |
+
|
56 |
+
else:
|
57 |
+
yield '0:{text}\n'.format(text=json.dumps(choice.delta.content))
|
58 |
+
|
59 |
+
if chunk.choices == []:
|
60 |
+
usage = chunk.usage
|
61 |
+
prompt_tokens = usage.prompt_tokens
|
62 |
+
completion_tokens = usage.completion_tokens
|
63 |
+
yield 'd:{{"finishReason":"{reason}","usage":{{"promptTokens":{prompt},"completionTokens":{completion}}}}}\n'.format(
|
64 |
+
reason="stop",
|
65 |
+
prompt=prompt_tokens,
|
66 |
+
completion=completion_tokens
|
67 |
+
)
|
68 |
+
|
69 |
+
except Exception as e:
|
70 |
+
print(f"Error in stream_text: {str(e)}")
|
71 |
+
yield f"Error: {str(e)}".encode('utf-8')
|
72 |
+
|
73 |
+
@app.post("/api/landing_page_chat")
|
74 |
+
async def landing_page_chat(request: ChatRequest,):
|
75 |
+
response = StreamingResponse(
|
76 |
+
stream_text(request.messages),
|
77 |
+
)
|
78 |
+
response.headers['x-vercel-ai-data-stream'] = 'v1'
|
79 |
+
return response
|
80 |
+
|
81 |
+
@app.get("/api/hello")
|
82 |
+
async def root():
|
83 |
+
return {"message": "Hello, World!"}
|
backend/requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
fastapi[standard]
|
2 |
+
uvicorn>=0.15.0
|
3 |
+
pydantic>=1.8.0
|
4 |
+
python-dotenv>=0.19.0
|
5 |
+
openai>=1.0.0
|
6 |
+
python-dotenv>=0.19.0
|
next.config.ts
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { NextConfig } from "next";
|
2 |
+
|
3 |
+
const nextConfig: NextConfig = {
|
4 |
+
output: 'standalone',
|
5 |
+
async rewrites() {
|
6 |
+
return [
|
7 |
+
{
|
8 |
+
source: '/api/:path*',
|
9 |
+
destination: 'http://localhost:8000/api/:path*' // FastAPI backend
|
10 |
+
}
|
11 |
+
]
|
12 |
+
}
|
13 |
+
};
|
14 |
+
|
15 |
+
export default nextConfig;
|
package-lock.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
package.json
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "playgo_next",
|
3 |
+
"version": "0.1.0",
|
4 |
+
"private": true,
|
5 |
+
"scripts": {
|
6 |
+
"dev": "next dev",
|
7 |
+
"build": "next build",
|
8 |
+
"start": "next start",
|
9 |
+
"lint": "next lint"
|
10 |
+
},
|
11 |
+
"dependencies": {
|
12 |
+
"ai": "^3.4.31",
|
13 |
+
"next": "15.0.2",
|
14 |
+
"react": "19.0.0-rc-02c0e824-20241028",
|
15 |
+
"react-dom": "19.0.0-rc-02c0e824-20241028"
|
16 |
+
},
|
17 |
+
"devDependencies": {
|
18 |
+
"@types/node": "^20",
|
19 |
+
"@types/react": "^18",
|
20 |
+
"@types/react-dom": "^18",
|
21 |
+
"@types/tailwindcss": "^3.0.11",
|
22 |
+
"eslint": "^8",
|
23 |
+
"eslint-config-next": "15.0.2",
|
24 |
+
"postcss": "^8",
|
25 |
+
"tailwindcss": "^3.4.14",
|
26 |
+
"typescript": "^5"
|
27 |
+
}
|
28 |
+
}
|
pnpm-lock.yaml
ADDED
The diff for this file is too large to render.
See raw diff
|
|
postcss.config.mjs
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/** @type {import('postcss-load-config').Config} */
|
2 |
+
const config = {
|
3 |
+
plugins: {
|
4 |
+
tailwindcss: {},
|
5 |
+
},
|
6 |
+
};
|
7 |
+
|
8 |
+
export default config;
|
public/file.svg
ADDED
|
public/globe.svg
ADDED
|
public/next.svg
ADDED
|
public/vercel.svg
ADDED
|
public/window.svg
ADDED
|
tailwind.config.ts
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { Config } from "tailwindcss";
|
2 |
+
|
3 |
+
const config: Config = {
|
4 |
+
darkMode: 'class',
|
5 |
+
content: [
|
6 |
+
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
7 |
+
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
8 |
+
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
9 |
+
],
|
10 |
+
theme: {
|
11 |
+
extend: {
|
12 |
+
colors: {
|
13 |
+
// Brand colors - slightly adjusted for dark mode compatibility
|
14 |
+
primary: {
|
15 |
+
DEFAULT: '#4ECDC4',
|
16 |
+
dark: '#3DAA9D',
|
17 |
+
light: '#7EDCD6',
|
18 |
+
},
|
19 |
+
secondary: {
|
20 |
+
DEFAULT: '#FF6B6B',
|
21 |
+
dark: '#E65555',
|
22 |
+
light: '#FF8F8F',
|
23 |
+
},
|
24 |
+
accent: {
|
25 |
+
blue: '#45B7D1',
|
26 |
+
yellow: '#FDCB6E',
|
27 |
+
dark: {
|
28 |
+
blue: '#3A9BB2',
|
29 |
+
yellow: '#D4A85D',
|
30 |
+
},
|
31 |
+
},
|
32 |
+
// Semantic colors
|
33 |
+
background: {
|
34 |
+
primary: 'var(--background-primary)',
|
35 |
+
secondary: 'var(--background-secondary)',
|
36 |
+
tertiary: 'var(--background-tertiary)',
|
37 |
+
card: 'var(--card-background)',
|
38 |
+
glass: 'var(--glass-background)',
|
39 |
+
},
|
40 |
+
text: {
|
41 |
+
primary: 'var(--text-primary)',
|
42 |
+
secondary: 'var(--text-secondary)',
|
43 |
+
tertiary: 'var(--text-tertiary)',
|
44 |
+
},
|
45 |
+
border: {
|
46 |
+
DEFAULT: 'var(--border)',
|
47 |
+
secondary: 'var(--border-secondary)',
|
48 |
+
card: 'var(--card-border)',
|
49 |
+
},
|
50 |
+
},
|
51 |
+
backdropBlur: {
|
52 |
+
xs: '2px',
|
53 |
+
},
|
54 |
+
boxShadow: {
|
55 |
+
'dark': '0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.24)',
|
56 |
+
},
|
57 |
+
},
|
58 |
+
},
|
59 |
+
plugins: [],
|
60 |
+
};
|
61 |
+
export default config;
|
tsconfig.json
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"compilerOptions": {
|
3 |
+
"target": "ES2017",
|
4 |
+
"lib": ["dom", "dom.iterable", "esnext"],
|
5 |
+
"allowJs": true,
|
6 |
+
"skipLibCheck": true,
|
7 |
+
"strict": true,
|
8 |
+
"noEmit": true,
|
9 |
+
"esModuleInterop": true,
|
10 |
+
"module": "esnext",
|
11 |
+
"moduleResolution": "bundler",
|
12 |
+
"resolveJsonModule": true,
|
13 |
+
"isolatedModules": true,
|
14 |
+
"jsx": "preserve",
|
15 |
+
"incremental": true,
|
16 |
+
"plugins": [
|
17 |
+
{
|
18 |
+
"name": "next"
|
19 |
+
}
|
20 |
+
],
|
21 |
+
"paths": {
|
22 |
+
"@/*": ["./*"]
|
23 |
+
}
|
24 |
+
},
|
25 |
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
26 |
+
"exclude": ["node_modules"]
|
27 |
+
}
|