diff --git a/README.md b/README.md index 372f6851c..f9100506f 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ ![cover](./docs/mj2a1.jpg) ## 支持功能 +- [x] 支持 pika 文生视频,图生视频 - [x] 支持 openai realtime [点击观看.youtube](https://www.youtube.com/watch?v=pKvVi3oBRQU) [B站](https://www.bilibili.com/video/BV1Kt22YPE2c/) - [x] 支持 kling 可灵 文生视频,图生视频, 绘图 - [x] 支持 ideogram 绘图 @@ -17,6 +18,7 @@ - [x] 支持 viggle 舞蹈 - [x] 支持 suno 单独模块,可歌词调整 曲风调整 - [x] 支持 suno 以音频生成音频 +- [x] 支持 flux dall.e 文生图 - [x] 原chatgpt web 所有功能 - [x] chatgpt web 支持自定义api key、base_url - [x] midjourney 文生图、垫图+文生图 、图变 U1到U4 、 V1到V4、重绘等操作 @@ -75,7 +77,7 @@ | AUTH_SECRET_ERROR_TIME | 防爆破验证:停留时间 单位分钟 | 无 | ✅ | x| | CLOSE_MD_PREVIEW | 是否不关闭输入预览 | 无 | ✅ | ✅| | UPLOAD_TYPE | 指定上传方式 [`R2` R2上传] [`API` 跟随UI前端中转]、[`Container` 本地容器]、[`MyUrl` 自定义链接] | 空 | ✅ | x| -| MENU_DISABLE | 菜单禁用 可选:gpts,draws,gallery,music,video,dance | 空 | ✅ | ✅| +| MENU_DISABLE | 菜单禁用 可选:gpts,draws,gallery,music,video,dance,realtime | 空 | ✅ | ✅| | VISION_MODEL | 默认使用的识图 可选:`gpt-4o`,`gpt-4-turb`,`gpt-4-vision-preview`等 | 空 | ✅ | ✅| | SYSTEM_MESSAGE | 自定义默认角色消息 | 空 | ✅ | ✅| | CUSTOM_VISION_MODELS | 自定义可视图模型 用`,` 分开 | 空 | ✅ | ✅| diff --git a/changlog.md b/changlog.md index cc766a97c..8164c9b4c 100644 --- a/changlog.md +++ b/changlog.md @@ -1,6 +1,10 @@ # 功能升级日志 # 计划 +# 2.21.6 +- 😄 新增:pika 文生视频 图生视频 +- 🐞 修复:luma 支持长宽比例 + # 2.21.5 - 🐞 修复:luam、runway视频删除、viggle删除、kling删除、suno删除? #510 diff --git a/package.json b/package.json index 740c3b91b..91ecaeb03 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chatgpt-web-midjourney-proxy", - "version": "2.21.5", + "version": "2.21.6", "private": false, "description": "ChatGPT Web Midjourney Proxy", "author": "Dooy ", diff --git a/service/.env.example b/service/.env.example index 34d4fa4a1..af33e441b 100644 --- a/service/.env.example +++ b/service/.env.example @@ -127,4 +127,12 @@ SYSTEM_MESSAGE=“You are ChatGPT, a large language model trained by OpenAI.” #LUMA_SERVER #luma api的服务器 LUMA_SERVER=https://your-luma-server #LUMA_KEY #luma api的key -LUMA_KEY=xxxx \ No newline at end of file +LUMA_KEY=xxxx + +#PIKA设置 +#PIKA_SERVER= +#LUMA_KEY=xxxx + +#ideogram设置 +#IDEO_SERVER= +#IDEO_KEY=xxxx \ No newline at end of file diff --git a/service/src/index.ts b/service/src/index.ts index 7b16a944e..cf4fbfed6 100644 --- a/service/src/index.ts +++ b/service/src/index.ts @@ -17,7 +17,7 @@ import FormData from 'form-data' import axios from 'axios'; import AWS from 'aws-sdk'; import { v4 as uuidv4} from 'uuid'; -import { viggleProxyFileDo,viggleProxy, lumaProxy, runwayProxy, ideoProxy, ideoProxyFileDo, klingProxy } from './myfun' +import { viggleProxyFileDo,viggleProxy, lumaProxy, runwayProxy, ideoProxy, ideoProxyFileDo, klingProxy, pikaProxy } from './myfun' const app = express() @@ -352,6 +352,7 @@ app.use('/kling' ,authV2, klingProxy ); app.use('/ideogram/remix' ,authV2, upload2.single('image_file'), ideoProxyFileDo ); app.use('/ideogram' ,authV2, ideoProxy ); +app.use('/pika' ,authV2, pikaProxy ); diff --git a/service/src/myfun.ts b/service/src/myfun.ts index 0297b4020..c375f81a6 100644 --- a/service/src/myfun.ts +++ b/service/src/myfun.ts @@ -89,6 +89,23 @@ export const ideoProxy=proxy(process.env.IDEO_SERVER?? API_BASE_URL, { }) +export const pikaProxy=proxy(process.env.PIKA_SERVER?? API_BASE_URL, { + https: false, limit: '10mb', + proxyReqPathResolver: function (req) { + return req.originalUrl //req.originalUrl.replace('/sunoapi', '') // 将URL中的 `/openapi` 替换为空字符串 + }, + proxyReqOptDecorator: function (proxyReqOpts, srcReq) { + if ( process.env.PIKA_KEY ) proxyReqOpts.headers['Authorization'] ='Bearer '+process.env.PIKA_KEY; + else proxyReqOpts.headers['Authorization'] ='Bearer '+process.env.OPENAI_API_KEY; + proxyReqOpts.headers['Content-Type'] = 'application/json'; + proxyReqOpts.headers['Mj-Version'] = pkg.version; + return proxyReqOpts; + }, + +}) + + + //req, res, next export const ideoProxyFileDo=async( req:Request, res:Response, next?:NextFunction)=>{ console.log('req.originalUrl', req.originalUrl ); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index a21e35a8f..c95926381 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "ChatGPT-MJ", - "version": "2.21.5" + "version": "2.21.6" }, "tauri": { "allowlist": { diff --git a/src/api/openapi.ts b/src/api/openapi.ts index d5b7b8798..0c78dd143 100644 --- a/src/api/openapi.ts +++ b/src/api/openapi.ts @@ -528,6 +528,7 @@ export const openaiSetting= ( q:any,ms:MessageApiInjection )=>{ VIGGLE_SERVER:url, IDEO_SERVER:url, KLING_SERVER:url, + PIKA_SERVER:url, OPENAI_API_KEY:key, MJ_API_SECRET:key, @@ -537,6 +538,7 @@ export const openaiSetting= ( q:any,ms:MessageApiInjection )=>{ VIGGLE_KEY:key, IDEO_KEY:key, KLING_KEY:key, + PIKA_KEY:key, } ) blurClean(); gptServerStore.setMyData( gptServerStore.myData ); diff --git a/src/api/pika.ts b/src/api/pika.ts new file mode 100644 index 000000000..33a5b8cea --- /dev/null +++ b/src/api/pika.ts @@ -0,0 +1,118 @@ +import { gptServerStore, homeStore, useAuthStore } from "@/store"; +import { mlog } from "./mjapi"; +import { sleep } from "./suno"; +import { RunwayTask, runwayStore } from "./runwayStore"; +import { PikaTask, pikaStore } from "./pikaStore"; + +function getHeaderAuthorization(){ + let headers={} + if( homeStore.myData.vtoken ){ + const vtokenh={ 'x-vtoken': homeStore.myData.vtoken ,'x-ctoken': homeStore.myData.ctoken}; + headers= {...headers, ...vtokenh} + } + if(!gptServerStore.myData.RUNWAY_KEY){ + const authStore = useAuthStore() + if( authStore.token ) { + const bmi= { 'x-ptoken': authStore.token }; + headers= {...headers, ...bmi } + return headers; + } + return headers + } + const bmi={ + 'Authorization': 'Bearer ' +gptServerStore.myData.PIKA_KEY + } + headers= {...headers, ...bmi } + return headers +} + +export const getUrl=(url:string)=>{ + if(url.indexOf('http')==0) return url; + + const pro_prefix= url.indexOf('/pro')>-1?'/pro':'';//homeStore.myData.is_luma_pro?'/pro':'' + url= url.replaceAll('/pro','') + if(gptServerStore.myData.PIKA_SERVER ){ + if(gptServerStore.myData.PIKA_SERVER.indexOf('/pro')>0){ + return `${ gptServerStore.myData.PIKA_SERVER}/pika${url}`; + } + return `${ gptServerStore.myData.PIKA_SERVER}${pro_prefix}/pika${url}`; + } + return `${pro_prefix}/pika${url}`; +} + + +export const pikaFetch=(url:string,data?:any,opt2?:any )=>{ + mlog('pikaFetch', url ); + let headers= opt2?.upFile?{}: {'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(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( async (d) =>{ + if (!d.ok) { + let msg = '发生错误: '+ d.status + try{ + let bjson:any = await d.json(); + msg = '('+ d.status+')发生错误: '+(bjson?.error?.message??'' ) + }catch( e ){ + } + homeStore.myData.ms && homeStore.myData.ms.error(msg ) + throw new Error( msg ); + } + + d.json().then(d=> resolve(d)).catch(e=>{ + + homeStore.myData.ms && homeStore.myData.ms.error('发生错误'+ e ) + reject(e) + } + )}) + .catch(e=>{ + if (e.name === 'TypeError' && e.message === 'Failed to fetch') { + homeStore.myData.ms && homeStore.myData.ms.error('跨域|CORS error' ) + } + else homeStore.myData.ms && homeStore.myData.ms.error('发生错误:'+e ) + mlog('e', e.stat ) + reject(e) + }) + }) + +} + +export const pikaFeed= async(id:string)=>{ + const sunoS = new pikaStore(); + for(let i=0; i<200;i++){ + try{ + let a= await pikaFetch('/feed/' +id ) + let task= a as PikaTask; + mlog("task",a ) + if(!task.videos || task.videos.length==0) continue; + task.last_feed=new Date().getTime() + + + sunoS.save( task ) + homeStore.setMyData({act:'PikaFeed'}); + if( task.videos[0].status=='error' || 'finished'== task.videos[0].status ){ + break; + } + }catch(e){ + } + await sleep(5200) + } + +} + + \ No newline at end of file diff --git a/src/api/pikaStore.ts b/src/api/pikaStore.ts new file mode 100644 index 000000000..0454938a4 --- /dev/null +++ b/src/api/pikaStore.ts @@ -0,0 +1,59 @@ +import { ss } from "@/utils/storage"; +interface Video { + id: string; + status: string; + seed: number; + resultUrl: string; + sharingUrl: string; + videoPoster: string; + imageThumb: string; + duration: number; + error: string; + progress: number; +} + +// 定义主结构体接口 +export interface PikaTask { + id: string; + promptText: string; + videos: Video[]; + last_feed?:number; +} + +export class pikaStore{ + //private id: string; + private localKey='pika-store'; + public save(obj:PikaTask ){ + if(!obj.id ) throw "taskID must"; + let arr= this.getObjs(); + let i= arr.findIndex( v=>v.id==obj.id ); + if(i>-1) arr[i]= obj; + else arr.push(obj); + ss.set(this.localKey, arr ); + return this; + } + public findIndex(id:string){ + return this.getObjs().findIndex( v=>v.id== id ) + } + + public getObjs():PikaTask[]{ + const obj = ss.get( this.localKey ) as undefined| PikaTask[]; + if(!obj) return []; + return obj; + } + public getOneById(id:string):PikaTask|null{ + const i= this.findIndex(id) + if(i<0) return null; + let arr= this.getObjs(); + return arr[i] + } + public delete( obj:PikaTask ){ + if(!obj.id ) throw "id must"; + let arr= this.getObjs(); + let i= arr.findIndex( v=>v.id==obj.id ); + if(i<0) return false + arr.splice(i, 1); + ss.set(this.localKey, arr ); + return true; + } +} \ No newline at end of file diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 7104f69b8..080c1f00e 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -232,8 +232,12 @@ export default { rtservererror: 'WebSocket connection server error!', rtservererror2: 'Recording is not supported, it may be due to device reasons!', rtconecting: 'Connecting to the server' - ,confirmDelete:'Are you sure?' - + ,confirmDelete:'Are you sure?', + pikaabout: 'About Pika', + pikaserver: 'Pika API Address', + pikakeyPlaceholder: 'Pika API Key (optional)', + createFail: 'Creation failed', + selecteff: 'Reference Effect' }, "mjset": { "server": "Server", diff --git a/src/locales/fr-FR.ts b/src/locales/fr-FR.ts index c72ec0c37..5ff04ac5c 100644 --- a/src/locales/fr-FR.ts +++ b/src/locales/fr-FR.ts @@ -229,7 +229,13 @@ export default { rtsuccess: 'Connexion normale, maintien de l\'appel', rtservererror: 'Erreur de connexion au serveur WebSocket !', rtservererror2: 'Enregistrement non pris en charge, cela peut être dû à un problème de matériel !', -rtconecting: 'Connexion au serveur en cours' + rtconecting: 'Connexion au serveur en cours', + "confirmDelete": "Êtes-vous sûr de vouloir supprimer ?", + "pikaabout": "À propos de Pika", + "pikaserver": "Adresse API Pika", + "pikakeyPlaceholder": "Clé API Pika (facultatif)", + "createFail": "Échec de la création", + "selecteff": "Effet de référence" }, "mjset": { "server": "Serveur", diff --git a/src/locales/ko-KR.ts b/src/locales/ko-KR.ts index 8752c17ef..5c5f88a8b 100644 --- a/src/locales/ko-KR.ts +++ b/src/locales/ko-KR.ts @@ -227,7 +227,15 @@ export default { rtsuccess: '연결이 정상이며 통화를 유지하고 있습니다', rtservererror: 'WebSocket 서버 연결 오류!', rtservererror2: '녹음이 지원되지 않습니다. 장치 문제일 수 있습니다!', - rtconecting: '서버에 연결 중' + rtconecting: '서버에 연결 중', + + "confirmDelete": "삭제하시겠습니까?", + "pikaabout": "Pika 관련", + "pikaserver": "Pika API 주소", + "pikakeyPlaceholder": "Pika API 키 (선택 사항)", + "createFail": "생성 실패", + "selecteff": "참고 효과" + }, "mjset": { "server": "서버" diff --git a/src/locales/ru-RU.ts b/src/locales/ru-RU.ts index 231a56040..06d600046 100644 --- a/src/locales/ru-RU.ts +++ b/src/locales/ru-RU.ts @@ -216,22 +216,28 @@ export default { seed: 'Число семени 1~2147483647', klingInfo: 'Описание:
  • 1. Высокое качество стоит в 3.5 раза дороже
  • 2. 10 секунд стоит в 2 раза дороже
  • 3. Последний кадр должен иметь эталонное изображение для действия
  • ' ,"camera_type": "Объектив", -"cnull": "Умное соответствие", -"down_back": "Опустить и отдалить", -"forward_up": "Продвинуть вперед и поднять", -"right_turn_forward": "Повернуть вправо и продвинуться вперед", -"left_turn_forward": "Повернуть влево и продвинуться вперед" -,kling:'Kling', -rttab: 'Голос', -rtinfo: 'Служба голосового общения в реальном времени (realtime)', -rtsetting: 'Пожалуйста, настройте сервер. В настоящее время Realtime поддерживает только удаленные службы; для локальных услуг свяжитесь с автором.', -rjcloded: 'Соединение разорвано', -checkkey: 'Пожалуйста, проверьте правильность API-ключа', -rtsuccess: 'Соединение нормально, поддерживаем звонок', -rtservererror: 'Ошибка подключения к серверу WebSocket!', -rtservererror2: 'Запись не поддерживается, возможно, это связано с устройством!', -rtconecting: 'Подключение к серверу' + "cnull": "Умное соответствие", + "down_back": "Опустить и отдалить", + "forward_up": "Продвинуть вперед и поднять", + "right_turn_forward": "Повернуть вправо и продвинуться вперед", + "left_turn_forward": "Повернуть влево и продвинуться вперед" + ,kling:'Kling', + rttab: 'Голос', + rtinfo: 'Служба голосового общения в реальном времени (realtime)', + rtsetting: 'Пожалуйста, настройте сервер. В настоящее время Realtime поддерживает только удаленные службы; для локальных услуг свяжитесь с автором.', + rjcloded: 'Соединение разорвано', + checkkey: 'Пожалуйста, проверьте правильность API-ключа', + rtsuccess: 'Соединение нормально, поддерживаем звонок', + rtservererror: 'Ошибка подключения к серверу WebSocket!', + rtservererror2: 'Запись не поддерживается, возможно, это связано с устройством!', + rtconecting: 'Подключение к серверу' + ,"confirmDelete": "Вы уверены, что хотите удалить?", + "pikaabout": "О Pika", + "pikaserver": "Адрес API Pika", + "pikakeyPlaceholder": "API-ключ Pika (необязательно)", + "createFail": "Не удалось создать", + "selecteff": "Эффект справки" }, "mjset": { "server": "Сервер", diff --git a/src/locales/tr-TR.ts b/src/locales/tr-TR.ts index 182dad507..d4391e972 100644 --- a/src/locales/tr-TR.ts +++ b/src/locales/tr-TR.ts @@ -229,7 +229,13 @@ export default { rtsuccess: 'Bağlantı normal, görüşmeyi sürdürüyor', rtservererror: 'WebSocket sunucu bağlantı hatası!', rtservererror2: 'Kaydetme desteklenmiyor, bu cihaz kaynaklı bir sorun olabilir!', - rtconecting: 'Sunucuya bağlanılıyor' + rtconecting: 'Sunucuya bağlanılıyor', + "confirmDelete": "Silmek istediğinize emin misiniz?", + "pikaabout": "Pika Hakkında", + "pikaserver": "Pika API Adresi", + "pikakeyPlaceholder": "Pika API Anahtarı (isteğe bağlı)", + "createFail": "Oluşturma başarısız", + "selecteff": "Referans Etkisi" }, "mjset": { "server": "Sunucu", diff --git a/src/locales/vi-VN.ts b/src/locales/vi-VN.ts index c3b7c8185..17a19412d 100644 --- a/src/locales/vi-VN.ts +++ b/src/locales/vi-VN.ts @@ -228,7 +228,15 @@ export default { rtsuccess: 'Kết nối bình thường, duy trì cuộc gọi', rtservererror: 'Lỗi kết nối máy chủ WebSocket!', rtservererror2: 'Không hỗ trợ ghi âm, có thể do thiết bị!', - rtconecting: 'Đang kết nối đến máy chủ' + rtconecting: 'Đang kết nối đến máy chủ', + + "confirmDelete": "Bạn có chắc chắn muốn xóa không?", + "pikaabout": "Về Pika", + "pikaserver": "Địa chỉ API Pika", + "pikakeyPlaceholder": "Khóa API Pika (tùy chọn)", + "createFail": "Tạo không thành công", + "selecteff": "Hiệu ứng tham khảo" + }, "mjset": { "server": "Máy chủ", diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 3649be81e..6cbefba5c 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -338,6 +338,12 @@ export default { ,rtconecting:'正在连接服务器' ,confirmDelete:'确认要删除?' + ,pikaabout:'Pika 相关' + ,pikaserver:'Pika 接口地址' + ,pikakeyPlaceholder:'Pika 的API Key 可不填' + ,createFail:'生成失败' + ,selecteff:'参考效果' + }, draw: { diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 83a6996fd..2e471c80d 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -224,7 +224,13 @@ export default { rtsuccess: '連接正常保持通話', rtservererror: 'websocket連接服務器錯誤!', rtservererror2: '不支持錄音,可能是設備原因!', - rtconecting: '正在連接服務器' + rtconecting: '正在連接服務器', + "confirmDelete": "確認要刪除?", + "pikaabout": "Pika 相關", + "pikaserver": "Pika 接口地址", + "pikakeyPlaceholder": "Pika 的API Key 可不填", + "createFail": "生成失敗", + "selecteff": "參考效果" }, "mjset": { diff --git a/src/store/homeStore.ts b/src/store/homeStore.ts index 10110a0de..0d01ccd0d 100644 --- a/src/store/homeStore.ts +++ b/src/store/homeStore.ts @@ -112,6 +112,8 @@ export interface gptServerType{ IDEO_KEY:string KLING_SERVER:string KLING_KEY:string + PIKA_SERVER:string + PIKA_KEY:string IS_SET_SYNC?:boolean GPTS_GX?:boolean IS_LUMA_PRO?:boolean @@ -147,6 +149,8 @@ let v:gptServerType={ IDEO_KEY:'', KLING_SERVER:'', KLING_KEY:'', + PIKA_SERVER:'', + PIKA_KEY:'', TTS_VOICE:'alloy' } return v ; diff --git a/src/views/kling/kgInputVideo.vue b/src/views/kling/kgInputVideo.vue index 2785bb042..b6713826b 100644 --- a/src/views/kling/kgInputVideo.vue +++ b/src/views/kling/kgInputVideo.vue @@ -117,7 +117,7 @@ onMounted(() => {
    {{ $t('mj.nohead') }}
    - +
    diff --git a/src/views/luma/lumaInput.vue b/src/views/luma/lumaInput.vue index 51e7195d5..8b7cec50b 100644 --- a/src/views/luma/lumaInput.vue +++ b/src/views/luma/lumaInput.vue @@ -129,7 +129,7 @@ const mvOption= [