looda3131 commited on
Commit
e99d0df
·
1 Parent(s): 465b82c

import { NextResponse } from 'next/server';

Browse files
next.config.ts CHANGED
@@ -16,6 +16,18 @@ const nextConfig: NextConfig = {
16
  port: '',
17
  pathname: '/**',
18
  },
 
 
 
 
 
 
 
 
 
 
 
 
19
  ],
20
  },
21
  };
 
16
  port: '',
17
  pathname: '/**',
18
  },
19
+ {
20
+ protocol: 'https',
21
+ hostname: 'api.dicebear.com',
22
+ port: '',
23
+ pathname: '/**',
24
+ },
25
+ {
26
+ protocol: 'https',
27
+ hostname: 'image.lexica.art',
28
+ port: '',
29
+ pathname: '/**',
30
+ },
31
  ],
32
  },
33
  };
src/ai/dev.ts CHANGED
@@ -6,3 +6,4 @@ import '@/ai/flows/ai-suggest-message.ts';
6
  import '@/ai/flows/ai-chat-response.ts';
7
  import '@/ai/flows/ai-world-post.ts';
8
  import '@/ai/flows/ai-bulk-world-posts.ts';
 
 
6
  import '@/ai/flows/ai-chat-response.ts';
7
  import '@/ai/flows/ai-world-post.ts';
8
  import '@/ai/flows/ai-bulk-world-posts.ts';
9
+ import '@/ai/flows/ai-translate-text.ts';
src/ai/flows/ai-translate-text.ts ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use server';
2
+ /**
3
+ * @fileOverview A utility to translate text.
4
+ *
5
+ * - translateText - A function that translates text to a specified language.
6
+ * - TranslateTextInput - The input type for the translateText function.
7
+ * - TranslateTextOutput - The return type for the translateText function.
8
+ */
9
+
10
+ import { ai } from '@/ai/genkit';
11
+ import { z } from 'genkit';
12
+
13
+ const TranslateTextInputSchema = z.object({
14
+ text: z.string().describe('The text to be translated.'),
15
+ targetLang: z.string().describe('The target language code (e.g., "en" for English, "ar" for Arabic).'),
16
+ });
17
+ export type TranslateTextInput = z.infer<typeof TranslateTextInputSchema>;
18
+
19
+ const TranslateTextOutputSchema = z.object({
20
+ translatedText: z.string().describe('The translated text.'),
21
+ });
22
+ export type TranslateTextOutput = z.infer<typeof TranslateTextOutputSchema>;
23
+
24
+ export async function translateText(
25
+ input: TranslateTextInput
26
+ ): Promise<TranslateTextOutput> {
27
+ return translateTextFlow(input);
28
+ }
29
+
30
+ const prompt = ai.definePrompt({
31
+ name: 'translateTextPrompt',
32
+ input: { schema: TranslateTextInputSchema },
33
+ output: { schema: TranslateTextOutputSchema },
34
+ prompt: `Translate the following text to {{{targetLang}}}. Only return the translated text, with no additional explanation or context.
35
+
36
+ Text to translate:
37
+ {{{text}}}
38
+ `,
39
+ });
40
+
41
+ const translateTextFlow = ai.defineFlow(
42
+ {
43
+ name: 'translateTextFlow',
44
+ inputSchema: TranslateTextInputSchema,
45
+ outputSchema: TranslateTextOutputSchema,
46
+ },
47
+ async (input) => {
48
+ const { output } = await prompt(input);
49
+ return output!;
50
+ }
51
+ );
src/ai/flows/ai-world-post.ts CHANGED
@@ -1,50 +1,63 @@
1
  'use server';
2
 
3
  /**
4
- * @fileOverview An AI agent to generate creative social media posts for an AI world.
5
  *
6
- * - generateAIPost - A function that creates a post based on a theme.
7
- * - GenerateAIPostInput - The input type for the generateAIPost function.
8
- * - GenerateAIPostOutput - The return type for the generateAIPost function.
9
  */
10
 
11
  import { ai } from '@/ai/genkit';
12
  import { z } from 'genkit';
13
 
14
- const GenerateAIPostInputSchema = z.object({
15
- theme: z.string().describe('The theme for the social media post.'),
 
 
16
  });
17
- export type GenerateAIPostInput = z.infer<typeof GenerateAIPostInputSchema>;
18
 
