feat: consolidate idkgng branch features, integrate Go chatbot service, and fix local configurations
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -11,6 +11,8 @@ node_modules
|
|||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
.env
|
||||||
|
*.env
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
@@ -23,3 +25,4 @@ dist-ssr
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
*.db
|
*.db
|
||||||
|
*.exe
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ─── DATABASE CONNECTION CONFIGURATION ───
|
# ─── DATABASE CONNECTION CONFIGURATION ───
|
||||||
spring.datasource.url=jdbc:postgresql://localhost:5433/rit_freshers_hub?sslmode=disable
|
spring.datasource.url=jdbc:postgresql://localhost:5432/rit_freshers_hub?sslmode=disable
|
||||||
spring.datasource.username=postgres
|
spring.datasource.username=postgres
|
||||||
spring.datasource.password=${DB_PASSWORD:Anbukathir@#$2006}
|
spring.datasource.password=${DB_PASSWORD:Anbukathir@#$2006}
|
||||||
|
|
||||||
|
|||||||
26
chatbot-service/Dockerfile
Normal file
26
chatbot-service/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# --- Stage 1: Build the Go binary ---
|
||||||
|
FROM golang:1.21-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the source code and configuration files
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build a statically linked binary for Linux
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o chatbot-service .
|
||||||
|
|
||||||
|
# --- Stage 2: Create the final minimal image ---
|
||||||
|
FROM alpine:latest
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install security certificates just in case
|
||||||
|
RUN apk --no-cache add ca-certificates
|
||||||
|
|
||||||
|
# Copy the binary and data file from the builder stage
|
||||||
|
COPY --from=builder /app/chatbot-service .
|
||||||
|
COPY --from=builder /app/qna.json .
|
||||||
|
|
||||||
|
# Expose port 8081
|
||||||
|
EXPOSE 8081
|
||||||
|
|
||||||
|
# Run the chatbot service
|
||||||
|
CMD ["./chatbot-service"]
|
||||||
3
chatbot-service/go.mod
Normal file
3
chatbot-service/go.mod
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module rit-chatbot-service
|
||||||
|
|
||||||
|
go 1.21
|
||||||
267
chatbot-service/main.go
Normal file
267
chatbot-service/main.go
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FAQ struct {
|
||||||
|
Keywords []string `json:"keywords"`
|
||||||
|
Question string `json:"question"`
|
||||||
|
Answer string `json:"answer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatRequest struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatResponse struct {
|
||||||
|
Answer string `json:"answer"`
|
||||||
|
MatchedQuestion string `json:"matched_question,omitempty"`
|
||||||
|
Confidence float64 `json:"confidence"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
faqs []FAQ
|
||||||
|
idf map[string]float64
|
||||||
|
docVectors []map[string]float64
|
||||||
|
docNorms []float64
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stopwords set to filter out noise
|
||||||
|
var stopwords = map[string]bool{
|
||||||
|
"a": true, "about": true, "above": true, "after": true, "again": true, "against": true, "all": true, "am": true,
|
||||||
|
"an": true, "and": true, "any": true, "are": true, "as": true, "at": true, "be": true, "because": true,
|
||||||
|
"been": true, "before": true, "being": true, "below": true, "between": true, "both": true, "but": true, "by": true,
|
||||||
|
"can": true, "could": true, "did": true, "do": true, "does": true, "doing": true, "down": true, "during": true,
|
||||||
|
"each": true, "few": true, "for": true, "from": true, "further": true, "had": true, "has": true, "have": true,
|
||||||
|
"having": true, "he": true, "her": true, "here": true, "hers": true, "herself": true, "him": true, "himself": true,
|
||||||
|
"his": true, "how": true, "i": true, "if": true, "in": true, "into": true, "is": true, "it": true, "its": true,
|
||||||
|
"itself": true, "me": true, "more": true, "most": true, "my": true, "myself": true, "no": true, "nor": true,
|
||||||
|
"not": true, "of": true, "off": true, "on": true, "once": true, "only": true, "or": true, "other": true,
|
||||||
|
"our": true, "ours": true, "ourselves": true, "out": true, "over": true, "own": true, "same": true, "she": true,
|
||||||
|
"should": true, "so": true, "some": true, "such": true, "than": true, "that": true, "the": true, "their": true,
|
||||||
|
"theirs": true, "them": true, "themselves": true, "then": true, "there": true, "these": true, "they": true,
|
||||||
|
"this": true, "those": true, "through": true, "to": true, "too": true, "under": true, "until": true, "up": true,
|
||||||
|
"very": true, "was": true, "we": true, "were": true, "what": true, "when": true, "where": true, "which": true,
|
||||||
|
"while": true, "who": true, "whom": true, "why": true, "with": true, "you": true, "your": true, "yours": true,
|
||||||
|
"yourself": true, "yourselves": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var cleanRegex = regexp.MustCompile(`[^a-z0-9\s]`)
|
||||||
|
|
||||||
|
func tokenize(text string) []string {
|
||||||
|
text = strings.ToLower(text)
|
||||||
|
text = cleanRegex.ReplaceAllString(text, " ")
|
||||||
|
words := strings.Fields(text)
|
||||||
|
var tokens []string
|
||||||
|
for _, word := range words {
|
||||||
|
if !stopwords[word] && len(word) > 1 {
|
||||||
|
tokens = append(tokens, word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
func initTFIDF() {
|
||||||
|
docFrequencies := make(map[string]int)
|
||||||
|
allTokens := make([][]string, len(faqs))
|
||||||
|
|
||||||
|
for i, faq := range faqs {
|
||||||
|
// Combine question and keywords to form the index document
|
||||||
|
docText := faq.Question + " " + strings.Join(faq.Keywords, " ")
|
||||||
|
tokens := tokenize(docText)
|
||||||
|
allTokens[i] = tokens
|
||||||
|
|
||||||
|
uniqueTokens := make(map[string]bool)
|
||||||
|
for _, token := range tokens {
|
||||||
|
uniqueTokens[token] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for token := range uniqueTokens {
|
||||||
|
docFrequencies[token]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate IDF for each term
|
||||||
|
idf = make(map[string]float64)
|
||||||
|
numDocs := float64(len(faqs))
|
||||||
|
for token, df := range docFrequencies {
|
||||||
|
idf[token] = math.Log(1.0 + (numDocs / float64(df)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute TF-IDF vectors for FAQs
|
||||||
|
docVectors = make([]map[string]float64, len(faqs))
|
||||||
|
docNorms = make([]float64, len(faqs))
|
||||||
|
|
||||||
|
for i, tokens := range allTokens {
|
||||||
|
tfMap := make(map[string]float64)
|
||||||
|
for _, token := range tokens {
|
||||||
|
tfMap[token]++
|
||||||
|
}
|
||||||
|
|
||||||
|
vector := make(map[string]float64)
|
||||||
|
var sqSum float64
|
||||||
|
|
||||||
|
for token, tf := range tfMap {
|
||||||
|
tfidfVal := tf * idf[token]
|
||||||
|
vector[token] = tfidfVal
|
||||||
|
sqSum += tfidfVal * tfidfVal
|
||||||
|
}
|
||||||
|
|
||||||
|
docVectors[i] = vector
|
||||||
|
docNorms[i] = math.Sqrt(sqSum)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("TF-IDF Chatbot engine initialized successfully with %d Q&As.", len(faqs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBestMatch(query string) (int, float64) {
|
||||||
|
queryTokens := tokenize(query)
|
||||||
|
if len(queryTokens) == 0 {
|
||||||
|
return -1, 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute TF for query
|
||||||
|
queryTF := make(map[string]float64)
|
||||||
|
for _, token := range queryTokens {
|
||||||
|
queryTF[token]++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute TF-IDF vector for query
|
||||||
|
queryVector := make(map[string]float64)
|
||||||
|
var querySqSum float64
|
||||||
|
for token, tf := range queryTF {
|
||||||
|
if idfVal, exists := idf[token]; exists {
|
||||||
|
tfidfVal := tf * idfVal
|
||||||
|
queryVector[token] = tfidfVal
|
||||||
|
querySqSum += tfidfVal * tfidfVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryNorm := math.Sqrt(querySqSum)
|
||||||
|
if queryNorm == 0 {
|
||||||
|
return -1, 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
bestIdx := -1
|
||||||
|
bestScore := -1.0
|
||||||
|
|
||||||
|
for i, docVector := range docVectors {
|
||||||
|
var dotProduct float64
|
||||||
|
for token, qVal := range queryVector {
|
||||||
|
if dVal, exists := docVector[token]; exists {
|
||||||
|
dotProduct += qVal * dVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cosineSim float64
|
||||||
|
if docNorms[i] > 0 {
|
||||||
|
cosineSim = dotProduct / (queryNorm * docNorms[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply exact keyword boosting
|
||||||
|
keywordMatches := 0
|
||||||
|
for _, kw := range faqs[i].Keywords {
|
||||||
|
for _, qTok := range queryTokens {
|
||||||
|
if strings.ToLower(kw) == qTok {
|
||||||
|
keywordMatches++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A boost of 0.15 for each exact keyword match
|
||||||
|
boost := float64(keywordMatches) * 0.15
|
||||||
|
score := cosineSim + boost
|
||||||
|
|
||||||
|
if score > bestScore {
|
||||||
|
bestScore = score
|
||||||
|
bestIdx = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestIdx, bestScore
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleChat(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Add CORS headers
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
|
||||||
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||||
|
|
||||||
|
if r.Method == http.MethodOptions {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req ChatRequest
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bestIdx, score := getBestMatch(req.Message)
|
||||||
|
threshold := 0.18
|
||||||
|
|
||||||
|
var resp ChatResponse
|
||||||
|
if bestIdx != -1 && score >= threshold {
|
||||||
|
resp = ChatResponse{
|
||||||
|
Answer: faqs[bestIdx].Answer,
|
||||||
|
MatchedQuestion: faqs[bestIdx].Question,
|
||||||
|
Confidence: score,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resp = ChatResponse{
|
||||||
|
Answer: "I'm sorry, I couldn't find an answer to your question about RIT Chennai. Please try rephrasing your question or contact our administrative office at +91 8925977445 or mail@ritchennai.edu.in.",
|
||||||
|
Confidence: score,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleHealth(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"status":"UP"}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Load FAQs
|
||||||
|
file, err := os.ReadFile("qna.json")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to read qna.json: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(file, &faqs)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to parse qna.json: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
initTFIDF()
|
||||||
|
|
||||||
|
port := os.Getenv("PORT")
|
||||||
|
if port == "" {
|
||||||
|
port = "8081"
|
||||||
|
}
|
||||||
|
|
||||||
|
http.HandleFunc("/api/chat", handleChat)
|
||||||
|
http.HandleFunc("/api/health", handleHealth)
|
||||||
|
|
||||||
|
log.Printf("Chatbot service listening on port %s...", port)
|
||||||
|
if err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil); err != nil {
|
||||||
|
log.Fatalf("Server failed to start: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
192
chatbot-service/qna.json
Normal file
192
chatbot-service/qna.json
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"keywords": ["about", "established", "history", "rit", "rajalakshmi", "autonomous", "sabari"],
|
||||||
|
"question": "What is Rajalakshmi Institute of Technology?",
|
||||||
|
"answer": "Rajalakshmi Institute of Technology (RIT Chennai) is a premier Autonomous engineering institution established in 2008 by the Sabari Foundation. It is a part of the Rajalakshmi Institutions group, affiliated with Anna University, Chennai, and approved by the AICTE, New Delhi."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["accreditation", "naac", "nba", "grade", "rank"],
|
||||||
|
"question": "What is the NAAC accreditation and academic standing of RIT Chennai?",
|
||||||
|
"answer": "RIT Chennai holds the highest accreditation grade of 'A++' from the National Assessment and Accreditation Council (NAAC) and offers NBA-approved undergraduate engineering courses."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["location", "address", "where", "campus", "poonamallee", "kuthambakkam"],
|
||||||
|
"question": "Where is the RIT Chennai campus located?",
|
||||||
|
"answer": "The main campus is located at Bangalore Highway Road, Kuthambakkam, Poonamallee, Chennai - 600124. The administrative office is located at #69, New Avadi Road, Kilpauk, Chennai - 600010."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["contact", "phone", "number", "call", "office", "admission number", "admissions phone"],
|
||||||
|
"question": "What are the contact numbers for the campus and the administrative office?",
|
||||||
|
"answer": "The main campus can be reached at 044-6718 1600 / 01 or +91 8925977445. The administrative office in Kilpauk can be reached at 044-26460124, 26442472, or 26461316 (Fax: 044-26445151)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["email", "mail", "contact email", "support email"],
|
||||||
|
"question": "What is the official email address for RIT Chennai queries?",
|
||||||
|
"answer": "The official contact email is mail@ritchennai.edu.in for both general queries and admissions."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["courses", "departments", "branches", "programs", "ug", "undergraduate"],
|
||||||
|
"question": "What undergraduate (UG) engineering programs are offered at RIT?",
|
||||||
|
"answer": "RIT offers BE in Computer Science and Engineering, Computer and Communication Engineering, CSE (Artificial Intelligence and Machine Learning), Electronics and Communication Engineering, Mechanical Engineering, CSE (Cyber Security), and Electronics Engineering (VLSI Design and Technology). It also offers BTech in Artificial Intelligence and Data Science, Computer Science and Business Systems, and BioTechnology."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["pg", "postgraduate", "master", "mtech", "me"],
|
||||||
|
"question": "What postgraduate (PG) and research programs are offered?",
|
||||||
|
"answer": "RIT offers ME in Electronics and Communication (VLSI Design) and MTech in Data Science. It is also an Anna University Approved Research Institute offering Ph.D. programs across all Engineering, Technology, Science & Humanities disciplines."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["admissions", "apply", "enquiry", "join", "registration"],
|
||||||
|
"question": "How can I apply for admissions or make an admission enquiry?",
|
||||||
|
"answer": "You can register, make enquiries, and submit applications online through the official RIT admission portal at https://apply.ritchennai.org/."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["fees", "payment", "online fees", "pay", "worldline"],
|
||||||
|
"question": "How do I pay my college fees online?",
|
||||||
|
"answer": "You can make secure online fee payments through the Worldline portal at: https://epayments.in.worldline.com/rajalakshmi?swith=rollnumber."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["library", "books", "hours", "timing", "saturday"],
|
||||||
|
"question": "What are the timings and opening hours of the central library?",
|
||||||
|
"answer": "The central library is open from 8:00 AM to 5:00 PM on all working days, and from 10:00 AM to 2:00 PM on Saturdays."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["library size", "volumes", "capacity", "journals", "magazines"],
|
||||||
|
"question": "What is the size and collection capacity of the RIT central library?",
|
||||||
|
"answer": "The library occupies 607 Sq.m. and houses over 18,328 volumes of textbooks and reference books. The college subscribes to 24 printed national journals, 26 magazines, and offers online access to IEEE resources."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["digital library", "computers", "wifi", "cd", "dvd"],
|
||||||
|
"question": "Does the library have digital resources or Wi-Fi?",
|
||||||
|
"answer": "Yes, the digital library is equipped with 25 computers for accessing online journals and e-resources. Wi-Fi is enabled throughout the library. Additionally, the electronic library contains a collection of more than 900 CD-ROMs."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["british council", "bcl", "membership"],
|
||||||
|
"question": "Does RIT have institutional memberships with other libraries?",
|
||||||
|
"answer": "Yes, RIT has an institutional membership with the British Council Library (BCL), Chennai. Students and faculty can borrow books, journals, CDs, and DVDs using the BCL institutional card."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["opac", "catalog", "search books", "barcode"],
|
||||||
|
"question": "How do students search for books in the library?",
|
||||||
|
"answer": "The library is equipped with a computerized Online Public Access Catalogue (OPAC) to search for books and CD-ROMs. All books are bar-coded, and a barcode system is used for all transactions."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["hostel", "accommodation", "stay", "rooms", "amenities"],
|
||||||
|
"question": "What hostel facilities and room amenities are provided at RIT?",
|
||||||
|
"answer": "RIT offers separate, spacious hostels for boys and girls. Each student is provided with a cot, cupboard, study table, and chair. The hostels feature dining halls, uninterrupted water supply, reading halls, internet connection, TV, and RO drinking water on each floor."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["hostel warden", "guardian", "supervision", "medical"],
|
||||||
|
"question": "How are the hostels supervised and are there medical facilities?",
|
||||||
|
"answer": "Each hostel is supervised by a resident warden. Senior faculty members also stay as academic guardians to assist students after class hours. Hostels have access to a 24/7 medical facility."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["canteen", "food", "dining", "mess"],
|
||||||
|
"question": "Is there a canteen on campus?",
|
||||||
|
"answer": "Yes, a well-maintained canteen operates inside the campus, serving hygienic vegetarian and non-vegetarian food options for students and staff."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["transport", "bus", "routes", "city"],
|
||||||
|
"question": "What transport and college bus facilities are available?",
|
||||||
|
"answer": "RIT operates a fleet of 29 buses covering various routes across Chennai city to ensure safe and hassle-free transportation for students and staff."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["transport contact", "bus manager", "venkatesan"],
|
||||||
|
"question": "Who should I contact for transport and bus route queries?",
|
||||||
|
"answer": "For transport-related queries, you can contact the Transport Coordinator, Mr. Venkatesan.M, at +91 63807 51700."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["bus routes website", "routes list"],
|
||||||
|
"question": "Where can I view the detailed list of bus routes?",
|
||||||
|
"answer": "The detailed bus routes and schedule can be viewed at the dedicated transport portal: http://www.rittransport.com/."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["sports", "games", "indoor", "outdoor", "gym"],
|
||||||
|
"question": "What indoor and outdoor sports facilities are available on campus?",
|
||||||
|
"answer": "RIT provides indoor facilities for Chess, Carrom, Table Tennis, and Badminton, and outdoor facilities for Volleyball, Football, Cricket, Kho-Kho, and Kabaddi."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["physical director", "sports coach", "sports training"],
|
||||||
|
"question": "Is there professional sports training or coaching available?",
|
||||||
|
"answer": "Yes, the college has a qualified Physical Education staff member who trains students for inter-collegiate tournaments and sports events."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["atm", "cash", "bank"],
|
||||||
|
"question": "Is there an ATM facility on the RIT campus?",
|
||||||
|
"answer": "Yes, there is an in-campus 24-hour ATM facility available for students and staff to withdraw cash safely."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["health care", "doctor", "medical center", "inpatient", "hospital"],
|
||||||
|
"question": "What medical and healthcare facilities are available at RIT?",
|
||||||
|
"answer": "RIT has a well-equipped on-campus Health Centre with separate inpatient facilities for male and female students. It is manned by a Medical Officer and a Healthcare Assistant, providing medical care to all students and staff at no cost."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["counselling", "mentor", "counselor", "academic progress"],
|
||||||
|
"question": "How does the student mentoring and counseling system work?",
|
||||||
|
"answer": "Every faculty member acts as a student counselor. Each counselor is assigned around 15 students for periodic counseling to monitor academic progress, extracurricular activities, and address personal/academic concerns."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["expert counselling", "psychologist", "specialized counselling"],
|
||||||
|
"question": "Is specialized counseling available for students?",
|
||||||
|
"answer": "Yes, specialized counseling sessions by external experts are arranged by the college as and when required to support student well-being."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["cells", "committees", "student support"],
|
||||||
|
"question": "What student welfare cells and committees exist at RIT?",
|
||||||
|
"answer": "RIT maintains several active cells: the Antiragging Cell, Internal Complaints Committee (ICC), Grievance and Redressal Cell, Women Empowerment Cell (WEC), Institution's Innovation Council (IIC) Cell, and SC/ST Cell."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["antiragging", "ragging", "complaint", "policy"],
|
||||||
|
"question": "What is the college policy on ragging?",
|
||||||
|
"answer": "RIT has a strict zero-tolerance policy towards ragging. The Antiragging Cell ensures absolute compliance with anti-ragging regulations to maintain a safe campus environment for freshers."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["women cell", "icc", "wec", "harassment"],
|
||||||
|
"question": "What are the roles of the ICC and the Women Empowerment Cell?",
|
||||||
|
"answer": "The Internal Complaints Committee (ICC) and Women Empowerment Cell (WEC) promote a gender-sensitive campus, organizing awareness programs and addressing any grievances related to gender discrimination or harassment."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["grievance", "redressal", "complaints", "academic issues"],
|
||||||
|
"question": "How can students submit academic or administrative complaints?",
|
||||||
|
"answer": "Students can approach the Grievances and Redressal Cell to submit formal complaints. The cell reviews all matters promptly to ensure fair resolutions."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["sc/st cell", "scholarships", "welfare"],
|
||||||
|
"question": "What assistance does the SC/ST Cell provide?",
|
||||||
|
"answer": "The SC/ST Cell assists students from scheduled castes and scheduled tribes in obtaining government scholarships, academic counseling, and general welfare support."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["code of conduct", "rules", "regulations", "discipline"],
|
||||||
|
"question": "Where can I read the official student code of conduct?",
|
||||||
|
"answer": "The official Student Code of Conduct PDF can be accessed directly at: https://ritchennai.org/downloads/CODE%20OF%20CONDUCT%20FOR%20STUDENTS.pdf."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["curriculum", "syllabus", "regulations", "academic calendar"],
|
||||||
|
"question": "Where can I find academic regulations, syllabus, and curriculum details?",
|
||||||
|
"answer": "Regulations and syllabus files are published under the Academics section at: https://ritchennai.org/Regulations.php and https://ritchennai.org/Curriculum.php."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["alumni", "registration", "network"],
|
||||||
|
"question": "How can RIT graduates register with the alumni network?",
|
||||||
|
"answer": "Alumni can register and join the official network online at the alumni registration portal: https://ritchennai.org/alumni-reg.php."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["placement", "training", "jobs", "placements", "recruiters"],
|
||||||
|
"question": "What placement training and assistance does RIT offer?",
|
||||||
|
"answer": "RIT's Training and Placement Cell organizes structured training starting from the first year. It includes aptitude training, soft skills, coding bootcamps, and mock interviews. Prominent recruiters include top-tier IT and core engineering companies."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["clubs", "activities", "extra-curricular", "nss", "yrc", "rotaract"],
|
||||||
|
"question": "What extra-curricular clubs can students join at RIT?",
|
||||||
|
"answer": "Students can join various clubs including YUVA, UBA (Unnat Bharat Abhiyan), National Service Scheme (NSS), Youth Red Cross (YRC), Rotaract Club, Women Empowerment Club, Techsparks, STEAM, Fusion Language, Vaarithi Muthamizh Mandram, Artist League, Photography, Nippon, Telugu, and Podcast clubs."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["uba", "unnat bharat abhiyan", "rural"],
|
||||||
|
"question": "What is the UBA (Unnat Bharat Abhiyan) at RIT?",
|
||||||
|
"answer": "Unnat Bharat Abhiyan (UBA) is a flagship national program. RIT is a participating institution, engaging students in rural development, village adoption, and community welfare programs."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keywords": ["iic", "incubation", "innovation", "startups", "edc"],
|
||||||
|
"question": "Does RIT support student startups, entrepreneurship, and innovation?",
|
||||||
|
"answer": "Yes, RIT supports innovation through the Institution's Innovation Council (IIC) Cell and the EDC/TBI Cell (Entrepreneurship Development Cell), facilitating student project incubations and startup activities (Details: https://ritchennai.org/downloads/EDC.pdf)."
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -41,28 +41,43 @@ export default function AIAssistant() {
|
|||||||
};
|
};
|
||||||
setMessages((prev) => [...prev, userMsg]);
|
setMessages((prev) => [...prev, userMsg]);
|
||||||
setInput('');
|
setInput('');
|
||||||
setIsTyping(true);
|
setIsTyping(true); // Call the Go chatbot microservice
|
||||||
|
|
||||||
// Simulate AI response
|
|
||||||
await new Promise((r) => setTimeout(r, 1500));
|
|
||||||
setIsTyping(false);
|
|
||||||
|
|
||||||
const responses: Record<string, string> = {
|
|
||||||
hostel: "**RIT Hostel Facilities:**\n\n• Separate hostels for boys and girls\n• Wi-Fi connectivity in all rooms\n• 24/7 security and CCTV\n• Hygienic canteen with vegetarian & non-vegetarian options\n• Common rooms with TV and recreation\n• In-house medical facility\n\nFor hostel admission, contact the hostel office with your Aadhaar card, medical certificate, and filled hostel application form.",
|
|
||||||
bus: "**RIT Bus Routes:**\n\nRIT operates **15+ bus routes** covering major areas of Chennai:\n\n• Route 01: Chennai Central → Koyambedu → Porur → RIT (7:00 AM)\n• Route 02: Tambaram → Chrompet → Pallavaram → RIT (7:15 AM)\n• Route 03: Anna Nagar → Vadapalani → RIT (7:20 AM)\n\nAll buses depart from respective stops by 7:30 AM. Return buses leave RIT at 4:30 PM and 6:00 PM.",
|
|
||||||
library: "**RIT Library Information:**\n\n• **Timings:** Mon-Sat 8:00 AM – 8:00 PM, Sunday 10:00 AM – 5:00 PM\n• **Collection:** Over 50,000 books, 200+ journals\n• **Digital Access:** IEEE Xplore, ACM Digital Library, Scopus\n• **Services:** Book borrowing (4 books, 14 days), Reference, Photocopying\n• **Wi-Fi:** Available throughout the library\n\nYou'll need your student ID card to access the library.",
|
|
||||||
};
|
|
||||||
|
|
||||||
const lower = text.toLowerCase();
|
|
||||||
let responseText = '';
|
let responseText = '';
|
||||||
|
try {
|
||||||
|
const res = await fetch('http://localhost:8081/api/chat', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ message: text }),
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
responseText = data.answer;
|
||||||
|
} else {
|
||||||
|
throw new Error('API server returned error status');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Could not connect to Go chatbot service, using local mock responses:', error);
|
||||||
|
|
||||||
if (lower.includes('hostel')) responseText = responses.hostel;
|
const responses: Record<string, string> = {
|
||||||
else if (lower.includes('bus') || lower.includes('route')) responseText = responses.bus;
|
hostel: "**RIT Hostel Facilities:**\n\n• Separate hostels for boys and girls\n• Wi-Fi connectivity in all rooms\n• 24/7 security and CCTV\n• Hygienic canteen with vegetarian & non-vegetarian options\n• Common rooms with TV and recreation\n• In-house medical facility\n\nFor hostel admission, contact the hostel office.",
|
||||||
else if (lower.includes('library')) responseText = responses.library;
|
bus: "**RIT Bus Routes:**\n\nRIT operates **29 bus routes** covering major areas of Chennai. For queries, contact Mr. Venkatesan at +91 63807 51700 or visit http://www.rittransport.com/.",
|
||||||
else {
|
library: "**RIT Library Information:**\n\n• **Timings:** Mon-Fri 8:00 AM – 5:00 PM, Saturday 10:00 AM – 2:00 PM\n• **Collection:** Over 18,328 volumes of textbooks and reference books\n• **Digital Access:** Computerized OPAC, 25 computer systems in Digital Library, and Wi-Fi enabled online access.",
|
||||||
responseText = `Thank you for your question about **"${text}"**!\n\nI'm connected to RIT's knowledge base and can help you with:\n\n• 📚 Academic information & syllabus\n• 🏠 Hostel & accommodation\n• 🚌 Bus routes & timings\n• 📋 Admission procedures\n• 🏛️ Campus facilities\n• 👩🏫 Faculty information\n• 🎉 Events & clubs\n\nCould you be more specific about what you'd like to know? I'll give you the most accurate information!`;
|
};
|
||||||
|
|
||||||
|
const lower = text.toLowerCase();
|
||||||
|
if (lower.includes('hostel')) {
|
||||||
|
responseText = responses.hostel;
|
||||||
|
} else if (lower.includes('bus') || lower.includes('route')) {
|
||||||
|
responseText = responses.bus;
|
||||||
|
} else if (lower.includes('library')) {
|
||||||
|
responseText = responses.library;
|
||||||
|
} else {
|
||||||
|
responseText = `Thank you for your question about **"${text}"**!\n\nI couldn't reach the chatbot API server. Please make sure the Go service is running on http://localhost:8081.`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsTyping(false);
|
||||||
|
|
||||||
const aiMsg: ChatMessage = {
|
const aiMsg: ChatMessage = {
|
||||||
id: (Date.now() + 1).toString(),
|
id: (Date.now() + 1).toString(),
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
@@ -100,12 +115,38 @@ export default function AIAssistant() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
|
<a
|
||||||
|
href="https://t.me/Ritchatbot_bot"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-semibold text-white transition-all shadow-sm hover:brightness-105 active:scale-95"
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(135deg, #0088cc, #24A1DE)',
|
||||||
|
fontFamily: 'Inter, sans-serif'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Send className="w-3.5 h-3.5" />
|
||||||
|
Use on Telegram
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://discord.com/api/oauth2/authorize?client_id=1476942599167414506&permissions=8&scope=bot"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-semibold text-white transition-all shadow-sm hover:brightness-105 active:scale-95"
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(135deg, #5865F2, #7289DA)',
|
||||||
|
fontFamily: 'Inter, sans-serif'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Bot className="w-3.5 h-3.5" />
|
||||||
|
Use on Discord
|
||||||
|
</a>
|
||||||
<motion.button
|
<motion.button
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
onClick={clearChat}
|
onClick={clearChat}
|
||||||
className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs text-[#94A3B8] hover:text-[#475569] hover:bg-gray-100 transition-all"
|
className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs text-[#94A3B8] hover:text-[#475569] hover:bg-gray-100 transition-all border border-[#E5E7EB] bg-white"
|
||||||
>
|
>
|
||||||
<RefreshCw className="w-3.5 h-3.5" />
|
<RefreshCw className="w-3.5 h-3.5" />
|
||||||
New Chat
|
New Chat
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"telegram_bot_token": "8859374355:AAH0dhwstkTBhRerRTjzmb2RG2fjPbigzvo",
|
"telegram_bot_token": "",
|
||||||
"helper_chat_ids": [971749136,5567776672],
|
"helper_chat_ids": [971749136,5567776672],
|
||||||
"discord_bot_token": "",
|
"discord_bot_token": "",
|
||||||
"discord_helper_user_ids": [789393727641878568],
|
"discord_helper_user_ids": [789393727641878568],
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
|
import asyncio
|
||||||
|
import discord
|
||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -37,8 +39,9 @@ def load_config():
|
|||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
config = load_config()
|
config = load_config()
|
||||||
BOT_TOKEN = config.get("telegram_bot_token")
|
BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN") or config.get("telegram_bot_token")
|
||||||
BACKEND_URL = config.get("spring_backend_url")
|
DISCORD_TOKEN = os.environ.get("DISCORD_BOT_TOKEN") or config.get("discord_bot_token")
|
||||||
|
BACKEND_URL = os.environ.get("SPRING_BACKEND_URL") or config.get("spring_backend_url", "http://localhost:8080")
|
||||||
|
|
||||||
# Database Setup
|
# Database Setup
|
||||||
DB_PATH = os.path.join(os.path.dirname(__file__), "bot_mappings.db")
|
DB_PATH = os.path.join(os.path.dirname(__file__), "bot_mappings.db")
|
||||||
@@ -81,14 +84,16 @@ def get_question_id(chat_id: int, message_id: int) -> int:
|
|||||||
return row[0] if row else None
|
return row[0] if row else None
|
||||||
|
|
||||||
# Telegram API Helpers
|
# Telegram API Helpers
|
||||||
def send_telegram_message(chat_id: int, text: str, reply_to_message_id: int = None) -> dict:
|
def send_telegram_message(chat_id: int, text: str, reply_to_message_id: int = None, force_reply: bool = True, token: str = None) -> dict:
|
||||||
url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
|
active_token = token or BOT_TOKEN
|
||||||
|
url = f"https://api.telegram.org/bot{active_token}/sendMessage"
|
||||||
payload = {
|
payload = {
|
||||||
"chat_id": chat_id,
|
"chat_id": chat_id,
|
||||||
"text": text,
|
"text": text,
|
||||||
"parse_mode": "Markdown",
|
"parse_mode": "Markdown"
|
||||||
"reply_markup": {"force_reply": True, "selective": True}
|
|
||||||
}
|
}
|
||||||
|
if force_reply:
|
||||||
|
payload["reply_markup"] = {"force_reply": True, "selective": True}
|
||||||
if reply_to_message_id:
|
if reply_to_message_id:
|
||||||
payload["reply_to_message_id"] = reply_to_message_id
|
payload["reply_to_message_id"] = reply_to_message_id
|
||||||
|
|
||||||
@@ -143,15 +148,23 @@ def telegram_polling_thread():
|
|||||||
|
|
||||||
logging.info(f"Received message from chat {chat_id}: '{text}'")
|
logging.info(f"Received message from chat {chat_id}: '{text}'")
|
||||||
|
|
||||||
# Help helper find their Chat ID
|
# Welcome command
|
||||||
if text == "/start":
|
if text == "/start":
|
||||||
welcome_text = (
|
if chat_id in helpers:
|
||||||
f"👋 *Welcome to RIT Freshers Hub Intermediary Bot!*\n\n"
|
welcome_text = (
|
||||||
f"To configure this helper, register this Chat ID in the `config.json` file:\n"
|
f"👋 *Welcome back, RIT Senior Helper!*\n\n"
|
||||||
f"`{chat_id}`\n\n"
|
f"You are registered as an authorized helper. You will receive new student questions here "
|
||||||
f"Once registered, you will receive new student questions here and can reply directly to them."
|
f"and can reply directly to them to post answers to the Q&A board."
|
||||||
)
|
)
|
||||||
send_telegram_message(chat_id, welcome_text)
|
else:
|
||||||
|
welcome_text = (
|
||||||
|
f"👋 *Welcome to the RIT Student Assistant Bot!*\n\n"
|
||||||
|
f"I can help you answer any questions about Rajalakshmi Institute of Technology (RIT Chennai) — "
|
||||||
|
f"from courses, placements, and hostels, to transport, library hours, and sports.\n\n"
|
||||||
|
f"💬 *Just type your question here!* (e.g., _What courses are offered?_ or _How do I pay fees online?_)\n\n"
|
||||||
|
f"_(For Senior Helpers: To receive student Q&A broadcasts here, register your Chat ID `{chat_id}` in config.json)_"
|
||||||
|
)
|
||||||
|
send_telegram_message(chat_id, welcome_text, force_reply=False, token=bot_token)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Process reply messages
|
# Process reply messages
|
||||||
@@ -160,7 +173,7 @@ def telegram_polling_thread():
|
|||||||
# Verify if helper is authorized
|
# Verify if helper is authorized
|
||||||
if chat_id not in helpers:
|
if chat_id not in helpers:
|
||||||
logging.warning(f"Unauthorized message from chat ID {chat_id}")
|
logging.warning(f"Unauthorized message from chat ID {chat_id}")
|
||||||
send_telegram_message(chat_id, "⚠️ You are not registered as an authorized helper in config.json.")
|
send_telegram_message(chat_id, "⚠️ You are not registered as an authorized helper in config.json.", token=bot_token)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
original_message_id = reply_to["message_id"]
|
original_message_id = reply_to["message_id"]
|
||||||
@@ -183,29 +196,158 @@ def telegram_polling_thread():
|
|||||||
try:
|
try:
|
||||||
res = requests.post(backend_endpoint, json=answer_payload, timeout=10)
|
res = requests.post(backend_endpoint, json=answer_payload, timeout=10)
|
||||||
if res.status_code == 200 or res.status_code == 201:
|
if res.status_code == 200 or res.status_code == 201:
|
||||||
send_telegram_message(chat_id, "✅ *Answer posted successfully to the Q&A board!*", reply_to_message_id=message["message_id"])
|
send_telegram_message(chat_id, "✅ *Answer posted successfully to the Q&A board!*", reply_to_message_id=message["message_id"], token=bot_token)
|
||||||
else:
|
else:
|
||||||
send_telegram_message(chat_id, f"❌ *Failed to post answer to backend.* (Status: {res.status_code})\nResponse: {res.text[:100]}", reply_to_message_id=message["message_id"])
|
send_telegram_message(chat_id, f"❌ *Failed to post answer to backend.* (Status: {res.status_code})\nResponse: {res.text[:100]}", reply_to_message_id=message["message_id"], token=bot_token)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error calling backend endpoint {backend_endpoint}: {e}")
|
logging.error(f"Error calling backend endpoint {backend_endpoint}: {e}")
|
||||||
send_telegram_message(chat_id, f"❌ *Connection error to backend.* ({e})", reply_to_message_id=message["message_id"])
|
send_telegram_message(chat_id, f"❌ *Connection error to backend.* ({e})", reply_to_message_id=message["message_id"], token=bot_token)
|
||||||
else:
|
else:
|
||||||
send_telegram_message(chat_id, "❓ This message does not correspond to any active question or the mapping has expired.", reply_to_message_id=message["message_id"])
|
send_telegram_message(chat_id, "❓ This message does not correspond to any active question or the mapping has expired.", reply_to_message_id=message["message_id"], token=bot_token)
|
||||||
else:
|
else:
|
||||||
# Not a reply message and not "/start"
|
# Direct chat with the chatbot service
|
||||||
if not text.startswith("/"):
|
if not text:
|
||||||
send_telegram_message(chat_id, "💡 To submit an answer to a question, please **reply directly** to the question message sent by the bot.")
|
continue
|
||||||
|
|
||||||
|
logging.info(f"Querying Go chatbot service for user {chat_id}: '{text}'")
|
||||||
|
chatbot_service_url = "http://localhost:8081/api/chat"
|
||||||
|
try:
|
||||||
|
res = requests.post(chatbot_service_url, json={"message": text}, timeout=10)
|
||||||
|
if res.status_code == 200:
|
||||||
|
ans_data = res.json()
|
||||||
|
bot_response = ans_data.get("answer", "I am having trouble processing that question.")
|
||||||
|
send_telegram_message(chat_id, bot_response, force_reply=False, token=bot_token)
|
||||||
|
else:
|
||||||
|
logging.error(f"Go chatbot API returned status code {res.status_code}")
|
||||||
|
send_telegram_message(chat_id, "⚠️ The RIT Chatbot service is currently experiencing issues. Please try again later.", force_reply=False, token=bot_token)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to connect to Go chatbot service: {e}")
|
||||||
|
send_telegram_message(chat_id, "⚠️ I cannot connect to the RIT Chatbot database right now. Please make sure the service is online.", force_reply=False, token=bot_token)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error in long polling loop: {e}")
|
logging.error(f"Error in long polling loop: {e}")
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
# Start Polling Thread
|
# Telegram polling thread will be started inside main()
|
||||||
polling_thread = threading.Thread(target=telegram_polling_thread, daemon=True)
|
|
||||||
polling_thread.start()
|
# Discord Bot Client Setup
|
||||||
|
intents = discord.Intents.default()
|
||||||
|
intents.messages = True
|
||||||
|
intents.message_content = True
|
||||||
|
|
||||||
|
discord_client = discord.Client(intents=intents)
|
||||||
|
discord_loop = None
|
||||||
|
|
||||||
|
@discord_client.event
|
||||||
|
async def on_ready():
|
||||||
|
logging.info(f"Discord Bot logged in as {discord_client.user}!")
|
||||||
|
|
||||||
|
@discord_client.event
|
||||||
|
async def on_message(message):
|
||||||
|
if message.author == discord_client.user:
|
||||||
|
return
|
||||||
|
|
||||||
|
is_dm = isinstance(message.channel, discord.DMChannel)
|
||||||
|
is_mention = discord_client.user in message.mentions
|
||||||
|
|
||||||
|
if not is_dm and not is_mention:
|
||||||
|
return
|
||||||
|
|
||||||
|
content = message.content
|
||||||
|
if is_mention:
|
||||||
|
# Strip out bot mention tags
|
||||||
|
mention_str = f"<@{discord_client.user.id}>"
|
||||||
|
mention_nick_str = f"<@!{discord_client.user.id}>"
|
||||||
|
content = content.replace(mention_str, "").replace(mention_nick_str, "").strip()
|
||||||
|
|
||||||
|
# Process DM helper replies to active questions
|
||||||
|
if is_dm and message.reference and message.reference.message_id:
|
||||||
|
current_config = load_config()
|
||||||
|
discord_helpers = current_config.get("discord_helper_user_ids", [])
|
||||||
|
author_id = message.author.id
|
||||||
|
|
||||||
|
# Check if the author is a registered helper
|
||||||
|
if author_id in [int(x) for x in discord_helpers if str(x).isdigit()]:
|
||||||
|
original_message_id = message.reference.message_id
|
||||||
|
question_id = get_question_id(author_id, original_message_id)
|
||||||
|
|
||||||
|
if question_id:
|
||||||
|
author_name = message.author.name
|
||||||
|
logging.info(f"Submitting Discord answer for question {question_id} by helper '{author_name}'")
|
||||||
|
|
||||||
|
# Post answer to Spring Boot backend
|
||||||
|
backend_url = os.environ.get("SPRING_BACKEND_URL") or current_config.get("spring_backend_url")
|
||||||
|
backend_endpoint = f"{backend_url}/api/questions/{question_id}/answers"
|
||||||
|
answer_payload = {
|
||||||
|
"body": content,
|
||||||
|
"author": author_name
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
res = requests.post(backend_endpoint, json=answer_payload, timeout=10)
|
||||||
|
if res.status_code in [200, 201]:
|
||||||
|
await message.reply("✅ *Answer posted successfully to the Q&A board!*")
|
||||||
|
else:
|
||||||
|
await message.reply(f"❌ *Failed to post answer to backend.* (Status: {res.status_code})")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error calling backend endpoint {backend_endpoint}: {e}")
|
||||||
|
await message.reply(f"❌ *Connection error to backend.* ({e})")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Direct query fallback to Go chatbot service
|
||||||
|
if not content.strip():
|
||||||
|
return
|
||||||
|
|
||||||
|
logging.info(f"Querying Go chatbot service for Discord user {message.author.id}: '{content}'")
|
||||||
|
chatbot_service_url = "http://localhost:8081/api/chat"
|
||||||
|
try:
|
||||||
|
def call_chatbot():
|
||||||
|
return requests.post(chatbot_service_url, json={"message": content}, timeout=10)
|
||||||
|
|
||||||
|
# Run requests.post in executor to keep the Discord loop non-blocking
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
res = await loop.run_in_executor(None, call_chatbot)
|
||||||
|
|
||||||
|
if res.status_code == 200:
|
||||||
|
ans_data = res.json()
|
||||||
|
bot_response = ans_data.get("answer", "I am having trouble processing that question.")
|
||||||
|
await message.reply(bot_response)
|
||||||
|
else:
|
||||||
|
logging.error(f"Go chatbot API returned status code {res.status_code}")
|
||||||
|
await message.reply("⚠️ The RIT Chatbot service is currently experiencing issues. Please try again later.")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to connect to Go chatbot service: {e}")
|
||||||
|
await message.reply("⚠️ I cannot connect to the RIT Chatbot database right now. Please make sure the service is online.")
|
||||||
|
|
||||||
|
async def broadcast_discord_question(question_id: int, title: str, body: str, author: str, user_ids: list):
|
||||||
|
formatted_msg = (
|
||||||
|
f"❓ **New Student Question!**\n\n"
|
||||||
|
f"👤 **Author:** {author}\n"
|
||||||
|
f"📌 **Topic:** {title}\n"
|
||||||
|
f"📝 **Details:** {body}\n\n"
|
||||||
|
f"💬 **Reply directly to this message to submit your answer.**"
|
||||||
|
)
|
||||||
|
for user_id_val in user_ids:
|
||||||
|
try:
|
||||||
|
user_id = int(user_id_val)
|
||||||
|
user = await discord_client.fetch_user(user_id)
|
||||||
|
if user:
|
||||||
|
msg = await user.send(formatted_msg)
|
||||||
|
save_mapping(user_id, msg.id, question_id)
|
||||||
|
logging.info(f"Sent Discord DM to helper {user_id}")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to send Discord DM to helper {user_id_val}: {e}")
|
||||||
|
|
||||||
|
async def run_discord_bot():
|
||||||
|
global discord_loop
|
||||||
|
logging.info("Starting Discord bot...")
|
||||||
|
discord_loop = asyncio.get_running_loop()
|
||||||
|
try:
|
||||||
|
await discord_client.start(DISCORD_TOKEN)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Discord Bot failed to run: {e}")
|
||||||
|
|
||||||
# FastAPI Web Server Setup
|
# FastAPI Web Server Setup
|
||||||
app = FastAPI(title="RIT Telegram Intermediary Bot HTTP Server")
|
app = FastAPI(title="RIT Telegram & Discord Intermediary Bot HTTP Server")
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
@@ -224,31 +366,70 @@ class QuestionPayload(BaseModel):
|
|||||||
@app.post("/send_question")
|
@app.post("/send_question")
|
||||||
def send_question(payload: QuestionPayload):
|
def send_question(payload: QuestionPayload):
|
||||||
current_config = load_config()
|
current_config = load_config()
|
||||||
helpers = current_config.get("helper_chat_ids", [])
|
|
||||||
|
|
||||||
if not helpers:
|
# 1. Telegram Broadcast
|
||||||
logging.warning("No helper chat IDs registered in config.json.")
|
telegram_helpers = current_config.get("helper_chat_ids", [])
|
||||||
return {"status": "ignored", "reason": "No helpers registered"}
|
telegram_sent = 0
|
||||||
|
if telegram_helpers:
|
||||||
|
logging.info(f"Broadcasting question {payload.question_id} to {len(telegram_helpers)} Telegram helpers.")
|
||||||
|
formatted_msg = (
|
||||||
|
f"❓ *New Student Question!*\n\n"
|
||||||
|
f"👤 *Author:* {payload.author}\n"
|
||||||
|
f"📌 *Topic:* {payload.title}\n"
|
||||||
|
f"📝 *Details:* {payload.body}\n\n"
|
||||||
|
f"💬 *Reply to this message directly to submit your answer.*"
|
||||||
|
)
|
||||||
|
for chat_id in telegram_helpers:
|
||||||
|
res = send_telegram_message(chat_id, formatted_msg)
|
||||||
|
if res.get("ok"):
|
||||||
|
message_id = res["result"]["message_id"]
|
||||||
|
save_mapping(chat_id, message_id, payload.question_id)
|
||||||
|
telegram_sent += 1
|
||||||
|
|
||||||
logging.info(f"Broadcasting question {payload.question_id} to {len(helpers)} helpers.")
|
# 2. Discord Broadcast
|
||||||
|
discord_helpers = current_config.get("discord_helper_user_ids", [])
|
||||||
|
discord_sent = 0
|
||||||
|
if discord_helpers and DISCORD_TOKEN:
|
||||||
|
logging.info(f"Broadcasting question {payload.question_id} to {len(discord_helpers)} Discord helpers.")
|
||||||
|
if discord_loop:
|
||||||
|
try:
|
||||||
|
asyncio.run_coroutine_threadsafe(
|
||||||
|
broadcast_discord_question(payload.question_id, payload.title, payload.body, payload.author, discord_helpers),
|
||||||
|
discord_loop
|
||||||
|
)
|
||||||
|
discord_sent = len(discord_helpers)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error scheduling Discord broadcast: {e}")
|
||||||
|
else:
|
||||||
|
logging.warning("Discord loop not running. Skipping Discord broadcast.")
|
||||||
|
|
||||||
formatted_msg = (
|
return {
|
||||||
f"❓ *New Student Question!*\n\n"
|
"status": "success",
|
||||||
f"👤 *Author:* {payload.author}\n"
|
"telegram_delivered_to": telegram_sent,
|
||||||
f"📌 *Topic:* {payload.title}\n"
|
"discord_queued_for": discord_sent
|
||||||
f"📝 *Details:* {payload.body}\n\n"
|
}
|
||||||
f"💬 *Reply to this message directly to submit your answer.*"
|
|
||||||
)
|
|
||||||
|
|
||||||
sent_count = 0
|
async def run_uvicorn():
|
||||||
for chat_id in helpers:
|
config = uvicorn.Config(app, host="0.0.0.0", port=8082, loop="asyncio")
|
||||||
res = send_telegram_message(chat_id, formatted_msg)
|
server = uvicorn.Server(config)
|
||||||
if res.get("ok"):
|
await server.serve()
|
||||||
message_id = res["result"]["message_id"]
|
|
||||||
save_mapping(chat_id, message_id, payload.question_id)
|
|
||||||
sent_count += 1
|
|
||||||
|
|
||||||
return {"status": "success", "delivered_to": sent_count}
|
async def main():
|
||||||
|
# Start Telegram long polling thread
|
||||||
|
polling_thread = threading.Thread(target=telegram_polling_thread, daemon=True)
|
||||||
|
polling_thread.start()
|
||||||
|
|
||||||
|
tasks = []
|
||||||
|
if DISCORD_TOKEN:
|
||||||
|
tasks.append(run_discord_bot())
|
||||||
|
else:
|
||||||
|
logging.warning("Discord Bot Token is empty. Skipping Discord bot startup.")
|
||||||
|
|
||||||
|
tasks.append(run_uvicorn())
|
||||||
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8082)
|
try:
|
||||||
|
asyncio.run(main())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logging.info("Shutting down bot server...")
|
||||||
|
|||||||
Reference in New Issue
Block a user