mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-19 01:36:38 +08:00
luma page, upload image and remove image function is ready
This commit is contained in:
parent
f71a2f5263
commit
ab421f2185
@ -26,7 +26,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
@ -228,6 +227,7 @@ func needLogin(c *gin.Context) bool {
|
||||
c.Request.URL.Path == "/api/suno/client" ||
|
||||
c.Request.URL.Path == "/api/suno/detail" ||
|
||||
c.Request.URL.Path == "/api/suno/play" ||
|
||||
c.Request.URL.Path == "/api/download" ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/config/") ||
|
||||
@ -316,64 +316,58 @@ func staticResourceMiddleware() gin.HandlerFunc {
|
||||
|
||||
url := c.Request.URL.String()
|
||||
// 拦截生成缩略图请求
|
||||
if strings.HasPrefix(url, "/static/") {
|
||||
if strings.Contains(url, "?imageView2") {
|
||||
r := strings.SplitAfter(url, "imageView2")
|
||||
size := strings.Split(r[1], "/")
|
||||
if len(size) != 8 {
|
||||
c.String(http.StatusNotFound, "invalid thumb args")
|
||||
return
|
||||
}
|
||||
with := utils.IntValue(size[3], 0)
|
||||
height := utils.IntValue(size[5], 0)
|
||||
quality := utils.IntValue(size[7], 75)
|
||||
|
||||
// 打开图片文件
|
||||
filePath := strings.TrimLeft(c.Request.URL.Path, "/")
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
c.String(http.StatusNotFound, "Image not found")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 解码图片
|
||||
img, _, err := image.Decode(file)
|
||||
// for .webp image
|
||||
if err != nil {
|
||||
img, err = webp.Decode(file)
|
||||
}
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error decoding image")
|
||||
return
|
||||
}
|
||||
|
||||
var newImg image.Image
|
||||
if height == 0 || with == 0 {
|
||||
// 固定宽度,高度自适应
|
||||
newImg = resize.Resize(uint(with), uint(height), img, resize.Lanczos3)
|
||||
} else {
|
||||
// 生成缩略图
|
||||
newImg = resize.Thumbnail(uint(with), uint(height), img, resize.Lanczos3)
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
err = jpeg.Encode(&buffer, newImg, &jpeg.Options{Quality: quality})
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 设置图片缓存有效期为一年 (365天)
|
||||
c.Header("Cache-Control", "max-age=31536000, public")
|
||||
// 直接输出图像数据流
|
||||
c.Data(http.StatusOK, "image/jpeg", buffer.Bytes())
|
||||
c.Abort() // 中断请求
|
||||
} else if strings.Contains(url, "?download=true") {
|
||||
filename := filepath.Base(url)
|
||||
c.Header("Content-Disposition", "attachment; filename="+filename)
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
if strings.HasPrefix(url, "/static/") && strings.Contains(url, "?imageView2") {
|
||||
r := strings.SplitAfter(url, "imageView2")
|
||||
size := strings.Split(r[1], "/")
|
||||
if len(size) != 8 {
|
||||
c.String(http.StatusNotFound, "invalid thumb args")
|
||||
return
|
||||
}
|
||||
with := utils.IntValue(size[3], 0)
|
||||
height := utils.IntValue(size[5], 0)
|
||||
quality := utils.IntValue(size[7], 75)
|
||||
|
||||
// 打开图片文件
|
||||
filePath := strings.TrimLeft(c.Request.URL.Path, "/")
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
c.String(http.StatusNotFound, "Image not found")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 解码图片
|
||||
img, _, err := image.Decode(file)
|
||||
// for .webp image
|
||||
if err != nil {
|
||||
img, err = webp.Decode(file)
|
||||
}
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error decoding image")
|
||||
return
|
||||
}
|
||||
|
||||
var newImg image.Image
|
||||
if height == 0 || with == 0 {
|
||||
// 固定宽度,高度自适应
|
||||
newImg = resize.Resize(uint(with), uint(height), img, resize.Lanczos3)
|
||||
} else {
|
||||
// 生成缩略图
|
||||
newImg = resize.Thumbnail(uint(with), uint(height), img, resize.Lanczos3)
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
err = jpeg.Encode(&buffer, newImg, &jpeg.Options{Quality: quality})
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 设置图片缓存有效期为一年 (365天)
|
||||
c.Header("Cache-Control", "max-age=31536000, public")
|
||||
// 直接输出图像数据流
|
||||
c.Data(http.StatusOK, "image/jpeg", buffer.Bytes())
|
||||
c.Abort() // 中断请求
|
||||
|
||||
}
|
||||
c.Next()
|
||||
|
@ -17,19 +17,21 @@ import (
|
||||
"geekai/utils/resp"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UploadHandler struct {
|
||||
type NetHandler struct {
|
||||
BaseHandler
|
||||
uploaderManager *oss.UploaderManager
|
||||
}
|
||||
|
||||
func NewUploadHandler(app *core.AppServer, db *gorm.DB, manager *oss.UploaderManager) *UploadHandler {
|
||||
return &UploadHandler{BaseHandler: BaseHandler{App: app, DB: db}, uploaderManager: manager}
|
||||
func NewNetHandler(app *core.AppServer, db *gorm.DB, manager *oss.UploaderManager) *NetHandler {
|
||||
return &NetHandler{BaseHandler: BaseHandler{App: app, DB: db}, uploaderManager: manager}
|
||||
}
|
||||
|
||||
func (h *UploadHandler) Upload(c *gin.Context) {
|
||||
func (h *NetHandler) Upload(c *gin.Context) {
|
||||
file, err := h.uploaderManager.GetUploadHandler().PutFile(c, "file")
|
||||
if err != nil {
|
||||
resp.ERROR(c, err.Error())
|
||||
@ -60,7 +62,7 @@ func (h *UploadHandler) Upload(c *gin.Context) {
|
||||
resp.SUCCESS(c, file)
|
||||
}
|
||||
|
||||
func (h *UploadHandler) List(c *gin.Context) {
|
||||
func (h *NetHandler) List(c *gin.Context) {
|
||||
var data struct {
|
||||
Urls []string `json:"urls,omitempty"`
|
||||
}
|
||||
@ -95,7 +97,7 @@ func (h *UploadHandler) List(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Remove remove files
|
||||
func (h *UploadHandler) Remove(c *gin.Context) {
|
||||
func (h *NetHandler) Remove(c *gin.Context) {
|
||||
userId := h.GetLoginUserId(c)
|
||||
id := h.GetInt(c, "id", 0)
|
||||
var file model.File
|
||||
@ -119,3 +121,28 @@ func (h *UploadHandler) Remove(c *gin.Context) {
|
||||
_ = h.uploaderManager.GetUploadHandler().Delete(objectKey)
|
||||
resp.SUCCESS(c)
|
||||
}
|
||||
|
||||
func (h *NetHandler) Download(c *gin.Context) {
|
||||
fileUrl := c.Query("url")
|
||||
// 使用http工具下载文件
|
||||
if fileUrl == "" {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
// 使用http.Get下载文件
|
||||
r, err := http.Get(fileUrl)
|
||||
if err != nil {
|
||||
resp.ERROR(c, err.Error())
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
resp.ERROR(c, "error status:"+r.Status)
|
||||
return
|
||||
}
|
||||
|
||||
// 将下载的文件内容写入响应
|
||||
c.Status(http.StatusOK)
|
||||
_, _ = io.Copy(c.Writer, r.Body)
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ func main() {
|
||||
fx.Provide(handler.NewChatRoleHandler),
|
||||
fx.Provide(handler.NewUserHandler),
|
||||
fx.Provide(chatimpl.NewChatHandler),
|
||||
fx.Provide(handler.NewUploadHandler),
|
||||
fx.Provide(handler.NewNetHandler),
|
||||
fx.Provide(handler.NewSmsHandler),
|
||||
fx.Provide(handler.NewRedeemHandler),
|
||||
fx.Provide(handler.NewCaptchaHandler),
|
||||
@ -249,10 +249,11 @@ func main() {
|
||||
group.POST("tokens", h.Tokens)
|
||||
group.GET("stop", h.StopGenerate)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.UploadHandler) {
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.NetHandler) {
|
||||
s.Engine.POST("/api/upload", h.Upload)
|
||||
s.Engine.POST("/api/upload/list", h.List)
|
||||
s.Engine.GET("/api/upload/remove", h.Remove)
|
||||
s.Engine.GET("/api/download", h.Download)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.SmsHandler) {
|
||||
group := s.Engine.Group("/api/sms/")
|
||||
|
BIN
web/public/images/loading.gif
Normal file
BIN
web/public/images/loading.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -91,6 +91,22 @@
|
||||
position relative
|
||||
}
|
||||
}
|
||||
.params {
|
||||
display flex
|
||||
justify-content right
|
||||
color #e1e1e1
|
||||
font-size 14px
|
||||
padding 10px 30px
|
||||
|
||||
.item-group {
|
||||
margin-left 20px
|
||||
.label {
|
||||
margin-right 5px
|
||||
position relative
|
||||
top 1px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -150,7 +166,16 @@
|
||||
font-size 14px
|
||||
|
||||
.iconfont {
|
||||
font-size 12px
|
||||
font-size 11px
|
||||
position relative
|
||||
margin-right 5px
|
||||
top -2px
|
||||
}
|
||||
|
||||
.el-image {
|
||||
width 14px
|
||||
height 14px
|
||||
margin-right 5px
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
@ -69,3 +69,17 @@ export function httpPost(url, data = {}, options = {}) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function httpDownload(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
responseType: 'blob' // 将响应类型设置为 `blob`
|
||||
}).then(response => {
|
||||
resolve(response)
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
@ -2,15 +2,24 @@
|
||||
<div class="page-luma">
|
||||
<div class="prompt-box">
|
||||
<div class="images">
|
||||
<div v-for="img in images" class="item">
|
||||
<div v-for="img in images" :key="img" class="item">
|
||||
<el-image :src="img" fit="cover"/>
|
||||
<el-icon><CircleCloseFilled /></el-icon>
|
||||
<el-icon @click="remove(img)"><CircleCloseFilled /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prompt-container">
|
||||
<div class="input-container">
|
||||
<div class="upload-icon">
|
||||
<i class="iconfont icon-image"></i>
|
||||
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
:http-request="upload"
|
||||
accept=".jpg,.png,.jpeg"
|
||||
>
|
||||
<i class="iconfont icon-image"></i>
|
||||
</el-upload>
|
||||
</div>
|
||||
<textarea
|
||||
class="prompt-input"
|
||||
@ -24,6 +33,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="params">
|
||||
<div class="item-group">
|
||||
<span class="label">循环</span>
|
||||
<el-switch v-model="loop" size="small" style="--el-switch-on-color:#BF78BF;" />
|
||||
</div>
|
||||
<div class="item-group">
|
||||
<span class="label">提示词优化</span>
|
||||
<el-switch v-model="promptExtend" size="small" style="--el-switch-on-color:#BF78BF;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -40,12 +59,11 @@
|
||||
</div>
|
||||
<div class="video-name">{{item.name}}</div>
|
||||
<div class="opts">
|
||||
<a :href="item.url+'?download=true'" download="video.mp4">
|
||||
<button class="btn">
|
||||
<i class="iconfont icon-download"></i>
|
||||
<span>下载</span>
|
||||
</button>
|
||||
</a>
|
||||
<button class="btn" @click="download(item)" :disabled="item.downloading">
|
||||
<i class="iconfont icon-download" v-if="!item.downloading"></i>
|
||||
<el-image src="/images/loading.gif" fit="cover" v-else />
|
||||
<span>下载</span>
|
||||
</button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@ -56,19 +74,21 @@
|
||||
<script setup>
|
||||
import {ref} from "vue";
|
||||
import {CircleCloseFilled} from "@element-plus/icons-vue";
|
||||
import {httpDownload, httpPost} from "@/utils/http";
|
||||
import {showMessageError} from "@/utils/dialog";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
const row = ref(1)
|
||||
const prompt = ref('')
|
||||
const images = ref([
|
||||
"http://nk.img.r9it.com/chatgpt-plus/1719371605709871.jpg",
|
||||
"http://nk.img.r9it.com/chatgpt-plus/1719371605709871.jpg"
|
||||
])
|
||||
const loop = ref(false)
|
||||
const promptExtend = ref(false)
|
||||
const images = ref([])
|
||||
|
||||
const videos = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: 'a dancing girl',
|
||||
url: 'http://localhost:5678/static/upload/2024/8/1724574661747320.mp4',
|
||||
url: 'https://storage.cdn-luma.com/dream_machine/d133794f-3124-4059-a9f2-e5fed79f0d5b/watermarked_video01944f69966f14d33b6c4486a8cfb8dde.mp4',
|
||||
cover: 'https://storage.cdn-luma.com/dream_machine/d133794f-3124-4059-a9f2-e5fed79f0d5b/video_0_thumb.jpg',
|
||||
playing: false
|
||||
},
|
||||
@ -102,6 +122,44 @@ const videos = ref([
|
||||
},
|
||||
])
|
||||
|
||||
const download = (item) => {
|
||||
const downloadURL = `${process.env.VUE_APP_API_HOST}/api/download?url=${item.url}`
|
||||
// parse filename
|
||||
const urlObj = new URL(item.url);
|
||||
const fileName = urlObj.pathname.split('/').pop();
|
||||
item.downloading = true
|
||||
httpDownload(downloadURL).then(response => {
|
||||
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(() => {
|
||||
showMessageError("下载失败")
|
||||
item.downloading = false
|
||||
})
|
||||
}
|
||||
|
||||
const upload = (file) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file.file, file.name);
|
||||
// 执行上传操作
|
||||
httpPost('/api/upload', formData).then((res) => {
|
||||
images.value.push(res.data.url)
|
||||
ElMessage.success({message: "上传成功", duration: 500})
|
||||
}).catch((e) => {
|
||||
ElMessage.error('图片上传失败:' + e.message)
|
||||
})
|
||||
};
|
||||
|
||||
const remove = (img) => {
|
||||
images.value = images.value.filter(item => item !== img)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user