mjalmoudy94 a2284db4f4 اعمال motion بر صفحهٔ اکانت‌های agy
صفحهٔ `/admin/agy-accounts` بعد از پاس motion ساخته شد، بنابراین ریشهٔ
خودش `.anim-stagger` نداشت و کل صفحه یک‌جا ظاهر می‌شد در حالی که بقیهٔ
پنل آبشاری وارد می‌شوند. فهرست اکانت‌ها هم که async لود می‌شود stagger
نگرفته بود.

هر صفحهٔ جدیدی که بعد از این ساخته شود باید همین دو کلاس را بگیرد؛
`motion.test.tsx` واژگان را محافظت می‌کند اما اعمال آن روی صفحه‌های
تازه را نمی‌گیرد.
2026-10-13 08:35:56 +03:30

Smart Organization — پلتفرم نیروی کار هوش مصنوعی

نسخهٔ وب مستقل و چندسازمانی محصول. این پروژه کاملاً جدا از ربات تلگرام است و هیچ فایلی از ریشهٔ مخزن را در runtime خود import نمی‌کند.


۱. محصول چیست

یک وب اپلیکیشن چندسازمانی برای «نیروی کار هوش مصنوعی سازمانی».

کاربر نهایی این‌ها را می‌بیند: سازمان، مدیر هوش مصنوعی، کارمندهای هوش مصنوعی، پروژه، وظیفه، فایل/خروجی، مصرف و پرداخت.

کاربر نهایی این‌ها را نمی‌بیند: نام مدل، provider، base_url، API key، قیمت خام provider. این‌ها فقط در Admin Console و فقط برای مالک سامانه قابل مدیریت‌اند.

پلتفرم سه لایهٔ نقش دارد:

  1. مالک سامانه (platform owner) — providerها، مدل‌ها، routing policy، تعرفه‌ها، sandbox templateها، مشاهدهٔ کل مصرف.
  2. مدیر سازمان مشتری — ساخت سازمان، دعوت عضو، پروژه، فعال‌سازی کارمند، مصرف.
  3. عضو سازمان — گفتگو با مدیر/کارمند، ساخت task، آپلود فایل، دیدن خروجی.

۲. پیش‌نیازها

ابزار نسخه
Node.js >= 20
pnpm 9.x
Python 3.13
Docker + Compose برای postgres/redis

فعال‌سازی pnpm در صورت نبود:

corepack enable pnpm
corepack prepare pnpm@9.12.0 --activate

۳. راه‌اندازی سریع

# ۱) نصب وابستگی‌های JS
pnpm install

# ۲) ساخت venv پایتون برای backend
pnpm api:venv

# ۳) کپی فایل env و پر کردن مقادیر (پیش‌فرض‌ها همه mock هستند)
cp .env.example .env

# ۴) بالا آوردن postgres و redis
docker compose -f infra/docker/docker-compose.yml up -d postgres redis

# ۵) اجرای migration و seed
pnpm db:migrate
pnpm db:seed

# ۶) اجرای هم‌زمان frontend و backend
pnpm dev

آدرس‌ها پس از اجرا:

حساب‌های نمونه (فقط محیط توسعه)

pnpm db:seed دو حساب می‌سازد و هر دو عضو سازمان نمونه هستند، پس بعد از ورود بلافاصله یک داشبورد پر می‌بینید:

نقش ایمیل رمز
کاربر نمونه demo@example.com demo12345
مالک سامانه (admin console) owner@example.com owner12345

این‌ها رمزهای توسعه هستند و در app/seed.py سخت‌کد شده‌اند. هیچ‌وقت در production از این‌ها استفاده نکنید و JWT_SECRET / ENCRYPTION_KEY واقعی بگذارید (نگهبان config در حالت production مقادیر placeholder را رد می‌کند).

نکته: دامنهٔ ایمیل عمداً example.com است. اگر .local یا .test بگذارید، email-validator آن را رد می‌کند و حساب ساخته می‌شود ولی ورود به آن ناممکن است. tests/test_seed.py همین را تضمین می‌کند.

