| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032 |
- import i18next from 'i18next';
- import { Modal, Tag, Typography } from '@douyinfe/semi-ui';
- import { copy, isMobile, showSuccess } from './utils.js';
- export function renderText(text, limit) {
- if (text.length > limit) {
- return text.slice(0, limit - 3) + '...';
- }
- return text;
- }
- /**
- * Render group tags based on the input group string
- * @param {string} group - The input group string
- * @returns {JSX.Element} - The rendered group tags
- */
- export function renderGroup(group) {
- if (group === '') {
- return (
- <Tag size='large' key='default' color='orange'>
- {i18next.t('用户分组')}
- </Tag>
- );
- }
- const tagColors = {
- vip: 'yellow',
- pro: 'yellow',
- svip: 'red',
- premium: 'red',
- };
- const groups = group.split(',').sort();
- return (
- <span key={group}>
- {groups.map((group) => (
- <Tag
- size='large'
- color={tagColors[group] || stringToColor(group)}
- key={group}
- onClick={async (event) => {
- event.stopPropagation();
- if (await copy(group)) {
- showSuccess(i18next.t('已复制:') + group);
- } else {
- Modal.error({
- title: t('无法复制到剪贴板,请手动复制'),
- content: group,
- });
- }
- }}
- >
- {group}
- </Tag>
- ))}
- </span>
- );
- }
- export function renderRatio(ratio) {
- let color = 'green';
- if (ratio > 5) {
- color = 'red';
- } else if (ratio > 3) {
- color = 'orange';
- } else if (ratio > 1) {
- color = 'blue';
- }
- return (
- <Tag color={color}>
- {ratio}x {i18next.t('倍率')}
- </Tag>
- );
- }
- const measureTextWidth = (
- text,
- style = {
- fontSize: '14px',
- fontFamily:
- '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
- },
- containerWidth,
- ) => {
- const span = document.createElement('span');
- span.style.visibility = 'hidden';
- span.style.position = 'absolute';
- span.style.whiteSpace = 'nowrap';
- span.style.fontSize = style.fontSize;
- span.style.fontFamily = style.fontFamily;
- span.textContent = text;
- document.body.appendChild(span);
- const width = span.offsetWidth;
- document.body.removeChild(span);
- return width;
- };
- export function truncateText(text, maxWidth = 200) {
- if (!isMobile()) {
- return text;
- }
- if (!text) return text;
- try {
- // Handle percentage-based maxWidth
- let actualMaxWidth = maxWidth;
- if (typeof maxWidth === 'string' && maxWidth.endsWith('%')) {
- const percentage = parseFloat(maxWidth) / 100;
- // Use window width as fallback container width
- actualMaxWidth = window.innerWidth * percentage;
- }
- const width = measureTextWidth(text);
- if (width <= actualMaxWidth) return text;
- let left = 0;
- let right = text.length;
- let result = text;
- while (left <= right) {
- const mid = Math.floor((left + right) / 2);
- const truncated = text.slice(0, mid) + '...';
- const currentWidth = measureTextWidth(truncated);
- if (currentWidth <= actualMaxWidth) {
- result = truncated;
- left = mid + 1;
- } else {
- right = mid - 1;
- }
- }
- return result;
- } catch (error) {
- console.warn(
- 'Text measurement failed, falling back to character count',
- error,
- );
- if (text.length > 20) {
- return text.slice(0, 17) + '...';
- }
- return text;
- }
- }
- export const renderGroupOption = (item) => {
- const {
- disabled,
- selected,
- label,
- value,
- focused,
- className,
- style,
- onMouseEnter,
- onClick,
- empty,
- emptyContent,
- ...rest
- } = item;
- const baseStyle = {
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: '8px 16px',
- cursor: disabled ? 'not-allowed' : 'pointer',
- backgroundColor: focused ? 'var(--semi-color-fill-0)' : 'transparent',
- opacity: disabled ? 0.5 : 1,
- ...(selected && {
- backgroundColor: 'var(--semi-color-primary-light-default)',
- }),
- '&:hover': {
- backgroundColor: !disabled && 'var(--semi-color-fill-1)',
- },
- };
- const handleClick = () => {
- if (!disabled && onClick) {
- onClick();
- }
- };
- const handleMouseEnter = (e) => {
- if (!disabled && onMouseEnter) {
- onMouseEnter(e);
- }
- };
- return (
- <div
- style={baseStyle}
- onClick={handleClick}
- onMouseEnter={handleMouseEnter}
- >
- <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
- <Typography.Text strong type={disabled ? 'tertiary' : undefined}>
- {value}
- </Typography.Text>
- <Typography.Text type='secondary' size='small'>
- {label}
- </Typography.Text>
- </div>
- {item.ratio && renderRatio(item.ratio)}
- </div>
- );
- };
- export function renderNumber(num) {
- if (num >= 1000000000) {
- return (num / 1000000000).toFixed(1) + 'B';
- } else if (num >= 1000000) {
- return (num / 1000000).toFixed(1) + 'M';
- } else if (num >= 10000) {
- return (num / 1000).toFixed(1) + 'k';
- } else {
- return num;
- }
- }
- export function renderQuotaNumberWithDigit(num, digits = 2) {
- if (typeof num !== 'number' || isNaN(num)) {
- return 0;
- }
- let displayInCurrency = localStorage.getItem('display_in_currency');
- num = num.toFixed(digits);
- if (displayInCurrency) {
- return '$' + num;
- }
- return num;
- }
- export function renderNumberWithPoint(num) {
- if (num === undefined) return '';
- num = num.toFixed(2);
- if (num >= 100000) {
- // Convert number to string to manipulate it
- let numStr = num.toString();
- // Find the position of the decimal point
- let decimalPointIndex = numStr.indexOf('.');
- let wholePart = numStr;
- let decimalPart = '';
- // If there is a decimal point, split the number into whole and decimal parts
- if (decimalPointIndex !== -1) {
- wholePart = numStr.slice(0, decimalPointIndex);
- decimalPart = numStr.slice(decimalPointIndex);
- }
- // Take the first two and last two digits of the whole number part
- let shortenedWholePart = wholePart.slice(0, 2) + '..' + wholePart.slice(-2);
- // Return the formatted number
- return shortenedWholePart + decimalPart;
- }
- // If the number is less than 100,000, return it unmodified
- return num;
- }
- export function getQuotaPerUnit() {
- let quotaPerUnit = localStorage.getItem('quota_per_unit');
- quotaPerUnit = parseFloat(quotaPerUnit);
- return quotaPerUnit;
- }
- export function renderUnitWithQuota(quota) {
- let quotaPerUnit = localStorage.getItem('quota_per_unit');
- quotaPerUnit = parseFloat(quotaPerUnit);
- quota = parseFloat(quota);
- return quotaPerUnit * quota;
- }
- export function getQuotaWithUnit(quota, digits = 6) {
- let quotaPerUnit = localStorage.getItem('quota_per_unit');
- quotaPerUnit = parseFloat(quotaPerUnit);
- return (quota / quotaPerUnit).toFixed(digits);
- }
- export function renderQuotaWithAmount(amount) {
- let displayInCurrency = localStorage.getItem('display_in_currency');
- displayInCurrency = displayInCurrency === 'true';
- if (displayInCurrency) {
- return '$' + amount;
- } else {
- return renderUnitWithQuota(amount);
- }
- }
- export function renderQuota(quota, digits = 2) {
- let quotaPerUnit = localStorage.getItem('quota_per_unit');
- let displayInCurrency = localStorage.getItem('display_in_currency');
- quotaPerUnit = parseFloat(quotaPerUnit);
- displayInCurrency = displayInCurrency === 'true';
- if (displayInCurrency) {
- return '$' + (quota / quotaPerUnit).toFixed(digits);
- }
- return renderNumber(quota);
- }
- export function renderModelPrice(
- inputTokens,
- completionTokens,
- modelRatio,
- modelPrice = -1,
- completionRatio,
- groupRatio,
- cacheTokens = 0,
- cacheRatio = 1.0,
- image = false,
- imageRatio = 1.0,
- imageOutputTokens = 0,
- ) {
- if (modelPrice !== -1) {
- return i18next.t(
- '模型价格:${{price}} * 分组倍率:{{ratio}} = ${{total}}',
- {
- price: modelPrice,
- ratio: groupRatio,
- total: modelPrice * groupRatio,
- },
- );
- } else {
- if (completionRatio === undefined) {
- completionRatio = 0;
- }
- let inputRatioPrice = modelRatio * 2.0;
- let completionRatioPrice = modelRatio * 2.0 * completionRatio;
- let cacheRatioPrice = modelRatio * 2.0 * cacheRatio;
- let imageRatioPrice = modelRatio * 2.0 * imageRatio;
- // Calculate effective input tokens (non-cached + cached with ratio applied)
- let effectiveInputTokens =
- inputTokens - cacheTokens + cacheTokens * cacheRatio;
- // Handle image tokens if present
- if (image && imageOutputTokens > 0) {
- effectiveInputTokens = inputTokens - imageOutputTokens + imageOutputTokens * imageRatio;
- }
- let price =
- (effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio +
- (completionTokens / 1000000) * completionRatioPrice * groupRatio;
- return (
- <>
- <article>
- <p>
- {i18next.t('输入价格:${{price}} / 1M tokens', {
- price: inputRatioPrice,
- })}
- </p>
- <p>
- {i18next.t(
- '输出价格:${{price}} * {{completionRatio}} = ${{total}} / 1M tokens (补全倍率: {{completionRatio}})',
- {
- price: inputRatioPrice,
- total: completionRatioPrice,
- completionRatio: completionRatio,
- },
- )}
- </p>
- {cacheTokens > 0 && (
- <p>
- {i18next.t(
- '缓存价格:${{price}} * {{cacheRatio}} = ${{total}} / 1M tokens (缓存倍率: {{cacheRatio}})',
- {
- price: inputRatioPrice,
- total: inputRatioPrice * cacheRatio,
- cacheRatio: cacheRatio,
- },
- )}
- </p>
- )}
- {image && imageOutputTokens > 0 && (
- <p>
- {i18next.t(
- '图片输入价格:${{price}} * {{ratio}} = ${{total}} / 1M tokens (图片倍率: {{imageRatio}})',
- {
- price: imageRatioPrice,
- ratio: groupRatio,
- total: imageRatioPrice * groupRatio,
- imageRatio: imageRatio,
- },
- )}
- </p>
- )}
- <p></p>
- <p>
- {cacheTokens > 0 && !image
- ? i18next.t(
- '输入 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
- {
- nonCacheInput: inputTokens - cacheTokens,
- cacheInput: cacheTokens,
- cachePrice: inputRatioPrice * cacheRatio,
- price: inputRatioPrice,
- completion: completionTokens,
- compPrice: completionRatioPrice,
- ratio: groupRatio,
- total: price.toFixed(6),
- },
- )
- : image && imageOutputTokens > 0
- ? i18next.t(
- '输入 {{nonImageInput}} tokens + 图片输入 {{imageInput}} tokens * {{imageRatio}} / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
- {
- nonImageInput: inputTokens - imageOutputTokens,
- imageInput: imageOutputTokens,
- imageRatio: imageRatio,
- price: inputRatioPrice,
- completion: completionTokens,
- compPrice: completionRatioPrice,
- ratio: groupRatio,
- total: price.toFixed(6),
- },
- )
- : i18next.t(
- '输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
- {
- input: inputTokens,
- price: inputRatioPrice,
- completion: completionTokens,
- compPrice: completionRatioPrice,
- ratio: groupRatio,
- total: price.toFixed(6),
- },
- )}
- </p>
- <p>{i18next.t('仅供参考,以实际扣费为准')}</p>
- </article>
- </>
- );
- }
- }
- export function renderLogContent(
- modelRatio,
- completionRatio,
- modelPrice = -1,
- groupRatio,
- user_group_ratio,
- image = false,
- imageRatio = 1.0,
- useUserGroupRatio = undefined
- ) {
- const ratioLabel = useUserGroupRatio ? i18next.t('专属倍率') : i18next.t('分组倍率');
- const ratio = useUserGroupRatio ? user_group_ratio : groupRatio;
- if (modelPrice !== -1) {
- return i18next.t('模型价格 ${{price}},{{ratioType}} {{ratio}}', {
- price: modelPrice,
- ratioType: ratioLabel,
- ratio
- });
- } else {
- if (image) {
- return i18next.t('模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},图片输入倍率 {{imageRatio}},{{ratioType}} {{ratio}}', {
- modelRatio: modelRatio,
- completionRatio: completionRatio,
- imageRatio: imageRatio,
- ratioType: ratioLabel,
- ratio
- });
- } else {
- return i18next.t('模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},{{ratioType}} {{ratio}}', {
- modelRatio: modelRatio,
- completionRatio: completionRatio,
- ratioType: ratioLabel,
- ratio
- });
- }
- }
- }
- export function renderModelPriceSimple(
- modelRatio,
- modelPrice = -1,
- groupRatio,
- cacheTokens = 0,
- cacheRatio = 1.0,
- image = false,
- imageRatio = 1.0,
- ) {
- if (modelPrice !== -1) {
- return i18next.t('价格:${{price}} * 分组:{{ratio}}', {
- price: modelPrice,
- ratio: groupRatio,
- });
- } else {
- if (image && cacheTokens !== 0) {
- return i18next.t(
- '模型: {{ratio}} * {{ratioType}}: {{groupRatio}} * 缓存倍率: {{cacheRatio}} * 图片输入倍率: {{imageRatio}}',
- {
- ratio: modelRatio,
- ratioType: ratioLabel,
- groupRatio: groupRatio,
- cacheRatio: cacheRatio,
- imageRatio: imageRatio,
- },
- );
- } else if (image) {
- return i18next.t(
- '模型: {{ratio}} * {{ratioType}}: {{groupRatio}} * 图片输入倍率: {{imageRatio}}',
- {
- ratio: modelRatio,
- ratioType: ratioLabel,
- groupRatio: groupRatio,
- imageRatio: imageRatio,
- },
- );
- } else if (cacheTokens !== 0) {
- return i18next.t(
- '模型: {{ratio}} * 分组: {{groupRatio}} * 缓存: {{cacheRatio}}',
- {
- ratio: modelRatio,
- groupRatio: groupRatio,
- cacheRatio: cacheRatio,
- },
- );
- } else {
- return i18next.t('模型: {{ratio}} * 分组: {{groupRatio}}', {
- ratio: modelRatio,
- groupRatio: groupRatio,
- });
- }
- }
- }
- export function renderAudioModelPrice(
- inputTokens,
- completionTokens,
- modelRatio,
- modelPrice = -1,
- completionRatio,
- audioInputTokens,
- audioCompletionTokens,
- audioRatio,
- audioCompletionRatio,
- groupRatio,
- cacheTokens = 0,
- cacheRatio = 1.0,
- ) {
- // 1 ratio = $0.002 / 1K tokens
- if (modelPrice !== -1) {
- return i18next.t(
- '模型价格:${{price}} * 分组倍率:{{ratio}} = ${{total}}',
- {
- price: modelPrice,
- ratio: groupRatio,
- total: modelPrice * groupRatio,
- },
- );
- } else {
- if (completionRatio === undefined) {
- completionRatio = 0;
- }
- // try toFixed audioRatio
- audioRatio = parseFloat(audioRatio).toFixed(6);
- // 这里的 *2 是因为 1倍率=0.002刀,请勿删除
- let inputRatioPrice = modelRatio * 2.0;
- let completionRatioPrice = modelRatio * 2.0 * completionRatio;
- let cacheRatioPrice = modelRatio * 2.0 * cacheRatio;
- // Calculate effective input tokens (non-cached + cached with ratio applied)
- const effectiveInputTokens =
- inputTokens - cacheTokens + cacheTokens * cacheRatio;
- let textPrice =
- (effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio +
- (completionTokens / 1000000) * completionRatioPrice * groupRatio;
- let audioPrice =
- (audioInputTokens / 1000000) * inputRatioPrice * audioRatio * groupRatio +
- (audioCompletionTokens / 1000000) *
- inputRatioPrice *
- audioRatio *
- audioCompletionRatio *
- groupRatio;
- let price = textPrice + audioPrice;
- return (
- <>
- <article>
- <p>
- {i18next.t('提示价格:${{price}} / 1M tokens', {
- price: inputRatioPrice,
- })}
- </p>
- <p>
- {i18next.t(
- '补全价格:${{price}} * {{completionRatio}} = ${{total}} / 1M tokens (补全倍率: {{completionRatio}})',
- {
- price: inputRatioPrice,
- total: completionRatioPrice,
- completionRatio: completionRatio,
- },
- )}
- </p>
- {cacheTokens > 0 && (
- <p>
- {i18next.t(
- '缓存价格:${{price}} * {{cacheRatio}} = ${{total}} / 1M tokens (缓存倍率: {{cacheRatio}})',
- {
- price: inputRatioPrice,
- total: inputRatioPrice * cacheRatio,
- cacheRatio: cacheRatio,
- },
- )}
- </p>
- )}
- <p>
- {i18next.t(
- '音频提示价格:${{price}} * {{audioRatio}} = ${{total}} / 1M tokens (音频倍率: {{audioRatio}})',
- {
- price: inputRatioPrice,
- total: inputRatioPrice * audioRatio,
- audioRatio: audioRatio,
- },
- )}
- </p>
- <p>
- {i18next.t(
- '音频补全价格:${{price}} * {{audioRatio}} * {{audioCompRatio}} = ${{total}} / 1M tokens (音频补全倍率: {{audioCompRatio}})',
- {
- price: inputRatioPrice,
- total: inputRatioPrice * audioRatio * audioCompletionRatio,
- audioRatio: audioRatio,
- audioCompRatio: audioCompletionRatio,
- },
- )}
- </p>
- <p>
- {cacheTokens > 0
- ? i18next.t(
- '文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
- {
- nonCacheInput: inputTokens - cacheTokens,
- cacheInput: cacheTokens,
- cachePrice: inputRatioPrice * cacheRatio,
- price: inputRatioPrice,
- completion: completionTokens,
- compPrice: completionRatioPrice,
- total: textPrice.toFixed(6),
- },
- )
- : i18next.t(
- '文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
- {
- input: inputTokens,
- price: inputRatioPrice,
- completion: completionTokens,
- compPrice: completionRatioPrice,
- total: textPrice.toFixed(6),
- },
- )}
- </p>
- <p>
- {i18next.t(
- '音频提示 {{input}} tokens / 1M tokens * ${{audioInputPrice}} + 音频补全 {{completion}} tokens / 1M tokens * ${{audioCompPrice}} = ${{total}}',
- {
- input: audioInputTokens,
- completion: audioCompletionTokens,
- audioInputPrice: audioRatio * inputRatioPrice,
- audioCompPrice:
- audioRatio * audioCompletionRatio * inputRatioPrice,
- total: audioPrice.toFixed(6),
- },
- )}
- </p>
- <p>
- {i18next.t(
- '总价:文字价格 {{textPrice}} + 音频价格 {{audioPrice}} = ${{total}}',
- {
- total: price.toFixed(6),
- textPrice: textPrice.toFixed(6),
- audioPrice: audioPrice.toFixed(6),
- },
- )}
- </p>
- <p>{i18next.t('仅供参考,以实际扣费为准')}</p>
- </article>
- </>
- );
- }
- }
- export function renderQuotaWithPrompt(quota, digits) {
- let displayInCurrency = localStorage.getItem('display_in_currency');
- displayInCurrency = displayInCurrency === 'true';
- if (displayInCurrency) {
- return (
- ' | ' + i18next.t('等价金额') + ': ' + renderQuota(quota, digits) + ''
- );
- }
- return '';
- }
- const colors = [
- 'amber',
- 'blue',
- 'cyan',
- 'green',
- 'grey',
- 'indigo',
- 'light-blue',
- 'lime',
- 'orange',
- 'pink',
- 'purple',
- 'red',
- 'teal',
- 'violet',
- 'yellow',
- ];
- // 基础10色色板 (N ≤ 10)
- const baseColors = [
- '#1664FF', // 主色
- '#1AC6FF',
- '#FF8A00',
- '#3CC780',
- '#7442D4',
- '#FFC400',
- '#304D77',
- '#B48DEB',
- '#009488',
- '#FF7DDA',
- ];
- // 扩展20色色板 (10 < N ≤ 20)
- const extendedColors = [
- '#1664FF',
- '#B2CFFF',
- '#1AC6FF',
- '#94EFFF',
- '#FF8A00',
- '#FFCE7A',
- '#3CC780',
- '#B9EDCD',
- '#7442D4',
- '#DDC5FA',
- '#FFC400',
- '#FAE878',
- '#304D77',
- '#8B959E',
- '#B48DEB',
- '#EFE3FF',
- '#009488',
- '#59BAA8',
- '#FF7DDA',
- '#FFCFEE',
- ];
- export const modelColorMap = {
- 'dall-e': 'rgb(147,112,219)', // 深紫色
- // 'dall-e-2': 'rgb(147,112,219)', // 介于紫色和蓝色之间的色调
- 'dall-e-3': 'rgb(153,50,204)', // 介于紫罗兰和洋红之间的色调
- 'gpt-3.5-turbo': 'rgb(184,227,167)', // 浅绿色
- // 'gpt-3.5-turbo-0301': 'rgb(131,220,131)', // 亮绿色
- 'gpt-3.5-turbo-0613': 'rgb(60,179,113)', // 海洋绿
- 'gpt-3.5-turbo-1106': 'rgb(32,178,170)', // 浅海洋绿
- 'gpt-3.5-turbo-16k': 'rgb(149,252,206)', // 淡橙色
- 'gpt-3.5-turbo-16k-0613': 'rgb(119,255,214)', // 淡桃
- 'gpt-3.5-turbo-instruct': 'rgb(175,238,238)', // 粉蓝色
- 'gpt-4': 'rgb(135,206,235)', // 天蓝色
- // 'gpt-4-0314': 'rgb(70,130,180)', // 钢蓝色
- 'gpt-4-0613': 'rgb(100,149,237)', // 矢车菊蓝
- 'gpt-4-1106-preview': 'rgb(30,144,255)', // 道奇蓝
- 'gpt-4-0125-preview': 'rgb(2,177,236)', // 深天蓝
- 'gpt-4-turbo-preview': 'rgb(2,177,255)', // 深天蓝
- 'gpt-4-32k': 'rgb(104,111,238)', // 中紫色
- // 'gpt-4-32k-0314': 'rgb(90,105,205)', // 暗灰蓝色
- 'gpt-4-32k-0613': 'rgb(61,71,139)', // 暗蓝灰色
- 'gpt-4-all': 'rgb(65,105,225)', // 皇家蓝
- 'gpt-4-gizmo-*': 'rgb(0,0,255)', // 纯蓝色
- 'gpt-4-vision-preview': 'rgb(25,25,112)', // 午夜蓝
- 'text-ada-001': 'rgb(255,192,203)', // 粉红色
- 'text-babbage-001': 'rgb(255,160,122)', // 浅珊瑚色
- 'text-curie-001': 'rgb(219,112,147)', // 苍紫罗兰色
- // 'text-davinci-002': 'rgb(199,21,133)', // 中紫罗兰红色
- 'text-davinci-003': 'rgb(219,112,147)', // 苍紫罗兰色(与Curie相同,表示同一个系列)
- 'text-davinci-edit-001': 'rgb(255,105,180)', // 热粉色
- 'text-embedding-ada-002': 'rgb(255,182,193)', // 浅粉红
- 'text-embedding-v1': 'rgb(255,174,185)', // 浅粉红色(略有区别)
- 'text-moderation-latest': 'rgb(255,130,171)', // 强粉色
- 'text-moderation-stable': 'rgb(255,160,122)', // 浅珊瑚色(与Babbage相同,表示同一类功能)
- 'tts-1': 'rgb(255,140,0)', // 深橙色
- 'tts-1-1106': 'rgb(255,165,0)', // 橙色
- 'tts-1-hd': 'rgb(255,215,0)', // 金色
- 'tts-1-hd-1106': 'rgb(255,223,0)', // 金黄色(略有区别)
- 'whisper-1': 'rgb(245,245,220)', // 米色
- 'claude-3-opus-20240229': 'rgb(255,132,31)', // 橙红色
- 'claude-3-sonnet-20240229': 'rgb(253,135,93)', // 橙色
- 'claude-3-haiku-20240307': 'rgb(255,175,146)', // 浅橙色
- 'claude-2.1': 'rgb(255,209,190)', // 浅橙色(略有区别)
- };
- export function modelToColor(modelName) {
- // 1. 如果模型在预定义的 modelColorMap 中,使用预定义颜色
- if (modelColorMap[modelName]) {
- return modelColorMap[modelName];
- }
- // 2. 生成一个稳定的数字作为索引
- let hash = 0;
- for (let i = 0; i < modelName.length; i++) {
- hash = (hash << 5) - hash + modelName.charCodeAt(i);
- hash = hash & hash; // Convert to 32-bit integer
- }
- hash = Math.abs(hash);
- // 3. 根据模型名称长度选择不同的色板
- const colorPalette = modelName.length > 10 ? extendedColors : baseColors;
- // 4. 使用hash值选择颜色
- const index = hash % colorPalette.length;
- return colorPalette[index];
- }
- export function stringToColor(str) {
- let sum = 0;
- for (let i = 0; i < str.length; i++) {
- sum += str.charCodeAt(i);
- }
- let i = sum % colors.length;
- return colors[i];
- }
- export function renderClaudeModelPrice(
- inputTokens,
- completionTokens,
- modelRatio,
- modelPrice = -1,
- completionRatio,
- groupRatio,
- cacheTokens = 0,
- cacheRatio = 1.0,
- cacheCreationTokens = 0,
- cacheCreationRatio = 1.0,
- ) {
- const ratioLabel = false ? i18next.t('专属倍率') : i18next.t('分组倍率');
- if (modelPrice !== -1) {
- return i18next.t(
- '模型价格:${{price}} * {{ratioType}}:{{ratio}} = ${{total}}',
- {
- price: modelPrice,
- ratioType: ratioLabel,
- ratio: groupRatio,
- total: modelPrice * groupRatio,
- },
- );
- } else {
- if (completionRatio === undefined) {
- completionRatio = 0;
- }
- const completionRatioValue = completionRatio || 0;
- const inputRatioPrice = modelRatio * 2.0;
- const completionRatioPrice = modelRatio * 2.0 * completionRatioValue;
- let cacheRatioPrice = (modelRatio * 2.0 * cacheRatio).toFixed(2);
- let cacheCreationRatioPrice = modelRatio * 2.0 * cacheCreationRatio;
- // Calculate effective input tokens (non-cached + cached with ratio applied + cache creation with ratio applied)
- const nonCachedTokens = inputTokens;
- const effectiveInputTokens =
- nonCachedTokens +
- cacheTokens * cacheRatio +
- cacheCreationTokens * cacheCreationRatio;
- let price =
- (effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio +
- (completionTokens / 1000000) * completionRatioPrice * groupRatio;
- return (
- <>
- <article>
- <p>
- {i18next.t('提示价格:${{price}} / 1M tokens', {
- price: inputRatioPrice,
- })}
- </p>
- <p>
- {i18next.t(
- '补全价格:${{price}} * {{ratio}} = ${{total}} / 1M tokens',
- {
- price: inputRatioPrice,
- ratio: completionRatio,
- total: completionRatioPrice,
- },
- )}
- </p>
- {cacheTokens > 0 && (
- <p>
- {i18next.t(
- '缓存价格:${{price}} * {{ratio}} = ${{total}} / 1M tokens (缓存倍率: {{cacheRatio}})',
- {
- price: inputRatioPrice,
- ratio: cacheRatio,
- total: cacheRatioPrice,
- cacheRatio: cacheRatio,
- },
- )}
- </p>
- )}
- {cacheCreationTokens > 0 && (
- <p>
- {i18next.t(
- '缓存创建价格:${{price}} * {{ratio}} = ${{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})',
- {
- price: inputRatioPrice,
- ratio: cacheCreationRatio,
- total: cacheCreationRatioPrice,
- cacheCreationRatio: cacheCreationRatio,
- },
- )}
- </p>
- )}
- <p></p>
- <p>
- {cacheTokens > 0 || cacheCreationTokens > 0
- ? i18next.t(
- '提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
- {
- nonCacheInput: nonCachedTokens,
- cacheInput: cacheTokens,
- cacheRatio: cacheRatio,
- cacheCreationInput: cacheCreationTokens,
- cacheCreationRatio: cacheCreationRatio,
- cachePrice: cacheRatioPrice,
- cacheCreationPrice: cacheCreationRatioPrice,
- price: inputRatioPrice,
- completion: completionTokens,
- compPrice: completionRatioPrice,
- ratio: groupRatio,
- total: price.toFixed(6),
- },
- )
- : i18next.t(
- '提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
- {
- input: inputTokens,
- price: inputRatioPrice,
- completion: completionTokens,
- compPrice: completionRatioPrice,
- ratio: groupRatio,
- total: price.toFixed(6),
- },
- )}
- </p>
- <p>{i18next.t('仅供参考,以实际扣费为准')}</p>
- </article>
- </>
- );
- }
- }
- export function renderClaudeLogContent(
- modelRatio,
- completionRatio,
- modelPrice = -1,
- groupRatio,
- cacheRatio = 1.0,
- cacheCreationRatio = 1.0,
- ) {
- const ratioLabel = false ? i18next.t('专属倍率') : i18next.t('分组倍率');
- if (modelPrice !== -1) {
- return i18next.t('模型价格 ${{price}},{{ratioType}} {{ratio}}', {
- price: modelPrice,
- ratioType: ratioLabel,
- ratio: groupRatio,
- });
- } else {
- return i18next.t(
- '模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},缓存倍率 {{cacheRatio}},缓存创建倍率 {{cacheCreationRatio}},{{ratioType}} {{ratio}}',
- {
- modelRatio: modelRatio,
- completionRatio: completionRatio,
- cacheRatio: cacheRatio,
- cacheCreationRatio: cacheCreationRatio,
- ratioType: ratioLabel,
- ratio: groupRatio,
- },
- );
- }
- }
- export function renderClaudeModelPriceSimple(
- modelRatio,
- modelPrice = -1,
- groupRatio,
- cacheTokens = 0,
- cacheRatio = 1.0,
- cacheCreationTokens = 0,
- cacheCreationRatio = 1.0,
- ) {
- const ratioLabel = false ? i18next.t('专属倍率') : i18next.t('分组');
- if (modelPrice !== -1) {
- return i18next.t('价格:${{price}} * {{ratioType}}:{{ratio}}', {
- price: modelPrice,
- ratioType: ratioLabel,
- ratio: groupRatio,
- });
- } else {
- if (cacheTokens !== 0 || cacheCreationTokens !== 0) {
- return i18next.t(
- '模型: {{ratio}} * {{ratioType}}: {{groupRatio}} * 缓存: {{cacheRatio}}',
- {
- ratio: modelRatio,
- ratioType: ratioLabel,
- groupRatio: groupRatio,
- cacheRatio: cacheRatio,
- cacheCreationRatio: cacheCreationRatio,
- },
- );
- } else {
- return i18next.t('模型: {{ratio}} * {{ratioType}}: {{groupRatio}}', {
- ratio: modelRatio,
- ratioType: ratioLabel,
- groupRatio: groupRatio,
- });
- }
- }
- }
|