add message history

This commit is contained in:
2026-05-06 07:13:06 -06:00
parent 4ce19c5e40
commit 8845845bf0
3 changed files with 159 additions and 1 deletions

View File

@@ -8,7 +8,7 @@ from django.contrib.auth.decorators import login_required
from django.db.models import Count from django.db.models import Count
from .models import Conversation, Message from .models import Conversation, Message
from .schemas import ConversationOut, MessageOut from .schemas import ConversationOut, MessageOut, PromptHistoryItemOut
from account.models import RoleChoices from account.models import RoleChoices
router = Router() router = Router()
@@ -55,6 +55,58 @@ def list_messages(request, conversation_id: UUID):
] ]
@router.get("/history/{task_id}", response=List[PromptHistoryItemOut])
@login_required
def list_prompt_history(request, task_id: int):
"""
获取当前用户在某任务下的历史对话轮次。
只返回用户提示词和后一条 assistant 消息中的页面代码,用于前端渲染缩略图。
"""
conversations = Conversation.objects.filter(
user=request.user,
task_id=task_id,
).prefetch_related("messages")
items = []
for conv in conversations:
messages = list(conv.messages.all().order_by("created", "id"))
for idx, user_msg in enumerate(messages):
if user_msg.role != "user":
continue
assistant_msg = None
for reply in messages[idx + 1:]:
if reply.role == "user":
break
if reply.role == "assistant":
assistant_msg = reply
break
if not assistant_msg:
continue
items.append(
(
user_msg.created,
{
"user_message_id": user_msg.id,
"assistant_message_id": assistant_msg.id,
"submission_id": assistant_msg.submission_id,
"source": user_msg.source,
"prompt": user_msg.content,
"prompt_level": user_msg.prompt_level,
"code_html": assistant_msg.code_html,
"code_css": assistant_msg.code_css,
"code_js": assistant_msg.code_js,
"created": user_msg.created.isoformat(),
},
)
)
return [item for _, item in sorted(items, key=lambda row: row[0], reverse=True)]
@router.post("/conversations/{conversation_id}/classify") @router.post("/conversations/{conversation_id}/classify")
@login_required @login_required
def classify_conversation(request, conversation_id: UUID, force: bool = False): def classify_conversation(request, conversation_id: UUID, force: bool = False):

View File

@@ -15,6 +15,19 @@ class MessageOut(Schema):
created: str created: str
class PromptHistoryItemOut(Schema):
user_message_id: int
assistant_message_id: int
submission_id: Optional[UUID] = None
source: str
prompt: str
prompt_level: Optional[int] = None
code_html: Optional[str] = None
code_css: Optional[str] = None
code_js: Optional[str] = None
created: str
class ConversationOut(Schema): class ConversationOut(Schema):
id: UUID id: UUID
user_id: int user_id: int

View File

@@ -175,3 +175,96 @@ class DeleteMessagePairTest(TestCase):
self.client.force_login(other) self.client.force_login(other)
resp = self.client.delete(f"/api/prompt/messages/{self.asst_msg.id}/pair") resp = self.client.delete(f"/api/prompt/messages/{self.asst_msg.id}/pair")
self.assertEqual(resp.status_code, 403) self.assertEqual(resp.status_code, 403)
class PromptHistoryTest(TestCase):
def setUp(self):
self.user = _make_user("history-user")
self.other = _make_user("history-other")
self.task = _make_task()
self.other_task = Task.objects.create(
title="Other Task", task_type="challenge", display=2, content=""
)
def _pair(
self,
user,
task,
prompt,
source="conversation",
html="<main>page</main>",
css="main { color: red; }",
js="",
):
conv = Conversation.objects.create(user=user, task=task)
user_msg = Message.objects.create(
conversation=conv,
role="user",
source=source,
content=prompt,
)
asst_msg = Message.objects.create(
conversation=conv,
role="assistant",
source=source,
content="" if source == "manual" else "answer",
code_html=html,
code_css=css,
code_js=js,
)
return user_msg, asst_msg
def test_history_returns_ai_and_manual_prompt_rounds_with_page_code(self):
ai_user, ai_asst = self._pair(self.user, self.task, "做一个登录页")
manual_user, manual_asst = self._pair(
self.user,
self.task,
"我让外部 AI 做一个卡片",
source="manual",
html="<section>card</section>",
)
self.client.force_login(self.user)
resp = self.client.get(f"/api/prompt/history/{self.task.id}")
self.assertEqual(resp.status_code, 200)
data = resp.json()
ids = {item["user_message_id"] for item in data}
self.assertEqual(ids, {ai_user.id, manual_user.id})
by_source = {item["source"]: item for item in data}
self.assertEqual(by_source["conversation"]["assistant_message_id"], ai_asst.id)
self.assertEqual(by_source["conversation"]["prompt"], "做一个登录页")
self.assertEqual(by_source["manual"]["assistant_message_id"], manual_asst.id)
self.assertEqual(by_source["manual"]["code_html"], "<section>card</section>")
self.assertNotIn("content", by_source["conversation"])
def test_history_is_scoped_to_current_user_and_task(self):
own_user, _ = self._pair(self.user, self.task, "自己的提示词")
self._pair(self.other, self.task, "别人的提示词")
self._pair(self.user, self.other_task, "其他任务提示词")
self.client.force_login(self.user)
resp = self.client.get(f"/api/prompt/history/{self.task.id}")
self.assertEqual(resp.status_code, 200)
data = resp.json()
self.assertEqual(len(data), 1)
self.assertEqual(data[0]["user_message_id"], own_user.id)
self.assertEqual(data[0]["prompt"], "自己的提示词")
def test_history_keeps_rounds_without_page_code(self):
conv = Conversation.objects.create(user=self.user, task=self.task)
user_msg = Message.objects.create(conversation=conv, role="user", content="只聊天")
asst_msg = Message.objects.create(conversation=conv, role="assistant", content="没有代码")
self.client.force_login(self.user)
resp = self.client.get(f"/api/prompt/history/{self.task.id}")
self.assertEqual(resp.status_code, 200)
data = resp.json()
self.assertEqual(len(data), 1)
self.assertEqual(data[0]["user_message_id"], user_msg.id)
self.assertEqual(data[0]["assistant_message_id"], asst_msg.id)
self.assertIsNone(data[0]["code_html"])
self.assertIsNone(data[0]["code_css"])
self.assertIsNone(data[0]["code_js"])