diff --git a/hospexplorer/ask/admin.py b/hospexplorer/ask/admin.py index 8c38f3f..3910bbc 100644 --- a/hospexplorer/ask/admin.py +++ b/hospexplorer/ask/admin.py @@ -1,3 +1,16 @@ from django.contrib import admin +from ask.models import QARecord -# Register your models here. + +@admin.register(QARecord) +class QARecordAdmin(admin.ModelAdmin): + list_display = ["id", "user", "truncated_question", "question_timestamp", "answer_timestamp"] + list_filter = ["question_timestamp", "user"] + search_fields = ["question_text", "answer_text", "user__username"] + readonly_fields = ["question_timestamp", "answer_timestamp", "answer_raw_response"] + raw_id_fields = ["user"] + date_hierarchy = "question_timestamp" + + def truncated_question(self, obj): + return obj.question_text[:75] + "..." if len(obj.question_text) > 75 else obj.question_text + truncated_question.short_description = "Question" diff --git a/hospexplorer/ask/migrations/0001_initial.py b/hospexplorer/ask/migrations/0001_initial.py new file mode 100644 index 0000000..98d680a --- /dev/null +++ b/hospexplorer/ask/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 6.0.1 on 2026-01-30 19:36 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='QARecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('question_text', models.TextField()), + ('question_timestamp', models.DateTimeField(auto_now_add=True)), + ('answer_text', models.TextField(blank=True, default='')), + ('answer_raw_response', models.JSONField(default=dict)), + ('answer_timestamp', models.DateTimeField(blank=True, null=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='qa_records', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Q&A Record', + 'verbose_name_plural': 'Q&A Records', + 'ordering': ['-question_timestamp'], + 'indexes': [models.Index(fields=['user', '-question_timestamp'], name='ask_qarecor_user_id_f4353f_idx')], + }, + ), + ] diff --git a/hospexplorer/ask/migrations/0002_qarecord_is_error.py b/hospexplorer/ask/migrations/0002_qarecord_is_error.py new file mode 100644 index 0000000..10ddcac --- /dev/null +++ b/hospexplorer/ask/migrations/0002_qarecord_is_error.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.1 on 2026-02-04 23:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ask', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='qarecord', + name='is_error', + field=models.BooleanField(default=False), + ), + ] diff --git a/hospexplorer/ask/models.py b/hospexplorer/ask/models.py index 71a8362..aa69d52 100644 --- a/hospexplorer/ask/models.py +++ b/hospexplorer/ask/models.py @@ -1,3 +1,32 @@ from django.db import models +from django.conf import settings -# Create your models here. + +class QARecord(models.Model): + """ + Stores a question-answer pair from user interactions with the LLM. + """ + # Question fields + question_text = models.TextField() + question_timestamp = models.DateTimeField(auto_now_add=True) + + # Answer fields + answer_text = models.TextField(blank=True, default="") + answer_raw_response = models.JSONField(default=dict) + answer_timestamp = models.DateTimeField(null=True, blank=True) + is_error = models.BooleanField(default=False) + + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="qa_records") + + class Meta: + ordering = ["-question_timestamp"] + verbose_name = "Q&A Record" + verbose_name_plural = "Q&A Records" + indexes = [ + models.Index(fields=["user", "-question_timestamp"]), + ] + + def __str__(self): + truncated = self.question_text[:50] + suffix = "..." if len(self.question_text) > 50 else "" + return f"{self.user.username}: {truncated}{suffix}" diff --git a/hospexplorer/ask/static/css/styles.css b/hospexplorer/ask/static/css/styles.css index 07c61b4..42db981 100644 --- a/hospexplorer/ask/static/css/styles.css +++ b/hospexplorer/ask/static/css/styles.css @@ -10870,3 +10870,36 @@ body.sb-sidenav-toggled #wrapper #sidebar-wrapper { margin-left: -15rem; } } + +/* Recent Questions Sidebar Styles */ +#sidebar-wrapper { + display: flex; + flex-direction: column; +} + +#sidebar-wrapper .list-group { + flex: 1; + overflow-y: auto; + max-height: calc(100vh - 180px); +} + +#sidebar-wrapper .question-item { + font-size: 0.875rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + border-left: 3px solid transparent; + transition: border-color 0.2s, background-color 0.2s; +} + +#sidebar-wrapper .question-item:hover { + border-left-color: #0d6efd; + background-color: #f8f9fa; +} + +#sidebar-wrapper .sidebar-section-heading { + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + font-size: 0.75rem; +} diff --git a/hospexplorer/ask/static/js/scripts.js b/hospexplorer/ask/static/js/scripts.js index 81efcaa..92acbb1 100644 --- a/hospexplorer/ask/static/js/scripts.js +++ b/hospexplorer/ask/static/js/scripts.js @@ -10,17 +10,19 @@ window.addEventListener('DOMContentLoaded', event => { // Toggle the side navigation - const sidebarToggle = document.body.querySelector('#sidebarToggle'); - if (sidebarToggle) { - // Uncomment Below to persist sidebar toggle between refreshes - // if (localStorage.getItem('sb|sidebar-toggle') === 'true') { - // document.body.classList.toggle('sb-sidenav-toggled'); - // } - sidebarToggle.addEventListener('click', event => { + const sidebarToggles = document.body.querySelectorAll('#sidebarToggle'); + + // Restore sidebar state from localStorage + if (localStorage.getItem('sb|sidebar-toggle') === 'true') { + document.body.classList.add('sb-sidenav-toggled'); + } + + sidebarToggles.forEach(toggle => { + toggle.addEventListener('click', event => { event.preventDefault(); document.body.classList.toggle('sb-sidenav-toggled'); localStorage.setItem('sb|sidebar-toggle', document.body.classList.contains('sb-sidenav-toggled')); }); - } + }); }); diff --git a/hospexplorer/ask/templates/_base.html b/hospexplorer/ask/templates/_base.html index 6665c53..9af7852 100644 --- a/hospexplorer/ask/templates/_base.html +++ b/hospexplorer/ask/templates/_base.html @@ -17,14 +17,30 @@
-