Merge remote-tracking branch 'origin/ai-hub' into ai-hub

This commit is contained in:
chenchun
2025-10-16 09:35:56 +08:00
18 changed files with 208 additions and 141 deletions

View File

@@ -21,7 +21,7 @@ public class GoodsListOutput
/// 商品参考价格 /// 商品参考价格
/// </summary> /// </summary>
public decimal ReferencePrice { get; set; } public decimal ReferencePrice { get; set; }
/// <summary> /// <summary>
/// 商品实际价格(折扣后的价格) /// 商品实际价格(折扣后的价格)
/// </summary> /// </summary>
@@ -31,7 +31,7 @@ public class GoodsListOutput
/// 折扣金额(仅尊享包) /// 折扣金额(仅尊享包)
/// </summary> /// </summary>
public decimal? DiscountAmount { get; set; } public decimal? DiscountAmount { get; set; }
/// <summary> /// <summary>
/// 商品类别 /// 商品类别
/// </summary> /// </summary>
@@ -43,9 +43,13 @@ public class GoodsListOutput
public string Remark { get; set; } public string Remark { get; set; }
/// <summary> /// <summary>
/// 折扣说明(仅尊享包) /// 折扣说明(仅尊享包)
/// </summary> /// </summary>
public string? DiscountDescription { get; set; } public string? DiscountDescription { get; set; }
}
/// <summary>
/// 商品类型
/// </summary>
public GoodsTypeEnum GoodsType { get; set; }
}

View File

@@ -261,7 +261,8 @@ public class PayService : ApplicationService, IPayService
GoodsCategory = goodsType.GetGoodsCategory().ToString(), GoodsCategory = goodsType.GetGoodsCategory().ToString(),
Remark = goodsType.GetRemark(), Remark = goodsType.GetRemark(),
DiscountAmount = discountAmount, DiscountAmount = discountAmount,
DiscountDescription = discountDescription DiscountDescription = discountDescription,
GoodsType = goodsType
}; };
goodsList.Add(goodsItem); goodsList.Add(goodsItem);

View File

@@ -10,11 +10,11 @@ namespace Yi.Framework.AiHub.Domain.Shared.Enums;
public class PriceAttribute : Attribute public class PriceAttribute : Attribute
{ {
public decimal Price { get; } public decimal Price { get; }
public decimal ReferencePrice { get; } public decimal ReferencePrice { get; }
public int ValidMonths { get; } public int ValidMonths { get; }
public PriceAttribute(double price, int validMonths, double referencePrice) public PriceAttribute(double price, int validMonths, double referencePrice)
{ {
Price = (decimal)price; Price = (decimal)price;
@@ -93,39 +93,32 @@ public class TokenAmountAttribute : Attribute
public enum GoodsTypeEnum public enum GoodsTypeEnum
{ {
// VIP服务 // VIP服务
[Price(29.9, 1,29.9)] [Price(29.9, 1, 29.9)] [DisplayName("YiXinVip 1 month", "1个月", "灵活选择")] [GoodsCategory(GoodsCategoryType.Vip)]
[DisplayName("YiXinVip 1 month", "1个月", "灵活选择")]
[GoodsCategory(GoodsCategoryType.Vip)]
YiXinVip1 = 1, YiXinVip1 = 1,
[Price(83.7, 3,27.9)] [Price(83.7, 3, 27.9)] [DisplayName("YiXinVip 3 month", "3个月", "短期体验")] [GoodsCategory(GoodsCategoryType.Vip)]
[DisplayName("YiXinVip 3 month", "3个月", "短期体验")]
[GoodsCategory(GoodsCategoryType.Vip)]
YiXinVip3 = 3, YiXinVip3 = 3,
[Price(155.4, 6,25.9)] [Price(155.4, 6, 25.9)] [DisplayName("YiXinVip 6 month", "6个月", "年度热销")] [GoodsCategory(GoodsCategoryType.Vip)]
[DisplayName("YiXinVip 6 month", "6个月", "年度热销")]
[GoodsCategory(GoodsCategoryType.Vip)]
YiXinVip6 = 6, YiXinVip6 = 6,
[Price(183.2, 8,22.9)] [Price(183.2, 8, 22.9)]
[DisplayName("YiXinVip 8 month", "8个月推荐", "限时活动,超高性价比")] [DisplayName("YiXinVip 8 month", "8个月推荐", "限时活动,超高性价比")]
[GoodsCategory(GoodsCategoryType.Vip)] [GoodsCategory(GoodsCategoryType.Vip)]
YiXinVip8 = 8, YiXinVip8 = 8,
// 尊享包服务 - 需要VIP资格才能购买 // 尊享包服务 - 需要VIP资格才能购买
[Price(188.9, 0,1750)] [Price(188.9, 0, 1750)]
[DisplayName("YiXinPremiumPackage 5000W Tokens", "5000万Tokens", "简单尝试")] [DisplayName("YiXinPremiumPackage 5000W Tokens", "5000万Tokens", "简单尝试")]
[GoodsCategory(GoodsCategoryType.PremiumPackage)] [GoodsCategory(GoodsCategoryType.PremiumPackage)]
[TokenAmount(5000)] [TokenAmount(50000000)]
PremiumPackage5000W = 101, PremiumPackage5000W = 101,
[Price(248.9, 0,3500)] [Price(248.9, 0, 3500)]
[DisplayName("YiXinPremiumPackage 10000W Tokens", "1亿Tokens推荐", "极致性价比")] [DisplayName("YiXinPremiumPackage 10000W Tokens", "1亿Tokens推荐", "极致性价比")]
[GoodsCategory(GoodsCategoryType.PremiumPackage)] [GoodsCategory(GoodsCategoryType.PremiumPackage)]
[TokenAmount(10000)] [TokenAmount(100000000)]
PremiumPackage10000W = 102, PremiumPackage10000W = 102,
} }
public static class GoodsTypeEnumExtensions public static class GoodsTypeEnumExtensions
@@ -304,6 +297,6 @@ public static class GoodsTypeEnumExtensions
var discountedPrice = originalPrice - discount; var discountedPrice = originalPrice - discount;
// 确保价格不为负数至少为0.01元 // 确保价格不为负数至少为0.01元
return Math.Max(discountedPrice, 0.01m); return Math.Round(discountedPrice, 2);
} }
} }

