الأطر والمنصات

Minaya عبارة عن وسم سكريبت واحد، لذا فهو يعمل على أي موقع دون الحاجة إلى تكامل مع إطار عمل معيّن. فيما يلي التفاصيل الخاصة بكل منصة — أين يوضع المقتطف، وما الذي يجب الانتباه إليه.

أي نهج ينطبق عليك

المنصةالنهج
HTML / أي موقعيحمّل widget.js
Next.jsيحمّل widget.js
React (Vite / CRA)يحمّل widget.js
Angularيحمّل widget.js
WordPressيحمّل widget.js
Shopifyيحمّل widget.js
React NativeWebView
Android (Kotlin)WebView أو REST API
iOS (Swift)WebView أو REST API

كل منصات المتصفح تحمّل نفس ملف widget.js. الجوال الأصلي (native) لا يملك DOM، لذا تستضيف تلك المنصات الودجت إما داخل WebView أو تستدعي REST API العامة وتعرض المحادثة في واجهتها الخاصة.

HTML / أي موقع

وسم سكريبت واحد قبل وسم إغلاق body.

الصق المقتطف

أضف هذا مباشرة قبل </body> في كل صفحة يجب أن تعرض الودجت.

<script
  src="https://widget.minaya.ai/widget.js"
  data-site-key="your-site-key"
  data-api-url="https://api.minaya.ai"
  async
></script>

Next.js

استخدم next/script حتى يُحمَّل الودجت بعد أن تصبح الصفحة تفاعلية.

أضفه إلى التخطيط الجذري (root layout)

App Router — app/layout.tsx. استراتيجية lazyOnload تُبقي الودجت خارج المسار الحرج.

import Script from "next/script";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://widget.minaya.ai/widget.js"
          data-site-key="your-site-key"
          data-api-url="https://api.minaya.ai"
          strategy="lazyOnload"
        />
      </body>
    </html>
  );
}

بديل Pages Router

إذا كنت تستخدم pages/_app.tsx، اعرض نفس مكوّن <Script> هناك بدلًا من ذلك.

ملاحظة. لا تضع السكريبت في next/head — يزيل Next وسوم السكريبت منه. استخدم next/script.

React (Vite / CRA)

أضف الوسم إلى index.html، أو أدرجه من خطّاف (hook).

الأبسط — index.html

الصق المقتطف قبل </body> في ملف index.html الخاص بك. لا حاجة لشيء آخر.

<script
  src="https://widget.minaya.ai/widget.js"
  data-site-key="your-site-key"
  data-api-url="https://api.minaya.ai"
  async
></script>

أو ثبّته من مكوّن

مفيد عندما تريد الودجت في مسارات معيّنة فقط. عملية التنظيف تزيل الوسم عند إلغاء التثبيت.

import { useEffect } from "react";

function MinayaWidget() {
  useEffect(() => {
    const script = document.createElement("script");
    script.src = "https://widget.minaya.ai/widget.js";
    script.dataset.siteKey = "your-site-key";
    script.dataset.apiUrl = "https://api.minaya.ai";
    script.async = true;
    document.body.appendChild(script);

    return () => {
      script.remove();
    };
  }, []);

  return null;
}

Angular

أضف الوسم إلى index.html، أو أدرجه من مكوّن.

الأبسط — src/index.html

الصق المقتطف قبل </body>.

<script
  src="https://widget.minaya.ai/widget.js"
  data-site-key="your-site-key"
  data-api-url="https://api.minaya.ai"
  async
></script>

أو حمّله من مكوّن

Renderer2 يبقي التعامل مع DOM متوافقًا مع العرض من جانب الخادم.

import { Component, OnInit, Renderer2, Inject } from "@angular/core";
import { DOCUMENT } from "@angular/common";

@Component({ selector: "app-minaya", template: "" })
export class MinayaComponent implements OnInit {
  constructor(
    private renderer: Renderer2,
    @Inject(DOCUMENT) private document: Document,
  ) {}

