feat(chat): add token & cost estimator badges (M9.6)
Per-message token count badge (e.g. "125 tok", "1.2k tok") next to timestamp. ContextBar tooltip shows running total and estimated cost based on model pricing table. Pricing for Claude Haiku/Sonnet/Opus. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
210fa7d3c6
commit
55faf5043a
@@ -205,6 +205,8 @@
|
||||
<div v-if="!isEditing" class="flex items-center gap-2 mt-1.5">
|
||||
<span class="text-[10px] select-none text-white/30">{{ formattedTime }}</span>
|
||||
<span v-if="message.editedAt" class="text-[10px] text-white/25 select-none">(edited)</span>
|
||||
<span v-if="showTokenCount" class="text-[10px] text-white/20 select-none" :title="`~${estimatedTokens} tokens`">{{ tokenLabel }}</span>
|
||||
<span v-if="message.feedback" class="text-[10px] select-none">{{ message.feedback === 'up' ? '👍' : '👎' }}</span>
|
||||
<button
|
||||
v-if="inlineFilms.length > 1"
|
||||
class="text-[10px] text-accent/70 hover:text-accent transition-colors"
|
||||
@@ -457,6 +459,15 @@ const formattedTime = computed(() => {
|
||||
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
})
|
||||
|
||||
// Token estimate (~4 chars per token)
|
||||
const estimatedTokens = computed(() => Math.ceil(props.message.content.length / 4))
|
||||
const showTokenCount = computed(() => props.message.content.length > 20)
|
||||
const tokenLabel = computed(() => {
|
||||
const t = estimatedTokens.value
|
||||
if (t >= 1000) return `${(t / 1000).toFixed(1)}k tok`
|
||||
return `${t} tok`
|
||||
})
|
||||
|
||||
function openPanelWithContext() {
|
||||
updatePanelFromText(props.message.content, props.triggeringQuery, props.message.webResults ?? [])
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
@scroll-to-message="scrollToMessageIndex"
|
||||
/>
|
||||
|
||||
<ContextBar :messages="messages" />
|
||||
<ContextBar :messages="messages" :active-model="activeModel" />
|
||||
|
||||
<PersonaSelector />
|
||||
<MemoryPanel />
|
||||
@@ -144,7 +144,7 @@ defineEmits<{
|
||||
}>()
|
||||
|
||||
const chatStore = useChatStore()
|
||||
const { sendMessage, stopGeneration, editAndResend, regenerateLastResponse } = useAI()
|
||||
const { sendMessage, stopGeneration, editAndResend, regenerateLastResponse, activeModel } = useAI()
|
||||
const { updatePanelFromText, panelOpen, activeTab, availableTabs, setActiveTab } = useContentPanel()
|
||||
import { useCodeContext } from '@/composables/useCodeContext'
|
||||
const codeContext = useCodeContext()
|
||||
|
||||
@@ -25,6 +25,7 @@ import type { Message } from '@aiui/core/types/message'
|
||||
const props = defineProps<{
|
||||
messages: Message[]
|
||||
contextWindow?: number
|
||||
activeModel?: string
|
||||
}>()
|
||||
|
||||
// Default context window sizes per model (tokens)
|
||||
@@ -44,9 +45,30 @@ const percentage = computed(() => {
|
||||
return (estimatedTokens.value / maxTokens.value) * 100
|
||||
})
|
||||
|
||||
// Model pricing per million tokens (input/output)
|
||||
const MODEL_PRICING: Record<string, { input: number; output: number }> = {
|
||||
'claude-haiku-4.5': { input: 0.80, output: 4.00 },
|
||||
'claude-sonnet-4': { input: 3.00, output: 15.00 },
|
||||
'claude-opus-4': { input: 15.00, output: 75.00 },
|
||||
}
|
||||
|
||||
const estimatedCost = computed(() => {
|
||||
const pricing = MODEL_PRICING[props.activeModel ?? '']
|
||||
if (!pricing) return null
|
||||
// Rough split: 70% input, 30% output
|
||||
const inputTokens = estimatedTokens.value * 0.7
|
||||
const outputTokens = estimatedTokens.value * 0.3
|
||||
const cost = (inputTokens * pricing.input + outputTokens * pricing.output) / 1_000_000
|
||||
return cost
|
||||
})
|
||||
|
||||
const tooltipText = computed(() => {
|
||||
const est = estimatedTokens.value.toLocaleString()
|
||||
const max = maxTokens.value.toLocaleString()
|
||||
return `~${est} / ${max} tokens used`
|
||||
let text = `~${est} / ${max} tokens used`
|
||||
if (estimatedCost.value !== null) {
|
||||
text += ` · ~$${estimatedCost.value.toFixed(4)}`
|
||||
}
|
||||
return text
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user