Skip to main content

Overview

The useUsageStats hook tracks usage analytics for PolyChat-AI, including conversation counts, message counts, and response times. Built with Zustand and persisted to localStorage.

Import

import { useUsageStats } from '@/hooks/useUsageStats';

Usage

const {
  totalConversations,
  totalMessages,
  totalUserMessages,
  totalAssistantMessages,
  avgResponseTimeMs,
  perModel,
  lastUpdated,
  recordUserMessage,
  recordAssistantResponse,
  recordNewConversation,
  resetStats,
} = useUsageStats();

State Properties

totalConversations
number
Total number of conversations started across all models. Defaults to 0.
totalMessages
number
Total number of messages (user + assistant) across all models. Defaults to 0.
totalUserMessages
number
Total number of user messages sent. Defaults to 0.
totalAssistantMessages
number
Total number of assistant responses received. Defaults to 0.
avgResponseTimeMs
number
Global average response time in milliseconds across all models. Defaults to 0.
perModel
Record<string, ModelStats>
Statistics broken down by model ID. Each entry contains conversations, messages, and avgResponseTimeMs.
lastUpdated
string
ISO date string of the last stats update.

Methods

recordUserMessage

Records a user message sent to one or more models.
recordUserMessage(modelIds: string[]): void
modelIds
string[]
required
Array of model IDs that received the message.
Updates:
  • Increments totalMessages by the number of models
  • Increments totalUserMessages by 1
  • Increments messages count for each model in perModel
  • Updates lastUpdated timestamp
Example:
const { recordUserMessage } = useUsageStats();

// User sends message to 2 models
recordUserMessage(['openai/gpt-4-turbo', 'anthropic/claude-3.5-sonnet']);

recordAssistantResponse

Records an assistant response and its response time.
recordAssistantResponse(modelId: string, responseTimeMs: number): void
modelId
string
required
ID of the model that generated the response.
responseTimeMs
number
required
Time taken to generate the response in milliseconds.
Updates:
  • Increments totalMessages by 1
  • Increments totalAssistantMessages by 1
  • Updates global avgResponseTimeMs using running average
  • Updates model-specific avgResponseTimeMs in perModel
  • Increments model’s messages count in perModel
  • Updates lastUpdated timestamp
Example:
const { recordAssistantResponse } = useUsageStats();

const startTime = performance.now();
// ... AI response generation ...
const responseTime = performance.now() - startTime;

recordAssistantResponse('openai/gpt-4-turbo', responseTime);

recordNewConversation

Records the start of a new conversation.
recordNewConversation(modelId: string): void
modelId
string
required
ID of the model used for the conversation.
Updates:
  • Increments totalConversations by 1
  • Increments model’s conversations count in perModel
  • Updates lastUpdated timestamp
Example:
const { recordNewConversation } = useUsageStats();

// User starts new conversation
recordNewConversation('anthropic/claude-3.5-sonnet');

resetStats

Resets all statistics to default values.
resetStats(): void
Example:
const { resetStats } = useUsageStats();

const handleReset = () => {
  if (confirm('Reset all usage statistics?')) {
    resetStats();
  }
};

Types

UsageStats

interface UsageStats {
  totalConversations: number;
  totalMessages: number;
  totalUserMessages: number;
  totalAssistantMessages: number;
  avgResponseTimeMs: number;
  perModel: Record<string, ModelStats>;
  lastUpdated: string; // ISO date string
}

ModelStats

interface ModelStats {
  conversations: number;
  messages: number;
  avgResponseTimeMs: number;
}

Default Values

const DEFAULT_STATS: UsageStats = {
  totalConversations: 0,
  totalMessages: 0,
  totalUserMessages: 0,
  totalAssistantMessages: 0,
  avgResponseTimeMs: 0,
  perModel: {},
  lastUpdated: new Date().toISOString(),
};

Response Time Calculation

Response times are calculated using a running average to avoid storing all individual times:

Model-Specific Average

const newModelAvg = model.messages > 0
  ? Math.round(
      (model.avgResponseTimeMs * Math.max(model.messages - 1, 0) + responseTimeMs) /
      model.messages
    )
  : responseTimeMs;

Global Average

const newGlobalAvg = state.totalAssistantMessages > 0
  ? Math.round(
      (state.avgResponseTimeMs * state.totalAssistantMessages + responseTimeMs) /
      newTotalAssistant
    )
  : responseTimeMs;

