Skip to content

Commit 9d23ba9

Browse files
committed
Merge remote-tracking branch 'origin' into frontend
2 parents bc3f18b + 26b478b commit 9d23ba9

6 files changed

Lines changed: 110 additions & 5 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,4 @@ MindVault AI is designed with standard SaaS security practices in mind:
116116

117117
## Documentation
118118

119-
- [Architecture](docs/architecture.md)
119+
- [Architecture](backend/architecture.md)

backend/app/adapters/inbound/api/v1/routes_chat.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from fastapi import APIRouter, Depends
77
from fastapi.responses import StreamingResponse
88

9-
from app.application.dto.requests import ChatRequest
9+
from app.application.dto.requests import ChatRequest, CreateSessionRequest
10+
from app.application.dto.responses import SessionResponse
1011
from app.infrastructure.config import settings
1112
from app.infrastructure.di.container import Container
1213
from app.infrastructure.security.auth import get_current_claims
@@ -17,6 +18,26 @@
1718
router = APIRouter(prefix="/chats", tags=["chats"])
1819

1920

21+
@router.post("", status_code=201)
22+
async def create_session(
23+
payload: CreateSessionRequest,
24+
claims: dict = Depends(get_current_claims),
25+
chat_service=Depends(Container.get_chat_service),
26+
) -> SessionResponse:
27+
user_id = UUID(claims["sub"])
28+
org_id = UUID(claims["org_id"])
29+
session = await chat_service.create_session(
30+
org_id=org_id,
31+
user_id=user_id,
32+
title=payload.title,
33+
)
34+
return SessionResponse(
35+
id=str(session.id),
36+
title=session.title,
37+
created_at=session.created_at.isoformat() if session.created_at else None,
38+
)
39+
40+
2041
@router.post(
2142
"/{session_id}/ask",
2243
dependencies=[

backend/app/application/dto/requests.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ class MFAVerifyRequest(BaseModel):
7171
code: StrictStr = Field(..., min_length=6, max_length=8)
7272

7373

74+
class CreateSessionRequest(BaseModel):
75+
model_config = ConfigDict(extra="forbid", strict=True)
76+
title: StrictStr = Field(..., min_length=1, max_length=255)
77+
78+
7479
class ChatRequest(BaseModel):
7580
model_config = ConfigDict(extra="forbid", strict=True)
7681
message: StrictStr = Field(..., min_length=1, max_length=10000)
@@ -80,3 +85,8 @@ class SearchRequest(BaseModel):
8085
model_config = ConfigDict(extra="forbid", strict=True)
8186
query: StrictStr = Field(..., min_length=1, max_length=10000)
8287
top_k: StrictInt = Field(default=5, ge=1, le=50)
88+
89+
90+
class CreateSesssionRequest(BaseModel):
91+
model_config = ConfigDict(extra="forbid", strict=True)
92+
initial_message: StrictStr = Field(..., min_length=1, max_length=10000)

backend/app/application/dto/responses.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ class InviteMemberResponse(BaseModel):
4949
invite_url: str
5050

5151

52+
class SessionResponse(BaseModel):
53+
model_config = ConfigDict(extra="forbid", strict=True)
54+
id: str
55+
title: str
56+
created_at: str | None = None
57+
58+
5259
class UsageResponse(BaseModel):
5360
model_config = ConfigDict(extra="forbid", strict=True)
5461
org_id: str
@@ -93,3 +100,11 @@ class SearchResponse(BaseModel):
93100
items: list[SearchChunkResponse]
94101
citations: list[dict]
95102
total: int
103+
104+
105+
class CreateSessionresponse(BaseModel):
106+
model_config = ConfigDict(extra="forbid", strict=True)
107+
session_id: str
108+
title: str | None = None
109+
created_at: str
110+
last_message_at: str

backend/app/application/services/chat_service.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22

33
import json
44
import logging
5-
from datetime import datetime
5+
from datetime import datetime, timezone
66
from typing import AsyncGenerator
77
from uuid import UUID
88

9+
from app.adapters.outbound.db.repositories.chat_session_repository_implementation import ( # noqa: E501
10+
NotFound as SessionNotFound,
11+
)
912
from app.application.services.hybrid_search_service import HybridSearchService
1013
from app.domain.entities.chat_message import ChatMessage
14+
from app.domain.entities.chat_session import ChatSession
1115
from app.domain.ports.outbound.llm_port import LLMPort
1216
from app.domain.ports.outbound.reranker import Reranker
1317
from app.domain.services.citation_policy import (
@@ -44,6 +48,35 @@ def __init__(
4448
self._model = model
4549
self._usage_service = usage_service
4650

51+
async def create_session(
52+
self,
53+
*,
54+
org_id: UUID,
55+
user_id: UUID,
56+
title: str,
57+
) -> ChatSession:
58+
session = ChatSession.create(org_id=org_id, user_id=user_id, title=title)
59+
async with self._uow_factory() as uow:
60+
await uow.sessions.create_chat_session(session)
61+
await uow.commit()
62+
return session
63+
64+
async def _ensure_session(
65+
self, uow, *, session_id: UUID, org_id: UUID, user_id: UUID
66+
) -> None:
67+
try:
68+
await uow.sessions.get_chat_session(session_id)
69+
except SessionNotFound:
70+
session = ChatSession(
71+
id=session_id,
72+
org_id=org_id,
73+
user_id=user_id,
74+
title="New Chat",
75+
created_at=datetime.now(timezone.utc),
76+
updated_at=datetime.now(timezone.utc),
77+
)
78+
await uow.sessions.create_chat_session(session)
79+
4780
async def ask_question(
4881
self,
4982
*,
@@ -77,6 +110,9 @@ async def ask_question(
77110
citations_as_dicts = [c.__dict__ for c in citations]
78111

79112
async with self._uow_factory() as uow:
113+
await self._ensure_session(
114+
uow, session_id=session_id, org_id=org_id, user_id=user_id
115+
)
80116
history = await uow.messages.get_recent_by_session(session_id, limit=6)
81117

82118
user_msg = ChatMessage.create_user_message(
@@ -141,6 +177,10 @@ async def _persist_answer(
141177
token_count: int,
142178
) -> None:
143179
async with self._uow_factory() as uow:
180+
await self._ensure_session(
181+
uow, session_id=session_id, org_id=org_id, user_id=user_id
182+
)
183+
144184
ai_msg = ChatMessage.create_assistant_message(
145185
session_id=session_id,
146186
org_id=org_id,

backend/app/domain/entities/chat_session.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass, field
4-
from datetime import datetime
4+
from datetime import datetime, timezone
55
from typing import Any
6-
from uuid import UUID
6+
from uuid import UUID, uuid4
77

88

99
@dataclass(slots=True)
@@ -16,3 +16,22 @@ class ChatSession:
1616
last_message_at: datetime | None = None
1717
created_at: datetime | None = None
1818
updated_at: datetime | None = None
19+
20+
@staticmethod
21+
def create(
22+
*,
23+
org_id: UUID,
24+
user_id: UUID,
25+
title: str,
26+
metadata: dict[str, Any] | None = None,
27+
) -> ChatSession:
28+
now = datetime.now(timezone.utc)
29+
return ChatSession(
30+
id=uuid4(),
31+
org_id=org_id,
32+
user_id=user_id,
33+
title=title,
34+
metadata=metadata or {},
35+
created_at=now,
36+
updated_at=now,
37+
)

0 commit comments

Comments
 (0)