mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	merge v4.1.2
This commit is contained in:
		@@ -4,8 +4,8 @@ VUE_APP_USER=18575670125
 | 
			
		||||
VUE_APP_PASS=12345678
 | 
			
		||||
VUE_APP_ADMIN_USER=admin
 | 
			
		||||
VUE_APP_ADMIN_PASS=admin123
 | 
			
		||||
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
 | 
			
		||||
VUE_APP_KEY_PREFIX=GeekAI_DEV_
 | 
			
		||||
VUE_APP_TITLE="Geek-AI 创作系统"
 | 
			
		||||
VUE_APP_VERSION=v4.1.1
 | 
			
		||||
VUE_APP_VERSION=v4.1.2
 | 
			
		||||
VUE_APP_DOCS_URL=https://docs.geekai.me
 | 
			
		||||
VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,7 @@
 | 
			
		||||
VUE_APP_API_HOST=
 | 
			
		||||
VUE_APP_WS_HOST=
 | 
			
		||||
VUE_APP_KEY_PREFIX=ChatPLUS_
 | 
			
		||||
VUE_APP_KEY_PREFIX=GeekAI_
 | 
			
		||||
VUE_APP_TITLE="Geek-AI 创作系统"
 | 
			
		||||
VUE_APP_VERSION=v4.1.1
 | 
			
		||||
VUE_APP_VERSION=v4.1.2
 | 
			
		||||
VUE_APP_DOCS_URL=https://docs.geekai.me
 | 
			
		||||
VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
 | 
			
		||||
