fix: 增加对话token显示,token消耗统计
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
"ComputedRef": true,
|
||||
"DirectiveBinding": true,
|
||||
"EffectScope": true,
|
||||
"ElMessage": true,
|
||||
"ElMessageBox": true,
|
||||
"ExtractDefaultPropTypes": true,
|
||||
"ExtractPropTypes": true,
|
||||
"ExtractPublicPropTypes": true,
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"@vueuse/core": "^13.5.0",
|
||||
"@vueuse/integrations": "^13.5.0",
|
||||
"driver.js": "^1.3.6",
|
||||
"echarts": "^6.0.0",
|
||||
"element-plus": "^2.10.4",
|
||||
"fingerprintjs": "^0.5.3",
|
||||
"hook-fetch": "^2.0.4-beta.1",
|
||||
|
||||
23
Yi.Ai.Vue3/pnpm-lock.yaml
generated
23
Yi.Ai.Vue3/pnpm-lock.yaml
generated
@@ -35,6 +35,9 @@ importers:
|
||||
driver.js:
|
||||
specifier: ^1.3.6
|
||||
version: 1.3.6
|
||||
echarts:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
element-plus:
|
||||
specifier: ^2.10.4
|
||||
version: 2.10.4(vue@3.5.17(typescript@5.8.3))
|
||||
@@ -1919,6 +1922,9 @@ packages:
|
||||
duplexer@0.1.2:
|
||||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||
|
||||
echarts@6.0.0:
|
||||
resolution: {integrity: sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==}
|
||||
|
||||
electron-to-chromium@1.5.165:
|
||||
resolution: {integrity: sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==}
|
||||
|
||||
@@ -4450,6 +4456,9 @@ packages:
|
||||
peerDependencies:
|
||||
typescript: '>=4.0.0'
|
||||
|
||||
tslib@2.3.0:
|
||||
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
@@ -4853,6 +4862,9 @@ packages:
|
||||
resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==}
|
||||
engines: {node: '>=12.20'}
|
||||
|
||||
zrender@6.0.0:
|
||||
resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==}
|
||||
|
||||
zwitch@2.0.4:
|
||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||
|
||||
@@ -6789,6 +6801,11 @@ snapshots:
|
||||
|
||||
duplexer@0.1.2: {}
|
||||
|
||||
echarts@6.0.0:
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
zrender: 6.0.0
|
||||
|
||||
electron-to-chromium@1.5.165: {}
|
||||
|
||||
element-plus@2.10.4(vue@3.5.17(typescript@5.8.3)):
|
||||
@@ -9819,6 +9836,8 @@ snapshots:
|
||||
picomatch: 4.0.2
|
||||
typescript: 5.8.3
|
||||
|
||||
tslib@2.3.0: {}
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
type-check@0.4.0:
|
||||
@@ -10359,4 +10378,8 @@ snapshots:
|
||||
|
||||
yocto-queue@1.2.1: {}
|
||||
|
||||
zrender@6.0.0:
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
|
||||
zwitch@2.0.4: {}
|
||||
|
||||
@@ -19,3 +19,12 @@ export function getApiKey() {
|
||||
export function getRechargeLog() {
|
||||
return get<any>('/recharge/account').json();
|
||||
}
|
||||
|
||||
// 查询用户近7天token消耗
|
||||
export function getLast7DaysTokenUsage() {
|
||||
return get<any>('/usage-statistics/last7Days-token-usage').json();
|
||||
}
|
||||
// 查询用户token消耗各模型占比
|
||||
export function getModelTokenUsage() {
|
||||
return get<any>('/usage-statistics/model-token-usage').json();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ interface Props {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '弹窗标题',
|
||||
width: '800px',
|
||||
width: '1000px',
|
||||
defaultActive: '',
|
||||
});
|
||||
|
||||
@@ -55,6 +55,7 @@ function handleConfirm() {
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
:width="width"
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
<script lang="ts" setup>
|
||||
import { PieChart } from '@element-plus/icons-vue';
|
||||
import { BarChart, PieChart as EPieChart, LineChart } from 'echarts/charts';
|
||||
import {
|
||||
GraphicComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
} from 'echarts/components';
|
||||
// 按需引入 ECharts
|
||||
import * as echarts from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { getLast7DaysTokenUsage, getModelTokenUsage } from '@/api';
|
||||
|
||||
// 注册必要的组件
|
||||
echarts.use([
|
||||
LineChart,
|
||||
EPieChart,
|
||||
BarChart,
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
GraphicComponent,
|
||||
CanvasRenderer,
|
||||
]);
|
||||
|
||||
// 图表引用
|
||||
const lineChart = ref(null);
|
||||
const pieChart = ref(null);
|
||||
const barChart = ref(null);
|
||||
let lineChartInstance: any = null;
|
||||
let pieChartInstance: any = null;
|
||||
let barChartInstance: any = null;
|
||||
|
||||
// 数据
|
||||
const dateRange = ref([
|
||||
new Date(new Date().setDate(new Date().getDate() - 30)),
|
||||
new Date(),
|
||||
]);
|
||||
const loading = ref(false);
|
||||
const totalTokens = ref(0);
|
||||
const usageData = ref<any[]>([]);
|
||||
const modelUsageData = ref<any[]>([]);
|
||||
|
||||
// 日期选择器选项
|
||||
const pickerOptions = {
|
||||
disabledDate(time: Date) {
|
||||
return time > new Date();
|
||||
},
|
||||
shortcuts: [{
|
||||
text: '最近一周',
|
||||
onClick(picker: any) {
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setDate(start.getDate() - 7);
|
||||
picker.$emit('pick', [start, end]);
|
||||
},
|
||||
}, {
|
||||
text: '最近一个月',
|
||||
onClick(picker: any) {
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setMonth(start.getMonth() - 1);
|
||||
picker.$emit('pick', [start, end]);
|
||||
},
|
||||
}, {
|
||||
text: '最近三个月',
|
||||
onClick(picker: any) {
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setMonth(start.getMonth() - 3);
|
||||
picker.$emit('pick', [start, end]);
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
// 获取用量数据
|
||||
async function fetchUsageData() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const [res, res2] = await Promise.all([
|
||||
getLast7DaysTokenUsage(),
|
||||
getModelTokenUsage(),
|
||||
]);
|
||||
|
||||
usageData.value = res.data || [];
|
||||
modelUsageData.value = res2.data || [];
|
||||
totalTokens.value = usageData.value.reduce((sum, item) => sum + item.tokens, 0);
|
||||
|
||||
updateCharts();
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取用量数据失败:', error);
|
||||
ElMessage.error('获取用量数据失败');
|
||||
}
|
||||
finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
function initCharts() {
|
||||
if (lineChart.value) {
|
||||
lineChartInstance = echarts.init(lineChart.value);
|
||||
}
|
||||
if (pieChart.value) {
|
||||
pieChartInstance = echarts.init(pieChart.value);
|
||||
}
|
||||
if (barChart.value) {
|
||||
barChartInstance = echarts.init(barChart.value);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resizeCharts);
|
||||
}
|
||||
|
||||
// 更新图表数据
|
||||
function updateCharts() {
|
||||
updateLineChart();
|
||||
updatePieChart();
|
||||
updateBarChart();
|
||||
}
|
||||
|
||||
// 更新折线图
|
||||
function updateLineChart() {
|
||||
if (!lineChartInstance)
|
||||
return;
|
||||
|
||||
const dates = usageData.value.map((item) => {
|
||||
const date = new Date(item.date);
|
||||
return `${date.getMonth() + 1}月${date.getDate()}日`;
|
||||
});
|
||||
|
||||
const tokens = usageData.value.map(item => item.tokens);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: '{b}<br/>用量: {c} ',
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dates,
|
||||
axisLabel: {
|
||||
rotate: 45,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: 'Token用量',
|
||||
axisLine: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
series: [{
|
||||
data: tokens,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(58, 77, 233, 0.8)' },
|
||||
{ offset: 1, color: 'rgba(58, 77, 233, 0.1)' },
|
||||
]),
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#3a4de9',
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
},
|
||||
symbolSize: 8,
|
||||
}],
|
||||
};
|
||||
|
||||
lineChartInstance.setOption(option);
|
||||
}
|
||||
|
||||
// 更新饼图
|
||||
function updatePieChart() {
|
||||
if (!pieChartInstance || modelUsageData.value.length === 0)
|
||||
return;
|
||||
|
||||
const data = modelUsageData.value.map(item => ({
|
||||
name: item.model,
|
||||
value: item.tokens,
|
||||
}));
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c} tokens ({d}%)',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 'center',
|
||||
data: data.map(item => item.name),
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '模型用量',
|
||||
type: 'pie',
|
||||
radius: ['50%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '18',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
pieChartInstance.setOption(option);
|
||||
}
|
||||
|
||||
// 更新柱状图
|
||||
function updateBarChart() {
|
||||
if (!barChartInstance || modelUsageData.value.length === 0)
|
||||
return;
|
||||
|
||||
const models = modelUsageData.value.map(item => item.model);
|
||||
const tokens = modelUsageData.value.map(item => item.tokens);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
formatter: '{b}<br/>用量: {c} tokens',
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
name: 'Token用量',
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: models,
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '用量',
|
||||
type: 'bar',
|
||||
data: tokens,
|
||||
itemStyle: {
|
||||
color(params: any) {
|
||||
const colorList = [
|
||||
'#3a4de9',
|
||||
'#6a5acd',
|
||||
'#9370db',
|
||||
'#8a2be2',
|
||||
'#9932cc',
|
||||
'#ba55d3',
|
||||
'#da70d6',
|
||||
'#ee82ee',
|
||||
'#dda0dd',
|
||||
'#ff00ff',
|
||||
];
|
||||
return colorList[params.dataIndex % colorList.length];
|
||||
},
|
||||
borderRadius: [0, 4, 4, 0],
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
formatter: '{c} ',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
barChartInstance.setOption(option);
|
||||
}
|
||||
|
||||
// 调整图表大小
|
||||
function resizeCharts() {
|
||||
lineChartInstance?.resize();
|
||||
pieChartInstance?.resize();
|
||||
barChartInstance?.resize();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initCharts();
|
||||
fetchUsageData();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resizeCharts);
|
||||
lineChartInstance?.dispose();
|
||||
pieChartInstance?.dispose();
|
||||
barChartInstance?.dispose();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="usage-statistics">
|
||||
<div class="header">
|
||||
<h2>
|
||||
<el-icon><PieChart /></el-icon>
|
||||
Token用量统计
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<el-card v-loading="loading" class="chart-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>近七天每日Token消耗量</span>
|
||||
<el-tag type="primary">
|
||||
总计: {{ totalTokens }} tokens
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container">
|
||||
<div ref="lineChart" class="chart" style="height: 400px;" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card v-loading="loading" class="chart-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>各模型Token消耗占比</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container">
|
||||
<div ref="pieChart" class="chart" style="height: 400px;" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card v-loading="loading" class="chart-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>各模型Token消耗量</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container">
|
||||
<div ref="barChart" class="chart" style="height: 400px;" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 样式保持不变 */
|
||||
.usage-statistics {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h2 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header .el-icon {
|
||||
margin-right: 8px;
|
||||
color: #3a4de9;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -62,6 +62,7 @@ const navItems = [
|
||||
// { name: 'permission', label: '权限管理', icon: 'Key' },
|
||||
{ name: 'apiKey', label: 'API密钥', icon: 'Key' },
|
||||
{ name: 'rechargeLog', label: '充值记录', icon: 'Document' },
|
||||
{ name: 'usageStatistics', label: '用量统计', icon: 'Histogram' },
|
||||
];
|
||||
function openDialog() {
|
||||
dialogVisible.value = true;
|
||||
@@ -259,6 +260,10 @@ function openVipGuide() {
|
||||
<template #user>
|
||||
<user-management />
|
||||
</template>
|
||||
<!-- 用量统计 -->
|
||||
<template #usageStatistics>
|
||||
<usage-statistics />
|
||||
</template>
|
||||
|
||||
<!-- 角色管理内容 -->
|
||||
<template #role>
|
||||
|
||||
@@ -111,52 +111,55 @@ watch(
|
||||
// 封装数据处理逻辑
|
||||
function handleDataChunk(chunk: AnyObject) {
|
||||
try {
|
||||
const reasoningChunk = chunk.choices?.[0].delta.reasoning_content;
|
||||
if (reasoningChunk) {
|
||||
// 开始思考链状态
|
||||
bubbleItems.value[bubbleItems.value.length - 1].thinkingStatus = 'thinking';
|
||||
bubbleItems.value[bubbleItems.value.length - 1].loading = true;
|
||||
bubbleItems.value[bubbleItems.value.length - 1].thinlCollapse = true;
|
||||
if (bubbleItems.value.length) {
|
||||
bubbleItems.value[bubbleItems.value.length - 1].reasoning_content += reasoningChunk;
|
||||
}
|
||||
// 安全获取 delta 和 content
|
||||
const delta = chunk.choices?.[0]?.delta;
|
||||
const reasoningChunk = delta?.reasoning_content;
|
||||
const parsedChunk = delta?.content;
|
||||
|
||||
// usage 处理(可以移动到 startSSE 里也可以写这里)
|
||||
if (chunk.usage) {
|
||||
const { prompt_tokens, completion_tokens, total_tokens } = chunk.usage;
|
||||
const latest = bubbleItems.value[bubbleItems.value.length - 1];
|
||||
latest.tokenUsage = {
|
||||
prompt: prompt_tokens,
|
||||
completion: completion_tokens,
|
||||
total: total_tokens,
|
||||
};
|
||||
}
|
||||
|
||||
if (reasoningChunk) {
|
||||
const latest = bubbleItems.value[bubbleItems.value.length - 1];
|
||||
latest.thinkingStatus = 'thinking';
|
||||
latest.loading = true;
|
||||
latest.thinlCollapse = true;
|
||||
latest.reasoning_content += reasoningChunk;
|
||||
}
|
||||
|
||||
// 另一种思考中形式,content中有 <think></think> 的格式
|
||||
// 一开始匹配到 <think> 开始,匹配到 </think> 结束,并处理标签中的内容为思考内容
|
||||
const parsedChunk = chunk.choices?.[0].delta.content;
|
||||
if (parsedChunk) {
|
||||
const thinkStart = parsedChunk.includes('<think>');
|
||||
const thinkEnd = parsedChunk.includes('</think>');
|
||||
if (thinkStart) {
|
||||
|
||||
if (thinkStart)
|
||||
isThinking = true;
|
||||
}
|
||||
if (thinkEnd) {
|
||||
if (thinkEnd)
|
||||
isThinking = false;
|
||||
}
|
||||
|
||||
const latest = bubbleItems.value[bubbleItems.value.length - 1];
|
||||
|
||||
if (isThinking) {
|
||||
// 开始思考链状态
|
||||
bubbleItems.value[bubbleItems.value.length - 1].thinkingStatus = 'thinking';
|
||||
bubbleItems.value[bubbleItems.value.length - 1].loading = true;
|
||||
bubbleItems.value[bubbleItems.value.length - 1].thinlCollapse = true;
|
||||
if (bubbleItems.value.length) {
|
||||
bubbleItems.value[bubbleItems.value.length - 1].reasoning_content += parsedChunk
|
||||
.replace('<think>', '')
|
||||
.replace('</think>', '');
|
||||
}
|
||||
latest.thinkingStatus = 'thinking';
|
||||
latest.loading = true;
|
||||
latest.thinlCollapse = true;
|
||||
latest.reasoning_content += parsedChunk.replace('<think>', '').replace('</think>', '');
|
||||
}
|
||||
else {
|
||||
// 结束 思考链状态
|
||||
bubbleItems.value[bubbleItems.value.length - 1].thinkingStatus = 'end';
|
||||
bubbleItems.value[bubbleItems.value.length - 1].loading = false;
|
||||
if (bubbleItems.value.length) {
|
||||
bubbleItems.value[bubbleItems.value.length - 1].content += parsedChunk;
|
||||
}
|
||||
latest.thinkingStatus = 'end';
|
||||
latest.loading = false;
|
||||
latest.content += parsedChunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
// 这里如果使用了中断,会有报错,可以忽略不管
|
||||
console.error('解析数据时出错:', err);
|
||||
}
|
||||
}
|
||||
@@ -308,8 +311,9 @@ function copy(item: any) {
|
||||
<div class="footer-wrapper">
|
||||
<div class="footer-container">
|
||||
<div class="footer-time">
|
||||
{{ item.creationTime }}
|
||||
|
||||
<span v-if="item.creationTime "> {{ item.creationTime }}</span>
|
||||
<span v-if="((item.role === 'ai' || item.role === 'assistant') && item?.tokenUsage?.total) " class="footer-token">
|
||||
{{ ((item.role === 'ai' || item.role === 'assistant') && item?.tokenUsage?.total) ? `token:${item?.tokenUsage?.total}` : '' }}</span>
|
||||
<el-button icon="DocumentCopy" size="small" circle @click="copy(item)" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -421,4 +425,41 @@ function copy(item: any) {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
.footer-time {
|
||||
font-size: 12px;
|
||||
margin-top: 3px;
|
||||
.footer-token {
|
||||
background: rgba(1, 183, 86, 0.53);
|
||||
padding: 0 4px;
|
||||
margin: 0 2px;
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-container {
|
||||
:deep(.el-button + .el-button) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
padding: 12px;
|
||||
background: linear-gradient(to right, #fdfcfb 0%, #ffd1ab 100%);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.loading-container span {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,8 +3,6 @@ import { useUserStore } from '@/stores/index.js';
|
||||
// 判断是否是 VIP 用户
|
||||
export function isUserVip(): boolean {
|
||||
const userStore = useUserStore();
|
||||
console.log('isUserVip----', userStore);
|
||||
|
||||
const userRoles = userStore.userInfo?.roles ?? [];
|
||||
return userRoles.some((role: any) => role.roleCode === 'YiXinAi-Vip');
|
||||
}
|
||||
|
||||
2
Yi.Ai.Vue3/types/components.d.ts
vendored
2
Yi.Ai.Vue3/types/components.d.ts
vendored
@@ -34,6 +34,7 @@ declare module 'vue' {
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
FilesSelect: typeof import('./../src/components/FilesSelect/index.vue')['default']
|
||||
IconSelect: typeof import('./../src/components/IconSelect/index.vue')['default']
|
||||
@@ -49,6 +50,7 @@ declare module 'vue' {
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SvgIcon: typeof import('./../src/components/SvgIcon/index.vue')['default']
|
||||
UsageStatistics: typeof import('./../src/components/userPersonalCenter/components/UsageStatistics.vue')['default']
|
||||
UserManagement: typeof import('./../src/components/userPersonalCenter/components/UserManagement.vue')['default']
|
||||
VerificationCode: typeof import('./../src/components/LoginDialog/components/FormLogin/VerificationCode.vue')['default']
|
||||
WelecomeText: typeof import('./../src/components/WelecomeText/index.vue')['default']
|
||||
|
||||
Reference in New Issue
Block a user