الأطر والمنصات
Minaya عبارة عن وسم سكريبت واحد، لذا فهو يعمل على أي موقع دون الحاجة إلى تكامل مع إطار عمل معيّن. فيما يلي التفاصيل الخاصة بكل منصة — أين يوضع المقتطف، وما الذي يجب الانتباه إليه.
أي نهج ينطبق عليك
| المنصة | النهج |
|---|---|
| HTML / أي موقع | يحمّل widget.js |
| Next.js | يحمّل widget.js |
| React (Vite / CRA) | يحمّل widget.js |
| Angular | يحمّل widget.js |
| WordPress | يحمّل widget.js |
| Shopify | يحمّل widget.js |
| React Native | WebView |
| 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 النسخة الثانية، لذا يجب أن ترى زر إطلاق واحدًا فقط؛ وإذا رأيت اثنين، فأحدهما منتج محادثة مختلف.