Spaces:
Sleeping
Sleeping
Download templates/index.html from Abdallah4Zain/TasteEngine: direct link, hf CLI and curl.
- Browser
- Download file 10.2 kB
-
https://huggingface.co/spaces/Abdallah4Zain/TasteEngine/resolve/main/templates/index.html
- Command line
-
hf download hf://spaces/Abdallah4Zain/TasteEngine/templates/index.html
-
curl -L -o index.html https://huggingface.co/spaces/Abdallah4Zain/TasteEngine/resolve/main/templates/index.html
10.2 kB
| {% extends "base.html" %} | |
| {% block title %}TasteEngine — Home{% endblock %} | |
| {% block content %} | |
| {% from "macros.html" import glass, tag %} | |
| {% call glass("Welcome to TasteEngine") %} | |
| <p class="text-muted" style="font-size: 0.9rem;"> | |
| An intelligent multi-approach recommender system. Compare Collaborative Filtering, | |
| Content-Based, and Knowledge-Based recommendations side by side. | |
| </p> | |
| {% endcall %} | |
| <div x-data="homeApp()" class="row"> | |
| {% call glass("Select a User") %} | |
| <div class="form-group"> | |
| <label class="form-label" for="userSelect">User</label> | |
| <select id="userSelect" x-model="selectedUserId" @change="onUserChange"> | |
| <option value="">-- Choose a user --</option> | |
| {% for u in users %} | |
| <option value="{{ u.id }}">{{ u.name }} (Age: {{ u.age }})</option> | |
| {% endfor %} | |
| </select> | |
| </div> | |
| <div class="profile-card mt-2" x-show="selectedUser" x-cloak x-transition> | |
| <div class="profile-avatar" x-text="selectedUser?.name?.charAt(0) || '?'"></div> | |
| <div class="profile-info"> | |
| <div class="profile-name" x-text="selectedUser?.name"></div> | |
| <div class="profile-detail" x-text="'Age: ' + selectedUser?.age"></div> | |
| <div class="profile-detail" x-text="'Budget: $' + selectedUser?.budget_min?.toFixed(2) + ' - $' + selectedUser?.budget_max?.toFixed(2)"></div> | |
| <div class="profile-tags mt-1"> | |
| <template x-for="cat in (selectedUser?.categories || [])" :key="cat"> | |
| <span class="tag" x-text="cat"></span> | |
| </template> | |
| <template x-for="b in (selectedUser?.brands || [])" :key="b"> | |
| <span class="tag tag-brand" x-text="b"></span> | |
| </template> | |
| </div> | |
| </div> | |
| </div> | |
| <div x-show="selectedUser" x-cloak x-transition class="mt-2"> | |
| <button class="btn btn-secondary btn-sm" @click="showEditor = !showEditor"> | |
| <span x-text="showEditor ? 'Hide Preferences' : 'Edit Preferences'"></span> | |
| </button> | |
| </div> | |
| <div x-show="showEditor && selectedUser" x-cloak x-transition class="mt-2"> | |
| <div class="glass" style="margin-bottom:0;padding:1rem;"> | |
| <h3 style="font-size:0.9rem;color:var(--text-primary);margin-bottom:0.75rem;">Budget Preferences</h3> | |
| <div class="preference-slider"> | |
| <label>Min Budget</label> | |
| <input type="range" :min="0" :max="selectedUser?.budget_max || 100" x-model.number="editBudgetMin"> | |
| <span class="slider-value" x-text="'$' + editBudgetMin"></span> | |
| </div> | |
| <div class="preference-slider"> | |
| <label>Max Budget</label> | |
| <input type="range" :min="editBudgetMin" :max="500" x-model.number="editBudgetMax"> | |
| <span class="slider-value" x-text="'$' + editBudgetMax"></span> | |
| </div> | |
| <button class="btn btn-primary btn-sm mt-2" @click="savePreferences()" :disabled="savingPrefs"> | |
| <span x-show="!savingPrefs">Save Preferences</span> | |
| <span x-show="savingPrefs">Saving...</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mt-2" x-cloak> | |
| <button class="btn btn-secondary btn-sm" @click="showCreator = !showCreator"> | |
| <span x-text="showCreator ? 'Cancel' : '+ Create New User'"></span> | |
| </button> | |
| </div> | |
| <div x-show="showCreator" x-cloak x-transition class="mt-2"> | |
| <div class="glass" style="margin-bottom:0;padding:1rem;"> | |
| <h3 style="font-size:0.9rem;color:var(--text-primary);margin-bottom:0.75rem;">New User</h3> | |
| <div class="form-row"> | |
| <div class="form-group"> | |
| <label class="form-label">Name</label> | |
| <input type="text" x-model="newName" placeholder="e.g. John"> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Age</label> | |
| <input type="number" x-model.number="newAge" min="1" max="120" value="25"> | |
| </div> | |
| </div> | |
| <div class="form-row"> | |
| <div class="form-group"> | |
| <label class="form-label">Min Budget</label> | |
| <input type="number" x-model.number="newBudgetMin" min="0" value="0"> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Max Budget</label> | |
| <input type="number" x-model.number="newBudgetMax" min="0" value="500"> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Preferred Categories</label> | |
| <select multiple x-model="newCategories" style="min-height:80px;"> | |
| {% for cat in categories %} | |
| <option value="{{ cat }}">{{ cat }}</option> | |
| {% endfor %} | |
| </select> | |
| <span class="form-hint">Ctrl+click to select multiple</span> | |
| </div> | |
| <button class="btn btn-primary btn-sm mt-2" @click="createUser()" :disabled="creatingUser"> | |
| <span x-show="!creatingUser">Create User</span> | |
| <span x-show="creatingUser">Creating...</span> | |
| </button> | |
| </div> | |
| </div> | |
| {% endcall %} | |
| {% call glass("Quick Actions") %} | |
| <p class="text-muted" style="font-size: 0.85rem; margin-bottom: 1rem;"> | |
| Get started by selecting a user above, then: | |
| </p> | |
| <a href="/recommend" class="btn btn-primary" style="display: inline-flex; text-decoration: none; margin-bottom: 0.5rem;">Get Recommendations</a> | |
| <br> | |
| <a href="/evaluate" class="btn btn-secondary" style="display: inline-flex; text-decoration: none;">View Evaluation</a> | |
| {% endcall %} | |
| </div> | |
| {% call glass("Available Approaches") %} | |
| <div class="row" style="grid-template-columns: 1fr 1fr 1fr;"> | |
| <div class="text-center" style="padding: 1rem;"> | |
| <div style="font-size: 2.5rem; margin-bottom: 0.5rem;">🤝</div> | |
| <h3 style="color: var(--text-primary); font-size: 1rem; margin-bottom: 0.3rem;">Collaborative Filtering</h3> | |
| <p class="text-dim" style="font-size: 0.8rem;">5 methods: User, Item, SVD, KNN, Slope One</p> | |
| </div> | |
| <div class="text-center" style="padding: 1rem;"> | |
| <div style="font-size: 2.5rem; margin-bottom: 0.5rem;">🏷️</div> | |
| <h3 style="color: var(--text-primary); font-size: 1rem; margin-bottom: 0.3rem;">Content-Based</h3> | |
| <p class="text-dim" style="font-size: 0.8rem;">TF-IDF + Cosine Similarity, Feature Matching</p> | |
| </div> | |
| <div class="text-center" style="padding: 1rem;"> | |
| <div style="font-size: 2.5rem; margin-bottom: 0.5rem;">⚙️</div> | |
| <h3 style="color: var(--text-primary); font-size: 1rem; margin-bottom: 0.3rem;">Knowledge-Based</h3> | |
| <p class="text-dim" style="font-size: 0.8rem;">Constraint, Rule, Utility-based filtering</p> | |
| </div> | |
| </div> | |
| {% endcall %} | |
| {% endblock %} | |
| {% block scripts %} | |
| <script> | |
| const userData = {{ users | tojson | safe }}; | |
| document.addEventListener('alpine:init', () => { | |
| Alpine.data('homeApp', () => ({ | |
| selectedUserId: localStorage.getItem('selectedUser') || null, | |
| selectedUser: null, | |
| showEditor: false, | |
| editBudgetMin: 0, | |
| editBudgetMax: 100, | |
| savingPrefs: false, | |
| showCreator: false, | |
| newName: '', | |
| newAge: 25, | |
| newBudgetMin: 0, | |
| newBudgetMax: 500, | |
| newCategories: [], | |
| creatingUser: false, | |
| init() { | |
| if (this.selectedUserId) { | |
| this.selectedUserId = parseInt(this.selectedUserId); | |
| this.onUserChange(); | |
| } | |
| }, | |
| onUserChange() { | |
| const uid = this.selectedUserId ? parseInt(this.selectedUserId) : null; | |
| if (!uid) { | |
| this.selectedUser = null; | |
| return; | |
| } | |
| this.selectedUser = userData.find(u => u.id === uid); | |
| if (this.selectedUser) { | |
| this.editBudgetMin = this.selectedUser.budget_min; | |
| this.editBudgetMax = this.selectedUser.budget_max; | |
| } | |
| localStorage.setItem('selectedUser', uid); | |
| }, | |
| async savePreferences() { | |
| if (!this.selectedUser) return; | |
| this.savingPrefs = true; | |
| try { | |
| const r = await fetch(`/api/user/${this.selectedUser.id}/preferences`, { | |
| method: 'PUT', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| budget_min: this.editBudgetMin, | |
| budget_max: this.editBudgetMax | |
| }) | |
| }); | |
| const data = await r.json(); | |
| if (data.success) { | |
| this.selectedUser.budget_min = this.editBudgetMin; | |
| this.selectedUser.budget_max = this.editBudgetMax; | |
| toast('Preferences saved!', 'success'); | |
| this.showEditor = false; | |
| } | |
| } catch (err) { | |
| toast('Error saving preferences', 'error'); | |
| } finally { | |
| this.savingPrefs = false; | |
| } | |
| }, | |
| async createUser() { | |
| if (!this.newName.trim()) { toast('Please enter a name', 'warning'); return; } | |
| this.creatingUser = true; | |
| try { | |
| const r = await fetch('/api/users', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| name: this.newName.trim(), | |
| age: this.newAge, | |
| categories: this.newCategories, | |
| budget_min: this.newBudgetMin, | |
| budget_max: this.newBudgetMax, | |
| }) | |
| }); | |
| const data = await r.json(); | |
| if (data.success) { | |
| toast(`User "${data.name}" created!`, 'success'); | |
| this.showCreator = false; | |
| this.newName = ''; | |
| this.newAge = 25; | |
| this.newCategories = []; | |
| const newUser = { id: data.user_id, name: data.name, age: this.newAge, | |
| categories: this.newCategories, brands: [], | |
| budget_min: this.newBudgetMin, budget_max: this.newBudgetMax }; | |
| userData.push(newUser); | |
| this.selectedUserId = data.user_id; | |
| this.selectedUser = newUser; | |
| this.onUserChange(); | |
| } | |
| } catch (err) { | |
| toast('Error creating user: ' + err.message, 'error'); | |
| } finally { | |
| this.creatingUser = false; | |
| } | |
| } | |
| })); | |
| }); | |
| </script> | |
| {% endblock %} | |