File size: 4,993 Bytes
6757240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d595b97
6757240
 
 
82229f9
 
 
 
 
 
6757240
 
d595b97
 
6757240
 
 
 
d595b97
6757240
 
 
 
 
 
 
 
 
82229f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6757240
82229f9
 
 
 
 
 
6757240
 
 
82229f9
6757240
 
 
82229f9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
class LoraSelector extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
        }
        
        .lora-container {
          position: relative;
        }
        
        .lora-select {
          width: 100%;
          background: #374151;
          border: 1px solid #4b5563;
          border-radius: 0.5rem;
          padding: 0.75rem;
          color: white;
          font-size: 1rem;
          cursor: pointer;
          appearance: none;
          background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
          background-repeat: no-repeat;
          background-position: right 0.75rem center;
          background-size: 1rem;
        }
        
        .lora-select:focus {
          outline: none;
          border-color: #10b981;
          box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.3);
        }
        
        .loading {
          position: absolute;
          right: 10px;
          top: 50%;
          transform: translateY(-50%);
          display: none;
        }
        
        .spinner {
          width: 20px;
          height: 20px;
          border: 2px solid rgba(255, 255, 255, 0.3);
          border-radius: 50%;
          border-top-color: #10b981;
          animation: spin 1s ease-in-out infinite;
        }
        
        @keyframes spin {
          to { transform: rotate(360deg); }
        }
        
        .error {
          color: #ef4444;
          font-size: 0.875rem;
          margin-top: 0.5rem;
          display: none;
        }
      </style>
      
      <div class="lora-container">
        <select class="lora-select" id="loraSelect">
          <option value="">Loading LoRA models...</option>
        </select>
        <div class="loading" id="loadingIndicator">
          <div class="spinner"></div>
        </div>
      </div>
      <div class="error" id="errorMessage"></div>
    `;
    
    this.loadLoraModels();
  }
  
  async loadLoraModels() {
    const selectElement = this.shadowRoot.getElementById('loraSelect');
    const loadingIndicator = this.shadowRoot.getElementById('loadingIndicator');
    const errorMessage = this.shadowRoot.getElementById('errorMessage');
    
    try {
      loadingIndicator.style.display = 'block';
      
      // Fetch LoRA models from Hugging Face API
      const response = await fetch('https://huggingface.co/api/models/Playtime-AI/Wan2.2-Loras');
      const data = await response.json();
      // Get safetensors files
      const safetensorsFiles = data.siblings.filter(file => file.rfilename.endsWith('.safetensors'));
      
      // Clear loading option
      selectElement.innerHTML = '<option value="">Select a LoRA model</option>';
      
      // Add each LoRA model as an option
      safetensorsFiles.forEach(file => {
        const option = document.createElement('option');
        const modelName = file.rfilename.replace('.safetensors', '');
        option.value = JSON.stringify({
          filename: file.rfilename,
          trigger: this.getTriggerWord(modelName)
        });
        option.textContent = modelName;
        selectElement.appendChild(option);
      });
      
      // Dispatch event when models are loaded
      this.dispatchEvent(new CustomEvent('loras-loaded', {
        detail: { models: safetensorsFiles },
        bubbles: true
      }));
} catch (error) {
      console.error('Error loading LoRA models:', error);
      selectElement.innerHTML = '<option value="">Failed to load models</option>';
      errorMessage.textContent = 'Failed to load LoRA models. Please try again later.';
      errorMessage.style.display = 'block';
    } finally {
      loadingIndicator.style.display = 'none';
    }
  }
  
  getTriggerWord(modelName) {
    const triggers = {
      'Wan2.2-Disco-Diffusion': 'disco style',
      'Wan2.2-Futuristic-City': 'futuristic cityscape',
      'Wan2.2-Anime-Art': 'anime art style',
      'Wan2.2-Cyberpunk': 'cyberpunk aesthetic',
      'Wan2.2-Fantasy': 'fantasy illustration',
      'Wan2.2-Photorealistic': 'photorealistic',
      'Wan2.2-Watercolor': 'watercolor painting',
      'Wan2.2-Oil-Painting': 'oil painting',
      'Wan2.2-Sketch': 'pencil sketch',
      'Wan2.2-Impressionist': 'impressionist painting'
    };
    
    return triggers[modelName] || 'enhanced style';
  }
  
  get selectedValue() {
    const selected = this.shadowRoot.getElementById('loraSelect').value;
    try {
      return JSON.parse(selected);
    } catch {
      return { filename: '', trigger: '' };
    }
  }
  
  set selectedValue(value) {
    this.shadowRoot.getElementById('loraSelect').value = JSON.stringify(value);
  }
}

customElements.define('lora-selector', LoraSelector);