mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	add back-to-top component for all list page
This commit is contained in:
		@@ -456,15 +456,44 @@ func (h *MidJourneyHandler) Remove(c *gin.Context) {
 | 
			
		||||
		resp.ERROR(c, "记录不存在")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove job recode
 | 
			
		||||
	res := h.DB.Delete(&job)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, res.Error.Error())
 | 
			
		||||
	tx := h.DB.Begin()
 | 
			
		||||
	if err := tx.Delete(&job).Error; err != nil {
 | 
			
		||||
		tx.Rollback()
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// refund power
 | 
			
		||||
	err := tx.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power)).Error
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		tx.Rollback()
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var user model.User
 | 
			
		||||
	h.DB.Where("id = ?", job.UserId).First(&user)
 | 
			
		||||
	err = tx.Create(&model.PowerLog{
 | 
			
		||||
		UserId:    user.Id,
 | 
			
		||||
		Username:  user.Username,
 | 
			
		||||
		Type:      types.PowerConsume,
 | 
			
		||||
		Amount:    job.Power,
 | 
			
		||||
		Balance:   user.Power + job.Power,
 | 
			
		||||
		Mark:      types.PowerAdd,
 | 
			
		||||
		Model:     "mid-journey",
 | 
			
		||||
		Remark:    fmt.Sprintf("绘画任务失败,退回算力。任务ID:%s", job.TaskId),
 | 
			
		||||
		CreatedAt: time.Now(),
 | 
			
		||||
	}).Error
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		tx.Rollback()
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	tx.Commit()
 | 
			
		||||
 | 
			
		||||
	// remove image
 | 
			
		||||
	err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
 | 
			
		||||
	err = h.uploader.GetUploadHandler().Delete(job.ImgURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Error("remove image failed: ", err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ package mj
 | 
			
		||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"geekai/core/types"
 | 
			
		||||
	logger2 "geekai/logger"
 | 
			
		||||
	"geekai/service"
 | 
			
		||||
@@ -188,28 +187,6 @@ func (p *ServicePool) SyncTaskProgress() {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, job := range jobs {
 | 
			
		||||
				// 失败或者 30 分钟还没完成的任务删除并退回算力
 | 
			
		||||
				if time.Now().Sub(job.CreatedAt) > time.Minute*30 {
 | 
			
		||||
					p.db.Delete(&job)
 | 
			
		||||
					// 退回算力
 | 
			
		||||
					tx := p.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power))
 | 
			
		||||
					if tx.Error == nil && tx.RowsAffected > 0 {
 | 
			
		||||
						var user model.User
 | 
			
		||||
						p.db.Where("id = ?", job.UserId).First(&user)
 | 
			
		||||
						p.db.Create(&model.PowerLog{
 | 
			
		||||
							UserId:    user.Id,
 | 
			
		||||
							Username:  user.Username,
 | 
			
		||||
							Type:      types.PowerConsume,
 | 
			
		||||
							Amount:    job.Power,
 | 
			
		||||
							Balance:   user.Power + job.Power,
 | 
			
		||||
							Mark:      types.PowerAdd,
 | 
			
		||||
							Model:     "mid-journey",
 | 
			
		||||
							Remark:    fmt.Sprintf("绘画任务失败,退回算力。任务ID:%s", job.TaskId),
 | 
			
		||||
							CreatedAt: time.Now(),
 | 
			
		||||
						})
 | 
			
		||||
					}
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				if servicePlus := p.getService(job.ChannelId); servicePlus != nil {
 | 
			
		||||
					_ = servicePlus.Notify(job)
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,7 @@
 | 
			
		||||
            background-color #383838
 | 
			
		||||
            border 1px solid #454545
 | 
			
		||||
            border-radius 5px
 | 
			
		||||
            padding 10px
 | 
			
		||||
            padding 5px
 | 
			
		||||
            margin-bottom 10px
 | 
			
		||||
            display flex
 | 
			
		||||
            flex-flow column
 | 
			
		||||
@@ -91,12 +91,13 @@
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .el-image {
 | 
			
		||||
              height 60px
 | 
			
		||||
              height 30px
 | 
			
		||||
              width 100%
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .text {
 | 
			
		||||
              margin-top 6px
 | 
			
		||||
              margin-top 4px
 | 
			
		||||
              font-size 12px
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
          }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										77
									
								
								web/src/components/BackTop.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								web/src/components/BackTop.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <button v-if="showButton" @click="scrollToTop" class="scroll-to-top" :style="{bottom: bottom + 'px', right: right + 'px', backgroundColor: bgColor}">
 | 
			
		||||
    <el-icon><ArrowUpBold /></el-icon>
 | 
			
		||||
  </button>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import {ArrowUpBold} from "@element-plus/icons-vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'BackTop',
 | 
			
		||||
  components: {ArrowUpBold},
 | 
			
		||||
  props: {
 | 
			
		||||
    bottom: {
 | 
			
		||||
      type: Number,
 | 
			
		||||
      default: 30
 | 
			
		||||
    },
 | 
			
		||||
    right: {
 | 
			
		||||
      type: Number,
 | 
			
		||||
      default: 30
 | 
			
		||||
    },
 | 
			
		||||
    bgColor: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: '#007bff'
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      showButton: false
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.checkScroll();
 | 
			
		||||
    window.addEventListener('resize', this.checkScroll);
 | 
			
		||||
    this.$el.parentElement.addEventListener('scroll', this.checkScroll);
 | 
			
		||||
  },
 | 
			
		||||
  beforeUnmount() {
 | 
			
		||||
    window.removeEventListener('resize', this.checkScroll);
 | 
			
		||||
    this.$el.parentElement.removeEventListener('scroll', this.checkScroll);
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    scrollToTop() {
 | 
			
		||||
      const container = this.$el.parentElement;
 | 
			
		||||
      container.scrollTo({
 | 
			
		||||
        top: 0,
 | 
			
		||||
        behavior: 'smooth'
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    checkScroll() {
 | 
			
		||||
      const container = this.$el.parentElement;
 | 
			
		||||
      this.showButton = container.scrollTop > 50;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
.scroll-to-top {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  color: white;
 | 
			
		||||
  border: none;
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  transition: opacity 0.3s;
 | 
			
		||||
  width 40px
 | 
			
		||||
  height 40px
 | 
			
		||||
  display flex
 | 
			
		||||
  justify-content center
 | 
			
		||||
  align-items center
 | 
			
		||||
  font-size 20px
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    opacity: 0.6;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -118,6 +118,8 @@
 | 
			
		||||
                      v-if="item.type==='prompt'" :data="item" :list-style="listStyle"/>
 | 
			
		||||
                  <chat-reply v-else-if="item.type==='reply'" :data="item" @regen="reGenerate" :read-only="false" :list-style="listStyle"/>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <back-top :right="30" :bottom="100" bg-color="#19C27D"/>
 | 
			
		||||
              </div><!-- end chat box -->
 | 
			
		||||
 | 
			
		||||
              <div class="input-box">
 | 
			
		||||
@@ -220,6 +222,8 @@ import FileSelect from "@/components/FileSelect.vue";
 | 
			
		||||
import FileList from "@/components/FileList.vue";
 | 
			
		||||
import ChatSetting from "@/components/ChatSetting.vue";
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
import BackTop from "@/components/BackTop.vue";
 | 
			
		||||
import {showMessageError} from "@/utils/dialog";
 | 
			
		||||
 | 
			
		||||
const title = ref('ChatGPT-智能助手');
 | 
			
		||||
const models = ref([])
 | 
			
		||||
@@ -603,18 +607,6 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 心跳函数
 | 
			
		||||
  const sendHeartbeat = () => {
 | 
			
		||||
    clearTimeout(heartbeatHandle.value)
 | 
			
		||||
    new Promise((resolve, reject) => {
 | 
			
		||||
      if (socket.value !== null) {
 | 
			
		||||
        socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
 | 
			
		||||
      }
 | 
			
		||||
      resolve("success")
 | 
			
		||||
    }).then(() => {
 | 
			
		||||
      heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  const _socket = new WebSocket(host + `/api/chat/new?session_id=${sessionId.value}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
 | 
			
		||||
  _socket.addEventListener('open', () => {
 | 
			
		||||
    chatData.value = []; // 初始化聊天数据
 | 
			
		||||
@@ -636,8 +628,6 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
    } else { // 加载聊天记录
 | 
			
		||||
      loadChatHistory(chat_id);
 | 
			
		||||
    }
 | 
			
		||||
    // 发送心跳消息
 | 
			
		||||
    sendHeartbeat()
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('message', event => {
 | 
			
		||||
@@ -648,7 +638,7 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
        reader.onload = () => {
 | 
			
		||||
          const data = JSON.parse(String(reader.result));
 | 
			
		||||
          if (data.type === 'start') {
 | 
			
		||||
            const prePrompt = chatData.value[chatData.value.length-1].content
 | 
			
		||||
            const prePrompt = chatData.value[chatData.value.length-1]?.content
 | 
			
		||||
            chatData.value.push({
 | 
			
		||||
              type: "reply",
 | 
			
		||||
              id: randString(32),
 | 
			
		||||
@@ -689,8 +679,10 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
          } else {
 | 
			
		||||
            lineBuffer.value += data.content;
 | 
			
		||||
            const reply = chatData.value[chatData.value.length - 1]
 | 
			
		||||
            reply['orgContent'] = lineBuffer.value;
 | 
			
		||||
            reply['content'] = md.render(processContent(lineBuffer.value));
 | 
			
		||||
            if (reply) {
 | 
			
		||||
              reply['orgContent'] = lineBuffer.value;
 | 
			
		||||
              reply['content'] = md.render(processContent(lineBuffer.value));
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          // 将聊天框的滚动条滑动到最底部
 | 
			
		||||
          nextTick(() => {
 | 
			
		||||
@@ -716,7 +708,7 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
      connect(chat_id, role_id)
 | 
			
		||||
    }).catch(() => {
 | 
			
		||||
      loading.value = true
 | 
			
		||||
      setTimeout(() => connect(chat_id, role_id), 3000)
 | 
			
		||||
      showMessageError("会话已断开,刷新页面...")
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -174,7 +174,7 @@
 | 
			
		||||
              </div> <!-- end finish job list-->
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
 | 
			
		||||
        </div><!-- end task list box -->
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -193,6 +193,7 @@ import Clipboard from "clipboard";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import TaskList from "@/components/TaskList.vue";
 | 
			
		||||
import BackTop from "@/components/BackTop.vue";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = ref(0)
 | 
			
		||||
// const paramBoxHeight = ref(0)
 | 
			
		||||
 
 | 
			
		||||
@@ -593,6 +593,7 @@
 | 
			
		||||
            </div> <!-- end finish job list-->
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
 | 
			
		||||
      </div><!-- end task list box -->
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@@ -613,6 +614,7 @@ import {getSessionId} from "@/store/session";
 | 
			
		||||
import {copyObj, removeArrayItem} from "@/utils/libs";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import TaskList from "@/components/TaskList.vue";
 | 
			
		||||
import BackTop from "@/components/BackTop.vue";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = ref(0)
 | 
			
		||||
const paramBoxHeight = ref(0)
 | 
			
		||||
@@ -1014,7 +1016,7 @@ const publishImage = (item, action) => {
 | 
			
		||||
    item.publish = action
 | 
			
		||||
    page.value = 0
 | 
			
		||||
    isOver.value = false
 | 
			
		||||
    fetchFinishJobs()
 | 
			
		||||
    item.publish = action
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error(text + "失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 
 | 
			
		||||
@@ -345,7 +345,7 @@
 | 
			
		||||
              </div> <!-- end finish job list-->
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
 | 
			
		||||
        </div><!-- end task list box -->
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -476,6 +476,7 @@ import {useRouter} from "vue-router";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import TaskList from "@/components/TaskList.vue";
 | 
			
		||||
import BackTop from "@/components/BackTop.vue";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = ref(0)
 | 
			
		||||
// const paramBoxHeight = ref(0)
 | 
			
		||||
@@ -755,7 +756,7 @@ const publishImage = (event, item, action) => {
 | 
			
		||||
    item.publish = action
 | 
			
		||||
    page.value = 0
 | 
			
		||||
    isOver.value = false
 | 
			
		||||
    fetchFinishJobs()
 | 
			
		||||
    item.publish = action
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error(text + "失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 
 | 
			
		||||
@@ -163,7 +163,10 @@
 | 
			
		||||
          <i class="iconfont icon-face"></i>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
      </div>
 | 
			
		||||
        <back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
 | 
			
		||||
 | 
			
		||||
      </div><!-- end of waterfall -->
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- 任务详情弹框 -->
 | 
			
		||||
    <el-dialog v-model="showTaskDialog" title="绘画任务详情" :fullscreen="true">
 | 
			
		||||
@@ -301,6 +304,7 @@ import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import BackTop from "@/components/BackTop.vue";
 | 
			
		||||
 | 
			
		||||
const data = ref({
 | 
			
		||||
  "mj": [],
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user