Download ui.py from FannyFa/FannyFa-Model-V1: direct link, hf CLI and curl.
- Browser
- Download file 35.2 kB
-
https://huggingface.co/FannyFa/FannyFa-Model-V1/resolve/main/ui.py
- Command line
-
hf download hf://FannyFa/FannyFa-Model-V1/ui.py
-
curl -L -o ui.py https://huggingface.co/FannyFa/FannyFa-Model-V1/resolve/main/ui.py
35.2 kB
| import os | |
| import sys | |
| import platform | |
| import json | |
| import shutil | |
| from pathlib import Path | |
| from typing import Optional | |
| import torch | |
| from rich.panel import Panel | |
| from rich.prompt import Prompt, Confirm, FloatPrompt | |
| from rich.table import Table | |
| from rich import box | |
| from rich.markup import escape as rich_escape | |
| from config import ( | |
| MODEL_DIR, PLOT_DIR, EXPORT_DIR, DATA_DIR, | |
| DEFAULT_MEMORY_LIMIT_GB, DEFAULT_MODEL | |
| ) | |
| from utils import console, Theme, set_memory_hard_limit | |
| from models import ModelManager | |
| from history import TrainingHistory | |
| from data_loader import EnhancedDatasetLoader, DatasetStats | |
| from trainer import EnhancedTrainingModule, get_gpu_info, get_device | |
| from chat import EnhancedChatModule | |
| from backup_module import backup_menu | |
| try: | |
| HAS_PSUTIL = True | |
| import psutil | |
| except ImportError: | |
| HAS_PSUTIL = False | |
| try: | |
| HAS_MATPLOTLIB = True | |
| import matplotlib | |
| matplotlib.use('Agg') | |
| import matplotlib.pyplot as plt | |
| except ImportError: | |
| HAS_MATPLOTLIB = False | |
| try: | |
| HAS_PANDAS = True | |
| import pandas as pd | |
| except ImportError: | |
| HAS_PANDAS = False | |
| try: | |
| HAS_EVALUATE = True | |
| import evaluate | |
| except ImportError: | |
| HAS_EVALUATE = False | |
| try: | |
| HAS_PEFT = True | |
| from peft import PeftModel | |
| except ImportError: | |
| HAS_PEFT = False | |
| try: | |
| HAS_BNB = True | |
| import bitsandbytes as bnb | |
| except ImportError: | |
| HAS_BNB = False | |
| try: | |
| HAS_SACREBLEU = True | |
| import sacrebleu | |
| except ImportError: | |
| HAS_SACREBLEU = False | |
| def logo() -> None: | |
| console.print(Panel( | |
| """[bold red] | |
| ████████ ███████ █████ ██ █████ ██ ██ ██ | |
| ██░░░░░░ ██░░░░██ ██░░░██ ██ ██░░░██ ██ ░██ ░██ | |
| ██ ██ ░██░██ ░░ ██ ██ ░██ ██ ░████ | |
| ███████ ███████ ░██ ████ ███████ ██ ░██░ | |
| ██░░░░ ██░░░░ ░░██ ████ ██░░░░ ██ ██░ | |
| ██ ██ ░░████ ██ ██ ██░ | |
| ████████ ██ ░░██ ██ ██ ██░ | |
| ░░░░░░░░ ░░ ░░ ░░ ░░ ░░ | |
| """, | |
| width=55, title="[bold yellow]ULTIMATE AI CHATBOT v2.5 FIXED", style="bold yellow" | |
| )) | |
| def manage_models() -> None: | |
| model_manager = ModelManager() | |
| console.print(Panel(Theme.header(" MANAJEMEN MODEL"), title="MODEL", style="yellow")) | |
| models = model_manager.list_models() | |
| if not models: | |
| console.print(Theme.warning("Belum ada model")) | |
| return | |
| table = Table(title="Daftar Model", box=box.ROUNDED) | |
| table.add_column("No", style="cyan") | |
| table.add_column("ID", style="green") | |
| table.add_column("Base Model", style="white") | |
| table.add_column("Dataset", style="yellow") | |
| table.add_column("Samples", style="blue") | |
| table.add_column("Status", style="magenta") | |
| table.add_column("Created", style="dim") | |
| table.add_column("Metrics", style="cyan") | |
| current = model_manager.get_current_model() | |
| for idx, (key, val) in enumerate(models.items(), 1): | |
| status = " Active" if key == current else "Available" | |
| metrics = val.get("metrics", {}) | |
| if metrics.get('bleu'): | |
| metric_str = f"BLEU: {metrics['bleu']:.3f}" | |
| elif metrics.get('perplexity'): | |
| metric_str = f"PPL: {metrics['perplexity']:.2f}" | |
| elif metrics.get('bleu_sacrebleu'): | |
| metric_str = f"BLEU: {metrics['bleu_sacrebleu']:.3f}" | |
| else: | |
| metric_str = "-" | |
| samples = val.get('training_samples', '?') | |
| if isinstance(samples, int): | |
| samples = f"{samples:,}" | |
| table.add_row( | |
| str(idx), | |
| key, | |
| val.get("base_model", "?"), | |
| val.get("dataset_format", "?"), | |
| samples, | |
| status, | |
| val.get("created_date", "-")[:10], | |
| metric_str | |
| ) | |
| console.print(table) | |
| console.print("\n" + Theme.info("Pilihan:")) | |
| console.print(" [green]1. Set model aktif[/green]") | |
| console.print(" [green]2. Lihat detail model[/green]") | |
| console.print(" [green]3. Hapus model[/green]") | |
| console.print(" [green]4. Export model[/green]") | |
| console.print(" [green]5. Kembali[/green]") | |
| choice = Prompt.ask("[yellow]Pilih", choices=['1', '2', '3', '4', '5']) | |
| if choice == '1': | |
| model_id = Prompt.ask("[cyan]ID model", choices=list(models.keys())) | |
| model_manager.set_current_model(model_id) | |
| console.print(Theme.success(f" Model aktif: {model_id}")) | |
| elif choice == '2': | |
| model_id = Prompt.ask("[cyan]ID model", choices=list(models.keys())) | |
| info = models.get(model_id) | |
| if info: | |
| table = Table(title=f"Detail: {model_id}", box=box.ROUNDED) | |
| table.add_column("Key", style="cyan") | |
| table.add_column("Value", style="green") | |
| for k, v in info.items(): | |
| if k not in ['metrics', 'config']: | |
| table.add_row(k, str(v)[:200]) | |
| console.print(table) | |
| if 'metrics' in info and info['metrics']: | |
| console.print("\n" + Theme.header(" Metrics:")) | |
| for k, v in info['metrics'].items(): | |
| if isinstance(v, float): | |
| console.print(f" {k}: {v:.4f}") | |
| else: | |
| console.print(f" {k}: {v}") | |
| elif choice == '3': | |
| model_id = Prompt.ask("[cyan]ID model yang akan dihapus", choices=list(models.keys())) | |
| if Confirm.ask(Theme.error(f"Hapus {model_id}?"), default=False): | |
| if model_manager.delete_model(model_id): | |
| console.print(Theme.success(" Model dihapus")) | |
| else: | |
| console.print(Theme.error("Gagal hapus")) | |
| elif choice == '4': | |
| model_id = Prompt.ask("[cyan]ID model yang akan diexport", choices=list(models.keys())) | |
| model_path = os.path.join(MODEL_DIR, model_id) | |
| if os.path.exists(model_path): | |
| try: | |
| export_dir = Path(EXPORT_DIR) / model_id | |
| shutil.copytree(model_path, export_dir) | |
| console.print(Theme.success(f" Model exported to {export_dir}")) | |
| except Exception as e: | |
| console.print(Theme.error(f"Export failed: {e}")) | |
| def dataset_inspector() -> None: | |
| console.print(Panel(Theme.header(" DATASET INSPECTOR"), title="INSPECTOR", style="bold yellow")) | |
| filepath = select_dataset_file() | |
| if filepath is None: | |
| return | |
| if not os.path.exists(filepath): | |
| console.print(Theme.error("File tidak ditemukan!")) | |
| return | |
| console.print(Theme.info("Analyzing dataset...")) | |
| try: | |
| loader = EnhancedDatasetLoader() | |
| samples, stats = loader.load(filepath) | |
| except Exception as e: | |
| console.print(Theme.error(f"Error: {rich_escape(str(e))}")) | |
| return | |
| console.print("\n" + Theme.header(" Dataset Statistics:")) | |
| table = Table(title="Dataset Info", box=box.ROUNDED) | |
| table.add_column("Metric", style="cyan") | |
| table.add_column("Value", style="green") | |
| for key, value in stats.__dict__.items(): | |
| if key not in ['warnings', 'conversation_pairs', 'word_frequency', 'char_frequency']: | |
| if isinstance(value, float): | |
| table.add_row(key, f"{value:.2f}") | |
| else: | |
| table.add_row(key, str(value)) | |
| console.print(table) | |
| if stats.warnings: | |
| console.print("\n" + Theme.warning("Warnings:")) | |
| for warn in stats.warnings[:5]: | |
| console.print(f" {warn}") | |
| if samples: | |
| console.print("\n" + Theme.header(" Sample Texts:")) | |
| for idx, text in enumerate(samples[:3], 1): | |
| preview = text[:200] + "..." if len(text) > 200 else text | |
| console.print(f"[green][{idx}][/green]\n{rich_escape(preview)}\n") | |
| if stats.word_frequency: | |
| console.print("\n" + Theme.info(" Top Words:")) | |
| word_table = Table(title="Word Frequency", box=box.SIMPLE) | |
| word_table.add_column("Word", style="cyan") | |
| word_table.add_column("Count", style="green") | |
| for word, count in list(stats.word_frequency.items())[:20]: | |
| word_table.add_row(word, str(count)) | |
| console.print(word_table) | |
| def visualize_history_menu() -> None: | |
| model_manager = ModelManager() | |
| models = model_manager.list_models() | |
| if not models: | |
| console.print(Theme.warning("Tidak ada model untuk divisualisasi")) | |
| return | |
| console.print(Theme.info("Pilih model:")) | |
| for idx, (mid, _) in enumerate(models.items(), 1): | |
| console.print(f" {idx}. {mid}") | |
| choice = Prompt.ask("[yellow]Pilih", default="1") | |
| try: | |
| model_id = list(models.keys())[int(choice)-1] | |
| except (ValueError, IndexError): | |
| console.print(Theme.error("Pilihan invalid")) | |
| return | |
| model_path = os.path.join(MODEL_DIR, model_id) | |
| history_path = os.path.join(model_path, "training_history.json") | |
| if not os.path.exists(history_path): | |
| console.print(Theme.warning("Tidak ada history untuk model ini")) | |
| return | |
| try: | |
| with open(history_path, 'r') as f: | |
| history_data = json.load(f) | |
| history = TrainingHistory.from_dict(history_data) | |
| history.print_summary() | |
| if HAS_MATPLOTLIB: | |
| if Confirm.ask("[yellow]Generate plot?", default=True): | |
| plot_path = os.path.join(PLOT_DIR, f"{model_id}_history.png") | |
| history.plot(plot_path) | |
| overfit = history.detect_overfitting() | |
| if overfit['overfitting_detected']: | |
| console.print(Theme.error(overfit['warning'])) | |
| except Exception as e: | |
| console.print(Theme.error(f"Error reading history: {e}")) | |
| def enhanced_system_stats() -> None: | |
| console.print(Panel(Theme.header(" SYSTEM STATISTICS"), title="STATS", style="yellow")) | |
| table = Table(title="System Information", box=box.ROUNDED) | |
| table.add_column("Info", style="cyan") | |
| table.add_column("Value", style="green") | |
| table.add_row("Python Version", platform.python_version()) | |
| table.add_row("PyTorch Version", torch.__version__) | |
| try: | |
| import transformers | |
| table.add_row("Transformers Version", transformers.__version__) | |
| except: | |
| pass | |
| try: | |
| import datasets | |
| table.add_row("Datasets Version", datasets.__version__) | |
| except: | |
| pass | |
| cuda_available = torch.cuda.is_available() | |
| table.add_row("CUDA Available", "Yes" if cuda_available else "No") | |
| if cuda_available: | |
| table.add_row("GPU Name", torch.cuda.get_device_name(0)) | |
| props = torch.cuda.get_device_properties(0) | |
| vram_gb = props.total_memory / (1024**3) | |
| table.add_row("GPU VRAM", f"{vram_gb:.2f} GB") | |
| table.add_row("GPU Compute", f"{props.major}.{props.minor}") | |
| table.add_row("GPU Count", str(torch.cuda.device_count())) | |
| allocated = torch.cuda.memory_allocated() / (1024**3) | |
| table.add_row("GPU Used", f"{allocated:.2f} GB") | |
| table.add_row("CPU Count", str(os.cpu_count())) | |
| if HAS_PSUTIL: | |
| memory = psutil.virtual_memory() | |
| table.add_row("RAM Used", f"{memory.used / (1024**3):.2f} GB") | |
| table.add_row("RAM Total", f"{memory.total / (1024**3):.2f} GB") | |
| table.add_row("RAM Percent", f"{memory.percent}%") | |
| cpu_freq = psutil.cpu_freq() | |
| if cpu_freq: | |
| table.add_row("CPU Frequency", f"{cpu_freq.current:.0f} MHz") | |
| table.add_row("CPU Usage", f"{psutil.cpu_percent(interval=0.5)}%") | |
| model_manager = ModelManager() | |
| models = model_manager.list_models() | |
| table.add_row("Models Saved", str(len(models))) | |
| table.add_row("Active Model", model_manager.get_current_model() or "None") | |
| table.add_row("Multi-GPU", "Yes" if model_manager.is_multi_gpu() else "No") | |
| mem_limit = model_manager.config.get('memory_limit_gb', 0) | |
| table.add_row("Memory Protection", f"{mem_limit} GB" if mem_limit > 0 else "DISABLED") | |
| table.add_row("PEFT Available", "Yes" if HAS_PEFT else "No") | |
| table.add_row("8-bit Available", "Yes" if HAS_BNB else "No") | |
| table.add_row("Evaluate Available", "Yes" if HAS_EVALUATE else "No") | |
| table.add_row("SacreBLEU Available", "Yes" if HAS_SACREBLEU else "No") | |
| table.add_row("Matplotlib Available", "Yes" if HAS_MATPLOTLIB else "No") | |
| table.add_row("Pandas Available", "Yes" if HAS_PANDAS else "No") | |
| console.print(table) | |
| input("\n[yellow]Press Enter...[/yellow]") | |
| def enhanced_config_menu() -> None: | |
| console.print(Panel(Theme.header(" KONFIGURASI"), title="CONFIG", style="yellow")) | |
| console.print(Theme.info("Pilihan:")) | |
| console.print(" [green]1. Training Config[/green]") | |
| console.print(" [green]2. Generation Config[/green]") | |
| console.print(" [green]3. Memory Protection Settings[/green]") | |
| console.print(" [green]4. Kembali[/green]") | |
| cfg_choice = Prompt.ask("Pilih", choices=['1', '2', '3', '4']) | |
| if cfg_choice == '1': | |
| _config_training() | |
| elif cfg_choice == '2': | |
| _config_generation() | |
| elif cfg_choice == '3': | |
| _config_memory_protection() | |
| else: | |
| return | |
| def _config_training() -> None: | |
| model_manager = ModelManager() | |
| train_cfg = model_manager.get_training_config() | |
| console.print(Theme.header("Training Configuration:")) | |
| table = Table(title="Current Settings", box=box.ROUNDED) | |
| table.add_column("Parameter", style="cyan") | |
| table.add_column("Value", style="green") | |
| for k, v in train_cfg.items(): | |
| if k not in ['peft_config', 'data_augmentation']: | |
| if isinstance(v, float): | |
| v_str = f"{v:.6f}" if v < 0.001 else f"{v:.4f}" | |
| else: | |
| v_str = str(v) | |
| table.add_row(k, v_str) | |
| console.print(table) | |
| if Confirm.ask("[yellow]Ubah konfigurasi?", default=False): | |
| for k in train_cfg.keys(): | |
| if k in ['peft_config', 'data_augmentation']: | |
| continue | |
| new_val = Prompt.ask(f"{k}", default=str(train_cfg[k])) | |
| try: | |
| if k in ['learning_rate', 'validation_split', 'warmup_ratio', 'weight_decay', 'gradient_clip_value']: | |
| train_cfg[k] = float(new_val) | |
| elif k in ['batch_size', 'num_epochs', 'max_length', 'warmup_steps', | |
| 'save_steps', 'logging_steps', 'early_stopping_patience', | |
| 'seed', 'gradient_accumulation_steps', 'max_samples_limit', 'num_proc']: | |
| train_cfg[k] = int(new_val) | |
| elif k in ['fp16', 'bf16', 'use_peft', 'freeze_embeddings', | |
| 'dynamic_grad_accumulation', 'use_8bit', 'load_best_model_at_end']: | |
| train_cfg[k] = new_val.lower() in ['true', '1', 'yes'] | |
| else: | |
| train_cfg[k] = new_val | |
| except ValueError: | |
| console.print(Theme.warning(f"Skipped {k}")) | |
| model_manager.update_training_config(**train_cfg) | |
| console.print(Theme.success(" Konfigurasi diperbarui")) | |
| def _config_generation() -> None: | |
| model_manager = ModelManager() | |
| gen_cfg = model_manager.get_generation_config() | |
| console.print(Theme.header("Generation Configuration:")) | |
| table = Table(title="Current Settings", box=box.ROUNDED) | |
| table.add_column("Parameter", style="cyan") | |
| table.add_column("Value", style="green") | |
| for k, v in gen_cfg.items(): | |
| if isinstance(v, float): | |
| v_str = f"{v:.4f}" | |
| else: | |
| v_str = str(v) | |
| table.add_row(k, v_str) | |
| console.print(table) | |
| if Confirm.ask("[yellow]Ubah konfigurasi?", default=False): | |
| for k in gen_cfg.keys(): | |
| new_val = Prompt.ask(f"{k}", default=str(gen_cfg[k])) | |
| try: | |
| if k in ['temperature', 'top_p', 'repetition_penalty']: | |
| gen_cfg[k] = float(new_val) | |
| elif k in ['max_new_tokens', 'top_k', 'no_repeat_ngram_size', 'max_context_length']: | |
| gen_cfg[k] = int(new_val) | |
| else: | |
| gen_cfg[k] = new_val | |
| except ValueError: | |
| console.print(Theme.warning(f"Skipped {k}")) | |
| model_manager.update_generation_config(**gen_cfg) | |
| console.print(Theme.success(" Konfigurasi diperbarui")) | |
| def _config_memory_protection() -> None: | |
| model_manager = ModelManager() | |
| console.print("\n" + Theme.header("Memory Protection Settings:")) | |
| console.print(Theme.warning(" Memory protection helps prevent OOM killer")) | |
| current_limit = model_manager.config.get('memory_limit_gb', 0) | |
| console.print(f" Current memory limit: {current_limit if current_limit > 0 else 'DISABLED'} GB") | |
| if Confirm.ask("[yellow]Enable memory protection?", default=current_limit > 0): | |
| new_limit = FloatPrompt.ask("Memory limit (GB)", default=current_limit or DEFAULT_MEMORY_LIMIT_GB) | |
| model_manager.config['memory_limit_gb'] = new_limit | |
| console.print(Theme.success(f" Memory limit set to {new_limit} GB")) | |
| if platform.system().lower() == "linux": | |
| if set_memory_hard_limit(new_limit): | |
| console.print(Theme.success(" Protection active")) | |
| else: | |
| console.print(Theme.warning(" Protection may not be active")) | |
| else: | |
| model_manager.config['memory_limit_gb'] = 0 | |
| console.print(Theme.warning(" Memory protection disabled")) | |
| model_manager.save_config() | |
| def select_dataset_file(prompt: str = "Pilih file dataset") -> Optional[str]: | |
| from pathlib import Path | |
| from rich.prompt import Prompt | |
| from config import DATA_DIR | |
| supported_ext = ('.txt', '.json', '.jsonl', '.csv', '.tsv', '.parquet', '.arrow', '.json.gz', '.jsonl.gz') | |
| data_dir = Path(DATA_DIR) | |
| files = [] | |
| for ext in supported_ext: | |
| files.extend(data_dir.glob(f"*{ext}")) | |
| files = sorted(files) | |
| if not files: | |
| console.print(Theme.warning("Tidak ada file dataset di folder 'data/'. Silakan masukkan path manual.")) | |
| path = Prompt.ask("[cyan]Masukkan path file dataset") | |
| if os.path.exists(path): | |
| return path | |
| console.print(Theme.error("File tidak ditemukan!")) | |
| return None | |
| console.print(Theme.info("File dataset tersedia:")) | |
| for idx, f in enumerate(files, 1): | |
| try: | |
| size = f.stat().st_size | |
| if size > 100 * 1024 * 1024: | |
| size_str = f"{size / (1024*1024*1024):.2f} GB" | |
| elif size > 1024 * 1024: | |
| size_str = f"{size / (1024*1024):.2f} MB" | |
| elif size > 1024: | |
| size_str = f"{size / 1024:.2f} KB" | |
| else: | |
| size_str = f"{size} B" | |
| console.print(f" {idx}. {f.name} ({size_str})") | |
| except Exception: | |
| console.print(f" {idx}. {f.name}") | |
| console.print(" 0. Masukkan path manual") | |
| choice = Prompt.ask("[yellow]Pilih nomor", default="1") | |
| if choice == "0": | |
| path = Prompt.ask("[cyan]Masukkan path file dataset") | |
| if os.path.exists(path): | |
| return path | |
| console.print(Theme.error("File tidak ditemukan!")) | |
| return None | |
| try: | |
| idx = int(choice) - 1 | |
| if 0 <= idx < len(files): | |
| return str(files[idx]) | |
| console.print(Theme.error("Nomor tidak valid!")) | |
| return None | |
| except ValueError: | |
| console.print(Theme.error("Input harus berupa angka!")) | |
| return None | |
| # ============================================================ | |
| # HELPER: load model untuk RAG pipeline | |
| # ============================================================ | |
| def _load_rag_model(model_path: str): | |
| """Load tokenizer + model untuk RAG pipeline. Return (tokenizer, model).""" | |
| from transformers import AutoTokenizer, AutoModelForCausalLM | |
| device = get_device() | |
| dtype = torch.float16 if device == "cuda" else torch.float32 | |
| is_peft = False | |
| base_model_name = None | |
| if os.path.exists(os.path.join(model_path, "adapter_config.json")): | |
| is_peft = True | |
| base_info_path = os.path.join(model_path, "base_model_info.json") | |
| if os.path.exists(base_info_path): | |
| try: | |
| with open(base_info_path) as f: | |
| info = json.load(f) | |
| if info.get("use_peft", False): | |
| is_peft = True | |
| base_model_name = info.get("base_model") | |
| except Exception: | |
| pass | |
| tokenizer = AutoTokenizer.from_pretrained(model_path) | |
| if tokenizer.pad_token is None: | |
| tokenizer.pad_token = tokenizer.eos_token | |
| if is_peft and base_model_name and HAS_PEFT: | |
| try: | |
| from peft import PeftModel | |
| base = AutoModelForCausalLM.from_pretrained( | |
| base_model_name, torch_dtype=dtype, low_cpu_mem_usage=True | |
| ) | |
| model = PeftModel.from_pretrained(base, model_path) | |
| except Exception as e: | |
| console.print(Theme.warning(f"PEFT load failed: {e}, fallback full model")) | |
| model = AutoModelForCausalLM.from_pretrained( | |
| model_path, torch_dtype=dtype, low_cpu_mem_usage=True | |
| ) | |
| else: | |
| model = AutoModelForCausalLM.from_pretrained( | |
| model_path, torch_dtype=dtype, low_cpu_mem_usage=True | |
| ) | |
| model = model.to(device) | |
| model.eval() | |
| return tokenizer, model | |
| # ============================================================ | |
| # SUBMENU: QUIZ MODULE | |
| # ============================================================ | |
| def quiz_module_menu() -> None: | |
| try: | |
| from quiz_module import QuizManager | |
| except Exception as e: | |
| console.print(Theme.error(f"Quiz module tidak tersedia: {rich_escape(str(e))}")) | |
| input("\n[yellow]Press Enter...[/yellow]") | |
| return | |
| try: | |
| quiz = QuizManager() | |
| except Exception as e: | |
| console.print(Theme.error(f"Gagal inisialisasi QuizManager: {rich_escape(str(e))}")) | |
| input("\n[yellow]Press Enter...[/yellow]") | |
| return | |
| # --- Auto-seed pertanyaan dari dataset kalau masih kosong --- | |
| if not quiz.questions: | |
| console.print(Theme.warning("Belum ada pertanyaan tersimpan.")) | |
| if Confirm.ask("[yellow]Generate pertanyaan dari dataset sekarang?", default=True): | |
| filepath = select_dataset_file() | |
| if filepath: | |
| console.print(Theme.info("Loading dataset...")) | |
| try: | |
| loader = EnhancedDatasetLoader() | |
| samples, stats = loader.load(filepath) | |
| qa_pairs = stats.conversation_pairs or [] | |
| if not qa_pairs: | |
| console.print(Theme.warning("Tidak ada pasangan Q&A di dataset")) | |
| console.print(Theme.dim("Quiz hanya mendukung format user/assistant, prompt/response, instruction/response")) | |
| else: | |
| max_q = min(len(qa_pairs), 200) | |
| selected_pairs = qa_pairs[:max_q] | |
| new_questions = quiz.generate_questions_from_qa_pairs(selected_pairs) | |
| quiz.add_questions(new_questions) | |
| console.print(Theme.success(f" ✓ {len(new_questions)} pertanyaan dibuat dari {max_q} Q&A pairs")) | |
| except Exception as e: | |
| console.print(Theme.error(f"Gagal generate: {rich_escape(str(e))}")) | |
| input("\n[yellow]Press Enter...[/yellow]") | |
| return | |
| console.print(Panel(Theme.header(" QUIZ MODULE"), title="QUIZ", style="bold yellow")) | |
| console.print(f" [dim]Total pertanyaan tersedia: {len(quiz.questions)}[/dim]") | |
| console.print(" [green]1. Start Quiz[/green]") | |
| console.print(" [green]2. View Results[/green]") | |
| console.print(" [green]3. Generate lebih banyak pertanyaan dari dataset[/green]") | |
| console.print(" [green]4. Kembali[/green]") | |
| sub = Prompt.ask("[yellow]Pilih", choices=['1', '2', '3', '4']) | |
| try: | |
| if sub == '1': | |
| if not quiz.questions: | |
| console.print(Theme.error("Tidak ada pertanyaan. Pilih opsi 3 dulu.")) | |
| else: | |
| num_str = Prompt.ask("[cyan]Jumlah soal", default="5") | |
| try: | |
| num = int(num_str) | |
| except ValueError: | |
| num = 5 | |
| num = max(1, min(num, len(quiz.questions))) | |
| quiz_id = quiz.create_auto_quiz(num) | |
| if quiz_id: | |
| quiz.start_quiz(quiz_id) | |
| elif sub == '2': | |
| quiz.display_results() | |
| elif sub == '3': | |
| filepath = select_dataset_file() | |
| if filepath: | |
| try: | |
| loader = EnhancedDatasetLoader() | |
| samples, stats = loader.load(filepath) | |
| qa_pairs = stats.conversation_pairs or [] | |
| if not qa_pairs: | |
| console.print(Theme.error("Dataset tidak punya pasangan Q&A")) | |
| else: | |
| new_questions = quiz.generate_questions_from_qa_pairs(qa_pairs) | |
| quiz.add_questions(new_questions) | |
| console.print(Theme.success(f" ✓ {len(new_questions)} pertanyaan ditambahkan")) | |
| except Exception as e: | |
| console.print(Theme.error(f"Gagal generate: {rich_escape(str(e))}")) | |
| except Exception as e: | |
| console.print(Theme.error(f"Error: {rich_escape(str(e))}")) | |
| input("\n[yellow]Press Enter...[/yellow]") | |
| # ============================================================ | |
| # SUBMENU: KNOWLEDGE BASE (SEMANTIC SEARCH) | |
| # ============================================================ | |
| def knowledge_base_menu() -> None: | |
| try: | |
| from semantic_search import KnowledgeBase | |
| except Exception as e: | |
| console.print(Theme.error(f"Knowledge Base tidak tersedia: {rich_escape(str(e))}")) | |
| input("\n[yellow]Press Enter...[/yellow]") | |
| return | |
| try: | |
| kb = KnowledgeBase() | |
| except Exception as e: | |
| console.print(Theme.error(f"Gagal inisialisasi KnowledgeBase: {rich_escape(str(e))}")) | |
| input("\n[yellow]Press Enter...[/yellow]") | |
| return | |
| console.print(Panel(Theme.header(" KNOWLEDGE BASE"), title="KB", style="bold yellow")) | |
| console.print(f" [dim]Dokumen tersimpan: {len(kb.documents)}[/dim]") | |
| console.print(" [green]1. Search[/green]") | |
| console.print(" [green]2. Add Document[/green]") | |
| console.print(" [green]3. Import dari Dataset[/green]") | |
| console.print(" [green]4. Kembali[/green]") | |
| sub = Prompt.ask("[yellow]Pilih", choices=['1', '2', '3', '4']) | |
| try: | |
| if sub == '1': | |
| query = Prompt.ask("[cyan]Search query") | |
| if not query.strip(): | |
| return | |
| top_k_str = Prompt.ask("[cyan]Top K", default="5") | |
| try: | |
| top_k = int(top_k_str) | |
| except ValueError: | |
| top_k = 5 | |
| results = kb.search(query, top_k=top_k) | |
| if not results: | |
| console.print(Theme.warning("Tidak ada hasil")) | |
| else: | |
| for i, r in enumerate(results, 1): | |
| score = r.get('score', 0.0) | |
| text = r.get('text', '') | |
| console.print(f"[green]#{i} Score: {score:.3f}[/green]") | |
| console.print(rich_escape(text[:300])) | |
| console.print() | |
| elif sub == '2': | |
| text = Prompt.ask("[cyan]Masukkan teks dokumen") | |
| if text.strip(): | |
| if hasattr(kb, 'add_document'): | |
| try: | |
| import time as _t | |
| doc_id = f"doc_{int(_t.time())}" | |
| kb.add_document(doc_id, text) | |
| console.print(Theme.success(f" Dokumen ditambahkan ({doc_id})")) | |
| except TypeError: | |
| kb.add_document(text) | |
| console.print(Theme.success(" Dokumen ditambahkan")) | |
| elif sub == '3': | |
| filepath = select_dataset_file() | |
| if filepath: | |
| try: | |
| loader = EnhancedDatasetLoader() | |
| samples, stats = loader.load(filepath) | |
| added = 0 | |
| import time as _t | |
| for i, text in enumerate(samples[:500]): | |
| if text.strip(): | |
| try: | |
| kb.add_document(f"ds_{i}_{int(_t.time() * 1000)}", text) | |
| added += 1 | |
| except Exception: | |
| pass | |
| console.print(Theme.success(f" ✓ {added} dokumen di-import")) | |
| except Exception as e: | |
| console.print(Theme.error(f"Gagal import: {rich_escape(str(e))}")) | |
| except Exception as e: | |
| console.print(Theme.error(f"Error: {rich_escape(str(e))}")) | |
| input("\n[yellow]Press Enter...[/yellow]") | |
| # ============================================================ | |
| # SUBMENU: RAG PIPELINE | |
| # ============================================================ | |
| def rag_pipeline_menu() -> None: | |
| try: | |
| from rag_module import RAGPipeline | |
| except Exception as e: | |
| console.print(Theme.error(f"RAG Pipeline tidak tersedia: {rich_escape(str(e))}")) | |
| input("\n[yellow]Press Enter...[/yellow]") | |
| return | |
| try: | |
| rag = RAGPipeline() | |
| except Exception as e: | |
| console.print(Theme.error(f"Gagal inisialisasi RAGPipeline: {rich_escape(str(e))}")) | |
| input("\n[yellow]Press Enter...[/yellow]") | |
| return | |
| console.print(Panel(Theme.header(" RAG PIPELINE"), title="RAG", style="bold yellow")) | |
| # --- Load model aktif untuk generation --- | |
| model_manager = ModelManager() | |
| current_model = model_manager.get_current_model() | |
| if not current_model: | |
| console.print(Theme.warning("Tidak ada model aktif. RAG akan bekerja tanpa generation (retrieve only).")) | |
| console.print(Theme.dim("Set model aktif via menu [03] Manage Model terlebih dahulu.")) | |
| else: | |
| model_path = os.path.join(MODEL_DIR, current_model) | |
| if not os.path.exists(model_path): | |
| console.print(Theme.error(f"Model path tidak ditemukan: {model_path}")) | |
| else: | |
| console.print(Theme.info(f"Loading model untuk RAG: {current_model}...")) | |
| try: | |
| tokenizer, model = _load_rag_model(model_path) | |
| rag.set_model(tokenizer, model) | |
| console.print(Theme.success(" Model RAG siap!")) | |
| except Exception as e: | |
| console.print(Theme.error(f"Gagal load model: {rich_escape(str(e))}")) | |
| console.print(Theme.warning("RAG akan berjalan tanpa generation")) | |
| try: | |
| if hasattr(rag, 'query_interactive'): | |
| rag.query_interactive() | |
| else: | |
| query = Prompt.ask("[cyan]Query") | |
| if query.strip(): | |
| result = rag.query(query) | |
| console.print(Panel(rich_escape(str(result)), title="Answer", style="green")) | |
| except Exception as e: | |
| console.print(Theme.error(f"Error: {rich_escape(str(e))}")) | |
| input("\n[yellow]Press Enter...[/yellow]") | |
| # ============================================================ | |
| # MAIN MENU | |
| # ============================================================ | |
| def enhanced_menu() -> None: | |
| while True: | |
| os.system('clear' if os.name == 'posix' else 'cls') | |
| logo() | |
| model_manager = ModelManager() | |
| current_model = model_manager.get_current_model() | |
| model_display = f"[green]{current_model}" if current_model else "[red]None" | |
| mem_limit = model_manager.config.get('memory_limit_gb', 0) | |
| mem_status = "[green]ACTIVE" if mem_limit > 0 else "[red]INACTIVE" | |
| # Status Google Drive | |
| drive_mounted = Path("/content/drive/MyDrive").exists() | |
| drive_status = "[green]CONNECTED" if drive_mounted else "[yellow]NOT MOUNTED" | |
| console.print(Panel( | |
| f"""[bold white][[bold green]01[/bold white]] [bold white]Training Ultimate - Auto-LoRA • Multi-GPU | |
| [bold white][[bold green]02[/bold white]] [bold white]Chat Enhanced - Chat dengan context management | |
| [bold white][[bold green]03[/bold white]] [bold white]Manage Model - List, set aktif, hapus | |
| [bold white][[bold green]04[/bold white]] [bold white]Dataset Inspector - Analisa dataset | |
| [bold white][[bold green]05[/bold white]] [bold white]Visualize History - Plot training metrics | |
| [bold white][[bold green]06[/bold white]] [bold white]Konfigurasi - Training & Generation settings | |
| [bold white][[bold green]07[/bold white]] [bold white]System Stats - Informasi sistem | |
| [bold white][[bold green]08[/bold white]] [bold white]Quiz Module - Latihan soal otomatis | |
| [bold white][[bold green]09[/bold white]] [bold white]Knowledge Base - Semantic search dokumen | |
| [bold white][[bold green]10[/bold white]] [bold white]RAG Pipeline - Retrieval-Augmented Generation | |
| [bold white][[bold green]11[/bold white]] [bold white]Backup Project - Export ke Google Drive | |
| [bold white][[bold green]12[/bold white]] [bold white]Exit""", | |
| width=70, title="[bold yellow]ULTIMATE MENU v2.5 FIXED", style="bold yellow" | |
| )) | |
| console.print(Panel( | |
| f"[cyan]Status: [white]ACTIVE\n" | |
| f"[cyan]Model Aktif: {model_display}\n" | |
| f"[cyan]Memory Protection: {mem_status}\n" | |
| f"[cyan]Google Drive: {drive_status}\n" | |
| f"[cyan]Multi-GPU: {'[green]Enabled[/green]' if model_manager.is_multi_gpu() else '[dim]Disabled[/dim]'}", | |
| width=40, title="[yellow]INFO", style="yellow" | |
| )) | |
| pilihan = Prompt.ask("[bold yellow] Pilih (1-12)") | |
| if pilihan in ['1', '01']: | |
| EnhancedTrainingModule().run() | |
| elif pilihan in ['2', '02']: | |
| EnhancedChatModule().run() | |
| elif pilihan in ['3', '03']: | |
| manage_models() | |
| elif pilihan in ['4', '04']: | |
| dataset_inspector() | |
| elif pilihan in ['5', '05']: | |
| visualize_history_menu() | |
| elif pilihan in ['6', '06']: | |
| enhanced_config_menu() | |
| elif pilihan in ['7', '07']: | |
| enhanced_system_stats() | |
| elif pilihan in ['8', '08']: | |
| quiz_module_menu() | |
| elif pilihan in ['9', '09']: | |
| knowledge_base_menu() | |
| elif pilihan in ['10']: | |
| rag_pipeline_menu() | |
| elif pilihan in ['11']: | |
| backup_menu() | |
| elif pilihan in ['12', 'exit', 'q']: | |
| console.print(Theme.success(" Keluar...")) | |
| sys.exit(0) | |
| else: | |
| console.print(Theme.error("Pilihan tidak valid!")) | |
| input("\n[yellow]Press Enter...[/yellow]") |