/* 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: (
{key ? t('请选择模型。') : t('选择模型后可一键填充当前选中令牌(或本页第一个令牌)。')}