♻️ refactor: optimize state management, switch to @tanstack/vue-virtual, and improve performance

- Centralize `useAgents` and `useModels` state within the Nuxt app context to prevent data leaks and improve initialization.
- Migrate virtualization from `vue-virtual-scroller` to `@tanstack/vue-virtual` with new `RowVirtualizerFixed` and `RowVirtualizerDynamic` components.
- Upgrade Nuxt to v4.3.1 and remove `@vue-macros/nuxt`.
- Replace `big.js` with an optimized custom `lshDecimal` string manipulation logic for pricing calculations in the provider API.
- Implement automatic focus redirection in `ChatInput` to capture standard keyboard input.
- Refactor Sidenav and Settings components to utilize virtualization for long lists (topics, agents, models).
- Enhance theme colors and mobile experience. More work to come on both of these.
This commit is contained in:
Zoe
2026-02-23 15:56:10 +00:00
parent 59bb7fbc12
commit 6ee4087a29
42 changed files with 889 additions and 828 deletions
+69 -17
View File
@@ -1,5 +1,4 @@
import { type Entity } from '@triplit/client';
import Big from 'big.js';
import * as z from 'zod';
import { SupportedModalities } from '~/types/model';
import { Providers } from '~/types/model';
@@ -246,14 +245,65 @@ const getModelData = (modelId: string, providerId: string, modelsDevData: any) =
}
}
const formatBig = (bigValue: Big) => {
let str = bigValue.toString();
// const formatBig = (bigValue: Big) => {
// let str = bigValue.toString();
if (!str.includes('.')) return str + '.00';
if (str.split('.')[1]!.length === 1) return str + '0';
// if (!str.includes('.')) return str + '.00';
// if (str.split('.')[1]!.length === 1) return str + '0';
return str;
};
// return str;
// };
// this function multiplies a string in the format of 'D.DD' by 1_000_000
// it does this by finding the first digit that is not a zero, and then
// left shifting it in decimal by 3 places
const lshDecimal = (number: string, shift: number) => {
if (number.length === 0) {
return '0.00';
}
let value = '';
let isNegative = number[0] === '-';
let [integerPart, fractionalPart] = number.substring(isNegative ? 1 : 0).split('.');
if (!fractionalPart) {
fractionalPart = '';
}
if (shift <= fractionalPart.length) {
integerPart += fractionalPart.substring(0, shift);
fractionalPart = fractionalPart.substring(shift);
} else if (shift > fractionalPart.length) {
integerPart += fractionalPart;
for (let i = 0; i < shift - fractionalPart.length; i++) {
integerPart += '0';
}
fractionalPart = '';
}
if (isNegative) {
value += '-';
}
integerPart = integerPart!.replace(/^0+/, '');
if (integerPart.length === 0) {
integerPart = '0';
}
value += integerPart;
if (fractionalPart.length > 0) {
if (fractionalPart.length < 2) {
fractionalPart += '0';
}
value += '.' + fractionalPart;
} else {
value += '.00';
}
return value;
}
const normalizeResponse = async (response: Record<string, any>, provider: typeof Providers[number], baseUrl: string, modelsDevData: any) => {
switch (provider) {
@@ -289,35 +339,37 @@ const normalizeResponse = async (response: Record<string, any>, provider: typeof
switch (key) {
case 'prompt':
case 'completion':
case 'request':
case 'image':
case 'audio':
case 'discount':
pricing[key] = formatBig(Big(model.pricing[key]).mul(1_000_000));
pricing[key] = lshDecimal(model.pricing[key], 6);
break;
case 'request':
pricing['request'] = lshDecimal(model.pricing[key], 3);
break;
case 'image_tokens':
pricing['imageTokens'] = formatBig(Big(model.pricing[key]).mul(1_000_000));
pricing['imageTokens'] = lshDecimal(model.pricing[key], 6);
break;
case 'image_output':
pricing['imageOutput'] = formatBig(Big(model.pricing[key]).mul(1_000_000));
pricing['imageOutput'] = lshDecimal(model.pricing[key], 6);
break;
case 'audio_output':
pricing['audioOutput'] = formatBig(Big(model.pricing[key]).mul(1_000_000));
pricing['audioOutput'] = lshDecimal(model.pricing[key], 6);
break;
case 'input_audio_cache':
pricing['inputAudioCache'] = formatBig(Big(model.pricing[key]).mul(1_000_000));
pricing['inputAudioCache'] = lshDecimal(model.pricing[key], 6);
break;
case 'web_search':
pricing['webSearch'] = formatBig(Big(model.pricing[key]).mul(1_000_000));
pricing['webSearch'] = lshDecimal(model.pricing[key], 3);
break;
case 'internal_reasoning':
pricing['internalReasoning'] = formatBig(Big(model.pricing[key]).mul(1_000_000));
pricing['internalReasoning'] = lshDecimal(model.pricing[key], 6);
break;
case 'input_cache_read':
pricing['inputCacheRead'] = formatBig(Big(model.pricing[key]).mul(1_000_000));
pricing['inputCacheRead'] = lshDecimal(model.pricing[key], 6);
break;
case 'input_cache_write':
pricing['inputCacheWrite'] = formatBig(Big(model.pricing[key]).mul(1_000_000));
pricing['inputCacheWrite'] = lshDecimal(model.pricing[key], 6);
break;
}
}