From ad27f75febe37a1bef7531878a34fd25dd0ab272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ti=E1=BA=BFn=20Th=C3=A0nh=20H=E1=BB=A9a?= Date: Wed, 15 Nov 2023 18:32:55 +0700 Subject: [PATCH 1/3] fix: Missing toast messages for some actions --- src/main/java/Controllers/LoginController.java | 4 ++-- src/main/java/Controllers/PromotionManagerController.java | 4 ++-- src/main/java/Controllers/SignUpController.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/Controllers/LoginController.java b/src/main/java/Controllers/LoginController.java index feb15642..b350569c 100644 --- a/src/main/java/Controllers/LoginController.java +++ b/src/main/java/Controllers/LoginController.java @@ -59,8 +59,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) // Validate the login credentials if (!valid.loginValidation(email,password)){ - session.setAttribute("isSuccessful", false); - response.sendRedirect("/home#failure_login_info"); + session.setAttribute("toastMessage", "error-login-credentials"); + response.sendRedirect("/"); return; } diff --git a/src/main/java/Controllers/PromotionManagerController.java b/src/main/java/Controllers/PromotionManagerController.java index 3d925d9d..81985686 100644 --- a/src/main/java/Controllers/PromotionManagerController.java +++ b/src/main/java/Controllers/PromotionManagerController.java @@ -62,11 +62,11 @@ private void doGetVoucher(HttpServletRequest request, HttpServletResponse respon response.sendRedirect("/promotionManager"); } else { session.setAttribute("toastMessage", "error-delete-voucher"); - response.sendRedirect("/promotionManager#failure_delete_voucher"); + response.sendRedirect("/promotionManager"); } } session.setAttribute("toastMessage", "error-delete-voucher"); - response.sendRedirect("/promotionManager#failure_delete_voucher"); + response.sendRedirect("/promotionManager"); } private void doPostAddVoucher(HttpServletRequest request, HttpServletResponse response) diff --git a/src/main/java/Controllers/SignUpController.java b/src/main/java/Controllers/SignUpController.java index 83b0c121..1dbb9e42 100644 --- a/src/main/java/Controllers/SignUpController.java +++ b/src/main/java/Controllers/SignUpController.java @@ -77,7 +77,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) ValidationUtils valid = new ValidationUtils(); if (!valid.signUpValidation(username,email,pass)){ - response.sendRedirect("/home#failure_register"); + response.sendRedirect("/"); return; } From 6cbefe44b777f6630aa4fea641342d8f1b940af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ti=E1=BA=BFn=20Th=C3=A0nh=20H=E1=BB=A9a?= Date: Wed, 15 Nov 2023 19:39:56 +0700 Subject: [PATCH 2/3] fix: Incorrect order total after applying discounts --- src/main/java/Controllers/CheckoutController.java | 3 ++- src/main/webapp/checkout.jsp | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/Controllers/CheckoutController.java b/src/main/java/Controllers/CheckoutController.java index f83da1bc..c3cdf4cd 100644 --- a/src/main/java/Controllers/CheckoutController.java +++ b/src/main/java/Controllers/CheckoutController.java @@ -253,7 +253,8 @@ protected void doPostOrder(HttpServletRequest request, HttpServletResponse respo String voucherCode = request.getParameter("txtVoucherCode"); Voucher voucher = voucherDAO.getVoucherByCode(voucherCode); if (voucher != null ) { - orderTotalDouble = orderTotalDouble * voucher.getVoucherDiscount(); + double voucherpercent = voucher.getVoucherDiscount(); + orderTotalDouble = orderTotalDouble * (voucherpercent == 1 ? 1 : 1 - voucherpercent); voucherDAO.updateQuantity(voucher); order.setVoucherID(voucher.getVoucherID()); } else { diff --git a/src/main/webapp/checkout.jsp b/src/main/webapp/checkout.jsp index 1d5b9d31..8e3f0645 100644 --- a/src/main/webapp/checkout.jsp +++ b/src/main/webapp/checkout.jsp @@ -161,7 +161,11 @@
-

Tổng thanh toán: đ

+

Tổng thanh toán: + đ +

