Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
924b6a5
[HOP-14] Added message like bubbles with sender/reciever type style, …
Girik1105 Feb 13, 2026
1f58441
[HOP-14] Added nice scroll effects when llm answers, and scroll down …
Girik1105 Feb 17, 2026
83e5c5b
[HOP-14] Fixed comments, re added removed code
Girik1105 Feb 18, 2026
48b18bc
[HOP-17] Added conversation model and registered in admin
Girik1105 Feb 18, 2026
005e472
[HOP-17] Added urls, views and context processor for conversations (i…
Girik1105 Feb 19, 2026
f5a2c49
Merge branch 'develop' into story/HOP-14
Girik1105 Feb 20, 2026
2a52f7b
[HOP-17] fixed template, removed extra code
Girik1105 Feb 20, 2026
3d9ad77
[HOP17] Merged HOP-14 since its dependent
Girik1105 Feb 20, 2026
13e2a36
[HOP-17] Added redirect to conversation so refresh dosent lose curren…
Girik1105 Feb 20, 2026
23d8000
[HOP-17] Resolved Merge Conflicts
Girik1105 Feb 24, 2026
c0b3a4a
[HOP-17] Implemented sidebar for conversations
Girik1105 Feb 24, 2026
68c6649
[HOP-17] Added convo title
Girik1105 Feb 25, 2026
daaf35b
[HOP-17] Resolved merge conflicts
Girik1105 Feb 27, 2026
666f4df
[HOP-17] delete changed to conversations
Girik1105 Feb 27, 2026
d45887a
[HOP-17] Fixed merge conflicts, slight css adjustment
Girik1105 Feb 27, 2026
8dc4b75
[HOP-17] removed debug comments
Girik1105 Feb 27, 2026
7ad468e
[HOP-17] improved comments
Girik1105 Feb 27, 2026
db6a617
[HOP-17] Removed wrong branch code from here
Girik1105 Feb 27, 2026
a17e024
[HOP-17] fixed migrations, restore develop base add conversation as 0003
Girik1105 Mar 3, 2026
e4cf337
[HOP-17] Resolved merge conflicts
Girik1105 Mar 4, 2026
70d5a76
[HOP-17] Resolved merge conflicts and made path as re_path
Girik1105 Mar 4, 2026
35b9aba
[HOP-17] makemigrations --merge and migrate
Girik1105 Mar 4, 2026
3306793
[HOP-17] Fixed conversation button title
Girik1105 Mar 4, 2026
ae56be9
Merge branch 'develop' into story/HOP-17
Girik1105 Mar 9, 2026
ddfdc4d
[HOP-17] Fixed conversation limit variable
Girik1105 Mar 9, 2026
d6fe967
[HOP-17] Added conversation limit default to 10, pull from env
Girik1105 Mar 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions hospexplorer/ask/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
from django.contrib import admin
from ask.models import TermsAcceptance, QARecord
from ask.models import Conversation, TermsAcceptance, QARecord


class QARecordInline(admin.TabularInline):
model = QARecord
extra = 0
readonly_fields = ("question_text", "question_timestamp", "answer_text", "answer_timestamp", "is_error")
fields = ("question_text", "question_timestamp", "answer_text", "answer_timestamp", "is_error")


@admin.register(Conversation)
class ConversationAdmin(admin.ModelAdmin):
list_display = ("id", "title", "user", "created_at", "updated_at")
list_filter = ("user",)
search_fields = ("title", "user__username")
inlines = [QARecordInline]


@admin.register(TermsAcceptance)
Expand All @@ -22,11 +37,11 @@ def has_delete_permission(self, request, obj=None):

@admin.register(QARecord)
class QARecordAdmin(admin.ModelAdmin):
list_display = ["id", "user", "truncated_question", "question_timestamp", "answer_timestamp"]
list_filter = ["question_timestamp", "user"]
list_display = ["id", "user", "conversation", "truncated_question", "question_timestamp", "answer_timestamp", "is_error"]
list_filter = ["question_timestamp", "user", "is_error"]
search_fields = ["question_text", "answer_text", "user__username"]
readonly_fields = ["question_timestamp", "answer_timestamp", "answer_raw_response"]
raw_id_fields = ["user"]
raw_id_fields = ["user", "conversation"]
date_hierarchy = "question_timestamp"

def truncated_question(self, obj):
Expand Down
25 changes: 25 additions & 0 deletions hospexplorer/ask/context_processors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,29 @@
from django.conf import settings
from django.urls import reverse
from ask.models import Conversation


def sidebar_conversations(request):
if request.user.is_authenticated:
limit = settings.SIDEBAR_CONVERSATIONS_LIMIT
conversations = Conversation.objects.filter(
user=request.user
)[:limit]

sidebar_items = []
for conv in conversations:
label = conv.title if conv.title else conv.created_at.strftime("%b %d, %Y %I:%M %p")
sidebar_items.append({
"id": conv.id,
"label": label,
"url": reverse("ask:conversation", kwargs={"conversation_id": conv.id}),
"updated_at": conv.updated_at.isoformat(),
})
return {
"sidebar_conversations": sidebar_items,
"sidebar_conversations_limit": limit,
}
return {"sidebar_conversations": [], "sidebar_conversations_limit": 0}


# Django context processor registered in settings TEMPLATES
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 6.0.1 on 2026-03-03 18:19

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('ask', '0002_qarecord_is_error'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='Conversation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(blank=True, default='', max_length=200)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conversations', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-updated_at'],
},
),
migrations.AddField(
model_name='qarecord',
name='conversation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='qa_records', to='ask.conversation'),
preserve_default=False,
),
]
14 changes: 14 additions & 0 deletions hospexplorer/ask/migrations/0005_merge_20260304_2256.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 6.0.2 on 2026-03-04 22:56

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('ask', '0003_conversation_qarecord_conversation'),
('ask', '0004_merge_0003_querytask_0003_termsacceptance'),
]

