Enhance smoking tracking features with AI next smoke time suggestions

- Added new API endpoint `GET /api/v1/smoke/next_smoke_time` to provide AI-generated suggestions for the next smoking time based on user data.
- Introduced a new database table `fa_smoke_ai_next_smoke` to store structured AI time node suggestions.
- Updated smoke handler and service to integrate the new AI next smoke time functionality.
- Enhanced documentation to reflect the new API endpoint and its usage, including details on how to generate AI time nodes.
This commit is contained in:
nepiedg
2026-01-20 07:08:52 +00:00
parent dc54c4e934
commit 6cf7eb2294
15 changed files with 1394 additions and 27 deletions
@@ -30,6 +30,11 @@ const (
defaultTemperature = 0.7
)
const (
SmokeAIAdviceTypeDaily = "daily_advice"
SmokeAIAdviceTypeNextSmoke = "next_smoke_time"
)
type SmokeAIAdviceService struct {
db *gorm.DB
cfg config.AIConfig
@@ -82,7 +87,7 @@ func (s *SmokeAIAdviceService) GetOrGenerate(ctx context.Context, user *usermode
promptVersion = DefaultAdvicePromptVersion
}
cached, err := s.getCached(ctx, int(user.ID), adviceDate, promptVersion)
cached, err := s.getCached(ctx, int(user.ID), SmokeAIAdviceTypeDaily, adviceDate, promptVersion)
if err != nil {
return nil, err
}
@@ -114,6 +119,7 @@ func (s *SmokeAIAdviceService) GetOrGenerate(ctx context.Context, user *usermode
record := smokemodel.SmokeAIAdvice{
UID: int(user.ID),
Type: SmokeAIAdviceTypeDaily,
AdviceDate: dateOnly(adviceDate),
PromptVersion: promptVersion,
Provider: "openai-compatible",
@@ -166,11 +172,11 @@ func (s *SmokeAIAdviceService) Unlock(ctx context.Context, user *usermodel.User,
return nil
}
func (s *SmokeAIAdviceService) getCached(ctx context.Context, uid int, adviceDate time.Time, promptVersion string) (*smokemodel.SmokeAIAdvice, error) {
func (s *SmokeAIAdviceService) getCached(ctx context.Context, uid int, adviceType string, adviceDate time.Time, promptVersion string) (*smokemodel.SmokeAIAdvice, error) {
var record smokemodel.SmokeAIAdvice
err := s.db.WithContext(ctx).
Where("uid = ? AND advice_date = ? AND prompt_version = ? AND (deletetime IS NULL OR deletetime = 0)",
uid, dateOnly(adviceDate).Format("2006-01-02"), promptVersion).
Where("uid = ? AND type = ? AND advice_date = ? AND prompt_version = ? AND (deletetime IS NULL OR deletetime = 0)",
uid, adviceType, dateOnly(adviceDate).Format("2006-01-02"), promptVersion).
First(&record).Error
if err == nil {
return &record, nil
@@ -231,7 +237,7 @@ func (s *SmokeAIAdviceService) buildSnapshot(ctx context.Context, uid int, advic
return adviceSnapshot{}, nil, ErrNoSmokeLogs
}
profile := s.loadAdviceProfile(ctx, uid)
profile := loadAdviceUserProfile(ctx, s.db, uid)
type timedLog struct {
log smokemodel.SmokeLog
@@ -299,9 +305,9 @@ func (s *SmokeAIAdviceService) buildSnapshot(ctx context.Context, uid int, advic
return snap, b, nil
}
func (s *SmokeAIAdviceService) loadAdviceProfile(ctx context.Context, uid int) *adviceUserProfile {
func loadAdviceUserProfile(ctx context.Context, db *gorm.DB, uid int) *adviceUserProfile {
var profile smokemodel.SmokeUserProfile
err := s.db.WithContext(ctx).
err := db.WithContext(ctx).
Where("uid = ? AND deleted_at IS NULL", uid).
First(&profile).Error
if err != nil {