feat: image wall page is ready

This commit is contained in:
RockYang 2023-10-12 18:09:50 +08:00
parent 8b14e141d0
commit 1ae79331e7
12 changed files with 254 additions and 56 deletions

View File

@ -1,12 +1,12 @@
# ChatGPT-Plus
**ChatGPT-PLUS** 基于 AI 大语言模型 API 实现的 AI 助手全套开源解决方案,自带运营管理后台,开箱即用。集成了 OpenAI, Azure,
ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。主要有如下特性:
ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了 MidJourney 和 Stable Diffusion AI绘画功能。主要有如下特性:
* 完整的开源系统,前端应用和后台管理系统皆可开箱即用。
* 聊天体验跟 ChatGPT 官方版本完全一致。
* 内置了各种预训练好的角色,比如小红书写手,英语翻译大师,苏格拉底,孔子,乔布斯,周报助手等。轻松满足你的各种聊天和应用需求。
* 支持 MidJourney AI 绘画集成,开箱即用。
* 支持 MidJourney / Stable Diffusion AI 绘画集成,开箱即用。
* 支持使用个人微信二维码作为充值收费的支付渠道,无需企业支付通道。(可定制开发其他支付通道支持)
* 集成插件 API 功能,可结合 GPT 开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI 绘画函数插件。

View File

@ -315,14 +315,26 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
// JobList 获取 MJ 任务列表
func (h *MidJourneyHandler) JobList(c *gin.Context) {
status := h.GetInt(c, "status", 0)
var items []model.MidJourneyJob
var res *gorm.DB
userId, _ := c.Get(types.LoginUserID)
userId := h.GetInt(c, "user_id", 0)
page := h.GetInt(c, "page", 0)
pageSize := h.GetInt(c, "page_size", 0)
session := h.db.Session(&gorm.Session{})
if status == 1 {
res = h.db.Where("user_id = ? AND progress = 100", userId).Order("id DESC").Find(&items)
session = session.Where("progress = ?", 100).Order("id DESC")
} else {
res = h.db.Where("user_id = ? AND progress < 100", userId).Order("id ASC").Find(&items)
session = session.Where("progress < ?", 100).Order("id ASC")
}
if userId > 0 {
session = session.Where("user_id = ?", userId)
}
if page > 0 && pageSize > 0 {
offset := (page - 1) * pageSize
session = session.Offset(offset).Limit(pageSize)
}
var items []model.MidJourneyJob
res := session.Find(&items)
if res.Error != nil {
resp.ERROR(c, types.NoData)
return

11
web/package-lock.json generated
View File

@ -23,6 +23,7 @@
"pinia": "^2.1.4",
"qs": "^6.11.1",
"sortablejs": "^1.15.0",
"v3-waterfall": "^1.2.1",
"vant": "^4.5.0",
"vue": "^3.2.13",
"vue-router": "^4.0.15"
@ -10459,6 +10460,11 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/v3-waterfall": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/v3-waterfall/-/v3-waterfall-1.2.1.tgz",
"integrity": "sha512-zjfT1FuHupsAahvS4mr3Yb8k2SHB8srW6st+/cBXwrsyhbCcj8Qhb1QtNUuEIx/tbpLQrMpxtJunZXkaKBfAEA=="
},
"node_modules/v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
@ -19518,6 +19524,11 @@
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true
},
"v3-waterfall": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/v3-waterfall/-/v3-waterfall-1.2.1.tgz",
"integrity": "sha512-zjfT1FuHupsAahvS4mr3Yb8k2SHB8srW6st+/cBXwrsyhbCcj8Qhb1QtNUuEIx/tbpLQrMpxtJunZXkaKBfAEA=="
},
"v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",

View File