operations = [
]
33 changes: 32 additions & 1 deletion hospexplorer/ask/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,27 @@ class Status(models.TextChoices):
updated_at = models.DateTimeField(auto_now=True)


class Conversation(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="conversations",
)
title = models.CharField(max_length=200, blank=True, default="")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

class Meta:
ordering = ["-updated_at"]

def __str__(self):
if self.title:
truncated = self.title[:50]
suffix = "..." if len(self.title) > 50 else ""
return f"Conversation {self.id}: {truncated}{suffix}"
return f"Conversation {self.id} ({self.user.username})"


class TermsAcceptance(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
Expand All @@ -53,6 +74,12 @@ class QARecord(models.Model):
"""
Stores a question-answer pair from user interactions with the LLM.
"""
conversation = models.ForeignKey(
Conversation,
on_delete=models.CASCADE,
related_name="qa_records",
)

# Question fields
question_text = models.TextField()
question_timestamp = models.DateTimeField(auto_now_add=True)
Expand All @@ -63,7 +90,11 @@ class QARecord(models.Model):
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")
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="qa_records",
)

class Meta:
ordering = ["-question_timestamp"]
Expand Down
1 change: 1 addition & 0 deletions hospexplorer/ask/static/css/ask.css
Original file line number Diff line number Diff line change
Expand Up @@ -358,4 +358,5 @@
.chat-input-bar {
padding: 0.5rem 1rem;
}

}
11 changes: 10 additions & 1 deletion hospexplorer/ask/static/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -10894,10 +10894,19 @@ body.sb-sidenav-toggled #wrapper #sidebar-wrapper {
}

#sidebar-wrapper .question-item:hover {
border-left-color: #0d6efd;
border-left-color: #8c1d40;
background-color: #f8f9fa;
}

#sidebar-wrapper .question-item.active {
border-left-color: #8c1d40;
background-color: rgba(140, 29, 64, 0.1);
color: #8c1d40;
font-weight: 600;
border-color: transparent;
border-left: 3px solid #8c1d40;
}

