| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- /*
- Copyright (C) 2025 QuantumNous
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
- For commercial licensing, please contact support@quantumnous.com
- */
- import {
- getUserIdFromLocalStorage,
- showError,
- formatMessageForAPI,
- isValidMessage,
- } from './utils';
- import axios from 'axios';
- import { MESSAGE_ROLES } from '../constants/playground.constants';
- export let API = axios.create({
- baseURL: import.meta.env.VITE_REACT_APP_SERVER_URL
- ? import.meta.env.VITE_REACT_APP_SERVER_URL
- : '',
- headers: {
- 'New-API-User': getUserIdFromLocalStorage(),
- 'Cache-Control': 'no-store',
- },
- });
- function patchAPIInstance(instance) {
- const originalGet = instance.get.bind(instance);
- const inFlightGetRequests = new Map();
- const genKey = (url, config = {}) => {
- const params = config.params ? JSON.stringify(config.params) : '{}';
- return `${url}?${params}`;
- };
- instance.get = (url, config = {}) => {
- if (config?.disableDuplicate) {
- return originalGet(url, config);
- }
- const key = genKey(url, config);
- if (inFlightGetRequests.has(key)) {
- return inFlightGetRequests.get(key);
- }
- const reqPromise = originalGet(url, config).finally(() => {
- inFlightGetRequests.delete(key);
- });
- inFlightGetRequests.set(key, reqPromise);
- return reqPromise;
- };
- }
- patchAPIInstance(API);
- export function updateAPI() {
- API = axios.create({
- baseURL: import.meta.env.VITE_REACT_APP_SERVER_URL
- ? import.meta.env.VITE_REACT_APP_SERVER_URL
- : '',
- headers: {
- 'New-API-User': getUserIdFromLocalStorage(),
- 'Cache-Control': 'no-store',
- },
- });
- patchAPIInstance(API);
- }
- API.interceptors.response.use(
- (response) => response,
- (error) => {
- // 如果请求配置中显式要求跳过全局错误处理,则不弹出默认错误提示
- if (error.config && error.config.skipErrorHandler) {
- return Promise.reject(error);
- }
- showError(error);
- return Promise.reject(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,
- group: inputs.group,
- 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]) => {
- const enabled = parameterEnabled[key];
- const value = inputs[param];
- const hasValue = value !== undefined && value !== null;
- if (enabled && hasValue) {
- payload[param] = value;
- }
- });
- 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;
- };
- // 原来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 url = new URL(auth_url);
- url.searchParams.set('client_id', client_id);
- url.searchParams.set('redirect_uri', `${window.location.origin}/oauth/oidc`);
- url.searchParams.set('response_type', 'code');
- url.searchParams.set('scope', 'openid profile email');
- url.searchParams.set('state', state);
- if (openInNewTab) {
- window.open(url.toString(), '_blank');
- } else {
- window.location.href = url.toString();
- }
- }
- 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 [];
- }
|