| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| 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);
|
| },
|
| );
|
|
|
|
|
|
|
|
|
| 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;
|
| };
|
|
|
|
|
| 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;
|
| };
|
|
|
|
|
|
|
| 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 '';
|
| }
|
| }
|
|
|
| async function prepareOAuthState(options = {}) {
|
| const { shouldLogout = false } = options;
|
| if (shouldLogout) {
|
| try {
|
| await API.get('/api/user/logout', { skipErrorHandler: true });
|
| } catch (err) {}
|
| localStorage.removeItem('user');
|
| updateAPI();
|
| }
|
| return await getOAuthState();
|
| }
|
|
|
| export async function onDiscordOAuthClicked(client_id, options = {}) {
|
| const state = await prepareOAuthState(options);
|
| if (!state) return;
|
| const redirect_uri = `${window.location.origin}/oauth/discord`;
|
| const response_type = 'code';
|
| const scope = 'identify+openid';
|
| window.open(
|
| `https://discord.com/oauth2/authorize?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope}&state=${state}`,
|
| );
|
| }
|
|
|
| export async function onOIDCClicked(
|
| auth_url,
|
| client_id,
|
| openInNewTab = false,
|
| options = {},
|
| ) {
|
| const state = await prepareOAuthState(options);
|
| 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, options = {}) {
|
| const state = await prepareOAuthState(options);
|
| 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,
|
| options = { shouldLogout: false },
|
| ) {
|
| const state = await prepareOAuthState(options);
|
| 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 [];
|
| }
|
|
|