Spaces:
Running
Running
Upload 10 files
#160
by
John79722
- opened
- .env.local +1 -0
- .gitignore +0 -2
- README.md +10 -18
- index.css +321 -0
- index.html +23 -49
- index.tsx +489 -0
- metadata.json +6 -0
- package.json +7 -53
- tsconfig.json +24 -11
- vite.config.ts +15 -12
.env.local
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
GEMINI_API_KEY=PLACEHOLDER_API_KEY
|
.gitignore
CHANGED
@@ -22,5 +22,3 @@ dist-ssr
|
|
22 |
*.njsproj
|
23 |
*.sln
|
24 |
*.sw?
|
25 |
-
.env
|
26 |
-
.aider*
|
|
|
22 |
*.njsproj
|
23 |
*.sln
|
24 |
*.sw?
|
|
|
|
README.md
CHANGED
@@ -1,22 +1,14 @@
|
|
1 |
-
|
2 |
-
title: DeepSite
|
3 |
-
emoji: 🐳
|
4 |
-
colorFrom: blue
|
5 |
-
colorTo: blue
|
6 |
-
sdk: docker
|
7 |
-
pinned: true
|
8 |
-
app_port: 5173
|
9 |
-
license: mit
|
10 |
-
short_description: Generate any application with DeepSeek
|
11 |
-
models:
|
12 |
-
- deepseek-ai/DeepSeek-V3-0324
|
13 |
-
- deepseek-ai/DeepSeek-R1-0528
|
14 |
-
---
|
15 |
|
16 |
-
|
17 |
|
18 |
-
|
19 |
|
20 |
-
|
21 |
|
22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Run and deploy your AI Studio app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
+
This contains everything you need to run your app locally.
|
4 |
|
5 |
+
## Run Locally
|
6 |
|
7 |
+
**Prerequisites:** Node.js
|
8 |
|
9 |
+
|
10 |
+
1. Install dependencies:
|
11 |
+
`npm install`
|
12 |
+
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
13 |
+
3. Run the app:
|
14 |
+
`npm run dev`
|
index.css
ADDED
@@ -0,0 +1,321 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
body {
|
3 |
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
4 |
+
margin: 0;
|
5 |
+
padding: 0;
|
6 |
+
background-color: #f4f7f9;
|
7 |
+
color: #333;
|
8 |
+
line-height: 1.6;
|
9 |
+
}
|
10 |
+
|
11 |
+
#root {
|
12 |
+
display: flex;
|
13 |
+
justify-content: center;
|
14 |
+
padding: 20px;
|
15 |
+
min-height: 100vh;
|
16 |
+
box-sizing: border-box;
|
17 |
+
}
|
18 |
+
|
19 |
+
.container {
|
20 |
+
background-color: #fff;
|
21 |
+
padding: 30px;
|
22 |
+
border-radius: 12px; /* Slightly more rounded */
|
23 |
+
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1); /* Softer shadow */
|
24 |
+
width: 100%;
|
25 |
+
max-width: 960px; /* Wider for more fields */
|
26 |
+
}
|
27 |
+
|
28 |
+
h1 {
|
29 |
+
color: #2c3e50;
|
30 |
+
text-align: center;
|
31 |
+
margin-bottom: 35px; /* Increased margin */
|
32 |
+
font-size: 2.2em; /* Slightly larger */
|
33 |
+
}
|
34 |
+
|
35 |
+
.form-grid {
|
36 |
+
display: grid;
|
37 |
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); /* Adjust minmax for wider layout */
|
38 |
+
gap: 25px; /* Increased gap */
|
39 |
+
margin-bottom: 30px; /* Increased margin */
|
40 |
+
}
|
41 |
+
|
42 |
+
.form-group {
|
43 |
+
display: flex;
|
44 |
+
flex-direction: column;
|
45 |
+
}
|
46 |
+
|
47 |
+
.form-group label {
|
48 |
+
margin-bottom: 10px; /* Increased margin */
|
49 |
+
font-weight: 600;
|
50 |
+
color: #4a5568; /* Slightly different color */
|
51 |
+
}
|
52 |
+
|
53 |
+
.form-group select,
|
54 |
+
.form-group input[type="text"],
|
55 |
+
.form-group input[type="url"],
|
56 |
+
.form-group input[type="number"],
|
57 |
+
.form-group textarea {
|
58 |
+
padding: 14px; /* Increased padding */
|
59 |
+
border: 1px solid #cbd5e0; /* Softer border */
|
60 |
+
border-radius: 8px; /* More rounded */
|
61 |
+
font-size: 1rem;
|
62 |
+
background-color: #fff;
|
63 |
+
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
64 |
+
color: #2d3748;
|
65 |
+
}
|
66 |
+
|
67 |
+
.form-group select:focus,
|
68 |
+
.form-group input:focus,
|
69 |
+
.form-group textarea:focus {
|
70 |
+
outline: none;
|
71 |
+
border-color: #3182ce; /* Tailwind blue-500 */
|
72 |
+
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); /* Focus ring */
|
73 |
+
}
|
74 |
+
|
75 |
+
.form-group textarea {
|
76 |
+
min-height: 120px; /* Increased height */
|
77 |
+
resize: vertical;
|
78 |
+
}
|
79 |
+
|
80 |
+
.full-width {
|
81 |
+
grid-column: 1 / -1;
|
82 |
+
}
|
83 |
+
|
84 |
+
.output-controls {
|
85 |
+
margin-top: 10px;
|
86 |
+
}
|
87 |
+
|
88 |
+
.checkbox-group {
|
89 |
+
display: flex;
|
90 |
+
flex-direction: column; /* Stack checkboxes for better readability */
|
91 |
+
gap: 12px; /* Space between checkboxes */
|
92 |
+
padding: 10px;
|
93 |
+
background-color: #f9fafb;
|
94 |
+
border-radius: 6px;
|
95 |
+
border: 1px solid #e2e8f0;
|
96 |
+
}
|
97 |
+
|
98 |
+
.checkbox-group label {
|
99 |
+
display: flex;
|
100 |
+
align-items: center;
|
101 |
+
gap: 8px;
|
102 |
+
font-weight: 500;
|
103 |
+
color: #4a5568;
|
104 |
+
cursor: pointer;
|
105 |
+
}
|
106 |
+
|
107 |
+
.checkbox-group input[type="checkbox"] {
|
108 |
+
width: 18px; /* Custom size */
|
109 |
+
height: 18px;
|
110 |
+
accent-color: #3182ce;
|
111 |
+
}
|
112 |
+
|
113 |
+
|
114 |
+
.generate-button {
|
115 |
+
display: block;
|
116 |
+
width: 100%;
|
117 |
+
padding: 16px; /* Increased padding */
|
118 |
+
background-color: #3182ce; /* Tailwind blue-600 */
|
119 |
+
color: white;
|
120 |
+
border: none;
|
121 |
+
border-radius: 8px; /* More rounded */
|
122 |
+
font-size: 1.15rem; /* Slightly larger */
|
123 |
+
font-weight: 600;
|
124 |
+
cursor: pointer;
|
125 |
+
transition: background-color 0.2s ease-in-out;
|
126 |
+
margin-top: 15px;
|
127 |
+
margin-bottom: 35px; /* Increased margin */
|
128 |
+
}
|
129 |
+
|
130 |
+
.generate-button:hover:not(:disabled) {
|
131 |
+
background-color: #2b6cb0; /* Tailwind blue-700 */
|
132 |
+
}
|
133 |
+
|
134 |
+
.generate-button:disabled {
|
135 |
+
background-color: #a0aec0; /* Tailwind gray-500 */
|
136 |
+
cursor: not-allowed;
|
137 |
+
}
|
138 |
+
|
139 |
+
.output-section, .qa-section {
|
140 |
+
margin-top: 35px; /* Increased margin */
|
141 |
+
padding: 25px; /* Increased padding */
|
142 |
+
background-color: #f8fafc; /* Lighter gray */
|
143 |
+
border: 1px solid #e2e8f0; /* Softer border */
|
144 |
+
border-radius: 8px;
|
145 |
+
}
|
146 |
+
|
147 |
+
.output-section h2, .qa-section h2 {
|
148 |
+
margin-top: 0;
|
149 |
+
color: #2c5282; /* Tailwind blue-800 */
|
150 |
+
border-bottom: 2px solid #3182ce;
|
151 |
+
padding-bottom: 12px;
|
152 |
+
margin-bottom: 20px;
|
153 |
+
}
|
154 |
+
|
155 |
+
.variation-block {
|
156 |
+
background-color: #ffffff;
|
157 |
+
border: 1px solid #e2e8f0;
|
158 |
+
border-radius: 6px;
|
159 |
+
padding: 20px;
|
160 |
+
margin-bottom: 20px;
|
161 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
162 |
+
}
|
163 |
+
|
164 |
+
.variation-block h3 {
|
165 |
+
margin-top: 0;
|
166 |
+
margin-bottom: 15px;
|
167 |
+
color: #2d3748;
|
168 |
+
font-size: 1.2em;
|
169 |
+
}
|
170 |
+
|
171 |
+
.output-content {
|
172 |
+
white-space: pre-wrap;
|
173 |
+
word-wrap: break-word;
|
174 |
+
background-color: #fdfdff; /* Very light background */
|
175 |
+
padding: 15px;
|
176 |
+
border-radius: 4px;
|
177 |
+
border: 1px solid #edf2f7;
|
178 |
+
min-height: 100px;
|
179 |
+
font-family: 'Myanmar3', 'Padauk', 'Noto Sans Myanmar', sans-serif; /* Expanded Burmese fonts */
|
180 |
+
line-height: 1.7;
|
181 |
+
color: #1a202c; /* Darker text for readability */
|
182 |
+
}
|
183 |
+
|
184 |
+
.output-actions { /* No longer used, buttons are per variation or global export */
|
185 |
+
margin-top: 15px;
|
186 |
+
display: flex;
|
187 |
+
gap: 10px;
|
188 |
+
flex-wrap: wrap;
|
189 |
+
}
|
190 |
+
|
191 |
+
.action-button {
|
192 |
+
padding: 10px 18px; /* Adjusted padding */
|
193 |
+
border-radius: 6px; /* More rounded */
|
194 |
+
font-size: 0.95rem; /* Slightly larger */
|
195 |
+
font-weight: 500;
|
196 |
+
cursor: pointer;
|
197 |
+
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out, border-color 0.2s ease-in-out;
|
198 |
+
border: 1px solid transparent;
|
199 |
+
}
|
200 |
+
|
201 |
+
.copy-button {
|
202 |
+
background-color: #ebf8ff; /* Tailwind blue-100 */
|
203 |
+
color: #2b6cb0; /* Tailwind blue-700 */
|
204 |
+
border-color: #90cdf4; /* Tailwind blue-300 */
|
205 |
+
margin-top: 15px;
|
206 |
+
}
|
207 |
+
|
208 |
+
.copy-button:hover {
|
209 |
+
background-color: #bee3f8; /* Tailwind blue-200 */
|
210 |
+
border-color: #63b3ed; /* Tailwind blue-400 */
|
211 |
+
}
|
212 |
+
|
213 |
+
.export-button {
|
214 |
+
background-color: #38a169; /* Tailwind green-600 */
|
215 |
+
color: white;
|
216 |
+
border-color: #38a169;
|
217 |
+
}
|
218 |
+
|
219 |
+
.export-button:hover {
|
220 |
+
background-color: #2f855a; /* Tailwind green-700 */
|
221 |
+
border-color: #2f855a;
|
222 |
+
}
|
223 |
+
|
224 |
+
.export-all-button {
|
225 |
+
display: block; /* Make it full width */
|
226 |
+
width: auto; /* Or specific width if preferred */
|
227 |
+
margin-top: 25px; /* Space above */
|
228 |
+
padding: 12px 20px;
|
229 |
+
font-size: 1rem;
|
230 |
+
}
|
231 |
+
|
232 |
+
|
233 |
+
.qa-metrics {
|
234 |
+
list-style: none;
|
235 |
+
padding: 0;
|
236 |
+
}
|
237 |
+
|
238 |
+
.qa-metrics li {
|
239 |
+
margin-bottom: 12px; /* Increased margin */
|
240 |
+
padding: 10px 12px; /* Adjusted padding */
|
241 |
+
border-radius: 6px; /* More rounded */
|
242 |
+
background-color: #e9ecef;
|
243 |
+
color: #495057;
|
244 |
+
}
|
245 |
+
|
246 |
+
.qa-metrics li strong {
|
247 |
+
color: #2c3e50;
|
248 |
+
}
|
249 |
+
|
250 |
+
.loading-message, .error-message {
|
251 |
+
text-align: center;
|
252 |
+
padding: 18px; /* Increased padding */
|
253 |
+
margin-top: 25px; /* Increased margin */
|
254 |
+
border-radius: 8px; /* More rounded */
|
255 |
+
font-size: 1.05em;
|
256 |
+
}
|
257 |
+
|
258 |
+
.loading-message {
|
259 |
+
color: #2b6cb0; /* Tailwind blue-700 */
|
260 |
+
background-color: #ebf8ff; /* Tailwind blue-100 */
|
261 |
+
border: 1px solid #90cdf4; /* Tailwind blue-300 */
|
262 |
+
}
|
263 |
+
|
264 |
+
.error-message {
|
265 |
+
color: #c53030; /* Tailwind red-700 */
|
266 |
+
background-color: #fff5f5; /* Tailwind red-100 */
|
267 |
+
border: 1px solid #fc8181; /* Tailwind red-400 */
|
268 |
+
}
|
269 |
+
|
270 |
+
/* Responsive adjustments */
|
271 |
+
@media (max-width: 768px) {
|
272 |
+
.container {
|
273 |
+
padding: 20px;
|
274 |
+
}
|
275 |
+
h1 {
|
276 |
+
font-size: 1.9em;
|
277 |
+
}
|
278 |
+
.form-grid {
|
279 |
+
grid-template-columns: 1fr; /* Stack form groups on smaller screens */
|
280 |
+
}
|
281 |
+
.action-button {
|
282 |
+
flex-grow: 1;
|
283 |
+
}
|
284 |
+
.checkbox-group {
|
285 |
+
flex-direction: column; /* Ensure stacking on small screens too */
|
286 |
+
}
|
287 |
+
}
|
288 |
+
|
289 |
+
@media (max-width: 480px) {
|
290 |
+
h1 {
|
291 |
+
font-size: 1.6em;
|
292 |
+
}
|
293 |
+
.form-group select,
|
294 |
+
.form-group input,
|
295 |
+
.form-group textarea,
|
296 |
+
.generate-button,
|
297 |
+
.action-button {
|
298 |
+
font-size: 0.95rem;
|
299 |
+
}
|
300 |
+
.generate-button {
|
301 |
+
padding: 14px;
|
302 |
+
}
|
303 |
+
.action-button {
|
304 |
+
padding: 10px 14px;
|
305 |
+
}
|
306 |
+
}
|
307 |
+
|
308 |
+
/* Ensure Burmese fonts are available if not system-wide */
|
309 |
+
@font-face {
|
310 |
+
font-family: 'Noto Sans Myanmar';
|
311 |
+
src: url('https://fonts.gstatic.com/s/notosansmyanmar/v10/AlNativeKyauk.woff2') format('woff2'); /* Replace with actual path or CDN */
|
312 |
+
font-weight: normal;
|
313 |
+
font-style: normal;
|
314 |
+
}
|
315 |
+
|
316 |
+
@font-face {
|
317 |
+
font-family: 'Padauk';
|
318 |
+
src: url('https://fonts.gstatic.com/s/padauk/v7/RrQRboJg-id7OnbBa0_g3Rs.woff2') format('woff2'); /* Replace with actual path or CDN */
|
319 |
+
font-weight: normal;
|
320 |
+
font-style: normal;
|
321 |
+
}
|
index.html
CHANGED
@@ -1,52 +1,26 @@
|
|
1 |
<!DOCTYPE html>
|
2 |
<html lang="en">
|
3 |
-
|
4 |
-
<meta charset="
|
5 |
-
<
|
6 |
-
<
|
7 |
-
<
|
8 |
-
<
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
name="twitter:description"
|
23 |
-
content="DeepSite is a web development tool that
|
24 |
-
helps you build websites with AI, no code required. Let's deploy your
|
25 |
-
website with DeepSite and enjoy the magic of AI."
|
26 |
-
/>
|
27 |
-
<meta name="twitter:image" content="/banner.png" />
|
28 |
-
<meta property="og:type" content="website" />
|
29 |
-
<meta property="og:title" content="DeepSite | Build with AI ✨" />
|
30 |
-
<meta
|
31 |
-
property="og:description"
|
32 |
-
content="DeepSite is a web development tool that
|
33 |
-
helps you build websites with AI, no code required. Let's deploy your
|
34 |
-
website with DeepSite and enjoy the magic of AI."
|
35 |
-
/>
|
36 |
-
<meta property="og:image" content="/banner.png" />
|
37 |
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
38 |
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
39 |
-
<link
|
40 |
-
href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap"
|
41 |
-
rel="stylesheet"
|
42 |
-
/>
|
43 |
-
<link
|
44 |
-
href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap"
|
45 |
-
rel="stylesheet"
|
46 |
-
/>
|
47 |
-
</head>
|
48 |
-
<body>
|
49 |
<div id="root"></div>
|
50 |
-
<script type="module" src="
|
51 |
-
|
52 |
-
</
|
|
|
|
1 |
<!DOCTYPE html>
|
2 |
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Burmese Social Media Content Generator</title>
|
7 |
+
<link rel="stylesheet" href="index.css">
|
8 |
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>✍️</text></svg>">
|
9 |
+
<script type="importmap">
|
10 |
+
{
|
11 |
+
"imports": {
|
12 |
+
"react-dom/": "https://esm.sh/react-dom@^19.1.0/",
|
13 |
+
"react": "https://esm.sh/react@^19.1.0",
|
14 |
+
"@google/genai": "https://esm.sh/@google/genai@^1.3.0",
|
15 |
+
"react/": "https://esm.sh/react@^19.1.0/"
|
16 |
+
}
|
17 |
+
}
|
18 |
+
</script>
|
19 |
+
<link rel="stylesheet" href="/index.css">
|
20 |
+
</head>
|
21 |
+
<body>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
<div id="root"></div>
|
23 |
+
<script type="module" src="index.tsx"></script>
|
24 |
+
<script type="module" src="/index.tsx"></script>
|
25 |
+
</body>
|
26 |
+
</html>
|
index.tsx
ADDED
@@ -0,0 +1,489 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import React, { useState, useEffect } from 'react';
|
3 |
+
import ReactDOM from 'react-dom/client';
|
4 |
+
import { GoogleGenAI, GenerateContentResponse } from "@google/genai";
|
5 |
+
|
6 |
+
const App = () => {
|
7 |
+
const [platform, setPlatform] = useState<string>('facebook');
|
8 |
+
const [contentType, setContentType] = useState<string>('facebook-post');
|
9 |
+
const [contentLength, setContentLength] = useState<string>('standard');
|
10 |
+
const [contentCategory, setContentCategory] = useState<string>('promotion'); // Kept for general theme
|
11 |
+
const [style, setStyle] = useState<string>('friendly'); // Renamed from tone
|
12 |
+
const [productName, setProductName] = useState<string>('');
|
13 |
+
const [keyMessage, setKeyMessage] = useState<string>('');
|
14 |
+
const [facebookPageLink, setFacebookPageLink] = useState<string>('');
|
15 |
+
const [objective, setObjective] = useState<string>('brand-awareness');
|
16 |
+
const [targetAudience, setTargetAudience] = useState<string>('');
|
17 |
+
const [keywords, setKeywords] = useState<string>('');
|
18 |
+
const [includeCTA, setIncludeCTA] = useState<boolean>(true);
|
19 |
+
const [includeEmojis, setIncludeEmojis] = useState<boolean>(true);
|
20 |
+
const [includeHashtags, setIncludeHashtags] = useState<boolean>(false);
|
21 |
+
const [numVariations, setNumVariations] = useState<number>(1);
|
22 |
+
|
23 |
+
const [generatedContents, setGeneratedContents] = useState<string[]>([]);
|
24 |
+
const [isLoading, setIsLoading] = useState<boolean>(false);
|
25 |
+
const [error, setError] = useState<string | null>(null);
|
26 |
+
// const [copyButtonText, setCopyButtonText] = useState<string>('📋 Copy Content'); // Will be per variation
|
27 |
+
|
28 |
+
const [qaMetrics, setQaMetrics] = useState<{
|
29 |
+
grammar: string;
|
30 |
+
narrativeFlow: string;
|
31 |
+
culturalContext: string;
|
32 |
+
optimizationSuggestion: string;
|
33 |
+
engagementScore: string;
|
34 |
+
} | null>(null);
|
35 |
+
|
36 |
+
const API_KEY = process.env.API_KEY;
|
37 |
+
let ai: GoogleGenAI | null = null;
|
38 |
+
if (API_KEY) {
|
39 |
+
ai = new GoogleGenAI({ apiKey: API_KEY });
|
40 |
+
} else {
|
41 |
+
console.error("API_KEY environment variable not set.");
|
42 |
+
}
|
43 |
+
|
44 |
+
const platformContentTypeOptions: Record<string, Array<{ value: string; label: string }>> = {
|
45 |
+
facebook: [
|
46 |
+
{ value: 'facebook-post', label: 'Post (Text, Multimedia)' },
|
47 |
+
{ value: 'facebook-story-text', label: 'Story (Text-based)' },
|
48 |
+
{ value: 'facebook-ad-copy', label: 'Ad Copy' },
|
49 |
+
{ value: 'facebook-reel-script', label: 'Reel Script' },
|
50 |
+
],
|
51 |
+
instagram: [
|
52 |
+
{ value: 'instagram-caption', label: 'Caption (for Post/Reel)' },
|
53 |
+
{ value: 'instagram-story-text', label: 'Story (Text-based)' },
|
54 |
+
{ value: 'instagram-reel-script', label: 'Reel Script' },
|
55 |
+
{ value: 'instagram-ad-copy', label: 'Ad Copy' },
|
56 |
+
],
|
57 |
+
tiktok: [
|
58 |
+
{ value: 'tiktok-video-script', label: 'Video Script' },
|
59 |
+
{ value: 'tiktok-ad-script', label: 'Ad Script' },
|
60 |
+
],
|
61 |
+
youtube: [
|
62 |
+
{ value: 'youtube-video-script', label: 'Video Script (Main)' },
|
63 |
+
{ value: 'youtube-short-script', label: 'Short Script' },
|
64 |
+
{ value: 'youtube-community-post', label: 'Community Post' },
|
65 |
+
{ value: 'youtube-video-description', label: 'Video Description' },
|
66 |
+
],
|
67 |
+
viber: [
|
68 |
+
{ value: 'viber-broadcast', label: 'Broadcast Message' },
|
69 |
+
{ value: 'viber-channel-update', label: 'Channel Update' },
|
70 |
+
],
|
71 |
+
telegram: [
|
72 |
+
{ value: 'telegram-channel-post', label: 'Channel Post' },
|
73 |
+
{ value: 'telegram-group-message', label: 'Group Message' },
|
74 |
+
]
|
75 |
+
};
|
76 |
+
|
77 |
+
const simplifiedLengthOptions = [
|
78 |
+
{ value: 'concise', label: 'Concise / Short' },
|
79 |
+
{ value: 'standard', label: 'Standard / Medium' },
|
80 |
+
{ value: 'detailed', label: 'Detailed / Long' },
|
81 |
+
];
|
82 |
+
|
83 |
+
const styleOptions = [
|
84 |
+
{ value: 'polite', label: 'Polite (Formal Burmese)' },
|
85 |
+
{ value: 'friendly', label: 'Friendly (Casual Burmese)' },
|
86 |
+
{ value: 'professional', label: 'Professional (Business Burmese)'},
|
87 |
+
{ value: 'humorous', label: 'Humorous' },
|
88 |
+
{ value: 'persuasive', label: 'Persuasive' },
|
89 |
+
{ value: 'urgent', label: 'Urgent' },
|
90 |
+
{ value: 'empathetic', label: 'Empathetic' },
|
91 |
+
];
|
92 |
+
|
93 |
+
const objectiveOptions = [
|
94 |
+
{ value: 'brand-awareness', label: 'Brand Awareness' },
|
95 |
+
{ value: 'lead-generation', label: 'Lead Generation' },
|
96 |
+
{ value: 'sales', label: 'Sales / Conversions' },
|
97 |
+
{ value: 'engagement', label: 'Engagement (Likes, Comments, Shares)' },
|
98 |
+
{ value: 'educate', label: 'Educate Audience' },
|
99 |
+
{ value: 'event-promotion', label: 'Event Promotion'},
|
100 |
+
];
|
101 |
+
|
102 |
+
useEffect(() => {
|
103 |
+
const currentPlatformOptions = platformContentTypeOptions[platform] || [];
|
104 |
+
if (currentPlatformOptions.length > 0 && !currentPlatformOptions.find(opt => opt.value === contentType)) {
|
105 |
+
setContentType(currentPlatformOptions[0].value);
|
106 |
+
}
|
107 |
+
// Ensure contentLength is valid for the simplified options
|
108 |
+
if (!simplifiedLengthOptions.find(opt => opt.value === contentLength)) {
|
109 |
+
setContentLength(simplifiedLengthOptions[0].value); // Default to first option if current is invalid
|
110 |
+
}
|
111 |
+
}, [platform, contentType, contentLength]); // Added contentLength to dependencies
|
112 |
+
|
113 |
+
const handleGenerateContent = async () => {
|
114 |
+
if (!ai) {
|
115 |
+
setError("Gemini AI client is not initialized. Check API Key configuration.");
|
116 |
+
return;
|
117 |
+
}
|
118 |
+
if (!productName.trim() && !keyMessage.trim() && contentCategory !== 'seasonal' && objective !== 'brand-awareness') {
|
119 |
+
setError("Please enter Product/Service Name or Key Message/Details, or select a broader category/objective.");
|
120 |
+
return;
|
121 |
+
}
|
122 |
+
|
123 |
+
|
124 |
+
setIsLoading(true);
|
125 |
+
setError(null);
|
126 |
+
setGeneratedContents([]);
|
127 |
+
setQaMetrics(null);
|
128 |
+
|
129 |
+
let lengthInstruction = "";
|
130 |
+
switch (contentLength) {
|
131 |
+
case 'concise':
|
132 |
+
lengthInstruction = "a concise and short piece of content.";
|
133 |
+
break;
|
134 |
+
case 'standard':
|
135 |
+
lengthInstruction = "a standard length piece of content, providing adequate detail.";
|
136 |
+
break;
|
137 |
+
case 'detailed':
|
138 |
+
lengthInstruction = "a detailed and comprehensive piece of content.";
|
139 |
+
break;
|
140 |
+
default:
|
141 |
+
lengthInstruction = "a standard length piece of content.";
|
142 |
+
}
|
143 |
+
|
144 |
+
const selectedContentTypeLabel = platformContentTypeOptions[platform]?.find(opt => opt.value === contentType)?.label || contentType;
|
145 |
+
const selectedStyleLabel = styleOptions.find(opt => opt.value === style)?.label || style;
|
146 |
+
const selectedObjectiveLabel = objectiveOptions.find(opt => opt.value === objective)?.label || objective;
|
147 |
+
const capitalizedCategory = contentCategory.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
148 |
+
|
149 |
+
|
150 |
+
const prompt = `
|
151 |
+
You are an expert Burmese social media copywriter with deep understanding of native Burmese speaker patterns and cultural nuances relevant to Myanmar.
|
152 |
+
Your style should emulate successful content found on Myanmar social media business pages and promotional posts.
|
153 |
+
|
154 |
+
Generate ${numVariations} distinct variation(s) of content based on the following specifications:
|
155 |
+
- Language: Burmese
|
156 |
+
- Platform: ${platform.charAt(0).toUpperCase() + platform.slice(1)}
|
157 |
+
- Content Type: ${selectedContentTypeLabel}
|
158 |
+
- Desired Content Scope/Length: Generate ${lengthInstruction}
|
159 |
+
- Primary Objective/Goal: ${selectedObjectiveLabel}
|
160 |
+
- Content Theme/Category: ${capitalizedCategory}
|
161 |
+
- Style: ${selectedStyleLabel} (translate this style appropriately into Burmese)
|
162 |
+
${productName.trim() ? `- Product/Service Name: "${productName}"` : ''}
|
163 |
+
${keyMessage.trim() ? `- Key Message/Details: "${keyMessage}"` : ''}
|
164 |
+
${targetAudience.trim() ? `- Target Audience: "${targetAudience}"` : ''}
|
165 |
+
${keywords.trim() ? `- Keywords to incorporate (if natural): "${keywords}"` : ''}
|
166 |
+
${facebookPageLink.trim() ? `- Business Context/Reference Facebook Page: ${facebookPageLink.trim()}. (Adapt to their style if possible)` : ''}
|
167 |
+
|
168 |
+
Output Instructions:
|
169 |
+
- Ensure each variation is clearly separated by "---VARIATION SEPARATOR---". If only 1 variation is requested, this separator is not needed.
|
170 |
+
- For each variation:
|
171 |
+
${includeEmojis ? "- Seamlessly integrate relevant Burmese-style emojis." : "- Do not use emojis unless absolutely essential for the content type."}
|
172 |
+
${includeCTA ? `- Include a clear and natural-sounding Call To Action (CTA) suitable for the platform and objective.` : "- A direct Call To Action is not a priority unless inherent to the content type."}
|
173 |
+
${includeHashtags ? `- Include a few relevant and effective hashtags (Burmese or English as appropriate).` : "- Do not include hashtags unless specifically requested by the content type itself."}
|
174 |
+
- Ensure the output is ONLY the generated Burmese content, ready to be used.
|
175 |
+
- Focus on creating engaging, grammatically correct, and culturally appropriate content for the Myanmar market.
|
176 |
+
- If the content type is a script (e.g., Reel Script, Video Script), format it appropriately with scene descriptions or speaker cues if applicable.
|
177 |
+
|
178 |
+
Please provide exactly ${numVariations} variation(s).
|
179 |
+
`;
|
180 |
+
|
181 |
+
try {
|
182 |
+
const response: GenerateContentResponse = await ai.models.generateContent({
|
183 |
+
model: 'gemini-2.5-flash-preview-04-17',
|
184 |
+
contents: prompt,
|
185 |
+
});
|
186 |
+
|
187 |
+
const text = response.text;
|
188 |
+
if (numVariations > 1) {
|
189 |
+
setGeneratedContents(text.split("---VARIATION SEPARATOR---").map(v => v.trim()).filter(v => v));
|
190 |
+
} else {
|
191 |
+
setGeneratedContents([text.trim()]);
|
192 |
+
}
|
193 |
+
|
194 |
+
|
195 |
+
// Simulate QA metrics (applied to the first variation for simplicity)
|
196 |
+
setQaMetrics({
|
197 |
+
grammar: Math.random() > 0.15 ? '✅ Excellent Burmese Grammar' : '⚠️ Review Grammar Advised',
|
198 |
+
narrativeFlow: Math.random() > 0.2 ? '👍 Coherent Narrative Flow' : '🤔 Flow Could Be Smoother',
|
199 |
+
culturalContext: Math.random() > 0.1 ? '✅ Culturally Appropriate for Myanmar' : '❓ Verify Cultural Context Locally',
|
200 |
+
optimizationSuggestion: ['Consider A/B testing variations.', 'Analyze competitor content for this objective.', 'Ensure the visual pairing matches the text.'][Math.floor(Math.random() * 3)],
|
201 |
+
engagementScore: `Predicted: ${Math.floor(Math.random() * 31) + 65}% Engagement`
|
202 |
+
});
|
203 |
+
|
204 |
+
} catch (e: any) {
|
205 |
+
console.error("Error generating content:", e);
|
206 |
+
setError(`Failed to generate content. ${e.message || 'Please try again.'}`);
|
207 |
+
setGeneratedContents([]);
|
208 |
+
} finally {
|
209 |
+
setIsLoading(false);
|
210 |
+
}
|
211 |
+
};
|
212 |
+
|
213 |
+
const handleCopyVariation = (textToCopy: string, variationIndex: number) => {
|
214 |
+
navigator.clipboard.writeText(textToCopy)
|
215 |
+
.then(() => {
|
216 |
+
// Visually indicate copy success, perhaps on the button itself
|
217 |
+
const button = document.getElementById(`copy-button-${variationIndex}`);
|
218 |
+
if (button) {
|
219 |
+
const originalText = button.innerHTML;
|
220 |
+
button.innerHTML = '✅ Copied!';
|
221 |
+
setTimeout(() => { button.innerHTML = originalText; }, 2000);
|
222 |
+
}
|
223 |
+
})
|
224 |
+
.catch(err => {
|
225 |
+
console.error('Failed to copy variation: ', err);
|
226 |
+
setError('Failed to copy content to clipboard.');
|
227 |
+
});
|
228 |
+
};
|
229 |
+
|
230 |
+
const handleExportContent = () => {
|
231 |
+
if (generatedContents.length > 0) {
|
232 |
+
const allContent = generatedContents.map((content, index) => {
|
233 |
+
return `--- Variation ${index + 1} ---\n${content}\n\n`;
|
234 |
+
}).join('');
|
235 |
+
const blob = new Blob([allContent], { type: 'text/plain;charset=utf-8' });
|
236 |
+
const link = document.createElement('a');
|
237 |
+
link.href = URL.createObjectURL(blob);
|
238 |
+
link.download = 'burmese_social_media_content.txt';
|
239 |
+
document.body.appendChild(link);
|
240 |
+
link.click();
|
241 |
+
document.body.removeChild(link);
|
242 |
+
URL.revokeObjectURL(link.href);
|
243 |
+
}
|
244 |
+
};
|
245 |
+
|
246 |
+
const currentPlatformContentTypeOptions = platformContentTypeOptions[platform] || [];
|
247 |
+
|
248 |
+
return (
|
249 |
+
<div className="container">
|
250 |
+
<h1>✍️ Burmese Social Media Content Generator V2</h1>
|
251 |
+
|
252 |
+
{!API_KEY && (
|
253 |
+
<div className="error-message" role="alert">
|
254 |
+
API Key is not configured. This application requires the API_KEY environment variable to be set to function.
|
255 |
+
</div>
|
256 |
+
)}
|
257 |
+
|
258 |
+
<div className="form-grid">
|
259 |
+
{/* Platform */}
|
260 |
+
<div className="form-group">
|
261 |
+
<label htmlFor="platform">Platform</label>
|
262 |
+
<select id="platform" value={platform} onChange={(e) => setPlatform(e.target.value)} aria-label="Platform">
|
263 |
+
{Object.keys(platformContentTypeOptions).map(plat => (
|
264 |
+
<option key={plat} value={plat}>{plat.charAt(0).toUpperCase() + plat.slice(1)}</option>
|
265 |
+
))}
|
266 |
+
</select>
|
267 |
+
</div>
|
268 |
+
|
269 |
+
{/* Content Type (Dynamic) */}
|
270 |
+
<div className="form-group">
|
271 |
+
<label htmlFor="contentType">Content Type</label>
|
272 |
+
<select
|
273 |
+
id="contentType"
|
274 |
+
value={contentType}
|
275 |
+
onChange={(e) => setContentType(e.target.value)}
|
276 |
+
aria-label="Content Type"
|
277 |
+
disabled={currentPlatformContentTypeOptions.length === 0}
|
278 |
+
>
|
279 |
+
{currentPlatformContentTypeOptions.map(option => (
|
280 |
+
<option key={option.value} value={option.value}>{option.label}</option>
|
281 |
+
))}
|
282 |
+
</select>
|
283 |
+
</div>
|
284 |
+
|
285 |
+
{/* Content Length/Scope */}
|
286 |
+
<div className="form-group">
|
287 |
+
<label htmlFor="contentLength">Content Length/Scope</label>
|
288 |
+
<select id="contentLength" value={contentLength} onChange={(e) => setContentLength(e.target.value)} aria-label="Content Length or Scope">
|
289 |
+
{simplifiedLengthOptions.map(option => (
|
290 |
+
<option key={option.value} value={option.value}>{option.label}</option>
|
291 |
+
))}
|
292 |
+
</select>
|
293 |
+
</div>
|
294 |
+
|
295 |
+
{/* Objective/Goal */}
|
296 |
+
<div className="form-group">
|
297 |
+
<label htmlFor="objective">Objective/Goal</label>
|
298 |
+
<select id="objective" value={objective} onChange={(e) => setObjective(e.target.value)} aria-label="Objective or Goal">
|
299 |
+
{objectiveOptions.map(option => (
|
300 |
+
<option key={option.value} value={option.value}>{option.label}</option>
|
301 |
+
))}
|
302 |
+
</select>
|
303 |
+
</div>
|
304 |
+
|
305 |
+
{/* Style (was Tone) */}
|
306 |
+
<div className="form-group">
|
307 |
+
<label htmlFor="style">Style</label>
|
308 |
+
<select id="style" value={style} onChange={(e) => setStyle(e.target.value)} aria-label="Content Style">
|
309 |
+
{styleOptions.map(option => (
|
310 |
+
<option key={option.value} value={option.value}>{option.label}</option>
|
311 |
+
))}
|
312 |
+
</select>
|
313 |
+
</div>
|
314 |
+
|
315 |
+
{/* Content Category (General Theme) */}
|
316 |
+
<div className="form-group">
|
317 |
+
<label htmlFor="contentCategory">Content Theme/Category</label>
|
318 |
+
<select id="contentCategory" value={contentCategory} onChange={(e) => setContentCategory(e.target.value)} aria-label="Content Category or Theme">
|
319 |
+
<option value="promotion">Product/Service Promotions</option>
|
320 |
+
<option value="info">Information/Specifications</option>
|
321 |
+
<option value="seasonal">Seasonal Greetings/Wishes</option>
|
322 |
+
<option value="explainer">Explainer Content</option>
|
323 |
+
<option value="sales">Sales Announcements</option>
|
324 |
+
<option value="creative-ad">Creative Story/Advertisement</option>
|
325 |
+
<option value="tutorial">Tutorial/How-to</option>
|
326 |
+
<option value="behind-the-scenes">Behind The Scenes</option>
|
327 |
+
<option value="user-generated-content">User-Generated Content Spotlight</option>
|
328 |
+
<option value="question-answer">Question & Answer</option>
|
329 |
+
</select>
|
330 |
+
</div>
|
331 |
+
|
332 |
+
|
333 |
+
{/* Product/Service Name */}
|
334 |
+
<div className="form-group full-width">
|
335 |
+
<label htmlFor="productName">Product/Service Name (Optional if covered by Key Message)</label>
|
336 |
+
<input
|
337 |
+
type="text"
|
338 |
+
id="productName"
|
339 |
+
value={productName}
|
340 |
+
onChange={(e) => setProductName(e.target.value)}
|
341 |
+
placeholder="e.g., Shwe Coffee Mix, Yangon Tech Services"
|
342 |
+
aria-label="Product or Service Name"
|
343 |
+
/>
|
344 |
+
</div>
|
345 |
+
|
346 |
+
{/* Key Message/Details */}
|
347 |
+
<div className="form-group full-width">
|
348 |
+
<label htmlFor="keyMessage">Key Message/Details/Topic</label>
|
349 |
+
<textarea
|
350 |
+
id="keyMessage"
|
351 |
+
value={keyMessage}
|
352 |
+
onChange={(e) => setKeyMessage(e.target.value)}
|
353 |
+
placeholder="e.g., Special discount, new features, benefits, topic of the content..."
|
354 |
+
aria-label="Key Message, Details or Topic"
|
355 |
+
aria-required="true"
|
356 |
+
></textarea>
|
357 |
+
</div>
|
358 |
+
|
359 |
+
{/* Target Audience */}
|
360 |
+
<div className="form-group full-width">
|
361 |
+
<label htmlFor="targetAudience">Target Audience (Optional)</label>
|
362 |
+
<textarea
|
363 |
+
id="targetAudience"
|
364 |
+
value={targetAudience}
|
365 |
+
onChange={(e) => setTargetAudience(e.target.value)}
|
366 |
+
placeholder="e.g., Young adults in Yangon, small business owners, families with children"
|
367 |
+
aria-label="Target Audience"
|
368 |
+
></textarea>
|
369 |
+
</div>
|
370 |
+
|
371 |
+
{/* Keywords */}
|
372 |
+
<div className="form-group full-width">
|
373 |
+
<label htmlFor="keywords">Keywords (Comma separated, Optional)</label>
|
374 |
+
<input
|
375 |
+
type="text"
|
376 |
+
id="keywords"
|
377 |
+
value={keywords}
|
378 |
+
onChange={(e) => setKeywords(e.target.value)}
|
379 |
+
placeholder="e.g., organic, sustainable, best coffee, tech solutions"
|
380 |
+
aria-label="Keywords, comma separated"
|
381 |
+
/>
|
382 |
+
</div>
|
383 |
+
|
384 |
+
<div className="form-group full-width">
|
385 |
+
<label htmlFor="facebookPageLink">Business Facebook Page Link (Optional for style reference)</label>
|
386 |
+
<input
|
387 |
+
type="url"
|
388 |
+
id="facebookPageLink"
|
389 |
+
value={facebookPageLink}
|
390 |
+
onChange={(e) => setFacebookPageLink(e.target.value)}
|
391 |
+
placeholder="e.g., https://www.facebook.com/yourbusinesspage"
|
392 |
+
aria-label="Facebook Page Link (Optional for style reference)"
|
393 |
+
/>
|
394 |
+
</div>
|
395 |
+
|
396 |
+
{/* Output Controls */}
|
397 |
+
<div className="form-group full-width output-controls">
|
398 |
+
<label>Output Controls:</label>
|
399 |
+
<div className="checkbox-group">
|
400 |
+
<label htmlFor="includeCTA">
|
401 |
+
<input type="checkbox" id="includeCTA" checked={includeCTA} onChange={(e) => setIncludeCTA(e.target.checked)} />
|
402 |
+
Include Call-to-Action (CTA)
|
403 |
+
</label>
|
404 |
+
<label htmlFor="includeEmojis">
|
405 |
+
<input type="checkbox" id="includeEmojis" checked={includeEmojis} onChange={(e) => setIncludeEmojis(e.target.checked)} />
|
406 |
+
Include Emojis
|
407 |
+
</label>
|
408 |
+
<label htmlFor="includeHashtags">
|
409 |
+
<input type="checkbox" id="includeHashtags" checked={includeHashtags} onChange={(e) => setIncludeHashtags(e.target.checked)} />
|
410 |
+
Include Hashtags
|
411 |
+
</label>
|
412 |
+
</div>
|
413 |
+
</div>
|
414 |
+
{/* Number of Variations */}
|
415 |
+
<div className="form-group">
|
416 |
+
<label htmlFor="numVariations">Number of Variations (1-3)</label>
|
417 |
+
<input
|
418 |
+
type="number"
|
419 |
+
id="numVariations"
|
420 |
+
value={numVariations}
|
421 |
+
onChange={(e) => setNumVariations(Math.max(1, Math.min(3, parseInt(e.target.value, 10) || 1)))}
|
422 |
+
min="1"
|
423 |
+
max="3"
|
424 |
+
aria-label="Number of content variations"
|
425 |
+
/>
|
426 |
+
</div>
|
427 |
+
</div>
|
428 |
+
|
429 |
+
|
430 |
+
<button
|
431 |
+
className="generate-button"
|
432 |
+
onClick={handleGenerateContent}
|
433 |
+
disabled={isLoading || !API_KEY}
|
434 |
+
aria-label="Generate Social Media Content"
|
435 |
+
>
|
436 |
+
{isLoading ? 'Generating...' : `✨ Generate ${numVariations} Variation(s)`}
|
437 |
+
</button>
|
438 |
+
|
439 |
+
{error && <div className="error-message" role="alert">{error}</div>}
|
440 |
+
|
441 |
+
{isLoading && <div className="loading-message" aria-busy="true" aria-live="assertive">Generating Burmese content, please wait...</div>}
|
442 |
+
|
443 |
+
{generatedContents.length > 0 && (
|
444 |
+
<div className="output-section">
|
445 |
+
<h2>Generated Content (Burmese)</h2>
|
446 |
+
{generatedContents.map((content, index) => (
|
447 |
+
<div key={index} className="variation-block">
|
448 |
+
<h3>Variation {index + 1}</h3>
|
449 |
+
<div className="output-content" aria-live="polite">{content}</div>
|
450 |
+
<button
|
451 |
+
id={`copy-button-${index}`}
|
452 |
+
onClick={() => handleCopyVariation(content, index)}
|
453 |
+
className="action-button copy-button"
|
454 |
+
aria-label={`Copy variation ${index + 1} to clipboard`}
|
455 |
+
>
|
456 |
+
📋 Copy Variation {index + 1}
|
457 |
+
</button>
|
458 |
+
</div>
|
459 |
+
))}
|
460 |
+
{generatedContents.length > 0 && (
|
461 |
+
<button
|
462 |
+
onClick={handleExportContent}
|
463 |
+
className="action-button export-button export-all-button"
|
464 |
+
aria-label="Export all generated variations as a text file"
|
465 |
+
>
|
466 |
+
💾 Export All as .txt
|
467 |
+
</button>
|
468 |
+
)}
|
469 |
+
</div>
|
470 |
+
)}
|
471 |
+
|
472 |
+
{qaMetrics && generatedContents.length > 0 && (
|
473 |
+
<div className="qa-section">
|
474 |
+
<h2>Content Quality Assurance (Simulated for Variation 1)</h2>
|
475 |
+
<ul className="qa-metrics">
|
476 |
+
<li><strong>Grammar:</strong> {qaMetrics.grammar}</li>
|
477 |
+
<li><strong>Narrative Flow:</strong> {qaMetrics.narrativeFlow}</li>
|
478 |
+
<li><strong>Cultural Context:</strong> {qaMetrics.culturalContext}</li>
|
479 |
+
<li><strong>Optimization Suggestion:</strong> {qaMetrics.optimizationSuggestion}</li>
|
480 |
+
<li><strong>Engagement Predictor:</strong> {qaMetrics.engagementScore}</li>
|
481 |
+
</ul>
|
482 |
+
</div>
|
483 |
+
)}
|
484 |
+
</div>
|
485 |
+
);
|
486 |
+
};
|
487 |
+
|
488 |
+
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
489 |
+
root.render(<React.StrictMode><App /></React.StrictMode>);
|
metadata.json
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "Burmese Social Media Content Generator",
|
3 |
+
"description": "Generates Burmese social media content (text posts, video scripts) using Gemini AI, with options for category, tone, and length. Includes simulated content quality assurance.",
|
4 |
+
"requestFramePermissions": [],
|
5 |
+
"prompt": ""
|
6 |
+
}
|
package.json
CHANGED
@@ -1,67 +1,21 @@
|
|
1 |
{
|
2 |
-
"name": "
|
3 |
"private": true,
|
4 |
"version": "0.0.0",
|
5 |
"type": "module",
|
6 |
"scripts": {
|
7 |
"dev": "vite",
|
8 |
-
"build": "
|
9 |
-
"
|
10 |
-
"preview": "vite preview",
|
11 |
-
"start": "node server.js"
|
12 |
},
|
13 |
"dependencies": {
|
14 |
-
"
|
15 |
-
"
|
16 |
-
"@
|
17 |
-
"@radix-ui/react-avatar": "^1.1.10",
|
18 |
-
"@radix-ui/react-dialog": "^1.1.14",
|
19 |
-
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
20 |
-
"@radix-ui/react-popover": "^1.1.14",
|
21 |
-
"@radix-ui/react-select": "^2.2.5",
|
22 |
-
"@radix-ui/react-slot": "^1.2.3",
|
23 |
-
"@radix-ui/react-switch": "^1.2.5",
|
24 |
-
"@radix-ui/react-tabs": "^1.1.12",
|
25 |
-
"@radix-ui/react-toggle": "^1.1.9",
|
26 |
-
"@radix-ui/react-toggle-group": "^1.1.10",
|
27 |
-
"@radix-ui/react-tooltip": "^1.2.7",
|
28 |
-
"@tailwindcss/vite": "^4.0.15",
|
29 |
-
"@xenova/transformers": "^2.17.2",
|
30 |
-
"body-parser": "^1.20.3",
|
31 |
-
"class-variance-authority": "^0.7.1",
|
32 |
-
"classnames": "^2.5.1",
|
33 |
-
"clsx": "^2.1.1",
|
34 |
-
"cookie-parser": "^1.4.7",
|
35 |
-
"dotenv": "^16.4.7",
|
36 |
-
"express": "^4.21.2",
|
37 |
-
"lucide-react": "^0.511.0",
|
38 |
-
"next-themes": "^0.4.6",
|
39 |
-
"react": "^19.0.0",
|
40 |
-
"react-dom": "^19.0.0",
|
41 |
-
"react-icons": "^5.5.0",
|
42 |
-
"react-markdown": "^10.1.0",
|
43 |
-
"react-speech-recognition": "^4.0.0",
|
44 |
-
"react-toastify": "^11.0.5",
|
45 |
-
"react-use": "^17.6.0",
|
46 |
-
"sonner": "^2.0.3",
|
47 |
-
"tailwind-merge": "^3.3.0",
|
48 |
-
"tailwindcss": "^4.0.15"
|
49 |
},
|
50 |
"devDependencies": {
|
51 |
-
"@
|
52 |
-
"@types/express": "^5.0.1",
|
53 |
-
"@types/node": "^22.15.21",
|
54 |
-
"@types/react": "^19.0.10",
|
55 |
-
"@types/react-dom": "^19.0.4",
|
56 |
-
"@types/react-speech-recognition": "^3.9.6",
|
57 |
-
"@vitejs/plugin-react": "^4.3.4",
|
58 |
-
"eslint": "^9.21.0",
|
59 |
-
"eslint-plugin-react-hooks": "^5.1.0",
|
60 |
-
"eslint-plugin-react-refresh": "^0.4.19",
|
61 |
-
"globals": "^15.15.0",
|
62 |
-
"tw-animate-css": "^1.3.0",
|
63 |
"typescript": "~5.7.2",
|
64 |
-
"typescript-eslint": "^8.24.1",
|
65 |
"vite": "^6.2.0"
|
66 |
}
|
67 |
}
|
|
|
1 |
{
|
2 |
+
"name": "burmese-social-media-content-generator",
|
3 |
"private": true,
|
4 |
"version": "0.0.0",
|
5 |
"type": "module",
|
6 |
"scripts": {
|
7 |
"dev": "vite",
|
8 |
+
"build": "vite build",
|
9 |
+
"preview": "vite preview"
|
|
|
|
|
10 |
},
|
11 |
"dependencies": {
|
12 |
+
"react-dom": "^19.1.0",
|
13 |
+
"react": "^19.1.0",
|
14 |
+
"@google/genai": "^1.3.0"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
},
|
16 |
"devDependencies": {
|
17 |
+
"@types/node": "^22.14.0",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
"typescript": "~5.7.2",
|
|
|
19 |
"vite": "^6.2.0"
|
20 |
}
|
21 |
}
|
tsconfig.json
CHANGED
@@ -1,17 +1,30 @@
|
|
1 |
{
|
2 |
-
"files": [],
|
3 |
-
"references": [
|
4 |
-
{
|
5 |
-
"path": "./tsconfig.app.json"
|
6 |
-
},
|
7 |
-
{
|
8 |
-
"path": "./tsconfig.node.json"
|
9 |
-
}
|
10 |
-
],
|
11 |
"compilerOptions": {
|
12 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
"paths": {
|
14 |
-
"@/*":
|
15 |
}
|
16 |
}
|
17 |
}
|
|
|
1 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
"compilerOptions": {
|
3 |
+
"target": "ES2020",
|
4 |
+
"experimentalDecorators": true,
|
5 |
+
"useDefineForClassFields": false,
|
6 |
+
"module": "ESNext",
|
7 |
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
8 |
+
"skipLibCheck": true,
|
9 |
+
|
10 |
+
/* Bundler mode */
|
11 |
+
"moduleResolution": "bundler",
|
12 |
+
"allowImportingTsExtensions": true,
|
13 |
+
"isolatedModules": true,
|
14 |
+
"moduleDetection": "force",
|
15 |
+
"noEmit": true,
|
16 |
+
"allowJs": true,
|
17 |
+
"jsx": "react-jsx",
|
18 |
+
|
19 |
+
/* Linting */
|
20 |
+
"strict": true,
|
21 |
+
"noUnusedLocals": true,
|
22 |
+
"noUnusedParameters": true,
|
23 |
+
"noFallthroughCasesInSwitch": true,
|
24 |
+
"noUncheckedSideEffectImports": true,
|
25 |
+
|
26 |
"paths": {
|
27 |
+
"@/*" : ["./*"]
|
28 |
}
|
29 |
}
|
30 |
}
|
vite.config.ts
CHANGED
@@ -1,14 +1,17 @@
|
|
1 |
-
import path from
|
2 |
-
import
|
3 |
-
import react from "@vitejs/plugin-react";
|
4 |
-
import { defineConfig } from "vite";
|
5 |
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
14 |
});
|
|
|
1 |
+
import path from 'path';
|
2 |
+
import { defineConfig, loadEnv } from 'vite';
|
|
|
|
|
3 |
|
4 |
+
export default defineConfig(({ mode }) => {
|
5 |
+
const env = loadEnv(mode, '.', '');
|
6 |
+
return {
|
7 |
+
define: {
|
8 |
+
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
9 |
+
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
10 |
+
},
|
11 |
+
resolve: {
|
12 |
+
alias: {
|
13 |
+
'@': path.resolve(__dirname, '.'),
|
14 |
+
}
|
15 |
+
}
|
16 |
+
};
|
17 |
});
|