feat(backend): Django + OpenCode 백엔드 추가 — 5_django_backend/
앱이 기대하는 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>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
세션·메시지 미러 — 프론트 `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,
|
||||
}
|
||||
Reference in New Issue
Block a user