diff --git a/hospexplorer/ask/llm_connector.py b/hospexplorer/ask/llm_connector.py index 25f897d..8f4d597 100644 --- a/hospexplorer/ask/llm_connector.py +++ b/hospexplorer/ask/llm_connector.py @@ -1,7 +1,8 @@ -import requests +import httpx from django.conf import settings -def query_llm(query): + +async def query_llm(query): headers = { "Authorization": f"Bearer {settings.LLM_TOKEN}", "Content-Type": "application/json", @@ -19,8 +20,13 @@ def query_llm(query): "max_tokens": settings.LLM_MAX_TOKENS } - response = requests.post(settings.LLM_HOST + settings.LLM_QUERY_ENDPOINT, json=payload, headers=headers, timeout=60) + async with httpx.AsyncClient() as client: + response = await client.post( + settings.LLM_HOST + settings.LLM_QUERY_ENDPOINT, + json=payload, + headers=headers, + timeout=settings.LLM_TIMEOUT + ) - response.raise_for_status() # raises on 4xx/5xx - print(response) + response.raise_for_status() return response.json() \ No newline at end of file diff --git a/hospexplorer/ask/migrations/0001_initial.py b/hospexplorer/ask/migrations/0001_initial.py new file mode 100644 index 0000000..345cf72 --- /dev/null +++ b/hospexplorer/ask/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# Generated by Django 6.0.2 on 2026-02-10 16:50 + +import django.db.models.deletion +import uuid +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='QueryTask', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('query_text', models.TextField()), + ('status', models.CharField(choices=[('pending', 'Pending'), ('processing', 'Processing'), ('completed', 'Completed'), ('failed', 'Failed')], db_index=True, default='pending', max_length=20)), + ('result', models.TextField(blank=True, default='')), + ('error_message', models.TextField(blank=True, default='')), + ('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='query_tasks', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/hospexplorer/ask/models.py b/hospexplorer/ask/models.py index 71a8362..db98399 100644 --- a/hospexplorer/ask/models.py +++ b/hospexplorer/ask/models.py @@ -1,3 +1,30 @@ +import uuid + +from django.conf import settings from django.db import models -# Create your models here. + +class QueryTask(models.Model): + class Status(models.TextChoices): + PENDING = "pending", "Pending" + PROCESSING = "processing", "Processing" + COMPLETED = "completed", "Completed" + FAILED = "failed", "Failed" + + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name="query_tasks", + ) + query_text = models.TextField() + status = models.CharField( + max_length=20, + choices=Status.choices, + default=Status.PENDING, + db_index=True, + ) + result = models.TextField(blank=True, default="") + error_message = models.TextField(blank=True, default="") + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) diff --git a/hospexplorer/ask/templates/_base.html b/hospexplorer/ask/templates/_base.html index 6665c53..8b9ed9e 100644 --- a/hospexplorer/ask/templates/_base.html +++ b/hospexplorer/ask/templates/_base.html @@ -11,6 +11,7 @@ + diff --git a/hospexplorer/ask/templates/_response.html b/hospexplorer/ask/templates/_response.html new file mode 100644 index 0000000..397c21b --- /dev/null +++ b/hospexplorer/ask/templates/_response.html @@ -0,0 +1,3 @@ + diff --git a/hospexplorer/ask/templates/index.html b/hospexplorer/ask/templates/index.html index c015a36..a74a172 100644 --- a/hospexplorer/ask/templates/index.html +++ b/hospexplorer/ask/templates/index.html @@ -17,30 +17,88 @@

Hopper

userQuery: '', answers: [], isLoading: false, + pollInterval: null, async getAnswer() { + if (!this.userQuery.trim()) return; this.isLoading = true; + try { - const response = await fetch('{% url 'ask:query-llm' %}?query=' + encodeURIComponent(this.userQuery)); - const data = await response.json(); - if (!response.ok || data.error) { - this.answers.push('Something went wrong. Please try again.'); - } else { - this.answers.push(data.message); + const submitResponse = await fetch( + '{% url 'ask:submit-query' %}?query=' + encodeURIComponent(this.userQuery) + ); + const submitData = await submitResponse.json(); + + if (!submitResponse.ok || submitData.error) { + this.answers.push(submitData.error || 'Something went wrong. Please try again.'); + this.isLoading = false; + return; } + + this.pollForResult(submitData.task_id); } catch (error) { this.answers.push('Something went wrong. Please try again.'); + this.isLoading = false; } - this.isLoading = false; + }, + + pollForResult(taskId) { + if (this.pollInterval) clearInterval(this.pollInterval); + + let attempts = 0; + const maxAttempts = 50; + const pollUrl = '{% url 'ask:poll-query' task_id='00000000-0000-0000-0000-000000000000' %}'.replace( + '00000000-0000-0000-0000-000000000000', taskId + ); + + this.pollInterval = setInterval(async () => { + attempts++; + if (attempts >= maxAttempts) { + clearInterval(this.pollInterval); + this.pollInterval = null; + this.answers.push('Request timed out. Please try again.'); + this.isLoading = false; + return; + } + + try { + const pollResponse = await fetch(pollUrl); + const pollData = await pollResponse.json(); + + if (pollData.status === 'completed') { + clearInterval(this.pollInterval); + this.pollInterval = null; + this.answers.push(pollData.result); + this.isLoading = false; + } else if (pollData.status === 'failed') { + clearInterval(this.pollInterval); + this.pollInterval = null; + this.answers.push(pollData.error || 'Something went wrong. Please try again.'); + this.isLoading = false; + } + } catch (error) { + clearInterval(this.pollInterval); + this.pollInterval = null; + this.answers.push('Something went wrong. Please try again.'); + this.isLoading = false; + } + }, 3000); } }"> -
- - -
+
+
+ + +
+
+
-
+