ওয়েবসাইটে নিজের এআই সহকারী বসান: Enel Retail দিয়ে হাতে কলমে সম্পূর্ণ ইন্টিগ্রেশন

ধরুন আপনার একটি ওয়েবসাইট আছে। আপনি চান সেখানে এমন একটি এআই সহকারী বসাতে, যেখানে কাস্টমার সরাসরি কথা বলতে পারবে। কিন্তু সহকারীটি শুধু সাধারণ প্রশ্নের উত্তর দেবে না। এটি কথোপকথন মনে রাখবে, বাস্তব অর্ডারের তথ্য দেখবে, অনুমোদিত ব্যবসার নিয়ম পড়বে, প্রয়োজনীয় তথ্য না থাকলে পরের প্রশ্ন করবে এবং দরকার হলে লেসন ৮-এর টিকিট ব্যবস্থায় অনুরোধটি নথিভুক্ত করবে।

এই লেসনের মূল ধারণা: লেসন ৮-এ আমরা ব্যাকএন্ড অটোমেশন বানিয়েছি। লেসন ৯-এ সেই ব্যাকএন্ডের সামনে একটি ওয়েবসাইট এবং কথোপকথনভিত্তিক এআই ইন্টারফেস বসাচ্ছি।

এনেল রিটেইল লেসন ৯ ডেমো ওয়েবসাইট
এনেল রিটেইল: এআই ইন্টিগ্রেটর বাংলা ল্যাবের লেসন ৯ ডেমো পরিবেশ

শুরু করার আগে: লেসন ৮ শেষ করুন

এই গাইডটি লেসন ৮-এর ওপর তৈরি। তাই লেসন ৮-এর তিনটি ভিডিও এবং সেখানে তৈরি করা গুগল শিট ও Code.gs আগে সম্পন্ন করে নিন। এখানে আমরা সেই অংশগুলো আবার বানাব না।

  • Orders শিট: অর্ডারের বাস্তব তথ্য
  • Policy শিট: ডেলিভারি, রিটার্ন, ডিসকাউন্ট ও মানব এসকেলেশনের অনুমোদিত নিয়ম
  • Tickets শিট: কাস্টমার অনুরোধ, সিদ্ধান্ত, পর্যালোচনা ও টিকিট অবস্থা
  • Code.gs: জেমিনি সংযোগ, অর্ডার যাচাই, ব্যবসার নিয়ম, টিকিট রাউটিং ও এসকেলেশনসহ লেসন ৮-এর মূল অটোমেশন

লেসন ৮-এর সম্পূর্ণ কোড ও ব্যাখ্যা: এই গাইডটি খুলুন

গুরুত্বপূর্ণ: লেসন ৮-এর Google Form মুছে ফেলতে হবে না। ওয়েবসাইটের Enel Assist হচ্ছে একই ব্যাকএন্ডে আসার আরেকটি নতুন চ্যানেল।

আজকের পুরো স্থাপত্য

কাস্টমারের একটি বার্তা কীভাবে ওয়েবসাইট থেকে লেসন ৮-এর অটোমেশনে পৌঁছায়, সেটা নিচের চিত্রে দেখা যাচ্ছে।

এনেল অ্যাসিস্ট লেসন ৯ আর্কিটেকচার
ওয়েবসাইট থেকে কথোপকথন, অর্ডার, পলিসি এবং টিকিট পর্যন্ত লেসন ৯-এর প্রবাহ

কাস্টমার → ওয়েবসাইট → Enel Assist → script.js → WebApp.gs → WebConversation.gs → Orders + Policy → WebTicket.gs → Lesson 8 Automation → Tickets / Escalation

ডেমো ওয়েবসাইট ও সোর্স কোড

ডেমোর জন্য আমি Enel Retail নামে একটি কাল্পনিক ব্যবসার ওয়েবসাইট গিটহাব পেজেসে রেখেছি। আপনার নিজের ওয়েবসাইট না থাকলে পুরো রিপোজিটরি কপি করে অনুশীলন করতে পারেন। আর নিজের ওয়েবসাইট থাকলে পুরো সাইট কপি করার দরকার নেই; শুধু Enel Assist-এর প্রয়োজনীয় অংশগুলো নিজের সাইটে বসাবেন।

লাইভ Enel Retail ডেমো
গিটহাব রিপোজিটরি

ওয়েবসাইটের কোন ফাইলে কী যোগ করেছি

ফ্রন্টএন্ডে তিনটি অংশ গুরুত্বপূর্ণ: index.html, style.css এবং script.js। নিচে প্রতিটি অংশের কাজ এবং tested code দেওয়া হলো।

১. index.html: চ্যাট সহকারীর কাঠামো

এইচটিএমএল অংশটি চারটি দৃশ্যমান জিনিস তৈরি করে: Enel Assist launcher button, chat panel, conversation area এবং customer input box। নিজের সাধারণ HTML সাইটে এটি সাধারণত closing </body> tag-এর আগে বসানো যায়।

  • assistantLauncher: নিচের Enel Assist বাটন
  • assistantPanel: পুরো chat window
  • chatBody: customer এবং assistant message দেখানোর জায়গা
  • chatFormchatInput: message লেখা ও পাঠানোর অংশ
কপি করার জন্য সম্পূর্ণ Enel Assist HTML অংশ
<button class="assistant-launcher" id="assistantLauncher" type="button">
    <span class="launcher-icon">✦</span>
    <span>
        <strong>Enel Assist</strong>
        <small>আপনার এআই সহকারী</small>
    </span>
</button>

<aside class="assistant-panel" id="assistantPanel" aria-hidden="true">

    <div class="assistant-header">
        <div class="assistant-title">
            <span class="assistant-avatar">✦</span>

            <div>
                <strong>Enel Assist</strong>
                <small>আপনার এআই সহকারী</small>
            </div>
        </div>

        <div class="assistant-actions">
            <button id="minimizeAssistant" type="button">−</button>
            <button id="closeAssistant" type="button">×</button>
        </div>
    </div>

    <div class="chat-body" id="chatBody">

        <div class="message bot-message">
            <span class="message-avatar">✦</span>

            <div class="bubble">
                <strong>আসসালামু আলাইকুম!</strong>
                <p>
                    আমি Enel Assist। আপনার অর্ডার, ডেলিভারি,
                    রিটার্ন বা পণ্য সম্পর্কে জানতে সাহায্য করতে পারি।
                </p>
                <p>কীভাবে সাহায্য করতে পারি?</p>
            </div>
        </div>

    </div>

    <form class="chat-input" id="chatForm">
        <input
            id="chatInput"
            type="text"
            placeholder="এখানে আপনার প্রশ্ন লিখুন..."
            autocomplete="off"
        />

        <button type="submit">➜</button>
    </form>

</aside>

<script src="script.js?v=5"></script>

