feat(ai): add ai context usage info
- Upgraded the version of `@folo-services/ai-tools` from 0.2.20 to 0.2.31 in the package.json file. - Introduced a new `TokenUsagePill` component to display token usage information in the AI chat message. - Updated various components to utilize the new `TokenUsagePill` for better visibility of token metrics. - Refactored type definitions in the chat store and related components to improve type safety and clarity. These changes aim to enhance the user experience by providing clearer insights into token usage and improving the overall structure of the AI chat components. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
a05c3950db
commit
70bf3fad2b
|
|
@ -119,7 +119,7 @@
|
|||
"@follow/models": "workspace:*",
|
||||
"@follow/types": "workspace:*",
|
||||
"@follow/utils": "workspace:*",
|
||||
"@folo-services/ai-tools": "0.2.20",
|
||||
"@folo-services/ai-tools": "0.2.31",
|
||||
"@types/node": "24.0.10",
|
||||
"@vite-pwa/assets-generator": "1.0.0",
|
||||
"fake-indexeddb": "6.0.1",
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ const AIDisplayFeedsPartBase = ({
|
|||
return (
|
||||
<DisplayCardWrapper title={title || "RSS Feeds"} emoji="📡" description={`${totalFeeds} feeds`}>
|
||||
{/* Statistics Overview */}
|
||||
<div className="@[600px]:grid-cols-4 grid grid-cols-2 gap-4 md:grid-cols-4">
|
||||
<div className="@[600px]:grid-cols-3 @[400px]:grid-cols-2 grid grid-cols-1 gap-4">
|
||||
<StatCard title="Total Feeds" value={totalFeeds} emoji="📊" />
|
||||
<StatCard
|
||||
title="Active Feeds"
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import {
|
|||
useMessages,
|
||||
} from "~/modules/ai-chat/store/hooks"
|
||||
|
||||
import type { AIChatContextBlock } from "../../store/types"
|
||||
import { convertLexicalToMarkdown } from "../../utils/lexical-markdown"
|
||||
import { GlobalFileDropZone } from "../file/GlobalFileDropZone"
|
||||
import { AIErrorFallback } from "./AIErrorFallback"
|
||||
|
|
@ -119,7 +120,7 @@ const ChatInterfaceContent = ({ centerInputOnEmpty }: ChatInterfaceProps) => {
|
|||
(message: string | EditorState, editor: LexicalEditor | null) => {
|
||||
resetScrollState()
|
||||
|
||||
const blocks = [] as any[]
|
||||
const blocks = [] as AIChatContextBlock[]
|
||||
|
||||
for (const block of blockActions.getBlocks()) {
|
||||
if (block.type === "fileAttachment" && block.attachment.serverUrl) {
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ import * as React from "react"
|
|||
import { toast } from "sonner"
|
||||
|
||||
import { copyToClipboard } from "~/lib/clipboard"
|
||||
import type { BizUIMetadata, BizUITools } from "~/modules/ai-chat/store/types"
|
||||
import type { BizUIMessage, BizUIMetadata, BizUITools } from "~/modules/ai-chat/store/types"
|
||||
|
||||
import { MentionPlugin } from "../../editor"
|
||||
import type { RichTextPart } from "../../types/ChatSession"
|
||||
import { convertLexicalToMarkdown } from "../../utils/lexical-markdown"
|
||||
import { AIMessageParts } from "./AIMessageParts"
|
||||
import { TokenUsagePill } from "./TokenUsagePill"
|
||||
|
||||
export interface ChatMessage {
|
||||
id: string
|
||||
|
|
@ -22,7 +23,7 @@ export interface ChatMessage {
|
|||
}
|
||||
|
||||
interface AIChatMessageProps {
|
||||
message: UIMessage<BizUIMetadata, UIDataTypes, BizUITools>
|
||||
message: BizUIMessage
|
||||
}
|
||||
|
||||
// Utility function for converting message to markdown
|
||||
|
|
@ -96,6 +97,16 @@ export const AIChatMessage: React.FC<AIChatMessageProps> = React.memo(({ message
|
|||
<i className="i-mgc-copy-2-cute-re size-3" />
|
||||
<span>Copy</span>
|
||||
</button>
|
||||
{message.metadata && (
|
||||
<TokenUsagePill metadata={message.metadata}>
|
||||
<button
|
||||
type="button"
|
||||
className="text-text-secondary hover:bg-fill-tertiary flex items-center gap-1 rounded-md px-2 py-1 text-xs transition-colors"
|
||||
>
|
||||
<i className="i-mgc-information-cute-re size-3" />
|
||||
</button>
|
||||
</TokenUsagePill>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="h-6" />
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export const AIDataBlockPart: React.FC<AIDataBlockPartProps> = React.memo(({ blo
|
|||
<div className="min-w-0 max-w-full text-left">
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex flex-wrap items-center gap-1.5 rounded-lg px-2 py-1",
|
||||
"inline-flex flex-wrap items-center gap-1.5 rounded-lg py-1 pl-2 pr-1",
|
||||
"bg-fill-secondary border-border/50 border",
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
import { Tooltip, TooltipContent, TooltipTrigger } from "@follow/components/ui/tooltip/index.js"
|
||||
import type { BizUIMetadata } from "@folo-services/ai-tools"
|
||||
import * as React from "react"
|
||||
|
||||
import { formatTokenCountString } from "~/modules/settings/tabs/ai/usage/utils"
|
||||
|
||||
interface TokenUsagePillProps {
|
||||
metadata: BizUIMetadata | undefined
|
||||
className?: string
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const formatDuration = (ms: number): string => {
|
||||
if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`
|
||||
return `${ms}ms`
|
||||
}
|
||||
|
||||
export const TokenUsagePill: React.FC<TokenUsagePillProps> = ({ metadata, children }) => {
|
||||
if (!metadata) return null
|
||||
|
||||
const hasReasoningTokens = metadata.reasoningTokens != null && metadata.reasoningTokens > 0
|
||||
const hasCachedInputTokens = metadata.cachedInputTokens != null && metadata.cachedInputTokens > 0
|
||||
const hasBillingMultiplier =
|
||||
metadata.billingMultiplier != null && metadata.billingMultiplier !== 1
|
||||
const hasDuration = metadata.duration != null
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||
<TooltipContent side="top" align="center" sideOffset={8}>
|
||||
<div className="mb-2 flex flex-col gap-2">
|
||||
<div className="text-text text-xs">Model Info</div>
|
||||
<div className="text-text-secondary font-mono text-xs">
|
||||
{metadata.modelUsed ?? "Unknown"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2 text-xs">
|
||||
<div className="text-text font-medium">Token Usage</div>
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
{metadata.totalTokens != null && (
|
||||
<div className="flex justify-between gap-2">
|
||||
<span className="text-text-secondary">Total:</span>
|
||||
<span className="text-text font-mono">
|
||||
{formatTokenCountString(metadata.totalTokens)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{metadata.billedTokens != null && (
|
||||
<div className="flex justify-between gap-2">
|
||||
<span className="text-text-secondary">Billed:</span>
|
||||
<span className="text-accent font-mono">
|
||||
{formatTokenCountString(metadata.billedTokens)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{metadata.contextTokens != null && (
|
||||
<div className="flex justify-between gap-2">
|
||||
<span className="text-text-secondary">Context:</span>
|
||||
<span className="text-text font-mono">
|
||||
{formatTokenCountString(metadata.contextTokens)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{metadata.outputTokens != null && (
|
||||
<div className="flex justify-between gap-2">
|
||||
<span className="text-text-secondary">Output:</span>
|
||||
<span className="text-text font-mono">
|
||||
{formatTokenCountString(metadata.outputTokens)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{hasReasoningTokens && (
|
||||
<div className="flex justify-between gap-2">
|
||||
<span className="text-text-secondary">Reasoning:</span>
|
||||
<span className="text-text font-mono">
|
||||
{formatTokenCountString(metadata.reasoningTokens!)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{hasCachedInputTokens && (
|
||||
<div className="flex justify-between gap-2">
|
||||
<span className="text-text-secondary">Cached:</span>
|
||||
<span className="text-text font-mono">
|
||||
{formatTokenCountString(metadata.cachedInputTokens!)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{(hasDuration || hasBillingMultiplier) && (
|
||||
<>
|
||||
<hr className="border-fill-secondary" />
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
{hasDuration && (
|
||||
<div className="flex justify-between gap-2">
|
||||
<span className="text-text-secondary">Duration:</span>
|
||||
<span className="text-text font-mono">
|
||||
{formatDuration(metadata.duration!)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{hasBillingMultiplier && (
|
||||
<div className="flex justify-between gap-2">
|
||||
<span className="text-text-secondary">Multiplier:</span>
|
||||
<span className="text-text font-mono">{metadata.billingMultiplier!}×</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
import { Spring } from "@follow/components/constants/spring.js"
|
||||
import { stopPropagation, thenable } from "@follow/utils"
|
||||
import type { UIDataTypes, UIMessage } from "ai"
|
||||
import type { LexicalEditor, SerializedEditorState } from "lexical"
|
||||
import { AnimatePresence, m } from "motion/react"
|
||||
import * as React from "react"
|
||||
|
||||
import { useEditingMessageId, useSetEditingMessageId } from "~/modules/ai-chat/atoms/session"
|
||||
import { useChatActions } from "~/modules/ai-chat/store/hooks"
|
||||
import type { AIChatContextBlock, BizUIMetadata, BizUITools } from "~/modules/ai-chat/store/types"
|
||||
import type { AIChatContextBlock, BizUIMessage } from "~/modules/ai-chat/store/types"
|
||||
|
||||
import type { RichTextPart } from "../../types/ChatSession"
|
||||
import { convertLexicalToMarkdown } from "../../utils/lexical-markdown"
|
||||
|
|
@ -16,7 +15,7 @@ import { EditableMessage } from "./EditableMessage"
|
|||
import { UserMessageParts } from "./UserMessageParts"
|
||||
|
||||
interface UserChatMessageProps {
|
||||
message: UIMessage<BizUIMetadata, UIDataTypes, BizUITools>
|
||||
message: BizUIMessage
|
||||
}
|
||||
|
||||
export const UserChatMessage: React.FC<UserChatMessageProps> = React.memo(({ message }) => {
|
||||
|
|
|
|||
|
|
@ -157,8 +157,6 @@ class AIPersistServiceStatic {
|
|||
messages
|
||||
.filter((message) => message.parts.length > 0)
|
||||
.map((message) => {
|
||||
const convertedParts = message.parts as any[]
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
chatId,
|
||||
|
|
@ -169,7 +167,7 @@ class AIPersistServiceStatic {
|
|||
finishedAt: message.metadata?.finishTime
|
||||
? new Date(message.metadata.finishTime)
|
||||
: undefined,
|
||||
messageParts: convertedParts,
|
||||
messageParts: message.parts,
|
||||
metadata: message.metadata,
|
||||
} as typeof aiChatMessagesTable.$inferInsert
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import type { ChatSlice } from "./types"
|
|||
export class ChatSliceActions {
|
||||
constructor(
|
||||
private params: Parameters<StateCreator<ChatSlice, [], [], ChatSlice>>,
|
||||
private chatInstance: ZustandChat<BizUIMessage>,
|
||||
private chatInstance: ZustandChat,
|
||||
) {
|
||||
return autoBindThis(this)
|
||||
}
|
||||
|
|
@ -182,7 +182,7 @@ export class ChatSliceActions {
|
|||
this.chatInstance.destroy()
|
||||
|
||||
// Create new chat instance
|
||||
const newChatInstance = new ZustandChat<BizUIMessage>(
|
||||
const newChatInstance = new ZustandChat(
|
||||
{
|
||||
id: newChatId,
|
||||
messages: [],
|
||||
|
|
@ -221,7 +221,7 @@ export class ChatSliceActions {
|
|||
this.chatInstance.destroy()
|
||||
|
||||
// Create new chat instance with loaded messages
|
||||
const newChatInstance = new ZustandChat<BizUIMessage>(
|
||||
const newChatInstance = new ZustandChat(
|
||||
{
|
||||
id: chatId,
|
||||
messages,
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import { ZustandChatState } from "./chat-state"
|
|||
import type { ChatSlice } from "./types"
|
||||
|
||||
// Custom Chat class that uses Zustand-integrated state
|
||||
export class ZustandChat<UI_MESSAGE extends BizUIMessage> extends AbstractChat<UI_MESSAGE> {
|
||||
override state: ZustandChatState<UI_MESSAGE>
|
||||
export class ZustandChat extends AbstractChat<BizUIMessage> {
|
||||
override state: ZustandChatState
|
||||
#unsubscribeFns: (() => void)[] = []
|
||||
|
||||
constructor(
|
||||
{ messages, ...init }: ChatInit<UI_MESSAGE>,
|
||||
{ messages, ...init }: ChatInit<BizUIMessage>,
|
||||
updateZustandState: (updater: (state: ChatSlice) => ChatSlice) => void,
|
||||
) {
|
||||
const state = new ZustandChatState(messages, updateZustandState, init.id || "")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
/* eslint-disable unicorn/no-for-loop */
|
||||
import type { ChatState, ChatStatus } from "ai"
|
||||
import { throttle } from "es-toolkit/compat"
|
||||
import { produce } from "immer"
|
||||
|
||||
import { AIPersistService } from "../../services"
|
||||
import { ChatStateEventEmitter } from "../event-system/event-emitter"
|
||||
|
|
@ -7,14 +9,14 @@ import type { BizUIMessage } from "../types"
|
|||
import type { ChatSlice } from "./types"
|
||||
|
||||
// Zustand Chat State that implements AI SDK ChatState interface
|
||||
export class ZustandChatState<UI_MESSAGE extends BizUIMessage> implements ChatState<UI_MESSAGE> {
|
||||
#messages: UI_MESSAGE[]
|
||||
export class ZustandChatState implements ChatState<BizUIMessage> {
|
||||
#messages: BizUIMessage[]
|
||||
#status: ChatStatus = "ready"
|
||||
#error: Error | undefined = undefined
|
||||
#eventEmitter = new ChatStateEventEmitter<UI_MESSAGE>()
|
||||
#eventEmitter = new ChatStateEventEmitter()
|
||||
|
||||
constructor(
|
||||
initialMessages: UI_MESSAGE[] = [],
|
||||
initialMessages: BizUIMessage[] = [],
|
||||
private updateZustandState: (updater: (state: ChatSlice) => ChatSlice) => void,
|
||||
private chatId: string,
|
||||
) {
|
||||
|
|
@ -25,10 +27,36 @@ export class ZustandChatState<UI_MESSAGE extends BizUIMessage> implements ChatSt
|
|||
#setupEventHandlers(): void {
|
||||
// Setup event handlers for automatic Zustand synchronization
|
||||
this.#eventEmitter.on("messages", ({ messages }) => {
|
||||
this.updateZustandState((state) => ({
|
||||
...state,
|
||||
messages: [...messages],
|
||||
}))
|
||||
this.updateZustandState(
|
||||
produce((state) => {
|
||||
const stateMessages = state.messages
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const message = messages[i]!
|
||||
if (!stateMessages[i]) {
|
||||
stateMessages[i] = structuredClone(message) as any
|
||||
} else {
|
||||
const stateMessage = stateMessages[i]!
|
||||
stateMessage.id = message.id
|
||||
|
||||
for (let j = 0; j < message.parts.length; j++) {
|
||||
const statePart = stateMessage.parts[j] || {}
|
||||
const messagePart = message.parts[j]!
|
||||
|
||||
Object.assign(statePart, messagePart)
|
||||
stateMessage.parts[j] = statePart as any
|
||||
}
|
||||
|
||||
stateMessage.parts.length = message.parts.length
|
||||
|
||||
stateMessage.role = message.role
|
||||
|
||||
stateMessage.metadata = stateMessage.metadata ?? {}
|
||||
Object.assign(stateMessage.metadata, message.metadata)
|
||||
}
|
||||
}
|
||||
stateMessages.length = messages.length
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
this.#eventEmitter.on("status", ({ status }) => {
|
||||
|
|
@ -69,19 +97,20 @@ export class ZustandChatState<UI_MESSAGE extends BizUIMessage> implements ChatSt
|
|||
this.#eventEmitter.emit("error", { error: newError })
|
||||
}
|
||||
|
||||
get messages(): UI_MESSAGE[] {
|
||||
get messages(): BizUIMessage[] {
|
||||
return this.#messages
|
||||
}
|
||||
|
||||
set messages(newMessages: UI_MESSAGE[]) {
|
||||
set messages(newMessages: BizUIMessage[]) {
|
||||
this.#messages = [...newMessages]
|
||||
|
||||
this.#eventEmitter.emit("messages", { messages: this.#messages })
|
||||
|
||||
// Auto-persist messages when they change
|
||||
this.#persistMessages()
|
||||
}
|
||||
|
||||
pushMessage = (message: UI_MESSAGE) => {
|
||||
pushMessage = (message: BizUIMessage) => {
|
||||
this.messages = this.#messages.concat(message)
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +120,7 @@ export class ZustandChatState<UI_MESSAGE extends BizUIMessage> implements ChatSt
|
|||
this.messages = this.#messages.slice(0, -1)
|
||||
}
|
||||
|
||||
replaceMessage = (index: number, message: UI_MESSAGE) => {
|
||||
replaceMessage = (index: number, message: BizUIMessage) => {
|
||||
if (index < 0 || index >= this.#messages.length) return
|
||||
|
||||
this.messages = [
|
||||
|
|
@ -123,7 +152,7 @@ export class ZustandChatState<UI_MESSAGE extends BizUIMessage> implements ChatSt
|
|||
}
|
||||
|
||||
// Internal event subscription with payload access
|
||||
onMessagesChange = (listener: (messages: UI_MESSAGE[]) => void): (() => void) => {
|
||||
onMessagesChange = (listener: (messages: BizUIMessage[]) => void): (() => void) => {
|
||||
return this.#eventEmitter.on("messages", ({ messages }) => listener(messages))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import type { BizUIMessage } from "../types"
|
|||
import type { ChatStateEvents, ChatStateEventType } from "./types"
|
||||
|
||||
// Event emitter for AI chat state changes with typed payloads
|
||||
export class ChatStateEventEmitter<UI_MESSAGE extends BizUIMessage> {
|
||||
export class ChatStateEventEmitter {
|
||||
#listeners = new Map<ChatStateEventType, Set<(payload: any) => void>>()
|
||||
|
||||
on<T extends ChatStateEventType>(
|
||||
event: T,
|
||||
listener: (payload: ChatStateEvents<UI_MESSAGE>[T]) => void,
|
||||
listener: (payload: ChatStateEvents<BizUIMessage>[T]) => void,
|
||||
): () => void {
|
||||
if (!this.#listeners.has(event)) {
|
||||
this.#listeners.set(event, new Set())
|
||||
|
|
@ -20,7 +20,7 @@ export class ChatStateEventEmitter<UI_MESSAGE extends BizUIMessage> {
|
|||
}
|
||||
}
|
||||
|
||||
emit<T extends ChatStateEventType>(event: T, payload: ChatStateEvents<UI_MESSAGE>[T]): void {
|
||||
emit<T extends ChatStateEventType>(event: T, payload: ChatStateEvents<BizUIMessage>[T]): void {
|
||||
this.#listeners.get(event)?.forEach((listener) => {
|
||||
try {
|
||||
listener(payload)
|
||||
|
|
|
|||
|
|
@ -7,14 +7,13 @@ import { ChatSliceActions } from "../chat-core/chat-actions"
|
|||
import { ZustandChat } from "../chat-core/chat-instance"
|
||||
import type { ChatSlice } from "../chat-core/types"
|
||||
import { createChatTransport } from "../transport"
|
||||
import type { BizUIMessage } from "../types"
|
||||
|
||||
export const createChatSlice: StateCreator<ChatSlice, [], [], ChatSlice> = (...params) => {
|
||||
const [set, get] = params
|
||||
const chatId = nanoid()
|
||||
|
||||
// Create chat instance with Zustand integration
|
||||
const chatInstance = new ZustandChat<BizUIMessage>(
|
||||
const chatInstance = new ZustandChat(
|
||||
{
|
||||
id: chatId,
|
||||
messages: [],
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ export interface FileAttachment {
|
|||
name: string
|
||||
type: string
|
||||
size: number
|
||||
dataUrl: string
|
||||
dataUrl?: string
|
||||
previewUrl?: string
|
||||
uploadStatus: "processing" | "uploading" | "completed" | "error"
|
||||
uploadStatus?: "processing" | "uploading" | "completed" | "error"
|
||||
serverUrl?: string
|
||||
errorMessage?: string
|
||||
/** Upload progress percentage (0-100) */
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useModalStack } from "~/components/ui/modal/stacked/hooks"
|
|||
import { useAIConfiguration } from "~/modules/ai-chat/hooks/useAIConfiguration"
|
||||
|
||||
import { DetailedUsageModal, UsageProgressRing, UsageWarningBanner } from "./components"
|
||||
import { formatTimeRemaining, formatTokenCount } from "./utils"
|
||||
import { formatTimeRemaining, formatTokenCountString } from "./utils"
|
||||
|
||||
export const UsageAnalysisSection = () => {
|
||||
const { t } = useTranslation("ai")
|
||||
|
|
@ -57,8 +57,7 @@ export const UsageAnalysisSection = () => {
|
|||
<div className="flex-1 space-y-2">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className="text-text text-lg font-semibold">
|
||||
{formatTokenCount(rateLimit.remainingTokens).value}
|
||||
{formatTokenCount(rateLimit.remainingTokens).unit}
|
||||
{formatTokenCountString(rateLimit.remainingTokens)}
|
||||
</span>
|
||||
<span className="text-text-secondary text-sm">
|
||||
{t("usage_analysis.tokens_remaining")}
|
||||
|
|
@ -66,9 +65,7 @@ export const UsageAnalysisSection = () => {
|
|||
</div>
|
||||
|
||||
<div className="text-text-tertiary text-xs">
|
||||
{formatTokenCount(usage.used).value}
|
||||
{formatTokenCount(usage.used).unit} / {formatTokenCount(usage.total).value}
|
||||
{formatTokenCount(usage.total).unit} used
|
||||
{formatTokenCountString(usage.used)} / {formatTokenCountString(usage.total)} used
|
||||
</div>
|
||||
|
||||
<div className="text-text-secondary text-xs">
|
||||
|
|
|
|||
|
|
@ -78,6 +78,11 @@ export const DetailedUsageModal = () => {
|
|||
})
|
||||
const maxHourCount = Math.max(1, ...hourBuckets)
|
||||
|
||||
const formattedUsageTokens = formatTokenCount(usage.used)
|
||||
const formattedRemainingTokens = formatTokenCount(rateLimit.remainingTokens)
|
||||
const formattedTotalTokens = formatTokenCount(usage.total)
|
||||
const formattedAvgDaily = formatTokenCount(avgDaily)
|
||||
|
||||
return (
|
||||
<div className="max-h-[80vh] min-h-[640px] w-[500px] max-w-full space-y-6 overflow-y-auto">
|
||||
<p className="text-text-secondary text-sm">
|
||||
|
|
@ -106,15 +111,15 @@ export const DetailedUsageModal = () => {
|
|||
<div className="bg-fill-secondary/40 rounded-lg p-4">
|
||||
<Metric
|
||||
label={t("usage_analysis.tokens_used")}
|
||||
value={formatTokenCount(usage.used).value}
|
||||
unit={formatTokenCount(usage.used).unit}
|
||||
value={formattedUsageTokens.value}
|
||||
unit={formattedUsageTokens.unit}
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-fill-secondary/40 rounded-lg p-4">
|
||||
<Metric
|
||||
label={t("usage_analysis.tokens_remaining")}
|
||||
value={formatTokenCount(rateLimit.remainingTokens).value}
|
||||
unit={formatTokenCount(rateLimit.remainingTokens).unit}
|
||||
value={formattedRemainingTokens.value}
|
||||
unit={formattedRemainingTokens.unit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -122,8 +127,8 @@ export const DetailedUsageModal = () => {
|
|||
<div className="bg-fill-secondary/20 rounded-lg p-3">
|
||||
<StatCompact
|
||||
label={t("usage_analysis.total_limit")}
|
||||
value={formatTokenCount(usage.total).value}
|
||||
unit={formatTokenCount(usage.total).unit}
|
||||
value={formattedTotalTokens.value}
|
||||
unit={formattedTotalTokens.unit}
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-fill-secondary/20 rounded-lg p-3">
|
||||
|
|
@ -139,8 +144,8 @@ export const DetailedUsageModal = () => {
|
|||
<div className="border-fill-tertiary bg-fill-secondary/10 rounded-lg border p-4">
|
||||
<StatCompact
|
||||
label={t("analytics.avg_per_day", { defaultValue: "Avg/day" })}
|
||||
value={formatTokenCount(avgDaily).value}
|
||||
unit={formatTokenCount(avgDaily).unit}
|
||||
value={formattedAvgDaily.value}
|
||||
unit={formattedAvgDaily.unit}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-fill-tertiary bg-fill-secondary/10 rounded-lg border p-4">
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@follow/components/ui/
|
|||
import type { ModelPattern } from "@follow-app/client-sdk"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { formatTokenCount } from "../utils"
|
||||
import { formatTokenCount, formatTokenCountString } from "../utils"
|
||||
import { BarList } from "./charts"
|
||||
|
||||
interface EfficiencyTabProps {
|
||||
|
|
@ -22,12 +22,15 @@ export const EfficiencyTab = ({ byModel }: EfficiencyTabProps) => {
|
|||
<CardContent>
|
||||
{byModel?.length > 0 ? (
|
||||
<BarList
|
||||
data={byModel.map((m) => ({
|
||||
label: m.model ?? "unknown",
|
||||
value: m.avgEfficiency || 0,
|
||||
right: `${formatTokenCount(m.totalTokens ?? 0).value}${formatTokenCount(m.totalTokens ?? 0).unit}`,
|
||||
}))}
|
||||
format={(v) => `${formatTokenCount(v).value}${formatTokenCount(v).unit}`}
|
||||
data={byModel.map((m) => {
|
||||
const formatted = formatTokenCount(m.totalTokens ?? 0)
|
||||
return {
|
||||
label: m.model ?? "unknown",
|
||||
value: m.avgEfficiency || 0,
|
||||
right: `${formatted.value}${formatted.unit}`,
|
||||
}
|
||||
})}
|
||||
format={(v) => formatTokenCountString(v)}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-text-tertiary py-8 text-center text-sm">
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@follow/components/ui/
|
|||
import type { DailyPattern } from "@follow-app/client-sdk"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { formatTokenCount } from "../utils"
|
||||
import { formatTokenCountString } from "../utils"
|
||||
import { Sparkline } from "./charts"
|
||||
|
||||
interface OverviewTabProps {
|
||||
|
|
@ -33,10 +33,7 @@ export const OverviewTab = ({ dailyTotals, peakDay }: OverviewTabProps) => {
|
|||
{peakDay?.date ? (
|
||||
<span>
|
||||
<span>{t("analytics.peak", { defaultValue: "Peak" })}: </span>
|
||||
<span>
|
||||
{formatTokenCount(peakDay.totalTokens).value}
|
||||
{formatTokenCount(peakDay.totalTokens).unit}
|
||||
</span>
|
||||
<span>{formatTokenCountString(peakDay.totalTokens)}</span>
|
||||
<span>{" · "}</span>
|
||||
<span>{new Date(peakDay.date).toLocaleDateString()} </span>
|
||||
<span>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@follow/components/ui/
|
|||
import type { UsagePattern } from "@follow-app/client-sdk"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { formatTokenCount } from "../utils"
|
||||
import { formatTokenCountString } from "../utils"
|
||||
import { BarList, TinyBars } from "./charts"
|
||||
|
||||
interface PatternsTabProps {
|
||||
|
|
@ -53,7 +53,8 @@ export const PatternsTab = ({ hourBuckets, maxHourCount, byOperation }: Patterns
|
|||
data={byOperation.map((o) => ({
|
||||
label: o.operationType ?? "unknown",
|
||||
value: o.percentage || 0,
|
||||
right: `${formatTokenCount(o.totalTokens ?? 0).value}${formatTokenCount(o.totalTokens ?? 0).unit}`,
|
||||
|
||||
right: formatTokenCountString(o.totalTokens ?? 0),
|
||||
}))}
|
||||
suffix="%"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -15,3 +15,8 @@ export const formatTimeRemaining = (ms: number): string => {
|
|||
const rem = minutes % 60
|
||||
return rem ? `${hours}h ${rem}m` : `${hours}h`
|
||||
}
|
||||
|
||||
export const formatTokenCountString = (count: number): string => {
|
||||
const formatted = formatTokenCount(count)
|
||||
return `${formatted.value}${formatted.unit}`
|
||||
}
|
||||
|
|
|
|||
168
pnpm-lock.yaml
168
pnpm-lock.yaml
|
|
@ -768,8 +768,8 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../../../../packages/internal/utils
|
||||
'@folo-services/ai-tools':
|
||||
specifier: 0.2.20
|
||||
version: 0.2.20(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
specifier: 0.2.31
|
||||
version: 0.2.31(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
'@types/node':
|
||||
specifier: 24.0.10
|
||||
version: 24.0.10
|
||||
|
|
@ -1967,20 +1967,20 @@ packages:
|
|||
graphql:
|
||||
optional: true
|
||||
|
||||
'@ai-sdk/gateway@1.0.15':
|
||||
resolution: {integrity: sha512-xySXoQ29+KbGuGfmDnABx+O6vc7Gj7qugmj1kGpn0rW0rQNn6UKUuvscKMzWyv1Uv05GyC1vqHq8ZhEOLfXscQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4
|
||||
|
||||
'@ai-sdk/gateway@1.0.2':
|
||||
resolution: {integrity: sha512-RCE6v6/7JMzSHmjB0R7oWT54y/VYeEv+6NYLtkQKdxN871UawFFHpL7TW7IRnn+/SHdRlr7XoWxxGdHEaGRKiA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4
|
||||
|
||||
'@ai-sdk/openai@2.0.2':
|
||||
resolution: {integrity: sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4
|
||||
|
||||
'@ai-sdk/provider-utils@3.0.0':
|
||||
resolution: {integrity: sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw==}
|
||||
'@ai-sdk/openai@2.0.22':
|
||||
resolution: {integrity: sha512-qjSIPL5+LNM9flcBPeR64ZWeAZdYg4XWkAK34H3FaY61dSbuIaeqFPSzmQUrxotVcphAzgfL5tuYRqRYP2ZYyg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4
|
||||
|
|
@ -1991,6 +1991,12 @@ packages:
|
|||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4
|
||||
|
||||
'@ai-sdk/provider-utils@3.0.7':
|
||||
resolution: {integrity: sha512-o3BS5/t8KnBL3ubP8k3w77AByOypLm+pkIL/DCw0qKkhDbvhCy+L3hRTGPikpdb8WHcylAeKsjgwOxhj4cqTUA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4
|
||||
|
||||
'@ai-sdk/provider@2.0.0':
|
||||
resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -4048,20 +4054,17 @@ packages:
|
|||
'@follow-app/client-sdk@0.3.48':
|
||||
resolution: {integrity: sha512-nPRnKQwzTTvPaIMOfZierMi9+TUlxc2his1FrGmkXOUbyMe6VtnChMtxHWU03Kdb2mnCJrVmOzJa0CwW6NAvmQ==}
|
||||
|
||||
'@folo-services/ai-tools@0.2.20':
|
||||
resolution: {integrity: sha512-1AJI48H2g64JhuTruSqQKUrn4Tc353hMFdKAbohh65AxTAMN/1pIw9w17Dtjm+g5BVlZUXYKaiIvaN9tHsXLNA==}
|
||||
'@folo-services/ai-tools@0.2.31':
|
||||
resolution: {integrity: sha512-oqTVMBc6UCLOt/3M9GX4I+DJ8WT2U8Hlok1MN/Sruw8sQu0ZvtoDvkuiJEtssOQAGDifzHm7z8AVmOgNoGrKUg==}
|
||||
|
||||
'@folo-services/constants@0.1.27':
|
||||
resolution: {integrity: sha512-ZCTgPvLiNfKiOU3E7m0x6Y6w/8PIkEHVl7C5pH+Tt36DrM81vHZjy+zPnZISgjOd3DYTJFHTOCI2ReFRx8Ir3g==}
|
||||
|
||||
'@folo-services/drizzle@0.1.13':
|
||||
resolution: {integrity: sha512-h6tndGnTLrAmfvxK5XsXGdZaMi2GBmFZBFiBqtabC7Y05A1ZSOaWg/d9f60Ydh586LmVonz5VGrhvzTdn5ukhg==}
|
||||
|
||||
'@folo-services/drizzle@0.1.21':
|
||||
resolution: {integrity: sha512-yJAPJz05h2uio5wPOHYbzaeFhJNn5UU9S4HPLQa3L8DNE9IG9DMsNAJqFTEpXQPwEQOf2n3k8FCF8GP/SygA7w==}
|
||||
|
||||
'@folo-services/exceptions@0.1.11':
|
||||
resolution: {integrity: sha512-OMKat8KcIxjw9pBfG3pC1kRcIs0YiuaYmMDA2MRy8AEGcki8F9u5e5QH7OKgP/LlNR7Fs2Csz1XetvcC4nB+RQ==}
|
||||
'@folo-services/drizzle@0.1.22':
|
||||
resolution: {integrity: sha512-n4L0bBA5AxqJn5K3k+BTzmtuZh90LAO7G6zALCo3H+BOsnMsQ1OCsQH/xFg7m6ICQ80LJVbHdUN6b4ZKUoQJ/g==}
|
||||
|
||||
'@folo-services/exceptions@0.1.16':
|
||||
resolution: {integrity: sha512-ThmLLe1Dv2CFsDIgcuY6kJhYJnjiQ7UEsewaqc25MkuImDabhzWnnad+3zRnJGLpveaMeEZ69uqUsTbKC+fR7g==}
|
||||
|
|
@ -7002,6 +7005,12 @@ packages:
|
|||
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ai@5.0.26:
|
||||
resolution: {integrity: sha512-bGNtG+nYQ2U+5mzuLbxIg9WxGQJ2u5jv2gYgP8C+CJ1YI4qqIjvjOgGEZWzvNet8jiOGIlqstsht9aQefKzmBw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4
|
||||
|
||||
ai@5.0.4:
|
||||
resolution: {integrity: sha512-nizyqRxFlJShCAh/xPqor9EYpuaiGqCcYqau9A2GaSL+di+Qf6pA+Hos5x1orlodmT+EciZfoC2Sq+I12OmIUg==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -9428,6 +9437,10 @@ packages:
|
|||
resolution: {integrity: sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
eventsource-parser@3.0.5:
|
||||
resolution: {integrity: sha512-bSRG85ZrMdmWtm7qkF9He9TNRzc/Bm99gEJMaQoHJ9E6Kv9QBbsldh2oMj7iXmYNEAVvNgvv5vPorG6W+XtBhQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
evp_bytestokey@1.0.3:
|
||||
resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==}
|
||||
|
||||
|
|
@ -15845,6 +15858,12 @@ snapshots:
|
|||
optionalDependencies:
|
||||
graphql: 16.8.1
|
||||
|
||||
'@ai-sdk/gateway@1.0.15(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 2.0.0
|
||||
'@ai-sdk/provider-utils': 3.0.7(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/gateway@1.0.2(zod@3.25.75)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 2.0.0
|
||||
|
|
@ -15857,20 +15876,12 @@ snapshots:
|
|||
'@ai-sdk/provider-utils': 3.0.1(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/openai@2.0.2(zod@3.25.76)':
|
||||
'@ai-sdk/openai@2.0.22(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 2.0.0
|
||||
'@ai-sdk/provider-utils': 3.0.0(zod@3.25.76)
|
||||
'@ai-sdk/provider-utils': 3.0.7(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider-utils@3.0.0(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 2.0.0
|
||||
'@standard-schema/spec': 1.0.0
|
||||
eventsource-parser: 3.0.3
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.24.5(zod@3.25.76)
|
||||
|
||||
'@ai-sdk/provider-utils@3.0.1(zod@3.25.75)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 2.0.0
|
||||
|
|
@ -15887,6 +15898,13 @@ snapshots:
|
|||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.24.5(zod@3.25.76)
|
||||
|
||||
'@ai-sdk/provider-utils@3.0.7(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 2.0.0
|
||||
'@standard-schema/spec': 1.0.0
|
||||
eventsource-parser: 3.0.5
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider@2.0.0':
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
|
|
@ -18973,11 +18991,11 @@ snapshots:
|
|||
- sql.js
|
||||
- sqlite3
|
||||
|
||||
'@folo-services/ai-tools@0.2.20(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)':
|
||||
'@folo-services/ai-tools@0.2.31(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)':
|
||||
dependencies:
|
||||
'@ai-sdk/openai': 2.0.2(zod@3.25.76)
|
||||
'@folo-services/drizzle': 0.1.13(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)
|
||||
ai: 5.0.4(zod@3.25.76)
|
||||
'@ai-sdk/openai': 2.0.22(zod@3.25.76)
|
||||
'@folo-services/drizzle': 0.1.22(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)
|
||||
ai: 5.0.26(zod@3.25.76)
|
||||
drizzle-orm: 0.44.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
zod: 3.25.76
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -19016,45 +19034,6 @@ snapshots:
|
|||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@folo-services/drizzle@0.1.13(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)':
|
||||
dependencies:
|
||||
'@folo-services/exceptions': 0.1.11
|
||||
drizzle-orm: 0.44.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
drizzle-zod: 0.7.1(drizzle-orm@0.44.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3))(zod@3.25.76)
|
||||
nanoid: 5.1.5
|
||||
pg: 8.16.3
|
||||
zod: 3.25.76
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/client-rds-data'
|
||||
- '@cloudflare/workers-types'
|
||||
- '@electric-sql/pglite'
|
||||
- '@libsql/client'
|
||||
- '@libsql/client-wasm'
|
||||
- '@neondatabase/serverless'
|
||||
- '@op-engineering/op-sqlite'
|
||||
- '@opentelemetry/api'
|
||||
- '@planetscale/database'
|
||||
- '@prisma/client'
|
||||
- '@tidbcloud/serverless'
|
||||
- '@types/better-sqlite3'
|
||||
- '@types/pg'
|
||||
- '@types/sql.js'
|
||||
- '@upstash/redis'
|
||||
- '@vercel/postgres'
|
||||
- '@xata.io/client'
|
||||
- better-sqlite3
|
||||
- bun-types
|
||||
- expo-sqlite
|
||||
- gel
|
||||
- knex
|
||||
- kysely
|
||||
- mysql2
|
||||
- pg-native
|
||||
- postgres
|
||||
- prisma
|
||||
- sql.js
|
||||
- sqlite3
|
||||
|
||||
'@folo-services/drizzle@0.1.21(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)':
|
||||
dependencies:
|
||||
'@folo-services/exceptions': 0.1.16
|
||||
|
|
@ -19094,7 +19073,44 @@ snapshots:
|
|||
- sql.js
|
||||
- sqlite3
|
||||
|
||||
'@folo-services/exceptions@0.1.11': {}
|
||||
'@folo-services/drizzle@0.1.22(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)':
|
||||
dependencies:
|
||||
'@folo-services/exceptions': 0.1.16
|
||||
drizzle-orm: 0.44.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
drizzle-zod: 0.7.1(drizzle-orm@0.44.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3))(zod@3.25.76)
|
||||
nanoid: 5.1.5
|
||||
pg: 8.16.3
|
||||
zod: 3.25.76
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/client-rds-data'
|
||||
- '@cloudflare/workers-types'
|
||||
- '@electric-sql/pglite'
|
||||
- '@libsql/client'
|
||||
- '@libsql/client-wasm'
|
||||
- '@neondatabase/serverless'
|
||||
- '@op-engineering/op-sqlite'
|
||||
- '@opentelemetry/api'
|
||||
- '@planetscale/database'
|
||||
- '@prisma/client'
|
||||
- '@tidbcloud/serverless'
|
||||
- '@types/better-sqlite3'
|
||||
- '@types/pg'
|
||||
- '@types/sql.js'
|
||||
- '@upstash/redis'
|
||||
- '@vercel/postgres'
|
||||
- '@xata.io/client'
|
||||
- better-sqlite3
|
||||
- bun-types
|
||||
- expo-sqlite
|
||||
- gel
|
||||
- knex
|
||||
- kysely
|
||||
- mysql2
|
||||
- pg-native
|
||||
- postgres
|
||||
- prisma
|
||||
- sql.js
|
||||
- sqlite3
|
||||
|
||||
'@folo-services/exceptions@0.1.16': {}
|
||||
|
||||
|
|
@ -22653,6 +22669,14 @@ snapshots:
|
|||
clean-stack: 2.2.0
|
||||
indent-string: 4.0.0
|
||||
|
||||
ai@5.0.26(zod@3.25.76):
|
||||
dependencies:
|
||||
'@ai-sdk/gateway': 1.0.15(zod@3.25.76)
|
||||
'@ai-sdk/provider': 2.0.0
|
||||
'@ai-sdk/provider-utils': 3.0.7(zod@3.25.76)
|
||||
'@opentelemetry/api': 1.9.0
|
||||
zod: 3.25.76
|
||||
|
||||
ai@5.0.4(zod@3.25.75):
|
||||
dependencies:
|
||||
'@ai-sdk/gateway': 1.0.2(zod@3.25.75)
|
||||
|
|
@ -25633,6 +25657,8 @@ snapshots:
|
|||
|
||||
eventsource-parser@3.0.3: {}
|
||||
|
||||
eventsource-parser@3.0.5: {}
|
||||
|
||||
evp_bytestokey@1.0.3:
|
||||
dependencies:
|
||||
md5.js: 1.3.5
|
||||
|
|
|
|||
Loading…
Reference in New Issue