  ngOnInit(): void {
    const script = this.renderer.createElement("script");
    script.src = "https://widget.minaya.ai/widget.js";
    script.setAttribute("data-site-key", "your-site-key");
    script.setAttribute("data-api-url", "https://api.minaya.ai");
    script.async = true;
    this.renderer.appendChild(this.document.body, script);
  }
}

WordPress

الصقه في تذييل القالب، أو استخدم إضافة سكريبتات الرأس/التذييل.

الخيار أ — إضافة (موصى به)

ثبّت WPCode أو Insert Headers and Footers، افتح إعداداتها، والصق المقتطف في مربع Footer. يصمد هذا أمام تحديثات القالب.

<script
  src="https://widget.minaya.ai/widget.js"
  data-site-key="your-site-key"
  data-api-url="https://api.minaya.ai"
  async
></script>

الخيار ب — تعديل القالب

Appearance → Theme File Editor → footer.php. الصق مباشرة قبل </body>. استخدم قالبًا فرعيًا (child theme)، وإلا فسيمحوه أي تحديث.

تحقق

افتح موقعك في نافذة خاصة. يظهر زر الإطلاق أسفل اليمين خلال ثانية أو ثانيتين.

ملاحظة. يعمل مع Elementor وDivi ومنشئي الصفحات الآخرين — فهو جافاسكريبت عادي، وليس تكاملًا مع إضافة معيّنة.

Shopify

أضفه إلى theme.liquid قبل وسم إغلاق body.

افتح محرر القالب

Online Store → Themes → ... → Edit code.

عدّل theme.liquid

ضمن Layout، افتح theme.liquid والصق المقتطف مباشرة قبل </body>. احفظ.

<script
  src="https://widget.minaya.ai/widget.js"
  data-site-key="your-site-key"
  data-api-url="https://api.minaya.ai"
  async
></script>

اسمح بنطاق متجرك

في Minaya، أضف كلًا من نطاق myshopify.com الخاص بك ونطاقك المخصص إلى Allowed origins.

ملاحظة. يحظر Shopify سكريبتات الطرف الثالث في صفحات الدفع (checkout) ما لم تكن على خطة Shopify Plus، لذا لن يظهر الودجت أثناء الدفع.

React Native

لا يوجد DOM في React Native، لذا استضف الودجت داخل WebView.

ثبّت react-native-webview

npm install react-native-webview، ثم شغّل pod install لنظام iOS.

npm install react-native-webview

اعرض الودجت داخل WebView

يفتح الودجت تلقائيًا حتى لا يضطر الزائر للبحث عن زر إطلاق داخل WebView.

import { WebView } from "react-native-webview";

const html = `<!doctype html>
<html>
  <head><meta name="viewport" content="width=device-width, initial-scale=1" /></head>
  <body>
    <script
      src="https://widget.minaya.ai/widget.js"
      data-site-key="your-site-key"
      data-api-url="https://api.minaya.ai"
    ></script>
  </body>
</html>`;

function SupportChat() {
  return (
    <WebView
      originWhitelist={["*"]}
      source={{ html, baseUrl: "https://api.minaya.ai" }}
    />
  );
}

فعّل Open automatically

في Widget appearance، فعّل Open automatically حتى تكون اللوحة مفتوحة بالفعل داخل WebView.

ملاحظة. يجب ضبط baseUrl، وإلا سيكون أصل الطلب (origin) قيمة null وسيرفض فحص مفتاح الموقع الطلب. أضف ذلك الأصل إلى Allowed origins.

Android (Kotlin)

إما استضف الودجت داخل WebView، أو استدعِ REST API واستخدم واجهتك الخاصة.

الخيار أ — WebView

المسار الأسرع. يجب تفعيل JavaScript، وإلا فلن يعمل الودجت.

val webView = findViewById<WebView>(R.id.webView)
webView.settings.javaScriptEnabled = true

val html = """
  <!doctype html>
  <html>
    <head><meta name="viewport" content="width=device-width, initial-scale=1" /></head>
    <body>
      <script src="https://widget.minaya.ai/widget.js"
              data-site-key="your-site-key"
              data-api-url="https://api.minaya.ai"></script>
    </body>
  </html>
""".trimIndent()