২. style.css: চ্যাটবটের নকশা

CSS অংশটি launcher-এর অবস্থান, chat window-এর আকার, header, message bubble, customer message এবং input area-এর নকশা নিয়ন্ত্রণ করে। আপনার সাইটের রঙের সঙ্গে মিলিয়ে এই অংশে রঙ, size এবং position পরিবর্তন করতে পারবেন।

কপি করার জন্য সম্পূর্ণ Enel Assist CSS অংশ
.assistant-launcher {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 178px;
  border: 0;
  border-radius: 999px;
  padding: 10px 16px 10px 10px;
  color: white;
  background: linear-gradient(135deg, #0754a7, #1677e8);
  box-shadow: 0 18px 38px rgba(0, 78, 170, .28);
  cursor: pointer;
  text-align: left;
}

.launcher-icon,
.assistant-avatar {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: #0754a7;
  background: white;
  font-weight: 800;
}

.launcher-icon {
  width: 38px;
  height: 38px;
}

.assistant-launcher strong,
.assistant-launcher small {
  display: block;
}

.assistant-launcher small {
  margin-top: 1px;
  color: rgba(255,255,255,.78);
  font-size: 10px;
}

.assistant-panel {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 70;
  width: min(390px, calc(100vw - 28px));
  height: min(610px, calc(100vh - 48px));
  display: flex;
  flex-direction: column;
  border: 1px solid #b8d0e7;
  border-radius: 18px;
  overflow: hidden;
  background: white;
  box-shadow: 0 24px 70px rgba(16,42,67,.25);
  opacity: 0;
  visibility: hidden;
  transform: translateY(18px) scale(.98);
  transform-origin: bottom right;
  transition: opacity .22s ease, transform .22s ease, visibility .22s ease;
}

.assistant-panel.open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
}