اجرای کل استک با Docker

docker compose -f infra/docker/docker-compose.yml up --build

۴. اسکریپت‌ها

دستور کار
pnpm dev اجرای هم‌زمان Next.js و FastAPI
pnpm build build فرانت‌اند
pnpm lint ESLint (web) + ruff (api)
pnpm typecheck tsc (web) + mypy (api)
pnpm test vitest (web) + pytest (api)
pnpm format Prettier (web) + ruff format (api)
pnpm db:migrate alembic upgrade head
pnpm db:seed داده‌های نمونه (platform owner، کاربر نمونه، سازمان، templateها)
pnpm api:venv ساخت venv پایتون
pnpm worker:billing یک پاس worker صورتحساب
pnpm worker:ai-runtime یک پاس worker اجرای taskها
pnpm worker:sandbox-gateway یک پاس پاک‌سازی sessionهای بی‌استفاده

۵. معماری

۵.۱ سبک معماری

  • Clean Architecture + Hexagonal / Ports & Adapters
  • DDD برای مرزبندی bounded contextها
  • Event-Driven برای billing، audit و observability (الگوی Outbox)
  • Modular Monolith در فاز اول — microservice زودهنگام ممنوع

قاعدهٔ سخت: لایهٔ domain هیچ dependency به FastAPI، SQLAlchemy، Redis، HTTP یا provider خارجی ندارد. فقط application و infrastructure به آن وابسته‌اند، نه برعکس.

۵.۲ Bounded Contextها

identity · billing · ai_control · workforce · sandbox · knowledge · audit · projects · platform

۵.۳ Ports الزامی

ModelProviderPort · PaymentProviderPort · SandboxProviderPort · GitProviderPort · DomainRouterPort · StoragePort · VectorStorePort · NotificationPort · AuditPort · UsageLedgerPort

هر port حداقل یک adapter mock دارد تا کل استک بدون هیچ credential واقعی اجرا شود.

۵.۴ جریان درخواست

Browser → Web (Next.js/BFF) → API (FastAPI)
                                  ├── Domain Modules
                                  ├── Event Bus / Outbox → Workers
                                  ├── AI Runtime        → Model Providers
                                  ├── Sandbox Gateway   → E2B / Daytona
                                  └── Billing Processor → Stripe

۶. ساختار پوشه

web/
  PLAN.md                 پلن محصول و معماری
  TODO.md                 چک‌لیست اجرایی
  README.md               همین فایل
  package.json            اسکریپت‌های ریشه
  pnpm-workspace.yaml     تعریف workspace
  .env.example            نمونهٔ env (هیچ secret واقعی ندارد)
  apps/
    web/                  Next.js + TypeScript (UI)
    api/                  FastAPI + SQLAlchemy (backend)
  services/
    ai-runtime/           worker اجرای Agent
    sandbox-gateway/      wrapper ایزوله‌سازی اجرا
    billing-worker/       پردازش usage و invoice
  packages/
    contracts/            تایپ‌ها و قراردادهای مشترک
    ui/                   componentهای پایه (design system)
  infra/
    docker/               docker-compose
    migrations/           محل migrationها
  docs/
    architecture/         مستندات معماری
    product/              مستندات محصول
    current-infra-notes.md

۷. کجا کد بنویسیم

می‌خواهم... محل
صفحه یا component UI بسازم apps/web/src/app یا apps/web/src/features
component پایه/design system packages/ui
endpoint جدید بسازم apps/api/app/interfaces/http
use case بنویسم apps/api/app/application
منطق دامنه بنویسم apps/api/app/domain/<context>
adapter یا اتصال به سرویس بیرونی apps/api/app/infrastructure
worker پس‌زمینه بنویسم apps/api/app/workers (پوستهٔ اجرا در services/<name>)
خطا ثبت/پاک‌سازی کنم apps/api/app/core/redact.py + apps/api/app/infrastructure/error_log.py
قرارداد مشترک front/back packages/contracts

