前端页面展示内容修改,带totaltoken
This commit is contained in:
@@ -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',
|
||||||
|
|||||||
@@ -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: '设置',
|
||||||
|
|||||||
@@ -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: '設定',
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 } })
|
||||||
|
|||||||
38
ai-chat-web/src/typings/chat.d.ts
vendored
38
ai-chat-web/src/typings/chat.d.ts
vendored
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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']"
|
||||||
|
|||||||
@@ -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)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user