.assistant-header {
  min-height: 68px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 11px 14px;
  color: white;
  background: linear-gradient(135deg, #0754a7, #1677e8);
}

.assistant-title {
  display: flex;
  align-items: center;
  gap: 10px;
}

.assistant-avatar {
  width: 42px;
  height: 42px;
}

.assistant-title strong,
.assistant-title small {
  display: block;
}

.assistant-title small {
  color: rgba(255,255,255,.76);
  font-size: 10px;
}

.assistant-actions {
  display: flex;
  gap: 5px;
}

.assistant-actions button {
  width: 31px;
  height: 31px;
  border: 0;
  border-radius: 8px;
  color: white;
  background: transparent;
  cursor: pointer;
  font-size: 20px;
}

.assistant-actions button:hover {
  background: rgba(255,255,255,.12);
}

.chat-body {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  background: #fbfcfe;
}

.message {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 14px;
}

.message-avatar {
  width: 30px;
  height: 30px;
  flex: 0 0 30px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: white;
  background: var(--blue, #1677e8);
  font-size: 12px;
}

.bubble {
  max-width: 82%;
  padding: 12px 13px;
  border-radius: 13px;
  background: #f0f4f8;
  color: #2d4053;
  font-size: 14px;
}

.bubble p {
  margin: 6px 0 0;
}

.user-message {
  justify-content: flex-end;
}

.user-message .bubble {
  background: #e6f3ea;
  color: #244d34;
}

.chat-input {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 11px;
  border-top: 1px solid #d8e3ed;
  background: white;
}

.chat-input input {
  flex: 1;
  min-width: 0;
  height: 44px;
  padding: 0 13px;
  border: 1px solid #d4e0eb;
  border-radius: 12px;
  outline: 0;
}

.chat-input input:focus {
  border-color: #69a8e8;
  box-shadow: 0 0 0 3px rgba(19,104,206,.08);
}

.chat-input button {
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: 50%;
  color: white;
  background: var(--blue, #1677e8);
  cursor: pointer;
  font-size: 18px;
}

৩. script.js: আচরণ, memory এবং backend connection

script.js হচ্ছে browser-এর মূল control layer। এটি chat খোলা ও বন্ধ করা, customer ও assistant message দেখানো, সাম্প্রতিক conversation মনে রাখা, অপেক্ষার সময় typing dots দেখানো এবং Apps Script backend-এর সঙ্গে যোগাযোগ করার কাজ করে।

নিজের project-এ সবচেয়ে গুরুত্বপূর্ণ পরিবর্তন: BACKEND_URL অবশ্যই আপনার নিজের Apps Script Web App URL দিয়ে বদলাতে হবে।

Backend-এ শুধু সর্বশেষ message নয়, conversation history-ও পাঠানো হচ্ছে। এ কারণেই customer প্রথমে “আমার অর্ডার আসেনি” বলার পর পরের message-এ শুধু “RF1042” লিখলেও system বুঝতে পারে order numberটি কোন প্রসঙ্গে দেওয়া হয়েছে।

কপি করার জন্য সম্পূর্ণ script.js
const BACKEND_URL =
    'https://script.google.com/macros/s/AKfycbxguhTn7cnkDsZHF0J9v0JvyEvFZ4IPMeVdkG25--hd9vxkEFMBgWTFK0KWaw1XZXWj/exec';


const launcher = document.getElementById('assistantLauncher');
const panel = document.getElementById('assistantPanel');
const closeButton = document.getElementById('closeAssistant');
const minimizeButton = document.getElementById('minimizeAssistant');

const chatForm = document.getElementById('chatForm');
const chatInput = document.getElementById('chatInput');
const chatBody = document.getElementById('chatBody');


// -------------------------------------------------------------
// Conversation memory
// -------------------------------------------------------------

const conversationHistory = [];


// -------------------------------------------------------------
// Open / close assistant
// -------------------------------------------------------------

function openAssistant() {

    panel.classList.add('open');

    panel.setAttribute(
        'aria-hidden',
        'false'
    );

    launcher.style.display = 'none';

    chatInput.focus();
}


function closeAssistant() {

    panel.classList.remove('open');

    panel.setAttribute(
        'aria-hidden',
        'true'
    );

    launcher.style.display = 'flex';
}


// -------------------------------------------------------------
// Customer message
// -------------------------------------------------------------

function addUserMessage(text) {

    const message =
        document.createElement('div');

    message.className =
        'message user-message';

    message.innerHTML = `
        <div class="bubble">
            ${escapeHtml(text)}
        </div>
    `;

    chatBody.appendChild(message);

    chatBody.scrollTop =
        chatBody.scrollHeight;
}


// -------------------------------------------------------------
// Assistant message
// -------------------------------------------------------------

function addBotMessage(text) {

    const message =
        document.createElement('div');

    message.className =
        'message bot-message';

    message.innerHTML = `
        <span class="message-avatar">✦</span>

        <div class="bubble">
            ${escapeHtml(text)}
        </div>
    `;

    chatBody.appendChild(message);

    chatBody.scrollTop =
        chatBody.scrollHeight;
}


// -------------------------------------------------------------
// Typing indicator
// -------------------------------------------------------------

function showTypingIndicator() {

    const message =
        document.createElement('div');

    message.className =
        'message bot-message';

    message.innerHTML = `
        <span class="message-avatar">✦</span>

        <div class="bubble"
             id="typingIndicator">
            •
        </div>
    `;

    chatBody.appendChild(message);

    chatBody.scrollTop =
        chatBody.scrollHeight;


    const bubble =
        message.querySelector(
            '#typingIndicator'
        );


    let step = 1;


    const timer =
        window.setInterval(
            function() {

                step++;

                if (step > 3) {
                    step = 1;
                }


                if (step === 1) {
                    bubble.textContent = '•';
                }

                if (step === 2) {
                    bubble.textContent = '•  •';
                }

                if (step === 3) {
                    bubble.textContent =
                        '•  •  •';
                }

            },
            450
        );


    message.typingTimer = timer;


    return message;
}


function removeTypingIndicator(message) {

    if (!message) {
        return;
    }


    if (message.typingTimer) {

        window.clearInterval(
            message.typingTimer
        );
    }


    message.remove();
}


// -------------------------------------------------------------
// Safe text
// -------------------------------------------------------------

function escapeHtml(text) {

    const div =
        document.createElement('div');

    div.textContent = text;

    return div.innerHTML;
}


// -------------------------------------------------------------
// Keep conversation memory reasonably small
// -------------------------------------------------------------

function getConversationHistory() {

    return conversationHistory.slice(-20);
}


// -------------------------------------------------------------
// Send message + conversation history to Apps Script
// -------------------------------------------------------------

async function sendToBackend(customerMessage) {

    const response =
        await fetch(
            BACKEND_URL,
            {

                method: 'POST',

                redirect: 'follow',

                headers: {
                    'Content-Type':
                        'text/plain;charset=utf-8'
                },

                body: JSON.stringify({

                    message:
                        customerMessage,

                    history:
                        getConversationHistory()

                })
            }
        );


    if (!response.ok) {

        throw new Error(
            'Backend request failed'
        );
    }


    const data =
        await response.json();


    if (!data.ok) {

        throw new Error(
            data.error ||
            'Backend error'
        );
    }


    return data.reply;
}


// -------------------------------------------------------------
// Events
// -------------------------------------------------------------

launcher.addEventListener(
    'click',
    openAssistant
);


closeButton.addEventListener(
    'click',
    closeAssistant
);


minimizeButton.addEventListener(
    'click',
    closeAssistant
);


// -------------------------------------------------------------
// Customer sends a message
// -------------------------------------------------------------

chatForm.addEventListener(
    'submit',
    async function(event) {

        event.preventDefault();


        const customerMessage =
            chatInput.value.trim();


        if (!customerMessage) {
            return;
        }


        addUserMessage(
            customerMessage
        );


        conversationHistory.push({
            role: 'customer',
            text: customerMessage
        });


        chatInput.value = '';

        chatInput.disabled = true;


        const typingMessage =
            showTypingIndicator();


        try {

            const reply =
                await sendToBackend(
                    customerMessage
                );


            removeTypingIndicator(
                typingMessage
            );


            addBotMessage(
                reply
            );


            conversationHistory.push({
                role: 'assistant',
                text: reply
            });


        } catch (error) {

            console.error(error);


            removeTypingIndicator(
                typingMessage
            );


            const errorMessage =
                'দুঃখিত, এই মুহূর্তে আপনার বার্তাটি প্রসেস করা যাচ্ছে না।';


            addBotMessage(
                errorMessage
            );


        } finally {

            chatInput.disabled = false;

            chatInput.focus();
        }
    }
);

Apps Script-এ চারটি নতুন file

লেসন ৮-এর Code.gs রেখে আমরা চারটি নতুন file যোগ করেছি। প্রতিটি file-এর একটি নির্দিষ্ট দায়িত্ব আছে, যাতে পুরো system বুঝতে এবং পরে পরিবর্তন করতে সহজ হয়।

WebApp.gs: backend-এর main controller

ওয়েবসাইট থেকে request এখানে আসে। এটি conversation analysis চালায়, missing information থাকলে পরের প্রশ্ন পাঠায়, তথ্য complete হলে ticket bridge চালায়, এবং final reply আবার ওয়েবসাইটে ফেরত দেয়। একই conversation-এ আগে ticket তৈরি হয়ে থাকলে duplicate ticket হওয়া থেকেও এটি রক্ষা করে।

সম্পূর্ণ WebApp.gs
/**
 * Enel Assist Web Application
 *
 * Connects:
 *
 * Website
 * -> Conversation Manager
 * -> Real Order Data
 * -> Approved Policy
 * -> Ticket Bridge
 * -> Lesson 8 Business Rules
 * -> Customer Chat
 */


function doGet(e) {

  return enelJsonResponse_({
    ok: true,
    message: 'Enel Assist backend is running'
  });
}


function doPost(e) {

  try {

    const data =
      JSON.parse(
        e.postData.contents || '{}'
      );


    const customerMessage =
      String(
        data.message || ''
      ).trim();


    const history =
      Array.isArray(data.history)
        ? data.history
        : [];


    if (!customerMessage) {

      return enelJsonResponse_({
        ok: false,
        error:
          'No customer message received'
      });
    }


    const existingTicketId =
      findExistingEnelTicketId_(
        history
      );


    const state =
      analyzeEnelConversation_(
        history,
        customerMessage
      );


    if (
      state.ticketRequired &&
      !state.ticketReady
    ) {

      return enelJsonResponse_({

        ok: true,

        reply:
          state.nextQuestion,

        ticketCreated:
          false,

        ticketReady:
          false,

        missingField:
          state.missingField

      });
    }


    if (
      state.ticketRequired &&
      state.ticketReady &&
      !existingTicketId
    ) {

      const businessReply =
        buildEnelCompletedReply_(
          state
        );


      const ticket =
        createEnelTicket_({

          customerName:
            state.order
              ? state.order.customerName
              : '',

          orderNumber:
            state.orderNumber,

          product:
            state.product ||
            (
              state.order
                ? state.order.product
                : ''
            ),

          customerMessage:
            customerMessage,

          conversationText:
            state.conversationText,

          intent:
            state.intent,

          urgency:
            state.urgency,

          language:
            'bn',

          sentiment:
            state.sentiment,

          suggestedRoute:
            'A',

          decisionReason:
            'Enel Assist collected the required information from the website conversation.',

          reply:
            businessReply

        });


      const finalReply =
        buildEnelTicketConfirmation_(
          businessReply,
          ticket
        );


      return enelJsonResponse_({

        ok: true,

        reply:
          finalReply,

        ticketCreated:
          true,

        ticketId:
          ticket.ticketId,

        route:
          ticket.route,

        humanReview:
          ticket.humanReview,

        ticketStatus:
          ticket.ticketStatus

      });
    }


    if (
      state.ticketRequired &&
      state.ticketReady &&
      existingTicketId
    ) {

      const reply =
        buildEnelExistingTicketReply_(
          history,
          customerMessage,
          existingTicketId
        );


      return enelJsonResponse_({

        ok: true,

        reply:
          reply,

        ticketCreated:
          false,

        existingTicketId:
          existingTicketId

      });
    }


    const reply =
      buildEnelGeneralReply_(
        state,
        customerMessage
      );


    return enelJsonResponse_({

      ok: true,

      reply:
        reply,

      ticketCreated:
        false

    });


  } catch (error) {

    console.error(error);


    return enelJsonResponse_({

      ok: false,

      error:
        String(error)

    });
  }
}


function buildEnelCompletedReply_(state) {

  if (
    state.intent ===
    'order_status'
  ) {

    const order =
      state.order;


    if (!order) {

      return (
        'দুঃখিত, অর্ডারটির তথ্য যাচাই করা যাচ্ছে না।'
      );
    }


    const prompt = `
You are Enel Assist, the customer service AI assistant for Enel Retail.

The customer reported an order or delivery problem.

REAL ORDER INFORMATION:

Order ID: ${order.orderId}
Customer Name: ${order.customerName}
Product: ${order.product}
Order Date: ${order.orderDate}
Expected Delivery: ${order.expectedDelivery}
Current Status: ${order.currentStatus}
Last Update: ${order.lastUpdate}

Explain the current real order status to the customer.

Rules:

- Reply in natural Bangla.
- Be polite and concise.
- Use only the information supplied above.
- Never invent a delivery date.
- Never invent an order status.
- If delivery is delayed, clearly say it is delayed.
- If a new delivery date is not confirmed, say so clearly.
- Do not mention technical systems.
- Do not mention Google Sheets.
- Do not mention Gemini.
- Write Bangla words using Bangla characters.
- Order numbers may remain in English characters.

Return only the customer-facing reply.
`;


    const schema = {

      type: 'OBJECT',

      properties: {

        reply: {
          type: 'STRING'
        }

      },

      required: ['reply']
    };


    const result =
      callGeminiJson_(
        prompt,
        schema
      );


    return String(
      result.reply || ''
    ).trim();
  }


  if (
    state.intent ===
    'return'
  ) {

    const returnPolicy =
      getEnelReturnPolicy_();


    let reply =
      'আপনার পণ্য ফেরত দেওয়ার অনুরোধটি প্রয়োজনীয় তথ্যসহ নথিভুক্ত করা হয়েছে।';


    if (returnPolicy) {

      reply +=
        '\n\nরিটার্ন ও এক্সচেঞ্জের নিয়ম অনুযায়ী: ' +
        returnPolicy;
    }


    return reply;
  }


  return (
    'আপনার অনুরোধটি প্রয়োজনীয় তথ্যসহ নথিভুক্ত করা হয়েছে।'
  );
}


function buildEnelTicketConfirmation_(
  businessReply,
  ticket
) {

  let outcome = '';


  if (
    ticket.route ===
    'B_HOLD_FOR_REVIEW'
  ) {

    outcome =
      '\n\nঅনুরোধটি একজন দায়িত্বপ্রাপ্ত ব্যক্তির পর্যালোচনার জন্য রাখা হয়েছে।';
  }


  if (
    ticket.route ===
    'C_ESCALATE'
  ) {

    outcome =
      '\n\nবিষয়টি অগ্রাধিকার দিয়ে সংশ্লিষ্ট দলের কাছে পাঠানো হয়েছে।';
  }


  return (
    businessReply +
    outcome +
    '\n\nআপনার টিকিট নম্বর: ' +
    ticket.ticketId
  );
}


function buildEnelExistingTicketReply_(
  history,
  customerMessage,
  ticketId
) {

  const conversation =
    formatConversationHistory_(
      history
    );


  const prompt = `
You are Enel Assist.

A customer service ticket has already been created for this conversation.

Ticket ID:

${ticketId}

Conversation:

${conversation}

Latest customer message:

"${customerMessage}"

Reply naturally in Bangla.

Rules:

- Do not create or suggest creating another ticket for the same issue.
- If the customer says thank you, respond briefly and politely.
- You may remind them of the existing ticket number when useful.
- Do not invent an update to the ticket.
- Do not invent an order status.
- Do not invent company policy.
- If they clearly want to report a completely different issue, ask them to start a new conversation.
- Write Bangla words using Bangla characters.
- Ticket IDs may remain in English characters.

Return only the customer-facing reply.
`;


  const schema = {

    type: 'OBJECT',

    properties: {

      reply: {
        type: 'STRING'
      }

    },

    required: ['reply']
  };


  const result =
    callGeminiJson_(
      prompt,
      schema
    );


  return String(
    result.reply || ''
  ).trim();
}


function buildEnelGeneralReply_(
  state,
  customerMessage
) {

  let orderInformation =
    'No verified order information is available.';


  if (
    state.order
  ) {

    orderInformation = `
Verified Order ID: ${state.order.orderId}
Customer Name: ${state.order.customerName}
Product: ${state.order.product}
Order Date: ${state.order.orderDate}
Expected Delivery: ${state.order.expectedDelivery}
Current Status: ${state.order.currentStatus}
Last Update: ${state.order.lastUpdate}
`;
  }


  const prompt = `
You are Enel Assist, the customer service AI assistant for Enel Retail.

Conversation:

${state.conversationText}

Latest customer message:

"${customerMessage}"

Detected intent:

${state.intent}

Verified information if available:

${orderInformation}

Continue the conversation naturally.

Rules:

- Reply in natural Bangla.
- Be polite and concise.
- Ask only one question at a time.
- Do not ask for information already provided.
- Never invent product information.
- Never invent order information.
- Never invent refund information.
- Never invent company policy.
- If required information is unavailable, clearly say so.
- Write Bangla words using Bangla characters.
- Order numbers and product codes may remain in English characters.
- Do not mention Google Sheets.
- Do not mention Apps Script.
- Do not mention Gemini.
- Do not mention technical implementation.

Return only the customer-facing reply.
`;


  const schema = {

    type: 'OBJECT',

    properties: {

      reply: {
        type: 'STRING'
      }

    },

    required: ['reply']
  };


  const result =
    callGeminiJson_(
      prompt,
      schema
    );


  return String(
    result.reply || ''
  ).trim();
}


function findExistingEnelTicketId_(
  history
) {

  if (
    !Array.isArray(history)
  ) {

    return '';
  }


  for (
    let i =
      history.length - 1;

    i >= 0;

    i--
  ) {

    const text =
      String(
        history[i].text || ''
      );


    const match =
      text.match(
        /\bTKT-\d{8}-\d{6}-\d{3}\b/
      );


    if (match) {

      return match[0];
    }
  }


  return '';
}


function formatConversationHistory_(
  history
) {

  if (
    !history ||
    !history.length
  ) {

    return (
      'No previous conversation.'
    );
  }


  return history
    .slice(-20)
    .map(
      function(item) {

        const role =
          item.role === 'assistant'
            ? 'Enel Assist'
            : 'Customer';


        return (
          role +
          ': ' +
          String(
            item.text || ''
          ).trim()
        );
      }
    )
    .join('\n');
}


function enelJsonResponse_(
  data
) {

  return ContentService
    .createTextOutput(
      JSON.stringify(data)
    )
    .setMimeType(
      ContentService.MimeType.JSON
    );
}

WebConversation.gs: কথোপকথনের decision layer

এই file customer-এর intent, order number, return reason, urgency এবং sentiment extract করে। তারপর deterministic business logic দিয়ে ঠিক করে কী তথ্য missing এবং ticket তৈরি করার মতো তথ্য complete হয়েছে কি না।

নকশার মূল নীতি: AI কথোপকথন বুঝতে সাহায্য করে; কিন্তু workflow complete কি না, সেটা শুধু AI-এর ওপর ছেড়ে দেওয়া হয়নি। Apps Script-এর rule দিয়ে final completeness ঠিক করা হয়।

সম্পূর্ণ WebConversation.gs
/**
 * Enel Assist Conversation Manager
 *
 * Purpose:
 * 1. Understand the customer's issue
 * 2. Remember information already supplied
 * 3. Identify missing information
 * 4. Validate an order number when available
 * 5. Decide whether enough information exists to create a ticket
 *
 * Gemini understands the conversation.
 * Apps Script makes the final completeness decision.
 */


function analyzeEnelConversation_(
  history,
  latestMessage
) {

  const conversationText =
    buildEnelConversationText_(
      history,
      latestMessage
    );


  const prompt = `
You are analysing a customer service conversation for Enel Retail.

CONVERSATION:

${conversationText}

Extract what is already known from the conversation.

Use one of these intent values only:

order_status
return
product_info
other

Definitions:

order_status:
The customer is asking about delivery, tracking, delay,
missing delivery, or the status of an existing order.

return:
The customer wants to return, exchange or send back a product.

product_info:
The customer is asking about a product.

other:
Anything that does not fit the above categories.

Return the information already supplied by the customer.

Rules:

- Never invent information.
- If something is unknown, return an empty string.
- Extract an order number if one appears anywhere in the conversation.
- For a return request, extract the reason for return if the customer has supplied it.
- Summarise the customer's main issue briefly.
- Urgency must be one of: low, medium, high.
- Sentiment must be one of: positive, neutral, negative.
- Do not decide whether the ticket is complete.
`;


  const schema = {

    type: 'OBJECT',

    properties: {

      intent: {
        type: 'STRING'
      },

      issue_summary: {
        type: 'STRING'
      },

      order_number: {
        type: 'STRING'
      },

      product: {
        type: 'STRING'
      },

      return_reason: {
        type: 'STRING'
      },

      urgency: {
        type: 'STRING'
      },

      sentiment: {
        type: 'STRING'
      }

    },

    required: [
      'intent',
      'issue_summary',
      'order_number',
      'product',
      'return_reason',
      'urgency',
      'sentiment'
    ]
  };


  const ai =
    callGeminiJson_(
      prompt,
      schema
    );


  const intent =
    normalizeEnelIntent_(
      ai.intent
    );


  let orderNumber =
    cleanEnelValue_(
      ai.order_number
    );


  if (!orderNumber) {

    orderNumber =
      extractEnelOrderNumber_(
        conversationText
      );
  }


  const issueSummary =
    cleanEnelValue_(
      ai.issue_summary
    );


  const product =
    cleanEnelValue_(
      ai.product
    );


  const returnReason =
    cleanEnelValue_(
      ai.return_reason
    );


  const urgency =
    normalizeEnelUrgency_(
      ai.urgency
    );


  const sentiment =
    normalizeEnelSentiment_(
      ai.sentiment
    );


  let orderFound = false;

  let order = null;


  if (orderNumber) {

    order =
      findOrderById_(
        orderNumber
      );


    orderFound =
      Boolean(order);
  }


  const completion =
    getEnelCompletionState_({

      intent:
        intent,

      orderNumber:
        orderNumber,

      orderFound:
        orderFound,

      returnReason:
        returnReason,

      product:
        product,

      issueSummary:
        issueSummary

    });


  const nextQuestion =
    getEnelNextQuestion_(
      completion.missingField,
      orderNumber,
      orderFound
    );


  return {

    intent:
      intent,

    issueSummary:
      issueSummary,

    orderNumber:
      orderNumber,

    orderFound:
      orderFound,

    product:
      product,

    returnReason:
      returnReason,

    urgency:
      urgency,

    sentiment:
      sentiment,

    ticketRequired:
      completion.ticketRequired,

    ticketReady:
      completion.ticketReady,

    missingField:
      completion.missingField,

    nextQuestion:
      nextQuestion,

    conversationText:
      conversationText,

    order:
      order
  };
}


function buildEnelConversationText_(
  history,
  latestMessage
) {

  const items =
    Array.isArray(history)
      ? history.slice(-20)
      : [];


  const lines = [];


  items.forEach(
    function(item) {

      const text =
        cleanEnelValue_(
          item.text
        );


      if (!text) {
        return;
      }


      const role =
        item.role === 'assistant'
          ? 'Enel Assist'
          : 'Customer';


      lines.push(
        role + ': ' + text
      );
    }
  );


  const latest =
    cleanEnelValue_(
      latestMessage
    );


  const expectedLastLine =
    'Customer: ' + latest;


  if (
    latest &&
    lines[lines.length - 1] !==
      expectedLastLine
  ) {

    lines.push(
      expectedLastLine
    );
  }


  if (!lines.length) {

    return 'Customer: ' + latest;
  }


  return lines.join('\n');
}


function extractEnelOrderNumber_(text) {

  const value =
    String(text || '');


  const matches =
    value.match(
      /\b[A-Za-z]{2,6}-?\d{3,10}\b/g
    );


  if (
    !matches ||
    !matches.length
  ) {

    return '';
  }


  return String(
    matches[matches.length - 1]
  )
    .trim()
    .toUpperCase();
}


function getEnelCompletionState_(state) {

  const intent =
    state.intent;


  if (
    intent === 'order_status'
  ) {

    if (!state.orderNumber) {

      return {
        ticketRequired: true,
        ticketReady: false,
        missingField: 'order_number'
      };
    }


    if (!state.orderFound) {

      return {
        ticketRequired: true,
        ticketReady: false,
        missingField: 'valid_order_number'
      };
    }


    return {
      ticketRequired: true,
      ticketReady: true,
      missingField: ''
    };
  }


  if (
    intent === 'return'
  ) {

    if (!state.orderNumber) {

      return {
        ticketRequired: true,
        ticketReady: false,
        missingField: 'order_number'
      };
    }


    if (!state.orderFound) {

      return {
        ticketRequired: true,
        ticketReady: false,
        missingField: 'valid_order_number'
      };
    }


    if (!state.returnReason) {

      return {
        ticketRequired: true,
        ticketReady: false,
        missingField: 'return_reason'
      };
    }


    return {
      ticketRequired: true,
      ticketReady: true,
      missingField: ''
    };
  }


  if (
    intent === 'product_info'
  ) {

    return {
      ticketRequired: false,
      ticketReady: false,
      missingField: ''
    };
  }


  return {
    ticketRequired: false,
    ticketReady: false,
    missingField: ''
  };
}


function getEnelNextQuestion_(
  missingField,
  orderNumber,
  orderFound
) {

  if (
    missingField ===
    'order_number'
  ) {

    return (
      'আপনার অর্ডার নম্বরটি বলবেন?'
    );
  }


  if (
    missingField ===
    'valid_order_number'
  ) {

    return (
      'এই অর্ডার নম্বরটি খুঁজে পাওয়া যাচ্ছে না। ' +
      'নম্বরটি আবার দেখে সঠিকভাবে লিখবেন?'
    );
  }


  if (
    missingField ===
    'return_reason'
  ) {

    return (
      'পণ্যটি ফেরত দেওয়ার কারণটি সংক্ষেপে বলবেন?'
    );
  }


  return '';
}


function normalizeEnelIntent_(value) {

  const text =
    cleanEnelValue_(value)
      .toLowerCase();


  if (
    text === 'order_status' ||
    text === 'delivery' ||
    text === 'delivery_issue' ||
    text === 'tracking'
  ) {

    return 'order_status';
  }


  if (
    text === 'return' ||
    text === 'return_request' ||
    text === 'exchange'
  ) {

    return 'return';
  }


  if (
    text === 'product_info' ||
    text === 'product'
  ) {

    return 'product_info';
  }


  return 'other';
}


function normalizeEnelUrgency_(value) {

  const text =
    cleanEnelValue_(value)
      .toLowerCase();


  if (
    text === 'high'
  ) {
    return 'high';
  }


  if (
    text === 'medium'
  ) {
    return 'medium';
  }


  return 'low';
}


function normalizeEnelSentiment_(value) {

  const text =
    cleanEnelValue_(value)
      .toLowerCase();


  if (
    text === 'positive'
  ) {
    return 'positive';
  }


  if (
    text === 'negative'
  ) {
    return 'negative';
  }


  return 'neutral';
}


function cleanEnelValue_(value) {

  return String(
    value === null ||
    value === undefined
      ? ''
      : value
  ).trim();
}


function findOrderById_(orderId) {

  const spreadsheet =
    SpreadsheetApp.getActiveSpreadsheet();


  if (!spreadsheet) {

    throw new Error(
      'Could not access the spreadsheet.'
    );
  }


  const sheet =
    spreadsheet.getSheetByName(
      CONFIG.ORDERS_SHEET
    );


  if (!sheet) {

    throw new Error(
      'Orders sheet not found: ' +
      CONFIG.ORDERS_SHEET
    );
  }


  const values =
    sheet.getDataRange().getValues();


  const wantedId =
    String(orderId)
      .trim()
      .toUpperCase();


  for (
    let i = 1;
    i < values.length;
    i++
  ) {

    const rowId =
      String(values[i][0])
        .trim()
        .toUpperCase();


    if (rowId === wantedId) {

      return {

        orderId:
          values[i][0],

        customerName:
          values[i][1],

        phoneNumber:
          values[i][2],

        product:
          values[i][3],

        orderDate:
          values[i][4],

        expectedDelivery:
          values[i][5],

        currentStatus:
          values[i][6],

        lastUpdate:
          values[i][7]

      };
    }
  }


  return null;
}


function testEnelConversationAnalysis() {

  const history = [

    {
      role: 'customer',
      text: 'আমি পণ্যটি ফেরত দিতে চাই।'
    },

    {
      role: 'assistant',
      text: 'আপনার অর্ডার নম্বরটি বলবেন?'
    },

    {
      role: 'customer',
      text: 'RF1042'
    }

  ];


  const result =
    analyzeEnelConversation_(
      history,
      'RF1042'
    );


  console.log(
    JSON.stringify(result)
  );
}

WebPolicy.gs: অনুমোদিত business policy

Return, delivery, discount বা escalation rule prompt-এর মধ্যে স্থায়ীভাবে লিখে রাখা হয়নি। এই file লেসন ৮-এর Policy sheet থেকে approved rule পড়ে। ফলে business policy বদলালে chatbot code পরিবর্তন না করেও Sheet-এর rule পরিবর্তন করা যায়।

Enel Assist Policy Sheet
লেসন ৮-এর Policy sheet থেকে approved return, delivery, discount ও escalation rule পড়া হচ্ছে
সম্পূর্ণ WebPolicy.gs
/**
 * Enel Assist Policy Reader
 *
 * Reads approved business rules from the existing
 * Lesson 8 Policy sheet.
 *
 * No policy text is hard-coded into the chatbot.
 */


function getEnelPolicies_() {

  const spreadsheet =
    SpreadsheetApp.getActiveSpreadsheet();


  if (!spreadsheet) {

    throw new Error(
      'Could not access the spreadsheet.'
    );
  }


  const sheet =
    spreadsheet.getSheetByName(
      CONFIG.POLICY_SHEET
    );


  if (!sheet) {

    throw new Error(
      'Policy sheet not found: ' +
      CONFIG.POLICY_SHEET
    );
  }


  const values =
    sheet.getDataRange().getValues();


  if (
    !values ||
    values.length < 2
  ) {

    throw new Error(
      'Policy sheet does not contain policy data.'
    );
  }


  const headers =
    values[0];


  const policyRow =
    values[1];


  const policies = {};


  for (
    let i = 0;
    i < headers.length;
    i++
  ) {

    const header =
      String(
        headers[i] || ''
      ).trim();


    const value =
      String(
        policyRow[i] || ''
      ).trim();


    if (
      header &&
      value
    ) {

      policies[header] =
        value;
    }
  }


  return policies;
}


function getEnelPolicy_(policyName) {

  const policies =
    getEnelPolicies_();


  const wanted =
    String(
      policyName || ''
    ).trim();


  if (!wanted) {

    return '';
  }


  return String(
    policies[wanted] || ''
  ).trim();
}


function getEnelReturnPolicy_() {

  return getEnelPolicy_(
    'Return and Exchange'
  );
}


function getEnelDeliveryPolicy_() {

  return getEnelPolicy_(
    'Delivery Time'
  );
}


function getEnelDiscountPolicy_() {

  return getEnelPolicy_(
    'Discount'
  );
}


function getEnelEscalationPolicy_() {

  return getEnelPolicy_(
    'Human Escalation'
  );
}


function testEnelPolicyReader() {

  const result = {

    returnPolicy:
      getEnelReturnPolicy_(),

    deliveryPolicy:
      getEnelDeliveryPolicy_(),

    discountPolicy:
      getEnelDiscountPolicy_(),

    escalationPolicy:
      getEnelEscalationPolicy_()

  };


  console.log(
    JSON.stringify(result)
  );
}

WebTicket.gs: conversation থেকে Lesson 8 ticket

প্রয়োজনীয় তথ্য complete হলে এই file conversation-কে লেসন ৮-এর existing ticket structure-এ পাঠায়। Source হিসেবে Website | Enel Assist লেখা হয়, এবং customer conversation, order ID, issue type, urgency, validation, decision, human review ও ticket status একই Tickets sheet-এ আসে।

Enel Assist Tickets Sheet
ওয়েবসাইটের কথোপকথন লেসন ৮-এর existing Tickets sheet-এ নথিভুক্ত হচ্ছে
সম্পূর্ণ WebTicket.gs
/**
 * Enel Assist -> Lesson 8 Ticket Bridge
 *
 * This file does NOT replace Code.gs.
 * It reuses the existing Lesson 8 ticket structure,
 * order validation, business rules and escalation functions.
 */


function createEnelTicket_(data) {

  const ss =
    SpreadsheetApp.getActiveSpreadsheet();


  const ticketsSheet =
    ensureSheet_(
      ss,
      CONFIG.TICKETS_SHEET,
      TICKET_HEADERS
    );


  const suppliedOrderNumber =
    cleanText_(data.orderNumber);


  const order =
    findOrder_(
      ss,
      suppliedOrderNumber
    );


  let customerName =
    cleanText_(data.customerName);


  if (
    !customerName &&
    order.found &&
    order.details
  ) {

    customerName =
      pick_(
        order.details,
        [
          'Customer Name',
          'Name',
          'কাস্টমারের নাম',
          'নাম'
        ]
      );
  }


  const form = {

    customerName:
      customerName,

    email:
      cleanText_(data.email),

    phone:
      cleanText_(data.phone),

    source:
      'Website | Enel Assist',

    orderNumber:
      suppliedOrderNumber,

    product:
      cleanText_(
        data.product ||
        order.product
      ),

    message:
      cleanText_(
        data.conversationText ||
        data.customerMessage
      )
  };


  const ai = {

    intent:
      cleanText_(
        data.intent ||
        'other'
      ),

    order_number:
      suppliedOrderNumber,

    product:
      cleanText_(
        data.product ||
        order.product
      ),

    urgency:
      cleanText_(
        data.urgency ||
        'low'
      ),

    language:
      cleanText_(
        data.language ||
        'bn'
      ),

    sentiment:
      cleanText_(
        data.sentiment ||
        'neutral'
      ),

    suggested_route:
      cleanText_(
        data.suggestedRoute ||
        'A'
      ),

    decision_reason:
      cleanText_(
        data.decisionReason ||
        'Enel Assist conversation completed.'
      ),

    reply:
      cleanText_(
        data.reply
      )
  };


  const injectionDetected =
    containsPromptInjection_(
      form.message
    );


  const decision =
    decideRoute_(
      ai,
      order,
      injectionDetected,
      false
    );


  const ticketId =
    createTicketId_();


  let draftReply =
    decision.route === 'C_ESCALATE'
      ? ''
      : cleanText_(ai.reply);


  if (
    !draftReply &&
    decision.route !== 'C_ESCALATE'
  ) {

    draftReply =
      defaultReply_(
        form.customerName,
        ticketId
      );
  }


  let deliveryStatus =
    'Chat reply delivered';


  if (
    decision.route ===
    'B_HOLD_FOR_REVIEW'
  ) {

    deliveryStatus =
      'Held for human review';
  }


  if (
    decision.route ===
    'C_ESCALATE'
  ) {

    if (
      CONFIG.SEND_ESCALATION_EMAIL
    ) {

      sendEscalation_(
        ticketId,
        form,
        ai,
        order,
        decision.reason
      );


      deliveryStatus =
        'Escalation email sent';

    } else {

      deliveryStatus =
        'Escalation email disabled';
    }
  }


  const orderValidation =
    order.found
      ? (
          'Valid' +
          (
            order.status
              ? ' | ' + order.status
              : ''
          )
        )
      : 'Not found';


  const humanReview =
    decision.route ===
    'A_AUTO_REPLY'
      ? 'No'
      : 'Yes';


  ticketsSheet.appendRow([

    new Date(),

    form.customerName,

    form.source,

    form.message,

    suppliedOrderNumber,

    cleanText_(ai.intent),

    cleanText_(ai.urgency),

    orderValidation,

    decision.route +
      ' | ' +
      decision.reason,

    draftReply,

    humanReview,

    deliveryStatus +
      ' | ' +
      ticketId

  ]);


  SpreadsheetApp.flush();


  return {

    ticketCreated: true,

    ticketId:
      ticketId,

    route:
      decision.route,

    humanReview:
      humanReview,

    ticketStatus:
      deliveryStatus,

    orderValidation:
      orderValidation
  };
}


function testEnelTicketBridge() {

  const result =
    createEnelTicket_({

      orderNumber:
        'RF1042',

      customerMessage:
        'আমার অর্ডারের পণ্যটি সম্পর্কে সাধারণ তথ্য জানতে চাই।',

      conversationText:
        'Customer: আমার অর্ডারের পণ্যটি সম্পর্কে সাধারণ তথ্য জানতে চাই।\n' +
        'Enel Assist: আপনার অর্ডার নম্বরটি বলবেন?\n' +
        'Customer: RF1042',

      intent:
        'product_info',

      urgency:
        'low',

      language:
        'bn',

      sentiment:
        'neutral',

      suggestedRoute:
        'A',

      decisionReason:
        'Safe enquiry with a verified order.',

      reply:
        'আপনার অনুরোধটি গ্রহণ করা হয়েছে।'

    });


  console.log(
    JSON.stringify(result)
  );
}

লেসন ৯ লার্নার প্যাক ডাউনলোড করুন

ভিডিও দেখে শত শত লাইন code টাইপ করার প্রয়োজন নেই। এই লার্নার প্যাকে Lesson 9-এ ব্যবহৃত chatbot HTML snippet, CSS, JavaScript এবং চারটি Apps Script file একসঙ্গে রাখা হয়েছে। Code.gs লেসন ৮-এর final version থেকেই ব্যবহার করবেন। আগে article-এর ব্যাখ্যা দেখে কোন file কোথায় ব্যবহার হচ্ছে বুঝে নিন, তারপর নিজের project অনুযায়ী প্রয়োজনীয় অংশ copy করুন।

⬇ Enel Assist Lesson 9 লার্নার প্যাক ডাউনলোড করুন

ZIP file সরাসরি enamulhaque.co.uk থেকে download হবে।

Google Sheet-এ কোন data থাকতে হবে

Orders

এই demo-তে order lookup-এর জন্য প্রথম আটটি column ব্যবহার করা হয়েছে:

  • Order ID
  • Customer Name
  • Phone Number
  • Product
  • Order Date
  • Expected Delivery
  • Current Status
  • Last Update

Policy

বর্তমান demo-তে বিশেষভাবে Delivery Time, Return and Exchange, Discount এবং Human Escalation policy পড়া যায়।

Tickets

লেসন ৮-এর existing ১২-column ticket structure-ই reuse করা হয়েছে: Timestamp, Customer Name, Source, Customer Message, Order ID, Issue Type, Urgency, Order Validation, Decision, Draft Reply, Human Review এবং Ticket Status।

Apps Script web app deploy করবেন যেভাবে

  1. লেসন ৮-এর Apps Script project খুলুন।
  2. WebApp.gs, WebConversation.gs, WebPolicy.gs এবং WebTicket.gs file যোগ করুন।
  3. লেসন ৮-এর existing Code.gs এবং Script Properties-এ রাখা Gemini API key আগের মতোই রাখুন।
  4. Apps Script থেকে Web App হিসেবে নতুন deployment তৈরি বা existing deployment update করুন।
  5. Website থেকে request আসতে পারে এমন access setting নির্বাচন করুন। বাস্তব production system-এ authentication, rate limiting এবং access control আলাদাভাবে পরিকল্পনা করুন।
  6. Deploy হওয়ার পর পাওয়া /exec URL কপি করুন।
  7. script.js-এর BACKEND_URL-এ সেই URL বসান।

দুটি test দিয়ে system যাচাই করুন

Test 1: order status

Customer: আমার অর্ডার এখনো আসেনি
Enel Assist: আপনার অর্ডার নম্বরটি বলবেন?
Customer: RF1042

System RF1042-কে real Orders sheet-এ খুঁজে current status এবং last update থেকে customer-facing উত্তর তৈরি করবে।

Test 2: return request

Customer: আমি পণ্যটি ফেরত দিতে চাই
Enel Assist: আপনার অর্ডার নম্বরটি বলবেন?
Customer: RF1042
Enel Assist: পণ্যটি ফেরত দেওয়ার কারণটি সংক্ষেপে বলবেন?
Customer: সাইজ ঠিক হয়নি

এখানে system order validate করবে, missing return reason সংগ্রহ করবে, Policy sheet থেকে approved Return and Exchange rule পড়বে, তারপর Lesson 8-এর existing Tickets sheet-এ ticket তৈরি করবে।

Enel Assist final return workflow
Final tested return workflow: missing information সংগ্রহ, approved policy এবং ticket number

আপনার সাইট যদি সাধারণ HTML বা GitHub Pages হয়

  1. Chat HTML block সাধারণত </body>-এর আগে বসান।
  2. Chat CSS নিজের stylesheet-এর শেষে যোগ করুন।
  3. script.js file যোগ করুন এবং page-এ script reference দিন।
  4. BACKEND_URL নিজের Apps Script Web App URL দিয়ে বদলান।
  5. Browser cache পুরোনো JavaScript দেখালে script URL-এ version query ব্যবহার করতে পারেন, যেমন script.js?v=6

আপনার সাইট যদি WordPress হয়

WordPress-এ implementation theme এবং setup অনুযায়ী একটু ভিন্ন হতে পারে। একটি নির্দিষ্ট page-এ শুধু interface দেখাতে চাইলে Custom HTML block ব্যবহার করা যায়। Site-wide chatbot চাইলে HTML সাধারণত site-wide footer/template বা custom-code mechanism-এ, CSS theme-এর Additional CSS বা child theme-এ, এবং JavaScript child theme বা নিরাপদ site-wide custom-code ব্যবস্থায় যোগ করা ভালো।

WordPress-এ লক্ষ্য: HTML structure, CSS এবং JavaScript যেন একই page-এ load হয় এবং script.js আপনার Apps Script Web App URL-এ request পাঠাতে পারে।

GoDaddy-তে website থাকলে

GoDaddy Managed WordPress

সাইটটি যদি WordPress হয়, তাহলে উপরের WordPress পদ্ধতিই ব্যবহার করবেন। GoDaddy এখানে hosting provider; Enel Assist-এর architecture বদলাবে না।

GoDaddy Website Builder

আপনার editor বা plan যদি Custom HTML / embed এবং JavaScript চালাতে দেয়, তাহলে chatbot-এর HTML/CSS/JavaScript অংশ সেখানে বসানো যাবে। যদি JavaScript embed সীমিত থাকে, বিকল্প হিসেবে একটি button থেকে আলাদা Enel Assist web page খোলা যায়।

অন্য website platform হলে

মূল প্রশ্ন হচ্ছে platform-টি custom HTML, CSS এবং JavaScript চালাতে দেয় কি না। দিলে একই widget architecture ব্যবহার করা যাবে। না দিলে একটি external assistant page বা platform-specific integration layer প্রয়োজন হবে। Backend Apps Script একই থাকতে পারে।

নিরাপত্তা: এই অংশটি বাদ দেবেন না

Gemini API key কখনো HTML, public GitHub repository বা browser-side JavaScript-এ রাখবেন না। API key server-side Apps Script-এর Script Properties-এ রাখুন।

  • Demo data কাল্পনিক বা পরিচয়মুক্ত রাখুন।
  • Production system-এ public web endpoint-এর জন্য authentication, rate limiting, abuse protection এবং logging পরিকল্পনা করুন।
  • AI যেন order status, refund, discount বা policy invent না করে—real data এবং approved policy থেকে উত্তর দেওয়ার rule রাখুন।
  • Business-critical সিদ্ধান্তের জন্য Lesson 8-এর human review / escalation control বজায় রাখুন।

এক লাইনে লেসন ৯

একটি সাধারণ ওয়েবসাইটকে এমন একটি এআই customer agent-এ রূপান্তর করা, যেটি কথোপকথন বুঝে বাস্তব business data ও policy ব্যবহার করে এবং প্রয়োজন হলে existing automation-এর মাধ্যমে ticket তৈরি করে।

প্রয়োজনীয় লিংক


নোট: Enel Retail, Enel Assist এবং এই article-এর সব customer/order information শিক্ষামূলক demo data। বাস্তব customer information ব্যবহার করার আগে আপনার প্রতিষ্ঠানের privacy, security এবং data-handling requirements অনুসরণ করুন।