نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی
مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت. تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
# AGY Web — پلتفرم نیروی کار هوش مصنوعی
|
||||
|
||||
نسخهٔ وب مستقل و چندسازمانی محصول. این پروژه **کاملاً جدا از ربات تلگرام** است و
|
||||
هیچ فایلی از ریشهٔ مخزن را در runtime خود import نمیکند.
|
||||
|
||||
- پلن محصول و معماری: [`PLAN.md`](./PLAN.md)
|
||||
- چکلیست اجرایی کامل: [`TODO.md`](./TODO.md)
|
||||
- یادداشت زیرساخت فعلی (فقط مرجع): [`docs/current-infra-notes.md`](./docs/current-infra-notes.md)
|
||||
|
||||
---
|
||||
|
||||
## ۱. محصول چیست
|
||||
|
||||
یک وب اپلیکیشن چندسازمانی برای «نیروی کار هوش مصنوعی سازمانی».
|
||||
|
||||
**کاربر نهایی اینها را میبیند:** سازمان، مدیر هوش مصنوعی، کارمندهای هوش مصنوعی،
|
||||
پروژه، وظیفه، فایل/خروجی، مصرف و پرداخت.
|
||||
|
||||
**کاربر نهایی اینها را نمیبیند:** نام مدل، 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 در صورت نبود:
|
||||
|
||||
```bash
|
||||
corepack enable pnpm
|
||||
corepack prepare pnpm@9.12.0 --activate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۳. راهاندازی سریع
|
||||
|
||||
```bash
|
||||
# ۱) نصب وابستگیهای 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
|
||||
```
|
||||
|
||||
آدرسها پس از اجرا:
|
||||
|
||||
- Frontend: <http://localhost:3000>
|
||||
- API: <http://localhost:8000>
|
||||
- API docs (Swagger): <http://localhost:8000/docs>
|
||||
- Health: <http://localhost:8000/health>
|
||||
|
||||
### حسابهای نمونه (فقط محیط توسعه)
|
||||
|
||||
`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
|
||||
|
||||
```bash
|
||||
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 واقعی اجرا شود.
|
||||
|
||||
### ۵.۴ جریان درخواست
|
||||
|
||||
```text
|
||||
Browser → Web (Next.js/BFF) → API (FastAPI)
|
||||
├── Domain Modules
|
||||
├── Event Bus / Outbox → Workers
|
||||
├── AI Runtime → Model Providers
|
||||
├── Sandbox Gateway → E2B / Daytona
|
||||
└── Billing Processor → Stripe
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۶. ساختار پوشه
|
||||
|
||||
```text
|
||||
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` |
|
||||
| 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 آماده میماند.
|
||||
|
||||
---
|
||||
|
||||
## ۹. 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` بدهید:
|
||||
|
||||
```bash
|
||||
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` برمیگرداند. کاربر میتواند فقط همین یک رشته را گزارش کند
|
||||
تا دقیقاً یک رکورد پیدا شود.
|
||||
|
||||
```bash
|
||||
# ۵۰۰ بگیر، سپس با همان شناسه رکورد را پیدا کن
|
||||
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` و سازمان مربوطه ثبت
|
||||
میشود.
|
||||
|
||||
---
|
||||
|
||||
## ۱۲. مستندات
|
||||
|
||||
- [`docs/architecture/overview.md`](./docs/architecture/overview.md) — نمای کلی معماری
|
||||
- [`docs/security-checklist.md`](./docs/security-checklist.md) — چکلیست امنیتی
|
||||
- [`docs/current-infra-notes.md`](./docs/current-infra-notes.md) — زیرساخت فعلی (مرجع)
|
||||
Reference in New Issue
Block a user