diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/DailyTask/ClaimTaskRewardInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/DailyTask/ClaimTaskRewardInput.cs new file mode 100644 index 00000000..611e0fd2 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/DailyTask/ClaimTaskRewardInput.cs @@ -0,0 +1,12 @@ +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.DailyTask; + +/// +/// 领取任务奖励输入 +/// +public class ClaimTaskRewardInput +{ + /// + /// 任务等级(1=1000w任务,2=3000w任务) + /// + public int TaskLevel { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/DailyTask/DailyTaskStatusOutput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/DailyTask/DailyTaskStatusOutput.cs new file mode 100644 index 00000000..e1da434e --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/DailyTask/DailyTaskStatusOutput.cs @@ -0,0 +1,58 @@ +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.DailyTask; + +/// +/// 每日任务状态输出 +/// +public class DailyTaskStatusOutput +{ + /// + /// 今日消耗的尊享包Token数 + /// + public long TodayConsumedTokens { get; set; } + + /// + /// 任务列表 + /// + public List Tasks { get; set; } = new(); +} + +/// +/// 每日任务项 +/// +public class DailyTaskItem +{ + /// + /// 任务等级(1=1000w任务,2=3000w任务) + /// + public int Level { get; set; } + + /// + /// 任务名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 任务描述 + /// + public string Description { get; set; } = string.Empty; + + /// + /// 任务要求的Token消耗量 + /// + public long RequiredTokens { get; set; } + + /// + /// 奖励的Token数量 + /// + public long RewardTokens { get; set; } + + /// + /// 任务状态:0=未完成,1=可领取,2=已领取 + /// + public int Status { get; set; } + + /// + /// 任务进度百分比(0-100) + /// + public decimal Progress { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/DailyTaskService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/DailyTaskService.cs new file mode 100644 index 00000000..263197ff --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/DailyTaskService.cs @@ -0,0 +1,185 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using SqlSugar; +using Volo.Abp.Application.Services; +using Volo.Abp.Users; +using Yi.Framework.AiHub.Application.Contracts.Dtos.DailyTask; +using Yi.Framework.AiHub.Domain.Entities; +using Yi.Framework.AiHub.Domain.Entities.Chat; +using Yi.Framework.AiHub.Domain.Extensions; +using Yi.Framework.AiHub.Domain.Managers; +using Yi.Framework.AiHub.Domain.Shared.Consts; +using Yi.Framework.SqlSugarCore.Abstractions; + +namespace Yi.Framework.AiHub.Application.Services; + +/// +/// 每日任务服务 +/// +[Authorize] +public class DailyTaskService : ApplicationService +{ + private readonly ISqlSugarRepository _dailyTaskRepository; + private readonly ISqlSugarRepository _messageRepository; + private readonly ISqlSugarRepository _premiumPackageRepository; + private readonly ILogger _logger; + + // 任务配置 + private readonly Dictionary + _taskConfigs = new() + { + { 1, (10000000, 2000000, "尊享包1000w token任务", "累积使用尊享包 1000w token") }, // 1000w消耗 -> 200w奖励 + { 2, (30000000, 4000000, "尊享包3000w token任务", "累积使用尊享包 3000w token") } // 3000w消耗 -> 600w奖励 + }; + + public DailyTaskService( + ISqlSugarRepository dailyTaskRepository, + ISqlSugarRepository messageRepository, + ISqlSugarRepository premiumPackageRepository, + ILogger logger) + { + _dailyTaskRepository = dailyTaskRepository; + _messageRepository = messageRepository; + _premiumPackageRepository = premiumPackageRepository; + _logger = logger; + } + + /// + /// 获取今日任务状态 + /// + /// + public async Task GetTodayTaskStatusAsync() + { + var userId = CurrentUser.GetId(); + var today = DateTime.Today; + + // 1. 统计今日尊享包Token消耗量 + var todayConsumed = await GetTodayPremiumTokenConsumptionAsync(userId, today); + + // 2. 查询今日已领取的任务 + var claimedTasks = await _dailyTaskRepository._DbQueryable + .Where(x => x.UserId == userId && x.TaskDate == today) + .Select(x => new { x.TaskLevel, x.IsRewarded }) + .ToListAsync(); + + // 3. 构建任务列表 + var tasks = new List(); + foreach (var (level, config) in _taskConfigs) + { + var claimed = claimedTasks.FirstOrDefault(x => x.TaskLevel == level); + int status; + + if (claimed != null && claimed.IsRewarded) + { + status = 2; // 已领取 + } + else if (todayConsumed >= config.RequiredTokens) + { + status = 1; // 可领取 + } + else + { + status = 0; // 未完成 + } + + var progress = todayConsumed >= config.RequiredTokens + ? 100 + : Math.Round((decimal)todayConsumed / config.RequiredTokens * 100, 2); + + tasks.Add(new DailyTaskItem + { + Level = level, + Name = config.Name, + Description = config.Description, + RequiredTokens = config.RequiredTokens, + RewardTokens = config.RewardTokens, + Status = status, + Progress = progress + }); + } + + return new DailyTaskStatusOutput + { + TodayConsumedTokens = todayConsumed, + Tasks = tasks + }; + } + + /// + /// 领取任务奖励 + /// + /// + /// + public async Task ClaimTaskRewardAsync(ClaimTaskRewardInput input) + { + var userId = CurrentUser.GetId(); + var today = DateTime.Today; + + // 1. 验证任务等级 + if (!_taskConfigs.TryGetValue(input.TaskLevel, out var taskConfig)) + { + throw new UserFriendlyException($"无效的任务等级: {input.TaskLevel}"); + } + + // 2. 检查是否已领取 + var existingRecord = await _dailyTaskRepository._DbQueryable + .Where(x => x.UserId == userId && x.TaskDate == today && x.TaskLevel == input.TaskLevel) + .FirstAsync(); + + if (existingRecord != null) + { + throw new UserFriendlyException("今日该任务奖励已领取,请明天再来!"); + } + + // 3. 验证今日Token消耗是否达标 + var todayConsumed = await GetTodayPremiumTokenConsumptionAsync(userId, today); + if (todayConsumed < taskConfig.RequiredTokens) + { + throw new UserFriendlyException( + $"Token消耗未达标!需要 {taskConfig.RequiredTokens / 10000}w,当前 {todayConsumed / 10000}w"); + } + + // 4. 创建奖励包(使用 PremiumPackageManager) + + var premiumPackage = + new PremiumPackageAggregateRoot(userId, taskConfig.RewardTokens, $"每日任务:{taskConfig.Name}") + { + PurchaseAmount = 0, // 奖励不需要付费 + Remark = $"{today:yyyy-MM-dd} 每日任务奖励" + }; + + await _premiumPackageRepository.InsertAsync(premiumPackage); + + // 5. 记录领取记录 + var record = new DailyTaskRewardRecordAggregateRoot(userId, input.TaskLevel, today, taskConfig.RewardTokens) + { + Remark = $"完成任务{input.TaskLevel},名称:{taskConfig.Name},消耗 {todayConsumed / 10000}w token" + }; + + await _dailyTaskRepository.InsertAsync(record); + + _logger.LogInformation( + $"用户 {userId} 领取每日任务 {input.TaskLevel} 奖励成功,获得 {taskConfig.RewardTokens / 10000}w tokens"); + } + + /// + /// 获取今日尊享包Token消耗量 + /// + /// 用户ID + /// 今日日期 + /// 消耗的Token总数 + private async Task GetTodayPremiumTokenConsumptionAsync(Guid userId, DateTime today) + { + var tomorrow = today.AddDays(1); + + // 查询今日所有使用尊享包模型的消息(role=system 表示消耗) + var totalTokens = await _messageRepository._DbQueryable + .Where(x => x.UserId == userId) + .Where(x => x.Role == "system") // system角色表示实际消耗 + .Where(x => PremiumPackageConst.ModeIds.Contains(x.ModelId)) // 尊享包模型 + .Where(x => x.CreationTime >= today && x.CreationTime < tomorrow) + .SumAsync(x => x.TokenUsage.TotalTokenCount); + + return totalTokens; + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/DailyTaskRewardRecordAggregateRoot.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/DailyTaskRewardRecordAggregateRoot.cs new file mode 100644 index 00000000..0d11f15d --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/DailyTaskRewardRecordAggregateRoot.cs @@ -0,0 +1,57 @@ +using SqlSugar; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Yi.Framework.AiHub.Domain.Entities; + +/// +/// 每日任务奖励领取记录 +/// +[SugarTable("Ai_DailyTaskRewardRecord")] +[SugarIndex($"index_{nameof(UserId)}_{nameof(TaskDate)}", + nameof(UserId), OrderByType.Asc, + nameof(TaskDate), OrderByType.Desc)] +public class DailyTaskRewardRecordAggregateRoot : FullAuditedAggregateRoot +{ + public DailyTaskRewardRecordAggregateRoot() + { + } + + public DailyTaskRewardRecordAggregateRoot(Guid userId, int taskLevel, DateTime taskDate, long rewardTokens) + { + UserId = userId; + TaskLevel = taskLevel; + TaskDate = taskDate.Date; // 确保只存储日期部分 + RewardTokens = rewardTokens; + IsRewarded = true; + } + + /// + /// 用户ID + /// + public Guid UserId { get; set; } + + /// + /// 任务等级(1=1000w任务,2=3000w任务) + /// + public int TaskLevel { get; set; } + + /// + /// 任务日期(只包含日期,不包含时间) + /// + public DateTime TaskDate { get; set; } + + /// + /// 奖励的Token数量 + /// + public long RewardTokens { get; set; } + + /// + /// 是否已发放奖励 + /// + public bool IsRewarded { get; set; } + + /// + /// 备注信息 + /// + public string? Remark { get; set; } +} diff --git a/Yi.Ai.Vue3/src/api/dailyTask/index.ts b/Yi.Ai.Vue3/src/api/dailyTask/index.ts new file mode 100644 index 00000000..37db6c41 --- /dev/null +++ b/Yi.Ai.Vue3/src/api/dailyTask/index.ts @@ -0,0 +1,12 @@ +import { get, post } from '@/utils/request'; +import type { DailyTaskStatusOutput, ClaimTaskRewardInput } from './types'; + +// 获取今日任务状态 +export function getTodayTaskStatus() { + return get('/daily-task/today-task-status').json(); +} + +// 领取任务奖励 +export function claimTaskReward(data: ClaimTaskRewardInput) { + return post('/daily-task/claim-task-reward', data).json(); +} diff --git a/Yi.Ai.Vue3/src/api/dailyTask/types.ts b/Yi.Ai.Vue3/src/api/dailyTask/types.ts new file mode 100644 index 00000000..7426bddc --- /dev/null +++ b/Yi.Ai.Vue3/src/api/dailyTask/types.ts @@ -0,0 +1,21 @@ +// 每日任务状态 +export interface DailyTaskStatusOutput { + todayConsumedTokens: number; // 今日消耗的尊享包Token数 + tasks: DailyTaskItem[]; // 任务列表 +} + +// 每日任务项 +export interface DailyTaskItem { + level: number; // 任务等级(1=1000w任务,2=3000w任务) + name: string; // 任务名称 + description: string; // 任务描述 + requiredTokens: number; // 任务要求的Token消耗量 + rewardTokens: number; // 奖励的Token数量 + status: number; // 任务状态:0=未完成,1=可领取,2=已领取 + progress: number; // 任务进度百分比(0-100) +} + +// 领取任务奖励输入 +export interface ClaimTaskRewardInput { + taskLevel: number; // 任务等级(1=1000w任务,2=3000w任务) +} diff --git a/Yi.Ai.Vue3/src/components/userPersonalCenter/components/DailyTask.vue b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/DailyTask.vue new file mode 100644 index 00000000..f9b2341f --- /dev/null +++ b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/DailyTask.vue @@ -0,0 +1,434 @@ + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components/Header/components/Avatar.vue b/Yi.Ai.Vue3/src/layouts/components/Header/components/Avatar.vue index 8332b15f..c4589079 100644 --- a/Yi.Ai.Vue3/src/layouts/components/Header/components/Avatar.vue +++ b/Yi.Ai.Vue3/src/layouts/components/Header/components/Avatar.vue @@ -65,9 +65,11 @@ const navItems = [ // { name: 'permission', label: '权限管理', icon: 'Key' }, // { name: 'userInfo', label: '用户信息', icon: 'User' }, { name: 'apiKey', label: 'API密钥', icon: 'Key' }, + { name: 'rechargeLog', label: '充值记录', icon: 'Document' }, { name: 'usageStatistics', label: '用量统计', icon: 'Histogram' }, { name: 'premiumService', label: '尊享服务', icon: 'ColdDrink' }, + { name: 'dailyTask', label: '每日任务', icon: 'Trophy' } // { name: 'usageStatistics2', label: '用量统计2', icon: 'Histogram' }, ]; function openDialog() { @@ -344,6 +346,9 @@ function onProductPackage() { + diff --git a/Yi.Ai.Vue3/types/components.d.ts b/Yi.Ai.Vue3/types/components.d.ts index 3b634da7..702505a4 100644 --- a/Yi.Ai.Vue3/types/components.d.ts +++ b/Yi.Ai.Vue3/types/components.d.ts @@ -10,6 +10,7 @@ declare module 'vue' { export interface GlobalComponents { AccountPassword: typeof import('./../src/components/LoginDialog/components/FormLogin/AccountPassword.vue')['default'] APIKeyManagement: typeof import('./../src/components/userPersonalCenter/components/APIKeyManagement.vue')['default'] + DailyTask: typeof import('./../src/components/userPersonalCenter/components/DailyTask.vue')['default'] DeepThinking: typeof import('./../src/components/DeepThinking/index.vue')['default'] ElAlert: typeof import('element-plus/es')['ElAlert'] ElAvatar: typeof import('element-plus/es')['ElAvatar'] diff --git a/Yi.Ai.Vue3/types/import_meta.d.ts b/Yi.Ai.Vue3/types/import_meta.d.ts index d8a60d41..b3e9d275 100644 --- a/Yi.Ai.Vue3/types/import_meta.d.ts +++ b/Yi.Ai.Vue3/types/import_meta.d.ts @@ -6,7 +6,6 @@ interface ImportMetaEnv { readonly VITE_WEB_ENV: string; readonly VITE_WEB_BASE_API: string; readonly VITE_API_URL: string; - readonly VITE_BUILD_COMPRESS: string; readonly VITE_SSO_SEVER_URL: string; readonly VITE_APP_VERSION: string; }