@ -17,14 +17,15 @@
"good-storage": "^1.1.1",
"highlight.js": "^11.7.0",
"json-bigint": "^1.0.0",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"md-editor-v3": "^2.2.1",
"pinia": "^2.1.4",
"qs": "^6.11.1",
"sortablejs": "^1.15.0",
"v3-waterfall": "^1.2.1",
"vant": "^4.5.0",
"vue": "^3.2.13",
"lodash": "^4.17.21",
"vue-router": "^4.0.15"
},
"devDependencies": {

View File

@ -0,0 +1,46 @@
.page-images-wall {
display: flex;
background-color: #282c34;
}
.page-images-wall .inner {
width: 100%;
color: #fff;
overflow: hidden;
}
.page-images-wall .inner .header {
display: flex;
padding: 0 40px;
}
.page-images-wall .inner .header h2 {
width: 300px;
}
.page-images-wall .inner .header .settings {
width: 100%;
display: flex;
justify-content: right;
}
.page-images-wall .inner .header .settings .el-radio-group {
font-size: 16px;
}
.page-images-wall .inner .header .settings .el-radio-group .el-radio {
color: #fff;
}
.page-images-wall .inner .waterfall {
position: relative;
margin: 0 auto;
overflow-y: auto;
overflow-x: hidden;
}
.page-images-wall .custom-scroll ::-webkit-scrollbar {
width: 10px; /* 滚动条宽度 */
}
.page-images-wall .custom-scroll ::-webkit-scrollbar-track {
background-color: #282c34;
}
.page-images-wall .custom-scroll ::-webkit-scrollbar-thumb {
background-color: #444;
border-radius: 10px;
}
.page-images-wall .custom-scroll ::-webkit-scrollbar-thumb:hover {
background-color: #666;
}

View File

@ -0,0 +1,69 @@
.page-images-wall {
display: flex;
background-color: #282c34;
.inner {
width 100%
color #ffffff
overflow hidden
.header {
display flex
padding 0 40px
h2 {
width 300px
}
.settings {
width 100%
display flex
justify-content right
.el-radio-group {
font-size 16px
.el-radio {
color #ffffff
}
}
}
}
.waterfall {
position: relative;
margin: 0 auto;
overflow-y auto
overflow-x hidden
}
}
.custom-scroll {
/* */
::-webkit-scrollbar {
width: 10px; /* */
}
/* */
::-webkit-scrollbar-track {
background-color: #282C34;
}
/* */
::-webkit-scrollbar-thumb {
background-color: #444444;
border-radius 10px
}
/* */
::-webkit-scrollbar-thumb:hover {
background-color: #666666;
}
}
}

View File

@ -33,6 +33,8 @@ import {
Uploader
} from "vant";
import router from "@/router";
import 'v3-waterfall/dist/style.css'
import V3waterfall from "v3-waterfall";
const app = createApp(App)
app.use(createPinia())
@ -62,6 +64,7 @@ app.use(ShareSheet);
app.use(Switch);
app.use(Uploader);
app.use(Tag);
app.use(V3waterfall)
app.use(router).use(ElementPlus).mount('#app')

View File

@ -40,9 +40,9 @@ const routes = [
},
{
name: 'images',
path: '/images',
meta: {title: '绘画社区'},
component: () => import('@/views/Images.vue'),
path: '/images-wall',
meta: {title: '作品展示'},
component: () => import('@/views/ImagesWall.vue'),
},
{
name: 'user-invitation',

View File

@ -45,7 +45,7 @@ const navs = ref([
{path: "/mj", icon: "image", title: "MJ 绘画"},
{path: "/sd", icon: "palette", title: "SD 绘画"},
{path: "/apps", icon: "menu", title: "应用中心"},
{path: "/images", icon: "image-list", title: "绘画社区"},
{path: "/images-wall", icon: "image-list", title: "作品展示"},
{path: "/knowledge", icon: "book", title: "我的知识库"},
{path: "/member", icon: "vip-user", title: "会员计划"},
{path: "/invite", icon: "share", title: "推广计划"},

View File

@ -379,7 +379,6 @@ import {getSessionId, getUserToken} from "@/store/session";
const listBoxHeight = ref(window.innerHeight - 40)
const mjBoxHeight = ref(window.innerHeight - 150)
window.onresize = () => {
listBoxHeight.value = window.innerHeight - 40
mjBoxHeight.value = window.innerHeight - 150
@ -476,14 +475,14 @@ onMounted(() => {
checkSession().then(user => {
imgCalls.value = user['img_calls']
//
httpGet("/api/mj/jobs?status=0").then(res => {
httpGet(`/api/mj/jobs?status=0&user_id=${user['id']}`).then(res => {
runningJobs.value = res.data
}).catch(e => {
ElMessage.error("获取任务失败:" + e.message)
})
//
httpGet("/api/mj/jobs?status=1").then(res => {
httpGet(`/api/mj/jobs?status=1&user_id=${user['id']}`).then(res => {
finishedJobs.value = res.data
previewImgList.value = []
for (let index in finishedJobs.value) {

View File

@ -1,41 +0,0 @@
<template>
<div class="page-images" :style="{ height: winHeight + 'px' }">
<div class="inner">
<h1>绘画作品广场</h1>
<h2>页面正在紧锣密鼓开发中敬请期待</h2>
</div>
</div>
</template>
<script setup>
import {ref} from "vue"
const winHeight = ref(window.innerHeight)
</script>
<style lang="stylus" scoped>
.page-images {
display: flex;
justify-content: center;
align-items center
background-color: #282c34;
.inner {
text-align center
h1 {
color: #202020;
font-size: 80px;
font-weight: bold;
letter-spacing: 0.1em;
text-shadow: -1px -1px 1px #111111, 2px 2px 1px #363636;
}
h2 {
color #ffffff;
font-weight: bold;
}
}
}
</style>

View File

@ -0,0 +1,98 @@
<template>
<div class="page-images-wall">
<div class="inner custom-scroll">
<div class="header">
<h2>AI 绘画作品墙</h2>
<div class="settings">
<el-radio-group v-model="imgType">
<el-radio label="mj" size="large">MidJourney</el-radio>
<el-radio label="sd" size="large">Stable Diffusion</el-radio>
</el-radio-group>
</div>
</div>
<v3-waterfall class="waterfall" id="waterfall" :list="list" srcKey="img_url" :gap="12" :colWidth="colWidth"
:style="{ height:listBoxHeight + 'px' }"
:distanceToScroll="100" :isLoading="loading" :isOver="over" @scrollReachBottom="getNext">
<template #default="slotProp">
<div class="list-item">
<el-image :src="slotProp.item['img_url']+'?imageView2/4/w/300/q/75'"
:zoom-rate="1.2"
:preview-src-list="[slotProp.item['img_url']]"
:preview-teleported="true"
:initial-index="10"
loading="lazy">
<template #placeholder>
<div class="image-slot">
正在加载图片
</div>
</template>
<template #error>
<div class="image-slot">
<el-icon v-if="slotProp.item['img'] !== ''">
<Picture/>
</el-icon>
</div>
</template>
</el-image>
</div>
</template>
</v3-waterfall>
</div>
</div>
</template>
<script setup>
import {ref} from "vue"
import {Picture} from "@element-plus/icons-vue";
import {httpGet} from "@/utils/http";
import {ElMessage} from "element-plus";
const list = ref([])
const loading = ref(true)
const over = ref(false)
const imgType = ref("mj") //
const listBoxHeight = window.innerHeight - 100
const colWidth = ref(240)
//
const calcColWidth = () => {
const listBoxWidth = window.innerWidth - 60 - 80
const rows = Math.floor(listBoxWidth / colWidth.value)
colWidth.value = Math.floor((listBoxWidth - (rows - 1) * 12) / rows)
}
calcColWidth()
window.onresize = () => {
calcColWidth()
}
const page = ref(0)
const pageSize = ref(20)
//
const getNext = () => {
loading.value = true
page.value = page.value + 1
//
httpGet(`/api/mj/jobs?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
loading.value = false
if (list.value.length === 0) {
list.value = res.data
return
} else if (res.data.length < pageSize.value) {
over.value = true
}
list.value = list.value.concat(res.data)
}).catch(e => {
ElMessage.error("获取图片失败:" + e.message)
})
}
getNext()
</script>
<style lang="stylus" scoped>
@import "@/assets/css/images-wall.styl"
</style>