Spaces:
Sleeping
Sleeping
Delete chatbot.py
Browse files- chatbot.py +0 -210
chatbot.py
DELETED
|
@@ -1,210 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
chatbot.py — Conversational AI that answers from what it has actually learned.
|
| 3 |
-
Synthesizes knowledge into natural paragraph answers, like ChatGPT/Claude/Gemini.
|
| 4 |
-
"""
|
| 5 |
-
import json, re, math, os, random
|
| 6 |
-
from collections import Counter, defaultdict
|
| 7 |
-
from html import unescape
|
| 8 |
-
|
| 9 |
-
KNOWLEDGE_FILE = 'knowledge.jsonl'
|
| 10 |
-
STOPWORDS = {
|
| 11 |
-
'a','an','the','is','it','in','on','at','to','for','of','and','or','but',
|
| 12 |
-
'was','are','were','be','been','have','has','had','do','does','did','will',
|
| 13 |
-
'would','could','should','may','might','that','this','these','those','with',
|
| 14 |
-
'from','by','as','not','also','than','then','so','if','when','what','how',
|
| 15 |
-
'who','which','its','their','our','your','my','his','her','we','they','he',
|
| 16 |
-
'she','you','i','me','him','us','them','said','says',
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
def tokenize(text):
|
| 20 |
-
text = re.sub(r'[^\w\s]', ' ', text.lower())
|
| 21 |
-
return [w for w in text.split() if w not in STOPWORDS and len(w) > 2]
|
| 22 |
-
|
| 23 |
-
def clean_text(text):
|
| 24 |
-
"""Remove HTML artifacts from stored text."""
|
| 25 |
-
text = unescape(text)
|
| 26 |
-
text = re.sub(r'&#?[a-zA-Z0-9]+;', ' ', text)
|
| 27 |
-
text = re.sub(r'<[^>]+>', ' ', text)
|
| 28 |
-
text = re.sub(r'\s+', ' ', text).strip()
|
| 29 |
-
return text
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
class KnowledgeBase:
|
| 33 |
-
def __init__(self):
|
| 34 |
-
self.docs=[]; self.doc_tokens=[]; self.idf={}; self.last_count=0
|
| 35 |
-
self._load()
|
| 36 |
-
|
| 37 |
-
def _load(self):
|
| 38 |
-
if not os.path.exists(KNOWLEDGE_FILE): return
|
| 39 |
-
new_docs = []
|
| 40 |
-
try:
|
| 41 |
-
with open(KNOWLEDGE_FILE,'r',encoding='utf-8') as f:
|
| 42 |
-
for line in f:
|
| 43 |
-
line=line.strip()
|
| 44 |
-
if line:
|
| 45 |
-
try: new_docs.append(json.loads(line))
|
| 46 |
-
except: pass
|
| 47 |
-
except: return
|
| 48 |
-
if len(new_docs)==self.last_count: return
|
| 49 |
-
self.docs=new_docs; self.last_count=len(new_docs)
|
| 50 |
-
self.doc_tokens=[tokenize(clean_text(d.get('text',''))) for d in self.docs]
|
| 51 |
-
N=len(self.docs)
|
| 52 |
-
df=defaultdict(int)
|
| 53 |
-
for tokens in self.doc_tokens:
|
| 54 |
-
for w in set(tokens): df[w]+=1
|
| 55 |
-
self.idf={w:math.log((N+1)/(c+1))+1 for w,c in df.items()}
|
| 56 |
-
|
| 57 |
-
def search(self, query, top_k=8):
|
| 58 |
-
self._load()
|
| 59 |
-
if not self.docs: return []
|
| 60 |
-
q=tokenize(query)
|
| 61 |
-
if not q: return []
|
| 62 |
-
def score(dt):
|
| 63 |
-
tf=Counter(dt); n=max(len(dt),1)
|
| 64 |
-
return sum((tf[w]/n)*self.idf.get(w,0) for w in q if w in tf)
|
| 65 |
-
ranked=sorted(range(len(self.docs)),key=lambda i:-score(self.doc_tokens[i]))
|
| 66 |
-
return [(self.docs[i], score(self.doc_tokens[i])) for i in ranked[:top_k]
|
| 67 |
-
if score(self.doc_tokens[i])>0]
|
| 68 |
-
|
| 69 |
-
def get_stats(self):
|
| 70 |
-
self._load()
|
| 71 |
-
return {
|
| 72 |
-
'total': len(self.docs),
|
| 73 |
-
'categories': dict(Counter(d.get('category','other') for d in self.docs)),
|
| 74 |
-
'sources': dict(Counter(d.get('source','unknown') for d in self.docs)),
|
| 75 |
-
}
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
def _synthesize(query, results):
|
| 79 |
-
"""
|
| 80 |
-
Turn retrieved article snippets into a natural conversational answer.
|
| 81 |
-
Like ChatGPT — reads all sources, writes a coherent paragraph response.
|
| 82 |
-
"""
|
| 83 |
-
q_tokens = set(tokenize(query))
|
| 84 |
-
|
| 85 |
-
# Gather the best individual sentences across all docs
|
| 86 |
-
all_sentences = []
|
| 87 |
-
for doc, sc in results:
|
| 88 |
-
text = clean_text(doc.get('text', ''))
|
| 89 |
-
# Split into sentences
|
| 90 |
-
sents = re.split(r'(?<=[.!?])\s+', text)
|
| 91 |
-
for s in sents:
|
| 92 |
-
s = s.strip()
|
| 93 |
-
if len(s) < 30: continue
|
| 94 |
-
overlap = len(q_tokens & set(tokenize(s)))
|
| 95 |
-
# Slight relevance boost for sentences that mention query words
|
| 96 |
-
all_sentences.append((overlap + sc * 0.3, s))
|
| 97 |
-
|
| 98 |
-
# Deduplicate and pick top sentences
|
| 99 |
-
seen = set(); chosen = []
|
| 100 |
-
for score, sent in sorted(all_sentences, reverse=True):
|
| 101 |
-
key = sent[:60].lower()
|
| 102 |
-
if key not in seen and len(chosen) < 6:
|
| 103 |
-
seen.add(key)
|
| 104 |
-
chosen.append(sent)
|
| 105 |
-
|
| 106 |
-
if not chosen:
|
| 107 |
-
return None
|
| 108 |
-
|
| 109 |
-
# Build a flowing paragraph (not a bullet dump)
|
| 110 |
-
# Join sentences intelligently
|
| 111 |
-
answer_sents = chosen[:4]
|
| 112 |
-
|
| 113 |
-
# Fix any leftover HTML entities in chosen sentences
|
| 114 |
-
answer_sents = [unescape(s) for s in answer_sents]
|
| 115 |
-
|
| 116 |
-
# Combine into paragraphs
|
| 117 |
-
if len(answer_sents) <= 2:
|
| 118 |
-
paragraph = ' '.join(answer_sents)
|
| 119 |
-
else:
|
| 120 |
-
paragraph = ' '.join(answer_sents[:2])
|
| 121 |
-
rest = ' '.join(answer_sents[2:])
|
| 122 |
-
paragraph = paragraph + '\n\n' + rest
|
| 123 |
-
|
| 124 |
-
return paragraph.strip()
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
class RAGChatbot:
|
| 128 |
-
def __init__(self):
|
| 129 |
-
self.kb = KnowledgeBase()
|
| 130 |
-
|
| 131 |
-
def _answer(self, query, results, stats):
|
| 132 |
-
total = stats['total']
|
| 133 |
-
|
| 134 |
-
if total == 0:
|
| 135 |
-
return ("My brain is empty right now 🧠\n\n"
|
| 136 |
-
"Go to **Control → ▶ Start Text** to feed me internet articles. "
|
| 137 |
-
"The more I read, the smarter I get!")
|
| 138 |
-
|
| 139 |
-
if not results:
|
| 140 |
-
topics = ', '.join(list(stats['categories'].keys())[:5])
|
| 141 |
-
return (f"I've read **{total} articles** so far, but I haven't learned "
|
| 142 |
-
f"enough about *{query}* yet.\n\n"
|
| 143 |
-
f"My current knowledge is mostly about: **{topics}**.\n\n"
|
| 144 |
-
f"Keep me training and I'll learn more over time! 📚")
|
| 145 |
-
|
| 146 |
-
answer = _synthesize(query, results)
|
| 147 |
-
if not answer:
|
| 148 |
-
return (f"I found related articles but couldn't form a clear answer yet. "
|
| 149 |
-
f"Try asking in a different way!")
|
| 150 |
-
|
| 151 |
-
# Conversational wrap — just the answer, clean and simple
|
| 152 |
-
return answer
|
| 153 |
-
|
| 154 |
-
def chat(self, user_message, history):
|
| 155 |
-
history = list(history or [])
|
| 156 |
-
msg = user_message.strip()
|
| 157 |
-
if not msg: return history
|
| 158 |
-
|
| 159 |
-
lower = msg.lower()
|
| 160 |
-
stats = self.kb.get_stats()
|
| 161 |
-
|
| 162 |
-
# ── Greetings ──
|
| 163 |
-
if lower in ('hi','hello','hey','sup','yo','hiya','howdy','helo','hai'):
|
| 164 |
-
total = stats['total']
|
| 165 |
-
if total == 0:
|
| 166 |
-
bot_reply = ("Hey! 👋 I'm your AI, but I haven't read anything yet.\n\n"
|
| 167 |
-
"Go to **Control → ▶ Start Text** to start teaching me!")
|
| 168 |
-
else:
|
| 169 |
-
topics = ', '.join(list(stats['categories'].keys())[:4])
|
| 170 |
-
bot_reply = (f"Hey! 👋 I've read **{total} articles** so far.\n\n"
|
| 171 |
-
f"I know quite a bit about **{topics}**. What do you want to know?")
|
| 172 |
-
|
| 173 |
-
elif any(p in lower for p in ('how are you','how r you',"what's up",'whats up')):
|
| 174 |
-
bot_reply = "I'm doing great — always learning! 🤖 What do you want to know?"
|
| 175 |
-
|
| 176 |
-
elif any(p in lower for p in ('who are you','what are you','what can you do','tell me about yourself')):
|
| 177 |
-
bot_reply = (
|
| 178 |
-
"I'm a **Living Neural Network** — an AI that learns from real internet articles in real time! 🧠\n\n"
|
| 179 |
-
f"I've read **{stats['total']} articles** so far. Unlike ChatGPT, I only know what "
|
| 180 |
-
"I've actually been trained on — no pre-built knowledge. "
|
| 181 |
-
"The more articles I read, the smarter I get. Ask me anything I might have learned!"
|
| 182 |
-
)
|
| 183 |
-
|
| 184 |
-
elif lower in ('stats','status','what do you know','knowledge'):
|
| 185 |
-
lines = '\n'.join(f"• **{k}**: {v} articles"
|
| 186 |
-
for k,v in sorted(stats['categories'].items(),key=lambda x:-x[1]))
|
| 187 |
-
srcs = ', '.join(f"{k} ({v})" for k,v in list(stats['sources'].items())[:6])
|
| 188 |
-
bot_reply = (f"**My knowledge so far:**\n\n{lines or '• nothing yet'}\n\n"
|
| 189 |
-
f"**Sources I've read from:** {srcs or 'none yet'}\n\n"
|
| 190 |
-
f"**Total:** {stats['total']} articles")
|
| 191 |
-
|
| 192 |
-
elif lower in ('help','?','commands'):
|
| 193 |
-
bot_reply = ("Just ask me anything — I'll answer from what I've learned!\n\n"
|
| 194 |
-
"**Examples:**\n"
|
| 195 |
-
"- *What is science?*\n"
|
| 196 |
-
"- *Who is Elon Musk?*\n"
|
| 197 |
-
"- *What's happening in AI?*\n"
|
| 198 |
-
"- *Tell me about climate change*\n\n"
|
| 199 |
-
"Type `stats` to see what I know.")
|
| 200 |
-
|
| 201 |
-
elif any(p in lower for p in ('thanks','thank you','thx','ty')):
|
| 202 |
-
bot_reply = "You're welcome! 😊"
|
| 203 |
-
|
| 204 |
-
else:
|
| 205 |
-
results = self.kb.search(msg, top_k=8)
|
| 206 |
-
bot_reply = self._answer(msg, results, stats)
|
| 207 |
-
|
| 208 |
-
history.append({"role": "user", "content": user_message})
|
| 209 |
-
history.append({"role": "assistant", "content": bot_reply})
|
| 210 |
-
return history
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|