harismlnaslm commited on
Commit
e513905
·
1 Parent(s): 6eaa3c3

Replace app.py with pure API version - no HTML interfaces

Browse files
Files changed (3) hide show
  1. app.py +253 -395
  2. app_api_only.py +426 -0
  3. app_backup.py +568 -0
app.py CHANGED
@@ -1,23 +1,19 @@
1
  #!/usr/bin/env python3
2
  """
3
- Textilindo AI Assistant - Hugging Face Spaces FastAPI Application
4
- Main application file for deployment on Hugging Face Spaces
5
  """
6
 
7
  import os
8
  import json
9
  import logging
 
10
  from pathlib import Path
11
- from typing import Optional, Dict, Any
12
- from fastapi import FastAPI, HTTPException, Request, BackgroundTasks
13
- from fastapi.responses import HTMLResponse, JSONResponse
14
- from fastapi.staticfiles import StaticFiles
15
- from fastapi.middleware.cors import CORSMiddleware
16
  from pydantic import BaseModel
17
  import uvicorn
18
- from huggingface_hub import InferenceClient
19
- import requests
20
- from datetime import datetime
21
 
22
  # Setup logging
23
  logging.basicConfig(level=logging.INFO)
@@ -25,21 +21,40 @@ logger = logging.getLogger(__name__)
25
 
26
  # Initialize FastAPI app
27
  app = FastAPI(
28
- title="Textilindo AI Assistant",
29
- description="AI Assistant for Textilindo textile company",
30
  version="1.0.0"
31
  )
32
 
33
- # Add CORS middleware
34
- app.add_middleware(
35
- CORSMiddleware,
36
- allow_origins=["*"],
37
- allow_credentials=True,
38
- allow_methods=["*"],
39
- allow_headers=["*"],
40
- )
 
 
 
 
41
 
42
  # Request/Response models
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  class ChatRequest(BaseModel):
44
  message: str
45
  conversation_id: Optional[str] = None
@@ -49,240 +64,47 @@ class ChatResponse(BaseModel):
49
  conversation_id: str
50
  status: str = "success"
51
 
52
- class HealthResponse(BaseModel):
53
- status: str
54
- message: str
55
- version: str = "1.0.0"
56
-
57
- class TextilindoAI:
58
- """Textilindo AI Assistant using HuggingFace Inference API"""
59
-
60
- def __init__(self):
61
- self.api_key = os.getenv('HUGGINGFACE_API_KEY')
62
- self.model = os.getenv('DEFAULT_MODEL', 'meta-llama/Llama-3.1-8B-Instruct')
63
- self.system_prompt = self.load_system_prompt()
64
-
65
- if not self.api_key:
66
- logger.warning("HUGGINGFACE_API_KEY not found. Using mock responses.")
67
- self.client = None
68
- else:
69
- try:
70
- self.client = InferenceClient(
71
- token=self.api_key,
72
- model=self.model
73
- )
74
- logger.info(f"Initialized with model: {self.model}")
75
- except Exception as e:
76
- logger.error(f"Failed to initialize InferenceClient: {e}")
77
- self.client = None
78
-
79
- def load_system_prompt(self) -> str:
80
- """Load system prompt from config file"""
81
- try:
82
- prompt_path = Path("configs/system_prompt.md")
83
- if prompt_path.exists():
84
- with open(prompt_path, 'r', encoding='utf-8') as f:
85
- content = f.read()
86
-
87
- # Extract system prompt from markdown
88
- if 'SYSTEM_PROMPT = """' in content:
89
- start = content.find('SYSTEM_PROMPT = """') + len('SYSTEM_PROMPT = """')
90
- end = content.find('"""', start)
91
- return content[start:end].strip()
92
- else:
93
- # Fallback: use entire content
94
- return content.strip()
95
- else:
96
- return self.get_default_system_prompt()
97
- except Exception as e:
98
- logger.error(f"Error loading system prompt: {e}")
99
- return self.get_default_system_prompt()
100
-
101
- def get_default_system_prompt(self) -> str:
102
- """Default system prompt if file not found"""
103
- return """You are a friendly and helpful AI assistant for Textilindo, a textile company.
104
-
105
- Always respond in Indonesian (Bahasa Indonesia).
106
- Keep responses short and direct.
107
- Be friendly and helpful.
108
- Use exact information from the knowledge base.
109
- The company uses yards for sales.
110
- Minimum purchase is 1 roll (67-70 yards)."""
111
-
112
- def generate_response(self, user_message: str) -> str:
113
- """Generate response using HuggingFace Inference API"""
114
- if not self.client:
115
- return self.get_mock_response(user_message)
116
-
117
- try:
118
- # Create full prompt with system prompt
119
- full_prompt = f"<|system|>\n{self.system_prompt}\n<|user|>\n{user_message}\n<|assistant|>\n"
120
-
121
- # Generate response
122
- response = self.client.text_generation(
123
- full_prompt,
124
- max_new_tokens=512,
125
- temperature=0.7,
126
- top_p=0.9,
127
- top_k=40,
128
- repetition_penalty=1.1,
129
- stop_sequences=["<|end|>", "<|user|>"]
130
- )
131
-
132
- # Extract only the assistant's response
133
- if "<|assistant|>" in response:
134
- assistant_response = response.split("<|assistant|>")[-1].strip()
135
- assistant_response = assistant_response.replace("<|end|>", "").strip()
136
- return assistant_response
137
- else:
138
- return response
139
-
140
- except Exception as e:
141
- logger.error(f"Error generating response: {e}")
142
- return self.get_mock_response(user_message)
143
-
144
- def get_mock_response(self, user_message: str) -> str:
145
- """Mock responses for testing without API key"""
146
- mock_responses = {
147
- "dimana lokasi textilindo": "Textilindo berkantor pusat di Jl. Raya Prancis No.39, Kosambi Tim., Kec. Kosambi, Kabupaten Tangerang, Banten 15213",
148
- "jam berapa textilindo beroperasional": "Jam operasional Senin-Jumat 08:00-17:00, Sabtu 08:00-12:00.",
149
- "berapa ketentuan pembelian": "Minimal order 1 roll per jenis kain",
150
- "bagaimana dengan pembayarannya": "Pembayaran dapat dilakukan via transfer bank atau cash on delivery",
151
- "apa ada gratis ongkir": "Gratis ongkir untuk order minimal 5 roll.",
152
- "apa bisa dikirimkan sample": "hallo kak untuk sampel kita bisa kirimkan gratis ya kak 😊"
153
  }