export function checkSession() {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
        httpGet('/api/user/session').then(res => {
 | 
			
		||||
            resolve(res.data)
 | 
			
		||||
        }).catch(err => {
 | 
			
		||||
            reject(err)
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function checkAdminSession() {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
        httpGet('/api/admin/session').then(res => {
 | 
			
		||||
            resolve(res)
 | 
			
		||||
        }).catch(err => {
 | 
			
		||||
            reject(err)
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
@@ -221,134 +221,7 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // 任务列表
 | 
			
		||||
 | 
			
		||||
        .job-list-box {
 | 
			
		||||
 | 
			
		||||
          @import "running-job-list.styl"
 | 
			
		||||
          
 | 
			
		||||
          .finish-job-list {
 | 
			
		||||
            #waterfall {
 | 
			
		||||
              display flex
 | 
			
		||||
              justify-content center
 | 
			
		||||
              padding-top 20px
 | 
			
		||||
              flex-flow column
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
              .job-item {
 | 
			
		||||
                width 100%
 | 
			
		||||
                height 100%
 | 
			
		||||
                border 1px solid #666666
 | 
			
		||||
                padding 6px
 | 
			
		||||
                overflow hidden
 | 
			
		||||
                border-radius 6px
 | 
			
		||||
                transition: all 0.3s ease; /* 添加过渡效果 */
 | 
			
		||||
                position relative
 | 
			
		||||
 | 
			
		||||
                .opt {
 | 
			
		||||
                  .opt-line {
 | 
			
		||||
                    margin 6px 0
 | 
			
		||||
 | 
			
		||||
                    ul {
 | 
			
		||||
                      display flex
 | 
			
		||||
                      flex-flow row
 | 
			
		||||
 | 
			
		||||
                      li {
 | 
			
		||||
                        margin-right 6px
 | 
			
		||||
 | 
			
		||||
                        a {
 | 
			
		||||
                          padding 3px 0
 | 
			
		||||
                          width 40px
 | 
			
		||||
                          text-align center
 | 
			
		||||
                          border-radius 5px
 | 
			
		||||
                          display block
 | 
			
		||||
                          cursor pointer
 | 
			
		||||
                          background-color #4E5058
 | 
			
		||||
                          color #ffffff
 | 
			
		||||
 | 
			
		||||
                          &:hover {
 | 
			
		||||
                            background-color #6D6F78
 | 
			
		||||
                          }
 | 
			
		||||
                        }
 | 
			
		||||
                      }
 | 
			
		||||
 | 
			
		||||
                      .show-prompt {
 | 
			
		||||
                        font-size 20px
 | 
			
		||||
                        cursor pointer
 | 
			
		||||
                      }
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                .remove {
 | 
			
		||||
                  display none
 | 
			
		||||
                  position absolute
 | 
			
		||||
                  right 10px
 | 
			
		||||
                  top 10px
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                &:hover {
 | 
			
		||||
                  .remove {
 | 
			
		||||
                    display block
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
              .animate {
 | 
			
		||||
                &:hover {
 | 
			
		||||
                  box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */
 | 
			
		||||
                  transform: translateY(-10px); /* 向上移动10像素 */
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
          .el-image {
 | 
			
		||||
            width 100%
 | 
			
		||||
            height 100%
 | 
			
		||||
            overflow visible
 | 
			
		||||
 | 
			
		||||
            .el-image-viewer__wrapper {
 | 
			
		||||
              img {
 | 
			
		||||
                width auto
 | 
			
		||||
                height auto
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .image-slot {
 | 
			
		||||
              display flex
 | 
			
		||||
              flex-flow column
 | 
			
		||||
              justify-content center
 | 
			
		||||
              align-items center
 | 
			
		||||
              min-height 200px
 | 
			
		||||
              color #ffffff
 | 
			
		||||
 | 
			
		||||
              .iconfont {
 | 
			
		||||
                font-size 50px
 | 
			
		||||
                margin-bottom 10px
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .el-image.upscale {
 | 
			
		||||
            img {
 | 
			
		||||
              height 310px
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .image-slot {
 | 
			
		||||
              height 310px
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .el-image-viewer__wrapper {
 | 
			
		||||
              img {
 | 
			
		||||
                width auto
 | 
			
		||||
                height auto
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        @import "waterfall-list.styl"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .no-more-data {
 | 
			
		||||
 
 | 
			
		||||
@@ -220,136 +220,8 @@
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // 任务列表
 | 
			
		||||
 | 
			
		||||
        .job-list-box {
 | 
			
		||||
 | 
			
		||||
          @import "running-job-list.styl"
 | 
			
		||||
 | 
			
		||||
          .finish-job-list {
 | 
			
		||||
            #waterfall {
 | 
			
		||||
              display flex
 | 
			
		||||
              justify-content center
 | 
			
		||||
              padding-top 20px
 | 
			
		||||
              flex-flow column
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
              .job-item {
 | 
			
		||||
                width 100%
 | 
			
		||||
                height 100%
 | 
			
		||||
                border 1px solid #666666
 | 
			
		||||
                padding 6px
 | 
			
		||||
                overflow hidden
 | 
			
		||||
                border-radius 6px
 | 
			
		||||
                transition: all 0.3s ease; /* 添加过渡效果 */
 | 
			
		||||
                position relative
 | 
			
		||||
 | 
			
		||||
                .opt {
 | 
			
		||||
                  .opt-line {
 | 
			
		||||
                    margin 6px 0
 | 
			
		||||
 | 
			
		||||
                    ul {
 | 
			
		||||
                      display flex
 | 
			
		||||
                      flex-flow row
 | 
			
		||||
 | 
			
		||||
                      li {
 | 
			
		||||
                        margin-right 6px
 | 
			
		||||
 | 
			
		||||
                        a {
 | 
			
		||||
                          padding 3px 0
 | 
			
		||||
                          width 40px
 | 
			
		||||
                          text-align center
 | 
			
		||||
                          border-radius 5px
 | 
			
		||||
                          display block
 | 
			
		||||
                          cursor pointer
 | 
			
		||||
                          background-color #4E5058
 | 
			
		||||
                          color #ffffff
 | 
			
		||||
 | 
			
		||||
                          &:hover {
 | 
			
		||||
                            background-color #6D6F78
 | 
			
		||||
                          }
 | 
			
		||||
                        }
 | 
			
		||||
                      }
 | 
			
		||||
 | 
			
		||||
                      .show-prompt {
 | 
			
		||||
                        font-size 20px
 | 
			
		||||
                        cursor pointer
 | 
			
		||||
                      }
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                .remove {
 | 
			
		||||
                  display none
 | 
			
		||||
                  position absolute
 | 
			
		||||
                  right 10px
 | 
			
		||||
                  top 10px
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                &:hover {
 | 
			
		||||
                  .remove {
 | 
			
		||||
                    display block
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
              .animate {
 | 
			
		||||
                &:hover {
 | 
			
		||||
                  box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */
 | 
			
		||||
                  transform: translateY(-10px); /* 向上移动10像素 */
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
          .el-image {
 | 
			
		||||
            width 100%
 | 
			
		||||
            height 100%
 | 
			
		||||
            overflow visible
 | 
			
		||||
 | 
			
		||||
            .el-image-viewer__wrapper {
 | 
			
		||||
              img {
 | 
			
		||||
                width auto
 | 
			
		||||
                height auto
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .image-slot {
 | 
			
		||||
              display flex
 | 
			
		||||
              flex-flow column
 | 
			
		||||
              justify-content center
 | 
			
		||||
              align-items center
 | 
			
		||||
              min-height 200px
 | 
			
		||||
              color #ffffff
 | 
			
		||||
 | 
			
		||||
              .iconfont {
 | 
			
		||||
                font-size 50px
 | 
			
		||||
                margin-bottom 10px
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .el-image.upscale {
 | 
			
		||||
            img {
 | 
			
		||||
              height 310px
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .image-slot {
 | 
			
		||||
              height 310px
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .el-image-viewer__wrapper {
 | 
			
		||||
              img {
 | 
			
		||||
                width auto
 | 
			
		||||
                height auto
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        @import "waterfall-list.styl"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .no-more-data {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										146
									
								
								web/src/assets/css/waterfall-list.styl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								web/src/assets/css/waterfall-list.styl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
			
		||||
.job-list-box {
 | 
			
		||||
 | 
			
		||||
  @import "running-job-list.styl"
 | 
			
		||||
 | 
			
		||||
  .finish-job-list {
 | 
			
		||||
    #waterfall {
 | 
			
		||||
      display flex
 | 
			
		||||
      justify-content center
 | 
			
		||||
      padding-top 20px
 | 
			
		||||
      flex-flow column
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      .job-item {
 | 
			
		||||
        width 100%
 | 
			
		||||
        height 100%
 | 
			
		||||
        border 1px solid #666666
 | 
			
		||||
        padding 6px
 | 
			
		||||
        overflow hidden
 | 
			
		||||
        border-radius 6px
 | 
			
		||||
        transition: all 0.3s ease; /* 添加过渡效果 */
 | 
			
		||||
        position relative
 | 
			
		||||
 | 
			
		||||
        .opt {
 | 
			
		||||
          .opt-line {
 | 
			
		||||
            margin 6px 0
 | 
			
		||||
 | 
			
		||||
            ul {
 | 
			
		||||
              display flex
 | 
			
		||||
              flex-flow row
 | 
			
		||||
 | 
			
		||||
              li {
 | 
			
		||||
                margin-right 6px
 | 
			
		||||
 | 
			
		||||
                a {
 | 
			
		||||
                  padding 3px 0
 | 
			
		||||
                  width 40px
 | 
			
		||||
                  text-align center
 | 
			
		||||
                  border-radius 5px
 | 
			
		||||
                  display block
 | 
			
		||||
                  cursor pointer
 | 
			
		||||
                  background-color #4E5058
 | 
			
		||||
                  color #ffffff
 | 
			
		||||
 | 
			
		||||
                  &:hover {
 | 
			
		||||
                    background-color #6D6F78
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              .show-prompt {
 | 
			
		||||
                font-size 20px
 | 
			
		||||
                cursor pointer
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        .remove {
 | 
			
		||||
          display none
 | 
			
		||||
          position absolute
 | 
			
		||||
          right 10px
 | 
			
		||||
          top 10px
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          .remove {
 | 
			
		||||
            display block
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      .animate {
 | 
			
		||||
        &:hover {
 | 
			
		||||
          box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */
 | 
			
		||||
          transform: translateY(-10px); /* 向上移动10像素 */
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  .el-image {
 | 
			
		||||
    width 100%
 | 
			
		||||
    height 100%
 | 
			
		||||
    overflow visible
 | 
			
		||||
 | 
			
		||||
    .el-image-viewer__wrapper {
 | 
			
		||||
      img {
 | 
			
		||||
        width auto
 | 
			
		||||
        height auto
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .image-slot {
 | 
			
		||||
      display flex
 | 
			
		||||
      flex-flow column
 | 
			
		||||
      justify-content center
 | 
			
		||||
      align-items center
 | 
			
		||||
      min-height 220px
 | 
			
		||||
      color #ffffff
 | 
			
		||||
 | 
			
		||||
      .err-msg-container {
 | 
			
		||||
        overflow hidden
 | 
			
		||||
        word-break break-all
 | 
			
		||||
        padding 15px
 | 
			
		||||
        .title {
 | 
			
		||||
          font-size 20px
 | 
			
		||||
          text-align center
 | 
			
		||||
          font-weight bold
 | 
			
		||||
          color #f56c6c
 | 
			
		||||
          margin-bottom 30px
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .opt {
 | 
			
		||||
          display flex
 | 
			
		||||
          justify-content center
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      .iconfont {
 | 
			
		||||
        font-size 50px
 | 
			
		||||
        margin-bottom 10px
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-image.upscale {
 | 
			
		||||
    img {
 | 
			
		||||
      height 310px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .image-slot {
 | 
			
		||||
      height 310px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-image-viewer__wrapper {
 | 
			
		||||
      img {
 | 
			
		||||
        width auto
 | 
			
		||||
        height auto
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="chat-item">
 | 
			
		||||
        <div class="content" v-html="data.content"></div>
 | 
			
		||||
        <div class="content" v-html="md.render(processContent(data.content))"></div>
 | 
			
		||||
        <div class="bar" v-if="data.created_at">
 | 
			
		||||
          <span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
 | 
			
		||||
                    <span class="bar-item">tokens: {{ data.tokens }}</span>
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
                  content="复制回答"
 | 
			
		||||
                  placement="bottom"
 | 
			
		||||
              >
 | 
			
		||||
                <el-icon class="copy-reply" :data-clipboard-text="data.orgContent">
 | 
			
		||||
                <el-icon class="copy-reply" :data-clipboard-text="data.content">
 | 
			
		||||
                  <DocumentCopy/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
 | 
			
		||||
          <span class="bar-item" @click="synthesis(data.orgContent)">
 | 
			
		||||
          <span class="bar-item" @click="synthesis(data.content)">
 | 
			
		||||
            <el-tooltip
 | 
			
		||||
                class="box-item"
 | 
			
		||||
                effect="dark"
 | 
			
		||||
@@ -69,7 +69,7 @@
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="chat-item">
 | 
			
		||||
        <div class="content-wrapper">
 | 
			
		||||
          <div class="content" v-html="data.content"></div>
 | 
			
		||||
          <div class="content" v-html="md.render(processContent(data.content))"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="bar" v-if="data.created_at">
 | 
			
		||||
          <span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
 | 
			
		||||
@@ -81,7 +81,7 @@
 | 
			
		||||
                  content="复制回答"
 | 
			
		||||
                  placement="bottom"
 | 
			
		||||
              >
 | 
			
		||||
                <el-icon class="copy-reply" :data-clipboard-text="data.orgContent">
 | 
			
		||||
                <el-icon class="copy-reply" :data-clipboard-text="data.content">
 | 
			
		||||
                  <DocumentCopy/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
@@ -98,7 +98,7 @@
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
 | 
			
		||||
          <span class="bar-item bg" @click="synthesis(data.orgContent)">
 | 
			
		||||
          <span class="bar-item bg" @click="synthesis(data.content)">
 | 
			
		||||
            <el-tooltip
 | 
			
		||||
                class="box-item"
 | 
			
		||||
                effect="dark"
 | 
			
		||||
@@ -118,7 +118,8 @@
 | 
			
		||||
<script setup>
 | 
			
		||||
import {Clock, DocumentCopy, Refresh} from "@element-plus/icons-vue";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {dateFormat} from "@/utils/libs";
 | 
			
		||||
import {dateFormat, processContent} from "@/utils/libs";
 | 
			
		||||
import hl from "highlight.js";
 | 
			
		||||
// eslint-disable-next-line no-undef,no-unused-vars
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  data: {
 | 
			
		||||
@@ -128,7 +129,6 @@ const props = defineProps({
 | 
			
		||||
      content: "",
 | 
			
		||||
      created_at: "",
 | 
			
		||||
      tokens: 0,
 | 
			
		||||
      orgContent: ""
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  readOnly: {
 | 
			
		||||
@@ -141,6 +141,33 @@ const props = defineProps({
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const mathjaxPlugin = require('markdown-it-mathjax3')
 | 
			
		||||
const md = require('markdown-it')({
 | 
			
		||||
  breaks: true,
 | 
			
		||||
  html: true,
 | 
			
		||||
  linkify: true,
 | 
			
		||||
  typographer: true,
 | 
			
		||||
  highlight: function (str, lang) {
 | 
			
		||||
    const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
 | 
			
		||||
    // 显示复制代码按钮
 | 
			
		||||
    const copyBtn = `<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
 | 
			
		||||
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(/<\/textarea>/g, '</textarea>')}</textarea>`
 | 
			
		||||
    if (lang && hl.getLanguage(lang)) {
 | 
			
		||||
      const langHtml = `<span class="lang-name">${lang}</span>`
 | 
			
		||||
      // 处理代码高亮
 | 
			
		||||
      const preCode = hl.highlight(lang, str, true).value
 | 
			
		||||
      // 将代码包裹在 pre 中
 | 
			
		||||
      return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 处理代码高亮
 | 
			
		||||
    const preCode = md.utils.escapeHtml(str)
 | 
			
		||||
    // 将代码包裹在 pre 中
 | 
			
		||||
    return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
md.use(mathjaxPlugin)
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['regen']);
 | 
			
		||||
 | 
			
		||||
if (!props.data.icon) {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {showMessageError} from "@/utils/dialog";
 | 
			
		||||
import {getLicenseInfo, getSystemInfo} from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
const title = ref("")
 | 
			
		||||
const version = ref(process.env.VUE_APP_VERSION)
 | 
			
		||||
@@ -30,14 +31,14 @@ const props = defineProps({
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 获取系统配置
 | 
			
		||||
httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
getSystemInfo().then(res => {
 | 
			
		||||
  title.value = res.data.title??process.env.VUE_APP_TITLE
 | 
			
		||||
  copyRight.value = res.data.copyright?res.data.copyright:'极客学长 © 2023 - '+new Date().getFullYear()+' All rights reserved.'
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  showMessageError("获取系统配置失败:" + e.message)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
httpGet("/api/config/license").then(res => {
 | 
			
		||||
getLicenseInfo().then(res => {
 | 
			
		||||
  license.value = res.data
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  showMessageError("获取 License 失败:" + e.message)
 | 
			
		||||
 
 | 
			
		||||
@@ -221,14 +221,15 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {nextTick, onUnmounted, ref, watch} from "vue"
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ref, watch} from "vue"
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {setUserToken} from "@/store/session";
 | 
			
		||||
import {validateEmail, validateMobile} from "@/utils/validate";
 | 
			
		||||
import {Checked, Close, Iphone, Lock, Message} from "@element-plus/icons-vue";
 | 
			
		||||
import SendMsg from "@/components/SendMsg.vue";
 | 
			
		||||
import {arrayContains} from "@/utils/libs";
 | 
			
		||||
import {getSystemInfo} from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
@@ -256,7 +257,7 @@ const wxImg = ref("/images/wx.png")
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const emits = defineEmits(['hide', 'success']);
 | 
			
		||||
 | 
			
		||||
httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
getSystemInfo().then(res => {
 | 
			
		||||
  if (res.data) {
 | 
			
		||||
    const registerWays = res.data['register_ways']
 | 
			
		||||
    if (arrayContains(registerWays, "mobile")) {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,9 @@
 | 
			
		||||
      :title="title"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="form" id="bind-mobile-form">
 | 
			
		||||
      <el-alert v-if="mobile !== ''" type="info" show-icon :closable="false" style="margin-bottom: 20px;">
 | 
			
		||||
        <p>请输入您参与众筹的 <strong style="color:#F56C6C">微信支付转账单号</strong> 兑换相应的对话次数。</p>
 | 
			
		||||
      </el-alert>
 | 
			
		||||
 | 
			
		||||
      <el-form :model="form">
 | 
			
		||||
        <el-form-item label="转账单号">
 | 
			
		||||
          <el-input v-model="form.tx_id"/>
 | 
			
		||||
        <el-form-item label="兑换码">
 | 
			
		||||
          <el-input v-model="form.code"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -22,7 +18,7 @@
 | 
			
		||||
    <template #footer>
 | 
			
		||||
      <span class="dialog-footer">
 | 
			
		||||
        <el-button type="primary" @click="save">
 | 
			
		||||
          确认核销
 | 
			
		||||
          立即兑换
 | 
			
		||||
        </el-button>
 | 
			
		||||
      </span>
 | 
			
		||||
    </template>
 | 
			
		||||
@@ -33,36 +29,33 @@
 | 
			
		||||
import {computed, ref} from "vue";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  show: Boolean,
 | 
			
		||||
  mobile: String
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const showDialog = computed(() => {
 | 
			
		||||
  return props.show
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const title = ref('众筹码核销')
 | 
			
		||||
const title = ref('兑换码核销')
 | 
			
		||||
const form = ref({
 | 
			
		||||
  tx_id: '',
 | 
			
		||||
  code: '',
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['hide']);
 | 
			
		||||
 | 
			
		||||
const save = () => {
 | 
			
		||||
  if (form.value.tx_id === '') {
 | 
			
		||||
    return ElMessage.error({message: "请输入微信支付转账单号"});
 | 
			
		||||
  if (form.value.code === '') {
 | 
			
		||||
    return ElMessage.error({message: "请输入兑换码"});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  httpPost('/api/reward/verify', form.value).then(() => {
 | 
			
		||||
    ElMessage.success({
 | 
			
		||||
      message: '核销成功',
 | 
			
		||||
      duration: 1000,
 | 
			
		||||
      onClose: () => location.reload()
 | 
			
		||||
    })
 | 
			
		||||
  httpPost('/api/redeem/verify', form.value).then(() => {
 | 
			
		||||
    showMessageOK("兑换成功!")
 | 
			
		||||
    emits('hide', true)
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error({message: "核销失败:" + e.message});
 | 
			
		||||
    showMessageError("兑换失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -50,7 +50,7 @@ import {ElMessage} from "element-plus";
 | 
			
		||||
import {Plus} from "@element-plus/icons-vue";
 | 
			
		||||
import Compressor from "compressorjs";
 | 
			
		||||
import {dateFormat} from "@/utils/libs";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession} from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
const user = ref({
 | 
			
		||||
  vip: false,
 | 
			
		||||
 
 | 
			
		||||
@@ -56,8 +56,8 @@
 | 
			
		||||
<script setup>
 | 
			
		||||
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {getSystemInfo} from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
const title = ref(process.env.VUE_APP_TITLE)
 | 
			
		||||
const version = ref(process.env.VUE_APP_VERSION)
 | 
			
		||||
@@ -99,7 +99,7 @@ const capabilities = ref([
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    title.value = res.data.title
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
 
 | 
			
		||||
@@ -118,8 +118,8 @@ const items = [
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'reward',
 | 
			
		||||
    index: '/admin/reward',
 | 
			
		||||
    title: '众筹管理',
 | 
			
		||||
    index: '/admin/redeem',
 | 
			
		||||
    title: '兑换码',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'control',
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
import {useTagsStore} from '@/store/tags';
 | 
			
		||||
import {onBeforeRouteUpdate, useRoute, useRouter} from 'vue-router';
 | 
			
		||||
import {ArrowDown, Close} from "@element-plus/icons-vue";
 | 
			
		||||
import {checkAdminSession} from "@/action/session";
 | 
			
		||||
import {checkAdminSession} from "@/store/cache";
 | 
			
		||||
import {ElMessageBox} from "element-plus";
 | 
			
		||||
import {computed} from "vue";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,6 @@
 | 
			
		||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
import {createRouter, createWebHistory} from "vue-router";
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
 | 
			
		||||
const routes = [
 | 
			
		||||
    {
 | 
			
		||||
@@ -188,10 +186,10 @@ const routes = [
 | 
			
		||||
                component: () => import('@/views/admin/Order.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/admin/reward',
 | 
			
		||||
                name: 'admin-reward',
 | 
			
		||||
                meta: {title: '众筹管理'},
 | 
			
		||||
                component: () => import('@/views/admin/Reward.vue'),
 | 
			
		||||
                path: '/admin/redeem',
 | 
			
		||||
                name: 'admin-redeem',
 | 
			
		||||
                meta: {title: '兑换码管理'},
 | 
			
		||||
                component: () => import('@/views/admin/Redeem.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/admin/loginLog',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										90
									
								
								web/src/store/cache.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								web/src/store/cache.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import Storage from "good-storage";
 | 
			
		||||
import {showMessageError} from "@/utils/dialog";
 | 
			
		||||
 | 
			
		||||
const userDataKey = "USER_INFO_CACHE_KEY"
 | 
			
		||||
const adminDataKey = "ADMIN_INFO_CACHE_KEY"
 | 
			
		||||
const systemInfoKey = "SYSTEM_INFO_CACHE_KEY"
 | 
			
		||||
const licenseInfoKey = "LICENSE_INFO_CACHE_KEY"
 | 
			
		||||
export function checkSession() {
 | 
			
		||||
    const item = Storage.get(userDataKey) ?? {expire:0, data:null}
 | 
			
		||||
    if (item.expire > Date.now()) {
 | 
			
		||||
        return Promise.resolve(item.data)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
        httpGet('/api/user/session').then(res => {
 | 
			
		||||
            item.data = res.data
 | 
			
		||||
            // cache expires after 5 minutes
 | 
			
		||||
            item.expire = Date.now() + 1000 * 60 * 5
 | 
			
		||||
            Storage.set(userDataKey, item)
 | 
			
		||||
            resolve(item.data)
 | 
			
		||||
        }).catch(err => {
 | 
			
		||||
            Storage.remove(userDataKey)
 | 
			
		||||
            reject(err)
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function removeUserInfo() {
 | 
			
		||||
    Storage.remove(userDataKey)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function checkAdminSession() {
 | 
			
		||||
    const item = Storage.get(adminDataKey) ?? {expire:0, data:null}
 | 
			
		||||
    if (item.expire > Date.now()) {
 | 
			
		||||
        return Promise.resolve(item.data)
 | 
			
		||||
    }
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
        httpGet('/api/admin/session').then(res => {
 | 
			
		||||
            item.data = res.data
 | 
			
		||||
            // cache expires after 10 minutes
 | 
			
		||||
            item.expire = Date.now() + 1000 * 60 * 10
 | 
			
		||||
            Storage.set(adminDataKey, item)
 | 
			
		||||
            resolve(item.data)
 | 
			
		||||
        }).catch(err => {
 | 
			
		||||
            reject(err)
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function removeAdminInfo() {
 | 
			
		||||
    Storage.remove(adminDataKey)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getSystemInfo() {
 | 
			
		||||
    const item = Storage.get(systemInfoKey) ?? {expire:0, data:null}
 | 
			
		||||
    if (item.expire > Date.now()) {
 | 
			
		||||
        return Promise.resolve(item.data)
 | 
			
		||||
    }
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
        httpGet('/api/config/get?key=system').then(res => {
 | 
			
		||||
            item.data = res
 | 
			
		||||
            // cache expires after 10 minutes
 | 
			
		||||
            item.expire = Date.now() + 1000 * 60 * 10
 | 
			
		||||
            Storage.set(systemInfoKey, item)
 | 
			
		||||
            resolve(item.data)
 | 
			
		||||
        }).catch(err => {
 | 
			
		||||
            reject(err)
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getLicenseInfo() {
 | 
			
		||||
    const item = Storage.get(licenseInfoKey) ?? {expire:0, data:null}
 | 
			
		||||
    if (item.expire > Date.now()) {
 | 
			
		||||
        return Promise.resolve(item.data)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
        httpGet('/api/config/license').then(res => {
 | 
			
		||||
            item.data = res
 | 
			
		||||
            // cache expires after 10 minutes
 | 
			
		||||
            item.expire = Date.now() + 1000 * 60 * 10
 | 
			
		||||
            Storage.set(licenseInfoKey, item)
 | 
			
		||||
            resolve(item.data)
 | 
			
		||||
        }).catch(err => {
 | 
			
		||||
            reject(err)
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
import Storage from 'good-storage'
 | 
			
		||||
 | 
			
		||||
const CHAT_CONFIG_KEY = process.env.VUE_APP_KEY_PREFIX + "chat_config"
 | 
			
		||||
 | 
			
		||||
export function getChatConfig() {
 | 
			
		||||
    return Storage.get(CHAT_CONFIG_KEY)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function setChatConfig(chatConfig) {
 | 
			
		||||
    Storage.set(CHAT_CONFIG_KEY, chatConfig)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import {randString} from "@/utils/libs";
 | 
			
		||||
import Storage from "good-storage";
 | 
			
		||||
import {removeAdminInfo, removeUserInfo} from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * storage handler
 | 
			
		||||
@@ -22,6 +23,7 @@ export function setUserToken(token) {
 | 
			
		||||
 | 
			
		||||
export function removeUserToken() {
 | 
			
		||||
    Storage.remove(UserTokenKey)
 | 
			
		||||
    removeUserInfo()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getAdminToken() {
 | 
			
		||||
@@ -34,4 +36,5 @@ export function setAdminToken(token) {
 | 
			
		||||
 | 
			
		||||
export function removeAdminToken() {
 | 
			
		||||
    Storage.remove(AdminTokenKey)
 | 
			
		||||
    removeAdminInfo()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,6 @@ axios.interceptors.response.use(
 | 
			
		||||
            } else {
 | 
			
		||||
                removeUserToken()
 | 
			
		||||
            }
 | 
			
		||||
            console.log(error.response.data)
 | 
			
		||||
            error.response.data.message = "请先登录"
 | 
			
		||||
            return Promise.reject(error.response.data)
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@
 | 
			
		||||
import {onMounted, ref} from "vue"
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession} from "@/store/cache";
 | 
			
		||||
import {arrayContains, removeArrayItem, substr} from "@/utils/libs";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
 
 | 
			
		||||
@@ -204,26 +204,24 @@ import {Delete, Edit, More, Plus, Promotion, Search, Share, VideoPause} from '@e
 | 
			
		||||
import 'highlight.js/styles/a11y-dark.css'
 | 
			
		||||
import {
 | 
			
		||||
  isMobile,
 | 
			
		||||
  processContent,
 | 
			
		||||
  randString,
 | 
			
		||||
  removeArrayItem,
 | 
			
		||||
  UUID
 | 
			
		||||
} from "@/utils/libs";
 | 
			
		||||
import {ElMessage, ElMessageBox} from "element-plus";
 | 
			
		||||
import hl from "highlight.js";
 | 
			
		||||
import {getSessionId, getUserToken, removeUserToken} from "@/store/session";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import Welcome from "@/components/Welcome.vue";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
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";
 | 
			
		||||
import hl from "highlight.js";
 | 
			
		||||
 | 
			
		||||
const title = ref('ChatGPT-智能助手');
 | 
			
		||||
const models = ref([])
 | 
			
		||||
@@ -262,12 +260,18 @@ if (isMobile()) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取系统配置
 | 
			
		||||
httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
getSystemInfo().then(res => {
 | 
			
		||||
  title.value = res.data.title
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const md = require('markdown-it')({
 | 
			
		||||
  breaks: true,
 | 
			
		||||
  html: true,
 | 
			
		||||
  linkify: true,
 | 
			
		||||
  typographer: true
 | 
			
		||||
});
 | 
			
		||||
// 获取系统公告
 | 
			
		||||
httpGet("/api/config/get?key=notice").then(res => {
 | 
			
		||||
  try {
 | 
			
		||||
@@ -367,7 +371,6 @@ const initData = () => {
 | 
			
		||||
  inputRef.value.addEventListener('paste', (event) => {
 | 
			
		||||
    const items = (event.clipboardData || window.clipboardData).items;
 | 
			
		||||
    let fileFound = false;
 | 
			
		||||
    loading.value = true
 | 
			
		||||
 | 
			
		||||
    for (let item of items) {
 | 
			
		||||
      if (item.kind === 'file') {
 | 
			
		||||
@@ -376,6 +379,7 @@ const initData = () => {
 | 
			
		||||
 | 
			
		||||
        const formData = new FormData();
 | 
			
		||||
        formData.append('file', file);
 | 
			
		||||
        loading.value = true
 | 
			
		||||
        // 执行上传操作
 | 
			
		||||
        httpPost('/api/upload', formData).then((res) => {
 | 
			
		||||
          files.value.push(res.data)
 | 
			
		||||
@@ -389,7 +393,7 @@ const initData = () => {
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    if (!fileFound) {
 | 
			
		||||
      document.getElementById('status').innerText = 'No file found in paste data.';
 | 
			
		||||
    }
 | 
			
		||||
@@ -547,33 +551,6 @@ const removeChat = function (chat) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mathjaxPlugin = require('markdown-it-mathjax3')
 | 
			
		||||
const md = require('markdown-it')({
 | 
			
		||||
  breaks: true,
 | 
			
		||||
  html: true,
 | 
			
		||||
  linkify: true,
 | 
			
		||||
  typographer: true,
 | 
			
		||||
  highlight: function (str, lang) {
 | 
			
		||||
    const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
 | 
			
		||||
    // 显示复制代码按钮
 | 
			
		||||
    const copyBtn = `<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
 | 
			
		||||
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(/<\/textarea>/g, '</textarea>')}</textarea>`
 | 
			
		||||
    if (lang && hl.getLanguage(lang)) {
 | 
			
		||||
      const langHtml = `<span class="lang-name">${lang}</span>`
 | 
			
		||||
      // 处理代码高亮
 | 
			
		||||
      const preCode = hl.highlight(lang, str, true).value
 | 
			
		||||
      // 将代码包裹在 pre 中
 | 
			
		||||
      return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 处理代码高亮
 | 
			
		||||
    const preCode = md.utils.escapeHtml(str)
 | 
			
		||||
    // 将代码包裹在 pre 中
 | 
			
		||||
    return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
md.use(mathjaxPlugin)
 | 
			
		||||
 | 
			
		||||
// 创建 socket 连接
 | 
			
		||||
const prompt = ref('');
 | 
			
		||||
const showStopGenerate = ref(false); // 停止生成
 | 
			
		||||
@@ -622,7 +599,6 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
        id: randString(32),
 | 
			
		||||
        icon: _role['icon'],
 | 
			
		||||
        content: _role['hello_msg'],
 | 
			
		||||
        orgContent: _role['hello_msg'],
 | 
			
		||||
      })
 | 
			
		||||
      ElMessage.success({message: "对话连接成功!", duration: 1000})
 | 
			
		||||
    } else { // 加载聊天记录
 | 
			
		||||
@@ -645,7 +621,6 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
              icon: _role['icon'],
 | 
			
		||||
              prompt:prePrompt,
 | 
			
		||||
              content: "",
 | 
			
		||||
              orgContent: "",
 | 
			
		||||
            });
 | 
			
		||||
          } else if (data.type === 'end') { // 消息接收完毕
 | 
			
		||||
            // 追加当前会话到会话列表
 | 
			
		||||
@@ -680,8 +655,7 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
            lineBuffer.value += data.content;
 | 
			
		||||
            const reply = chatData.value[chatData.value.length - 1]
 | 
			
		||||
            if (reply) {
 | 
			
		||||
              reply['orgContent'] = lineBuffer.value;
 | 
			
		||||
              reply['content'] = md.render(processContent(lineBuffer.value));
 | 
			
		||||
              reply['content'] = lineBuffer.value;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          // 将聊天框的滚动条滑动到最底部
 | 
			
		||||
@@ -845,12 +819,8 @@ const loadChatHistory = function (chatId) {
 | 
			
		||||
    }
 | 
			
		||||
    showHello.value = false
 | 
			
		||||
    for (let i = 0; i < data.length; i++) {
 | 
			
		||||
      data[i].orgContent = data[i].content;
 | 
			
		||||
      if (data[i].type === 'reply') {
 | 
			
		||||
        data[i].content = md.render(processContent(data[i].content))
 | 
			
		||||
        if (i > 0) {
 | 
			
		||||
          data[i].prompt = data[i - 1].orgContent
 | 
			
		||||
        }
 | 
			
		||||
      if (data[i].type === 'reply' && i > 0) {
 | 
			
		||||
        data[i].prompt = data[i - 1].content
 | 
			
		||||
      }
 | 
			
		||||
      chatData.value.push(data[i]);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -125,6 +125,24 @@
 | 
			
		||||
                          </template>
 | 
			
		||||
                        </el-image>
 | 
			
		||||
 | 
			
		||||
                        <el-image v-else-if="slotProp.item.progress === 101">
 | 
			
		||||
                          <template #error>
 | 
			
		||||
                            <div class="image-slot">
 | 
			
		||||
                              <div class="err-msg-container">
 | 
			
		||||
                                <div class="title">任务失败</div>
 | 
			
		||||
                                <div class="opt">
 | 
			
		||||
                                  <el-popover title="错误详情" trigger="click" :width="250" :content="slotProp.item['err_msg']" placement="top">
 | 
			
		||||
                                    <template #reference>
 | 
			
		||||
                                      <el-button type="info">详情</el-button>
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                  </el-popover>
 | 
			
		||||
                                  <el-button type="danger"  @click="removeImage(slotProp.item)">删除</el-button>
 | 
			
		||||
                                </div>
 | 
			
		||||
                              </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                          </template>
 | 
			
		||||
                        </el-image>
 | 
			
		||||
 | 
			
		||||
                        <el-image v-else>
 | 
			
		||||
                          <template #error>
 | 
			
		||||
                            <div class="image-slot">
 | 
			
		||||
@@ -136,17 +154,17 @@
 | 
			
		||||
 | 
			
		||||
                        <div class="remove">
 | 
			
		||||
                          <el-tooltip content="删除" placement="top" effect="light">
 | 
			
		||||
                            <el-button type="danger" :icon="Delete" @click="removeImage($event,slotProp.item)" circle/>
 | 
			
		||||
                            <el-button type="danger" :icon="Delete" @click="removeImage(slotProp.item)" circle/>
 | 
			
		||||
                          </el-tooltip>
 | 
			
		||||
                          <el-tooltip content="分享" placement="top" effect="light" v-if="slotProp.item.publish">
 | 
			
		||||
                            <el-button type="warning"
 | 
			
		||||
                                       @click="publishImage($event,slotProp.item, false)"
 | 
			
		||||
                                       @click="publishImage(slotProp.item, false)"
 | 
			
		||||
                                       circle>
 | 
			
		||||
                              <i class="iconfont icon-cancel-share"></i>
 | 
			
		||||
                            </el-button>
 | 
			
		||||
                          </el-tooltip>
 | 
			
		||||
                          <el-tooltip content="取消分享" placement="top" effect="light" v-else>
 | 
			
		||||
                            <el-button type="success" @click="publishImage($event,slotProp.item, true)" circle>
 | 
			
		||||
                            <el-button type="success" @click="publishImage(slotProp.item, true)" circle>
 | 
			
		||||
                              <i class="iconfont icon-share-bold"></i>
 | 
			
		||||
                            </el-button>
 | 
			
		||||
                          </el-tooltip>
 | 
			
		||||
@@ -185,12 +203,12 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, onUnmounted, ref} from "vue"
 | 
			
		||||
import {nextTick, onMounted, onUnmounted, ref} from "vue"
 | 
			
		||||
import {Delete, InfoFilled, Picture} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import TaskList from "@/components/TaskList.vue";
 | 
			
		||||
import BackTop from "@/components/BackTop.vue";
 | 
			
		||||
@@ -245,7 +263,7 @@ onMounted(() => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    dallPower.value = res.data["dall_power"]
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
@@ -286,25 +304,9 @@ const connect = () => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 心跳函数
 | 
			
		||||
  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/dall/client?user_id=${userId.value}`);
 | 
			
		||||
  _socket.addEventListener('open', () => {
 | 
			
		||||
    socket.value = _socket;
 | 
			
		||||
 | 
			
		||||
    // 发送心跳消息
 | 
			
		||||
    sendHeartbeat()
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('message', event => {
 | 
			
		||||
@@ -313,12 +315,12 @@ const connect = () => {
 | 
			
		||||
      reader.readAsText(event.data, "UTF-8")
 | 
			
		||||
      reader.onload = () => {
 | 
			
		||||
        const message = String(reader.result)
 | 
			
		||||
        if (message === "FINISH") {
 | 
			
		||||
        if (message === "FINISH" || message === "FAIL") {
 | 
			
		||||
          page.value = 0
 | 
			
		||||
          isOver.value = false
 | 
			
		||||
          fetchFinishJobs(page.value)
 | 
			
		||||
        }
 | 
			
		||||
        fetchRunningJobs()
 | 
			
		||||
        nextTick(() => fetchRunningJobs())
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
@@ -336,22 +338,7 @@ const fetchRunningJobs = () => {
 | 
			
		||||
  }
 | 
			
		||||
  // 获取运行中的任务
 | 
			
		||||
  httpGet(`/api/dall/jobs?finish=false`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    const _jobs = []
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
      if (jobs[i].progress === -1) {
 | 
			
		||||
        ElNotification({
 | 
			
		||||
          title: '任务执行失败',
 | 
			
		||||
          dangerouslyUseHTMLString: true,
 | 
			
		||||
          message: `任务ID:${jobs[i]['task_id']}<br />原因:${jobs[i]['err_msg']}`,
 | 
			
		||||
          type: 'error',
 | 
			
		||||
        })
 | 
			
		||||
        power.value += dallPower.value
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
      _jobs.push(jobs[i])
 | 
			
		||||
    }
 | 
			
		||||
    runningJobs.value = _jobs
 | 
			
		||||
    runningJobs.value = res.data
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取任务失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
@@ -409,8 +396,7 @@ const generate = () => {
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const removeImage = (event, item) => {
 | 
			
		||||
  event.stopPropagation()
 | 
			
		||||
const removeImage = (item) => {
 | 
			
		||||
  ElMessageBox.confirm(
 | 
			
		||||
      '此操作将会删除任务和图片,继续操作码?',
 | 
			
		||||
      '删除提示',
 | 
			
		||||
@@ -420,7 +406,7 @@ const removeImage = (event, item) => {
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
      }
 | 
			
		||||
  ).then(() => {
 | 
			
		||||
    httpGet("/api/dall/remove", {id: item.id, user_id: item.user}).then(() => {
 | 
			
		||||
    httpGet("/api/dall/remove", {id: item.id}).then(() => {
 | 
			
		||||
      ElMessage.success("任务删除成功")
 | 
			
		||||
      page.value = 0
 | 
			
		||||
      isOver.value = false
 | 
			
		||||
@@ -437,18 +423,16 @@ const previewImg = (item) => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 发布图片到作品墙
 | 
			
		||||
const publishImage = (event, item, action) => {
 | 
			
		||||
  event.stopPropagation()
 | 
			
		||||
const publishImage = (item, action) => {
 | 
			
		||||
  let text = "图片发布"
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    text = "取消发布"
 | 
			
		||||
  }
 | 
			
		||||
  httpGet("/api/dall/publish", {id: item.id, action: action,user_id:item.user_id}).then(() => {
 | 
			
		||||
  httpGet("/api/dall/publish", {id: item.id, action: action}).then(() => {
 | 
			
		||||
    ElMessage.success(text + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
    page.value = 0
 | 
			
		||||
    isOver.value = false
 | 
			
		||||
    fetchFinishJobs()
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error(text + "失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
            effect="light"
 | 
			
		||||
            content="部署文档"
 | 
			
		||||
            placement="bottom">
 | 
			
		||||
          <a href="https://docs.geekai.me/install/" class="link-button" target="_blank">
 | 
			
		||||
          <a :href="docsURL" class="link-button" target="_blank">
 | 
			
		||||
            <i class="iconfont icon-book"></i>
 | 
			
		||||
          </a>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
@@ -138,7 +138,7 @@ import {onMounted, ref, watch} from "vue";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {UserFilled} from "@element-plus/icons-vue";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession, getLicenseInfo, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {removeUserToken} from "@/store/session";
 | 
			
		||||
import LoginDialog from "@/components/LoginDialog.vue";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
@@ -185,7 +185,7 @@ const changeNav = (item) => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    logo.value = res.data.logo
 | 
			
		||||
    title.value = res.data.title
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
@@ -204,7 +204,7 @@ onMounted(() => {
 | 
			
		||||
    ElMessage.error("获取系统菜单失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/config/license").then(res => {
 | 
			
		||||
  getLicenseInfo().then(res => {
 | 
			
		||||
    license.value = res.data
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    license.value = {de_copy: false}
 | 
			
		||||
 
 | 
			
		||||
@@ -487,7 +487,7 @@
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
                      </el-image>
 | 
			
		||||
                      <el-image v-else-if="slotProp.item['err_msg'] !== ''">
 | 
			
		||||
                      <el-image v-else-if="slotProp.item.progress === 101">
 | 
			
		||||
                        <template #error>
 | 
			
		||||
                          <div class="image-slot">
 | 
			
		||||
                            <div class="err-msg-container">
 | 
			
		||||
@@ -608,7 +608,7 @@ import Compressor from "compressorjs";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {copyObj, removeArrayItem} from "@/utils/libs";
 | 
			
		||||
@@ -802,7 +802,7 @@ const initData = () => {
 | 
			
		||||
 | 
			
		||||
const mjPower = ref(1)
 | 
			
		||||
const mjActionPower = ref(1)
 | 
			
		||||
httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
getSystemInfo().then(res => {
 | 
			
		||||
  mjPower.value = res.data["mj_power"]
 | 
			
		||||
  mjActionPower.value = res.data["mj_action_power"]
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
 
 | 
			
		||||
@@ -313,21 +313,41 @@
 | 
			
		||||
                      :isOver="isOver"
 | 
			
		||||
                      @scrollReachBottom="fetchFinishJobs()">
 | 
			
		||||
                    <template #default="slotProp">
 | 
			
		||||
                      <div class="job-item animate" @click="showTask(slotProp.item)">
 | 
			
		||||
                        <el-image
 | 
			
		||||
                            :src="slotProp.item['img_thumb']"
 | 
			
		||||
                            fit="cover"
 | 
			
		||||
                            loading="lazy"/>
 | 
			
		||||
                        <div class="remove">
 | 
			
		||||
                          <el-button type="danger" :icon="Delete" @click="removeImage($event,slotProp.item)" circle/>
 | 
			
		||||
                          <el-button type="warning" v-if="slotProp.item.publish"
 | 
			
		||||
                                     @click="publishImage($event,slotProp.item, false)"
 | 
			
		||||
                                     circle>
 | 
			
		||||
                            <i class="iconfont icon-cancel-share"></i>
 | 
			
		||||
                          </el-button>
 | 
			
		||||
                          <el-button type="success" v-else @click="publishImage($event,slotProp.item, true)" circle>
 | 
			
		||||
                            <i class="iconfont icon-share-bold"></i>
 | 
			
		||||
                          </el-button>
 | 
			
		||||
                      <div class="job-item animate">
 | 
			
		||||
                        <el-image v-if="slotProp.item.progress === 101">
 | 
			
		||||
                          <template #error>
 | 
			
		||||
                            <div class="image-slot">
 | 
			
		||||
                              <div class="err-msg-container">
 | 
			
		||||
                                <div class="title">任务失败</div>
 | 
			
		||||
                                <div class="opt">
 | 
			
		||||
                                  <el-popover title="错误详情" trigger="click" :width="250" :content="slotProp.item['err_msg']" placement="top">
 | 
			
		||||
                                    <template #reference>
 | 
			
		||||
                                      <el-button type="info">详情</el-button>
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                  </el-popover>
 | 
			
		||||
                                  <el-button type="danger"  @click="removeImage(slotProp.item)">删除</el-button>
 | 
			
		||||
                                </div>
 | 
			
		||||
                              </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                          </template>
 | 
			
		||||
                        </el-image>
 | 
			
		||||
                        <div v-else>
 | 
			
		||||
                          <el-image
 | 
			
		||||
                              :src="slotProp.item['img_thumb']"
 | 
			
		||||
                              @click="showTask(slotProp.item)"
 | 
			
		||||
                              fit="cover"
 | 
			
		||||
                              loading="lazy"/>
 | 
			
		||||
                          <div class="remove">
 | 
			
		||||
                            <el-button type="danger" :icon="Delete" @click="removeImage(slotProp.item)" circle/>
 | 
			
		||||
                            <el-button type="warning" v-if="slotProp.item.publish"
 | 
			
		||||
                                       @click="publishImage(slotProp.item, false)"
 | 
			
		||||
                                       circle>
 | 
			
		||||
                              <i class="iconfont icon-cancel-share"></i>
 | 
			
		||||
                            </el-button>
 | 
			
		||||
                            <el-button type="success" v-else @click="publishImage(slotProp.item, true)" circle>
 | 
			
		||||
                              <i class="iconfont icon-share-bold"></i>
 | 
			
		||||
                            </el-button>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
@@ -466,12 +486,12 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, onUnmounted, ref} from "vue"
 | 
			
		||||
import {nextTick, onMounted, onUnmounted, ref} from "vue"
 | 
			
		||||
import {Delete, DocumentCopy, InfoFilled, Orange, Picture} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
@@ -540,25 +560,9 @@ const connect = () => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 心跳函数
 | 
			
		||||
  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/sd/client?user_id=${userId.value}`);
 | 
			
		||||
  _socket.addEventListener('open', () => {
 | 
			
		||||
    socket.value = _socket;
 | 
			
		||||
 | 
			
		||||
    // 发送心跳消息
 | 
			
		||||
    sendHeartbeat()
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('message', event => {
 | 
			
		||||
@@ -567,12 +571,12 @@ const connect = () => {
 | 
			
		||||
      reader.readAsText(event.data, "UTF-8")
 | 
			
		||||
      reader.onload = () => {
 | 
			
		||||
        const message = String(reader.result)
 | 
			
		||||
        if (message === "FINISH") {
 | 
			
		||||
        if (message === "FINISH" || message === "FAIL") {
 | 
			
		||||
          page.value = 0
 | 
			
		||||
          isOver.value = false
 | 
			
		||||
          fetchFinishJobs()
 | 
			
		||||
        }
 | 
			
		||||
        fetchRunningJobs()
 | 
			
		||||
        nextTick(() => fetchRunningJobs())
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
@@ -596,7 +600,7 @@ onMounted(() => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    sdPower.value = res.data.sd_power
 | 
			
		||||
    params.value.neg_prompt = res.data.sd_neg_prompt
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
@@ -633,22 +637,7 @@ const fetchRunningJobs = () => {
 | 
			
		||||
 | 
			
		||||
  // 获取运行中的任务
 | 
			
		||||
  httpGet(`/api/sd/jobs?finish=0`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    const _jobs = []
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
      if (jobs[i].progress === -1) {
 | 
			
		||||
        ElNotification({
 | 
			
		||||
          title: '任务执行失败',
 | 
			
		||||
          dangerouslyUseHTMLString: true,
 | 
			
		||||
          message: `任务ID:${jobs[i]['task_id']}<br />原因:${jobs[i]['err_msg']}`,
 | 
			
		||||
          type: 'error',
 | 
			
		||||
        })
 | 
			
		||||
        power.value += sdPower.value
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
      _jobs.push(jobs[i])
 | 
			
		||||
    }
 | 
			
		||||
    runningJobs.value = _jobs
 | 
			
		||||
    runningJobs.value = res.data
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取任务失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
@@ -699,7 +688,7 @@ const generate = () => {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (params.value.seed === '') {
 | 
			
		||||
  if (!params.value.seed) {
 | 
			
		||||
    params.value.seed = -1
 | 
			
		||||
  }
 | 
			
		||||
  params.value.session_id = getSessionId()
 | 
			
		||||
@@ -721,8 +710,7 @@ const copyParams = (row) => {
 | 
			
		||||
  showTaskDialog.value = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const removeImage = (event, item) => {
 | 
			
		||||
  event.stopPropagation()
 | 
			
		||||
const removeImage = (item) => {
 | 
			
		||||
  ElMessageBox.confirm(
 | 
			
		||||
      '此操作将会删除任务和图片,继续操作码?',
 | 
			
		||||
      '删除提示',
 | 
			
		||||
@@ -732,7 +720,7 @@ const removeImage = (event, item) => {
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
      }
 | 
			
		||||
  ).then(() => {
 | 
			
		||||
    httpGet("/api/sd/remove", {id: item.id, user_id: item.user}).then(() => {
 | 
			
		||||
    httpGet("/api/sd/remove", {id: item.id}).then(() => {
 | 
			
		||||
      ElMessage.success("任务删除成功")
 | 
			
		||||
      page.value = 0
 | 
			
		||||
      isOver.value = false
 | 
			
		||||
@@ -745,13 +733,12 @@ const removeImage = (event, item) => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 发布图片到作品墙
 | 
			
		||||
const publishImage = (event, item, action) => {
 | 
			
		||||
  event.stopPropagation()
 | 
			
		||||
const publishImage = (item, action) => {
 | 
			
		||||
  let text = "图片发布"
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    text = "取消发布"
 | 
			
		||||
  }
 | 
			
		||||
  httpGet("/api/sd/publish", {id: item.id, action: action, user_id: item.user}).then(() => {
 | 
			
		||||
  httpGet("/api/sd/publish", {id: item.id, action: action}).then(() => {
 | 
			
		||||
    ElMessage.success(text + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
    page.value = 0
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ import FooterBar from "@/components/FooterBar.vue";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {isMobile} from "@/utils/libs";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession, getLicenseInfo, getSystemInfo} from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
 | 
			
		||||
@@ -131,7 +131,7 @@ const color = btnColors.value[Math.floor(Math.random() * btnColors.value.length)
 | 
			
		||||
const theme = ref({bgColor: "#ffffff", btnBgColor: color.bgColor, btnTextColor: color.textColor, textColor: "#ffffff", imageBg:true})
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    title.value = res.data.title
 | 
			
		||||
    logo.value = res.data.logo
 | 
			
		||||
    if (res.data.index_bg_url === 'color') {
 | 
			
		||||
@@ -155,7 +155,7 @@ onMounted(() => {
 | 
			
		||||
    ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/config/license").then(res => {
 | 
			
		||||
  getLicenseInfo().then(res => {
 | 
			
		||||
    license.value = res.data
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    license.value = {de_copy: false}
 | 
			
		||||
 
 | 
			
		||||
@@ -93,7 +93,7 @@ import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import InviteList from "@/components/InviteList.vue";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
 | 
			
		||||
const inviteURL = ref("")
 | 
			
		||||
@@ -141,7 +141,7 @@ const initData = () => {
 | 
			
		||||
      ElMessage.error("获取邀请码失败:" + e.message)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
    getSystemInfo().then(res => {
 | 
			
		||||
      invitePower.value = res.data["invite_power"]
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@ import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import FooterBar from "@/components/FooterBar.vue";
 | 
			
		||||
import {isMobile} from "@/utils/libs";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession, getLicenseInfo, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {setUserToken} from "@/store/session";
 | 
			
		||||
import ResetPass from "@/components/ResetPass.vue";
 | 
			
		||||
import {showMessageError} from "@/utils/dialog";
 | 
			
		||||
@@ -85,14 +85,14 @@ const wechatLoginURL = ref('')
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  // 获取系统配置
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    logo.value = res.data.logo
 | 
			
		||||
    title.value = res.data.title
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showMessageError("获取系统配置失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/config/license").then(res => {
 | 
			
		||||
  getLicenseInfo().then(res => {
 | 
			
		||||
    licenseConfig.value = res.data
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showMessageError("获取 License 配置:" + e.message)
 | 
			
		||||
 
 | 
			
		||||
@@ -94,10 +94,10 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {nextTick, onMounted, onUnmounted, ref} from 'vue';
 | 
			
		||||
import {nextTick, onUnmounted, ref} from 'vue';
 | 
			
		||||
import {Markmap} from 'markmap-view';
 | 
			
		||||
import {Transformer} from 'markmap-lib';
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {Download} from "@element-plus/icons-vue";
 | 
			
		||||
@@ -126,29 +126,20 @@ const models = ref([])
 | 
			
		||||
const modelID = ref(0)
 | 
			
		||||
const loading = ref(false)
 | 
			
		||||
 | 
			
		||||
httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  title.value = res.data.title??process.env.VUE_APP_TITLE
 | 
			
		||||
  text.value = `# ${title.value}
 | 
			
		||||
 | 
			
		||||
- 完整的开源系统,前端应用和后台管理系统皆可开箱即用。
 | 
			
		||||
- 基于 Websocket 实现,完美的打字机体验。
 | 
			
		||||
- 内置了各种预训练好的角色应用,轻松满足你的各种聊天和应用需求。
 | 
			
		||||
- 支持 OPenAI,Azure,文心一言,讯飞星火,清华 ChatGLM等多个大语言模型。
 | 
			
		||||
- 支持 MidJourney / Stable Diffusion AI 绘画集成,开箱即用。
 | 
			
		||||
- 支持使用个人微信二维码作为充值收费的支付渠道,无需企业支付通道。
 | 
			
		||||
- 已集成支付宝支付功能,微信支付,支持多种会员套餐和点卡购买功能。
 | 
			
		||||
- 集成插件 API 功能,可结合大语言模型的 function 功能开发各种强大的插件。
 | 
			
		||||
`
 | 
			
		||||
getSystemInfo().then(res => {
 | 
			
		||||
  text.value = res.data['mark_map_text']
 | 
			
		||||
  content.value = text.value
 | 
			
		||||
  initData()
 | 
			
		||||
  try {
 | 
			
		||||
    markMap.value = Markmap.create(svgRef.value)
 | 
			
		||||
    const {el} = Toolbar.create(markMap.value);
 | 
			
		||||
    document.getElementById('toolbar').append(el);
 | 
			
		||||
    update()
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error(e)
 | 
			
		||||
  }
 | 
			
		||||
  nextTick(() => {
 | 
			
		||||
    try {
 | 
			
		||||
      markMap.value = Markmap.create(svgRef.value)
 | 
			
		||||
      const {el} = Toolbar.create(markMap.value);
 | 
			
		||||
      document.getElementById('toolbar').append(el);
 | 
			
		||||
      update()
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e)
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
})
 | 
			
		||||
@@ -181,6 +172,10 @@ const update = () => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const processContent = (text) => {
 | 
			
		||||
  if (!text) {
 | 
			
		||||
    return text
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const arr = []
 | 
			
		||||
  const lines = text.split("\n")
 | 
			
		||||
  for (let line of lines) {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
    <div class="member custom-scroll">
 | 
			
		||||
      <div class="inner">
 | 
			
		||||
        <div class="user-profile">
 | 
			
		||||
          <user-profile/>
 | 
			
		||||
          <user-profile :key="profileKey"/>
 | 
			
		||||
 | 
			
		||||
          <el-row class="user-opt" :gutter="20">
 | 
			
		||||
            <el-col :span="12">
 | 
			
		||||
@@ -12,11 +12,8 @@
 | 
			
		||||
            <el-col :span="12">
 | 
			
		||||
              <el-button type="primary" @click="showBindMobileDialog = true">更改账号</el-button>
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="12">
 | 
			
		||||
              <el-button type="primary" v-if="enableReward" @click="showRewardDialog = true">加入众筹</el-button>
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="12">
 | 
			
		||||
              <el-button type="primary" v-if="enableReward" @click="showRewardVerifyDialog = true">众筹核销
 | 
			
		||||
            <el-col :span="24">
 | 
			
		||||
              <el-button type="success" @click="showRedeemVerifyDialog = true">兑换码核销
 | 
			
		||||
              </el-button>
 | 
			
		||||
            </el-col>
 | 
			
		||||
 | 
			
		||||
@@ -99,24 +96,7 @@
 | 
			
		||||
      <bind-mobile v-if="isLogin" :show="showBindMobileDialog" :username="user.username"
 | 
			
		||||
                   @hide="showBindMobileDialog = false"/>
 | 
			
		||||
 | 
			
		||||
      <reward-verify v-if="isLogin" :show="showRewardVerifyDialog" @hide="showRewardVerifyDialog = false"/>
 | 
			
		||||
 | 
			
		||||
      <el-dialog
 | 
			
		||||
          v-model="showRewardDialog"
 | 
			
		||||
          :show-close="true"
 | 
			
		||||
          width="400px"
 | 
			
		||||
          title="参与众筹"
 | 
			
		||||
      >
 | 
			
		||||
        <el-alert type="info" :closable="false">
 | 
			
		||||
          <div style="font-size: 14px">您好,目前每单位算力众筹价格为 <strong style="color: #f56c6c">{{ powerPrice }}
 | 
			
		||||
          </strong>元。
 | 
			
		||||
            由于本人没有开通微信支付,付款后请凭借转账单号,点击【众筹核销】按钮手动核销。
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-alert>
 | 
			
		||||
        <div style="text-align: center;padding-top: 10px;">
 | 
			
		||||
          <el-image v-if="enableReward" :src="rewardImg"/>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-dialog>
 | 
			
		||||
      <redeem-verify v-if="isLogin" :show="showRedeemVerifyDialog" @hide="redeemCallback"/>
 | 
			
		||||
 | 
			
		||||
      <el-dialog
 | 
			
		||||
          v-model="showPayDialog"
 | 
			
		||||
@@ -160,11 +140,11 @@ import {ElMessage} from "element-plus";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import ItemList from "@/components/ItemList.vue";
 | 
			
		||||
import {InfoFilled, SuccessFilled} from "@element-plus/icons-vue";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import UserProfile from "@/components/UserProfile.vue";
 | 
			
		||||
import PasswordDialog from "@/components/PasswordDialog.vue";
 | 
			
		||||
import BindMobile from "@/components/ResetAccount.vue";
 | 
			
		||||
import RewardVerify from "@/components/RewardVerify.vue";
 | 
			
		||||
import RedeemVerify from "@/components/RedeemVerify.vue";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {removeUserToken} from "@/store/session";
 | 
			
		||||
import UserOrder from "@/components/UserOrder.vue";
 | 
			
		||||
@@ -179,8 +159,7 @@ const rewardImg = ref('/images/reward.png')
 | 
			
		||||
const qrcode = ref("")
 | 
			
		||||
const showPasswordDialog = ref(false);
 | 
			
		||||
const showBindMobileDialog = ref(false);
 | 
			
		||||
const showRewardDialog = ref(false);
 | 
			
		||||
const showRewardVerifyDialog = ref(false);
 | 
			
		||||
const showRedeemVerifyDialog = ref(false);
 | 
			
		||||
const text = ref("")
 | 
			
		||||
const user = ref(null)
 | 
			
		||||
const isLogin = ref(false)
 | 
			
		||||
@@ -200,6 +179,7 @@ const payName = ref("支付宝")
 | 
			
		||||
const curPay = ref("alipay") // 当前支付方式
 | 
			
		||||
const vipInfoText = ref("")
 | 
			
		||||
const store = useSharedStore()
 | 
			
		||||
const profileKey = ref(0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
@@ -216,7 +196,7 @@ onMounted(() => {
 | 
			
		||||
    ElMessage.error("获取产品套餐失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    rewardImg.value = res.data['reward_img']
 | 
			
		||||
    enableReward.value = res.data['enabled_reward']
 | 
			
		||||
    orderPayInfoText.value = res.data['order_pay_info_text']
 | 
			
		||||
@@ -368,8 +348,11 @@ const closeOrder = () => {
 | 
			
		||||
  activeOrderNo.value = ''
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const loginSuccess = () => {
 | 
			
		||||
  location.reload()
 | 
			
		||||
const redeemCallback = (success) => {
 | 
			
		||||
  showRedeemVerifyDialog.value = false
 | 
			
		||||
  if (success) {
 | 
			
		||||
    profileKey.value += 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -71,7 +71,7 @@ import {Search} from "@element-plus/icons-vue";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession} from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
const items = ref([])
 | 
			
		||||
const total = ref(0)
 | 
			
		||||
 
 | 
			
		||||
@@ -181,6 +181,7 @@ import {arrayContains} from "@/utils/libs";
 | 
			
		||||
import {setUserToken} from "@/store/session";
 | 
			
		||||
import {validateEmail, validateMobile} from "@/utils/validate";
 | 
			
		||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
 | 
			
		||||
import {getLicenseInfo, getSystemInfo} from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const title = ref('');
 | 
			
		||||
@@ -201,7 +202,7 @@ const activeName = ref("mobile")
 | 
			
		||||
const wxImg = ref("/images/wx.png")
 | 
			
		||||
const licenseConfig = ref({})
 | 
			
		||||
 | 
			
		||||
httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
getSystemInfo().then(res => {
 | 
			
		||||
  if (res.data) {
 | 
			
		||||
    title.value = res.data.title
 | 
			
		||||
    logo.value = res.data.logo
 | 
			
		||||
@@ -226,7 +227,7 @@ httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
httpGet("/api/config/license").then(res => {
 | 
			
		||||
getLicenseInfo().then(res => {
 | 
			
		||||
  licenseConfig.value = res.data
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  showMessageError("获取 License 配置:" + e.message)
 | 
			
		||||
 
 | 
			
		||||
@@ -277,7 +277,7 @@ import {compact} from "lodash";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
 | 
			
		||||
import Generating from "@/components/ui/Generating.vue";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession} from "@/store/cache";
 | 
			
		||||
import {ElMessage, ElMessageBox} from "element-plus";
 | 
			
		||||
import {formatTime} from "@/utils/libs";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
@@ -381,9 +381,9 @@ onMounted(() => {
 | 
			
		||||
 | 
			
		||||
  checkSession().then(user => {
 | 
			
		||||
    userId.value = user.id
 | 
			
		||||
    fetchData(1)
 | 
			
		||||
    connect()
 | 
			
		||||
  })
 | 
			
		||||
  fetchData(1)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
@@ -410,6 +410,8 @@ const fetchData = (_page) => {
 | 
			
		||||
    list.value = items
 | 
			
		||||
    noData.value = list.value.length === 0
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    noData.value = true
 | 
			
		||||
    showMessageError("获取作品列表失败:"+e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <textarea v-model="value"/>
 | 
			
		||||
   {{data}}
 | 
			
		||||
  </div>
 | 
			
		||||
  <svg ref="svgRef"/>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
@@ -10,34 +9,16 @@ import {ref, onMounted, onUpdated} from 'vue';
 | 
			
		||||
import {Markmap} from 'markmap-view';
 | 
			
		||||
import {loadJS, loadCSS} from 'markmap-common';
 | 
			
		||||
import {Transformer} from 'markmap-lib';
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
 | 
			
		||||
const transformer = new Transformer();
 | 
			
		||||
const {scripts, styles} = transformer.getAssets();
 | 
			
		||||
loadCSS(styles);
 | 
			
		||||
loadJS(scripts);
 | 
			
		||||
 | 
			
		||||
const initValue = `# markmap
 | 
			
		||||
 | 
			
		||||
- beautiful
 | 
			
		||||
- useful
 | 
			
		||||
- easy
 | 
			
		||||
- interactive
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const value = ref(initValue);
 | 
			
		||||
const svgRef = ref(null);
 | 
			
		||||
let mm;
 | 
			
		||||
 | 
			
		||||
const update = () => {
 | 
			
		||||
  const {root} = transformer.transform(value.value);
 | 
			
		||||
  mm.setData(root);
 | 
			
		||||
  mm.fit();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  mm = Markmap.create(svgRef.value);
 | 
			
		||||
  update();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onUpdated(update);
 | 
			
		||||
const data=ref("")
 | 
			
		||||
httpPost("/api/test/sse",{
 | 
			
		||||
  "message":"你是什么模型",
 | 
			
		||||
  "user_id":123
 | 
			
		||||
}).then(res=>{
 | 
			
		||||
  // const source = new EventSource("http://localhost:5678/api/test/sse");
 | 
			
		||||
  // source.onmessage = function(event) {
 | 
			
		||||
  //   console.log(event.data)
 | 
			
		||||
  // };
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,173 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-form label-width="150px" label-position="right" class="draw-config">
 | 
			
		||||
    <el-tabs type="border-card">
 | 
			
		||||
      <el-tab-pane label="MJ-PLUS">
 | 
			
		||||
        <div v-if="mjPlusConfigs">
 | 
			
		||||
          <div class="config-item" v-for="(v,k) in mjPlusConfigs">
 | 
			
		||||
            <el-form-item label="是否启用">
 | 
			
		||||
              <el-switch v-model="v['Enabled']"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="API 地址">
 | 
			
		||||
              <el-input v-model="v['ApiURL']" placeholder="API 地址"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="API 令牌">
 | 
			
		||||
              <el-input v-model="v['ApiKey']" placeholder="API KEY"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="绘画模式">
 | 
			
		||||
              <el-select v-model="v['Mode']" placeholder="请选择模式">
 | 
			
		||||
                <el-option v-for="item in mjModels" :value="item.value" :label="item.name" :key="item.value">{{
 | 
			
		||||
                    item.name
 | 
			
		||||
                  }}
 | 
			
		||||
                </el-option>
 | 
			
		||||
              </el-select>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <el-button class="remove" type="danger" :icon="Delete" circle @click="removeItem(mjPlusConfigs,k)"/>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-empty v-else></el-empty>
 | 
			
		||||
 | 
			
		||||
        <el-row style="justify-content: center; padding: 10px">
 | 
			
		||||
          <el-button round @click="addConfig(mjPlusConfigs)">
 | 
			
		||||
            <el-icon><Plus /></el-icon>
 | 
			
		||||
            <span>新增配置</span>
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </el-row>
 | 
			
		||||
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
      <el-tab-pane label="MJ-PROXY">
 | 
			
		||||
        <div v-if="mjProxyConfigs">
 | 
			
		||||
          <div class="config-item" v-for="(v,k) in mjProxyConfigs">
 | 
			
		||||
            <el-form-item label="是否启用">
 | 
			
		||||
              <el-switch v-model="v['Enabled']"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="API 地址">
 | 
			
		||||
              <el-input v-model="v['ApiURL']" placeholder="API 地址"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="API 令牌">
 | 
			
		||||
              <el-input v-model="v['ApiKey']" placeholder="API KEY"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <el-button class="remove" type="danger" :icon="Delete" circle @click="removeItem(mjProxyConfigs,k)"/>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-empty v-else />
 | 
			
		||||
 | 
			
		||||
        <el-row style="justify-content: center; padding: 10px">
 | 
			
		||||
          <el-button round @click="addConfig(mjProxyConfigs)">
 | 
			
		||||
            <el-icon>
 | 
			
		||||
              <Plus/>
 | 
			
		||||
            </el-icon>
 | 
			
		||||
            <span>新增配置</span>
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </el-row>
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
      <el-tab-pane label="Stable-Diffusion">
 | 
			
		||||
        <div v-if="sdConfigs">
 | 
			
		||||
          <div class="config-item" v-for="(v,k) in sdConfigs">
 | 
			
		||||
            <el-form-item label="是否启用">
 | 
			
		||||
              <el-switch v-model="v['Enabled']"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="API 地址">
 | 
			
		||||
              <el-input v-model="v['ApiURL']" placeholder="API 地址"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="API 令牌">
 | 
			
		||||
              <el-input v-model="v['ApiKey']" placeholder="API KEY"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="模型">
 | 
			
		||||
              <el-input v-model="v['Model']" placeholder="绘画模型"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-button class="remove" type="danger" :icon="Delete" circle @click="removeItem(sdConfigs,k)"/>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-empty v-else/>
 | 
			
		||||
 | 
			
		||||
        <el-row style="justify-content: center; padding: 10px">
 | 
			
		||||
          <el-button round @click="addConfig(sdConfigs)">
 | 
			
		||||
            <el-icon>
 | 
			
		||||
              <Plus/>
 | 
			
		||||
            </el-icon>
 | 
			
		||||
            <span>新增配置</span>
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </el-row>
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
    </el-tabs>
 | 
			
		||||
 | 
			
		||||
    <div style="padding: 10px;">
 | 
			
		||||
      <el-form-item>
 | 
			
		||||
        <el-button type="primary" @click="saveConfig()">保存</el-button>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
    </div>
 | 
			
		||||
  </el-form>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {Delete, Plus} from "@element-plus/icons-vue";
 | 
			
		||||
 | 
			
		||||
// 变量定义
 | 
			
		||||
const sdConfigs = ref([])
 | 
			
		||||
const mjPlusConfigs = ref([])
 | 
			
		||||
const mjProxyConfigs = ref([])
 | 
			
		||||
const mjModels = ref([
 | 
			
		||||
  {name: "慢速(Relax)", value: "relax"},
 | 
			
		||||
  {name: "快速(Fast)", value: "fast"},
 | 
			
		||||
  {name: "急速(Turbo)", value: "turbo"},
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
httpGet("/api/admin/config/get/app").then(res => {
 | 
			
		||||
  sdConfigs.value = res.data.sd
 | 
			
		||||
  mjPlusConfigs.value = res.data.mj_plus
 | 
			
		||||
  mjProxyConfigs.value = res.data.mj_proxy
 | 
			
		||||
}).catch(e =>{
 | 
			
		||||
  ElMessage.error("获取配置失败:"+e.message)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const addConfig = (configs) => {
 | 
			
		||||
  configs.push({
 | 
			
		||||
    Enabled: true,
 | 
			
		||||
    ApiKey: '',
 | 
			
		||||
    ApiURL: '',
 | 
			
		||||
    Mode: 'fast'
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const saveConfig = () => {
 | 
			
		||||
  httpPost('/api/admin/config/update/draw', {
 | 
			
		||||
    'sd': sdConfigs.value,
 | 
			
		||||
    'mj_plus': mjPlusConfigs.value,
 | 
			
		||||
    'mj_proxy': mjProxyConfigs.value
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    ElMessage.success("配置更新成功")
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("操作失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const removeItem = (arr, k) => {
 | 
			
		||||
  arr.splice(k, 1)
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.draw-config {
 | 
			
		||||
 | 
			
		||||
  .config-item {
 | 
			
		||||
    position relative
 | 
			
		||||
    padding 15px 10px 10px 10px
 | 
			
		||||
    border 1px solid var(--el-border-color)
 | 
			
		||||
    border-radius 10px
 | 
			
		||||
    margin-bottom 10px
 | 
			
		||||
 | 
			
		||||
    .remove {
 | 
			
		||||
      position absolute
 | 
			
		||||
      right 15px
 | 
			
		||||
      top 15px
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -139,6 +139,7 @@ const title = ref("")
 | 
			
		||||
const types = ref([
 | 
			
		||||
  {label: "对话", value:"chat"},
 | 
			
		||||
  {label: "Midjourney", value:"mj"},
 | 
			
		||||
  {label: "Stable-Diffusion", value:"sd"},
 | 
			
		||||
  {label: "DALL-E", value:"dalle"},
 | 
			
		||||
  {label: "Suno文生歌", value:"suno"},
 | 
			
		||||
  {label: "Luma视频", value:"luma"},
 | 
			
		||||
 
 | 
			
		||||
@@ -157,11 +157,7 @@
 | 
			
		||||
        <div v-for="item in messages" :key="item.id">
 | 
			
		||||
          <chat-prompt
 | 
			
		||||
              v-if="item.type==='prompt'"
 | 
			
		||||
              :icon="item.icon"
 | 
			
		||||
              :created-at="dateFormat(item['created_at'])"
 | 
			
		||||
              :tokens="item['tokens']"
 | 
			
		||||
              :model="item.model"
 | 
			
		||||
              :content="item.content"/>
 | 
			
		||||
              :data="item"/>
 | 
			
		||||
          <chat-reply v-else-if="item.type==='reply'"
 | 
			
		||||
                      :read-only="true"
 | 
			
		||||
                      :data="item"/>
 | 
			
		||||
@@ -288,33 +284,11 @@ const removeMessage = function (row) {
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mathjaxPlugin = require('markdown-it-mathjax3')
 | 
			
		||||
const md = require('markdown-it')({
 | 
			
		||||
  breaks: true,
 | 
			
		||||
  html: true,
 | 
			
		||||
  linkify: true,
 | 
			
		||||
  typographer: true,
 | 
			
		||||
  highlight: function (str, lang) {
 | 
			
		||||
    if (lang && hl.getLanguage(lang)) {
 | 
			
		||||
      // 处理代码高亮
 | 
			
		||||
      const preCode = hl.highlight(lang, str, true).value
 | 
			
		||||
      // 将代码包裹在 pre 中
 | 
			
		||||
      return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code></pre>`
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 处理代码高亮
 | 
			
		||||
    const preCode = md.utils.escapeHtml(str)
 | 
			
		||||
    // 将代码包裹在 pre 中
 | 
			
		||||
    return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code></pre>`
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
md.use(mathjaxPlugin)
 | 
			
		||||
 | 
			
		||||
const showContentDialog = ref(false)
 | 
			
		||||
const dialogContent = ref("")
 | 
			
		||||
const showContent = (content) => {
 | 
			
		||||
  showContentDialog.value = true
 | 
			
		||||
  dialogContent.value = md.render(processContent(content))
 | 
			
		||||
  dialogContent.value = processContent(content)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const showChatItemDialog = ref(false)
 | 
			
		||||
@@ -325,8 +299,6 @@ const showMessages = (row) => {
 | 
			
		||||
  httpGet('/api/admin/chat/history?chat_id=' + row.chat_id).then(res => {
 | 
			
		||||
    const data = res.data
 | 
			
		||||
    for (let i = 0; i < data.length; i++) {
 | 
			
		||||
      data[i].orgContent = data[i].content;
 | 
			
		||||
      data[i].content = md.render(processContent(data[i].content))
 | 
			
		||||
      messages.value.push(data[i]);
 | 
			
		||||
    }
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,12 @@
 | 
			
		||||
 | 
			
		||||
    <el-row>
 | 
			
		||||
      <el-table :data="items" :row-key="row => row.id" table-layout="auto">
 | 
			
		||||
        <el-table-column prop="name" label="模型名称"/>
 | 
			
		||||
        <el-table-column type="selection" width="38"></el-table-column>
 | 
			
		||||
        <el-table-column prop="name" label="模型名称">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span class="sort" :data-id="scope.row.id">{{ scope.row.name }}</span>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="value" label="模型值">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span>{{ scope.row.value }}</span>
 | 
			
		||||
@@ -174,6 +179,7 @@ import {dateFormat, removeArrayItem, substr} from "@/utils/libs";
 | 
			
		||||
import {DocumentCopy, InfoFilled, Plus,Search} from "@element-plus/icons-vue";
 | 
			
		||||
import {Sortable} from "sortablejs";
 | 
			
		||||
import ClipboardJS from "clipboard";
 | 
			
		||||
import Default from "md-editor-v3";
 | 
			
		||||
 | 
			
		||||
// 变量定义
 | 
			
		||||
const items = ref([])
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import AdminHeader from "@/components/admin/AdminHeader.vue";
 | 
			
		||||
import AdminSidebar from "@/components/admin/AdminSidebar.vue";
 | 
			
		||||
import AdminTags from "@/components/admin/AdminTags.vue";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {checkAdminSession} from "@/action/session";
 | 
			
		||||
import {checkAdminSession} from "@/store/cache";
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {getAdminTheme, setAdminTheme} from "@/store/system";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,7 @@ import {ElMessage} from "element-plus";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import FooterBar from "@/components/FooterBar.vue";
 | 
			
		||||
import {setAdminToken} from "@/store/session";
 | 
			
		||||
import {checkAdminSession} from "@/action/session";
 | 
			
		||||
import {checkAdminSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const title = ref('Geek-AI Console');
 | 
			
		||||
@@ -67,7 +67,7 @@ checkAdminSession().then(() => {
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 加载系统配置
 | 
			
		||||
httpGet('/api/config/get?key=system').then(res => {
 | 
			
		||||
getSystemInfo().then(res => {
 | 
			
		||||
  title.value = res.data.admin_title
 | 
			
		||||
  logo.value = res.data.logo
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
          style="margin: 0 10px;width: 200px; position: relative;top:3px;"
 | 
			
		||||
      />
 | 
			
		||||
      <el-button type="primary" :icon="Search" @click="fetchData">搜索</el-button>
 | 
			
		||||
      <el-button type="danger" :icon="Delete" @click="clearOrders">清空未支付订单</el-button>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <el-row>
 | 
			
		||||
@@ -76,9 +77,9 @@
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {ElMessage, ElMessageBox} from "element-plus";
 | 
			
		||||
import {dateFormat, removeArrayItem} from "@/utils/libs";
 | 
			
		||||
import {Search} from "@element-plus/icons-vue";
 | 
			
		||||
import {Delete, Search} from "@element-plus/icons-vue";
 | 
			
		||||
 | 
			
		||||
// 变量定义
 | 
			
		||||
const items = ref([])
 | 
			
		||||
@@ -123,6 +124,24 @@ const remove = function (row) {
 | 
			
		||||
    ElMessage.error("删除失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const clearOrders = () => {
 | 
			
		||||
  ElMessageBox.confirm(
 | 
			
		||||
      '此操作将会删除所有未支付订单,继续操作吗?',
 | 
			
		||||
      '删除提示',
 | 
			
		||||
      {
 | 
			
		||||
        confirmButtonText: '确认',
 | 
			
		||||
        cancelButtonText: '取消',
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
      }
 | 
			
		||||
  ).then(() => {
 | 
			
		||||
    httpGet("/api/admin/order/clear").then(() => {
 | 
			
		||||
      ElMessage.success("订单删除成功")
 | 
			
		||||
      page.value = 0
 | 
			
		||||
     fetchData()
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										251
									
								
								web/src/views/admin/Redeem.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								web/src/views/admin/Redeem.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,251 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container list" v-loading="loading">
 | 
			
		||||
    <div class="handle-box">
 | 
			
		||||
      <el-input v-model="query.code" placeholder="兑换码" class="handle-input mr10"></el-input>
 | 
			
		||||
      <el-select v-model="query.status" placeholder="核销状态" style="width: 100px" class="handle-input mr10">
 | 
			
		||||
        <el-option
 | 
			
		||||
            v-for="item in redeemStatus"
 | 
			
		||||
            :key="item.value"
 | 
			
		||||
            :label="item.label"
 | 
			
		||||
            :value="item.value"
 | 
			
		||||
        />
 | 
			
		||||
      </el-select>
 | 
			
		||||
      <el-button type="primary" :icon="Search" @click="fetchData">搜索</el-button>
 | 
			
		||||
      <el-button type="success" :icon="Plus" @click="add">添加兑换码</el-button>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <el-row>
 | 
			
		||||
      <el-table :data="items" :row-key="row => row.id">
 | 
			
		||||
        <el-table-column prop="name" label="名称"/>
 | 
			
		||||
        <el-table-column prop="code" label="兑换码">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span>{{ substr(scope.row.code, 24) }}</span>
 | 
			
		||||
            <el-icon class="copy-code" :data-clipboard-text="scope.row.code">
 | 
			
		||||
              <DocumentCopy/>
 | 
			
		||||
            </el-icon>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column label="兑换人">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span v-if="scope.row['username'] !== ''">{{ scope.row['username'] }}</span>
 | 
			
		||||
            <el-tag v-else>未兑换</el-tag>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="power" label="算力"/>
 | 
			
		||||
 | 
			
		||||
        <el-table-column label="生成时间">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span>{{ dateFormat(scope.row['created_at']) }}</span>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
 | 
			
		||||
        <el-table-column label="兑换时间">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span v-if="scope.row['redeemed_at'] > 0">{{ dateFormat(scope.row['redeemed_at']) }}</span>
 | 
			
		||||
            <el-tag v-else>未兑换</el-tag>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
 | 
			
		||||
        <el-table-column prop="enabled" label="启用状态">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-switch v-model="scope.row['enabled']" @change="set('enabled',scope.row)" :disabled="scope.row['redeemed_at']>0"/>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
 | 
			
		||||
        <el-table-column label="操作" width="180">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-popconfirm title="确定要删除当前记录吗?" @confirm="remove(scope.row)">
 | 
			
		||||
              <template #reference>
 | 
			
		||||
                <el-button size="small" type="danger">删除</el-button>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-popconfirm>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
 | 
			
		||||
      </el-table>
 | 
			
		||||
    </el-row>
 | 
			
		||||
 | 
			
		||||
    <div class="pagination">
 | 
			
		||||
      <el-pagination v-if="total > 0" background
 | 
			
		||||
                     layout="total,prev, pager, next"
 | 
			
		||||
                     :hide-on-single-page="true"
 | 
			
		||||
                     v-model:current-page="page"
 | 
			
		||||
                     v-model:page-size="pageSize"
 | 
			
		||||
                     @current-change="fetchData()"
 | 
			
		||||
                     :total="total"/>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <el-dialog
 | 
			
		||||
        v-model="showDialog"
 | 
			
		||||
        title="生成兑换码">
 | 
			
		||||
      <template #default>
 | 
			
		||||
        <el-form :model="item" label-width="120px" v-loading="dialogLoading">
 | 
			
		||||
          <el-form-item label="名称:" prop="name">
 | 
			
		||||
            <el-input v-model="item.name" autocomplete="off"/>
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
 | 
			
		||||
          <el-form-item label="算力额度:" prop="power">
 | 
			
		||||
            <el-input v-model.number="item.power" autocomplete="off"/>
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
 | 
			
		||||
          <el-form-item label="生成数量:" prop="num">
 | 
			
		||||
            <el-input v-model.number="item.num" />
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
        </el-form>
 | 
			
		||||
      </template>
 | 
			
		||||
 | 
			
		||||
      <template #footer>
 | 
			
		||||
            <span class="dialog-footer">
 | 
			
		||||
              <el-button @click="showDialog = false">取消</el-button>
 | 
			
		||||
              <el-button type="primary" @click="save">提交</el-button>
 | 
			
		||||
            </span>
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, onUnmounted, ref} from "vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {dateFormat, removeArrayItem, substr} from "@/utils/libs";
 | 
			
		||||
import {Delete, DocumentCopy, Plus, Search, UploadFilled} from "@element-plus/icons-vue";
 | 
			
		||||
import {showMessageError} from "@/utils/dialog";
 | 
			
		||||
import ClipboardJS from "clipboard";
 | 
			
		||||
 | 
			
		||||
// 变量定义
 | 
			
		||||
const items = ref([])
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
const query = ref({code:"",status:-1})
 | 
			
		||||
const redeemStatus = ref([
 | 
			
		||||
  {value: -1, label: "全部"},
 | 
			
		||||
  {value: 0, label: "未核销"},
 | 
			
		||||
  {value: 1, label: "已核销"},
 | 
			
		||||
])
 | 
			
		||||
const showDialog = ref(false)
 | 
			
		||||
const dialogLoading = ref(false)
 | 
			
		||||
const item = ref({name: "", power: 0, num: 1})
 | 
			
		||||
 | 
			
		||||
const clipboard = ref(null)
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  clipboard.value = new ClipboardJS('.copy-code');
 | 
			
		||||
  clipboard.value.on('success', () => {
 | 
			
		||||
    ElMessage.success('复制成功!');
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  clipboard.value.on('error', () => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  fetchData()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
  clipboard.value.destroy()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const add = () => {
 | 
			
		||||
  item.value = {name: "100算力点卡", power: 100, num: 1}
 | 
			
		||||
  showDialog.value = true
 | 
			
		||||
  dialogLoading.value = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const save = () => {
 | 
			
		||||
  if (item.value.name ===""){
 | 
			
		||||
    return showMessageError("请输入兑换码名称")
 | 
			
		||||
  }
 | 
			
		||||
  if (item.value.power === 0){
 | 
			
		||||
    return showMessageError("请输入算力额度")
 | 
			
		||||
  }
 | 
			
		||||
  if (item.value.num <= 0) {
 | 
			
		||||
    return showMessageError("请输入生成数量")
 | 
			
		||||
  }
 | 
			
		||||
  dialogLoading.value = true
 | 
			
		||||
  httpPost('/api/admin/redeem/create', item.value).then((res) => {
 | 
			
		||||
    ElMessage.success(`成功生成了${res.data.counter}个兑换码`)
 | 
			
		||||
    showDialog.value = false
 | 
			
		||||
    fetchData()
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error("生成失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const set = (filed, row) => {
 | 
			
		||||
  httpPost('/api/admin/redeem/set', {id: row.id, filed: filed, value: row[filed]}).then(() => {
 | 
			
		||||
    ElMessage.success("操作成功!")
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("操作失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const page = ref(1)
 | 
			
		||||
const pageSize = ref(12)
 | 
			
		||||
const total = ref(0)
 | 
			
		||||
const fetchData = () => {
 | 
			
		||||
  query.value.page = page.value
 | 
			
		||||
  query.value.page_size = pageSize.value
 | 
			
		||||
  httpGet('/api/admin/redeem/list', query.value).then((res) => {
 | 
			
		||||
    if (res.data) {
 | 
			
		||||
      items.value = res.data.items
 | 
			
		||||
      total.value = res.data.total
 | 
			
		||||
      page.value = res.data.page
 | 
			
		||||
      pageSize.value = res.data.page_size
 | 
			
		||||
    }
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const remove = function (row) {
 | 
			
		||||
  httpGet('/api/admin/redeem/remove?id=' + row.id).then(() => {
 | 
			
		||||
    ElMessage.success("删除成功!")
 | 
			
		||||
    fetchData()
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error("删除失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.list {
 | 
			
		||||
  .handle-box {
 | 
			
		||||
    margin-bottom 20px
 | 
			
		||||
    .handle-input {
 | 
			
		||||
      max-width 150px;
 | 
			
		||||
      margin-right 10px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .opt-box {
 | 
			
		||||
    padding-bottom: 10px;
 | 
			
		||||
    display flex;
 | 
			
		||||
    justify-content flex-end
 | 
			
		||||
 | 
			
		||||
    .el-icon {
 | 
			
		||||
      margin-right: 5px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-select {
 | 
			
		||||
    width: 100%
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .copy-code {
 | 
			
		||||
    cursor pointer
 | 
			
		||||
    margin-left 6px
 | 
			
		||||
    position relative
 | 
			
		||||
    top 2px
 | 
			
		||||
    font-size 14px
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .pagination {
 | 
			
		||||
    padding 20px 0
 | 
			
		||||
    display flex
 | 
			
		||||
    justify-content right
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,115 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container list" v-loading="loading">
 | 
			
		||||
 | 
			
		||||
    <el-row>
 | 
			
		||||
      <el-table :data="items" :row-key="row => row.id">
 | 
			
		||||
        <el-table-column prop="username" label="用户"/>
 | 
			
		||||
        <el-table-column prop="tx_id" label="转账单号"/>
 | 
			
		||||
        <el-table-column prop="amount" label="转账金额"/>
 | 
			
		||||
        <el-table-column prop="remark" label="备注"/>
 | 
			
		||||
 | 
			
		||||
        <el-table-column label="转账时间">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span>{{ dateFormat(scope.row['created_at']) }}</span>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
 | 
			
		||||
        <el-table-column label="核销时间">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span v-if="scope.row['status']">{{ dateFormat(scope.row['updated_at']) }}</span>
 | 
			
		||||
            <el-tag v-else>未核销</el-tag>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
 | 
			
		||||
        <el-table-column label="兑换详情">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span v-if="scope.row['exchange']['power'] > 0">增加{{ scope.row['exchange']['power'] }}算力</span>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
 | 
			
		||||
        <el-table-column label="操作" width="180">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-popconfirm title="确定要删除当前记录吗?" @confirm="remove(scope.row)">
 | 
			
		||||
              <template #reference>
 | 
			
		||||
                <el-button size="small" type="danger">删除</el-button>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-popconfirm>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
 | 
			
		||||
      </el-table>
 | 
			
		||||
    </el-row>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {dateFormat, removeArrayItem} from "@/utils/libs";
 | 
			
		||||
 | 
			
		||||
// 变量定义
 | 
			
		||||
const items = ref([])
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
 | 
			
		||||
// 获取数据
 | 
			
		||||
httpGet('/api/admin/reward/list').then((res) => {
 | 
			
		||||
  if (res.data) {
 | 
			
		||||
    // 初始化数据
 | 
			
		||||
    const arr = res.data;
 | 
			
		||||
    for (let i = 0; i < arr.length; i++) {
 | 
			
		||||
      arr[i].last_used_at = dateFormat(arr[i].last_used_at)
 | 
			
		||||
    }
 | 
			
		||||
    items.value = arr
 | 
			
		||||
  }
 | 
			
		||||
  loading.value = false
 | 
			
		||||
}).catch(() => {
 | 
			
		||||
  ElMessage.error("获取数据失败");
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const remove = function (row) {
 | 
			
		||||
  httpGet('/api/admin/reward/remove?id=' + row.id).then(() => {
 | 
			
		||||
    ElMessage.success("删除成功!")
 | 
			
		||||
    items.value = removeArrayItem(items.value, row, (v1, v2) => {
 | 
			
		||||
      return v1.id === v2.id
 | 
			
		||||
    })
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error("删除失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.list {
 | 
			
		||||
  .handle-box {
 | 
			
		||||
    margin-bottom 20px
 | 
			
		||||
 | 
			
		||||
    .handle-input {
 | 
			
		||||
      max-width 150px;
 | 
			
		||||
      margin-right 10px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .opt-box {
 | 
			
		||||
    padding-bottom: 10px;
 | 
			
		||||
    display flex;
 | 
			
		||||
    justify-content flex-end
 | 
			
		||||
 | 
			
		||||
    .el-icon {
 | 
			
		||||
      margin-right: 5px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-select {
 | 
			
		||||
    width: 100%
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .pagination {
 | 
			
		||||
    padding 20px 0
 | 
			
		||||
    display flex
 | 
			
		||||
    justify-content right
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -194,6 +194,33 @@
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item label="会员充值说明" prop="order_pay_timeout">
 | 
			
		||||
                  <div class="tip-input">
 | 
			
		||||
                    <el-input v-model="system['vip_info_text']" placeholder=""/>
 | 
			
		||||
                    <div class="info">
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="dark"
 | 
			
		||||
                          content="会员充值页面的充值说明文字"
 | 
			
		||||
                          raw-content
 | 
			
		||||
                          placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon>
 | 
			
		||||
                          <InfoFilled/>
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item label="MJ默认API模式" prop="mj_mode">
 | 
			
		||||
                  <el-select v-model="system['mj_mode']" placeholder="请选择模式">
 | 
			
		||||
                    <el-option v-for="item in mjModels" :value="item.value" :label="item.name" :key="item.value">{{
 | 
			
		||||
                        item.name
 | 
			
		||||
                      }}
 | 
			
		||||
                    </el-option>
 | 
			
		||||
                  </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
              </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
              <el-tab-pane label="算力配置">
 | 
			
		||||
@@ -258,84 +285,6 @@
 | 
			
		||||
                  <el-input v-model.number="system['suno_power']" placeholder="使用 Suno 生成一首音乐消耗算力"/>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
              </el-tab-pane>
 | 
			
		||||
              <el-tab-pane label="众筹支付">
 | 
			
		||||
                <el-form-item label="启用众筹功能" prop="enabled_reward">
 | 
			
		||||
                  <div class="tip-input">
 | 
			
		||||
                    <el-switch v-model="system['enabled_reward']"/>
 | 
			
		||||
                    <div class="info">
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="dark"
 | 
			
		||||
                          content="如果关闭次功能将不在用户菜单显示众筹二维码"
 | 
			
		||||
                          raw-content
 | 
			
		||||
                          placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon>
 | 
			
		||||
                          <InfoFilled/>
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <div v-if="system['enabled_reward']">
 | 
			
		||||
                  <el-form-item label="算力单价" prop="power_price">
 | 
			
		||||
                    <el-input v-model="system['power_price']"
 | 
			
		||||
                              placeholder="单位算力的价格,比如设置 0.1 表示捐赠1元钱可以得到10个单位算力"/>
 | 
			
		||||
                  </el-form-item>
 | 
			
		||||
                  <el-form-item label="收款二维码" prop="reward_img">
 | 
			
		||||
                    <el-input v-model="system['reward_img']" placeholder="众筹收款二维码地址">
 | 
			
		||||
                      <template #append>
 | 
			
		||||
                        <el-upload
 | 
			
		||||
                            :auto-upload="true"
 | 
			
		||||
                            :show-file-list="false"
 | 
			
		||||
                            @click="beforeUpload('reward_img')"
 | 
			
		||||
                            :http-request="uploadImg"
 | 
			
		||||
                        >
 | 
			
		||||
                          <el-icon class="uploader-icon">
 | 
			
		||||
                            <UploadFilled/>
 | 
			
		||||
                          </el-icon>
 | 
			
		||||
                        </el-upload>
 | 
			
		||||
                      </template>
 | 
			
		||||
                    </el-input>
 | 
			
		||||
                  </el-form-item>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <el-form-item label="订单超时时间" prop="order_pay_timeout">
 | 
			
		||||
                  <div class="tip-input">
 | 
			
		||||
                    <el-input v-model.number="system['order_pay_timeout']" placeholder="单位:秒"/>
 | 
			
		||||
                    <div class="info">
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="dark"
 | 
			
		||||
                          content="系统会定期清理超时未支付的订单<br/>默认值:900秒"
 | 
			
		||||
                          raw-content
 | 
			
		||||
                          placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon>
 | 
			
		||||
                          <InfoFilled/>
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item label="会员充值说明" prop="order_pay_timeout">
 | 
			
		||||
                  <div class="tip-input">
 | 
			
		||||
                    <el-input v-model="system['vip_info_text']" placeholder=""/>
 | 
			
		||||
                    <div class="info">
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="dark"
 | 
			
		||||
                          content="会员充值页面的充值说明文字"
 | 
			
		||||
                          raw-content
 | 
			
		||||
                          placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon>
 | 
			
		||||
                          <InfoFilled/>
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
              </el-tab-pane>
 | 
			
		||||
            </el-tabs>
 | 
			
		||||
 | 
			
		||||
            <div style="padding: 10px;">
 | 
			
		||||
@@ -354,15 +303,18 @@
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
      <el-tab-pane label="思维导图" name="mark_map">
 | 
			
		||||
        <md-editor class="mgb20" v-model="system['mark_map_text']" @on-upload-img="onUploadImg"/>
 | 
			
		||||
        <el-form-item>
 | 
			
		||||
          <div style="padding-top: 10px;margin-left: 150px;">
 | 
			
		||||
            <el-button type="primary" @click="save('system')">保存</el-button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
      <el-tab-pane label="菜单配置" name="menu">
 | 
			
		||||
        <Menu/>
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
      <el-tab-pane label="AI绘图配置" name="AIDrawing">
 | 
			
		||||
        <AIDrawing/>
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
      <el-tab-pane label="授权激活" name="license">
 | 
			
		||||
        <div class="container">
 | 
			
		||||
          <el-descriptions
 | 
			
		||||
@@ -431,7 +383,6 @@ import MdEditor from "md-editor-v3";
 | 
			
		||||
import 'md-editor-v3/lib/style.css';
 | 
			
		||||
import Menu from "@/views/admin/Menu.vue";
 | 
			
		||||
import {copyObj, dateFormat} from "@/utils/libs";
 | 
			
		||||
import AIDrawing from "@/views/admin/AIDrawing.vue";
 | 
			
		||||
 | 
			
		||||
const activeName = ref('basic')
 | 
			
		||||
const system = ref({models: []})
 | 
			
		||||
@@ -439,10 +390,14 @@ const configBak = ref({})
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
const systemFormRef = ref(null)
 | 
			
		||||
const models = ref([])
 | 
			
		||||
const openAIModels = ref([])
 | 
			
		||||
const notice = ref("")
 | 
			
		||||
const license = ref({is_active: false})
 | 
			
		||||
const menus = ref([])
 | 
			
		||||
const mjModels = ref([
 | 
			
		||||
  {name: "慢速(Relax)", value: "relax"},
 | 
			
		||||
  {name: "快速(Fast)", value: "fast"},
 | 
			
		||||
  {name: "急速(Turbo)", value: "turbo"},
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  // 加载系统配置
 | 
			
		||||
@@ -461,7 +416,6 @@ onMounted(() => {
 | 
			
		||||
 | 
			
		||||
  httpGet('/api/admin/model/list').then(res => {
 | 
			
		||||
    models.value = res.data
 | 
			
		||||
    openAIModels.value = models.value.filter(v => v.platform === "OpenAI")
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取模型失败:" + e.message)
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
    <el-row>
 | 
			
		||||
      <el-table :data="users.items" border class="table" :row-key="row => row.id"
 | 
			
		||||
                @selection-change="handleSelectionChange" table-layout="auto">
 | 
			
		||||
        <el-table-column type="selection" width="38"/>
 | 
			
		||||
        <el-table-column type="selection" width="38"></el-table-column>
 | 
			
		||||
        <el-table-column prop="mobile" label="账号">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span>{{ scope.row.username }}</span>
 | 
			
		||||
@@ -308,7 +308,7 @@ const saveUser = function () {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const handleSelectionChange = function (rows) {
 | 
			
		||||
  console.log(rows)
 | 
			
		||||
  // console.log(rows)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const resetPass = (row) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,7 @@
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {showConfirmDialog, showFailToast, showSuccessToast} from "vant";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession} from "@/store/cache";
 | 
			
		||||
import {router} from "@/router";
 | 
			
		||||
import {removeArrayItem, showLoginDialog} from "@/utils/libs";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -132,7 +132,7 @@ import 'highlight.js/styles/a11y-dark.css'
 | 
			
		||||
import ChatPrompt from "@/components/mobile/ChatPrompt.vue";
 | 
			
		||||
import ChatReply from "@/components/mobile/ChatReply.vue";
 | 
			
		||||
import {getSessionId, getUserToken} from "@/store/session";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession} from "@/store/cache";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {showLoginDialog} from "@/utils/dialog";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -76,7 +76,7 @@
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {arrayContains, removeArrayItem, showLoginDialog, substr} from "@/utils/libs";
 | 
			
		||||
import {showNotify} from "vant";
 | 
			
		||||
@@ -91,7 +91,7 @@ const roles = ref([])
 | 
			
		||||
const slogan = ref('你有多大想象力,AI就有多大创造力!')
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    title.value = res.data.title
 | 
			
		||||
    if (res.data.slogan) {
 | 
			
		||||
      slogan.value = res.data.slogan
 | 
			
		||||
 
 | 
			
		||||
@@ -160,7 +160,7 @@ import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import Compressor from 'compressorjs';
 | 
			
		||||
import {dateFormat, isWeChatBrowser, showLoginDialog} from "@/utils/libs";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession} from "@/store/cache";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {removeUserToken} from "@/store/session";
 | 
			
		||||
import bus from '@/store/eventbus'
 | 
			
		||||
@@ -210,7 +210,7 @@ onMounted(() => {
 | 
			
		||||
    showFailToast("获取产品套餐失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    vipMonthPower.value = res.data['vip_month_power']
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showFailToast("获取系统配置失败:" + e.message)
 | 
			
		||||
 
 | 
			
		||||
@@ -165,7 +165,7 @@ import {onMounted, onUnmounted, ref} from "vue"
 | 
			
		||||
import {Delete} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession} from "@/store/cache";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {
 | 
			
		||||
@@ -286,7 +286,7 @@ onMounted(() => {
 | 
			
		||||
    showNotify({type: 'danger', message: '复制失败', duration: 2000})
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    dallPower.value = res.data.dall_power
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showNotify({type: "danger", message: "获取系统配置失败:" + e.message})
 | 
			
		||||
 
 | 
			
		||||
@@ -280,7 +280,7 @@ import {showConfirmDialog, showFailToast, showImagePreview, showNotify, showSucc
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import Compressor from "compressorjs";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {Delete} from "@element-plus/icons-vue";
 | 
			
		||||
import {showLoginDialog} from "@/utils/libs";
 | 
			
		||||
@@ -366,7 +366,7 @@ onUnmounted(() => {
 | 
			
		||||
 | 
			
		||||
const mjPower = ref(1)
 | 
			
		||||
const mjActionPower = ref(1)
 | 
			
		||||
httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
getSystemInfo().then(res => {
 | 
			
		||||
  mjPower.value = res.data["mj_power"]
 | 
			
		||||
  mjActionPower.value = res.data["mj_action_power"]
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
 
 | 
			
		||||
@@ -217,7 +217,7 @@ import {onMounted, onUnmounted, ref} from "vue"
 | 
			
		||||
import {Delete} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {checkSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {
 | 
			
		||||
@@ -349,7 +349,7 @@ onMounted(() => {
 | 
			
		||||
    showNotify({type: 'danger', message: '复制失败', duration: 2000})
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    sdPower.value = res.data.sd_power
 | 
			
		||||
    params.value.neg_prompt = res.data.sd_neg_prompt
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user