-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add token usage tracking and message actions (#116)
* feat: add token usage tracking and message actions - Add token usage tracking for chat messages - Implement message actions UI with copy and token info - Add pricing constants for different LLM models
- Loading branch information
Showing
14 changed files
with
467 additions
and
22 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import * as Tooltip from '@radix-ui/react-tooltip' | ||
import { Check, CopyIcon } from 'lucide-react' | ||
import { useMemo, useState } from 'react' | ||
|
||
import { ChatAssistantMessage } from '../../types/chat' | ||
import { calculateLLMCost } from '../../utils/price-calculator' | ||
|
||
import LLMResponseInfoPopover from './LLMResponseInfoPopover' | ||
|
||
function CopyButton({ message }: { message: ChatAssistantMessage }) { | ||
const [copied, setCopied] = useState(false) | ||
|
||
const handleCopy = async () => { | ||
await navigator.clipboard.writeText(message.content) | ||
setCopied(true) | ||
setTimeout(() => { | ||
setCopied(false) | ||
}, 1500) | ||
} | ||
|
||
return ( | ||
<Tooltip.Provider delayDuration={0}> | ||
<Tooltip.Root> | ||
<Tooltip.Trigger asChild> | ||
<button> | ||
{copied ? ( | ||
<Check | ||
size={12} | ||
className="smtcmp-assistant-message-actions-icon--copied" | ||
/> | ||
) : ( | ||
<CopyIcon onClick={handleCopy} size={12} /> | ||
)} | ||
</button> | ||
</Tooltip.Trigger> | ||
<Tooltip.Portal> | ||
<Tooltip.Content className="smtcmp-tooltip-content"> | ||
Copy message | ||
</Tooltip.Content> | ||
</Tooltip.Portal> | ||
</Tooltip.Root> | ||
</Tooltip.Provider> | ||
) | ||
} | ||
|
||
function LLMResponesInfoButton({ message }: { message: ChatAssistantMessage }) { | ||
const cost = useMemo<number | null>(() => { | ||
if (!message.metadata?.model || !message.metadata?.usage) { | ||
return 0 | ||
} | ||
return calculateLLMCost({ | ||
model: message.metadata.model, | ||
usage: message.metadata.usage, | ||
}) | ||
}, [message]) | ||
|
||
return ( | ||
<Tooltip.Provider delayDuration={0}> | ||
<Tooltip.Root> | ||
<Tooltip.Trigger asChild> | ||
<div> | ||
<LLMResponseInfoPopover | ||
usage={message.metadata?.usage} | ||
estimatedPrice={cost} | ||
model={message.metadata?.model?.model} | ||
/> | ||
</div> | ||
</Tooltip.Trigger> | ||
<Tooltip.Portal> | ||
<Tooltip.Content className="smtcmp-tooltip-content"> | ||
View details | ||
</Tooltip.Content> | ||
</Tooltip.Portal> | ||
</Tooltip.Root> | ||
</Tooltip.Provider> | ||
) | ||
} | ||
|
||
export default function AssistantMessageActions({ | ||
message, | ||
}: { | ||
message: ChatAssistantMessage | ||
}) { | ||
return ( | ||
<div className="smtcmp-assistant-message-actions"> | ||
<LLMResponesInfoButton message={message} /> | ||
<CopyButton message={message} /> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import * as Popover from '@radix-ui/react-popover' | ||
import { | ||
ArrowDown, | ||
ArrowRightLeft, | ||
ArrowUp, | ||
Coins, | ||
Cpu, | ||
Info, | ||
} from 'lucide-react' | ||
|
||
import { ResponseUsage } from '../../types/llm/response' | ||
|
||
type LLMResponseInfoProps = { | ||
usage?: ResponseUsage | ||
estimatedPrice: number | null | ||
model?: string | ||
} | ||
|
||
export default function LLMResponseInfoPopover({ | ||
usage, | ||
estimatedPrice, | ||
model, | ||
}: LLMResponseInfoProps) { | ||
return ( | ||
<Popover.Root> | ||
<Popover.Trigger asChild> | ||
<button> | ||
<Info className="smtcmp-llm-info-icon--trigger" size={12} /> | ||
</button> | ||
</Popover.Trigger> | ||
{usage ? ( | ||
<Popover.Content className="smtcmp-popover-content smtcmp-llm-info-content"> | ||
<div className="smtcmp-llm-info-header">LLM Response Information</div> | ||
<div className="smtcmp-llm-info-tokens"> | ||
<div className="smtcmp-llm-info-tokens-header">Token Count</div> | ||
<div className="smtcmp-llm-info-tokens-grid"> | ||
<div className="smtcmp-llm-info-token-row"> | ||
<ArrowUp className="smtcmp-llm-info-icon--input" /> | ||
<span>Input:</span> | ||
<span className="smtcmp-llm-info-token-value"> | ||
{usage.prompt_tokens} | ||
</span> | ||
</div> | ||
<div className="smtcmp-llm-info-token-row"> | ||
<ArrowDown className="smtcmp-llm-info-icon--output" /> | ||
<span>Output:</span> | ||
<span className="smtcmp-llm-info-token-value"> | ||
{usage.completion_tokens} | ||
</span> | ||
</div> | ||
<div className="smtcmp-llm-info-token-row smtcmp-llm-info-token-total"> | ||
<ArrowRightLeft className="smtcmp-llm-info-icon--total" /> | ||
<span>Total:</span> | ||
<span className="smtcmp-llm-info-token-value"> | ||
{usage.total_tokens} | ||
</span> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="smtcmp-llm-info-footer-row"> | ||
<Coins className="smtcmp-llm-info-icon--footer" /> | ||
<span>Estimated Price:</span> | ||
<span className="smtcmp-llm-info-footer-value"> | ||
{estimatedPrice === null | ||
? 'Not available' | ||
: `$${estimatedPrice.toFixed(4)}`} | ||
</span> | ||
</div> | ||
<div className="smtcmp-llm-info-footer-row"> | ||
<Cpu className="smtcmp-llm-info-icon--footer" /> | ||
<span>Model:</span> | ||
<span className="smtcmp-llm-info-footer-value smtcmp-llm-info-model"> | ||
{model ?? 'Not available'} | ||
</span> | ||
</div> | ||
</Popover.Content> | ||
) : ( | ||
<Popover.Content className="smtcmp-popover-content"> | ||
<div>Usage statistics are not available for this model</div> | ||
</Popover.Content> | ||
)} | ||
</Popover.Root> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.