diff --git a/.env.example b/.env.example deleted file mode 100644 index bc46601..0000000 --- a/.env.example +++ /dev/null @@ -1,7 +0,0 @@ -# API Base URL -# 默认值: https://thinkflow.lz-t.top -API_BASE_URL=https://thinkflow.lz-t.top - -# API Host (用于 Nginx 代理的 Host 头) -# 默认值: thinkflow.lz-t.top -API_HOST=thinkflow.lz-t.top diff --git a/Dockerfile b/Dockerfile index 37959ff..9292931 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,16 +10,24 @@ COPY . . RUN npm run build -FROM nginx:alpine +FROM node:20-alpine -COPY --from=builder /app/dist /usr/share/nginx/html +WORKDIR /app -COPY nginx.conf.template /etc/nginx/templates/nginx.conf.template +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/proxy-server.js ./ -COPY entrypoint.sh /docker-entrypoint.sh +RUN npm install express cors http-proxy-middleware -RUN chmod +x /docker-entrypoint.sh +RUN apk add --no-cache nginx -EXPOSE 80 +COPY nginx.conf /etc/nginx/conf.d/default.conf -CMD ["/docker-entrypoint.sh"] +COPY start.sh /start.sh + +RUN chmod +x /start.sh + +EXPOSE 80 3000 + +CMD ["/start.sh"] diff --git a/Dockerfile.dev b/Dockerfile.dev deleted file mode 100644 index 61d8331..0000000 --- a/Dockerfile.dev +++ /dev/null @@ -1,13 +0,0 @@ -FROM node:20-alpine - -WORKDIR /app - -COPY package*.json ./ - -RUN npm install - -COPY . . - -EXPOSE 5173 - -CMD ["npm", "run", "dev", "--", "--host"] diff --git a/docker-compose.yml b/docker-compose.yml index 9b0bdb9..85e8e31 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,4 @@ services: container_name: thinkflow ports: - "80:80" - environment: - - API_BASE_URL=${API_BASE_URL:-https://thinkflow.lz-t.top} - - API_HOST=${API_HOST:-thinkflow.lz-t.top} restart: unless-stopped diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100644 index 9af4537..0000000 --- a/entrypoint.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -envsubst '${API_BASE_URL} ${API_HOST}' < /etc/nginx/templates/nginx.conf.template > /etc/nginx/conf.d/default.conf - -exec nginx -g 'daemon off;' diff --git a/nginx.conf b/nginx.conf index b4a3624..d47a85d 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,7 +1,7 @@ server { listen 80; server_name localhost; - root /usr/share/nginx/html; + root /app/dist; index index.html; location / { @@ -9,16 +9,16 @@ server { } location /api/chat { - proxy_pass https://thinkflow.lz-t.top/chat/completions; - proxy_set_header Host thinkflow.lz-t.top; + proxy_pass http://localhost:3000/api/chat; + proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /api/image { - proxy_pass https://thinkflow.lz-t.top/images/generations; - proxy_set_header Host thinkflow.lz-t.top; + proxy_pass http://localhost:3000/api/image; + proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; diff --git a/nginx.conf.template b/nginx.conf.template deleted file mode 100644 index 73ec9d3..0000000 --- a/nginx.conf.template +++ /dev/null @@ -1,29 +0,0 @@ -server { - listen 80; - server_name localhost; - root /usr/share/nginx/html; - index index.html; - - location / { - try_files $uri $uri/ /index.html; - } - - location /api/chat { - proxy_pass ${API_BASE_URL}/chat/completions; - proxy_set_header Host ${API_HOST}; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /api/image { - proxy_pass ${API_BASE_URL}/images/generations; - proxy_set_header Host ${API_HOST}; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - gzip on; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; -} diff --git a/package.json b/package.json index 4efbf51..7cd49ff 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,10 @@ "@vue-flow/minimap": "^1.5.4", "@vue-flow/node-resizer": "^1.5.0", "axios": "^1.6.7", + "cors": "^2.8.5", + "express": "^4.18.2", "html-to-image": "^1.11.13", + "http-proxy-middleware": "^2.0.6", "lucide-vue-next": "^0.322.0", "markdown-it": "^14.1.0", "vue": "^3.4.15", diff --git a/proxy-server.js b/proxy-server.js new file mode 100644 index 0000000..126871a --- /dev/null +++ b/proxy-server.js @@ -0,0 +1,45 @@ +const express = require('express'); +const { createProxyMiddleware } = require('http-proxy-middleware'); +const cors = require('cors'); + +const app = express(); +const PORT = process.env.PROXY_PORT || 3000; + +app.use(cors()); +app.use(express.json()); + +app.use('/api/chat', createProxyMiddleware({ + target: 'https://thinkflow.lz-t.top', + changeOrigin: true, + pathRewrite: { + '^/api/chat': '/chat/completions' + }, + onProxyReq: (proxyReq, req, res) => { + const customBaseUrl = req.headers['x-custom-base-url']; + if (customBaseUrl) { + const url = new URL(customBaseUrl); + proxyReq.path = url.pathname; + proxyReq.setHeader('Host', url.host); + } + } +})); + +app.use('/api/image', createProxyMiddleware({ + target: 'https://thinkflow.lz-t.top', + changeOrigin: true, + pathRewrite: { + '^/api/image': '/images/generations' + }, + onProxyReq: (proxyReq, req, res) => { + const customBaseUrl = req.headers['x-custom-base-url']; + if (customBaseUrl) { + const url = new URL(customBaseUrl); + proxyReq.path = url.pathname; + proxyReq.setHeader('Host', url.host); + } + } +})); + +app.listen(PORT, () => { + console.log(`Proxy server running on port ${PORT}`); +}); diff --git a/src/composables/useThinkFlow.ts b/src/composables/useThinkFlow.ts index 25b2c63..672fe86 100644 --- a/src/composables/useThinkFlow.ts +++ b/src/composables/useThinkFlow.ts @@ -761,12 +761,18 @@ export function useThinkFlow({ t, locale }: { t: Translate; locale: Ref const finalApiKey = apiConfig.mode === 'default' ? useConfig.apiKey || API_KEY : useConfig.apiKey try { + const headers: any = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${finalApiKey}` + } + + if (apiConfig.mode !== 'default' && useConfig.baseUrl) { + headers['X-Custom-Base-Url'] = useConfig.baseUrl + } + const response = await fetch(useConfig.baseUrl, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${finalApiKey}` - }, + headers, body: JSON.stringify({ model: useConfig.model, messages: [ @@ -813,12 +819,19 @@ export function useThinkFlow({ t, locale }: { t: Translate; locale: Ref const detail = node.data.description || '' const path = findPathToNode(nodeId) const context = path.length > 5 ? `... -> ${path.slice(-4).join(' -> ')}` : path.join(' -> ') + + const headers: any = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${finalApiKey}` + } + + if (apiConfig.mode !== 'default' && useConfig.baseUrl) { + headers['X-Custom-Base-Url'] = useConfig.baseUrl + } + const response = await fetch(useConfig.baseUrl, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${finalApiKey}` - }, + headers, body: JSON.stringify({ model: useConfig.model, prompt: t('prompts.image', { topic, detail, context }) @@ -869,12 +882,18 @@ export function useThinkFlow({ t, locale }: { t: Translate; locale: Ref const path = findPathToNode(nodeId) const context = path.join(' -> ') + const headers: any = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${finalApiKey}` + } + + if (apiConfig.mode !== 'default' && useConfig.baseUrl) { + headers['X-Custom-Base-Url'] = useConfig.baseUrl + } + const response = await fetch(useConfig.baseUrl, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${finalApiKey}` - }, + headers, body: JSON.stringify({ model: useConfig.model, messages: [{ role: 'user', content: t('prompts.deepDivePrompt', { rootTopic, context, topic, detail }) }] @@ -1031,12 +1050,18 @@ export function useThinkFlow({ t, locale }: { t: Translate; locale: Ref const finalApiKey = apiConfig.mode === 'default' ? useConfig.apiKey || API_KEY : useConfig.apiKey try { + const headers: any = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${finalApiKey}` + } + + if (apiConfig.mode !== 'default' && useConfig.baseUrl) { + headers['X-Custom-Base-Url'] = useConfig.baseUrl + } + const response = await fetch(useConfig.baseUrl, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${finalApiKey}` - }, + headers, body: JSON.stringify({ model: useConfig.model, messages: [ diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..e085a19 --- /dev/null +++ b/start.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +node proxy-server.js & + +nginx -g 'daemon off;'