Files
SnapOtter/apps/docs/ar/guide/architecture.md
T
SnapOtterandGitHub d10d0f544f fix: release QA hardening across processing, media, security, and CI gates (#649)
A release-readiness QA pass over the whole product. The commits split into
defects a user would hit and gates that were reporting green while measuring
nothing.

## Fixes that change behaviour

Rate limiting was bypassable on every install: TRUST_PROXY defaulted to true, so
request.ip came from a client-set header and a forged X-Forwarded-For got past
the login limiter. The default is now a private-network trust list.

A transient Postgres outage stranded in-flight jobs, leaving finished output on
disk with no row pointing at it. A reconciler now resolves those rows and adopts
the bytes rather than dropping the work.

A Redis connection that moved to a new address wedged every read-blocked
consumer, so completions stopped signalling while health still answered 200.
Socket timeouts plus subscriber pings recover it.

Installing more than one AI bundle left the shared venv multi-versioned and
silently broke three tools. The installer now reconciles distributions to one
version each.

Converting an image to JXL at quality 1 through 4 returned a 500, because
libjxl 0.7 rejects the distance those values compute. The quality is floored at
what the encoder honours. A missing ffmpeg was also reported to the user as a
corrupt upload; it now says the engine is unavailable.

RAW uploads reached an unpatched LibRaw on arm64, so it is built from source at
0.22.2, and the release scan was split so it can fail on an unfixed critical
instead of hiding it behind ignore-unfixed.

## Gates that could not fail

Two mutation lanes ran zero mutants because Stryker crawled the gitignored docs
build; coverage discarded its whole report on any failing test; the lint gate
skipped root tests, scripts, and two workspaces; and several generated matrices
counted a host missing ffmpeg as a passing tool. Each now measures what it
claims.

Full evidence and the outstanding release items are tracked locally and are not
part of this branch.
2026-07-27 15:37:30 +08:00

13 KiB

description, i18n_source_hash, i18n_provenance, i18n_output_hash, i18n_hash_version
description i18n_source_hash i18n_provenance i18n_output_hash i18n_hash_version
بنية المستودع الأحادي، وبنية التطبيقات والحزم، ودورة حياة الطلب، وبصمة الموارد الخاصة بـ SnapOtter. 50e076925c4b human dbd52e665939 2

البنية

SnapOtter مستودع أحادي مُدار بمساحات عمل pnpm وTurborepo. يُنشَر كحزمة Docker Compose من 3 حاويات: صورة تطبيق SnapOtter، وPostgreSQL 17، وRedis 8.

بنية المشروع

snapotter/
├── apps/
│   ├── api/          # Fastify backend
│   ├── web/          # React + Vite frontend
│   └── docs/         # This VitePress site
├── packages/
│   ├── image-engine/ # Sharp-based image operations
│   ├── media-engine/ # FFmpeg spawn + progress parsing
│   ├── doc-engine/   # qpdf, LibreOffice, ghostscript wrappers
│   ├── ai/           # Python AI model bridge
│   └── shared/       # Types, constants, i18n
└── docker/           # Dockerfile and Compose config

الحزم

@snapotter/image-engine

مكتبة معالجة الصور الأساسية المبنية على Sharp. تتولى جميع العمليات غير المعتمدة على الذكاء الاصطناعي: تغيير الحجم، والقصّ، والتدوير، والقلب، والتحويل، والضغط، وإزالة البيانات الوصفية، وتعديلات الألوان (السطوع، والتباين، والتشبّع، والتدرّج الرمادي، والسيبيا، والعكس، وقنوات الألوان).

ليس لهذه الحزمة أي اعتماديات شبكية وتعمل بالكامل داخل العملية نفسها.

@snapotter/ai

طبقة جسر تستدعي أوقات التشغيل الأصلية و Python ML. تستخدم معظم أدوات Python dispatcher المستمر الذي يقوم باستيراد المكتبات الثقيلة مسبقًا (PIL، وNumPy، وMediaPipe، وrembg) بحيث تتخطى الاستدعاءات اللاحقة عبء الاستيراد. OCR معزول عن تلك البيئة المشتركة القابلة للتغيير: يستدعي fast Tesseract الأصلي، بينما يستخدم balanced وbest JSONL dispatcher المستمر المخصص والمثبت على الجيل النشط غير القابل للتغيير RapidOCR/ONNX. يحمل كل طلب generation lease. يقوم التنشيط أولاً بتشغيل smoke test على أحد المرشحين، ثم يتحول ذريًا إلى dispatcher الخاص به. يستنزف dispatcher السابق قبل أن يتم تجميع البيانات المهملة.

النماذج لا تُحمَّل مسبقًا. يحمّل كل نص أداة أوزان نموذجه من القرص عند وقت الطلب ويتخلّص منها عند انتهاء الطلب. راجع بصمة الموارد للاطلاع على ملف الذاكرة الكامل.

العمليات المدعومة: إزالة الخلفية (rembg/BiRefNet)، والترقية (RealESRGAN)، وطمس الوجه (MediaPipe)، وتحسين الوجه (GFPGAN/CodeFormer)، ومحو الكائنات (LaMa ONNX)، و OCR (Tesseract و RapidOCR مع نماذج PP-OCR ONNX)، والتلوين (DDColor)، والضوضاء الإزالة، وإزالة العين الحمراء، واستعادة الصور، وإنشاء صور جواز السفر، وتثبيت الشفافية (BiRefNet HR-matting)، وتغيير الحجم مع مراعاة المحتوى (Go caire ثنائي).

البرامج النصية Python موجودة في packages/ai/python/. يتم تثبيت حزم النماذج الاختيارية الكبيرة عند الطلب في وحدة تخزين /data/ai المستمرة. يستخدم OCR الدقيق عناصر موقعة خاصة بالمنصة؛ لا تتطلب طبقة Tesseract المدمجة تنزيل حزمة النموذج.

@snapotter/shared

أنواع TypeScript المشتركة، والثوابت (مثل APP_VERSION وتعريفات الأدوات)، وسلاسل ترجمة i18n المستخدمة من الواجهتين الأمامية والخلفية معًا.

التطبيقات

API (apps/api)

خادم Fastify v5 يكشف 243 مسار أداة عبر خمس وسائط (الصور، والفيديو، والصوت، وPDF، والملف) ويتولى:

  • رفع الملفات، وإدارة مساحة العمل المؤقتة، وتخزين الملفات الدائم
  • مكتبة ملفات المستخدم (جدول user_files): يُخزَّن التعديل المحفوظ افتراضيًا كملف جديد مستقل، أو كإصدار مرتبط بالأب عند الكتابة فوق الأصل. تسجّل الأدوات المطبَّقة (toolChain) وتحصل على مصغّر مُولَّد تلقائيًا لصفحة الملفات
  • تنفيذ الأدوات (يوجّه كل طلب أداة إلى محرك الصور أو جسر الذكاء الاصطناعي)
  • تنسيق خطوط الأنابيب (ربط أدوات متعددة بالتتابع)
  • المعالجة الدفعية مع التحكم في التزامن عبر طوابير مهام BullMQ (المجمّعات: image، media، ai، docs، system)
  • مصادقة المستخدم، وRBAC (أدوار المسؤول/المستخدم مع مجموعة أذونات كاملة)، وإدارة مفاتيح API، وتحديد المعدل
  • إدارة الفرق - إنشاء/قراءة/تحديث/حذف للمسؤول فقط؛ يُسنَد المستخدمون إلى فريق عبر حقل team في ملفهم الشخصي
  • إعدادات وقت التشغيل - مخزن مفتاح-قيمة في جدول settings يتحكم في disabledTools وenableExperimentalTools وloginAttemptLimit ومقابض تشغيلية أخرى دون إعادة نشر
  • علامة تجارية مخصّصة وتفضيلات وقت تشغيل عبر إعدادات مدعومة بقاعدة البيانات
  • توثيق Scalar/OpenAPI في /api/docs
  • تقديم الواجهة الأمامية المبنية كتطبيق صفحة واحدة في الإنتاج

الاعتماديات الأساسية: Fastify، وDrizzle ORM (pg-core، node-postgres)، وSharp، وBullMQ، وioredis، وZod للتحقق.

يتولى الخادم الإيقاف الأنيق عند SIGTERM/SIGINT: يصرّف اتصالات HTTP، ويوقف عمّال BullMQ، ويوقف موزِّع Python، ويغلق اتصال قاعدة البيانات.

Web (apps/web)

تطبيق React 19 من صفحة واحدة مبني بـ Vite. يستخدم Zustand لإدارة الحالة، وTailwind CSS v4 للتنسيق، وLucide للأيقونات. يتواصل مع API عبر REST وSSE (لتتبّع التقدّم).

تتضمن الصفحات مساحة عمل للأدوات، وصفحة ملفات لإدارة عمليات الرفع والنتائج الدائمة، وأداة بناء أتمتة/خطوط أنابيب، ولوحة إعدادات المسؤول.

تُقدَّم الواجهة الأمامية المبنية بواسطة الواجهة الخلفية Fastify في الإنتاج، لذا لا يوجد خادم ويب منفصل في حاوية Docker.

Docs (apps/docs)

هذا موقع VitePress. يُنشَر إلى Cloudflare Pages تلقائيًا عند الدفع إلى main.

كيف يتدفّق الطلب

  1. يختار المستخدم أداة في واجهة الويب ويرفع ملفًا.
  2. ترسل الواجهة الأمامية طلب POST متعدد الأجزاء إلى /api/v1/tools/:section/:toolId مع الملف والإعدادات.
  3. يتحقق مسار API من المُدخَل بواسطة Zod، ثم يوزّع المعالجة.
  4. بالنسبة للأدوات القياسية، تُدرَج المهمة في مجمّع BullMQ المناسب (image أو media أو docs بحسب الوسيط). يوجّه عامل BullMQ داخل العملية الصورة تلقائيًا بناءً على البيانات الوصفية EXIF، ويشغّل دالة معالجة الأداة، ويعيد النتيجة.
  5. بالنسبة لمعظم أدوات الذكاء الاصطناعي، يرسل جسر TypeScript طلبًا إلى Python dispatcher المستمر. بدلاً من ذلك، يستدعي OCR السريع Tesseract، ويبدأ OCR الدقيق الملف القابل للتنفيذ المثبت من جيل OCR النشط غير القابل للتغيير. يتم تثبيت طبقة OCR المطلوبة عند الدخول ولا يتم تغييرها أبدًا بصمت أثناء التنفيذ.
  6. يُحفَظ تقدّم المهمة في جدول jobs في PostgreSQL بحيث تبقى الحالة عبر إعادة تشغيل الحاوية. تُسلَّم التحديثات في الوقت الفعلي عبر SSE في /api/v1/jobs/:jobId/progress.
  7. يعيد API jobId وdownloadUrl. ينزّل المستخدم الملف المعالَج من /api/v1/download/:jobId/:filename.

بالنسبة لخطوط الأنابيب، يغذّي API مُخرَج كل خطوة كمُدخَل للخطوة التالية، ويشغّلها بالتتابع.

بالنسبة للمعالجة الدفعية، يستخدم API تدفّقات BullMQ مع مهام فرعية لكل خطوة ويعيد ملف ZIP يضمّ جميع الملفات المعالَجة.

بصمة الموارد

صُمِّم SnapOtter لاستهلاك ذاكرة منخفض عند الخمول. لا شيء يُحمَّل مسبقًا أو يُبقى دافئًا عند البدء.

عند الخمول

تعمل عملية Node.js/Fastify، وPostgreSQL، وRedis. ذاكرة الخمول المعتادة نحو 200-300 ميغابايت عبر الحاويات الثلاث جميعها (عملية Node.js، وPostgres، وRedis). لا عملية Python، ولا أوزان نماذج في الذاكرة.

ما الذي يبدأ، ومتى

المكوّن يبدأ عند الذاكرة أثناء النشاط
خادم Fastify + Postgres + Redis بدء الحاوية نحو 200-300 ميغابايت إجمالًا
عمّال BullMQ بدء الحاوية (داخل العملية) عامل واحد لكل مجمّع (image، media، ai، docs، system)
موزِّع Python أول طلب أداة ذكاء اصطناعي مفسّر Python + المكتبات المستوردة مسبقًا (PIL، NumPy، MediaPipe، rembg) - دون أوزان نماذج
أوزان نماذج الذكاء الاصطناعي أثناء طلب الأداة المحدّدة تُحمَّل من القرص، وتُحرَّر عند انتهاء الطلب

تحميل النماذج

كل ملفات أوزان النماذج (بإجمالي عدة غيغابايت) تقيم على القرص في /opt/models/ طوال الوقت. يحمّل كل نص أداة ذكاء اصطناعي نموذجه (نماذجه) وحدها إلى الذاكرة طوال مدة الطلب، ثم يحرّرها. تستدعي بعض النصوص صراحةً del model وtorch.cuda.empty_cache() بعد الاستدلال لضمان إعادة الذاكرة فورًا.

لا توجد ذاكرة تخزين مؤقت للنماذج بين الطلبات. تشغيل أداة الذكاء الاصطناعي نفسها بالتتابع يعيد تحميل النموذج في كل مرة. يبقي هذا ذاكرة الخمول قرب الصفر مقابل تأخير تحميل النموذج في كل طلب ذكاء اصطناعي.

بدء بارد لأول طلب ذكاء اصطناعي

موزِّع Python لا يعمل عند بدء الحاوية. يطلق أول طلب ذكاء اصطناعي أمرين بالتوازي: يبدأ الموزِّع بالإحماء في الخلفية، ويتراجع الطلب نفسه إلى إطلاق عملية Python فرعية لمرة واحدة. وحالما يشير الموزِّع إلى جاهزيته، تستخدمه كل طلبات الذكاء الاصطناعي اللاحقة مباشرة وتتخطّى كلفة إطلاق العملية الفرعية.