mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-26 04:54:25 -07:00
fix(ui): hide model quota in /stats and refactor quota display (#24206)
This commit is contained in:
@@ -164,23 +164,9 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
|||||||
{itemForDisplay.type === 'stats' && (
|
{itemForDisplay.type === 'stats' && (
|
||||||
<StatsDisplay
|
<StatsDisplay
|
||||||
duration={itemForDisplay.duration}
|
duration={itemForDisplay.duration}
|
||||||
quotas={itemForDisplay.quotas}
|
|
||||||
selectedAuthType={itemForDisplay.selectedAuthType}
|
selectedAuthType={itemForDisplay.selectedAuthType}
|
||||||
userEmail={itemForDisplay.userEmail}
|
userEmail={itemForDisplay.userEmail}
|
||||||
tier={itemForDisplay.tier}
|
tier={itemForDisplay.tier}
|
||||||
currentModel={itemForDisplay.currentModel}
|
|
||||||
quotaStats={
|
|
||||||
itemForDisplay.pooledRemaining !== undefined ||
|
|
||||||
itemForDisplay.pooledLimit !== undefined ||
|
|
||||||
itemForDisplay.pooledResetTime !== undefined
|
|
||||||
? {
|
|
||||||
remaining: itemForDisplay.pooledRemaining,
|
|
||||||
limit: itemForDisplay.pooledLimit,
|
|
||||||
resetTime: itemForDisplay.pooledResetTime,
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
creditBalance={itemForDisplay.creditBalance}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{itemForDisplay.type === 'model_stats' && (
|
{itemForDisplay.type === 'model_stats' && (
|
||||||
|
|||||||
@@ -65,6 +65,15 @@ describe('<ModelDialog />', () => {
|
|||||||
getGemini31FlashLiteLaunchedSync: () => boolean;
|
getGemini31FlashLiteLaunchedSync: () => boolean;
|
||||||
getProModelNoAccess: () => Promise<boolean>;
|
getProModelNoAccess: () => Promise<boolean>;
|
||||||
getProModelNoAccessSync: () => boolean;
|
getProModelNoAccessSync: () => boolean;
|
||||||
|
getLastRetrievedQuota: () =>
|
||||||
|
| {
|
||||||
|
buckets: Array<{
|
||||||
|
modelId?: string;
|
||||||
|
remainingFraction?: number;
|
||||||
|
resetTime?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockConfig: MockConfig = {
|
const mockConfig: MockConfig = {
|
||||||
@@ -76,6 +85,7 @@ describe('<ModelDialog />', () => {
|
|||||||
getGemini31FlashLiteLaunchedSync: mockGetGemini31FlashLiteLaunchedSync,
|
getGemini31FlashLiteLaunchedSync: mockGetGemini31FlashLiteLaunchedSync,
|
||||||
getProModelNoAccess: mockGetProModelNoAccess,
|
getProModelNoAccess: mockGetProModelNoAccess,
|
||||||
getProModelNoAccessSync: mockGetProModelNoAccessSync,
|
getProModelNoAccessSync: mockGetProModelNoAccessSync,
|
||||||
|
getLastRetrievedQuota: () => ({ buckets: [] }),
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useCallback, useContext, useMemo, useState, useEffect } from 'react';
|
import { useCallback, useContext, useMemo, useState, useEffect } from 'react';
|
||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
|
import { ModelQuotaDisplay } from './ModelQuotaDisplay.js';
|
||||||
|
import { useUIState } from '../contexts/UIStateContext.js';
|
||||||
import {
|
import {
|
||||||
PREVIEW_GEMINI_MODEL,
|
PREVIEW_GEMINI_MODEL,
|
||||||
PREVIEW_GEMINI_3_1_MODEL,
|
PREVIEW_GEMINI_3_1_MODEL,
|
||||||
@@ -37,6 +39,7 @@ interface ModelDialogProps {
|
|||||||
export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||||
const config = useContext(ConfigContext);
|
const config = useContext(ConfigContext);
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
const { terminalWidth } = useUIState();
|
||||||
const [hasAccessToProModel, setHasAccessToProModel] = useState<boolean>(
|
const [hasAccessToProModel, setHasAccessToProModel] = useState<boolean>(
|
||||||
() => !(config?.getProModelNoAccessSync() ?? false),
|
() => !(config?.getProModelNoAccessSync() ?? false),
|
||||||
);
|
);
|
||||||
@@ -338,20 +341,24 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
|||||||
</Box>
|
</Box>
|
||||||
<Box marginTop={1} flexDirection="column">
|
<Box marginTop={1} flexDirection="column">
|
||||||
<Box>
|
<Box>
|
||||||
<Text color={theme.text.primary}>
|
<Text bold color={theme.text.primary}>
|
||||||
Remember model for future sessions:{' '}
|
Remember model for future sessions:{' '}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.status.success}>
|
<Text color={theme.status.success}>
|
||||||
{persistMode ? 'true' : 'false'}
|
{persistMode ? 'true' : 'false'}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text color={theme.text.secondary}> (Press Tab to toggle)</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Text color={theme.text.secondary}>(Press Tab to toggle)</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box marginTop={1} flexDirection="column">
|
<Box flexDirection="column">
|
||||||
<Text color={theme.text.secondary}>
|
<Text color={theme.text.secondary}>
|
||||||
{'> To use a specific Gemini model on startup, use the --model flag.'}
|
{'> To use a specific Gemini model on startup, use the --model flag.'}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
<ModelQuotaDisplay
|
||||||
|
buckets={config?.getLastRetrievedQuota()?.buckets}
|
||||||
|
availableWidth={terminalWidth - 2}
|
||||||
|
/>
|
||||||
<Box marginTop={1} flexDirection="column">
|
<Box marginTop={1} flexDirection="column">
|
||||||
<Text color={theme.text.secondary}>(Press Esc to close)</Text>
|
<Text color={theme.text.secondary}>(Press Esc to close)</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { renderWithProviders } from '../../test-utils/render.js';
|
||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { ModelQuotaDisplay } from './ModelQuotaDisplay.js';
|
||||||
|
|
||||||
|
describe('<ModelQuotaDisplay />', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.stubEnv('TZ', 'UTC');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllEnvs();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders quota information when buckets are provided', async () => {
|
||||||
|
const now = new Date('2025-01-01T12:00:00Z');
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime(now);
|
||||||
|
|
||||||
|
const resetTime = new Date(now.getTime() + 1000 * 60 * 90).toISOString(); // 1 hour 30 minutes from now
|
||||||
|
|
||||||
|
const buckets = [
|
||||||
|
{
|
||||||
|
modelId: 'gemini-2.5-pro',
|
||||||
|
remainingFraction: 0.75,
|
||||||
|
resetTime,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { lastFrame } = await renderWithProviders(
|
||||||
|
<ModelQuotaDisplay buckets={buckets} availableWidth={100} />,
|
||||||
|
{ width: 100 },
|
||||||
|
);
|
||||||
|
const output = lastFrame();
|
||||||
|
|
||||||
|
expect(output).toContain('Model usage');
|
||||||
|
expect(output).toContain('Pro');
|
||||||
|
expect(output).toContain('25%');
|
||||||
|
expect(output).toContain('Resets:');
|
||||||
|
expect(output).toMatchSnapshot();
|
||||||
|
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders nothing when no buckets are provided', async () => {
|
||||||
|
const { lastFrame } = await renderWithProviders(
|
||||||
|
<ModelQuotaDisplay buckets={[]} availableWidth={100} />,
|
||||||
|
{ width: 100 },
|
||||||
|
);
|
||||||
|
const output = lastFrame({ allowEmpty: true });
|
||||||
|
expect(output).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters models based on modelsToShow prop', async () => {
|
||||||
|
const buckets = [
|
||||||
|
{
|
||||||
|
modelId: 'gemini-2.5-pro',
|
||||||
|
remainingFraction: 0.5,
|
||||||
|
resetTime: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelId: 'gemini-2.5-flash',
|
||||||
|
remainingFraction: 0.8,
|
||||||
|
resetTime: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { lastFrame } = await renderWithProviders(
|
||||||
|
<ModelQuotaDisplay
|
||||||
|
buckets={buckets}
|
||||||
|
modelsToShow={['gemini-2.5-pro']}
|
||||||
|
availableWidth={100}
|
||||||
|
/>,
|
||||||
|
{ width: 100 },
|
||||||
|
);
|
||||||
|
const output = lastFrame();
|
||||||
|
|
||||||
|
expect(output).toContain('Pro');
|
||||||
|
expect(output).not.toContain('Flash');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { Box, Text } from 'ink';
|
||||||
|
import { ProgressBar } from './ProgressBar.js';
|
||||||
|
import { theme } from '../semantic-colors.js';
|
||||||
|
import { formatResetTime } from '../utils/formatters.js';
|
||||||
|
import { getDisplayString } from '@google/gemini-cli-core';
|
||||||
|
import { useConfig } from '../contexts/ConfigContext.js';
|
||||||
|
import { useUIState } from '../contexts/UIStateContext.js';
|
||||||
|
|
||||||
|
interface LocalBucket {
|
||||||
|
modelId?: string;
|
||||||
|
remainingFraction?: number;
|
||||||
|
resetTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModelQuotaDisplayProps {
|
||||||
|
buckets?: LocalBucket[];
|
||||||
|
availableWidth?: number;
|
||||||
|
modelsToShow?: string[];
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModelUsageRowProps {
|
||||||
|
row: {
|
||||||
|
modelId: string;
|
||||||
|
name: string;
|
||||||
|
usedFraction: number;
|
||||||
|
usedPercentage: number;
|
||||||
|
resetTime?: string;
|
||||||
|
};
|
||||||
|
availableWidth?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModelUsageRow = ({ row, availableWidth }: ModelUsageRowProps) => {
|
||||||
|
const { terminalWidth } = useUIState();
|
||||||
|
|
||||||
|
const nameColumnWidth = 12;
|
||||||
|
const percentageWidth = 4;
|
||||||
|
const resetColumnWidth = 26;
|
||||||
|
const usedPercentage = row.usedPercentage;
|
||||||
|
|
||||||
|
const nameLabel = row.name;
|
||||||
|
const percentageLabel = `${usedPercentage.toFixed(0)}%`.padEnd(
|
||||||
|
percentageWidth,
|
||||||
|
);
|
||||||
|
const resetLabel = row.resetTime
|
||||||
|
? formatResetTime(row.resetTime, 'column')
|
||||||
|
.slice(0, resetColumnWidth)
|
||||||
|
.padEnd(resetColumnWidth)
|
||||||
|
: ''.padEnd(resetColumnWidth);
|
||||||
|
|
||||||
|
// Calculate the exact width of all fixed adjacent siblings
|
||||||
|
const nameColWidth = nameColumnWidth;
|
||||||
|
const percentColWidth = percentageWidth + 1; // width + marginLeft
|
||||||
|
const resetColWidth = resetColumnWidth + 1; // width + marginLeft
|
||||||
|
|
||||||
|
const fixedSiblingWidth = nameColWidth + percentColWidth + resetColWidth;
|
||||||
|
|
||||||
|
const calcWidth = availableWidth ?? terminalWidth;
|
||||||
|
const defaultPadding = availableWidth != null ? 0 : 4;
|
||||||
|
|
||||||
|
// Subtract fixed sibling widths from total width.
|
||||||
|
// We keep a small buffer (e.g., 3) to prevent edge-case wrapping.
|
||||||
|
const buffer = 3;
|
||||||
|
const barWidth = Math.max(
|
||||||
|
0,
|
||||||
|
calcWidth - defaultPadding - fixedSiblingWidth - buffer,
|
||||||
|
);
|
||||||
|
|
||||||
|
let percentageColor = theme.text.primary;
|
||||||
|
if (usedPercentage >= 100) {
|
||||||
|
percentageColor = theme.status.error;
|
||||||
|
} else if (usedPercentage >= 80) {
|
||||||
|
percentageColor = theme.status.warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box flexDirection="row" width="100%">
|
||||||
|
<Box width={nameColumnWidth}>
|
||||||
|
<Text color={theme.text.primary} wrap="truncate-end">
|
||||||
|
{nameLabel}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box flexGrow={1}>
|
||||||
|
<ProgressBar value={usedPercentage} width={barWidth} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box width={4} marginLeft={1}>
|
||||||
|
<Text color={percentageColor}>{percentageLabel}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box width={resetColumnWidth} marginLeft={1}>
|
||||||
|
<Text color={theme.text.secondary}>
|
||||||
|
{resetLabel.trim() ? `Resets: ${resetLabel}` : ''}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ModelQuotaDisplay = ({
|
||||||
|
buckets,
|
||||||
|
availableWidth,
|
||||||
|
modelsToShow = ['all'],
|
||||||
|
title = 'Model usage',
|
||||||
|
}: ModelQuotaDisplayProps) => {
|
||||||
|
const config = useConfig();
|
||||||
|
|
||||||
|
const modelsWithQuotas = useMemo(() => {
|
||||||
|
if (!buckets) return [];
|
||||||
|
|
||||||
|
let filteredBuckets = buckets.filter(
|
||||||
|
(b) => b.modelId && b.remainingFraction != null,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (modelsToShow.includes('current')) {
|
||||||
|
const currentModel = config.getActiveModel?.() ?? config.getModel?.();
|
||||||
|
filteredBuckets = filteredBuckets.filter(
|
||||||
|
(b) => b.modelId === currentModel,
|
||||||
|
);
|
||||||
|
} else if (!modelsToShow.includes('all')) {
|
||||||
|
filteredBuckets = filteredBuckets.filter(
|
||||||
|
(b) => b.modelId && modelsToShow.includes(b.modelId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupedByTier = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
modelId: string;
|
||||||
|
remainingFraction: number;
|
||||||
|
resetTime?: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
|
filteredBuckets.forEach((b) => {
|
||||||
|
const modelId = b.modelId;
|
||||||
|
const remainingFraction = b.remainingFraction;
|
||||||
|
if (!modelId || remainingFraction == null) return;
|
||||||
|
|
||||||
|
const tier =
|
||||||
|
config?.modelConfigService?.getModelDefinition(modelId)?.tier;
|
||||||
|
const groupKey = tier ?? modelId;
|
||||||
|
const existing = groupedByTier.get(groupKey);
|
||||||
|
|
||||||
|
if (!existing || remainingFraction < existing.remainingFraction) {
|
||||||
|
const tierDisplayNames: Record<string, string> = {
|
||||||
|
pro: 'Pro',
|
||||||
|
flash: 'Flash',
|
||||||
|
'flash-lite': 'Flash Lite',
|
||||||
|
};
|
||||||
|
const name = tier
|
||||||
|
? (tierDisplayNames[tier] ?? tier)
|
||||||
|
: getDisplayString(modelId, config);
|
||||||
|
|
||||||
|
groupedByTier.set(groupKey, {
|
||||||
|
modelId,
|
||||||
|
remainingFraction,
|
||||||
|
resetTime: b.resetTime,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(groupedByTier.entries()).map(([key, data]) => {
|
||||||
|
const usedFraction = 1 - data.remainingFraction;
|
||||||
|
const usedPercentage = usedFraction * 100;
|
||||||
|
return {
|
||||||
|
modelId: key,
|
||||||
|
name: data.name,
|
||||||
|
usedFraction,
|
||||||
|
usedPercentage,
|
||||||
|
resetTime: data.resetTime,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [buckets, config, modelsToShow]);
|
||||||
|
|
||||||
|
if (modelsWithQuotas.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box flexDirection="column" marginTop={1}>
|
||||||
|
{/* Rule Line */}
|
||||||
|
<Box
|
||||||
|
borderStyle="single"
|
||||||
|
borderTop={true}
|
||||||
|
borderBottom={false}
|
||||||
|
borderLeft={false}
|
||||||
|
borderRight={false}
|
||||||
|
borderColor={theme.border.default}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box flexDirection="column">
|
||||||
|
<Box marginBottom={1}>
|
||||||
|
<Text bold color={theme.text.primary}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{modelsWithQuotas.map((row) => (
|
||||||
|
<ModelUsageRow
|
||||||
|
key={row.modelId}
|
||||||
|
row={row}
|
||||||
|
availableWidth={availableWidth}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { ProgressBar } from './ProgressBar.js';
|
||||||
|
import { renderWithProviders } from '../../test-utils/render.js';
|
||||||
|
|
||||||
|
describe('<ProgressBar />', () => {
|
||||||
|
it('renders 0% correctly', async () => {
|
||||||
|
const { lastFrame } = await renderWithProviders(
|
||||||
|
<ProgressBar value={0} width={10} />,
|
||||||
|
);
|
||||||
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders 50% correctly', async () => {
|
||||||
|
const { lastFrame } = await renderWithProviders(
|
||||||
|
<ProgressBar value={50} width={10} />,
|
||||||
|
);
|
||||||
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders warning threshold correctly', async () => {
|
||||||
|
const { lastFrame } = await renderWithProviders(
|
||||||
|
<ProgressBar value={85} width={10} warningThreshold={80} />,
|
||||||
|
);
|
||||||
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders error threshold correctly at 100%', async () => {
|
||||||
|
const { lastFrame } = await renderWithProviders(
|
||||||
|
<ProgressBar value={100} width={10} />,
|
||||||
|
);
|
||||||
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from 'react';
|
||||||
|
import { Box, Text } from 'ink';
|
||||||
|
import { theme } from '../semantic-colors.js';
|
||||||
|
|
||||||
|
interface ProgressBarProps {
|
||||||
|
value: number; // 0 to 100
|
||||||
|
width: number;
|
||||||
|
warningThreshold?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProgressBar: React.FC<ProgressBarProps> = ({
|
||||||
|
value,
|
||||||
|
width,
|
||||||
|
warningThreshold = 80,
|
||||||
|
}) => {
|
||||||
|
const safeValue = Math.min(Math.max(value, 0), 100);
|
||||||
|
const activeChars = Math.ceil((safeValue / 100) * width);
|
||||||
|
const inactiveChars = width - activeChars;
|
||||||
|
|
||||||
|
let color = theme.status.success;
|
||||||
|
if (safeValue >= 100) {
|
||||||
|
color = theme.status.error;
|
||||||
|
} else if (safeValue >= warningThreshold) {
|
||||||
|
color = theme.status.warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box flexDirection="row">
|
||||||
|
<Text color={color}>{'▬'.repeat(activeChars)}</Text>
|
||||||
|
<Text color={theme.border.default}>{'▬'.repeat(inactiveChars)}</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -9,10 +9,7 @@ import { describe, it, expect, vi } from 'vitest';
|
|||||||
import { StatsDisplay } from './StatsDisplay.js';
|
import { StatsDisplay } from './StatsDisplay.js';
|
||||||
import * as SessionContext from '../contexts/SessionContext.js';
|
import * as SessionContext from '../contexts/SessionContext.js';
|
||||||
import { type SessionMetrics } from '../contexts/SessionContext.js';
|
import { type SessionMetrics } from '../contexts/SessionContext.js';
|
||||||
import {
|
import { ToolCallDecision } from '@google/gemini-cli-core';
|
||||||
ToolCallDecision,
|
|
||||||
type RetrieveUserQuotaResponse,
|
|
||||||
} from '@google/gemini-cli-core';
|
|
||||||
|
|
||||||
// Mock the context to provide controlled data for testing
|
// Mock the context to provide controlled data for testing
|
||||||
vi.mock('../contexts/SessionContext.js', async (importOriginal) => {
|
vi.mock('../contexts/SessionContext.js', async (importOriginal) => {
|
||||||
@@ -124,10 +121,13 @@ describe('<StatsDisplay />', () => {
|
|||||||
const { lastFrame } = await renderWithMockedStats(metrics);
|
const { lastFrame } = await renderWithMockedStats(metrics);
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
|
|
||||||
expect(output).toContain('gemini-2.5-pro');
|
expect(output).toContain('Performance');
|
||||||
expect(output).toContain('gemini-2.5-flash');
|
expect(output).toContain('Interaction Summary');
|
||||||
expect(output).toContain('15,000');
|
expect(output).toContain('Model Usage');
|
||||||
expect(output).toContain('10,000');
|
expect(output).toContain('Reqs');
|
||||||
|
expect(output).toContain('Input Tokens');
|
||||||
|
expect(output).toContain('Cache Reads');
|
||||||
|
expect(output).toContain('Output Tokens');
|
||||||
expect(output).toMatchSnapshot();
|
expect(output).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ describe('<StatsDisplay />', () => {
|
|||||||
expect(output).toContain('Performance');
|
expect(output).toContain('Performance');
|
||||||
expect(output).toContain('Interaction Summary');
|
expect(output).toContain('Interaction Summary');
|
||||||
expect(output).toContain('User Agreement');
|
expect(output).toContain('User Agreement');
|
||||||
expect(output).toContain('gemini-2.5-pro');
|
expect(output).toContain('Model Usage');
|
||||||
expect(output).toMatchSnapshot();
|
expect(output).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -406,174 +406,6 @@ describe('<StatsDisplay />', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Quota Display', () => {
|
|
||||||
it('renders quota information when quotas are provided', async () => {
|
|
||||||
const now = new Date('2025-01-01T12:00:00Z');
|
|
||||||
vi.useFakeTimers();
|
|
||||||
vi.setSystemTime(now);
|
|
||||||
|
|
||||||
const metrics = createTestMetrics({
|
|
||||||
models: {
|
|
||||||
'gemini-2.5-pro': {
|
|
||||||
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 },
|
|
||||||
tokens: {
|
|
||||||
input: 50,
|
|
||||||
prompt: 100,
|
|
||||||
candidates: 100,
|
|
||||||
total: 250,
|
|
||||||
cached: 50,
|
|
||||||
thoughts: 0,
|
|
||||||
tool: 0,
|
|
||||||
},
|
|
||||||
roles: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const resetTime = new Date(now.getTime() + 1000 * 60 * 90).toISOString(); // 1 hour 30 minutes from now
|
|
||||||
|
|
||||||
const quotas: RetrieveUserQuotaResponse = {
|
|
||||||
buckets: [
|
|
||||||
{
|
|
||||||
modelId: 'gemini-2.5-pro',
|
|
||||||
remainingAmount: '75',
|
|
||||||
remainingFraction: 0.75,
|
|
||||||
resetTime,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
useSessionStatsMock.mockReturnValue({
|
|
||||||
stats: {
|
|
||||||
sessionId: 'test-session-id',
|
|
||||||
sessionStartTime: new Date(),
|
|
||||||
metrics,
|
|
||||||
lastPromptTokenCount: 0,
|
|
||||||
promptCount: 5,
|
|
||||||
},
|
|
||||||
|
|
||||||
getPromptCount: () => 5,
|
|
||||||
startNewPrompt: vi.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const { lastFrame } = await renderWithProviders(
|
|
||||||
<StatsDisplay duration="1s" quotas={quotas} />,
|
|
||||||
{ width: 100 },
|
|
||||||
);
|
|
||||||
const output = lastFrame();
|
|
||||||
|
|
||||||
expect(output).toContain('Model usage');
|
|
||||||
expect(output).toContain('25%');
|
|
||||||
expect(output).toContain('Usage resets');
|
|
||||||
expect(output).toMatchSnapshot();
|
|
||||||
|
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders pooled quota information for auto mode', async () => {
|
|
||||||
const now = new Date('2025-01-01T12:00:00Z');
|
|
||||||
vi.useFakeTimers();
|
|
||||||
vi.setSystemTime(now);
|
|
||||||
|
|
||||||
const metrics = createTestMetrics();
|
|
||||||
const quotas: RetrieveUserQuotaResponse = {
|
|
||||||
buckets: [
|
|
||||||
{
|
|
||||||
modelId: 'gemini-2.5-pro',
|
|
||||||
remainingAmount: '10',
|
|
||||||
remainingFraction: 0.1, // limit = 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
modelId: 'gemini-2.5-flash',
|
|
||||||
remainingAmount: '700',
|
|
||||||
remainingFraction: 0.7, // limit = 1000
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
useSessionStatsMock.mockReturnValue({
|
|
||||||
stats: {
|
|
||||||
sessionId: 'test-session-id',
|
|
||||||
sessionStartTime: new Date(),
|
|
||||||
metrics,
|
|
||||||
lastPromptTokenCount: 0,
|
|
||||||
promptCount: 5,
|
|
||||||
},
|
|
||||||
getPromptCount: () => 5,
|
|
||||||
startNewPrompt: vi.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const { lastFrame } = await renderWithProviders(
|
|
||||||
<StatsDisplay
|
|
||||||
duration="1s"
|
|
||||||
quotas={quotas}
|
|
||||||
currentModel="auto"
|
|
||||||
quotaStats={{
|
|
||||||
remaining: 710,
|
|
||||||
limit: 1100,
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
{ width: 100 },
|
|
||||||
);
|
|
||||||
const output = lastFrame();
|
|
||||||
|
|
||||||
// (1 - 710/1100) * 100 = 35.5%
|
|
||||||
expect(output).toContain('35%');
|
|
||||||
expect(output).toContain('Usage limit: 1,100');
|
|
||||||
expect(output).toMatchSnapshot();
|
|
||||||
|
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders quota information for unused models', async () => {
|
|
||||||
const now = new Date('2025-01-01T12:00:00Z');
|
|
||||||
vi.useFakeTimers();
|
|
||||||
vi.setSystemTime(now);
|
|
||||||
|
|
||||||
// No models in metrics, but a quota for gemini-2.5-flash
|
|
||||||
const metrics = createTestMetrics();
|
|
||||||
|
|
||||||
const resetTime = new Date(now.getTime() + 1000 * 60 * 120).toISOString(); // 2 hours from now
|
|
||||||
|
|
||||||
const quotas: RetrieveUserQuotaResponse = {
|
|
||||||
buckets: [
|
|
||||||
{
|
|
||||||
modelId: 'gemini-2.5-flash',
|
|
||||||
remainingAmount: '50',
|
|
||||||
remainingFraction: 0.5,
|
|
||||||
resetTime,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
useSessionStatsMock.mockReturnValue({
|
|
||||||
stats: {
|
|
||||||
sessionId: 'test-session-id',
|
|
||||||
sessionStartTime: new Date(),
|
|
||||||
metrics,
|
|
||||||
lastPromptTokenCount: 0,
|
|
||||||
promptCount: 5,
|
|
||||||
},
|
|
||||||
getPromptCount: () => 5,
|
|
||||||
startNewPrompt: vi.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const { lastFrame } = await renderWithProviders(
|
|
||||||
<StatsDisplay duration="1s" quotas={quotas} />,
|
|
||||||
{ width: 100 },
|
|
||||||
);
|
|
||||||
const output = lastFrame();
|
|
||||||
|
|
||||||
expect(output).toContain('gemini-2.5-flash');
|
|
||||||
expect(output).toContain('-'); // for requests
|
|
||||||
expect(output).toContain('50%');
|
|
||||||
expect(output).toContain('Usage resets');
|
|
||||||
expect(output).toMatchSnapshot();
|
|
||||||
|
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('User Identity Display', () => {
|
describe('User Identity Display', () => {
|
||||||
it('renders User row with Auth Method and Tier', async () => {
|
it('renders User row with Auth Method and Tier', async () => {
|
||||||
const metrics = createTestMetrics();
|
const metrics = createTestMetrics();
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { Box, Text, useStdout } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { ThemedGradient } from './ThemedGradient.js';
|
import { ThemedGradient } from './ThemedGradient.js';
|
||||||
import { theme } from '../semantic-colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
import { formatDuration, formatResetTime } from '../utils/formatters.js';
|
import { formatDuration } from '../utils/formatters.js';
|
||||||
import {
|
import {
|
||||||
useSessionStats,
|
useSessionStats,
|
||||||
type ModelMetrics,
|
type ModelMetrics,
|
||||||
@@ -19,25 +19,10 @@ import {
|
|||||||
TOOL_SUCCESS_RATE_MEDIUM,
|
TOOL_SUCCESS_RATE_MEDIUM,
|
||||||
USER_AGREEMENT_RATE_HIGH,
|
USER_AGREEMENT_RATE_HIGH,
|
||||||
USER_AGREEMENT_RATE_MEDIUM,
|
USER_AGREEMENT_RATE_MEDIUM,
|
||||||
CACHE_EFFICIENCY_HIGH,
|
|
||||||
CACHE_EFFICIENCY_MEDIUM,
|
|
||||||
getUsedStatusColor,
|
|
||||||
QUOTA_USED_WARNING_THRESHOLD,
|
|
||||||
QUOTA_USED_CRITICAL_THRESHOLD,
|
|
||||||
} from '../utils/displayUtils.js';
|
} from '../utils/displayUtils.js';
|
||||||
import { computeSessionStats } from '../utils/computeStats.js';
|
import { computeSessionStats } from '../utils/computeStats.js';
|
||||||
import {
|
|
||||||
type Config,
|
|
||||||
type RetrieveUserQuotaResponse,
|
|
||||||
isActiveModel,
|
|
||||||
getDisplayString,
|
|
||||||
isAutoModel,
|
|
||||||
AuthType,
|
|
||||||
} from '@google/gemini-cli-core';
|
|
||||||
import { useSettings } from '../contexts/SettingsContext.js';
|
import { useSettings } from '../contexts/SettingsContext.js';
|
||||||
import { useConfig } from '../contexts/ConfigContext.js';
|
|
||||||
import type { QuotaStats } from '../types.js';
|
import type { QuotaStats } from '../types.js';
|
||||||
import { QuotaStatsInfo } from './QuotaStatsInfo.js';
|
|
||||||
|
|
||||||
// A more flexible and powerful StatRow component
|
// A more flexible and powerful StatRow component
|
||||||
interface StatRowProps {
|
interface StatRowProps {
|
||||||
@@ -87,433 +72,94 @@ const Section: React.FC<SectionProps> = ({ title, children }) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Logic for building the unified list of table rows
|
// Logic for building the unified list of table rows
|
||||||
const buildModelRows = (
|
|
||||||
models: Record<string, ModelMetrics>,
|
|
||||||
config: Config,
|
|
||||||
quotas?: RetrieveUserQuotaResponse,
|
|
||||||
useGemini3_1 = false,
|
|
||||||
useGemini3_1FlashLite = false,
|
|
||||||
useCustomToolModel = false,
|
|
||||||
) => {
|
|
||||||
const getBaseModelName = (name: string) => name.replace('-001', '');
|
|
||||||
const usedModelNames = new Set(
|
|
||||||
Object.keys(models)
|
|
||||||
.map(getBaseModelName)
|
|
||||||
.map((name) => getDisplayString(name, config)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 1. Models with active usage
|
interface ModelUsageTableProps {
|
||||||
const activeRows = Object.entries(models).map(([name, metrics]) => {
|
|
||||||
const modelName = getBaseModelName(name);
|
|
||||||
const cachedTokens = metrics.tokens.cached;
|
|
||||||
const inputTokens = metrics.tokens.input;
|
|
||||||
return {
|
|
||||||
key: name,
|
|
||||||
modelName: getDisplayString(modelName, config),
|
|
||||||
requests: metrics.api.totalRequests,
|
|
||||||
cachedTokens: cachedTokens.toLocaleString(),
|
|
||||||
inputTokens: inputTokens.toLocaleString(),
|
|
||||||
outputTokens: metrics.tokens.candidates.toLocaleString(),
|
|
||||||
bucket: quotas?.buckets?.find((b) => b.modelId === modelName),
|
|
||||||
isActive: true,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Models with quota only
|
|
||||||
const quotaRows =
|
|
||||||
quotas?.buckets
|
|
||||||
?.filter(
|
|
||||||
(b) =>
|
|
||||||
b.modelId &&
|
|
||||||
isActiveModel(
|
|
||||||
b.modelId,
|
|
||||||
useGemini3_1,
|
|
||||||
useGemini3_1FlashLite,
|
|
||||||
useCustomToolModel,
|
|
||||||
) &&
|
|
||||||
!usedModelNames.has(getDisplayString(b.modelId, config)),
|
|
||||||
)
|
|
||||||
.map((bucket) => ({
|
|
||||||
key: bucket.modelId!,
|
|
||||||
modelName: getDisplayString(bucket.modelId!, config),
|
|
||||||
requests: '-',
|
|
||||||
cachedTokens: '-',
|
|
||||||
inputTokens: '-',
|
|
||||||
outputTokens: '-',
|
|
||||||
bucket,
|
|
||||||
isActive: false,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
return [...activeRows, ...quotaRows];
|
|
||||||
};
|
|
||||||
|
|
||||||
const ModelUsageTable: React.FC<{
|
|
||||||
models: Record<string, ModelMetrics>;
|
models: Record<string, ModelMetrics>;
|
||||||
config: Config;
|
}
|
||||||
quotas?: RetrieveUserQuotaResponse;
|
|
||||||
cacheEfficiency: number;
|
|
||||||
totalCachedTokens: number;
|
|
||||||
currentModel?: string;
|
|
||||||
pooledRemaining?: number;
|
|
||||||
pooledLimit?: number;
|
|
||||||
pooledResetTime?: string;
|
|
||||||
useGemini3_1?: boolean;
|
|
||||||
useGemini3_1FlashLite?: boolean;
|
|
||||||
useCustomToolModel?: boolean;
|
|
||||||
}> = ({
|
|
||||||
models,
|
|
||||||
config,
|
|
||||||
quotas,
|
|
||||||
cacheEfficiency,
|
|
||||||
totalCachedTokens,
|
|
||||||
currentModel,
|
|
||||||
pooledRemaining,
|
|
||||||
pooledLimit,
|
|
||||||
pooledResetTime,
|
|
||||||
useGemini3_1,
|
|
||||||
useGemini3_1FlashLite,
|
|
||||||
useCustomToolModel,
|
|
||||||
}) => {
|
|
||||||
const { stdout } = useStdout();
|
|
||||||
const terminalWidth = stdout?.columns ?? 84;
|
|
||||||
const rows = buildModelRows(
|
|
||||||
models,
|
|
||||||
config,
|
|
||||||
quotas,
|
|
||||||
useGemini3_1,
|
|
||||||
useGemini3_1FlashLite,
|
|
||||||
useCustomToolModel,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (rows.length === 0) {
|
const ModelUsageTable: React.FC<ModelUsageTableProps> = ({ models }) => {
|
||||||
return null;
|
const nameWidth = 28;
|
||||||
}
|
const requestsWidth = 8;
|
||||||
|
const inputTokensWidth = 14;
|
||||||
const showQuotaColumn = !!quotas && rows.some((row) => !!row.bucket);
|
const cacheReadsWidth = 14;
|
||||||
|
const outputTokensWidth = 14;
|
||||||
const nameWidth = 23;
|
|
||||||
const requestsWidth = 5;
|
|
||||||
const uncachedWidth = 15;
|
|
||||||
const cachedWidth = 14;
|
|
||||||
const outputTokensWidth = 15;
|
|
||||||
const percentageWidth = showQuotaColumn ? 6 : 0;
|
|
||||||
const resetWidth = 22;
|
|
||||||
|
|
||||||
// Total width of other columns (including parent box paddingX={2})
|
|
||||||
const fixedWidth = nameWidth + requestsWidth + percentageWidth + resetWidth;
|
|
||||||
const outerPadding = 4;
|
|
||||||
const availableForUsage = terminalWidth - outerPadding - fixedWidth;
|
|
||||||
|
|
||||||
const usageLimitWidth = showQuotaColumn
|
|
||||||
? Math.max(10, Math.min(24, availableForUsage))
|
|
||||||
: 0;
|
|
||||||
const progressBarWidth = Math.max(2, usageLimitWidth - 4);
|
|
||||||
|
|
||||||
const renderProgressBar = (
|
|
||||||
usedFraction: number,
|
|
||||||
color: string,
|
|
||||||
totalSteps = 20,
|
|
||||||
) => {
|
|
||||||
let filledSteps = Math.round(usedFraction * totalSteps);
|
|
||||||
|
|
||||||
// If something is used (fraction > 0) but rounds to 0, show 1 tick.
|
|
||||||
// If < 100% (fraction < 1) but rounds to 20, show 19 ticks.
|
|
||||||
if (usedFraction > 0 && usedFraction < 1) {
|
|
||||||
filledSteps = Math.min(Math.max(filledSteps, 1), totalSteps - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const emptySteps = Math.max(0, totalSteps - filledSteps);
|
|
||||||
return (
|
|
||||||
<Box flexDirection="row" flexShrink={0}>
|
|
||||||
<Text wrap="truncate-end">
|
|
||||||
<Text color={color}>{'▬'.repeat(filledSteps)}</Text>
|
|
||||||
<Text color={theme.border.default}>{'▬'.repeat(emptySteps)}</Text>
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const cacheEfficiencyColor = getStatusColor(cacheEfficiency, {
|
|
||||||
green: CACHE_EFFICIENCY_HIGH,
|
|
||||||
yellow: CACHE_EFFICIENCY_MEDIUM,
|
|
||||||
});
|
|
||||||
|
|
||||||
const totalWidth =
|
|
||||||
nameWidth +
|
|
||||||
requestsWidth +
|
|
||||||
(showQuotaColumn
|
|
||||||
? usageLimitWidth + percentageWidth + resetWidth
|
|
||||||
: uncachedWidth + cachedWidth + outputTokensWidth);
|
|
||||||
|
|
||||||
const isAuto = currentModel && isAutoModel(currentModel);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" marginBottom={1}>
|
<Box flexDirection="column" marginTop={1}>
|
||||||
{isAuto &&
|
<Text bold color={theme.text.primary}>
|
||||||
showQuotaColumn &&
|
Model Usage
|
||||||
pooledRemaining !== undefined &&
|
</Text>
|
||||||
pooledLimit !== undefined &&
|
<Text color={theme.text.secondary}>
|
||||||
pooledLimit > 0 && (
|
Use /model to view model quota information
|
||||||
<Box flexDirection="column" marginTop={0} marginBottom={1}>
|
</Text>
|
||||||
<QuotaStatsInfo
|
<Box height={1} />
|
||||||
remaining={pooledRemaining}
|
|
||||||
limit={pooledLimit}
|
|
||||||
resetTime={pooledResetTime}
|
|
||||||
/>
|
|
||||||
<Text color={theme.text.primary}>
|
|
||||||
For a full token breakdown, run `/stats model`.
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Box alignItems="flex-end">
|
{/* Header */}
|
||||||
<Box width={nameWidth} flexShrink={0}>
|
|
||||||
<Text bold color={theme.text.primary}>
|
|
||||||
Model
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
width={requestsWidth}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="flex-end"
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
<Text bold color={theme.text.primary}>
|
|
||||||
Reqs
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{!showQuotaColumn && (
|
|
||||||
<>
|
|
||||||
<Box
|
|
||||||
width={uncachedWidth}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="flex-end"
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
<Text bold color={theme.text.primary}>
|
|
||||||
Input Tokens
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
width={cachedWidth}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="flex-end"
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
<Text bold color={theme.text.primary}>
|
|
||||||
Cache Reads
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
width={outputTokensWidth}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="flex-end"
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
<Text bold color={theme.text.primary}>
|
|
||||||
Output Tokens
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{showQuotaColumn && (
|
|
||||||
<>
|
|
||||||
<Box
|
|
||||||
width={usageLimitWidth}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="flex-start"
|
|
||||||
paddingLeft={4}
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
<Text bold color={theme.text.primary}>
|
|
||||||
Model usage
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box width={percentageWidth} flexShrink={0} />
|
|
||||||
<Box
|
|
||||||
width={resetWidth}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="flex-start"
|
|
||||||
paddingLeft={2}
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
<Text bold color={theme.text.primary} wrap="truncate-end">
|
|
||||||
Usage resets
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Divider */}
|
|
||||||
<Box
|
<Box
|
||||||
borderStyle="round"
|
|
||||||
borderBottom={true}
|
borderBottom={true}
|
||||||
|
borderStyle="single"
|
||||||
|
borderColor={theme.border.default}
|
||||||
borderTop={false}
|
borderTop={false}
|
||||||
borderLeft={false}
|
borderLeft={false}
|
||||||
borderRight={false}
|
borderRight={false}
|
||||||
borderColor={theme.border.default}
|
>
|
||||||
width={totalWidth}
|
<Box width={nameWidth}>
|
||||||
></Box>
|
<Text bold color={theme.text.secondary}>
|
||||||
|
Model
|
||||||
{rows.map((row) => {
|
|
||||||
let effectiveUsedFraction = 0;
|
|
||||||
let usedPercentage = 0;
|
|
||||||
let statusColor = theme.ui.comment;
|
|
||||||
let percentageText = '';
|
|
||||||
|
|
||||||
if (row.bucket && row.bucket.remainingFraction != null) {
|
|
||||||
const actualUsedFraction = 1 - row.bucket.remainingFraction;
|
|
||||||
effectiveUsedFraction =
|
|
||||||
actualUsedFraction === 0 && row.isActive
|
|
||||||
? 0.001
|
|
||||||
: actualUsedFraction;
|
|
||||||
usedPercentage = effectiveUsedFraction * 100;
|
|
||||||
statusColor =
|
|
||||||
getUsedStatusColor(usedPercentage, {
|
|
||||||
warning: QUOTA_USED_WARNING_THRESHOLD,
|
|
||||||
critical: QUOTA_USED_CRITICAL_THRESHOLD,
|
|
||||||
}) ?? (row.isActive ? theme.text.primary : theme.ui.comment);
|
|
||||||
percentageText =
|
|
||||||
usedPercentage > 0 && usedPercentage < 1
|
|
||||||
? `${usedPercentage.toFixed(1)}%`
|
|
||||||
: `${usedPercentage.toFixed(0)}%`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box key={row.key}>
|
|
||||||
<Box width={nameWidth} flexShrink={0}>
|
|
||||||
<Text
|
|
||||||
color={row.isActive ? theme.text.primary : theme.text.secondary}
|
|
||||||
wrap="truncate-end"
|
|
||||||
>
|
|
||||||
{row.modelName}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
width={requestsWidth}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="flex-end"
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
color={row.isActive ? theme.text.primary : theme.text.secondary}
|
|
||||||
>
|
|
||||||
{row.requests}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
{!showQuotaColumn && (
|
|
||||||
<>
|
|
||||||
<Box
|
|
||||||
width={uncachedWidth}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="flex-end"
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
color={
|
|
||||||
row.isActive ? theme.text.primary : theme.text.secondary
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{row.inputTokens}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
width={cachedWidth}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="flex-end"
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
<Text color={theme.text.secondary}>{row.cachedTokens}</Text>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
width={outputTokensWidth}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="flex-end"
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
color={
|
|
||||||
row.isActive ? theme.text.primary : theme.text.secondary
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{row.outputTokens}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{showQuotaColumn && (
|
|
||||||
<>
|
|
||||||
<Box
|
|
||||||
width={usageLimitWidth}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="flex-start"
|
|
||||||
paddingLeft={4}
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
{row.bucket && row.bucket.remainingFraction != null && (
|
|
||||||
<Box flexDirection="row" flexShrink={0}>
|
|
||||||
{renderProgressBar(
|
|
||||||
effectiveUsedFraction,
|
|
||||||
statusColor,
|
|
||||||
progressBarWidth,
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
width={percentageWidth}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="flex-end"
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
{row.bucket && row.bucket.remainingFraction != null && (
|
|
||||||
<Box>
|
|
||||||
{row.bucket.remainingFraction === 0 ? (
|
|
||||||
<Text color={theme.status.error} wrap="truncate-end">
|
|
||||||
Limit
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<Text color={statusColor} wrap="truncate-end">
|
|
||||||
{percentageText}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
width={resetWidth}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="flex-start"
|
|
||||||
paddingLeft={2}
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
<Text color={theme.text.secondary} wrap="truncate-end">
|
|
||||||
{row.bucket?.resetTime &&
|
|
||||||
formatResetTime(row.bucket.resetTime, 'column')
|
|
||||||
? formatResetTime(row.bucket.resetTime, 'column')
|
|
||||||
: ''}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{cacheEfficiency > 0 && !showQuotaColumn && (
|
|
||||||
<Box flexDirection="column" marginTop={1}>
|
|
||||||
<Text color={theme.text.primary}>
|
|
||||||
<Text color={theme.status.success}>Savings Highlight:</Text>{' '}
|
|
||||||
{totalCachedTokens.toLocaleString()} (
|
|
||||||
<Text color={cacheEfficiencyColor}>
|
|
||||||
{cacheEfficiency.toFixed(1)}%
|
|
||||||
</Text>
|
|
||||||
) of input tokens were served from the cache, reducing costs.
|
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
<Box width={requestsWidth} justifyContent="flex-end">
|
||||||
|
<Text bold color={theme.text.secondary}>
|
||||||
|
Reqs
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box width={inputTokensWidth} justifyContent="flex-end">
|
||||||
|
<Text bold color={theme.text.secondary}>
|
||||||
|
Input Tokens
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box width={cacheReadsWidth} justifyContent="flex-end">
|
||||||
|
<Text bold color={theme.text.secondary}>
|
||||||
|
Cache Reads
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box width={outputTokensWidth} justifyContent="flex-end">
|
||||||
|
<Text bold color={theme.text.secondary}>
|
||||||
|
Output Tokens
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Rows */}
|
||||||
|
{Object.entries(models).map(([name, modelMetrics]) => (
|
||||||
|
<Box key={name}>
|
||||||
|
<Box width={nameWidth}>
|
||||||
|
<Text color={theme.text.primary} wrap="truncate-end">
|
||||||
|
{name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box width={requestsWidth} justifyContent="flex-end">
|
||||||
|
<Text color={theme.text.primary}>
|
||||||
|
{modelMetrics.api.totalRequests}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box width={inputTokensWidth} justifyContent="flex-end">
|
||||||
|
<Text color={theme.text.primary}>
|
||||||
|
{modelMetrics.tokens.prompt.toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box width={cacheReadsWidth} justifyContent="flex-end">
|
||||||
|
<Text color={theme.text.primary}>
|
||||||
|
{modelMetrics.tokens.cached.toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box width={outputTokensWidth} justifyContent="flex-end">
|
||||||
|
<Text color={theme.text.primary}>
|
||||||
|
{modelMetrics.tokens.candidates.toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -521,7 +167,6 @@ const ModelUsageTable: React.FC<{
|
|||||||
interface StatsDisplayProps {
|
interface StatsDisplayProps {
|
||||||
duration: string;
|
duration: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
quotas?: RetrieveUserQuotaResponse;
|
|
||||||
footer?: string;
|
footer?: string;
|
||||||
selectedAuthType?: string;
|
selectedAuthType?: string;
|
||||||
userEmail?: string;
|
userEmail?: string;
|
||||||
@@ -534,30 +179,17 @@ interface StatsDisplayProps {
|
|||||||
export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
||||||
duration,
|
duration,
|
||||||
title,
|
title,
|
||||||
quotas,
|
|
||||||
footer,
|
footer,
|
||||||
selectedAuthType,
|
selectedAuthType,
|
||||||
userEmail,
|
userEmail,
|
||||||
tier,
|
tier,
|
||||||
currentModel,
|
|
||||||
quotaStats,
|
|
||||||
creditBalance,
|
creditBalance,
|
||||||
}) => {
|
}) => {
|
||||||
const { stats } = useSessionStats();
|
const { stats } = useSessionStats();
|
||||||
const { metrics } = stats;
|
const { metrics } = stats;
|
||||||
const { models, tools, files } = metrics;
|
const { tools, files, models } = metrics;
|
||||||
const computed = computeSessionStats(metrics);
|
const computed = computeSessionStats(metrics);
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const config = useConfig();
|
|
||||||
const useGemini3_1 = config.getGemini31LaunchedSync?.() ?? false;
|
|
||||||
const useGemini3_1FlashLite =
|
|
||||||
config.getGemini31FlashLiteLaunchedSync?.() ?? false;
|
|
||||||
const useCustomToolModel =
|
|
||||||
useGemini3_1 &&
|
|
||||||
config.getContentGeneratorConfig().authType === AuthType.USE_GEMINI;
|
|
||||||
const pooledRemaining = quotaStats?.remaining;
|
|
||||||
const pooledLimit = quotaStats?.limit;
|
|
||||||
const pooledResetTime = quotaStats?.resetTime;
|
|
||||||
|
|
||||||
const showUserIdentity = settings.merged.ui.showUserIdentity;
|
const showUserIdentity = settings.merged.ui.showUserIdentity;
|
||||||
|
|
||||||
@@ -697,20 +329,9 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
|||||||
</Text>
|
</Text>
|
||||||
</SubStatRow>
|
</SubStatRow>
|
||||||
</Section>
|
</Section>
|
||||||
<ModelUsageTable
|
|
||||||
models={models}
|
{Object.keys(models).length > 0 && <ModelUsageTable models={models} />}
|
||||||
config={config}
|
|
||||||
quotas={quotas}
|
|
||||||
cacheEfficiency={computed.cacheEfficiency}
|
|
||||||
totalCachedTokens={computed.totalCachedTokens}
|
|
||||||
currentModel={currentModel}
|
|
||||||
pooledRemaining={pooledRemaining}
|
|
||||||
pooledLimit={pooledLimit}
|
|
||||||
pooledResetTime={pooledResetTime}
|
|
||||||
useGemini3_1={useGemini3_1}
|
|
||||||
useGemini3_1FlashLite={useGemini3_1FlashLite}
|
|
||||||
useCustomToolModel={useCustomToolModel}
|
|
||||||
/>
|
|
||||||
{renderFooter()}
|
{renderFooter()}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`<ModelQuotaDisplay /> > renders quota information when buckets are provided 1`] = `
|
||||||
|
"
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Model usage
|
||||||
|
|
||||||
|
Pro ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ 25% Resets: 1:30 PM (1h 30m)
|
||||||
|
"
|
||||||
|
`;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`<ProgressBar /> > renders 0% correctly 1`] = `
|
||||||
|
"▬▬▬▬▬▬▬▬▬▬
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<ProgressBar /> > renders 50% correctly 1`] = `
|
||||||
|
"▬▬▬▬▬▬▬▬▬▬
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<ProgressBar /> > renders error threshold correctly at 100% 1`] = `
|
||||||
|
"▬▬▬▬▬▬▬▬▬▬
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<ProgressBar /> > renders warning threshold correctly 1`] = `
|
||||||
|
"▬▬▬▬▬▬▬▬▬▬
|
||||||
|
"
|
||||||
|
`;
|
||||||
@@ -17,12 +17,13 @@ exports[`<SessionSummaryDisplay /> > renders the summary display with a title 1`
|
|||||||
│ » API Time: 50.2s (100.0%) │
|
│ » API Time: 50.2s (100.0%) │
|
||||||
│ » Tool Time: 0s (0.0%) │
|
│ » Tool Time: 0s (0.0%) │
|
||||||
│ │
|
│ │
|
||||||
│ Model Reqs Input Tokens Cache Reads Output Tokens │
|
|
||||||
│ ──────────────────────────────────────────────────────────────────────── │
|
|
||||||
│ gemini-2.5-pro 10 500 500 2,000 │
|
|
||||||
│ │
|
│ │
|
||||||
│ Savings Highlight: 500 (50.0%) of input tokens were served from the cache, reducing costs. │
|
│ Model Usage │
|
||||||
|
│ Use /model to view model quota information │
|
||||||
│ │
|
│ │
|
||||||
|
│ Model Reqs Input Tokens Cache Reads Output Tokens │
|
||||||
|
│ ────────────────────────────────────────────────────────────────────────────────────────────── │
|
||||||
|
│ gemini-2.5-pro 10 1,000 500 2,000 │
|
||||||
│ To resume this session: gemini --resume test-session │
|
│ To resume this session: gemini --resume test-session │
|
||||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -117,10 +117,13 @@ exports[`<StatsDisplay /> > Conditional Rendering Tests > hides Efficiency secti
|
|||||||
│ » API Time: 100ms (100.0%) │
|
│ » API Time: 100ms (100.0%) │
|
||||||
│ » Tool Time: 0s (0.0%) │
|
│ » Tool Time: 0s (0.0%) │
|
||||||
│ │
|
│ │
|
||||||
│ Model Reqs Input Tokens Cache Reads Output Tokens │
|
|
||||||
│ ──────────────────────────────────────────────────────────────────────── │
|
|
||||||
│ gemini-2.5-pro 1 100 0 100 │
|
|
||||||
│ │
|
│ │
|
||||||
|
│ Model Usage │
|
||||||
|
│ Use /model to view model quota information │
|
||||||
|
│ │
|
||||||
|
│ Model Reqs Input Tokens Cache Reads Output Tokens │
|
||||||
|
│ ────────────────────────────────────────────────────────────────────────────────────────────── │
|
||||||
|
│ gemini-2.5-pro 1 100 0 100 │
|
||||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
@@ -145,84 +148,6 @@ exports[`<StatsDisplay /> > Conditional Rendering Tests > hides User Agreement w
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<StatsDisplay /> > Quota Display > renders pooled quota information for auto mode 1`] = `
|
|
||||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
||||||
│ │
|
|
||||||
│ Session Stats │
|
|
||||||
│ │
|
|
||||||
│ Interaction Summary │
|
|
||||||
│ Session ID: test-session-id │
|
|
||||||
│ Tool Calls: 0 ( ✓ 0 x 0 ) │
|
|
||||||
│ Success Rate: 0.0% │
|
|
||||||
│ │
|
|
||||||
│ Performance │
|
|
||||||
│ Wall Time: 1s │
|
|
||||||
│ Agent Active: 0s │
|
|
||||||
│ » API Time: 0s (0.0%) │
|
|
||||||
│ » Tool Time: 0s (0.0%) │
|
|
||||||
│ │
|
|
||||||
│ 35% used │
|
|
||||||
│ Usage limit: 1,100 │
|
|
||||||
│ Usage limits span all sessions and reset daily. │
|
|
||||||
│ For a full token breakdown, run \`/stats model\`. │
|
|
||||||
│ │
|
|
||||||
│ Model Reqs Model usage Usage resets │
|
|
||||||
│ ──────────────────────────────────────────────────────────────────────────────── │
|
|
||||||
│ gemini-2.5-pro - ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ 90% │
|
|
||||||
│ gemini-2.5-flash - ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ 30% │
|
|
||||||
│ │
|
|
||||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<StatsDisplay /> > Quota Display > renders quota information for unused models 1`] = `
|
|
||||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
||||||
│ │
|
|
||||||
│ Session Stats │
|
|
||||||
│ │
|
|
||||||
│ Interaction Summary │
|
|
||||||
│ Session ID: test-session-id │
|
|
||||||
│ Tool Calls: 0 ( ✓ 0 x 0 ) │
|
|
||||||
│ Success Rate: 0.0% │
|
|
||||||
│ │
|
|
||||||
│ Performance │
|
|
||||||
│ Wall Time: 1s │
|
|
||||||
│ Agent Active: 0s │
|
|
||||||
│ » API Time: 0s (0.0%) │
|
|
||||||
│ » Tool Time: 0s (0.0%) │
|
|
||||||
│ │
|
|
||||||
│ Model Reqs Model usage Usage resets │
|
|
||||||
│ ──────────────────────────────────────────────────────────────────────────────── │
|
|
||||||
│ gemini-2.5-flash - ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ 50% 2:00 PM (2h) │
|
|
||||||
│ │
|
|
||||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<StatsDisplay /> > Quota Display > renders quota information when quotas are provided 1`] = `
|
|
||||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
||||||
│ │
|
|
||||||
│ Session Stats │
|
|
||||||
│ │
|
|
||||||
│ Interaction Summary │
|
|
||||||
│ Session ID: test-session-id │
|
|
||||||
│ Tool Calls: 0 ( ✓ 0 x 0 ) │
|
|
||||||
│ Success Rate: 0.0% │
|
|
||||||
│ │
|
|
||||||
│ Performance │
|
|
||||||
│ Wall Time: 1s │
|
|
||||||
│ Agent Active: 100ms │
|
|
||||||
│ » API Time: 100ms (100.0%) │
|
|
||||||
│ » Tool Time: 0s (0.0%) │
|
|
||||||
│ │
|
|
||||||
│ Model Reqs Model usage Usage resets │
|
|
||||||
│ ──────────────────────────────────────────────────────────────────────────────── │
|
|
||||||
│ gemini-2.5-pro 1 ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ 25% 1:30 PM (1h 30m) │
|
|
||||||
│ │
|
|
||||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<StatsDisplay /> > Title Rendering > renders the custom title when a title prop is provided 1`] = `
|
exports[`<StatsDisplay /> > Title Rendering > renders the custom title when a title prop is provided 1`] = `
|
||||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ │
|
│ │
|
||||||
@@ -279,13 +204,14 @@ exports[`<StatsDisplay /> > renders a table with two models correctly 1`] = `
|
|||||||
│ » API Time: 19.5s (100.0%) │
|
│ » API Time: 19.5s (100.0%) │
|
||||||
│ » Tool Time: 0s (0.0%) │
|
│ » Tool Time: 0s (0.0%) │
|
||||||
│ │
|
│ │
|
||||||
│ Model Reqs Input Tokens Cache Reads Output Tokens │
|
|
||||||
│ ──────────────────────────────────────────────────────────────────────── │
|
|
||||||
│ gemini-2.5-pro 3 500 500 2,000 │
|
|
||||||
│ gemini-2.5-flash 5 15,000 10,000 15,000 │
|
|
||||||
│ │
|
│ │
|
||||||
│ Savings Highlight: 10,500 (40.4%) of input tokens were served from the cache, reducing costs. │
|
│ Model Usage │
|
||||||
|
│ Use /model to view model quota information │
|
||||||
│ │
|
│ │
|
||||||
|
│ Model Reqs Input Tokens Cache Reads Output Tokens │
|
||||||
|
│ ────────────────────────────────────────────────────────────────────────────────────────────── │
|
||||||
|
│ gemini-2.5-pro 3 1,000 500 2,000 │
|
||||||
|
│ gemini-2.5-flash 5 25,000 10,000 15,000 │
|
||||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
@@ -307,12 +233,13 @@ exports[`<StatsDisplay /> > renders all sections when all data is present 1`] =
|
|||||||
│ » API Time: 100ms (44.8%) │
|
│ » API Time: 100ms (44.8%) │
|
||||||
│ » Tool Time: 123ms (55.2%) │
|
│ » Tool Time: 123ms (55.2%) │
|
||||||
│ │
|
│ │
|
||||||
│ Model Reqs Input Tokens Cache Reads Output Tokens │
|
|
||||||
│ ──────────────────────────────────────────────────────────────────────── │
|
|
||||||
│ gemini-2.5-pro 1 50 50 100 │
|
|
||||||
│ │
|
│ │
|
||||||
│ Savings Highlight: 50 (50.0%) of input tokens were served from the cache, reducing costs. │
|
│ Model Usage │
|
||||||
|
│ Use /model to view model quota information │
|
||||||
│ │
|
│ │
|
||||||
|
│ Model Reqs Input Tokens Cache Reads Output Tokens │
|
||||||
|
│ ────────────────────────────────────────────────────────────────────────────────────────────── │
|
||||||
|
│ gemini-2.5-pro 1 100 50 100 │
|
||||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|||||||
+401
@@ -0,0 +1,401 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1460" height="615" viewBox="0 0 1460 615">
|
||||||
|
<style>
|
||||||
|
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||||
|
</style>
|
||||||
|
<rect width="1460" height="615" fill="#000000" />
|
||||||
|
<g transform="translate(10, 10)">
|
||||||
|
<text x="0" y="19" fill="#333333" textLength="1404" lengthAdjust="spacingAndGlyphs">┌─────────────────────────────┬──────────────────────────────┬─────────────────────────────┬──────────────────────────────┬─────┬────────┬─────────┬───────┐</text>
|
||||||
|
<text x="0" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="36" fill="#87afff" textLength="243" lengthAdjust="spacingAndGlyphs" font-weight="bold">Comprehensive Architectural</text>
|
||||||
|
<text x="270" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="288" y="36" fill="#87afff" textLength="234" lengthAdjust="spacingAndGlyphs" font-weight="bold">Implementation Details for</text>
|
||||||
|
<text x="549" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="567" y="36" fill="#87afff" textLength="216" lengthAdjust="spacingAndGlyphs" font-weight="bold">Longitudinal Performance</text>
|
||||||
|
<text x="819" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="837" y="36" fill="#87afff" textLength="252" lengthAdjust="spacingAndGlyphs" font-weight="bold">Strategic Security Framework</text>
|
||||||
|
<text x="1098" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1116" y="36" fill="#87afff" textLength="27" lengthAdjust="spacingAndGlyphs" font-weight="bold">Key</text>
|
||||||
|
<text x="1152" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1170" y="36" fill="#87afff" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">Status</text>
|
||||||
|
<text x="1233" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1251" y="36" fill="#87afff" textLength="63" lengthAdjust="spacingAndGlyphs" font-weight="bold">Version</text>
|
||||||
|
<text x="1323" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1341" y="36" fill="#87afff" textLength="45" lengthAdjust="spacingAndGlyphs" font-weight="bold">Owner</text>
|
||||||
|
<text x="1395" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="53" fill="#87afff" textLength="189" lengthAdjust="spacingAndGlyphs" font-weight="bold">Specification for the</text>
|
||||||
|
<text x="270" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="288" y="53" fill="#87afff" textLength="171" lengthAdjust="spacingAndGlyphs" font-weight="bold">the High-Throughput</text>
|
||||||
|
<text x="549" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="567" y="53" fill="#87afff" textLength="135" lengthAdjust="spacingAndGlyphs" font-weight="bold">Analysis Across</text>
|
||||||
|
<text x="819" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="837" y="53" fill="#87afff" textLength="252" lengthAdjust="spacingAndGlyphs" font-weight="bold">for Mitigating Sophisticated</text>
|
||||||
|
<text x="1098" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="70" fill="#87afff" textLength="234" lengthAdjust="spacingAndGlyphs" font-weight="bold">Distributed Infrastructure</text>
|
||||||
|
<text x="270" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="288" y="70" fill="#87afff" textLength="180" lengthAdjust="spacingAndGlyphs" font-weight="bold">Asynchronous Message</text>
|
||||||
|
<text x="549" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="567" y="70" fill="#87afff" textLength="180" lengthAdjust="spacingAndGlyphs" font-weight="bold">Multi-Regional Cloud</text>
|
||||||
|
<text x="819" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="837" y="70" fill="#87afff" textLength="180" lengthAdjust="spacingAndGlyphs" font-weight="bold">Cross-Site Scripting</text>
|
||||||
|
<text x="1098" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="87" fill="#87afff" textLength="45" lengthAdjust="spacingAndGlyphs" font-weight="bold">Layer</text>
|
||||||
|
<text x="270" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="288" y="87" fill="#87afff" textLength="216" lengthAdjust="spacingAndGlyphs" font-weight="bold">Processing Pipeline with</text>
|
||||||
|
<text x="549" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="567" y="87" fill="#87afff" textLength="171" lengthAdjust="spacingAndGlyphs" font-weight="bold">Deployment Clusters</text>
|
||||||
|
<text x="819" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="837" y="87" fill="#87afff" textLength="135" lengthAdjust="spacingAndGlyphs" font-weight="bold">Vulnerabilities</text>
|
||||||
|
<text x="1098" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="270" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="288" y="104" fill="#87afff" textLength="180" lengthAdjust="spacingAndGlyphs" font-weight="bold">Extended Scalability</text>
|
||||||
|
<text x="549" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="819" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1098" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="270" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="288" y="121" fill="#87afff" textLength="207" lengthAdjust="spacingAndGlyphs" font-weight="bold">Features and Redundancy</text>
|
||||||
|
<text x="549" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="819" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1098" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="270" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="288" y="138" fill="#87afff" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">Protocols</text>
|
||||||
|
<text x="549" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="819" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1098" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="155" fill="#333333" textLength="1404" lengthAdjust="spacingAndGlyphs">├─────────────────────────────┼──────────────────────────────┼─────────────────────────────┼──────────────────────────────┼─────┼────────┼─────────┼───────┤</text>
|
||||||
|
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="172" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> The primary architecture </text>
|
||||||
|
<text x="270" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="172" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> Each message is processed </text>
|
||||||
|
<text x="549" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="172" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Historical data indicates a </text>
|
||||||
|
<text x="819" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="172" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> A multi-layered defense </text>
|
||||||
|
<text x="1098" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1107" y="172" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs"> INF </text>
|
||||||
|
<text x="1152" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1161" y="172" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> Active </text>
|
||||||
|
<text x="1233" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1242" y="172" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs"> v2.4 </text>
|
||||||
|
<text x="1323" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1332" y="172" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs"> J. </text>
|
||||||
|
<text x="1395" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="189" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> utilizes a decoupled </text>
|
||||||
|
<text x="270" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="189" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> through a series of </text>
|
||||||
|
<text x="549" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="189" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> significant reduction in </text>
|
||||||
|
<text x="819" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="189" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> strategy incorporates </text>
|
||||||
|
<text x="1098" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1332" y="189" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs"> Doe </text>
|
||||||
|
<text x="1395" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="206" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> microservices approach, </text>
|
||||||
|
<text x="270" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="206" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> specialized workers that </text>
|
||||||
|
<text x="549" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="206" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> tail latency when utilizing </text>
|
||||||
|
<text x="819" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="206" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> content security policies, </text>
|
||||||
|
<text x="1098" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="223" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> leveraging container </text>
|
||||||
|
<text x="270" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="223" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> handle data transformation, </text>
|
||||||
|
<text x="549" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="223" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> edge computing nodes closer </text>
|
||||||
|
<text x="819" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="223" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> input sanitization </text>
|
||||||
|
<text x="1098" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="240" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> orchestration for </text>
|
||||||
|
<text x="270" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="240" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> validation, and persistent </text>
|
||||||
|
<text x="549" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="240" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> to the geographic location </text>
|
||||||
|
<text x="819" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="240" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> libraries, and regular </text>
|
||||||
|
<text x="1098" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="257" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> scalability and fault </text>
|
||||||
|
<text x="270" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="257" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> storage using a persistent </text>
|
||||||
|
<text x="549" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="257" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> of the end-user base. </text>
|
||||||
|
<text x="819" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="257" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> automated penetration </text>
|
||||||
|
<text x="1098" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="274" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> tolerance in high-load </text>
|
||||||
|
<text x="270" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="274" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> queue. </text>
|
||||||
|
<text x="549" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="819" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="274" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> testing routines. </text>
|
||||||
|
<text x="1098" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="291" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> scenarios. </text>
|
||||||
|
<text x="270" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="549" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="291" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Monitoring tools have </text>
|
||||||
|
<text x="819" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1098" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="270" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="308" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> The pipeline features </text>
|
||||||
|
<text x="549" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="308" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> captured a steady increase </text>
|
||||||
|
<text x="819" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="308" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> Developers are required to </text>
|
||||||
|
<text x="1098" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="325" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> This layer provides the </text>
|
||||||
|
<text x="270" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="325" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> built-in retry mechanisms </text>
|
||||||
|
<text x="549" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="325" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> in throughput efficiency </text>
|
||||||
|
<text x="819" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="325" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> undergo mandatory security </text>
|
||||||
|
<text x="1098" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="342" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> fundamental building blocks </text>
|
||||||
|
<text x="270" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="342" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> with exponential backoff to </text>
|
||||||
|
<text x="549" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="342" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> since the introduction of </text>
|
||||||
|
<text x="819" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="342" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> training focusing on the </text>
|
||||||
|
<text x="1098" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="359" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> for service discovery, load </text>
|
||||||
|
<text x="270" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="359" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> ensure message delivery </text>
|
||||||
|
<text x="549" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="359" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> the vectorized query engine </text>
|
||||||
|
<text x="819" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="359" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> OWASP Top Ten to ensure that </text>
|
||||||
|
<text x="1098" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="376" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> balancing, and </text>
|
||||||
|
<text x="270" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="376" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> integrity even during </text>
|
||||||
|
<text x="549" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="376" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> in the primary data </text>
|
||||||
|
<text x="819" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="376" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> security is integrated into </text>
|
||||||
|
<text x="1098" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="393" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> inter-service communication </text>
|
||||||
|
<text x="270" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="393" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> transient network or service </text>
|
||||||
|
<text x="549" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="393" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> warehouse. </text>
|
||||||
|
<text x="819" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="393" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> the initial design phase. </text>
|
||||||
|
<text x="1098" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="410" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> via highly efficient </text>
|
||||||
|
<text x="270" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="410" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> failures. </text>
|
||||||
|
<text x="549" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="819" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1098" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="427" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> protocol buffers. </text>
|
||||||
|
<text x="270" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="549" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="427" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Resource utilization </text>
|
||||||
|
<text x="819" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="427" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> The implementation of a </text>
|
||||||
|
<text x="1098" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="270" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="444" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> Horizontal autoscaling is </text>
|
||||||
|
<text x="549" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="444" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> metrics demonstrate that </text>
|
||||||
|
<text x="819" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="444" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> robust Identity and Access </text>
|
||||||
|
<text x="1098" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="461" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Advanced telemetry and </text>
|
||||||
|
<text x="270" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="461" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> triggered automatically </text>
|
||||||
|
<text x="549" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="461" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> the transition to </text>
|
||||||
|
<text x="819" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="461" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> Management system ensures </text>
|
||||||
|
<text x="1098" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="478" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> logging integrations allow </text>
|
||||||
|
<text x="270" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="478" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> based on the depth of the </text>
|
||||||
|
<text x="549" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="478" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> serverless compute for </text>
|
||||||
|
<text x="819" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="478" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> that the principle of least </text>
|
||||||
|
<text x="1098" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="495" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> for real-time monitoring of </text>
|
||||||
|
<text x="270" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="495" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> processing queue, ensuring </text>
|
||||||
|
<text x="549" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="495" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> intermittent tasks has </text>
|
||||||
|
<text x="819" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="495" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> privilege is strictly </text>
|
||||||
|
<text x="1098" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="512" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> system health and rapid </text>
|
||||||
|
<text x="270" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="512" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> consistent performance </text>
|
||||||
|
<text x="549" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="512" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> resulted in a thirty </text>
|
||||||
|
<text x="819" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="512" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> enforced across all </text>
|
||||||
|
<text x="1098" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="529" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> identification of </text>
|
||||||
|
<text x="270" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="529" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> during unexpected traffic </text>
|
||||||
|
<text x="549" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="558" y="529" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> percent cost optimization. </text>
|
||||||
|
<text x="819" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="828" y="529" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> environments. </text>
|
||||||
|
<text x="1098" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="546" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> bottlenecks within the </text>
|
||||||
|
<text x="270" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="279" y="546" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> spikes. </text>
|
||||||
|
<text x="549" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="819" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1098" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="563" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> service mesh. </text>
|
||||||
|
<text x="270" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="549" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="819" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1098" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1152" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1233" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1323" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="1395" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="580" fill="#333333" textLength="1404" lengthAdjust="spacingAndGlyphs">└─────────────────────────────┴──────────────────────────────┴─────────────────────────────┴──────────────────────────────┴─────┴────────┴─────────┴───────┘</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 43 KiB |
@@ -165,6 +165,45 @@ exports[`TableRenderer > renders a complex table with mixed content lengths corr
|
|||||||
└─────────────────────────────┴──────────────────────────────┴─────────────────────────────┴──────────────────────────────┴─────┴────────┴─────────┴───────┘"
|
└─────────────────────────────┴──────────────────────────────┴─────────────────────────────┴──────────────────────────────┴─────┴────────┴─────────┴───────┘"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`TableRenderer > renders a complex table with mixed content lengths correctly 2`] = `
|
||||||
|
"
|
||||||
|
┌─────────────────────────────┬──────────────────────────────┬─────────────────────────────┬──────────────────────────────┬─────┬────────┬─────────┬───────┐
|
||||||
|
│ Comprehensive Architectural │ Implementation Details for │ Longitudinal Performance │ Strategic Security Framework │ Key │ Status │ Version │ Owner │
|
||||||
|
│ Specification for the │ the High-Throughput │ Analysis Across │ for Mitigating Sophisticated │ │ │ │ │
|
||||||
|
│ Distributed Infrastructure │ Asynchronous Message │ Multi-Regional Cloud │ Cross-Site Scripting │ │ │ │ │
|
||||||
|
│ Layer │ Processing Pipeline with │ Deployment Clusters │ Vulnerabilities │ │ │ │ │
|
||||||
|
│ │ Extended Scalability │ │ │ │ │ │ │
|
||||||
|
│ │ Features and Redundancy │ │ │ │ │ │ │
|
||||||
|
│ │ Protocols │ │ │ │ │ │ │
|
||||||
|
├─────────────────────────────┼──────────────────────────────┼─────────────────────────────┼──────────────────────────────┼─────┼────────┼─────────┼───────┤
|
||||||
|
│ The primary architecture │ Each message is processed │ Historical data indicates a │ A multi-layered defense │ INF │ Active │ v2.4 │ J. │
|
||||||
|
│ utilizes a decoupled │ through a series of │ significant reduction in │ strategy incorporates │ │ │ │ Doe │
|
||||||
|
│ microservices approach, │ specialized workers that │ tail latency when utilizing │ content security policies, │ │ │ │ │
|
||||||
|
│ leveraging container │ handle data transformation, │ edge computing nodes closer │ input sanitization │ │ │ │ │
|
||||||
|
│ orchestration for │ validation, and persistent │ to the geographic location │ libraries, and regular │ │ │ │ │
|
||||||
|
│ scalability and fault │ storage using a persistent │ of the end-user base. │ automated penetration │ │ │ │ │
|
||||||
|
│ tolerance in high-load │ queue. │ │ testing routines. │ │ │ │ │
|
||||||
|
│ scenarios. │ │ Monitoring tools have │ │ │ │ │ │
|
||||||
|
│ │ The pipeline features │ captured a steady increase │ Developers are required to │ │ │ │ │
|
||||||
|
│ This layer provides the │ built-in retry mechanisms │ in throughput efficiency │ undergo mandatory security │ │ │ │ │
|
||||||
|
│ fundamental building blocks │ with exponential backoff to │ since the introduction of │ training focusing on the │ │ │ │ │
|
||||||
|
│ for service discovery, load │ ensure message delivery │ the vectorized query engine │ OWASP Top Ten to ensure that │ │ │ │ │
|
||||||
|
│ balancing, and │ integrity even during │ in the primary data │ security is integrated into │ │ │ │ │
|
||||||
|
│ inter-service communication │ transient network or service │ warehouse. │ the initial design phase. │ │ │ │ │
|
||||||
|
│ via highly efficient │ failures. │ │ │ │ │ │ │
|
||||||
|
│ protocol buffers. │ │ Resource utilization │ The implementation of a │ │ │ │ │
|
||||||
|
│ │ Horizontal autoscaling is │ metrics demonstrate that │ robust Identity and Access │ │ │ │ │
|
||||||
|
│ Advanced telemetry and │ triggered automatically │ the transition to │ Management system ensures │ │ │ │ │
|
||||||
|
│ logging integrations allow │ based on the depth of the │ serverless compute for │ that the principle of least │ │ │ │ │
|
||||||
|
│ for real-time monitoring of │ processing queue, ensuring │ intermittent tasks has │ privilege is strictly │ │ │ │ │
|
||||||
|
│ system health and rapid │ consistent performance │ resulted in a thirty │ enforced across all │ │ │ │ │
|
||||||
|
│ identification of │ during unexpected traffic │ percent cost optimization. │ environments. │ │ │ │ │
|
||||||
|
│ bottlenecks within the │ spikes. │ │ │ │ │ │ │
|
||||||
|
│ service mesh. │ │ │ │ │ │ │ │
|
||||||
|
└─────────────────────────────┴──────────────────────────────┴─────────────────────────────┴──────────────────────────────┴─────┴────────┴─────────┴───────┘
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`TableRenderer > renders a table with long headers and 4 columns correctly 1`] = `
|
exports[`TableRenderer > renders a table with long headers and 4 columns correctly 1`] = `
|
||||||
"┌───────────────┬───────────────┬──────────────────┬──────────────────┐
|
"┌───────────────┬───────────────┬──────────────────┬──────────────────┐
|
||||||
│ Very Long │ Very Long │ Very Long Column │ Very Long Column │
|
│ Very Long │ Very Long │ Very Long Column │ Very Long Column │
|
||||||
|
|||||||
Reference in New Issue
Block a user