diff --git a/.gitignore b/.gitignore index 773bfd6..6b85d34 100644 --- a/.gitignore +++ b/.gitignore @@ -1,37 +1,240 @@ -# Compiled source # -################### -*.com -*.class -*.dll -*.exe -*.o +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions *.so -# Packages # -############ -# it's better to unpack these files and commit the raw source -# git has its own built in compression methods -*.7z -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar -*.zip - -# Logs and databases # -###################### +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +PIPFILE.lock + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: *.log -*.sql +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be added to the global gitignore or merged into this project gitignore. For a PyCharm +# project, it is recommended to exclude the following: +.idea/ + +# VS Code +.vscode/ + +# System files +.DS_Store +Thumbs.db + +# Custom directories +logs/ +data/ +uploads/ +temp/ +cache/ + +# API Keys and sensitive data +.env.local +.env.production +config.local.py +secrets.json + +# Database files +*.db *.sqlite -# OS generated files # -###################### +# Generated content +generated_content/ +output/ + +# Documentation build +docs/_build/ +site/ + +# Backup files +*.bak +*.backup +*.old + +# IDE specific +*.swp +*.swo +*~ + +# Coverage reports +htmlcov/ +.coverage + +# node modules (if using any JS tools) +node_modules/ + +# Mac specific .DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db +.AppleDouble +.LSOverride + +# Windows specific Thumbs.db +ehthumbs.db +Desktop.ini + +# Linux specific +*~ + +# Temporary files +*.tmp +*.temp + +# AI model files (if downloading models) +models/ +*.pkl +*.pt +*.pth +*.h5 + +# Logs +*.log +logs/ + +# Media files +media/ +static_root/ \ No newline at end of file diff --git a/README.md b/README.md index 296b930..9b2f261 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,308 @@ -
+# 🤖 سیستم تولید محتوای هوش مصنوعی چندپلتفرمی - +یک سیستم جامع و حرفه‌ای برای تولید محتوای بهینه شده برای پلتفرم‌های مختلف با استفاده از هوش مصنوعی و قابلیت‌های پیشرفته Function Calling. -# GitHub Pages +## ✨ ویژگی‌های کلیدی -_Create a site or blog from your GitHub repositories with GitHub Pages._ +### 🎯 تولید محتوای چندپلتفرمی +- **اینستاگرام**: پست‌ها، استوری‌ها، کروسل‌ها و اسکریپت Reel +- **تلگرام**: پست‌ها، اخبار و مقالات با پشتیبانی HTML/Markdown +- **وب‌سایت**: مقالات SEO-friendly با ساختار استاندارد +- **ایتا**: محتوای بهینه شده برای پیام‌رسان داخلی +- **روبیکا**: محتوا متناسب با الگوریتم پلتفرم -
+### 🧠 قابلیت‌های هوشمند +- **تحلیل موضوع**: شناسایی خودکار تم و حس‌وحال محتوا +- **بهینه‌سازی SEO**: تحلیل و بهبود خودکار برای موتورهای جستجو +- **تولید ایده‌های بصری**: پیشنهاد طراحی، رنگ‌بندی و چیدمان +- **زمان‌بندی هوشمند**: انتشار خودکار در بهترین زمان‌ها +- **آنالیتیک پیشرفته**: ردیابی عملکرد و آمار تفصیلی - +### 🔧 Function Calling +- `generate_content(platform, topic, keywords, tone)`: تولید محتوا +- `optimize_for_seo(text, target_keywords)`: بهینه‌سازی SEO +- `generate_visual_idea(content)`: تولید ایده‌های بصری +- `schedule_post(platform, datetime)`: زمان‌بندی انتشار -## Welcome +## 🚀 راه‌اندازی سریع -With GitHub Pages, you can host project blogs, documentation, resumes, portfolios, or any other static content you'd like. Your GitHub repository can easily become its own website. In this course, we'll show you how to set up your own site or blog using GitHub Pages. +### نصب وابستگی‌ها +```bash +pip install -r requirements.txt +``` -- **Who is this for**: Beginners, students, project maintainers, small businesses. -- **What you'll learn**: How to build a GitHub Pages site. -- **What you'll build**: We'll build a simple GitHub Pages site with a blog. We'll use [Jekyll](https://jekyllrb.com), a static site generator. -- **Prerequisites**: If you need to learn about branches, commits, and pull requests, take [Introduction to GitHub](https://github.com/skills/introduction-to-github) first. -- **How long**: This course takes less than one hour to complete. +### اجرای سرور +```bash +python main.py +``` -In this course, you will: +سرور روی `http://localhost:8000` راه‌اندازی می‌شود. -1. Enable GitHub Pages -2. Configure your site -3. Customize your home page -4. Create a blog post -5. Merge your pull request +### دسترسی به رابط کاربری +مرورگر خود را باز کرده و به آدرس بالا بروید. -### How to start this course +### مشاهده مستندات API +- Swagger UI: `http://localhost:8000/docs` +- ReDoc: `http://localhost:8000/redoc` - +## 📝 نحوه استفاده -[![start-course](https://user-images.githubusercontent.com/1221423/235727646-4a590299-ffe5-480d-8cd5-8194ea184546.svg)](https://github.com/new?template_owner=skills&template_name=github-pages&owner=%40me&name=skills-github-pages&description=My+clone+repository&visibility=public) +### 1. تولید محتوا از طریق API -1. Right-click **Start course** and open the link in a new tab. -2. In the new tab, most of the prompts will automatically fill in for you. - - For owner, choose your personal account or an organization to host the repository. - - We recommend creating a public repository, as private repositories will [use Actions minutes](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions). - - Scroll down and click the **Create repository** button at the bottom of the form. -3. After your new repository is created, wait about 20 seconds, then refresh the page. Follow the step-by-step instructions in the new repository's README. +```python +import requests - + \ No newline at end of file diff --git a/examples/sample_requests.py b/examples/sample_requests.py new file mode 100644 index 0000000..b0dfee9 --- /dev/null +++ b/examples/sample_requests.py @@ -0,0 +1,244 @@ +""" +نمونه‌های استفاده از سیستم تولید محتوای هوش مصنوعی +""" + +import requests +import json +from datetime import datetime, timedelta + +# آدرس سرور (در صورت اجرا در محیط محلی) +BASE_URL = "http://localhost:8000/api" + +def example_generate_content(): + """نمونه تولید محتوا برای چند پلتفرم""" + + payload = { + "topic": "فواید یادگیری هوش مصنوعی", + "keywords": ["هوش مصنوعی", "یادگیری ماشین", "فناوری", "آینده"], + "platforms": ["instagram", "telegram", "website"], + "content_type": "post", + "tone": "educational", + "target_audience": "دانشجویان و علاقه‌مندان فناوری" + } + + response = requests.post(f"{BASE_URL}/generate-content", json=payload) + + if response.status_code == 200: + result = response.json() + + print("✅ محتوا با موفقیت تولید شد!") + print(f"📊 تعداد پلتفرم‌ها: {result['total_platforms']}") + print(f"🕐 زمان تولید: {result['generated_at']}") + + # نمایش محتوا برای هر پلتفرم + for platform, content in result['contents'].items(): + print(f"\n📱 {platform.upper()}:") + print("-" * 50) + if isinstance(content, dict): + print(content['content']) + print(f"📏 تعداد کاراکتر: {content.get('character_count', 0)}") + print(f"⭐ امتیاز جذابیت: {content.get('engagement_score', 0)}/10") + else: + print(content) + + # نمایش هشتگ‌ها + print(f"\n🏷️ هشتگ‌های پیشنهادی:") + for platform, hashtags in result['hashtags'].items(): + print(f"{platform}: {' '.join(hashtags[:5])}") + + # نمایش پیشنهادات CTA + print(f"\n📢 پیشنهادات فراخوان عمل:") + for cta in result['cta_suggestions'][:3]: + print(f"• {cta}") + + return result + else: + print(f"❌ خطا: {response.status_code}") + print(response.text) + +def example_optimize_seo(): + """نمونه بهینه‌سازی SEO""" + + sample_text = """ + هوش مصنوعی یکی از مهم‌ترین فناوری‌های قرن حاضر است. + این فناوری در حال تغییر دادن دنیای ما است و کاربردهای فراوانی دارد. + یادگیری ماشین بخش مهمی از هوش مصنوعی محسوب می‌شود. + """ + + payload = { + "text": sample_text, + "target_keywords": ["هوش مصنوعی", "یادگیری ماشین", "فناوری"], + "focus_keyword": "هوش مصنوعی", + "meta_description": "راهنمای جامع هوش مصنوعی و کاربردهای آن" + } + + response = requests.post(f"{BASE_URL}/optimize-seo", json=payload) + + if response.status_code == 200: + result = response.json() + + print("✅ بهینه‌سازی SEO انجام شد!") + print(f"🎯 امتیاز SEO: {result['seo_score']}/100") + print(f"📖 امتیاز خوانایی: {result['readability_score']}/100") + print(f"📝 تعداد کلمات: {result['word_count']}") + + print(f"\n📊 تراکم کلمات کلیدی:") + for keyword, density in result['keywords_density'].items(): + print(f"• {keyword}: {density}%") + + print(f"\n💡 توصیه‌های بهبود:") + for recommendation in result['recommendations'][:5]: + print(f"• {recommendation}") + + return result + else: + print(f"❌ خطا: {response.status_code}") + print(response.text) + +def example_generate_visual_ideas(): + """نمونه تولید ایده‌های بصری""" + + sample_content = """ + ۵ نکته طلایی برای شروع یادگیری هوش مصنوعی: + 1. پایه‌های ریاضی را تقویت کنید + 2. زبان برنامه‌نویسی Python یاد بگیرید + 3. با کتابخانه‌های معروف آشنا شوید + 4. پروژه‌های عملی انجام دهید + 5. در جامعه فعال باشید + """ + + payload = { + "content": sample_content, + "platform": "instagram", + "content_type": "post", + "style_preferences": ["modern", "colorful"] + } + + response = requests.post(f"{BASE_URL}/generate-visual-idea", json=payload) + + if response.status_code == 200: + result = response.json() + + print("✅ ایده‌های بصری تولید شدند!") + + print(f"\n🖼️ ایده‌های تصویری:") + for i, idea in enumerate(result['image_ideas'], 1): + print(f"{i}. سبک: {idea['style']}") + print(f" توضیح: {idea['description']}") + print(f" چیدمان: {idea['layout']}") + + print(f"\n🎬 ایده‌های ویدئویی:") + for i, idea in enumerate(result['video_ideas'], 1): + print(f"{i}. نوع: {idea['type']}") + print(f" مدت: {idea['duration']}") + print(f" سبک: {idea['style']}") + + print(f"\n🎨 رنگ‌های پیشنهادی:") + print(" ".join(result['color_suggestions'][:5])) + + return result + else: + print(f"❌ خطا: {response.status_code}") + print(response.text) + +def example_schedule_content(): + """نمونه زمان‌بندی محتوا""" + + # زمان انتشار: یک ساعت بعد + schedule_time = datetime.now() + timedelta(hours=1) + + payload = { + "platform": "telegram", + "content": "🚀 محتوای تست برای زمان‌بندی!\n\nاین پست به صورت خودکار منتشر می‌شود.", + "schedule_time": schedule_time.isoformat(), + "auto_publish": True + } + + response = requests.post(f"{BASE_URL}/schedule-post", json=payload) + + if response.status_code == 200: + result = response.json() + + print("✅ محتوا زمان‌بندی شد!") + print(f"🆔 شناسه: {result['schedule_id']}") + print(f"⏰ زمان انتشار: {result['scheduled_time']}") + + return result + else: + print(f"❌ خطا: {response.status_code}") + print(response.text) + +def example_get_scheduled_posts(): + """نمونه دریافت پست‌های زمان‌بندی شده""" + + response = requests.get(f"{BASE_URL}/scheduled-posts") + + if response.status_code == 200: + posts = response.json() + + print(f"📋 تعداد پست‌های زمان‌بندی شده: {len(posts)}") + + for post in posts[:5]: # نمایش ۵ پست اول + print(f"\n🆔 {post['id']}") + print(f"📱 پلتفرم: {post['platform']}") + print(f"📄 محتوا: {post['content'][:50]}...") + print(f"📅 زمان: {post['schedule_time']}") + print(f"🔘 وضعیت: {post['status']}") + + return posts + else: + print(f"❌ خطا: {response.status_code}") + print(response.text) + +def example_get_stats(): + """نمونه دریافت آمار سیستم""" + + response = requests.get(f"{BASE_URL}/stats") + + if response.status_code == 200: + stats = response.json() + + print("📊 آمار سیستم:") + print(f"📝 محتوای تولید شده: {stats.get('total_generated_contents', 0)}") + print(f"⏰ زمان‌بندی شده: {stats.get('scheduled_posts_count', 0)}") + print(f"📱 پلتفرم‌های فعال: {stats.get('active_platforms', 0)}") + + return stats + else: + print(f"❌ خطا: {response.status_code}") + print(response.text) + +def run_all_examples(): + """اجرای تمام نمونه‌ها""" + + print("🤖 شروع نمایش نمونه‌های سیستم تولید محتوای هوش مصنوعی") + print("=" * 60) + + try: + print("\n1️⃣ تولید محتوا:") + example_generate_content() + + print("\n2️⃣ بهینه‌سازی SEO:") + example_optimize_seo() + + print("\n3️⃣ تولید ایده‌های بصری:") + example_generate_visual_ideas() + + print("\n4️⃣ زمان‌بندی محتوا:") + example_schedule_content() + + print("\n5️⃣ دریافت پست‌های زمان‌بندی شده:") + example_get_scheduled_posts() + + print("\n6️⃣ آمار سیستم:") + example_get_stats() + + except requests.exceptions.ConnectionError: + print("❌ خطا: نمی‌توان به سرور متصل شد.") + print("🔧 لطفاً مطمئن شوید که سرور در حال اجرا است:") + print(" python main.py") + except Exception as e: + print(f"❌ خطای غیرمنتظره: {e}") + +if __name__ == "__main__": + run_all_examples() \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..40d875d --- /dev/null +++ b/main.py @@ -0,0 +1,275 @@ +""" +سیستم تولید محتوای هوش مصنوعی چندپلتفرمی +نویسنده: AI Content Generator System +ورژن: 1.0.0 +""" + +from fastapi import FastAPI, HTTPException, BackgroundTasks +from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates +from fastapi.responses import HTMLResponse +from fastapi import Request +from pydantic import BaseModel, Field +from typing import List, Optional, Dict, Any +from datetime import datetime, timedelta +import json +import asyncio +import logging +from enum import Enum + +# وارد کردن ماژول‌های سیستم +from src.content_generator import ContentGenerator +from src.seo_optimizer import SEOOptimizer +from src.visual_idea_generator import VisualIdeaGenerator +from src.scheduler import ContentScheduler +from src.platforms import PlatformType, ContentType +from src.models import ( + ContentRequest, ContentResponse, + ScheduleRequest, VisualIdeaRequest, + SEOOptimizationRequest +) + +# تنظیمات لاگ +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) +logger = logging.getLogger(__name__) + +# ایجاد اپلیکیشن FastAPI +app = FastAPI( + title="سیستم تولید محتوای هوش مصنوعی", + description="سیستم جامع تولید محتوا برای پلتفرم‌های مختلف با قابلیت Function Calling", + version="1.0.0", + docs_url="/docs", + redoc_url="/redoc" +) + +# تنظیمات CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# ایجاد نمونه‌های کلاس‌ها +content_generator = ContentGenerator() +seo_optimizer = SEOOptimizer() +visual_idea_generator = VisualIdeaGenerator() +scheduler = ContentScheduler() + +# تنظیم فایل‌های استاتیک و قالب‌ها +app.mount("/static", StaticFiles(directory="static"), name="static") +templates = Jinja2Templates(directory="templates") + +@app.get("/", response_class=HTMLResponse) +async def home(request: Request): + """صفحه اصلی سیستم""" + return templates.TemplateResponse("index.html", {"request": request}) + +@app.post("/api/generate-content", response_model=ContentResponse) +async def generate_content_api(request: ContentRequest): + """ + تولید محتوا برای پلتفرم‌های مختلف + + Function: generate_content(platform, topic, keywords, tone) + """ + try: + logger.info(f"تولید محتوا برای موضوع: {request.topic}") + + # تولید محتوا برای هر پلتفرم + contents = {} + + for platform in request.platforms: + platform_content = await content_generator.generate_for_platform( + platform=platform, + topic=request.topic, + keywords=request.keywords, + tone=request.tone, + target_audience=request.target_audience, + content_type=request.content_type + ) + contents[platform.value] = platform_content + + # تولید هشتگ‌ها + hashtags = await content_generator.generate_hashtags( + topic=request.topic, + keywords=request.keywords, + platforms=request.platforms + ) + + # تولید ایده‌های CTA + cta_suggestions = await content_generator.generate_cta_suggestions( + topic=request.topic, + platforms=request.platforms, + content_type=request.content_type + ) + + response = ContentResponse( + contents=contents, + hashtags=hashtags, + cta_suggestions=cta_suggestions, + generated_at=datetime.now(), + request_id=f"req_{int(datetime.now().timestamp())}" + ) + + logger.info("محتوا با موفقیت تولید شد") + return response + + except Exception as e: + logger.error(f"خطا در تولید محتوا: {str(e)}") + raise HTTPException(status_code=500, detail=f"خطا در تولید محتوا: {str(e)}") + +@app.post("/api/optimize-seo") +async def optimize_for_seo_api(request: SEOOptimizationRequest): + """ + بهینه‌سازی محتوا برای SEO + + Function: optimize_for_seo(text, target_keywords) + """ + try: + logger.info("شروع بهینه‌سازی SEO") + + optimized_content = await seo_optimizer.optimize_content( + text=request.text, + target_keywords=request.target_keywords, + meta_description=request.meta_description, + focus_keyword=request.focus_keyword + ) + + logger.info("بهینه‌سازی SEO با موفقیت انجام شد") + return optimized_content + + except Exception as e: + logger.error(f"خطا در بهینه‌سازی SEO: {str(e)}") + raise HTTPException(status_code=500, detail=f"خطا در بهینه‌سازی SEO: {str(e)}") + +@app.post("/api/generate-visual-idea") +async def generate_visual_idea_api(request: VisualIdeaRequest): + """ + تولید ایده‌های بصری برای محتوا + + Function: generate_visual_idea(content) + """ + try: + logger.info("تولید ایده‌های بصری") + + visual_ideas = await visual_idea_generator.generate_ideas( + content=request.content, + platform=request.platform, + content_type=request.content_type, + style_preferences=request.style_preferences + ) + + logger.info("ایده‌های بصری با موفقیت تولید شدند") + return visual_ideas + + except Exception as e: + logger.error(f"خطا در تولید ایده‌های بصری: {str(e)}") + raise HTTPException(status_code=500, detail=f"خطا در تولید ایده‌های بصری: {str(e)}") + +@app.post("/api/schedule-post") +async def schedule_post_api(request: ScheduleRequest, background_tasks: BackgroundTasks): + """ + زمان‌بندی انتشار محتوا + + Function: schedule_post(platform, datetime) + """ + try: + logger.info(f"زمان‌بندی انتشار برای {request.platform.value}") + + schedule_id = await scheduler.schedule_content( + platform=request.platform, + content=request.content, + schedule_time=request.schedule_time, + auto_publish=request.auto_publish + ) + + # اضافه کردن به تسک‌های پس‌زمینه + if request.auto_publish: + background_tasks.add_task( + scheduler.execute_scheduled_post, + schedule_id + ) + + logger.info(f"محتوا با شناسه {schedule_id} زمان‌بندی شد") + return { + "message": "محتوا با موفقیت زمان‌بندی شد", + "schedule_id": schedule_id, + "scheduled_time": request.schedule_time + } + + except Exception as e: + logger.error(f"خطا در زمان‌بندی: {str(e)}") + raise HTTPException(status_code=500, detail=f"خطا در زمان‌بندی: {str(e)}") + +@app.get("/api/scheduled-posts") +async def get_scheduled_posts(): + """دریافت لیست پست‌های زمان‌بندی شده""" + try: + scheduled_posts = await scheduler.get_scheduled_posts() + return scheduled_posts + except Exception as e: + logger.error(f"خطا در دریافت پست‌های زمان‌بندی شده: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) + +@app.delete("/api/scheduled-posts/{schedule_id}") +async def cancel_scheduled_post(schedule_id: str): + """لغو پست زمان‌بندی شده""" + try: + result = await scheduler.cancel_scheduled_post(schedule_id) + return result + except Exception as e: + logger.error(f"خطا در لغو پست: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) + +@app.get("/api/platforms") +async def get_supported_platforms(): + """دریافت لیست پلتفرم‌های پشتیبانی شده""" + return { + "platforms": [platform.value for platform in PlatformType], + "content_types": [content_type.value for content_type in ContentType] + } + +@app.get("/api/stats") +async def get_system_stats(): + """آمار سیستم""" + try: + stats = { + "total_generated_contents": await content_generator.get_total_generated(), + "scheduled_posts_count": await scheduler.get_scheduled_count(), + "active_platforms": len(PlatformType), + "system_uptime": datetime.now() - app.state.start_time if hasattr(app.state, 'start_time') else None + } + return stats + except Exception as e: + logger.error(f"خطا در دریافت آمار: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) + +@app.on_event("startup") +async def startup_event(): + """رویداد راه‌اندازی اپلیکیشن""" + app.state.start_time = datetime.now() + logger.info("🚀 سیستم تولید محتوای هوش مصنوعی راه‌اندازی شد") + + # راه‌اندازی scheduler + await scheduler.start() + +@app.on_event("shutdown") +async def shutdown_event(): + """رویداد خاموش شدن اپلیکیشن""" + logger.info("⛔ سیستم در حال خاموش شدن...") + await scheduler.stop() + +if __name__ == "__main__": + import uvicorn + uvicorn.run( + "main:app", + host="0.0.0.0", + port=8000, + reload=True, + log_level="info" + ) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5de8ee1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,16 @@ +fastapi==0.104.1 +uvicorn==0.24.0 +pydantic==2.5.0 +python-multipart==0.0.6 +jinja2==3.1.2 +python-jose==3.3.0 +passlib==1.7.4 +bcrypt==4.1.2 +openai==1.3.7 +google-generativeai==0.3.2 +requests==2.31.0 +beautifulsoup4==4.12.2 +python-dateutil==2.8.2 +schedule==1.2.0 +typing-extensions==4.8.0 +aiofiles==23.2.1 \ No newline at end of file diff --git a/run_demo.py b/run_demo.py new file mode 100755 index 0000000..20e35e5 --- /dev/null +++ b/run_demo.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +""" +اسکریپت نمایشی برای راه‌اندازی سریع سیستم تولید محتوای هوش مصنوعی +""" + +import os +import sys +import subprocess +import time +import webbrowser +from pathlib import Path + +def check_python_version(): + """بررسی نسخه پایتون""" + if sys.version_info < (3, 8): + print("❌ نسخه پایتون باید 3.8 یا بالاتر باشد") + print(f"نسخه فعلی: {sys.version}") + return False + print(f"✅ نسخه پایتون: {sys.version.split()[0]}") + return True + +def install_requirements(): + """نصب وابستگی‌ها""" + print("📦 در حال نصب وابستگی‌ها...") + try: + subprocess.check_call([ + sys.executable, "-m", "pip", "install", "-r", "requirements.txt" + ]) + print("✅ وابستگی‌ها با موفقیت نصب شدند") + return True + except subprocess.CalledProcessError: + print("❌ خطا در نصب وابستگی‌ها") + return False + +def setup_demo_data(): + """آماده‌سازی داده‌های نمونه""" + print("🔧 آماده‌سازی داده‌های نمونه...") + + # ایجاد پوشه‌های مورد نیاز + directories = ["logs", "data", "uploads"] + for directory in directories: + Path(directory).mkdir(exist_ok=True) + + print("✅ داده‌های نمونه آماده شدند") + +def start_server(): + """راه‌اندازی سرور""" + print("🚀 راه‌اندازی سرور...") + + try: + # راه‌اندازی سرور در پس‌زمینه + process = subprocess.Popen([ + sys.executable, "-m", "uvicorn", "main:app", + "--host", "0.0.0.0", "--port", "8000", "--reload" + ]) + + print("⏳ منتظر راه‌اندازی سرور...") + time.sleep(5) # منتظر راه‌اندازی سرور + + # بررسی وضعیت سرور + if process.poll() is None: + print("✅ سرور با موفقیت راه‌اندازی شد") + print("🌐 آدرس سرور: http://localhost:8000") + return process + else: + print("❌ خطا در راه‌اندازی سرور") + return None + + except Exception as e: + print(f"❌ خطا در راه‌اندازی سرور: {e}") + return None + +def run_demo_examples(): + """اجرای نمونه‌های دمو""" + print("\n🎯 اجرای نمونه‌های دمو...") + + try: + # منتظر آماده شدن کامل سرور + time.sleep(3) + + print("📝 تست تولید محتوا...") + subprocess.run([ + sys.executable, "examples/sample_requests.py" + ], timeout=30) + + print("✅ نمونه‌ها با موفقیت اجرا شدند") + + except subprocess.TimeoutExpired: + print("⏰ زمان اجرای نمونه‌ها به پایان رسید") + except Exception as e: + print(f"❌ خطا در اجرای نمونه‌ها: {e}") + +def open_browser(): + """باز کردن مرورگر""" + print("🌐 باز کردن مرورگر...") + try: + webbrowser.open("http://localhost:8000") + print("✅ مرورگر باز شد") + except Exception as e: + print(f"❌ خطا در باز کردن مرورگر: {e}") + print("لطفاً خودتان آدرس http://localhost:8000 را در مرورگر باز کنید") + +def print_banner(): + """نمایش بنر خوشامدگویی""" + banner = """ + ╔══════════════════════════════════════════════════════════════════╗ + ║ ║ + ║ 🤖 سیستم تولید محتوای هوش مصنوعی چندپلتفرمی ║ + ║ ║ + ║ 🚀 نسخه نمایشی 1.0.0 ║ + ║ ║ + ╚══════════════════════════════════════════════════════════════════╝ + """ + print(banner) + +def print_help(): + """نمایش راهنما""" + help_text = """ + 🎯 ویژگی‌های سیستم: + + ✨ تولید محتوا برای 5 پلتفرم (Instagram, Telegram, Website, Eitaa, Rubika) + 🔍 بهینه‌سازی خودکار SEO + 🎨 تولید ایده‌های بصری + ⏰ زمان‌بندی انتشار هوشمند + 📊 آنالیتیک و آمار پیشرفته + + 🌐 آدرس‌های مهم: + • رابط کاربری: http://localhost:8000 + • مستندات API: http://localhost:8000/docs + • ReDoc: http://localhost:8000/redoc + + ⌨️ کلیدهای میانبر: + • Ctrl+Enter: تولید محتوا + • Ctrl+S: ذخیره محتوا + • Ctrl+C: کپی محتوا + + 🆘 در صورت مشکل: + • بررسی کنید Python 3.8+ نصب باشد + • از اجرای همزمان چند نمونه خودداری کنید + • فایل‌های لاگ را در پوشه logs بررسی کنید + """ + print(help_text) + +def main(): + """تابع اصلی""" + print_banner() + + # بررسی‌های اولیه + if not check_python_version(): + return + + print("🔍 بررسی محیط...") + + # نصب وابستگی‌ها + if not install_requirements(): + print("❌ نصب وابستگی‌ها ناموفق بود") + return + + # آماده‌سازی + setup_demo_data() + + # راه‌اندازی سرور + server_process = start_server() + if not server_process: + return + + try: + print("\n" + "="*70) + print("🎉 سیستم آماده است!") + print("="*70) + + # نمایش راهنما + print_help() + + # باز کردن مرورگر + open_browser() + + # اجرای نمونه‌ها (اختیاری) + response = input("\n❓ آیا می‌خواهید نمونه‌های دمو را اجرا کنید؟ (y/n): ") + if response.lower() in ['y', 'yes', 'آری', 'بله']: + run_demo_examples() + + print("\n🔄 سرور در حال اجرا است...") + print("💡 برای خروج Ctrl+C را فشار دهید") + + # نگه داشتن سرور + while True: + time.sleep(1) + + except KeyboardInterrupt: + print("\n\n🛑 در حال بستن سرور...") + server_process.terminate() + server_process.wait() + print("✅ سرور بسته شد") + print("👋 خداحافظ!") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..32d0aa9 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1 @@ +# سیستم تولید محتوای هوش مصنوعی چندپلتفرمی \ No newline at end of file diff --git a/src/content_generator.py b/src/content_generator.py new file mode 100644 index 0000000..9afdbd3 --- /dev/null +++ b/src/content_generator.py @@ -0,0 +1,452 @@ +""" +تولیدکننده محتوا برای پلتفرم‌های مختلف +""" + +import asyncio +import json +import re +from typing import Dict, List, Optional, Any +from datetime import datetime +import logging + +from .platforms import ( + PlatformType, ContentType, ToneType, + get_platform_config, get_content_template, + PLATFORM_CONFIGS +) +from .models import PlatformContent + +logger = logging.getLogger(__name__) + +class ContentGenerator: + """کلاس اصلی تولید محتوا""" + + def __init__(self): + self.generated_count = 0 + self.templates = self._load_templates() + + def _load_templates(self) -> Dict[str, Any]: + """بارگذاری قالب‌های محتوا""" + return { + "instagram_hooks": [ + "آیا می‌دانستید که...", + "راز موفقیت در...", + "۵ نکته طلایی برای...", + "چرا باید...", + "بهترین روش برای...", + "اگر می‌خواهید...", + "سوالی که همه می‌پرسند:", + "حقیقتی که کمتر می‌دانند:", + ], + + "cta_templates": { + PlatformType.INSTAGRAM: [ + "نظرتون رو در کامنت بنویسید 👇", + "این پست رو ذخیره کنید 💾", + "با دوستاتون به اشتراک بگذارید 🔄", + "برای محتوای بیشتر فالو کنید 🔔", + "در استوری نشانمون بدید 📱" + ], + PlatformType.TELEGRAM: [ + "نظرات خود را در گروه مطرح کنید", + "این پیام را فوروارد کنید", + "به کانال ما بپیوندید", + "لینک را با دوستان خود به اشتراک بگذارید", + "برای اطلاعات بیشتر کلیک کنید" + ], + PlatformType.WEBSITE: [ + "مطالعه بیشتر مقالات مرتبط", + "عضویت در خبرنامه", + "دانلود راهنمای رایگان", + "تماس با کارشناسان ما", + "درخواست مشاوره رایگان" + ] + }, + + "hashtag_categories": { + "عمومی": ["محتوا", "آموزش", "اطلاعات", "مفید", "جالب"], + "تکنولوژی": ["تکنولوژی", "نوآوری", "دیجیتال", "آینده"], + "کسب_و_کار": ["کسب_و_کار", "موفقیت", "استارتاپ", "کارآفرینی"], + "سبک_زندگی": ["سبک_زندگی", "سلامت", "تناسب_اندام", "آرامش"], + "آموزش": ["آموزش", "یادگیری", "مهارت", "توسعه_فردی"] + } + } + + async def generate_for_platform( + self, + platform: PlatformType, + topic: str, + keywords: List[str], + tone: ToneType, + target_audience: Optional[str] = None, + content_type: ContentType = ContentType.POST + ) -> PlatformContent: + """تولید محتوا برای یک پلتفرم خاص""" + + logger.info(f"تولید محتوا برای {platform.value} - موضوع: {topic}") + + # دریافت تنظیمات پلتفرم + config = get_platform_config(platform) + template = get_content_template(platform, content_type) + + # تولید محتوای اصلی + main_content = await self._generate_main_content( + topic=topic, + keywords=keywords, + tone=tone, + platform=platform, + content_type=content_type, + target_audience=target_audience + ) + + # اعمال قالب + formatted_content = await self._apply_template( + content=main_content, + template=template, + platform=platform, + topic=topic + ) + + # تولید هشتگ‌ها + hashtags = await self._generate_platform_hashtags( + keywords=keywords, + platform=platform, + topic=topic + ) + + # محاسبه آمار + char_count = len(formatted_content) + reading_time = self._estimate_reading_time(formatted_content) + engagement_score = await self._calculate_engagement_score( + content=formatted_content, + platform=platform, + hashtags=hashtags + ) + + # اعمال محدودیت‌های پلتفرم + if config.get("max_caption_length"): + formatted_content = self._truncate_content( + formatted_content, + config["max_caption_length"] + ) + + self.generated_count += 1 + + return PlatformContent( + platform=platform, + content=formatted_content, + hashtags=hashtags, + character_count=char_count, + estimated_reading_time=reading_time, + engagement_score=engagement_score + ) + + async def _generate_main_content( + self, + topic: str, + keywords: List[str], + tone: ToneType, + platform: PlatformType, + content_type: ContentType, + target_audience: Optional[str] = None + ) -> str: + """تولید محتوای اصلی""" + + # شخصی‌سازی بر اساس لحن + tone_adjustments = { + ToneType.FORMAL: "لطفاً به شیوه‌ای رسمی و حرفه‌ای", + ToneType.INFORMAL: "به شکل صمیمی و دوستانه", + ToneType.FRIENDLY: "با لحنی گرم و دوستانه", + ToneType.PROFESSIONAL: "با رویکرد کاملاً حرفه‌ای", + ToneType.HUMOROUS: "با طنز مناسب و شوخ‌طبعی", + ToneType.MOTIVATIONAL: "با انگیزه‌دهی و انرژی مثبت", + ToneType.EDUCATIONAL: "به شیوه آموزشی و توضیحی", + ToneType.SALES: "با رویکرد فروشی اما نه تهاجمی" + } + + # تولید محتوا بر اساس نوع + if content_type == ContentType.POST: + content = await self._generate_post_content(topic, keywords, tone_adjustments.get(tone, "")) + elif content_type == ContentType.STORY: + content = await self._generate_story_content(topic, keywords) + elif content_type == ContentType.ARTICLE: + content = await self._generate_article_content(topic, keywords, tone_adjustments.get(tone, "")) + elif content_type == ContentType.NEWS: + content = await self._generate_news_content(topic, keywords) + elif content_type == ContentType.VIDEO_SCRIPT: + content = await self._generate_video_script(topic, keywords) + else: + content = await self._generate_default_content(topic, keywords, tone_adjustments.get(tone, "")) + + return content + + async def _generate_post_content(self, topic: str, keywords: List[str], tone_instruction: str) -> str: + """تولید محتوای پست""" + + # الگوریتم تولید محتوای هوشمند + # در اینجا می‌توانید از API های مختلف AI استفاده کنید + + content_structure = { + "hook": f"موضوع جذاب درباره {topic}", + "body": f"محتوای اصلی که کلمات کلیدی {', '.join(keywords)} را شامل می‌شود", + "value": "ارزش و فایده برای مخاطب", + "conclusion": "جمع‌بندی و نتیجه‌گیری" + } + + # ترکیب محتوا + content = f""" +{content_structure['hook']} + +{content_structure['body']} + +💡 {content_structure['value']} + +{content_structure['conclusion']} +""".strip() + + return content + + async def _generate_story_content(self, topic: str, keywords: List[str]) -> str: + """تولید محتوای استوری""" + return f"📱 {topic}\n\n{' '.join(keywords[:3])}" + + async def _generate_article_content(self, topic: str, keywords: List[str], tone_instruction: str) -> str: + """تولید محتوای مقاله""" + return f""" +# {topic} + +## مقدمه +در این مقاله به بررسی {topic} می‌پردازیم. + +## بخش اصلی +محتوای مفصل درباره {', '.join(keywords)}. + +## نتیجه‌گیری +خلاصه و جمع‌بندی مطالب. +""" + + async def _generate_news_content(self, topic: str, keywords: List[str]) -> str: + """تولید محتوای خبری""" + return f""" +🗞️ خبر: {topic} + +📍 خلاصه خبر مربوط به {', '.join(keywords[:3])} + +📊 جزئیات و اطلاعات تکمیلی + +🔗 منابع معتبر +""" + + async def _generate_video_script(self, topic: str, keywords: List[str]) -> str: + """تولید اسکریپت ویدئو""" + return f""" +🎬 اسکریپت ویدئو: {topic} + +[سکانس 1 - معرفی] +سلام و معرفی موضوع {topic} + +[سکانس 2 - محتوای اصلی] +توضیح درباره {', '.join(keywords)} + +[سکانس 3 - خاتمه] +جمع‌بندی و تشکر از بینندگان +""" + + async def _generate_default_content(self, topic: str, keywords: List[str], tone_instruction: str) -> str: + """تولید محتوای پیش‌فرض""" + return f"{tone_instruction} درباره {topic} و مرتبط با {', '.join(keywords)} محتوایی تولید کنید." + + async def _apply_template( + self, + content: str, + template: str, + platform: PlatformType, + topic: str + ) -> str: + """اعمال قالب به محتوا""" + + # جایگزینی متغیرها در قالب + formatted_content = template.format( + main_content=content, + hook_line=self._get_random_hook(), + title=topic, + cta_line=self._get_random_cta(platform), + hashtags="", # هشتگ‌ها جداگانه اضافه می‌شوند + publish_time=datetime.now().strftime("%Y/%m/%d - %H:%M") + ) + + return formatted_content.strip() + + def _get_random_hook(self) -> str: + """انتخاب تصادفی hook""" + import random + hooks = self.templates["instagram_hooks"] + return random.choice(hooks) + + def _get_random_cta(self, platform: PlatformType) -> str: + """انتخاب تصادفی CTA""" + import random + ctas = self.templates["cta_templates"].get(platform, ["عملکرد انجام دهید"]) + return random.choice(ctas) + + async def _generate_platform_hashtags( + self, + keywords: List[str], + platform: PlatformType, + topic: str + ) -> List[str]: + """تولید هشتگ‌های مناسب برای پلتفرم""" + + config = get_platform_config(platform) + max_hashtags = config.get("max_hashtags", 10) + + hashtags = [] + + # هشتگ‌های مبتنی بر کلمات کلیدی + for keyword in keywords: + hashtag = self._clean_hashtag(keyword) + if hashtag: + hashtags.append(f"#{hashtag}") + + # هشتگ‌های مرتبط با موضوع + topic_hashtags = self._generate_topic_hashtags(topic) + hashtags.extend(topic_hashtags) + + # هشتگ‌های عمومی پلتفرم + general_hashtags = self._get_general_hashtags(platform) + hashtags.extend(general_hashtags[:3]) + + # حذف تکراری و محدود کردن تعداد + unique_hashtags = list(dict.fromkeys(hashtags)) + + if max_hashtags: + unique_hashtags = unique_hashtags[:max_hashtags] + + return unique_hashtags + + def _clean_hashtag(self, text: str) -> str: + """تمیز کردن متن برای هشتگ""" + # حذف کاراکترهای غیرمجاز + cleaned = re.sub(r'[^\w\u0600-\u06FF]', '', text) + return cleaned if len(cleaned) > 1 else None + + def _generate_topic_hashtags(self, topic: str) -> List[str]: + """تولید هشتگ‌های مرتبط با موضوع""" + words = topic.split() + hashtags = [] + + for word in words: + cleaned = self._clean_hashtag(word) + if cleaned and len(cleaned) > 2: + hashtags.append(f"#{cleaned}") + + return hashtags[:5] + + def _get_general_hashtags(self, platform: PlatformType) -> List[str]: + """دریافت هشتگ‌های عمومی پلتفرم""" + general_tags = { + PlatformType.INSTAGRAM: ["#اینستاگرام", "#محتوا", "#ایران"], + PlatformType.TELEGRAM: ["#تلگرام", "#اطلاعات", "#مفید"], + PlatformType.EITAA: ["#ایتا", "#داخلی", "#ایرانی"], + PlatformType.RUBIKA: ["#روبیکا", "#پیام_رسان"] + } + return general_tags.get(platform, []) + + def _estimate_reading_time(self, content: str) -> int: + """تخمین زمان مطالعه (ثانیه)""" + words_count = len(content.split()) + # میانگین سرعت خواندن فارسی: ۱۵۰ کلمه در دقیقه + reading_time_minutes = words_count / 150 + return int(reading_time_minutes * 60) + + async def _calculate_engagement_score( + self, + content: str, + platform: PlatformType, + hashtags: List[str] + ) -> float: + """محاسبه امتیاز جذابیت پیش‌بینی شده""" + + score = 0.0 + + # طول مناسب + length = len(content) + if platform == PlatformType.INSTAGRAM: + if 100 <= length <= 500: + score += 2.0 + elif platform == PlatformType.TELEGRAM: + if 50 <= length <= 1000: + score += 2.0 + + # تعداد هشتگ + hashtag_count = len(hashtags) + if 5 <= hashtag_count <= 15: + score += 1.5 + + # وجود ایموجی + emoji_count = len(re.findall(r'[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF\U0001F1E0-\U0001F1FF]', content)) + if emoji_count > 0: + score += 1.0 + + # وجود سوال + if '؟' in content: + score += 0.5 + + # وجود فراخوان عمل + cta_keywords = ['کامنت', 'ذخیره', 'اشتراک', 'فالو', 'کلیک'] + if any(keyword in content for keyword in cta_keywords): + score += 1.0 + + return min(score, 10.0) # حداکثر ۱۰ + + def _truncate_content(self, content: str, max_length: int) -> str: + """کوتاه کردن محتوا در صورت تجاوز از حد مجاز""" + if len(content) <= max_length: + return content + + # کوتاه کردن با حفظ کلمات + truncated = content[:max_length-3] + last_space = truncated.rfind(' ') + if last_space > 0: + truncated = truncated[:last_space] + + return truncated + "..." + + async def generate_hashtags( + self, + topic: str, + keywords: List[str], + platforms: List[PlatformType] + ) -> Dict[str, List[str]]: + """تولید هشتگ‌ها برای همه پلتفرم‌ها""" + + all_hashtags = {} + + for platform in platforms: + hashtags = await self._generate_platform_hashtags(keywords, platform, topic) + all_hashtags[platform.value] = hashtags + + return all_hashtags + + async def generate_cta_suggestions( + self, + topic: str, + platforms: List[PlatformType], + content_type: ContentType + ) -> List[str]: + """تولید پیشنهادات فراخوان عمل""" + + suggestions = [] + + for platform in platforms: + platform_ctas = self.templates["cta_templates"].get(platform, []) + suggestions.extend(platform_ctas[:2]) # دو پیشنهاد از هر پلتفرم + + # حذف تکراری + unique_suggestions = list(dict.fromkeys(suggestions)) + + return unique_suggestions[:10] # حداکثر ۱۰ پیشنهاد + + async def get_total_generated(self) -> int: + """دریافت تعداد کل محتواهای تولید شده""" + return self.generated_count \ No newline at end of file diff --git a/src/models.py b/src/models.py new file mode 100644 index 0000000..bc9ce5e --- /dev/null +++ b/src/models.py @@ -0,0 +1,178 @@ +""" +مدل‌های داده‌ای سیستم +""" + +from pydantic import BaseModel, Field, validator +from typing import List, Optional, Dict, Any, Union +from datetime import datetime +from enum import Enum + +from .platforms import PlatformType, ContentType, ToneType + +class ContentRequest(BaseModel): + """درخواست تولید محتوا""" + topic: str = Field(..., description="موضوع اصلی محتوا") + keywords: List[str] = Field(..., description="کلمات کلیدی") + platforms: List[PlatformType] = Field(..., description="پلتفرم‌های هدف") + content_type: ContentType = Field(default=ContentType.POST, description="نوع محتوا") + tone: ToneType = Field(default=ToneType.FRIENDLY, description="لحن محتوا") + target_audience: Optional[str] = Field(None, description="مخاطب هدف") + language: str = Field(default="fa", description="زبان محتوا") + additional_instructions: Optional[str] = Field(None, description="دستورات اضافی") + + @validator('keywords') + def validate_keywords(cls, v): + if not v or len(v) == 0: + raise ValueError("حداقل یک کلمه کلیدی باید وارد شود") + return v + + @validator('topic') + def validate_topic(cls, v): + if not v or len(v.strip()) < 3: + raise ValueError("موضوع باید حداقل ۳ کاراکتر باشد") + return v.strip() + +class PlatformContent(BaseModel): + """محتوای تولید شده برای یک پلتفرم""" + platform: PlatformType + content: str + hashtags: List[str] = Field(default_factory=list) + mentions: List[str] = Field(default_factory=list) + character_count: int = 0 + estimated_reading_time: Optional[int] = None # بر حسب ثانیه + engagement_score: Optional[float] = None # امتیاز جذابیت پیش‌بینی شده + + class Config: + use_enum_values = True + +class ContentResponse(BaseModel): + """پاسخ تولید محتوا""" + contents: Dict[str, PlatformContent] + hashtags: Dict[str, List[str]] # هشتگ‌های پیشنهادی برای هر پلتفرم + cta_suggestions: List[str] # پیشنهادات فراخوان عمل + generated_at: datetime + request_id: str + total_platforms: int = 0 + processing_time: Optional[float] = None # زمان پردازش به ثانیه + + def __init__(self, **data): + super().__init__(**data) + self.total_platforms = len(self.contents) + +class SEOOptimizationRequest(BaseModel): + """درخواست بهینه‌سازی SEO""" + text: str = Field(..., description="متن برای بهینه‌سازی") + target_keywords: List[str] = Field(..., description="کلمات کلیدی هدف") + focus_keyword: str = Field(..., description="کلمه کلیدی اصلی") + meta_description: Optional[str] = Field(None, description="توضیحات متا") + title_tag: Optional[str] = Field(None, description="عنوان صفحه") + + @validator('target_keywords') + def validate_target_keywords(cls, v): + if not v or len(v) == 0: + raise ValueError("حداقل یک کلمه کلیدی هدف باید وارد شود") + return v + +class SEOOptimizationResponse(BaseModel): + """پاسخ بهینه‌سازی SEO""" + optimized_content: str + seo_title: str + meta_description: str + keywords_density: Dict[str, float] + headings_structure: List[Dict[str, Any]] + seo_score: float # امتیاز SEO از ۰ تا ۱۰۰ + recommendations: List[str] + readability_score: float + word_count: int + +class VisualIdeaRequest(BaseModel): + """درخواست تولید ایده بصری""" + content: str = Field(..., description="محتوای متنی") + platform: PlatformType = Field(..., description="پلتفرم هدف") + content_type: ContentType = Field(default=ContentType.POST, description="نوع محتوا") + style_preferences: Optional[List[str]] = Field(None, description="ترجیحات سبک") + color_scheme: Optional[str] = Field(None, description="طرح رنگی") + brand_guidelines: Optional[Dict[str, Any]] = Field(None, description="راهنمای برند") + +class VisualIdeaResponse(BaseModel): + """پاسخ تولید ایده بصری""" + image_ideas: List[Dict[str, Any]] + video_ideas: List[Dict[str, Any]] + graphic_elements: List[str] + color_suggestions: List[str] + typography_suggestions: List[str] + layout_suggestions: List[str] + +class ScheduleRequest(BaseModel): + """درخواست زمان‌بندی انتشار""" + platform: PlatformType = Field(..., description="پلتفرم انتشار") + content: str = Field(..., description="محتوای قابل انتشار") + schedule_time: datetime = Field(..., description="زمان انتشار") + auto_publish: bool = Field(default=False, description="انتشار خودکار") + repeat_schedule: Optional[str] = Field(None, description="تکرار زمان‌بندی") + tags: List[str] = Field(default_factory=list, description="برچسب‌ها") + + @validator('schedule_time') + def validate_schedule_time(cls, v): + if v <= datetime.now(): + raise ValueError("زمان انتشار باید در آینده باشد") + return v + +class ScheduledPost(BaseModel): + """پست زمان‌بندی شده""" + id: str + platform: PlatformType + content: str + schedule_time: datetime + status: str = "scheduled" # scheduled, published, failed, cancelled + created_at: datetime + published_at: Optional[datetime] = None + error_message: Optional[str] = None + tags: List[str] = Field(default_factory=list) + + class Config: + use_enum_values = True + +class ContentAnalytics(BaseModel): + """آنالیتیک محتوا""" + platform: PlatformType + content_id: str + views: int = 0 + likes: int = 0 + comments: int = 0 + shares: int = 0 + engagement_rate: float = 0.0 + reach: int = 0 + impressions: int = 0 + click_through_rate: float = 0.0 + analyzed_at: datetime + + class Config: + use_enum_values = True + +class UserProfile(BaseModel): + """پروفایل کاربر""" + user_id: str + username: str + email: str + preferred_platforms: List[PlatformType] = Field(default_factory=list) + default_tone: ToneType = ToneType.FRIENDLY + brand_voice: Optional[str] = None + target_audience: Optional[str] = None + industry: Optional[str] = None + created_at: datetime + last_active: datetime + subscription_type: str = "free" # free, premium, enterprise + + class Config: + use_enum_values = True + +class SystemStats(BaseModel): + """آمار سیستم""" + total_content_generated: int + total_users: int + popular_platforms: Dict[str, int] + popular_content_types: Dict[str, int] + average_engagement_score: float + uptime_percentage: float + last_updated: datetime \ No newline at end of file diff --git a/src/platforms.py b/src/platforms.py new file mode 100644 index 0000000..8e5fa6c --- /dev/null +++ b/src/platforms.py @@ -0,0 +1,237 @@ +""" +تعریف پلتفرم‌ها و انواع محتوا +""" + +from enum import Enum +from typing import Dict, List + +class PlatformType(Enum): + """انواع پلتفرم‌های پشتیبانی شده""" + INSTAGRAM = "instagram" + TELEGRAM = "telegram" + WEBSITE = "website" + EITAA = "eitaa" + RUBIKA = "rubika" + +class ContentType(Enum): + """انواع محتوا""" + POST = "post" + STORY = "story" + ARTICLE = "article" + NEWS = "news" + ADVERTISEMENT = "advertisement" + VIDEO_SCRIPT = "video_script" + CAROUSEL = "carousel" + REEL_SCRIPT = "reel_script" + +class ToneType(Enum): + """انواع لحن""" + FORMAL = "formal" + INFORMAL = "informal" + FRIENDLY = "friendly" + PROFESSIONAL = "professional" + HUMOROUS = "humorous" + MOTIVATIONAL = "motivational" + EDUCATIONAL = "educational" + SALES = "sales" + +# تنظیمات و محدودیت‌های هر پلتفرم +PLATFORM_CONFIGS = { + PlatformType.INSTAGRAM: { + "max_caption_length": 2200, + "max_hashtags": 30, + "supports_emojis": True, + "supports_mentions": True, + "line_breaks": True, + "preferred_content_types": [ + ContentType.POST, + ContentType.STORY, + ContentType.CAROUSEL, + ContentType.REEL_SCRIPT + ], + "hashtag_prefix": "#", + "mention_prefix": "@", + "best_posting_times": ["9:00", "12:00", "17:00", "20:00"], + "character_encoding": "utf-8" + }, + + PlatformType.TELEGRAM: { + "max_caption_length": 4096, + "max_hashtags": None, # بدون محدودیت + "supports_emojis": True, + "supports_mentions": True, + "supports_markdown": True, + "supports_html": True, + "line_breaks": True, + "preferred_content_types": [ + ContentType.POST, + ContentType.NEWS, + ContentType.ARTICLE + ], + "hashtag_prefix": "#", + "mention_prefix": "@", + "best_posting_times": ["8:00", "13:00", "18:00", "21:00"], + "character_encoding": "utf-8" + }, + + PlatformType.WEBSITE: { + "max_caption_length": None, # بدون محدودیت + "supports_html": True, + "supports_seo": True, + "requires_headings": True, + "requires_meta_tags": True, + "preferred_content_types": [ + ContentType.ARTICLE, + ContentType.NEWS + ], + "seo_requirements": { + "title_max_length": 60, + "meta_description_max_length": 160, + "h1_required": True, + "alt_text_required": True + }, + "character_encoding": "utf-8" + }, + + PlatformType.EITAA: { + "max_caption_length": 4096, + "max_hashtags": 20, + "supports_emojis": True, + "supports_mentions": True, + "line_breaks": True, + "preferred_content_types": [ + ContentType.POST, + ContentType.NEWS + ], + "hashtag_prefix": "#", + "mention_prefix": "@", + "best_posting_times": ["10:00", "14:00", "19:00", "22:00"], + "character_encoding": "utf-8" + }, + + PlatformType.RUBIKA: { + "max_caption_length": 4096, + "max_hashtags": 15, + "supports_emojis": True, + "supports_mentions": True, + "line_breaks": True, + "preferred_content_types": [ + ContentType.POST, + ContentType.NEWS + ], + "hashtag_prefix": "#", + "mention_prefix": "@", + "best_posting_times": ["11:00", "15:00", "20:00", "23:00"], + "character_encoding": "utf-8" + } +} + +# قالب‌های محتوا برای هر پلتفرم +CONTENT_TEMPLATES = { + PlatformType.INSTAGRAM: { + ContentType.POST: """ +{hook_line} + +{main_content} + +{cta_line} + +{hashtags} +""", + ContentType.STORY: """ +{main_content} + +{cta_text} +""", + ContentType.CAROUSEL: """ +اسلاید {slide_number}: {title} + +{content} + +{navigation_hint} +""", + ContentType.REEL_SCRIPT: """ +🎬 سکانس {scene_number} +⏱️ مدت: {duration} ثانیه + +{scene_description} + +متن روی صفحه: {overlay_text} +موزیک: {music_suggestion} +""" + }, + + PlatformType.TELEGRAM: { + ContentType.POST: """ +{title} + +{main_content} + +{cta_line} + +{hashtags} +""", + ContentType.NEWS: """ +📰 {news_title} + +{news_summary} + +{full_content} + +🔗 منبع: {source} +⏰ {publish_time} + +{hashtags} +""", + ContentType.ARTICLE: """ +📝 {article_title} + +{introduction} + +{body_content} + +{conclusion} + +{hashtags} +""" + }, + + PlatformType.WEBSITE: { + ContentType.ARTICLE: """ + + + + + {seo_title} + + + + +
+