View File

@@ -139,7 +139,7 @@ public class AiGateWayManager : DomainService
await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId, await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId,
new MessageInputDto new MessageInputDto
{ {
Content = request.Messages?.LastOrDefault().Content ?? string.Empty, Content = sessionId is null ? "不予存储" : request.Messages?.LastOrDefault().Content ?? string.Empty,
ModelId = request.Model, ModelId = request.Model,
TokenUsage = data.Usage, TokenUsage = data.Usage,
}); });
@@ -147,7 +147,8 @@ public class AiGateWayManager : DomainService
await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId, await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId,
new MessageInputDto new MessageInputDto
{ {
Content = data.Choices?.FirstOrDefault()?.Delta.Content, Content =
sessionId is null ? "不予存储" : data.Choices?.FirstOrDefault()?.Delta.Content ?? string.Empty,
ModelId = request.Model, ModelId = request.Model,
TokenUsage = data.Usage TokenUsage = data.Usage
}); });
@@ -271,7 +272,7 @@ public class AiGateWayManager : DomainService
await _aiMessageManager.CreateUserMessageAsync(userId, sessionId, await _aiMessageManager.CreateUserMessageAsync(userId, sessionId,
new MessageInputDto new MessageInputDto
{ {
Content = request.Messages?.LastOrDefault()?.Content ?? string.Empty, Content = sessionId is null ? "不予存储" : request.Messages?.LastOrDefault()?.Content ?? string.Empty,
ModelId = request.Model, ModelId = request.Model,
TokenUsage = tokenUsage, TokenUsage = tokenUsage,
}); });
@@ -279,7 +280,7 @@ public class AiGateWayManager : DomainService
await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId, await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId,
new MessageInputDto new MessageInputDto
{ {
Content = backupSystemContent.ToString(), Content = sessionId is null ? "不予存储" : backupSystemContent.ToString(),
ModelId = request.Model, ModelId = request.Model,
TokenUsage = tokenUsage TokenUsage = tokenUsage
}); });
@@ -333,7 +334,7 @@ public class AiGateWayManager : DomainService
await _aiMessageManager.CreateUserMessageAsync(userId, sessionId, await _aiMessageManager.CreateUserMessageAsync(userId, sessionId,
new MessageInputDto new MessageInputDto
{ {
Content = request.Prompt, Content = sessionId is null ? "不予存储" : request.Prompt,
ModelId = model, ModelId = model,
TokenUsage = response.Usage, TokenUsage = response.Usage,
}); });
@@ -341,13 +342,13 @@ public class AiGateWayManager : DomainService
await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId, await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId,
new MessageInputDto new MessageInputDto
{ {
Content = response.Results?.FirstOrDefault()?.Url, Content = sessionId is null ? "不予存储" : response.Results?.FirstOrDefault()?.Url,
ModelId = model, ModelId = model,
TokenUsage = response.Usage TokenUsage = response.Usage
}); });
await _usageStatisticsManager.SetUsageAsync(userId, model, response.Usage); await _usageStatisticsManager.SetUsageAsync(userId, model, response.Usage);
// 扣减尊享token包用量 // 扣减尊享token包用量
if (userId is not null && PremiumPackageConst.ModeIds.Contains(request.Model)) if (userId is not null && PremiumPackageConst.ModeIds.Contains(request.Model))
{ {
@@ -528,7 +529,7 @@ public class AiGateWayManager : DomainService
await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId, await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId,
new MessageInputDto new MessageInputDto
{ {
Content = request.Messages?.FirstOrDefault()?.Content ?? string.Empty, Content = sessionId is null ? "不予存储" : request.Messages?.FirstOrDefault()?.Content ?? string.Empty,
ModelId = request.Model, ModelId = request.Model,
TokenUsage = data.TokenUsage, TokenUsage = data.TokenUsage,
}); });
@@ -536,7 +537,7 @@ public class AiGateWayManager : DomainService
await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId, await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId,
new MessageInputDto new MessageInputDto
{ {
Content = data.content?.FirstOrDefault()?.text, Content = sessionId is null ? "不予存储" : data.content?.FirstOrDefault()?.text,
ModelId = request.Model, ModelId = request.Model,
TokenUsage = data.TokenUsage TokenUsage = data.TokenUsage
}); });
@@ -601,35 +602,12 @@ public class AiGateWayManager : DomainService
_logger.LogError(e, $"Ai对话异常"); _logger.LogError(e, $"Ai对话异常");
var errorContent = $"对话Ai异常异常信息\n当前Ai模型{request.Model}\n异常信息{e.Message}\n异常堆栈:{e}"; var errorContent = $"对话Ai异常异常信息\n当前Ai模型{request.Model}\n异常信息{e.Message}\n异常堆栈:{e}";
throw new UserFriendlyException(errorContent); throw new UserFriendlyException(errorContent);
// var model = new AnthropicStreamDto
// {
// Message = new AnthropicChatCompletionDto
// {
// content =
// [
// new AnthropicChatCompletionDtoContent
// {
// text = errorContent,
// }
// ],
// },
// Error = new AnthropicStreamErrorDto
// {
// Type = null,
// Message = errorContent
// }
// };
// var message = JsonConvert.SerializeObject(model, new JsonSerializerSettings
// {
// ContractResolver = new CamelCasePropertyNamesContractResolver()
// });
// await response.WriteAsJsonAsync(message, ThorJsonSerializer.DefaultOptions);
} }
await _aiMessageManager.CreateUserMessageAsync(userId, sessionId, await _aiMessageManager.CreateUserMessageAsync(userId, sessionId,
new MessageInputDto new MessageInputDto
{ {
Content = request.Messages?.LastOrDefault()?.Content ?? string.Empty, Content = sessionId is null ? "不予存储" : request.Messages?.LastOrDefault()?.Content ?? string.Empty,
ModelId = request.Model, ModelId = request.Model,
TokenUsage = tokenUsage, TokenUsage = tokenUsage,
}); });
@@ -637,7 +615,7 @@ public class AiGateWayManager : DomainService
await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId, await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId,
new MessageInputDto new MessageInputDto
{ {
Content = backupSystemContent.ToString(), Content = sessionId is null ? "不予存储" : backupSystemContent.ToString(),
ModelId = request.Model, ModelId = request.Model,
TokenUsage = tokenUsage TokenUsage = tokenUsage
}); });
@@ -648,9 +626,9 @@ public class AiGateWayManager : DomainService
if (userId.HasValue && tokenUsage is not null) if (userId.HasValue && tokenUsage is not null)
{ {
var totalTokens = tokenUsage.TotalTokens ?? 0; var totalTokens = tokenUsage.TotalTokens ?? 0;
if (totalTokens > 0) if (tokenUsage.TotalTokens > 0)
{ {
await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens); await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens);
} }
} }
} }

