Files
2026-04-26 22:05:21 +08:00

326 lines
12 KiB
PHP

<?php
declare(strict_types=1);
namespace app\smt\controller\v1;
use app\smt\common\Response;
use app\smt\controller\BaseController;
use app\smt\service\AchievementService;
use app\smt\service\QuitPlanService;
use app\smt\service\SmokeAiService;
use app\smt\service\SmokeService;
use app\smt\service\Support;
use think\App;
class Smoke extends BaseController
{
protected $smokeService;
protected $smokeAiService;
protected $quitPlanService;
protected $achievementService;
public function __construct(App $app)
{
parent::__construct($app);
$this->smokeService = new SmokeService();
$this->smokeAiService = new SmokeAiService();
$this->quitPlanService = new QuitPlanService();
$this->achievementService = new AchievementService();
}
public function home()
{
try {
$user = $this->getCurrentSmtUser();
$uid = (int) $user['id'];
$now = Support::now();
$planDate = Support::dateOnly($now);
$profileView = $this->smokeService->getProfileView($uid);
$defaultSuggestion = $this->smokeService->getDefaultNextSuggestion($uid, $now, $planDate, $profileView);
$homeSummary = $this->smokeService->getHomeSummary($uid, $now);
$motivation = $this->smokeService->motivation($uid, $profileView['profile'], $now);
$timer = [
'seconds_since_last' => (int) ($homeSummary['seconds_since_last'] ?? -1),
'next_suggested_at' => (string) ($defaultSuggestion['next_smoke_at'] ?? ''),
'next_suggested_clock' => Support::formatClock((string) ($defaultSuggestion['next_smoke_at'] ?? '')),
'suggestion_source' => 'default',
];
$cachedAiNext = $this->smokeAiService->getCachedNextSmoke($user, $planDate, 'v1');
if ($cachedAiNext) {
$timer['suggestion_source'] = 'ai';
$timer['next_suggested_at'] = (string) ($cachedAiNext['suggested_at'] ?? '');
$timer['next_suggested_clock'] = Support::formatClock((string) ($cachedAiNext['suggested_at'] ?? ''));
$timer['ai_time_nodes'] = $cachedAiNext['time_nodes'] ?? [];
$timer['ai_advice'] = (string) ($cachedAiNext['advice'] ?? '');
}
$dailySummaryRecord = $this->smokeAiService->getCachedByType($uid, SmokeAiService::TYPE_DAILY_SUMMARY, $planDate, 'v1');
$dailySummary = null;
if ($dailySummaryRecord) {
$dailySummary = [
'date' => (string) ($dailySummaryRecord['date'] ?? $planDate->format(Support::DATE_LAYOUT)),
'content' => (string) ($dailySummaryRecord['content'] ?? ''),
'model' => (string) ($dailySummaryRecord['model'] ?? ''),
'status' => 'available',
];
}
return Response::success([
'timer' => $timer,
'summary' => [
'today_count' => (int) ($homeSummary['today_count'] ?? 0),
'daily_target' => (int) (($profileView['profile']['baseline_cigs_per_day'] ?? 0)),
'resisted_count' => (int) ($homeSummary['resisted_count'] ?? 0),
'reduced_from_yesterday' => (int) ($homeSummary['reduced_from_yesterday'] ?? 0),
'exceeded_yesterday' => (bool) ($homeSummary['exceeded_yesterday'] ?? false),
],
'daily_summary' => $dailySummary,
'motivation' => $motivation,
]);
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function profile()
{
try {
return Response::success($this->smokeService->getProfileView($this->getCurrentSmtUserId()));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function saveProfile()
{
try {
return Response::success($this->smokeService->upsertProfile($this->getCurrentSmtUserId(), $this->request->post()));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function createLog()
{
try {
return Response::success($this->smokeService->createLog($this->getCurrentSmtUserId(), $this->request->post(), false));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function logs()
{
try {
return Response::success($this->smokeService->listLogs($this->getCurrentSmtUserId(), $this->request->get()));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function updateLog(int $id)
{
try {
return Response::success($this->smokeService->updateLog($this->getCurrentSmtUserId(), $id, $this->request->post()));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function deleteLog(int $id)
{
try {
return Response::success($this->smokeService->deleteLog($this->getCurrentSmtUserId(), $id));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function stats()
{
try {
$uid = $this->getCurrentSmtUserId();
return Response::success($this->smokeService->stats($uid, $this->request->get(), $this->smokeService->getProfile($uid)));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function aiNextSmokeTime()
{
try {
$user = $this->getCurrentSmtUser();
$uid = (int) $user['id'];
$now = Support::now();
$planDate = $this->resolvePlanDate((string) $this->request->get('date', ''), $now);
if ($planDate < Support::dateOnly($now)) {
throw new \RuntimeException('date 不能早于今天', 400);
}
$mode = strtolower(trim((string) $this->request->get('mode', 'auto')));
if (!in_array($mode, ['auto', 'ai', 'default'], true)) {
throw new \RuntimeException('mode 参数错误,应为 auto|ai|default', 400);
}
$profileView = $this->smokeService->getProfileView($uid);
$defaultSuggestion = $this->smokeService->getDefaultNextSuggestion($uid, $now, $planDate, $profileView);
$response = [
'source' => 'default',
'suggested_at' => (string) ($defaultSuggestion['next_smoke_at'] ?? ''),
'time_nodes' => [],
'advice' => '',
];
if ($mode !== 'default') {
$aiSuggestion = $mode === 'ai'
? $this->smokeAiService->getOrGenerateNextSmoke($user, $now, $planDate, 'v1', $defaultSuggestion)
: $this->smokeAiService->getCachedNextSmoke($user, $planDate, 'v1');
if ($aiSuggestion) {
$response['source'] = 'ai';
$response['suggested_at'] = (string) ($aiSuggestion['suggested_at'] ?? '');
$response['time_nodes'] = $aiSuggestion['time_nodes'] ?? [];
$response['advice'] = (string) ($aiSuggestion['advice'] ?? '');
}
}
return Response::success($response);
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function unlockAiAdvice()
{
try {
$date = !empty($this->request->post('date'))
? Support::parseDate((string) $this->request->post('date'), 'date')
: Support::dateOnly(Support::now()->modify('-1 day'));
return Response::success($this->smokeAiService->unlock($this->getCurrentSmtUser(), $date));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function aiDailySummary()
{
try {
$user = $this->getCurrentSmtUser();
$date = !empty($this->request->get('date'))
? Support::parseDate((string) $this->request->get('date'), 'date')
: Support::dateOnly();
$record = $this->smokeAiService->getOrGenerateDailySummary($user, $date, 'v1');
return Response::success([
'date' => (string) ($record['date'] ?? $date->format(Support::DATE_LAYOUT)),
'content' => (string) ($record['content'] ?? ''),
'model' => (string) ($record['model'] ?? ''),
]);
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function createShare()
{
try {
return Response::success($this->smokeService->createShare($this->getCurrentSmtUserId(), $this->request->post()));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function shareRead(string $token)
{
try {
return Response::success($this->smokeService->getShareView($token, $this->request->get()));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function generateQuitPlan()
{
try {
return Response::success($this->quitPlanService->generate($this->getCurrentSmtUserId(), $this->request->post()));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function quitPlan()
{
try {
return Response::success($this->quitPlanService->getActivePlan($this->getCurrentSmtUserId()));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function quitPlanDays()
{
try {
$planId = (int) $this->request->get('plan_id', 0);
return Response::success($this->quitPlanService->getPlanDays($this->getCurrentSmtUserId(), $planId > 0 ? $planId : null));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function resetQuitPlan()
{
try {
return Response::success($this->quitPlanService->reset($this->getCurrentSmtUserId(), $this->request->post()));
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function achievementThemes()
{
try {
return Response::success(['themes' => $this->achievementService->listActiveThemes()]);
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
public function achievement()
{
try {
$uid = $this->getCurrentSmtUserId();
$profile = $this->smokeService->getProfile($uid);
if (!$profile || empty($profile['achievement_theme_id'])) {
return Response::success(['achievement' => null]);
}
$days = $this->smokeService->getStreakDays($uid);
$achievement = $this->achievementService->getUserAchievement((int) $profile['achievement_theme_id'], $days);
return Response::success(['achievement' => $achievement]);
} catch (\Throwable $e) {
return Response::error($e->getMessage(), $e->getCode() ?: 500);
}
}
private function resolvePlanDate(string $raw, \DateTimeImmutable $now): \DateTimeImmutable
{
$value = strtolower(trim($raw));
if ($value === '' || $value === 'today') {
return Support::dateOnly($now);
}
if ($value === 'tomorrow') {
return Support::dateOnly($now->modify('+1 day'));
}
return Support::parseDate($value, 'date');
}
}