api.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import {
  16. getUserIdFromLocalStorage,
  17. showError,
  18. formatMessageForAPI,
  19. isValidMessage,
  20. } from './utils';
  21. import axios from 'axios';
  22. import { MESSAGE_ROLES } from '../constants/playground.constants';
  23. export let API = axios.create({
  24. baseURL: import.meta.env.VITE_REACT_APP_SERVER_URL
  25. ? import.meta.env.VITE_REACT_APP_SERVER_URL
  26. : '',
  27. headers: {
  28. 'New-API-User': getUserIdFromLocalStorage(),
  29. 'Cache-Control': 'no-store',
  30. },
  31. });
  32. function patchAPIInstance(instance) {
  33. const originalGet = instance.get.bind(instance);
  34. const inFlightGetRequests = new Map();
  35. const genKey = (url, config = {}) => {
  36. const params = config.params ? JSON.stringify(config.params) : '{}';
  37. return `${url}?${params}`;
  38. };
  39. instance.get = (url, config = {}) => {
  40. if (config?.disableDuplicate) {
  41. return originalGet(url, config);
  42. }
  43. const key = genKey(url, config);
  44. if (inFlightGetRequests.has(key)) {
  45. return inFlightGetRequests.get(key);
  46. }
  47. const reqPromise = originalGet(url, config).finally(() => {
  48. inFlightGetRequests.delete(key);
  49. });
  50. inFlightGetRequests.set(key, reqPromise);
  51. return reqPromise;
  52. };
  53. }
  54. patchAPIInstance(API);
  55. export function updateAPI() {
  56. API = axios.create({
  57. baseURL: import.meta.env.VITE_REACT_APP_SERVER_URL
  58. ? import.meta.env.VITE_REACT_APP_SERVER_URL
  59. : '',
  60. headers: {
  61. 'New-API-User': getUserIdFromLocalStorage(),
  62. 'Cache-Control': 'no-store',
  63. },
  64. });
  65. patchAPIInstance(API);
  66. }
  67. API.interceptors.response.use(
  68. (response) => response,
  69. (error) => {
  70. // 如果请求配置中显式要求跳过全局错误处理,则不弹出默认错误提示
  71. if (error.config && error.config.skipErrorHandler) {
  72. return Promise.reject(error);
  73. }
  74. showError(error);
  75. return Promise.reject(error);
  76. },
  77. );
  78. // playground
  79. // 构建API请求负载
  80. export const buildApiPayload = (
  81. messages,
  82. systemPrompt,
  83. inputs,
  84. parameterEnabled,
  85. ) => {
  86. const processedMessages = messages
  87. .filter(isValidMessage)
  88. .map(formatMessageForAPI)
  89. .filter(Boolean);
  90. // 如果有系统提示,插入到消息开头
  91. if (systemPrompt && systemPrompt.trim()) {
  92. processedMessages.unshift({
  93. role: MESSAGE_ROLES.SYSTEM,
  94. content: systemPrompt.trim(),
  95. });
  96. }
  97. const payload = {
  98. model: inputs.model,
  99. group: inputs.group,
  100. messages: processedMessages,
  101. stream: inputs.stream,
  102. };
  103. // 添加启用的参数
  104. const parameterMappings = {
  105. temperature: 'temperature',
  106. top_p: 'top_p',
  107. max_tokens: 'max_tokens',
  108. frequency_penalty: 'frequency_penalty',
  109. presence_penalty: 'presence_penalty',
  110. seed: 'seed',
  111. };
  112. Object.entries(parameterMappings).forEach(([key, param]) => {
  113. const enabled = parameterEnabled[key];
  114. const value = inputs[param];
  115. const hasValue = value !== undefined && value !== null;
  116. if (enabled && hasValue) {
  117. payload[param] = value;
  118. }
  119. });
  120. return payload;
  121. };
  122. // 处理API错误响应
  123. export const handleApiError = (error, response = null) => {
  124. const errorInfo = {
  125. error: error.message || '未知错误',
  126. timestamp: new Date().toISOString(),
  127. stack: error.stack,
  128. };
  129. if (response) {
  130. errorInfo.status = response.status;
  131. errorInfo.statusText = response.statusText;
  132. }
  133. if (error.message.includes('HTTP error')) {
  134. errorInfo.details = '服务器返回了错误状态码';
  135. } else if (error.message.includes('Failed to fetch')) {
  136. errorInfo.details = '网络连接失败或服务器无响应';
  137. }
  138. return errorInfo;
  139. };
  140. // 处理模型数据
  141. export const processModelsData = (data, currentModel) => {
  142. const modelOptions = data.map((model) => ({
  143. label: model,
  144. value: model,
  145. }));
  146. const hasCurrentModel = modelOptions.some(
  147. (option) => option.value === currentModel,
  148. );
  149. const selectedModel =
  150. hasCurrentModel && modelOptions.length > 0
  151. ? currentModel
  152. : modelOptions[0]?.value;
  153. return { modelOptions, selectedModel };
  154. };
  155. // 处理分组数据
  156. export const processGroupsData = (data, userGroup) => {
  157. let groupOptions = Object.entries(data).map(([group, info]) => ({
  158. label:
  159. info.desc.length > 20 ? info.desc.substring(0, 20) + '...' : info.desc,
  160. value: group,
  161. ratio: info.ratio,
  162. fullLabel: info.desc,
  163. }));
  164. if (groupOptions.length === 0) {
  165. groupOptions = [
  166. {
  167. label: '用户分组',
  168. value: '',
  169. ratio: 1,
  170. },
  171. ];
  172. } else if (userGroup) {
  173. const userGroupIndex = groupOptions.findIndex((g) => g.value === userGroup);
  174. if (userGroupIndex > -1) {
  175. const userGroupOption = groupOptions.splice(userGroupIndex, 1)[0];
  176. groupOptions.unshift(userGroupOption);
  177. }
  178. }
  179. return groupOptions;
  180. };
  181. // 原来components中的utils.js
  182. export async function getOAuthState() {
  183. let path = '/api/oauth/state';
  184. let affCode = localStorage.getItem('aff');
  185. if (affCode && affCode.length > 0) {
  186. path += `?aff=${affCode}`;
  187. }
  188. const res = await API.get(path);
  189. const { success, message, data } = res.data;
  190. if (success) {
  191. return data;
  192. } else {
  193. showError(message);
  194. return '';
  195. }
  196. }
  197. export async function onOIDCClicked(auth_url, client_id, openInNewTab = false) {
  198. const state = await getOAuthState();
  199. if (!state) return;
  200. const url = new URL(auth_url);
  201. url.searchParams.set('client_id', client_id);
  202. url.searchParams.set('redirect_uri', `${window.location.origin}/oauth/oidc`);
  203. url.searchParams.set('response_type', 'code');
  204. url.searchParams.set('scope', 'openid profile email');
  205. url.searchParams.set('state', state);
  206. if (openInNewTab) {
  207. window.open(url.toString(), '_blank');
  208. } else {
  209. window.location.href = url.toString();
  210. }
  211. }
  212. export async function onGitHubOAuthClicked(github_client_id) {
  213. const state = await getOAuthState();
  214. if (!state) return;
  215. window.open(
  216. `https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`,
  217. );
  218. }
  219. export async function onLinuxDOOAuthClicked(linuxdo_client_id) {
  220. const state = await getOAuthState();
  221. if (!state) return;
  222. window.open(
  223. `https://connect.linux.do/oauth2/authorize?response_type=code&client_id=${linuxdo_client_id}&state=${state}`,
  224. );
  225. }
  226. let channelModels = undefined;
  227. export async function loadChannelModels() {
  228. const res = await API.get('/api/models');
  229. const { success, data } = res.data;
  230. if (!success) {
  231. return;
  232. }
  233. channelModels = data;
  234. localStorage.setItem('channel_models', JSON.stringify(data));
  235. }
  236. export function getChannelModels(type) {
  237. if (channelModels !== undefined && type in channelModels) {
  238. if (!channelModels[type]) {
  239. return [];
  240. }
  241. return channelModels[type];
  242. }
  243. let models = localStorage.getItem('channel_models');
  244. if (!models) {
  245. return [];
  246. }
  247. channelModels = JSON.parse(models);
  248. if (type in channelModels) {
  249. return channelModels[type];
  250. }
  251. return [];
  252. }