diff --git a/CHANGELOG.md b/CHANGELOG.md index 6123875c..33fee544 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,12 @@ ## v4.2.5 -- 功能优化:在代码右下角增加复制代码功能按钮,增加收起和展开代码功能。 +- 功能优化:在代码右下角增加复制代码功能按钮,增加收起和展开代码功能 - Bug 修复:修复 Shift + Enter 不换行的 Bug - Bug 修复:修复管理后台菜单添加页面的文本错误 +- Bug 修复:解决聊天页面异常退出不断重连的 bug +- 功能优化:把 Luma 和可灵视频生成页面整合成一个视频创作中心页面,统一管理视频任务 +- 功能新增:增加即梦 AI 专题页面,支持即梦官方原生 API 的图片和视频生成 🎉🎉🎉 ## v4.2.4 diff --git a/web/src/App.vue b/web/src/App.vue index 2875a867..41319f43 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -118,6 +118,18 @@ html, body { } } +.el-popper.is-customized { + /* 设置内边距以保证高度为32px */ + padding: 6px 12px; + background: linear-gradient(180deg, #e1bee7, #7e57c2); + color #fff +} + +.el-popper.is-customized .el-popper__arrow::before { + background: linear-gradient(180deg, #b39ddb, #7e57c2); + right: 0; +} + /* 省略显示 */ .ellipsis { overflow: hidden; diff --git a/web/src/assets/css/keling.styl b/web/src/assets/css/keling.styl deleted file mode 100644 index 0605d130..00000000 --- a/web/src/assets/css/keling.styl +++ /dev/null @@ -1,363 +0,0 @@ -.page-keling - display flex - min-height 100vh - :deep(.el-form-item__label) - color var(--text-theme-color) - .grid-content - // background-color #383838 - background var(--card-bg) - border-radius 8px - padding 8px 14px - display flex - cursor pointer - margin-bottom 10px - // border 1px solid #383838 - border 1px solid var(--chat-bg) - &:hover - border 1px solid var(--theme-border-hover) - .icon - width 20px - height 20px - margin-bottom 5px - .texts - margin-left 5px - margin-top 2px - color var(--text-theme-color) - .param-line.pt - padding-top 5px - padding-bottom 5px - .grid-content.active - // color #47fff1 - // background-color #585858 - border 1px solid var(--theme-border-hover) - .h-20 - height 4rem !important - .main-content - padding-right 1.5rem - padding-left 1.5rem - padding-bottom 1rem - flex 1 - background var(--chat-bg) - // width: 100%; - // padding 0 10px 10px 10px - color var(--text-theme-color) - overflow-x hidden - .camera-control - padding 10px - border-radius 4px - background var(--card-bg) - :deep(.el-form-item:last-child) - margin-bottom 0 !important - .title-tabs - :deep(.el-tabs__item.is-active) - color var(--theme-textcolor-normal) - font-size 18px - :deep(.el-tabs__item) - color var(--text-theme-color) - font-size 18px - .el-tabs - --el-tabs-header-height 55px - .el-tabs__item - color var(--text-theme-color) - font-size 18px - .el-tabs__item.is-active, .title-tabs .el-tabs__item.is-active - .title-tabs .el-tabs__active-bar - background-color var(--theme-textcolor-normal) - :deep(.el-textarea) - --el-input-focus-border-color var(--el-color-primary) - :deep(.el-textarea__inner) - background transparent - color var(--text-theme-color) - .el-input__wrapper - background transparent - padding 5px - .text - margin-bottom 10px - color #6b778c - font-size 15px - .param-line.pt - padding-top 5px - padding-bottom 5px - .form-item-inner - display flex - align-items center - .el-icon - margin-left 10px - .el-form-item__label - color var(--text-theme-color) - // 图片上传样式 - .img-inline - display flex - gap 20px - align-items center - .img-uploader - text-align center - :deep(.el-upload) - border 1px dashed var(--el-border-color) - border-radius 6px - cursor pointer - position relative - overflow hidden - width 120px - height 120px - line-height 120px - transition var(--el-transition-duration-fast) - margin-bottom 20px - &:hover - border-color var(--el-color-primary) - .el-icon.uploader-icon - font-size 28px - color #8c939d - width 100% - height 120px - text-align center - .img-list-box - display flex - .img-item - width 120px - position relative - margin-right 10px - .el-image - width 120px - height 120px - border-radius 5px - .el-button - position absolute - right 5px - top 5px - width 20px - height 20px - .el-row.text-info - width 100% - padding 10px 0 - .el-tag - margin-right 10px - // 提交按钮 - .submit-btn - display flex - margin 20px 0 - .el-button - width 200px - .video-list - .btn - margin-right 10px - border none - border-radius 5px - padding 5px 10px - cursor pointer - color var(--theme-text-color-primary) - background-color var(--btn-bg) - &:hover - opacity 0.7 - .list-box - padding 0 - .item - display flex - flex-flow row - align-items center - min-height 100px - padding 10px 15px - border-radius 10px - cursor pointer - margin-bottom 20px - background var(--chat-bg) - .left - .container - width 160px - position relative - max-height 120px - overflow hidden - display flex - justify-content center - align-items center - .video - width 160px - border-radius 5px - .el-image - width 160px - height 90px - border-radius 5px - .duration - position absolute - bottom 0 - right 0 - background-color rgba(14, 8, 8, 0.7) - padding 0 3px - font-family 'Input Sans' - font-size 14px - font-weight 700 - border-radius 0.125rem - .play - position absolute - width 100% - height 100% - top 0 - left 50% - border none - border-radius 5px - background rgba(100, 100, 100, 0.3) - cursor pointer - color var(--text-theme-color) - opacity 0 - transform translate(-50%, 0px) - transition opacity 0.3s ease 0s - &:hover - .play - opacity 1 - // display block - .center - width 100% - // border 1px solid saddlebrown - display flex - justify-content center - align-items flex-start - flex-flow column - padding 0 20px - .prompt, .failed - padding 0 - font-size 16px - max-height 60px - line-height 28px - overflow hidden - text-overflow ellipsis - .prompt - color var(--text-fb) - cursor text - .failed - color #E4696B - .right - display flex - justify-content right - min-width 200px - font-size 14px - padding 0 - .tools - display flex - justify-content left - align-items center - flex-flow row - height 90px - .btn-publish - padding 2px 10px - .text - margin-right 10px - .btn-icon - background none - padding 6px - transition background 0.6s ease 0s - color #919191 - &:hover - // background #5f5958 - // color #e1e1e1 - color var(--el-color-primary) - .downloading - width 16px - .pagination - margin-top 20px - display flex - justify-content center - .inner - display flex - width 100% - .mj-box - margin 10px - // background-color #262626 - // border 1px solid #454545 - // height: calc(100vh - 50px) - // overflow: scroll - min-width 300px - max-width 300px - padding 20px - border-radius 10px - color var(--text-theme-color) - font-size 14px - overflow auto - h2 - font-weight bold - font-size 20px - text-align center - color var(--theme-textcolor-normal) - // 隐藏滚动条 - ::-webkit-scrollbar - width 0 - height 0 - background-color transparent - .mj-params - margin-top 10px - overflow auto - .param-line - padding 0 10px - .el-icon - position relative - .model - background var(--card-bg) - // border 1px solid #454545 - border-radius 8px - padding 5px - margin-bottom 10px - display flex - flex-flow column - align-items center - cursor pointer - border 1px solid var(--chat-bg) - &:hover - border 1px solid var(--theme-border-hover) - .el-image - height 40px - width 100% - .text - margin-top 4px - font-size 12px - .model.active - // color #47fff1 - // background-color #585858 - border 1px solid var(--theme-border-hover) - .form-item-inner - display flex - align-items center - .el-select - --el-select-input-focus-border-color var(--el-color-primary) - --el-input-focus-border-color var(--el-color-primary) - .el-input__wrapper - background var(--chat-bg) - .el-input__inner - color var(--text-theme-color) - .el-icon - margin-left 10px - .img-uploader - .el-upload - border 1px dashed var(--el-border-color) - border-radius 6px - cursor pointer - position relative - overflow hidden - width 100% - transition var(--el-transition-duration-fast) - &:hover - border-color var(--el-color-primary) - .el-icon.uploader-icon - font-size 28px - color #8c939d - width 100% - height 120px - text-align center - .param-line.pt - display flex - align-items center - padding-top 5px - padding-bottom 5px - .el-form - .el-form-item__label - color var(--text-theme-color) - .el-input, .el-slider - width 180px - .uploader-icon - font-size 24px - position relative - top 3px - .no-more-data - text-align center - padding 30px - .generate-btn - .iconfont - margin-right 5px diff --git a/web/src/assets/css/luma.css b/web/src/assets/css/luma.css deleted file mode 100644 index ec316d99..00000000 --- a/web/src/assets/css/luma.css +++ /dev/null @@ -1,142 +0,0 @@ -.page-luma { - display: flex; - height: 100%; - background-color: #0e0808; - overflow: auto; - flex-flow: column; - align-items: center; - background: linear-gradient(180deg, rgba(75,62,53,0.8), rgba(144,50,181,0.3)); -} -.page-luma .prompt-box { - display: flex; - max-width: 56rem; - width: 100%; - padding: 20px; - flex-flow: column; -} -.page-luma .prompt-box .images { - display: flex; - flex-flow: row; - padding-bottom: 10px; - justify-content: center; -} -.page-luma .prompt-box .images .item { - position: relative; -} -.page-luma .prompt-box .images .item .el-image { - width: 100px; - height: 100px; - border-radius: 6px; - margin-right: 10px; -} -.page-luma .prompt-box .images .item .el-icon { - position: absolute; - cursor: pointer; - font-size: 20px; - color: #545454; - right: 10px; - top: 0; -} -.page-luma .prompt-box .images .item .el-icon:hover { - color: #888; -} -.page-luma .prompt-box .prompt-container { - width: 100%; -} -.page-luma .prompt-box .prompt-container .input-container { - background: linear-gradient(90deg, rgba(75,62,53,0.8), rgba(144,50,181,0.3)); - border-radius: 28px; - padding: 10px 20px; - display: flex; - align-items: center; - box-shadow: 0 2px 10px rgba(0,0,0,0.2); -} -.page-luma .prompt-box .prompt-container .input-container .prompt-input { - background: transparent; - border: none; - outline: none; - color: #fff; - font-size: 14px; - width: 100%; - padding: 10px; - resize: none; - white-space: pre-wrap; - word-wrap: break-word; - line-height: 24px; - overflow-wrap: break-word; - scrollbar-width: none; /* 隐藏滚动条 */ -} -.page-luma .prompt-box .prompt-container .input-container .prompt-input::placeholder { - color: rgba(255,255,255,0.6); -} -.page-luma .prompt-box .prompt-container .input-container .prompt-input::-webkit-scrollbar { - display: none; -} -.page-luma .prompt-box .prompt-container .input-container .upload-icon, -.page-luma .prompt-box .prompt-container .input-container .send-icon { - color: #e1e1e1; -} -.page-luma .prompt-box .prompt-container .input-container .upload-icon .iconfont, -.page-luma .prompt-box .prompt-container .input-container .send-icon .iconfont { - font-size: 20px; - cursor: pointer; -} -.page-luma .prompt-box .prompt-container .input-container .upload-icon { - position: relative; -} -.page-luma .video-container { - display: flex; - flex-flow: column; - width: 100%; - padding: 0 40px; -} -.page-luma .video-container .h-title { - color: #fff; - width: 100%; - font-size: 36px; - text-align: left; -} -.page-luma .video-container .videos .item { - margin-bottom: 20px; -} -.page-luma .video-container .videos .item .video-box { - width: 100%; - border-radius: 10px; -} -.page-luma .video-container .videos .item .video-box video, -.page-luma .video-container .videos .item .video-box img { - width: 100%; - height: 100%; - object-fit: cover; - border-radius: 10px; - cursor: pointer; -} -.page-luma .video-container .videos .item .video-name { - color: #e1e1e1; - font-size: 16px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - padding: 6px 0; - text-align: center; -} -.page-luma .video-container .videos .item .opts { - display: flex; - justify-content: center; -} -.page-luma .video-container .videos .item .opts .btn { - margin-right: 10px; - background-color: rgba(255,255,255,0.15); - border: none; - border-radius: 20px; - padding: 3px 15px; - cursor: pointer; - color: #fff; - font-size: 14px; -} -.page-luma .video-container .videos .item .opts .btn .iconfont { - font-size: 12px; -} -.page-luma .video-container .videos .item .opts .btn:hover { - background-color: rgba(255,255,255,0.2); -} diff --git a/web/src/assets/css/luma.styl b/web/src/assets/css/luma.styl deleted file mode 100644 index aebc0a25..00000000 --- a/web/src/assets/css/luma.styl +++ /dev/null @@ -1,362 +0,0 @@ -.page-luma { - display flex - height 100% - // background-color #0E0808 - // background: var(--chat-bg); - - overflow auto - //justify-content center - flex-flow column - align-items center - // background: linear-gradient(180deg, rgba(75, 62, 53, 0.8), rgba(144, 50, 181, 0.3)); - - - .prompt-box { - display flex - max-width 56rem - width 100% - padding 20px - flex-flow column - - .images { - display flex - flex-flow row - padding-bottom 10px - justify-content center - align-items center - - .item { - position relative - - .el-image { - width 100px - height 100px - border-radius 6px - margin-right 10px - } - - .el-icon { - position absolute - cursor pointer - font-size 20px - color #545454 - right 10px - top 0 - - &:hover { - color #888888 - } - } - } - - .btn-swap { - margin-right 10px - .icon-exchange{ - color var(--text-theme-color) - cursor pointer - } - } - } - - - .prompt-container { - width: 100%; - .input-container { - background: var(--chat-bg); - // background: linear-gradient(90deg, rgba(75, 62, 53, 0.8), rgba(144, 50, 181, 0.3)); - border-radius: 28px; - padding: 10px 20px; - display: flex; - align-items: center; - margin-bottom: 16px; - // box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); - - .prompt-input { - background: transparent; - border: none; - outline: none; - color var(--text-theme-color); - font-size: 14px; - width: 100%; - padding: 10px; - resize: none; - white-space: pre-wrap; - word-wrap: break-word; - line-height 24px - overflow-wrap: break-word; - - scrollbar-width: none; /* 隐藏滚动条 */ - - &::-webkit-scrollbar { - display: none; - } - } - - .upload-icon, .send-icon { - color var( --el-color-primary) - .iconfont { - font-size 20px - cursor pointer - } - } - .upload-icon { - position relative - } - } - .params { - display flex - justify-content right - color var(--text-theme-color); - font-size 14px - padding 10px 30px - - .item-group { - margin-left 20px - .label { - margin-right 5px - position relative - top 1px - } - } - } - } - - } - - - .video-container { - display flex - flex-flow column - width 100% - padding 0 40px - - .h-title { - color var(--text-theme-color) - width 100% - // font-size 36px - text-align left - } - - .list-box { - padding 0 - .item { - display flex - flex-flow row - align-items center - min-height 100px - padding 10px 15px - border-radius 10px - cursor pointer - margin-bottom 20px - background: var(--chat-bg); - - - - .left { - .container { - width 160px - position relative - - .video{ - width 160px - border-radius 5px - } - - .el-image { - width 160px - height 90px - border-radius 5px - } - - .duration { - position absolute - bottom 0 - right 0 - background-color rgba(14,8,8,.7) - padding 0 3px - font-family 'Input Sans' - font-size 14px - font-weight 700 - border-radius .125rem - } - - .play { - position absolute - width: 100% - height 100% - top: 0; - left: 50%; - border none - border-radius 5px - background rgba(100, 100, 100, 0.3) - cursor pointer - color var(--text-theme-color) - - opacity 0 - transform: translate(-50%, 0px); - transition opacity 0.3s ease 0s - } - - &:hover { - .play { - opacity 1 - //display block - } - } - } - } - - .center { - width 100% - //border 1px solid saddlebrown - display flex - justify-content center - align-items flex-start - flex-flow column - padding 0 20px - - .prompt,.failed { - padding 0 - font-size 16px - max-height 80px - line-height 28px - overflow hidden - text-overflow ellipsis - } - .prompt { - color var( --text-fb) - cursor: text - } - .failed { - color #E4696B - } - } - - .right { - display flex - justify-content right - min-width 200px; - font-size 14px - padding 0 - - .tools { - display flex - justify-content left - align-items center - flex-flow row - height 90px - - .btn-publish { - padding 2px 10px - - .text { - margin-right 10px - } - } - - .btn-icon { - background none - padding 6px - transition background 0.6s ease 0s - color #919191 - - &:hover { - // background #5f5958 - // color #e1e1e1 - color:var(--el-color-primary) - } - - .downloading { - width 16px - } - } - } - } - } - - } - - .pagination { - margin-top 20px - display flex - justify-content center - } - - //.videos { - // .item { - // margin-bottom 20px - // - // .video-box { - // width 100% - // aspect-ratio: 16/9; - // border-radius 10px - // video,img { - // width: 100%; - // height: 100%; - // object-fit: cover; - // border-radius 10px - // cursor pointer - // } - // } - // - // - // .video-name { - // color #e1e1e1 - // font-size 16px - // white-space nowrap - // overflow hidden - // text-overflow ellipsis - // padding 6px 0 - // text-align center - // } - // - // .opts { - // display flex - // justify-content center - // .btn { - // margin-right 10px - // background-color hsla(0,0%,100%,.15) - // border none - // border-radius 20px - // padding 3px 15px - // cursor pointer - // color var(--text-theme-color) - // font-size 14px - // - // .iconfont { - // font-size 11px - // position relative - // margin-right 5px - // top -2px - // } - // - // .el-image { - // width 14px - // height 14px - // margin-right 5px - // } - // - // &:hover { - // background-color hsla(0,0%,100%,.2) - // } - // } - // } - // } - //} - } - - .btn { - margin-right 10px - border none - border-radius 5px - padding 5px 10px - cursor pointer - color: var(--theme-text-color-primary) - background-color var(--btn-bg) - - &:hover { - opacity: 0.7 - } - } - -} \ No newline at end of file diff --git a/web/src/assets/css/theme-light.styl b/web/src/assets/css/theme-light.styl index 3642c494..69405a1a 100644 --- a/web/src/assets/css/theme-light.styl +++ b/web/src/assets/css/theme-light.styl @@ -5,6 +5,7 @@ --text-fb:#000; --text-color: #5b62ce; // 主要的文本颜色 --normal-color: rgba(43, 54, 116, 1); // 普通颜色 + --theme-textcolor-normal:#5b62ce;; p, h1, h2, h3, h4, h5, h6, article { font-family: $font-regular; } diff --git a/web/src/assets/css/video.styl b/web/src/assets/css/video.styl new file mode 100644 index 00000000..21d11a40 --- /dev/null +++ b/web/src/assets/css/video.styl @@ -0,0 +1,567 @@ +// 视频生成页面统一样式 +.page-video { + display: flex; + min-height: 100vh; + background: var(--chat-bg); + + // Element Plus 样式覆盖 + :deep(.el-form-item__label) { + color: var(--text-theme-color); + } + + :deep(.el-textarea) { + --el-input-focus-border-color: var(--el-color-primary); + } + + :deep(.el-textarea__inner) { + background: transparent; + color: var(--text-theme-color); + } + + .el-input__wrapper { + background: transparent; + padding: 5px; + } + + // 左侧参数面板 + .params-panel { + min-width: 320px; + max-width: 320px; + margin: 10px; + padding: 0 15px 20px 15px; + border-radius: 10px; + color: var(--text-theme-color); + font-size: 14px; + overflow: auto; + background: var(--card-bg); + + h2 { + font-weight: bold; + font-size: 20px; + text-align: center; + color: var(--theme-textcolor-normal); + margin-bottom: 20px; + } + + // 隐藏滚动条 + ::-webkit-scrollbar { + width: 0; + height: 0; + background-color: transparent; + } + + // 标签页样式 + .video-type-tabs { + margin-bottom: 20px; + + :deep(.el-tabs__item.is-active) { + color: var(--theme-textcolor-normal); + font-size: 16px; + } + + :deep(.el-tabs__item) { + color: var(--text-theme-color); + font-size: 16px; + } + + :deep(.el-tabs__active-bar) { + background-color: var(--theme-textcolor-normal); + } + + .el-tabs { + --el-tabs-header-height: 45px; + } + } + + // 参数行 + .param-line { + padding: 5px 0; + + &.pt { + padding-top: 10px; + padding-bottom: 10px; + } + + .label { + margin-right: 5px; + position: relative; + top: 1px; + } + + .form-item-inner { + display: flex; + align-items: center; + + .el-icon { + margin-left: 10px; + } + } + } + + // 表单项样式 + .el-form { + .el-form-item__label { + color: var(--text-theme-color); + } + + .el-input, .el-slider { + width: 100%; + } + + .el-select { + width: 100%; + --el-select-input-focus-border-color: var(--el-color-primary); + --el-input-focus-border-color: var(--el-color-primary); + + .el-input__wrapper { + background: var(--chat-bg); + } + + .el-input__inner { + color: var(--text-theme-color); + } + } + } + + // 网格内容项 + .grid-content { + background: var(--card-bg); + border-radius: 8px; + padding: 8px 14px; + display: flex; + cursor: pointer; + margin-bottom: 10px; + border: 1px solid var(--chat-bg); + + &:hover { + border: 1px solid var(--theme-border-hover); + } + + &.active { + border: 1px solid var(--theme-border-hover); + } + + .icon { + width: 20px; + height: 20px; + margin-bottom: 5px; + + &.proportion { + width: 20px; + height: 20px; + } + } + + .texts { + margin-left: 5px; + margin-top: 2px; + color: var(--text-theme-color); + } + } + + // 运镜控制 + .camera-control { + padding: 10px; + border-radius: 4px; + background: var(--card-bg); + + :deep(.el-form-item:last-child) { + margin-bottom: 0 !important; + } + } + + // 生成按钮 + .generate-btn { + .iconfont { + margin-right: 5px; + } + } + + // 项目组样式 + .item-group { + display: flex; + align-items: center; + margin-bottom: 15px; + + .label { + margin-right: 10px; + } + } + + // 图片模式切换样式 + .image-mode-toggle { + display: flex; + align-items: center; + justify-content: space-between; + + .label { + margin-right: 10px; + color: var(--text-theme-color); + } + } + + // 过渡动画 + .slide-fade-enter-active { + transition: all 0.3s ease-out; + } + + .slide-fade-leave-active { + transition: all 0.3s ease-in; + } + + .slide-fade-enter-from { + transform: translateY(-10px); + opacity: 0; + } + + .slide-fade-leave-to { + transform: translateY(-10px); + opacity: 0; + } + } + + + // KeLing 参数面板特有样式 + .params-container { + // 任务类型标签页 + .task-type-tabs { + margin-bottom: 20px; + + .text { + margin-bottom: 10px; + color: #6b778c; + font-size: 15px; + } + } + + // 图片上传样式 + .img-inline { + display: flex; + flex-wrap: wrap; + + .img-uploader { + text-align: center; + position: relative; + + :deep(.el-upload) { + border: 1px dashed var(--el-border-color); + border-radius: 6px; + cursor: pointer; + position: relative; + overflow: hidden; + width: 120px; + height: 120px; + transition: var(--el-transition-duration-fast); + margin-bottom: 20px; + + &:hover { + border-color: var(--el-color-primary); + } + } + + .removeimg { + position: absolute; + right: -5px; + top: -5px; + z-index: 10; + cursor: pointer; + color: #f56c6c; + font-size: 20px; + } + + } + + .btn-swap { + .icon-exchange { + color: var(--text-theme-color); + cursor: pointer; + font-size: 20px; + } + } + } + + // 提交按钮 + .submit-btn { + display: flex; + margin: 20px 0; + + .el-button { + width: 100%; + } + } + + // 算力信息 + .text-info { + width: 100%; + padding: 10px 0; + + .el-tag { + margin-right: 10px; + } + } + } + + // 右侧主要内容区域 + .main-content { + padding: 1.5rem; + flex: 1; + background: var(--chat-bg); + color: var(--text-theme-color); + overflow-x: hidden; + + // 作品标题栏 + .works-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + + .h-title { + color: var(--text-theme-color); + margin: 0; + } + + // .filter-buttons { + // .el-button-group { + // .el-button { + // --el-button-bg-color: var(--card-bg); + // --el-button-border-color: var(--chat-bg); + // --el-button-text-color: var(--text-theme-color); + // --el-button-hover-bg-color: var(--theme-border-hover); + // --el-button-hover-border-color: var(--theme-border-hover); + // --el-button-active-bg-color: var(--el-color-primary); + // --el-button-active-border-color: var(--el-color-primary); + + // &.is-type-primary { + // --el-button-bg-color: var(--el-color-primary); + // --el-button-border-color: var(--el-color-primary); + // --el-button-text-color: #ffffff; + // } + // } + // } + // } + } + + // 任务列表 + .video-list { + .list-box { + padding: 0; + + .item { + display: flex; + flex-flow: row; + align-items: center; + min-height: 100px; + padding: 10px 15px; + border-radius: 10px; + cursor: pointer; + margin-bottom: 20px; + background: var(--card-bg); + + .left { + .container { + width: 160px; + position: relative; + overflow: hidden; + display: flex; + justify-content: center; + align-items: center; + + .video { + width: 160px; + height: 120px; + border-radius: 5px; + background-color: var(--el-fill-color-light); + } + + .el-image { + width: 160px; + height: 90px; + border-radius: 5px; + } + + .duration { + position: absolute; + bottom: 0; + right: 0; + background-color: rgba(14, 8, 8, 0.7); + padding: 0 3px; + font-family: 'Input Sans'; + font-size: 14px; + font-weight: 700; + border-radius: 0.125rem; + } + + .play { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 50%; + border: none; + border-radius: 5px; + background: rgba(100, 100, 100, 0.3); + cursor: pointer; + color: var(--text-theme-color); + opacity: 0; + transform: translate(-50%, 0px); + transition: opacity 0.3s ease 0s; + } + + &:hover { + .play { + opacity: 1; + } + } + } + } + + .center { + width: 100%; + display: flex; + justify-content: center; + align-items: flex-start; + flex-flow: column; + padding: 0 20px; + + .prompt, .failed { + padding: 0; + font-size: 16px; + max-height: 80px; + line-height: 28px; + overflow: hidden; + text-overflow: ellipsis; + } + + .prompt { + color: var(--text-fb); + cursor: text; + } + + .failed { + color: #E4696B; + } + + .pb-2 { + padding-bottom: 8px; + + .el-tag { + margin-right: 4px; + margin-bottom: 4px; + } + } + } + + .right { + display: flex; + justify-content: right; + min-width: 200px; + font-size: 14px; + padding: 0; + + .tools { + display: flex; + justify-content: left; + align-items: center; + flex-flow: row; + height: 90px; + + .btn-publish { + padding: 2px 10px; + + .text { + margin-right: 10px; + } + } + + .btn-icon { + background: none; + padding: 6px; + transition: background 0.6s ease 0s; + color: #919191; + + &:hover { + color: var(--el-color-primary); + } + + .downloading { + width: 16px; + } + } + } + } + + .right-error { + display: flex; + justify-content: center; + align-items: center; + } + } + } + + // 分页 + .pagination { + margin-top: 20px; + display: flex; + justify-content: center; + } + } + } + + // 通用按钮样式 + .btn { + margin-right: 10px; + border: none; + border-radius: 5px; + padding: 5px 10px; + cursor: pointer; + color: var(--theme-text-color-primary); + background-color: var(--btn-bg); + + &:hover { + opacity: 0.7; + } + } + + // 无数据样式 + .no-data { + text-align: center; + padding: 40px; + color: var(--text-theme-color); + } +} + +// 响应式设计 +@media (max-width: 768px) { + .page-video { + flex-direction: column; + + .params-panel { + min-width: 100%; + max-width: 100%; + margin: 10px 0; + } + + .main-content { + padding: 1rem; + + .video-list .list-box .item { + .left .container { + width: 120px; + + .video, .el-image { + width: 120px; + } + } + + .center { + padding: 0 10px; + } + + .right { + min-width: 120px; + } + } + } + } +} \ No newline at end of file diff --git a/web/src/assets/iconfont/iconfont.css b/web/src/assets/iconfont/iconfont.css index 9f31c509..834e6462 100644 --- a/web/src/assets/iconfont/iconfont.css +++ b/web/src/assets/iconfont/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 4125778 */ - src: url('iconfont.woff2?t=1740279975534') format('woff2'), - url('iconfont.woff?t=1740279975534') format('woff'), - url('iconfont.ttf?t=1740279975534') format('truetype'); + src: url('iconfont.woff2?t=1752731646117') format('woff2'), + url('iconfont.woff?t=1752731646117') format('woff'), + url('iconfont.ttf?t=1752731646117') format('truetype'); } .iconfont { @@ -13,6 +13,142 @@ -moz-osx-font-smoothing: grayscale; } +.icon-video:before { + content: "\e63f"; +} + +.icon-empty-box:before { + content: "\e638"; +} + +.icon-check2:before { + content: "\e7e2"; +} + +.icon-creator:before { + content: "\e6a1"; +} + +.icon-withdraw:before { + content: "\e689"; +} + +.icon-withdraw-log:before { + content: "\e635"; +} + +.icon-money:before { + content: "\e831"; +} + +.icon-doller:before { + content: "\e633"; +} + +.icon-wallet:before { + content: "\e64d"; +} + +.icon-check:before { + content: "\e810"; +} + +.icon-refuse:before { + content: "\e629"; +} + +.icon-Reject:before { + content: "\e70d"; +} + +.icon-clock:before { + content: "\e65d"; +} + +.icon-eye-close:before { + content: "\e7aa"; +} + +.icon-eye-open:before { + content: "\e7ab"; +} + +.icon-list:before { + content: "\e650"; +} + +.icon-categroy:before { + content: "\e620"; +} + +.icon-zhankai:before { + content: "\e632"; +} + +.icon-wechat-mini:before { + content: "\e63d"; +} + +.icon-niutou:before { + content: "\e64c"; +} + +.icon-qiniu:before { + content: "\e62c"; +} + +.icon-storage:before { + content: "\e69a"; +} + +.icon-localstorage:before { + content: "\ea8d"; +} + +.icon-minio:before { + content: "\e855"; +} + +.icon-aliyun:before { + content: "\e672"; +} + +.icon-sms:before { + content: "\e82c"; +} + +.icon-duanxin:before { + content: "\e65c"; +} + +.icon-yanzm:before { + content: "\e625"; +} + +.icon-yaoqm:before { + content: "\e66e"; +} + +.icon-epay:before { + content: "\e628"; +} + +.icon-coze:before { + content: "\e61b"; +} + +.icon-token:before { + content: "\e68e"; +} + +.icon-reset:before { + content: "\e649"; +} + +.icon-stats:before { + content: "\e878"; +} + .icon-keling:before { content: "\eab7"; } @@ -289,7 +425,7 @@ content: "\e6c4"; } -.icon-mp1:before { +.icon-mp4:before { content: "\e647"; } diff --git a/web/src/assets/iconfont/iconfont.js b/web/src/assets/iconfont/iconfont.js index eaf24581..0e090b2d 100644 --- a/web/src/assets/iconfont/iconfont.js +++ b/web/src/assets/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4125778='',(a=>{var l=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var h,t,i,o,p,z=function(l,c){c.parentNode.insertBefore(l,c)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}h=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?z(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=h,o=a.document,p=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,m())})}function m(){p||(p=!0,i())}function s(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(s,50)}m()}})(window); \ No newline at end of file +window._iconfont_svg_string_4125778='',(a=>{var l=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var h,t,i,o,z,m=function(l,c){c.parentNode.insertBefore(l,c)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}h=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?m(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=h,o=a.document,z=!1,v(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,p())})}function p(){z||(z=!0,i())}function v(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(v,50)}p()}})(window); \ No newline at end of file diff --git a/web/src/assets/iconfont/iconfont.json b/web/src/assets/iconfont/iconfont.json index 31dab4f6..9f5dc208 100644 --- a/web/src/assets/iconfont/iconfont.json +++ b/web/src/assets/iconfont/iconfont.json @@ -5,6 +5,244 @@ "css_prefix_text": "icon-", "description": "", "glyphs": [ + { + "icon_id": "1283", + "name": "视频", + "font_class": "video", + "unicode": "e63f", + "unicode_decimal": 58943 + }, + { + "icon_id": "35224131", + "name": "empty-box", + "font_class": "empty-box", + "unicode": "e638", + "unicode_decimal": 58936 + }, + { + "icon_id": "9143175", + "name": "审核", + "font_class": "check2", + "unicode": "e7e2", + "unicode_decimal": 59362 + }, + { + "icon_id": "15450788", + "name": "创作者中心", + "font_class": "creator", + "unicode": "e6a1", + "unicode_decimal": 59041 + }, + { + "icon_id": "1134341", + "name": "提现", + "font_class": "withdraw", + "unicode": "e689", + "unicode_decimal": 59017 + }, + { + "icon_id": "10887127", + "name": "提现记录", + "font_class": "withdraw-log", + "unicode": "e635", + "unicode_decimal": 58933 + }, + { + "icon_id": "34452904", + "name": "money-rmb", + "font_class": "money", + "unicode": "e831", + "unicode_decimal": 59441 + }, + { + "icon_id": "34467697", + "name": "doller", + "font_class": "doller", + "unicode": "e633", + "unicode_decimal": 58931 + }, + { + "icon_id": "9512709", + "name": "钱包¥", + "font_class": "wallet", + "unicode": "e64d", + "unicode_decimal": 58957 + }, + { + "icon_id": "8365142", + "name": "check", + "font_class": "check", + "unicode": "e810", + "unicode_decimal": 59408 + }, + { + "icon_id": "10213506", + "name": "refuse", + "font_class": "refuse", + "unicode": "e629", + "unicode_decimal": 58921 + }, + { + "icon_id": "19393806", + "name": "Reject", + "font_class": "Reject", + "unicode": "e70d", + "unicode_decimal": 59149 + }, + { + "icon_id": "248916", + "name": "clock", + "font_class": "clock", + "unicode": "e65d", + "unicode_decimal": 58973 + }, + { + "icon_id": "6151096", + "name": "eye-close", + "font_class": "eye-close", + "unicode": "e7aa", + "unicode_decimal": 59306 + }, + { + "icon_id": "6151097", + "name": "eye-open", + "font_class": "eye-open", + "unicode": "e7ab", + "unicode_decimal": 59307 + }, + { + "icon_id": "6145570", + "name": "list", + "font_class": "list", + "unicode": "e650", + "unicode_decimal": 58960 + }, + { + "icon_id": "13127646", + "name": "categroy", + "font_class": "categroy", + "unicode": "e620", + "unicode_decimal": 58912 + }, + { + "icon_id": "1613505", + "name": "展开", + "font_class": "zhankai", + "unicode": "e632", + "unicode_decimal": 58930 + }, + { + "icon_id": "10905663", + "name": "微信小程序", + "font_class": "wechat-mini", + "unicode": "e63d", + "unicode_decimal": 58941 + }, + { + "icon_id": "21530643", + "name": "牛头", + "font_class": "niutou", + "unicode": "e64c", + "unicode_decimal": 58956 + }, + { + "icon_id": "24877229", + "name": "七牛云", + "font_class": "qiniu", + "unicode": "e62c", + "unicode_decimal": 58924 + }, + { + "icon_id": "3717493", + "name": "存储服务", + "font_class": "storage", + "unicode": "e69a", + "unicode_decimal": 59034 + }, + { + "icon_id": "7133059", + "name": "本地存储", + "font_class": "localstorage", + "unicode": "ea8d", + "unicode_decimal": 60045 + }, + { + "icon_id": "9360420", + "name": "minio", + "font_class": "minio", + "unicode": "e855", + "unicode_decimal": 59477 + }, + { + "icon_id": "21053628", + "name": "阿里云", + "font_class": "aliyun", + "unicode": "e672", + "unicode_decimal": 58994 + }, + { + "icon_id": "30046100", + "name": "comment-sms", + "font_class": "sms", + "unicode": "e82c", + "unicode_decimal": 59436 + }, + { + "icon_id": "4893414", + "name": "短信", + "font_class": "duanxin", + "unicode": "e65c", + "unicode_decimal": 58972 + }, + { + "icon_id": "553324", + "name": "验证码", + "font_class": "yanzm", + "unicode": "e625", + "unicode_decimal": 58917 + }, + { + "icon_id": "1264836", + "name": "邀请码", + "font_class": "yaoqm", + "unicode": "e66e", + "unicode_decimal": 58990 + }, + { + "icon_id": "24827618", + "name": "网易支付", + "font_class": "epay", + "unicode": "e628", + "unicode_decimal": 58920 + }, + { + "icon_id": "43863501", + "name": "Coze", + "font_class": "coze", + "unicode": "e61b", + "unicode_decimal": 58907 + }, + { + "icon_id": "11551884", + "name": "token", + "font_class": "token", + "unicode": "e68e", + "unicode_decimal": 59022 + }, + { + "icon_id": "38795534", + "name": "reset", + "font_class": "reset", + "unicode": "e649", + "unicode_decimal": 58953 + }, + { + "icon_id": "5838820", + "name": "统计", + "font_class": "stats", + "unicode": "e878", + "unicode_decimal": 59512 + }, { "icon_id": "42692844", "name": "可灵大模型", @@ -491,7 +729,7 @@ { "icon_id": "12600802", "name": "mp4", - "font_class": "mp1", + "font_class": "mp4", "unicode": "e647", "unicode_decimal": 58951 }, diff --git a/web/src/assets/iconfont/iconfont.ttf b/web/src/assets/iconfont/iconfont.ttf index 085f047c..47385b32 100644 Binary files a/web/src/assets/iconfont/iconfont.ttf and b/web/src/assets/iconfont/iconfont.ttf differ diff --git a/web/src/assets/iconfont/iconfont.woff b/web/src/assets/iconfont/iconfont.woff index 7f78795a..6029718b 100644 Binary files a/web/src/assets/iconfont/iconfont.woff and b/web/src/assets/iconfont/iconfont.woff differ diff --git a/web/src/assets/iconfont/iconfont.woff2 b/web/src/assets/iconfont/iconfont.woff2 index 5fe0cc00..ceefa2f6 100644 Binary files a/web/src/assets/iconfont/iconfont.woff2 and b/web/src/assets/iconfont/iconfont.woff2 differ diff --git a/web/src/router.js b/web/src/router.js index beb2550d..8e05f8ac 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -104,16 +104,10 @@ const routes = [ component: () => import('@/views/Song.vue'), }, { - name: 'luma', - path: '/luma', - meta: { title: 'Luma视频创作' }, - component: () => import('@/views/Luma.vue'), - }, - { - name: 'keling', - path: '/keling', - meta: { title: 'KeLing视频创作' }, - component: () => import('@/views/KeLing.vue'), + name: 'video', + path: '/video', + meta: { title: '视频创作中心' }, + component: () => import('@/views/Video.vue'), }, ], }, diff --git a/web/src/store/video.js b/web/src/store/video.js new file mode 100644 index 00000000..e84c01fc --- /dev/null +++ b/web/src/store/video.js @@ -0,0 +1,590 @@ +// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// * Copyright 2023 The Geek-AI Authors. All rights reserved. +// * Use of this source code is governed by a Apache-2.0 license +// * that can be found in the LICENSE file. +// * @Author yangjian102621@163.com +// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import nodata from '@/assets/img/no-data.png' +import { checkSession, getSystemInfo } from '@/store/cache' +import { closeLoading, showLoading, showMessageError, showMessageOK } from '@/utils/dialog' +import { httpDownload, httpGet, httpPost } from '@/utils/http' +import { replaceImg, substr } from '@/utils/libs' +import Clipboard from 'clipboard' +import { ElMessage, ElMessageBox } from 'element-plus' +import { defineStore } from 'pinia' +import { computed, reactive, ref } from 'vue' + +export const useVideoStore = defineStore('video', () => { + // 当前活跃的视频类型 + const activeVideoType = ref('luma') + + // 共同状态 + const loading = ref(false) + const list = ref([]) + const noData = ref(true) + const page = ref(1) + const pageSize = ref(10) + const total = ref(0) + const taskPulling = ref(true) + const pullHandler = ref(null) + const clipboard = ref(null) + + // 视频预览 + const showDialog = ref(false) + const currentVideoUrl = ref('') + + // 用户信息 + const isLogin = ref(false) + const availablePower = ref(100) + + // 任务筛选 + const taskFilter = ref('all') // 'all', 'luma', 'keling' + + // Luma 相关状态 + const lumaUseImageMode = ref(false) // 是否使用图片辅助生成 + const lumaParams = reactive({ + prompt: '', + expand_prompt: false, + loop: false, + image: '', // 起始帧 + image_tail: '', // 结束帧 + }) + + // KeLing 相关状态 + const isGenerating = ref(false) + const generating = ref(false) + const kelingPowerCost = ref(10) + const lumaPowerCost = ref(10) + const showCameraControl = ref(false) + const keLingPowers = ref({}) + + const models = ref([ + { text: '可灵 1.6', value: 'kling-v1-6' }, + { text: '可灵 1.5', value: 'kling-v1-5' }, + { text: '可灵 1.0', value: 'kling-v1' }, + ]) + + const rates = [ + { css: 'square', value: '1:1', text: '1:1', img: '/images/mj/rate_1_1.png' }, + { css: 'size16-9', value: '16:9', text: '16:9', img: '/images/mj/rate_16_9.png' }, + { css: 'size9-16', value: '9:16', text: '9:16', img: '/images/mj/rate_9_16.png' }, + ] + + // KeLing 相关状态 + const kelingUseImageMode = ref(false) // 是否使用图片辅助生成 + const kelingParams = reactive({ + model: 'kling-v1-6', + prompt: '', + negative_prompt: '', + cfg_scale: 0.7, + mode: 'std', + aspect_ratio: '16:9', + duration: '5', + camera_control: { + type: '', + config: { + horizontal: 0, + vertical: 0, + pan: 0, + tilt: 0, + roll: 0, + zoom: 0, + }, + }, + image: '', + image_tail: '', + }) + + // 计算属性 + const currentList = computed(() => { + return list.value.filter((item) => { + if (taskFilter.value === 'all') { + return true + } else if (taskFilter.value === 'luma') { + return item.type === 'luma' || !item.type // 兼容旧数据 + } else if (taskFilter.value === 'keling') { + return item.type === 'keling' + } + return true + }) + }) + + // 初始化方法 + const init = async () => { + try { + const user = await checkSession() + isLogin.value = true + availablePower.value = user.power + + // 初始化剪贴板 + if (clipboard.value) { + clipboard.value.destroy() + } + clipboard.value = new Clipboard('.copy-prompt') + clipboard.value.on('success', () => { + ElMessage.success('复制成功!') + }) + clipboard.value.on('error', () => { + ElMessage.error('复制失败!') + }) + + // 获取系统信息 + const sysInfo = await getSystemInfo() + lumaPowerCost.value = sysInfo.data.luma_power + keLingPowers.value = sysInfo.data.keling_powers + updateModelPower() + + // 获取数据并开始轮询 + await fetchData(1) + startPolling() + } catch (error) { + console.error('初始化失败:', error) + } + } + + // 清理方法 + const cleanup = () => { + if (clipboard.value) { + clipboard.value.destroy() + } + stopPolling() + } + + // 开始轮询 + const startPolling = () => { + if (pullHandler.value) { + clearInterval(pullHandler.value) + } + pullHandler.value = setInterval(() => { + if (taskPulling.value) { + fetchData(page.value) + } + }, 5000) + } + + // 停止轮询 + const stopPolling = () => { + if (pullHandler.value) { + clearInterval(pullHandler.value) + pullHandler.value = null + } + } + + // 获取任务列表 + const fetchData = async (_page) => { + if (_page) { + page.value = _page + } + + try { + const res = await httpGet('/api/video/list', { + page: page.value, + page_size: pageSize.value, + type: taskFilter.value === 'all' ? '' : taskFilter.value, + }) + + total.value = res.data.total + let needPull = false + const items = [] + + for (let v of res.data.items) { + if (v.progress === 0 || v.progress === 102) { + needPull = true + } + items.push({ + ...v, + downloading: false, + }) + } + + loading.value = false + taskPulling.value = needPull + + if (JSON.stringify(list.value) !== JSON.stringify(items)) { + list.value = items + } + noData.value = list.value.length === 0 + } catch (error) { + loading.value = false + noData.value = true + console.error('获取任务列表失败:', error) + } + } + + // Luma 相关方法 + const uploadLumaStartImage = async (file) => { + const formData = new FormData() + formData.append('file', file.file) + + try { + showLoading('图片上传中...') + const res = await httpPost('/api/upload', formData) + lumaParams.image = res.data.url + ElMessage.success('上传成功') + closeLoading() + } catch (error) { + showMessageError('上传失败: ' + error.message) + closeLoading() + } + } + + const uploadLumaEndImage = async (file) => { + const formData = new FormData() + formData.append('file', file.file) + + try { + showLoading('图片上传中...') + const res = await httpPost('/api/upload', formData) + lumaParams.image_tail = res.data.url + ElMessage.success('上传成功') + } catch (error) { + showMessageError('上传失败: ' + error.message) + } finally { + closeLoading() + } + } + + const removeLumaImage = (type) => { + if (type === 'start') { + lumaParams.image = '' + } else if (type === 'end') { + lumaParams.image_tail = '' + } + } + + const switchLumaImages = () => { + ;[lumaParams.image, lumaParams.image_tail] = [lumaParams.image_tail, lumaParams.image] + } + + const toggleLumaImageMode = (enabled) => { + lumaUseImageMode.value = enabled + // 关闭时清空图片 + if (!enabled) { + lumaParams.image = '' + lumaParams.image_tail = '' + } + } + + const createLumaVideo = async () => { + if (!lumaParams.prompt?.trim()) { + return ElMessage.error('请输入视频描述') + } + + if (lumaUseImageMode.value && !lumaParams.image) { + return ElMessage.error('请上传起始帧图片') + } + + // 处理参数 + const requestData = { + ...lumaParams, + task_type: lumaUseImageMode.value ? 'image2video' : 'text2video', + } + + // 处理图片链接 + if (requestData.image) { + requestData.first_frame_img = replaceImg(requestData.image) + } + if (requestData.image_tail) { + requestData.end_frame_img = replaceImg(requestData.image_tail) + } + + try { + await httpPost('/api/video/luma/create', requestData) + await fetchData(1) + taskPulling.value = true + showMessageOK('创建任务成功') + } catch (error) { + showMessageError('创建任务失败:' + error.message) + } + } + + // KeLing 相关方法 + const changeRate = (item) => { + kelingParams.aspect_ratio = item.value + } + + const updateModelPower = () => { + showCameraControl.value = kelingParams.model === 'kling-v1-5' && kelingParams.mode === 'pro' + kelingPowerCost.value = + keLingPowers.value[`${kelingParams.model}_${kelingParams.mode}_${kelingParams.duration}`] || + 10 + } + + const toggleKelingImageMode = (enabled) => { + kelingUseImageMode.value = enabled + // 关闭时清空图片 + if (!enabled) { + kelingParams.image = '' + kelingParams.image_tail = '' + } + } + + const uploadKelingStartImage = async (file) => { + const formData = new FormData() + formData.append('file', file.file) + + try { + showLoading('图片上传中...') + const res = await httpPost('/api/upload', formData) + kelingParams.image = res.data.url + ElMessage.success('上传成功') + closeLoading() + } catch (error) { + showMessageError('上传失败: ' + error.message) + closeLoading() + } + } + + const uploadKelingEndImage = async (file) => { + const formData = new FormData() + formData.append('file', file.file) + + try { + showLoading('图片上传中...') + const res = await httpPost('/api/upload', formData) + kelingParams.image_tail = res.data.url + ElMessage.success('上传成功') + } catch (error) { + showMessageError('上传失败: ' + error.message) + } finally { + closeLoading() + } + } + + const removeKelingImage = (type) => { + if (type === 'start') { + kelingParams.image = '' + } else if (type === 'end') { + kelingParams.image_tail = '' + } + } + + const switchKelingImages = () => { + ;[kelingParams.image, kelingParams.image_tail] = [kelingParams.image_tail, kelingParams.image] + } + + const createKelingVideo = async () => { + if (generating.value) return + + if (!kelingParams.prompt?.trim()) { + return ElMessage.error('请输入视频描述') + } + + if (kelingParams.prompt.length > 500) { + return ElMessage.error('视频描述不能超过 500 个字符') + } + + if (kelingUseImageMode.value && !kelingParams.image) { + return ElMessage.error('请上传起始帧图片') + } + + generating.value = true + + // 处理参数 + const requestData = { + ...kelingParams, + task_type: kelingUseImageMode.value ? 'image2video' : 'text2video', + } + + // 处理图片链接 + if (requestData.image) { + requestData.image = replaceImg(requestData.image) + } + if (requestData.image_tail) { + requestData.image_tail = replaceImg(requestData.image_tail) + } + + try { + await httpPost('/api/video/keling/create', requestData) + showMessageOK('任务创建成功') + + // 新增重置 + page.value = 1 + list.value.unshift({ + progress: 0, + prompt: requestData.prompt, + raw_data: { + task_type: requestData.task_type, + model: requestData.model, + duration: requestData.duration, + mode: requestData.mode, + }, + }) + taskPulling.value = true + } catch (error) { + showMessageError('创建失败: ' + error.message) + } finally { + generating.value = false + } + } + + // 提示词生成 + const generatePrompt = async () => { + if (isGenerating.value) return + + const prompt = activeVideoType.value === 'luma' ? lumaParams.prompt : kelingParams.prompt + if (!prompt) { + return showMessageError('请输入原始提示词') + } + + isGenerating.value = true + showLoading('正在生成视频脚本...') + + try { + const res = await httpPost('/api/prompt/video', { prompt }) + if (activeVideoType.value === 'luma') { + lumaParams.prompt = res.data + } else { + kelingParams.prompt = res.data + } + closeLoading() + } catch (error) { + showMessageError('生成提示词失败:' + error.message) + closeLoading() + } finally { + isGenerating.value = false + } + } + + // 视频预览 + const playVideo = (item) => { + currentVideoUrl.value = replaceImg(item.video_url) + showDialog.value = true + } + + // 视频下载 + const downloadVideo = async (item) => { + const url = replaceImg(item.video_url) + const downloadURL = `${import.meta.env.VITE_API_HOST}/api/download?url=${url}` + const urlObj = new URL(url) + const fileName = urlObj.pathname.split('/').pop() + + item.downloading = true + + try { + const response = await httpDownload(downloadURL) + const blob = new Blob([response.data]) + const link = document.createElement('a') + link.href = URL.createObjectURL(blob) + link.download = fileName + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + URL.revokeObjectURL(link.href) + item.downloading = false + } catch (error) { + showMessageError('下载失败') + item.downloading = false + } + } + + // 删除任务 + const removeJob = async (item) => { + try { + await ElMessageBox.confirm('此操作将会删除任务相关文件,继续操作码?', '删除提示', { + confirmButtonText: '确认', + cancelButtonText: '取消', + type: 'warning', + }) + + await httpGet('/api/video/remove', { id: item.id }) + ElMessage.success('任务删除成功') + await fetchData() + } catch (error) { + if (error !== 'cancel') { + ElMessage.error('任务删除失败:' + error.message) + } + } + } + + // 发布任务 + const publishJob = async (item) => { + try { + await httpGet('/api/video/publish', { id: item.id, publish: item.publish }) + ElMessage.success('操作成功') + } catch (error) { + ElMessage.error('操作失败:' + error.message) + } + } + + // 切换视频类型 + const switchVideoType = (type) => { + activeVideoType.value = type + } + + // 切换任务筛选 + const switchTaskFilter = (filter) => { + taskFilter.value = filter + page.value = 1 + fetchData(1) + } + + return { + // 状态 + activeVideoType, + loading, + list, + currentList, + noData, + page, + pageSize, + total, + taskPulling, + showDialog, + currentVideoUrl, + isLogin, + availablePower, + nodata, + taskFilter, + + // Luma 状态 + lumaUseImageMode, + lumaParams, + lumaPowerCost, + // KeLing 状态 + kelingUseImageMode, + isGenerating, + generating, + kelingPowerCost, + showCameraControl, + keLingPowers, + models, + rates, + kelingParams, + + // 方法 + init, + cleanup, + fetchData, + switchVideoType, + switchTaskFilter, + + // Luma 方法 + toggleLumaImageMode, + uploadLumaStartImage, + uploadLumaEndImage, + removeLumaImage, + switchLumaImages, + createLumaVideo, + + // KeLing 方法 + toggleKelingImageMode, + changeRate, + updateModelPower, + uploadKelingStartImage, + uploadKelingEndImage, + removeKelingImage, + switchKelingImages, + createKelingVideo, + + // 共同方法 + generatePrompt, + playVideo, + downloadVideo, + removeJob, + publishJob, + substr, + replaceImg, + } +}) diff --git a/web/src/views/KeLing.vue b/web/src/views/KeLing.vue deleted file mode 100644 index c9fd13e2..00000000 --- a/web/src/views/KeLing.vue +++ /dev/null @@ -1,745 +0,0 @@ - - - - - diff --git a/web/src/views/Luma.vue b/web/src/views/Luma.vue deleted file mode 100644 index 778a90ff..00000000 --- a/web/src/views/Luma.vue +++ /dev/null @@ -1,387 +0,0 @@ - - - - - diff --git a/web/src/views/Video.vue b/web/src/views/Video.vue new file mode 100644 index 00000000..c8281602 --- /dev/null +++ b/web/src/views/Video.vue @@ -0,0 +1,645 @@ + + + + +