ممنوع: نوشتن هر کد جدید در فایل‌های ریشهٔ مخزن (bot.py, web_panel.py, bot_menus.py, bot_messages.py) و هر وابستگی به telegram, chat_id, callback_data, InlineKeyboardMarkup.


۸. سرویس‌های خارجی و حالت mock

همهٔ سرویس‌های بیرونی پیش‌فرض روی mock هستند. برای فعال‌سازی واقعی، این envها را پر کنید:

قابلیت env مقادیر
مدل AI_DEFAULT_PROVIDER mock | openai_compatible | agy
Git GIT_PROVIDER mock | gitea
دامنهٔ تست DOMAIN_ROUTER mock | caddy
Sandbox SANDBOX_PROVIDER mock | daytona | e2b
پرداخت PAYMENT_PROVIDER mock | stripe

اگر credential واقعی نداریم، قابلیت حذف نمی‌شود: مدل دیتابیس، interface، UI و adapter mock کامل ساخته می‌شود و adapter واقعی پشت همین flag آماده می‌ماند.

ارائه‌دهندهٔ agy (Google Antigravity)

agy کلید API ندارد؛ اکانت‌ها هویت Google هستند و از طریق OAuth وارد می‌شوند. برای فعال‌سازی:

  1. AI_DEFAULT_PROVIDER=agy را در .env بگذارید و pnpm db:seed را دوباره اجرا کنید (کتالوگ مدل‌های agy فعال و کاتالوگ mock غیرفعال می‌شود).
  2. با حساب مالک سامانه وارد کنسول مدیریت شوید و از /admin/accounts → «افزودن اکانت agy» شروع کنید: لینک احراز هویت گوگل باز می‌شود، بعد از تأیید، URL بازگشت را از نوار آدرس مرورگر کپی و Paste کنید (صفحهٔ مقصد عمداً بارگذاری نمی‌شود؛ پارامترها همان‌جا هستند).
  3. هر تعداد اکانت Google که بخواهید اضافه کنید. مدل‌ها را در /admin/models در صورت نیاز فعال کنید.

پول چرخشی، بار را بین اکانت‌ها پخش می‌کند و اکانتی که ۴۲۹ گرفته تا پایان پنجرهٔ سهمیهٔ آن کنار گذاشته می‌شود (هم در حافظه و هم در دیتابیس، تا workerها هم همان تصویر را ببینند). refresh tokenها فقط encrypted at rest ذخیره می‌شوند و هیچ‌وقت از API برنمی‌گردند.


۹. Workerهای پس‌زمینه

سه worker وجود دارد. همه در apps/api/app/workers/ پیاده شده‌اند و services/<name> پوستهٔ اجرای آن‌هاست:

worker کار دستور
billing-worker جمع‌بندی usage ledger به فاکتور draft (هر دوره یک‌بار، idempotent) pnpm worker:billing
ai-runtime برداشتن taskهای در صف و اجرای آن‌ها از مسیر run engine pnpm worker:ai-runtime
sandbox-gateway متوقف‌کردن sessionهای بی‌استفاده و ثبت audit pnpm worker:sandbox-gateway

هر دستور یک پاس اجرا می‌کند و خارج می‌شود (شکل مناسب cron). برای اجرای مستمر --loop بدهید:

cd apps/api && .venv/bin/python -m app.workers billing --loop --interval 3600

