앱이 기대하는 base-backend 계약(envelope·JWT·/chat/stream SSE)을 그대로 구현. Django 는 로그인·세션 미러(SQLite/PG)·OpenCode 이벤트 번역만 맡고, 답변은 전용 OpenCode 인스턴스(opencode/ workspace, codeassist 에이전트)가 만듦. - accounts: 이메일 로그인, access 60분 / refresh 14일, entra/config 는 501 - chat: 세션 목록·검색·메시지·취소 + POST /chat/stream 어댑터(part.delta→token, idle→usage/done, 클라 끊겨도 턴 감시 태스크가 DB 마무리, 첫 이벤트 60초 타임아웃) - OpenCode 1.18 멀티 프로젝트라 모든 요청에 ?directory= 부착 - docs-lib 에 OpenCode SDK 1.18.6 타입 원본, pytest 32 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
62 lines
2.4 KiB
Python
62 lines
2.4 KiB
Python
"""
|
|
세션·메시지 미러 — 프론트 `features/snap/contract/types.ts` 와 1:1.
|
|
|
|
대화 원본(컨텍스트)은 OpenCode 가 들고 있고, 여긴 목록·검색·usage·isGenerating 용 사본.
|
|
OpenCode 는 페이지네이션·본문 검색·메시지별 토큰을 안 주니까 여기서 함.
|
|
"""
|
|
|
|
from django.conf import settings
|
|
from django.db import models
|
|
|
|
|
|
class ChatSession(models.Model):
|
|
id = models.CharField(primary_key=True, max_length=64) # OpenCode session id 그대로
|
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="chat_sessions")
|
|
title = models.CharField(max_length=200, blank=True) # 사용자가 지은 제목 (지금은 안 씀)
|
|
title_llm = models.CharField(max_length=200, blank=True) # OpenCode 가 지은 제목
|
|
is_generating = models.BooleanField(default=False)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
ordering = ["-updated_at"]
|
|
|
|
def as_dto(self) -> dict:
|
|
return {
|
|
"id": self.id,
|
|
"title": self.title or None,
|
|
"titleLlm": self.title_llm or None,
|
|
"isGenerating": self.is_generating,
|
|
"createdAt": self.created_at.isoformat(),
|
|
"updatedAt": self.updated_at.isoformat(),
|
|
}
|
|
|
|
|
|
class ChatMessage(models.Model):
|
|
ROLES = [("user", "user"), ("assistant", "assistant"), ("system", "system")]
|
|
|
|
session = models.ForeignKey(ChatSession, on_delete=models.CASCADE, related_name="messages")
|
|
role = models.CharField(max_length=16, choices=ROLES)
|
|
content = models.TextField() # markdown
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
# assistant 행에만 채움
|
|
input_tokens = models.IntegerField(null=True, blank=True)
|
|
output_tokens = models.IntegerField(null=True, blank=True)
|
|
cost_usd = models.FloatField(null=True, blank=True)
|
|
elapsed_ms = models.IntegerField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
ordering = ["created_at", "id"]
|
|
|
|
def as_dto(self) -> dict:
|
|
return {
|
|
"sessionId": self.session_id,
|
|
"role": self.role,
|
|
"content": self.content,
|
|
"createdAt": self.created_at.isoformat(),
|
|
"inputTokens": self.input_tokens,
|
|
"outputTokens": self.output_tokens,
|
|
"costUsd": self.cost_usd,
|
|
"elapsedMs": self.elapsed_ms,
|
|
}
|