154
-
155
- # Simple keyword matching
156
- user_lower = user_message.lower()
157
- for key, response in mock_responses.items():
158
- if any(word in user_lower for word in key.split()):
159
- return response
160
-
161
- return "Halo! Saya adalah asisten AI Textilindo. Bagaimana saya bisa membantu Anda hari ini? 😊"
162
-
163
- # Initialize AI assistant
164
- ai_assistant = TextilindoAI()
165
-
166
- # Routes
167
- @app.get("/", response_class=HTMLResponse)
168
- async def root():
169
- """Serve the main chat interface"""
170
- try:
171
- with open("templates/chat.html", "r", encoding="utf-8") as f:
172
- return HTMLResponse(content=f.read())
173
- except FileNotFoundError:
174
- return HTMLResponse(content="""
175
- <!DOCTYPE html>
176
- <html>
177
- <head>
178
- <title>Textilindo AI Assistant</title>
179
- <meta charset="utf-8">
180
- <style>
181
- body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
182
- .chat-container { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin: 20px 0; }
183
- .message { margin: 10px 0; padding: 10px; border-radius: 5px; }
184
- .user { background-color: #e3f2fd; text-align: right; }
185
- .assistant { background-color: #f5f5f5; }
186
- input[type="text"] { width: 70%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
187
- button { padding: 10px 20px; background-color: #2196f3; color: white; border: none; border-radius: 5px; cursor: pointer; }
188
- </style>
189
- </head>
190
- <body>
191
- <h1>🤖 Textilindo AI Assistant</h1>
192
- <div class="chat-container">
193
- <div id="chat-messages"></div>
194
- <div style="margin-top: 20px;">
195
- <input type="text" id="message-input" placeholder="Tulis pesan Anda..." onkeypress="handleKeyPress(event)">
196
- <button onclick="sendMessage()">Kirim</button>
197
- </div>
198
- </div>
199
- <script>
200
- async function sendMessage() {
201
- const input = document.getElementById('message-input');
202
- const message = input.value.trim();
203
- if (!message) return;
204
-
205
- // Add user message
206
- addMessage(message, 'user');
207
- input.value = '';
208
-
209
- // Get AI response
210
- try {
211
- const response = await fetch('/chat', {
212
- method: 'POST',
213
- headers: { 'Content-Type': 'application/json' },
214
- body: JSON.stringify({ message: message })
215
- });
216
- const data = await response.json();
217
- addMessage(data.response, 'assistant');
218
- } catch (error) {
219
- addMessage('Maaf, terjadi kesalahan. Silakan coba lagi.', 'assistant');
220
- }
221
- }
222
-
223
- function addMessage(text, sender) {
224
- const messages = document.getElementById('chat-messages');
225
- const div = document.createElement('div');
226
- div.className = `message ${sender}`;
227
- div.textContent = text;
228
- messages.appendChild(div);
229
- messages.scrollTop = messages.scrollHeight;
230
- }
231
-
232
- function handleKeyPress(event) {
233
- if (event.key === 'Enter') {
234
- sendMessage();
235
- }
236
- }
237
- </script>
238
- </body>
239
- </html>
240
- """)
241
-
242
- @app.post("/chat", response_model=ChatResponse)
243
- async def chat(request: ChatRequest):
244
- """Chat endpoint"""
245
- try:
246
- response = ai_assistant.generate_response(request.message)
247
- return ChatResponse(
248
- response=response,
249
- conversation_id=request.conversation_id or "default",
250
- status="success"
251
- )
252
- except Exception as e:
253
- logger.error(f"Error in chat endpoint: {e}")
254
- raise HTTPException(status_code=500, detail="Internal server error")
255
 
256
- @app.get("/health", response_model=HealthResponse)
 
257
  async def health_check():
258
  """Health check endpoint"""
259
- return HealthResponse(
260
- status="healthy",
261
- message="Textilindo AI Assistant is running",
262
- version="1.0.0"
263
- )
264
-
265
- @app.get("/info")
266
- async def get_info():
267
- """Get application information"""
268
  return {
269
- "name": "Textilindo AI Assistant",
270
- "version": "1.0.0",
271
- "model": ai_assistant.model,
272
- "has_api_key": bool(ai_assistant.api_key),
273
- "client_initialized": bool(ai_assistant.client)
274
  }
275
 
276
- # Import training API
277
- from training_api import (
278
- TrainingRequest, TrainingResponse, training_status,
279
- train_model_async, load_training_config, load_training_data, check_gpu_availability
280
- )
281
-
282
  # Training API endpoints
283
  @app.post("/api/train/start", response_model=TrainingResponse)
284
- async def start_training_api(request: TrainingRequest, background_tasks: BackgroundTasks):
285
- """Start training process via API"""
 
 
286
  if training_status["is_training"]:
287
  raise HTTPException(status_code=400, detail="Training already in progress")
288
 
@@ -315,12 +137,12 @@ async def start_training_api(request: TrainingRequest, background_tasks: Backgro
315
  )
316
 
317
  @app.get("/api/train/status")
318
- async def get_training_status_api():
319
  """Get current training status"""
320
  return training_status
321
 
322
  @app.get("/api/train/data")
323
- async def get_training_data_info_api():
324
  """Get information about available training data"""
325
  data_dir = Path("data")
326
  if not data_dir.exists():
@@ -350,10 +172,9 @@ async def get_training_data_info_api():
350
  }
351
 
352
  @app.get("/api/train/gpu")
353
- async def get_gpu_info_api():
354
  """Get GPU information"""
355
  try:
356
- import torch
357
  gpu_available = torch.cuda.is_available()
358
  if gpu_available:
359
  gpu_count = torch.cuda.device_count()
@@ -371,7 +192,7 @@ async def get_gpu_info_api():
371
  return {"error": str(e)}
372
 
373
  @app.post("/api/train/test")
374
- async def test_trained_model_api():
375
  """Test the trained model"""
376
  model_path = "./models/textilindo-trained"
377
  if not Path(model_path).exists():
@@ -379,7 +200,6 @@ async def test_trained_model_api():
379
 
380
  try:
381
  from transformers import AutoTokenizer, AutoModelForCausalLM
382
- import torch
383
 
384
  tokenizer = AutoTokenizer.from_pretrained(model_path)
385
  model = AutoModelForCausalLM.from_pretrained(model_path)
@@ -409,160 +229,198 @@ async def test_trained_model_api():
409
  except Exception as e:
410
  return {"error": str(e)}
411
 
412
- # Legacy training endpoints (for backward compatibility)
413
- @app.get("/train")
414
- async def training_interface():
415
- """Training interface"""
416
- try:
417
- with open("templates/training.html", "r", encoding="utf-8") as f:
418
- return HTMLResponse(content=f.read())
419
- except FileNotFoundError:
420
- return HTMLResponse(content="""
421
- <!DOCTYPE html>
422
- <html>
423
- <head>
424
- <title>Textilindo AI Training</title>
425
- <meta charset="utf-8">
426
- <style>
427
- body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
428
- .container { background: #f5f5f5; padding: 20px; border-radius: 10px; margin: 20px 0; }
429
- button { background: #2196f3; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; }
430
- button:hover { background: #1976d2; }
431
- .log { background: #000; color: #0f0; padding: 10px; border-radius: 5px; font-family: monospace; height: 300px; overflow-y: auto; }
432
- </style>
433
- </head>
434
- <body>
435
- <h1>🤖 Textilindo AI Training Interface</h1>
436
-
437
- <div class="container">
438
- <h2>Training Options</h2>
439
- <p>Choose your training method:</p>
440
-
441
- <button onclick="startLightweightTraining()">Start Lightweight Training</button>
442
- <button onclick="checkResources()">Check Resources</button>
443
- <button onclick="viewData()">View Training Data</button>
444
- </div>
445
-
446
- <div class="container">
447
- <h2>Training Log</h2>
448
- <div id="log" class="log">Ready to start training...</div>
449
- </div>
450
-
451
- <script>
452
- function addLog(message) {
453
- const log = document.getElementById('log');
454
- const timestamp = new Date().toLocaleTimeString();
455
- log.innerHTML += `[${timestamp}] ${message}\\n`;
456
- log.scrollTop = log.scrollHeight;
457
- }
458
-
459
- async function startLightweightTraining() {
460
- addLog('Starting lightweight training...');
461
- try {
462
- const response = await fetch('/train/start', {
463
- method: 'POST',
464
- headers: { 'Content-Type': 'application/json' }
465
- });
466
- const result = await response.json();
467
- addLog(`Training result: ${result.message}`);
468
- } catch (error) {
469
- addLog(`Error: ${error.message}`);
470
- }
471
- }
472
-
473
- async function checkResources() {
474
- addLog('Checking resources...');
475
- try {
476
- const response = await fetch('/train/status');
477
- const result = await response.json();
478
- addLog(`Resources: ${JSON.stringify(result, null, 2)}`);
479
- } catch (error) {
480
- addLog(`Error: ${error.message}`);
481
- }
482
- }
483
-
484
- async function viewData() {
485
- addLog('Loading training data...');
486
- try {
487
- const response = await fetch('/train/data');
488
- const result = await response.json();
489
- addLog(`Data files: ${result.files.join(', ')}`);
490
- } catch (error) {
491
- addLog(`Error: ${error.message}`);
492
- }
493
- }
494
- </script>
495
- </body>
496
- </html>
497
- """)
498
-
499
- @app.post("/train/start")
500
- async def start_training():
501
- """Start lightweight training"""
502
  try:
503
- # Import training script
504
- import subprocess
505
- import sys
 
 
 
 
 
506
 
507
- # Run the training script
508
- result = subprocess.run([
509
- sys.executable, "train_on_space.py"
510
- ], capture_output=True, text=True, timeout=300) # 5 minute timeout
 
 
 
 
 
 
 
 
 
 
511
 
