mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-15 02:49:16 +08:00
refactor: add shared types and improve ChatStatusBar with i18n
- Add electron/shared/types.ts as single source of truth for TokenUsage and AgentRuntimeStatus across main/preload/renderer - Refactor ChatStatusBar.vue to use shared types and i18n keys instead of hardcoded phase text mappings - Add phaseShort translations for zh-CN and en-US
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Electron 主进程 / Preload / 渲染进程共享的 Agent 类型定义
|
||||||
|
*
|
||||||
|
* 此文件是 AgentRuntimeStatus、TokenUsage 等跨进程类型的唯一定义源。
|
||||||
|
* 所有使用方应从此处导入,避免重复定义导致类型漂移。
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface TokenUsage {
|
||||||
|
promptTokens: number
|
||||||
|
completionTokens: number
|
||||||
|
totalTokens: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentRuntimeStatus {
|
||||||
|
phase: 'preparing' | 'thinking' | 'tool_running' | 'responding' | 'completed' | 'aborted' | 'error'
|
||||||
|
round: number
|
||||||
|
toolsUsed: number
|
||||||
|
currentTool?: string
|
||||||
|
contextTokens: number
|
||||||
|
totalUsage: TokenUsage
|
||||||
|
updatedAt: number
|
||||||
|
}
|
||||||
@@ -6,32 +6,16 @@ import { useToast } from '@nuxt/ui/runtime/composables/useToast.js'
|
|||||||
import { usePromptStore } from '@/stores/prompt'
|
import { usePromptStore } from '@/stores/prompt'
|
||||||
import { useLayoutStore } from '@/stores/layout'
|
import { useLayoutStore } from '@/stores/layout'
|
||||||
import { useLLMStore } from '@/stores/llm'
|
import { useLLMStore } from '@/stores/llm'
|
||||||
|
import type { AgentRuntimeStatus } from '@electron/shared/types'
|
||||||
|
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
interface AgentRuntimeStatusView {
|
|
||||||
phase: 'preparing' | 'thinking' | 'tool_running' | 'responding' | 'completed' | 'aborted' | 'error'
|
|
||||||
round: number
|
|
||||||
toolsUsed: number
|
|
||||||
currentTool?: string
|
|
||||||
contextTokens: number
|
|
||||||
contextWindow: number
|
|
||||||
contextUsage: number
|
|
||||||
totalUsage: { promptTokens: number; completionTokens: number; totalTokens: number }
|
|
||||||
nodeCount?: number
|
|
||||||
tagCount?: number
|
|
||||||
segmentSize?: number
|
|
||||||
checkoutCount?: number
|
|
||||||
activeAnchorNodeId?: string | null
|
|
||||||
updatedAt: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
chatType: 'group' | 'private'
|
chatType: 'group' | 'private'
|
||||||
sessionTokenUsage: { totalTokens: number }
|
sessionTokenUsage: { totalTokens: number }
|
||||||
agentStatus?: AgentRuntimeStatusView | null
|
agentStatus?: AgentRuntimeStatus | null
|
||||||
hasLLMConfig: boolean
|
hasLLMConfig: boolean
|
||||||
isCheckingConfig: boolean
|
isCheckingConfig: boolean
|
||||||
}>()
|
}>()
|
||||||
@@ -67,29 +51,7 @@ const agentPhaseText = computed(() => {
|
|||||||
|
|
||||||
const agentPhaseShortText = computed(() => {
|
const agentPhaseShortText = computed(() => {
|
||||||
if (!props.agentStatus) return ''
|
if (!props.agentStatus) return ''
|
||||||
if (locale.value.startsWith('zh')) {
|
return t(`ai.chat.statusBar.agent.phaseShort.${props.agentStatus.phase}`)
|
||||||
const map: Record<AgentRuntimeStatusView['phase'], string> = {
|
|
||||||
preparing: '准备',
|
|
||||||
thinking: '思考',
|
|
||||||
tool_running: '工具',
|
|
||||||
responding: '生成',
|
|
||||||
completed: '完成',
|
|
||||||
aborted: '停止',
|
|
||||||
error: '错误',
|
|
||||||
}
|
|
||||||
return map[props.agentStatus.phase]
|
|
||||||
}
|
|
||||||
|
|
||||||
const map: Record<AgentRuntimeStatusView['phase'], string> = {
|
|
||||||
preparing: 'Prep',
|
|
||||||
thinking: 'Think',
|
|
||||||
tool_running: 'Tool',
|
|
||||||
responding: 'Reply',
|
|
||||||
completed: 'Done',
|
|
||||||
aborted: 'Stop',
|
|
||||||
error: 'Err',
|
|
||||||
}
|
|
||||||
return map[props.agentStatus.phase]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const agentPhaseClass = computed(() => {
|
const agentPhaseClass = computed(() => {
|
||||||
@@ -113,32 +75,14 @@ const agentPhaseClass = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const contextUsagePercent = computed(() => {
|
|
||||||
if (!props.agentStatus) return 0
|
|
||||||
return Math.round((props.agentStatus.contextUsage || 0) * 100)
|
|
||||||
})
|
|
||||||
|
|
||||||
const contextUsageWidth = computed(() => `${Math.max(0, Math.min(100, contextUsagePercent.value))}%`)
|
|
||||||
|
|
||||||
function formatNumber(value: number): string {
|
function formatNumber(value: number): string {
|
||||||
if (!Number.isFinite(value)) return '0'
|
if (!Number.isFinite(value)) return '0'
|
||||||
return new Intl.NumberFormat().format(Math.max(0, Math.round(value)))
|
return new Intl.NumberFormat().format(Math.max(0, Math.round(value)))
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextUsageText = computed(() => {
|
const contextTokensText = computed(() => {
|
||||||
if (!props.agentStatus) return '--'
|
if (!props.agentStatus) return '--'
|
||||||
return t('ai.chat.statusBar.agent.contextUsage', {
|
return formatCompactNumber(props.agentStatus.contextTokens)
|
||||||
used: formatNumber(props.agentStatus.contextTokens),
|
|
||||||
total: formatNumber(props.agentStatus.contextWindow),
|
|
||||||
percent: contextUsagePercent.value,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const agentStatsText = computed(() => {
|
|
||||||
if (!props.agentStatus) return ''
|
|
||||||
const segment = props.agentStatus.segmentSize ?? 0
|
|
||||||
const tags = props.agentStatus.tagCount ?? 0
|
|
||||||
return t('ai.chat.statusBar.agent.stats', { segment, tags })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function formatCompactNumber(value: number): string {
|
function formatCompactNumber(value: number): string {
|
||||||
@@ -155,8 +99,7 @@ const agentCompactTitle = computed(() => {
|
|||||||
if (!props.agentStatus) return ''
|
if (!props.agentStatus) return ''
|
||||||
return [
|
return [
|
||||||
`${t('ai.chat.statusBar.agent.label')}: ${agentPhaseText.value}`,
|
`${t('ai.chat.statusBar.agent.label')}: ${agentPhaseText.value}`,
|
||||||
contextUsageText.value,
|
`${t('ai.chat.statusBar.agent.contextTokens')}: ${formatNumber(props.agentStatus.contextTokens)}`,
|
||||||
agentStatsText.value,
|
|
||||||
`${t('ai.chat.statusBar.tokenUsageTitle')}: ${totalTokenUsageText.value}`,
|
`${t('ai.chat.statusBar.tokenUsageTitle')}: ${totalTokenUsageText.value}`,
|
||||||
].join('\n')
|
].join('\n')
|
||||||
})
|
})
|
||||||
@@ -359,13 +302,8 @@ async function openAiLogFile() {
|
|||||||
<span class="rounded px-1 py-0.5 text-[10px] font-medium" :class="agentPhaseClass">
|
<span class="rounded px-1 py-0.5 text-[10px] font-medium" :class="agentPhaseClass">
|
||||||
{{ agentPhaseShortText }}
|
{{ agentPhaseShortText }}
|
||||||
</span>
|
</span>
|
||||||
<div class="h-1.5 w-10 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
|
<UIcon name="i-heroicons-document-text" class="h-3 w-3 text-gray-400 dark:text-gray-500" />
|
||||||
<div
|
<span class="text-[10px] text-gray-500 dark:text-gray-400">{{ contextTokensText }}</span>
|
||||||
class="h-full rounded-full bg-pink-500 transition-all duration-300"
|
|
||||||
:style="{ width: contextUsageWidth }"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span class="text-[10px] text-gray-500 dark:text-gray-400">{{ contextUsagePercent }}%</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
},
|
},
|
||||||
"agent": {
|
"agent": {
|
||||||
"label": "Agent",
|
"label": "Agent",
|
||||||
"contextUsage": "{used}/{total} ({percent}%)",
|
"contextTokens": "Context",
|
||||||
"stats": "seg {segment} · tags {tags}",
|
"stats": "seg {segment} · tags {tags}",
|
||||||
"phase": {
|
"phase": {
|
||||||
"preparing": "Preparing",
|
"preparing": "Preparing",
|
||||||
@@ -98,6 +98,15 @@
|
|||||||
"completed": "Completed",
|
"completed": "Completed",
|
||||||
"aborted": "Stopped",
|
"aborted": "Stopped",
|
||||||
"error": "Error"
|
"error": "Error"
|
||||||
|
},
|
||||||
|
"phaseShort": {
|
||||||
|
"preparing": "Prep",
|
||||||
|
"thinking": "Think",
|
||||||
|
"tool_running": "Tool",
|
||||||
|
"responding": "Reply",
|
||||||
|
"completed": "Done",
|
||||||
|
"aborted": "Stop",
|
||||||
|
"error": "Err"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tokenUsageTitle": "Total token usage in this session",
|
"tokenUsageTitle": "Total token usage in this session",
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
},
|
},
|
||||||
"agent": {
|
"agent": {
|
||||||
"label": "Agent",
|
"label": "Agent",
|
||||||
"contextUsage": "{used}/{total} ({percent}%)",
|
"contextTokens": "上下文",
|
||||||
"stats": "段 {segment} · 标签 {tags}",
|
"stats": "段 {segment} · 标签 {tags}",
|
||||||
"phase": {
|
"phase": {
|
||||||
"preparing": "准备中",
|
"preparing": "准备中",
|
||||||
@@ -98,6 +98,15 @@
|
|||||||
"completed": "已完成",
|
"completed": "已完成",
|
||||||
"aborted": "已停止",
|
"aborted": "已停止",
|
||||||
"error": "错误"
|
"error": "错误"
|
||||||
|
},
|
||||||
|
"phaseShort": {
|
||||||
|
"preparing": "准备",
|
||||||
|
"thinking": "思考",
|
||||||
|
"tool_running": "工具",
|
||||||
|
"responding": "生成",
|
||||||
|
"completed": "完成",
|
||||||
|
"aborted": "停止",
|
||||||
|
"error": "错误"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tokenUsageTitle": "本次会话累计 Token 使用量",
|
"tokenUsageTitle": "本次会话累计 Token 使用量",
|
||||||
|
|||||||
Reference in New Issue
Block a user