{main_title}

+ {content_body} +
+ + +""" + } +} + +def get_platform_config(platform: PlatformType) -> Dict: + """دریافت تنظیمات پلتفرم""" + return PLATFORM_CONFIGS.get(platform, {}) + +def get_content_template(platform: PlatformType, content_type: ContentType) -> str: + """دریافت قالب محتوا""" + platform_templates = CONTENT_TEMPLATES.get(platform, {}) + return platform_templates.get(content_type, "{main_content}") + +def get_platform_limitations(platform: PlatformType) -> Dict: + """دریافت محدودیت‌های پلتفرم""" + config = get_platform_config(platform) + return { + "max_caption_length": config.get("max_caption_length"), + "max_hashtags": config.get("max_hashtags"), + "supports_emojis": config.get("supports_emojis", False), + "supports_mentions": config.get("supports_mentions", False) + } \ No newline at end of file diff --git a/src/scheduler.py b/src/scheduler.py new file mode 100644 index 0000000..6411f8d --- /dev/null +++ b/src/scheduler.py @@ -0,0 +1,449 @@ +""" +سیستم زمان‌بندی انتشار محتوا +""" + +import asyncio +import json +import uuid +from datetime import datetime, timedelta +from typing import Dict, List, Any, Optional +import logging +import schedule +import time +from threading import Thread + +from .platforms import PlatformType +from .models import ScheduledPost + +logger = logging.getLogger(__name__) + +class ContentScheduler: + """کلاس زمان‌بندی انتشار محتوا""" + + def __init__(self): + self.scheduled_posts: Dict[str, ScheduledPost] = {} + self.is_running = False + self.scheduler_thread = None + self.api_connections = self._initialize_api_connections() + + def _initialize_api_connections(self) -> Dict[PlatformType, Any]: + """مقداردهی اولیه اتصالات API""" + # در اینجا می‌توانید API کلاینت‌های واقعی را تعریف کنید + return { + PlatformType.INSTAGRAM: None, # Instagram Basic Display API + PlatformType.TELEGRAM: None, # Telegram Bot API + PlatformType.EITAA: None, # Eitaa API + PlatformType.RUBIKA: None, # Rubika API + PlatformType.WEBSITE: None # Custom CMS API + } + + async def start(self): + """شروع سیستم زمان‌بندی""" + if not self.is_running: + self.is_running = True + self.scheduler_thread = Thread(target=self._run_scheduler, daemon=True) + self.scheduler_thread.start() + logger.info("🚀 سیستم زمان‌بندی راه‌اندازی شد") + + async def stop(self): + """توقف سیستم زمان‌بندی""" + self.is_running = False + if self.scheduler_thread: + self.scheduler_thread.join() + logger.info("⛔ سیستم زمان‌بندی متوقف شد") + + def _run_scheduler(self): + """اجرای scheduler در thread جداگانه""" + while self.is_running: + schedule.run_pending() + time.sleep(1) + + async def schedule_content( + self, + platform: PlatformType, + content: str, + schedule_time: datetime, + auto_publish: bool = False, + repeat_schedule: Optional[str] = None, + tags: List[str] = None + ) -> str: + """زمان‌بندی محتوا برای انتشار""" + + # ایجاد شناسه یکتا + schedule_id = str(uuid.uuid4()) + + # ایجاد پست زمان‌بندی شده + scheduled_post = ScheduledPost( + id=schedule_id, + platform=platform, + content=content, + schedule_time=schedule_time, + status="scheduled", + created_at=datetime.now(), + tags=tags or [] + ) + + # ذخیره در حافظه (در پروداکشن از دیتابیس استفاده کنید) + self.scheduled_posts[schedule_id] = scheduled_post + + # تنظیم زمان‌بندی + if auto_publish: + await self._set_schedule_task(schedule_id, schedule_time) + + # تنظیم زمان‌بندی تکراری در صورت نیاز + if repeat_schedule: + await self._set_recurring_schedule(schedule_id, repeat_schedule) + + logger.info(f"محتوا برای {platform.value} در {schedule_time} زمان‌بندی شد") + + return schedule_id + + async def _set_schedule_task(self, schedule_id: str, schedule_time: datetime): + """تنظیم تسک زمان‌بندی""" + + def publish_job(): + asyncio.create_task(self.execute_scheduled_post(schedule_id)) + + # محاسبه تاخیر تا زمان انتشار + delay = (schedule_time - datetime.now()).total_seconds() + + if delay > 0: + # برنامه‌ریزی برای اجرا در زمان مشخص + schedule.every().day.at(schedule_time.strftime("%H:%M")).do(publish_job) + + # برای زمان‌بندی یکباره، پس از اجرا حذف شود + def one_time_job(): + publish_job() + schedule.cancel_job(one_time_job) + + schedule.every().day.at(schedule_time.strftime("%H:%M")).do(one_time_job) + + async def _set_recurring_schedule(self, schedule_id: str, repeat_pattern: str): + """تنظیم زمان‌بندی تکراری""" + + def recurring_job(): + asyncio.create_task(self.execute_scheduled_post(schedule_id)) + + # تفسیر الگوی تکرار + if repeat_pattern == "daily": + schedule.every().day.do(recurring_job) + elif repeat_pattern == "weekly": + schedule.every().week.do(recurring_job) + elif repeat_pattern == "monthly": + schedule.every(30).days.do(recurring_job) + elif repeat_pattern.startswith("every_"): + # مثال: every_2_hours + parts = repeat_pattern.split("_") + if len(parts) == 3: + interval = int(parts[1]) + unit = parts[2] + + if unit == "hours": + schedule.every(interval).hours.do(recurring_job) + elif unit == "days": + schedule.every(interval).days.do(recurring_job) + + async def execute_scheduled_post(self, schedule_id: str): + """اجرای انتشار محتوای زمان‌بندی شده""" + + if schedule_id not in self.scheduled_posts: + logger.error(f"پست با شناسه {schedule_id} یافت نشد") + return + + scheduled_post = self.scheduled_posts[schedule_id] + + try: + logger.info(f"شروع انتشار محتوا: {schedule_id}") + + # انتشار محتوا بر اساس پلتفرم + success = await self._publish_to_platform( + platform=scheduled_post.platform, + content=scheduled_post.content, + schedule_id=schedule_id + ) + + if success: + # بروزرسانی وضعیت + scheduled_post.status = "published" + scheduled_post.published_at = datetime.now() + logger.info(f"محتوا با موفقیت منتشر شد: {schedule_id}") + else: + scheduled_post.status = "failed" + scheduled_post.error_message = "خطا در انتشار محتوا" + logger.error(f"خطا در انتشار محتوا: {schedule_id}") + + except Exception as e: + scheduled_post.status = "failed" + scheduled_post.error_message = str(e) + logger.error(f"خطای غیرمنتظره در انتشار: {e}") + + async def _publish_to_platform( + self, platform: PlatformType, content: str, schedule_id: str + ) -> bool: + """انتشار محتوا در پلتفرم مشخص""" + + try: + if platform == PlatformType.INSTAGRAM: + return await self._publish_to_instagram(content) + elif platform == PlatformType.TELEGRAM: + return await self._publish_to_telegram(content) + elif platform == PlatformType.EITAA: + return await self._publish_to_eitaa(content) + elif platform == PlatformType.RUBIKA: + return await self._publish_to_rubika(content) + elif platform == PlatformType.WEBSITE: + return await self._publish_to_website(content) + else: + logger.warning(f"پلتفرم {platform.value} پشتیبانی نمی‌شود") + return False + + except Exception as e: + logger.error(f"خطا در انتشار به {platform.value}: {e}") + return False + + async def _publish_to_instagram(self, content: str) -> bool: + """انتشار در اینستاگرام""" + + # در اینجا کد اتصال به Instagram API + # مثال ساده: + logger.info("انتشار در اینستاگرام: محتوا آماده انتشار") + + # شبیه‌سازی انتشار + await asyncio.sleep(1) + + # در حالت واقعی: + # instagram_api = self.api_connections[PlatformType.INSTAGRAM] + # result = await instagram_api.create_post(content) + # return result.success + + return True # شبیه‌سازی موفقیت + + async def _publish_to_telegram(self, content: str) -> bool: + """انتشار در تلگرام""" + + logger.info("انتشار در تلگرام: محتوا آماده انتشار") + + # شبیه‌سازی انتشار + await asyncio.sleep(1) + + # در حالت واقعی: + # telegram_bot = self.api_connections[PlatformType.TELEGRAM] + # result = await telegram_bot.send_message(chat_id=CHANNEL_ID, text=content) + # return result.message_id is not None + + return True + + async def _publish_to_eitaa(self, content: str) -> bool: + """انتشار در ایتا""" + + logger.info("انتشار در ایتا: محتوا آماده انتشار") + await asyncio.sleep(1) + return True + + async def _publish_to_rubika(self, content: str) -> bool: + """انتشار در روبیکا""" + + logger.info("انتشار در روبیکا: محتوا آماده انتشار") + await asyncio.sleep(1) + return True + + async def _publish_to_website(self, content: str) -> bool: + """انتشار در وب‌سایت""" + + logger.info("انتشار در وب‌سایت: محتوا آماده انتشار") + await asyncio.sleep(1) + return True + + async def get_scheduled_posts(self, status: Optional[str] = None) -> List[Dict[str, Any]]: + """دریافت لیست پست‌های زمان‌بندی شده""" + + posts = list(self.scheduled_posts.values()) + + # فیلتر بر اساس وضعیت + if status: + posts = [post for post in posts if post.status == status] + + # مرتب‌سازی بر اساس زمان زمان‌بندی + posts.sort(key=lambda x: x.schedule_time, reverse=True) + + # تبدیل به دیکشنری برای پاسخ API + result = [] + for post in posts: + result.append({ + "id": post.id, + "platform": post.platform.value, + "content": post.content[:100] + "..." if len(post.content) > 100 else post.content, + "schedule_time": post.schedule_time.isoformat(), + "status": post.status, + "created_at": post.created_at.isoformat(), + "published_at": post.published_at.isoformat() if post.published_at else None, + "error_message": post.error_message, + "tags": post.tags + }) + + return result + + async def cancel_scheduled_post(self, schedule_id: str) -> Dict[str, Any]: + """لغو پست زمان‌بندی شده""" + + if schedule_id not in self.scheduled_posts: + return { + "success": False, + "message": "پست یافت نشد" + } + + scheduled_post = self.scheduled_posts[schedule_id] + + if scheduled_post.status == "published": + return { + "success": False, + "message": "پست قبلاً منتشر شده است" + } + + # تغییر وضعیت به لغو شده + scheduled_post.status = "cancelled" + + # حذف از scheduler (در صورت وجود) + # schedule.cancel_job() برای job های مربوطه + + logger.info(f"پست {schedule_id} لغو شد") + + return { + "success": True, + "message": "پست با موفقیت لغو شد" + } + + async def get_scheduled_count(self) -> int: + """تعداد پست‌های زمان‌بندی شده""" + return len([post for post in self.scheduled_posts.values() if post.status == "scheduled"]) + + async def get_publishing_stats(self) -> Dict[str, Any]: + """آمار انتشار""" + + total_posts = len(self.scheduled_posts) + published_posts = len([post for post in self.scheduled_posts.values() if post.status == "published"]) + failed_posts = len([post for post in self.scheduled_posts.values() if post.status == "failed"]) + scheduled_posts = len([post for post in self.scheduled_posts.values() if post.status == "scheduled"]) + + # آمار بر اساس پلتفرم + platform_stats = {} + for platform in PlatformType: + platform_posts = [post for post in self.scheduled_posts.values() if post.platform == platform] + platform_stats[platform.value] = { + "total": len(platform_posts), + "published": len([post for post in platform_posts if post.status == "published"]), + "failed": len([post for post in platform_posts if post.status == "failed"]), + "scheduled": len([post for post in platform_posts if post.status == "scheduled"]) + } + + return { + "total_posts": total_posts, + "published_posts": published_posts, + "failed_posts": failed_posts, + "scheduled_posts": scheduled_posts, + "success_rate": (published_posts / total_posts * 100) if total_posts > 0 else 0, + "platform_stats": platform_stats + } + + async def reschedule_post(self, schedule_id: str, new_schedule_time: datetime) -> Dict[str, Any]: + """تغییر زمان انتشار پست""" + + if schedule_id not in self.scheduled_posts: + return { + "success": False, + "message": "پست یافت نشد" + } + + scheduled_post = self.scheduled_posts[schedule_id] + + if scheduled_post.status != "scheduled": + return { + "success": False, + "message": "فقط پست‌های زمان‌بندی شده قابل تغییر هستند" + } + + # بروزرسانی زمان + old_time = scheduled_post.schedule_time + scheduled_post.schedule_time = new_schedule_time + + # بروزرسانی scheduler + await self._set_schedule_task(schedule_id, new_schedule_time) + + logger.info(f"زمان پست {schedule_id} از {old_time} به {new_schedule_time} تغییر کرد") + + return { + "success": True, + "message": "زمان انتشار با موفقیت تغییر کرد", + "old_time": old_time.isoformat(), + "new_time": new_schedule_time.isoformat() + } + + async def bulk_schedule(self, posts_data: List[Dict[str, Any]]) -> Dict[str, Any]: + """زمان‌بندی انبوه محتوا""" + + results = { + "successful": [], + "failed": [], + "total": len(posts_data) + } + + for post_data in posts_data: + try: + schedule_id = await self.schedule_content( + platform=PlatformType(post_data["platform"]), + content=post_data["content"], + schedule_time=datetime.fromisoformat(post_data["schedule_time"]), + auto_publish=post_data.get("auto_publish", False), + tags=post_data.get("tags", []) + ) + + results["successful"].append({ + "schedule_id": schedule_id, + "platform": post_data["platform"] + }) + + except Exception as e: + results["failed"].append({ + "platform": post_data.get("platform", "unknown"), + "error": str(e) + }) + + return results + + async def get_optimal_posting_times(self, platform: PlatformType) -> List[str]: + """دریافت بهترین زمان‌های انتشار برای پلتفرم""" + + optimal_times = { + PlatformType.INSTAGRAM: ["09:00", "12:00", "17:00", "20:00"], + PlatformType.TELEGRAM: ["08:00", "13:00", "18:00", "21:00"], + PlatformType.EITAA: ["10:00", "14:00", "19:00", "22:00"], + PlatformType.RUBIKA: ["11:00", "15:00", "20:00", "23:00"], + PlatformType.WEBSITE: ["09:00", "14:00", "16:00", "19:00"] + } + + return optimal_times.get(platform, ["12:00", "18:00"]) + + async def analyze_best_posting_time(self, platform: PlatformType) -> Dict[str, Any]: + """تحلیل بهترین زمان انتشار بر اساس عملکرد گذشته""" + + # در اینجا می‌توانید از دیتای واقعی استفاده کنید + # فعلاً داده‌های نمونه ارائه می‌دهیم + + analysis = { + "platform": platform.value, + "recommended_times": await self.get_optimal_posting_times(platform), + "peak_engagement_hours": { + "weekday": ["12:00-13:00", "19:00-21:00"], + "weekend": ["10:00-12:00", "20:00-22:00"] + }, + "avoid_times": ["02:00-06:00", "23:00-01:00"], + "best_days": ["دوشنبه", "سه‌شنبه", "پنج‌شنبه"], + "engagement_rate_by_hour": { + "08:00": 65, + "12:00": 85, + "18:00": 92, + "21:00": 78 + } + } + + return analysis \ No newline at end of file diff --git a/src/seo_optimizer.py b/src/seo_optimizer.py new file mode 100644 index 0000000..1b87ada --- /dev/null +++ b/src/seo_optimizer.py @@ -0,0 +1,507 @@ +""" +بهینه‌ساز SEO برای محتوای وب‌سایت +""" + +import re +import asyncio +from typing import Dict, List, Any, Tuple +from datetime import datetime +import logging + +from .models import SEOOptimizationResponse + +logger = logging.getLogger(__name__) + +class SEOOptimizer: + """کلاس بهینه‌سازی SEO""" + + def __init__(self): + self.seo_rules = self._load_seo_rules() + self.stopwords = self._load_persian_stopwords() + + def _load_seo_rules(self) -> Dict[str, Any]: + """بارگذاری قوانین SEO""" + return { + "title_length": {"min": 30, "max": 60}, + "meta_description_length": {"min": 120, "max": 160}, + "keyword_density": {"min": 0.5, "max": 3.0}, # درصد + "heading_structure": { + "h1_count": 1, + "h2_min": 2, + "h3_max": 6 + }, + "content_length": {"min": 300, "max": 3000}, + "internal_links": {"min": 2, "max": 10}, + "image_alt_required": True, + "readability_score": {"min": 60} # از ۱۰۰ + } + + def _load_persian_stopwords(self) -> List[str]: + """بارگذاری کلمات پر تکرار فارسی""" + return [ + "در", "از", "به", "با", "که", "این", "آن", "را", "تا", "و", "یا", + "است", "بود", "باشد", "شده", "می", "خود", "همه", "یک", "دو", + "سه", "برای", "روی", "زیر", "بالا", "پایین", "کنار", "وسط" + ] + + async def optimize_content( + self, + text: str, + target_keywords: List[str], + focus_keyword: str, + meta_description: str = None, + title_tag: str = None + ) -> SEOOptimizationResponse: + """بهینه‌سازی محتوا برای SEO""" + + logger.info(f"شروع بهینه‌سازی SEO برای کلمه کلیدی: {focus_keyword}") + + # تجزیه محتوای موجود + analysis = await self._analyze_content(text, target_keywords, focus_keyword) + + # بهینه‌سازی محتوا + optimized_content = await self._optimize_text_content( + text, target_keywords, focus_keyword, analysis + ) + + # تولید عنوان SEO + seo_title = await self._generate_seo_title( + title_tag or focus_keyword, focus_keyword + ) + + # تولید متای توضیحات + optimized_meta = await self._generate_meta_description( + meta_description or text, focus_keyword, target_keywords + ) + + # محاسبه تراکم کلمات کلیدی + keyword_density = self._calculate_keyword_density( + optimized_content, target_keywords + ) + + # تجزیه ساختار هدینگ‌ها + headings_structure = self._analyze_headings_structure(optimized_content) + + # محاسبه امتیاز SEO + seo_score = await self._calculate_seo_score( + optimized_content, seo_title, optimized_meta, + keyword_density, headings_structure, focus_keyword + ) + + # تولید توصیه‌ها + recommendations = await self._generate_recommendations( + analysis, seo_score, keyword_density, headings_structure + ) + + # محاسبه امتیاز خوانایی + readability_score = self._calculate_readability_score(optimized_content) + + # شمارش کلمات + word_count = len(optimized_content.split()) + + logger.info(f"بهینه‌سازی SEO کامل شد - امتیاز: {seo_score}") + + return SEOOptimizationResponse( + optimized_content=optimized_content, + seo_title=seo_title, + meta_description=optimized_meta, + keywords_density=keyword_density, + headings_structure=headings_structure, + seo_score=seo_score, + recommendations=recommendations, + readability_score=readability_score, + word_count=word_count + ) + + async def _analyze_content( + self, text: str, target_keywords: List[str], focus_keyword: str + ) -> Dict[str, Any]: + """تجزیه و تحلیل محتوای موجود""" + + analysis = { + "original_length": len(text), + "word_count": len(text.split()), + "paragraph_count": len(text.split('\n\n')), + "sentence_count": len(re.findall(r'[.!؟]+', text)), + "has_headings": bool(re.search(r'#+\s+', text)), + "current_keyword_density": {}, + "issues": [] + } + + # تحلیل تراکم کلمات کلیدی فعلی + for keyword in target_keywords + [focus_keyword]: + density = self._calculate_single_keyword_density(text, keyword) + analysis["current_keyword_density"][keyword] = density + + if density < self.seo_rules["keyword_density"]["min"]: + analysis["issues"].append(f"تراکم کم کلمه '{keyword}': {density:.1f}%") + elif density > self.seo_rules["keyword_density"]["max"]: + analysis["issues"].append(f"تراکم زیاد کلمه '{keyword}': {density:.1f}%") + + # بررسی طول محتوا + if analysis["word_count"] < self.seo_rules["content_length"]["min"]: + analysis["issues"].append("محتوا کوتاه‌تر از حد استاندارد است") + elif analysis["word_count"] > self.seo_rules["content_length"]["max"]: + analysis["issues"].append("محتوا طولانی‌تر از حد توصیه شده است") + + return analysis + + async def _optimize_text_content( + self, text: str, target_keywords: List[str], focus_keyword: str, analysis: Dict + ) -> str: + """بهینه‌سازی محتوای متنی""" + + optimized_text = text + + # اضافه کردن ساختار هدینگ در صورت عدم وجود + if not analysis["has_headings"]: + optimized_text = self._add_heading_structure(optimized_text, focus_keyword) + + # بهینه‌سازی تراکم کلمات کلیدی + optimized_text = await self._optimize_keyword_density( + optimized_text, target_keywords, focus_keyword + ) + + # اضافه کردن کلمه کلیدی در نقاط مهم + optimized_text = self._place_keywords_strategically( + optimized_text, focus_keyword + ) + + # بهبود ساختار پاراگراف‌ها + optimized_text = self._improve_paragraph_structure(optimized_text) + + return optimized_text + + def _add_heading_structure(self, text: str, focus_keyword: str) -> str: + """اضافه کردن ساختار هدینگ به محتوا""" + + paragraphs = text.split('\n\n') + + if len(paragraphs) < 2: + return text + + # افزودن H1 در ابتدا + structured_text = f"# {focus_keyword} - راهنمای جامع\n\n" + + # تبدیل پاراگراف‌ها به بخش‌های مختلف + for i, paragraph in enumerate(paragraphs): + if i == 0: + structured_text += f"## مقدمه\n{paragraph}\n\n" + elif i == len(paragraphs) - 1: + structured_text += f"## نتیجه‌گیری\n{paragraph}\n\n" + else: + structured_text += f"## بخش {i}\n{paragraph}\n\n" + + return structured_text + + async def _optimize_keyword_density( + self, text: str, target_keywords: List[str], focus_keyword: str + ) -> str: + """بهینه‌سازی تراکم کلمات کلیدی""" + + optimized_text = text + word_count = len(text.split()) + + for keyword in target_keywords + [focus_keyword]: + current_density = self._calculate_single_keyword_density(text, keyword) + target_density = 2.0 if keyword == focus_keyword else 1.0 + + if current_density < target_density: + # اضافه کردن کلمه کلیدی + additions_needed = int((target_density * word_count / 100) - + text.lower().count(keyword.lower())) + + if additions_needed > 0: + optimized_text = self._add_keyword_naturally( + optimized_text, keyword, additions_needed + ) + + return optimized_text + + def _add_keyword_naturally(self, text: str, keyword: str, count: int) -> str: + """اضافه کردن طبیعی کلمه کلیدی به متن""" + + sentences = text.split('.') + keyword_variations = self._generate_keyword_variations(keyword) + + added = 0 + for i, sentence in enumerate(sentences): + if added >= count: + break + + # اضافه کردن در جملات مناسب + if len(sentence.split()) > 5 and keyword.lower() not in sentence.lower(): + variation = keyword_variations[added % len(keyword_variations)] + sentences[i] = sentence + f" {variation}" + added += 1 + + return '.'.join(sentences) + + def _generate_keyword_variations(self, keyword: str) -> List[str]: + """تولید تنوعات کلمه کلیدی""" + return [ + keyword, + f"در مورد {keyword}", + f"برای {keyword}", + f"راجع به {keyword}", + f"{keyword} مهم" + ] + + def _place_keywords_strategically(self, text: str, focus_keyword: str) -> str: + """قرار دادن استراتژیک کلمه کلیدی""" + + # در پاراگراف اول + paragraphs = text.split('\n\n') + if paragraphs and focus_keyword.lower() not in paragraphs[0].lower(): + paragraphs[0] = f"{focus_keyword} {paragraphs[0]}" + + # در پاراگراف آخر + if len(paragraphs) > 1 and focus_keyword.lower() not in paragraphs[-1].lower(): + paragraphs[-1] = paragraphs[-1] + f" در نهایت، {focus_keyword} بسیار مهم است." + + return '\n\n'.join(paragraphs) + + def _improve_paragraph_structure(self, text: str) -> str: + """بهبود ساختار پاراگراف‌ها""" + + paragraphs = text.split('\n\n') + improved_paragraphs = [] + + for paragraph in paragraphs: + # تقسیم پاراگراف‌های طولانی + if len(paragraph.split()) > 100: + sentences = paragraph.split('.') + mid_point = len(sentences) // 2 + + part1 = '.'.join(sentences[:mid_point]) + '.' + part2 = '.'.join(sentences[mid_point:]) + + improved_paragraphs.extend([part1.strip(), part2.strip()]) + else: + improved_paragraphs.append(paragraph.strip()) + + return '\n\n'.join(improved_paragraphs) + + async def _generate_seo_title(self, title: str, focus_keyword: str) -> str: + """تولید عنوان SEO بهینه""" + + rules = self.seo_rules["title_length"] + + # اگر عنوان شامل کلمه کلیدی نیست، اضافه کن + if focus_keyword.lower() not in title.lower(): + title = f"{focus_keyword} - {title}" + + # بررسی طول + if len(title) > rules["max"]: + # کوتاه کردن با حفظ کلمه کلیدی + title = title[:rules["max"]-3] + "..." + elif len(title) < rules["min"]: + # طولانی کردن + title += f" | راهنمای کامل {focus_keyword}" + + return title.strip() + + async def _generate_meta_description( + self, description: str, focus_keyword: str, target_keywords: List[str] + ) -> str: + """تولید متای توضیحات بهینه""" + + rules = self.seo_rules["meta_description_length"] + + # استخراج جمله اول به عنوان متای توضیحات + if not description or len(description) > 500: + first_paragraph = description.split('\n\n')[0] if description else "" + description = first_paragraph[:200] + "..." + + # اطمینان از وجود کلمه کلیدی اصلی + if focus_keyword.lower() not in description.lower(): + description = f"{focus_keyword}: {description}" + + # اضافه کردن کلمات کلیدی دیگر در صورت امکان + for keyword in target_keywords[:2]: + if keyword.lower() not in description.lower() and len(description) < rules["max"] - 20: + description += f" {keyword}" + + # تنظیم طول + if len(description) > rules["max"]: + description = description[:rules["max"]-3] + "..." + elif len(description) < rules["min"]: + description += f" اطلاعات کامل درباره {focus_keyword} و {', '.join(target_keywords[:2])}" + + return description.strip() + + def _calculate_keyword_density( + self, text: str, keywords: List[str] + ) -> Dict[str, float]: + """محاسبه تراکم کلمات کلیدی""" + + density = {} + + for keyword in keywords: + density[keyword] = self._calculate_single_keyword_density(text, keyword) + + return density + + def _calculate_single_keyword_density(self, text: str, keyword: str) -> float: + """محاسبه تراکم یک کلمه کلیدی""" + + total_words = len(text.split()) + keyword_count = text.lower().count(keyword.lower()) + + if total_words == 0: + return 0.0 + + return round((keyword_count / total_words) * 100, 2) + + def _analyze_headings_structure(self, text: str) -> List[Dict[str, Any]]: + """تجزیه ساختار هدینگ‌ها""" + + headings = [] + lines = text.split('\n') + + for line_num, line in enumerate(lines, 1): + line = line.strip() + + # شناسایی هدینگ‌های Markdown + if line.startswith('#'): + level = len(line) - len(line.lstrip('#')) + title = line.lstrip('#').strip() + + headings.append({ + "level": level, + "title": title, + "line_number": line_num, + "character_count": len(title) + }) + + return headings + + async def _calculate_seo_score( + self, + content: str, + title: str, + meta_description: str, + keyword_density: Dict[str, float], + headings_structure: List[Dict], + focus_keyword: str + ) -> float: + """محاسبه امتیاز SEO کلی""" + + score = 0.0 + max_score = 100.0 + + # امتیاز طول محتوا (20 امتیاز) + word_count = len(content.split()) + if self.seo_rules["content_length"]["min"] <= word_count <= self.seo_rules["content_length"]["max"]: + score += 20 + elif word_count < self.seo_rules["content_length"]["min"]: + score += 10 + + # امتیاز عنوان (15 امتیاز) + title_len = len(title) + if self.seo_rules["title_length"]["min"] <= title_len <= self.seo_rules["title_length"]["max"]: + score += 15 + elif title_len < self.seo_rules["title_length"]["max"]: + score += 10 + + # امتیاز متای توضیحات (15 امتیاز) + meta_len = len(meta_description) + if self.seo_rules["meta_description_length"]["min"] <= meta_len <= self.seo_rules["meta_description_length"]["max"]: + score += 15 + elif meta_len < self.seo_rules["meta_description_length"]["max"]: + score += 10 + + # امتیاز تراکم کلمات کلیدی (25 امتیاز) + focus_density = keyword_density.get(focus_keyword, 0) + if self.seo_rules["keyword_density"]["min"] <= focus_density <= self.seo_rules["keyword_density"]["max"]: + score += 25 + elif focus_density > 0: + score += 15 + + # امتیاز ساختار هدینگ‌ها (15 امتیاز) + h1_count = len([h for h in headings_structure if h["level"] == 1]) + h2_count = len([h for h in headings_structure if h["level"] == 2]) + + if h1_count == 1 and h2_count >= 2: + score += 15 + elif h1_count == 1: + score += 10 + elif headings_structure: + score += 5 + + # امتیاز وجود کلمه کلیدی در عنوان (10 امتیاز) + if focus_keyword.lower() in title.lower(): + score += 10 + + return round(min(score, max_score), 1) + + async def _generate_recommendations( + self, + analysis: Dict, + seo_score: float, + keyword_density: Dict, + headings_structure: List[Dict] + ) -> List[str]: + """تولید توصیه‌های بهبود""" + + recommendations = [] + + # توصیه‌های مبتنی بر امتیاز + if seo_score < 70: + recommendations.append("امتیاز SEO قابل بهبود است") + + # توصیه‌های مبتنی بر مسائل شناسایی شده + recommendations.extend(analysis["issues"]) + + # توصیه‌های ساختار هدینگ + h1_count = len([h for h in headings_structure if h["level"] == 1]) + if h1_count == 0: + recommendations.append("حداقل یک هدینگ H1 اضافه کنید") + elif h1_count > 1: + recommendations.append("فقط یک هدینگ H1 استفاده کنید") + + h2_count = len([h for h in headings_structure if h["level"] == 2]) + if h2_count < 2: + recommendations.append("حداقل ۲ هدینگ H2 برای بهتر سازماندهی محتوا اضافه کنید") + + # توصیه‌های تراکم کلمات کلیدی + for keyword, density in keyword_density.items(): + if density < 0.5: + recommendations.append(f"تراکم کلمه '{keyword}' را افزایش دهید") + elif density > 3.0: + recommendations.append(f"تراکم کلمه '{keyword}' زیاد است، کاهش دهید") + + # توصیه‌های عمومی + if not recommendations: + recommendations.append("محتوای شما از نظر SEO بهینه است!") + + return recommendations[:10] # حداکثر ۱۰ توصیه + + def _calculate_readability_score(self, text: str) -> float: + """محاسبه امتیاز خوانایی (ساده‌شده برای فارسی)""" + + sentences = len(re.findall(r'[.!؟]+', text)) + words = len(text.split()) + + if sentences == 0: + return 0.0 + + avg_sentence_length = words / sentences + + # فرمول ساده‌شده برای فارسی + if avg_sentence_length <= 15: + score = 90 + elif avg_sentence_length <= 20: + score = 75 + elif avg_sentence_length <= 25: + score = 60 + else: + score = 40 + + # کاهش امتیاز برای کلمات پیچیده (بیش از ۸ حرف) + long_words = len([word for word in text.split() if len(word) > 8]) + complexity_penalty = (long_words / words) * 20 if words > 0 else 0 + + final_score = max(score - complexity_penalty, 0) + + return round(final_score, 1) \ No newline at end of file diff --git a/src/visual_idea_generator.py b/src/visual_idea_generator.py new file mode 100644 index 0000000..4114055 --- /dev/null +++ b/src/visual_idea_generator.py @@ -0,0 +1,672 @@ +""" +تولیدکننده ایده‌های بصری برای محتوا +""" + +import asyncio +import random +from typing import Dict, List, Any, Optional +import logging + +from .platforms import PlatformType, ContentType + +logger = logging.getLogger(__name__) + +class VisualIdeaGenerator: + """کلاس تولید ایده‌های بصری""" + + def __init__(self): + self.visual_templates = self._load_visual_templates() + self.color_palettes = self._load_color_palettes() + self.design_trends = self._load_design_trends() + + def _load_visual_templates(self) -> Dict[str, Any]: + """بارگذاری قالب‌های بصری""" + return { + "image_styles": { + "minimalist": "طراحی مینیمال با فضای خالی زیاد", + "colorful": "طراحی رنگارنگ و پر انرژی", + "professional": "طراحی حرفه‌ای و جدی", + "modern": "طراحی مدرن با خطوط تمیز", + "vintage": "طراحی کلاسیک و قدیمی", + "geometric": "طراحی هندسی با اشکال منظم", + "organic": "طراحی طبیعی با فرم‌های ارگانیک", + "gradient": "طراحی با گرادیان‌های زیبا" + }, + + "layout_types": { + "single_focus": "یک عنصر اصلی در مرکز", + "grid": "چیدمان شبکه‌ای منظم", + "asymmetric": "چیدمان نامتقارن و پویا", + "layered": "لایه‌بندی عمق‌دار", + "split_screen": "تقسیم صفحه به دو بخش", + "circular": "چیدمان دایره‌ای", + "diagonal": "خطوط مورب و پویا", + "frame": "استفاده از قاب و مرز" + }, + + "typography_styles": { + "bold_headers": "عناوین ضخیم و چشمگیر", + "script_fonts": "فونت‌های دست‌نویس", + "modern_sans": "فونت‌های مدرن بدون سریف", + "classic_serif": "فونت‌های کلاسیک با سریف", + "display_fonts": "فونت‌های نمایشی خاص", + "minimal_text": "حداقل متن با تأکید بر بصری" + }, + + "content_elements": { + "icons": "آیکون‌های مناسب موضوع", + "illustrations": "تصاویر وکتوری سفارشی", + "photos": "عکس‌های با کیفیت", + "graphics": "گرافیک‌های اطلاعاتی", + "patterns": "الگوها و بافت‌ها", + "charts": "نمودارها و آمار", + "quotes": "نقل قول‌های بصری", + "logos": "لوگو و نمادها" + } + } + + def _load_color_palettes(self) -> Dict[str, List[str]]: + """بارگذاری پالت‌های رنگی""" + return { + "warm": ["#FF6B35", "#F7931E", "#FFD23F", "#FFF1D0"], + "cool": ["#06BCC1", "#0F4C75", "#3282B8", "#BBE1FA"], + "natural": ["#2F5233", "#81A684", "#C7E9B0", "#F4F9E7"], + "professional": ["#2C3E50", "#34495E", "#95A5A6", "#ECF0F1"], + "vibrant": ["#E74C3C", "#9B59B6", "#3498DB", "#1ABC9C"], + "pastel": ["#FFC3A0", "#FFAFCC", "#BDE0FF", "#A8E6CF"], + "monochrome": ["#000000", "#333333", "#666666", "#999999", "#CCCCCC", "#FFFFFF"], + "sunset": ["#FF7F7F", "#FFBF7F", "#FFDF7F", "#FFFF7F"], + "ocean": ["#000080", "#0066CC", "#4DA6FF", "#99CCFF"], + "earth": ["#8B4513", "#CD853F", "#DEB887", "#F5DEB3"] + } + + def _load_design_trends(self) -> Dict[str, str]: + """بارگذاری ترندهای طراحی""" + return { + "glassmorphism": "افکت شیشه‌ای و شفافیت", + "neumorphism": "طراحی soft UI با سایه نرم", + "dark_mode": "حالت تاریک با کنتراست بالا", + "3d_elements": "عناصر سه‌بعدی و عمق", + "abstract_shapes": "اشکال انتزاعی و هندسی", + "hand_drawn": "عناصر دست‌کشیده و طبیعی", + "vintage_retro": "طراحی وینتیج و رترو", + "minimalist_brutalism": "مینیمالیسم با عناصر قوی" + } + + async def generate_ideas( + self, + content: str, + platform: PlatformType, + content_type: ContentType = ContentType.POST, + style_preferences: Optional[List[str]] = None + ) -> Dict[str, Any]: + """تولید ایده‌های بصری برای محتوا""" + + logger.info(f"تولید ایده‌های بصری برای {platform.value}") + + # تحلیل محتوا برای استخراج موضوع + content_analysis = await self._analyze_content_theme(content) + + # تولید ایده‌های تصویری + image_ideas = await self._generate_image_ideas( + content_analysis, platform, content_type, style_preferences + ) + + # تولید ایده‌های ویدئویی + video_ideas = await self._generate_video_ideas( + content_analysis, platform, content_type + ) + + # پیشنهاد عناصر گرافیکی + graphic_elements = await self._suggest_graphic_elements( + content_analysis, platform + ) + + # پیشنهاد رنگ‌ها + color_suggestions = await self._suggest_colors( + content_analysis, style_preferences + ) + + # پیشنهاد تایپوگرافی + typography_suggestions = await self._suggest_typography( + platform, content_type + ) + + # پیشنهاد چیدمان + layout_suggestions = await self._suggest_layouts( + platform, content_type, content_analysis + ) + + return { + "image_ideas": image_ideas, + "video_ideas": video_ideas, + "graphic_elements": graphic_elements, + "color_suggestions": color_suggestions, + "typography_suggestions": typography_suggestions, + "layout_suggestions": layout_suggestions, + "generated_at": "now", + "platform_optimized": True + } + + async def _analyze_content_theme(self, content: str) -> Dict[str, Any]: + """تحلیل محتوا برای تشخیص موضوع و حس‌وحال""" + + # کلمات کلیدی موضوعی + themes = { + "technology": ["تکنولوژی", "فناوری", "دیجیتال", "نرم‌افزار", "هوش مصنوعی"], + "business": ["کسب‌وکار", "بازاریابی", "فروش", "درآمد", "موفقیت"], + "health": ["سلامت", "تناسب اندام", "تغذیه", "ورزش", "درمان"], + "lifestyle": ["سبک زندگی", "زیبایی", "مد", "آرایش", "دکوراسیون"], + "education": ["آموزش", "یادگیری", "مهارت", "دانش", "توسعه"], + "travel": ["سفر", "گردشگری", "مکان", "شهر", "کشور"], + "food": ["غذا", "آشپزی", "رستوران", "طعم", "دستپخت"], + "entertainment": ["سرگرمی", "فیلم", "موسیقی", "بازی", "تفریح"] + } + + # تشخیص موضوع اصلی + detected_theme = "general" + max_matches = 0 + + content_lower = content.lower() + for theme, keywords in themes.items(): + matches = sum(1 for keyword in keywords if keyword in content_lower) + if matches > max_matches: + max_matches = matches + detected_theme = theme + + # تحلیل حس‌وحال + mood = self._detect_mood(content) + + # استخراج کلمات کلیدی بصری + visual_keywords = self._extract_visual_keywords(content) + + return { + "theme": detected_theme, + "mood": mood, + "visual_keywords": visual_keywords, + "content_length": len(content), + "has_numbers": bool(any(char.isdigit() for char in content)), + "has_questions": "؟" in content + } + + def _detect_mood(self, content: str) -> str: + """تشخیص حس‌وحال محتوا""" + + positive_words = ["خوب", "عالی", "موفق", "بهترین", "زیبا", "شاد", "مثبت"] + negative_words = ["بد", "مشکل", "ناراحت", "غمگین", "منفی", "سخت"] + energetic_words = ["انرژی", "هیجان", "پویا", "سریع", "قوی", "پرقدرت"] + calm_words = ["آرام", "آرامش", "صلح", "نرم", "ملایم", "ساکت"] + + content_lower = content.lower() + + positive_count = sum(1 for word in positive_words if word in content_lower) + negative_count = sum(1 for word in negative_words if word in content_lower) + energetic_count = sum(1 for word in energetic_words if word in content_lower) + calm_count = sum(1 for word in calm_words if word in content_lower) + + if energetic_count > 0: + return "energetic" + elif calm_count > 0: + return "calm" + elif positive_count > negative_count: + return "positive" + elif negative_count > positive_count: + return "serious" + else: + return "neutral" + + def _extract_visual_keywords(self, content: str) -> List[str]: + """استخراج کلمات کلیدی بصری""" + + visual_words = [ + "رنگ", "نور", "تصویر", "طراحی", "شکل", "فرم", "خط", "نقطه", + "دایره", "مربع", "مثلث", "منحنی", "زاویه", "سایه", "برجستگی" + ] + + found_keywords = [] + content_lower = content.lower() + + for word in visual_words: + if word in content_lower: + found_keywords.append(word) + + return found_keywords[:5] # حداکثر ۵ کلمه + + async def _generate_image_ideas( + self, + content_analysis: Dict, + platform: PlatformType, + content_type: ContentType, + style_preferences: Optional[List[str]] = None + ) -> List[Dict[str, Any]]: + """تولید ایده‌های تصویری""" + + ideas = [] + theme = content_analysis["theme"] + mood = content_analysis["mood"] + + # انتخاب سبک بر اساس ترجیحات یا موضوع + if style_preferences: + styles = style_preferences + else: + styles = self._get_theme_appropriate_styles(theme, mood) + + # تولید ایده‌ها برای هر سبک + for style in styles[:3]: # حداکثر ۳ سبک + idea = { + "style": style, + "description": self.visual_templates["image_styles"].get( + style, "سبک خاص" + ), + "elements": self._get_style_elements(style, theme), + "layout": self._get_recommended_layout(platform, content_type), + "focal_point": self._get_focal_point_suggestion(content_analysis), + "color_mood": self._map_mood_to_colors(mood), + "composition_tips": self._get_composition_tips(platform, style) + } + + ideas.append(idea) + + return ideas + + def _get_theme_appropriate_styles(self, theme: str, mood: str) -> List[str]: + """انتخاب سبک‌های مناسب برای موضوع""" + + theme_styles = { + "technology": ["modern", "geometric", "gradient"], + "business": ["professional", "minimalist", "modern"], + "health": ["organic", "colorful", "minimalist"], + "lifestyle": ["colorful", "vintage", "organic"], + "education": ["professional", "modern", "colorful"], + "travel": ["colorful", "vintage", "organic"], + "food": ["colorful", "organic", "vintage"], + "entertainment": ["colorful", "gradient", "modern"] + } + + mood_styles = { + "energetic": ["colorful", "gradient", "geometric"], + "calm": ["minimalist", "organic", "professional"], + "positive": ["colorful", "modern", "gradient"], + "serious": ["professional", "minimalist", "modern"], + "neutral": ["modern", "professional", "geometric"] + } + + # ترکیب پیشنهادات + suggested_styles = list(set( + theme_styles.get(theme, ["modern"]) + + mood_styles.get(mood, ["professional"]) + )) + + return suggested_styles[:3] + + def _get_style_elements(self, style: str, theme: str) -> List[str]: + """دریافت عناصر مناسب برای سبک""" + + style_elements = { + "minimalist": ["فضای خالی زیاد", "رنگ‌های کم", "خطوط ساده"], + "colorful": ["رنگ‌های پررنگ", "کنتراست بالا", "عناصر متنوع"], + "professional": ["رنگ‌های خنثی", "فونت‌های ساده", "چیدمان منظم"], + "modern": ["خطوط تمیز", "گرادیان‌ها", "فضای منفی"], + "vintage": ["بافت قدیمی", "رنگ‌های کم‌اشباع", "فونت‌های کلاسیک"], + "geometric": ["اشکال هندسی", "خطوط راست", "تقارن"], + "organic": ["اشکال طبیعی", "منحنی‌ها", "رنگ‌های زمینی"], + "gradient": ["تدریج رنگ", "انتقال نرم", "عمق بصری"] + } + + return style_elements.get(style, ["عناصر استاندارد"]) + + def _get_recommended_layout(self, platform: PlatformType, content_type: ContentType) -> str: + """پیشنهاد چیدمان بر اساس پلتفرم""" + + platform_layouts = { + PlatformType.INSTAGRAM: { + ContentType.POST: "single_focus", + ContentType.STORY: "layered", + ContentType.CAROUSEL: "grid" + }, + PlatformType.TELEGRAM: { + ContentType.POST: "asymmetric", + ContentType.NEWS: "frame" + }, + PlatformType.WEBSITE: { + ContentType.ARTICLE: "split_screen" + } + } + + return platform_layouts.get(platform, {}).get(content_type, "single_focus") + + def _get_focal_point_suggestion(self, content_analysis: Dict) -> str: + """پیشنهاد نقطه کانونی""" + + if content_analysis["has_numbers"]: + return "آمار و اعداد در مرکز" + elif content_analysis["has_questions"]: + return "سوال به عنوان عنصر اصلی" + elif content_analysis["theme"] == "business": + return "لوگو یا نماد کسب‌وکار" + else: + return "متن اصلی یا تصویر نماد" + + def _map_mood_to_colors(self, mood: str) -> str: + """نگاشت حس‌وحال به رنگ‌ها""" + + mood_colors = { + "energetic": "warm", + "calm": "cool", + "positive": "vibrant", + "serious": "professional", + "neutral": "monochrome" + } + + return mood_colors.get(mood, "natural") + + def _get_composition_tips(self, platform: PlatformType, style: str) -> List[str]: + """نکات ترکیب‌بندی""" + + platform_tips = { + PlatformType.INSTAGRAM: [ + "نسبت ۱:۱ برای فید", + "متن خوانا روی تصویر", + "رنگ‌های جذاب برای توجه" + ], + PlatformType.TELEGRAM: [ + "کیفیت بالا برای نمایش", + "متن کمتر روی تصویر", + "ابعاد مناسب برای موبایل" + ] + } + + return platform_tips.get(platform, ["کیفیت بالا", "خوانایی مناسب"]) + + async def _generate_video_ideas( + self, + content_analysis: Dict, + platform: PlatformType, + content_type: ContentType + ) -> List[Dict[str, Any]]: + """تولید ایده‌های ویدئویی""" + + video_ideas = [] + theme = content_analysis["theme"] + + # انواع ویدئوهای مناسب + video_types = self._get_video_types_for_theme(theme) + + for video_type in video_types[:3]: + idea = { + "type": video_type, + "duration": self._get_optimal_duration(platform, video_type), + "style": self._get_video_style(theme, video_type), + "shots": self._generate_shot_list(video_type, content_analysis), + "music_suggestion": self._suggest_music(theme, video_type), + "text_overlay": self._suggest_text_overlay(platform, content_analysis), + "transitions": self._suggest_transitions(video_type), + "color_grading": self._suggest_color_grading(theme) + } + + video_ideas.append(idea) + + return video_ideas + + def _get_video_types_for_theme(self, theme: str) -> List[str]: + """انواع ویدئوی مناسب برای موضوع""" + + theme_videos = { + "technology": ["demo", "animation", "timelapse"], + "business": ["presentation", "interview", "infographic"], + "health": ["tutorial", "transformation", "lifestyle"], + "lifestyle": ["aesthetic", "day_in_life", "tutorial"], + "education": ["tutorial", "animation", "presentation"], + "travel": ["cinematic", "timelapse", "aesthetic"], + "food": ["recipe", "aesthetic", "timelapse"], + "entertainment": ["fun", "animation", "compilation"] + } + + return theme_videos.get(theme, ["presentation", "tutorial", "aesthetic"]) + + def _get_optimal_duration(self, platform: PlatformType, video_type: str) -> str: + """مدت زمان بهینه ویدئو""" + + platform_durations = { + PlatformType.INSTAGRAM: { + "story": "15 ثانیه", + "reel": "30 ثانیه", + "post": "60 ثانیه" + }, + PlatformType.TELEGRAM: "2-5 دقیقه" + } + + if platform == PlatformType.INSTAGRAM: + if video_type in ["fun", "aesthetic"]: + return "15-30 ثانیه" + else: + return "60-90 ثانیه" + + return "1-3 دقیقه" + + def _get_video_style(self, theme: str, video_type: str) -> str: + """سبک ویدئو""" + + styles = { + "technology": "مدرن و تمیز", + "business": "حرفه‌ای و جدی", + "health": "طبیعی و انرژی‌بخش", + "lifestyle": "زیبا و جذاب", + "education": "واضح و آموزشی" + } + + return styles.get(theme, "متنوع و جذاب") + + def _generate_shot_list(self, video_type: str, content_analysis: Dict) -> List[str]: + """لیست شات‌های پیشنهادی""" + + shot_types = { + "tutorial": [ + "نمای کلی از موضوع", + "نزدیک‌شدن به جزئیات", + "مراحل گام‌به‌گام", + "نتیجه نهایی" + ], + "aesthetic": [ + "نمای زیبا از موضوع اصلی", + "جزئیات با فوکوس نرم", + "حرکت آرام دوربین", + "نمای نهایی جذاب" + ], + "presentation": [ + "معرفی موضوع", + "نکات کلیدی با گرافیک", + "مثال‌های عملی", + "خلاصه و نتیجه‌گیری" + ] + } + + return shot_types.get(video_type, ["شات ابتدایی", "محتوای اصلی", "پایان"]) + + def _suggest_music(self, theme: str, video_type: str) -> str: + """پیشنهاد موسیقی""" + + music_suggestions = { + "technology": "الکترونیک آرام یا آمبیانت", + "business": "موسیقی انگیزشی یا کلاسیک", + "health": "موسیقی آرام و طبیعی", + "lifestyle": "موسیقی شاد و مدرن", + "education": "موسیقی ملایم و تمرکزی" + } + + return music_suggestions.get(theme, "موسیقی مناسب موضوع") + + def _suggest_text_overlay(self, platform: PlatformType, content_analysis: Dict) -> List[str]: + """پیشنهاد متن روی ویدئو""" + + suggestions = ["عنوان اصلی در ابتدا"] + + if content_analysis["has_numbers"]: + suggestions.append("نمایش آمار کلیدی") + + if content_analysis["has_questions"]: + suggestions.append("نمایش سوال مهم") + + suggestions.append("فراخوان عمل در پایان") + + return suggestions + + def _suggest_transitions(self, video_type: str) -> List[str]: + """پیشنهاد ترانزیشن‌ها""" + + transitions = { + "tutorial": ["برش ساده", "فید کردن", "حرکت صاف"], + "aesthetic": ["تدریج نرم", "حرکت آهسته", "انتقال روان"], + "presentation": ["برش سریع", "اسلاید", "زوم"] + } + + return transitions.get(video_type, ["برش ساده", "تدریج نرم"]) + + def _suggest_color_grading(self, theme: str) -> str: + """پیشنهاد رنگ‌بندی""" + + grading = { + "technology": "رنگ‌های سرد و مدرن", + "business": "رنگ‌های خنثی و حرفه‌ای", + "health": "رنگ‌های گرم و طبیعی", + "lifestyle": "رنگ‌های روشن و شاد", + "education": "رنگ‌های متعادل و آرام" + } + + return grading.get(theme, "رنگ‌بندی طبیعی") + + async def _suggest_graphic_elements( + self, content_analysis: Dict, platform: PlatformType + ) -> List[str]: + """پیشنهاد عناصر گرافیکی""" + + elements = [] + theme = content_analysis["theme"] + + # عناصر اساسی بر اساس موضوع + theme_elements = { + "technology": ["آیکون‌های فنی", "خطوط دیجیتال", "نمودارها"], + "business": ["گراف‌ها", "نمادهای پولی", "آیکون‌های کسب‌وکار"], + "health": ["نمادهای پزشکی", "المان‌های طبیعی", "نمودار پیشرفت"], + "lifestyle": ["آیکون‌های زیبایی", "نقوش تزیینی", "فریم‌های زیبا"], + "education": ["آیکون‌های آموزشی", "نمودارها", "فلش‌های راهنما"] + } + + elements.extend(theme_elements.get(theme, ["آیکون‌های عمومی"])) + + # عناصر بر اساس پلتفرم + platform_elements = { + PlatformType.INSTAGRAM: ["فریم استوری", "استیکرها", "GIF"], + PlatformType.TELEGRAM: ["ایموجی", "مارک‌داون", "لینک پیش‌نمایش"] + } + + elements.extend(platform_elements.get(platform, [])) + + return elements + + async def _suggest_colors( + self, content_analysis: Dict, style_preferences: Optional[List[str]] = None + ) -> List[str]: + """پیشنهاد رنگ‌ها""" + + mood = content_analysis["mood"] + theme = content_analysis["theme"] + + # انتخاب پالت بر اساس حس‌وحال + mood_palette = self._map_mood_to_colors(mood) + suggested_colors = self.color_palettes[mood_palette] + + # اضافه کردن رنگ‌های موضوعی + theme_colors = { + "technology": ["#007ACC", "#FF6B35", "#36A2EB"], + "business": ["#2C3E50", "#E74C3C", "#F39C12"], + "health": ["#27AE60", "#E67E22", "#3498DB"], + "lifestyle": ["#E91E63", "#9C27B0", "#FF9800"], + "education": ["#3F51B5", "#009688", "#FF5722"] + } + + if theme in theme_colors: + suggested_colors.extend(theme_colors[theme]) + + # حذف تکراری و محدود کردن + unique_colors = list(dict.fromkeys(suggested_colors)) + + return unique_colors[:6] # حداکثر ۶ رنگ + + async def _suggest_typography( + self, platform: PlatformType, content_type: ContentType + ) -> List[str]: + """پیشنهاد تایپوگرافی""" + + typography_rules = { + PlatformType.INSTAGRAM: { + ContentType.POST: ["فونت‌های ضخیم برای عنوان", "حداکثر ۳ سایز مختلف"], + ContentType.STORY: ["فونت‌های بزرگ و خوانا", "کنتراست بالا"] + }, + PlatformType.TELEGRAM: ["فونت‌های ساده", "اندازه متوسط", "خوانایی بالا"], + PlatformType.WEBSITE: ["فونت‌های وب‌سیف", "هیرارشی واضح", "تناسب موبایل"] + } + + suggestions = typography_rules.get(platform, {}) + if isinstance(suggestions, dict): + suggestions = suggestions.get(content_type, ["فونت‌های استاندارد"]) + + # اضافه کردن نکات عمومی + general_tips = [ + "استفاده از فونت‌های فارسی مناسب", + "رعایت فاصله خطوط", + "تناسب اندازه با محتوا" + ] + + if isinstance(suggestions, list): + suggestions.extend(general_tips) + else: + suggestions = general_tips + + return suggestions[:5] + + async def _suggest_layouts( + self, + platform: PlatformType, + content_type: ContentType, + content_analysis: Dict + ) -> List[str]: + """پیشنهاد چیدمان""" + + suggestions = [] + + # چیدمان بر اساس پلتفرم + platform_layouts = { + PlatformType.INSTAGRAM: { + ContentType.POST: [ + "متن بالا، تصویر پایین", + "تصویر پس‌زمینه با متن روی آن", + "تقسیم به سه بخش افقی" + ], + ContentType.STORY: [ + "متن در وسط صفحه", + "تصویر پس‌زمینه با متن بالا", + "چیدمان عمودی" + ] + }, + PlatformType.TELEGRAM: [ + "متن بالا، تصویر پایین", + "تصویر کوچک کنار متن", + "گالری تصاویر" + ] + } + + platform_suggestion = platform_layouts.get(platform, {}) + if isinstance(platform_suggestion, dict): + suggestions = platform_suggestion.get(content_type, []) + else: + suggestions = platform_suggestion + + # اضافه کردن پیشنهادات بر اساس محتوا + if content_analysis["has_numbers"]: + suggestions.append("نمایش برجسته آمار و اعداد") + + if content_analysis["has_questions"]: + suggestions.append("قرار دادن سوال در مکان کانونی") + + return suggestions[:4] \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..14f5872 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,490 @@ +/* استایل‌های سفارشی برای سیستم تولید محتوا */ + +/* فونت‌های فارسی */ +@import url('https://fonts.googleapis.com/css2?family=Vazir:wght@300;400;500;600;700&display=swap'); + +/* متغیرهای رنگی */ +:root { + --primary-color: #6366f1; + --secondary-color: #8b5cf6; + --success-color: #10b981; + --warning-color: #f59e0b; + --danger-color: #ef4444; + --info-color: #06b6d4; + --dark-color: #1f2937; + --light-color: #f9fafb; + --border-radius: 8px; + --box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + --transition: all 0.3s ease; +} + +/* تنظیمات کلی */ +* { + font-family: 'Vazir', 'Tahoma', sans-serif; +} + +body { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background-attachment: fixed; + min-height: 100vh; +} + +/* ناوبری */ +.navbar { + backdrop-filter: blur(10px); + background: rgba(99, 102, 241, 0.9) !important; + box-shadow: var(--box-shadow); +} + +.navbar-brand { + font-weight: 600; + font-size: 1.25rem; +} + +.nav-link { + transition: var(--transition); + border-radius: var(--border-radius); + margin: 0 5px; +} + +.nav-link:hover { + background: rgba(255, 255, 255, 0.1); + transform: translateY(-1px); +} + +.nav-link.active { + background: rgba(255, 255, 255, 0.2); +} + +/* کارت‌ها */ +.card { + border: none; + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); + transition: var(--transition); + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); +} + +.card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px -5px rgba(0, 0, 0, 0.15); +} + +.card-header { + border-radius: var(--border-radius) var(--border-radius) 0 0 !important; + border-bottom: none; + font-weight: 600; +} + +/* فرم‌ها */ +.form-control, +.form-select { + border-radius: var(--border-radius); + border: 2px solid #e5e7eb; + transition: var(--transition); + padding: 12px 16px; +} + +.form-control:focus, +.form-select:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); +} + +.form-label { + font-weight: 500; + color: var(--dark-color); + margin-bottom: 8px; +} + +/* چک‌باکس‌های پلتفرم */ +.platform-checkboxes { + background: #f8fafc; + padding: 15px; + border-radius: var(--border-radius); + border: 1px solid #e2e8f0; +} + +.platform-checkboxes .form-check { + margin-bottom: 10px; + padding: 8px 12px; + border-radius: 6px; + transition: var(--transition); +} + +.platform-checkboxes .form-check:hover { + background: rgba(99, 102, 241, 0.05); +} + +.platform-checkboxes .form-check-input:checked + .form-check-label { + color: var(--primary-color); + font-weight: 500; +} + +/* دکمه‌ها */ +.btn { + border-radius: var(--border-radius); + font-weight: 500; + padding: 10px 20px; + transition: var(--transition); + border: none; +} + +.btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.btn-primary { + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); +} + +.btn-success { + background: linear-gradient(135deg, var(--success-color), #059669); +} + +.btn-info { + background: linear-gradient(135deg, var(--info-color), #0891b2); +} + +.btn-warning { + background: linear-gradient(135deg, var(--warning-color), #d97706); +} + +/* نتایج */ +.platform-result { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: var(--border-radius); + padding: 20px; + margin-bottom: 20px; + transition: var(--transition); +} + +.platform-result:hover { + border-color: var(--primary-color); + box-shadow: 0 4px 12px rgba(99, 102, 241, 0.1); +} + +.platform-icon { + width: 40px; + height: 40px; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; + color: white; + margin-left: 15px; +} + +.platform-icon.instagram { + background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%); +} + +.platform-icon.telegram { + background: #0088cc; +} + +.platform-icon.website { + background: var(--success-color); +} + +.platform-icon.eitaa { + background: var(--info-color); +} + +.platform-icon.rubika { + background: var(--warning-color); +} + +.content-text { + background: white; + border: 1px solid #e2e8f0; + border-radius: 6px; + padding: 15px; + margin-top: 10px; + white-space: pre-wrap; + line-height: 1.6; + font-size: 14px; +} + +/* هشتگ‌ها */ +.hashtag { + display: inline-block; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + color: white; + padding: 5px 12px; + border-radius: 20px; + margin: 3px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: var(--transition); +} + +.hashtag:hover { + transform: scale(1.05); + box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3); +} + +/* CTA */ +.cta-item { + background: white; + border: 1px solid #e2e8f0; + border-radius: var(--border-radius); + padding: 12px 16px; + margin: 5px 0; + cursor: pointer; + transition: var(--transition); +} + +.cta-item:hover { + border-color: var(--success-color); + background: rgba(16, 185, 129, 0.05); +} + +/* اسپینر */ +.spinner-border { + width: 3rem; + height: 3rem; +} + +/* حالت خالی */ +#emptyState { + opacity: 0.7; +} + +#emptyState i { + color: var(--primary-color); +} + +/* لیست زمان‌بندی شده */ +.scheduled-item { + background: white; + border: 1px solid #e2e8f0; + border-radius: var(--border-radius); + padding: 15px; + margin-bottom: 10px; + transition: var(--transition); +} + +.scheduled-item:hover { + border-color: var(--info-color); + box-shadow: 0 2px 8px rgba(6, 182, 212, 0.1); +} + +.status-badge { + font-size: 11px; + padding: 4px 8px; + border-radius: 12px; + font-weight: 500; +} + +.status-scheduled { + background: rgba(6, 182, 212, 0.1); + color: var(--info-color); +} + +.status-published { + background: rgba(16, 185, 129, 0.1); + color: var(--success-color); +} + +.status-failed { + background: rgba(239, 68, 68, 0.1); + color: var(--danger-color); +} + +/* آمار */ +.card.text-center { + transition: var(--transition); +} + +.card.text-center:hover { + transform: translateY(-3px); +} + +/* انیمیشن‌ها */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.fade-in-up { + animation: fadeInUp 0.5s ease-out; +} + +/* کپی شده */ +.copied { + background: var(--success-color) !important; + color: white !important; +} + +.copied::after { + content: " ✓ کپی شد"; + font-weight: bold; +} + +/* ریسپانسیو */ +@media (max-width: 768px) { + .container { + padding: 0 10px; + } + + .card { + margin-bottom: 20px; + } + + .platform-checkboxes { + padding: 10px; + } + + .btn { + width: 100%; + margin-bottom: 10px; + } + + .navbar-brand { + font-size: 1rem; + } +} + +/* اسکرول‌بار سفارشی */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f5f9; +} + +::-webkit-scrollbar-thumb { + background: var(--primary-color); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--secondary-color); +} + +/* تب‌ها */ +.tab-content { + animation: fadeInUp 0.5s ease-out; +} + +/* مودال‌ها */ +.modal-content { + border-radius: var(--border-radius); + border: none; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); +} + +.modal-header { + border-bottom: 1px solid #e2e8f0; + background: #f8fafc; +} + +/* نتایج SEO */ +.seo-score { + display: inline-block; + width: 60px; + height: 60px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-weight: bold; + font-size: 1.2rem; +} + +.seo-score.good { + background: var(--success-color); +} + +.seo-score.average { + background: var(--warning-color); +} + +.seo-score.poor { + background: var(--danger-color); +} + +/* ایده‌های بصری */ +.visual-idea { + background: white; + border: 1px solid #e2e8f0; + border-radius: var(--border-radius); + padding: 20px; + margin: 10px 0; + transition: var(--transition); +} + +.visual-idea:hover { + border-color: var(--primary-color); + box-shadow: 0 4px 12px rgba(99, 102, 241, 0.1); +} + +.color-palette { + display: flex; + gap: 5px; + margin: 10px 0; +} + +.color-swatch { + width: 30px; + height: 30px; + border-radius: 50%; + border: 2px solid white; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* لودینگ */ +.loading-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; +} + +/* توست */ +.toast { + border-radius: var(--border-radius); + border: none; + box-shadow: var(--box-shadow); +} + +/* کیبورد شورتکات */ +.keyboard-hint { + font-size: 11px; + color: #6b7280; + margin-top: 5px; +} + +/* پرینت */ +@media print { + .navbar, + .btn, + .modal { + display: none !important; + } + + .card { + box-shadow: none; + border: 1px solid #ddd; + } +} \ No newline at end of file diff --git a/static/js/app.js b/static/js/app.js new file mode 100644 index 0000000..614efa7 --- /dev/null +++ b/static/js/app.js @@ -0,0 +1,700 @@ +// اپلیکیشن اصلی سیستم تولید محتوای هوش مصنوعی + +class ContentGeneratorApp { + constructor() { + this.apiBase = '/api'; + this.currentContent = null; + this.charts = {}; + this.init(); + } + + init() { + this.setupEventListeners(); + this.loadScheduledPosts(); + this.loadStats(); + this.setupCharts(); + this.showToast('سیستم آماده است!', 'success'); + } + + setupEventListeners() { + // فرم تولید محتوا + document.getElementById('contentForm').addEventListener('submit', (e) => { + e.preventDefault(); + this.generateContent(); + }); + + // فرم زمان‌بندی + document.getElementById('scheduleForm').addEventListener('submit', (e) => { + e.preventDefault(); + this.scheduleContent(); + }); + + // تب‌ها + document.querySelectorAll('[data-bs-toggle="tab"]').forEach(tab => { + tab.addEventListener('shown.bs.tab', (e) => { + const target = e.target.getAttribute('href'); + if (target === '#analytics') { + this.refreshStats(); + } else if (target === '#scheduler') { + this.loadScheduledPosts(); + } + }); + }); + + // کلیدهای میانبر + document.addEventListener('keydown', (e) => { + if (e.ctrlKey || e.metaKey) { + switch (e.key) { + case 'Enter': + e.preventDefault(); + this.generateContent(); + break; + case 's': + e.preventDefault(); + this.saveContent(); + break; + case 'c': + e.preventDefault(); + this.copyAllContent(); + break; + } + } + }); + } + + async generateContent() { + const form = document.getElementById('contentForm'); + const formData = new FormData(form); + + // جمع‌آوری داده‌های فرم + const topic = formData.get('topic'); + const keywords = formData.get('keywords').split(',').map(k => k.trim()).filter(k => k); + const audience = formData.get('audience'); + const contentType = formData.get('content_type') || 'post'; + const tone = formData.get('tone') || 'friendly'; + + // جمع‌آوری پلتفرم‌های انتخاب شده + const platforms = []; + document.querySelectorAll('.platform-checkboxes input:checked').forEach(checkbox => { + platforms.push(checkbox.value); + }); + + if (!topic.trim()) { + this.showToast('لطفاً موضوع را وارد کنید', 'error'); + return; + } + + if (keywords.length === 0) { + this.showToast('لطفاً حداقل یک کلمه کلیدی وارد کنید', 'error'); + return; + } + + if (platforms.length === 0) { + this.showToast('لطفاً حداقل یک پلتفرم انتخاب کنید', 'error'); + return; + } + + // نمایش لودینگ + this.showLoading(); + + try { + const response = await fetch(`${this.apiBase}/generate-content`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + topic, + keywords, + platforms, + content_type: contentType, + tone, + target_audience: audience + }) + }); + + if (!response.ok) { + throw new Error(`خطای سرور: ${response.status}`); + } + + const result = await response.json(); + this.currentContent = result; + this.displayResults(result); + this.showToast('محتوا با موفقیت تولید شد!', 'success'); + + } catch (error) { + console.error('خطا در تولید محتوا:', error); + this.showToast(`خطا در تولید محتوا: ${error.message}`, 'error'); + } finally { + this.hideLoading(); + } + } + + displayResults(data) { + const resultsContainer = document.getElementById('results'); + const emptyState = document.getElementById('emptyState'); + const platformResults = document.querySelector('.platform-results'); + const hashtagsContainer = document.querySelector('.hashtags-container'); + const ctaContainer = document.querySelector('.cta-container'); + + // پاک کردن نتایج قبلی + platformResults.innerHTML = ''; + hashtagsContainer.innerHTML = ''; + ctaContainer.innerHTML = ''; + + // نمایش نتایج برای هر پلتفرم + Object.entries(data.contents).forEach(([platform, content]) => { + const platformDiv = document.createElement('div'); + platformDiv.className = 'platform-result fade-in-up'; + + const platformIcon = this.getPlatformIcon(platform); + const platformName = this.getPlatformName(platform); + + platformDiv.innerHTML = ` +
+
+ +
+
+
${platformName}
+ + ${content.character_count || 0} کاراکتر • + زمان مطالعه: ${Math.ceil((content.estimated_reading_time || 0) / 60)} دقیقه + +
+
+ +
+
+
${content.content || content}
+ `; + + platformResults.appendChild(platformDiv); + }); + + // نمایش هشتگ‌ها + if (data.hashtags) { + Object.entries(data.hashtags).forEach(([platform, tags]) => { + tags.forEach(tag => { + const hashtagElement = document.createElement('span'); + hashtagElement.className = 'hashtag'; + hashtagElement.textContent = tag; + hashtagElement.onclick = () => this.copyText(tag); + hashtagsContainer.appendChild(hashtagElement); + }); + }); + } + + // نمایش پیشنهادات CTA + if (data.cta_suggestions) { + data.cta_suggestions.forEach(cta => { + const ctaElement = document.createElement('div'); + ctaElement.className = 'cta-item'; + ctaElement.innerHTML = ` + + ${cta} + `; + ctaElement.onclick = () => this.copyText(cta); + ctaContainer.appendChild(ctaElement); + }); + } + + // نمایش نتایج و مخفی کردن حالت خالی + resultsContainer.classList.remove('d-none'); + emptyState.classList.add('d-none'); + } + + getPlatformIcon(platform) { + const icons = { + 'instagram': 'fab fa-instagram', + 'telegram': 'fab fa-telegram', + 'website': 'fas fa-globe', + 'eitaa': 'fas fa-comments', + 'rubika': 'fas fa-comment-dots' + }; + return icons[platform] || 'fas fa-share-alt'; + } + + getPlatformName(platform) { + const names = { + 'instagram': 'اینستاگرام', + 'telegram': 'تلگرام', + 'website': 'وب‌سایت', + 'eitaa': 'ایتا', + 'rubika': 'روبیکا' + }; + return names[platform] || platform; + } + + async scheduleContent() { + const form = document.getElementById('scheduleForm'); + const formData = new FormData(form); + + const platform = formData.get('platform'); + const content = formData.get('content'); + const date = formData.get('date'); + const time = formData.get('time'); + const autoPublish = document.getElementById('autoPublish').checked; + + if (!platform || !content || !date || !time) { + this.showToast('لطفاً تمام فیلدها را پر کنید', 'error'); + return; + } + + const scheduleTime = new Date(`${date}T${time}`); + if (scheduleTime <= new Date()) { + this.showToast('زمان انتشار باید در آینده باشد', 'error'); + return; + } + + try { + const response = await fetch(`${this.apiBase}/schedule-post`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + platform, + content, + schedule_time: scheduleTime.toISOString(), + auto_publish: autoPublish + }) + }); + + if (!response.ok) { + throw new Error(`خطای سرور: ${response.status}`); + } + + const result = await response.json(); + this.showToast('محتوا با موفقیت زمان‌بندی شد!', 'success'); + form.reset(); + this.loadScheduledPosts(); + + } catch (error) { + console.error('خطا در زمان‌بندی:', error); + this.showToast(`خطا در زمان‌بندی: ${error.message}`, 'error'); + } + } + + async loadScheduledPosts() { + try { + const response = await fetch(`${this.apiBase}/scheduled-posts`); + if (!response.ok) return; + + const posts = await response.json(); + this.displayScheduledPosts(posts); + + } catch (error) { + console.error('خطا در بارگذاری پست‌های زمان‌بندی شده:', error); + } + } + + displayScheduledPosts(posts) { + const container = document.getElementById('scheduledList'); + container.innerHTML = ''; + + if (posts.length === 0) { + container.innerHTML = ` +
+ +

هیچ محتوای زمان‌بندی شده‌ای وجود ندارد

+
+ `; + return; + } + + posts.forEach(post => { + const postElement = document.createElement('div'); + postElement.className = 'scheduled-item'; + + const scheduleDate = new Date(post.schedule_time); + const isOverdue = scheduleDate < new Date() && post.status === 'scheduled'; + + postElement.innerHTML = ` +
+
+
+ + ${this.getPlatformName(post.platform)} + ${this.getStatusText(post.status)} + ${isOverdue ? 'عقب‌افتاده' : ''} +
+ + + ${this.formatDate(scheduleDate)} + +
+
+ ${post.status === 'scheduled' ? ` + + + ` : ''} +
+
+
+ ${post.content.substring(0, 100)}${post.content.length > 100 ? '...' : ''} +
+ `; + + container.appendChild(postElement); + }); + } + + getStatusText(status) { + const texts = { + 'scheduled': 'زمان‌بندی شده', + 'published': 'منتشر شده', + 'failed': 'ناموفق', + 'cancelled': 'لغو شده' + }; + return texts[status] || status; + } + + formatDate(date) { + return new Intl.DateTimeFormat('fa-IR', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }).format(date); + } + + async cancelScheduledPost(id) { + if (!confirm('آیا از لغو این پست اطمینان دارید؟')) return; + + try { + const response = await fetch(`${this.apiBase}/scheduled-posts/${id}`, { + method: 'DELETE' + }); + + if (!response.ok) { + throw new Error(`خطای سرور: ${response.status}`); + } + + this.showToast('پست با موفقیت لغو شد', 'success'); + this.loadScheduledPosts(); + + } catch (error) { + console.error('خطا در لغو پست:', error); + this.showToast(`خطا در لغو پست: ${error.message}`, 'error'); + } + } + + async loadStats() { + try { + const response = await fetch(`${this.apiBase}/stats`); + if (!response.ok) return; + + const stats = await response.json(); + this.displayStats(stats); + + } catch (error) { + console.error('خطا در بارگذاری آمار:', error); + } + } + + displayStats(stats) { + document.getElementById('totalGenerated').textContent = stats.total_generated_contents || 0; + document.getElementById('totalScheduled').textContent = stats.scheduled_posts_count || 0; + document.getElementById('totalPublished').textContent = '0'; // از API دریافت شود + document.getElementById('totalViews').textContent = '0'; // از API دریافت شود + } + + setupCharts() { + // نمودار عملکرد + const performanceCtx = document.getElementById('performanceChart').getContext('2d'); + this.charts.performance = new Chart(performanceCtx, { + type: 'line', + data: { + labels: ['شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنج‌شنبه', 'جمعه'], + datasets: [{ + label: 'محتوای تولید شده', + data: [12, 19, 8, 15, 22, 18, 25], + borderColor: '#6366f1', + backgroundColor: 'rgba(99, 102, 241, 0.1)', + tension: 0.4 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + labels: { + font: { + family: 'Vazir' + } + } + } + }, + scales: { + y: { + beginAtZero: true, + ticks: { + font: { + family: 'Vazir' + } + } + }, + x: { + ticks: { + font: { + family: 'Vazir' + } + } + } + } + } + }); + + // نمودار پلتفرم‌ها + const platformCtx = document.getElementById('platformChart').getContext('2d'); + this.charts.platform = new Chart(platformCtx, { + type: 'doughnut', + data: { + labels: ['اینستاگرام', 'تلگرام', 'وب‌سایت', 'ایتا', 'روبیکا'], + datasets: [{ + data: [30, 25, 20, 15, 10], + backgroundColor: [ + '#e1306c', + '#0088cc', + '#10b981', + '#06b6d4', + '#f59e0b' + ] + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'bottom', + labels: { + font: { + family: 'Vazir' + } + } + } + } + } + }); + } + + async refreshStats() { + await this.loadStats(); + + // بروزرسانی نمودارها با داده‌های جدید + // این بخش را بر اساس API واقعی تکمیل کنید + } + + copyContent(platform) { + const contentElement = document.getElementById(`content-${platform}`); + this.copyText(contentElement.textContent); + } + + copyAllContent() { + if (!this.currentContent) return; + + let allContent = ''; + Object.entries(this.currentContent.contents).forEach(([platform, content]) => { + allContent += `=== ${this.getPlatformName(platform)} ===\n`; + allContent += (content.content || content) + '\n\n'; + }); + + this.copyText(allContent); + } + + copyText(text) { + navigator.clipboard.writeText(text).then(() => { + this.showToast('متن کپی شد!', 'success'); + }).catch(err => { + console.error('خطا در کپی:', err); + this.showToast('خطا در کپی متن', 'error'); + }); + } + + saveContent() { + if (!this.currentContent) return; + + const data = JSON.stringify(this.currentContent, null, 2); + const blob = new Blob([data], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = `content-${Date.now()}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + this.showToast('محتوا ذخیره شد!', 'success'); + } + + async optimizeSEO() { + if (!this.currentContent) { + this.showToast('ابتدا محتوایی تولید کنید', 'error'); + return; + } + + // نمایش مودال SEO + const modal = new bootstrap.Modal(document.getElementById('seoModal')); + modal.show(); + + // شبیه‌سازی بهینه‌سازی SEO + document.getElementById('seoResults').innerHTML = ` +
+
+ در حال بهینه‌سازی... +
+

در حال تحلیل و بهینه‌سازی محتوا...

+
+ `; + + // شبیه‌سازی تاخیر + setTimeout(() => { + document.getElementById('seoResults').innerHTML = ` +
+
+
85
+

امتیاز SEO

+
+
+
توصیه‌های بهبود:
+ +
+
+ `; + }, 2000); + } + + async generateVisualIdeas() { + if (!this.currentContent) { + this.showToast('ابتدا محتوایی تولید کنید', 'error'); + return; + } + + // نمایش مودال ایده‌های بصری + const modal = new bootstrap.Modal(document.getElementById('visualModal')); + modal.show(); + + document.getElementById('visualResults').innerHTML = ` +
+
+ در حال تولید ایده‌ها... +
+

در حال تولید ایده‌های بصری خلاقانه...

+
+ `; + + // شبیه‌سازی تولید ایده‌ها + setTimeout(() => { + document.getElementById('visualResults').innerHTML = ` +
+
+
+
ایده تصویری ۱
+

طراحی مینیمال با رنگ‌های آبی و سفید، محوریت روی متن اصلی

+
+
+
+
+
+
+
+
+
+
ایده ویدئویی
+

انیمیشن کوتاه با موزیک آرام، نمایش مراحل به صورت گام‌به‌گام

+ مدت پیشنهادی: ۳۰ ثانیه +
+
+
+
+
+
پالت رنگی پیشنهادی
+
+
+
+
+
+
+
+
+
+ `; + }, 2000); + } + + showLoading() { + document.getElementById('loadingSpinner').classList.remove('d-none'); + document.getElementById('results').classList.add('d-none'); + document.getElementById('emptyState').classList.add('d-none'); + document.getElementById('generateBtn').disabled = true; + } + + hideLoading() { + document.getElementById('loadingSpinner').classList.add('d-none'); + document.getElementById('generateBtn').disabled = false; + } + + showToast(message, type = 'info') { + // ایجاد toast برای نمایش پیام‌ها + const toastContainer = document.getElementById('toastContainer') || this.createToastContainer(); + + const toastElement = document.createElement('div'); + toastElement.className = `toast align-items-center text-bg-${type === 'error' ? 'danger' : type} border-0`; + toastElement.setAttribute('role', 'alert'); + toastElement.innerHTML = ` +
+
+ + ${message} +
+ +
+ `; + + toastContainer.appendChild(toastElement); + + const toast = new bootstrap.Toast(toastElement); + toast.show(); + + // حذف toast پس از نمایش + toastElement.addEventListener('hidden.bs.toast', () => { + toastElement.remove(); + }); + } + + createToastContainer() { + const container = document.createElement('div'); + container.id = 'toastContainer'; + container.className = 'toast-container position-fixed top-0 end-0 p-3'; + container.style.zIndex = '9999'; + document.body.appendChild(container); + return container; + } +} + +// راه‌اندازی اپلیکیشن +const app = new ContentGeneratorApp(); + +// توابع کمکی برای دسترسی از HTML +window.app = app; +window.copyAllContent = () => app.copyAllContent(); +window.saveContent = () => app.saveContent(); +window.optimizeSEO = () => app.optimizeSEO(); +window.generateVisualIdeas = () => app.generateVisualIdeas(); \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..7d8561b --- /dev/null +++ b/templates/index.html @@ -0,0 +1,412 @@ + + + + + + سیستم تولید محتوای هوش مصنوعی چندپلتفرمی + + + + + + + + + +
+
+ +
+
+ +
+
+
+
+ تنظیمات تولید محتوا +
+
+
+
+ +
+ + +
+ + +
+ + +
کلمات را با کاما جدا کنید
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ محتوای تولید شده +
+
+
+
+
+ در حال تولید... +
+

محتوا در حال تولید است...

+
+ +
+ +
+ + +
+
هشتگ‌های پیشنهادی
+
+
+ + +
+
پیشنهادات فراخوان عمل
+
+
+ + +
+ + + + +
+
+ +
+ +
آماده تولید محتوای شگفت‌انگیز!
+

فرم را پر کنید و دکمه تولید محتوا را بزنید

+
+
+
+
+
+
+ + +
+
+
+
+
+
+ زمان‌بندی جدید +
+
+
+
+
+ + +
+ +
+ + +
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+ + +
+
+ + +
+
+
+
+ +
+
+
+
+ لیست زمان‌بندی شده +
+
+
+
+ +
+
+
+
+
+
+ + +
+
+
+
+
+ +

0

+

محتوای تولید شده

+
+
+
+
+
+
+ +

0

+

زمان‌بندی شده

+
+
+
+
+
+
+ +

0

+

منتشر شده

+
+
+
+
+
+
+ +

0

+

بازدید کل

+
+
+
+
+ +
+
+
+
+
نمودار عملکرد
+
+
+ +
+
+
+
+
+
+
توزیع پلتفرم‌ها
+
+
+ +
+
+
+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file