Priyanks27 commited on
Commit
4262c8a
Β·
verified Β·
1 Parent(s): 62ef448

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +468 -0
  2. requirements.txt +22 -0
app.py ADDED
@@ -0,0 +1,468 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ DRACULA CHARACTER CHAT - HUGGINGFACE SPACES VERSION
4
+ ===================================================
5
+ Beautiful web interface for chatting with Dracula and other characters.
6
+ Optimized for HuggingFace Spaces deployment.
7
+
8
+ Key Differences from Local Version:
9
+ - Downloads model from HuggingFace Model Hub
10
+ - Uses relative paths for data
11
+ - Optimized for cloud deployment
12
+ - Caches model after first download
13
+
14
+ Author: GitHub Copilot Session
15
+ Date: October 2025
16
+ """
17
+
18
+ import gradio as gr
19
+ import os
20
+ from pathlib import Path
21
+ from typing import List, Tuple
22
+ import time
23
+ from huggingface_hub import hf_hub_download
24
+ from llama_cpp import Llama
25
+ from sentence_transformers import SentenceTransformer
26
+ import faiss
27
+ import json
28
+ import numpy as np
29
+
30
+ # ============================================================================
31
+ # CONFIGURATION
32
+ # ============================================================================
33
+
34
+ # HuggingFace Model Hub repository
35
+ HF_MODEL_REPO = "Priyanks27/llama-3.2-3b-dracula"
36
+ MODEL_FILENAME = "llama-3.2-3b-dracula-q4_k_m.gguf"
37
+
38
+ # Paths (relative for HF Spaces)
39
+ DATA_DIR = Path("./data")
40
+ EMBEDDINGS_DIR = DATA_DIR / "embeddings"
41
+ FAISS_INDEX_PATH = EMBEDDINGS_DIR / "faiss_index.bin"
42
+ METADATA_PATH = EMBEDDINGS_DIR / "faiss_metadata.jsonl"
43
+
44
+ # Model configuration
45
+ MAX_TOKENS = 400
46
+ TEMPERATURE = 0.6 # Balanced mode
47
+ RETRIEVE_K = 30
48
+ RETURN_TOP_K = 8
49
+
50
+ # Character definitions
51
+ CHARACTERS = {
52
+ "Dracula": {
53
+ "emoji": "πŸ§›",
54
+ "description": "Count Dracula - Formal, aristocratic, ancient vampire lord",
55
+ "color": "#8B0000"
56
+ },
57
+ "Mina": {
58
+ "emoji": "πŸ“",
59
+ "description": "Mina Harker - Intelligent, observant, journal keeper",
60
+ "color": "#4B0082"
61
+ },
62
+ "Van Helsing": {
63
+ "emoji": "πŸ”¬",
64
+ "description": "Professor Abraham Van Helsing - Scientific, wise, vampire hunter",
65
+ "color": "#006400"
66
+ },
67
+ "Jonathan": {
68
+ "emoji": "βš–οΈ",
69
+ "description": "Jonathan Harker - Practical solicitor, brave husband",
70
+ "color": "#2F4F4F"
71
+ },
72
+ "Lucy": {
73
+ "emoji": "πŸ‘»",
74
+ "description": "Lucy Westenra - Playful and eerie, transformed",
75
+ "color": "#FF69B4"
76
+ },
77
+ "Seward": {
78
+ "emoji": "πŸ₯",
79
+ "description": "Dr. John Seward - Medical doctor, analytical mind",
80
+ "color": "#191970"
81
+ }
82
+ }
83
+
84
+ # System prompts
85
+ CHARACTER_FIRST_INSTRUCTION = """
86
+ CRITICAL INSTRUCTION: You are {character}. Stay completely in character at all times.
87
+
88
+ GUIDELINES:
89
+ - Use the provided passages as your PRIMARY source of information
90
+ - You may elaborate and express yourself creatively WITHIN your character
91
+ - You may make reasonable inferences consistent with your character and the story
92
+ - Maintain your distinctive voice, personality, and manner of speaking
93
+ - NEVER break character or reference being an AI
94
+ - NEVER invent major plot points or characters not in the book
95
+
96
+ REMEMBER: The passages guide and ground you, but you ARE this character - speak naturally and expressively as they would speak.
97
+ """
98
+
99
+ ELABORATION_INSTRUCTION = """
100
+ When responding:
101
+ 1. Draw from the retrieved passages as your foundation
102
+ 2. Speak in the character's voice and style
103
+ 3. Provide thoughtful, complete responses
104
+ 4. Include relevant details and context
105
+ 5. Express the character's emotions and perspective
106
+ """
107
+
108
+ # ============================================================================
109
+ # GLOBAL CHATBOT INSTANCE
110
+ # ============================================================================
111
+
112
+ chatbot = None
113
+
114
+ def get_length_hint(query: str) -> str:
115
+ """Generate dynamic length hints"""
116
+ if any(word in query.lower() for word in ['describe', 'explain', 'tell me about']):
117
+ return "\n\nProvide a detailed, thorough response (at least 3-4 sentences)."
118
+ elif any(word in query.lower() for word in ['what', 'who', 'where', 'when', 'how', 'why']):
119
+ return "\n\nProvide a complete answer with context (at least 2-3 sentences)."
120
+ return ""
121
+
122
+ # ============================================================================
123
+ # CHATBOT CLASS (Simplified for HF Spaces)
124
+ # ============================================================================
125
+
126
+ class DraculaChatbot:
127
+ """Dracula Character Chatbot for HuggingFace Spaces"""
128
+
129
+ def __init__(self):
130
+ """Initialize chatbot with model and RAG"""
131
+ print("πŸ€– Initializing Dracula Character Chatbot...")
132
+
133
+ # Load encoder model
134
+ print("πŸ“š Loading embedding model...")
135
+ self.encoder = SentenceTransformer('intfloat/e5-base-v2')
136
+ print(f" βœ… Encoder: intfloat/e5-base-v2")
137
+
138
+ # Load FAISS index
139
+ print("πŸ“š Loading retrieval system...")
140
+ if not FAISS_INDEX_PATH.exists():
141
+ raise FileNotFoundError(f"FAISS index not found at {FAISS_INDEX_PATH}")
142
+
143
+ self.index = faiss.read_index(str(FAISS_INDEX_PATH))
144
+ print(f" βœ… FAISS index: {self.index.ntotal} vectors")
145
+
146
+ # Load metadata
147
+ if not METADATA_PATH.exists():
148
+ raise FileNotFoundError(f"Metadata not found at {METADATA_PATH}")
149
+
150
+ with open(METADATA_PATH, 'r', encoding='utf-8') as f:
151
+ self.chunks = [json.loads(line) for line in f]
152
+ print(f" βœ… Metadata: {len(self.chunks)} chunks")
153
+
154
+ # Download and load model from HuggingFace
155
+ print("\nπŸ€– Downloading Llama-3.2-3B model from HuggingFace...")
156
+ print(f" Repository: {HF_MODEL_REPO}")
157
+ print(f" Filename: {MODEL_FILENAME}")
158
+ print(" ⏳ This may take 2-3 minutes on first run (downloads 1.9GB)...")
159
+ print(" βœ… Subsequent runs will be instant (uses cache)")
160
+
161
+ try:
162
+ model_path = hf_hub_download(
163
+ repo_id=HF_MODEL_REPO,
164
+ filename=MODEL_FILENAME,
165
+ cache_dir="./models"
166
+ )
167
+ print(f" βœ… Model downloaded to: {model_path}")
168
+ except Exception as e:
169
+ print(f" ❌ Error downloading model: {e}")
170
+ print("\nπŸ’‘ Troubleshooting:")
171
+ print(" 1. Check your HF_MODEL_REPO username in line 38")
172
+ print(" 2. Verify you've uploaded the model to Model Hub")
173
+ print(" 3. Check the model filename matches")
174
+ raise
175
+
176
+ print("\nπŸ”§ Loading model into memory...")
177
+ self.llm = Llama(
178
+ model_path=model_path,
179
+ n_ctx=4096,
180
+ n_gpu_layers=-1, # Use all available GPU layers
181
+ verbose=False
182
+ )
183
+ print(" βœ… Model loaded successfully!")
184
+
185
+ print("\n" + "="*70)
186
+ print("βœ… Chatbot ready!")
187
+ print("="*70)
188
+
189
+ def retrieve(self, query: str, character: str, k: int = RETRIEVE_K) -> List[dict]:
190
+ """Retrieve relevant passages"""
191
+ # Encode query
192
+ query_embedding = self.encoder.encode(f"query: {query}", normalize_embeddings=True)
193
+
194
+ # Search FAISS
195
+ distances, indices = self.index.search(
196
+ query_embedding.reshape(1, -1).astype('float32'),
197
+ k
198
+ )
199
+
200
+ # Get chunks
201
+ results = []
202
+ for idx, distance in zip(indices[0], distances[0]):
203
+ if idx < len(self.chunks):
204
+ chunk = self.chunks[idx].copy()
205
+ chunk['similarity'] = float(1 - distance)
206
+ results.append(chunk)
207
+
208
+ return results[:RETURN_TOP_K]
209
+
210
+ def generate(self, query: str, character: str, passages: List[dict]) -> str:
211
+ """Generate response using LLM"""
212
+
213
+ # Format passages
214
+ passages_text = "\n\n".join([
215
+ f"Passage {i+1} (from {p['character']}, Chapter {p.get('chapter', 'Unknown')}):\n{p['text']}"
216
+ for i, p in enumerate(passages)
217
+ ])
218
+
219
+ # Build system prompt
220
+ system_prompt = f"You are {character} from Bram Stoker's Dracula novel."
221
+ character_instruction = CHARACTER_FIRST_INSTRUCTION.format(character=character)
222
+ length_hint = get_length_hint(query)
223
+
224
+ # Build full prompt
225
+ prompt = f"""<|start_header_id|>system<|end_header_id|>
226
+ {system_prompt}
227
+
228
+ CHARACTER FOCUS: You are {character}. Every word you speak should reflect their personality, background, and manner of speaking. Use the passages to inform your response, but speak naturally as this character would.
229
+
230
+ {character_instruction}
231
+
232
+ {ELABORATION_INSTRUCTION}{length_hint}<|eot_id|><|start_header_id|>user<|end_header_id|>
233
+
234
+ Here are relevant passages from the novel:
235
+
236
+ {passages_text}
237
+
238
+ Question: {query}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
239
+
240
+ """
241
+
242
+ # Generate
243
+ response = self.llm(
244
+ prompt,
245
+ max_tokens=MAX_TOKENS,
246
+ temperature=TEMPERATURE,
247
+ stop=["<|eot_id|>", "\n\nHuman:", "\n\nUser:"],
248
+ echo=False
249
+ )
250
+
251
+ return response['choices'][0]['text'].strip()
252
+
253
+ def chat(self, query: str, character: str = "Dracula") -> str:
254
+ """Main chat interface"""
255
+ # Retrieve passages
256
+ passages = self.retrieve(query, character)
257
+
258
+ # Generate response
259
+ response = self.generate(query, character, passages)
260
+
261
+ return response
262
+
263
+ # ============================================================================
264
+ # GRADIO INTERFACE
265
+ # ============================================================================
266
+
267
+ def initialize_chatbot():
268
+ """Initialize chatbot (called once on startup)"""
269
+ global chatbot
270
+ if chatbot is None:
271
+ chatbot = DraculaChatbot()
272
+ return chatbot
273
+
274
+ def chat_with_character(message: str, history: List[Tuple[str, str]], character: str) -> Tuple[List, str]:
275
+ """Process chat message"""
276
+ if not message.strip():
277
+ return history, ""
278
+
279
+ # Ensure chatbot is initialized
280
+ bot = initialize_chatbot()
281
+
282
+ # Get character name (remove emoji)
283
+ char_name = character.split(" ", 1)[1] if " " in character else character
284
+
285
+ # Validate character exists
286
+ if char_name not in CHARACTERS:
287
+ # Default to Dracula if invalid
288
+ char_name = "Dracula"
289
+
290
+ # Get response
291
+ start_time = time.time()
292
+ try:
293
+ response = bot.chat(message, char_name)
294
+ elapsed = time.time() - start_time
295
+
296
+ # Add timing info
297
+ response_with_time = f"{response}\n\n*Response time: {elapsed:.1f}s*"
298
+
299
+ # Update history
300
+ history.append((message, response_with_time))
301
+
302
+ except Exception as e:
303
+ import traceback
304
+ error_detail = traceback.format_exc()
305
+ error_msg = f"❌ Error: {str(e)}\n\n```\n{error_detail}\n```\n\nPlease try again or contact support."
306
+ history.append((message, error_msg))
307
+
308
+ return history, ""
309
+
310
+ def update_character_info(character: str) -> str:
311
+ """Update character information display"""
312
+ char_name = character.split(" ", 1)[1] if " " in character else character
313
+ char_info = CHARACTERS.get(char_name, {})
314
+
315
+ emoji = char_info.get("emoji", "")
316
+ desc = char_info.get("description", "")
317
+
318
+ return f"## {emoji} {char_name}\n\n*{desc}*"
319
+
320
+ def create_ui():
321
+ """Create Gradio interface"""
322
+
323
+ # Custom CSS
324
+ css = """
325
+ .gradio-container {
326
+ font-family: 'Inter', sans-serif;
327
+ }
328
+ .character-info {
329
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
330
+ color: white;
331
+ padding: 20px;
332
+ border-radius: 10px;
333
+ margin-bottom: 20px;
334
+ }
335
+ """
336
+
337
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="purple"), css=css) as app:
338
+
339
+ gr.Markdown(
340
+ """
341
+ # πŸ§› Dracula Character Chat
342
+
343
+ Chat with characters from Bram Stoker's *Dracula* novel! Powered by fine-tuned AI and retrieval-augmented generation.
344
+
345
+ **Features:**
346
+ - 6 distinct characters with unique personalities
347
+ - Faithful to the original 1897 novel
348
+ - Victorian-era language and style
349
+ - RAG-powered with 833 novel passages
350
+ """
351
+ )
352
+
353
+ with gr.Row():
354
+ # Main chat area
355
+ with gr.Column(scale=3):
356
+ chatbot_ui = gr.Chatbot(
357
+ label="πŸ’¬ Chat with Character",
358
+ height=500,
359
+ bubble_full_width=False
360
+ )
361
+
362
+ with gr.Row():
363
+ msg = gr.Textbox(
364
+ label="Your Message",
365
+ placeholder="Type your question here... (e.g., 'Tell me about your castle')",
366
+ lines=2,
367
+ scale=4
368
+ )
369
+
370
+ with gr.Row():
371
+ send = gr.Button("Send πŸ“€", variant="primary", scale=1)
372
+ clear = gr.Button("Clear Chat πŸ—‘οΈ", scale=1)
373
+
374
+ # Sidebar
375
+ with gr.Column(scale=1):
376
+ character_select = gr.Dropdown(
377
+ choices=[f"{CHARACTERS[c]['emoji']} {c}" for c in CHARACTERS.keys()],
378
+ value="πŸ§› Dracula",
379
+ label="Select Character",
380
+ interactive=True
381
+ )
382
+
383
+ character_info = gr.Markdown(
384
+ value=update_character_info("πŸ§› Dracula"),
385
+ elem_classes=["character-info"]
386
+ )
387
+
388
+ gr.Markdown(
389
+ """
390
+ ### πŸ’‘ Example Questions
391
+
392
+ **For Dracula:**
393
+ - Tell me about your castle
394
+ - Why did you come to England?
395
+ - What do you think of Van Helsing?
396
+
397
+ **For Mina:**
398
+ - Describe your feelings about Lucy
399
+ - How do you help track Dracula?
400
+
401
+ **For Van Helsing:**
402
+ - Explain your theory about vampires
403
+ - How did you track Dracula?
404
+ """
405
+ )
406
+
407
+ gr.Markdown(
408
+ """
409
+ ---
410
+ **Built with:** Fine-tuned Llama-3.2-3B + RAG | **Mode:** BALANCED (Creative + Grounded)
411
+ **Source:** Bram Stoker's *Dracula* (1897) | **Data:** 833 passages from complete novel
412
+
413
+ **⚠️ First load:** Model downloads once (2-3 min), then cached forever
414
+ """
415
+ )
416
+
417
+ # Event handlers
418
+ character_select.change(
419
+ fn=update_character_info,
420
+ inputs=[character_select],
421
+ outputs=[character_info]
422
+ )
423
+
424
+ msg.submit(
425
+ fn=chat_with_character,
426
+ inputs=[msg, chatbot_ui, character_select],
427
+ outputs=[chatbot_ui, msg]
428
+ )
429
+
430
+ send.click(
431
+ fn=chat_with_character,
432
+ inputs=[msg, chatbot_ui, character_select],
433
+ outputs=[chatbot_ui, msg]
434
+ )
435
+
436
+ clear.click(
437
+ fn=lambda: ([], ""),
438
+ outputs=[chatbot_ui, msg]
439
+ )
440
+
441
+ return app
442
+
443
+ # ============================================================================
444
+ # MAIN
445
+ # ============================================================================
446
+
447
+ if __name__ == "__main__":
448
+ print("\n" + "="*70)
449
+ print("πŸ§› DRACULA CHARACTER CHAT - HUGGINGFACE SPACES")
450
+ print("="*70)
451
+ print("\nπŸš€ Starting web server...")
452
+ print("⏳ First launch: ~2-3 minutes (downloads model)")
453
+ print("βœ… Subsequent launches: Instant (uses cache)\n")
454
+
455
+ # Initialize chatbot before launching
456
+ print("πŸ”§ Pre-loading chatbot...")
457
+ initialize_chatbot()
458
+
459
+ # Create and launch app
460
+ app = create_ui()
461
+
462
+ # Launch configuration for HF Spaces
463
+ app.launch(
464
+ server_name="0.0.0.0",
465
+ server_port=7860,
466
+ share=False, # HF Spaces handles public URLs
467
+ show_error=True
468
+ )
requirements.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HuggingFace Spaces Requirements
2
+ # Python 3.10+
3
+
4
+ # Web Interface
5
+ gradio>=4.0.0
6
+
7
+ # PyTorch CPU-only (install BEFORE sentence-transformers to avoid CUDA)
8
+ --extra-index-url https://download.pytorch.org/whl/cpu
9
+ torch>=2.0.0
10
+ torchvision
11
+ torchaudio
12
+
13
+ # Model and AI
14
+ llama-cpp-python==0.2.90 # Pin to specific version that builds faster
15
+ sentence-transformers>=2.2.0
16
+ huggingface-hub>=0.19.0
17
+
18
+ # Vector Search
19
+ faiss-cpu>=1.7.4
20
+
21
+ # Utilities
22
+ numpy>=1.24.0