/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
For commercial licensing, please contact support@quantumnous.com
*/
import React, { useEffect, useRef, useState } from 'react';
import { Notification, Button, Space, Toast, Typography, Select } from '@douyinfe/semi-ui';
import { API, showError, getModelCategories, selectFilter } from '../../../helpers';
import CardPro from '../../common/ui/CardPro';
import TokensTable from './TokensTable';
import TokensActions from './TokensActions';
import TokensFilters from './TokensFilters';
import TokensDescription from './TokensDescription';
import EditTokenModal from './modals/EditTokenModal';
import { useTokensData } from '../../../hooks/tokens/useTokensData';
import { useIsMobile } from '../../../hooks/common/useIsMobile';
import { createCardProPagination } from '../../../helpers/utils';
function TokensPage() {
// Define the function first, then pass it into the hook to avoid TDZ errors
const openFluentNotificationRef = useRef(null);
const tokensData = useTokensData((key) => openFluentNotificationRef.current?.(key));
const isMobile = useIsMobile();
const latestRef = useRef({ tokens: [], selectedKeys: [], t: (k) => k, selectedModel: '', prefillKey: '' });
const [modelOptions, setModelOptions] = useState([]);
const [selectedModel, setSelectedModel] = useState('');
const [fluentNoticeOpen, setFluentNoticeOpen] = useState(false);
const [prefillKey, setPrefillKey] = useState('');
// Keep latest data for handlers inside notifications
useEffect(() => {
latestRef.current = {
tokens: tokensData.tokens,
selectedKeys: tokensData.selectedKeys,
t: tokensData.t,
selectedModel,
prefillKey,
};
}, [tokensData.tokens, tokensData.selectedKeys, tokensData.t, selectedModel, prefillKey]);
const loadModels = async () => {
try {
const res = await API.get('/api/user/models');
const { success, message, data } = res.data || {};
if (success) {
const categories = getModelCategories(tokensData.t);
const options = (data || []).map((model) => {
let icon = null;
for (const [key, category] of Object.entries(categories)) {
if (key !== 'all' && category.filter({ model_name: model })) {
icon = category.icon;
break;
}
}
return {
label: (
{icon}
{model}
),
value: model,
};
});
setModelOptions(options);
} else {
showError(tokensData.t(message));
}
} catch (e) {
showError(e.message || 'Failed to load models');
}
};
function openFluentNotification(key) {
const { t } = latestRef.current;
const SUPPRESS_KEY = 'fluent_notify_suppressed';
if (modelOptions.length === 0) {
// fire-and-forget; a later effect will refresh the notice content
loadModels()
}
if (!key && localStorage.getItem(SUPPRESS_KEY) === '1') return;
const container = document.getElementById('fluent-new-api-container');
if (!container) {
Toast.warning(t('未检测到 FluentRead(流畅阅读),请确认扩展已启用'));
return;
}
setPrefillKey(key || '');
setFluentNoticeOpen(true);
Notification.info({
id: 'fluent-detected',
title: t('检测到 FluentRead(流畅阅读)'),
content: (