fix: 增加对话token显示,token消耗统计

This commit is contained in:
Gsh
2025-08-10 00:56:44 +08:00
parent a9c3a1bcec
commit 3eb27c3d35
10 changed files with 545 additions and 37 deletions

View File

@@ -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>