From 7ec60822cccbf113272e1b641def95905087578f Mon Sep 17 00:00:00 2001 From: quzanh1130 Date: Sun, 7 Jul 2024 01:04:18 +0700 Subject: [PATCH 3/3] update chatbot with rag using api --- README.md | 1 + api/chatbot/Dockerfile | 13 + api/chatbot/app.py | 35 + api/chatbot/chatbot_rag.py | 92 +++ api/chatbot/food_description.json | 657 ++++++++++++++++++ api/chatbot/minsearch.py | 96 +++ api/chatbot/requirements.txt | 7 + api/psqlserver/requirements.txt | 1 + api/textai_server/Dockerfile | 11 + api/textai_server/app.py | 39 +- api/textai_server/requirements.txt | 4 +- docker-compose.yml | 42 +- .../webapp/WEB-INF/jspf/admin/orders.jspf | 2 +- .../jspf/common/components/chatbox.jspf | 30 + src/main/webapp/assets/css/style.css | 206 ++++++ src/main/webapp/assets/js/chatbot.js | 85 +++ src/main/webapp/assets/js/home.js | 1 + src/main/webapp/index.jsp | 5 +- 18 files changed, 1292 insertions(+), 35 deletions(-) create mode 100644 api/chatbot/Dockerfile create mode 100644 api/chatbot/app.py create mode 100644 api/chatbot/chatbot_rag.py create mode 100644 api/chatbot/food_description.json create mode 100644 api/chatbot/minsearch.py create mode 100644 api/chatbot/requirements.txt create mode 100644 api/textai_server/Dockerfile create mode 100644 src/main/webapp/WEB-INF/jspf/common/components/chatbox.jspf create mode 100644 src/main/webapp/assets/js/chatbot.js diff --git a/README.md b/README.md index 064495d1..3fec688c 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ FFood is a food website that lets customers order food in a quick and convenient - View Food by Categories. - View Food Details. - Rate Food. +- Chat bot - Search Food by keyword. - Search Food by image. - User cart Management. diff --git a/api/chatbot/Dockerfile b/api/chatbot/Dockerfile new file mode 100644 index 00000000..267b91c9 --- /dev/null +++ b/api/chatbot/Dockerfile @@ -0,0 +1,13 @@ +# Use an official Python runtime as a parent image +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 + +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt +RUN export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python + +COPY . . + +EXPOSE 8100 + +# Run app.py when the container launches +CMD ["python3", "app.py"] diff --git a/api/chatbot/app.py b/api/chatbot/app.py new file mode 100644 index 00000000..158fe032 --- /dev/null +++ b/api/chatbot/app.py @@ -0,0 +1,35 @@ +import minsearch +import json +from fastapi import FastAPI, HTTPException +from fastapi.middleware.cors import CORSMiddleware +import uvicorn +import asyncio +import nest_asyncio +from chatbot_rag import rag, read_json + +nest_asyncio.apply() + +app = FastAPI() +file = 'food_description.json' +index = read_json(file) +print() + +# Add CORS Middleware +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +@app.get("/rag/") +async def rag_endpoint(query: str): + try: + answer = rag(query, index) + return {"answer": answer} + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + +if __name__ == '__main__': + uvicorn.run('app:app', port=8100, host='0.0.0.0', loop='asyncio') diff --git a/api/chatbot/chatbot_rag.py b/api/chatbot/chatbot_rag.py new file mode 100644 index 00000000..62f1e589 --- /dev/null +++ b/api/chatbot/chatbot_rag.py @@ -0,0 +1,92 @@ +import minsearch +import json +from g4f.client import Client +import g4f + +_providers = [ + g4f.Provider.Aichat, + g4f.Provider.ChatBase, + g4f.Provider.Bing, + g4f.Provider.GptGo, + g4f.Provider.You, + g4f.Provider.Yqcloud, +] + +def llm(prompt): + client = Client() + print("Creating chat completion...") + chat_completion = client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": prompt}], + ignored=["Ylokh", "GptGo", "AItianhu", "Aibn", "Myshell", "FreeGpt"], + stream=True + ) + + response = "" + print("Waiting for completion...") + for completion in chat_completion: + response += completion.choices[0].delta.content or "" + + return {"response": response} + +def read_json(file): + with open(file, 'rt', encoding='utf-8') as f_in: # Specify UTF-8 encoding + docs_raw = json.load(f_in) + print(f"Read {len(docs_raw)} documents from {file}.") + + documents = [] + for course_dict in docs_raw: + for doc in course_dict['items']: + doc['category'] = course_dict['category'] + documents.append(doc) + + index = minsearch.Index( + text_fields=["question", "description", "name"], + keyword_fields=["category"] + ) + index.fit(documents) + print("Index has been initialized.") + return index + +def search(query, index): + + if index is None: + raise RuntimeError("Index has not been initialized. Call read_json() first.") + + boost = {'question': 3.0, 'section': 0.5} + + results = index.search( + query=query, + filter_dict={'category': 'FFood'}, + boost_dict=boost, + num_results=5 + ) + + return results + +def build_prompt(query, search_results): + prompt_template = """ +You're a vietnamese food ordering assistant. Answer the QUESTION based on the CONTEXT from the food description database. +Use only the facts from the CONTEXT when answering the QUESTION. + +QUESTION: {question} + +CONTEXT: +{context} +""".strip() + + context = "" + + for doc in search_results: + context = context + f"name: {doc['name']}\nquestion: {doc['question']}\nanswer: {doc['description']}\n\n" + + prompt = prompt_template.format(question=query, context=context).strip() + return prompt + +def rag(query, index): + print(f"Searching for: {query}") + search_results = search(query, index) + print(f"Found {len(search_results)} search results.") + prompt = build_prompt(query, search_results) + answer = llm(prompt) + return answer \ No newline at end of file diff --git a/api/chatbot/food_description.json b/api/chatbot/food_description.json new file mode 100644 index 00000000..a5655ac7 --- /dev/null +++ b/api/chatbot/food_description.json @@ -0,0 +1,657 @@ +[ + { + "category": "FFood", + "items": [ + { + "name": "Banh Bèo", + "description": "Steamed rice cake topped with shrimp, scallions, and crispy shallots.", + "question": "What is Bánh Bèo?" + }, + { + "name": "Bánh Bột Lộc", + "description": "Chewy tapioca dumplings filled with shrimp and pork.", + "question": "What is Bánh Bột Lộc?" + }, + { + "name": "Bánh Căn", + "description": "Mini pancakes made from rice flour, often served with dipping sauce.", + "question": "What is Bánh Căn?" + }, + { + "name": "Bánh Canh", + "description": "Thick noodle soup with various toppings.", + "question": "What is Bánh Canh?" + }, + { + "name": "Bánh Chưng", + "description": "Traditional rice cake with pork and mung beans, wrapped in banana leaves.", + "question": "What is Bánh Chưng?" + }, + { + "name": "Bánh Cuốn", + "description": "Steamed rice rolls filled with minced pork and mushrooms.", + "question": "What is Bánh Cuốn?" + }, + { + "name": "Bánh Đúc", + "description": "Steamed rice cake, often served with savory toppings.", + "question": "What is Bánh Đúc?" + }, + { + "name": "Bánh Giò", + "description": "Pyramid-shaped rice cake filled with pork and mushrooms.", + "question": "What is Bánh Giò?" + }, + { + "name": "Bánh Khọt", + "description": "Mini savory pancakes topped with shrimp and herbs.", + "question": "What is Bánh Khọt?" + }, + { + "name": "Bánh Mì", + "description": "Vietnamese baguette sandwich with various fillings.", + "question": "What is Bánh Mì?" + }, + { + "name": "Bánh Pía", + "description": "Flaky pastry filled with mung beans and salted egg yolk.", + "question": "What is Bánh Pía?" + }, + { + "name": "Bánh Tét", + "description": "Cylindrical rice cake with pork and mung beans, wrapped in banana leaves.", + "question": "What is Bánh Tét?" + }, + { + "name": "Bánh Tráng Nướng", + "description": "Grilled rice paper topped with various ingredients.", + "question": "What is Bánh Tráng Nướng?" + }, + { + "name": "Bánh Xèo", + "description": "Vietnamese crispy pancake filled with shrimp, pork, and bean sprouts.", + "question": "What is Bánh Xèo?" + }, + { + "name": "Bún Bò Huế", + "description": "Spicy beef noodle soup from Huế.", + "question": "What is Bún Bò Huế?" + }, + { + "name": "Bún Đậu Mắm Tôm", + "description": "Rice noodles with tofu, herbs, and fermented shrimp paste.", + "question": "What is Bún Đậu Mắm Tôm?" + }, + { + "name": "Bún Mắm", + "description": "Fermented fish noodle soup with a variety of toppings.", + "question": "What is Bún Mắm?" + }, + { + "name": "Bún Riêu", + "description": "Crab noodle soup with a tomato broth.", + "question": "What is Bún Riêu?" + }, + { + "name": "Bún Thịt Nướng", + "description": "Grilled pork with rice noodles and fresh herbs.", + "question": "What is Bún Thịt Nướng?" + }, + { + "name": "Cá Kho Tộ", + "description": "Caramelized fish in a clay pot.", + "question": "What is Cá Kho Tộ?" + }, + { + "name": "Canh Chua", + "description": "Vietnamese sour soup with fish, pineapple, and tamarind.", + "question": "What is Canh Chua?" + }, + { + "name": "Cao Lầu", + "description": "Noodle dish from Hội An with pork and fresh greens.", + "question": "What is Cao Lầu?" + }, + { + "name": "Cháo Lòng", + "description": "Rice porridge with pig innards.", + "question": "What is Cháo Lòng?" + }, + { + "name": "Cơm Tấm", + "description": "Broken rice served with grilled pork and various sides.", + "question": "What is Cơm Tấm?" + }, + { + "name": "Gỏi Cuốn", + "description": "Fresh spring rolls with shrimp, pork, and herbs.", + "question": "What is Gỏi Cuốn?" + }, + { + "name": "Hủ Tiếu", + "description": "Rice noodle soup with pork and seafood.", + "question": "What is Hủ Tiếu?" + }, + { + "name": "Mì Quảng", + "description": "Turmeric noodles with shrimp, pork, and fresh herbs.", + "question": "What is Mì Quảng?" + }, + { + "name": "Nem Chua", + "description": "Fermented pork sausage.", + "question": "What is Nem Chua?" + }, + { + "name": "Xôi Xéo", + "description": "Sticky rice with mung bean paste and fried shallots.", + "question": "What is Xôi Xéo?" + }, + { + "name": "Apple Pie", + "description": "Classic dessert pie filled with spiced apples.", + "question": "What is Apple Pie?" + }, + { + "name": "Baby Back Ribs", + "description": "Pork ribs cooked until tender, often grilled with barbecue sauce.", + "question": "What are Baby Back Ribs?" + }, + { + "name": "Baklava", + "description": "Sweet pastry made of layers of filo dough, filled with nuts and honey.", + "question": "What is Baklava?" + }, + { + "name": "Beef Carpaccio", + "description": "Thinly sliced raw beef, typically served with lemon, olive oil, and parmesan.", + "question": "What is Beef Carpaccio?" + }, + { + "name": "Beef Tartare", + "description": "Raw ground beef mixed with spices and condiments.", + "question": "What is Beef Tartare?" + }, + { + "name": "Beet Salad", + "description": "Salad made with cooked or raw beets, often mixed with greens and cheese.", + "question": "What is Beet Salad?" + }, + { + "name": "Beignets", + "description": "Deep-fried pastry dusted with powdered sugar, popular in New Orleans.", + "question": "What are Beignets?" + }, + { + "name": "Bibimbap", + "description": "Korean rice dish topped with assorted vegetables, meat, and a fried egg.", + "question": "What is Bibimbap?" + }, + { + "name": "Bread Pudding", + "description": "Dessert made from bread soaked in a mixture of milk, eggs, and sugar.", + "question": "What is Bread Pudding?" + }, + { + "name": "Breakfast Burrito", + "description": "Tortilla filled with eggs, cheese, and other breakfast ingredients.", + "question": "What is a Breakfast Burrito?" + }, + { + "name": "Bruschetta", + "description": "Grilled bread topped with tomatoes, garlic, and basil.", + "question": "What is Bruschetta?" + }, + { + "name": "Caesar Salad", + "description": "Salad made with romaine lettuce, croutons, parmesan cheese, and Caesar dressing.", + "question": "What is Caesar Salad?" + }, + { + "name": "Cannoli", + "description": "Italian pastry tube filled with sweetened ricotta cheese.", + "question": "What is Cannoli?" + }, + { + "name": "Caprese Salad", + "description": "Salad made with fresh tomatoes, mozzarella cheese, basil, olive oil, and balsamic vinegar.", + "question": "What is Caprese Salad?" + }, + { + "name": "Carrot Cake", + "description": "Cake made with grated carrots and typically topped with cream cheese frosting.", + "question": "What is Carrot Cake?" + }, + { + "name": "Ceviche", + "description": "Seafood dish where raw fish is marinated in citrus juices, often with onions, cilantro, and chili peppers.", + "question": "What is Ceviche?" + }, + { + "name": "Cheese Plate", + "description": "Selection of cheeses served with accompaniments like fruits, nuts, and bread.", + "question": "What is a Cheese Plate?" + }, + { + "name": "Cheesecake", + "description": "Dessert with a crust made of crushed cookies or graham crackers, filled with a mixture of soft cheese, eggs, and sugar.", + "question": "What is Cheesecake?" + }, + { + "name": "Chicken Curry", + "description": "Curry dish made with chicken, curry spices, and often coconut milk or tomatoes.", + "question": "What is Chicken Curry?" + }, + { + "name": "Chicken Quesadilla", + "description": "Tortilla filled with melted cheese and chicken, grilled until crispy.", + "question": "What is Chicken Quesadilla?" + }, + { + "name": "Chicken Wings", + "description": "Chicken wings typically fried or baked and coated in sauce.", + "question": "What are Chicken Wings?" + }, + { + "name": "Chocolate Cake", + "description": "Cake flavored with chocolate, often with layers of chocolate icing or ganache.", + "question": "What is Chocolate Cake?" + }, + { + "name": "Chocolate Mousse", + "description": "Light and airy dessert made with chocolate, eggs, and whipped cream.", + "question": "What is Chocolate Mousse?" + }, + { + "name": "Churros", + "description": "Fried dough pastry dusted with sugar, often served with chocolate sauce.", + "question": "What are Churros?" + }, + { + "name": "Clam Chowder", + "description": "Cream-based soup with clams, potatoes, onions, and celery.", + "question": "What is Clam Chowder?" + }, + { + "name": "Club Sandwich", + "description": "Sandwich with layers of toasted bread, lettuce, tomato, bacon, turkey, and mayonnaise.", + "question": "What is a Club Sandwich?" + }, + { + "name": "Crab Cakes", + "description": "Patties made from crab meat, breadcrumbs, and seasonings, often pan-fried.", + "question": "What are Crab Cakes?" + }, + { + "name": "Creme Brulee", + "description": "Dessert consisting of rich custard topped with caramelized sugar.", + "question": "What is Creme Brulee?" + }, + { + "name": "Croque Madame", + "description": "Grilled ham and cheese sandwich topped with a fried egg.", + "question": "What is Croque Madame?" + }, + { + "name": "Cupcakes", + "description": "Small cakes baked in cup-shaped containers and typically frosted.", + "question": "What are Cupcakes?" + }, + { + "name": "Deviled Eggs", + "description": "Hard-boiled eggs sliced in half, filled with a seasoned yolk mixture.", + "question": "What are Deviled Eggs?" + }, + { + "name": "Donuts", + "description": "Deep-fried sweet dough, often ring-shaped and topped with glaze or sugar.", + "question": "What are Donuts?" + }, + { + "name": "Dumplings", + "description": "Small pieces of dough filled with meat, vegetables, or sweets, typically boiled or steamed.", + "question": "What are Dumplings?" + }, + { + "name": "Edamame", + "description": "Steamed or boiled young soybeans in the pod, often served as a snack.", + "question": "What is Edamame?" + }, + { + "name": "Eggs Benedict", + "description": "Breakfast dish consisting of poached eggs and Canadian bacon on an English muffin, topped with hollandaise sauce.", + "question": "What is Eggs Benedict?" + }, + { + "name": "Escargots", + "description": "Cooked land snails often served with garlic and butter.", + "question": "What are Escargots?" + }, + { + "name": "Falafel", + "description": "Fried balls or patties made from ground chickpeas or fava beans, often served in pita bread.", + "question": "What is Falafel?" + }, + { + "name": "Filet Mignon", + "description": "Tender cut of beef from the tenderloin, typically grilled or pan-seared.", + "question": "What is Filet Mignon?" + }, + { + "name": "Fish and Chips", + "description": "Deep-fried fish served with thick-cut fries, often accompanied by tartar sauce.", + "question": "What are Fish and Chips?" + }, + { + "name": "Foie Gras", + "description": "Duck or goose liver that has been specially fattened, often used in French cuisine.", + "question": "What is Foie Gras?" + }, + { + "name": "French Fries", + "description": "Potato sticks deep-fried until crispy, often served as a side dish.", + "question": "What are French Fries?" + }, + { + "name": "French Onion Soup", + "description": "Soup made with caramelized onions, beef broth, and topped with melted cheese and croutons.", + "question": "What is French Onion Soup?" + }, + { + "name": "French Toast", + "description": "Bread dipped in eggs and milk, then fried until golden brown, often served with syrup.", + "question": "What is French Toast?" + }, + { + "name": "Fried Calamari", + "description": "Squid rings coated in batter and deep-fried, often served with marinara sauce.", + "question": "What is Fried Calamari?" + }, + { + "name": "Fried Rice", + "description": "Stir-fried rice with vegetables, meat, and sometimes egg.", + "question": "What is Fried Rice?" + }, + { + "name": "Frozen Yogurt", + "description": "Dessert similar to ice cream but made with yogurt.", + "question": "What is Frozen Yogurt?" + }, + { + "name": "Garlic Bread", + "description": "Bread topped with garlic butter and often toasted.", + "question": "What is Garlic Bread?" + }, + { + "name": "Gnocchi", + "description": "Italian dumplings made from potatoes, flour, and sometimes egg, served with sauce.", + "question": "What are Gnocchi?" + }, + { + "name": "Greek Salad", + "description": "Salad made with tomatoes, cucumbers, olives, feta cheese, and olive oil.", + "question": "What is Greek Salad?" + }, + { + "name": "Grilled Cheese Sandwich", + "description": "Sandwich with melted cheese between slices of buttered and grilled bread.", + "question": "What is a Grilled Cheese Sandwich?" + }, + { + "name": "Grilled Salmon", + "description": "Salmon fillet grilled until tender and often seasoned with herbs or lemon.", + "question": "What is Grilled Salmon?" + }, + { + "name": "Guacamole", + "description": "Mexican dip made from mashed avocado, lime juice, onions, tomatoes, and cilantro.", + "question": "What is Guacamole?" + }, + { + "name": "Gyoza", + "description": "Japanese dumplings filled with ground meat and vegetables, typically pan-fried.", + "question": "What are Gyoza?" + }, + { + "name": "Hamburger", + "description": "Sandwich consisting of a cooked beef patty in a bun, often with various toppings.", + "question": "What is a Hamburger?" + }, + { + "name": "Hot and Sour Soup", + "description": "Chinese soup with a spicy and tangy broth, often containing mushrooms, tofu, and bamboo shoots.", + "question": "What is Hot and Sour Soup?" + }, + { + "name": "Hot Dog", + "description": "Cooked sausage served in a sliced bun, often with condiments like mustard and ketchup.", + "question": "What is a Hot Dog?" + }, + { + "name": "Huevos Rancheros", + "description": "Mexican breakfast dish consisting of eggs served on tortillas with salsa.", + "question": "What are Huevos Rancheros?" + }, + { + "name": "Hummus", + "description": "Middle Eastern dip made from mashed chickpeas, tahini, lemon juice, and garlic.", + "question": "What is Hummus?" + }, + { + "name": "Ice Cream", + "description": "Frozen dessert made from cream, sugar, and often flavored with fruit or other ingredients.", + "question": "What is Ice Cream?" + }, + { + "name": "Lasagna", + "description": "Italian pasta dish made with layers of pasta, cheese, meat sauce, and sometimes vegetables.", + "question": "What is Lasagna?" + }, + { + "name": "Lobster Bisque", + "description": "Creamy soup made from lobster, often flavored with sherry.", + "question": "What is Lobster Bisque?" + }, + { + "name": "Lobster Roll Sandwich", + "description": "Sandwich filled with lobster meat, typically mixed with mayonnaise, lemon juice, and herbs, served in a grilled or toasted roll.", + "question": "What is a Lobster Roll Sandwich?" + }, + { + "name": "Macaroni and Cheese", + "description": "Pasta dish with cheese sauce, often baked until golden and bubbly.", + "question": "What is Macaroni and Cheese?" + }, + { + "name": "Macarons", + "description": "Colorful French pastry made from almond flour, sugar, and egg whites, sandwiched together with ganache, buttercream, or jam.", + "question": "What are Macarons?" + }, + { + "name": "Miso Soup", + "description": "Japanese soup made with miso paste and dashi broth, often containing tofu, seaweed, and green onions.", + "question": "What is Miso Soup?" + }, + { + "name": "Mussels", + "description": "Shellfish cooked in a broth of wine, garlic, butter, and herbs.", + "question": "What are Mussels?" + }, + { + "name": "Nachos", + "description": "Mexican dish of tortilla chips topped with melted cheese and often other toppings like meat, beans, and salsa.", + "question": "What are Nachos?" + }, + { + "name": "Non Food", + "description": "This category denotes items that are not food.", + "question": "What does Non Food mean in this context?" + }, + { + "name": "Omelette", + "description": "Egg dish cooked with various fillings such as cheese, vegetables, and meats.", + "question": "What is an Omelette?" + }, + { + "name": "Onion Rings", + "description": "Sliced onions dipped in batter and deep-fried until crispy.", + "question": "What are Onion Rings?" + }, + { + "name": "Oysters", + "description": "Shellfish often served raw on the half shell, sometimes with lemon juice or a mignonette sauce.", + "question": "What are Oysters?" + }, + { + "name": "Pad Thai", + "description": "Thai stir-fried rice noodles with eggs, tofu, shrimp, peanuts, and bean sprouts.", + "question": "What is Pad Thai?" + }, + { + "name": "Paella", + "description": "Spanish rice dish made with saffron, seafood, chicken, and/or vegetables.", + "question": "What is Paella?" + }, + { + "name": "Pancakes", + "description": "Flat cakes made from batter and fried in a pan, often served with syrup or toppings.", + "question": "What are Pancakes?" + }, + { + "name": "Panna Cotta", + "description": "Italian dessert made with cream, milk, sugar, and gelatin, often flavored with vanilla.", + "question": "What is Panna Cotta?" + }, + { + "name": "Peking Duck", + "description": "Chinese dish of roasted duck with crispy skin, often served with pancakes, hoisin sauce, and scallions.", + "question": "What is Peking Duck?" + }, + { + "name": "Pho", + "description": "Vietnamese noodle soup with broth, rice noodles, herbs, and meat (typically beef or chicken).", + "question": "What is Pho?" + }, + { + "name": "Pizza", + "description": "Italian dish consisting of a round, flat base of dough topped with tomato sauce, cheese, and various toppings.", + "question": "What is Pizza?" + }, + { + "name": "Pork Chop", + "description": "Cut of pork loin or rib, often grilled, fried, or baked.", + "question": "What is a Pork Chop?" + }, + { + "name": "Poutine", + "description": "Canadian dish of fries topped with cheese curds and gravy.", + "question": "What is Poutine?" + }, + { + "name": "Prime Rib", + "description": "Roast beef cut from the rib section, often served with au jus.", + "question": "What is Prime Rib?" + }, + { + "name": "Pulled Pork Sandwich", + "description": "Sandwich made with shredded, slow-cooked pork, typically served with barbecue sauce.", + "question": "What is a Pulled Pork Sandwich?" + }, + { + "name": "Ramen", + "description": "Japanese noodle soup dish with broth, noodles, meat (often pork), and toppings like egg and nori.", + "question": "What is Ramen?" + }, + { + "name": "Ravioli", + "description": "Italian pasta parcels filled with cheese, meat, or vegetables, typically served with sauce.", + "question": "What are Ravioli?" + }, + { + "name": "Red Velvet Cake", + "description": "Moist, slightly chocolate-flavored cake with cream cheese frosting, often dyed red.", + "question": "What is Red Velvet Cake?" + }, + { + "name": "Risotto", + "description": "Italian rice dish cooked with broth until creamy, often flavored with cheese and vegetables or meat.", + "question": "What is Risotto?" + }, + { + "name": "Samosa", + "description": "Indian pastry filled with spiced potatoes, peas, and sometimes meat, fried until crispy.", + "question": "What is Samosa?" + }, + { + "name": "Sashimi", + "description": "Japanese dish of thinly sliced raw fish or seafood, served with soy sauce and wasabi.", + "question": "What is Sashimi?" + }, + { + "name": "Scallops", + "description": "Shellfish with a delicate texture, often pan-seared or grilled.", + "question": "What are Scallops?" + }, + { + "name": "Seaweed Salad", + "description": "Japanese salad made with seaweed, typically dressed with sesame oil, soy sauce, and vinegar.", + "question": "What is Seaweed Salad?" + }, + { + "name": "Shrimp and Grits", + "description": "Southern dish of shrimp served over creamy grits, often seasoned with bacon and spices.", + "question": "What are Shrimp and Grits?" + }, + { + "name": "Spaghetti Bolognese", + "description": "Italian pasta dish with meat sauce made from ground beef, tomatoes, and herbs, served over spaghetti.", + "question": "What is Spaghetti Bolognese?" + }, + { + "name": "Spaghetti Carbonara", + "description": "Italian pasta dish made with eggs, cheese (typically Pecorino Romano), pancetta, and black pepper.", + "question": "What is Spaghetti Carbonara?" + }, + { + "name": "Spring Rolls", + "description": "Asian appetizer consisting of vegetables, sometimes meat, and sometimes noodles rolled in a thin wrapper and fried or served fresh.", + "question": "What are Spring Rolls?" + }, + { + "name": "Steak", + "description": "High-quality cut of beef cooked to preference, often grilled or pan-seared.", + "question": "What is Steak?" + }, + { + "name": "Strawberry Shortcake", + "description": "Dessert made with layers of shortcake, whipped cream, and fresh strawberries.", + "question": "What is Strawberry Shortcake?" + }, + { + "name": "Sushi", + "description": "Japanese dish of vinegared rice combined with various ingredients such as seafood, vegetables, and occasionally tropical fruits.", + "question": "What is Sushi?" + }, + { + "name": "Tacos", + "description": "Mexican dish consisting of a folded or rolled tortilla filled with various ingredients, typically including meat, beans, lettuce, and salsa.", + "question": "What are Tacos?" + }, + { + "name": "Takoyaki", + "description": "Japanese snack made of battered octopus pieces cooked in a special molded pan, typically served with takoyaki sauce and bonito flakes.", + "question": "What is Takoyaki?" + }, + { + "name": "Tiramisu", + "description": "Italian dessert made of layers of coffee-soaked ladyfingers, mascarpone cheese, and cocoa powder.", + "question": "What is Tiramisu?" + }, + { + "name": "Tuna Tartare", + "description": "Dish made with raw tuna finely chopped or minced, often seasoned with ingredients like soy sauce, sesame oil, and avocado.", + "question": "What is Tuna Tartare?" + } + ] + } +] diff --git a/api/chatbot/minsearch.py b/api/chatbot/minsearch.py new file mode 100644 index 00000000..92a46294 --- /dev/null +++ b/api/chatbot/minsearch.py @@ -0,0 +1,96 @@ +import pandas as pd + +from sklearn.feature_extraction.text import TfidfVectorizer +from sklearn.metrics.pairwise import cosine_similarity + +import numpy as np + + +class Index: + """ + A simple search index using TF-IDF and cosine similarity for text fields and exact matching for keyword fields. + + Attributes: + text_fields (list): List of text field names to index. + keyword_fields (list): List of keyword field names to index. + vectorizers (dict): Dictionary of TfidfVectorizer instances for each text field. + keyword_df (pd.DataFrame): DataFrame containing keyword field data. + text_matrices (dict): Dictionary of TF-IDF matrices for each text field. + docs (list): List of documents indexed. + """ + + def __init__(self, text_fields, keyword_fields, vectorizer_params={}): + """ + Initializes the Index with specified text and keyword fields. + + Args: + text_fields (list): List of text field names to index. + keyword_fields (list): List of keyword field names to index. + vectorizer_params (dict): Optional parameters to pass to TfidfVectorizer. + """ + self.text_fields = text_fields + self.keyword_fields = keyword_fields + + self.vectorizers = {field: TfidfVectorizer(**vectorizer_params) for field in text_fields} + self.keyword_df = None + self.text_matrices = {} + self.docs = [] + + def fit(self, docs): + """ + Fits the index with the provided documents. + + Args: + docs (list of dict): List of documents to index. Each document is a dictionary. + """ + self.docs = docs + keyword_data = {field: [] for field in self.keyword_fields} + + for field in self.text_fields: + texts = [doc.get(field, '') for doc in docs] + self.text_matrices[field] = self.vectorizers[field].fit_transform(texts) + + for doc in docs: + for field in self.keyword_fields: + keyword_data[field].append(doc.get(field, '')) + + self.keyword_df = pd.DataFrame(keyword_data) + + return self + + def search(self, query, filter_dict={}, boost_dict={}, num_results=10): + """ + Searches the index with the given query, filters, and boost parameters. + + Args: + query (str): The search query string. + filter_dict (dict): Dictionary of keyword fields to filter by. Keys are field names and values are the values to filter by. + boost_dict (dict): Dictionary of boost scores for text fields. Keys are field names and values are the boost scores. + num_results (int): The number of top results to return. Defaults to 10. + + Returns: + list of dict: List of documents matching the search criteria, ranked by relevance. + """ + query_vecs = {field: self.vectorizers[field].transform([query]) for field in self.text_fields} + scores = np.zeros(len(self.docs)) + + # Compute cosine similarity for each text field and apply boost + for field, query_vec in query_vecs.items(): + sim = cosine_similarity(query_vec, self.text_matrices[field]).flatten() + boost = boost_dict.get(field, 1) + scores += sim * boost + + # Apply keyword filters + for field, value in filter_dict.items(): + if field in self.keyword_fields: + mask = self.keyword_df[field] == value + scores = scores * mask.to_numpy() + + # Use argpartition to get top num_results indices + top_indices = np.argpartition(scores, -num_results)[-num_results:] + top_indices = top_indices[np.argsort(-scores[top_indices])] + + # Filter out zero-score results + top_docs = [self.docs[i] for i in top_indices if scores[i] > 0] + + return top_docs \ No newline at end of file diff --git a/api/chatbot/requirements.txt b/api/chatbot/requirements.txt new file mode 100644 index 00000000..df22921f --- /dev/null +++ b/api/chatbot/requirements.txt @@ -0,0 +1,7 @@ +pandas +scikit-learn +g4f +curl-cffi +uvicorn +fastapi +nest_asyncio \ No newline at end of file diff --git a/api/psqlserver/requirements.txt b/api/psqlserver/requirements.txt index d86ce988..e9aecc34 100644 --- a/api/psqlserver/requirements.txt +++ b/api/psqlserver/requirements.txt @@ -9,3 +9,4 @@ Unidecode wikipedia pytz g4f +curl_cffi \ No newline at end of file diff --git a/api/textai_server/Dockerfile b/api/textai_server/Dockerfile new file mode 100644 index 00000000..60b1434d --- /dev/null +++ b/api/textai_server/Dockerfile @@ -0,0 +1,11 @@ +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8 + +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt +RUN export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python + +COPY . . + +EXPOSE 8123 + +CMD ["python3", "app.py"] \ No newline at end of file diff --git a/api/textai_server/app.py b/api/textai_server/app.py index 637b502f..cb89b57c 100644 --- a/api/textai_server/app.py +++ b/api/textai_server/app.py @@ -3,16 +3,15 @@ from fastapi.middleware.cors import CORSMiddleware from starlette.staticfiles import StaticFiles from fastapi.responses import HTMLResponse +from g4f.client import Client import g4f import uvicorn +import asyncio +import nest_asyncio + +nest_asyncio.apply() + app = FastAPI() -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], # Thay đổi thành nguồn của ứng dụng JavaScript của bạn - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) _providers = [ g4f.Provider.Aichat, @@ -23,18 +22,30 @@ g4f.Provider.Yqcloud, ] +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # Thay đổi thành nguồn của ứng dụng JavaScript của bạn + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) @app.get("/generate_description/") async def generate_description(food_name: str = Query(..., title="Food Name")): t = f'As a waiter of a restaurant, generate a description of {food_name} in a non-scientific way. The description should be easy to understand for normal people. The description must be written in Vietnamese with a language that is close to human language as possible. Avoid descriptive and robotic descriptions. The description length must be limited to 1 paragraph and must not exceed 30 words. Do not add recommendations in the last sentence.' - - response = g4f.ChatCompletion.create( - model=g4f.models.default, - messages=[{"role": "user", "content": t}], + client = Client() + chat_completion = client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": t}], + ignored=["Ylokh", "GptGo", "AItianhu", "Aibn", "Myshell", "FreeGpt"], + stream=True ) + response = "" + for completion in chat_completion: + response += completion.choices[0].delta.content or "" + return {"description": response} - -if __name__ == '__main__': - uvicorn.run('app:app', port=8123, host='0.0.0.0') +if __name__ == '__main__': + uvicorn.run('app:app', port=8123, host='0.0.0.0', loop='asyncio') diff --git a/api/textai_server/requirements.txt b/api/textai_server/requirements.txt index 1b92c771..6d26a84d 100644 --- a/api/textai_server/requirements.txt +++ b/api/textai_server/requirements.txt @@ -3,4 +3,6 @@ pathlib2 requests fastapi protobuf==3.20 -g4f==0.1.8.7 +g4f +curl-cffi +nest_asyncio diff --git a/docker-compose.yml b/docker-compose.yml index de14e3da..45b97568 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: - SA_PASSWORD=sa@123456 - MSSQL_PID=Developer ports: - - 1433 + - "1433:1433" volumes: - sqlserver_data:/var/opt/mssql restart: always @@ -36,11 +36,10 @@ services: networks: - ffood-network - ai-food-classification: build: ./api/Food_Classifier ports: - - 8000:8000 + - "8000:8000" depends_on: - sqlserver - sqlserver.configurator @@ -51,25 +50,35 @@ services: psql-server: build: ./api/psqlserver ports: - - 8001:8001 + - "8001:8001" depends_on: - sqlserver - sqlserver.configurator restart: always networks: - ffood-network - - # textai_server: - # build: ./api/textai_server - # ports: - # - 8123:8123 - # depends_on: - # - sqlserver - # - sqlserver.configurator - # restart: always - # networks: - # - ffood-network + chatbot_rag: + build: ./api/chatbot + ports: + - "8100:8100" + depends_on: + - sqlserver + - sqlserver.configurator + restart: always + networks: + - ffood-network + + textai_server: + build: ./api/textai_server + ports: + - "8123:8123" + depends_on: + - sqlserver + - sqlserver.configurator + restart: always + networks: + - ffood-network tomcat-app: build: @@ -89,8 +98,5 @@ networks: ffood-network: name: ffood-network - volumes: sqlserver_data: - - diff --git a/src/main/webapp/WEB-INF/jspf/admin/orders.jspf b/src/main/webapp/WEB-INF/jspf/admin/orders.jspf index f66c95d7..062b8c4e 100644 --- a/src/main/webapp/WEB-INF/jspf/admin/orders.jspf +++ b/src/main/webapp/WEB-INF/jspf/admin/orders.jspf @@ -86,7 +86,7 @@ diff --git a/src/main/webapp/WEB-INF/jspf/common/components/chatbox.jspf b/src/main/webapp/WEB-INF/jspf/common/components/chatbox.jspf new file mode 100644 index 00000000..ae0223fa --- /dev/null +++ b/src/main/webapp/WEB-INF/jspf/common/components/chatbox.jspf @@ -0,0 +1,30 @@ + +
+
+
+
+ image +
+
+

