import React, { useState, useEffect, useRef, useCallback } from ‘react’;
import { initializeApp } from ‘firebase/app’;
import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from ‘firebase/auth’;
import { getFirestore, doc, setDoc, getDoc, collection, query, onSnapshot, addDoc, deleteDoc } from ‘firebase/firestore’;
// Removed shadcn/ui imports and will use native HTML elements with Tailwind for styling
import { Plus, Trash2, Download, Copy } from ‘lucide-react’; // Using Lucide React for icons
// Helper function to safely get appId and firebaseConfig
const getFirebaseConfig = () => {
try {
return typeof __firebase_config !== ‘undefined’ ? JSON.parse(__firebase_config) : {};
} catch (e) {
console.error(“Error parsing __firebase_config:”, e);
return {};
}
};
const appId = typeof __app_id !== ‘undefined’ ? __app_id : ‘default-app-id’;
const firebaseConfig = getFirebaseConfig();
// Initialize Firebase (outside component to prevent re-initialization)
let app, db, auth;
if (Object.keys(firebaseConfig).length > 0) {
app = initializeApp(firebaseConfig);
db = getFirestore(app);
auth = getAuth(app);
}
// Main App Component
const App = () => {
const [user, setUser] = useState(null);
const [sampleArticles, setSampleArticles] = useState([]);
const [newSampleArticle, setNewSampleArticle] = useState(”);
const [keyword, setKeyword] = useState(”);
const [articleLength, setArticleLength] = useState(1500); // Default to 1500 words, changed to number
const [generatedArticle, setGeneratedArticle] = useState(”);
const [isLoading, setIsLoading] = useState(false);
const [message, setMessage] = useState(”); // For user messages (e.g., success/error)
const [isAuthReady, setIsAuthReady] = useState(false);
// Firestore Refs
const sampleArticlesRef = user && db ? collection(db, `artifacts/${appId}/users/${user.uid}/sample_articles`) : null;
const generatedArticlesRef = user && db ? collection(db, `artifacts/${appId}/users/${user.uid}/generated_articles`) : null;
// Authentication and Firestore Initialization
useEffect(() => {
if (!app || !auth || !db) {
setMessage(“Firebase is not configured. Please ensure __firebase_config is correctly set.”);
setIsAuthReady(false);
return;
}
const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
if (currentUser) {
setUser(currentUser);
} else {
try {
if (typeof __initial_auth_token !== ‘undefined’ && __initial_auth_token) {
await signInWithCustomToken(auth, __initial_auth_token);
} else {
await signInAnonymously(auth);
}
} catch (error) {
console.error(“Error signing in:”, error);
setMessage(`Authentication failed: ${error.message}`);
}
}
setIsAuthReady(true);
});
return () => unsubscribe();
}, []);
// Fetch sample articles when user is authenticated
useEffect(() => {
if (!isAuthReady || !user || !sampleArticlesRef) return;
const q = query(sampleArticlesRef);
const unsubscribe = onSnapshot(q, (snapshot) => {
const articles = snapshot.docs.map(doc => ({ id: doc.id, …doc.data() }));
setSampleArticles(articles);
}, (error) => {
console.error(“Error fetching sample articles:”, error);
setMessage(“Error fetching sample articles.”);
});
return () => unsubscribe();
}, [isAuthReady, user, sampleArticlesRef]);
// Function to add a new sample article
const addSampleArticle = async () => {
if (!newSampleArticle.trim() || !user || !sampleArticlesRef) {
setMessage(“Please enter some text for the sample article.”);
return;
}
try {
await addDoc(sampleArticlesRef, {
content: newSampleArticle,
createdAt: new Date(),
userId: user.uid,
});
setNewSampleArticle(”);
setMessage(“Sample article added!”);
} catch (error) {
console.error(“Error adding sample article:”, error);
setMessage(“Failed to add sample article.”);
}
};
// Function to delete a sample article
const deleteSampleArticle = async (id) => {
if (!user || !sampleArticlesRef) return;
try {
await deleteDoc(doc(sampleArticlesRef, id));
setMessage(“Sample article deleted!”);
} catch (error) {
console.error(“Error deleting sample article:”, error);
setMessage(“Failed to delete sample article.”);
}
};
// Function to generate article using Gemini API
const generateArticle = async () => {
if (!keyword.trim()) {
setMessage(“Please enter a keyword to generate the article.”);
return;
}
if (sampleArticles.length === 0) {
setMessage(“Please upload 5-10 sample articles first for style guidance.”);
return;
}
setIsLoading(true);
setMessage(‘Generating article…’);
// Construct the prompt using sample articles for style guidance
let prompt = `You are an expert article writer. Your task is to generate a comprehensive, human-like, AdSense-friendly, and plagiarism-free article on the topic “${keyword}”.
You must follow the exact writing style, structure, and tone from the following sample human-written articles. Analyze them carefully for introduction style (especially starting with a user problem and quick solution), paragraph length, heading hierarchy (H1, H2, H3), vocabulary, voice, and overall flow.
—BEGIN SAMPLE ARTICLES FOR STYLE GUIDANCE—
${sampleArticles.map(sa => `\n—SAMPLE ARTICLE—\n${sa.content}\n—END SAMPLE ARTICLE—`).join(‘\n’)}
—END SAMPLE ARTICLES FOR STYLE GUIDANCE—
Now, generate a new article following these specific requirements:
1. **Topic**: “${keyword}”
2. **Writing Style, Structure, and Tone**: Strictly adhere to the examples provided in the “SAMPLE ARTICLES FOR STYLE GUIDANCE” section above.
3. **Real-time Information**: Incorporate the latest and most relevant information available about the topic. If specific external data isn’t provided, use your up-to-date general knowledge.
4. **Introduction**: Start with a common user problem related to “${keyword}” and immediately offer a quick, concise solution.
5. **Structure**:
* Use a clear H1 for the main title.
* Break down the content with SEO-friendly H2 and H3 headings.
* Ensure paragraphs are well-structured and easy to read.
6. **EEAT (Expertise, Experience, Authoritativeness, Trustworthiness)**: Write the article in a way that demonstrates high EEAT. Use credible language, present facts clearly, and show a deep understanding of the subject.
7. **Human-like, AdSense-friendly, Plagiarism-free**: Ensure the generated content reads naturally, adheres to advertising policies, and is original.
8. **Length**: The article should be approximately ${articleLength} words long. Adjust the depth and breadth of topics to fit this word count.
9. **Conclusion & FAQ**: End the article with a strong concluding paragraph and then include an “FAQ” section. The FAQ should contain 3-5 common questions related to “${keyword}” based on typical search intent, with concise answers.
Begin the article now.`;
try {
const payload = {
contents: [{ role: “user”, parts: [{ text: prompt }] }],
};
const apiKey = “”; // Canvas will provide this at runtime if empty
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
const response = await fetch(apiUrl, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify(payload)
});
const result = await response.json();
if (result.candidates && result.candidates.length > 0 &&
result.candidates[0].content && result.candidates[0].content.parts &&
result.candidates[0].content.parts.length > 0) {
const text = result.candidates[0].content.parts[0].text;
setGeneratedArticle(text);
setMessage(“Article generated successfully!”);
// Save generated article to Firestore
if (user && generatedArticlesRef) {
await addDoc(generatedArticlesRef, {
keyword: keyword,
articleContent: text,
length: articleLength,
createdAt: new Date(),
userId: user.uid,
});
}
} else {
console.error(“Unexpected API response structure:”, result);
setMessage(“Failed to generate article: Unexpected response from AI.”);
}
} catch (error) {
console.error(“Error generating article:”, error);
setMessage(`Failed to generate article: ${error.message}`);
} finally {
setIsLoading(false);
}
};
// Function to copy generated article to clipboard
const copyArticleToClipboard = () => {
if (generatedArticle) {
// Use document.execCommand for better iframe compatibility
const textarea = document.createElement(‘textarea’);
textarea.value = generatedArticle;
document.body.appendChild(textarea);
textarea.select();
document.execCommand(‘copy’);
document.body.removeChild(textarea);
setMessage(‘Article copied to clipboard!’);
}
};
// Function to export article as TXT
const exportArticleAsTxt = () => {
if (generatedArticle) {
const blob = new Blob([generatedArticle], { type: ‘text/plain;charset=utf-8’ });
const url = URL.createObjectURL(blob);
const a = document.createElement(‘a’);
a.href = url;
a.download = `${keyword.replace(/\s+/g, ‘-‘)}-article.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
setMessage(‘Article exported as TXT!’);
}
};
// Slider component for article length
const LengthSlider = ({ value, onChange, min, max, step }) => {
return (
onChange(Number(e.target.value))}
className=”w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700″
/>
);
};
if (!isAuthReady) {
return (
Loading application…
);
}
// Ensure user ID is displayed for multi-user context
const displayUserId = user ? `User ID: ${user.uid}` : ‘Not authenticated’;
return (
AI Article Writer
{displayUserId}
{message && (
{message}
)}
{/* Left Column: Sample Articles & Generation Controls */}
{/* Replaced Card with div */}
1. Provide Sample Articles
Paste 5-10 human-written articles here. The AI will learn their style, structure, and tone.
{sampleArticles.length > 0 && (
Your Samples:
{sampleArticles.map((sa, index) => (
{index + 1}. {sa.content.substring(0, 100)}…
))}
)}
2. Generate New Article
Enter a keyword and set the desired length.
{/* Replaced Label with label */}
setKeyword(e.target.value)}
className=”w-full rounded-lg border border-gray-300 dark:border-gray-600 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-gray-100 p-2 mb-4″ // Replaced Input with input
/>
{/* Replaced Label with label */}