quick fix
This commit is contained in:
30
.env.example
30
.env.example
@@ -29,16 +29,16 @@ AI_TEMPERATURE=0.7
|
|||||||
# Bot Identity & Personality
|
# Bot Identity & Personality
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# The bot's name, used in the system prompt to tell the AI who it is
|
# The bot's name, used in the system prompt to tell the AI who it is
|
||||||
BOT_NAME=My Bot
|
BOT_NAME="My Bot"
|
||||||
|
|
||||||
# Personality traits that define how the bot responds (used in system prompt)
|
# Personality traits that define how the bot responds (used in system prompt)
|
||||||
BOT_PERSONALITY=helpful and friendly
|
BOT_PERSONALITY="helpful and friendly"
|
||||||
|
|
||||||
# Message shown when someone mentions the bot without saying anything
|
# Message shown when someone mentions the bot without saying anything
|
||||||
BOT_DESCRIPTION=I'm an AI assistant here to help you.
|
BOT_DESCRIPTION="I'm an AI assistant here to help you."
|
||||||
|
|
||||||
# Status message shown in Discord (displays as "Watching <BOT_STATUS>")
|
# Status message shown in Discord (displays as "Watching <BOT_STATUS>")
|
||||||
BOT_STATUS=for mentions
|
BOT_STATUS="for mentions"
|
||||||
|
|
||||||
# Optional: Override the entire system prompt (leave commented to use auto-generated)
|
# Optional: Override the entire system prompt (leave commented to use auto-generated)
|
||||||
# SYSTEM_PROMPT=You are a custom assistant...
|
# SYSTEM_PROMPT=You are a custom assistant...
|
||||||
@@ -104,7 +104,7 @@ FACT_EXTRACTION_RATE=0.3
|
|||||||
PROACTIVE_ENABLED=true
|
PROACTIVE_ENABLED=true
|
||||||
|
|
||||||
# Enable cross-user associations (privacy-sensitive - shows shared interests)
|
# Enable cross-user associations (privacy-sensitive - shows shared interests)
|
||||||
CROSS_USER_ENABLED=false
|
CROSS_USER_ENABLED=true
|
||||||
|
|
||||||
# Enable bot opinion formation (bot develops topic preferences)
|
# Enable bot opinion formation (bot develops topic preferences)
|
||||||
OPINION_FORMATION_ENABLED=true
|
OPINION_FORMATION_ENABLED=true
|
||||||
@@ -119,18 +119,18 @@ MOOD_DECAY_RATE=0.1
|
|||||||
# Command Toggles
|
# Command Toggles
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# Master switch for all commands (when false, bot handles via conversation)
|
# Master switch for all commands (when false, bot handles via conversation)
|
||||||
COMMANDS_ENABLED=true
|
COMMANDS_ENABLED=false
|
||||||
|
|
||||||
# Individual command toggles
|
# Individual command toggles
|
||||||
CMD_RELATIONSHIP_ENABLED=true
|
# CMD_RELATIONSHIP_ENABLED=true
|
||||||
CMD_MOOD_ENABLED=true
|
# CMD_MOOD_ENABLED=true
|
||||||
CMD_BOTSTATS_ENABLED=true
|
# CMD_BOTSTATS_ENABLED=true
|
||||||
CMD_OURHISTORY_ENABLED=true
|
# CMD_OURHISTORY_ENABLED=true
|
||||||
CMD_BIRTHDAY_ENABLED=true
|
# CMD_BIRTHDAY_ENABLED=true
|
||||||
CMD_REMEMBER_ENABLED=true
|
# CMD_REMEMBER_ENABLED=true
|
||||||
CMD_SETNAME_ENABLED=true
|
# CMD_SETNAME_ENABLED=true
|
||||||
CMD_WHATDOYOUKNOW_ENABLED=true
|
# CMD_WHATDOYOUKNOW_ENABLED=true
|
||||||
CMD_FORGETME_ENABLED=true
|
# CMD_FORGETME_ENABLED=true
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# Logging & Monitoring
|
# Logging & Monitoring
|
||||||
|
|||||||
@@ -1,32 +1,33 @@
|
|||||||
services:
|
services:
|
||||||
daemon-boyfriend:
|
daemon-boyfriend:
|
||||||
build: .
|
build: .
|
||||||
container_name: daemon-boyfriend
|
container_name: daemon-boyfriend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
- PYTHONUNBUFFERED=1
|
- PYTHONUNBUFFERED=1
|
||||||
- DATABASE_URL=postgresql+asyncpg://daemon:${POSTGRES_PASSWORD:-daemon}@postgres:5432/daemon_boyfriend
|
- DATABASE_URL=postgresql+asyncpg://daemon:${POSTGRES_PASSWORD:-daemon}@postgres:5432/daemon_boyfriend
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
container_name: daemon-boyfriend-postgres
|
container_name: daemon-boyfriend-postgres
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: daemon
|
POSTGRES_USER: daemon
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-daemon}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-daemon}
|
||||||
POSTGRES_DB: daemon_boyfriend
|
POSTGRES_DB: daemon_boyfriend
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
healthcheck:
|
- ./schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro
|
||||||
test: ["CMD-SHELL", "pg_isready -U daemon -d daemon_boyfriend"]
|
healthcheck:
|
||||||
interval: 10s
|
test: ["CMD-SHELL", "pg_isready -U daemon -d daemon_boyfriend"]
|
||||||
timeout: 5s
|
interval: 10s
|
||||||
retries: 5
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
"""SQLAlchemy base model and metadata configuration."""
|
"""SQLAlchemy base model and metadata configuration."""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from sqlalchemy import MetaData
|
from sqlalchemy import DateTime, MetaData
|
||||||
from sqlalchemy.ext.asyncio import AsyncAttrs
|
from sqlalchemy.ext.asyncio import AsyncAttrs
|
||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||||
|
|
||||||
|
|
||||||
|
def utc_now() -> datetime:
|
||||||
|
"""Return current UTC time as timezone-aware datetime."""
|
||||||
|
return datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
# Naming convention for constraints (helps with migrations)
|
# Naming convention for constraints (helps with migrations)
|
||||||
convention = {
|
convention = {
|
||||||
"ix": "ix_%(column_0_label)s",
|
"ix": "ix_%(column_0_label)s",
|
||||||
@@ -23,6 +29,8 @@ class Base(AsyncAttrs, DeclarativeBase):
|
|||||||
|
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
|
|
||||||
# Common timestamp columns
|
# Common timestamp columns - use timezone-aware datetimes
|
||||||
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
updated_at: Mapped[datetime] = mapped_column(default=datetime.utcnow, onupdate=datetime.utcnow)
|
updated_at: Mapped[datetime] = mapped_column(
|
||||||
|
DateTime(timezone=True), default=utc_now, onupdate=utc_now
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from sqlalchemy import ARRAY, BigInteger, Boolean, ForeignKey, Integer, String, Text
|
from sqlalchemy import ARRAY, BigInteger, Boolean, DateTime, ForeignKey, Integer, String, Text
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from .base import Base
|
from .base import Base, utc_now
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .user import User
|
from .user import User
|
||||||
@@ -21,8 +21,10 @@ class Conversation(Base):
|
|||||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
|
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
|
||||||
guild_id: Mapped[int | None] = mapped_column(BigInteger)
|
guild_id: Mapped[int | None] = mapped_column(BigInteger)
|
||||||
channel_id: Mapped[int | None] = mapped_column(BigInteger, index=True)
|
channel_id: Mapped[int | None] = mapped_column(BigInteger, index=True)
|
||||||
started_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
last_message_at: Mapped[datetime] = mapped_column(default=datetime.utcnow, index=True)
|
last_message_at: Mapped[datetime] = mapped_column(
|
||||||
|
DateTime(timezone=True), default=utc_now, index=True
|
||||||
|
)
|
||||||
message_count: Mapped[int] = mapped_column(Integer, default=0)
|
message_count: Mapped[int] = mapped_column(Integer, default=0)
|
||||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,20 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from sqlalchemy import ARRAY, BigInteger, Boolean, ForeignKey, String, Text, UniqueConstraint
|
from sqlalchemy import (
|
||||||
|
ARRAY,
|
||||||
|
BigInteger,
|
||||||
|
Boolean,
|
||||||
|
DateTime,
|
||||||
|
ForeignKey,
|
||||||
|
String,
|
||||||
|
Text,
|
||||||
|
UniqueConstraint,
|
||||||
|
)
|
||||||
from sqlalchemy.dialects.postgresql import JSONB
|
from sqlalchemy.dialects.postgresql import JSONB
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from .base import Base
|
from .base import Base, utc_now
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .user import User
|
from .user import User
|
||||||
@@ -21,7 +30,7 @@ class Guild(Base):
|
|||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
discord_id: Mapped[int] = mapped_column(BigInteger, unique=True, index=True)
|
discord_id: Mapped[int] = mapped_column(BigInteger, unique=True, index=True)
|
||||||
name: Mapped[str] = mapped_column(String(255))
|
name: Mapped[str] = mapped_column(String(255))
|
||||||
joined_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
joined_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||||
settings: Mapped[dict] = mapped_column(JSONB, default=dict)
|
settings: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||||||
|
|
||||||
@@ -41,7 +50,7 @@ class GuildMember(Base):
|
|||||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
|
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
|
||||||
guild_nickname: Mapped[str | None] = mapped_column(String(255))
|
guild_nickname: Mapped[str | None] = mapped_column(String(255))
|
||||||
roles: Mapped[list[str] | None] = mapped_column(ARRAY(Text), default=None)
|
roles: Mapped[list[str] | None] = mapped_column(ARRAY(Text), default=None)
|
||||||
joined_guild_at: Mapped[datetime | None] = mapped_column(default=None)
|
joined_guild_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
guild: Mapped["Guild"] = relationship(back_populates="members")
|
guild: Mapped["Guild"] = relationship(back_populates="members")
|
||||||
|
|||||||
@@ -3,11 +3,20 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from sqlalchemy import BigInteger, Boolean, Float, ForeignKey, String, Text, UniqueConstraint
|
from sqlalchemy import (
|
||||||
|
BigInteger,
|
||||||
|
Boolean,
|
||||||
|
DateTime,
|
||||||
|
Float,
|
||||||
|
ForeignKey,
|
||||||
|
String,
|
||||||
|
Text,
|
||||||
|
UniqueConstraint,
|
||||||
|
)
|
||||||
from sqlalchemy.dialects.postgresql import JSONB
|
from sqlalchemy.dialects.postgresql import JSONB
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from .base import Base
|
from .base import Base, utc_now
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .user import User, UserFact
|
from .user import User, UserFact
|
||||||
@@ -24,13 +33,13 @@ class BotState(Base):
|
|||||||
# Current mood state (valence-arousal model)
|
# Current mood state (valence-arousal model)
|
||||||
mood_valence: Mapped[float] = mapped_column(Float, default=0.0) # -1.0 (sad) to 1.0 (happy)
|
mood_valence: Mapped[float] = mapped_column(Float, default=0.0) # -1.0 (sad) to 1.0 (happy)
|
||||||
mood_arousal: Mapped[float] = mapped_column(Float, default=0.0) # -1.0 (calm) to 1.0 (excited)
|
mood_arousal: Mapped[float] = mapped_column(Float, default=0.0) # -1.0 (calm) to 1.0 (excited)
|
||||||
mood_updated_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
mood_updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
|
|
||||||
# Bot statistics
|
# Bot statistics
|
||||||
total_messages_sent: Mapped[int] = mapped_column(default=0)
|
total_messages_sent: Mapped[int] = mapped_column(default=0)
|
||||||
total_facts_learned: Mapped[int] = mapped_column(default=0)
|
total_facts_learned: Mapped[int] = mapped_column(default=0)
|
||||||
total_users_known: Mapped[int] = mapped_column(default=0)
|
total_users_known: Mapped[int] = mapped_column(default=0)
|
||||||
first_activated_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
first_activated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
|
|
||||||
# Bot preferences (evolved over time)
|
# Bot preferences (evolved over time)
|
||||||
preferences: Mapped[dict] = mapped_column(JSONB, default=dict)
|
preferences: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||||||
@@ -50,8 +59,8 @@ class BotOpinion(Base):
|
|||||||
discussion_count: Mapped[int] = mapped_column(default=0)
|
discussion_count: Mapped[int] = mapped_column(default=0)
|
||||||
|
|
||||||
reasoning: Mapped[str | None] = mapped_column(Text)
|
reasoning: Mapped[str | None] = mapped_column(Text)
|
||||||
formed_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
formed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
last_reinforced_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
last_reinforced_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
|
|
||||||
__table_args__ = (UniqueConstraint("guild_id", "topic"),)
|
__table_args__ = (UniqueConstraint("guild_id", "topic"),)
|
||||||
|
|
||||||
@@ -81,8 +90,8 @@ class UserRelationship(Base):
|
|||||||
# Inside jokes / shared references
|
# Inside jokes / shared references
|
||||||
shared_references: Mapped[dict] = mapped_column(JSONB, default=dict)
|
shared_references: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||||||
|
|
||||||
first_interaction_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
first_interaction_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
last_interaction_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
last_interaction_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
user: Mapped["User"] = relationship(back_populates="relationships")
|
user: Mapped["User"] = relationship(back_populates="relationships")
|
||||||
@@ -128,7 +137,7 @@ class ScheduledEvent(Base):
|
|||||||
channel_id: Mapped[int | None] = mapped_column(BigInteger)
|
channel_id: Mapped[int | None] = mapped_column(BigInteger)
|
||||||
|
|
||||||
event_type: Mapped[str] = mapped_column(String(50), index=True)
|
event_type: Mapped[str] = mapped_column(String(50), index=True)
|
||||||
trigger_at: Mapped[datetime] = mapped_column(index=True)
|
trigger_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
|
||||||
|
|
||||||
title: Mapped[str] = mapped_column(String(255))
|
title: Mapped[str] = mapped_column(String(255))
|
||||||
context: Mapped[dict] = mapped_column(JSONB, default=dict)
|
context: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||||||
@@ -137,7 +146,7 @@ class ScheduledEvent(Base):
|
|||||||
recurrence_rule: Mapped[str | None] = mapped_column(String(100))
|
recurrence_rule: Mapped[str | None] = mapped_column(String(100))
|
||||||
|
|
||||||
status: Mapped[str] = mapped_column(String(20), default="pending", index=True)
|
status: Mapped[str] = mapped_column(String(20), default="pending", index=True)
|
||||||
triggered_at: Mapped[datetime | None] = mapped_column(default=None)
|
triggered_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
user: Mapped["User"] = relationship(back_populates="scheduled_events")
|
user: Mapped["User"] = relationship(back_populates="scheduled_events")
|
||||||
@@ -159,7 +168,7 @@ class FactAssociation(Base):
|
|||||||
|
|
||||||
association_type: Mapped[str] = mapped_column(String(50))
|
association_type: Mapped[str] = mapped_column(String(50))
|
||||||
strength: Mapped[float] = mapped_column(Float, default=0.5)
|
strength: Mapped[float] = mapped_column(Float, default=0.5)
|
||||||
discovered_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
discovered_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
fact_1: Mapped["UserFact"] = relationship(foreign_keys=[fact_id_1])
|
fact_1: Mapped["UserFact"] = relationship(foreign_keys=[fact_id_1])
|
||||||
@@ -183,4 +192,6 @@ class MoodHistory(Base):
|
|||||||
trigger_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"))
|
trigger_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"))
|
||||||
trigger_description: Mapped[str | None] = mapped_column(Text)
|
trigger_description: Mapped[str | None] = mapped_column(Text)
|
||||||
|
|
||||||
recorded_at: Mapped[datetime] = mapped_column(default=datetime.utcnow, index=True)
|
recorded_at: Mapped[datetime] = mapped_column(
|
||||||
|
DateTime(timezone=True), default=utc_now, index=True
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,10 +3,19 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from sqlalchemy import BigInteger, Boolean, Float, ForeignKey, String, Text, UniqueConstraint
|
from sqlalchemy import (
|
||||||
|
BigInteger,
|
||||||
|
Boolean,
|
||||||
|
DateTime,
|
||||||
|
Float,
|
||||||
|
ForeignKey,
|
||||||
|
String,
|
||||||
|
Text,
|
||||||
|
UniqueConstraint,
|
||||||
|
)
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from .base import Base
|
from .base import Base, utc_now
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .conversation import Conversation, Message
|
from .conversation import Conversation, Message
|
||||||
@@ -24,8 +33,8 @@ class User(Base):
|
|||||||
discord_username: Mapped[str] = mapped_column(String(255))
|
discord_username: Mapped[str] = mapped_column(String(255))
|
||||||
discord_display_name: Mapped[str | None] = mapped_column(String(255))
|
discord_display_name: Mapped[str | None] = mapped_column(String(255))
|
||||||
custom_name: Mapped[str | None] = mapped_column(String(255))
|
custom_name: Mapped[str | None] = mapped_column(String(255))
|
||||||
first_seen_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
first_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
last_seen_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
@@ -88,8 +97,10 @@ class UserFact(Base):
|
|||||||
confidence: Mapped[float] = mapped_column(Float, default=1.0)
|
confidence: Mapped[float] = mapped_column(Float, default=1.0)
|
||||||
source: Mapped[str] = mapped_column(String(50), default="conversation")
|
source: Mapped[str] = mapped_column(String(50), default="conversation")
|
||||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
|
||||||
learned_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
learned_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
last_referenced_at: Mapped[datetime | None] = mapped_column(default=None)
|
last_referenced_at: Mapped[datetime | None] = mapped_column(
|
||||||
|
DateTime(timezone=True), default=None
|
||||||
|
)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
user: Mapped["User"] = relationship(back_populates="facts")
|
user: Mapped["User"] = relationship(back_populates="facts")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Association Service - discovers and manages cross-user fact associations."""
|
"""Association Service - discovers and manages cross-user fact associations."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from sqlalchemy import and_, select
|
from sqlalchemy import and_, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@@ -97,7 +97,7 @@ class AssociationService:
|
|||||||
fact_id_2=fact_2.id,
|
fact_id_2=fact_2.id,
|
||||||
association_type=association_type,
|
association_type=association_type,
|
||||||
strength=strength,
|
strength=strength,
|
||||||
discovered_at=datetime.utcnow(),
|
discovered_at=datetime.now(timezone.utc),
|
||||||
)
|
)
|
||||||
self._session.add(assoc)
|
self._session.add(assoc)
|
||||||
await self._session.flush()
|
await self._session.flush()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@@ -311,7 +311,7 @@ Return ONLY the JSON array, no other text."""
|
|||||||
confidence=fact_data.get("confidence", 0.8),
|
confidence=fact_data.get("confidence", 0.8),
|
||||||
source="auto_extraction",
|
source="auto_extraction",
|
||||||
is_active=True,
|
is_active=True,
|
||||||
learned_at=datetime.utcnow(),
|
learned_at=datetime.now(timezone.utc),
|
||||||
# New fields from Living AI
|
# New fields from Living AI
|
||||||
category=fact_data["type"],
|
category=fact_data["type"],
|
||||||
importance=fact_data.get("importance", 0.5),
|
importance=fact_data.get("importance", 0.5),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@@ -60,7 +60,9 @@ class MoodService:
|
|||||||
bot_state = await self.get_or_create_bot_state(guild_id)
|
bot_state = await self.get_or_create_bot_state(guild_id)
|
||||||
|
|
||||||
# Apply time decay toward neutral
|
# Apply time decay toward neutral
|
||||||
hours_since_update = (datetime.utcnow() - bot_state.mood_updated_at).total_seconds() / 3600
|
hours_since_update = (
|
||||||
|
datetime.now(timezone.utc) - bot_state.mood_updated_at
|
||||||
|
).total_seconds() / 3600
|
||||||
decay_factor = max(0, 1 - (settings.mood_decay_rate * hours_since_update))
|
decay_factor = max(0, 1 - (settings.mood_decay_rate * hours_since_update))
|
||||||
|
|
||||||
valence = bot_state.mood_valence * decay_factor
|
valence = bot_state.mood_valence * decay_factor
|
||||||
@@ -105,7 +107,7 @@ class MoodService:
|
|||||||
bot_state = await self.get_or_create_bot_state(guild_id)
|
bot_state = await self.get_or_create_bot_state(guild_id)
|
||||||
bot_state.mood_valence = new_valence
|
bot_state.mood_valence = new_valence
|
||||||
bot_state.mood_arousal = new_arousal
|
bot_state.mood_arousal = new_arousal
|
||||||
bot_state.mood_updated_at = datetime.utcnow()
|
bot_state.mood_updated_at = datetime.now(timezone.utc)
|
||||||
|
|
||||||
# Record history
|
# Record history
|
||||||
await self._record_mood_history(
|
await self._record_mood_history(
|
||||||
@@ -140,7 +142,7 @@ class MoodService:
|
|||||||
async def get_stats(self, guild_id: int | None = None) -> dict:
|
async def get_stats(self, guild_id: int | None = None) -> dict:
|
||||||
"""Get bot statistics."""
|
"""Get bot statistics."""
|
||||||
bot_state = await self.get_or_create_bot_state(guild_id)
|
bot_state = await self.get_or_create_bot_state(guild_id)
|
||||||
age_delta = datetime.utcnow() - bot_state.first_activated_at
|
age_delta = datetime.now(timezone.utc) - bot_state.first_activated_at
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"age_days": age_delta.days,
|
"age_days": age_delta.days,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Opinion Service - manages bot opinion formation on topics."""
|
"""Opinion Service - manages bot opinion formation on topics."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@@ -77,7 +77,7 @@ class OpinionService:
|
|||||||
)
|
)
|
||||||
opinion.interest_level = max(0.0, min(1.0, opinion.interest_level))
|
opinion.interest_level = max(0.0, min(1.0, opinion.interest_level))
|
||||||
|
|
||||||
opinion.last_reinforced_at = datetime.utcnow()
|
opinion.last_reinforced_at = datetime.now(timezone.utc)
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Updated opinion on '{topic}': sentiment={opinion.sentiment:.2f}, "
|
f"Updated opinion on '{topic}': sentiment={opinion.sentiment:.2f}, "
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Persistent conversation management using PostgreSQL."""
|
"""Persistent conversation management using PostgreSQL."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@@ -38,7 +38,7 @@ class PersistentConversationManager:
|
|||||||
Conversation model instance
|
Conversation model instance
|
||||||
"""
|
"""
|
||||||
# Look for recent active conversation in this channel
|
# Look for recent active conversation in this channel
|
||||||
cutoff = datetime.utcnow() - self._timeout
|
cutoff = datetime.now(timezone.utc) - self._timeout
|
||||||
|
|
||||||
stmt = select(Conversation).where(
|
stmt = select(Conversation).where(
|
||||||
Conversation.user_id == user.id,
|
Conversation.user_id == user.id,
|
||||||
@@ -133,7 +133,7 @@ class PersistentConversationManager:
|
|||||||
self._session.add(message)
|
self._session.add(message)
|
||||||
|
|
||||||
# Update conversation stats
|
# Update conversation stats
|
||||||
conversation.last_message_at = datetime.utcnow()
|
conversation.last_message_at = datetime.now(timezone.utc)
|
||||||
conversation.message_count += 1
|
conversation.message_count += 1
|
||||||
|
|
||||||
await self._session.flush()
|
await self._session.flush()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@@ -71,7 +71,7 @@ Examples:
|
|||||||
if result and result.get("has_event"):
|
if result and result.get("has_event"):
|
||||||
days_until = result.get("days_until", 1) or 1
|
days_until = result.get("days_until", 1) or 1
|
||||||
# Schedule follow-up for 1 day after the event
|
# Schedule follow-up for 1 day after the event
|
||||||
trigger_at = datetime.utcnow() + timedelta(days=days_until + 1)
|
trigger_at = datetime.now(timezone.utc) + timedelta(days=days_until + 1)
|
||||||
|
|
||||||
event = ScheduledEvent(
|
event = ScheduledEvent(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
@@ -159,7 +159,7 @@ Examples:
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Create the event
|
# Create the event
|
||||||
trigger_at = datetime.utcnow() + timedelta(days=days_until + 1)
|
trigger_at = datetime.now(timezone.utc) + timedelta(days=days_until + 1)
|
||||||
|
|
||||||
event = ScheduledEvent(
|
event = ScheduledEvent(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
@@ -300,7 +300,7 @@ Examples:
|
|||||||
|
|
||||||
def _next_birthday(self, birthday: datetime) -> datetime:
|
def _next_birthday(self, birthday: datetime) -> datetime:
|
||||||
"""Calculate the next occurrence of a birthday."""
|
"""Calculate the next occurrence of a birthday."""
|
||||||
today = datetime.utcnow().date()
|
today = datetime.now(timezone.utc).date()
|
||||||
this_year = birthday.replace(year=today.year)
|
this_year = birthday.replace(year=today.year)
|
||||||
|
|
||||||
if this_year.date() < today:
|
if this_year.date() < today:
|
||||||
@@ -322,7 +322,7 @@ Examples:
|
|||||||
|
|
||||||
async def get_pending_events(self, before: datetime | None = None) -> list[ScheduledEvent]:
|
async def get_pending_events(self, before: datetime | None = None) -> list[ScheduledEvent]:
|
||||||
"""Get events that should be triggered."""
|
"""Get events that should be triggered."""
|
||||||
cutoff = before or datetime.utcnow()
|
cutoff = before or datetime.now(timezone.utc)
|
||||||
stmt = (
|
stmt = (
|
||||||
select(ScheduledEvent)
|
select(ScheduledEvent)
|
||||||
.where(
|
.where(
|
||||||
@@ -390,7 +390,7 @@ Examples:
|
|||||||
async def mark_event_triggered(self, event: ScheduledEvent) -> None:
|
async def mark_event_triggered(self, event: ScheduledEvent) -> None:
|
||||||
"""Mark an event as triggered and handle recurrence."""
|
"""Mark an event as triggered and handle recurrence."""
|
||||||
event.status = "triggered"
|
event.status = "triggered"
|
||||||
event.triggered_at = datetime.utcnow()
|
event.triggered_at = datetime.now(timezone.utc)
|
||||||
|
|
||||||
# Handle recurring events
|
# Handle recurring events
|
||||||
if event.is_recurring and event.recurrence_rule:
|
if event.is_recurring and event.recurrence_rule:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Relationship Service - manages relationship tracking with users."""
|
"""Relationship Service - manages relationship tracking with users."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@@ -69,7 +69,7 @@ class RelationshipService:
|
|||||||
rel = await self.get_or_create_relationship(user, guild_id)
|
rel = await self.get_or_create_relationship(user, guild_id)
|
||||||
|
|
||||||
rel.total_interactions += 1
|
rel.total_interactions += 1
|
||||||
rel.last_interaction_at = datetime.utcnow()
|
rel.last_interaction_at = datetime.now(timezone.utc)
|
||||||
|
|
||||||
# Track sentiment
|
# Track sentiment
|
||||||
if sentiment > 0.2:
|
if sentiment > 0.2:
|
||||||
@@ -211,7 +211,7 @@ class RelationshipService:
|
|||||||
level = self.get_level(rel.relationship_score)
|
level = self.get_level(rel.relationship_score)
|
||||||
|
|
||||||
# Calculate time since first interaction
|
# Calculate time since first interaction
|
||||||
time_known = datetime.utcnow() - rel.first_interaction_at
|
time_known = datetime.now(timezone.utc) - rel.first_interaction_at
|
||||||
days_known = time_known.days
|
days_known = time_known.days
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Self Awareness Service - provides bot self-reflection and statistics."""
|
"""Self Awareness Service - provides bot self-reflection and statistics."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from sqlalchemy import func, select
|
from sqlalchemy import func, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@@ -36,7 +36,7 @@ class SelfAwarenessService:
|
|||||||
await self._session.flush()
|
await self._session.flush()
|
||||||
|
|
||||||
# Calculate age
|
# Calculate age
|
||||||
age_delta = datetime.utcnow() - bot_state.first_activated_at
|
age_delta = datetime.now(timezone.utc) - bot_state.first_activated_at
|
||||||
|
|
||||||
# Count users (from database)
|
# Count users (from database)
|
||||||
user_count = await self._count_users()
|
user_count = await self._count_users()
|
||||||
@@ -76,7 +76,7 @@ class SelfAwarenessService:
|
|||||||
facts_count = facts_result.scalar() or 0
|
facts_count = facts_result.scalar() or 0
|
||||||
|
|
||||||
if rel:
|
if rel:
|
||||||
days_known = (datetime.utcnow() - rel.first_interaction_at).days
|
days_known = (datetime.now(timezone.utc) - rel.first_interaction_at).days
|
||||||
return {
|
return {
|
||||||
"first_met": rel.first_interaction_at,
|
"first_met": rel.first_interaction_at,
|
||||||
"days_known": days_known,
|
"days_known": days_known,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""User management service."""
|
"""User management service."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@@ -40,7 +40,7 @@ class UserService:
|
|||||||
|
|
||||||
if user:
|
if user:
|
||||||
# Update last seen and current name
|
# Update last seen and current name
|
||||||
user.last_seen_at = datetime.utcnow()
|
user.last_seen_at = datetime.now(timezone.utc)
|
||||||
user.discord_username = username
|
user.discord_username = username
|
||||||
if display_name:
|
if display_name:
|
||||||
user.discord_display_name = display_name
|
user.discord_display_name = display_name
|
||||||
@@ -232,7 +232,7 @@ class UserService:
|
|||||||
for fact in facts[:20]: # Limit to 20 most recent facts
|
for fact in facts[:20]: # Limit to 20 most recent facts
|
||||||
lines.append(f"- [{fact.fact_type}] {fact.fact_content}")
|
lines.append(f"- [{fact.fact_type}] {fact.fact_content}")
|
||||||
# Mark as referenced
|
# Mark as referenced
|
||||||
fact.last_referenced_at = datetime.utcnow()
|
fact.last_referenced_at = datetime.now(timezone.utc)
|
||||||
|
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user