webView.loadDataWithBaseURL("https://api.minaya.ai", html, "text/html", "UTF-8", null)

الخيار ب — واجهة أصلية فوق REST API

ابنِ المحادثة في Compose أو Views واستدعِ الـ API مباشرة. احتفظ بـ sessionId المُعاد وأرسله مجددًا لمتابعة المحادثة.

data class ChatRequest(val message: String, val sessionId: String? = null)
data class ChatResponse(val sessionId: String, val reply: String)



// Retrofit setup
val api = Retrofit.Builder()
    .baseUrl("https://api.minaya.ai/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    .create(MinayaApi::class.java)

val response = api.chat("your-site-key", ChatRequest("What are your hours?"))

ملاحظة. مفتاح موقعك مضمَّن داخل حزمة التطبيق، لذا عامله كعنصر عام (public). Allowed origins لا تحمي حركة المرور الأصلية (native) — اعتمد على حدود الرسائل الخاصة بكل خطة.

iOS (Swift)

إما استضف الودجت داخل WKWebView، أو استدعِ REST API واستخدم واجهتك الخاصة.

الخيار أ — WKWebView

يحمّل نفس الودجت الذي يستخدمه موقعك.

import WebKit

let webView = WKWebView(frame: view.bounds)
view.addSubview(webView)

let html = """
<!doctype html>
<html>
  <head><meta name="viewport" content="width=device-width, initial-scale=1" /></head>
  <body>
    <script src="https://widget.minaya.ai/widget.js"
            data-site-key="your-site-key"
            data-api-url="https://api.minaya.ai"></script>
  </body>
</html>
"""

webView.loadHTMLString(html, baseURL: URL(string: "https://api.minaya.ai"))

الخيار ب — واجهة أصلية فوق REST API

ابنِ المحادثة في SwiftUI واستدعِ الـ API مباشرة. خزّن sessionId لمتابعة المحادثة عبر الرسائل.

struct ChatResponse: Decodable {
    let sessionId: String
    let reply: String
}

func sendMessage(_ text: String, sessionId: String?) async throws -> ChatResponse {
    var request = URLRequest(url: URL(string: "https://api.minaya.ai/public/chat")!)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue("your-site-key", forHTTPHeaderField: "X-Site-Key")

    var body: [String: Any] = ["message": text]
    if let sessionId { body["sessionId"] = sessionId }
    request.httpBody = try JSONSerialization.data(withJSONObject: body)

    let (data, _) = try await URLSession.shared.data(for: request)
    return try JSONDecoder().decode(ChatResponse.self, from: data)
}

ملاحظة. مفتاح موقعك مضمَّن داخل حزمة التطبيق، لذا عامله كعنصر عام (public). Allowed origins لا تحمي حركة المرور الأصلية (native) — اعتمد على حدود الرسائل الخاصة بكل خطة.

استكشاف الأخطاء وإصلاحها

الودجت لا يظهر

افتح وحدة تحكم المتصفح (console). غياب data-site-key أو data-api-url يسجّل خطأ صريحًا. إذا كانت وحدة التحكم نظيفة، تحقق من أن وسم السكريبت داخل <body> وغير محظور بواسطة سياسة أمان المحتوى (CSP).

خطأ 403 في كل طلب

الأصل (origin) الذي يرسل الطلب غير مدرج في القائمة المسموح بها للودجت. أضف الأصل بدقّة — متضمّنًا المخطط (scheme) — ضمن Allowed origins. WebView بدون baseUrl يرسل أصلًا فارغًا (null)، وهذا لا يتطابق أبدًا.

ودجتان على الصفحة

السكريبت مُضمَّن مرتين. تتجاهل Minaya النسخة الثانية، لذا يجب أن ترى زر إطلاق واحدًا فقط؛ وإذا رأيت اثنين، فأحدهما منتج محادثة مختلف.