#sidebar-wrapper .sidebar-section-heading {
font-weight: 600;
text-transform: uppercase;
Expand Down
74 changes: 43 additions & 31 deletions hospexplorer/ask/templates/_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,35 @@
<div class="d-flex" id="wrapper">
<!-- Sidebar-->
<div class="border-end bg-white d-flex flex-column" id="sidebar-wrapper"
x-data="sidebarQuestions()"
@question-asked.window="addQuestion($event.detail)">
x-data="sidebarConversations()"
@conversation-updated.window="upsertConversation($event.detail)">
<div class="sidebar-heading border-bottom bg-white">
<img width="50" src="{% static 'assets/asu.png' %}">
Hopper
</div>
<div class="list-group list-group-flush flex-grow-1">
<!-- Recent Questions Section -->
{% if user.is_authenticated %}
<div class="sidebar-section-heading small text-muted px-3 py-2 border-bottom bg-light">
Recent Questions
Conversations
</div>
<template x-for="question in questions" :key="question.id">
<a href="#" class="list-group-item list-group-item-action py-2 px-3 question-item"
:title="question.question_text"
x-text="truncate(question.question_text, 35)">
<div class="p-3">
<form method="post" action="{% url 'ask:new-conversation' %}">
{% csrf_token %}
<button type="submit" class="btn btn-outline-secondary btn-sm w-100">New Chat +</button>
</form>
</div>
{% endif %}
<!-- Conversations Section -->

<template x-for="conv in conversations" :key="conv.id">
<a :href="conv.url" class="list-group-item list-group-item-action py-2 px-3 question-item"
:class="{ 'active': conv.id === activeConversationId }"
:title="conv.label"
x-text="conv.label">
</a>
</template>
<div x-show="questions.length === 0" class="text-muted small px-3 py-2">
No questions yet
<div x-show="conversations.length === 0" class="text-muted small px-3 py-2">
No conversations yet
</div>
</div>
{% if user.is_authenticated %}
Expand All @@ -64,7 +74,7 @@
</li>
<li>
<button type="button" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#deleteHistoryModal">
Delete Question History
Delete All Conversations
</button>
</li>
<li><hr class="dropdown-divider"></li>
Expand Down Expand Up @@ -95,11 +105,11 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteHistoryModalLabel">Delete Question History</h5>
<h5 class="modal-title" id="deleteHistoryModalLabel">Delete All Conversations</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Are you sure you want to delete your question history? This action is irreversible.
Are you sure you want to delete all your conversations? This action is irreversible.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button>
Expand Down Expand Up @@ -141,29 +151,31 @@ <h5 class="modal-title" id="deleteHistoryModalLabel">Delete Question History</h5
}
});
</script>
<!-- Sidebar Questions Component -->
<!-- Sidebar Conversations Component -->
{{ sidebar_conversations|json_script:"sidebar-conversations-data" }}
<script>
window.initialQuestions = {{ recent_questions_json|safe|default:"[]" }};
window.initialConversations = JSON.parse(
document.getElementById('sidebar-conversations-data').textContent
);

function sidebarQuestions() {
function sidebarConversations() {
return {
questions: window.initialQuestions || [],
conversations: window.initialConversations || [],
activeConversationId: {{ conversation.id|default:"null" }},
maxItems: {{ sidebar_conversations_limit|default:"10" }},

addQuestion(detail) {
const newQuestion = {
id: Date.now(),
question_text: detail.text
};
this.questions.unshift(newQuestion);
if (this.questions.length > 10) {
this.questions = this.questions.slice(0, 10);
upsertConversation(detail) {
this.conversations = this.conversations.filter(c => c.id !== detail.id);
this.conversations.unshift({
id: detail.id,
label: detail.label,
url: detail.url,
});
// 10 conversations shown in the sidebar
if (this.conversations.length > this.maxItems) {
this.conversations = this.conversations.slice(0, this.maxItems);
}
},

truncate(text, length) {
if (!text) return '';
if (text.length <= length) return text;
return text.substring(0, length) + '...';
this.activeConversationId = detail.id;
}
};
}
Expand Down
Loading