新增自定义模型
This commit is contained in:
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "Prisma",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
2
prisma/.gitignore
vendored
2
prisma/.gitignore
vendored
@@ -22,3 +22,5 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
prisma/.env
|
||||||
|
prisma/.env.example
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { GoogleGenAI } from "@google/genai";
|
import { GoogleGenAI } from "@google/genai";
|
||||||
import OpenAI from "openai";
|
import OpenAI from "openai";
|
||||||
import { ApiProvider, AppConfig, CustomModel } from './types';
|
import { ApiProvider, CustomModel } from './types';
|
||||||
|
|
||||||
type AIProviderConfig = {
|
type AIProviderConfig = {
|
||||||
provider?: ApiProvider;
|
provider?: ApiProvider;
|
||||||
@@ -8,39 +8,99 @@ type AIProviderConfig = {
|
|||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Find custom model configuration by model name
|
|
||||||
*/
|
|
||||||
export const findCustomModel = (modelName: string, customModels?: CustomModel[]): CustomModel | undefined => {
|
export const findCustomModel = (modelName: string, customModels?: CustomModel[]): CustomModel | undefined => {
|
||||||
return customModels?.find(m => m.name === modelName);
|
return customModels?.find(m => m.name === modelName);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// External API base URLs for production
|
||||||
|
const PROVIDER_BASE_URLS: Record<string, string> = {
|
||||||
|
openai: 'https://api.openai.com/v1',
|
||||||
|
deepseek: 'https://api.deepseek.com/v1',
|
||||||
|
anthropic: 'https://api.anthropic.com/v1',
|
||||||
|
xai: 'https://api.x.ai/v1',
|
||||||
|
mistral: 'https://api.mistral.ai/v1',
|
||||||
|
custom: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if we're in development mode
|
||||||
|
const isDevelopment = import.meta.env?.MODE === 'development' || process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
|
// Store the current custom API target URL
|
||||||
|
let currentCustomApiUrl: string | null = null;
|
||||||
|
|
||||||
|
// Setup fetch interceptor to add X-Target-URL header for custom API proxy
|
||||||
|
const originalFetch = typeof window !== 'undefined' ? window.fetch.bind(window) : null;
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined' && originalFetch) {
|
||||||
|
window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||||
|
let urlString: string;
|
||||||
|
if (typeof input === 'string') {
|
||||||
|
urlString = input;
|
||||||
|
} else if (input instanceof URL) {
|
||||||
|
urlString = input.toString();
|
||||||
|
} else {
|
||||||
|
urlString = input.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a custom-api request and we have a target URL, add the header
|
||||||
|
if (urlString.includes('/custom-api') && currentCustomApiUrl) {
|
||||||
|
const headers = new Headers(init?.headers);
|
||||||
|
headers.set('X-Target-URL', currentCustomApiUrl);
|
||||||
|
console.log('[Fetch] Adding X-Target-URL header:', currentCustomApiUrl);
|
||||||
|
|
||||||
|
return originalFetch(input, {
|
||||||
|
...init,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalFetch(input, init);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const getAI = (config?: AIProviderConfig) => {
|
export const getAI = (config?: AIProviderConfig) => {
|
||||||
const provider = config?.provider || 'google';
|
const provider = config?.provider || 'google';
|
||||||
// Support both Vite env vars (VITE_) and standard env vars for flexibility
|
|
||||||
const apiKey = config?.apiKey || (import.meta.env as any).VITE_API_KEY || process.env.API_KEY;
|
const apiKey = config?.apiKey || (import.meta.env as any).VITE_API_KEY || process.env.API_KEY;
|
||||||
|
|
||||||
if (provider === 'openai' || provider === 'deepseek' || provider === 'custom' || provider === 'anthropic' || provider === 'xai' || provider === 'mistral') {
|
if (provider === 'openai' || provider === 'deepseek' || provider === 'custom' || provider === 'anthropic' || provider === 'xai' || provider === 'mistral') {
|
||||||
const options: any = {
|
const options: any = {
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
// WARNING: dangerouslyAllowBrowser enables client-side API calls
|
|
||||||
// This is acceptable for local development but NOT production
|
|
||||||
// In production, use a backend proxy to protect API keys
|
|
||||||
dangerouslyAllowBrowser: true,
|
dangerouslyAllowBrowser: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (config?.baseUrl) {
|
if (config?.baseUrl) {
|
||||||
options.baseURL = config.baseUrl;
|
// Custom baseUrl from Configuration UI
|
||||||
} else if (provider === 'deepseek') {
|
if (isDevelopment) {
|
||||||
options.baseURL = 'https://api.deepseek.com/v1';
|
// Store the target URL for the fetch interceptor
|
||||||
} else if (provider === 'anthropic') {
|
currentCustomApiUrl = config.baseUrl;
|
||||||
options.baseURL = 'https://api.anthropic.com/v1';
|
// Use proxy path
|
||||||
} else if (provider === 'xai') {
|
options.baseURL = `${window.location.origin}/custom-api`;
|
||||||
options.baseURL = 'https://api.x.ai/v1';
|
console.log('[API] Using custom API proxy:', {
|
||||||
} else if (provider === 'mistral') {
|
proxyPath: options.baseURL,
|
||||||
options.baseURL = 'https://api.mistral.ai/v1';
|
targetUrl: currentCustomApiUrl,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// In production, use the URL directly
|
||||||
|
options.baseURL = config.baseUrl;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const providerBaseUrl = PROVIDER_BASE_URLS[provider];
|
||||||
|
if (providerBaseUrl) {
|
||||||
|
if (isDevelopment) {
|
||||||
|
// In development, use proxy to avoid CORS for known providers
|
||||||
|
options.baseURL = `${window.location.origin}/${provider}/v1`;
|
||||||
|
} else {
|
||||||
|
options.baseURL = providerBaseUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[API] OpenAI client config:', {
|
||||||
|
provider,
|
||||||
|
baseURL: options.baseURL,
|
||||||
|
hasApiKey: !!options.apiKey,
|
||||||
|
customTarget: currentCustomApiUrl,
|
||||||
|
});
|
||||||
return new OpenAI(options);
|
return new OpenAI(options);
|
||||||
} else {
|
} else {
|
||||||
const options: any = {
|
const options: any = {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const Header = ({ selectedModel, setSelectedModel, onOpenSettings, onToggleSideb
|
|||||||
className="relative bg-white border border-slate-200 text-slate-800 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-auto p-2.5 outline-none appearance-none cursor-pointer pl-3 pr-8 shadow-sm font-medium hover:bg-slate-50 transition-colors"
|
className="relative bg-white border border-slate-200 text-slate-800 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-auto p-2.5 outline-none appearance-none cursor-pointer pl-3 pr-8 shadow-sm font-medium hover:bg-slate-50 transition-colors"
|
||||||
>
|
>
|
||||||
{availableModels.map(m => (
|
{availableModels.map(m => (
|
||||||
<option key={m.value} value={m.value}>{m.label}</option>
|
<option key={`${m.provider}-${m.value}`} value={m.value}>{m.label}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<ChevronDown className="absolute right-3 top-3 text-slate-400 pointer-events-none group-hover:text-slate-600 transition-colors" size={14} />
|
<ChevronDown className="absolute right-3 top-3 text-slate-400 pointer-events-none group-hover:text-slate-600 transition-colors" size={14} />
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Plus, Trash2, Bot, Key, Globe, ChevronDown, ChevronUp } from 'lucide-react';
|
import { Plus, Trash2, Bot, Key, Globe, ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
import { AppConfig, ApiProvider, CustomModel } from '../../types';
|
import { AppConfig, ApiProvider, CustomModel } from '../../types';
|
||||||
|
import { MODELS } from '../../config';
|
||||||
|
|
||||||
interface ModelSectionProps {
|
interface ModelSectionProps {
|
||||||
config: AppConfig;
|
config: AppConfig;
|
||||||
@@ -20,9 +21,25 @@ const ModelSection = ({ config, setConfig }: ModelSectionProps) => {
|
|||||||
const handleAddModel = () => {
|
const handleAddModel = () => {
|
||||||
if (!newModelName.trim()) return;
|
if (!newModelName.trim()) return;
|
||||||
|
|
||||||
|
const trimmedName = newModelName.trim();
|
||||||
|
|
||||||
|
// Check if model name already exists in preset models
|
||||||
|
const existingPresetModel = MODELS.find(m => m.value === trimmedName);
|
||||||
|
if (existingPresetModel) {
|
||||||
|
alert(`Model name "${trimmedName}" already exists as a preset model. Please choose a different name.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if model name already exists in custom models
|
||||||
|
const existingCustomModel = customModels.find(m => m.name === trimmedName);
|
||||||
|
if (existingCustomModel) {
|
||||||
|
alert(`Model name "${trimmedName}" already exists. Please choose a different name.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const newModel: CustomModel = {
|
const newModel: CustomModel = {
|
||||||
id: `custom-${Date.now()}`,
|
id: `custom-${Date.now()}`,
|
||||||
name: newModelName.trim(),
|
name: trimmedName,
|
||||||
provider: newModelProvider,
|
provider: newModelProvider,
|
||||||
apiKey: newModelApiKey || undefined,
|
apiKey: newModelApiKey || undefined,
|
||||||
baseUrl: newModelBaseUrl || undefined
|
baseUrl: newModelBaseUrl || undefined
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ export const useDeepThink = () => {
|
|||||||
setProcessStartTime(Date.now());
|
setProcessStartTime(Date.now());
|
||||||
setProcessEndTime(null);
|
setProcessEndTime(null);
|
||||||
|
|
||||||
const provider = getAIProvider(model);
|
|
||||||
const customModelConfig = findCustomModel(model, config.customModels);
|
const customModelConfig = findCustomModel(model, config.customModels);
|
||||||
|
const provider = customModelConfig?.provider || getAIProvider(model);
|
||||||
|
|
||||||
const ai = getAI({
|
const ai = getAI({
|
||||||
provider,
|
provider,
|
||||||
|
|||||||
1
prisma/index.css
Normal file
1
prisma/index.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/* Base styles are handled by Tailwind CSS */
|
||||||
@@ -1,18 +1,5 @@
|
|||||||
/**
|
|
||||||
* Network Interceptor
|
|
||||||
*
|
|
||||||
* Intercepts global fetch calls to redirect Gemini API requests
|
|
||||||
* from the default endpoint to a user-defined custom base URL.
|
|
||||||
*
|
|
||||||
* Uses Object.defineProperty to bypass "getter-only" restrictions on window.fetch
|
|
||||||
* in certain sandboxed or strict environments.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const originalFetch = window.fetch;
|
const originalFetch = window.fetch;
|
||||||
|
|
||||||
/**
|
|
||||||
* Robustly applies a function to the window.fetch property.
|
|
||||||
*/
|
|
||||||
const applyFetch = (fn: typeof window.fetch) => {
|
const applyFetch = (fn: typeof window.fetch) => {
|
||||||
try {
|
try {
|
||||||
Object.defineProperty(window, 'fetch', {
|
Object.defineProperty(window, 'fetch', {
|
||||||
@@ -22,7 +9,6 @@ const applyFetch = (fn: typeof window.fetch) => {
|
|||||||
enumerable: true
|
enumerable: true
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Fallback for environments where defineProperty on window might fail
|
|
||||||
try {
|
try {
|
||||||
(window as any).fetch = fn;
|
(window as any).fetch = fn;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -33,15 +19,12 @@ const applyFetch = (fn: typeof window.fetch) => {
|
|||||||
|
|
||||||
export const setInterceptorUrl = (baseUrl: string | null) => {
|
export const setInterceptorUrl = (baseUrl: string | null) => {
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
// Restore original fetch when disabled
|
|
||||||
applyFetch(originalFetch);
|
applyFetch(originalFetch);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize the base URL
|
|
||||||
let normalizedBase = baseUrl.trim();
|
let normalizedBase = baseUrl.trim();
|
||||||
try {
|
try {
|
||||||
// Basic validation
|
|
||||||
new URL(normalizedBase);
|
new URL(normalizedBase);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("[Prisma] Invalid Base URL provided:", normalizedBase);
|
console.warn("[Prisma] Invalid Base URL provided:", normalizedBase);
|
||||||
@@ -65,28 +48,22 @@ export const setInterceptorUrl = (baseUrl: string | null) => {
|
|||||||
|
|
||||||
const defaultHost = 'generativelanguage.googleapis.com';
|
const defaultHost = 'generativelanguage.googleapis.com';
|
||||||
|
|
||||||
// Check if the request is targeting the Google Gemini API
|
|
||||||
if (urlString.includes(defaultHost)) {
|
if (urlString.includes(defaultHost)) {
|
||||||
try {
|
try {
|
||||||
const url = new URL(urlString);
|
const url = new URL(urlString);
|
||||||
const proxy = new URL(normalizedBase);
|
const proxy = new URL(normalizedBase);
|
||||||
|
|
||||||
// Replace protocol and host
|
|
||||||
url.protocol = proxy.protocol;
|
url.protocol = proxy.protocol;
|
||||||
url.host = proxy.host;
|
url.host = proxy.host;
|
||||||
|
|
||||||
// Prepend proxy path if it exists (e.g., /v1/proxy)
|
|
||||||
if (proxy.pathname !== '/') {
|
if (proxy.pathname !== '/') {
|
||||||
const cleanPath = proxy.pathname.endsWith('/') ? proxy.pathname.slice(0, -1) : proxy.pathname;
|
const cleanPath = proxy.pathname.endsWith('/') ? proxy.pathname.slice(0, -1) : proxy.pathname;
|
||||||
// Ensure we don't double up slashes
|
|
||||||
url.pathname = cleanPath + url.pathname;
|
url.pathname = cleanPath + url.pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newUrl = url.toString();
|
const newUrl = url.toString();
|
||||||
|
|
||||||
// Handle the different types of fetch inputs
|
|
||||||
if (input instanceof Request) {
|
if (input instanceof Request) {
|
||||||
// Re-create the request with the new URL and original properties
|
|
||||||
const requestData: RequestInit = {
|
const requestData: RequestInit = {
|
||||||
method: input.method,
|
method: input.method,
|
||||||
headers: input.headers,
|
headers: input.headers,
|
||||||
@@ -99,9 +76,7 @@ export const setInterceptorUrl = (baseUrl: string | null) => {
|
|||||||
integrity: input.integrity,
|
integrity: input.integrity,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Merge with init if provided
|
|
||||||
const mergedInit = { ...requestData, ...init };
|
const mergedInit = { ...requestData, ...init };
|
||||||
|
|
||||||
return originalFetch(new URL(newUrl), mergedInit);
|
return originalFetch(new URL(newUrl), mergedInit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,149 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { defineConfig, loadEnv } from 'vite';
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
|
import type { Connect } from 'vite';
|
||||||
|
|
||||||
|
// Custom middleware to handle dynamic proxy for /custom-api
|
||||||
|
function customApiProxyMiddleware(): Connect.NextHandleFunction {
|
||||||
|
return async (req, res, next) => {
|
||||||
|
if (!req.url?.startsWith('/custom-api')) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetUrl = req.headers['x-target-url'] as string;
|
||||||
|
if (!targetUrl) {
|
||||||
|
res.statusCode = 400;
|
||||||
|
res.end(JSON.stringify({ error: 'Missing X-Target-URL header' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(targetUrl);
|
||||||
|
const targetPath = req.url.replace(/^\/custom-api/, '');
|
||||||
|
const fullUrl = `${url.origin}${targetPath}`;
|
||||||
|
|
||||||
|
console.log('[Custom Proxy] Forwarding:', req.method, req.url, '->', fullUrl);
|
||||||
|
|
||||||
|
// Collect request body
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
for await (const chunk of req) {
|
||||||
|
chunks.push(chunk as Buffer);
|
||||||
|
}
|
||||||
|
const body = Buffer.concat(chunks);
|
||||||
|
|
||||||
|
// Forward headers (excluding hop-by-hop headers)
|
||||||
|
const forwardHeaders: Record<string, string> = {};
|
||||||
|
const skipHeaders = ['host', 'connection', 'x-target-url', 'transfer-encoding'];
|
||||||
|
for (const [key, value] of Object.entries(req.headers)) {
|
||||||
|
if (!skipHeaders.includes(key.toLowerCase()) && value) {
|
||||||
|
forwardHeaders[key] = Array.isArray(value) ? value[0] : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
forwardHeaders['host'] = url.host;
|
||||||
|
|
||||||
|
// Make the request
|
||||||
|
const response = await fetch(fullUrl, {
|
||||||
|
method: req.method,
|
||||||
|
headers: forwardHeaders,
|
||||||
|
body: ['GET', 'HEAD'].includes(req.method || '') ? undefined : body,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Forward response status and headers
|
||||||
|
res.statusCode = response.status;
|
||||||
|
response.headers.forEach((value, key) => {
|
||||||
|
if (!['transfer-encoding', 'connection'].includes(key.toLowerCase())) {
|
||||||
|
res.setHeader(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stream the response body
|
||||||
|
if (response.body) {
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
res.write(value);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
reader.releaseLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('[Custom Proxy] Error:', error.message);
|
||||||
|
res.statusCode = 502;
|
||||||
|
res.end(JSON.stringify({ error: 'Proxy error', message: error.message }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, '.', '');
|
const env = loadEnv(mode, '.', '');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
|
proxy: {
|
||||||
|
// Proxy for OpenAI API
|
||||||
|
'/openai/v1': {
|
||||||
|
target: 'https://api.openai.com',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/openai\/v1/, '/v1'),
|
||||||
|
},
|
||||||
|
// Proxy for DeepSeek API
|
||||||
|
'/deepseek/v1': {
|
||||||
|
target: 'https://api.deepseek.com',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/deepseek\/v1/, '/v1'),
|
||||||
|
},
|
||||||
|
// Proxy for Anthropic API
|
||||||
|
'/anthropic/v1': {
|
||||||
|
target: 'https://api.anthropic.com',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/anthropic\/v1/, '/v1'),
|
||||||
|
},
|
||||||
|
// Proxy for xAI API
|
||||||
|
'/xai/v1': {
|
||||||
|
target: 'https://api.x.ai',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/xai\/v1/, '/v1'),
|
||||||
|
},
|
||||||
|
// Proxy for Mistral API
|
||||||
|
'/mistral/v1': {
|
||||||
|
target: 'https://api.mistral.ai',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/mistral\/v1/, '/v1'),
|
||||||
|
},
|
||||||
|
// Proxy for Google Gemini API
|
||||||
|
'/v1beta/models': {
|
||||||
|
target: 'https://generativelanguage.googleapis.com',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true,
|
||||||
|
},
|
||||||
|
'/v1/models': {
|
||||||
|
target: 'https://generativelanguage.googleapis.com',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
plugins: [react()],
|
plugins: [
|
||||||
|
react(),
|
||||||
|
{
|
||||||
|
name: 'custom-api-proxy',
|
||||||
|
configureServer(server) {
|
||||||
|
server.middlewares.use(customApiProxyMiddleware());
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
define: {
|
define: {
|
||||||
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
||||||
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
||||||
|
|||||||
Reference in New Issue
Block a user