Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .app_env_example
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# Replace with real server URL for production
export LLM_HOST=http://mock:3000/

# This is only a fall back for when no active workflow is set in Sim Workflows through the admin
# Once you have set an active workflow, this will not be used
export LLM_TOKEN=your-token
export LLM_MAX_TOKENS=4096

Expand Down
76 changes: 76 additions & 0 deletions hospexplorer/ask/static/css/ask.css
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,82 @@
text-decoration: underline;
}

/* Search results */
.search-results-header {
font-size: 0.85rem;
font-weight: 600;
color: #495057;
text-transform: uppercase;
letter-spacing: 0.03em;
margin-bottom: 0.75rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid #8c1d40;
}

.search-result {
padding: 0.85rem;
margin-bottom: 0.6rem;
background: #f8f9fa;
border-radius: 0.5rem;
border-left: 3px solid #8c1d40;
}

.search-result:last-child {
margin-bottom: 0;
}

.search-result-number {
font-size: 0.75rem;
font-weight: 700;
color: #8c1d40;
text-transform: uppercase;
letter-spacing: 0.03em;
margin-bottom: 0.2rem;
}

.search-result-title {
font-size: 1rem;
font-weight: 600;
color: #8c1d40;
text-decoration: none;
display: block;
line-height: 1.35;
}

a.search-result-title:hover {
text-decoration: underline;
color: #6b1530;
}

span.search-result-title {
color: #333;
}

.search-result-summary {
margin: 0.4rem 0 0.3rem;
font-size: 0.88rem;
color: #444;
line-height: 1.5;
}

.search-result-relevance {
margin: 0;
font-size: 0.8rem;
color: #777;
font-style: italic;
}

.search-result-relevance strong {
font-style: normal;
color: #555;
}

.search-no-results {
color: #666;
font-style: italic;
padding: 1rem 0;
}

/* Scroll to bottom button */

.scroll-down-btn {
Expand Down
2 changes: 2 additions & 0 deletions hospexplorer/ask/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ def run_llm_task(task_id, record_id, conversation_id):
if not llm_response.get("success") or "output" not in llm_response:
raise ValueError("LLM response is missing structure")

# content is a JSON string with search_results from the LLM
# sent to frontend as is, parsed on the frontend by window.renderChatMessage() in index.html
content = llm_response["output"].get("content", "")

task.result = content
Expand Down
52 changes: 51 additions & 1 deletion hospexplorer/ask/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
showScrollBtn: false,
glossaryOpen: false,

renderMessage(msg) {
return window.renderChatMessage(msg);
},

get userQuestions() {
return this.messages
.map((msg, index) => ({ ...msg, index }))
Expand Down Expand Up @@ -174,7 +178,7 @@ <h4>How can I help you today?</h4>
<div class="chat-avatar" x-text="msg.role === 'user' ? 'You' : 'AI'"></div>
<div class="chat-bubble"
:class="'chat-bubble-' + msg.role"
x-html="msg.role === 'assistant' ? marked.parse(msg.content) : msg.content">
x-html="renderMessage(msg)">
</div>
</div>
</template>
Expand Down Expand Up @@ -249,4 +253,50 @@ <h6 class="mb-0">Question History</h6>
</div>
</div>
</div>
<script>
// tries to parse content as JSON with search_results and renders document cards
// Falls back to markdown rendering (via marked.parse) for older conversations or non JSON responses
window.renderChatMessage = function(msg) {
function escapeHtml(text) {
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}

if (msg.role === 'user') return escapeHtml(msg.content);

try {
var parsed = JSON.parse(msg.content.trim());
if (parsed && Array.isArray(parsed.search_results)) {
var results = parsed.search_results;
if (results.length === 0) {
return '<p class="search-no-results">No documents were found.</p>';
}
var count = results.length;
var header = '<div class="search-results-header">' + count + ' document' + (count !== 1 ? 's' : '') + ' found</div>';
var cards = results.map(function(r, i) {
var title = escapeHtml(r.title || 'Untitled');
var summary = escapeHtml(r.summary || '');
var relevance = escapeHtml(r.relevance || '');
var titleHtml = r.url
? '<a href="' + escapeHtml(r.url) + '" target="_blank" rel="noopener" class="search-result-title">' + title + '</a>'
: '<span class="search-result-title">' + title + '</span>';
var html = '<div class="search-result">';
html += '<div class="search-result-number">Result ' + (i + 1) + '</div>';
html += titleHtml;
html += '<p class="search-result-summary">' + summary + '</p>';
if (relevance) {
html += '<p class="search-result-relevance"><strong>Relevance:</strong> ' + relevance + '</p>';
}
html += '</div>';
return html;
}).join('');
return header + cards;
}
} catch (e) {
// If no JSON, fall back to markdown rendering
}
return marked.parse(msg.content);
};
</script>
{% endblock %}