Spaces:
Running
Running
طيب رائع لاكن انظر نريد مثلا ان البلاد تتحدد من قائمه بلدان وليس المسخد
Browse files- src/ai/flows/ai-generate-image-query.ts +14 -11
- src/ai/flows/ai-generate-post-ideas.ts +14 -8
- src/app/login/page.tsx +35 -5
- src/lib/countries.ts +33 -0
src/ai/flows/ai-generate-image-query.ts
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
|
|
| 1 |
'use server';
|
| 2 |
/**
|
| 3 |
* @fileOverview An AI agent to generate an image search query from post content.
|
| 4 |
-
*
|
| 5 |
-
* - generateImageQuery - A function that creates an image query.
|
| 6 |
-
* - GenerateImageQueryInput - The input type for the function.
|
| 7 |
-
* - GenerateImageQueryOutput - The return type for the function.
|
| 8 |
*/
|
| 9 |
|
| 10 |
import { safeGenerateChat } from '@/lib/gemini-client';
|
| 11 |
import type { GenerateImageQueryInput, GenerateImageQueryOutput } from './types';
|
| 12 |
|
| 13 |
-
const promptTemplate = (content: string) => `Based on the following social media post content, generate a very precise and descriptive English search query for an image that
|
| 14 |
|
| 15 |
-
**STRICT RULES:**
|
| 16 |
-
1.
|
| 17 |
-
2.
|
| 18 |
-
3.
|
| 19 |
-
4. Only return the search query text, with no additional explanation
|
| 20 |
|
| 21 |
**Post Content:**
|
| 22 |
"${content}"
|
|
@@ -28,5 +26,10 @@ export async function generateImageQuery(
|
|
| 28 |
): Promise<GenerateImageQueryOutput> {
|
| 29 |
const prompt = promptTemplate(input.content);
|
| 30 |
const query = await safeGenerateChat(prompt);
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
}
|
|
|
|
| 1 |
+
|
| 2 |
'use server';
|
| 3 |
/**
|
| 4 |
* @fileOverview An AI agent to generate an image search query from post content.
|
| 5 |
+
* Strictly enforced "NO HUMAN" policy for imagery.
|
|
|
|
|
|
|
|
|
|
| 6 |
*/
|
| 7 |
|
| 8 |
import { safeGenerateChat } from '@/lib/gemini-client';
|
| 9 |
import type { GenerateImageQueryInput, GenerateImageQueryOutput } from './types';
|
| 10 |
|
| 11 |
+
const promptTemplate = (content: string) => `Based on the following social media post content, generate a very precise and descriptive English search query for an image that fits the post's mood.
|
| 12 |
|
| 13 |
+
**STRICT RULES - NO HUMANS:**
|
| 14 |
+
1. The query MUST NOT contain any words related to humans, people, faces, eyes, hands, men, women, children, or any biological human parts.
|
| 15 |
+
2. Focus strictly on: nature, landscapes, inanimate objects, architecture, abstract patterns, animals, or celestial bodies.
|
| 16 |
+
3. If the post content is too abstract, return "abstract textures and colors (no people)".
|
| 17 |
+
4. Only return the search query text, with no additional explanation.
|
| 18 |
|
| 19 |
**Post Content:**
|
| 20 |
"${content}"
|
|
|
|
| 26 |
): Promise<GenerateImageQueryOutput> {
|
| 27 |
const prompt = promptTemplate(input.content);
|
| 28 |
const query = await safeGenerateChat(prompt);
|
| 29 |
+
|
| 30 |
+
// Safety fallback: strip any human-related words just in case
|
| 31 |
+
const humanWords = /\b(man|woman|person|people|face|boy|girl|human|body|hands|eyes|child|kid)\b/gi;
|
| 32 |
+
const sanitizedQuery = query.replace(humanWords, 'object').trim();
|
| 33 |
+
|
| 34 |
+
return { imageQuery: sanitizedQuery };
|
| 35 |
}
|
src/ai/flows/ai-generate-post-ideas.ts
CHANGED
|
@@ -4,26 +4,32 @@
|
|
| 4 |
/**
|
| 5 |
* @fileOverview An AI agent to generate a bulk of social media post ideas.
|
| 6 |
* Now takes user location and language into account for better localization.
|
|
|
|
| 7 |
*/
|
| 8 |
|
| 9 |
import { safeGenerateContent } from '@/lib/gemini-client';
|
| 10 |
import type { GeneratePostIdeasInput, GeneratePostIdeasOutput } from './types';
|
| 11 |
|
| 12 |
-
const promptTemplate = (count: number, location: string, language: string) => `You are a
|
| 13 |
|
| 14 |
**CONTEXT:**
|
| 15 |
- User Location: ${location}
|
| 16 |
- Output Language: ${language === 'en' ? 'English' : 'Colloquial Egyptian Arabic'}
|
| 17 |
|
| 18 |
-
**
|
| 19 |
-
1. **
|
| 20 |
-
2. **
|
| 21 |
-
3. **
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
Generate ${count} distinct post packages. Each package must contain:
|
| 24 |
1. **content:** The post text.
|
| 25 |
2. **isMeme:** Boolean.
|
| 26 |
-
3. **imageQuery:** Precise English search query (NO
|
| 27 |
4. **category:** 'funny', 'sad', 'deep', 'angry', 'inspirational', 'general'.
|
| 28 |
5. **initialComments:** 3-5 realistic comments in the target language.
|
| 29 |
|
|
@@ -35,7 +41,7 @@ Generate ${count} distinct post packages. Each package must contain:
|
|
| 35 |
{
|
| 36 |
"content": "Post text",
|
| 37 |
"isMeme": true,
|
| 38 |
-
"imageQuery": "
|
| 39 |
"category": "funny",
|
| 40 |
"initialComments": ["Comment 1", "Comment 2"]
|
| 41 |
}
|
|
@@ -50,7 +56,7 @@ export async function generatePostIdeas(
|
|
| 50 |
const location = input.location || 'Egypt';
|
| 51 |
const language = input.language || 'ar';
|
| 52 |
|
| 53 |
-
console.log(`AI REQUEST: Generating ${input.count} posts for ${location} in ${language}...`);
|
| 54 |
const prompt = promptTemplate(input.count, location, language);
|
| 55 |
const output = await safeGenerateContent(prompt);
|
| 56 |
return output as GeneratePostIdeasOutput;
|
|
|
|
| 4 |
/**
|
| 5 |
* @fileOverview An AI agent to generate a bulk of social media post ideas.
|
| 6 |
* Now takes user location and language into account for better localization.
|
| 7 |
+
* Strict safety and non-human imagery rules applied.
|
| 8 |
*/
|
| 9 |
|
| 10 |
import { safeGenerateContent } from '@/lib/gemini-client';
|
| 11 |
import type { GeneratePostIdeasInput, GeneratePostIdeasOutput } from './types';
|
| 12 |
|
| 13 |
+
const promptTemplate = (count: number, location: string, language: string) => `You are a culturally sensitive social media content creator. Your goal is to generate posts that feel authentic, relatable, and emotionally engaging for people in ${location}.
|
| 14 |
|
| 15 |
**CONTEXT:**
|
| 16 |
- User Location: ${location}
|
| 17 |
- Output Language: ${language === 'en' ? 'English' : 'Colloquial Egyptian Arabic'}
|
| 18 |
|
| 19 |
+
**STRICT CULTURAL SAFETY RULES:**
|
| 20 |
+
1. **NO Sexual Content:** Strictly prohibit any sexual themes, innuendos, or references to sexual activity.
|
| 21 |
+
2. **NO Alcohol/Drugs:** Do not mention or promote alcohol, narcotics, or any illegal substances.
|
| 22 |
+
3. **NO Offense:** Strictly avoid sensitive religious topics, hate speech, or offensive political debates.
|
| 23 |
+
4. **Local Context:** Adapt your humor and topics to be relevant to the culture of ${location}. If in an Arab country, ensure respect for local customs.
|
| 24 |
+
|
| 25 |
+
**STRICT IMAGERY RULES:**
|
| 26 |
+
1. **NO HUMANS:** The "imageQuery" field MUST NOT contain any words related to humans, people, faces, men, women, children, or body parts.
|
| 27 |
+
2. **Focus on:** Landscapes, objects, nature, architecture, abstract art, textures, or animals.
|
| 28 |
|
| 29 |
Generate ${count} distinct post packages. Each package must contain:
|
| 30 |
1. **content:** The post text.
|
| 31 |
2. **isMeme:** Boolean.
|
| 32 |
+
3. **imageQuery:** Precise English search query (NO HUMANS ALLOWED).
|
| 33 |
4. **category:** 'funny', 'sad', 'deep', 'angry', 'inspirational', 'general'.
|
| 34 |
5. **initialComments:** 3-5 realistic comments in the target language.
|
| 35 |
|
|
|
|
| 41 |
{
|
| 42 |
"content": "Post text",
|
| 43 |
"isMeme": true,
|
| 44 |
+
"imageQuery": "Landscape showing high mountains at dawn (no humans)",
|
| 45 |
"category": "funny",
|
| 46 |
"initialComments": ["Comment 1", "Comment 2"]
|
| 47 |
}
|
|
|
|
| 56 |
const location = input.location || 'Egypt';
|
| 57 |
const language = input.language || 'ar';
|
| 58 |
|
| 59 |
+
console.log(`AI REQUEST: Generating ${input.count} posts for ${location} in ${language} with strict safety rules...`);
|
| 60 |
const prompt = promptTemplate(input.count, location, language);
|
| 61 |
const output = await safeGenerateContent(prompt);
|
| 62 |
return output as GeneratePostIdeasOutput;
|
src/app/login/page.tsx
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
|
| 4 |
import { Button } from "@/components/ui/button";
|
| 5 |
import { Header } from "@/components/header";
|
| 6 |
-
import { Loader2, LogIn,
|
| 7 |
import { useAuth } from "@/contexts/auth-context";
|
| 8 |
import { useEffect, useState } from "react";
|
| 9 |
import { useRouter } from "next/navigation";
|
|
@@ -13,6 +13,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
| 13 |
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
| 14 |
import { useToast } from "@/hooks/use-toast";
|
| 15 |
import { useLanguage } from "@/contexts/language-context";
|
|
|
|
|
|
|
| 16 |
|
| 17 |
export default function LoginPage() {
|
| 18 |
const { user, userData, loading: authLoading, signInWithGoogle, signInWithUsername, completeProfile } = useAuth();
|
|
@@ -64,6 +66,10 @@ export default function LoginPage() {
|
|
| 64 |
const handleCompleteProfile = async (e: React.FormEvent) => {
|
| 65 |
e.preventDefault();
|
| 66 |
if (!validateAge(age)) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
setIsSigningIn(true);
|
| 68 |
await completeProfile(parseInt(age), location);
|
| 69 |
setIsSigningIn(false);
|
|
@@ -86,7 +92,7 @@ export default function LoginPage() {
|
|
| 86 |
<form onSubmit={handleCompleteProfile}>
|
| 87 |
<CardHeader>
|
| 88 |
<CardTitle>إكمال البيانات</CardTitle>
|
| 89 |
-
<CardDescription>يرجى
|
| 90 |
</CardHeader>
|
| 91 |
<CardContent className="space-y-4">
|
| 92 |
<div className="space-y-2">
|
|
@@ -95,7 +101,19 @@ export default function LoginPage() {
|
|
| 95 |
</div>
|
| 96 |
<div className="space-y-2">
|
| 97 |
<Label>{t.location}</Label>
|
| 98 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
</div>
|
| 100 |
</CardContent>
|
| 101 |
<CardFooter>
|
|
@@ -156,14 +174,26 @@ export default function LoginPage() {
|
|
| 156 |
<Label>{t.password}</Label>
|
| 157 |
<Input type="password" value={password} onChange={e => setPassword(e.target.value)} required />
|
| 158 |
</div>
|
| 159 |
-
<div className="grid grid-cols-
|
| 160 |
<div className="space-y-2">
|
| 161 |
<Label>{t.age}</Label>
|
| 162 |
<Input type="number" value={age} onChange={e => setAge(e.target.value)} required />
|
| 163 |
</div>
|
| 164 |
<div className="space-y-2">
|
| 165 |
<Label>{t.location}</Label>
|
| 166 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
</div>
|
| 168 |
</div>
|
| 169 |
</CardContent>
|
|
|
|
| 3 |
|
| 4 |
import { Button } from "@/components/ui/button";
|
| 5 |
import { Header } from "@/components/header";
|
| 6 |
+
import { Loader2, LogIn, MessageSquare, Chrome } from "lucide-react";
|
| 7 |
import { useAuth } from "@/contexts/auth-context";
|
| 8 |
import { useEffect, useState } from "react";
|
| 9 |
import { useRouter } from "next/navigation";
|
|
|
|
| 13 |
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
| 14 |
import { useToast } from "@/hooks/use-toast";
|
| 15 |
import { useLanguage } from "@/contexts/language-context";
|
| 16 |
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
| 17 |
+
import { countries } from "@/lib/countries";
|
| 18 |
|
| 19 |
export default function LoginPage() {
|
| 20 |
const { user, userData, loading: authLoading, signInWithGoogle, signInWithUsername, completeProfile } = useAuth();
|
|
|
|
| 66 |
const handleCompleteProfile = async (e: React.FormEvent) => {
|
| 67 |
e.preventDefault();
|
| 68 |
if (!validateAge(age)) return;
|
| 69 |
+
if (!location) {
|
| 70 |
+
toast({ title: t.mandatoryFields, variant: "destructive" });
|
| 71 |
+
return;
|
| 72 |
+
}
|
| 73 |
setIsSigningIn(true);
|
| 74 |
await completeProfile(parseInt(age), location);
|
| 75 |
setIsSigningIn(false);
|
|
|
|
| 92 |
<form onSubmit={handleCompleteProfile}>
|
| 93 |
<CardHeader>
|
| 94 |
<CardTitle>إكمال البيانات</CardTitle>
|
| 95 |
+
<CardDescription>يرجى اختيار بلدك وعمرك للمتابعة</CardDescription>
|
| 96 |
</CardHeader>
|
| 97 |
<CardContent className="space-y-4">
|
| 98 |
<div className="space-y-2">
|
|
|
|
| 101 |
</div>
|
| 102 |
<div className="space-y-2">
|
| 103 |
<Label>{t.location}</Label>
|
| 104 |
+
<Select onValueChange={setLocation} value={location}>
|
| 105 |
+
<SelectTrigger className="w-full">
|
| 106 |
+
<SelectValue placeholder="اختر بلدك" />
|
| 107 |
+
</SelectTrigger>
|
| 108 |
+
<SelectContent>
|
| 109 |
+
{countries.map((c) => (
|
| 110 |
+
<SelectItem key={c.code} value={c.name}>
|
| 111 |
+
<span className="ml-2">{c.flag}</span>
|
| 112 |
+
{c.name}
|
| 113 |
+
</SelectItem>
|
| 114 |
+
))}
|
| 115 |
+
</SelectContent>
|
| 116 |
+
</Select>
|
| 117 |
</div>
|
| 118 |
</CardContent>
|
| 119 |
<CardFooter>
|
|
|
|
| 174 |
<Label>{t.password}</Label>
|
| 175 |
<Input type="password" value={password} onChange={e => setPassword(e.target.value)} required />
|
| 176 |
</div>
|
| 177 |
+
<div className="grid grid-cols-1 gap-4">
|
| 178 |
<div className="space-y-2">
|
| 179 |
<Label>{t.age}</Label>
|
| 180 |
<Input type="number" value={age} onChange={e => setAge(e.target.value)} required />
|
| 181 |
</div>
|
| 182 |
<div className="space-y-2">
|
| 183 |
<Label>{t.location}</Label>
|
| 184 |
+
<Select onValueChange={setLocation} value={location}>
|
| 185 |
+
<SelectTrigger className="w-full">
|
| 186 |
+
<SelectValue placeholder="اختر بلدك" />
|
| 187 |
+
</SelectTrigger>
|
| 188 |
+
<SelectContent>
|
| 189 |
+
{countries.map((c) => (
|
| 190 |
+
<SelectItem key={c.code} value={c.name}>
|
| 191 |
+
<span className="ml-2">{c.flag}</span>
|
| 192 |
+
{c.name}
|
| 193 |
+
</SelectItem>
|
| 194 |
+
))}
|
| 195 |
+
</SelectContent>
|
| 196 |
+
</Select>
|
| 197 |
</div>
|
| 198 |
</div>
|
| 199 |
</CardContent>
|
src/lib/countries.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
export interface Country {
|
| 3 |
+
name: string;
|
| 4 |
+
code: string;
|
| 5 |
+
flag: string;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
export const countries: Country[] = [
|
| 9 |
+
{ name: "مصر", code: "EG", flag: "🇪🇬" },
|
| 10 |
+
{ name: "المملكة العربية السعودية", code: "SA", flag: "🇸🇦" },
|
| 11 |
+
{ name: "الإمارات العربية المتحدة", code: "AE", flag: "🇦🇪" },
|
| 12 |
+
{ name: "الكويت", code: "KW", flag: "🇰🇼" },
|
| 13 |
+
{ name: "قطر", code: "QA", flag: "🇶🇦" },
|
| 14 |
+
{ name: "البحرين", code: "BH", flag: "🇧🇭" },
|
| 15 |
+
{ name: "عمان", code: "OM", flag: "🇴🇲" },
|
| 16 |
+
{ name: "الأردن", code: "JO", flag: "🇯🇴" },
|
| 17 |
+
{ name: "لبنان", code: "LB", flag: "🇱🇧" },
|
| 18 |
+
{ name: "سوريا", code: "SY", flag: "🇸🇾" },
|
| 19 |
+
{ name: "العراق", code: "IQ", flag: "🇮🇶" },
|
| 20 |
+
{ name: "فلسطين", code: "PS", flag: "🇵🇸" },
|
| 21 |
+
{ name: "المغرب", code: "MA", flag: "🇲🇦" },
|
| 22 |
+
{ name: "تونس", code: "TN", flag: "🇹🇳" },
|
| 23 |
+
{ name: "الجزائر", code: "DZ", flag: "🇩🇿" },
|
| 24 |
+
{ name: "ليبيا", code: "LY", flag: "🇱🇾" },
|
| 25 |
+
{ name: "السودان", code: "SD", flag: "🇸🇩" },
|
| 26 |
+
{ name: "اليمن", code: "YE", flag: "🇾🇪" },
|
| 27 |
+
{ name: "الولايات المتحدة", code: "US", flag: "🇺🇸" },
|
| 28 |
+
{ name: "المملكة المتحدة", code: "GB", flag: "🇬🇧" },
|
| 29 |
+
{ name: "فرنسا", code: "FR", flag: "🇫🇷" },
|
| 30 |
+
{ name: "ألمانيا", code: "DE", flag: "🇩🇪" },
|
| 31 |
+
{ name: "كندا", code: "CA", flag: "🇨🇦" },
|
| 32 |
+
{ name: "تركيا", code: "TR", flag: "🇹🇷" }
|
| 33 |
+
].sort((a, b) => a.name.localeCompare(b.name, 'ar'));
|