前端页面展示内容修改,带totaltoken

This commit is contained in:
2026-04-10 10:13:05 +00:00
parent cf81290ae7
commit c888ca8844
8 changed files with 319 additions and 29 deletions

View File

@@ -48,6 +48,11 @@ export default {
clearHistoryConfirm: 'Are you sure to clear chat history?', clearHistoryConfirm: 'Are you sure to clear chat history?',
preview: 'Preview', preview: 'Preview',
showRawText: 'Show as raw text', showRawText: 'Show as raw text',
sourceSemantic: 'Semantic Match',
sourceLlm: 'LLM Output',
promptTokens: 'Prompt {count} tokens',
completionTokens: 'Completion {count} tokens',
sessionTokens: 'Session {count} tokens',
}, },
setting: { setting: {
setting: 'Setting', setting: 'Setting',

View File

@@ -48,6 +48,11 @@ export default {
clearHistoryConfirm: '确定清空聊天记录?', clearHistoryConfirm: '确定清空聊天记录?',
preview: '预览', preview: '预览',
showRawText: '显示原文', showRawText: '显示原文',
sourceSemantic: '语义匹配',
sourceLlm: '大模型输出',
promptTokens: '问题 {count} tokens',
completionTokens: '回答 {count} tokens',
sessionTokens: '本轮消耗 {count} tokens',
}, },
setting: { setting: {
setting: '设置', setting: '设置',

View File

@@ -48,6 +48,11 @@ export default {
clearHistoryConfirm: '確定清除紀錄?', clearHistoryConfirm: '確定清除紀錄?',
preview: '預覽', preview: '預覽',
showRawText: '顯示原文', showRawText: '顯示原文',
sourceSemantic: '語義匹配',
sourceLlm: '大模型輸出',
promptTokens: '問題 {count} tokens',
completionTokens: '回答 {count} tokens',
sessionTokens: '本輪消耗 {count} tokens',
}, },
setting: { setting: {
setting: '設定', setting: '設定',

View File

@@ -2,19 +2,36 @@ import { ss } from '@/utils/storage'
const LOCAL_NAME = 'chatStorage' const LOCAL_NAME = 'chatStorage'
export function emptyUsage(): Chat.TokenUsage {
return {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
}
}
export function defaultState(): Chat.ChatState { export function defaultState(): Chat.ChatState {
const uuid = 1002 const uuid = 1002
return { return {
active: uuid, active: uuid,
usingContext: true, usingContext: true,
history: [{ uuid, title: 'New Chat', isEdit: false }], history: [{ uuid, title: 'New Chat', isEdit: false }],
chat: [{ uuid, data: [] }], chat: [{ uuid, data: [], sessionUsage: emptyUsage() }],
} }
} }
export function getLocalState(): Chat.ChatState { export function getLocalState(): Chat.ChatState {
const localState = ss.get(LOCAL_NAME) const localState = ss.get(LOCAL_NAME)
return { ...defaultState(), ...localState } const state = { ...defaultState(), ...localState }
return {
...state,
chat: (state.chat || []).map((item: Partial<Chat.Session>) => ({
uuid: item.uuid || Date.now(),
data: item.data || [],
sessionUsage: { ...emptyUsage(), ...(item.sessionUsage || {}) },
})),
}
} }
export function setLocalState(state: Chat.ChatState) { export function setLocalState(state: Chat.ChatState) {

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { getLocalState, setLocalState } from './helper' import { emptyUsage, getLocalState, setLocalState } from './helper'
import { router } from '@/router' import { router } from '@/router'
export const useChatStore = defineStore('chat-store', { export const useChatStore = defineStore('chat-store', {
@@ -20,6 +20,14 @@ export const useChatStore = defineStore('chat-store', {
return state.chat.find(item => item.uuid === state.active)?.data ?? [] return state.chat.find(item => item.uuid === state.active)?.data ?? []
} }
}, },
getSessionUsageByUuid(state: Chat.ChatState) {
return (uuid?: number) => {
if (uuid)
return state.chat.find(item => item.uuid === uuid)?.sessionUsage ?? emptyUsage()
return state.chat.find(item => item.uuid === state.active)?.sessionUsage ?? emptyUsage()
}
},
}, },
actions: { actions: {
@@ -30,7 +38,7 @@ export const useChatStore = defineStore('chat-store', {
addHistory(history: Chat.History, chatData: Chat.Chat[] = []) { addHistory(history: Chat.History, chatData: Chat.Chat[] = []) {
this.history.unshift(history) this.history.unshift(history)
this.chat.unshift({ uuid: history.uuid, data: chatData }) this.chat.unshift({ uuid: history.uuid, data: chatData, sessionUsage: emptyUsage() })
this.active = history.uuid this.active = history.uuid
this.reloadRoute(history.uuid) this.reloadRoute(history.uuid)
}, },
@@ -97,7 +105,7 @@ export const useChatStore = defineStore('chat-store', {
if (this.history.length === 0) { if (this.history.length === 0) {
const uuid = Date.now() const uuid = Date.now()
this.history.push({ uuid, title: chat.text, isEdit: false }) this.history.push({ uuid, title: chat.text, isEdit: false })
this.chat.push({ uuid, data: [chat] }) this.chat.push({ uuid, data: [chat], sessionUsage: emptyUsage() })
this.active = uuid this.active = uuid
this.recordState() this.recordState()
} }
@@ -182,6 +190,32 @@ export const useChatStore = defineStore('chat-store', {
} }
}, },
addSessionUsageByUuid(uuid: number, usage: Partial<Chat.TokenUsage>) {
const sessionUsage = { ...emptyUsage(), ...usage }
if (!uuid || uuid === 0) {
if (this.chat.length) {
this.chat[0].sessionUsage = {
prompt_tokens: this.chat[0].sessionUsage.prompt_tokens + sessionUsage.prompt_tokens,
completion_tokens: this.chat[0].sessionUsage.completion_tokens + sessionUsage.completion_tokens,
total_tokens: this.chat[0].sessionUsage.total_tokens + sessionUsage.total_tokens,
}
this.recordState()
}
return
}
const index = this.chat.findIndex(item => item.uuid === uuid)
if (index !== -1) {
this.chat[index].sessionUsage = {
prompt_tokens: this.chat[index].sessionUsage.prompt_tokens + sessionUsage.prompt_tokens,
completion_tokens: this.chat[index].sessionUsage.completion_tokens + sessionUsage.completion_tokens,
total_tokens: this.chat[index].sessionUsage.total_tokens + sessionUsage.total_tokens,
}
this.recordState()
}
},
async reloadRoute(uuid?: number) { async reloadRoute(uuid?: number) {
this.recordState() this.recordState()
await router.push({ name: 'Chat', params: { uuid } }) await router.push({ name: 'Chat', params: { uuid } })

View File

@@ -1,4 +1,17 @@
declare namespace Chat { declare namespace Chat {
interface TokenUsage {
prompt_tokens: number
completion_tokens: number
total_tokens: number
}
type ReplySource = 'semantic_match' | 'llm'
interface MessageMeta {
source?: ReplySource | null
tokenUsed?: boolean
usage?: TokenUsage | null
}
interface Chat { interface Chat {
dateTime: string dateTime: string
@@ -8,6 +21,7 @@ declare namespace Chat {
loading?: boolean loading?: boolean
conversationOptions?: ConversationRequest | null conversationOptions?: ConversationRequest | null
requestOptions: { prompt: string; options?: ConversationRequest | null } requestOptions: { prompt: string; options?: ConversationRequest | null }
messageMeta?: MessageMeta | null
} }
interface History { interface History {
@@ -16,11 +30,17 @@ declare namespace Chat {
uuid: number uuid: number
} }
interface Session {
uuid: number
data: Chat[]
sessionUsage: TokenUsage
}
interface ChatState { interface ChatState {
active: number | null active: number | null
usingContext: boolean; usingContext: boolean;
history: History[] history: History[]
chat: { uuid: number; data: Chat[] }[] chat: Session[]
} }
interface ConversationRequest { interface ConversationRequest {
@@ -29,18 +49,22 @@ declare namespace Chat {
} }
interface ConversationResponse { interface ConversationResponse {
conversationId: string conversationId?: string
detail: { detail?: {
choices: { finish_reason: string; index: number; logprobs: any; text: string }[] choices: { finish_reason: string; index: number; logprobs: any; text: string }[]
created: number created: number
id: string id: string
model: string model: string
object: string object: string
usage: { completion_tokens: number; prompt_tokens: number; total_tokens: number } usage: TokenUsage
} }
id: string id?: string
parentMessageId: string parentMessageId?: string
role: string role?: string
text: string text: string
usage?: TokenUsage
source?: ReplySource
tokenUsed?: boolean
meta?: MessageMeta | null
} }
} }

View File

@@ -15,6 +15,7 @@ interface Props {
inversion?: boolean inversion?: boolean
error?: boolean error?: boolean
loading?: boolean loading?: boolean
messageMeta?: Chat.MessageMeta | null
} }
interface Emit { interface Emit {
@@ -36,6 +37,42 @@ const asRawText = ref(props.inversion)
const messageRef = ref<HTMLElement>() const messageRef = ref<HTMLElement>()
const sourceLabel = computed(() => {
if (props.inversion)
return ''
switch (props.messageMeta?.source) {
case 'semantic_match':
return t('chat.sourceSemantic')
case 'llm':
return t('chat.sourceLlm')
default:
return ''
}
})
const sourceClass = computed(() => {
switch (props.messageMeta?.source) {
case 'semantic_match':
return 'border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-900/60 dark:bg-sky-950/40 dark:text-sky-300'
case 'llm':
return 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900/60 dark:bg-amber-950/40 dark:text-amber-300'
default:
return 'border-[#d0d7de] bg-white text-[#57606a] dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-300'
}
})
const usageLabel = computed(() => {
const usage = props.messageMeta?.usage
if (!usage)
return ''
if (props.inversion)
return t('chat.promptTokens', { count: usage.prompt_tokens })
return t('chat.completionTokens', { count: usage.completion_tokens })
})
const options = computed(() => { const options = computed(() => {
const common = [ const common = [
{ {
@@ -93,9 +130,22 @@ function handleRegenerate() {
<AvatarComponent :image="inversion" /> <AvatarComponent :image="inversion" />
</div> </div>
<div class="overflow-hidden text-sm " :class="[inversion ? 'items-end' : 'items-start']"> <div class="overflow-hidden text-sm " :class="[inversion ? 'items-end' : 'items-start']">
<p class="text-xs text-[#b4bbc4]" :class="[inversion ? 'text-right' : 'text-left']"> <div class="flex flex-wrap items-center gap-2 text-xs" :class="[inversion ? 'justify-end' : 'justify-start']">
{{ dateTime }} <span class="font-medium text-[#9aa4af] dark:text-neutral-500">{{ dateTime }}</span>
</p> <span
v-if="sourceLabel"
class="rounded-full border px-2.5 py-1 text-[11px] font-medium leading-none"
:class="sourceClass"
>
{{ sourceLabel }}
</span>
<span
v-if="usageLabel"
class="rounded-full border border-violet-200 bg-violet-50 px-2.5 py-1 text-[11px] font-medium leading-none text-violet-700 dark:border-violet-900/60 dark:bg-violet-950/40 dark:text-violet-300"
>
{{ usageLabel }}
</span>
</div>
<div <div
class="flex items-end gap-1 mt-2" class="flex items-end gap-1 mt-2"
:class="[inversion ? 'flex-row-reverse' : 'flex-row']" :class="[inversion ? 'flex-row-reverse' : 'flex-row']"

View File

@@ -37,6 +37,8 @@ const { usingContext, toggleUsingContext } = useUsingContext()
const { uuid } = route.params as { uuid: string } const { uuid } = route.params as { uuid: string }
const dataSources = computed(() => chatStore.getChatByUuid(+uuid)) const dataSources = computed(() => chatStore.getChatByUuid(+uuid))
const sessionUsage = computed(() => chatStore.getSessionUsageByUuid(+uuid))
const sessionTokenText = computed(() => t('chat.sessionTokens', { count: sessionUsage.value.total_tokens }))
const conversationList = computed(() => dataSources.value.filter(item => (!item.inversion && !item.error))) const conversationList = computed(() => dataSources.value.filter(item => (!item.inversion && !item.error)))
const prompt = ref<string>('') const prompt = ref<string>('')
@@ -49,6 +51,102 @@ const promptStore = usePromptStore()
// 使用storeToRefs保证store修改后联想部分能够重新渲染 // 使用storeToRefs保证store修改后联想部分能够重新渲染
const { promptList: promptTemplate } = storeToRefs<any>(promptStore) const { promptList: promptTemplate } = storeToRefs<any>(promptStore)
function createEmptyUsage(): Chat.TokenUsage {
return {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
}
}
function mergeUsage(current: Chat.TokenUsage, next?: Partial<Chat.TokenUsage> | null): Chat.TokenUsage {
return {
prompt_tokens: current.prompt_tokens + Number(next?.prompt_tokens || 0),
completion_tokens: current.completion_tokens + Number(next?.completion_tokens || 0),
total_tokens: current.total_tokens + Number(next?.total_tokens || 0),
}
}
function normalizeUsage(usage?: Partial<Chat.TokenUsage> | null): Chat.TokenUsage | null {
if (!usage)
return null
return {
prompt_tokens: Number(usage.prompt_tokens || 0),
completion_tokens: Number(usage.completion_tokens || 0),
total_tokens: Number(usage.total_tokens || 0),
}
}
function normalizeResponseMeta(data: Chat.ConversationResponse): {
source: Chat.ReplySource | null
tokenUsed?: boolean
usage: Chat.TokenUsage | null
} {
const usage = normalizeUsage(data.meta?.usage || data.usage || data.detail?.usage || null)
const rawSource = data.meta?.source ?? data.source ?? null
const source = rawSource === 'llm' ? 'llm' : rawSource ? 'semantic_match' : null
const tokenUsed = data.meta?.tokenUsed ?? data.tokenUsed ?? (usage ? usage.total_tokens > 0 : undefined)
return {
source,
tokenUsed,
usage,
}
}
function buildPromptUsage(usage: Chat.TokenUsage): Chat.TokenUsage {
return {
prompt_tokens: usage.prompt_tokens,
completion_tokens: 0,
total_tokens: usage.prompt_tokens,
}
}
function buildCompletionUsage(usage: Chat.TokenUsage): Chat.TokenUsage {
return {
prompt_tokens: 0,
completion_tokens: usage.completion_tokens,
total_tokens: usage.completion_tokens,
}
}
function findQuestionIndex(answerIndex: number) {
for (let current = answerIndex - 1; current >= 0; current -= 1) {
if (dataSources.value[current]?.inversion)
return current
}
return -1
}
function updateMessageMeta(questionIndex: number, answerIndex: number, usage: Chat.TokenUsage, source: Chat.ReplySource | null, tokenUsed?: boolean) {
if (questionIndex >= 0) {
updateChatSome(
+uuid,
questionIndex,
{
messageMeta: {
tokenUsed,
usage: buildPromptUsage(usage),
},
},
)
}
updateChatSome(
+uuid,
answerIndex,
{
messageMeta: {
source,
tokenUsed,
usage: buildCompletionUsage(usage),
},
},
)
}
// 未知原因刷新页面loading 状态不会重置,手动重置 // 未知原因刷新页面loading 状态不会重置,手动重置
dataSources.value.forEach((item, index) => { dataSources.value.forEach((item, index) => {
if (item.loading) if (item.loading)
@@ -79,10 +177,13 @@ async function onConversation() {
error: false, error: false,
conversationOptions: null, conversationOptions: null,
requestOptions: { prompt: message, options: null }, requestOptions: { prompt: message, options: null },
messageMeta: null,
}, },
) )
scrollToBottom() scrollToBottom()
const questionIndex = dataSources.value.length - 1
loading.value = true loading.value = true
prompt.value = '' prompt.value = ''
@@ -102,13 +203,18 @@ async function onConversation() {
error: false, error: false,
conversationOptions: null, conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } }, requestOptions: { prompt: message, options: { ...options } },
messageMeta: null,
}, },
) )
scrollToBottom() scrollToBottom()
const answerIndex = dataSources.value.length - 1
try { try {
let lastText = '' let lastText = ''
let accumulatedUsage = createEmptyUsage()
const fetchChatAPIOnce = async () => { const fetchChatAPIOnce = async () => {
let usageApplied = false
await fetchChatAPIProcess<Chat.ConversationResponse>({ await fetchChatAPIProcess<Chat.ConversationResponse>({
prompt: message, prompt: message,
options, options,
@@ -122,22 +228,37 @@ async function onConversation() {
if (lastIndex !== -1) if (lastIndex !== -1)
chunk = responseText.substring(lastIndex) chunk = responseText.substring(lastIndex)
try { try {
const data = JSON.parse(chunk) const data = JSON.parse(chunk.trim()) as Chat.ConversationResponse
const responseMeta = normalizeResponseMeta(data)
const nextUsage = responseMeta.usage && !usageApplied
? mergeUsage(accumulatedUsage, responseMeta.usage)
: accumulatedUsage
updateChat( updateChat(
+uuid, +uuid,
dataSources.value.length - 1, answerIndex,
{ {
dateTime: new Date().toLocaleString(), dateTime: new Date().toLocaleString(),
text: lastText + data.text ?? '', text: lastText + data.text ?? '',
inversion: false, inversion: false,
error: false, error: false,
loading: false, loading: false,
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id }, conversationOptions: data.conversationId && data.id ? { conversationId: data.conversationId, parentMessageId: data.id } : null,
requestOptions: { prompt: message, options: { ...options } }, requestOptions: { prompt: message, options: { ...options } },
messageMeta: getChatByUuidAndIndex(+uuid, answerIndex)?.messageMeta ?? null,
}, },
) )
if (openLongReply && data.detail.choices[0].finish_reason === 'length') { if (responseMeta.source || responseMeta.usage || responseMeta.tokenUsed !== undefined)
updateMessageMeta(questionIndex, answerIndex, nextUsage, responseMeta.source, responseMeta.tokenUsed)
if (responseMeta.usage && !usageApplied) {
accumulatedUsage = nextUsage
usageApplied = true
chatStore.addSessionUsageByUuid(+uuid, responseMeta.usage)
}
if (openLongReply && data.detail?.choices?.[0]?.finish_reason === 'length' && data.id) {
options.parentMessageId = data.id options.parentMessageId = data.id
lastText = data.text lastText = data.text
message = '' message = ''
@@ -161,7 +282,7 @@ async function onConversation() {
if (error.message === 'canceled') { if (error.message === 'canceled') {
updateChatSome( updateChatSome(
+uuid, +uuid,
dataSources.value.length - 1, answerIndex,
{ {
loading: false, loading: false,
}, },
@@ -170,12 +291,12 @@ async function onConversation() {
return return
} }
const currentChat = getChatByUuidAndIndex(+uuid, dataSources.value.length - 1) const currentChat = getChatByUuidAndIndex(+uuid, answerIndex)
if (currentChat?.text && currentChat.text !== '') { if (currentChat?.text && currentChat.text !== '') {
updateChatSome( updateChatSome(
+uuid, +uuid,
dataSources.value.length - 1, answerIndex,
{ {
text: `${currentChat.text}\n[${errorMessage}]`, text: `${currentChat.text}\n[${errorMessage}]`,
error: false, error: false,
@@ -187,7 +308,7 @@ async function onConversation() {
updateChat( updateChat(
+uuid, +uuid,
dataSources.value.length - 1, answerIndex,
{ {
dateTime: new Date().toLocaleString(), dateTime: new Date().toLocaleString(),
text: errorMessage, text: errorMessage,
@@ -196,6 +317,7 @@ async function onConversation() {
loading: false, loading: false,
conversationOptions: null, conversationOptions: null,
requestOptions: { prompt: message, options: { ...options } }, requestOptions: { prompt: message, options: { ...options } },
messageMeta: getChatByUuidAndIndex(+uuid, answerIndex)?.messageMeta ?? null,
}, },
) )
scrollToBottomIfAtBottom() scrollToBottomIfAtBottom()
@@ -222,6 +344,9 @@ async function onRegenerate(index: number) {
loading.value = true loading.value = true
let accumulatedUsage = createEmptyUsage()
const questionIndex = findQuestionIndex(index)
updateChat( updateChat(
+uuid, +uuid,
index, index,
@@ -232,13 +357,15 @@ async function onRegenerate(index: number) {
error: false, error: false,
loading: true, loading: true,
conversationOptions: null, conversationOptions: null,
requestOptions: { prompt: message, ...options }, requestOptions: { prompt: message, options: { ...options } },
messageMeta: null,
}, },
) )
try { try {
let lastText = '' let lastText = ''
const fetchChatAPIOnce = async () => { const fetchChatAPIOnce = async () => {
let usageApplied = false
await fetchChatAPIProcess<Chat.ConversationResponse>({ await fetchChatAPIProcess<Chat.ConversationResponse>({
prompt: message, prompt: message,
options, options,
@@ -252,7 +379,12 @@ async function onRegenerate(index: number) {
if (lastIndex !== -1) if (lastIndex !== -1)
chunk = responseText.substring(lastIndex) chunk = responseText.substring(lastIndex)
try { try {
const data = JSON.parse(chunk) const data = JSON.parse(chunk.trim()) as Chat.ConversationResponse
const responseMeta = normalizeResponseMeta(data)
const nextUsage = responseMeta.usage && !usageApplied
? mergeUsage(accumulatedUsage, responseMeta.usage)
: accumulatedUsage
updateChat( updateChat(
+uuid, +uuid,
index, index,
@@ -262,12 +394,22 @@ async function onRegenerate(index: number) {
inversion: false, inversion: false,
error: false, error: false,
loading: false, loading: false,
conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id }, conversationOptions: data.conversationId && data.id ? { conversationId: data.conversationId, parentMessageId: data.id } : null,
requestOptions: { prompt: message, ...options }, requestOptions: { prompt: message, options: { ...options } },
messageMeta: getChatByUuidAndIndex(+uuid, index)?.messageMeta ?? null,
}, },
) )
if (openLongReply && data.detail.choices[0].finish_reason === 'length') { if (responseMeta.source || responseMeta.usage || responseMeta.tokenUsed !== undefined)
updateMessageMeta(questionIndex, index, nextUsage, responseMeta.source, responseMeta.tokenUsed)
if (responseMeta.usage && !usageApplied) {
accumulatedUsage = nextUsage
usageApplied = true
chatStore.addSessionUsageByUuid(+uuid, responseMeta.usage)
}
if (openLongReply && data.detail?.choices?.[0]?.finish_reason === 'length' && data.id) {
options.parentMessageId = data.id options.parentMessageId = data.id
lastText = data.text lastText = data.text
message = '' message = ''
@@ -306,7 +448,8 @@ async function onRegenerate(index: number) {
error: true, error: true,
loading: false, loading: false,
conversationOptions: null, conversationOptions: null,
requestOptions: { prompt: message, ...options }, requestOptions: { prompt: message, options: { ...options } },
messageMeta: getChatByUuidAndIndex(+uuid, index)?.messageMeta ?? null,
}, },
) )
} }
@@ -483,6 +626,12 @@ onUnmounted(() => {
class="w-full max-w-screen-xl m-auto dark:bg-[#101014]" class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"
:class="[isMobile ? 'p-2' : 'p-4']" :class="[isMobile ? 'p-2' : 'p-4']"
> >
<div class="sticky top-0 z-10 flex justify-center px-2 pb-2 pt-2">
<div class="flex items-center gap-2 rounded-full border border-[#d9e2ec] bg-white/88 px-3 py-2 text-xs text-[#52606d] shadow-[0_8px_30px_rgba(15,23,42,0.08)] backdrop-blur-md dark:border-neutral-700 dark:bg-[#121316]/82 dark:text-neutral-200">
<span class="inline-flex h-2 w-2 rounded-full bg-emerald-500" />
<span class="font-medium tracking-[0.02em]">{{ sessionTokenText }}</span>
</div>
</div>
<template v-if="!dataSources.length"> <template v-if="!dataSources.length">
<div class="flex items-center justify-center mt-4 text-center text-neutral-300"> <div class="flex items-center justify-center mt-4 text-center text-neutral-300">
<SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" /> <SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
@@ -499,6 +648,7 @@ onUnmounted(() => {
:inversion="item.inversion" :inversion="item.inversion"
:error="item.error" :error="item.error"
:loading="item.loading" :loading="item.loading"
:message-meta="item.messageMeta"
@regenerate="onRegenerate(index)" @regenerate="onRegenerate(index)"
@delete="handleDelete(index)" @delete="handleDelete(index)"
/> />