Chat support

+

Hi. My name is Sam. How can I help you?

+
+ +
+ +
+
+
+
+
+ +
+
+ +
+
diff --git a/src/main/webapp/assets/css/style.css b/src/main/webapp/assets/css/style.css index 2b9fb6ac..7337a920 100644 --- a/src/main/webapp/assets/css/style.css +++ b/src/main/webapp/assets/css/style.css @@ -38,3 +38,209 @@ td.highlight { height: 100%; width: 99%; /* 100% breaks the chart responsiveness*/ } + +/* CHATBOX +=============== */ + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: 'Nunito', sans-serif; + font-weight: 400; + font-size: 100%; + background: #F1F1F1; +} + +*, html { + --primaryGradient: linear-gradient(93.12deg, #dc6f13 0.52%, #ed9146 100%); + --secondaryGradient: linear-gradient(268.91deg, #dc6f13 -2.14%, #ed9146 99.69%); + --primaryBoxShadow: 0px 10px 15px rgba(0, 0, 0, 0.1); + --secondaryBoxShadow: 0px -10px 15px rgba(0, 0, 0, 0.1); + --primary: #dc6f13; +} + +/* CHATBOX +=============== */ +.chatbox { + position: fixed; + bottom: 30px; + right: 30px; + z-index: 1000; +} + +/* CONTENT IS CLOSE */ +.chatbox__support { + display: flex; + flex-direction: column; + background: #eee; + width: 300px; + height: 350px; + z-index: -123456; + opacity: 0; + transition: all .5s ease-in-out; +} + +/* CONTENT ISOPEN */ +.chatbox--active { + transform: translateY(-40px); + z-index: 123456; + opacity: 1; + +} + +/* BUTTON */ +.chatbox__button { + text-align: right; +} + +.send__button { + padding: 6px; + background: transparent; + border: none; + outline: none; + cursor: pointer; +} + + +/* HEADER */ +.chatbox__header { + position: sticky; + top: 0; + background: orange; +} + +/* MESSAGES */ +.chatbox__messages { + margin-top: auto; + display: flex; + overflow-y: scroll; + flex-direction: column-reverse; +} + +.messages__item { + background: orange; + max-width: 60.6%; + width: fit-content; +} + +.messages__item--operator { + margin-left: auto; +} + +.messages__item--visitor { + margin-right: auto; +} + +/* FOOTER */ +.chatbox__footer { + position: sticky; + bottom: 0; +} + +.chatbox__support { + background: #f9f9f9; + height: 450px; + width: 350px; + box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1); + border-top-left-radius: 20px; + border-top-right-radius: 20px; +} + +/* HEADER */ +.chatbox__header { + background: var(--primaryGradient); + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 15px 20px; + border-top-left-radius: 20px; + border-top-right-radius: 20px; + box-shadow: var(--primaryBoxShadow); +} + +.chatbox__image--header { + margin-right: 10px; +} + +.chatbox__heading--header { + font-size: 1.2rem; + color: white; +} + +.chatbox__description--header { + font-size: .9rem; + color: white; +} + +/* Messages */ +.chatbox__messages { + padding: 0 20px; +} + +.messages__item { + margin-top: 10px; + background: #E0E0E0; + padding: 8px 12px; + max-width: 70%; +} + +.messages__item--visitor, +.messages__item--typing { + border-top-left-radius: 20px; + border-top-right-radius: 20px; + border-bottom-right-radius: 20px; +} + +.messages__item--operator { + border-top-left-radius: 20px; + border-top-right-radius: 20px; + border-bottom-left-radius: 20px; + background: var(--primary); + color: white; +} + +/* FOOTER */ +.chatbox__footer { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 20px 20px; + background: var(--secondaryGradient); + box-shadow: var(--secondaryBoxShadow); + border-bottom-right-radius: 10px; + border-bottom-left-radius: 10px; + margin-top: 20px; +} + +.chatbox__footer input { + width: 80%; + border: none; + padding: 10px 10px; + border-radius: 30px; + text-align: left; +} + +.chatbox__send--footer { + color: white; +} + +.chatbox__button button, +.chatbox__button button:focus, +.chatbox__button button:visited { + padding: 10px; + background: white; + border: none; + outline: none; + border-top-left-radius: 50px; + border-top-right-radius: 50px; + border-bottom-left-radius: 50px; + box-shadow: 0px 10px 15px rgba(0, 0, 0, 0.1); + cursor: pointer; +} + diff --git a/src/main/webapp/assets/js/chatbot.js b/src/main/webapp/assets/js/chatbot.js new file mode 100644 index 00000000..1e3e35d2 --- /dev/null +++ b/src/main/webapp/assets/js/chatbot.js @@ -0,0 +1,85 @@ +class Chatbox { + constructor() { + this.args = { + openButton: document.querySelector('.chatbox__button'), + chatBox: document.querySelector('.chatbox__support'), + sendButton: document.querySelector('.send__button') + }; + + this.state = false; + this.messages = []; + } + + display() { + const { openButton, chatBox, sendButton } = this.args; + + openButton.addEventListener('click', () => this.toggleState(chatBox)); + sendButton.addEventListener('click', () => this.onSendButton(chatBox)); + + const node = chatBox.querySelector('input'); + node.addEventListener("keyup", ({ key }) => { + if (key === "Enter") { + this.onSendButton(chatBox); + } + }); + } + + toggleState(chatbox) { + this.state = !this.state; + + if (this.state) { + chatbox.classList.add('chatbox--active'); + } else { + chatbox.classList.remove('chatbox--active'); + } + } + + onSendButton(chatbox) { + const textField = chatbox.querySelector('input'); + const text1 = textField.value; + if (text1 === "") { + return; + } + + const msg1 = { name: "User", message: text1 }; + this.messages.push(msg1); + + fetch('http://localhost:8100/rag/?query=' + encodeURIComponent(text1), { + method: 'GET', + mode: 'cors', + headers: { + 'Content-Type': 'application/json' + }, + }) + .then(response => response.json()) + .then(data => { + const msg2 = { name: "Bot", message: data.answer.response }; + console.log(data.answer.response); + this.messages.push(msg2); + this.updateChatText(chatbox); + textField.value = ''; + }) + .catch(error => { + console.error('Error:', error); + this.updateChatText(chatbox); + textField.value = ''; + }); + } + + updateChatText(chatbox) { + let html = ''; + this.messages.slice().reverse().forEach(item => { + if (item.name === "Bot") { + html += '
' + item.message + '
'; + } else { + html += '
' + item.message + '
'; + } + }); + + const chatmessage = chatbox.querySelector('.chatbox__messages'); + chatmessage.innerHTML = html; + } +} + +const chatbox = new Chatbox(); +chatbox.display(); diff --git a/src/main/webapp/assets/js/home.js b/src/main/webapp/assets/js/home.js index 6b2de3ce..b6779357 100644 --- a/src/main/webapp/assets/js/home.js +++ b/src/main/webapp/assets/js/home.js @@ -384,3 +384,4 @@ function searchFoodByKeyword() { .catch((error) => console.error(error)); }, 300); } + diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp index 18bfdaaa..d785f76d 100644 --- a/src/main/webapp/index.jsp +++ b/src/main/webapp/index.jsp @@ -73,13 +73,16 @@ <%@ include file="WEB-INF/jspf/guest/components/ctaOrderBanner.jspf" %> - + <%@ include file="WEB-INF/jspf/common/components/footer.jspf" %> + <%@ include file="WEB-INF/jspf/common/components/chatbox.jspf" %> <%@ include file="WEB-INF/jspf/common/imports/javascript.jspf" %> <%@ include file="WEB-INF/jspf/common/imports/validation.jspf" %> + +