View File

@@ -112,7 +112,7 @@
<body> <body>
<!-- 加载动画容器 --> <!-- 加载动画容器 -->
<div id="yixinai-loader" class="loader-container"> <div id="yixinai-loader" class="loader-container">
<div class="loader-title">意心Ai</div> <div class="loader-title">意心Ai 2.0</div>
<div class="loader-subtitle">海外地址仅首次访问预计加载约10秒</div> <div class="loader-subtitle">海外地址仅首次访问预计加载约10秒</div>
<div class="loader-logo"> <div class="loader-logo">
<div class="pulse-box"></div> <div class="pulse-box"></div>

View File

@@ -16,6 +16,7 @@ export interface GoodsItem {
goodsCategory: string; // 商品分类 goodsCategory: string; // 商品分类
remark: string | null; // 备注(标签) remark: string | null; // 备注(标签)
discountDescription: string | null; // 折扣描述 discountDescription: string | null; // 折扣描述
goodsType: string | null; // 折扣描述
} }
// 获取商品列表 // 获取商品列表

View File

@@ -77,7 +77,6 @@ async function onReLogin() {
} }
function handleThirdPartyLogin(type: any) { function handleThirdPartyLogin(type: any) {
const redirectUri = encodeURIComponent(`${window.location.origin}/chat`); const redirectUri = encodeURIComponent(`${window.location.origin}/chat`);
console.log('cccc', type);
const popup = window.open( const popup = window.open(
`${SSO_SEVER_URL}/login?client_id=${type}&redirect_uri=${redirectUri}`, `${SSO_SEVER_URL}/login?client_id=${type}&redirect_uri=${redirectUri}`,
'SSOLogin', 'SSOLogin',
@@ -149,7 +148,6 @@ function handleLoginAgainYi() {
&& event.data.type === 'SSO_LOGIN_SUCCESS' && event.data.type === 'SSO_LOGIN_SUCCESS'
&& !isHandled) { && !isHandled) {
isHandled = true; isHandled = true;
console.log('111');
try { try {
// 清理监听 // 清理监听
window.removeEventListener('message', messageHandler); window.removeEventListener('message', messageHandler);

View File

@@ -1,8 +1,10 @@
<!-- 切换模型 -->
<script setup lang="ts"> <script setup lang="ts">
import type { GetSessionListVO } from '@/api/model/types'; import type { GetSessionListVO } from '@/api/model/types';
import { Lock } from '@element-plus/icons-vue'; import { Lock } from '@element-plus/icons-vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import Popover from '@/components/Popover/index.vue'; import Popover from '@/components/Popover/index.vue';
import SvgIcon from '@/components/SvgIcon/index.vue'; import SvgIcon from '@/components/SvgIcon/index.vue';
import { useUserStore } from '@/stores'; import { useUserStore } from '@/stores';
import { useModelStore } from '@/stores/modules/model'; import { useModelStore } from '@/stores/modules/model';
@@ -10,6 +12,7 @@ import { showProductPackage } from '@/utils/product-package.ts';
import { isUserVip } from '@/utils/user'; import { isUserVip } from '@/utils/user';
const router = useRouter(); const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
const modelStore = useModelStore(); const modelStore = useModelStore();
// 检查模型是否可用 // 检查模型是否可用
@@ -19,14 +22,21 @@ function isModelAvailable(item: GetSessionListVO) {
onMounted(async () => { onMounted(async () => {
await modelStore.requestModelList(); await modelStore.requestModelList();
if (modelStore.modelList.length > 0 && !modelStore.currentModelInfo?.modelId) { // 设置默认模型
if (
modelStore.modelList.length > 0
&& (!modelStore.currentModelInfo || !modelStore.currentModelInfo.modelId)
) {
modelStore.setCurrentModelInfo(modelStore.modelList[0]); modelStore.setCurrentModelInfo(modelStore.modelList[0]);
} }
}); });
const currentModelName = computed(() => modelStore.currentModelInfo?.modelName); const currentModelName = computed(
() => modelStore.currentModelInfo && modelStore.currentModelInfo.modelName,
);
const popoverList = computed(() => modelStore.modelList); const popoverList = computed(() => modelStore.modelList);
/* 弹出面板 开始 */
const popoverStyle = ref({ const popoverStyle = ref({
width: '200px', width: '200px',
padding: '4px', padding: '4px',
@@ -34,33 +44,38 @@ const popoverStyle = ref({
background: 'var(--el-bg-color, #fff)', background: 'var(--el-bg-color, #fff)',
border: '1px solid var(--el-border-color-light)', border: '1px solid var(--el-border-color-light)',
borderRadius: '8px', borderRadius: '8px',
boxShadow: '0 2px 12px rgba(0,0,0,0.1)', boxShadow: '0 2px 12px 0 rgba(0, 0, 0, 0.1)',
}); });
const popoverRef = ref(); const popoverRef = ref();
// 显示
async function showPopover() { async function showPopover() {
// 获取最新的模型列表
await modelStore.requestModelList(); await modelStore.requestModelList();
} }
/* ------------------------------- // 点击
模型点击逻辑 // 处理模型点击
-------------------------------- */
function handleModelClick(item: GetSessionListVO) { function handleModelClick(item: GetSessionListVO) {
if (!isModelAvailable(item)) { if (!isModelAvailable(item)) {
ElMessageBox.confirm( ElMessageBox.confirm(
` `
<div class="text-center leading-relaxed"> <div class="text-center leading-relaxed">
<h3 class="text-lg font-bold mb-3">${isUserVip() ? 'YiXinAI-VIP 会员' : '成为 YiXinAI-VIP'}</h3> <h3 class="text-lg font-bold mb-3">${isUserVip() ? 'YiXinAI-VIP 会员' : '成为 YiXinAI-VIP'}</h3>
<p class="mb-2"> <p class="mb-2">
${isUserVip() ${
isUserVip()
? '您已是尊贵会员,享受全部 AI 模型与专属服务。感谢支持!' ? '您已是尊贵会员,享受全部 AI 模型与专属服务。感谢支持!'
: '解锁所有 AI 模型,无限加速,专属客服,尽享尊贵体验。'} : '解锁所有 AI 模型,无限加速,专属客服,尽享尊贵体验。'
</p> }
<p class="text-sm text-gray-500"> </p>
${isUserVip() ? '您可随时访问产品页面查看更多特权内容。' : '请点击右上角登录按钮,登录后进行购买!'} ${
</p> isUserVip()
</div> ? '<p class="text-sm text-gray-500">您可随时访问产品页面查看更多特权内容。</p>'
`, : '<p class="text-sm text-gray-500">请点击右上角登录按钮,登录后进行购买!</p>'
}
</div>
`,
isUserVip() ? '会员状态' : '会员尊享', isUserVip() ? '会员状态' : '会员尊享',
{ {
confirmButtonText: '产品查看', confirmButtonText: '产品查看',
@@ -70,7 +85,18 @@ function handleModelClick(item: GetSessionListVO) {
center: true, center: true,
roundButton: true, roundButton: true,
}, },
).then(() => showProductPackage()); )
.then(() => {
showProductPackage();
// router.push({
// name: 'products', // 使用命名路由
// query: { from: isUserVip() ? 'vip' : 'user' }, // 可选:添加来源标识
// });
})
.catch(() => {
// 点击右上角关闭或“关闭”按钮,不执行任何操作
});
} }
modelStore.setCurrentModelInfo(item); modelStore.setCurrentModelInfo(item);
@@ -83,8 +109,8 @@ function handleModelClick(item: GetSessionListVO) {
规则2金色光泽VIP/付费) 规则2金色光泽VIP/付费)
规则3彩色流光尊享/高级) 规则3彩色流光尊享/高级)
-------------------------------- */ -------------------------------- */
function getModelStyleClass(item: GetSessionListVO) { function getModelStyleClass(modelName: any) {
const name = item.modelName.toLowerCase(); const name = modelName.toLowerCase();
// 规则3彩色流光 // 规则3彩色流光
if (name.includes('claude-sonnet-4-5-20250929')) { if (name.includes('claude-sonnet-4-5-20250929')) {
@@ -123,7 +149,7 @@ function getWrapperClass(item: GetSessionListVO) {
? 'hover:scale-[1.03] hover:shadow-[0_0_8px_rgba(0,0,0,0.1)] hover:border-gray-300' ? 'hover:scale-[1.03] hover:shadow-[0_0_8px_rgba(0,0,0,0.1)] hover:border-gray-300'
: 'opacity-60 cursor-not-allowed', : 'opacity-60 cursor-not-allowed',
isSelected isSelected
? 'border-2 border-blue-700 shadow-[0_0_10px_rgba(29,78,216,0.6)]' ? 'border-2 border-blue-700 shadow-[0_0_10px_rgba(29,78,216,1)]'
: 'border border-transparent cursor-pointer', : 'border border-transparent cursor-pointer',
]; ];
} }
@@ -135,33 +161,55 @@ function getWrapperClass(item: GetSessionListVO) {
ref="popoverRef" ref="popoverRef"
placement="top-start" placement="top-start"
:offset="[4, 0]" :offset="[4, 0]"
popover-class="popover-content"
:popover-style="popoverStyle" :popover-style="popoverStyle"
trigger="clickTarget" trigger="clickTarget"
@show="showPopover" @show="showPopover"
> >
<!-- 触发元素插槽 -->
<template #trigger> <template #trigger>
<div <div
class="flex items-center gap-1 p-2 rounded-md border border-blue-500 text-blue-600 cursor-pointer select-none model-select-box " class="model-select-box select-none flex items-center gap-4px p-10px rounded-10px cursor-pointer font-size-12px border-[rgba()] leading-snug"
> >
<SvgIcon name="models" size="12" /> <div class="model-select-box-icon">
<span class="text-sm font-medium">{{ currentModelName }}</span> <SvgIcon name="models" size="12" />
</div>
<div :class="getModelStyleClass(currentModelName)" class="model-select-box-text font-size-12px">
{{ currentModelName }}
</div>
</div> </div>
</template> </template>
<div class="flex flex-col gap-1 max-h-100 overflow-y-auto p-1"> <div class="popover-content-box">
<div <div
v-for="item in popoverList" v-for="item in popoverList"
:key="item.id" :key="item.id"
:class="getWrapperClass(item)" :class="getWrapperClass(item)"
@click="handleModelClick(item)" @click="handleModelClick(item)"
> >
<span :class="getModelStyleClass(item)"> <Popover
{{ item.modelName }} trigger-class="popover-trigger-item-text"
</span> popover-class="rounded-tooltip"
placement="right"
trigger="hover"
:offset="[12, 0]"
>
<template #trigger>
<span :class="getModelStyleClass(item.modelName)">
{{ item.modelName }}
</span>
</template>
<div
class="popover-content-box-item-text text-wrap max-w-200px rounded-lg p-8px font-size-12px line-height-tight"
>
{{ item.remark }}
</div>
</Popover>
<!-- VIP锁定图标 -->
<el-icon <el-icon
v-if="!isModelAvailable(item)" v-if="!isModelAvailable(item)"
class="absolute right-1 top-1/2 -translate-y-1/2 text-gray-400" class="absolute right-1 top-1/2 transform -translate-y-1/2"
> >
<Lock /> <Lock />
</el-icon> </el-icon>
@@ -171,7 +219,7 @@ function getWrapperClass(item: GetSessionListVO) {
</div> </div>
</template> </template>
<style scoped> <style scoped lang="scss">
.model-select-box { .model-select-box {
color: var(--el-color-primary, #409eff); color: var(--el-color-primary, #409eff);
background: var(--el-color-primary-light-9, rgb(235.9 245.3 255)); background: var(--el-color-primary-light-9, rgb(235.9 245.3 255));
@@ -179,6 +227,35 @@ function getWrapperClass(item: GetSessionListVO) {
border-radius: 10px; border-radius: 10px;
} }
.popover-content-box {
display: flex;
flex-direction: column;
gap: 4px;
height: 200px;
overflow: hidden auto;
:deep(.popover-trigger-item-text) {
width: 100%;
}
.popover-content-box-item-text {
color: white;
background-color: black;
}
// 滚动条样式
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: #f5f5f5;
}
&::-webkit-scrollbar-thumb {
background: #cccccc;
border-radius: 4px;
}
}
/* 彩色流光动画 */ /* 彩色流光动画 */
@keyframes gradientFlow { @keyframes gradientFlow {
0%, 100% { background-position: 0 50%; } 0%, 100% { background-position: 0 50%; }

View File

@@ -24,6 +24,7 @@ interface PackageItem {
discount: string; // 累计优惠描述,如"根据累加充值已优惠 ¥47.00" discount: string; // 累计优惠描述,如"根据累加充值已优惠 ¥47.00"
goodsName: string; // 用于创建订单 goodsName: string; // 用于创建订单
discountAmount?: number; // 优惠金额从discount字段解析 discountAmount?: number; // 优惠金额从discount字段解析
goodsType?: any; // 商品枚举
} }
// 商品数据(从接口获取) // 商品数据(从接口获取)
@@ -73,6 +74,7 @@ async function fetchGoodsList() {
tag: item.remark || '', tag: item.remark || '',
discount: item.discountDescription || '', discount: item.discountDescription || '',
goodsName: item.goodsName, goodsName: item.goodsName,
goodsType: item.goodsType,
})); }));
// 转换Token商品数据字段含义保持原定义 // 转换Token商品数据字段含义保持原定义
@@ -95,6 +97,7 @@ async function fetchGoodsList() {
tag: item.remark || '', tag: item.remark || '',
discount: item.discountDescription || '', discount: item.discountDescription || '',
goodsName: item.goodsName, goodsName: item.goodsName,
goodsType: item.goodsType,
discountAmount, discountAmount,
}; };
}); });
@@ -229,7 +232,7 @@ async function pay() {
try { try {
const returnUrl = `${window.location.origin}/pay-result`; const returnUrl = `${window.location.origin}/pay-result`;
const params = { const params = {
goodsName: selectPackageObject.value.goodsName, GoodsType: selectPackageObject.value.goodsType,
ReturnUrl: returnUrl, ReturnUrl: returnUrl,
}; };

View File

@@ -18,21 +18,23 @@ const packageData = ref<any>({
const usagePercent = computed(() => { const usagePercent = computed(() => {
if (packageData.value.totalQuota === 0) if (packageData.value.totalQuota === 0)
return 0; return 0;
return Math.round((packageData.value.usedQuota / packageData.value.totalQuota) * 100); return Number(((packageData.value.usedQuota / packageData.value.totalQuota) * 100).toFixed(2));
}); });
const remainingPercent = computed(() => { const remainingPercent = computed(() => {
return 100 - usagePercent.value; if (packageData.value.totalQuota === 0)
return 0;
return Number(((packageData.value.remainingQuota / packageData.value.totalQuota) * 100).toFixed(2));
}); });
// 获取进度条颜色 // 获取进度条颜色(基于剩余百分比)
const progressColor = computed(() => { const progressColor = computed(() => {
const percent = usagePercent.value; const percent = remainingPercent.value;
if (percent >= 90) if (percent <= 10)
return '#f56c6c'; // 红色 return '#f56c6c'; // 红色 - 剩余很少
if (percent >= 70) if (percent <= 30)
return '#e6a23c'; // 橙色 return '#e6a23c'; // 橙色 - 剩余较少
return '#67c23a'; // 绿色 return '#67c23a'; // 绿色 - 剩余充足
}); });
// 格式化数字 - 转换为万为单位 // 格式化数字 - 转换为万为单位
@@ -194,14 +196,14 @@ function onProductPackage() {
<!-- 进度条 --> <!-- 进度条 -->
<div class="progress-section"> <div class="progress-section">
<div class="progress-header"> <div class="progress-header">
<span class="progress-label">使用进度</span> <span class="progress-label">剩余进度</span>
<span class="progress-percent" :style="{ color: progressColor }"> <span class="progress-percent" :style="{ color: progressColor }">
{{ usagePercent }}% {{ remainingPercent }}%
</span> </span>
</div> </div>
<el-progress <el-progress
:percentage="usagePercent" :percentage="remainingPercent"
:color="progressColor" :color="progressColor"
:stroke-width="20" :stroke-width="20"
:show-text="false" :show-text="false"
@@ -210,11 +212,11 @@ function onProductPackage() {
<div class="progress-legend"> <div class="progress-legend">
<div class="legend-item"> <div class="legend-item">
<span class="legend-dot " :style="{ background: progressColor }" /> <span class="legend-dot " :style="{ background: progressColor }" />
<span class="legend-text">已使用: {{ usagePercent }}%</span> <span class="legend-text">剩余: {{ remainingPercent }}%</span>
</div> </div>
<div class="legend-item"> <div class="legend-item">
<span class="legend-dot remaining-dot" /> <span class="legend-dot used-dot" />
<span class="legend-text">剩余: {{ remainingPercent }}%</span> <span class="legend-text">已使用: {{ usagePercent }}%</span>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -156,7 +156,7 @@ onMounted(() => {
> >
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
请扫码加入微信交流群<br> 请扫码加入微信交流群<br>
备注ai获取专属客服支持 备注AI获取专属客服支持
</h3> </h3>
<div class="mb-4 flex items-center justify-center space-x-2"> <div class="mb-4 flex items-center justify-center space-x-2">
<span class="font-semibold">站长微信账号</span> <span class="font-semibold">站长微信账号</span>
@@ -182,15 +182,15 @@ onMounted(() => {
@click="showWechatFullscreenImage" @click="showWechatFullscreenImage"
> >
</div> </div>
<div class="px-4"> <!-- <div class="px-4"> -->
<h4>微信交流群</h4> <!-- <h4>微信交流群</h4> -->
<img <!-- <img -->
:src="wxGroupQD" <!-- :src="wxGroupQD" -->
class="w-50 py-5 h-70 border border-gray-200 rounded-lg shadow-md cursor-pointer hover:shadow-lg transition-transform hover:scale-105" <!-- class="w-50 py-5 h-70 border border-gray-200 rounded-lg shadow-md cursor-pointer hover:shadow-lg transition-transform hover:scale-105" -->
alt="微信二维码" <!-- alt="微信二维码" -->
@click="showWxGroupFullscreenImage" <!-- @click="showWxGroupFullscreenImage" -->
> <!-- > -->
</div> <!-- </div> -->
</div> </div>
<!-- 全屏放大二维码 --> <!-- 全屏放大二维码 -->
@@ -466,8 +466,6 @@ onMounted(() => {
:deep(.el-table th) { :deep(.el-table th) {
background-color: #f8fafc; background-color: #f8fafc;
color: #333; color: #333;
//background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
//color: white;
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
padding: 8px 0; padding: 8px 0;
@@ -475,7 +473,6 @@ onMounted(() => {
} }
:deep(.el-table th .cell) { :deep(.el-table th .cell) {
//color: white;
} }
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) { :deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {

View File

@@ -16,7 +16,6 @@ const props = withDefaults(defineProps<Props>(), {
// 从store获取模型列表 // 从store获取模型列表
const modelStore = useModelStore(); const modelStore = useModelStore();
const modelList = computed(() => modelStore.modelList); const modelList = computed(() => modelStore.modelList);
console.log('modelList---', modelList);
// 计算网格布局的列数 // 计算网格布局的列数
const gridTemplateColumns = computed(() => { const gridTemplateColumns = computed(() => {

View File

@@ -1,5 +1,6 @@
<!-- 头像 --> <!-- 头像 -->
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import Popover from '@/components/Popover/index.vue'; import Popover from '@/components/Popover/index.vue';
import SvgIcon from '@/components/SvgIcon/index.vue'; import SvgIcon from '@/components/SvgIcon/index.vue';
@@ -57,6 +58,7 @@ const popoverList = ref([
]); ]);
const dialogVisible = ref(false); const dialogVisible = ref(false);
const navItems = [ const navItems = [
{ name: 'user', label: '用户信息', icon: 'User' }, { name: 'user', label: '用户信息', icon: 'User' },
// { name: 'role', label: '角色管理', icon: 'Avatar' }, // { name: 'role', label: '角色管理', icon: 'Avatar' },
@@ -72,12 +74,11 @@ function openDialog() {
dialogVisible.value = true; dialogVisible.value = true;
} }
function handleConfirm(activeNav: string) { function handleConfirm(activeNav: string) {
console.log('确认操作,当前导航:', activeNav);
ElMessage.success('操作成功'); ElMessage.success('操作成功');
} }
// 导航切换
function handleNavChange(nav: string) { function handleNavChange(nav: string) {
console.log('导航切换:', nav);
} }
// 点击 // 点击

View File

@@ -45,9 +45,9 @@ const debouncedSend = useDebounceFn(async () => {
}); });
senderValue.value = ''; // 清空输入框 senderValue.value = ''; // 清空输入框
} }
catch (error) { catch (error: any) {
console.error('发送消息失败:', error); console.error('发送消息失败:', error);
ElMessage.error('发送消息失败,请重试'); ElMessage.error(error);
} }
finally { finally {
isSending.value = false; isSending.value = false;

View File

@@ -46,10 +46,13 @@ const inputValue = ref('');
const senderRef = ref<InstanceType<typeof Sender> | null>(null); const senderRef = ref<InstanceType<typeof Sender> | null>(null);
const bubbleItems = ref<MessageItem[]>([]); const bubbleItems = ref<MessageItem[]>([]);
const bubbleListRef = ref<BubbleListInstance | null>(null); const bubbleListRef = ref<BubbleListInstance | null>(null);
const isSending = ref(false);
const { stream, loading: isLoading, cancel } = useHookFetch({ const { stream, loading: isLoading, cancel } = useHookFetch({
request: send, request: send,
onError: async (error) => { onError: async (error) => {
isLoading.value = false;
if (error.status === 403) { if (error.status === 403) {
const data = await (error.response.json()); const data = await (error.response.json());
// 弹窗提示 // 弹窗提示
@@ -168,12 +171,13 @@ function handleDataChunk(chunk: AnyObject) {
function handleError(err: any) { function handleError(err: any) {
console.error('Fetch error:', err); console.error('Fetch error:', err);
} }
const isSending = ref(false);
async function startSSE(chatContent: string) { async function startSSE(chatContent: string) {
if (isSending.value) if (isSending.value)
return; return;
isSending.value = true;
try { try {
// 清空输入框 // 清空输入框
inputValue.value = ''; inputValue.value = '';
@@ -205,14 +209,20 @@ async function startSSE(chatContent: string) {
} }
else { else {
handleError(err); // 其他错误 handleError(err); // 其他错误
// ElMessage.error('消息发送失败,请重试');
} }
} }
finally { finally {
isSending.value = false; isSending.value = false;
// 停止打字器状态 // 停止打字器状态和加载状态
if (bubbleItems.value.length) { if (bubbleItems.value.length) {
bubbleItems.value[bubbleItems.value.length - 1].typing = false; const latest = bubbleItems.value[bubbleItems.value.length - 1];
latest.typing = false;
latest.loading = false;
if (latest.thinkingStatus === 'thinking') {
latest.thinkingStatus = 'end';
}
} }
} }
} }
@@ -221,8 +231,14 @@ async function startSSE(chatContent: string) {
async function cancelSSE() { async function cancelSSE() {
try { try {
cancel(); // 直接调用,无需参数 cancel(); // 直接调用,无需参数
isSending.value = false;
if (bubbleItems.value.length) { if (bubbleItems.value.length) {
bubbleItems.value[bubbleItems.value.length - 1].typing = false; const latest = bubbleItems.value[bubbleItems.value.length - 1];
latest.typing = false;
latest.loading = false;
if (latest.thinkingStatus === 'thinking') {
latest.thinkingStatus = 'end';
}
} }
} }
catch (err) { catch (err) {
@@ -254,7 +270,6 @@ function addMessage(message: string, isUser: boolean) {
// 展开收起 事件展示 // 展开收起 事件展示
function handleChange(payload: { value: boolean; status: ThinkingStatus }) { function handleChange(payload: { value: boolean; status: ThinkingStatus }) {
console.log('value', payload.value, 'status', payload.status);
} }
function handleDeleteCard(_item: FilesCardProps, index: number) { function handleDeleteCard(_item: FilesCardProps, index: number) {
@@ -279,7 +294,6 @@ watch(
// 复制 // 复制
function copy(item: any) { function copy(item: any) {
console.log('复制', item);
navigator.clipboard.writeText(item.content || '') navigator.clipboard.writeText(item.content || '')
.then(() => ElMessage.success('已复制到剪贴板')) .then(() => ElMessage.success('已复制到剪贴板'))
.catch(() => ElMessage.error('复制失败')); .catch(() => ElMessage.error('复制失败'));
@@ -312,7 +326,7 @@ function copy(item: any) {
<div class="footer-container"> <div class="footer-container">
<div class="footer-time"> <div class="footer-time">
<span v-if="item.creationTime "> {{ item.creationTime }}</span> <span v-if="item.creationTime "> {{ item.creationTime }}</span>
<span style="margin-left: 10px;" v-if="((item.role === 'ai' || item.role === 'assistant') && item?.tokenUsage?.total) " class="footer-token"> <span v-if="((item.role === 'ai' || item.role === 'assistant') && item?.tokenUsage?.total) " style="margin-left: 10px;" class="footer-token">
{{ ((item.role === 'ai' || item.role === 'assistant') && item?.tokenUsage?.total) ? `token:${item?.tokenUsage?.total}` : '' }}</span> {{ ((item.role === 'ai' || item.role === 'assistant') && item?.tokenUsage?.total) ? `token:${item?.tokenUsage?.total}` : '' }}</span>
<el-button icon="DocumentCopy" size="small" circle @click="copy(item)" /> <el-button icon="DocumentCopy" size="small" circle @click="copy(item)" />
</div> </div>

View File

@@ -63,7 +63,6 @@ async function onReLogin() {
} }
function handleThirdPartyLogin(type: any) { function handleThirdPartyLogin(type: any) {
const redirectUri = encodeURIComponent(`${window.location.origin}/chat`); const redirectUri = encodeURIComponent(`${window.location.origin}/chat`);
console.log('cccc', type);
const popup = window.open( const popup = window.open(
`${SSO_SEVER_URL}/login?client_id=${type}&redirect_uri=${redirectUri}`, `${SSO_SEVER_URL}/login?client_id=${type}&redirect_uri=${redirectUri}`,
'SSOLogin', 'SSOLogin',
@@ -170,7 +169,8 @@ function contactCustomerService() {
🎉 恭喜 🎉 恭喜
</h1> </h1>
<p class="text-xl font-semibold mb-6"> <p class="text-xl font-semibold mb-6">
您已成为尊贵的 <span class="text-orange-500">YixinAI VIP</span> 您已充值成功
<!-- 您已成为尊贵的 <span class="text-orange-500">YixinAI VIP</span> -->
</p> </p>
<!-- 订单信息卡片 --> <!-- 订单信息卡片 -->
@@ -244,8 +244,8 @@ function contactCustomerService() {
append-to-body append-to-body
> >
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
请扫码加入微信交流群<br> 请扫码加入微信交流群<br>
备注ai获取专属客服支持 备注ai获取专属客服支持
</h3> </h3>
<div class="py-10 flex items-center justify-center space-x-2"> <div class="py-10 flex items-center justify-center space-x-2">
<span class="font-semibold">站长微信账号</span> <span class="font-semibold">站长微信账号</span>

View File

@@ -19,8 +19,6 @@ declare module 'vue' {
ElCollapse: typeof import('element-plus/es')['ElCollapse'] ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElContainer: typeof import('element-plus/es')['ElContainer'] ElContainer: typeof import('element-plus/es')['ElContainer']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider'] ElDivider: typeof import('element-plus/es')['ElDivider']
ElEmpty: typeof import('element-plus/es')['ElEmpty'] ElEmpty: typeof import('element-plus/es')['ElEmpty']

View File

@@ -6,6 +6,7 @@ interface ImportMetaEnv {
readonly VITE_WEB_ENV: string; readonly VITE_WEB_ENV: string;
readonly VITE_WEB_BASE_API: string; readonly VITE_WEB_BASE_API: string;
readonly VITE_API_URL: string; readonly VITE_API_URL: string;
readonly VITE_BUILD_COMPRESS: string;
readonly VITE_SSO_SEVER_URL: string; readonly VITE_SSO_SEVER_URL: string;
readonly VITE_APP_VERSION: string; readonly VITE_APP_VERSION: string;
} }