diff --git a/.gitignore b/.gitignore index 897b8d403..3ddaa0548 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ coverage # Environment variables files /service/.env +/service/uploads \ No newline at end of file diff --git a/service/.env.example b/service/.env.example index 0c787ef96..5f2da5c70 100644 --- a/service/.env.example +++ b/service/.env.example @@ -46,5 +46,10 @@ HTTPS_PROXY= MJ_SERVER= # MJ_API_SECRET -MJ_API_SECRET= +MJ_API_SECRET= +#API_UPLOADER 是否可以上传 1 可以其他都不可以 +API_UPLOADER= + +#HIDE_SERVER 隐藏服务端 1 +HIDE_SERVER= diff --git a/service/package.json b/service/package.json index 159762489..dbbc2de89 100644 --- a/service/package.json +++ b/service/package.json @@ -35,6 +35,7 @@ "http-proxy-middleware": "^2.0.6", "https-proxy-agent": "^5.0.1", "isomorphic-fetch": "^3.0.0", + "multer": "1.4.5-lts.1", "node-fetch": "^3.3.0", "socks-proxy-agent": "^7.0.0" }, diff --git a/service/pnpm-lock.yaml b/service/pnpm-lock.yaml index ee7ac0548..9c6809d2a 100644 --- a/service/pnpm-lock.yaml +++ b/service/pnpm-lock.yaml @@ -34,6 +34,9 @@ dependencies: isomorphic-fetch: specifier: ^3.0.0 version: 3.0.0 + multer: + specifier: 1.4.5-lts.1 + version: 1.4.5-lts.1 node-fetch: specifier: ^3.3.0 version: 3.3.0 @@ -749,6 +752,10 @@ packages: picomatch: 2.3.1 dev: true + /append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + dev: false + /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true @@ -916,6 +923,13 @@ packages: load-tsconfig: 0.2.3 dev: true + /busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + dependencies: + streamsearch: 1.1.0 + dev: false + /bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -1042,6 +1056,16 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true + /concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 2.3.8 + typedarray: 0.0.6 + dev: false + /conf@11.0.1: resolution: {integrity: sha512-WlLiQboEjKx0bYx2IIRGedBgNjLAxtwPaCSnsjWPST5xR0DB4q8lcsO/bEH9ZRYNcj63Y9vj/JG/5Fg6uWzI0Q==} engines: {node: '>=14.16'} @@ -1077,6 +1101,10 @@ packages: engines: {node: '>= 0.6'} dev: false + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2462,6 +2490,10 @@ packages: call-bind: 1.0.2 dev: true + /isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + dev: false + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -2724,13 +2756,19 @@ packages: /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: true /minipass@4.2.4: resolution: {integrity: sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==} engines: {node: '>=8'} dev: true + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: false + /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} dev: false @@ -2741,6 +2779,19 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + /multer@1.4.5-lts.1: + resolution: {integrity: sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==} + engines: {node: '>= 6.0.0'} + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 1.6.2 + mkdirp: 0.5.6 + object-assign: 4.1.1 + type-is: 1.6.18 + xtend: 4.0.2 + dev: false + /mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} dependencies: @@ -2828,7 +2879,6 @@ packages: /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - dev: true /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} @@ -3057,6 +3107,10 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: false + /proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -3152,6 +3206,18 @@ packages: type-fest: 2.19.0 dev: false + /readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: false + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -3247,6 +3313,10 @@ packages: queue-microtask: 1.2.3 dev: true + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: false + /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: false @@ -3417,6 +3487,11 @@ packages: engines: {node: '>= 0.8'} dev: false + /streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + dev: false + /string.prototype.trimend@1.0.6: resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} dependencies: @@ -3433,6 +3508,12 @@ packages: es-abstract: 1.21.1 dev: true + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + dev: false + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3657,6 +3738,10 @@ packages: is-typed-array: 1.1.10 dev: true + /typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + dev: false + /typescript@4.9.5: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} @@ -3690,7 +3775,6 @@ packages: /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: true /utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} @@ -3811,6 +3895,11 @@ packages: engines: {node: '>=12'} dev: true + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: false + /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} diff --git a/service/src/index.ts b/service/src/index.ts index 6de68b727..9e22a31dc 100644 --- a/service/src/index.ts +++ b/service/src/index.ts @@ -4,7 +4,10 @@ import type { ChatMessage } from './chatgpt' import { chatConfig, chatReplyProcess, currentModel } from './chatgpt' import { auth } from './middleware/auth' import { limiter } from './middleware/limiter' -import { isNotEmptyString } from './utils/is' +import { isNotEmptyString,formattedDate } from './utils/is' +import multer from "multer" +import path from "path" +import fs from "fs" // const { createProxyMiddleware } = require('http-proxy-middleware'); //import {createProxyMiddleware} from "http-proxy-middleware" import proxy from "express-http-proxy" @@ -18,6 +21,8 @@ app.use(express.static('public')) //app.use(express.json()) app.use(bodyParser.json({ limit: '10mb' })); //大文件传输 + + app.all('*', (_, res, next) => { res.header('Access-Control-Allow-Origin', '*') res.header('Access-Control-Allow-Headers', 'authorization, Content-Type') @@ -65,7 +70,9 @@ router.post('/session', async (req, res) => { try { const AUTH_SECRET_KEY = process.env.AUTH_SECRET_KEY const hasAuth = isNotEmptyString(AUTH_SECRET_KEY) - res.send({ status: 'Success', message: '', data: { auth: hasAuth, model: currentModel() } }) + const isUpload= isNotEmptyString( process.env.API_UPLOADER ) + const isHideServer= isNotEmptyString( process.env.HIDE_SERVER ) + res.send({ status: 'Success', message: '', data: {isHideServer,isUpload, auth: hasAuth, model: currentModel() } }) } catch (error) { res.send({ status: 'Fail', message: error.message, data: null }) @@ -106,6 +113,48 @@ app.use('/mjapi', proxy(process.env.MJ_SERVER?process.env.MJ_SERVER:'https://api })); + + +// 设置存储引擎和文件保存路径 +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + let uploadFolderPath=`./uploads/${formattedDate()}/`;//` + + console.log('dir', __dirname ) ; + + if(!fs.existsSync('./uploads/')) { + fs.mkdirSync('./uploads/'); + } + if(!fs.existsSync(uploadFolderPath)) { + fs.mkdirSync(uploadFolderPath); + } + cb(null, `uploads/${formattedDate()}/`); + }, + filename: function (req, file, cb) { + let filename= Date.now() + path.extname(file.originalname); + console.log( 'file', filename ); + cb(null, filename); + } +}); +const upload = multer({ storage: storage }); +// 处理文件上传的路由 +const isUpload= isNotEmptyString( process.env.API_UPLOADER ) +if(isUpload){ + app.use('/openapi/v1/upload', upload.single('file'), (req, res) => { + //res.send('文件上传成功!'); + res.setHeader('Content-type', 'application/json' ); + if(req.file.filename) res.json({ url:`/uploads/${formattedDate()}/${ req.file.filename }`,created:Date.now() }) + else res.json({ error:`uploader fail`,created:Date.now() }) + }); +}else { + app.use('/openapi/v1/upload', (req, res) => { + //res.send('文件上传成功!'); + res.json({ error:`server is no open uploader `,created:Date.now() }) + }); +} +app.use('/uploads', express.static('uploads')); + + //代理openai 接口 app.use('/openapi', proxy(API_BASE_URL, { https: false, limit: '10mb', @@ -117,8 +166,7 @@ app.use('/openapi', proxy(API_BASE_URL, { proxyReqOpts.headers['Content-Type'] = 'application/json'; return proxyReqOpts; }, - //limit: '10mb' - + //limit: '10mb' })); diff --git a/service/src/utils/is.ts b/service/src/utils/is.ts index c1253f21d..5b332ab4e 100644 --- a/service/src/utils/is.ts +++ b/service/src/utils/is.ts @@ -17,3 +17,12 @@ export function isBoolean(value: T | unknown): value is boole export function isFunction any | void | never>(value: T | unknown): value is T { return Object.prototype.toString.call(value) === '[object Function]' } + +export const formattedDate=()=>{ + const currentDate = new Date(); + const year = currentDate.getFullYear(); + const month = (currentDate.getMonth() + 1).toString().padStart(2, '0'); + const day = currentDate.getDate().toString().padStart(2, '0'); + return `${year}${month}${day}`; + +} \ No newline at end of file diff --git a/src/api/openapi.ts b/src/api/openapi.ts index 60efcb840..c1d427a47 100644 --- a/src/api/openapi.ts +++ b/src/api/openapi.ts @@ -2,6 +2,7 @@ import { gptConfigStore, gptServerStore, homeStore } from "@/store"; import { mlog } from "./mjapi"; import { fetchSSE } from "./sse/fetchsse"; +import axios from 'axios'; export const KnowledgeCutOffDate: Record = { default: "2021-09", @@ -17,17 +18,22 @@ const getUrl=(url:string)=>{ return `/openapi${url}`; } export const gptGetUrl = getUrl -export const gptFetch=(url:string,data?:any)=>{ +export const gptFetch=(url:string,data?:any,opt2?:any )=>{ mlog('gptFetch', url ); let headers= {'Content-Type':'application/json'} + if(opt2 && opt2.headers ) headers= opt2.headers; headers={...headers,...getHeaderAuthorization()} return new Promise((resolve, reject) => { let opt:RequestInit ={method:'GET'}; opt.headers= headers ; - if(data) { - opt.body= JSON.stringify(data) ; + if(opt2?.upFile ){ opt.method='POST'; + opt.body=data as FormData ; + } + else if(data) { + opt.body= JSON.stringify(data) ; + opt.method='POST'; } fetch(getUrl(url), opt ) .then(d=>d.json().then(d=> resolve(d)) @@ -37,6 +43,20 @@ export const gptFetch=(url:string,data?:any)=>{ } +export const GptUploader = ( url:string, FormData:FormData )=>{ + url= gptGetUrl( url ); + let headers= {'Content-Type': 'multipart/form-data'} + + headers={...headers,...getHeaderAuthorization()} + return new Promise((resolve, reject) => { + axios.post( url , FormData, { + headers + }).then(response => resolve(response.data ) + ).catch(error =>reject(error) ); + }) + +} + export const subGPT= async (data:any, chat:Chat.Chat )=>{ let d:any; let action= data.action; diff --git a/src/components/common/Setting/index.vue b/src/components/common/Setting/index.vue index 5ade9fdb7..667ee362f 100644 --- a/src/components/common/Setting/index.vue +++ b/src/components/common/Setting/index.vue @@ -6,7 +6,7 @@ import Advanced from './Advanced.vue' import aiModel from '@/views/mj/aiModel.vue' import aiSetServer from '@/views/mj/aiSetServer.vue' import About from './About.vue' -import { useAuthStore } from '@/store' +import { homeStore, useAuthStore } from '@/store' import { SvgIcon } from '@/components/common' interface Props { @@ -62,7 +62,7 @@ const show = computed({ - +