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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ logs/*.log
# Django generated statics
staticfiles

# Django generated media
media/

# Virtual environments
.venv

Expand Down
29 changes: 27 additions & 2 deletions hospexplorer/ask/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

from django.contrib import admin

from ask.models import Conversation, TermsAcceptance, QARecord, SimWorkflow, WebsiteResource
from ask.kb_connector import add_website_to_kb
from ask.models import Conversation, TermsAcceptance, QARecord, SimWorkflow, WebsiteResource, PDFResource
from ask.kb_connector import add_website_to_kb, add_pdf_to_kb

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -158,3 +158,28 @@ def save_model(self, request, obj, form, change):
except Exception as e:
logger.exception("Failed to send website to KB: %s", obj.url)
self.message_user(request, f"Website saved but failed to send to Knowledge Base: {e}", level="warning")


@admin.register(PDFResource)
class PDFResourceAdmin(admin.ModelAdmin):
list_display = ("title", "file", "creator", "modified_at", "mcp_kb_document_id")
search_fields = ("title",)
readonly_fields = ("created_at", "modified_at", "creator", "modifier", "mcp_kb_document_id")

def save_model(self, request, obj, form, change):
if not change:
obj.creator = request.user
obj.modifier = request.user
super().save_model(request, obj, form, change)

try:
obj.file.open("rb")
file_bytes = obj.file.read()
obj.file.close()
result = add_pdf_to_kb(file_bytes, obj.file.name.split("/")[-1], obj.title)
obj.mcp_kb_document_id = result.get("doc_id")
obj.save(update_fields=["mcp_kb_document_id"])
self.message_user(request, f"PDF '{obj.title}' sent to Knowledge Base (doc_id={obj.mcp_kb_document_id}).")
except Exception as e:
logger.exception("Failed to send PDF to KB: %s", obj.file.name)
self.message_user(request, f"PDF saved but failed to send to Knowledge Base: {e}", level="warning")
30 changes: 30 additions & 0 deletions hospexplorer/ask/kb_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,36 @@ def add_website_to_kb(url):
return response.json()


def add_pdf_to_kb(file_bytes, filename, title, url=None):
"""Upload a PDF to the MCP KB server for ingestion.

Calls POST /docs/pdf/add on the MCP KB server with multipart form data.
The KB server extracts text, chunks it, generates embeddings,
and stores it for semantic search.
"""
headers = {
"Authorization": f"Bearer {settings.KB_MCP_JWT_TOKEN}",
}
endpoint = f"{settings.KB_MCP_HOST}/docs/pdf/add"

files = {"file": (filename, file_bytes, "application/pdf")}
data = {"title": title}
if url:
data["url"] = url

with httpx.Client() as client:
response = client.post(
endpoint,
headers=headers,
files=files,
data=data,
timeout=settings.KB_MCP_TIMEOUT,
)

response.raise_for_status()
return response.json()


def delete_kb_document(doc_id):
"""Delete a document from the MCP KB server by its ID.

Expand Down
34 changes: 34 additions & 0 deletions hospexplorer/ask/migrations/0011_pdfresource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 6.0.2 on 2026-04-03 23:17

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


class Migration(migrations.Migration):

dependencies = [
('ask', '0010_websiteresource_mcp_kb_document_id'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='PDFResource',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('description', models.TextField(blank=True, default='')),
('created_at', models.DateTimeField(auto_now_add=True)),
('modified_at', models.DateTimeField(auto_now=True)),
('file', models.FileField(upload_to='kb_pdfs/')),
('mcp_kb_document_id', models.IntegerField(blank=True, help_text='Document ID returned by the MCP Knowledge Base.', null=True)),
('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_created', to=settings.AUTH_USER_MODEL)),
('modifier', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_modified', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'PDF Resource',
'verbose_name_plural': 'PDF Resources',
},
),
]
9 changes: 9 additions & 0 deletions hospexplorer/ask/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ class Meta:
verbose_name_plural = "Website Resources"


class PDFResource(Resource):
file = models.FileField(upload_to="kb_pdfs/")
mcp_kb_document_id = models.IntegerField(null=True, blank=True, help_text="Document ID returned by the MCP Knowledge Base.")

class Meta:
verbose_name = "PDF Resource"
verbose_name_plural = "PDF Resources"


class QueryTask(models.Model):
class Status(models.TextChoices):
PENDING = "pending", "Pending"
Expand Down
Loading