py-aidol 0.1.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aidol/api/aidol.py +67 -73
- aidol/api/common.py +71 -0
- aidol/api/companion.py +156 -39
- aidol/api/lead.py +123 -0
- aidol/factories.py +8 -0
- aidol/models/__init__.py +2 -1
- aidol/models/aidol.py +4 -2
- aidol/models/aidol_lead.py +22 -0
- aidol/models/companion.py +20 -2
- aidol/protocols.py +26 -0
- aidol/repositories/__init__.py +2 -0
- aidol/repositories/aidol.py +2 -0
- aidol/repositories/aidol_lead.py +49 -0
- aidol/repositories/companion.py +38 -5
- aidol/schemas/__init__.py +12 -0
- aidol/schemas/aidol.py +10 -6
- aidol/schemas/aidol_lead.py +38 -0
- aidol/schemas/companion.py +122 -5
- aidol/services/companion_service.py +96 -0
- aidol/services/image_generation_service.py +56 -101
- {py_aidol-0.1.0.dist-info → py_aidol-0.3.0.dist-info}/METADATA +2 -1
- py_aidol-0.3.0.dist-info/RECORD +27 -0
- py_aidol-0.1.0.dist-info/RECORD +0 -21
- {py_aidol-0.1.0.dist-info → py_aidol-0.3.0.dist-info}/WHEEL +0 -0
aidol/api/lead.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lead API router
|
|
3
|
+
|
|
4
|
+
Public endpoints for collecting leads (emails).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Annotated
|
|
8
|
+
|
|
9
|
+
from aioia_core.fastapi import BaseCrudRouter
|
|
10
|
+
from aioia_core.settings import JWTSettings
|
|
11
|
+
from fastapi import APIRouter, Depends, Header, status
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
from sqlalchemy.orm import Session, sessionmaker
|
|
14
|
+
|
|
15
|
+
from aidol.protocols import (
|
|
16
|
+
AIdolLeadRepositoryFactoryProtocol,
|
|
17
|
+
AIdolLeadRepositoryProtocol,
|
|
18
|
+
AIdolRepositoryFactoryProtocol,
|
|
19
|
+
NoUpdate,
|
|
20
|
+
)
|
|
21
|
+
from aidol.schemas import AIdolLead, AIdolLeadCreate, AIdolUpdate
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LeadResponse(BaseModel):
|
|
25
|
+
"""Response for lead creation."""
|
|
26
|
+
|
|
27
|
+
email: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LeadRouter(
|
|
31
|
+
BaseCrudRouter[AIdolLead, AIdolLeadCreate, NoUpdate, AIdolLeadRepositoryProtocol]
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
Lead router.
|
|
35
|
+
|
|
36
|
+
Handles email collection.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
aidol_repository_factory: AIdolRepositoryFactoryProtocol,
|
|
42
|
+
**kwargs,
|
|
43
|
+
):
|
|
44
|
+
super().__init__(**kwargs)
|
|
45
|
+
self.aidol_repository_factory = aidol_repository_factory
|
|
46
|
+
|
|
47
|
+
def _register_routes(self) -> None:
|
|
48
|
+
"""Register routes."""
|
|
49
|
+
self._register_create_lead_route()
|
|
50
|
+
|
|
51
|
+
def _register_create_lead_route(self) -> None:
|
|
52
|
+
"""POST /leads - Collect email"""
|
|
53
|
+
|
|
54
|
+
@self.router.post(
|
|
55
|
+
f"/{self.resource_name}",
|
|
56
|
+
response_model=LeadResponse,
|
|
57
|
+
status_code=status.HTTP_201_CREATED,
|
|
58
|
+
summary="Collect Lead",
|
|
59
|
+
description="Collect email. Associates with AIdol if ClaimToken is valid.",
|
|
60
|
+
)
|
|
61
|
+
async def create_lead(
|
|
62
|
+
request: AIdolLeadCreate,
|
|
63
|
+
claim_token: Annotated[str | None, Header(alias="ClaimToken")] = None,
|
|
64
|
+
db_session: Session = Depends(self.get_db_dep),
|
|
65
|
+
lead_repository: AIdolLeadRepositoryProtocol = Depends(
|
|
66
|
+
self.get_repository_dep
|
|
67
|
+
),
|
|
68
|
+
):
|
|
69
|
+
"""Collect email."""
|
|
70
|
+
email_saved = False
|
|
71
|
+
|
|
72
|
+
# 1. Try to associate with AIdol if token is present
|
|
73
|
+
if claim_token:
|
|
74
|
+
# Reuse session from dependency
|
|
75
|
+
aidol_repo = self.aidol_repository_factory.create_repository(db_session)
|
|
76
|
+
|
|
77
|
+
# Find AIdol by claim_token
|
|
78
|
+
# Assuming get_all supports filters
|
|
79
|
+
items, _ = aidol_repo.get_all(
|
|
80
|
+
filters=[
|
|
81
|
+
{
|
|
82
|
+
"field": "claim_token",
|
|
83
|
+
"operator": "eq",
|
|
84
|
+
"value": claim_token,
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if items:
|
|
90
|
+
aidol = items[0]
|
|
91
|
+
# Update AIdol email
|
|
92
|
+
aidol_repo.update(aidol.id, AIdolUpdate(email=request.email))
|
|
93
|
+
email_saved = True
|
|
94
|
+
|
|
95
|
+
# 2. If not saved as AIdol email, create Lead
|
|
96
|
+
if not email_saved:
|
|
97
|
+
lead_repository.create(request)
|
|
98
|
+
|
|
99
|
+
return LeadResponse(email=request.email)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def create_lead_router(
|
|
103
|
+
db_session_factory: sessionmaker,
|
|
104
|
+
aidol_repository_factory: AIdolRepositoryFactoryProtocol,
|
|
105
|
+
lead_repository_factory: AIdolLeadRepositoryFactoryProtocol,
|
|
106
|
+
jwt_settings: JWTSettings | None = None,
|
|
107
|
+
resource_name: str = "leads",
|
|
108
|
+
tags: list[str] | None = None,
|
|
109
|
+
) -> APIRouter:
|
|
110
|
+
"""Create Lead router."""
|
|
111
|
+
router = LeadRouter(
|
|
112
|
+
model_class=AIdolLead,
|
|
113
|
+
create_schema=AIdolLeadCreate,
|
|
114
|
+
update_schema=NoUpdate, # Update not supported
|
|
115
|
+
db_session_factory=db_session_factory,
|
|
116
|
+
repository_factory=lead_repository_factory,
|
|
117
|
+
aidol_repository_factory=aidol_repository_factory,
|
|
118
|
+
user_info_provider=None, # No auth required for lead collection
|
|
119
|
+
jwt_secret_key=jwt_settings.secret_key if jwt_settings else None,
|
|
120
|
+
resource_name=resource_name,
|
|
121
|
+
tags=tags or ["Lead"],
|
|
122
|
+
)
|
|
123
|
+
return router.get_router()
|
aidol/factories.py
CHANGED
|
@@ -7,6 +7,7 @@ Uses BaseRepositoryFactory for BaseCrudRouter compatibility.
|
|
|
7
7
|
from aioia_core.factories import BaseRepositoryFactory
|
|
8
8
|
|
|
9
9
|
from aidol.repositories.aidol import AIdolRepository
|
|
10
|
+
from aidol.repositories.aidol_lead import AIdolLeadRepository
|
|
10
11
|
from aidol.repositories.companion import CompanionRepository
|
|
11
12
|
|
|
12
13
|
|
|
@@ -22,3 +23,10 @@ class CompanionRepositoryFactory(BaseRepositoryFactory[CompanionRepository]):
|
|
|
22
23
|
|
|
23
24
|
def __init__(self):
|
|
24
25
|
super().__init__(repository_class=CompanionRepository)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AIdolLeadRepositoryFactory(BaseRepositoryFactory[AIdolLeadRepository]):
|
|
29
|
+
"""Factory for creating AIdolLead repositories."""
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
super().__init__(repository_class=AIdolLeadRepository)
|
aidol/models/__init__.py
CHANGED
aidol/models/aidol.py
CHANGED
|
@@ -18,7 +18,9 @@ class DBAIdol(BaseModel):
|
|
|
18
18
|
__tablename__ = "aidols"
|
|
19
19
|
|
|
20
20
|
# id, created_at, updated_at inherited from BaseModel
|
|
21
|
-
name: Mapped[str] = mapped_column(String, nullable=
|
|
21
|
+
name: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
22
22
|
concept: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
23
|
-
profile_image_url: Mapped[str] = mapped_column(String, nullable=
|
|
23
|
+
profile_image_url: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
24
24
|
claim_token: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
|
25
|
+
email: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
26
|
+
greeting: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIdol Leads database model
|
|
3
|
+
|
|
4
|
+
Uses aioia_core.models.BaseModel which provides:
|
|
5
|
+
- id: Mapped[str] (primary key, UUID default)
|
|
6
|
+
- created_at: Mapped[datetime]
|
|
7
|
+
- updated_at: Mapped[datetime]
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from aioia_core.models import BaseModel
|
|
11
|
+
from sqlalchemy import String
|
|
12
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DBAIdolLead(BaseModel):
|
|
16
|
+
"""AIdol Lead (viewer email) database model"""
|
|
17
|
+
|
|
18
|
+
__tablename__ = "aidol_leads"
|
|
19
|
+
|
|
20
|
+
# id, created_at, updated_at inherited from BaseModel
|
|
21
|
+
aidol_id: Mapped[str] = mapped_column(String, nullable=False)
|
|
22
|
+
email: Mapped[str] = mapped_column(String, nullable=False)
|
aidol/models/companion.py
CHANGED
|
@@ -8,7 +8,7 @@ Uses aioia_core.models.BaseModel which provides:
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from aioia_core.models import BaseModel
|
|
11
|
-
from sqlalchemy import ForeignKey, Index, String, Text
|
|
11
|
+
from sqlalchemy import ForeignKey, Index, Integer, String, Text
|
|
12
12
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
13
13
|
|
|
14
14
|
|
|
@@ -19,9 +19,27 @@ class DBCompanion(BaseModel):
|
|
|
19
19
|
|
|
20
20
|
# id, created_at, updated_at inherited from BaseModel
|
|
21
21
|
aidol_id: Mapped[str | None] = mapped_column(ForeignKey("aidols.id"), nullable=True)
|
|
22
|
-
name: Mapped[str] = mapped_column(String, nullable=
|
|
22
|
+
name: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
23
|
+
gender: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
24
|
+
grade: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
23
25
|
biography: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
24
26
|
profile_picture_url: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
25
27
|
system_prompt: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
26
28
|
|
|
29
|
+
# MBTI scores (1-10 scale)
|
|
30
|
+
mbti_energy: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
31
|
+
mbti_perception: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
32
|
+
mbti_judgment: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
33
|
+
mbti_lifestyle: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
34
|
+
|
|
35
|
+
# Stats (0-100 scale)
|
|
36
|
+
vocal: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
37
|
+
dance: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
38
|
+
rap: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
39
|
+
visual: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
40
|
+
stamina: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
41
|
+
charm: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
42
|
+
|
|
43
|
+
position: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
44
|
+
|
|
27
45
|
__table_args__ = (Index("ix_companions_aidol_id", "aidol_id"),)
|
aidol/protocols.py
CHANGED
|
@@ -11,11 +11,14 @@ from typing import Protocol
|
|
|
11
11
|
|
|
12
12
|
import PIL.Image
|
|
13
13
|
from aioia_core import CrudRepositoryProtocol
|
|
14
|
+
from pydantic import BaseModel
|
|
14
15
|
from sqlalchemy.orm import Session
|
|
15
16
|
|
|
16
17
|
from aidol.schemas import (
|
|
17
18
|
AIdol,
|
|
18
19
|
AIdolCreate,
|
|
20
|
+
AIdolLead,
|
|
21
|
+
AIdolLeadCreate,
|
|
19
22
|
AIdolUpdate,
|
|
20
23
|
Companion,
|
|
21
24
|
CompanionCreate,
|
|
@@ -23,6 +26,10 @@ from aidol.schemas import (
|
|
|
23
26
|
)
|
|
24
27
|
|
|
25
28
|
|
|
29
|
+
class NoUpdate(BaseModel):
|
|
30
|
+
"""Placeholder for repositories without update support."""
|
|
31
|
+
|
|
32
|
+
|
|
26
33
|
class AIdolRepositoryProtocol(
|
|
27
34
|
CrudRepositoryProtocol[AIdol, AIdolCreate, AIdolUpdate], Protocol
|
|
28
35
|
):
|
|
@@ -98,3 +105,22 @@ class ImageStorageProtocol(Protocol):
|
|
|
98
105
|
image: PIL Image object to upload.
|
|
99
106
|
"""
|
|
100
107
|
...
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class AIdolLeadRepositoryProtocol(
|
|
111
|
+
CrudRepositoryProtocol[AIdolLead, AIdolLeadCreate, NoUpdate], Protocol
|
|
112
|
+
):
|
|
113
|
+
"""Protocol defining AIdolLead repository expectations.
|
|
114
|
+
|
|
115
|
+
Inherits CRUD operations from CrudRepositoryProtocol.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class AIdolLeadRepositoryFactoryProtocol(Protocol):
|
|
120
|
+
"""Protocol for factory that creates AIdolLeadRepositoryProtocol instances."""
|
|
121
|
+
|
|
122
|
+
def create_repository(
|
|
123
|
+
self, db_session: Session | None = None
|
|
124
|
+
) -> AIdolLeadRepositoryProtocol:
|
|
125
|
+
"""Create a repository instance."""
|
|
126
|
+
...
|
aidol/repositories/__init__.py
CHANGED
|
@@ -3,9 +3,11 @@ AIdol repositories
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from aidol.repositories.aidol import AIdolRepository
|
|
6
|
+
from aidol.repositories.aidol_lead import AIdolLeadRepository
|
|
6
7
|
from aidol.repositories.companion import CompanionRepository
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
9
10
|
"AIdolRepository",
|
|
11
|
+
"AIdolLeadRepository",
|
|
10
12
|
"CompanionRepository",
|
|
11
13
|
]
|
aidol/repositories/aidol.py
CHANGED
|
@@ -25,6 +25,8 @@ def _convert_db_aidol_to_model(db_aidol: DBAIdol) -> AIdol:
|
|
|
25
25
|
return AIdol(
|
|
26
26
|
id=db_aidol.id,
|
|
27
27
|
name=db_aidol.name,
|
|
28
|
+
email=db_aidol.email,
|
|
29
|
+
greeting=db_aidol.greeting,
|
|
28
30
|
concept=db_aidol.concept,
|
|
29
31
|
profile_image_url=db_aidol.profile_image_url,
|
|
30
32
|
claim_token=db_aidol.claim_token,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIdol Lead repository
|
|
3
|
+
|
|
4
|
+
Implements BaseRepository pattern for BaseCrudRouter compatibility.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import timezone
|
|
8
|
+
|
|
9
|
+
from aioia_core.repositories import BaseRepository
|
|
10
|
+
from sqlalchemy.orm import Session
|
|
11
|
+
|
|
12
|
+
from aidol.models import DBAIdolLead
|
|
13
|
+
from aidol.protocols import AIdolLeadRepositoryProtocol, NoUpdate
|
|
14
|
+
from aidol.schemas import AIdolLead, AIdolLeadCreate
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _convert_db_aidol_lead_to_model(db_lead: DBAIdolLead) -> AIdolLead:
|
|
18
|
+
"""Convert DB AIdolLead to Pydantic model."""
|
|
19
|
+
return AIdolLead(
|
|
20
|
+
id=db_lead.id,
|
|
21
|
+
aidol_id=db_lead.aidol_id,
|
|
22
|
+
email=db_lead.email,
|
|
23
|
+
created_at=db_lead.created_at.replace(tzinfo=timezone.utc),
|
|
24
|
+
updated_at=db_lead.updated_at.replace(tzinfo=timezone.utc),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _convert_aidol_lead_create_to_db(schema: AIdolLeadCreate) -> dict:
|
|
29
|
+
"""Convert AIdolLeadCreate schema to DB model data dict."""
|
|
30
|
+
return schema.model_dump(exclude_unset=True)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AIdolLeadRepository(
|
|
34
|
+
BaseRepository[AIdolLead, DBAIdolLead, AIdolLeadCreate, NoUpdate],
|
|
35
|
+
AIdolLeadRepositoryProtocol,
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Database-backed AIdolLead repository.
|
|
39
|
+
|
|
40
|
+
Extends BaseRepository for CRUD operations compatible with BaseCrudRouter.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, db_session: Session):
|
|
44
|
+
super().__init__(
|
|
45
|
+
db_session=db_session,
|
|
46
|
+
db_model=DBAIdolLead,
|
|
47
|
+
convert_to_model=_convert_db_aidol_lead_to_model,
|
|
48
|
+
convert_to_db_model=_convert_aidol_lead_create_to_db,
|
|
49
|
+
)
|
aidol/repositories/companion.py
CHANGED
|
@@ -10,7 +10,15 @@ from aioia_core.repositories import BaseRepository
|
|
|
10
10
|
from sqlalchemy.orm import Session
|
|
11
11
|
|
|
12
12
|
from aidol.models import DBCompanion
|
|
13
|
-
from aidol.schemas import
|
|
13
|
+
from aidol.schemas import (
|
|
14
|
+
Companion,
|
|
15
|
+
CompanionCreate,
|
|
16
|
+
CompanionStats,
|
|
17
|
+
CompanionUpdate,
|
|
18
|
+
Gender,
|
|
19
|
+
Grade,
|
|
20
|
+
Position,
|
|
21
|
+
)
|
|
14
22
|
|
|
15
23
|
|
|
16
24
|
def _convert_db_companion_to_model(db_companion: DBCompanion) -> Companion:
|
|
@@ -23,20 +31,45 @@ def _convert_db_companion_to_model(db_companion: DBCompanion) -> Companion:
|
|
|
23
31
|
id=db_companion.id,
|
|
24
32
|
aidol_id=db_companion.aidol_id,
|
|
25
33
|
name=db_companion.name,
|
|
34
|
+
gender=Gender(db_companion.gender) if db_companion.gender else None,
|
|
35
|
+
grade=Grade(db_companion.grade) if db_companion.grade else None,
|
|
26
36
|
biography=db_companion.biography,
|
|
27
37
|
profile_picture_url=db_companion.profile_picture_url,
|
|
38
|
+
position=Position(db_companion.position) if db_companion.position else None,
|
|
28
39
|
system_prompt=db_companion.system_prompt,
|
|
40
|
+
mbti_energy=db_companion.mbti_energy,
|
|
41
|
+
mbti_perception=db_companion.mbti_perception,
|
|
42
|
+
mbti_judgment=db_companion.mbti_judgment,
|
|
43
|
+
mbti_lifestyle=db_companion.mbti_lifestyle,
|
|
44
|
+
stats=CompanionStats(
|
|
45
|
+
vocal=db_companion.vocal or 0,
|
|
46
|
+
dance=db_companion.dance or 0,
|
|
47
|
+
rap=db_companion.rap or 0,
|
|
48
|
+
visual=db_companion.visual or 0,
|
|
49
|
+
stamina=db_companion.stamina or 0,
|
|
50
|
+
charm=db_companion.charm or 0,
|
|
51
|
+
),
|
|
29
52
|
created_at=db_companion.created_at.replace(tzinfo=timezone.utc),
|
|
30
53
|
updated_at=db_companion.updated_at.replace(tzinfo=timezone.utc),
|
|
31
54
|
)
|
|
32
55
|
|
|
33
56
|
|
|
34
|
-
def
|
|
35
|
-
|
|
57
|
+
def _convert_companion_schema_to_db(
|
|
58
|
+
schema: CompanionCreate | CompanionUpdate,
|
|
59
|
+
) -> dict:
|
|
60
|
+
"""Convert CompanionCreate/Update schema to DB model data dict.
|
|
36
61
|
|
|
62
|
+
Decomposes nested stats object into individual DB columns.
|
|
37
63
|
Includes system_prompt for AI configuration.
|
|
38
64
|
"""
|
|
39
|
-
|
|
65
|
+
data = schema.model_dump(exclude_unset=True, exclude={"stats"})
|
|
66
|
+
|
|
67
|
+
# Decompose stats into individual columns
|
|
68
|
+
if schema.stats is not None:
|
|
69
|
+
stats_dict = schema.stats.model_dump()
|
|
70
|
+
data.update(stats_dict)
|
|
71
|
+
|
|
72
|
+
return data
|
|
40
73
|
|
|
41
74
|
|
|
42
75
|
class CompanionRepository(
|
|
@@ -53,5 +86,5 @@ class CompanionRepository(
|
|
|
53
86
|
db_session=db_session,
|
|
54
87
|
db_model=DBCompanion,
|
|
55
88
|
convert_to_model=_convert_db_companion_to_model,
|
|
56
|
-
convert_to_db_model=
|
|
89
|
+
convert_to_db_model=_convert_companion_schema_to_db,
|
|
57
90
|
)
|
aidol/schemas/__init__.py
CHANGED
|
@@ -12,12 +12,17 @@ from aidol.schemas.aidol import (
|
|
|
12
12
|
ImageGenerationRequest,
|
|
13
13
|
ImageGenerationResponse,
|
|
14
14
|
)
|
|
15
|
+
from aidol.schemas.aidol_lead import AIdolLead, AIdolLeadBase, AIdolLeadCreate
|
|
15
16
|
from aidol.schemas.companion import (
|
|
16
17
|
Companion,
|
|
17
18
|
CompanionBase,
|
|
18
19
|
CompanionCreate,
|
|
19
20
|
CompanionPublic,
|
|
21
|
+
CompanionStats,
|
|
20
22
|
CompanionUpdate,
|
|
23
|
+
Gender,
|
|
24
|
+
Grade,
|
|
25
|
+
Position,
|
|
21
26
|
)
|
|
22
27
|
|
|
23
28
|
__all__ = [
|
|
@@ -29,9 +34,16 @@ __all__ = [
|
|
|
29
34
|
"ImageGenerationData",
|
|
30
35
|
"ImageGenerationRequest",
|
|
31
36
|
"ImageGenerationResponse",
|
|
37
|
+
"AIdolLead",
|
|
38
|
+
"AIdolLeadBase",
|
|
39
|
+
"AIdolLeadCreate",
|
|
32
40
|
"Companion",
|
|
33
41
|
"CompanionBase",
|
|
34
42
|
"CompanionCreate",
|
|
35
43
|
"CompanionPublic",
|
|
44
|
+
"CompanionStats",
|
|
36
45
|
"CompanionUpdate",
|
|
46
|
+
"Gender",
|
|
47
|
+
"Grade",
|
|
48
|
+
"Position",
|
|
37
49
|
]
|
aidol/schemas/aidol.py
CHANGED
|
@@ -24,20 +24,22 @@ class AIdolBase(BaseModel):
|
|
|
24
24
|
|
|
25
25
|
model_config = ConfigDict(populate_by_name=True, alias_generator=camelize)
|
|
26
26
|
|
|
27
|
-
name: str = Field(
|
|
27
|
+
name: str | None = Field(default=None, description="AIdol group name")
|
|
28
|
+
email: str | None = Field(default=None, description="Creator email")
|
|
29
|
+
greeting: str | None = Field(default=None, description="Greeting message")
|
|
28
30
|
concept: str | None = Field(default=None, description="Group concept or theme")
|
|
29
|
-
profile_image_url: str = Field(
|
|
31
|
+
profile_image_url: str | None = Field(default=None, description="Profile image URL")
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
class AIdolCreate(AIdolBase):
|
|
33
35
|
"""Schema for creating an AIdol group (no id).
|
|
34
36
|
|
|
35
|
-
claim_token is
|
|
37
|
+
claim_token is required for ownership verification.
|
|
36
38
|
"""
|
|
37
39
|
|
|
38
|
-
claim_token: str
|
|
39
|
-
|
|
40
|
-
description="
|
|
40
|
+
claim_token: str = Field(
|
|
41
|
+
...,
|
|
42
|
+
description="Client-generated UUID for ownership verification",
|
|
41
43
|
)
|
|
42
44
|
|
|
43
45
|
|
|
@@ -47,6 +49,8 @@ class AIdolUpdate(BaseModel):
|
|
|
47
49
|
model_config = ConfigDict(populate_by_name=True, alias_generator=camelize)
|
|
48
50
|
|
|
49
51
|
name: str | None = Field(default=None, description="AIdol group name")
|
|
52
|
+
email: str | None = Field(default=None, description="Creator email")
|
|
53
|
+
greeting: str | None = Field(default=None, description="Greeting message")
|
|
50
54
|
concept: str | None = Field(default=None, description="Group concept or theme")
|
|
51
55
|
profile_image_url: str | None = Field(default=None, description="Profile image URL")
|
|
52
56
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIdol Lead (viewer email) schemas
|
|
3
|
+
|
|
4
|
+
Schema hierarchy:
|
|
5
|
+
- AIdolLeadBase: Common fields
|
|
6
|
+
- AIdolLeadCreate: For creating a lead (no id)
|
|
7
|
+
- AIdolLead: Response with all fields
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
from humps import camelize
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AIdolLeadBase(BaseModel):
|
|
17
|
+
"""Base AIdol Lead model with common fields."""
|
|
18
|
+
|
|
19
|
+
model_config = ConfigDict(populate_by_name=True, alias_generator=camelize)
|
|
20
|
+
|
|
21
|
+
aidol_id: str = Field(..., description="AIdol group ID")
|
|
22
|
+
email: str = Field(..., description="Viewer email")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AIdolLeadCreate(AIdolLeadBase):
|
|
26
|
+
"""Schema for creating an AIdol lead (no id)."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AIdolLead(AIdolLeadBase):
|
|
30
|
+
"""AIdol Lead response schema with id and timestamps."""
|
|
31
|
+
|
|
32
|
+
model_config = ConfigDict(
|
|
33
|
+
populate_by_name=True, from_attributes=True, alias_generator=camelize
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
id: str = Field(..., description="Lead ID")
|
|
37
|
+
created_at: datetime = Field(..., description="Creation timestamp")
|
|
38
|
+
updated_at: datetime = Field(..., description="Last update timestamp")
|