512
- if result.returncode == 0:
513
- return {"message": "Training completed successfully!", "output": result.stdout}
514
- else:
515
- return {"message": "Training failed", "error": result.stderr}
516
-
517
- except subprocess.TimeoutExpired:
518
- return {"message": "Training timed out (5 minutes limit)"}
519
  except Exception as e:
520
- return {"message": f"Training error: {str(e)}"}
 
 
 
 
 
521
 
522
- @app.get("/train/status")
523
- async def training_status():
524
- """Get training status and resources"""
 
 
 
 
 
 
 
 
 
 
525
  try:
526
- import psutil
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
 
528
- return {
529
- "status": "ready",
530
- "cpu_count": psutil.cpu_count(),
531
- "memory_total_gb": round(psutil.virtual_memory().total / (1024**3), 2),
532
- "memory_available_gb": round(psutil.virtual_memory().available / (1024**3), 2),
533
- "disk_free_gb": round(psutil.disk_usage('.').free / (1024**3), 2)
534
- }
535
  except Exception as e:
536
- return {"status": "error", "message": str(e)}
 
 
 
 
 
 
537
 
538
- @app.get("/train/data")
539
- async def training_data():
540
- """Get training data information"""
541
  try:
542
- data_dir = Path("data")
543
- if data_dir.exists():
544
- jsonl_files = list(data_dir.glob("*.jsonl"))
545
- return {
546
- "files": [f.name for f in jsonl_files],
547
- "count": len(jsonl_files)
548
- }
549
- else:
550
- return {"files": [], "count": 0}
 
 
 
 
551
  except Exception as e:
552
- return {"error": str(e)}
553
-
554
- # Mount static files if they exist
555
- if Path("static").exists():
556
- app.mount("/static", StaticFiles(directory="static"), name="static")
557
 
558
  if __name__ == "__main__":
559
- # Get port from environment variable (Hugging Face Spaces uses 7860)
560
- port = int(os.getenv("PORT", 7860))
561
-
562
- # Run the application
563
- uvicorn.run(
564
- "app:app",
565
- host="0.0.0.0",
566
- port=port,
567
- log_level="info"
568
- )
 
1
  #!/usr/bin/env python3
2
  """
3
+ Textilindo AI Training API - Pure API Version
4
+ No HTML interfaces, only API endpoints for training and chat
5
  """
6
 
7
  import os
8
  import json
9
  import logging
10
+ import torch
11
  from pathlib import Path
12
+ from datetime import datetime
13
+ from typing import Dict, Any, Optional
14
+ from fastapi import FastAPI, HTTPException, BackgroundTasks
 
 
15
  from pydantic import BaseModel
16
  import uvicorn
 
 
 
17
 
18
  # Setup logging
19
  logging.basicConfig(level=logging.INFO)
 
21
 
22
  # Initialize FastAPI app
23
  app = FastAPI(
24
+ title="Textilindo AI Training API",
25
+ description="Pure API-based training system for Textilindo AI Assistant",
26
  version="1.0.0"
27
  )
28
 
29
+ # Training status storage
30
+ training_status = {
31
+ "is_training": False,
32
+ "progress": 0,
33
+ "status": "idle",
34
+ "current_step": 0,
35
+ "total_steps": 0,
36
+ "loss": 0.0,
37
+ "start_time": None,
38
+ "end_time": None,
39
+ "error": None
40
+ }
41
 
42
  # Request/Response models
43
+ class TrainingRequest(BaseModel):
44
+ model_name: str = "distilgpt2"
45
+ dataset_path: str = "data/lora_dataset_20250910_145055.jsonl"
46
+ config_path: str = "configs/training_config.yaml"
47
+ max_samples: int = 20
48
+ epochs: int = 1
49
+ batch_size: int = 1
50
+ learning_rate: float = 5e-5
51
+
52
+ class TrainingResponse(BaseModel):
53
+ success: bool
54
+ message: str
55
+ training_id: str
56
+ status: str
57
+
58
  class ChatRequest(BaseModel):
59
  message: str
60
  conversation_id: Optional[str] = None
 
64
  conversation_id: str
65
  status: str = "success"
66
 
67
+ # API Information
68
+ @app.get("/")
69
+ async def api_info():
70
+ """API information endpoint"""
71
+ return {
72
+ "name": "Textilindo AI Training API",
73
+ "version": "1.0.0",
74
+ "description": "Pure API-based training system for Textilindo AI Assistant",
75
+ "hardware": "2 vCPU, 16 GB RAM (CPU basic)",
76
+ "status": "ready",
77
+ "endpoints": {
78
+ "training": {
79
+ "start": "POST /api/train/start",
80
+ "status": "GET /api/train/status",
81
+ "data": "GET /api/train/data",
82
+ "gpu": "GET /api/train/gpu",
83
+ "test": "POST /api/train/test"
84
+ },
85
+ "chat": {
86
+ "chat": "POST /chat",
87
+ "health": "GET /health"
88
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  }
90
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
+ # Health check
93
+ @app.get("/health")
94
  async def health_check():
95
  """Health check endpoint"""
 
 
 
 
 
 
 
 
 
96
  return {
97
+ "status": "healthy",
98
+ "timestamp": datetime.now().isoformat(),
99
+ "hardware": "2 vCPU, 16 GB RAM"
 
 
100
  }
101
 
 
 
 
 
 
 
102
  # Training API endpoints
103
  @app.post("/api/train/start", response_model=TrainingResponse)
104
+ async def start_training(request: TrainingRequest, background_tasks: BackgroundTasks):
105
+ """Start training process"""
106
+ global training_status
107
+
108
  if training_status["is_training"]:
109
  raise HTTPException(status_code=400, detail="Training already in progress")
110
 
 
137
  )
138
 
139
  @app.get("/api/train/status")
140
+ async def get_training_status():
141
  """Get current training status"""
142
  return training_status
143
 
144
  @app.get("/api/train/data")
145
+ async def get_training_data_info():
146
  """Get information about available training data"""
147
  data_dir = Path("data")
148
  if not data_dir.exists():
 
172
  }
173
 
174
  @app.get("/api/train/gpu")
175
+ async def get_gpu_info():
176
  """Get GPU information"""
177
  try:
 
178
  gpu_available = torch.cuda.is_available()
179
  if gpu_available:
180
  gpu_count = torch.cuda.device_count()
 
192
  return {"error": str(e)}
193
 
194
  @app.post("/api/train/test")
195
+ async def test_trained_model():
196
  """Test the trained model"""
197
  model_path = "./models/textilindo-trained"
198
  if not Path(model_path).exists():
 
200
 
201
  try:
202
  from transformers import AutoTokenizer, AutoModelForCausalLM
 
203
 
204
  tokenizer = AutoTokenizer.from_pretrained(model_path)
205
  model = AutoModelForCausalLM.from_pretrained(model_path)
 
229
  except Exception as e:
230
  return {"error": str(e)}
231
 
