Spaces:
Sleeping
Sleeping
root commited on
Commit ·
d423ac0
1
Parent(s): 7d4bbcc
feat: add user setting to disable AI topic suggestions
Browse files- Added use_ai_suggestions column to users table with migration.
- Added toggle in settings page to enable/disable AI suggestions.
- Updated manual classification wizard to respect the setting (hides AI buttons and disables automated fetches).
- Updated keyboard shortcuts to skip AI calls when disabled.
- database.py +10 -4
- settings_routes.py +6 -2
- templates/question_entry_v2.html +14 -6
- templates/settings.html +9 -0
- user_auth.py +6 -3
database.py
CHANGED
|
@@ -395,11 +395,17 @@ def setup_database():
|
|
| 395 |
cursor.execute("SELECT group_name FROM sessions LIMIT 1")
|
| 396 |
except sqlite3.OperationalError:
|
| 397 |
cursor.execute("ALTER TABLE sessions ADD COLUMN group_name TEXT")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 398 |
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
|
|
|
|
|
|
| 403 |
|
| 404 |
# Migration to normalize subject names (e.g., "CHEMISTRY" to "Chemistry", "PHYSICS" to "Physics", etc.)
|
| 405 |
try:
|
|
|
|
| 395 |
cursor.execute("SELECT group_name FROM sessions LIMIT 1")
|
| 396 |
except sqlite3.OperationalError:
|
| 397 |
cursor.execute("ALTER TABLE sessions ADD COLUMN group_name TEXT")
|
| 398 |
+
try:
|
| 399 |
+
cursor.execute("SELECT pdf_viewer FROM users LIMIT 1")
|
| 400 |
+
except sqlite3.OperationalError:
|
| 401 |
+
cursor.execute("ALTER TABLE users ADD COLUMN pdf_viewer TEXT DEFAULT 'native'")
|
| 402 |
|
| 403 |
+
try:
|
| 404 |
+
cursor.execute("SELECT use_ai_suggestions FROM users LIMIT 1")
|
| 405 |
+
except sqlite3.OperationalError:
|
| 406 |
+
cursor.execute("ALTER TABLE users ADD COLUMN use_ai_suggestions INTEGER DEFAULT 1")
|
| 407 |
+
|
| 408 |
+
conn.commit()
|
| 409 |
|
| 410 |
# Migration to normalize subject names (e.g., "CHEMISTRY" to "Chemistry", "PHYSICS" to "Physics", etc.)
|
| 411 |
try:
|
settings_routes.py
CHANGED
|
@@ -35,6 +35,9 @@ def settings():
|
|
| 35 |
# --- Handle Two-Page Crop Toggle ---
|
| 36 |
two_page_crop = 1 if request.form.get('two_page_crop') else 0
|
| 37 |
|
|
|
|
|
|
|
|
|
|
| 38 |
# --- Handle Classifier Model Setting ---
|
| 39 |
classifier_model = request.form.get('classifier_model', 'gemini')
|
| 40 |
if classifier_model not in ['gemini', 'nova', 'gemma']:
|
|
@@ -75,8 +78,8 @@ def settings():
|
|
| 75 |
|
| 76 |
# --- Update Database ---
|
| 77 |
conn = get_db_connection()
|
| 78 |
-
conn.execute('UPDATE users SET neetprep_enabled = ?, v2_default = ?, magnifier_enabled = ?, two_page_crop = ?, dpi = ?, color_rm_dpi = ?, classifier_model = ?, pdf_viewer = ? WHERE id = ?',
|
| 79 |
-
(neetprep_enabled, v2_default, magnifier_enabled, two_page_crop, dpi, color_rm_dpi, classifier_model, pdf_viewer, current_user.id))
|
| 80 |
conn.commit()
|
| 81 |
conn.close()
|
| 82 |
|
|
@@ -85,6 +88,7 @@ def settings():
|
|
| 85 |
current_user.v2_default = v2_default
|
| 86 |
current_user.magnifier_enabled = magnifier_enabled
|
| 87 |
current_user.two_page_crop = two_page_crop
|
|
|
|
| 88 |
current_user.dpi = dpi
|
| 89 |
current_user.color_rm_dpi = color_rm_dpi
|
| 90 |
current_user.classifier_model = classifier_model
|
|
|
|
| 35 |
# --- Handle Two-Page Crop Toggle ---
|
| 36 |
two_page_crop = 1 if request.form.get('two_page_crop') else 0
|
| 37 |
|
| 38 |
+
# --- Handle AI Suggestions Toggle ---
|
| 39 |
+
use_ai_suggestions = 1 if request.form.get('use_ai_suggestions') else 0
|
| 40 |
+
|
| 41 |
# --- Handle Classifier Model Setting ---
|
| 42 |
classifier_model = request.form.get('classifier_model', 'gemini')
|
| 43 |
if classifier_model not in ['gemini', 'nova', 'gemma']:
|
|
|
|
| 78 |
|
| 79 |
# --- Update Database ---
|
| 80 |
conn = get_db_connection()
|
| 81 |
+
conn.execute('UPDATE users SET neetprep_enabled = ?, v2_default = ?, magnifier_enabled = ?, two_page_crop = ?, use_ai_suggestions = ?, dpi = ?, color_rm_dpi = ?, classifier_model = ?, pdf_viewer = ? WHERE id = ?',
|
| 82 |
+
(neetprep_enabled, v2_default, magnifier_enabled, two_page_crop, use_ai_suggestions, dpi, color_rm_dpi, classifier_model, pdf_viewer, current_user.id))
|
| 83 |
conn.commit()
|
| 84 |
conn.close()
|
| 85 |
|
|
|
|
| 88 |
current_user.v2_default = v2_default
|
| 89 |
current_user.magnifier_enabled = magnifier_enabled
|
| 90 |
current_user.two_page_crop = two_page_crop
|
| 91 |
+
current_user.use_ai_suggestions = use_ai_suggestions
|
| 92 |
current_user.dpi = dpi
|
| 93 |
current_user.color_rm_dpi = color_rm_dpi
|
| 94 |
current_user.classifier_model = classifier_model
|
templates/question_entry_v2.html
CHANGED
|
@@ -540,20 +540,24 @@
|
|
| 540 |
<div class="mb-2">
|
| 541 |
<label class="form-label small text-muted mb-2">Chapter / Topic</label>
|
| 542 |
<div class="input-group input-group-lg">
|
| 543 |
-
<input type="text" id="topic_input" class="form-control bg-dark text-white border-secondary" placeholder="Start typing
|
| 544 |
<button class="btn btn-outline-secondary" type="button" onclick="document.getElementById('topic_input').value=''; document.getElementById('topic_input').focus();">
|
| 545 |
<i class="bi bi-x-lg"></i>
|
| 546 |
</button>
|
|
|
|
| 547 |
<button class="btn btn-info" type="button" id="btn_get_suggestion" onclick="getAiSuggestion()">
|
| 548 |
<span class="spinner-border spinner-border-sm d-none" role="status"></span>
|
| 549 |
<i class="bi bi-stars"></i>
|
| 550 |
</button>
|
|
|
|
| 551 |
</div>
|
| 552 |
</div>
|
| 553 |
|
| 554 |
<!-- Suggestions -->
|
| 555 |
<div id="ai_suggestion_container" class="d-none">
|
|
|
|
| 556 |
<label class="form-label d-block small text-info mb-2 mt-1"><i class="bi bi-robot me-1"></i>AI Suggested:</label>
|
|
|
|
| 557 |
<div id="ai_suggestion_chips" class="d-flex flex-wrap gap-2"></div>
|
| 558 |
</div>
|
| 559 |
<div id="ai_error_msg" class="alert alert-danger d-none mt-2 py-2"></div>
|
|
@@ -1903,8 +1907,8 @@
|
|
| 1903 |
input.select();
|
| 1904 |
}, 100);
|
| 1905 |
|
| 1906 |
-
// Auto-load suggestions if topic is empty
|
| 1907 |
-
if (!document.getElementById('topic_input').value) {
|
| 1908 |
getAiSuggestion();
|
| 1909 |
}
|
| 1910 |
}
|
|
@@ -2138,14 +2142,18 @@
|
|
| 2138 |
// Clear and fetch fresh suggestions for the new subject
|
| 2139 |
input.value = '';
|
| 2140 |
input.focus();
|
| 2141 |
-
|
|
|
|
|
|
|
| 2142 |
}
|
| 2143 |
}
|
| 2144 |
|
| 2145 |
// Alt + S to trigger AI suggestion
|
| 2146 |
if (e.altKey && (e.key === 's' || e.key === 'S')) {
|
| 2147 |
-
|
| 2148 |
-
|
|
|
|
|
|
|
| 2149 |
}
|
| 2150 |
});
|
| 2151 |
});
|
|
|
|
| 540 |
<div class="mb-2">
|
| 541 |
<label class="form-label small text-muted mb-2">Chapter / Topic</label>
|
| 542 |
<div class="input-group input-group-lg">
|
| 543 |
+
<input type="text" id="topic_input" class="form-control bg-dark text-white border-secondary" placeholder="Start typing...">
|
| 544 |
<button class="btn btn-outline-secondary" type="button" onclick="document.getElementById('topic_input').value=''; document.getElementById('topic_input').focus();">
|
| 545 |
<i class="bi bi-x-lg"></i>
|
| 546 |
</button>
|
| 547 |
+
{% if current_user.use_ai_suggestions %}
|
| 548 |
<button class="btn btn-info" type="button" id="btn_get_suggestion" onclick="getAiSuggestion()">
|
| 549 |
<span class="spinner-border spinner-border-sm d-none" role="status"></span>
|
| 550 |
<i class="bi bi-stars"></i>
|
| 551 |
</button>
|
| 552 |
+
{% endif %}
|
| 553 |
</div>
|
| 554 |
</div>
|
| 555 |
|
| 556 |
<!-- Suggestions -->
|
| 557 |
<div id="ai_suggestion_container" class="d-none">
|
| 558 |
+
{% if current_user.use_ai_suggestions %}
|
| 559 |
<label class="form-label d-block small text-info mb-2 mt-1"><i class="bi bi-robot me-1"></i>AI Suggested:</label>
|
| 560 |
+
{% endif %}
|
| 561 |
<div id="ai_suggestion_chips" class="d-flex flex-wrap gap-2"></div>
|
| 562 |
</div>
|
| 563 |
<div id="ai_error_msg" class="alert alert-danger d-none mt-2 py-2"></div>
|
|
|
|
| 1907 |
input.select();
|
| 1908 |
}, 100);
|
| 1909 |
|
| 1910 |
+
// Auto-load suggestions if topic is empty and setting enabled
|
| 1911 |
+
if (!document.getElementById('topic_input').value && {% if current_user.use_ai_suggestions %}true{% else %}false{% endif %}) {
|
| 1912 |
getAiSuggestion();
|
| 1913 |
}
|
| 1914 |
}
|
|
|
|
| 2142 |
// Clear and fetch fresh suggestions for the new subject
|
| 2143 |
input.value = '';
|
| 2144 |
input.focus();
|
| 2145 |
+
if ({% if current_user.use_ai_suggestions %}true{% else %}false{% endif %}) {
|
| 2146 |
+
getAiSuggestion();
|
| 2147 |
+
}
|
| 2148 |
}
|
| 2149 |
}
|
| 2150 |
|
| 2151 |
// Alt + S to trigger AI suggestion
|
| 2152 |
if (e.altKey && (e.key === 's' || e.key === 'S')) {
|
| 2153 |
+
if ({% if current_user.use_ai_suggestions %}true{% else %}false{% endif %}) {
|
| 2154 |
+
e.preventDefault();
|
| 2155 |
+
getAiSuggestion();
|
| 2156 |
+
}
|
| 2157 |
}
|
| 2158 |
});
|
| 2159 |
});
|
templates/settings.html
CHANGED
|
@@ -98,6 +98,15 @@
|
|
| 98 |
Choose the AI model for automatic question classification. Gemini uses Google's Gemini API, while Nova uses Amazon's Nova model via OpenRouter. Requires relevant API keys to be set.
|
| 99 |
</div>
|
| 100 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
</fieldset>
|
| 102 |
<hr>
|
| 103 |
<fieldset>
|
|
|
|
| 98 |
Choose the AI model for automatic question classification. Gemini uses Google's Gemini API, while Nova uses Amazon's Nova model via OpenRouter. Requires relevant API keys to be set.
|
| 99 |
</div>
|
| 100 |
</div>
|
| 101 |
+
<div class="mb-3 form-check form-switch">
|
| 102 |
+
<input type="checkbox" class="form-check-input" id="use_ai_suggestions" name="use_ai_suggestions" {% if current_user.use_ai_suggestions %}checked{% endif %}>
|
| 103 |
+
<label class="form-check-label" for="use_ai_suggestions">
|
| 104 |
+
Enable AI Topic Suggestions
|
| 105 |
+
</label>
|
| 106 |
+
<div class="form-text">
|
| 107 |
+
Show AI-suggested topics and chapters in the manual classification wizard. If disabled, you can still type manually.
|
| 108 |
+
</div>
|
| 109 |
+
</div>
|
| 110 |
</fieldset>
|
| 111 |
<hr>
|
| 112 |
<fieldset>
|
user_auth.py
CHANGED
|
@@ -5,7 +5,7 @@ from utils import get_db_connection
|
|
| 5 |
|
| 6 |
class User(UserMixin):
|
| 7 |
"""User model for Flask-Login."""
|
| 8 |
-
def __init__(self, id, username, email, password_hash, neetprep_enabled, dpi, color_rm_dpi, v2_default=0, magnifier_enabled=1, two_page_crop=0, google_token=None, classifier_model='gemini', pdf_viewer='adobe'):
|
| 9 |
self.id = id
|
| 10 |
self.username = username
|
| 11 |
self.email = email
|
|
@@ -19,6 +19,7 @@ class User(UserMixin):
|
|
| 19 |
self.google_token = google_token
|
| 20 |
self.classifier_model = classifier_model
|
| 21 |
self.pdf_viewer = pdf_viewer # 'adobe', 'browser', or 'legacy'
|
|
|
|
| 22 |
|
| 23 |
@staticmethod
|
| 24 |
def get(user_id):
|
|
@@ -40,7 +41,8 @@ class User(UserMixin):
|
|
| 40 |
user_data.get('two_page_crop', 0),
|
| 41 |
user_data.get('google_token'),
|
| 42 |
user_data.get('classifier_model', 'gemini'),
|
| 43 |
-
user_data.get('pdf_viewer', 'adobe')
|
|
|
|
| 44 |
)
|
| 45 |
return None
|
| 46 |
|
|
@@ -64,7 +66,8 @@ class User(UserMixin):
|
|
| 64 |
user_data.get('two_page_crop', 0),
|
| 65 |
user_data.get('google_token'),
|
| 66 |
user_data.get('classifier_model', 'gemini'),
|
| 67 |
-
user_data.get('pdf_viewer', 'adobe')
|
|
|
|
| 68 |
)
|
| 69 |
return None
|
| 70 |
|
|
|
|
| 5 |
|
| 6 |
class User(UserMixin):
|
| 7 |
"""User model for Flask-Login."""
|
| 8 |
+
def __init__(self, id, username, email, password_hash, neetprep_enabled, dpi, color_rm_dpi, v2_default=0, magnifier_enabled=1, two_page_crop=0, google_token=None, classifier_model='gemini', pdf_viewer='adobe', use_ai_suggestions=1):
|
| 9 |
self.id = id
|
| 10 |
self.username = username
|
| 11 |
self.email = email
|
|
|
|
| 19 |
self.google_token = google_token
|
| 20 |
self.classifier_model = classifier_model
|
| 21 |
self.pdf_viewer = pdf_viewer # 'adobe', 'browser', or 'legacy'
|
| 22 |
+
self.use_ai_suggestions = use_ai_suggestions
|
| 23 |
|
| 24 |
@staticmethod
|
| 25 |
def get(user_id):
|
|
|
|
| 41 |
user_data.get('two_page_crop', 0),
|
| 42 |
user_data.get('google_token'),
|
| 43 |
user_data.get('classifier_model', 'gemini'),
|
| 44 |
+
user_data.get('pdf_viewer', 'adobe'),
|
| 45 |
+
user_data.get('use_ai_suggestions', 1)
|
| 46 |
)
|
| 47 |
return None
|
| 48 |
|
|
|
|
| 66 |
user_data.get('two_page_crop', 0),
|
| 67 |
user_data.get('google_token'),
|
| 68 |
user_data.get('classifier_model', 'gemini'),
|
| 69 |
+
user_data.get('pdf_viewer', 'adobe'),
|
| 70 |
+
user_data.get('use_ai_suggestions', 1)
|
| 71 |
)
|
| 72 |
return None
|
| 73 |
|