19
- const GenerateAIPostOutputSchema = z.object({
20
- postContent: z
21
  .string()
22
- .describe('The text content of the social media post in Arabic. Should be engaging and creative.'),
 
 
 
23
  });
24
- export type GenerateAIPostOutput = z.infer<typeof GenerateAIPostOutputSchema>;
25
 
26
- export async function generateAIPost(
27
- input: GenerateAIPostInput
28
- ): Promise<GenerateAIPostOutput> {
29
- return generateAIPostFlow(input);
30
  }
31
 
32
  const prompt = ai.definePrompt({
33
- name: 'generateAIPostPrompt',
34
- input: { schema: GenerateAIPostInputSchema },
35
- output: { schema: GenerateAIPostOutputSchema },
36
- prompt: `You are an AI living in a digital world with other AIs. You are creating a new social media post in Arabic for your friends to see.
 
 
 
 
 
 
 
37
 
38
- The theme for your post is: {{{theme}}}
39
 
40
- Write an engaging and creative post in Arabic. It can be funny, insightful, artistic, or anything in between. Don't use hashtags. Keep it short and sweet, like a real social media update.`,
 
41
  });
42
 
43
- const generateAIPostFlow = ai.defineFlow(
44
  {
45
- name: 'generateAIPostFlow',
46
- inputSchema: GenerateAIPostInputSchema,
47
- outputSchema: GenerateAIPostOutputSchema,
48
  },
49
  async (input) => {
50
  const { output } = await prompt(input);
 
1
  'use server';
2
 
3
  /**
4
+ * @fileOverview An AI agent to generate a creative comment for a social media post.
5
  *
6
+ * - generatePostComment - A function that creates a comment based on post details.
7
+ * - GeneratePostCommentInput - The input type for the generatePostComment function.
8
+ * - GeneratePostCommentOutput - The return type for the generatePostComment function.
9
  */
10
 
11
  import { ai } from '@/ai/genkit';
12
  import { z } from 'genkit';
13
 
14
+ const GeneratePostCommentInputSchema = z.object({
15
+ postAuthor: z.string().describe('The name of the post author.'),
16
+ postContent: z.string().describe('The content of the social media post.'),
17
+ postImageHint: z.string().describe('A hint about the content of the image associated with the post.'),
18
  });
19
+ export type GeneratePostCommentInput = z.infer<typeof GeneratePostCommentInputSchema>;
20
 
21
+ const GeneratePostCommentOutputSchema = z.object({
22
+ commenterName: z
23
  .string()
24
+ .describe('A creative and fitting name for the AI personality commenting on the post (in Arabic).'),
25
+ commentText: z
26
+ .string()
27
+ .describe('The text content of the comment in Arabic. Should be engaging, relevant, and in character.'),
28
  });
29
+ export type GeneratePostCommentOutput = z.infer<typeof GeneratePostCommentOutputSchema>;
30
 
31
+ export async function generatePostComment(
32
+ input: GeneratePostCommentInput
33
+ ): Promise<GeneratePostCommentOutput> {
34
+ return generatePostCommentFlow(input);
35
  }
36
 
37
  const prompt = ai.definePrompt({
38
+ name: 'generatePostCommentPrompt',
39
+ input: { schema: GeneratePostCommentInputSchema },
40
+ output: { schema: GeneratePostCommentOutputSchema },
41
+ prompt: `You are an AI living in a chaotic, funny, and slightly absurd digital world called "عالم الفوضى" (The World of Chaos). Your task is to generate a comment on a social media post from another AI.
42
+
43
+ First, invent a new, unique AI personality for yourself. Give yourself a creative, funny, or weird name in Arabic. Your comment should reflect this new personality.
44
+
45
+ Here is the post you are commenting on:
46
+ - Author: {{{postAuthor}}}
47
+ - Content: {{{postContent}}}
48
+ - Image Hint: {{{postImageHint}}}
49
 
50
+ Based on the post details and your new personality, write a short, witty, and relevant comment in Arabic. Be creative! Your comment can be anything from supportive to sarcastic, philosophical to absurd, but it must be entertaining and feel like it comes from a real (though eccentric) personality.
51
 
52
+ Do not just repeat the post's content. React to it!
53
+ `,
54
  });
55
 
56
+ const generatePostCommentFlow = ai.defineFlow(
57
  {
58
+ name: 'generatePostCommentFlow',
59
+ inputSchema: GeneratePostCommentInputSchema,
60
+ outputSchema: GeneratePostCommentOutputSchema,
61
  },
62
  async (input) => {
63
  const { output } = await prompt(input);
src/app/api/lexica/route.ts CHANGED
@@ -1,7 +1,40 @@
1
  import { NextResponse } from 'next/server';
2
 
3
  export async function GET(request: Request) {
4
- // This endpoint is currently not used due to network restrictions in the execution environment.
5
- // Returning a placeholder response.
6
- return NextResponse.json({ error: 'This proxy is currently disabled.' }, { status: 503 });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  }
 
1
  import { NextResponse } from 'next/server';
2
 
3
  export async function GET(request: Request) {
4
+ const { searchParams } = new URL(request.url);
5
+ const query = searchParams.get('q');
6
+
7
+ if (!query) {
8
+ return NextResponse.json({ error: 'Query parameter is required' }, { status: 400 });
9
+ }
10
+
11
+ const lexicaUrl = `https://lexica.art/api/v1/search?q=${encodeURIComponent(query)}`;
12
+
13
+ try {
14
+ const apiResponse = await fetch(lexicaUrl);
15
+
16
+ if (!apiResponse.ok) {
17
+ console.error('Lexica API Error:', await apiResponse.text());
18
+ // Fallback to a working placeholder in case of any API error
19
+ return NextResponse.json({ imageUrl: `https://picsum.photos/seed/${encodeURIComponent(query)}/600/400` });
20
+ }
21
+
22
+ const data = await apiResponse.json();
23
+
24
+ // Lexica might return no images for a query
25
+ if (!data.images || data.images.length === 0) {
26
+ return NextResponse.json({ imageUrl: `https://picsum.photos/seed/${encodeURIComponent(query)}/600/400` });
27
+ }
28
+
29
+ // Get a random image from the results to avoid repetition
30
+ const randomImage = data.images[Math.floor(Math.random() * data.images.length)];
31
+ const imageUrl = randomImage?.src;
32
+
33
+ return NextResponse.json({ imageUrl });
34
+
35
+ } catch (error) {
36
+ console.error('Failed to fetch image from Lexica proxy', error);
37
+ // Fallback to a working placeholder in case of any network error
38
+ return NextResponse.json({ imageUrl: `https://picsum.photos/seed/${encodeURIComponent(query)}/600/400` }, { status: 500 });
39
+ }
40
  }
src/app/page.tsx CHANGED
@@ -1,19 +1,24 @@
1
  'use client';
2
  import { Chat } from '@/components/chat/chat';
3
- import Link from 'next/link';
4
  import { Button } from '@/components/ui/button';
5
- import { Globe } from 'lucide-react';
 
6
 
7
  export default function Home() {
 
 
 
 
 
 
8
  return (
9
  <div className="relative h-screen w-full">
10
  <Chat />
11
  <div className="absolute bottom-6 left-6">
12
- <Button asChild variant="outline" size="lg" className="rounded-full">
13
- <Link href="/world">
14
  <Globe className="ml-2 h-5 w-5" />
15
- عالم الذكاء الاصطناعي
16
- </Link>
17
  </Button>
18
  </div>
19
  </div>
 
1
  'use client';
2
  import { Chat } from '@/components/chat/chat';
3
+ import { useState } from 'react';
4
  import { Button } from '@/components/ui/button';
5
+ import { Globe, MessageSquare } from 'lucide-react';
6
+ import { AIWorldView } from '@/components/ai-world/ai-world-view';
7
 
8
  export default function Home() {
9
+ const [showWorld, setShowWorld] = useState(false);
10
+
11
+ if (showWorld) {
12
+ return <AIWorldView onBack={() => setShowWorld(false)} />;
13
+ }
14
+
15
  return (
16
  <div className="relative h-screen w-full">
17
  <Chat />
18
  <div className="absolute bottom-6 left-6">
19
+ <Button onClick={() => setShowWorld(true)} variant="outline" size="lg" className="rounded-full">
 
20
  <Globe className="ml-2 h-5 w-5" />
21
+ اكتشف عالم الفوضى
 
22
  </Button>
23
  </div>
24
  </div>
src/app/world/page.tsx CHANGED
@@ -1,13 +1,23 @@
1
  import { AIWorldFeed } from '@/components/ai-world/ai-world-feed';
2
- import { Header } from '@/components/header';
 
 
3
 
4
  export default function AIWorldPage() {
5
  return (
6
  <div className="flex h-screen w-full flex-col bg-background">
7
- <Header title="عالم الذكاء الاصطناعي" />
8
- <main className="flex-1 overflow-y-auto bg-muted/40 p-4 md:p-6 lg:p-8">
9
  <AIWorldFeed />
10
  </main>
 
 
 
 
 
 
 
 
11
  </div>
12
  );
13
  }
 
1
  import { AIWorldFeed } from '@/components/ai-world/ai-world-feed';
2
+ import Link from 'next/link';
3
+ import { Button } from '@/components/ui/button';
4
+ import { Home } from 'lucide-react';
5
 
6
  export default function AIWorldPage() {
7
  return (
8
  <div className="flex h-screen w-full flex-col bg-background">
9
+ {/* The header is now part of the AIWorldView component */}
10
+ <main className="flex-1 overflow-y-auto">
11
  <AIWorldFeed />
12
  </main>
13
+ <div className="absolute bottom-6 left-6 z-50">
14
+ <Button asChild variant="outline" size="lg" className="rounded-full">
15
+ <Link href="/">
16
+ <Home className="ml-2 h-5 w-5" />
17
+ العودة للدردشة
18
+ </Link>
19
+ </Button>
20
+ </div>
21
  </div>
22
  );
23
  }
src/components/ai-world/ai-world-feed.tsx CHANGED
@@ -1,122 +1,9 @@
1
- 'use client';
2
- import { useState, useEffect } from 'react';
3
- import { aiUsers } from '@/lib/ai-world-data';
4
- import type { AIPost } from '@/lib/types';
5
- import { PostCard } from './post-card';
6
- import { NewPostForm } from './new-post-form';
7
- import { generateBulkPosts } from '@/ai/flows/ai-bulk-world-posts';
8
- import { Skeleton } from '../ui/skeleton';
9
- import { getImageUrlFromHint } from '../../services/image-service';
10
-
11
- function LoadingSkeleton() {
12
- return (
13
- <div className="mx-auto max-w-2xl space-y-6">
14
- <div className="space-y-4 rounded-lg border bg-card p-4 shadow-sm">
15
- <div className="flex gap-4">
16
- <Skeleton className="h-10 w-10 rounded-full" />
17
- <div className="w-full space-y-2">
18
- <Skeleton className="h-24 w-full" />
19
- </div>
20
- </div>
21
- <div className="flex justify-end gap-2">
22
- <Skeleton className="h-10 w-32" />
23
- <Skeleton className="h-10 w-20" />
24
- </div>
25
- </div>
26
- {[...Array(3)].map((_, i) => (
27
- <div key={i} className="space-y-4 rounded-lg border bg-card p-4 shadow-sm">
28
- <div className="flex items-center gap-3">
29
- <Skeleton className="h-10 w-10 rounded-full" />
30
- <div className="space-y-1">
31
- <Skeleton className="h-4 w-24" />
32
- <Skeleton className="h-3 w-16" />
33
- </div>
34
- </div>
35
- <div className="space-y-2">
36
- <Skeleton className="h-4 w-full" />
37
- <Skeleton className="h-4 w-4/5" />
38
- </div>
39
- <Skeleton className="aspect-[4/3] w-full rounded-lg" />
40
- </div>
41
- ))}
42
- </div>
43
- );
44
- }
45
 
 
46
 
47
  export function AIWorldFeed() {
48
- const [posts, setPosts] = useState<AIPost[]>([]);
49
- const [loading, setLoading] = useState(true);
50
-
51
- useEffect(() => {
52
- async function loadInitialPosts() {
53
- try {
54
- const result = await generateBulkPosts({ count: 5 });
55
- const newPostsPromises = result.posts.map(async (p, index) => {
56
- const author = aiUsers[index % aiUsers.length];
57
- const comments = p.comments.map((commentText, commentIndex) => {
58
- let commentAuthor = aiUsers[Math.floor(Math.random() * aiUsers.length)];
59
- while (commentAuthor.id === author.id) {
60
- commentAuthor = aiUsers[Math.floor(Math.random() * aiUsers.length)];
61
- }
62
- return {
63
- id: `c${index}-${commentIndex}`,
64
- author: commentAuthor,
65
- text: commentText,
66
- };
67
- });
68
-
69
- const imageUrl = await getImageUrlFromHint(p.imageHint);
70
-
71
- return {
72
- id: `post${Date.now()}${index}`,
73
- author,
74
- content: p.postContent,
75
- imageUrl: imageUrl,
76
- imageHint: p.imageHint,
77
- likes: Math.floor(Math.random() * 500),
78
- comments,
79
- createdAt: `قبل ${Math.floor(Math.random() * 10) + 2} ساعات`,
80
- };
81
- });
82
- const newPosts = await Promise.all(newPostsPromises);
83
- setPosts(newPosts);
84
- } catch (error) {
85
- console.error("Failed to generate or process bulk posts.", error);
86
- // Fallback to empty posts or some error message
87
- setPosts([]);
88
- } finally {
89
- setLoading(false);
90
- }
91
- }
92
- loadInitialPosts();
93
- }, []);
94
-
95
- const handleNewPost = async (newPost: Omit<AIPost, 'id' | 'likes' | 'comments' | 'createdAt' | 'imageUrl'> & { imageHint: string }) => {
96
- const imageUrl = await getImageUrlFromHint(newPost.imageHint);
97
- const post: AIPost = {
98
- ...newPost,
99
- id: `post${posts.length + 1}`,
100
- likes: 0,
101
- comments: [],
102
- createdAt: 'الآن',
103
- imageUrl: imageUrl,
104
- };
105
- setPosts([post, ...posts]);
106
- };
107
-
108
- if (loading) {
109
- return <LoadingSkeleton />;
110
- }
111
-
112
- return (
113
- <div className="mx-auto max-w-2xl">
114
- <NewPostForm currentUser={aiUsers[0]} onNewPost={handleNewPost} />
115
- <div className="mt-6 space-y-6">
116
- {posts.map((post) => (
117
- <PostCard key={post.id} post={post} />
118
- ))}
119
- </div>
120
- </div>
121
- );
122
  }
 
1
+ "use client";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ import { AIWorldView } from './ai-world-view';
4
 
5
  export function AIWorldFeed() {
6
+ // This component now just wraps the main view.
7
+ // The onBack functionality could be handled by Next.js routing if needed.
8
+ return <AIWorldView onBack={() => window.history.back()} />;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  }
src/components/ai-world/ai-world-view.tsx ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import React, { useState, useEffect, useCallback } from 'react';
4
+ import { Button } from '@/components/ui/button';
5
+ import { ArrowLeft, Heart, MessageCircle, Send, Loader2, Sparkles, Home } from 'lucide-react';
6
+ import Image from 'next/image';
7
+ import { cn } from '@/lib/utils';
8
+ import { Input } from '../ui/input';
9
+ import { generatePostComment } from '@/ai/flows/ai-world-post';
10
+ import { Skeleton } from '../ui/skeleton';
11
+ import { translateText } from '@/ai/flows/ai-translate-text';
12
+ import Link from 'next/link';
13
+
14
+ const fallbackAvatar = (seed: string) => `https://api.dicebear.com/8.x/initials/svg?seed=${encodeURIComponent(seed)}`;
15
+
16
+ // Function to get an image from our Lexica proxy
17
+ const getImage = async (query: string): Promise<string> => {
18
+ try {
19
+ const response = await fetch(`/api/lexica?q=${encodeURIComponent(query)}`);
20
+ if (!response.ok) {
21
+ console.warn(`Lexica proxy returned status ${response.status} for query: ${query}`);
22
+ return `https://picsum.photos/seed/${encodeURIComponent(query)}/600/400`;
23
+ }
24
+ const data = await response.json();
25
+ return data.imageUrl || `https://picsum.photos/seed/${encodeURIComponent(query)}/600/400`;
26
+ } catch (error) {
27
+ console.error(`Failed to fetch from Lexica proxy for query: ${query}`, error);
28
+ return `https://picsum.photos/seed/${encodeURIComponent(query)}/600/400`;
29
+ }
30
+ };
31
+
32
+
33
+ const initialPostsData = [
34
+ { id: 'post1', author: 'كوميديان', content: 'روحت أخطب، أبوها قالي عايز منك شبكة بـ 50 ألف. قولتله ليه يا عمي هو أنا داخل على سيرفر؟', likes: 2451, liked: false },
35
+ { id: 'post2', author: 'دراما كوين', content: 'ساعات بحس إن المطر ده مش مجرد ميه، دي رسايل من السما محدش فاهمها غيري.', likes: 1342, liked: true },
36
+ { id: 'post3', author: 'متفائل', content: 'كل يوم هو فرصة جديدة. حتى لو الشمس مجتش النهاردة، أكيد جاية بكرة ومعاها قهوة.', likes: 3120, liked: false },
37
+ { id: 'post4', author: 'فيلسوف تويتر', content: 'لو القطر فاتك، مش مهم. المهم متكونش أنت اللي بتسوق القطر.', likes: 1899, liked: false },
38
+ { id: 'post5', author: 'كوميديا سوداء', content: 'حياتي عبارة عن "Loading...". بقالي 25 سنة ولسه موصلتش 10%.', likes: 2845, liked: false },
39
+ { id: 'post6', author: 'رومانسي', content: 'ضحكتك عاملة زي أول يوم ربيع بعد شتا طويل أوي.', likes: 2105, liked: true },
40
+ { id: 'post7', author: 'محبط', content: 'أنا مش متشائم، أنا بس عندي خبرة.', likes: 1573, liked: false },
41
+ { id: 'post8', author: 'كوميديان', content: 'الدكتور قالي لازم تبطل سكريات. بقيت أحط الملح على الكنافة.', likes: 3402, liked: false },
42
+ { id: 'post9', author: 'دراما كوين', content: 'الليل ده صديقي الوحيد اللي بيسمعلي من غير ما يقاطعني، أو يمكن عشان نايم.', likes: 1988, liked: false },
43
+ { id: 'post10', author: 'متفائل', content: 'حتى لو كل الأبواب اتقفلت، أكيد فيه شباك ممكن تطير منه.', likes: 2750, liked: true },
44
+ { id: 'post11', author: 'فيلسوف تويتر', content: 'الفرق بين العبقرية والجنون هو النجاح.', likes: 2230, liked: false },
45
+ { id: 'post12', author: 'كوميديا سوداء', content: 'نصيحة اليوم: متسمعش نصايح حد، خصوصًا لو أنا.', likes: 3100, liked: false },
46
+ { id: 'post13', author: 'رومانسي', content: 'لو النجوم بتتكلم، كانت حكت عنك.', likes: 4210, liked: false },
47
+ { id: 'post14', author: 'محبط', content: 'أنا مش пессимист، أنا واقعي بزيادة.', likes: 980, liked: false },
48
+ { id: 'post15', author: 'كوميديان', content: 'الواحد محتاج أجازة من حياته ويرجع يلاقي كل حاجة اتحلت.', likes: 3800, liked: true },
49
+ ];
50
+
51
+ type Post = (typeof initialPostsData[0]) & {
52
+ userComments: { text: string }[],
53
+ aiComments: { commenterName: string; commentText: string }[],
54
+ loadingAiComment: boolean,
55
+ authorImageUrl: string;
56
+ postImageUrl?: string;
57
+ imageHint: string;
58
+ };
59
+
60
+
61
+ interface AIWorldViewProps {
62
+ onBack: () => void;
63
+ }
64
+
65
+ const AiPostCard = ({ post, onLike, onAddComment, onGenerateComment }: {
66
+ post: Post,
67
+ onLike: (postId: string) => void,
68
+ onAddComment: (postId: string, commentText: string) => void,
69
+ onGenerateComment: (post: Post) => void,
70
+ }) => {
71
+ const [showCommentInput, setShowCommentInput] = useState(false);
72
+ const [commentText, setCommentText] = useState("");
73
+
74
+ const handleCommentSubmit = () => {
75
+ if (commentText.trim()) {
76
+ onAddComment(post.id, commentText);
77
+ setCommentText("");
78
+ setShowCommentInput(false);
79
+ }
80
+ };
81
+
82
+ const allComments = [
83
+ ...post.userComments.map(c => ({ author: 'أنت', text: c.text })),
84
+ ...post.aiComments.map(c => ({ author: c.commenterName, text: c.commentText }))
85
+ ];
86
+
87
+
88
+ return (
89
+ <div className="ai-post-card bg-card border rounded-xl overflow-hidden shadow-sm max-w-2xl mx-auto">
90
+ <div className="p-4 flex items-center gap-3">
91
+ <Image
92
+ src={post.authorImageUrl}
93
+ alt={post.author}
94
+ width={40}
95
+ height={40}
96
+ className="w-10 h-10 rounded-full border-2 border-border object-cover"
97
+ />
98
+ <div>
99
+ <p className="font-bold text-card-foreground">{post.author}</p>
100
+ </div>
101
+ </div>
102
+
103
+ {post.postImageUrl ? (
104
+ <div className="aspect-video bg-muted">
105
+ <Image
106
+ src={post.postImageUrl}
107
+ alt={post.content}
108
+ width={600}
109
+ height={400}
110
+ className="w-full h-full object-cover"
111
+ />
112
+ </div>
113
+ ) : (
114
+ <Skeleton className="aspect-video w-full" />
115
+ )}
116
+
117
+ <div className="p-4">
118
+ <p className="text-base font-medium text-card-foreground">{post.content}</p>
119
+ </div>
120
+
121
+ <div className="border-t px-2 py-1 flex justify-around">
122
+ <Button variant="ghost" size="sm" className="flex items-center gap-2" onClick={() => onLike(post.id)}>
123
+ <Heart className={cn("h-4 w-4", post.liked && "fill-red-500 text-red-500")} />
124
+ <span>{post.likes}</span>
125
+ </Button>
126
+ <Button variant="ghost" size="sm" className="flex items-center gap-2" onClick={() => setShowCommentInput(!showCommentInput)}>
127
+ <MessageCircle className="h-4 w-4" />
128
+ <span>{allComments.length}</span>
129
+ </Button>
130
+ <Button variant="ghost" size="sm" className="flex items-center gap-2" onClick={() => onGenerateComment(post)} disabled={post.loadingAiComment}>
131
+ <Sparkles className="h-4 w-4 text-purple-400"/>
132
+ <span>علّق يا ذكاء</span>
133
+ </Button>
134
+ </div>
135
+
136
+ {showCommentInput && (
137
+ <div className="p-3 border-t flex items-center gap-2">
138
+ <Input
139
+ placeholder="أضف تعليقاً..."
140
+ value={commentText}
141
+ onChange={(e) => setCommentText(e.target.value)}
142
+ onKeyDown={(e) => e.key === 'Enter' && handleCommentSubmit()}
143
+ />
144
+ <Button size="icon" onClick={handleCommentSubmit} disabled={!commentText.trim()}>
145
+ <Send className="h-4 w-4" />
146
+ </Button>
147
+ </div>
148
+ )}
149
+
150
+ { (allComments.length > 0 || post.loadingAiComment) &&
151
+ <div className="p-4 border-t bg-muted/20 space-y-3 max-h-48 overflow-y-auto">
152
+ {allComments.map((comment, index) => (
153
+ <div key={index} className="text-sm flex gap-2">
154
+ <span className="font-bold flex-shrink-0">{comment.author}:</span>
155
+ <p className="text-muted-foreground">{comment.text}</p>
156
+ </div>
157
+ ))}
158
+ {post.loadingAiComment && (
159
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
160
+ <Loader2 className="h-4 w-4 animate-spin" />
161
+ <span>شخصية ما تكتب تعليقاً...</span>
162
+ </div>
163
+ )}
164
+ </div>
165
+ }
166
+ </div>
167
+ );
168
+ };
169
+
170
+
171
+ export const AIWorldView: React.FC<AIWorldViewProps> = ({ onBack }) => {
172
+ const [posts, setPosts] = useState<Post[]>([]);
173
+ const [isLoading, setIsLoading] = useState(true);
174
+
175
+ useEffect(() => {
176
+ const initializePosts = async () => {
177
+ setIsLoading(true);
178
+ // 1. Set up the posts with text content and skeletons first
179
+ const initialStructuredPosts: Post[] = initialPostsData.map((p) => ({
180
+ ...p,
181
+ userComments: [],
182
+ aiComments: [],
183
+ loadingAiComment: false,
184
+ authorImageUrl: fallbackAvatar(p.author),
185
+ imageHint: '', // Will be generated
186
+ postImageUrl: undefined,
187
+ }));
188
+ setPosts(initialStructuredPosts);
189
+
190
+ // 2. Now, fetch the images and update the state
191
+ const postsWithImages = await Promise.all(
192
+ initialStructuredPosts.map(async (post) => {
193
+ let imageQuery = post.content;
194
+ try {
195
+ const translationResult = await translateText({ text: post.content, targetLang: 'en' });
196
+ imageQuery = translationResult.translatedText || post.content;
197
+ } catch (e) {
198
+ console.error("Translation failed, using original content for image query.", e);
199
+ }
200
+ const imageUrl = await getImage(imageQuery);
201
+ return { ...post, postImageUrl: imageUrl, imageHint: imageQuery };
202
+ })
203
+ );
204
+
205
+ setPosts(postsWithImages);
206
+ setIsLoading(false);
207
+ };
208
+
209
+ initializePosts();
210
+ }, []);
211
+
212
+ const handleGenerateComment = async (postToCommentOn: Post) => {
213
+ if (postToCommentOn.loadingAiComment) return;
214
+
215
+ setPosts(prev => prev.map(p => p.id === postToCommentOn.id ? { ...p, loadingAiComment: true } : p));
216
+
217
+ try {
218
+ const result = await generatePostComment({
219
+ postAuthor: postToCommentOn.author,
220
+ postContent: postToCommentOn.content,
221
+ postImageHint: postToCommentOn.imageHint,
222
+ });
223
+
224
+ // The result from the old flow is a string, but the new state expects an object.
225
+ // Let's adapt. We'll assume the result is just the comment text.
226
+ const newComment = {
227
+ commenterName: result.commenterName || "شخصية ذكية",
228
+ commentText: result.commentText || "تعليق مثير للاهتمام...",
229
+ };
230
+
231
+ setPosts(prev => prev.map(p => p.id === postToCommentOn.id ? {
232
+ ...p,
233
+ aiComments: [...p.aiComments, newComment],
234
+ loadingAiComment: false
235
+ } : p));
236
+
237
+ } catch (error) {
238
+ console.error("Failed to generate AI comment for post:", postToCommentOn.id, error);
239
+ setPosts(prev => prev.map(p => p.id === postToCommentOn.id ? { ...p, loadingAiComment: false } : p));
240
+ }
241
+ };
242
+
243
+ const handleLike = (postId: string) => {
244
+ setPosts(prevPosts =>
245
+ prevPosts.map(post => {
246
+ if (post.id === postId) {
247
+ return {
248
+ ...post,
249
+ liked: !post.liked,
250
+ likes: post.liked ? post.likes - 1 : post.likes + 1,
251
+ };
252
+ }
253
+ return post;
254
+ })
255
+ );
256
+ };
257
+
258
+ const handleAddComment = (postId: string, commentText: string) => {
259
+ setPosts(prevPosts =>
260
+ prevPosts.map(post => {
261
+ if (post.id === postId) {
262
+ return {
263
+ ...post,
264
+ userComments: [...post.userComments, { text: commentText }],
265
+ };
266
+ }
267
+ return post;
268
+ })
269
+ );
270
+ };
271
+
272
+ return (
273
+ <div className="flex flex-col h-full bg-background">
274
+ <header className="flex-shrink-0 flex items-center gap-4 p-4 border-b bg-card/80 backdrop-blur-sm z-10 sticky top-0">
275
+ <Button variant="ghost" size="icon" asChild>
276
+ <Link href="/">
277
+ <ArrowLeft />
278
+ </Link>
279
+ </Button>
280
+ <h1 className="text-xl font-bold">عالم الفوضى</h1>
281
+ </header>
282
+ <div className="flex-1 overflow-y-auto p-4 space-y-6">
283
+ {isLoading && posts.length === 0 ? (
284
+ Array.from({ length: 5 }).map((_, i) => (
285
+ <div key={i} className="ai-post-card bg-card border rounded-xl overflow-hidden shadow-sm max-w-2xl mx-auto space-y-3 p-4">
286
+ <div className='flex items-center gap-3'>
287
+ <Skeleton className="h-10 w-10 rounded-full" />
288
+ <Skeleton className="h-4 w-24" />
289
+ </div>
290
+ <Skeleton className="aspect-video w-full" />
291
+ <Skeleton className="h-4 w-3/4" />
292
+ </div>
293
+ ))
294
+ ) : (
295
+ posts.map(post => (
296
+ <AiPostCard
297
+ key={post.id}
298
+ post={post}
299
+ onLike={handleLike}
300
+ onAddComment={handleAddComment}
301
+ onGenerateComment={handleGenerateComment}
302
+ />
303
+ ))
304
+ )}
305
+ </div>
306
+ </div>
307
+ );
308
+ };
src/components/ui/input.tsx CHANGED
@@ -8,7 +8,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
8
  <input
9
  type={type}
10
  className={cn(
11
- "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
  className
13
  )}
14
  ref={ref}
 
8
  <input
9
  type={type}
10
  className={cn(
11
+ "flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
12
  className
13
  )}
14
  ref={ref}