اصل مهم: workerها هیچ منطق تجاری ندارند. هر سه از app/application/container.py همان سرویس‌هایی را می‌سازند که HTTP API می‌سازد، پس worker و API هرگز نمی‌توانند دربارهٔ اینکه کدام adapter پشت یک port است اختلاف داشته باشند. جزئیات هر کدام در services/*/README.md.


۱۰. اصول تغییرناپذیر

  1. اجرای کد کاربر یا agent روی host اصلی ممنوع — فقط از مسیر Sandbox Gateway.
  2. هر endpoint چندسازمانی باید organization_id را enforce کند.
  3. هیچ secret، token یا API key در frontend یا لاگ چاپ نمی‌شود.
  4. هر عملیات destructive باید confirmation و audit log داشته باشد.
  5. مدل و provider از دید کاربر عادی پنهان است.
  6. هر مصرفی در usage ledger داخلی ثبت می‌شود (Stripe فقط adapter است).

۱۱. کنسول خطا

هر خطایی که سامانه بگیرد — در درخواست HTTP، در worker پس‌زمینه، یا در زمان راه‌اندازی — با جزئیات کامل در جدول error_events ثبت می‌شود و در /admin/errors (فقط مالک سامانه) قابل مشاهده است.

چه چیزی ثبت می‌شود

منبع چه زمانی شدت
استثنای مدیریت‌نشده و خطاهای ۵xx همیشه critical / error
ValueError بدون نوع مشخص همیشه warning
سایر پاسخ‌های ۴xx فقط با ERROR_LOG_CLIENT_ERRORS=1 warning

۴xx به‌صورت پیش‌فرض ثبت نمی‌شود چون یک API عمومی دائماً ۴۰۴ از اسکنرها می‌گیرد و ثبت همهٔ آن‌ها خطاهای واقعی را زیر خود دفن می‌کند. خطاهای ۵xx همیشه ثبت می‌شوند، مستقل از این تنظیم.

هر رکورد چه چیزی دارد

  • kind، error_type، message و stack (ردِّ کامل خطا)
  • method، path، status_code و request_id
  • organization_id و user_id (پس از احراز هویت)
  • fingerprint — هش پایدار از هویت خطا، برای گروه‌بندی
  • metadata_json — جزئیات اضافی (مثلاً task_id)

گروه‌بندی و رسیدگی

خطاهای یکسان بر اساس fingerprint در یک ردیف جمع می‌شوند، پس یک باگ که ۴۰۰۰ بار رخ داده یک خط با شمارنده است، نه ۴۰۰۰ خط. fingerprint عمداً سازمان و کاربر را در نظر نمی‌گیرد: یک باگ که دو مستأجر را می‌زند هنوز یک باگ است. بستن یک گروه همهٔ رخدادهای باز آن را می‌بندد و در audit log ثبت می‌شود.

پاک‌سازی خودکار secretها

هیچ خطایی بدون عبور از app/core/redact.py ذخیره نمی‌شود. سه لایه: کلیدهایی که معنایشان secret است (password، api_key، …)، الگوهای شناختنی (JWT، Bearer، scheme://user:pass@host، sk-…، ghp_…)، و خودِ مقادیر secret تنظیم‌شده در settings. یک لاگ خطا که رمز عبور ذخیره کند از نداشتن لاگ خطا بدتر است.

استفاده از request_id

هر پاسخ X-Request-ID دارد و هر بدنهٔ خطا هم همان شناسه را در error.request_id برمی‌گرداند. کاربر می‌تواند فقط همین یک رشته را گزارش کند تا دقیقاً یک رکورد پیدا شود.

# ۵۰۰ بگیر، سپس با همان شناسه رکورد را پیدا کن
curl -i http://localhost:8000/api/whatever -H "X-Request-ID: my-trace"
curl "http://localhost:8000/api/admin/errors?search=my-trace" -H "Authorization: Bearer $OWNER_TOKEN"

اجرای دستی workerها و ثبت خطا

خطای یک pass کامل با source=worker ثبت می‌شود و exit code غیرصفر می‌دهد تا cron متوجه شود. خطای یک task مشخص نیز با task_id و سازمان مربوطه ثبت می‌شود.


۱۲. مستندات

S
Description
Smart Org v2
Readme
1.5 MiB
Languages
Python 73.5%
TypeScript 26.3%
CSS 0.1%