api.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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 { getUserIdFromLocalStorage, showError, formatMessageForAPI, isValidMessage } from './utils';
  16. import axios from 'axios';
  17. import { MESSAGE_ROLES } from '../constants/playground.constants';
  18. export let API = axios.create({
  19. baseURL: import.meta.env.VITE_REACT_APP_SERVER_URL
  20. ? import.meta.env.VITE_REACT_APP_SERVER_URL
  21. : '',
  22. headers: {
  23. 'New-API-User': getUserIdFromLocalStorage(),
  24. 'Cache-Control': 'no-store',
  25. },
  26. });
  27. function patchAPIInstance(instance) {
  28. const originalGet = instance.get.bind(instance);
  29. const inFlightGetRequests = new Map();
  30. const genKey = (url, config = {}) => {
  31. const params = config.params ? JSON.stringify(config.params) : '{}';
  32. return `${url}?${params}`;
  33. };
  34. instance.get = (url, config = {}) => {
  35. if (config?.disableDuplicate) {
  36. return originalGet(url, config);
  37. }
  38. const key = genKey(url, config);
  39. if (inFlightGetRequests.has(key)) {
  40. return inFlightGetRequests.get(key);
  41. }
  42. const reqPromise = originalGet(url, config).finally(() => {
  43. inFlightGetRequests.delete(key);
  44. });
  45. inFlightGetRequests.set(key, reqPromise);
  46. return reqPromise;
  47. };
  48. }
  49. patchAPIInstance(API);
  50. export function updateAPI() {
  51. API = axios.create({
  52. baseURL: import.meta.env.VITE_REACT_APP_SERVER_URL
  53. ? import.meta.env.VITE_REACT_APP_SERVER_URL
  54. : '',
  55. headers: {
  56. 'New-API-User': getUserIdFromLocalStorage(),
  57. 'Cache-Control': 'no-store',
  58. },
  59. });
  60. patchAPIInstance(API);
  61. }
  62. API.interceptors.response.use(
  63. (response) => response,
  64. (error) => {
  65. // 如果请求配置中显式要求跳过全局错误处理,则不弹出默认错误提示
  66. if (error.config && error.config.skipErrorHandler) {
  67. return Promise.reject(error);
  68. }
  69. showError(error);
  70. return Promise.reject(error);
  71. },
  72. );
  73. // playground
  74. // 构建API请求负载
  75. export const buildApiPayload = (messages, systemPrompt, inputs, parameterEnabled) => {
  76. const processedMessages = messages
  77. .filter(isValidMessage)
  78. .map(formatMessageForAPI)
  79. .filter(Boolean);
  80. // 如果有系统提示,插入到消息开头
  81. if (systemPrompt && systemPrompt.trim()) {
  82. processedMessages.unshift({
  83. role: MESSAGE_ROLES.SYSTEM,
  84. content: systemPrompt.trim()
  85. });
  86. }
  87. const payload = {
  88. model: inputs.model,
  89. group: inputs.group,
  90. messages: processedMessages,
  91. group: inputs.group,
  92. stream: inputs.stream,
  93. };
  94. // 添加启用的参数
  95. const parameterMappings = {
  96. temperature: 'temperature',
  97. top_p: 'top_p',
  98. max_tokens: 'max_tokens',
  99. frequency_penalty: 'frequency_penalty',
  100. presence_penalty: 'presence_penalty',
  101. seed: 'seed'
  102. };
  103. Object.entries(parameterMappings).forEach(([key, param]) => {
  104. if (parameterEnabled[key] && inputs[param] !== undefined && inputs[param] !== null) {
  105. payload[param] = inputs[param];
  106. }
  107. });
  108. return payload;
  109. };
  110. // 处理API错误响应
  111. export const handleApiError = (error, response = null) => {
  112. const errorInfo = {
  113. error: error.message || '未知错误',
  114. timestamp: new Date().toISOString(),
  115. stack: error.stack
  116. };
  117. if (response) {
  118. errorInfo.status = response.status;
  119. errorInfo.statusText = response.statusText;
  120. }
  121. if (error.message.includes('HTTP error')) {
  122. errorInfo.details = '服务器返回了错误状态码';
  123. } else if (error.message.includes('Failed to fetch')) {
  124. errorInfo.details = '网络连接失败或服务器无响应';
  125. }
  126. return errorInfo;
  127. };
  128. // 处理模型数据
  129. export const processModelsData = (data, currentModel) => {
  130. const modelOptions = data.map(model => ({
  131. label: model,
  132. value: model,
  133. }));
  134. const hasCurrentModel = modelOptions.some(option => option.value === currentModel);
  135. const selectedModel = hasCurrentModel && modelOptions.length > 0
  136. ? currentModel
  137. : modelOptions[0]?.value;
  138. return { modelOptions, selectedModel };
  139. };
  140. // 处理分组数据
  141. export const processGroupsData = (data, userGroup) => {
  142. let groupOptions = Object.entries(data).map(([group, info]) => ({
  143. label: info.desc.length > 20 ? info.desc.substring(0, 20) + '...' : info.desc,
  144. value: group,
  145. ratio: info.ratio,
  146. fullLabel: info.desc,
  147. }));
  148. if (groupOptions.length === 0) {
  149. groupOptions = [{
  150. label: '用户分组',
  151. value: '',
  152. ratio: 1,
  153. }];
  154. } else if (userGroup) {
  155. const userGroupIndex = groupOptions.findIndex(g => g.value === userGroup);
  156. if (userGroupIndex > -1) {
  157. const userGroupOption = groupOptions.splice(userGroupIndex, 1)[0];
  158. groupOptions.unshift(userGroupOption);
  159. }
  160. }
  161. return groupOptions;
  162. };
  163. // 原来components中的utils.js
  164. export async function getOAuthState() {
  165. let path = '/api/oauth/state';
  166. let affCode = localStorage.getItem('aff');
  167. if (affCode && affCode.length > 0) {
  168. path += `?aff=${affCode}`;
  169. }
  170. const res = await API.get(path);
  171. const { success, message, data } = res.data;
  172. if (success) {
  173. return data;
  174. } else {
  175. showError(message);
  176. return '';
  177. }
  178. }
  179. export async function onOIDCClicked(auth_url, client_id, openInNewTab = false) {
  180. const state = await getOAuthState();
  181. if (!state) return;
  182. const redirect_uri = `${window.location.origin}/oauth/oidc`;
  183. const response_type = 'code';
  184. const scope = 'openid profile email';
  185. const url = `${auth_url}?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope}&state=${state}`;
  186. if (openInNewTab) {
  187. window.open(url);
  188. } else {
  189. window.location.href = url;
  190. }
  191. }
  192. export async function onGitHubOAuthClicked(github_client_id) {
  193. const state = await getOAuthState();
  194. if (!state) return;
  195. window.open(
  196. `https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`,
  197. );
  198. }
  199. export async function onLinuxDOOAuthClicked(linuxdo_client_id) {
  200. const state = await getOAuthState();
  201. if (!state) return;
  202. window.open(
  203. `https://connect.linux.do/oauth2/authorize?response_type=code&client_id=${linuxdo_client_id}&state=${state}`,
  204. );
  205. }
  206. let channelModels = undefined;
  207. export async function loadChannelModels() {
  208. const res = await API.get('/api/models');
  209. const { success, data } = res.data;
  210. if (!success) {
  211. return;
  212. }
  213. channelModels = data;
  214. localStorage.setItem('channel_models', JSON.stringify(data));
  215. }
  216. export function getChannelModels(type) {
  217. if (channelModels !== undefined && type in channelModels) {
  218. if (!channelModels[type]) {
  219. return [];
  220. }
  221. return channelModels[type];
  222. }
  223. let models = localStorage.getItem('channel_models');
  224. if (!models) {
  225. return [];
  226. }
  227. channelModels = JSON.parse(models);
  228. if (type in channelModels) {
  229. return channelModels[type];
  230. }
  231. return [];
  232. }