From a7535aab99e734d040f060dc089d04a5e736bada Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Tue, 3 Jun 2025 16:13:50 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(helpers):?= =?UTF-8?q?=20standardize=20file=20naming=20conventions=20and=20improve=20?= =?UTF-8?q?code=20organization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename files to follow camelCase naming convention: • auth-header.js → authUtils.js • other.js → logUtils.js • rehypeSplitWordsIntoSpans.js → textAnimationUtils.js - Update import paths in affected components: • Update exports in helpers/index.js • Fix import in LogsTable.js for logUtils • Fix import in MarkdownRenderer.js for textAnimationUtils - Remove old files after successful migration - Improve file naming clarity: • authUtils.js better describes authentication utilities • logUtils.js clearly indicates log processing functions • textAnimationUtils.js concisely describes text animation functionality This refactoring enhances code maintainability and follows consistent naming patterns throughout the helpers directory. --- web/src/components/LogsTable.js | 2 +- web/src/components/common/markdown/MarkdownRenderer.js | 4 ++-- web/src/components/playground/configStorage.js | 2 +- web/src/constants/index.js | 1 + .../constants.js => constants/playground.constants.js} | 0 web/src/{utils => helpers}/apiUtils.js | 0 web/src/helpers/{auth-header.js => authUtils.js} | 2 +- web/src/helpers/index.js | 6 +++++- web/src/helpers/{other.js => logUtils.js} | 2 +- web/src/{utils => helpers}/messageUtils.js | 2 +- .../textAnimationUtils.js} | 0 web/src/hooks/useApiRequest.js | 6 +++--- web/src/hooks/useDataLoader.js | 6 +++--- web/src/hooks/useMessageActions.js | 4 ++-- web/src/hooks/useMessageEdit.js | 4 ++-- web/src/hooks/usePlaygroundState.js | 4 ++-- web/src/hooks/useSyncMessageAndCustomBody.js | 2 +- web/src/pages/Playground/index.js | 4 ++-- 18 files changed, 28 insertions(+), 23 deletions(-) rename web/src/{utils/constants.js => constants/playground.constants.js} (100%) rename web/src/{utils => helpers}/apiUtils.js (100%) rename web/src/helpers/{auth-header.js => authUtils.js} (99%) rename web/src/helpers/{other.js => logUtils.js} (98%) rename web/src/{utils => helpers}/messageUtils.js (98%) rename web/src/{utils/rehypeSplitWordsIntoSpans.js => helpers/textAnimationUtils.js} (100%) diff --git a/web/src/components/LogsTable.js b/web/src/components/LogsTable.js index 912b198b..e28c22d4 100644 --- a/web/src/components/LogsTable.js +++ b/web/src/components/LogsTable.js @@ -44,7 +44,7 @@ import { stringToColor, } from '../helpers/render'; import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph'; -import { getLogOther } from '../helpers/other.js'; +import { getLogOther } from '../helpers/logUtils.js'; import { IconRefresh, IconSetting, diff --git a/web/src/components/common/markdown/MarkdownRenderer.js b/web/src/components/common/markdown/MarkdownRenderer.js index 870e0b6f..86fdf96e 100644 --- a/web/src/components/common/markdown/MarkdownRenderer.js +++ b/web/src/components/common/markdown/MarkdownRenderer.js @@ -1,6 +1,6 @@ import ReactMarkdown from 'react-markdown'; import 'katex/dist/katex.min.css'; -import 'highlight.js/styles/default.css'; +import 'highlight.js/styles/github.css'; import './markdown.css'; import RemarkMath from 'remark-math'; import RemarkBreaks from 'remark-breaks'; @@ -16,7 +16,7 @@ import { Button, Tooltip, Toast } from '@douyinfe/semi-ui'; import { copy } from '../../../helpers/utils'; import { IconCopy } from '@douyinfe/semi-icons'; import { useTranslation } from 'react-i18next'; -import { rehypeSplitWordsIntoSpans } from '../../../utils/rehypeSplitWordsIntoSpans'; +import { rehypeSplitWordsIntoSpans } from '../../../helpers/textAnimationUtils'; mermaid.initialize({ startOnLoad: false, diff --git a/web/src/components/playground/configStorage.js b/web/src/components/playground/configStorage.js index a4e068cb..91fda88a 100644 --- a/web/src/components/playground/configStorage.js +++ b/web/src/components/playground/configStorage.js @@ -1,4 +1,4 @@ -import { STORAGE_KEYS, DEFAULT_CONFIG } from '../../utils/constants'; +import { STORAGE_KEYS, DEFAULT_CONFIG } from '../../constants/playground.constants'; const MESSAGES_STORAGE_KEY = 'playground_messages'; diff --git a/web/src/constants/index.js b/web/src/constants/index.js index 3f8c0232..9538a7dc 100644 --- a/web/src/constants/index.js +++ b/web/src/constants/index.js @@ -3,3 +3,4 @@ export * from './user.constants'; export * from './toast.constants'; export * from './common.constant'; export * from './model.constants'; +export * from './playground.constants'; diff --git a/web/src/utils/constants.js b/web/src/constants/playground.constants.js similarity index 100% rename from web/src/utils/constants.js rename to web/src/constants/playground.constants.js diff --git a/web/src/utils/apiUtils.js b/web/src/helpers/apiUtils.js similarity index 100% rename from web/src/utils/apiUtils.js rename to web/src/helpers/apiUtils.js diff --git a/web/src/helpers/auth-header.js b/web/src/helpers/authUtils.js similarity index 99% rename from web/src/helpers/auth-header.js rename to web/src/helpers/authUtils.js index f094dd1b..75b4b0df 100644 --- a/web/src/helpers/auth-header.js +++ b/web/src/helpers/authUtils.js @@ -7,4 +7,4 @@ export function authHeader() { } else { return {}; } -} +} \ No newline at end of file diff --git a/web/src/helpers/index.js b/web/src/helpers/index.js index ac164960..fae2c6aa 100644 --- a/web/src/helpers/index.js +++ b/web/src/helpers/index.js @@ -1,4 +1,8 @@ export * from './history'; -export * from './auth-header'; +export * from './authUtils'; export * from './utils'; export * from './api'; +export * from './apiUtils'; +export * from './messageUtils'; +export * from './textAnimationUtils'; +export * from './logUtils'; diff --git a/web/src/helpers/other.js b/web/src/helpers/logUtils.js similarity index 98% rename from web/src/helpers/other.js rename to web/src/helpers/logUtils.js index c5d8c269..ffbe0d74 100644 --- a/web/src/helpers/other.js +++ b/web/src/helpers/logUtils.js @@ -4,4 +4,4 @@ export function getLogOther(otherStr) { } let other = JSON.parse(otherStr); return other; -} +} \ No newline at end of file diff --git a/web/src/utils/messageUtils.js b/web/src/helpers/messageUtils.js similarity index 98% rename from web/src/utils/messageUtils.js rename to web/src/helpers/messageUtils.js index 0ab23315..76412dec 100644 --- a/web/src/utils/messageUtils.js +++ b/web/src/helpers/messageUtils.js @@ -1,4 +1,4 @@ -import { THINK_TAG_REGEX, MESSAGE_ROLES } from './constants'; +import { THINK_TAG_REGEX, MESSAGE_ROLES } from '../constants/playground.constants'; // 生成唯一ID let messageId = 4; diff --git a/web/src/utils/rehypeSplitWordsIntoSpans.js b/web/src/helpers/textAnimationUtils.js similarity index 100% rename from web/src/utils/rehypeSplitWordsIntoSpans.js rename to web/src/helpers/textAnimationUtils.js diff --git a/web/src/hooks/useApiRequest.js b/web/src/hooks/useApiRequest.js index 55688726..5087dc19 100644 --- a/web/src/hooks/useApiRequest.js +++ b/web/src/hooks/useApiRequest.js @@ -6,15 +6,15 @@ import { API_ENDPOINTS, MESSAGE_STATUS, DEBUG_TABS -} from '../utils/constants'; +} from '../constants/playground.constants'; import { buildApiPayload, handleApiError -} from '../utils/apiUtils'; +} from '../helpers/apiUtils'; import { processThinkTags, processIncompleteThinkTags -} from '../utils/messageUtils'; +} from '../helpers/messageUtils'; export const useApiRequest = ( setMessage, diff --git a/web/src/hooks/useDataLoader.js b/web/src/hooks/useDataLoader.js index 2454b007..011a4f7c 100644 --- a/web/src/hooks/useDataLoader.js +++ b/web/src/hooks/useDataLoader.js @@ -1,8 +1,8 @@ import { useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { API, showError } from '../helpers/index.js'; -import { API_ENDPOINTS } from '../utils/constants'; -import { processModelsData, processGroupsData } from '../utils/apiUtils'; +import { API } from '../helpers/api'; +import { API_ENDPOINTS } from '../constants/playground.constants'; +import { processModelsData, processGroupsData } from '../helpers/apiUtils'; export const useDataLoader = ( userState, diff --git a/web/src/hooks/useMessageActions.js b/web/src/hooks/useMessageActions.js index 84d94d4d..4a0fba69 100644 --- a/web/src/hooks/useMessageActions.js +++ b/web/src/hooks/useMessageActions.js @@ -1,8 +1,8 @@ import { useCallback } from 'react'; import { Toast, Modal } from '@douyinfe/semi-ui'; import { useTranslation } from 'react-i18next'; -import { getTextContent } from '../utils/messageUtils'; -import { ERROR_MESSAGES } from '../utils/constants'; +import { getTextContent } from '../helpers/messageUtils'; +import { ERROR_MESSAGES } from '../constants/playground.constants'; export const useMessageActions = (message, setMessage, onMessageSend, saveMessages) => { const { t } = useTranslation(); diff --git a/web/src/hooks/useMessageEdit.js b/web/src/hooks/useMessageEdit.js index 513f2b32..e91ebae6 100644 --- a/web/src/hooks/useMessageEdit.js +++ b/web/src/hooks/useMessageEdit.js @@ -1,8 +1,8 @@ import { useCallback, useState, useRef } from 'react'; import { Toast, Modal } from '@douyinfe/semi-ui'; import { useTranslation } from 'react-i18next'; -import { getTextContent, buildApiPayload, createLoadingAssistantMessage } from '../utils/messageUtils'; -import { MESSAGE_ROLES } from '../utils/constants'; +import { getTextContent, buildApiPayload, createLoadingAssistantMessage } from '../helpers/messageUtils'; +import { MESSAGE_ROLES } from '../constants/playground.constants'; export const useMessageEdit = ( setMessage, diff --git a/web/src/hooks/usePlaygroundState.js b/web/src/hooks/usePlaygroundState.js index 9e76aa2e..b4b4af47 100644 --- a/web/src/hooks/usePlaygroundState.js +++ b/web/src/hooks/usePlaygroundState.js @@ -1,7 +1,7 @@ import { useState, useCallback, useRef, useEffect } from 'react'; -import { DEFAULT_MESSAGES, DEFAULT_CONFIG, DEBUG_TABS, MESSAGE_STATUS } from '../utils/constants'; +import { DEFAULT_MESSAGES, DEFAULT_CONFIG, DEBUG_TABS, MESSAGE_STATUS } from '../constants/playground.constants'; import { loadConfig, saveConfig, loadMessages, saveMessages } from '../components/playground/configStorage'; -import { processIncompleteThinkTags } from '../utils/messageUtils'; +import { processIncompleteThinkTags } from '../helpers/messageUtils'; export const usePlaygroundState = () => { // 使用惰性初始化,确保只在组件首次挂载时加载配置和消息 diff --git a/web/src/hooks/useSyncMessageAndCustomBody.js b/web/src/hooks/useSyncMessageAndCustomBody.js index 36e5fc67..6f0c19ad 100644 --- a/web/src/hooks/useSyncMessageAndCustomBody.js +++ b/web/src/hooks/useSyncMessageAndCustomBody.js @@ -1,5 +1,5 @@ import { useCallback, useRef } from 'react'; -import { MESSAGE_ROLES } from '../utils/constants'; +import { MESSAGE_ROLES } from '../constants/playground.constants'; export const useSyncMessageAndCustomBody = ( customRequestMode, diff --git a/web/src/pages/Playground/index.js b/web/src/pages/Playground/index.js index 3b890259..b495bc15 100644 --- a/web/src/pages/Playground/index.js +++ b/web/src/pages/Playground/index.js @@ -22,14 +22,14 @@ import { DEFAULT_MESSAGES, MESSAGE_ROLES, ERROR_MESSAGES -} from '../../utils/constants.js'; +} from '../../constants/playground.constants.js'; import { buildMessageContent, createMessage, createLoadingAssistantMessage, getTextContent, buildApiPayload -} from '../../utils/messageUtils.js'; +} from '../../helpers/messageUtils.js'; // Components import { From 64b565dc1586dd6f3b307cfae4055bbfb14756f5 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Tue, 3 Jun 2025 23:56:39 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(helpers):?= =?UTF-8?q?=20refactor=20the=20helpers=20folder=20and=20related=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/ChannelsTable.js | 10 +- web/src/components/HeaderBar.js | 3 +- web/src/components/LoginForm.js | 2 +- web/src/components/LogsTable.js | 26 +- web/src/components/OAuth2Callback.js | 3 +- web/src/components/PageLayout.js | 3 +- web/src/components/PersonalSetting.js | 10 +- web/src/components/RedemptionsTable.js | 2 +- web/src/components/RegisterForm.js | 2 +- web/src/components/SiderBar.js | 8 +- web/src/components/SystemSetting.js | 6 +- web/src/components/TokensTable.js | 3 +- web/src/components/UsersTable.js | 3 +- .../common/markdown/MarkdownRenderer.js | 3 +- web/src/components/playground/CodeViewer.js | 2 +- .../components/playground/SettingsPanel.js | 2 +- web/src/context/Style/index.js | 2 +- web/src/helpers/api.js | 106 +++++++ web/src/helpers/apiUtils.js | 105 ------- web/src/helpers/{authUtils.js => auth.js} | 0 web/src/helpers/index.js | 9 +- web/src/helpers/{logUtils.js => log.js} | 0 web/src/helpers/messageUtils.js | 201 ------------- web/src/helpers/render.js | 279 +++++++++++------- web/src/helpers/textAnimationUtils.js | 77 ----- web/src/helpers/utils.js | 163 ++++++++++ web/src/hooks/useApiRequest.js | 9 +- web/src/hooks/useDataLoader.js | 3 +- web/src/hooks/useMessageActions.js | 2 +- web/src/hooks/useMessageEdit.js | 2 +- web/src/hooks/usePlaygroundState.js | 2 +- web/src/pages/Detail/index.js | 6 +- web/src/pages/Playground/index.js | 9 +- web/src/pages/Redemption/EditRedemption.js | 6 +- .../Operation/ModelRationNotSetEditor.js | 3 +- .../Operation/ModelSettingsVisualEditor.js | 25 +- web/src/pages/Token/EditToken.js | 3 +- web/src/pages/TopUp/index.js | 9 +- web/src/pages/User/EditUser.js | 3 +- 39 files changed, 523 insertions(+), 589 deletions(-) delete mode 100644 web/src/helpers/apiUtils.js rename web/src/helpers/{authUtils.js => auth.js} (100%) rename web/src/helpers/{logUtils.js => log.js} (100%) delete mode 100644 web/src/helpers/messageUtils.js delete mode 100644 web/src/helpers/textAnimationUtils.js diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js index 974d8bd8..c6bdf75b 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/ChannelsTable.js @@ -5,14 +5,12 @@ import { showInfo, showSuccess, timestamp2string, + renderGroup, + renderNumberWithPoint, + renderQuota } from '../helpers'; import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../constants'; -import { - renderGroup, - renderNumberWithPoint, - renderQuota, -} from '../helpers/render'; import { Button, Divider, @@ -29,7 +27,7 @@ import { Typography, Checkbox, Card, - Select, + Select } from '@douyinfe/semi-ui'; import EditChannel from '../pages/Channel/EditChannel'; import { diff --git a/web/src/components/HeaderBar.js b/web/src/components/HeaderBar.js index 2209d0af..d4afac2c 100644 --- a/web/src/components/HeaderBar.js +++ b/web/src/components/HeaderBar.js @@ -3,7 +3,7 @@ import { Link, useNavigate, useLocation } from 'react-router-dom'; import { UserContext } from '../context/User'; import { useSetTheme, useTheme } from '../context/Theme'; import { useTranslation } from 'react-i18next'; -import { API, getLogo, getSystemName, showSuccess } from '../helpers'; +import { API, getLogo, getSystemName, showSuccess, stringToColor } from '../helpers'; import fireworks from 'react-fireworks'; import { CN, GB } from 'country-flag-icons/react/3x2'; import NoticeModal from './NoticeModal'; @@ -29,7 +29,6 @@ import { Typography, Skeleton, } from '@douyinfe/semi-ui'; -import { stringToColor } from '../helpers/render'; import { StatusContext } from '../context/Status/index.js'; import { useStyle, styleActions } from '../context/Style/index.js'; diff --git a/web/src/components/LoginForm.js b/web/src/components/LoginForm.js index 3f40a472..84b293b5 100644 --- a/web/src/components/LoginForm.js +++ b/web/src/components/LoginForm.js @@ -9,6 +9,7 @@ import { showSuccess, updateAPI, getSystemName, + setUserData } from '../helpers'; import { onGitHubOAuthClicked, @@ -31,7 +32,6 @@ import TelegramLoginButton from 'react-telegram-login'; import { IconGithubLogo, IconMail, IconLock } from '@douyinfe/semi-icons'; import OIDCIcon from './common/logo/OIDCIcon.js'; import WeChatIcon from './common/logo/WeChatIcon.js'; -import { setUserData } from '../helpers/data.js'; import LinuxDoIcon from './common/logo/LinuxDoIcon.js'; import { useTranslation } from 'react-i18next'; import Background from '../images/example.png'; diff --git a/web/src/components/LogsTable.js b/web/src/components/LogsTable.js index e28c22d4..5f2970cb 100644 --- a/web/src/components/LogsTable.js +++ b/web/src/components/LogsTable.js @@ -8,6 +8,18 @@ import { showError, showSuccess, timestamp2string, + renderAudioModelPrice, + renderClaudeLogContent, + renderClaudeModelPrice, + renderClaudeModelPriceSimple, + renderGroup, + renderLogContent, + renderModelPrice, + renderModelPriceSimple, + renderNumber, + renderQuota, + stringToColor, + getLogOther } from '../helpers'; import { @@ -30,21 +42,7 @@ import { DatePicker, } from '@douyinfe/semi-ui'; import { ITEMS_PER_PAGE } from '../constants'; -import { - renderAudioModelPrice, - renderClaudeLogContent, - renderClaudeModelPrice, - renderClaudeModelPriceSimple, - renderGroup, - renderLogContent, - renderModelPrice, - renderModelPriceSimple, - renderNumber, - renderQuota, - stringToColor, -} from '../helpers/render'; import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph'; -import { getLogOther } from '../helpers/logUtils.js'; import { IconRefresh, IconSetting, diff --git a/web/src/components/OAuth2Callback.js b/web/src/components/OAuth2Callback.js index 9edae986..8a36b443 100644 --- a/web/src/components/OAuth2Callback.js +++ b/web/src/components/OAuth2Callback.js @@ -1,9 +1,8 @@ import React, { useContext, useEffect, useState } from 'react'; import { Spin, Typography, Space } from '@douyinfe/semi-ui'; import { useNavigate, useSearchParams } from 'react-router-dom'; -import { API, showError, showSuccess, updateAPI } from '../helpers'; +import { API, showError, showSuccess, updateAPI, setUserData } from '../helpers'; import { UserContext } from '../context/User'; -import { setUserData } from '../helpers/data.js'; const OAuth2Callback = (props) => { const [searchParams, setSearchParams] = useSearchParams(); diff --git a/web/src/components/PageLayout.js b/web/src/components/PageLayout.js index 6ae50cad..76d8e6b7 100644 --- a/web/src/components/PageLayout.js +++ b/web/src/components/PageLayout.js @@ -7,8 +7,7 @@ import { ToastContainer } from 'react-toastify'; import React, { useContext, useEffect } from 'react'; import { useStyle } from '../context/Style/index.js'; import { useTranslation } from 'react-i18next'; -import { API, getLogo, getSystemName, showError } from '../helpers/index.js'; -import { setStatusData } from '../helpers/data.js'; +import { API, getLogo, getSystemName, showError, setStatusData } from '../helpers'; import { UserContext } from '../context/User/index.js'; import { StatusContext } from '../context/Status/index.js'; import { useLocation } from 'react-router-dom'; diff --git a/web/src/components/PersonalSetting.js b/web/src/components/PersonalSetting.js index 4ecb4788..9290d3bd 100644 --- a/web/src/components/PersonalSetting.js +++ b/web/src/components/PersonalSetting.js @@ -8,6 +8,10 @@ import { showError, showInfo, showSuccess, + getQuotaPerUnit, + renderQuota, + renderQuotaWithPrompt, + stringToColor } from '../helpers'; import Turnstile from 'react-turnstile'; import { UserContext } from '../context/User'; @@ -54,12 +58,6 @@ import { } from '@douyinfe/semi-icons'; import { SiTelegram, SiWechat, SiLinux } from 'react-icons/si'; import { Bell, Shield, Webhook, Globe, Settings, UserPlus, ShieldCheck } from 'lucide-react'; -import { - getQuotaPerUnit, - renderQuota, - renderQuotaWithPrompt, - stringToColor, -} from '../helpers/render'; import TelegramLoginButton from 'react-telegram-login'; import { useTranslation } from 'react-i18next'; diff --git a/web/src/components/RedemptionsTable.js b/web/src/components/RedemptionsTable.js index f9fd344a..637c7bdf 100644 --- a/web/src/components/RedemptionsTable.js +++ b/web/src/components/RedemptionsTable.js @@ -5,10 +5,10 @@ import { showError, showSuccess, timestamp2string, + renderQuota } from '../helpers'; import { ITEMS_PER_PAGE } from '../constants'; -import { renderQuota } from '../helpers/render'; import { Button, Card, diff --git a/web/src/components/RegisterForm.js b/web/src/components/RegisterForm.js index 24732676..78db98ad 100644 --- a/web/src/components/RegisterForm.js +++ b/web/src/components/RegisterForm.js @@ -8,6 +8,7 @@ import { showSuccess, updateAPI, getSystemName, + setUserData } from '../helpers'; import Turnstile from 'react-turnstile'; import { @@ -30,7 +31,6 @@ import OIDCIcon from './common/logo/OIDCIcon.js'; import LinuxDoIcon from './common/logo/LinuxDoIcon.js'; import WeChatIcon from './common/logo/WeChatIcon.js'; import TelegramLoginButton from 'react-telegram-login/src'; -import { setUserData } from '../helpers/data.js'; import { UserContext } from '../context/User/index.js'; import { useTranslation } from 'react-i18next'; import Background from '../images/example.png'; diff --git a/web/src/components/SiderBar.js b/web/src/components/SiderBar.js index d4399f92..97a4bb03 100644 --- a/web/src/components/SiderBar.js +++ b/web/src/components/SiderBar.js @@ -5,12 +5,8 @@ import { StatusContext } from '../context/Status'; import { useTranslation } from 'react-i18next'; import { - API, - getLogo, - getSystemName, isAdmin, - isMobile, - showError, + showError } from '../helpers'; import '../index.css'; @@ -39,8 +35,6 @@ import { Switch, Divider, } from '@douyinfe/semi-ui'; -import { setStatusData } from '../helpers/data.js'; -import { stringToColor } from '../helpers/render.js'; import { useSetTheme, useTheme } from '../context/Theme/index.js'; import { useStyle, styleActions } from '../context/Style/index.js'; import Text from '@douyinfe/semi-ui/lib/es/typography/text'; diff --git a/web/src/components/SystemSetting.js b/web/src/components/SystemSetting.js index 53ba675d..fa97df48 100644 --- a/web/src/components/SystemSetting.js +++ b/web/src/components/SystemSetting.js @@ -13,12 +13,12 @@ import { } from '@douyinfe/semi-ui'; const { Text } = Typography; import { + API, removeTrailingSlash, showError, showSuccess, - verifyJSON, -} from '../helpers/utils'; -import { API } from '../helpers/api'; + verifyJSON +} from '../helpers'; import axios from 'axios'; const SystemSetting = () => { diff --git a/web/src/components/TokensTable.js b/web/src/components/TokensTable.js index 92b74550..a3f21f1d 100644 --- a/web/src/components/TokensTable.js +++ b/web/src/components/TokensTable.js @@ -6,10 +6,11 @@ import { showError, showSuccess, timestamp2string, + renderGroup, + renderQuota } from '../helpers'; import { ITEMS_PER_PAGE } from '../constants'; -import { renderGroup, renderQuota } from '../helpers/render'; import { Button, Card, diff --git a/web/src/components/UsersTable.js b/web/src/components/UsersTable.js index e5085212..d4bc74a3 100644 --- a/web/src/components/UsersTable.js +++ b/web/src/components/UsersTable.js @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { API, showError, showSuccess } from '../helpers'; +import { API, showError, showSuccess, renderGroup, renderNumber, renderQuota } from '../helpers'; import { Button, Card, @@ -26,7 +26,6 @@ import { IconArrowDown, } from '@douyinfe/semi-icons'; import { ITEMS_PER_PAGE } from '../constants'; -import { renderGroup, renderNumber, renderQuota } from '../helpers/render'; import AddUser from '../pages/User/AddUser'; import EditUser from '../pages/User/EditUser'; import { useTranslation } from 'react-i18next'; diff --git a/web/src/components/common/markdown/MarkdownRenderer.js b/web/src/components/common/markdown/MarkdownRenderer.js index 86fdf96e..a48d34d1 100644 --- a/web/src/components/common/markdown/MarkdownRenderer.js +++ b/web/src/components/common/markdown/MarkdownRenderer.js @@ -13,10 +13,9 @@ import React from 'react'; import { useDebouncedCallback } from 'use-debounce'; import clsx from 'clsx'; import { Button, Tooltip, Toast } from '@douyinfe/semi-ui'; -import { copy } from '../../../helpers/utils'; +import { copy, rehypeSplitWordsIntoSpans } from '../../../helpers'; import { IconCopy } from '@douyinfe/semi-icons'; import { useTranslation } from 'react-i18next'; -import { rehypeSplitWordsIntoSpans } from '../../../helpers/textAnimationUtils'; mermaid.initialize({ startOnLoad: false, diff --git a/web/src/components/playground/CodeViewer.js b/web/src/components/playground/CodeViewer.js index e02b72e2..1ce723ce 100644 --- a/web/src/components/playground/CodeViewer.js +++ b/web/src/components/playground/CodeViewer.js @@ -2,7 +2,7 @@ import React, { useState, useMemo, useCallback } from 'react'; import { Button, Tooltip, Toast } from '@douyinfe/semi-ui'; import { Copy, ChevronDown, ChevronUp } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { copy } from '../../helpers/utils'; +import { copy } from '../../helpers'; const PERFORMANCE_CONFIG = { MAX_DISPLAY_LENGTH: 50000, // 最大显示字符数 diff --git a/web/src/components/playground/SettingsPanel.js b/web/src/components/playground/SettingsPanel.js index 4f3ea276..b2e8310a 100644 --- a/web/src/components/playground/SettingsPanel.js +++ b/web/src/components/playground/SettingsPanel.js @@ -14,7 +14,7 @@ import { Settings, } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { renderGroupOption } from '../../helpers/render.js'; +import { renderGroupOption } from '../../helpers'; import ParameterControl from './ParameterControl'; import ImageUrlInput from './ImageUrlInput'; import ConfigManager from './ConfigManager'; diff --git a/web/src/context/Style/index.js b/web/src/context/Style/index.js index 41d4633a..7bfe0ef7 100644 --- a/web/src/context/Style/index.js +++ b/web/src/context/Style/index.js @@ -2,7 +2,7 @@ import React, { useReducer, useEffect, useMemo, createContext } from 'react'; import { useLocation } from 'react-router-dom'; -import { isMobile as getIsMobile } from '../../helpers/index.js'; +import { isMobile as getIsMobile } from '../../helpers'; // Action Types const ACTION_TYPES = { diff --git a/web/src/helpers/api.js b/web/src/helpers/api.js index 84d2df1f..d58487b5 100644 --- a/web/src/helpers/api.js +++ b/web/src/helpers/api.js @@ -1,5 +1,6 @@ import { getUserIdFromLocalStorage, showError } from './utils'; import axios from 'axios'; +import { formatMessageForAPI } from './index.js'; export let API = axios.create({ baseURL: import.meta.env.VITE_REACT_APP_SERVER_URL @@ -29,3 +30,108 @@ API.interceptors.response.use( showError(error); }, ); + +// playground + +// 构建API请求负载 +export const buildApiPayload = (messages, systemPrompt, inputs, parameterEnabled) => { + const processedMessages = messages + .filter(isValidMessage) + .map(formatMessageForAPI) + .filter(Boolean); + + // 如果有系统提示,插入到消息开头 + if (systemPrompt && systemPrompt.trim()) { + processedMessages.unshift({ + role: MESSAGE_ROLES.SYSTEM, + content: systemPrompt.trim() + }); + } + + const payload = { + model: inputs.model, + messages: processedMessages, + stream: inputs.stream, + }; + + // 添加启用的参数 + const parameterMappings = { + temperature: 'temperature', + top_p: 'top_p', + max_tokens: 'max_tokens', + frequency_penalty: 'frequency_penalty', + presence_penalty: 'presence_penalty', + seed: 'seed' + }; + + Object.entries(parameterMappings).forEach(([key, param]) => { + if (parameterEnabled[key] && inputs[param] !== undefined && inputs[param] !== null) { + payload[param] = inputs[param]; + } + }); + + return payload; +}; + +// 处理API错误响应 +export const handleApiError = (error, response = null) => { + const errorInfo = { + error: error.message || '未知错误', + timestamp: new Date().toISOString(), + stack: error.stack + }; + + if (response) { + errorInfo.status = response.status; + errorInfo.statusText = response.statusText; + } + + if (error.message.includes('HTTP error')) { + errorInfo.details = '服务器返回了错误状态码'; + } else if (error.message.includes('Failed to fetch')) { + errorInfo.details = '网络连接失败或服务器无响应'; + } + + return errorInfo; +}; + +// 处理模型数据 +export const processModelsData = (data, currentModel) => { + const modelOptions = data.map(model => ({ + label: model, + value: model, + })); + + const hasCurrentModel = modelOptions.some(option => option.value === currentModel); + const selectedModel = hasCurrentModel && modelOptions.length > 0 + ? currentModel + : modelOptions[0]?.value; + + return { modelOptions, selectedModel }; +}; + +// 处理分组数据 +export const processGroupsData = (data, userGroup) => { + let groupOptions = Object.entries(data).map(([group, info]) => ({ + label: info.desc.length > 20 ? info.desc.substring(0, 20) + '...' : info.desc, + value: group, + ratio: info.ratio, + fullLabel: info.desc, + })); + + if (groupOptions.length === 0) { + groupOptions = [{ + label: '用户分组', + value: '', + ratio: 1, + }]; + } else if (userGroup) { + const userGroupIndex = groupOptions.findIndex(g => g.value === userGroup); + if (userGroupIndex > -1) { + const userGroupOption = groupOptions.splice(userGroupIndex, 1)[0]; + groupOptions.unshift(userGroupOption); + } + } + + return groupOptions; +}; \ No newline at end of file diff --git a/web/src/helpers/apiUtils.js b/web/src/helpers/apiUtils.js deleted file mode 100644 index 77f775fd..00000000 --- a/web/src/helpers/apiUtils.js +++ /dev/null @@ -1,105 +0,0 @@ -import { formatMessageForAPI } from './messageUtils'; - -// 构建API请求载荷 -export const buildApiPayload = (messages, systemPrompt, inputs, parameterEnabled) => { - const processedMessages = messages.map(formatMessageForAPI); - - // 如果有系统提示,插入到消息开头 - if (systemPrompt && systemPrompt.trim()) { - processedMessages.unshift({ - role: 'system', - content: systemPrompt.trim() - }); - } - - const payload = { - model: inputs.model, - messages: processedMessages, - stream: inputs.stream, - }; - - // 添加启用的参数 - if (parameterEnabled.temperature && inputs.temperature !== undefined) { - payload.temperature = inputs.temperature; - } - if (parameterEnabled.top_p && inputs.top_p !== undefined) { - payload.top_p = inputs.top_p; - } - if (parameterEnabled.max_tokens && inputs.max_tokens !== undefined) { - payload.max_tokens = inputs.max_tokens; - } - if (parameterEnabled.frequency_penalty && inputs.frequency_penalty !== undefined) { - payload.frequency_penalty = inputs.frequency_penalty; - } - if (parameterEnabled.presence_penalty && inputs.presence_penalty !== undefined) { - payload.presence_penalty = inputs.presence_penalty; - } - if (parameterEnabled.seed && inputs.seed !== undefined && inputs.seed !== null) { - payload.seed = inputs.seed; - } - - return payload; -}; - -// 处理API错误响应 -export const handleApiError = (error, response = null) => { - const errorInfo = { - error: error.message || '未知错误', - timestamp: new Date().toISOString(), - stack: error.stack - }; - - if (response) { - errorInfo.status = response.status; - errorInfo.statusText = response.statusText; - } - - if (error.message.includes('HTTP error')) { - errorInfo.details = '服务器返回了错误状态码'; - } else if (error.message.includes('Failed to fetch')) { - errorInfo.details = '网络连接失败或服务器无响应'; - } - - return errorInfo; -}; - -// 处理模型数据 -export const processModelsData = (data, currentModel) => { - const modelOptions = data.map(model => ({ - label: model, - value: model, - })); - - const hasCurrentModel = modelOptions.some(option => option.value === currentModel); - const selectedModel = hasCurrentModel && modelOptions.length > 0 - ? currentModel - : modelOptions[0]?.value; - - return { modelOptions, selectedModel }; -}; - -// 处理分组数据 -export const processGroupsData = (data, userGroup) => { - let groupOptions = Object.entries(data).map(([group, info]) => ({ - label: info.desc.length > 20 ? info.desc.substring(0, 20) + '...' : info.desc, - value: group, - ratio: info.ratio, - fullLabel: info.desc, - })); - - if (groupOptions.length === 0) { - groupOptions = [{ - label: '用户分组', - value: '', - ratio: 1, - }]; - } else if (userGroup) { - const userGroupIndex = groupOptions.findIndex(g => g.value === userGroup); - if (userGroupIndex > -1) { - const userGroupOption = groupOptions.splice(userGroupIndex, 1)[0]; - groupOptions.unshift(userGroupOption); - } - } - - return groupOptions; -}; \ No newline at end of file diff --git a/web/src/helpers/authUtils.js b/web/src/helpers/auth.js similarity index 100% rename from web/src/helpers/authUtils.js rename to web/src/helpers/auth.js diff --git a/web/src/helpers/index.js b/web/src/helpers/index.js index fae2c6aa..c43be2c9 100644 --- a/web/src/helpers/index.js +++ b/web/src/helpers/index.js @@ -1,8 +1,7 @@ export * from './history'; -export * from './authUtils'; +export * from './auth'; export * from './utils'; export * from './api'; -export * from './apiUtils'; -export * from './messageUtils'; -export * from './textAnimationUtils'; -export * from './logUtils'; +export * from './render'; +export * from './log'; +export * from './data'; diff --git a/web/src/helpers/logUtils.js b/web/src/helpers/log.js similarity index 100% rename from web/src/helpers/logUtils.js rename to web/src/helpers/log.js diff --git a/web/src/helpers/messageUtils.js b/web/src/helpers/messageUtils.js deleted file mode 100644 index 76412dec..00000000 --- a/web/src/helpers/messageUtils.js +++ /dev/null @@ -1,201 +0,0 @@ -import { THINK_TAG_REGEX, MESSAGE_ROLES } from '../constants/playground.constants'; - -// 生成唯一ID -let messageId = 4; -export const generateMessageId = () => `${messageId++}`; - -// 提取消息中的文本内容 -export const getTextContent = (message) => { - if (!message || !message.content) return ''; - - if (Array.isArray(message.content)) { - const textContent = message.content.find(item => item.type === 'text'); - return textContent?.text || ''; - } - return typeof message.content === 'string' ? message.content : ''; -}; - -// 处理 think 标签 -export const processThinkTags = (content, reasoningContent = '') => { - if (!content || !content.includes('')) { - return { content, reasoningContent }; - } - - const thoughts = []; - const replyParts = []; - let lastIndex = 0; - let match; - - THINK_TAG_REGEX.lastIndex = 0; - while ((match = THINK_TAG_REGEX.exec(content)) !== null) { - replyParts.push(content.substring(lastIndex, match.index)); - thoughts.push(match[1]); - lastIndex = match.index + match[0].length; - } - replyParts.push(content.substring(lastIndex)); - - const processedContent = replyParts.join('').replace(/<\/?think>/g, '').trim(); - const thoughtsStr = thoughts.join('\n\n---\n\n'); - const processedReasoningContent = reasoningContent && thoughtsStr - ? `${reasoningContent}\n\n---\n\n${thoughtsStr}` - : reasoningContent || thoughtsStr; - - return { - content: processedContent, - reasoningContent: processedReasoningContent - }; -}; - -// 处理未完成的 think 标签 -export const processIncompleteThinkTags = (content, reasoningContent = '') => { - if (!content) return { content: '', reasoningContent }; - - const lastOpenThinkIndex = content.lastIndexOf(''); - if (lastOpenThinkIndex === -1) { - return processThinkTags(content, reasoningContent); - } - - const fragmentAfterLastOpen = content.substring(lastOpenThinkIndex); - if (!fragmentAfterLastOpen.includes('')) { - const unclosedThought = fragmentAfterLastOpen.substring(''.length).trim(); - const cleanContent = content.substring(0, lastOpenThinkIndex); - const processedReasoningContent = unclosedThought - ? reasoningContent ? `${reasoningContent}\n\n---\n\n${unclosedThought}` : unclosedThought - : reasoningContent; - - return processThinkTags(cleanContent, processedReasoningContent); - } - - return processThinkTags(content, reasoningContent); -}; - -// 构建消息内容(包含图片) -export const buildMessageContent = (textContent, imageUrls = [], imageEnabled = false) => { - if (!textContent && (!imageUrls || imageUrls.length === 0)) { - return ''; - } - - const validImageUrls = imageUrls.filter(url => url && url.trim() !== ''); - - if (imageEnabled && validImageUrls.length > 0) { - return [ - { type: 'text', text: textContent || '' }, - ...validImageUrls.map(url => ({ - type: 'image_url', - image_url: { url: url.trim() } - })) - ]; - } - - return textContent || ''; -}; - -// 创建新消息 -export const createMessage = (role, content, options = {}) => ({ - role, - content, - createAt: Date.now(), - id: generateMessageId(), - ...options -}); - -// 创建加载中的助手消息 -export const createLoadingAssistantMessage = () => createMessage( - MESSAGE_ROLES.ASSISTANT, - '', - { - reasoningContent: '', - isReasoningExpanded: true, - isThinkingComplete: false, - hasAutoCollapsed: false, - status: 'loading' - } -); - -// 检查消息是否包含图片 -export const hasImageContent = (message) => { - return message && - Array.isArray(message.content) && - message.content.some(item => item.type === 'image_url'); -}; - -// 格式化消息用于API请求 -export const formatMessageForAPI = (message) => { - if (!message) return null; - - return { - role: message.role, - content: message.content - }; -}; - -// 验证消息是否有效 -export const isValidMessage = (message) => { - return message && - message.role && - (message.content || message.content === ''); -}; - -// 获取最后一条用户消息 -export const getLastUserMessage = (messages) => { - if (!Array.isArray(messages)) return null; - - for (let i = messages.length - 1; i >= 0; i--) { - if (messages[i].role === MESSAGE_ROLES.USER) { - return messages[i]; - } - } - return null; -}; - -// 获取最后一条助手消息 -export const getLastAssistantMessage = (messages) => { - if (!Array.isArray(messages)) return null; - - for (let i = messages.length - 1; i >= 0; i--) { - if (messages[i].role === MESSAGE_ROLES.ASSISTANT) { - return messages[i]; - } - } - return null; -}; - -// 构建API请求负载(从apiUtils移动过来) -export const buildApiPayload = (messages, systemPrompt, inputs, parameterEnabled) => { - const processedMessages = messages - .filter(isValidMessage) - .map(formatMessageForAPI) - .filter(Boolean); - - // 如果有系统提示,插入到消息开头 - if (systemPrompt && systemPrompt.trim()) { - processedMessages.unshift({ - role: MESSAGE_ROLES.SYSTEM, - content: systemPrompt.trim() - }); - } - - const payload = { - model: inputs.model, - messages: processedMessages, - stream: inputs.stream, - }; - - // 添加启用的参数 - const parameterMappings = { - temperature: 'temperature', - top_p: 'top_p', - max_tokens: 'max_tokens', - frequency_penalty: 'frequency_penalty', - presence_penalty: 'presence_penalty', - seed: 'seed' - }; - - Object.entries(parameterMappings).forEach(([key, param]) => { - if (parameterEnabled[key] && inputs[param] !== undefined && inputs[param] !== null) { - payload[param] = inputs[param]; - } - }); - - return payload; -}; \ No newline at end of file diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index fa1de023..862dd8eb 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -1,6 +1,7 @@ import i18next from 'i18next'; import { Modal, Tag, Typography } from '@douyinfe/semi-ui'; -import { copy, isMobile, showSuccess } from './utils.js'; +import { copy, isMobile, showSuccess } from './index.js'; +import { visit } from 'unist-util-visit'; export function renderText(text, limit) { if (text.length > limit) { @@ -419,11 +420,25 @@ export function renderModelPrice(

{cacheTokens > 0 && !image && !webSearch && !fileSearch ? i18next.t( - '输入 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}', + '输入 {{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 && !webSearch && !fileSearch + ? i18next.t( + '输入 {{nonImageInput}} tokens + 图片输入 {{imageInput}} tokens * {{imageRatio}} / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}', { - nonCacheInput: inputTokens - cacheTokens, - cacheInput: cacheTokens, - cachePrice: inputRatioPrice * cacheRatio, + nonImageInput: inputTokens - imageOutputTokens, + imageInput: imageOutputTokens, + imageRatio: imageRatio, price: inputRatioPrice, completion: completionTokens, compPrice: completionRatioPrice, @@ -431,82 +446,68 @@ export function renderModelPrice( total: price.toFixed(6), }, ) - : image && imageOutputTokens > 0 && !webSearch && !fileSearch - ? i18next.t( - '输入 {{nonImageInput}} tokens + 图片输入 {{imageInput}} tokens * {{imageRatio}} / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}', + : webSearch && webSearchCallCount > 0 && !image && !fileSearch + ? i18next.t( + '输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + Web搜索 {{webSearchCallCount}}次 / 1K 次 * ${{webSearchPrice}} * {{ratio}} = ${{total}}', { - nonImageInput: inputTokens - imageOutputTokens, - imageInput: imageOutputTokens, - imageRatio: imageRatio, + input: inputTokens, price: inputRatioPrice, completion: completionTokens, compPrice: completionRatioPrice, ratio: groupRatio, + webSearchCallCount, + webSearchPrice, total: price.toFixed(6), }, ) - : webSearch && webSearchCallCount > 0 && !image && !fileSearch - ? i18next.t( - '输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + Web搜索 {{webSearchCallCount}}次 / 1K 次 * ${{webSearchPrice}} * {{ratio}} = ${{total}}', + : fileSearch && + fileSearchCallCount > 0 && + !image && + !webSearch + ? i18next.t( + '输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + 文件搜索 {{fileSearchCallCount}}次 / 1K 次 * ${{fileSearchPrice}} * {{ratio}}= ${{total}}', { input: inputTokens, price: inputRatioPrice, completion: completionTokens, compPrice: completionRatioPrice, ratio: groupRatio, - webSearchCallCount, - webSearchPrice, + fileSearchCallCount, + fileSearchPrice, total: price.toFixed(6), }, ) - : fileSearch && + : webSearch && + webSearchCallCount > 0 && + fileSearch && fileSearchCallCount > 0 && - !image && - !webSearch - ? i18next.t( - '输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + 文件搜索 {{fileSearchCallCount}}次 / 1K 次 * ${{fileSearchPrice}} * {{ratio}}= ${{total}}', + !image + ? i18next.t( + '输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + Web搜索 {{webSearchCallCount}}次 / 1K 次 * ${{webSearchPrice}} * {{ratio}}+ 文件搜索 {{fileSearchCallCount}}次 / 1K 次 * ${{fileSearchPrice}} * {{ratio}}= ${{total}}', { input: inputTokens, price: inputRatioPrice, completion: completionTokens, compPrice: completionRatioPrice, ratio: groupRatio, + webSearchCallCount, + webSearchPrice, fileSearchCallCount, fileSearchPrice, total: price.toFixed(6), }, ) - : webSearch && - webSearchCallCount > 0 && - fileSearch && - fileSearchCallCount > 0 && - !image - ? i18next.t( - '输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + Web搜索 {{webSearchCallCount}}次 / 1K 次 * ${{webSearchPrice}} * {{ratio}}+ 文件搜索 {{fileSearchCallCount}}次 / 1K 次 * ${{fileSearchPrice}} * {{ratio}}= ${{total}}', - { - input: inputTokens, - price: inputRatioPrice, - completion: completionTokens, - compPrice: completionRatioPrice, - ratio: groupRatio, - webSearchCallCount, - webSearchPrice, - fileSearchCallCount, - fileSearchPrice, - 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), - }, - )} + '输入 {{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), + }, + )}

{i18next.t('仅供参考,以实际扣费为准')}

@@ -677,10 +678,10 @@ export function renderAudioModelPrice( let audioPrice = (audioInputTokens / 1000000) * inputRatioPrice * audioRatio * groupRatio + (audioCompletionTokens / 1000000) * - inputRatioPrice * - audioRatio * - audioCompletionRatio * - groupRatio; + inputRatioPrice * + audioRatio * + audioCompletionRatio * + groupRatio; let price = textPrice + audioPrice; return ( <> @@ -736,27 +737,27 @@ export function renderAudioModelPrice(

{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), - }, - ) + '文字提示 {{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), - }, - )} + '文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', + { + input: inputTokens, + price: inputRatioPrice, + completion: completionTokens, + compPrice: completionRatioPrice, + total: textPrice.toFixed(6), + }, + )}

{i18next.t( @@ -1024,33 +1025,33 @@ export function renderClaudeModelPrice(

{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), - }, - ) + '提示 {{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), - }, - )} + '提示 {{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), + }, + )}

{i18next.t('仅供参考,以实际扣费为准')}

@@ -1128,3 +1129,79 @@ export function renderClaudeModelPriceSimple( } } } + +/** + * rehype 插件:将段落等文本节点拆分为逐词 ,并添加淡入动画 class。 + * 仅在流式渲染阶段使用,避免已渲染文字重复动画。 + */ +export function rehypeSplitWordsIntoSpans(options = {}) { + const { previousContentLength = 0 } = options; + + return (tree) => { + let currentCharCount = 0; // 当前已处理的字符数 + + visit(tree, 'element', (node) => { + if ( + ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'strong'].includes(node.tagName) && + node.children + ) { + const newChildren = []; + node.children.forEach((child) => { + if (child.type === 'text') { + try { + // 使用 Intl.Segmenter 精准拆分中英文及标点 + const segmenter = new Intl.Segmenter('zh', { granularity: 'word' }); + const segments = segmenter.segment(child.value); + + Array.from(segments) + .map((seg) => seg.segment) + .filter(Boolean) + .forEach((word) => { + const wordStartPos = currentCharCount; + const wordEndPos = currentCharCount + word.length; + + // 判断这个词是否是新增的(在 previousContentLength 之后) + const isNewContent = wordStartPos >= previousContentLength; + + newChildren.push({ + type: 'element', + tagName: 'span', + properties: { + className: isNewContent ? ['animate-fade-in'] : [], + }, + children: [{ type: 'text', value: word }], + }); + + currentCharCount = wordEndPos; + }); + } catch (_) { + // Fallback:如果浏览器不支持 Segmenter + const textStartPos = currentCharCount; + const isNewContent = textStartPos >= previousContentLength; + + if (isNewContent) { + // 新内容,添加动画 + newChildren.push({ + type: 'element', + tagName: 'span', + properties: { + className: ['animate-fade-in'], + }, + children: [{ type: 'text', value: child.value }], + }); + } else { + // 旧内容,不添加动画 + newChildren.push(child); + } + + currentCharCount += child.value.length; + } + } else { + newChildren.push(child); + } + }); + node.children = newChildren; + } + }); + }; +} \ No newline at end of file diff --git a/web/src/helpers/textAnimationUtils.js b/web/src/helpers/textAnimationUtils.js deleted file mode 100644 index a9bb6db3..00000000 --- a/web/src/helpers/textAnimationUtils.js +++ /dev/null @@ -1,77 +0,0 @@ -import { visit } from 'unist-util-visit'; - -/** - * rehype 插件:将段落等文本节点拆分为逐词 ,并添加淡入动画 class。 - * 仅在流式渲染阶段使用,避免已渲染文字重复动画。 - */ -export function rehypeSplitWordsIntoSpans(options = {}) { - const { previousContentLength = 0 } = options; - - return (tree) => { - let currentCharCount = 0; // 当前已处理的字符数 - - visit(tree, 'element', (node) => { - if ( - ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'strong'].includes(node.tagName) && - node.children - ) { - const newChildren = []; - node.children.forEach((child) => { - if (child.type === 'text') { - try { - // 使用 Intl.Segmenter 精准拆分中英文及标点 - const segmenter = new Intl.Segmenter('zh', { granularity: 'word' }); - const segments = segmenter.segment(child.value); - - Array.from(segments) - .map((seg) => seg.segment) - .filter(Boolean) - .forEach((word) => { - const wordStartPos = currentCharCount; - const wordEndPos = currentCharCount + word.length; - - // 判断这个词是否是新增的(在 previousContentLength 之后) - const isNewContent = wordStartPos >= previousContentLength; - - newChildren.push({ - type: 'element', - tagName: 'span', - properties: { - className: isNewContent ? ['animate-fade-in'] : [], - }, - children: [{ type: 'text', value: word }], - }); - - currentCharCount = wordEndPos; - }); - } catch (_) { - // Fallback:如果浏览器不支持 Segmenter - const textStartPos = currentCharCount; - const isNewContent = textStartPos >= previousContentLength; - - if (isNewContent) { - // 新内容,添加动画 - newChildren.push({ - type: 'element', - tagName: 'span', - properties: { - className: ['animate-fade-in'], - }, - children: [{ type: 'text', value: child.value }], - }); - } else { - // 旧内容,不添加动画 - newChildren.push(child); - } - - currentCharCount += child.value.length; - } - } else { - newChildren.push(child); - } - }); - node.children = newChildren; - } - }); - }; -} \ No newline at end of file diff --git a/web/src/helpers/utils.js b/web/src/helpers/utils.js index 35f20c89..cd05653e 100644 --- a/web/src/helpers/utils.js +++ b/web/src/helpers/utils.js @@ -2,6 +2,7 @@ import { Toast } from '@douyinfe/semi-ui'; import { toastConstants } from '../constants'; import React from 'react'; import { toast } from 'react-toastify'; +import { THINK_TAG_REGEX, MESSAGE_ROLES } from '../constants/playground.constants'; const HTMLToastContent = ({ htmlContent }) => { return
; @@ -283,3 +284,165 @@ export function compareObjects(oldObject, newObject) { return changedProperties; } + +// playground message + +// 生成唯一ID +let messageId = 4; +export const generateMessageId = () => `${messageId++}`; + +// 提取消息中的文本内容 +export const getTextContent = (message) => { + if (!message || !message.content) return ''; + + if (Array.isArray(message.content)) { + const textContent = message.content.find(item => item.type === 'text'); + return textContent?.text || ''; + } + return typeof message.content === 'string' ? message.content : ''; +}; + +// 处理 think 标签 +export const processThinkTags = (content, reasoningContent = '') => { + if (!content || !content.includes('')) { + return { content, reasoningContent }; + } + + const thoughts = []; + const replyParts = []; + let lastIndex = 0; + let match; + + THINK_TAG_REGEX.lastIndex = 0; + while ((match = THINK_TAG_REGEX.exec(content)) !== null) { + replyParts.push(content.substring(lastIndex, match.index)); + thoughts.push(match[1]); + lastIndex = match.index + match[0].length; + } + replyParts.push(content.substring(lastIndex)); + + const processedContent = replyParts.join('').replace(/<\/?think>/g, '').trim(); + const thoughtsStr = thoughts.join('\n\n---\n\n'); + const processedReasoningContent = reasoningContent && thoughtsStr + ? `${reasoningContent}\n\n---\n\n${thoughtsStr}` + : reasoningContent || thoughtsStr; + + return { + content: processedContent, + reasoningContent: processedReasoningContent + }; +}; + +// 处理未完成的 think 标签 +export const processIncompleteThinkTags = (content, reasoningContent = '') => { + if (!content) return { content: '', reasoningContent }; + + const lastOpenThinkIndex = content.lastIndexOf(''); + if (lastOpenThinkIndex === -1) { + return processThinkTags(content, reasoningContent); + } + + const fragmentAfterLastOpen = content.substring(lastOpenThinkIndex); + if (!fragmentAfterLastOpen.includes('')) { + const unclosedThought = fragmentAfterLastOpen.substring(''.length).trim(); + const cleanContent = content.substring(0, lastOpenThinkIndex); + const processedReasoningContent = unclosedThought + ? reasoningContent ? `${reasoningContent}\n\n---\n\n${unclosedThought}` : unclosedThought + : reasoningContent; + + return processThinkTags(cleanContent, processedReasoningContent); + } + + return processThinkTags(content, reasoningContent); +}; + +// 构建消息内容(包含图片) +export const buildMessageContent = (textContent, imageUrls = [], imageEnabled = false) => { + if (!textContent && (!imageUrls || imageUrls.length === 0)) { + return ''; + } + + const validImageUrls = imageUrls.filter(url => url && url.trim() !== ''); + + if (imageEnabled && validImageUrls.length > 0) { + return [ + { type: 'text', text: textContent || '' }, + ...validImageUrls.map(url => ({ + type: 'image_url', + image_url: { url: url.trim() } + })) + ]; + } + + return textContent || ''; +}; + +// 创建新消息 +export const createMessage = (role, content, options = {}) => ({ + role, + content, + createAt: Date.now(), + id: generateMessageId(), + ...options +}); + +// 创建加载中的助手消息 +export const createLoadingAssistantMessage = () => createMessage( + MESSAGE_ROLES.ASSISTANT, + '', + { + reasoningContent: '', + isReasoningExpanded: true, + isThinkingComplete: false, + hasAutoCollapsed: false, + status: 'loading' + } +); + +// 检查消息是否包含图片 +export const hasImageContent = (message) => { + return message && + Array.isArray(message.content) && + message.content.some(item => item.type === 'image_url'); +}; + +// 格式化消息用于API请求 +export const formatMessageForAPI = (message) => { + if (!message) return null; + + return { + role: message.role, + content: message.content + }; +}; + +// 验证消息是否有效 +export const isValidMessage = (message) => { + return message && + message.role && + (message.content || message.content === ''); +}; + +// 获取最后一条用户消息 +export const getLastUserMessage = (messages) => { + if (!Array.isArray(messages)) return null; + + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].role === MESSAGE_ROLES.USER) { + return messages[i]; + } + } + return null; +}; + +// 获取最后一条助手消息 +export const getLastAssistantMessage = (messages) => { + if (!Array.isArray(messages)) return null; + + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].role === MESSAGE_ROLES.ASSISTANT) { + return messages[i]; + } + } + return null; +}; diff --git a/web/src/hooks/useApiRequest.js b/web/src/hooks/useApiRequest.js index 5087dc19..4a3b7c3a 100644 --- a/web/src/hooks/useApiRequest.js +++ b/web/src/hooks/useApiRequest.js @@ -1,20 +1,17 @@ import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { SSE } from 'sse'; -import { getUserIdFromLocalStorage } from '../helpers/index.js'; import { API_ENDPOINTS, MESSAGE_STATUS, DEBUG_TABS } from '../constants/playground.constants'; import { - buildApiPayload, - handleApiError -} from '../helpers/apiUtils'; -import { + getUserIdFromLocalStorage, + handleApiError, processThinkTags, processIncompleteThinkTags -} from '../helpers/messageUtils'; +} from '../helpers'; export const useApiRequest = ( setMessage, diff --git a/web/src/hooks/useDataLoader.js b/web/src/hooks/useDataLoader.js index 011a4f7c..83d53199 100644 --- a/web/src/hooks/useDataLoader.js +++ b/web/src/hooks/useDataLoader.js @@ -1,8 +1,7 @@ import { useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { API } from '../helpers/api'; +import { API, processModelsData, processGroupsData } from '../helpers'; import { API_ENDPOINTS } from '../constants/playground.constants'; -import { processModelsData, processGroupsData } from '../helpers/apiUtils'; export const useDataLoader = ( userState, diff --git a/web/src/hooks/useMessageActions.js b/web/src/hooks/useMessageActions.js index 4a0fba69..4cfcf9f1 100644 --- a/web/src/hooks/useMessageActions.js +++ b/web/src/hooks/useMessageActions.js @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import { Toast, Modal } from '@douyinfe/semi-ui'; import { useTranslation } from 'react-i18next'; -import { getTextContent } from '../helpers/messageUtils'; +import { getTextContent } from '../helpers'; import { ERROR_MESSAGES } from '../constants/playground.constants'; export const useMessageActions = (message, setMessage, onMessageSend, saveMessages) => { diff --git a/web/src/hooks/useMessageEdit.js b/web/src/hooks/useMessageEdit.js index e91ebae6..479524b6 100644 --- a/web/src/hooks/useMessageEdit.js +++ b/web/src/hooks/useMessageEdit.js @@ -1,7 +1,7 @@ import { useCallback, useState, useRef } from 'react'; import { Toast, Modal } from '@douyinfe/semi-ui'; import { useTranslation } from 'react-i18next'; -import { getTextContent, buildApiPayload, createLoadingAssistantMessage } from '../helpers/messageUtils'; +import { getTextContent, buildApiPayload, createLoadingAssistantMessage } from '../helpers'; import { MESSAGE_ROLES } from '../constants/playground.constants'; export const useMessageEdit = ( diff --git a/web/src/hooks/usePlaygroundState.js b/web/src/hooks/usePlaygroundState.js index b4b4af47..e8c4727d 100644 --- a/web/src/hooks/usePlaygroundState.js +++ b/web/src/hooks/usePlaygroundState.js @@ -1,7 +1,7 @@ import { useState, useCallback, useRef, useEffect } from 'react'; import { DEFAULT_MESSAGES, DEFAULT_CONFIG, DEBUG_TABS, MESSAGE_STATUS } from '../constants/playground.constants'; import { loadConfig, saveConfig, loadMessages, saveMessages } from '../components/playground/configStorage'; -import { processIncompleteThinkTags } from '../helpers/messageUtils'; +import { processIncompleteThinkTags } from '../helpers'; export const usePlaygroundState = () => { // 使用惰性初始化,确保只在组件首次挂载时加载配置和消息 diff --git a/web/src/pages/Detail/index.js b/web/src/pages/Detail/index.js index 44cbd857..8dfdbe97 100644 --- a/web/src/pages/Detail/index.js +++ b/web/src/pages/Detail/index.js @@ -30,14 +30,12 @@ import { showError, timestamp2string, timestamp2string1, -} from '../../helpers'; -import { getQuotaWithUnit, modelColorMap, renderNumber, renderQuota, - modelToColor, -} from '../../helpers/render'; + modelToColor +} from '../../helpers'; import { UserContext } from '../../context/User/index.js'; import { useTranslation } from 'react-i18next'; diff --git a/web/src/pages/Playground/index.js b/web/src/pages/Playground/index.js index b495bc15..8940dee3 100644 --- a/web/src/pages/Playground/index.js +++ b/web/src/pages/Playground/index.js @@ -7,9 +7,7 @@ import { Layout, Toast, Modal } from '@douyinfe/semi-ui'; import { UserContext } from '../../context/User/index.js'; import { useStyle, styleActions } from '../../context/Style/index.js'; -// Utils and hooks -import { getLogo } from '../../helpers/index.js'; -import { stringToColor } from '../../helpers/render.js'; +// hooks import { usePlaygroundState } from '../../hooks/usePlaygroundState.js'; import { useMessageActions } from '../../hooks/useMessageActions.js'; import { useApiRequest } from '../../hooks/useApiRequest.js'; @@ -19,17 +17,18 @@ import { useDataLoader } from '../../hooks/useDataLoader.js'; // Constants and utils import { - DEFAULT_MESSAGES, MESSAGE_ROLES, ERROR_MESSAGES } from '../../constants/playground.constants.js'; import { + getLogo, + stringToColor, buildMessageContent, createMessage, createLoadingAssistantMessage, getTextContent, buildApiPayload -} from '../../helpers/messageUtils.js'; +} from '../../helpers'; // Components import { diff --git a/web/src/pages/Redemption/EditRedemption.js b/web/src/pages/Redemption/EditRedemption.js index 1529a1cd..e720f90c 100644 --- a/web/src/pages/Redemption/EditRedemption.js +++ b/web/src/pages/Redemption/EditRedemption.js @@ -6,11 +6,9 @@ import { isMobile, showError, showSuccess, -} from '../../helpers'; -import { renderQuota, - renderQuotaWithPrompt, -} from '../../helpers/render'; + renderQuotaWithPrompt +} from '../../helpers'; import { AutoComplete, Button, diff --git a/web/src/pages/Setting/Operation/ModelRationNotSetEditor.js b/web/src/pages/Setting/Operation/ModelRationNotSetEditor.js index 496208f4..d5d8d832 100644 --- a/web/src/pages/Setting/Operation/ModelRationNotSetEditor.js +++ b/web/src/pages/Setting/Operation/ModelRationNotSetEditor.js @@ -17,8 +17,7 @@ import { IconSave, IconBolt, } from '@douyinfe/semi-icons'; -import { showError, showSuccess } from '../../../helpers'; -import { API } from '../../../helpers'; +import { API, showError, showSuccess } from '../../../helpers'; import { useTranslation } from 'react-i18next'; export default function ModelRatioNotSetEditor(props) { diff --git a/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js b/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js index 409aa13f..85426a5e 100644 --- a/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js +++ b/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js @@ -1,5 +1,5 @@ // ModelSettingsVisualEditor.js -import React, { useContext, useEffect, useState, useRef } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import { Table, Button, @@ -8,9 +8,7 @@ import { Form, Space, RadioGroup, - Radio, - Tabs, - TabPane, + Radio } from '@douyinfe/semi-ui'; import { IconDelete, @@ -19,11 +17,8 @@ import { IconSave, IconEdit, } from '@douyinfe/semi-icons'; -import { showError, showSuccess } from '../../../helpers'; -import { API } from '../../../helpers'; +import { API, showError, showSuccess, getQuotaPerUnit } from '../../../helpers'; import { useTranslation } from 'react-i18next'; -import { StatusContext } from '../../../context/Status/index.js'; -import { getQuotaPerUnit } from '../../../helpers/render.js'; export default function ModelSettingsVisualEditor(props) { const { t } = useTranslation(); @@ -304,11 +299,11 @@ export default function ModelSettingsVisualEditor(props) { prev.map((model, index) => index === existingModelIndex ? { - name: values.name, - price: values.price || '', - ratio: values.ratio || '', - completionRatio: values.completionRatio || '', - } + name: values.name, + price: values.price || '', + ratio: values.ratio || '', + completionRatio: values.completionRatio || '', + } : model, ), ); @@ -456,8 +451,8 @@ export default function ModelSettingsVisualEditor(props) { model.name === currentModel.name) + currentModel.name && + models.some((model) => model.name === currentModel.name) ? t('编辑模型') : t('添加模型') } diff --git a/web/src/pages/Token/EditToken.js b/web/src/pages/Token/EditToken.js index ad8c11c8..946164da 100644 --- a/web/src/pages/Token/EditToken.js +++ b/web/src/pages/Token/EditToken.js @@ -6,8 +6,9 @@ import { showError, showSuccess, timestamp2string, + renderGroupOption, + renderQuotaWithPrompt } from '../../helpers'; -import { renderGroupOption, renderQuotaWithPrompt } from '../../helpers/render'; import { AutoComplete, Banner, diff --git a/web/src/pages/TopUp/index.js b/web/src/pages/TopUp/index.js index d2e16cfc..550af5e0 100644 --- a/web/src/pages/TopUp/index.js +++ b/web/src/pages/TopUp/index.js @@ -1,10 +1,13 @@ import React, { useEffect, useState, useContext } from 'react'; -import { API, showError, showInfo, showSuccess } from '../../helpers'; import { + API, + showError, + showInfo, + showSuccess, renderQuota, renderQuotaWithAmount, - stringToColor, -} from '../../helpers/render'; + stringToColor +} from '../../helpers'; import { Layout, Typography, diff --git a/web/src/pages/User/EditUser.js b/web/src/pages/User/EditUser.js index 747628bb..dceb670a 100644 --- a/web/src/pages/User/EditUser.js +++ b/web/src/pages/User/EditUser.js @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { API, isMobile, showError, showSuccess } from '../../helpers'; -import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render'; +import { API, isMobile, showError, showSuccess, renderQuota, renderQuotaWithPrompt } from '../../helpers'; import { Button, Input, From b0a145fd5bc954aa053053fe37da9a32d90921c7 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Wed, 4 Jun 2025 00:01:41 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=F0=9F=90=9B=20fix(api):=20resolve=20miss?= =?UTF-8?q?ing=20imports=20in=20buildApiPayload=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix ReferenceError caused by undefined `isValidMessage` and `MESSAGE_ROLES` in the buildApiPayload function within api.js. Changes: - Add missing `isValidMessage` import from utils.js - Add missing `MESSAGE_ROLES` import from playground constants - Consolidate duplicate `formatMessageForAPI` import - Clean up import statements organization Resolves: ReferenceError: isValidMessage is not defined at buildApiPayload (api.js:39:13) --- web/src/helpers/api.js | 4 ++-- web/src/helpers/render.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/helpers/api.js b/web/src/helpers/api.js index d58487b5..73708b04 100644 --- a/web/src/helpers/api.js +++ b/web/src/helpers/api.js @@ -1,6 +1,6 @@ -import { getUserIdFromLocalStorage, showError } from './utils'; +import { getUserIdFromLocalStorage, showError, formatMessageForAPI, isValidMessage } from './utils'; import axios from 'axios'; -import { formatMessageForAPI } from './index.js'; +import { MESSAGE_ROLES } from '../constants/playground.constants'; export let API = axios.create({ baseURL: import.meta.env.VITE_REACT_APP_SERVER_URL diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 862dd8eb..c8302feb 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -1,6 +1,6 @@ import i18next from 'i18next'; import { Modal, Tag, Typography } from '@douyinfe/semi-ui'; -import { copy, isMobile, showSuccess } from './index.js'; +import { copy, isMobile, showSuccess } from './utils'; import { visit } from 'unist-util-visit'; export function renderText(text, limit) { From a92373c78ce5ede2ec0b85f986f633dc5e9d49f5 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Wed, 4 Jun 2025 00:11:06 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(helpers):?= =?UTF-8?q?=20refactor=20the=20`components/utils.js`=20to=20`helpers/api.j?= =?UTF-8?q?s`=20and=20related=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/ChannelsTable.js | 2 +- web/src/components/RegisterForm.js | 2 +- web/src/helpers/api.js | 79 ++++++++++++++++++++++++++- web/src/pages/Channel/EditChannel.js | 2 +- web/src/pages/Channel/EditTagModal.js | 2 +- 5 files changed, 82 insertions(+), 5 deletions(-) diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js index c6bdf75b..33c8c398 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/ChannelsTable.js @@ -46,7 +46,7 @@ import { IconCopy, IconSmallTriangleRight } from '@douyinfe/semi-icons'; -import { loadChannelModels } from './utils.js'; +import { loadChannelModels } from '../helpers'; import EditTagModal from '../pages/Channel/EditTagModal.js'; import { useTranslation } from 'react-i18next'; diff --git a/web/src/components/RegisterForm.js b/web/src/components/RegisterForm.js index 78db98ad..23ff94df 100644 --- a/web/src/components/RegisterForm.js +++ b/web/src/components/RegisterForm.js @@ -26,7 +26,7 @@ import { onGitHubOAuthClicked, onLinuxDOOAuthClicked, onOIDCClicked, -} from './utils.js'; +} from '../helpers'; import OIDCIcon from './common/logo/OIDCIcon.js'; import LinuxDoIcon from './common/logo/LinuxDoIcon.js'; import WeChatIcon from './common/logo/WeChatIcon.js'; diff --git a/web/src/helpers/api.js b/web/src/helpers/api.js index 73708b04..eb70cd27 100644 --- a/web/src/helpers/api.js +++ b/web/src/helpers/api.js @@ -134,4 +134,81 @@ export const processGroupsData = (data, userGroup) => { } return groupOptions; -}; \ No newline at end of file +}; + +// 原来components中的utils.js + +export async function getOAuthState() { + let path = '/api/oauth/state'; + let affCode = localStorage.getItem('aff'); + if (affCode && affCode.length > 0) { + path += `?aff=${affCode}`; + } + const res = await API.get(path); + const { success, message, data } = res.data; + if (success) { + return data; + } else { + showError(message); + return ''; + } +} + +export async function onOIDCClicked(auth_url, client_id, openInNewTab = false) { + const state = await getOAuthState(); + if (!state) return; + const redirect_uri = `${window.location.origin}/oauth/oidc`; + const response_type = 'code'; + const scope = 'openid profile email'; + const url = `${auth_url}?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope}&state=${state}`; + if (openInNewTab) { + window.open(url); + } else { + window.location.href = url; + } +} + +export async function onGitHubOAuthClicked(github_client_id) { + const state = await getOAuthState(); + if (!state) return; + window.open( + `https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`, + ); +} + +export async function onLinuxDOOAuthClicked(linuxdo_client_id) { + const state = await getOAuthState(); + if (!state) return; + window.open( + `https://connect.linux.do/oauth2/authorize?response_type=code&client_id=${linuxdo_client_id}&state=${state}`, + ); +} + +let channelModels = undefined; +export async function loadChannelModels() { + const res = await API.get('/api/models'); + const { success, data } = res.data; + if (!success) { + return; + } + channelModels = data; + localStorage.setItem('channel_models', JSON.stringify(data)); +} + +export function getChannelModels(type) { + if (channelModels !== undefined && type in channelModels) { + if (!channelModels[type]) { + return []; + } + return channelModels[type]; + } + let models = localStorage.getItem('channel_models'); + if (!models) { + return []; + } + channelModels = JSON.parse(models); + if (type in channelModels) { + return channelModels[type]; + } + return []; +} diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js index 810df9dc..feed8beb 100644 --- a/web/src/pages/Channel/EditChannel.js +++ b/web/src/pages/Channel/EditChannel.js @@ -26,7 +26,7 @@ import { Card, Tag, } from '@douyinfe/semi-ui'; -import { getChannelModels } from '../../components/utils.js'; +import { getChannelModels } from '../../helpers'; import { IconSave, IconClose, diff --git a/web/src/pages/Channel/EditTagModal.js b/web/src/pages/Channel/EditTagModal.js index 5ce05bec..52dd4bbb 100644 --- a/web/src/pages/Channel/EditTagModal.js +++ b/web/src/pages/Channel/EditTagModal.js @@ -27,7 +27,7 @@ import { IconUser, IconCode, } from '@douyinfe/semi-icons'; -import { getChannelModels } from '../../components/utils.js'; +import { getChannelModels } from '../../helpers'; import { useTranslation } from 'react-i18next'; const { Text, Title } = Typography; From 28d90f67546716dff02705e8bc5c83b516afcd58 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Wed, 4 Jun 2025 00:11:52 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(utils.js):?= =?UTF-8?q?=20refactor=20the=20`components/utils.js`=20to=20`helpers/api.j?= =?UTF-8?q?s`=20and=20related=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/utils.js | 76 ------------------------------------- 1 file changed, 76 deletions(-) delete mode 100644 web/src/components/utils.js diff --git a/web/src/components/utils.js b/web/src/components/utils.js deleted file mode 100644 index 93a5fb85..00000000 --- a/web/src/components/utils.js +++ /dev/null @@ -1,76 +0,0 @@ -import { API, showError } from '../helpers'; - -export async function getOAuthState() { - let path = '/api/oauth/state'; - let affCode = localStorage.getItem('aff'); - if (affCode && affCode.length > 0) { - path += `?aff=${affCode}`; - } - const res = await API.get(path); - const { success, message, data } = res.data; - if (success) { - return data; - } else { - showError(message); - return ''; - } -} - -export async function onOIDCClicked(auth_url, client_id, openInNewTab = false) { - const state = await getOAuthState(); - if (!state) return; - const redirect_uri = `${window.location.origin}/oauth/oidc`; - const response_type = 'code'; - const scope = 'openid profile email'; - const url = `${auth_url}?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope}&state=${state}`; - if (openInNewTab) { - window.open(url); - } else { - window.location.href = url; - } -} - -export async function onGitHubOAuthClicked(github_client_id) { - const state = await getOAuthState(); - if (!state) return; - window.open( - `https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`, - ); -} - -export async function onLinuxDOOAuthClicked(linuxdo_client_id) { - const state = await getOAuthState(); - if (!state) return; - window.open( - `https://connect.linux.do/oauth2/authorize?response_type=code&client_id=${linuxdo_client_id}&state=${state}`, - ); -} - -let channelModels = undefined; -export async function loadChannelModels() { - const res = await API.get('/api/models'); - const { success, data } = res.data; - if (!success) { - return; - } - channelModels = data; - localStorage.setItem('channel_models', JSON.stringify(data)); -} - -export function getChannelModels(type) { - if (channelModels !== undefined && type in channelModels) { - if (!channelModels[type]) { - return []; - } - return channelModels[type]; - } - let models = localStorage.getItem('channel_models'); - if (!models) { - return []; - } - channelModels = JSON.parse(models); - if (type in channelModels) { - return channelModels[type]; - } - return []; -} From 5a22f33bcfcc9085f45bcfb6e1dcfb8828b26a06 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Wed, 4 Jun 2025 00:14:15 +0800 Subject: [PATCH 06/10] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(components)?= =?UTF-8?q?:=20refactor=20the=20`components/utils.js`=20to=20`helpers/api.?= =?UTF-8?q?js`=20and=20related=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/LoginForm.js | 8 +++----- web/src/components/PersonalSetting.js | 10 ++++------ web/src/index.js | 4 ++-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/web/src/components/LoginForm.js b/web/src/components/LoginForm.js index 84b293b5..aa7d40da 100644 --- a/web/src/components/LoginForm.js +++ b/web/src/components/LoginForm.js @@ -9,13 +9,11 @@ import { showSuccess, updateAPI, getSystemName, - setUserData -} from '../helpers'; -import { + setUserData, onGitHubOAuthClicked, onOIDCClicked, - onLinuxDOOAuthClicked, -} from './utils'; + onLinuxDOOAuthClicked +} from '../helpers'; import Turnstile from 'react-turnstile'; import { Button, diff --git a/web/src/components/PersonalSetting.js b/web/src/components/PersonalSetting.js index 9290d3bd..7314308c 100644 --- a/web/src/components/PersonalSetting.js +++ b/web/src/components/PersonalSetting.js @@ -11,15 +11,13 @@ import { getQuotaPerUnit, renderQuota, renderQuotaWithPrompt, - stringToColor + stringToColor, + onGitHubOAuthClicked, + onOIDCClicked, + onLinuxDOOAuthClicked } from '../helpers'; import Turnstile from 'react-turnstile'; import { UserContext } from '../context/User'; -import { - onGitHubOAuthClicked, - onOIDCClicked, - onLinuxDOOAuthClicked, -} from './utils'; import { Avatar, Banner, diff --git a/web/src/index.js b/web/src/index.js index 1f180bbd..978f39d5 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -15,7 +15,7 @@ import './i18n/i18n.js'; const root = ReactDOM.createRoot(document.getElementById('root')); const { Sider, Content, Header, Footer } = Layout; root.render( - + // @@ -27,5 +27,5 @@ root.render( - , + // , ); From d27981bd34c90d74a5bc854f76af11c610f086b5 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Wed, 4 Jun 2025 00:21:03 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(auth):=20mo?= =?UTF-8?q?ve=20the=20auth=20component=20to=20the=20auth.js=20file=20in=20?= =?UTF-8?q?the=20helpers=20folder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/App.js | 6 ++---- web/src/components/AuthRedirect.js | 14 -------------- web/src/components/PrivateRoute.js | 12 ------------ web/src/helpers/auth.js | 25 ++++++++++++++++++++++++- 4 files changed, 26 insertions(+), 31 deletions(-) delete mode 100644 web/src/components/AuthRedirect.js delete mode 100644 web/src/components/PrivateRoute.js diff --git a/web/src/App.js b/web/src/App.js index 571d0745..e71b27cf 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -1,8 +1,8 @@ -import React, { lazy, Suspense, useContext, useEffect } from 'react'; +import React, { lazy, Suspense } from 'react'; import { Route, Routes, useLocation } from 'react-router-dom'; import Loading from './components/Loading'; import User from './pages/User'; -import { PrivateRoute } from './components/PrivateRoute'; +import { AuthRedirect, PrivateRoute } from './helpers'; import RegisterForm from './components/RegisterForm'; import LoginForm from './components/LoginForm'; import NotFound from './pages/NotFound'; @@ -18,7 +18,6 @@ import TopUp from './pages/TopUp'; import Log from './pages/Log'; import Chat from './pages/Chat'; import Chat2Link from './pages/Chat2Link'; -import { Layout } from '@douyinfe/semi-ui'; import Midjourney from './pages/Midjourney'; import Pricing from './pages/Pricing/index.js'; import Task from './pages/Task/index.js'; @@ -27,7 +26,6 @@ import OAuth2Callback from './components/OAuth2Callback.js'; import PersonalSetting from './components/PersonalSetting.js'; import Setup from './pages/Setup/index.js'; import SetupCheck from './components/SetupCheck'; -import AuthRedirect from './components/AuthRedirect'; const Home = lazy(() => import('./pages/Home')); const Detail = lazy(() => import('./pages/Detail')); diff --git a/web/src/components/AuthRedirect.js b/web/src/components/AuthRedirect.js deleted file mode 100644 index d22c675d..00000000 --- a/web/src/components/AuthRedirect.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { Navigate } from 'react-router-dom'; - -const AuthRedirect = ({ children }) => { - const user = localStorage.getItem('user'); - - if (user) { - return ; - } - - return children; -}; - -export default AuthRedirect; \ No newline at end of file diff --git a/web/src/components/PrivateRoute.js b/web/src/components/PrivateRoute.js deleted file mode 100644 index ca938c41..00000000 --- a/web/src/components/PrivateRoute.js +++ /dev/null @@ -1,12 +0,0 @@ -import { Navigate } from 'react-router-dom'; - -import { history } from '../helpers'; - -function PrivateRoute({ children }) { - if (!localStorage.getItem('user')) { - return ; - } - return children; -} - -export { PrivateRoute }; diff --git a/web/src/helpers/auth.js b/web/src/helpers/auth.js index 75b4b0df..cb694ccf 100644 --- a/web/src/helpers/auth.js +++ b/web/src/helpers/auth.js @@ -1,3 +1,7 @@ +import React from 'react'; +import { Navigate } from 'react-router-dom'; +import { history } from './history'; + export function authHeader() { // return authorization header with jwt token let user = JSON.parse(localStorage.getItem('user')); @@ -7,4 +11,23 @@ export function authHeader() { } else { return {}; } -} \ No newline at end of file +} + +export const AuthRedirect = ({ children }) => { + const user = localStorage.getItem('user'); + + if (user) { + return ; + } + + return children; +}; + +function PrivateRoute({ children }) { + if (!localStorage.getItem('user')) { + return ; + } + return children; +} + +export { PrivateRoute }; From 3f45153e7526d8e56f4b2d15315fc0bc19be9ab6 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Wed, 4 Jun 2025 00:42:06 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(components)?= =?UTF-8?q?:=20refactor=20the=20components=20folder=20structure=20and=20re?= =?UTF-8?q?lated=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/{src/images => public}/example.png | Bin web/src/App.js | 14 +++++----- web/src/components/{ => auth}/LoginForm.js | 12 ++++---- .../components/{ => auth}/OAuth2Callback.js | 4 +-- .../{ => auth}/PasswordResetConfirm.js | 4 +-- .../{ => auth}/PasswordResetForm.js | 4 +-- web/src/components/{ => auth}/RegisterForm.js | 14 +++++----- web/src/components/{ => common}/Loading.js | 0 web/src/components/{ => layout}/Footer.js | 4 +-- web/src/components/{ => layout}/HeaderBar.js | 12 ++++---- .../components/{ => layout}/NoticeModal.js | 2 +- web/src/components/{ => layout}/PageLayout.js | 10 +++---- web/src/components/{ => layout}/SiderBar.js | 19 ++++--------- .../components/{ => settings}/ModelSetting.js | 8 +++--- .../{ => settings}/OperationSetting.js | 26 +++++++++--------- .../components/{ => settings}/OtherSetting.js | 4 +-- .../{ => settings}/PersonalSetting.js | 4 +-- .../{ => settings}/RateLimitSetting.js | 6 ++-- .../{ => settings}/SystemSetting.js | 2 +- .../components/{ => table}/ChannelsTable.js | 10 +++---- web/src/components/{ => table}/LogsTable.js | 4 +-- web/src/components/{ => table}/MjLogsTable.js | 4 +-- .../components/{ => table}/ModelPricing.js | 6 ++-- .../{ => table}/RedemptionsTable.js | 6 ++-- .../components/{ => table}/TaskLogsTable.js | 4 +-- web/src/components/{ => table}/TokensTable.js | 8 +++--- web/src/components/{ => table}/UsersTable.js | 8 +++--- web/src/index.js | 3 +- web/src/pages/Channel/index.js | 2 +- web/src/pages/Home/index.js | 4 +-- web/src/pages/Log/index.js | 2 +- web/src/pages/Midjourney/index.js | 2 +- web/src/pages/Pricing/index.js | 2 +- web/src/pages/Redemption/index.js | 2 +- web/src/pages/Setting/index.js | 12 ++++---- web/src/pages/Task/index.js | 2 +- web/src/pages/Token/index.js | 2 +- web/src/pages/User/index.js | 2 +- 38 files changed, 113 insertions(+), 121 deletions(-) rename web/{src/images => public}/example.png (100%) rename web/src/components/{ => auth}/LoginForm.js (98%) rename web/src/components/{ => auth}/OAuth2Callback.js (96%) rename web/src/components/{ => auth}/PasswordResetConfirm.js (98%) rename web/src/components/{ => auth}/PasswordResetForm.js (98%) rename web/src/components/{ => auth}/RegisterForm.js (98%) rename web/src/components/{ => common}/Loading.js (100%) rename web/src/components/{ => layout}/Footer.js (97%) rename web/src/components/{ => layout}/HeaderBar.js (98%) rename web/src/components/{ => layout}/NoticeModal.js (98%) rename web/src/components/{ => layout}/PageLayout.js (94%) rename web/src/components/{ => layout}/SiderBar.js (96%) rename web/src/components/{ => settings}/ModelSetting.js (89%) rename web/src/components/{ => settings}/OperationSetting.js (82%) rename web/src/components/{ => settings}/OtherSetting.js (99%) rename web/src/components/{ => settings}/PersonalSetting.js (99%) rename web/src/components/{ => settings}/RateLimitSetting.js (87%) rename web/src/components/{ => settings}/SystemSetting.js (99%) rename web/src/components/{ => table}/ChannelsTable.js (99%) rename web/src/components/{ => table}/LogsTable.js (99%) rename web/src/components/{ => table}/MjLogsTable.js (99%) rename web/src/components/{ => table}/ModelPricing.js (99%) rename web/src/components/{ => table}/RedemptionsTable.js (99%) rename web/src/components/{ => table}/TaskLogsTable.js (99%) rename web/src/components/{ => table}/TokensTable.js (99%) rename web/src/components/{ => table}/UsersTable.js (98%) diff --git a/web/src/images/example.png b/web/public/example.png similarity index 100% rename from web/src/images/example.png rename to web/public/example.png diff --git a/web/src/App.js b/web/src/App.js index e71b27cf..ff0abbbb 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -1,15 +1,15 @@ import React, { lazy, Suspense } from 'react'; import { Route, Routes, useLocation } from 'react-router-dom'; -import Loading from './components/Loading'; +import Loading from './components/common/Loading.js'; import User from './pages/User'; import { AuthRedirect, PrivateRoute } from './helpers'; -import RegisterForm from './components/RegisterForm'; -import LoginForm from './components/LoginForm'; +import RegisterForm from './components/auth/RegisterForm.js'; +import LoginForm from './components/auth/LoginForm.js'; import NotFound from './pages/NotFound'; import Setting from './pages/Setting'; import EditUser from './pages/User/EditUser'; -import PasswordResetForm from './components/PasswordResetForm'; -import PasswordResetConfirm from './components/PasswordResetConfirm'; +import PasswordResetForm from './components/auth/PasswordResetForm.js'; +import PasswordResetConfirm from './components/auth/PasswordResetConfirm.js'; import Channel from './pages/Channel'; import Token from './pages/Token'; import EditChannel from './pages/Channel/EditChannel'; @@ -22,8 +22,8 @@ import Midjourney from './pages/Midjourney'; import Pricing from './pages/Pricing/index.js'; import Task from './pages/Task/index.js'; import Playground from './pages/Playground/index.js'; -import OAuth2Callback from './components/OAuth2Callback.js'; -import PersonalSetting from './components/PersonalSetting.js'; +import OAuth2Callback from './components/auth/OAuth2Callback.js'; +import PersonalSetting from './components/settings/PersonalSetting.js'; import Setup from './pages/Setup/index.js'; import SetupCheck from './components/SetupCheck'; diff --git a/web/src/components/LoginForm.js b/web/src/components/auth/LoginForm.js similarity index 98% rename from web/src/components/LoginForm.js rename to web/src/components/auth/LoginForm.js index aa7d40da..26ec53a4 100644 --- a/web/src/components/LoginForm.js +++ b/web/src/components/auth/LoginForm.js @@ -1,6 +1,6 @@ import React, { useContext, useEffect, useState } from 'react'; import { Link, useNavigate, useSearchParams } from 'react-router-dom'; -import { UserContext } from '../context/User'; +import { UserContext } from '../../context/User/index.js'; import { API, getLogo, @@ -13,7 +13,7 @@ import { onGitHubOAuthClicked, onOIDCClicked, onLinuxDOOAuthClicked -} from '../helpers'; +} from '../../helpers/index.js'; import Turnstile from 'react-turnstile'; import { Button, @@ -28,11 +28,11 @@ import Text from '@douyinfe/semi-ui/lib/es/typography/text'; import TelegramLoginButton from 'react-telegram-login'; import { IconGithubLogo, IconMail, IconLock } from '@douyinfe/semi-icons'; -import OIDCIcon from './common/logo/OIDCIcon.js'; -import WeChatIcon from './common/logo/WeChatIcon.js'; -import LinuxDoIcon from './common/logo/LinuxDoIcon.js'; +import OIDCIcon from '../common/logo/OIDCIcon.js'; +import WeChatIcon from '../common/logo/WeChatIcon.js'; +import LinuxDoIcon from '../common/logo/LinuxDoIcon.js'; import { useTranslation } from 'react-i18next'; -import Background from '../images/example.png'; +import Background from '/example.png'; const LoginForm = () => { const [inputs, setInputs] = useState({ diff --git a/web/src/components/OAuth2Callback.js b/web/src/components/auth/OAuth2Callback.js similarity index 96% rename from web/src/components/OAuth2Callback.js rename to web/src/components/auth/OAuth2Callback.js index 8a36b443..83f9db46 100644 --- a/web/src/components/OAuth2Callback.js +++ b/web/src/components/auth/OAuth2Callback.js @@ -1,8 +1,8 @@ import React, { useContext, useEffect, useState } from 'react'; import { Spin, Typography, Space } from '@douyinfe/semi-ui'; import { useNavigate, useSearchParams } from 'react-router-dom'; -import { API, showError, showSuccess, updateAPI, setUserData } from '../helpers'; -import { UserContext } from '../context/User'; +import { API, showError, showSuccess, updateAPI, setUserData } from '../../helpers'; +import { UserContext } from '../../context/User'; const OAuth2Callback = (props) => { const [searchParams, setSearchParams] = useSearchParams(); diff --git a/web/src/components/PasswordResetConfirm.js b/web/src/components/auth/PasswordResetConfirm.js similarity index 98% rename from web/src/components/PasswordResetConfirm.js rename to web/src/components/auth/PasswordResetConfirm.js index 7ed089bf..dc69ccdc 100644 --- a/web/src/components/PasswordResetConfirm.js +++ b/web/src/components/auth/PasswordResetConfirm.js @@ -1,10 +1,10 @@ import React, { useEffect, useState } from 'react'; -import { API, copy, showError, showNotice, getLogo, getSystemName } from '../helpers'; +import { API, copy, showError, showNotice, getLogo, getSystemName } from '../../helpers'; import { useSearchParams, Link } from 'react-router-dom'; import { Button, Card, Form, Typography } from '@douyinfe/semi-ui'; import { IconMail, IconLock } from '@douyinfe/semi-icons'; import { useTranslation } from 'react-i18next'; -import Background from '../images/example.png'; +import Background from '/example.png'; const { Text, Title } = Typography; diff --git a/web/src/components/PasswordResetForm.js b/web/src/components/auth/PasswordResetForm.js similarity index 98% rename from web/src/components/PasswordResetForm.js rename to web/src/components/auth/PasswordResetForm.js index f256f290..6abdfaa5 100644 --- a/web/src/components/PasswordResetForm.js +++ b/web/src/components/auth/PasswordResetForm.js @@ -1,11 +1,11 @@ import React, { useEffect, useState } from 'react'; -import { API, getLogo, showError, showInfo, showSuccess, getSystemName } from '../helpers'; +import { API, getLogo, showError, showInfo, showSuccess, getSystemName } from '../../helpers'; import Turnstile from 'react-turnstile'; import { Button, Card, Form, Typography } from '@douyinfe/semi-ui'; import { IconMail } from '@douyinfe/semi-icons'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import Background from '../images/example.png'; +import Background from '/example.png'; const { Text, Title } = Typography; diff --git a/web/src/components/RegisterForm.js b/web/src/components/auth/RegisterForm.js similarity index 98% rename from web/src/components/RegisterForm.js rename to web/src/components/auth/RegisterForm.js index 23ff94df..e87e586c 100644 --- a/web/src/components/RegisterForm.js +++ b/web/src/components/auth/RegisterForm.js @@ -9,7 +9,7 @@ import { updateAPI, getSystemName, setUserData -} from '../helpers'; +} from '../../helpers/index.js'; import Turnstile from 'react-turnstile'; import { Button, @@ -26,14 +26,14 @@ import { onGitHubOAuthClicked, onLinuxDOOAuthClicked, onOIDCClicked, -} from '../helpers'; -import OIDCIcon from './common/logo/OIDCIcon.js'; -import LinuxDoIcon from './common/logo/LinuxDoIcon.js'; -import WeChatIcon from './common/logo/WeChatIcon.js'; +} from '../../helpers/index.js'; +import OIDCIcon from '../common/logo/OIDCIcon.js'; +import LinuxDoIcon from '../common/logo/LinuxDoIcon.js'; +import WeChatIcon from '../common/logo/WeChatIcon.js'; import TelegramLoginButton from 'react-telegram-login/src'; -import { UserContext } from '../context/User/index.js'; +import { UserContext } from '../../context/User/index.js'; import { useTranslation } from 'react-i18next'; -import Background from '../images/example.png'; +import Background from '/example.png'; const RegisterForm = () => { const { t } = useTranslation(); diff --git a/web/src/components/Loading.js b/web/src/components/common/Loading.js similarity index 100% rename from web/src/components/Loading.js rename to web/src/components/common/Loading.js diff --git a/web/src/components/Footer.js b/web/src/components/layout/Footer.js similarity index 97% rename from web/src/components/Footer.js rename to web/src/components/layout/Footer.js index fbc33158..d549a4e0 100644 --- a/web/src/components/Footer.js +++ b/web/src/components/layout/Footer.js @@ -1,8 +1,8 @@ import React, { useEffect, useState, useMemo, useContext } from 'react'; import { useTranslation } from 'react-i18next'; import { Typography } from '@douyinfe/semi-ui'; -import { getFooterHTML, getLogo, getSystemName } from '../helpers'; -import { StatusContext } from '../context/Status'; +import { getFooterHTML, getLogo, getSystemName } from '../../helpers'; +import { StatusContext } from '../../context/Status'; const FooterBar = () => { const { t } = useTranslation(); diff --git a/web/src/components/HeaderBar.js b/web/src/components/layout/HeaderBar.js similarity index 98% rename from web/src/components/HeaderBar.js rename to web/src/components/layout/HeaderBar.js index d4afac2c..c78ba299 100644 --- a/web/src/components/HeaderBar.js +++ b/web/src/components/layout/HeaderBar.js @@ -1,12 +1,12 @@ import React, { useContext, useEffect, useState } from 'react'; import { Link, useNavigate, useLocation } from 'react-router-dom'; -import { UserContext } from '../context/User'; -import { useSetTheme, useTheme } from '../context/Theme'; +import { UserContext } from '../../context/User/index.js'; +import { useSetTheme, useTheme } from '../../context/Theme/index.js'; import { useTranslation } from 'react-i18next'; -import { API, getLogo, getSystemName, showSuccess, stringToColor } from '../helpers'; +import { API, getLogo, getSystemName, showSuccess, stringToColor } from '../../helpers/index.js'; import fireworks from 'react-fireworks'; import { CN, GB } from 'country-flag-icons/react/3x2'; -import NoticeModal from './NoticeModal'; +import NoticeModal from './NoticeModal.js'; import { IconClose, @@ -29,8 +29,8 @@ import { Typography, Skeleton, } from '@douyinfe/semi-ui'; -import { StatusContext } from '../context/Status/index.js'; -import { useStyle, styleActions } from '../context/Style/index.js'; +import { StatusContext } from '../../context/Status/index.js'; +import { useStyle, styleActions } from '../../context/Style/index.js'; const HeaderBar = () => { const { t, i18n } = useTranslation(); diff --git a/web/src/components/NoticeModal.js b/web/src/components/layout/NoticeModal.js similarity index 98% rename from web/src/components/NoticeModal.js rename to web/src/components/layout/NoticeModal.js index df45a581..626d8d17 100644 --- a/web/src/components/NoticeModal.js +++ b/web/src/components/layout/NoticeModal.js @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Button, Modal, Empty } from '@douyinfe/semi-ui'; import { useTranslation } from 'react-i18next'; -import { API, showError } from '../helpers'; +import { API, showError } from '../../helpers'; import { marked } from 'marked'; import { IllustrationNoContent, IllustrationNoContentDark } from '@douyinfe/semi-illustrations'; diff --git a/web/src/components/PageLayout.js b/web/src/components/layout/PageLayout.js similarity index 94% rename from web/src/components/PageLayout.js rename to web/src/components/layout/PageLayout.js index 76d8e6b7..6b0c4c6d 100644 --- a/web/src/components/PageLayout.js +++ b/web/src/components/layout/PageLayout.js @@ -1,15 +1,15 @@ import HeaderBar from './HeaderBar.js'; import { Layout } from '@douyinfe/semi-ui'; import SiderBar from './SiderBar.js'; -import App from '../App.js'; +import App from '../../App.js'; import FooterBar from './Footer.js'; import { ToastContainer } from 'react-toastify'; import React, { useContext, useEffect } from 'react'; -import { useStyle } from '../context/Style/index.js'; +import { useStyle } from '../../context/Style/index.js'; import { useTranslation } from 'react-i18next'; -import { API, getLogo, getSystemName, showError, setStatusData } from '../helpers'; -import { UserContext } from '../context/User/index.js'; -import { StatusContext } from '../context/Status/index.js'; +import { API, getLogo, getSystemName, showError, setStatusData } from '../../helpers/index.js'; +import { UserContext } from '../../context/User/index.js'; +import { StatusContext } from '../../context/Status/index.js'; import { useLocation } from 'react-router-dom'; const { Sider, Content, Header, Footer } = Layout; diff --git a/web/src/components/SiderBar.js b/web/src/components/layout/SiderBar.js similarity index 96% rename from web/src/components/SiderBar.js rename to web/src/components/layout/SiderBar.js index 97a4bb03..30117eee 100644 --- a/web/src/components/SiderBar.js +++ b/web/src/components/layout/SiderBar.js @@ -1,14 +1,12 @@ import React, { useContext, useEffect, useMemo, useState } from 'react'; -import { Link, useNavigate, useLocation } from 'react-router-dom'; -import { UserContext } from '../context/User'; -import { StatusContext } from '../context/Status'; +import { Link, useLocation } from 'react-router-dom'; +import { StatusContext } from '../../context/Status/index.js'; import { useTranslation } from 'react-i18next'; import { isAdmin, showError -} from '../helpers'; -import '../index.css'; +} from '../../helpers/index.js'; import { IconCalendarClock, @@ -17,26 +15,19 @@ import { IconTerminal, IconCreditCard, IconGift, - IconHelpCircle, IconHistogram, - IconHome, IconImage, IconKey, IconLayers, - IconPriceTag, IconSetting, IconUser, } from '@douyinfe/semi-icons'; import { - Avatar, - Dropdown, - Layout, Nav, - Switch, Divider, } from '@douyinfe/semi-ui'; -import { useSetTheme, useTheme } from '../context/Theme/index.js'; -import { useStyle, styleActions } from '../context/Style/index.js'; +import { useSetTheme, useTheme } from '../../context/Theme/index.js'; +import { useStyle, styleActions } from '../../context/Style/index.js'; import Text from '@douyinfe/semi-ui/lib/es/typography/text'; // 自定义侧边栏按钮样式 diff --git a/web/src/components/ModelSetting.js b/web/src/components/settings/ModelSetting.js similarity index 89% rename from web/src/components/ModelSetting.js rename to web/src/components/settings/ModelSetting.js index 9c60a390..1d583634 100644 --- a/web/src/components/ModelSetting.js +++ b/web/src/components/settings/ModelSetting.js @@ -1,11 +1,11 @@ import React, { useEffect, useState } from 'react'; import { Card, Spin, Tabs } from '@douyinfe/semi-ui'; -import { API, showError, showSuccess } from '../helpers'; +import { API, showError, showSuccess } from '../../helpers'; import { useTranslation } from 'react-i18next'; -import SettingGeminiModel from '../pages/Setting/Model/SettingGeminiModel.js'; -import SettingClaudeModel from '../pages/Setting/Model/SettingClaudeModel.js'; -import SettingGlobalModel from '../pages/Setting/Model/SettingGlobalModel.js'; +import SettingGeminiModel from '../../pages/Setting/Model/SettingGeminiModel.js'; +import SettingClaudeModel from '../../pages/Setting/Model/SettingClaudeModel.js'; +import SettingGlobalModel from '../../pages/Setting/Model/SettingGlobalModel.js'; const ModelSetting = () => { const { t } = useTranslation(); diff --git a/web/src/components/OperationSetting.js b/web/src/components/settings/OperationSetting.js similarity index 82% rename from web/src/components/OperationSetting.js rename to web/src/components/settings/OperationSetting.js index 28fb3a8f..2dc0b88e 100644 --- a/web/src/components/OperationSetting.js +++ b/web/src/components/settings/OperationSetting.js @@ -1,20 +1,20 @@ import React, { useEffect, useState } from 'react'; import { Card, Spin, Tabs } from '@douyinfe/semi-ui'; -import SettingsGeneral from '../pages/Setting/Operation/SettingsGeneral.js'; -import SettingsDrawing from '../pages/Setting/Operation/SettingsDrawing.js'; -import SettingsSensitiveWords from '../pages/Setting/Operation/SettingsSensitiveWords.js'; -import SettingsLog from '../pages/Setting/Operation/SettingsLog.js'; -import SettingsDataDashboard from '../pages/Setting/Operation/SettingsDataDashboard.js'; -import SettingsMonitoring from '../pages/Setting/Operation/SettingsMonitoring.js'; -import SettingsCreditLimit from '../pages/Setting/Operation/SettingsCreditLimit.js'; -import ModelSettingsVisualEditor from '../pages/Setting/Operation/ModelSettingsVisualEditor.js'; -import GroupRatioSettings from '../pages/Setting/Operation/GroupRatioSettings.js'; -import ModelRatioSettings from '../pages/Setting/Operation/ModelRatioSettings.js'; +import SettingsGeneral from '../../pages/Setting/Operation/SettingsGeneral.js'; +import SettingsDrawing from '../../pages/Setting/Operation/SettingsDrawing.js'; +import SettingsSensitiveWords from '../../pages/Setting/Operation/SettingsSensitiveWords.js'; +import SettingsLog from '../../pages/Setting/Operation/SettingsLog.js'; +import SettingsDataDashboard from '../../pages/Setting/Operation/SettingsDataDashboard.js'; +import SettingsMonitoring from '../../pages/Setting/Operation/SettingsMonitoring.js'; +import SettingsCreditLimit from '../../pages/Setting/Operation/SettingsCreditLimit.js'; +import ModelSettingsVisualEditor from '../../pages/Setting/Operation/ModelSettingsVisualEditor.js'; +import GroupRatioSettings from '../../pages/Setting/Operation/GroupRatioSettings.js'; +import ModelRatioSettings from '../../pages/Setting/Operation/ModelRatioSettings.js'; -import { API, showError, showSuccess } from '../helpers'; -import SettingsChats from '../pages/Setting/Operation/SettingsChats.js'; +import { API, showError, showSuccess } from '../../helpers'; +import SettingsChats from '../../pages/Setting/Operation/SettingsChats.js'; import { useTranslation } from 'react-i18next'; -import ModelRatioNotSetEditor from '../pages/Setting/Operation/ModelRationNotSetEditor.js'; +import ModelRatioNotSetEditor from '../../pages/Setting/Operation/ModelRationNotSetEditor.js'; const OperationSetting = () => { const { t } = useTranslation(); diff --git a/web/src/components/OtherSetting.js b/web/src/components/settings/OtherSetting.js similarity index 99% rename from web/src/components/OtherSetting.js rename to web/src/components/settings/OtherSetting.js index 570a86f8..a054e0da 100644 --- a/web/src/components/OtherSetting.js +++ b/web/src/components/settings/OtherSetting.js @@ -9,10 +9,10 @@ import { Space, Card, } from '@douyinfe/semi-ui'; -import { API, showError, showSuccess, timestamp2string } from '../helpers'; +import { API, showError, showSuccess, timestamp2string } from '../../helpers'; import { marked } from 'marked'; import { useTranslation } from 'react-i18next'; -import { StatusContext } from '../context/Status/index.js'; +import { StatusContext } from '../../context/Status/index.js'; import Text from '@douyinfe/semi-ui/lib/es/typography/text'; const OtherSetting = () => { diff --git a/web/src/components/PersonalSetting.js b/web/src/components/settings/PersonalSetting.js similarity index 99% rename from web/src/components/PersonalSetting.js rename to web/src/components/settings/PersonalSetting.js index 7314308c..d0717854 100644 --- a/web/src/components/PersonalSetting.js +++ b/web/src/components/settings/PersonalSetting.js @@ -15,9 +15,9 @@ import { onGitHubOAuthClicked, onOIDCClicked, onLinuxDOOAuthClicked -} from '../helpers'; +} from '../../helpers'; import Turnstile from 'react-turnstile'; -import { UserContext } from '../context/User'; +import { UserContext } from '../../context/User'; import { Avatar, Banner, diff --git a/web/src/components/RateLimitSetting.js b/web/src/components/settings/RateLimitSetting.js similarity index 87% rename from web/src/components/RateLimitSetting.js rename to web/src/components/settings/RateLimitSetting.js index 5f0200e1..4d061363 100644 --- a/web/src/components/RateLimitSetting.js +++ b/web/src/components/settings/RateLimitSetting.js @@ -1,10 +1,10 @@ import React, { useEffect, useState } from 'react'; import { Card, Spin, Tabs } from '@douyinfe/semi-ui'; -import { API, showError, showSuccess } from '../helpers'; -import SettingsChats from '../pages/Setting/Operation/SettingsChats.js'; +import { API, showError, showSuccess } from '../../helpers/index.js'; +import SettingsChats from '../../pages/Setting/Operation/SettingsChats.js'; import { useTranslation } from 'react-i18next'; -import RequestRateLimit from '../pages/Setting/RateLimit/SettingsRequestRateLimit.js'; +import RequestRateLimit from '../../pages/Setting/RateLimit/SettingsRequestRateLimit.js'; const RateLimitSetting = () => { const { t } = useTranslation(); diff --git a/web/src/components/SystemSetting.js b/web/src/components/settings/SystemSetting.js similarity index 99% rename from web/src/components/SystemSetting.js rename to web/src/components/settings/SystemSetting.js index fa97df48..8219159b 100644 --- a/web/src/components/SystemSetting.js +++ b/web/src/components/settings/SystemSetting.js @@ -18,7 +18,7 @@ import { showError, showSuccess, verifyJSON -} from '../helpers'; +} from '../../helpers'; import axios from 'axios'; const SystemSetting = () => { diff --git a/web/src/components/ChannelsTable.js b/web/src/components/table/ChannelsTable.js similarity index 99% rename from web/src/components/ChannelsTable.js rename to web/src/components/table/ChannelsTable.js index 33c8c398..9081aa63 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/table/ChannelsTable.js @@ -8,9 +8,9 @@ import { renderGroup, renderNumberWithPoint, renderQuota -} from '../helpers'; +} from '../../helpers/index.js'; -import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../constants'; +import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../../constants/index.js'; import { Button, Divider, @@ -29,7 +29,7 @@ import { Card, Select } from '@douyinfe/semi-ui'; -import EditChannel from '../pages/Channel/EditChannel'; +import EditChannel from '../../pages/Channel/EditChannel.js'; import { IconList, IconTreeTriangleDown, @@ -46,8 +46,8 @@ import { IconCopy, IconSmallTriangleRight } from '@douyinfe/semi-icons'; -import { loadChannelModels } from '../helpers'; -import EditTagModal from '../pages/Channel/EditTagModal.js'; +import { loadChannelModels } from '../../helpers/index.js'; +import EditTagModal from '../../pages/Channel/EditTagModal.js'; import { useTranslation } from 'react-i18next'; const ChannelsTable = () => { diff --git a/web/src/components/LogsTable.js b/web/src/components/table/LogsTable.js similarity index 99% rename from web/src/components/LogsTable.js rename to web/src/components/table/LogsTable.js index 5f2970cb..88992452 100644 --- a/web/src/components/LogsTable.js +++ b/web/src/components/table/LogsTable.js @@ -20,7 +20,7 @@ import { renderQuota, stringToColor, getLogOther -} from '../helpers'; +} from '../../helpers'; import { Avatar, @@ -41,7 +41,7 @@ import { Input, DatePicker, } from '@douyinfe/semi-ui'; -import { ITEMS_PER_PAGE } from '../constants'; +import { ITEMS_PER_PAGE } from '../../constants'; import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph'; import { IconRefresh, diff --git a/web/src/components/MjLogsTable.js b/web/src/components/table/MjLogsTable.js similarity index 99% rename from web/src/components/MjLogsTable.js rename to web/src/components/table/MjLogsTable.js index 08c47e7d..077512dc 100644 --- a/web/src/components/MjLogsTable.js +++ b/web/src/components/table/MjLogsTable.js @@ -7,7 +7,7 @@ import { showError, showSuccess, timestamp2string, -} from '../helpers'; +} from '../../helpers'; import { Button, @@ -25,7 +25,7 @@ import { Tag, Typography, } from '@douyinfe/semi-ui'; -import { ITEMS_PER_PAGE } from '../constants'; +import { ITEMS_PER_PAGE } from '../../constants'; import { IconEyeOpened, IconSearch, diff --git a/web/src/components/ModelPricing.js b/web/src/components/table/ModelPricing.js similarity index 99% rename from web/src/components/ModelPricing.js rename to web/src/components/table/ModelPricing.js index 39daa168..a9f06713 100644 --- a/web/src/components/ModelPricing.js +++ b/web/src/components/table/ModelPricing.js @@ -1,5 +1,5 @@ import React, { useContext, useEffect, useRef, useMemo, useState } from 'react'; -import { API, copy, showError, showInfo, showSuccess } from '../helpers'; +import { API, copy, showError, showInfo, showSuccess } from '../../helpers/index.js'; import { useTranslation } from 'react-i18next'; import { @@ -26,9 +26,9 @@ import { IconInfoCircle, IconCrown, } from '@douyinfe/semi-icons'; -import { UserContext } from '../context/User/index.js'; +import { UserContext } from '../../context/User/index.js'; import { AlertCircle } from 'lucide-react'; -import { MODEL_CATEGORIES } from '../constants'; +import { MODEL_CATEGORIES } from '../../constants/index.js'; const ModelPricing = () => { const { t } = useTranslation(); diff --git a/web/src/components/RedemptionsTable.js b/web/src/components/table/RedemptionsTable.js similarity index 99% rename from web/src/components/RedemptionsTable.js rename to web/src/components/table/RedemptionsTable.js index 637c7bdf..23813bbb 100644 --- a/web/src/components/RedemptionsTable.js +++ b/web/src/components/table/RedemptionsTable.js @@ -6,9 +6,9 @@ import { showSuccess, timestamp2string, renderQuota -} from '../helpers'; +} from '../../helpers'; -import { ITEMS_PER_PAGE } from '../constants'; +import { ITEMS_PER_PAGE } from '../../constants'; import { Button, Card, @@ -33,7 +33,7 @@ import { IconPlay, IconMore, } from '@douyinfe/semi-icons'; -import EditRedemption from '../pages/Redemption/EditRedemption'; +import EditRedemption from '../../pages/Redemption/EditRedemption'; import { useTranslation } from 'react-i18next'; const { Text } = Typography; diff --git a/web/src/components/TaskLogsTable.js b/web/src/components/table/TaskLogsTable.js similarity index 99% rename from web/src/components/TaskLogsTable.js rename to web/src/components/table/TaskLogsTable.js index c4a8414b..791ae9ce 100644 --- a/web/src/components/TaskLogsTable.js +++ b/web/src/components/table/TaskLogsTable.js @@ -7,7 +7,7 @@ import { showError, showSuccess, timestamp2string, -} from '../helpers'; +} from '../../helpers'; import { Button, @@ -24,7 +24,7 @@ import { Tag, Typography, } from '@douyinfe/semi-ui'; -import { ITEMS_PER_PAGE } from '../constants'; +import { ITEMS_PER_PAGE } from '../../constants'; import { IconEyeOpened, IconSearch, diff --git a/web/src/components/TokensTable.js b/web/src/components/table/TokensTable.js similarity index 99% rename from web/src/components/TokensTable.js rename to web/src/components/table/TokensTable.js index a3f21f1d..d4637953 100644 --- a/web/src/components/TokensTable.js +++ b/web/src/components/table/TokensTable.js @@ -8,9 +8,9 @@ import { timestamp2string, renderGroup, renderQuota -} from '../helpers'; +} from '../../helpers'; -import { ITEMS_PER_PAGE } from '../constants'; +import { ITEMS_PER_PAGE } from '../../constants'; import { Button, Card, @@ -40,9 +40,9 @@ import { IconHistogram, IconRotate, } from '@douyinfe/semi-icons'; -import EditToken from '../pages/Token/EditToken'; +import EditToken from '../../pages/Token/EditToken'; import { useTranslation } from 'react-i18next'; -import { UserContext } from '../context/User'; +import { UserContext } from '../../context/User'; function renderTimestamp(timestamp) { return <>{timestamp2string(timestamp)}; diff --git a/web/src/components/UsersTable.js b/web/src/components/table/UsersTable.js similarity index 98% rename from web/src/components/UsersTable.js rename to web/src/components/table/UsersTable.js index d4bc74a3..b5e5f7da 100644 --- a/web/src/components/UsersTable.js +++ b/web/src/components/table/UsersTable.js @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { API, showError, showSuccess, renderGroup, renderNumber, renderQuota } from '../helpers'; +import { API, showError, showSuccess, renderGroup, renderNumber, renderQuota } from '../../helpers'; import { Button, Card, @@ -25,9 +25,9 @@ import { IconArrowUp, IconArrowDown, } from '@douyinfe/semi-icons'; -import { ITEMS_PER_PAGE } from '../constants'; -import AddUser from '../pages/User/AddUser'; -import EditUser from '../pages/User/EditUser'; +import { ITEMS_PER_PAGE } from '../../constants'; +import AddUser from '../../pages/User/AddUser'; +import EditUser from '../../pages/User/EditUser'; import { useTranslation } from 'react-i18next'; const { Text } = Typography; diff --git a/web/src/index.js b/web/src/index.js index 978f39d5..dc0c438d 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -7,8 +7,9 @@ import { StatusProvider } from './context/Status'; import { Layout } from '@douyinfe/semi-ui'; import { ThemeProvider } from './context/Theme'; import { StyleProvider } from './context/Style/index.js'; -import PageLayout from './components/PageLayout.js'; +import PageLayout from './components/layout/PageLayout.js'; import './i18n/i18n.js'; +import './index.css'; // initialization diff --git a/web/src/pages/Channel/index.js b/web/src/pages/Channel/index.js index 523ea27c..dc9f8c48 100644 --- a/web/src/pages/Channel/index.js +++ b/web/src/pages/Channel/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import ChannelsTable from '../../components/ChannelsTable'; +import ChannelsTable from '../../components/table/ChannelsTable'; const File = () => { return ( diff --git a/web/src/pages/Home/index.js b/web/src/pages/Home/index.js index f676fe69..ba0c738c 100644 --- a/web/src/pages/Home/index.js +++ b/web/src/pages/Home/index.js @@ -5,9 +5,9 @@ import { StatusContext } from '../../context/Status'; import { marked } from 'marked'; import { useTranslation } from 'react-i18next'; import { IconGithubLogo } from '@douyinfe/semi-icons'; -import exampleImage from '../../images/example.png'; +import exampleImage from '..//example.png'; import { Link } from 'react-router-dom'; -import NoticeModal from '../../components/NoticeModal'; +import NoticeModal from '../../components/layout/NoticeModal'; import { Moonshot, OpenAI, XAI, Zhipu, Volcengine, Cohere, Claude, Gemini, Suno, Minimax, Wenxin, Spark, Qingyan, DeepSeek, Qwen, Midjourney, Grok, AzureAI, Hunyuan, Xinference } from '@lobehub/icons'; const { Text } = Typography; diff --git a/web/src/pages/Log/index.js b/web/src/pages/Log/index.js index 4ee109ca..4b6f8ba3 100644 --- a/web/src/pages/Log/index.js +++ b/web/src/pages/Log/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import LogsTable from '../../components/LogsTable'; +import LogsTable from '../../components/table/LogsTable'; const Token = () => ( <> diff --git a/web/src/pages/Midjourney/index.js b/web/src/pages/Midjourney/index.js index ed22ecd0..7c605bd4 100644 --- a/web/src/pages/Midjourney/index.js +++ b/web/src/pages/Midjourney/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import MjLogsTable from '../../components/MjLogsTable'; +import MjLogsTable from '../../components/table/MjLogsTable'; const Midjourney = () => ( <> diff --git a/web/src/pages/Pricing/index.js b/web/src/pages/Pricing/index.js index cb56a477..21fd7256 100644 --- a/web/src/pages/Pricing/index.js +++ b/web/src/pages/Pricing/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import ModelPricing from '../../components/ModelPricing.js'; +import ModelPricing from '../../components/table/ModelPricing.js'; const Pricing = () => ( <> diff --git a/web/src/pages/Redemption/index.js b/web/src/pages/Redemption/index.js index f877b601..0154d979 100644 --- a/web/src/pages/Redemption/index.js +++ b/web/src/pages/Redemption/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import RedemptionsTable from '../../components/RedemptionsTable'; +import RedemptionsTable from '../../components/table/RedemptionsTable'; const Redemption = () => { return ( diff --git a/web/src/pages/Setting/index.js b/web/src/pages/Setting/index.js index 0619ab9a..056fc207 100644 --- a/web/src/pages/Setting/index.js +++ b/web/src/pages/Setting/index.js @@ -3,13 +3,13 @@ import { Layout, TabPane, Tabs } from '@douyinfe/semi-ui'; import { useNavigate, useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import SystemSetting from '../../components/SystemSetting'; +import SystemSetting from '../../components/settings/SystemSetting.js'; import { isRoot } from '../../helpers'; -import OtherSetting from '../../components/OtherSetting'; -import PersonalSetting from '../../components/PersonalSetting'; -import OperationSetting from '../../components/OperationSetting'; -import RateLimitSetting from '../../components/RateLimitSetting.js'; -import ModelSetting from '../../components/ModelSetting.js'; +import OtherSetting from '../../components/settings/OtherSetting'; +import PersonalSetting from '../../components/settings/PersonalSetting.js'; +import OperationSetting from '../../components/settings/OperationSetting.js'; +import RateLimitSetting from '../../components/settings/RateLimitSetting.js'; +import ModelSetting from '../../components/settings/ModelSetting.js'; const Setting = () => { const { t } = useTranslation(); diff --git a/web/src/pages/Task/index.js b/web/src/pages/Task/index.js index 47b541b6..05326719 100644 --- a/web/src/pages/Task/index.js +++ b/web/src/pages/Task/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import TaskLogsTable from '../../components/TaskLogsTable.js'; +import TaskLogsTable from '../../components/table/TaskLogsTable.js'; const Task = () => ( <> diff --git a/web/src/pages/Token/index.js b/web/src/pages/Token/index.js index eea68941..d63247a4 100644 --- a/web/src/pages/Token/index.js +++ b/web/src/pages/Token/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import TokensTable from '../../components/TokensTable'; +import TokensTable from '../../components/table/TokensTable'; const Token = () => { return ( diff --git a/web/src/pages/User/index.js b/web/src/pages/User/index.js index f1d04419..59b441ae 100644 --- a/web/src/pages/User/index.js +++ b/web/src/pages/User/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import UsersTable from '../../components/UsersTable'; +import UsersTable from '../../components/table/UsersTable'; const User = () => { return ( From 943f21f3cb124b883637e95c50d48e392a1e5406 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Wed, 4 Jun 2025 01:00:48 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20refactor?= =?UTF-8?q?=20the=20logic=20of=20fetchTokenKeys=20and=20SetupCheck?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/App.js | 6 +-- web/src/components/SetupCheck.js | 18 -------- web/src/components/fetchTokenKeys.js | 68 ---------------------------- web/src/helpers/index.js | 1 + web/src/helpers/token.js | 45 ++++++++++++++++++ web/src/hooks/useSetupCheck.js | 32 +++++++++++++ web/src/hooks/useTokenKeys.js | 30 ++++++++++++ web/src/index.js | 4 +- web/src/pages/Chat/index.js | 2 +- web/src/pages/Chat2Link/index.js | 2 +- web/src/pages/Home/index.js | 2 +- 11 files changed, 116 insertions(+), 94 deletions(-) delete mode 100644 web/src/components/SetupCheck.js delete mode 100644 web/src/components/fetchTokenKeys.js create mode 100644 web/src/helpers/token.js create mode 100644 web/src/hooks/useSetupCheck.js create mode 100644 web/src/hooks/useTokenKeys.js diff --git a/web/src/App.js b/web/src/App.js index ff0abbbb..93b4e496 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -25,7 +25,7 @@ import Playground from './pages/Playground/index.js'; import OAuth2Callback from './components/auth/OAuth2Callback.js'; import PersonalSetting from './components/settings/PersonalSetting.js'; import Setup from './pages/Setup/index.js'; -import SetupCheck from './components/SetupCheck'; +import { useSetupCheck } from './hooks/useSetupCheck.js'; const Home = lazy(() => import('./pages/Home')); const Detail = lazy(() => import('./pages/Detail')); @@ -35,7 +35,7 @@ function App() { const location = useLocation(); return ( - + } /> - + ); } diff --git a/web/src/components/SetupCheck.js b/web/src/components/SetupCheck.js deleted file mode 100644 index 99364b00..00000000 --- a/web/src/components/SetupCheck.js +++ /dev/null @@ -1,18 +0,0 @@ -import React, { useContext, useEffect } from 'react'; -import { Navigate, useLocation } from 'react-router-dom'; -import { StatusContext } from '../context/Status'; - -const SetupCheck = ({ children }) => { - const [statusState] = useContext(StatusContext); - const location = useLocation(); - - useEffect(() => { - if (statusState?.status?.setup === false && location.pathname !== '/setup') { - window.location.href = '/setup'; - } - }, [statusState?.status?.setup, location.pathname]); - - return children; -}; - -export default SetupCheck; \ No newline at end of file diff --git a/web/src/components/fetchTokenKeys.js b/web/src/components/fetchTokenKeys.js deleted file mode 100644 index e9cec001..00000000 --- a/web/src/components/fetchTokenKeys.js +++ /dev/null @@ -1,68 +0,0 @@ -// src/hooks/useTokenKeys.js -import { useEffect, useState } from 'react'; -import { API, showError } from '../helpers'; - -async function fetchTokenKeys() { - try { - const response = await API.get('/api/token/?p=0&size=100'); - const { success, data } = response.data; - if (success) { - const activeTokens = data.filter((token) => token.status === 1); - return activeTokens.map((token) => token.key); - } else { - throw new Error('Failed to fetch token keys'); - } - } catch (error) { - console.error('Error fetching token keys:', error); - return []; - } -} - -function getServerAddress() { - let status = localStorage.getItem('status'); - let serverAddress = ''; - - if (status) { - try { - status = JSON.parse(status); - serverAddress = status.server_address || ''; - } catch (error) { - console.error('Failed to parse status from localStorage:', error); - } - } - - if (!serverAddress) { - serverAddress = window.location.origin; - } - - return serverAddress; -} - -export function useTokenKeys(id) { - const [keys, setKeys] = useState([]); - // const [chatLink, setChatLink] = useState(''); - const [serverAddress, setServerAddress] = useState(''); - const [isLoading, setIsLoading] = useState(true); - - useEffect(() => { - const loadAllData = async () => { - const fetchedKeys = await fetchTokenKeys(); - if (fetchedKeys.length === 0) { - showError('当前没有可用的启用令牌,请确认是否有令牌处于启用状态!'); - setTimeout(() => { - window.location.href = '/token'; - }, 1500); // 延迟 1.5 秒后跳转 - } - setKeys(fetchedKeys); - setIsLoading(false); - // setChatLink(link); - - const address = getServerAddress(); - setServerAddress(address); - }; - - loadAllData(); - }, []); - - return { keys, serverAddress, isLoading }; -} diff --git a/web/src/helpers/index.js b/web/src/helpers/index.js index c43be2c9..1524afbe 100644 --- a/web/src/helpers/index.js +++ b/web/src/helpers/index.js @@ -5,3 +5,4 @@ export * from './api'; export * from './render'; export * from './log'; export * from './data'; +export * from './token'; diff --git a/web/src/helpers/token.js b/web/src/helpers/token.js new file mode 100644 index 00000000..ecdeaa3a --- /dev/null +++ b/web/src/helpers/token.js @@ -0,0 +1,45 @@ +import { API } from './api'; + +/** + * 获取可用的token keys + * @returns {Promise} 返回active状态的token key数组 + */ +export async function fetchTokenKeys() { + try { + const response = await API.get('/api/token/?p=0&size=100'); + const { success, data } = response.data; + if (success) { + const activeTokens = data.filter((token) => token.status === 1); + return activeTokens.map((token) => token.key); + } else { + throw new Error('Failed to fetch token keys'); + } + } catch (error) { + console.error('Error fetching token keys:', error); + return []; + } +} + +/** + * 获取服务器地址 + * @returns {string} 服务器地址 + */ +export function getServerAddress() { + let status = localStorage.getItem('status'); + let serverAddress = ''; + + if (status) { + try { + status = JSON.parse(status); + serverAddress = status.server_address || ''; + } catch (error) { + console.error('Failed to parse status from localStorage:', error); + } + } + + if (!serverAddress) { + serverAddress = window.location.origin; + } + + return serverAddress; +} \ No newline at end of file diff --git a/web/src/hooks/useSetupCheck.js b/web/src/hooks/useSetupCheck.js new file mode 100644 index 00000000..d2233de8 --- /dev/null +++ b/web/src/hooks/useSetupCheck.js @@ -0,0 +1,32 @@ +import { useContext, useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; +import { StatusContext } from '../context/Status'; + +/** + * 自定义Hook:检查系统setup状态并进行重定向 + * @param {Object} options - 配置选项 + * @param {boolean} options.autoRedirect - 是否自动重定向,默认true + * @param {string} options.setupPath - setup页面路径,默认'/setup' + * @returns {Object} 返回setup状态信息 + */ +export function useSetupCheck(options = {}) { + const { autoRedirect = true, setupPath = '/setup' } = options; + const [statusState] = useContext(StatusContext); + const location = useLocation(); + + const isSetupComplete = statusState?.status?.setup !== false; + const needsSetup = !isSetupComplete && location.pathname !== setupPath; + + useEffect(() => { + if (autoRedirect && needsSetup) { + window.location.href = setupPath; + } + }, [autoRedirect, needsSetup, setupPath]); + + return { + isSetupComplete, + needsSetup, + statusState, + currentPath: location.pathname + }; +} \ No newline at end of file diff --git a/web/src/hooks/useTokenKeys.js b/web/src/hooks/useTokenKeys.js new file mode 100644 index 00000000..a6583591 --- /dev/null +++ b/web/src/hooks/useTokenKeys.js @@ -0,0 +1,30 @@ +import { useEffect, useState } from 'react'; +import { fetchTokenKeys, getServerAddress } from '../helpers/token'; +import { showError } from '../helpers'; + +export function useTokenKeys(id) { + const [keys, setKeys] = useState([]); + const [serverAddress, setServerAddress] = useState(''); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const loadAllData = async () => { + const fetchedKeys = await fetchTokenKeys(); + if (fetchedKeys.length === 0) { + showError('当前没有可用的启用令牌,请确认是否有令牌处于启用状态!'); + setTimeout(() => { + window.location.href = '/token'; + }, 1500); // 延迟 1.5 秒后跳转 + } + setKeys(fetchedKeys); + setIsLoading(false); + + const address = getServerAddress(); + setServerAddress(address); + }; + + loadAllData(); + }, []); + + return { keys, serverAddress, isLoading }; +} \ No newline at end of file diff --git a/web/src/index.js b/web/src/index.js index dc0c438d..0f57f5a1 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -16,7 +16,7 @@ import './index.css'; const root = ReactDOM.createRoot(document.getElementById('root')); const { Sider, Content, Header, Footer } = Layout; root.render( - // + @@ -28,5 +28,5 @@ root.render( - // , + , ); diff --git a/web/src/pages/Chat/index.js b/web/src/pages/Chat/index.js index 77ece906..bd4e19a1 100644 --- a/web/src/pages/Chat/index.js +++ b/web/src/pages/Chat/index.js @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { useTokenKeys } from '../../components/fetchTokenKeys'; +import { useTokenKeys } from '../../hooks/useTokenKeys'; import { Banner, Layout } from '@douyinfe/semi-ui'; import { useParams } from 'react-router-dom'; diff --git a/web/src/pages/Chat2Link/index.js b/web/src/pages/Chat2Link/index.js index a7b5e6ae..553dab3f 100644 --- a/web/src/pages/Chat2Link/index.js +++ b/web/src/pages/Chat2Link/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useTokenKeys } from '../../components/fetchTokenKeys'; +import { useTokenKeys } from '../../hooks/useTokenKeys'; const chat2page = () => { const { keys, chatLink, serverAddress, isLoading } = useTokenKeys(); diff --git a/web/src/pages/Home/index.js b/web/src/pages/Home/index.js index ba0c738c..4e8a61d6 100644 --- a/web/src/pages/Home/index.js +++ b/web/src/pages/Home/index.js @@ -5,7 +5,7 @@ import { StatusContext } from '../../context/Status'; import { marked } from 'marked'; import { useTranslation } from 'react-i18next'; import { IconGithubLogo } from '@douyinfe/semi-icons'; -import exampleImage from '..//example.png'; +import exampleImage from '/example.png'; import { Link } from 'react-router-dom'; import NoticeModal from '../../components/layout/NoticeModal'; import { Moonshot, OpenAI, XAI, Zhipu, Volcengine, Cohere, Claude, Gemini, Suno, Minimax, Wenxin, Spark, Qingyan, DeepSeek, Qwen, Midjourney, Grok, AzureAI, Hunyuan, Xinference } from '@lobehub/icons'; From e11a8514b1f1954bceabae8e589907b002dcfab1 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Wed, 4 Jun 2025 01:03:32 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=F0=9F=92=84=20style:=20Remove=20`min-h-s?= =?UTF-8?q?creen`=20style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/auth/LoginForm.js | 2 +- web/src/components/auth/PasswordResetConfirm.js | 2 +- web/src/components/auth/PasswordResetForm.js | 2 +- web/src/components/auth/RegisterForm.js | 2 +- web/src/components/settings/PersonalSetting.js | 2 +- web/src/components/table/ModelPricing.js | 2 +- web/src/pages/TopUp/index.js | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/src/components/auth/LoginForm.js b/web/src/components/auth/LoginForm.js index 26ec53a4..ec28dba8 100644 --- a/web/src/components/auth/LoginForm.js +++ b/web/src/components/auth/LoginForm.js @@ -503,7 +503,7 @@ const LoginForm = () => { }; return ( -
+
{/* 背景图片容器 - 放大并保持居中 */}
{ } return ( -
+
{/* 背景图片容器 - 放大并保持居中 */}
{ } return ( -
+
{/* 背景图片容器 - 放大并保持居中 */}
{ }; return ( -
+
{ }; return ( -
+
{/* 划转模态框 */} diff --git a/web/src/components/table/ModelPricing.js b/web/src/components/table/ModelPricing.js index a9f06713..22738127 100644 --- a/web/src/components/table/ModelPricing.js +++ b/web/src/components/table/ModelPricing.js @@ -481,7 +481,7 @@ const ModelPricing = () => { ), [filteredModels, loading, columns, rowSelection, pageSize, t]); return ( -
+
diff --git a/web/src/pages/TopUp/index.js b/web/src/pages/TopUp/index.js index 550af5e0..2f920647 100644 --- a/web/src/pages/TopUp/index.js +++ b/web/src/pages/TopUp/index.js @@ -265,7 +265,7 @@ const TopUp = () => { }; return ( -
+