Persistence

Statistics are automatically persisted to localStorage:
persist(
  (set, get) => ({ /* store */ }),
  { name: 'polychat-usage-stats' }
)
Storage key: polychat-usage-stats

Example: Usage Dashboard

import { useUsageStats } from '@/hooks/useUsageStats';

function UsageDashboard() {
  const {
    totalConversations,
    totalMessages,
    totalUserMessages,
    totalAssistantMessages,
    avgResponseTimeMs,
    perModel,
    lastUpdated,
    resetStats,
  } = useUsageStats();

  return (
    <div>
      <h2>Usage Statistics</h2>
      <p>Last updated: {new Date(lastUpdated).toLocaleString()}</p>

      <div className="stats">
        <div>
          <h3>Total Conversations</h3>
          <p>{totalConversations}</p>
        </div>
        <div>
          <h3>Total Messages</h3>
          <p>{totalMessages}</p>
        </div>
        <div>
          <h3>User Messages</h3>
          <p>{totalUserMessages}</p>
        </div>
        <div>
          <h3>Assistant Messages</h3>
          <p>{totalAssistantMessages}</p>
        </div>
        <div>
          <h3>Avg Response Time</h3>
          <p>{avgResponseTimeMs.toFixed(0)}ms</p>
        </div>
      </div>

      <h3>Per Model</h3>
      <table>
        <thead>
          <tr>
            <th>Model</th>
            <th>Conversations</th>
            <th>Messages</th>
            <th>Avg Response Time</th>
          </tr>
        </thead>
        <tbody>
          {Object.entries(perModel).map(([modelId, stats]) => (
            <tr key={modelId}>
              <td>{modelId}</td>
              <td>{stats.conversations}</td>
              <td>{stats.messages}</td>
              <td>{stats.avgResponseTimeMs.toFixed(0)}ms</td>
            </tr>
          ))}
        </tbody>
      </table>

      <button onClick={resetStats}>Reset Statistics</button>
    </div>
  );
}

Example: Model Comparison

import { useUsageStats } from '@/hooks/useUsageStats';

function ModelComparison() {
  const { perModel } = useUsageStats();

  const models = Object.entries(perModel)
    .map(([id, stats]) => ({ id, ...stats }))
    .sort((a, b) => b.messages - a.messages); // Sort by usage

  const fastest = models.reduce((prev, curr) =>
    curr.avgResponseTimeMs < prev.avgResponseTimeMs ? curr : prev
  );

  return (
    <div>
      <h2>Model Comparison</h2>
      
      <div>
        <h3>Most Used</h3>
        <p>{models[0]?.id}</p>
        <p>{models[0]?.messages} messages</p>
      </div>

      <div>
        <h3>Fastest</h3>
        <p>{fastest.id}</p>
        <p>{fastest.avgResponseTimeMs.toFixed(0)}ms avg</p>
      </div>

      <div>
        <h3>All Models</h3>
        {models.map((model) => (
          <div key={model.id}>
            <strong>{model.id}</strong>
            <div>
              {model.messages} messages | {model.conversations} conversations
            </div>
            <div>Avg: {model.avgResponseTimeMs.toFixed(0)}ms</div>
          </div>
        ))}
      </div>
    </div>
  );
}

Example: Integration with Chat

import { useChat } from '@/hooks/useChat';
import { useUsageStats } from '@/hooks/useUsageStats';

function ChatWithStats() {
  const { sendMessageToAll, selectedModels } = useChat();
  const { recordUserMessage, recordAssistantResponse } = useUsageStats();

  const handleSendMessage = async (content: string) => {
    // Record user message
    recordUserMessage(selectedModels);

    const startTime = performance.now();
    await sendMessageToAll(content);
    const responseTime = performance.now() - startTime;

    // Record assistant responses
    selectedModels.forEach((modelId) => {
      recordAssistantResponse(modelId, responseTime / selectedModels.length);
    });
  };

  return (
    <div>
      {/* Chat UI */}
    </div>
  );
}

Notes

  • All statistics are persisted automatically via Zustand middleware
  • Response times use running averages to save memory
  • Calling recordUserMessage with multiple model IDs increments total messages by the array length
  • Per-model stats are initialized on first use (lazy initialization)
  • ISO timestamps ensure consistent date formatting across locales

Build docs developers (and LLMs) love