service 修改 Redis 存储 KV

This commit is contained in:
2026-04-10 11:12:10 +00:00
parent c888ca8844
commit bc82e3e708
25 changed files with 322 additions and 3666 deletions

View File

@@ -50,8 +50,8 @@ export default {
showRawText: 'Show as raw text',
sourceSemantic: 'Semantic Match',
sourceLlm: 'LLM Output',
promptTokens: 'Prompt {count} tokens',
completionTokens: 'Completion {count} tokens',
inputTokens: 'Input {count}',
outputTokens: 'Output {count}',
sessionTokens: 'Session {count} tokens',
},
setting: {

View File

@@ -50,8 +50,8 @@ export default {
showRawText: '显示原文',
sourceSemantic: '语义匹配',
sourceLlm: '大模型输出',
promptTokens: '问题 {count} tokens',
completionTokens: '回答 {count} tokens',
inputTokens: 'Input {count}',
outputTokens: 'Output {count}',
sessionTokens: '本轮消耗 {count} tokens',
},
setting: {

View File

@@ -50,8 +50,8 @@ export default {
showRawText: '顯示原文',
sourceSemantic: '語義匹配',
sourceLlm: '大模型輸出',
promptTokens: '問題 {count} tokens',
completionTokens: '回答 {count} tokens',
inputTokens: 'Input {count}',
outputTokens: 'Output {count}',
sessionTokens: '本輪消耗 {count} tokens',
},
setting: {

View File

@@ -62,15 +62,20 @@ const sourceClass = computed(() => {
}
})
const usageLabel = computed(() => {
const inputUsageLabel = computed(() => {
const usage = props.messageMeta?.usage
if (!usage)
if (!usage || props.inversion)
return ''
if (props.inversion)
return t('chat.promptTokens', { count: usage.prompt_tokens })
return t('chat.inputTokens', { count: usage.prompt_tokens })
})
return t('chat.completionTokens', { count: usage.completion_tokens })
const outputUsageLabel = computed(() => {
const usage = props.messageMeta?.usage
if (!usage || props.inversion)
return ''
return t('chat.outputTokens', { count: usage.completion_tokens })
})
const options = computed(() => {
@@ -130,21 +135,35 @@ function handleRegenerate() {
<AvatarComponent :image="inversion" />
</div>
<div class="overflow-hidden text-sm " :class="[inversion ? 'items-end' : 'items-start']">
<div class="flex flex-wrap items-center gap-2 text-xs" :class="[inversion ? 'justify-end' : 'justify-start']">
<span class="font-medium text-[#9aa4af] dark:text-neutral-500">{{ dateTime }}</span>
<span
v-if="sourceLabel"
class="rounded-full border px-2.5 py-1 text-[11px] font-medium leading-none"
:class="sourceClass"
<div class="flex flex-col gap-2" :class="[inversion ? 'items-end' : 'items-start']">
<span class="text-xs font-medium text-[#9aa4af] dark:text-neutral-500">{{ dateTime }}</span>
<div
v-if="sourceLabel || inputUsageLabel || outputUsageLabel"
class="flex flex-wrap items-center gap-2 rounded-2xl border border-[#e6edf3] bg-white/85 px-2.5 py-2 shadow-[0_10px_25px_rgba(15,23,42,0.06)] backdrop-blur-sm dark:border-neutral-800 dark:bg-[#14161a]/90"
>
{{ 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
v-if="sourceLabel"
class="inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-semibold leading-none"
:class="sourceClass"
>
<SvgIcon icon="ri:radar-line" />
<span>{{ sourceLabel }}</span>
</div>
<div
v-if="inputUsageLabel"
class="inline-flex items-center gap-1.5 rounded-full border border-violet-200 bg-violet-50 px-2.5 py-1 text-[11px] font-semibold leading-none text-violet-700 dark:border-violet-900/60 dark:bg-violet-950/40 dark:text-violet-300"
>
<SvgIcon icon="ri:login-circle-line" />
<span>{{ inputUsageLabel }}</span>
</div>
<div
v-if="outputUsageLabel"
class="inline-flex items-center gap-1.5 rounded-full border border-fuchsia-200 bg-fuchsia-50 px-2.5 py-1 text-[11px] font-semibold leading-none text-fuchsia-700 dark:border-fuchsia-900/60 dark:bg-fuchsia-950/40 dark:text-fuchsia-300"
>
<SvgIcon icon="ri:logout-circle-r-line" />
<span>{{ outputUsageLabel }}</span>
</div>
</div>
</div>
<div
class="flex items-end gap-1 mt-2"

View File

@@ -95,45 +95,7 @@ function normalizeResponseMeta(data: Chat.ConversationResponse): {
}
}
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),
},
},
)
}
function updateMessageMeta(answerIndex: number, usage: Chat.TokenUsage, source: Chat.ReplySource | null, tokenUsed?: boolean) {
updateChatSome(
+uuid,
answerIndex,
@@ -141,7 +103,7 @@ function updateMessageMeta(questionIndex: number, answerIndex: number, usage: Ch
messageMeta: {
source,
tokenUsed,
usage: buildCompletionUsage(usage),
usage,
},
},
)
@@ -182,8 +144,6 @@ async function onConversation() {
)
scrollToBottom()
const questionIndex = dataSources.value.length - 1
loading.value = true
prompt.value = ''
@@ -250,7 +210,7 @@ async function onConversation() {
)
if (responseMeta.source || responseMeta.usage || responseMeta.tokenUsed !== undefined)
updateMessageMeta(questionIndex, answerIndex, nextUsage, responseMeta.source, responseMeta.tokenUsed)
updateMessageMeta(answerIndex, nextUsage, responseMeta.source, responseMeta.tokenUsed)
if (responseMeta.usage && !usageApplied) {
accumulatedUsage = nextUsage
@@ -343,9 +303,7 @@ async function onRegenerate(index: number) {
options = { ...requestOptions.options }
loading.value = true
let accumulatedUsage = createEmptyUsage()
const questionIndex = findQuestionIndex(index)
updateChat(
+uuid,
@@ -401,7 +359,7 @@ async function onRegenerate(index: number) {
)
if (responseMeta.source || responseMeta.usage || responseMeta.tokenUsed !== undefined)
updateMessageMeta(questionIndex, index, nextUsage, responseMeta.source, responseMeta.tokenUsed)
updateMessageMeta(index, nextUsage, responseMeta.source, responseMeta.tokenUsed)
if (responseMeta.usage && !usageApplied) {
accumulatedUsage = nextUsage