232
+ # Chat API endpoint
233
+ @app.post("/chat", response_model=ChatResponse)
234
+ async def chat(request: ChatRequest):
235
+ """Chat with the AI assistant"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  try:
237
+ # Simple mock response for now
238
+ mock_responses = {
239
+ "dimana lokasi textilindo": "Textilindo berkantor pusat di Jl. Raya Prancis No.39, Kosambi Tim., Kec. Kosambi, Kabupaten Tangerang, Banten 15213",
240
+ "jam berapa textilindo beroperasional": "Jam operasional Senin-Jumat 08:00-17:00, Sabtu 08:00-12:00.",
241
+ "berapa ketentuan pembelian": "Minimal order 1 roll per jenis kain",
242
+ "apa ada gratis ongkir": "Gratis ongkir untuk order minimal 5 roll.",
243
+ "apa bisa dikirimkan sample": "Hallo kak untuk sampel kita bisa kirimkan gratis ya kak 😊"
244
+ }
245
 
246
+ # Simple keyword matching
247
+ user_lower = request.message.lower()
248
+ response = "Halo! Saya adalah asisten AI Textilindo. Bagaimana saya bisa membantu Anda hari ini? 😊"
249
+
250
+ for key, mock_response in mock_responses.items():
251
+ if any(word in user_lower for word in key.split()):
252
+ response = mock_response
253
+ break
254
+
255
+ return ChatResponse(
256
+ response=response,
257
+ conversation_id=request.conversation_id or "default",
258
+ status="success"
259
+ )
260
 
 
 
 
 
 
 
 
261
  except Exception as e:
262
+ logger.error(f"Chat error: {e}")
263
+ return ChatResponse(
264
+ response="Maaf, terjadi kesalahan. Silakan coba lagi.",
265
+ conversation_id=request.conversation_id or "default",
266
+ status="error"
267
+ )
268
 
269
+ # Training function
270
+ async def train_model_async(
271
+ model_name: str,
272
+ dataset_path: str,
273
+ config_path: str,
274
+ max_samples: int,
275
+ epochs: int,
276
+ batch_size: int,
277
+ learning_rate: float
278
+ ):
279
+ """Async training function"""
280
+ global training_status
281
+
282
  try:
283
+ training_status.update({
284
+ "is_training": True,
285
+ "status": "starting",
286
+ "progress": 0,
287
+ "start_time": datetime.now().isoformat(),
288
+ "error": None
289
+ })
290
+
291
+ logger.info("🚀 Starting training...")
292
+
293
+ # Import training libraries
294
+ from transformers import (
295
+ AutoTokenizer,
296
+ AutoModelForCausalLM,
297
+ TrainingArguments,
298
+ Trainer,
299
+ DataCollatorForLanguageModeling
300
+ )
301
+ from datasets import Dataset
302
+
303
+ # Check GPU
304
+ gpu_available = torch.cuda.is_available()
305
+ logger.info(f"GPU available: {gpu_available}")
306
+
307
+ # Load model and tokenizer
308
+ logger.info(f"📥 Loading model: {model_name}")
309
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
310
+ if tokenizer.pad_token is None:
311
+ tokenizer.pad_token = tokenizer.eos_token
312
+
313
+ # Load model
314
+ if gpu_available:
315
+ model = AutoModelForCausalLM.from_pretrained(
316
+ model_name,
317
+ torch_dtype=torch.float16,
318
+ device_map="auto"
319
+ )
320
+ else:
321
+ model = AutoModelForCausalLM.from_pretrained(model_name)
322
+
323
+ logger.info("✅ Model loaded successfully")
324
+
325
+ # Load training data
326
+ training_data = load_training_data(dataset_path, max_samples)
327
+ if not training_data:
328
+ raise Exception("No training data loaded")
329
+
330
+ # Convert to dataset
331
+ dataset = Dataset.from_list(training_data)
332
+
333
+ def tokenize_function(examples):
334
+ return tokenizer(
335
+ examples["text"],
336
+ truncation=True,
337
+ padding=True,
338
+ max_length=256,
339
+ return_tensors="pt"
340
+ )
341
+
342
+ tokenized_dataset = dataset.map(tokenize_function, batched=True)
343
+
344
+ # Training arguments
345
+ training_args = TrainingArguments(
346
+ output_dir="./models/textilindo-trained",
347
+ num_train_epochs=epochs,
348
+ per_device_train_batch_size=batch_size,
349
+ gradient_accumulation_steps=2,
350
+ learning_rate=learning_rate,
351
+ warmup_steps=5,
352
+ save_steps=10,
353
+ logging_steps=1,
354
+ save_total_limit=1,
355
+ prediction_loss_only=True,
356
+ remove_unused_columns=False,
357
+ fp16=gpu_available,
358
+ dataloader_pin_memory=gpu_available,
359
+ report_to=None,
360
+ )
361
+
362
+ # Data collator
363
+ data_collator = DataCollatorForLanguageModeling(
364
+ tokenizer=tokenizer,
365
+ mlm=False,
366
+ )
367
+
368
+ # Create trainer
369
+ trainer = Trainer(
370
+ model=model,
371
+ args=training_args,
372
+ train_dataset=tokenized_dataset,
373
+ data_collator=data_collator,
374
+ tokenizer=tokenizer,
375
+ )
376
+
377
+ # Start training
378
+ training_status["status"] = "training"
379
+ trainer.train()
380
+
381
+ # Save model
382
+ model.save_pretrained("./models/textilindo-trained")
383
+ tokenizer.save_pretrained("./models/textilindo-trained")
384
+
385
+ # Update status
386
+ training_status.update({
387
+ "is_training": False,
388
+ "status": "completed",
389
+ "progress": 100,
390
+ "end_time": datetime.now().isoformat()
391
+ })
392
+
393
+ logger.info("✅ Training completed successfully!")
394
 
 
 
 
 
 
 
 
395
  except Exception as e:
396
+ logger.error(f"Training failed: {e}")
397
+ training_status.update({
398
+ "is_training": False,
399
+ "status": "failed",
400
+ "error": str(e),
401
+ "end_time": datetime.now().isoformat()
402
+ })
403
 
404
+ def load_training_data(dataset_path: str, max_samples: int = 20) -> list:
405
+ """Load training data from JSONL file"""
406
+ data = []
407
  try:
408
+ with open(dataset_path, 'r', encoding='utf-8') as f:
409
+ for i, line in enumerate(f):
410
+ if i >= max_samples:
411
+ break
412
+ if line.strip():
413
+ item = json.loads(line)
414
+ # Create training text
415
+ instruction = item.get('instruction', '')
416
+ output = item.get('output', '')
417
+ text = f"Question: {instruction} Answer: {output}"
418
+ data.append({"text": text})
419
+ logger.info(f"Loaded {len(data)} training samples")
420
+ return data
421
  except Exception as e:
422
+ logger.error(f"Error loading data: {e}")
423
+ return []
 
 
 
424
 
425
  if __name__ == "__main__":
426
+ uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
 
 
 
 
 
 
 
app_api_only.py ADDED
@@ -0,0 +1,426 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Textilindo AI Training API - Pure API Version
4
+ No HTML interfaces, only API endpoints for training and chat
5
+ """
6
+
7
+ import os
8
+ import json
9
+ import logging
10
+ import torch
11
+ from pathlib import Path
12
+ from datetime import datetime
13
+ from typing import Dict, Any, Optional
14
+ from fastapi import FastAPI, HTTPException, BackgroundTasks
15
+ from pydantic import BaseModel
16
+ import uvicorn
17
+
18
+ # Setup logging
19
+ logging.basicConfig(level=logging.INFO)
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # Initialize FastAPI app
23
+ app = FastAPI(
24
+ title="Textilindo AI Training API",
25
+ description="Pure API-based training system for Textilindo AI Assistant",
26
+ version="1.0.0"
27
+ )
28
+
29
+ # Training status storage
30
+ training_status = {
31
+ "is_training": False,
32
+ "progress": 0,
33
+ "status": "idle",
34
+ "current_step": 0,
35
+ "total_steps": 0,
36
+ "loss": 0.0,
37
+ "start_time": None,
38
+ "end_time": None,
39
+ "error": None
40
+ }
41
+
42
+ # Request/Response models
43
+ class TrainingRequest(BaseModel):
44
+ model_name: str = "distilgpt2"
45
+ dataset_path: str = "data/lora_dataset_20250910_145055.jsonl"
46
+ config_path: str = "configs/training_config.yaml"
47
+ max_samples: int = 20
48
+ epochs: int = 1
49
+ batch_size: int = 1
50
+ learning_rate: float = 5e-5
51
+
52
+ class TrainingResponse(BaseModel):
53
+ success: bool
54
+ message: str
55
+ training_id: str
56
+ status: str
57
+
58
+ class ChatRequest(BaseModel):
59
+ message: str
60
+ conversation_id: Optional[str] = None
61
+
62
+ class ChatResponse(BaseModel):
63
+ response: str
64
+ conversation_id: str
65
+ status: str = "success"
66
+
67
+ # API Information
68
+ @app.get("/")
69
+ async def api_info():
70
+ """API information endpoint"""
71
+ return {
72
+ "name": "Textilindo AI Training API",
73
+ "version": "1.0.0",
74
+ "description": "Pure API-based training system for Textilindo AI Assistant",
75
+ "hardware": "2 vCPU, 16 GB RAM (CPU basic)",
76
+ "status": "ready",
77
+ "endpoints": {
78
+ "training": {
79
+ "start": "POST /api/train/start",
80
+ "status": "GET /api/train/status",
81
+ "data": "GET /api/train/data",
82
+ "gpu": "GET /api/train/gpu",
83
+ "test": "POST /api/train/test"
84
+ },
85
+ "chat": {
86
+ "chat": "POST /chat",
87
+ "health": "GET /health"
88
+ }
89
+ }
90
+ }
91
+
92
+ # Health check
93
+ @app.get("/health")
94
+ async def health_check():
95
+ """Health check endpoint"""
96
+ return {
97
+ "status": "healthy",
98
+ "timestamp": datetime.now().isoformat(),
99
+ "hardware": "2 vCPU, 16 GB RAM"
100
+ }
101
+
102
+ # Training API endpoints
103
+ @app.post("/api/train/start", response_model=TrainingResponse)
104
+ async def start_training(request: TrainingRequest, background_tasks: BackgroundTasks):
105
+ """Start training process"""
106
+ global training_status
107
+
108
+ if training_status["is_training"]:
109
+ raise HTTPException(status_code=400, detail="Training already in progress")
110
+
111
+ # Validate inputs
112
+ if not Path(request.dataset_path).exists():
113
+ raise HTTPException(status_code=404, detail=f"Dataset not found: {request.dataset_path}")
114
+
115
+ if not Path(request.config_path).exists():
116
+ raise HTTPException(status_code=404, detail=f"Config not found: {request.config_path}")
117
+
118
+ # Start training in background
119
+ training_id = f"train_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
120
+
121
+ background_tasks.add_task(
122
+ train_model_async,
123
+ request.model_name,
124
+ request.dataset_path,
125
+ request.config_path,
126
+ request.max_samples,
127
+ request.epochs,
128
+ request.batch_size,
129
+ request.learning_rate
130
+ )
131
+
132
+ return TrainingResponse(
133
+ success=True,
134
+ message="Training started successfully",
135
+ training_id=training_id,
136
+ status="started"
137
+ )
138
+
139
+ @app.get("/api/train/status")
140
+ async def get_training_status():
141
+ """Get current training status"""
142
+ return training_status
143
+
144
+ @app.get("/api/train/data")
145
+ async def get_training_data_info():
146
+ """Get information about available training data"""
147
+ data_dir = Path("data")
148
+ if not data_dir.exists():
149
+ return {"files": [], "count": 0}
150
+
151
+ jsonl_files = list(data_dir.glob("*.jsonl"))
152
+ files_info = []
153
+
154
+ for file in jsonl_files:
155
+ try:
156
+ with open(file, 'r', encoding='utf-8') as f:
157
+ lines = f.readlines()
158
+ files_info.append({
159
+ "name": file.name,
160
+ "size": file.stat().st_size,
161
+ "lines": len(lines)
162
+ })
163
+ except Exception as e:
164
+ files_info.append({
165
+ "name": file.name,
166
+ "error": str(e)
167
+ })
168
+
169
+ return {
170
+ "files": files_info,
171
+ "count": len(jsonl_files)
172
+ }
173
+
174
+ @app.get("/api/train/gpu")
175
+ async def get_gpu_info():
176
+ """Get GPU information"""
177
+ try:
178
+ gpu_available = torch.cuda.is_available()
179
+ if gpu_available:
180
+ gpu_count = torch.cuda.device_count()
181
+ gpu_name = torch.cuda.get_device_name(0)
182
+ gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3)
183
+ return {
184
+ "available": True,
185
+ "count": gpu_count,
186
+ "name": gpu_name,
187
+ "memory_gb": round(gpu_memory, 2)
188
+ }
189
+ else:
190
+ return {"available": False}
191
+ except Exception as e:
192
+ return {"error": str(e)}
193
+
194
+ @app.post("/api/train/test")
195
+ async def test_trained_model():
196
+ """Test the trained model"""
197
+ model_path = "./models/textilindo-trained"
198
+ if not Path(model_path).exists():
199
+ return {"error": "No trained model found"}
200
+
201
+ try:
202
+ from transformers import AutoTokenizer, AutoModelForCausalLM
203
+
204
+ tokenizer = AutoTokenizer.from_pretrained(model_path)
205
+ model = AutoModelForCausalLM.from_pretrained(model_path)
206
+
207
+ # Test prompt
208
+ test_prompt = "Question: dimana lokasi textilindo? Answer:"
209
+ inputs = tokenizer(test_prompt, return_tensors="pt")
210
+
211
+ with torch.no_grad():
212
+ outputs = model.generate(
213
+ **inputs,
214
+ max_length=inputs.input_ids.shape[1] + 30,
215
+ temperature=0.7,
216
+ do_sample=True,
217
+ pad_token_id=tokenizer.eos_token_id
218
+ )
219
+
220
+ response = tokenizer.decode(outputs[0], skip_special_tokens=True)
221
+
222
+ return {
223
+ "success": True,
224
+ "test_prompt": test_prompt,
225
+ "response": response,
226
+ "model_path": model_path
227
+ }
228
+
229
+ except Exception as e:
230
+ return {"error": str(e)}
231
+
232
+ # Chat API endpoint
233
+ @app.post("/chat", response_model=ChatResponse)
234
+ async def chat(request: ChatRequest):
235
+ """Chat with the AI assistant"""
236
+ try:
237
+ # Simple mock response for now
238
+ mock_responses = {
239
+ "dimana lokasi textilindo": "Textilindo berkantor pusat di Jl. Raya Prancis No.39, Kosambi Tim., Kec. Kosambi, Kabupaten Tangerang, Banten 15213",
240
+ "jam berapa textilindo beroperasional": "Jam operasional Senin-Jumat 08:00-17:00, Sabtu 08:00-12:00.",
241
+ "berapa ketentuan pembelian": "Minimal order 1 roll per jenis kain",
242
+ "apa ada gratis ongkir": "Gratis ongkir untuk order minimal 5 roll.",
243
+ "apa bisa dikirimkan sample": "Hallo kak untuk sampel kita bisa kirimkan gratis ya kak 😊"
244
+ }
245
+
246
+ # Simple keyword matching
247
+ user_lower = request.message.lower()
248
+ response = "Halo! Saya adalah asisten AI Textilindo. Bagaimana saya bisa membantu Anda hari ini? 😊"
249
+
250
+ for key, mock_response in mock_responses.items():
251
+ if any(word in user_lower for word in key.split()):
252
+ response = mock_response
253
+ break
254
+
255
+ return ChatResponse(
256
+ response=response,
257
+ conversation_id=request.conversation_id or "default",
258
+ status="success"
259
+ )
260
+
261
+ except Exception as e:
262
+ logger.error(f"Chat error: {e}")
263
+ return ChatResponse(
264
+ response="Maaf, terjadi kesalahan. Silakan coba lagi.",
265
+ conversation_id=request.conversation_id or "default",
266
+ status="error"
267
+ )
268
+
269
+ # Training function
270
+ async def train_model_async(
271
+ model_name: str,
272
+ dataset_path: str,
273
+ config_path: str,
274
+ max_samples: int,
275
+ epochs: int,
276
+ batch_size: int,
277
+ learning_rate: float
278
+ ):
279
+ """Async training function"""
280
+ global training_status
281
+
282
+ try:
283
+ training_status.update({
284
+ "is_training": True,
285
+ "status": "starting",
286
+ "progress": 0,
287
+ "start_time": datetime.now().isoformat(),
288
+ "error": None
289
+ })
290
+
291
+ logger.info("🚀 Starting training...")
292
+
293
+ # Import training libraries
294
+ from transformers import (
295
+ AutoTokenizer,
296
+ AutoModelForCausalLM,
297
+ TrainingArguments,
298
+ Trainer,
299
+ DataCollatorForLanguageModeling
300
+ )
301
+ from datasets import Dataset
302
+
303
+ # Check GPU
304
+ gpu_available = torch.cuda.is_available()
305
+ logger.info(f"GPU available: {gpu_available}")
306
+
307
+ # Load model and tokenizer
308
+ logger.info(f"📥 Loading model: {model_name}")
309
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
310
+ if tokenizer.pad_token is None:
311
+ tokenizer.pad_token = tokenizer.eos_token
312
+
313
+ # Load model
314
+ if gpu_available:
315
+ model = AutoModelForCausalLM.from_pretrained(
316
+ model_name,
317
+ torch_dtype=torch.float16,
318
+ device_map="auto"
319
+ )
320
+ else:
321
+ model = AutoModelForCausalLM.from_pretrained(model_name)
322
+
323
+ logger.info("✅ Model loaded successfully")
324
+
325
+ # Load training data
326
+ training_data = load_training_data(dataset_path, max_samples)
327
+ if not training_data:
328
+ raise Exception("No training data loaded")
329
+
330
+ # Convert to dataset
331
+ dataset = Dataset.from_list(training_data)
332
+
333
+ def tokenize_function(examples):
334
+ return tokenizer(
335
+ examples["text"],
336
+ truncation=True,
337
+ padding=True,
338
+ max_length=256,
339
+ return_tensors="pt"
340
+ )
341
+
342
+ tokenized_dataset = dataset.map(tokenize_function, batched=True)
343
+
344
+ # Training arguments
345
+ training_args = TrainingArguments(
346
+ output_dir="./models/textilindo-trained",
347
+ num_train_epochs=epochs,
348
+ per_device_train_batch_size=batch_size,
349
+ gradient_accumulation_steps=2,
350
+ learning_rate=learning_rate,
351
+ warmup_steps=5,
352
+ save_steps=10,
353
+ logging_steps=1,
354
+ save_total_limit=1,
355
+ prediction_loss_only=True,
356
+ remove_unused_columns=False,
357
+ fp16=gpu_available,
358
+ dataloader_pin_memory=gpu_available,
359
+ report_to=None,
360
+ )
361
+
362
+ # Data collator
363
+ data_collator = DataCollatorForLanguageModeling(
364
+ tokenizer=tokenizer,
365
+ mlm=False,
366
+ )
367
+
368
+ # Create trainer
369
+ trainer = Trainer(
370
+ model=model,
371
+ args=training_args,
372
+ train_dataset=tokenized_dataset,
373
+ data_collator=data_collator,
374
+ tokenizer=tokenizer,
375
+ )
376
+
377
+ # Start training
378
+ training_status["status"] = "training"
379
+ trainer.train()
380
+
381
+ # Save model
382
+ model.save_pretrained("./models/textilindo-trained")
383
+ tokenizer.save_pretrained("./models/textilindo-trained")
384
+
385
+ # Update status
386
+ training_status.update({
387
+ "is_training": False,
388
+ "status": "completed",
389
+ "progress": 100,
390
+ "end_time": datetime.now().isoformat()
391
+ })
392
+
393
+ logger.info("✅ Training completed successfully!")
394
+
395
+ except Exception as e:
396
+ logger.error(f"Training failed: {e}")
397
+ training_status.update({
398
+ "is_training": False,
399
+ "status": "failed",
400
+ "error": str(e),
401
+ "end_time": datetime.now().isoformat()
402
+ })
403
+
404
+ def load_training_data(dataset_path: str, max_samples: int = 20) -> list:
405
+ """Load training data from JSONL file"""
406
+ data = []
407
+ try:
408
+ with open(dataset_path, 'r', encoding='utf-8') as f:
409
+ for i, line in enumerate(f):
410
+ if i >= max_samples:
411
+ break
412
+ if line.strip():
413
+ item = json.loads(line)
414
+ # Create training text
415
+ instruction = item.get('instruction', '')
416
+ output = item.get('output', '')
417
+ text = f"Question: {instruction} Answer: {output}"
418
+ data.append({"text": text})
419
+ logger.info(f"Loaded {len(data)} training samples")
420
+ return data
421
+ except Exception as e:
422
+ logger.error(f"Error loading data: {e}")
423
+ return []
424
+
425
+ if __name__ == "__main__":
426
+ uvicorn.run(app, host="0.0.0.0", port=7860)
app_backup.py ADDED
@@ -0,0 +1,568 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Textilindo AI Assistant - Hugging Face Spaces FastAPI Application
4
+ Main application file for deployment on Hugging Face Spaces
5
+ """
6
+
7
+ import os
8
+ import json
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Optional, Dict, Any
12
+ from fastapi import FastAPI, HTTPException, Request, BackgroundTasks
13
+ from fastapi.responses import HTMLResponse, JSONResponse
14
+ from fastapi.staticfiles import StaticFiles
15
+ from fastapi.middleware.cors import CORSMiddleware
16
+ from pydantic import BaseModel
17
+ import uvicorn
18
+ from huggingface_hub import InferenceClient
19
+ import requests
20
+ from datetime import datetime
21
+
22
+ # Setup logging
23
+ logging.basicConfig(level=logging.INFO)
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Initialize FastAPI app
27
+ app = FastAPI(
28
+ title="Textilindo AI Assistant",
29
+ description="AI Assistant for Textilindo textile company",
30
+ version="1.0.0"
31
+ )
32
+
33
+ # Add CORS middleware
34
+ app.add_middleware(
35
+ CORSMiddleware,
36
+ allow_origins=["*"],
37
+ allow_credentials=True,
38
+ allow_methods=["*"],
39
+ allow_headers=["*"],
40
+ )
41
+
42
+ # Request/Response models
43
+ class ChatRequest(BaseModel):
44
+ message: str
45
+ conversation_id: Optional[str] = None
46
+
47
+ class ChatResponse(BaseModel):
48
+ response: str
49
+ conversation_id: str
50
+ status: str = "success"
51
+
52
+ class HealthResponse(BaseModel):
53
+ status: str
54
+ message: str
55
+ version: str = "1.0.0"
56
+
57
+ class TextilindoAI:
58
+ """Textilindo AI Assistant using HuggingFace Inference API"""
59
+
60
+ def __init__(self):
61
+ self.api_key = os.getenv('HUGGINGFACE_API_KEY')
62
+ self.model = os.getenv('DEFAULT_MODEL', 'meta-llama/Llama-3.1-8B-Instruct')
63
+ self.system_prompt = self.load_system_prompt()
64
+
65
+ if not self.api_key:
66
+ logger.warning("HUGGINGFACE_API_KEY not found. Using mock responses.")
67
+ self.client = None
68
+ else:
69
+ try:
70
+ self.client = InferenceClient(
71
+ token=self.api_key,
72
+ model=self.model
73
+ )
74
+ logger.info(f"Initialized with model: {self.model}")
75
+ except Exception as e:
76
+ logger.error(f"Failed to initialize InferenceClient: {e}")
77
+ self.client = None
78
+
79
+ def load_system_prompt(self) -> str:
80
+ """Load system prompt from config file"""
81
+ try:
82
+ prompt_path = Path("configs/system_prompt.md")
83
+ if prompt_path.exists():
84
+ with open(prompt_path, 'r', encoding='utf-8') as f:
85
+ content = f.read()
86
+
87
+ # Extract system prompt from markdown
88
+ if 'SYSTEM_PROMPT = """' in content:
89
+ start = content.find('SYSTEM_PROMPT = """') + len('SYSTEM_PROMPT = """')
90
+ end = content.find('"""', start)
91
+ return content[start:end].strip()
92
+ else:
93
+ # Fallback: use entire content
94
+ return content.strip()
95
+ else:
96
+ return self.get_default_system_prompt()
97
+ except Exception as e:
98
+ logger.error(f"Error loading system prompt: {e}")
99
+ return self.get_default_system_prompt()
100
+
101
+ def get_default_system_prompt(self) -> str:
102
+ """Default system prompt if file not found"""
103
+ return """You are a friendly and helpful AI assistant for Textilindo, a textile company.
104
+
105
+ Always respond in Indonesian (Bahasa Indonesia).
106
+ Keep responses short and direct.
107
+ Be friendly and helpful.
108
+ Use exact information from the knowledge base.
109
+ The company uses yards for sales.
110
+ Minimum purchase is 1 roll (67-70 yards)."""
111
+
112
+ def generate_response(self, user_message: str) -> str:
113
+ """Generate response using HuggingFace Inference API"""
114
+ if not self.client:
115
+ return self.get_mock_response(user_message)
116
+
117
+ try:
118
+ # Create full prompt with system prompt
119
+ full_prompt = f"<|system|>\n{self.system_prompt}\n<|user|>\n{user_message}\n<|assistant|>\n"
120
+
121
+ # Generate response
122
+ response = self.client.text_generation(
123
+ full_prompt,
124
+ max_new_tokens=512,
125
+ temperature=0.7,
126
+ top_p=0.9,
127
+ top_k=40,
128
+ repetition_penalty=1.1,
129
+ stop_sequences=["<|end|>", "<|user|>"]
130
+ )
131
+
132
+ # Extract only the assistant's response
133
+ if "<|assistant|>" in response:
134
+ assistant_response = response.split("<|assistant|>")[-1].strip()
135
+ assistant_response = assistant_response.replace("<|end|>", "").strip()
136
+ return assistant_response
137
+ else:
138
+ return response
139
+
140
+ except Exception as e:
141
+ logger.error(f"Error generating response: {e}")
142
+ return self.get_mock_response(user_message)
143
+
144
+ def get_mock_response(self, user_message: str) -> str:
145
+ """Mock responses for testing without API key"""
146
+ mock_responses = {
147
+ "dimana lokasi textilindo": "Textilindo berkantor pusat di Jl. Raya Prancis No.39, Kosambi Tim., Kec. Kosambi, Kabupaten Tangerang, Banten 15213",
148
+ "jam berapa textilindo beroperasional": "Jam operasional Senin-Jumat 08:00-17:00, Sabtu 08:00-12:00.",
149
+ "berapa ketentuan pembelian": "Minimal order 1 roll per jenis kain",
150
+ "bagaimana dengan pembayarannya": "Pembayaran dapat dilakukan via transfer bank atau cash on delivery",
151
+ "apa ada gratis ongkir": "Gratis ongkir untuk order minimal 5 roll.",
152
+ "apa bisa dikirimkan sample": "hallo kak untuk sampel kita bisa kirimkan gratis ya kak 😊"
153
+ }
154
+
155
+ # Simple keyword matching
156
+ user_lower = user_message.lower()
157
+ for key, response in mock_responses.items():
158
+ if any(word in user_lower for word in key.split()):
159
+ return response
160
+
161
+ return "Halo! Saya adalah asisten AI Textilindo. Bagaimana saya bisa membantu Anda hari ini? 😊"
162
+
163
+ # Initialize AI assistant
164
+ ai_assistant = TextilindoAI()
165
+
166
+ # Routes
167
+ @app.get("/", response_class=HTMLResponse)
168
+ async def root():
169
+ """Serve the main chat interface"""
170
+ try:
171
+ with open("templates/chat.html", "r", encoding="utf-8") as f:
172
+ return HTMLResponse(content=f.read())
173
+ except FileNotFoundError:
174
+ return HTMLResponse(content="""
175
+ <!DOCTYPE html>
176
+ <html>
177
+ <head>
178
+ <title>Textilindo AI Assistant</title>
179
+ <meta charset="utf-8">
180
+ <style>
181
+ body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
182
+ .chat-container { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin: 20px 0; }
183
+ .message { margin: 10px 0; padding: 10px; border-radius: 5px; }
184
+ .user { background-color: #e3f2fd; text-align: right; }
185
+ .assistant { background-color: #f5f5f5; }
186
+ input[type="text"] { width: 70%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
187
+ button { padding: 10px 20px; background-color: #2196f3; color: white; border: none; border-radius: 5px; cursor: pointer; }
188
+ </style>
189
+ </head>
190
+ <body>
191
+ <h1>🤖 Textilindo AI Assistant</h1>
192
+ <div class="chat-container">
193
+ <div id="chat-messages"></div>
194
+ <div style="margin-top: 20px;">
195
+ <input type="text" id="message-input" placeholder="Tulis pesan Anda..." onkeypress="handleKeyPress(event)">
196
+ <button onclick="sendMessage()">Kirim</button>
197
+ </div>
198
+ </div>
199
+ <script>
200
+ async function sendMessage() {
201
+ const input = document.getElementById('message-input');
202
+ const message = input.value.trim();
203
+ if (!message) return;
204
+
205
+ // Add user message
206
+ addMessage(message, 'user');
207
+ input.value = '';
208
+
209
+ // Get AI response
210
+ try {
211
+ const response = await fetch('/chat', {
212
+ method: 'POST',
213
+ headers: { 'Content-Type': 'application/json' },
214
+ body: JSON.stringify({ message: message })
215
+ });
216
+ const data = await response.json();
217
+ addMessage(data.response, 'assistant');
218
+ } catch (error) {
219
+ addMessage('Maaf, terjadi kesalahan. Silakan coba lagi.', 'assistant');
220
+ }
221
+ }
222
+
223
+ function addMessage(text, sender) {
224
+ const messages = document.getElementById('chat-messages');
225
+ const div = document.createElement('div');
226
+ div.className = `message ${sender}`;
227
+ div.textContent = text;
228
+ messages.appendChild(div);
229
+ messages.scrollTop = messages.scrollHeight;
230
+ }
231
+
232
+ function handleKeyPress(event) {
233
+ if (event.key === 'Enter') {
234
+ sendMessage();
235
+ }
236
+ }
237
+ </script>
238
+ </body>
239
+ </html>
240
+ """)
241
+
242
+ @app.post("/chat", response_model=ChatResponse)
243
+ async def chat(request: ChatRequest):
244
+ """Chat endpoint"""
245
+ try:
246
+ response = ai_assistant.generate_response(request.message)
247
+ return ChatResponse(
248
+ response=response,
249
+ conversation_id=request.conversation_id or "default",
250
+ status="success"
251
+ )
252
+ except Exception as e:
253
+ logger.error(f"Error in chat endpoint: {e}")
254
+ raise HTTPException(status_code=500, detail="Internal server error")
255
+
256
+ @app.get("/health", response_model=HealthResponse)
257
+ async def health_check():
258
+ """Health check endpoint"""
259
+ return HealthResponse(
260
+ status="healthy",
261
+ message="Textilindo AI Assistant is running",
262
+ version="1.0.0"
263
+ )
264
+
265
+ @app.get("/info")
266
+ async def get_info():
267
+ """Get application information"""
268
+ return {
269
+ "name": "Textilindo AI Assistant",
270
+ "version": "1.0.0",
271
+ "model": ai_assistant.model,
272
+ "has_api_key": bool(ai_assistant.api_key),
273
+ "client_initialized": bool(ai_assistant.client)
274
+ }
275
+
276
+ # Import training API
277
+ from training_api import (
278
+ TrainingRequest, TrainingResponse, training_status,
279
+ train_model_async, load_training_config, load_training_data, check_gpu_availability
280
+ )
281
+
282
+ # Training API endpoints
283
+ @app.post("/api/train/start", response_model=TrainingResponse)
284
+ async def start_training_api(request: TrainingRequest, background_tasks: BackgroundTasks):
285
+ """Start training process via API"""
286
+ if training_status["is_training"]:
287
+ raise HTTPException(status_code=400, detail="Training already in progress")
288
+
289
+ # Validate inputs
290
+ if not Path(request.dataset_path).exists():
291
+ raise HTTPException(status_code=404, detail=f"Dataset not found: {request.dataset_path}")
292
+
293
+ if not Path(request.config_path).exists():
294
+ raise HTTPException(status_code=404, detail=f"Config not found: {request.config_path}")
295
+
296
+ # Start training in background
297
+ training_id = f"train_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
298
+
299
+ background_tasks.add_task(
300
+ train_model_async,
301
+ request.model_name,
302
+ request.dataset_path,
303
+ request.config_path,
304
+ request.max_samples,
305
+ request.epochs,
306
+ request.batch_size,
307
+ request.learning_rate
308
+ )
309
+
310
+ return TrainingResponse(
311
+ success=True,
312
+ message="Training started successfully",
313
+ training_id=training_id,
314
+ status="started"
315
+ )
316
+
317
+ @app.get("/api/train/status")
318
+ async def get_training_status_api():
319
+ """Get current training status"""
320
+ return training_status
321
+
322
+ @app.get("/api/train/data")
323
+ async def get_training_data_info_api():
324
+ """Get information about available training data"""
325
+ data_dir = Path("data")
326
+ if not data_dir.exists():
327
+ return {"files": [], "count": 0}
328
+
329
+ jsonl_files = list(data_dir.glob("*.jsonl"))
330
+ files_info = []
331
+
332
+ for file in jsonl_files:
333
+ try:
334
+ with open(file, 'r', encoding='utf-8') as f:
335
+ lines = f.readlines()
336
+ files_info.append({
337
+ "name": file.name,
338
+ "size": file.stat().st_size,
339
+ "lines": len(lines)
340
+ })
341
+ except Exception as e:
342
+ files_info.append({
343
+ "name": file.name,
344
+ "error": str(e)
345
+ })
346
+
347
+ return {
348
+ "files": files_info,
349
+ "count": len(jsonl_files)
350
+ }
351
+
352
+ @app.get("/api/train/gpu")
353
+ async def get_gpu_info_api():
354
+ """Get GPU information"""
355
+ try:
356
+ import torch
357
+ gpu_available = torch.cuda.is_available()
358
+ if gpu_available:
359
+ gpu_count = torch.cuda.device_count()
360
+ gpu_name = torch.cuda.get_device_name(0)
361
+ gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3)
362
+ return {
363
+ "available": True,
364
+ "count": gpu_count,
365
+ "name": gpu_name,
366
+ "memory_gb": round(gpu_memory, 2)
367
+ }
368
+ else:
369
+ return {"available": False}
370
+ except Exception as e:
371
+ return {"error": str(e)}
372
+
373
+ @app.post("/api/train/test")
374
+ async def test_trained_model_api():
375
+ """Test the trained model"""
376
+ model_path = "./models/textilindo-trained"
377
+ if not Path(model_path).exists():
378
+ return {"error": "No trained model found"}
379
+
380
+ try:
381
+ from transformers import AutoTokenizer, AutoModelForCausalLM
382
+ import torch
383
+
384
+ tokenizer = AutoTokenizer.from_pretrained(model_path)
385
+ model = AutoModelForCausalLM.from_pretrained(model_path)
386
+
387
+ # Test prompt
388
+ test_prompt = "Question: dimana lokasi textilindo? Answer:"
389
+ inputs = tokenizer(test_prompt, return_tensors="pt")
390
+
391
+ with torch.no_grad():
392
+ outputs = model.generate(
393
+ **inputs,
394
+ max_length=inputs.input_ids.shape[1] + 30,
395
+ temperature=0.7,
396
+ do_sample=True,
397
+ pad_token_id=tokenizer.eos_token_id
398
+ )
399
+
400
+ response = tokenizer.decode(outputs[0], skip_special_tokens=True)
401
+
402
+ return {
403
+ "success": True,
404
+ "test_prompt": test_prompt,
405
+ "response": response,
406
+ "model_path": model_path
407
+ }
408
+
409
+ except Exception as e:
410
+ return {"error": str(e)}
411
+
412
+ # Legacy training endpoints (for backward compatibility)
413
+ @app.get("/train")
414
+ async def training_interface():
415
+ """Training interface"""
416
+ try:
417
+ with open("templates/training.html", "r", encoding="utf-8") as f:
418
+ return HTMLResponse(content=f.read())
419
+ except FileNotFoundError:
420
+ return HTMLResponse(content="""
421
+ <!DOCTYPE html>
422
+ <html>
423
+ <head>
424
+ <title>Textilindo AI Training</title>
425
+ <meta charset="utf-8">
426
+ <style>
427
+ body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
428
+ .container { background: #f5f5f5; padding: 20px; border-radius: 10px; margin: 20px 0; }
429
+ button { background: #2196f3; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; }
430
+ button:hover { background: #1976d2; }
431
+ .log { background: #000; color: #0f0; padding: 10px; border-radius: 5px; font-family: monospace; height: 300px; overflow-y: auto; }
432
+ </style>
433
+ </head>
434
+ <body>
435
+ <h1>🤖 Textilindo AI Training Interface</h1>
436
+
437
+ <div class="container">
438
+ <h2>Training Options</h2>
439
+ <p>Choose your training method:</p>
440
+
441
+ <button onclick="startLightweightTraining()">Start Lightweight Training</button>
442
+ <button onclick="checkResources()">Check Resources</button>
443
+ <button onclick="viewData()">View Training Data</button>
444
+ </div>
445
+
446
+ <div class="container">
447
+ <h2>Training Log</h2>
448
+ <div id="log" class="log">Ready to start training...</div>
449
+ </div>
450
+
451
+ <script>
452
+ function addLog(message) {
453
+ const log = document.getElementById('log');
454
+ const timestamp = new Date().toLocaleTimeString();
455
+ log.innerHTML += `[${timestamp}] ${message}\\n`;
456
+ log.scrollTop = log.scrollHeight;
457
+ }
458
+
459
+ async function startLightweightTraining() {
460
+ addLog('Starting lightweight training...');
461
+ try {
462
+ const response = await fetch('/train/start', {
463
+ method: 'POST',
464
+ headers: { 'Content-Type': 'application/json' }
465
+ });
466
+ const result = await response.json();
467
+ addLog(`Training result: ${result.message}`);
468
+ } catch (error) {
469
+ addLog(`Error: ${error.message}`);
470
+ }
471
+ }
472
+
473
+ async function checkResources() {
474
+ addLog('Checking resources...');
475
+ try {
476
+ const response = await fetch('/train/status');
477
+ const result = await response.json();
478
+ addLog(`Resources: ${JSON.stringify(result, null, 2)}`);
479
+ } catch (error) {
480
+ addLog(`Error: ${error.message}`);
481
+ }
482
+ }
483
+
484
+ async function viewData() {
485
+ addLog('Loading training data...');
486
+ try {
487
+ const response = await fetch('/train/data');
488
+ const result = await response.json();
489
+ addLog(`Data files: ${result.files.join(', ')}`);
490
+ } catch (error) {
491
+ addLog(`Error: ${error.message}`);
492
+ }
493
+ }
494
+ </script>
495
+ </body>
496
+ </html>
497
+ """)
498
+
499
+ @app.post("/train/start")
500
+ async def start_training():
501
+ """Start lightweight training"""
502
+ try:
503
+ # Import training script
504
+ import subprocess
505
+ import sys
506
+
507
+ # Run the training script
508
+ result = subprocess.run([
509
+ sys.executable, "train_on_space.py"
510
+ ], capture_output=True, text=True, timeout=300) # 5 minute timeout
511
+
512
+ if result.returncode == 0:
513
+ return {"message": "Training completed successfully!", "output": result.stdout}
514
+ else:
515
+ return {"message": "Training failed", "error": result.stderr}
516
+
517
+ except subprocess.TimeoutExpired:
518
+ return {"message": "Training timed out (5 minutes limit)"}
519
+ except Exception as e:
520
+ return {"message": f"Training error: {str(e)}"}
521
+
522
+ @app.get("/train/status")
523
+ async def training_status():
524
+ """Get training status and resources"""
525
+ try:
526
+ import psutil
527
+
528
+ return {
529
+ "status": "ready",
530
+ "cpu_count": psutil.cpu_count(),
531
+ "memory_total_gb": round(psutil.virtual_memory().total / (1024**3), 2),
532
+ "memory_available_gb": round(psutil.virtual_memory().available / (1024**3), 2),
533
+ "disk_free_gb": round(psutil.disk_usage('.').free / (1024**3), 2)
534
+ }
535
+ except Exception as e:
536
+ return {"status": "error", "message": str(e)}
537
+
538
+ @app.get("/train/data")
539
+ async def training_data():
540
+ """Get training data information"""
541
+ try:
542
+ data_dir = Path("data")
543
+ if data_dir.exists():
544
+ jsonl_files = list(data_dir.glob("*.jsonl"))
545
+ return {
546
+ "files": [f.name for f in jsonl_files],
547
+ "count": len(jsonl_files)
548
+ }
549
+ else:
550
+ return {"files": [], "count": 0}
551
+ except Exception as e:
552
+ return {"error": str(e)}
553
+
554
+ # Mount static files if they exist
555
+ if Path("static").exists():
556
+ app.mount("/static", StaticFiles(directory="static"), name="static")
557
+
558
+ if __name__ == "__main__":
559
+ # Get port from environment variable (Hugging Face Spaces uses 7860)
560
+ port = int(os.getenv("PORT", 7860))
561
+
562
+ # Run the application
563
+ uvicorn.run(
564
+ "app:app",
565
+ host="0.0.0.0",
566
+ port=port,
567
+ log_level="info"
568
+ )