Spaces:
Sleeping
Sleeping
Upload 5 files
Browse files- .gitignore +40 -0
- Chatbotcontent.txt +137 -0
- Dockerfile +37 -0
- app.py +115 -0
- templates/index.html +233 -0
.gitignore
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
env/
|
| 8 |
+
build/
|
| 9 |
+
develop-eggs/
|
| 10 |
+
dist/
|
| 11 |
+
downloads/
|
| 12 |
+
eggs/
|
| 13 |
+
.eggs/
|
| 14 |
+
lib/
|
| 15 |
+
lib64/
|
| 16 |
+
parts/
|
| 17 |
+
sdist/
|
| 18 |
+
var/
|
| 19 |
+
*.egg-info/
|
| 20 |
+
.installed.cfg
|
| 21 |
+
*.egg
|
| 22 |
+
|
| 23 |
+
# Virtual Environment
|
| 24 |
+
venv/
|
| 25 |
+
.env
|
| 26 |
+
|
| 27 |
+
# IDE
|
| 28 |
+
.idea/
|
| 29 |
+
.vscode/
|
| 30 |
+
*.swp
|
| 31 |
+
*.swo
|
| 32 |
+
|
| 33 |
+
# Project specific
|
| 34 |
+
model_cache/
|
| 35 |
+
faiss_index/
|
| 36 |
+
*.pkl
|
| 37 |
+
hf_cache/
|
| 38 |
+
|
| 39 |
+
# Logs
|
| 40 |
+
*.log
|
Chatbotcontent.txt
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Company Name: Kairo Digital
|
| 2 |
+
|
| 3 |
+
Overview:
|
| 4 |
+
Kairo Digital is your one-stop creative powerhouse for everything from stunning websites to scroll-worthy content. We specialize in crafting digital experiences that look beautiful and perform flawlessly. Founded in 2025, Kairo Digital is committed to transforming businesses into impactful digital brands through a combination of strategy, storytelling, and smart technology.
|
| 5 |
+
|
| 6 |
+
We are more than a service provider—we are your creative collaborators. Our work blends design with performance, innovation with consistency, and art with business outcomes.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
Core Services:
|
| 11 |
+
|
| 12 |
+
1. Website Development
|
| 13 |
+
Build Your Online Presence.
|
| 14 |
+
|
| 15 |
+
We craft sleek, fast, and scalable websites tailored to meet your business goals. Whether you need a personal portfolio, corporate site, or a full-fledged e-commerce platform, we design web experiences that are:
|
| 16 |
+
- Mobile-friendly and responsive
|
| 17 |
+
- Visually striking yet functional
|
| 18 |
+
- Optimized for SEO and performance
|
| 19 |
+
- Easy to manage and update
|
| 20 |
+
|
| 21 |
+
Our development process focuses on user experience (UX), speed, and business conversion, ensuring your site isn’t just pretty—it performs.
|
| 22 |
+
|
| 23 |
+
2. Social Media Marketing
|
| 24 |
+
Engage and GROW.
|
| 25 |
+
|
| 26 |
+
Our social media strategies are designed to do more than get likes—we help you build real relationships with your audience that drive results. We manage everything from content creation to campaign execution, including:
|
| 27 |
+
- Strategy tailored to your brand
|
| 28 |
+
- Platform-specific content plans (Instagram, Facebook, LinkedIn, etc.)
|
| 29 |
+
- Branded posts, reels, and stories
|
| 30 |
+
- Analytics and performance tracking
|
| 31 |
+
|
| 32 |
+
We believe in organic growth fueled by consistency, creativity, and deep understanding of your audience.
|
| 33 |
+
|
| 34 |
+
3. Video Production
|
| 35 |
+
Bring Your Brand Story to Life.
|
| 36 |
+
|
| 37 |
+
Video is the most powerful medium today—and we use it to tell your story in a way that connects, inspires, and converts. Our video production services include:
|
| 38 |
+
- Brand introduction videos
|
| 39 |
+
- Social media reels and shorts
|
| 40 |
+
- Corporate/promotional films
|
| 41 |
+
- Event and product videos
|
| 42 |
+
|
| 43 |
+
Each video is conceptualized, scripted, shot, and edited with precision to reflect your brand's essence and grab attention in seconds.
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
Our Vision:
|
| 48 |
+
To be the digital agency that ambitious brands trust — known for our bold ideas, flawless design, and unmatched storytelling. We envision a digital future where creativity meets technology to fuel real business growth, and we’re here to lead that future with innovative, world-class digital services.
|
| 49 |
+
|
| 50 |
+
Our Mission:
|
| 51 |
+
To empower brands to thrive in the digital landscape through intelligent, visually compelling, and result-oriented solutions. Whether it's a website, a video, or a marketing strategy, our focus is on building meaningful connections between brands and their audiences.
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
Who Are We?
|
| 56 |
+
|
| 57 |
+
At Kairo Digital, we’re a tight-knit team of creators, developers, strategists, and storytellers who love turning ideas into impact. We don’t just make things look good—we make them work smart.
|
| 58 |
+
|
| 59 |
+
We combine:
|
| 60 |
+
- Artistry with analytics
|
| 61 |
+
- Visual design with technical strength
|
| 62 |
+
- Storytelling with strategy
|
| 63 |
+
To deliver complete digital experiences.
|
| 64 |
+
|
| 65 |
+
---
|
| 66 |
+
|
| 67 |
+
Why Choose Kairo Digital?
|
| 68 |
+
|
| 69 |
+
- We don’t just deliver services—we co-create with you as partners.
|
| 70 |
+
- We blend creativity with functionality to make your brand memorable.
|
| 71 |
+
- We focus on performance, from your website speed to your video’s engagement rate.
|
| 72 |
+
- Every piece of work is driven by results—whether that’s conversions, visibility, or engagement.
|
| 73 |
+
|
| 74 |
+
In short: We’re not just building websites, strategies, or videos — we’re building meaningful digital experiences that grow your business.
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
|
| 78 |
+
Highlights of Our Services:
|
| 79 |
+
|
| 80 |
+
Website Development:
|
| 81 |
+
- Mobile-first design
|
| 82 |
+
- E-commerce integration (Shopify, WooCommerce)
|
| 83 |
+
- SEO-ready structure
|
| 84 |
+
- CMS flexibility (WordPress, custom builds)
|
| 85 |
+
|
| 86 |
+
Social Media Marketing:
|
| 87 |
+
- Reels, posts, and stories tailored to trends
|
| 88 |
+
- Paid ad strategy and management
|
| 89 |
+
- Performance analytics and reports
|
| 90 |
+
- Monthly growth strategies
|
| 91 |
+
|
| 92 |
+
Video Production:
|
| 93 |
+
- Script to screen service
|
| 94 |
+
- Social media optimized edits
|
| 95 |
+
- On-location shoots and interviews
|
| 96 |
+
- Emotional storytelling through visuals
|
| 97 |
+
|
| 98 |
+
---
|
| 99 |
+
|
| 100 |
+
Aathithya, a name that shines so bright,
|
| 101 |
+
A guiding star, a source of light.
|
| 102 |
+
With wisdom deep and heart so true,
|
| 103 |
+
The world feels warmer because of you.
|
| 104 |
+
|
| 105 |
+
Frequently Asked Questions (FAQs):
|
| 106 |
+
|
| 107 |
+
Q: What does Kairo Digital do?
|
| 108 |
+
A: We offer website development, social media marketing, and video production services for brands looking to grow digitally.
|
| 109 |
+
|
| 110 |
+
Q: What industries do you work with?
|
| 111 |
+
A: We work with startups, small businesses, personal brands, service companies, and e-commerce businesses across various industries.
|
| 112 |
+
|
| 113 |
+
Q: Can you manage my entire digital presence?
|
| 114 |
+
A: Yes. We can handle everything from website creation to managing your social media and producing videos for your campaigns.
|
| 115 |
+
|
| 116 |
+
Q: How is your service different from others?
|
| 117 |
+
A: We don’t just deliver creative work—we ensure it performs. Everything we do is based on a blend of strategy, creativity, and business insight.
|
| 118 |
+
|
| 119 |
+
Q: How can I get started?
|
| 120 |
+
A: Just contact us via email or Instagram, or fill the form on our website for a free consultation.
|
| 121 |
+
|
| 122 |
+
---
|
| 123 |
+
|
| 124 |
+
Contact Information:
|
| 125 |
+
Email: kairoxdigital@gmail.com
|
| 126 |
+
Instagram: @kairo_digital_
|
| 127 |
+
Website: https://kairodigital.in/
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
Relevant Keywords and Tags (for search indexing):
|
| 132 |
+
digital agency, website design, website development, social media marketing, video production, brand storytelling, SEO websites, digital marketing, Kairo Digital, content creation, reels for business, branding, creative agency Chennai, visual marketing, e-commerce website builder
|
| 133 |
+
|
| 134 |
+
---
|
| 135 |
+
|
| 136 |
+
Date of Establishment: 2025
|
| 137 |
+
Based in: India
|
Dockerfile
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.12-slim
|
| 2 |
+
|
| 3 |
+
# Set working directory
|
| 4 |
+
WORKDIR /code
|
| 5 |
+
|
| 6 |
+
# Install system dependencies
|
| 7 |
+
RUN apt-get update && apt-get install -y \
|
| 8 |
+
build-essential \
|
| 9 |
+
python3-dev \
|
| 10 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 11 |
+
|
| 12 |
+
# Copy requirements first for better cache
|
| 13 |
+
COPY requirements.txt .
|
| 14 |
+
|
| 15 |
+
# Install Python dependencies
|
| 16 |
+
RUN pip install --no-cache-dir -r requirements.txt \
|
| 17 |
+
&& pip install --no-cache-dir \
|
| 18 |
+
sentence-transformers \
|
| 19 |
+
langchain-community \
|
| 20 |
+
faiss-cpu
|
| 21 |
+
|
| 22 |
+
# Copy application code
|
| 23 |
+
COPY . .
|
| 24 |
+
|
| 25 |
+
# Create necessary directories
|
| 26 |
+
RUN mkdir -p model_cache faiss_index
|
| 27 |
+
|
| 28 |
+
# Make sure the app creates files with correct permissions
|
| 29 |
+
ENV PYTHONUNBUFFERED=1
|
| 30 |
+
ENV HOST=0.0.0.0
|
| 31 |
+
ENV PORT=7860
|
| 32 |
+
|
| 33 |
+
# Expose port for HF Spaces
|
| 34 |
+
EXPOSE 7860
|
| 35 |
+
|
| 36 |
+
# Run the application
|
| 37 |
+
CMD ["python", "app.py"]
|
app.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import time
|
| 4 |
+
import pickle
|
| 5 |
+
from langchain_community.vectorstores import FAISS
|
| 6 |
+
from langchain.text_splitter import CharacterTextSplitter
|
| 7 |
+
from langchain_huggingface import HuggingFaceEmbeddings
|
| 8 |
+
from langchain.docstore.document import Document
|
| 9 |
+
from flask import Flask, request, jsonify, render_template
|
| 10 |
+
import requests
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
|
| 13 |
+
# Load environment variables from .env file
|
| 14 |
+
load_dotenv()
|
| 15 |
+
|
| 16 |
+
# Get API key from environment variable (HF Spaces sets this)
|
| 17 |
+
API_KEY = os.getenv("OPENROUTER_API_KEY")
|
| 18 |
+
if not API_KEY:
|
| 19 |
+
raise RuntimeError("OPENROUTER_API_KEY environment variable not set")
|
| 20 |
+
|
| 21 |
+
HEADERS = {
|
| 22 |
+
"Authorization": f"Bearer {API_KEY}",
|
| 23 |
+
"Content-Type": "application/json",
|
| 24 |
+
"HTTP-Referer": "https://github.com/your-username/KairoAPI", # Replace with your actual repository
|
| 25 |
+
"X-Title": "KairoAPI ChatBot",
|
| 26 |
+
"OpenRouter-Bypass-Key": API_KEY # Add bypass key to avoid prompt logging
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
# Load or preprocess documents
|
| 30 |
+
if os.path.exists("preprocessed_docs.pkl"):
|
| 31 |
+
with open("preprocessed_docs.pkl", "rb") as f:
|
| 32 |
+
documents = pickle.load(f)
|
| 33 |
+
else:
|
| 34 |
+
with open("Chatbotcontent.txt", "r", encoding="utf-8") as file:
|
| 35 |
+
content = file.read()
|
| 36 |
+
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
|
| 37 |
+
docs = text_splitter.split_text(content)
|
| 38 |
+
documents = [Document(page_content=doc) for doc in docs]
|
| 39 |
+
with open("preprocessed_docs.pkl", "wb") as f:
|
| 40 |
+
pickle.dump(documents, f)
|
| 41 |
+
|
| 42 |
+
# Initialize the embedding model
|
| 43 |
+
embedding_model = HuggingFaceEmbeddings(
|
| 44 |
+
model_name="intfloat/multilingual-e5-small",
|
| 45 |
+
cache_folder="./model_cache"
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
# Load or create FAISS index
|
| 49 |
+
if os.path.exists("faiss_index"):
|
| 50 |
+
vectorstore = FAISS.load_local("faiss_index", embedding_model, allow_dangerous_deserialization=True)
|
| 51 |
+
else:
|
| 52 |
+
vectorstore = FAISS.from_documents(documents, embedding_model)
|
| 53 |
+
vectorstore.save_local("faiss_index")
|
| 54 |
+
|
| 55 |
+
# System prompt
|
| 56 |
+
SYSTEM_PROMPT = """<|system|>Reasoning: ON. You are a helpful assistant representing our organization.
|
| 57 |
+
Always answer in a clean, first-person tone like a knowledgeable member of the team.
|
| 58 |
+
Avoid explaining reasoning steps, just give direct answers.<|end|>
|
| 59 |
+
"""
|
| 60 |
+
|
| 61 |
+
conversation_history = [
|
| 62 |
+
{"role": "system", "content": SYSTEM_PROMPT}
|
| 63 |
+
]
|
| 64 |
+
|
| 65 |
+
# Flask App
|
| 66 |
+
app = Flask(__name__)
|
| 67 |
+
|
| 68 |
+
@app.route("/")
|
| 69 |
+
def index():
|
| 70 |
+
return render_template("index.html")
|
| 71 |
+
|
| 72 |
+
@app.route("/chat", methods=["POST"])
|
| 73 |
+
def chat():
|
| 74 |
+
user_input = request.json.get("message")
|
| 75 |
+
if not user_input:
|
| 76 |
+
return jsonify({"error": "No input provided."}), 400
|
| 77 |
+
|
| 78 |
+
docs = vectorstore.similarity_search(user_input, k=3)
|
| 79 |
+
context = "\n\n".join([doc.page_content for doc in docs])
|
| 80 |
+
|
| 81 |
+
user_prompt = f"""Using the following context, answer the user's question in a clean and natural tone like you're part of the organization. Avoid showing reasoning steps. Be brief but informative.
|
| 82 |
+
|
| 83 |
+
<context>
|
| 84 |
+
{context}
|
| 85 |
+
</context>
|
| 86 |
+
|
| 87 |
+
Question: {user_input}
|
| 88 |
+
"""
|
| 89 |
+
conversation_history.append({"role": "user", "content": user_prompt})
|
| 90 |
+
|
| 91 |
+
payload = {
|
| 92 |
+
"model": "openai/gpt-3.5-turbo", # Using a more reliable model
|
| 93 |
+
"temperature": 0.7,
|
| 94 |
+
"top_p": 0.95,
|
| 95 |
+
"max_tokens": 1024,
|
| 96 |
+
"stream": False,
|
| 97 |
+
"messages": conversation_history[-8:],
|
| 98 |
+
"transforms": ["no-tokens"] # Disable token logging
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
try:
|
| 102 |
+
response = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=HEADERS, data=json.dumps(payload), timeout=10)
|
| 103 |
+
if response.status_code == 200:
|
| 104 |
+
reply = response.json()["choices"][0]["message"]["content"]
|
| 105 |
+
conversation_history.append({"role": "assistant", "content": reply})
|
| 106 |
+
return jsonify({"response": reply})
|
| 107 |
+
else:
|
| 108 |
+
return jsonify({"error": f"Error {response.status_code}: {response.text}"}), 500
|
| 109 |
+
except requests.exceptions.Timeout:
|
| 110 |
+
return jsonify({"error": "Request timed out (10s limit exceeded)"}), 504
|
| 111 |
+
except Exception as e:
|
| 112 |
+
return jsonify({"error": f"Unexpected error: {str(e)}"}), 500
|
| 113 |
+
|
| 114 |
+
if __name__ == "__main__":
|
| 115 |
+
app.run(host="0.0.0.0", port=7860)
|
templates/index.html
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
+
<title>Kairo RAG Chatbot</title>
|
| 7 |
+
<style>
|
| 8 |
+
/* Basic reset */
|
| 9 |
+
* {
|
| 10 |
+
box-sizing: border-box;
|
| 11 |
+
}
|
| 12 |
+
body {
|
| 13 |
+
margin: 0;
|
| 14 |
+
background-color: #121212;
|
| 15 |
+
color: #eee;
|
| 16 |
+
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
| 17 |
+
display: flex;
|
| 18 |
+
flex-direction: column;
|
| 19 |
+
height: 100vh;
|
| 20 |
+
overflow: hidden;
|
| 21 |
+
}
|
| 22 |
+
header {
|
| 23 |
+
background: #0a74da;
|
| 24 |
+
padding: 1rem;
|
| 25 |
+
font-size: 1.5rem;
|
| 26 |
+
font-weight: bold;
|
| 27 |
+
color: white;
|
| 28 |
+
text-align: center;
|
| 29 |
+
user-select: none;
|
| 30 |
+
}
|
| 31 |
+
main {
|
| 32 |
+
flex: 1;
|
| 33 |
+
padding: 1rem;
|
| 34 |
+
display: flex;
|
| 35 |
+
flex-direction: column;
|
| 36 |
+
overflow-y: auto;
|
| 37 |
+
gap: 1rem;
|
| 38 |
+
}
|
| 39 |
+
.chat-message {
|
| 40 |
+
max-width: 70%;
|
| 41 |
+
padding: 1rem;
|
| 42 |
+
border-radius: 12px;
|
| 43 |
+
line-height: 1.4;
|
| 44 |
+
white-space: pre-wrap;
|
| 45 |
+
word-wrap: break-word;
|
| 46 |
+
}
|
| 47 |
+
.user-msg {
|
| 48 |
+
background: #0a74da;
|
| 49 |
+
color: white;
|
| 50 |
+
align-self: flex-end;
|
| 51 |
+
border-bottom-right-radius: 0;
|
| 52 |
+
}
|
| 53 |
+
.bot-msg {
|
| 54 |
+
background: #222;
|
| 55 |
+
color: #ddd;
|
| 56 |
+
align-self: flex-start;
|
| 57 |
+
border-bottom-left-radius: 0;
|
| 58 |
+
}
|
| 59 |
+
footer {
|
| 60 |
+
background: #222;
|
| 61 |
+
padding: 1rem;
|
| 62 |
+
display: flex;
|
| 63 |
+
gap: 0.5rem;
|
| 64 |
+
user-select: none;
|
| 65 |
+
}
|
| 66 |
+
textarea {
|
| 67 |
+
flex: 1;
|
| 68 |
+
resize: none;
|
| 69 |
+
padding: 0.5rem;
|
| 70 |
+
font-size: 1rem;
|
| 71 |
+
border-radius: 8px;
|
| 72 |
+
border: none;
|
| 73 |
+
outline: none;
|
| 74 |
+
background: #1e1e1e;
|
| 75 |
+
color: white;
|
| 76 |
+
font-family: inherit;
|
| 77 |
+
}
|
| 78 |
+
button {
|
| 79 |
+
background: #0a74da;
|
| 80 |
+
border: none;
|
| 81 |
+
padding: 0 1.5rem;
|
| 82 |
+
border-radius: 8px;
|
| 83 |
+
color: white;
|
| 84 |
+
font-weight: bold;
|
| 85 |
+
cursor: pointer;
|
| 86 |
+
transition: background 0.2s ease;
|
| 87 |
+
position: relative;
|
| 88 |
+
}
|
| 89 |
+
button:hover {
|
| 90 |
+
background: #095bb5;
|
| 91 |
+
}
|
| 92 |
+
button:disabled {
|
| 93 |
+
background: #555;
|
| 94 |
+
cursor: not-allowed;
|
| 95 |
+
}
|
| 96 |
+
/* Loading spinner */
|
| 97 |
+
.spinner {
|
| 98 |
+
border: 3px solid rgba(255, 255, 255, 0.3);
|
| 99 |
+
border-top: 3px solid white;
|
| 100 |
+
border-radius: 50%;
|
| 101 |
+
width: 18px;
|
| 102 |
+
height: 18px;
|
| 103 |
+
animation: spin 1s linear infinite;
|
| 104 |
+
position: absolute;
|
| 105 |
+
top: 50%;
|
| 106 |
+
left: 50%;
|
| 107 |
+
margin: -9px 0 0 -9px;
|
| 108 |
+
display: none;
|
| 109 |
+
}
|
| 110 |
+
@keyframes spin {
|
| 111 |
+
0% { transform: rotate(0deg); }
|
| 112 |
+
100% { transform: rotate(360deg); }
|
| 113 |
+
}
|
| 114 |
+
</style>
|
| 115 |
+
</head>
|
| 116 |
+
<body>
|
| 117 |
+
<header>Kairo RAG Chatbot</header>
|
| 118 |
+
<main id="chat-window">
|
| 119 |
+
<!-- Chat messages will appear here -->
|
| 120 |
+
</main>
|
| 121 |
+
<footer>
|
| 122 |
+
<textarea id="input-msg" rows="2" placeholder="Type your message here..."></textarea>
|
| 123 |
+
<button id="send-btn">
|
| 124 |
+
Send
|
| 125 |
+
<div class="spinner" id="loading-spinner"></div>
|
| 126 |
+
</button>
|
| 127 |
+
</footer>
|
| 128 |
+
|
| 129 |
+
<script>
|
| 130 |
+
const chatWindow = document.getElementById('chat-window');
|
| 131 |
+
const inputMsg = document.getElementById('input-msg');
|
| 132 |
+
const sendBtn = document.getElementById('send-btn');
|
| 133 |
+
const spinner = document.getElementById('loading-spinner');
|
| 134 |
+
|
| 135 |
+
// Convert **bold** markdown in text to <b>bold</b> html
|
| 136 |
+
function markdownToHTML(text) {
|
| 137 |
+
// Simple regex for **bold**
|
| 138 |
+
return text.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
function appendUserMessage(text) {
|
| 142 |
+
const msgDiv = document.createElement('div');
|
| 143 |
+
msgDiv.classList.add('chat-message', 'user-msg');
|
| 144 |
+
msgDiv.textContent = text;
|
| 145 |
+
chatWindow.appendChild(msgDiv);
|
| 146 |
+
chatWindow.scrollTop = chatWindow.scrollHeight;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
// Append bot message with typing effect + markdown bold support
|
| 150 |
+
async function appendBotMessage(text) {
|
| 151 |
+
const msgDiv = document.createElement('div');
|
| 152 |
+
msgDiv.classList.add('chat-message', 'bot-msg');
|
| 153 |
+
chatWindow.appendChild(msgDiv);
|
| 154 |
+
|
| 155 |
+
// Convert markdown bold syntax to html tags before typing effect
|
| 156 |
+
const htmlText = markdownToHTML(text);
|
| 157 |
+
|
| 158 |
+
// We need to type the HTML content char by char, but respecting tags:
|
| 159 |
+
// So we parse it and add chars gradually. Here is a simple approach:
|
| 160 |
+
|
| 161 |
+
let i = 0;
|
| 162 |
+
let tag = false;
|
| 163 |
+
let tagBuffer = '';
|
| 164 |
+
while (i < htmlText.length) {
|
| 165 |
+
let char = htmlText[i];
|
| 166 |
+
if (char === '<') {
|
| 167 |
+
tag = true;
|
| 168 |
+
tagBuffer = '<';
|
| 169 |
+
} else if (tag) {
|
| 170 |
+
tagBuffer += char;
|
| 171 |
+
if (char === '>') {
|
| 172 |
+
// finished tag, add to innerHTML
|
| 173 |
+
msgDiv.innerHTML += tagBuffer;
|
| 174 |
+
tag = false;
|
| 175 |
+
tagBuffer = '';
|
| 176 |
+
}
|
| 177 |
+
} else {
|
| 178 |
+
msgDiv.innerHTML += char;
|
| 179 |
+
// Scroll as text grows
|
| 180 |
+
chatWindow.scrollTop = chatWindow.scrollHeight;
|
| 181 |
+
// Wait a bit for typing effect
|
| 182 |
+
await new Promise(resolve => setTimeout(resolve, 15)); // 15ms delay per char
|
| 183 |
+
}
|
| 184 |
+
i++;
|
| 185 |
+
}
|
| 186 |
+
// Final scroll adjustment
|
| 187 |
+
chatWindow.scrollTop = chatWindow.scrollHeight;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
async function sendMessage() {
|
| 191 |
+
const message = inputMsg.value.trim();
|
| 192 |
+
if (!message) return;
|
| 193 |
+
|
| 194 |
+
appendUserMessage(message);
|
| 195 |
+
inputMsg.value = '';
|
| 196 |
+
|
| 197 |
+
spinner.style.display = 'block';
|
| 198 |
+
sendBtn.disabled = true;
|
| 199 |
+
|
| 200 |
+
try {
|
| 201 |
+
const res = await fetch('/chat', {
|
| 202 |
+
method: 'POST',
|
| 203 |
+
headers: {'Content-Type': 'application/json'},
|
| 204 |
+
body: JSON.stringify({message})
|
| 205 |
+
});
|
| 206 |
+
const data = await res.json();
|
| 207 |
+
|
| 208 |
+
if (data.response) {
|
| 209 |
+
await appendBotMessage(data.response);
|
| 210 |
+
} else if (data.error) {
|
| 211 |
+
await appendBotMessage("Error: " + data.error);
|
| 212 |
+
} else {
|
| 213 |
+
await appendBotMessage("Unknown error");
|
| 214 |
+
}
|
| 215 |
+
} catch (err) {
|
| 216 |
+
await appendBotMessage("Network error: " + err.message);
|
| 217 |
+
} finally {
|
| 218 |
+
spinner.style.display = 'none';
|
| 219 |
+
sendBtn.disabled = false;
|
| 220 |
+
inputMsg.focus();
|
| 221 |
+
}
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
sendBtn.addEventListener('click', sendMessage);
|
| 225 |
+
inputMsg.addEventListener('keydown', (e) => {
|
| 226 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
| 227 |
+
e.preventDefault();
|
| 228 |
+
sendMessage();
|
| 229 |
+
}
|
| 230 |
+
});
|
| 231 |
+
</script>
|
| 232 |
+
</body>
|
| 233 |
+
</html>
|