mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-18 01:06:39 +08:00
finished refactor chat page UI
This commit is contained in:
parent
f617bde81b
commit
dad9254128
@ -6,4 +6,4 @@ VUE_APP_ADMIN_USER=admin
|
|||||||
VUE_APP_ADMIN_PASS=admin123
|
VUE_APP_ADMIN_PASS=admin123
|
||||||
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
|
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
|
||||||
VUE_APP_TITLE="Geek-AI 创作系统"
|
VUE_APP_TITLE="Geek-AI 创作系统"
|
||||||
VUE_APP_VERSION=v4.0.6
|
VUE_APP_VERSION=v4.0.7
|
||||||
|
@ -2,4 +2,4 @@ VUE_APP_API_HOST=
|
|||||||
VUE_APP_WS_HOST=
|
VUE_APP_WS_HOST=
|
||||||
VUE_APP_KEY_PREFIX=ChatPLUS_
|
VUE_APP_KEY_PREFIX=ChatPLUS_
|
||||||
VUE_APP_TITLE="Geek-AI 创作系统"
|
VUE_APP_TITLE="Geek-AI 创作系统"
|
||||||
VUE_APP_VERSION=v4.0.6
|
VUE_APP_VERSION=v4.0.7
|
||||||
|
@ -114,10 +114,13 @@ $borderColor = #4676d0;
|
|||||||
|
|
||||||
.tool-box {
|
.tool-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: center;
|
||||||
align-items: center;
|
padding-top 12px
|
||||||
padding 0 20px 10px 20px;
|
|
||||||
border-top 1px solid #3c3c3c;
|
border-top 1px solid #3c3c3c;
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
margin-right 5px
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,52 +129,6 @@ $borderColor = #4676d0;
|
|||||||
--el-main-padding: 0;
|
--el-main-padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
.chat-head {
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
background-color: #28292A
|
|
||||||
|
|
||||||
.chat-config {
|
|
||||||
display flex
|
|
||||||
flex-direction row
|
|
||||||
align-items: center;
|
|
||||||
justify-content center;
|
|
||||||
padding-top 10px;
|
|
||||||
|
|
||||||
.role-select-label {
|
|
||||||
color #ffffff
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-select {
|
|
||||||
max-width 150px;
|
|
||||||
margin-right 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.role-select {
|
|
||||||
max-width 130px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-button {
|
|
||||||
.el-icon {
|
|
||||||
margin-right 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconfont {
|
|
||||||
margin-right 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-circle {
|
|
||||||
margin-left 5px
|
|
||||||
|
|
||||||
.iconfont {
|
|
||||||
margin-right 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.chat-box {
|
.chat-box {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -207,23 +164,6 @@ $borderColor = #4676d0;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.re-generate {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.btn-box {
|
|
||||||
position: absolute
|
|
||||||
bottom: 10px;
|
|
||||||
|
|
||||||
.el-button {
|
|
||||||
.el-icon {
|
|
||||||
margin-right 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-box {
|
.input-box {
|
||||||
background-color: #ffffff
|
background-color: #ffffff
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -232,6 +172,26 @@ $borderColor = #4676d0;
|
|||||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||||
padding 0 15px;
|
padding 0 15px;
|
||||||
|
|
||||||
|
.tool-item {
|
||||||
|
margin-right 15px
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #19c37d;
|
||||||
|
display flex
|
||||||
|
justify-content center
|
||||||
|
justify-items center
|
||||||
|
padding 6px
|
||||||
|
cursor pointer
|
||||||
|
background #F2F2F2
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background #D5FAD3
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.input-container {
|
.input-container {
|
||||||
width 100%
|
width 100%
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -242,7 +202,6 @@ $borderColor = #4676d0;
|
|||||||
position relative
|
position relative
|
||||||
|
|
||||||
.el-textarea {
|
.el-textarea {
|
||||||
|
|
||||||
.el-textarea__inner::-webkit-scrollbar {
|
.el-textarea__inner::-webkit-scrollbar {
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
@ -263,8 +222,6 @@ $borderColor = #4676d0;
|
|||||||
.el-button {
|
.el-button {
|
||||||
padding 8px 5px;
|
padding 8px 5px;
|
||||||
border-radius 6px;
|
border-radius 6px;
|
||||||
background: rgb(25, 195, 125)
|
|
||||||
color #ffffff;
|
|
||||||
font-size 20px;
|
font-size 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
|
display flex
|
||||||
|
flex-flow row
|
||||||
|
|
||||||
|
.link-button {
|
||||||
|
margin-right 15px
|
||||||
|
color #e1e1e1
|
||||||
|
padding 0 10px
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color #414141
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-size 24px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.user-info {
|
.user-info {
|
||||||
width 100%
|
width 100%
|
||||||
padding 5px 0;
|
padding 5px 0;
|
||||||
@ -153,4 +170,18 @@
|
|||||||
background-color #f1f1f1
|
background-color #f1f1f1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-info-menu {
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
width 100%
|
||||||
|
justify-content left
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration none !important
|
||||||
|
color var(--el-primary-text-color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 4125778 */
|
font-family: "iconfont"; /* Project id 4125778 */
|
||||||
src: url('iconfont.woff2?t=1713766977199') format('woff2'),
|
src: url('iconfont.woff2?t=1715938850931') format('woff2'),
|
||||||
url('iconfont.woff?t=1713766977199') format('woff'),
|
url('iconfont.woff?t=1715938850931') format('woff'),
|
||||||
url('iconfont.ttf?t=1713766977199') format('truetype');
|
url('iconfont.ttf?t=1715938850931') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@ -13,6 +13,38 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-mic-bold:before {
|
||||||
|
content: "\e683";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-mic-thin:before {
|
||||||
|
content: "\e8c2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-attachment-cl:before {
|
||||||
|
content: "\e66a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-attachment-st:before {
|
||||||
|
content: "\e63b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-speaker:before {
|
||||||
|
content: "\e607";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-clear:before {
|
||||||
|
content: "\e900";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-bbs:before {
|
||||||
|
content: "\e623";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-license:before {
|
||||||
|
content: "\e65a";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-more:before {
|
.icon-more:before {
|
||||||
content: "\e63c";
|
content: "\e63c";
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@ -5,6 +5,62 @@
|
|||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "6539424",
|
||||||
|
"name": "麦克风",
|
||||||
|
"font_class": "mic-bold",
|
||||||
|
"unicode": "e683",
|
||||||
|
"unicode_decimal": 59011
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1727442",
|
||||||
|
"name": "213麦克风",
|
||||||
|
"font_class": "mic-thin",
|
||||||
|
"unicode": "e8c2",
|
||||||
|
"unicode_decimal": 59586
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "3730725",
|
||||||
|
"name": "attach-attachment-cl",
|
||||||
|
"font_class": "attachment-cl",
|
||||||
|
"unicode": "e66a",
|
||||||
|
"unicode_decimal": 58986
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "15748474",
|
||||||
|
"name": "st-attachment",
|
||||||
|
"font_class": "attachment-st",
|
||||||
|
"unicode": "e63b",
|
||||||
|
"unicode_decimal": 58939
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1010",
|
||||||
|
"name": "扬声器",
|
||||||
|
"font_class": "speaker",
|
||||||
|
"unicode": "e607",
|
||||||
|
"unicode_decimal": 58887
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "8094805",
|
||||||
|
"name": "clear",
|
||||||
|
"font_class": "clear",
|
||||||
|
"unicode": "e900",
|
||||||
|
"unicode_decimal": 59648
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "34803640",
|
||||||
|
"name": "论坛",
|
||||||
|
"font_class": "bbs",
|
||||||
|
"unicode": "e623",
|
||||||
|
"unicode_decimal": 58915
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "310708",
|
||||||
|
"name": "license",
|
||||||
|
"font_class": "license",
|
||||||
|
"unicode": "e65a",
|
||||||
|
"unicode_decimal": 58970
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "1421807",
|
"icon_id": "1421807",
|
||||||
"name": "更多",
|
"name": "更多",
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
<div class="chat-item">
|
<div class="chat-item">
|
||||||
<div class="content" v-html="content"></div>
|
<div class="content" v-html="content"></div>
|
||||||
<div class="bar" v-if="createdAt !== ''">
|
<div class="bar" v-if="createdAt">
|
||||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
|
<span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
|
||||||
<span class="bar-item">Tokens: {{ finalTokens }}</span>
|
<!-- <span class="bar-item">Tokens: {{ finalTokens }}</span>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,38 +2,61 @@
|
|||||||
<div class="chat-line chat-line-reply">
|
<div class="chat-line chat-line-reply">
|
||||||
<div class="chat-line-inner">
|
<div class="chat-line-inner">
|
||||||
<div class="chat-icon">
|
<div class="chat-icon">
|
||||||
<img :src="icon" alt="ChatGPT">
|
<img :src="data.icon" alt="ChatGPT">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chat-item">
|
<div class="chat-item">
|
||||||
<div class="content" v-html="content"></div>
|
<div class="content" v-html="data.content"></div>
|
||||||
<div class="bar" v-if="createdAt !== ''">
|
<div class="bar" v-if="data.created_at">
|
||||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
|
<span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
|
||||||
<span class="bar-item">Tokens: {{ tokens }}</span>
|
<!-- <span class="bar-item">Tokens: {{ tokens }}</span>-->
|
||||||
<span class="bar-item">
|
<span class="bar-item">
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="复制回答"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<el-icon class="copy-reply" :data-clipboard-text="data.orgContent">
|
||||||
|
<DocumentCopy/>
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
|
<span v-if="!readOnly">
|
||||||
|
<span class="bar-item" @click="reGenerate(data.prompt)">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="dark"
|
effect="dark"
|
||||||
content="复制回答"
|
content="重新生成"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
<el-icon class="copy-reply" :data-clipboard-text="orgContent">
|
<el-icon><Refresh/></el-icon>
|
||||||
<DocumentCopy/>
|
</el-tooltip>
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</span>
|
</span>
|
||||||
<span class="bar-item">
|
|
||||||
<el-dropdown trigger="click">
|
<span class="bar-item" @click="synthesis(data.orgContent)">
|
||||||
<span class="el-dropdown-link">
|
<el-tooltip
|
||||||
<el-icon><More/></el-icon>
|
class="box-item"
|
||||||
</span>
|
effect="dark"
|
||||||
<template #dropdown>
|
content="生成语音朗读"
|
||||||
<el-dropdown-menu>
|
placement="bottom"
|
||||||
<el-dropdown-item :icon="Headset" @click="synthesis(orgContent)">生成语音</el-dropdown-item>
|
>
|
||||||
</el-dropdown-menu>
|
<i class="iconfont icon-speaker"></i>
|
||||||
</template>
|
</el-tooltip>
|
||||||
</el-dropdown>
|
|
||||||
</span>
|
</span>
|
||||||
|
</span>
|
||||||
|
<!-- <span class="bar-item">-->
|
||||||
|
<!-- <el-dropdown trigger="click">-->
|
||||||
|
<!-- <span class="el-dropdown-link">-->
|
||||||
|
<!-- <el-icon><More/></el-icon>-->
|
||||||
|
<!-- </span>-->
|
||||||
|
<!-- <template #dropdown>-->
|
||||||
|
<!-- <el-dropdown-menu>-->
|
||||||
|
<!-- <el-dropdown-item :icon="Headset" @click="synthesis(orgContent)">生成语音</el-dropdown-item>-->
|
||||||
|
<!-- </el-dropdown-menu>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- </el-dropdown>-->
|
||||||
|
<!-- </span>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -41,36 +64,36 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {Clock, DocumentCopy, Headset, More} from "@element-plus/icons-vue";
|
import {Clock, DocumentCopy, Refresh} from "@element-plus/icons-vue";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
|
import {dateFormat} from "@/utils/libs";
|
||||||
// eslint-disable-next-line no-undef,no-unused-vars
|
// eslint-disable-next-line no-undef,no-unused-vars
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
content: {
|
data: {
|
||||||
type: String,
|
type: Object,
|
||||||
default: '',
|
default: {},
|
||||||
},
|
},
|
||||||
orgContent: {
|
readOnly: {
|
||||||
type: String,
|
type: Boolean,
|
||||||
default: '',
|
default: false
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
tokens: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
type: String,
|
|
||||||
default: 'images/gpt-icon.png',
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(['regen']);
|
||||||
|
|
||||||
|
if (!props.data.icon) {
|
||||||
|
props.data.icon = "images/gpt-icon.png"
|
||||||
|
}
|
||||||
|
|
||||||
const synthesis = (text) => {
|
const synthesis = (text) => {
|
||||||
console.log(text)
|
console.log(text)
|
||||||
ElMessage.info("语音合成功能暂不可用")
|
ElMessage.info("语音合成功能暂不可用")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重新生成
|
||||||
|
const reGenerate = (prompt) => {
|
||||||
|
emits('regen', prompt)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
@ -226,6 +249,7 @@ const synthesis = (text) => {
|
|||||||
padding 3px 5px;
|
padding 3px 5px;
|
||||||
margin-right 10px;
|
margin-right 10px;
|
||||||
border-radius 5px;
|
border-radius 5px;
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
position relative
|
position relative
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-container class="file-list-box">
|
<el-container class="file-list-box">
|
||||||
<el-tooltip class="box-item" effect="dark" content="打开文件管理中心">
|
<a class="file-upload-img" @click="fetchFiles">
|
||||||
<el-button class="file-upload-img" @click="fetchFiles">
|
<i class="iconfont icon-attachment-st"></i>
|
||||||
<el-icon>
|
</a>
|
||||||
<PictureFilled/>
|
|
||||||
</el-icon>
|
|
||||||
</el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="show"
|
v-model="show"
|
||||||
:close-on-click-modal="true"
|
:close-on-click-modal="true"
|
||||||
@ -58,7 +53,7 @@
|
|||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpGet, httpPost} from "@/utils/http";
|
||||||
import {Delete, PictureFilled, Plus} from "@element-plus/icons-vue";
|
import {Delete, Plus} from "@element-plus/icons-vue";
|
||||||
import {isImage, removeArrayItem} from "@/utils/libs";
|
import {isImage, removeArrayItem} from "@/utils/libs";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -132,11 +127,9 @@ const insertURL = (url) => {
|
|||||||
|
|
||||||
.file-list-box {
|
.file-list-box {
|
||||||
.file-upload-img {
|
.file-upload-img {
|
||||||
padding: 8px 5px;
|
.iconfont {
|
||||||
border-radius: 6px;
|
font-size: 24px;
|
||||||
background: #19c37d;
|
}
|
||||||
color: #fff;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dialog {
|
.el-dialog {
|
||||||
|
@ -221,7 +221,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed, ref} from "vue"
|
import {ref, watch} from "vue"
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpGet, httpPost} from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {setUserToken} from "@/store/session";
|
import {setUserToken} from "@/store/session";
|
||||||
@ -234,8 +234,9 @@ import {arrayContains} from "@/utils/libs";
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: Boolean,
|
show: Boolean,
|
||||||
});
|
});
|
||||||
const showDialog = computed(() => {
|
const showDialog = ref(false)
|
||||||
return props.show
|
watch(() => props.show, (newValue) => {
|
||||||
|
showDialog.value = newValue
|
||||||
})
|
})
|
||||||
|
|
||||||
const login = ref(true)
|
const login = ref(true)
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
import {createApp} from 'vue'
|
import {createApp} from 'vue'
|
||||||
import ElementPlus from "element-plus"
|
import ElementPlus from "element-plus"
|
||||||
import "element-plus/dist/index.css"
|
import "element-plus/dist/index.css"
|
||||||
|
import '@/assets/iconfont/iconfont.css';
|
||||||
import 'vant/lib/index.css';
|
import 'vant/lib/index.css';
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import {createPinia} from "pinia";
|
import {createPinia} from "pinia";
|
||||||
|
13
web/src/store/sharedata.js
Normal file
13
web/src/store/sharedata.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import {defineStore} from 'pinia';
|
||||||
|
|
||||||
|
export const useSharedStore = defineStore('shared', {
|
||||||
|
state: () => ({
|
||||||
|
showLoginDialog: false
|
||||||
|
}),
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
setShowLoginDialog(value) {
|
||||||
|
this.showLoginDialog = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -3,9 +3,6 @@
|
|||||||
<div class="chat-box" id="chat-box">
|
<div class="chat-box" id="chat-box">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h2>{{ chatTitle }}</h2>
|
<h2>{{ chatTitle }}</h2>
|
||||||
<el-button type="success" @click="exportChat" :icon="Promotion">
|
|
||||||
导出 PDF 文档
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-for="item in chatData" :key="item.id">
|
<div v-for="item in chatData" :key="item.id">
|
||||||
@ -14,17 +11,10 @@
|
|||||||
:icon="item.icon"
|
:icon="item.icon"
|
||||||
:created-at="dateFormat(item['created_at'])"
|
:created-at="dateFormat(item['created_at'])"
|
||||||
:tokens="item['tokens']"
|
:tokens="item['tokens']"
|
||||||
|
:model="item['model']"
|
||||||
:content="item.content"/>
|
:content="item.content"/>
|
||||||
<chat-reply v-else-if="item.type==='reply'"
|
<chat-reply v-else-if="item.type==='reply'"
|
||||||
:icon="item.icon"
|
:data="item" :read-only="true"/>
|
||||||
:org-content="item.orgContent"
|
|
||||||
:created-at="dateFormat(item['created_at'])"
|
|
||||||
:tokens="item['tokens']"
|
|
||||||
:content="item.content"/>
|
|
||||||
<chat-mid-journey v-else-if="item.type==='mj'"
|
|
||||||
:content="item.content"
|
|
||||||
:icon="item.icon"
|
|
||||||
:created-at="dateFormat(item['created_at'])"/>
|
|
||||||
</div>
|
</div>
|
||||||
</div><!-- end chat box -->
|
</div><!-- end chat box -->
|
||||||
</div>
|
</div>
|
||||||
@ -34,14 +24,13 @@
|
|||||||
import {dateFormat} from "@/utils/libs";
|
import {dateFormat} from "@/utils/libs";
|
||||||
import ChatReply from "@/components/ChatReply.vue";
|
import ChatReply from "@/components/ChatReply.vue";
|
||||||
import ChatPrompt from "@/components/ChatPrompt.vue";
|
import ChatPrompt from "@/components/ChatPrompt.vue";
|
||||||
import {nextTick, ref} from "vue";
|
import {nextTick, onMounted, ref} from "vue";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {httpGet} from "@/utils/http";
|
import {httpGet} from "@/utils/http";
|
||||||
import 'highlight.js/styles/a11y-dark.css'
|
import 'highlight.js/styles/a11y-dark.css'
|
||||||
import hl from "highlight.js";
|
import hl from "highlight.js";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {Promotion} from "@element-plus/icons-vue";
|
import Clipboard from "clipboard";
|
||||||
import ChatMidJourney from "@/components/ChatMidJourney.vue";
|
|
||||||
|
|
||||||
const chatData = ref([])
|
const chatData = ref([])
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -91,9 +80,16 @@ httpGet('/api/chat/detail?chat_id=' + chatId).then(res => {
|
|||||||
ElMessage.error("加载会失败: " + e.message)
|
ElMessage.error("加载会失败: " + e.message)
|
||||||
})
|
})
|
||||||
|
|
||||||
const exportChat = () => {
|
onMounted(() => {
|
||||||
window.print()
|
const clipboard = new Clipboard('.copy-reply');
|
||||||
}
|
clipboard.on('success', () => {
|
||||||
|
ElMessage.success('复制成功!');
|
||||||
|
})
|
||||||
|
|
||||||
|
clipboard.on('error', () => {
|
||||||
|
ElMessage.error('复制失败!');
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
.chat-export {
|
.chat-export {
|
||||||
@ -115,12 +111,15 @@ const exportChat = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.chat-line {
|
.chat-line-prompt {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
||||||
.chat-line-inner {
|
.chat-line-inner {
|
||||||
|
.chat-icon {
|
||||||
|
margin-right: 0
|
||||||
|
}
|
||||||
.content {
|
.content {
|
||||||
padding-top: 0
|
padding-top: 0
|
||||||
font-size 16px;
|
font-size 16px;
|
||||||
@ -138,10 +137,6 @@ const exportChat = () => {
|
|||||||
.chat-line-inner {
|
.chat-line-inner {
|
||||||
display flex
|
display flex
|
||||||
|
|
||||||
.copy-reply {
|
|
||||||
display none
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar-item {
|
.bar-item {
|
||||||
background-color: #f7f7f8;
|
background-color: #f7f7f8;
|
||||||
color: #888;
|
color: #888;
|
||||||
|
@ -59,92 +59,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tool-box">
|
<div class="tool-box">
|
||||||
<!-- <el-dropdown :hide-on-click="true" class="user-info" trigger="click" v-if="isLogin">-->
|
<el-button type="danger" size="small" @click="clearAllChats">
|
||||||
<!-- <span class="el-dropdown-link">-->
|
<i class="iconfont icon-clear"></i> 清空聊天记录
|
||||||
<!-- <el-image :src="loginUser.avatar"/>-->
|
</el-button>
|
||||||
<!-- <span class="username">{{ loginUser.nickname }}</span>-->
|
|
||||||
<!-- <el-icon><ArrowDown/></el-icon>-->
|
|
||||||
<!-- </span>-->
|
|
||||||
<!-- <template #dropdown>-->
|
|
||||||
<!-- <el-dropdown-menu style="width: 296px;">-->
|
|
||||||
<!-- <el-dropdown-item @click="showConfig">-->
|
|
||||||
<!-- <el-icon>-->
|
|
||||||
<!-- <Tools/>-->
|
|
||||||
<!-- </el-icon>-->
|
|
||||||
<!-- <span>账户信息</span>-->
|
|
||||||
<!-- </el-dropdown-item>-->
|
|
||||||
|
|
||||||
<!-- <el-dropdown-item @click="clearAllChats">-->
|
|
||||||
<!-- <el-icon>-->
|
|
||||||
<!-- <Delete/>-->
|
|
||||||
<!-- </el-icon>-->
|
|
||||||
<!-- <span>清除所有会话</span>-->
|
|
||||||
<!-- </el-dropdown-item>-->
|
|
||||||
|
|
||||||
<!-- <el-dropdown-item @click="logout">-->
|
|
||||||
<!-- <i class="iconfont icon-logout"></i>-->
|
|
||||||
<!-- <span>注销</span>-->
|
|
||||||
<!-- </el-dropdown-item>-->
|
|
||||||
|
|
||||||
<!-- <el-dropdown-item>-->
|
|
||||||
<!-- <i class="iconfont icon-github"></i>-->
|
|
||||||
<!-- <span>-->
|
|
||||||
<!-- powered by-->
|
|
||||||
<!-- <el-link type="primary" href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">chatgpt-plus-v3</el-link>-->
|
|
||||||
<!-- </span>-->
|
|
||||||
<!-- </el-dropdown-item>-->
|
|
||||||
<!-- </el-dropdown-menu>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-dropdown>-->
|
|
||||||
</div>
|
</div>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
<el-main v-loading="loading" element-loading-background="rgba(122, 122, 122, 0.3)">
|
<el-main v-loading="loading" element-loading-background="rgba(122, 122, 122, 0.3)">
|
||||||
<!-- <div class="chat-head">-->
|
|
||||||
<!-- <div class="chat-config">-->
|
|
||||||
<!-- <el-select v-model="roleId" filterable placeholder="角色" class="role-select" @change="_newChat"-->
|
|
||||||
<!-- style="width:150px">-->
|
|
||||||
<!-- <el-option-->
|
|
||||||
<!-- v-for="item in roles"-->
|
|
||||||
<!-- :key="item.id"-->
|
|
||||||
<!-- :label="item.name"-->
|
|
||||||
<!-- :value="item.id"-->
|
|
||||||
<!-- >-->
|
|
||||||
<!-- <div class="role-option">-->
|
|
||||||
<!-- <el-image :src="item.icon"></el-image>-->
|
|
||||||
<!-- <span>{{ item.name }}</span>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </el-option>-->
|
|
||||||
<!-- </el-select>-->
|
|
||||||
|
|
||||||
<!-- <el-select v-model="modelID" placeholder="模型" @change="_newChat" :disabled="disableModel"-->
|
|
||||||
<!-- style="width:150px">-->
|
|
||||||
<!-- <el-option-->
|
|
||||||
<!-- v-for="item in models"-->
|
|
||||||
<!-- :key="item.id"-->
|
|
||||||
<!-- :label="item.name"-->
|
|
||||||
<!-- :value="item.id"-->
|
|
||||||
<!-- >-->
|
|
||||||
<!-- <span>{{ item.name }}</span>-->
|
|
||||||
<!-- <el-tag style="margin-left: 5px; position: relative; top:-2px" type="info" size="small">{{-->
|
|
||||||
<!-- item.power-->
|
|
||||||
<!-- }}算力-->
|
|
||||||
<!-- </el-tag>-->
|
|
||||||
<!-- </el-option>-->
|
|
||||||
<!-- </el-select>-->
|
|
||||||
<!-- <el-button type="primary" @click="newChat">-->
|
|
||||||
<!-- <el-icon>-->
|
|
||||||
<!-- <Plus/>-->
|
|
||||||
<!-- </el-icon>-->
|
|
||||||
<!-- 新建对话-->
|
|
||||||
<!-- </el-button>-->
|
|
||||||
|
|
||||||
<!-- <el-button type="success" @click="exportChat" plain>-->
|
|
||||||
<!-- <i class="iconfont icon-export"></i>-->
|
|
||||||
<!-- <span>导出会话</span>-->
|
|
||||||
<!-- </el-button>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
|
|
||||||
<div class="chat-box" :style="{height: mainWinHeight+'px'}">
|
<div class="chat-box" :style="{height: mainWinHeight+'px'}">
|
||||||
<div>
|
<div>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
@ -160,63 +80,102 @@
|
|||||||
:tokens="item['tokens']"
|
:tokens="item['tokens']"
|
||||||
:model="getModelValue(modelID)"
|
:model="getModelValue(modelID)"
|
||||||
:content="item.content"/>
|
:content="item.content"/>
|
||||||
<chat-reply v-else-if="item.type==='reply'"
|
<chat-reply v-else-if="item.type==='reply'" :data="item" @regen="reGenerate" :read-only="false"/>
|
||||||
:icon="item.icon"
|
|
||||||
:org-content="item.orgContent"
|
|
||||||
:created-at="dateFormat(item['created_at'])"
|
|
||||||
:tokens="item['tokens']"
|
|
||||||
:content="item.content"/>
|
|
||||||
<chat-mid-journey v-else-if="item.type==='mj'"
|
|
||||||
:content="item.content"
|
|
||||||
:role-id="item.role_id"
|
|
||||||
:chat-id="item.chat_id"
|
|
||||||
:icon="item.icon"
|
|
||||||
@disable-input="disableInput(true)"
|
|
||||||
@enable-input="enableInput"
|
|
||||||
:created-at="dateFormat(item['created_at'])"/>
|
|
||||||
</div>
|
</div>
|
||||||
</div><!-- end chat box -->
|
</div><!-- end chat box -->
|
||||||
|
|
||||||
<div class="re-generate">
|
<el-affix position="bottom" :offset="0">
|
||||||
<div class="btn-box">
|
<div class="input-box">
|
||||||
<el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
|
<span class="tool-item">
|
||||||
<el-icon>
|
<el-tooltip effect="dark" content="聊天设置">
|
||||||
<VideoPause/>
|
<el-popover
|
||||||
</el-icon>
|
:width="300"
|
||||||
停止生成
|
trigger="click"
|
||||||
</el-button>
|
placement="top-start"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<i class="iconfont icon-config"></i>
|
||||||
|
</template>
|
||||||
|
|
||||||
<el-button type="primary" v-if="showReGenerate" @click="reGenerate" plain>
|
<template #default>
|
||||||
<el-icon>
|
<div class="chat-config">
|
||||||
<RefreshRight/>
|
<el-select v-model="roleId" filterable placeholder="角色" @change="newChat"
|
||||||
</el-icon>
|
class="role-select"
|
||||||
重新生成
|
style="width:150px">
|
||||||
</el-button>
|
<el-option
|
||||||
</div>
|
v-for="item in roles"
|
||||||
</div>
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
<div class="role-option">
|
||||||
|
<el-image :src="item.icon"></el-image>
|
||||||
|
<span>{{ item.name }}</span>
|
||||||
|
</div>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
<div class="input-box">
|
<el-select v-model="modelID" filterable placeholder="模型" @change="newChat"
|
||||||
<div class="input-container">
|
:disabled="disableModel"
|
||||||
<el-input
|
style="width:150px">
|
||||||
ref="textInput"
|
<el-option
|
||||||
v-model="prompt"
|
v-for="item in models"
|
||||||
v-on:keydown="inputKeyDown"
|
:key="item.id"
|
||||||
autofocus
|
:label="item.name"
|
||||||
type="textarea"
|
:value="item.id"
|
||||||
:rows="2"
|
>
|
||||||
placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
|
<span>{{ item.name }}</span>
|
||||||
/>
|
<el-tag style="margin-left: 5px; position: relative; top:-2px" type="info" size="small">{{
|
||||||
<span class="select-file">
|
item.power
|
||||||
<file-select v-if="isLogin" :user-id="loginUser.id" @selected="insertURL"/>
|
}}算力
|
||||||
|
</el-tag>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
|
||||||
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<span class="send-btn">
|
|
||||||
<el-button @click="sendMessage">
|
<span class="tool-item" @click="ElMessage.info('暂时不支持语音输入')">
|
||||||
|
<el-tooltip class="box-item" effect="dark" content="语音输入">
|
||||||
|
<i class="iconfont icon-mic-bold"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="tool-item">
|
||||||
|
<el-tooltip class="box-item" effect="dark" content="上传附件">
|
||||||
|
<file-select v-if="isLogin" :user-id="loginUser.id" @selected="insertURL"/>
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="input-container">
|
||||||
|
<el-input
|
||||||
|
ref="textInput"
|
||||||
|
v-model="prompt"
|
||||||
|
v-on:keydown="inputKeyDown"
|
||||||
|
autofocus
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
style="--el-input-focus-border-color:#21AA93;
|
||||||
|
border: 1px solid #21AA93;--el-input-border-color:#21AA93;
|
||||||
|
border-radius: 5px; --el-input-hover-border-color:#21AA93;"
|
||||||
|
placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
|
||||||
|
/>
|
||||||
|
<span class="send-btn">
|
||||||
|
<el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
|
||||||
|
<el-icon>
|
||||||
|
<VideoPause/>
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="sendMessage" color="#19c37d" style="color:#ffffff" v-else>
|
||||||
<el-icon><Promotion/></el-icon>
|
<el-icon><Promotion/></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- end input box -->
|
</div><!-- end input box -->
|
||||||
|
</el-affix>
|
||||||
</div><!-- end container -->
|
</div><!-- end container -->
|
||||||
</div><!-- end loading -->
|
</div><!-- end loading -->
|
||||||
</div>
|
</div>
|
||||||
@ -239,10 +198,6 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<config-dialog v-if="isLogin" :show="showConfigDialog" :models="models" @hide="showConfigDialog = false"/>
|
|
||||||
|
|
||||||
<login-dialog :show="showLoginDialog" @hide="showLoginDialog = false" @success="initData"/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -251,7 +206,7 @@
|
|||||||
import {nextTick, onMounted, onUnmounted, ref} from 'vue'
|
import {nextTick, onMounted, onUnmounted, ref} from 'vue'
|
||||||
import ChatPrompt from "@/components/ChatPrompt.vue";
|
import ChatPrompt from "@/components/ChatPrompt.vue";
|
||||||
import ChatReply from "@/components/ChatReply.vue";
|
import ChatReply from "@/components/ChatReply.vue";
|
||||||
import {Delete, Edit, More, Plus, Promotion, RefreshRight, Search, Share, VideoPause} from '@element-plus/icons-vue'
|
import {Delete, Edit, More, Plus, Promotion, Search, Share, VideoPause} from '@element-plus/icons-vue'
|
||||||
import 'highlight.js/styles/a11y-dark.css'
|
import 'highlight.js/styles/a11y-dark.css'
|
||||||
import {dateFormat, escapeHTML, isMobile, processContent, randString, removeArrayItem, UUID} from "@/utils/libs";
|
import {dateFormat, escapeHTML, isMobile, processContent, randString, removeArrayItem, UUID} from "@/utils/libs";
|
||||||
import {ElMessage, ElMessageBox} from "element-plus";
|
import {ElMessage, ElMessageBox} from "element-plus";
|
||||||
@ -260,12 +215,10 @@ import {getSessionId, getUserToken, removeUserToken} from "@/store/session";
|
|||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpGet, httpPost} from "@/utils/http";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import Clipboard from "clipboard";
|
import Clipboard from "clipboard";
|
||||||
import ConfigDialog from "@/components/ConfigDialog.vue";
|
|
||||||
import {checkSession} from "@/action/session";
|
import {checkSession} from "@/action/session";
|
||||||
import Welcome from "@/components/Welcome.vue";
|
import Welcome from "@/components/Welcome.vue";
|
||||||
import ChatMidJourney from "@/components/ChatMidJourney.vue";
|
import {useSharedStore} from "@/store/sharedata";
|
||||||
import FileSelect from "@/components/FileSelect.vue";
|
import FileSelect from "@/components/FileSelect.vue";
|
||||||
import LoginDialog from "@/components/LoginDialog.vue";
|
|
||||||
|
|
||||||
const title = ref('ChatGPT-智能助手');
|
const title = ref('ChatGPT-智能助手');
|
||||||
const models = ref([])
|
const models = ref([])
|
||||||
@ -283,14 +236,13 @@ const roles = ref([]);
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const roleId = ref(0)
|
const roleId = ref(0)
|
||||||
const newChatItem = ref(null);
|
const newChatItem = ref(null);
|
||||||
const showConfigDialog = ref(false);
|
|
||||||
const showLoginDialog = ref(false)
|
|
||||||
const isLogin = ref(false)
|
const isLogin = ref(false)
|
||||||
const showHello = ref(true)
|
const showHello = ref(true)
|
||||||
const textInput = ref(null)
|
const textInput = ref(null)
|
||||||
const showNotice = ref(false)
|
const showNotice = ref(false)
|
||||||
const notice = ref("")
|
const notice = ref("")
|
||||||
const noticeKey = ref("SYSTEM_NOTICE")
|
const noticeKey = ref("SYSTEM_NOTICE")
|
||||||
|
const store = useSharedStore();
|
||||||
|
|
||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
router.replace("/mobile/chat")
|
router.replace("/mobile/chat")
|
||||||
@ -426,22 +378,17 @@ const resizeElement = function () {
|
|||||||
leftBoxHeight.value = window.innerHeight - 90 - 45 - 82;
|
leftBoxHeight.value = window.innerHeight - 90 - 45 - 82;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _newChat = () => {
|
|
||||||
if (isLogin.value) {
|
|
||||||
newChat()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const disableModel = ref(false)
|
const disableModel = ref(false)
|
||||||
// 新建会话
|
// 新建会话
|
||||||
const newChat = () => {
|
const newChat = () => {
|
||||||
if (!isLogin.value) {
|
if (!isLogin.value) {
|
||||||
showLoginDialog.value = true
|
store.setShowLoginDialog(true)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const role = getRoleById(roleId.value)
|
const role = getRoleById(roleId.value)
|
||||||
showHello.value = role.key === 'gpt';
|
showHello.value = role.key === 'gpt';
|
||||||
// if the role bind a model, disable model change
|
// if the role bind a model, disable model change
|
||||||
|
disableModel.value = false
|
||||||
if (role.model_id > 0) {
|
if (role.model_id > 0) {
|
||||||
modelID.value = role.model_id
|
modelID.value = role.model_id
|
||||||
disableModel.value = true
|
disableModel.value = true
|
||||||
@ -469,7 +416,6 @@ const newChat = () => {
|
|||||||
};
|
};
|
||||||
activeChat.value = {} //取消激活的会话高亮
|
activeChat.value = {} //取消激活的会话高亮
|
||||||
showStopGenerate.value = false;
|
showStopGenerate.value = false;
|
||||||
showReGenerate.value = false;
|
|
||||||
connect(null, roleId.value)
|
connect(null, roleId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,7 +427,7 @@ const changeChat = (chat) => {
|
|||||||
|
|
||||||
const loadChat = function (chat) {
|
const loadChat = function (chat) {
|
||||||
if (!isLogin.value) {
|
if (!isLogin.value) {
|
||||||
showLoginDialog.value = true
|
store.setShowLoginDialog(true)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,7 +440,6 @@ const loadChat = function (chat) {
|
|||||||
roleId.value = chat.role_id;
|
roleId.value = chat.role_id;
|
||||||
modelID.value = chat.model_id;
|
modelID.value = chat.model_id;
|
||||||
showStopGenerate.value = false;
|
showStopGenerate.value = false;
|
||||||
showReGenerate.value = false;
|
|
||||||
connect(chat.chat_id, chat.role_id)
|
connect(chat.chat_id, chat.role_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,8 +545,6 @@ md.use(mathjaxPlugin)
|
|||||||
// 创建 socket 连接
|
// 创建 socket 连接
|
||||||
const prompt = ref('');
|
const prompt = ref('');
|
||||||
const showStopGenerate = ref(false); // 停止生成
|
const showStopGenerate = ref(false); // 停止生成
|
||||||
const showReGenerate = ref(false); // 重新生成
|
|
||||||
const previousText = ref(''); // 上一次提问
|
|
||||||
const lineBuffer = ref(''); // 输出缓冲行
|
const lineBuffer = ref(''); // 输出缓冲行
|
||||||
const socket = ref(null);
|
const socket = ref(null);
|
||||||
const activelyClose = ref(false); // 主动关闭
|
const activelyClose = ref(false); // 主动关闭
|
||||||
@ -646,7 +589,6 @@ const connect = function (chat_id, role_id) {
|
|||||||
const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
|
const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
|
||||||
_socket.addEventListener('open', () => {
|
_socket.addEventListener('open', () => {
|
||||||
chatData.value = []; // 初始化聊天数据
|
chatData.value = []; // 初始化聊天数据
|
||||||
previousText.value = '';
|
|
||||||
enableInput()
|
enableInput()
|
||||||
activelyClose.value = false;
|
activelyClose.value = false;
|
||||||
|
|
||||||
@ -686,7 +628,7 @@ const connect = function (chat_id, role_id) {
|
|||||||
} else if (data.type === 'end') { // 消息接收完毕
|
} else if (data.type === 'end') { // 消息接收完毕
|
||||||
// 追加当前会话到会话列表
|
// 追加当前会话到会话列表
|
||||||
if (isNewChat && newChatItem.value !== null) {
|
if (isNewChat && newChatItem.value !== null) {
|
||||||
newChatItem.value['title'] = previousText.value;
|
newChatItem.value['title'] = tmpChatTitle.value;
|
||||||
newChatItem.value['chat_id'] = chat_id;
|
newChatItem.value['chat_id'] = chat_id;
|
||||||
chatList.value.unshift(newChatItem.value);
|
chatList.value.unshift(newChatItem.value);
|
||||||
activeChat.value = newChatItem.value;
|
activeChat.value = newChatItem.value;
|
||||||
@ -751,13 +693,11 @@ const connect = function (chat_id, role_id) {
|
|||||||
|
|
||||||
const disableInput = (force) => {
|
const disableInput = (force) => {
|
||||||
canSend.value = false;
|
canSend.value = false;
|
||||||
showReGenerate.value = false;
|
|
||||||
showStopGenerate.value = !force;
|
showStopGenerate.value = !force;
|
||||||
}
|
}
|
||||||
|
|
||||||
const enableInput = () => {
|
const enableInput = () => {
|
||||||
canSend.value = true;
|
canSend.value = true;
|
||||||
showReGenerate.value = previousText.value !== "";
|
|
||||||
showStopGenerate.value = false;
|
showStopGenerate.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -782,7 +722,7 @@ const autofillPrompt = (text) => {
|
|||||||
// 发送消息
|
// 发送消息
|
||||||
const sendMessage = function () {
|
const sendMessage = function () {
|
||||||
if (!isLogin.value) {
|
if (!isLogin.value) {
|
||||||
showLoginDialog.value = true
|
store.setShowLoginDialog(true)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -800,7 +740,7 @@ const sendMessage = function () {
|
|||||||
id: randString(32),
|
id: randString(32),
|
||||||
icon: loginUser.value.avatar,
|
icon: loginUser.value.avatar,
|
||||||
content: md.render(escapeHTML(processContent(prompt.value))),
|
content: md.render(escapeHTML(processContent(prompt.value))),
|
||||||
created_at: new Date().getTime(),
|
created_at: new Date().getTime() / 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@ -810,15 +750,11 @@ const sendMessage = function () {
|
|||||||
showHello.value = false
|
showHello.value = false
|
||||||
disableInput(false)
|
disableInput(false)
|
||||||
socket.value.send(JSON.stringify({type: "chat", content: prompt.value}));
|
socket.value.send(JSON.stringify({type: "chat", content: prompt.value}));
|
||||||
previousText.value = prompt.value;
|
tmpChatTitle.value = prompt.value
|
||||||
prompt.value = '';
|
prompt.value = '';
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const showConfig = function () {
|
|
||||||
showConfigDialog.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearAllChats = function () {
|
const clearAllChats = function () {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
'确认要清空所有的会话历史记录吗?<br/>此操作不可以撤销!',
|
'确认要清空所有的会话历史记录吗?<br/>此操作不可以撤销!',
|
||||||
@ -866,6 +802,9 @@ const loadChatHistory = function (chatId) {
|
|||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
data[i].orgContent = data[i].content;
|
data[i].orgContent = data[i].content;
|
||||||
data[i].content = md.render(processContent(data[i].content))
|
data[i].content = md.render(processContent(data[i].content))
|
||||||
|
if (i > 0 && data[i].type === 'reply') {
|
||||||
|
data[i].prompt = data[i - 1].orgContent
|
||||||
|
}
|
||||||
chatData.value.push(data[i]);
|
chatData.value.push(data[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -887,9 +826,9 @@ const stopGenerate = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 重新生成
|
// 重新生成
|
||||||
const reGenerate = function () {
|
const reGenerate = function (prompt) {
|
||||||
disableInput(false)
|
disableInput(false)
|
||||||
const text = '重新生成上述问题的答案:' + previousText.value;
|
const text = '重新生成下面问题的答案:' + prompt;
|
||||||
// 追加消息
|
// 追加消息
|
||||||
chatData.value.push({
|
chatData.value.push({
|
||||||
type: "prompt",
|
type: "prompt",
|
||||||
@ -897,7 +836,7 @@ const reGenerate = function () {
|
|||||||
icon: loginUser.value.avatar,
|
icon: loginUser.value.avatar,
|
||||||
content: md.render(text)
|
content: md.render(text)
|
||||||
});
|
});
|
||||||
socket.value.send(JSON.stringify({type: "chat", content: previousText.value}));
|
socket.value.send(JSON.stringify({type: "chat", content: prompt}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const chatName = ref('')
|
const chatName = ref('')
|
||||||
@ -961,4 +900,37 @@ const insertURL = (url) => {
|
|||||||
padding 0 20px
|
padding 0 20px
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
.el-textarea {
|
||||||
|
.el-textarea__inner {
|
||||||
|
padding-right 40px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-config {
|
||||||
|
display flex
|
||||||
|
flex-direction row
|
||||||
|
padding-top 10px;
|
||||||
|
|
||||||
|
.role-select-label {
|
||||||
|
color #ffffff
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-select {
|
||||||
|
max-width 150px;
|
||||||
|
margin-right 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-select {
|
||||||
|
max-width 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
.el-icon {
|
||||||
|
margin-right 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -11,13 +11,34 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="navbar">
|
<div class="navbar">
|
||||||
|
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="部署文档"
|
||||||
|
placement="bottom">
|
||||||
|
<a href="https://ai.r9it.com/docs/install/" class="link-button" target="_blank">
|
||||||
|
<i class="iconfont icon-book"></i>
|
||||||
|
</a>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="项目源码"
|
||||||
|
placement="bottom">
|
||||||
|
<a href="https://github.com/yangjian102621/chatgpt-plus" class="link-button" target="_blank">
|
||||||
|
<i class="iconfont icon-github"></i>
|
||||||
|
</a>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
<el-dropdown :hide-on-click="true" class="user-info" trigger="click" v-if="loginUser.id">
|
<el-dropdown :hide-on-click="true" class="user-info" trigger="click" v-if="loginUser.id">
|
||||||
<span class="el-dropdown-link">
|
<span class="el-dropdown-link">
|
||||||
<el-image :src="loginUser.avatar"/>
|
<el-image :src="loginUser.avatar"/>
|
||||||
</span>
|
</span>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu class="user-info-menu">
|
||||||
<el-dropdown-item>
|
<el-dropdown-item @click="showConfigDialog = true">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<UserFilled/>
|
<UserFilled/>
|
||||||
</el-icon>
|
</el-icon>
|
||||||
@ -26,22 +47,18 @@
|
|||||||
|
|
||||||
<el-dropdown-item>
|
<el-dropdown-item>
|
||||||
<i class="iconfont icon-book"></i>
|
<i class="iconfont icon-book"></i>
|
||||||
<span>
|
<a href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
|
||||||
<el-link type="primary" href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
|
用户手册
|
||||||
用户手册
|
</a>
|
||||||
</el-link>
|
|
||||||
</span>
|
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
|
|
||||||
<el-dropdown-item>
|
<el-dropdown-item>
|
||||||
<i class="iconfont icon-github"></i>
|
<i class="iconfont icon-github"></i>
|
||||||
<span>
|
<a href="https://ai.r9it.com/docs/" target="_blank">
|
||||||
<el-link type="primary" href="https://ai.r9it.com/docs/" target="_blank">
|
Geek-AI {{ version }}
|
||||||
Geek-AI {{ version }}
|
</a>
|
||||||
</el-link>
|
|
||||||
</span>
|
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
|
<el-divider style="margin: 2px 0"/>
|
||||||
<el-dropdown-item @click="logout">
|
<el-dropdown-item @click="logout">
|
||||||
<i class="iconfont icon-logout"></i>
|
<i class="iconfont icon-logout"></i>
|
||||||
<span>退出登录</span>
|
<span>退出登录</span>
|
||||||
@ -49,6 +66,11 @@
|
|||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<el-button size="small" color="#21aa93" @click="show = true" round>登录</el-button>
|
||||||
|
<el-button size="small" @click="router.push('/register')" round>注册</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
@ -92,25 +114,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content" :style="{height: mainWinHeight+'px'}">
|
<div class="content" :style="{height: mainWinHeight+'px'}">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view :key="routerViewKey" v-slot="{ Component }">
|
||||||
<transition name="move" mode="out-in">
|
<transition name="move" mode="out-in">
|
||||||
<component :is="Component"></component>
|
<component :is="Component"></component>
|
||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<login-dialog :show="show" @hide="store.setShowLoginDialog(false)" @success="loginCallback"/>
|
||||||
|
<config-dialog v-if="loginUser.id" :show="showConfigDialog" @hide="showConfigDialog = false"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {onMounted, ref} from "vue";
|
import {onMounted, ref, watch} from "vue";
|
||||||
import {httpGet} from "@/utils/http";
|
import {httpGet} from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {UserFilled} from "@element-plus/icons-vue";
|
import {UserFilled} from "@element-plus/icons-vue";
|
||||||
import {checkSession} from "@/action/session";
|
import {checkSession} from "@/action/session";
|
||||||
import {removeUserToken} from "@/store/session";
|
import {removeUserToken} from "@/store/session";
|
||||||
|
import LoginDialog from "@/components/LoginDialog.vue";
|
||||||
|
import {useSharedStore} from "@/store/sharedata";
|
||||||
|
import ConfigDialog from "@/components/ConfigDialog.vue";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const logo = ref('/images/logo.png');
|
const logo = ref('/images/logo.png');
|
||||||
@ -121,6 +149,14 @@ const title = ref("")
|
|||||||
const mainWinHeight = window.innerHeight - 50
|
const mainWinHeight = window.innerHeight - 50
|
||||||
const loginUser = ref({})
|
const loginUser = ref({})
|
||||||
const version = ref(process.env.VUE_APP_VERSION)
|
const version = ref(process.env.VUE_APP_VERSION)
|
||||||
|
const routerViewKey = ref(0)
|
||||||
|
const showConfigDialog = ref(false)
|
||||||
|
|
||||||
|
const store = useSharedStore();
|
||||||
|
const show = ref(false)
|
||||||
|
watch(() => store.showLoginDialog, (newValue) => {
|
||||||
|
show.value = newValue
|
||||||
|
});
|
||||||
|
|
||||||
if (curPath.value === "/external") {
|
if (curPath.value === "/external") {
|
||||||
curPath.value = router.currentRoute.value.query.url
|
curPath.value = router.currentRoute.value.query.url
|
||||||
@ -154,23 +190,35 @@ onMounted(() => {
|
|||||||
ElMessage.error("获取系统菜单失败:" + e.message)
|
ElMessage.error("获取系统菜单失败:" + e.message)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
checkSession().then(user => {
|
checkSession().then(user => {
|
||||||
loginUser.value = user
|
loginUser.value = user
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
const logout = function () {
|
const logout = function () {
|
||||||
httpGet('/api/user/logout').then(() => {
|
httpGet('/api/user/logout').then(() => {
|
||||||
removeUserToken()
|
removeUserToken()
|
||||||
router.push("/login")
|
store.setShowLoginDialog(true)
|
||||||
|
loginUser.value = {}
|
||||||
|
// 刷新组件
|
||||||
|
routerViewKey.value += 1
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
ElMessage.error('注销失败!');
|
ElMessage.error('注销失败!');
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loginCallback = () => {
|
||||||
|
init()
|
||||||
|
// 刷新组件
|
||||||
|
routerViewKey.value += 1
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '@/assets/iconfont/iconfont.css';
|
|
||||||
@import "@/assets/css/home.styl"
|
@import "@/assets/css/home.styl"
|
||||||
</style>
|
</style>
|
||||||
|
@ -36,8 +36,12 @@
|
|||||||
|
|
||||||
<el-row class="opt" :gutter="20">
|
<el-row class="opt" :gutter="20">
|
||||||
<el-col :span="8"><el-link type="primary" @click="router.push('/register')">注册</el-link></el-col>
|
<el-col :span="8"><el-link type="primary" @click="router.push('/register')">注册</el-link></el-col>
|
||||||
<el-col :span="8"><el-link @click="showResetPass = true">重置密码</el-link></el-col>
|
<el-col :span="8">
|
||||||
<el-col :span="8"><el-link @click="router.push('/')">首页</el-link></el-col>
|
<el-link type="info" @click="showResetPass = true">重置密码</el-link>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-link type="info" @click="router.push('/')">首页</el-link>
|
||||||
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user