feat: optimize chat page data list style, support list style and chat style

This commit is contained in:
RockYang 2024-06-28 15:53:49 +08:00
parent 6998dd7af4
commit a692cf1338
12 changed files with 696 additions and 261 deletions

View File

@ -1,5 +1,5 @@
module.exports = {
presets: [
'@vue.css/cli-plugin-babel/preset'
'@vue/cli-plugin-babel/preset'
]
}

View File

@ -2,27 +2,27 @@
height: 100%;
}
#app .common-layout {
#app .chat-page {
height: 100%;
}
#app .common-layout .el-aside {
#app .chat-page .el-aside {
background-color: #252526;
}
#app .common-layout .el-aside .title-box {
#app .chat-page .el-aside .title-box {
padding: 6px 10px;
display: flex;
color: #fff;
font-size: 20px;
}
#app .common-layout .el-aside .title-box span {
#app .chat-page .el-aside .title-box span {
padding-top: 5px;
padding-left: 10px;
}
#app .common-layout .el-aside .chat-list {
#app .chat-page .el-aside .chat-list {
display: flex;
flex-flow: column;
background-color: #28292a;
@ -30,28 +30,28 @@
border-right: 1px solid #2f3032;
}
#app .common-layout .el-aside .chat-list .search-box {
#app .chat-page .el-aside .chat-list .search-box {
flex-wrap: wrap;
padding: 10px 15px;
}
#app .common-layout .el-aside .chat-list .search-box .el-input__wrapper {
#app .chat-page .el-aside .chat-list .search-box .el-input__wrapper {
background-color: #363535;
box-shadow: none;
}
#app .common-layout .el-aside .chat-list ::-webkit-scrollbar {
#app .chat-page .el-aside .chat-list ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
}
#app .common-layout .el-aside .chat-list .content {
#app .chat-page .el-aside .chat-list .content {
width: 100%;
overflow-y: scroll;
}
#app .common-layout .el-aside .chat-list .content .chat-list-item {
#app .chat-page .el-aside .chat-list .content .chat-list-item {
display: flex;
width: 100%;
justify-content: flex-start;
@ -59,17 +59,17 @@
cursor: pointer;
}
#app .common-layout .el-aside .chat-list .content .chat-list-item:hover {
#app .chat-page .el-aside .chat-list .content .chat-list-item:hover {
background-color: #343540;
}
#app .common-layout .el-aside .chat-list .content .chat-list-item .avatar {
#app .chat-page .el-aside .chat-list .content .chat-list-item .avatar {
width: 28px;
height: 28px;
border-radius: 50%;
}
#app .common-layout .el-aside .chat-list .content .chat-list-item .chat-title-input {
#app .chat-page .el-aside .chat-list .content .chat-list-item .chat-title-input {
font-size: 14px;
margin-top: 4px;
margin-left: 10px;
@ -79,7 +79,7 @@
width: 190px;
}
#app .common-layout .el-aside .chat-list .content .chat-list-item .chat-title {
#app .chat-page .el-aside .chat-list .content .chat-list-item .chat-title {
color: #c1c1c1;
padding: 5px 10px;
max-width: 220px;
@ -89,7 +89,7 @@
text-overflow: ellipsis;
}
#app .common-layout .el-aside .chat-list .content .chat-list-item .btn {
#app .chat-page .el-aside .chat-list .content .chat-list-item .btn {
display: none;
position: absolute;
right: 2px;
@ -97,19 +97,19 @@
color: #fff;
}
#app .common-layout .el-aside .chat-list .content .chat-list-item .btn .el-icon {
#app .chat-page .el-aside .chat-list .content .chat-list-item .btn .el-icon {
margin-right: 8px;
}
#app .common-layout .el-aside .chat-list .content .chat-list-item.active {
#app .chat-page .el-aside .chat-list .content .chat-list-item.active {
background-color: #343540;
}
#app .common-layout .el-aside .chat-list .content .chat-list-item.active .btn {
#app .chat-page .el-aside .chat-list .content .chat-list-item.active .btn {
display: inline;
}
#app .common-layout .el-aside .tool-box {
#app .chat-page .el-aside .tool-box {
display: flex;
justify-content: flex-end;
align-items: center;
@ -117,48 +117,48 @@
border-top: 1px solid #3c3c3c;
}
#app .common-layout .el-aside .tool-box .user-info {
#app .chat-page .el-aside .tool-box .user-info {
width: 100%;
padding-top: 10px;
}
#app .common-layout .el-aside .tool-box .user-info .el-dropdown-link {
#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link {
width: 100%;
cursor: pointer;
display: flex;
}
#app .common-layout .el-aside .tool-box .user-info .el-dropdown-link .el-image {
#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link .el-image {
width: 20px;
height: 20px;
border-radius: 5px;
}
#app .common-layout .el-aside .tool-box .user-info .el-dropdown-link .username {
#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link .username {
display: flex;
line-height: 22px;
width: 230px;
padding-left: 10px;
}
#app .common-layout .el-aside .tool-box .user-info .el-dropdown-link .el-icon {
#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link .el-icon {
color: #ccc;
line-height: 24px;
}
#app .common-layout .el-main {
#app .chat-page .el-main {
overflow: hidden;
--el-main-padding: 0;
margin: 0;
}
#app .common-layout .el-main .chat-head {
#app .chat-page .el-main .chat-head {
width: 100%;
height: 50px;
background-color: #28292a;
}
#app .common-layout .el-main .chat-head .chat-config {
#app .chat-page .el-main .chat-head .chat-config {
display: flex;
flex-direction: row;
align-items: center;
@ -166,54 +166,54 @@
padding-top: 10px;
}
#app .common-layout .el-main .chat-head .chat-config .role-select-label {
#app .chat-page .el-main .chat-head .chat-config .role-select-label {
color: #fff;
}
#app .common-layout .el-main .chat-head .chat-config .el-select {
#app .chat-page .el-main .chat-head .chat-config .el-select {
max-width: 150px;
margin-right: 10px;
}
#app .common-layout .el-main .chat-head .chat-config .role-select {
#app .chat-page .el-main .chat-head .chat-config .role-select {
max-width: 130px;
}
#app .common-layout .el-main .chat-head .chat-config .el-button .el-icon {
#app .chat-page .el-main .chat-head .chat-config .el-button .el-icon {
margin-right: 5px;
}
#app .common-layout .el-main .chat-head .iconfont {
#app .chat-page .el-main .chat-head .iconfont {
margin-right: 5px;
}
#app .common-layout .el-main .chat-head .is-circle {
#app .chat-page .el-main .chat-head .is-circle {
margin-left: 5px;
}
#app .common-layout .el-main .chat-head .is-circle .iconfont {
#app .chat-page .el-main .chat-head .is-circle .iconfont {
margin-right: 0;
}
#app .common-layout .el-main .chat-box {
#app .chat-page .el-main .chat-box {
min-width: 0;
flex: 1;
background-color: #fff;
border-left: 1px solid #4f4f4f;
}
#app .common-layout .el-main .chat-box #container {
#app .chat-page .el-main .chat-box #container {
overflow: hidden;
width: 100%;
}
#app .common-layout .el-main .chat-box #container ::-webkit-scrollbar {
#app .chat-page .el-main .chat-box #container ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
}
#app .common-layout .el-main .chat-box #container .chat-box {
#app .chat-page .el-main .chat-box #container .chat-box {
overflow-y: scroll;
--content-font-size: 16px;
--content-color: #c1c1c1;
@ -221,28 +221,28 @@
padding: 0 0 50px 0;
}
#app .common-layout .el-main .chat-box #container .chat-box .chat-line {
#app .chat-page .el-main .chat-box #container .chat-box .chat-line {
font-size: 14px;
display: flex;
align-items: flex-start;
}
#app .common-layout .el-main .chat-box #container .re-generate {
#app .chat-page .el-main .chat-box #container .re-generate {
position: relative;
display: flex;
justify-content: center;
}
#app .common-layout .el-main .chat-box #container .re-generate .btn-box {
#app .chat-page .el-main .chat-box #container .re-generate .btn-box {
position: absolute;
bottom: 10px;
}
#app .common-layout .el-main .chat-box #container .re-generate .btn-box .el-button .el-icon {
#app .chat-page .el-main .chat-box #container .re-generate .btn-box .el-button .el-icon {
margin-right: 5px;
}
#app .common-layout .el-main .chat-box #container .input-box {
#app .chat-page .el-main .chat-box #container .input-box {
background-color: #fff;
display: flex;
justify-content: center;
@ -251,7 +251,7 @@
padding: 0 15px;
}
#app .common-layout .el-main .chat-box #container .input-box .input-container {
#app .chat-page .el-main .chat-box #container .input-box .input-container {
width: 100%;
margin: 0;
border: none;
@ -261,24 +261,24 @@
position: relative;
}
#app .common-layout .el-main .chat-box #container .input-box .input-container .el-textarea .el-textarea__inner::-webkit-scrollbar {
#app .chat-page .el-main .chat-box #container .input-box .input-container .el-textarea .el-textarea__inner::-webkit-scrollbar {
width: 0;
height: 0;
}
#app .common-layout .el-main .chat-box #container .input-box .input-container .select-file {
#app .chat-page .el-main .chat-box #container .input-box .input-container .select-file {
position: absolute;
right: 48px;
top: 20px;
}
#app .common-layout .el-main .chat-box #container .input-box .input-container .send-btn {
#app .chat-page .el-main .chat-box #container .input-box .input-container .send-btn {
position: absolute;
right: 12px;
top: 20px;
}
#app .common-layout .el-main .chat-box #container .input-box .input-container .send-btn .el-button {
#app .chat-page .el-main .chat-box #container .input-box .input-container .send-btn .el-button {
padding: 8px 5px;
border-radius: 6px;
background: #19c37d;
@ -286,7 +286,7 @@
font-size: 20px;
}
#app .common-layout .el-main .chat-box #container::-webkit-scrollbar {
#app .chat-page .el-main .chat-box #container::-webkit-scrollbar {
width: 0;
height: 0;
}

View File

@ -4,7 +4,7 @@ $borderColor = #4676d0;
height: 100%;
.common-layout {
.chat-page {
height: 100%;
// left side
@ -156,6 +156,20 @@ $borderColor = #4676d0;
max-width 130px;
}
.setting {
padding 5px
border-radius 5px
cursor pointer
.iconfont {
font-size 18px
color #19c37d
}
&:hover {
background #D5FAD3
}
}
.el-button {
.el-icon {
margin-right 5px;

View File

@ -1,11 +1,49 @@
<template>
<div class="chat-line chat-line-prompt">
<div class="chat-line chat-line-prompt-list" v-if="listStyle === 'list'">
<div class="chat-line-inner">
<div class="chat-icon">
<img :src="data.icon" alt="User"/>
</div>
<div class="chat-item">
<div v-if="files.length > 0" class="file-list-box">
<div v-for="file in files">
<div class="image" v-if="isImage(file.ext)">
<el-image :src="file.url" fit="cover"/>
</div>
<div class="item" v-else>
<div class="icon">
<el-image :src="GetFileIcon(file.ext)" fit="cover" />
</div>
<div class="body">
<div class="title">
<el-link :href="file.url" target="_blank" style="--el-font-weight-primary:bold">{{file.name}}</el-link>
</div>
<div class="info">
<span>{{GetFileType(file.ext)}}</span>
<span>{{FormatFileSize(file.size)}}</span>
</div>
</div>
</div>
</div>
</div>
<div class="content" v-html="content"></div>
<div class="bar" v-if="data.created_at > 0">
<span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
<span class="bar-item">tokens: {{ finalTokens }}</span>
</div>
</div>
</div>
</div>
<div class="chat-line chat-line-prompt-chat" v-else>
<div class="chat-line-inner">
<div class="chat-icon">
<img :src="icon" alt="User"/>
<img :src="data.icon" alt="User"/>
</div>
<div class="chat-item">
<div v-if="files.length > 0" class="file-list-box">
<div v-for="file in files">
<div class="image" v-if="isImage(file.ext)">
@ -27,14 +65,15 @@
</div>
</div>
</div>
<div class="content" v-html="content"></div>
<div class="bar" v-if="createdAt">
<span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
<!-- <span class="bar-item">Tokens: {{ finalTokens }}</span>-->
<div class="content-wrapper">
<div class="content" v-html="content"></div>
</div>
<div class="bar" v-if="data.created_at > 0">
<span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
<span class="bar-item">tokens: {{ finalTokens }}</span>
</div>
</div>
</div>
</div>
</template>
@ -43,7 +82,7 @@ import {onMounted, ref} from "vue"
import {Clock} from "@element-plus/icons-vue";
import {httpPost} from "@/utils/http";
import hl from "highlight.js";
import {isImage, processPrompt, substr} from "@/utils/libs";
import {dateFormat, isImage, processPrompt} from "@/utils/libs";
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
const mathjaxPlugin = require('markdown-it-mathjax3')
@ -73,41 +112,35 @@ const md = require('markdown-it')({
});
md.use(mathjaxPlugin)
const props = defineProps({
content: {
type: String,
default: '',
data: {
type: Object,
default: {
content: '',
created_at: '',
tokens: 0,
model: '',
icon: '',
},
},
icon: {
listStyle: {
type: String,
default: 'images/user-icon.png',
},
createdAt: {
type: String,
default: '',
},
tokens: {
type: Number,
default: 0,
},
model: {
type: String,
default: '',
default: 'list',
},
})
const finalTokens = ref(props.tokens)
const content =ref(processPrompt(props.content))
const finalTokens = ref(props.data.tokens)
const content =ref(processPrompt(props.data.content))
const files = ref([])
onMounted(() => {
if (!finalTokens.value) {
httpPost("/api/chat/tokens", {text: props.content, model: props.model}).then(res => {
httpPost("/api/chat/tokens", {text: props.data.content, model: props.data.model}).then(res => {
finalTokens.value = res.data;
}).catch(() => {
})
}
const linkRegex = /(https?:\/\/\S+)/g;
const links = props.content.match(linkRegex);
const links = props.data.content.match(linkRegex);
if (links) {
httpPost("/api/upload/list", {urls: links}).then(res => {
files.value = res.data
@ -124,131 +157,263 @@ onMounted(() => {
<style lang="stylus">
@import '@/assets/css/markdown/vue.css';
.chat-line-prompt {
background-color #ffffff;
justify-content: center;
width 100%
padding-bottom: 1.5rem;
padding-top: 1.5rem;
border-bottom: 1px solid #d9d9e3;
.chat-page,.chat-export {
.chat-line-prompt-list {
background-color #ffffff;
justify-content: center;
width 100%
padding-bottom: 1.5rem;
padding-top: 1.5rem;
border-bottom: 1px solid #d9d9e3;
.chat-line-inner {
display flex;
width 100%;
max-width 900px;
padding-left 10px;
.chat-line-inner {
display flex;
width 100%;
max-width 900px;
padding-left 10px;
.chat-icon {
margin-right 20px;
.chat-icon {
margin-right 20px;
img {
width: 36px;
height: 36px;
border-radius: 10px;
padding: 1px;
}
}
.chat-item {
width 100%
padding: 0 5px 0 0;
overflow: hidden;
.file-list-box {
display flex
flex-flow column
.image {
display flex
flex-flow row
margin-right 10px
position relative
.el-image {
border 1px solid #e3e3e3
border-radius 10px
margin-bottom 10px
}
img {
width: 36px;
height: 36px;
border-radius: 10px;
padding: 1px;
}
.item {
display flex
flex-flow row
border-radius 10px
background-color #ffffff
border 1px solid #e3e3e3
padding 6px
margin-bottom 10px
}
.chat-item {
width 100%
padding: 0 5px 0 0;
overflow: hidden;
.file-list-box {
display flex
flex-flow column
.image {
display flex
flex-flow row
margin-right 10px
position relative
.icon {
.el-image {
width 40px
height 40px
border 1px solid #e3e3e3
border-radius 10px
margin-bottom 10px
}
}
.body {
margin-left 8px
font-size 14px
.title {
font-weight bold
line-height 24px
color #0D0D0D
}
.info {
color #B4B4B4
.item {
display flex
flex-flow row
border-radius 10px
background-color #ffffff
border 1px solid #e3e3e3
padding 6px
margin-bottom 10px
span {
margin-right 10px
.icon {
.el-image {
width 40px
height 40px
}
}
.body {
margin-left 8px
font-size 14px
.title {
font-weight bold
line-height 24px
color #0D0D0D
}
.info {
color #B4B4B4
span {
margin-right 10px
}
}
}
}
}
}
.content {
word-break break-word;
padding: 6px 10px;
color #374151;
font-size: var(--content-font-size);
border-radius: 5px;
overflow: auto;
.content {
word-break break-word;
padding: 0;
color #374151;
font-size: var(--content-font-size);
border-radius: 5px;
overflow: auto;
img {
max-width: 600px;
border-radius: 10px;
margin 10px 0
img {
max-width: 600px;
border-radius: 10px;
margin 10px 0
}
p {
line-height 1.5
}
p:last-child {
margin-bottom: 0
}
p:first-child {
margin-top 0
}
}
p {
line-height 1.5
}
.bar {
padding 10px 10px 10px 0;
p:last-child {
margin-bottom: 0
}
.bar-item {
background-color #f7f7f8;
color #888
padding 3px 5px;
margin-right 10px;
border-radius 5px;
p:first-child {
margin-top 0
}
}
.bar {
padding 10px;
.bar-item {
background-color #f7f7f8;
color #888
padding 3px 5px;
margin-right 10px;
border-radius 5px;
.el-icon {
position relative
top 2px;
.el-icon {
position relative
top 2px;
}
}
}
}
}
}
.chat-line-prompt-chat {
background-color #ffffff;
justify-content: center;
width 100%
padding-bottom: 1.5rem;
padding-top: 1.5rem;
.chat-line-inner {
display flex;
width 100%;
padding 0 25px;
.chat-icon {
margin-right 20px;
img {
width: 36px;
height: 36px;
border-radius: 50%;
padding: 1px;
}
}
.chat-item {
padding: 0;
overflow: hidden;
.file-list-box {
display flex
flex-flow column
.image {
display flex
flex-flow row
margin-right 10px
position relative
.el-image {
border 1px solid #e3e3e3
border-radius 10px
margin-bottom 10px
}
}
.item {
display flex
flex-flow row
border-radius 10px
background-color #ffffff
border 1px solid #e3e3e3
padding 6px
margin-bottom 10px
.icon {
.el-image {
width 40px
height 40px
}
}
.body {
margin-left 8px
font-size 14px
.title {
font-weight bold
line-height 24px
color #0D0D0D
}
.info {
color #B4B4B4
span {
margin-right 10px
}
}
}
}
}
.content-wrapper {
display flex
.content {
word-break break-word;
padding: 1rem
color #222222;
font-size: var(--content-font-size);
overflow: auto;
background-color #98e165
border-radius: 0 10px 10px 10px;
img {
max-width: 600px;
border-radius: 10px;
margin 10px 0
}
p {
line-height 1.5
}
p:last-child {
margin-bottom: 0
}
p:first-child {
margin-top 0
}
}
}
.bar {
padding 10px 10px 10px 0;
.bar-item {
color #888
padding 3px 5px;
margin-right 10px;
border-radius 5px;
.el-icon {
position relative
top 2px;
}
}
}
}
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="chat-line chat-line-reply">
<div class="chat-line chat-line-reply-list" v-if="listStyle === 'list'">
<div class="chat-line-inner">
<div class="chat-icon">
<img :src="data.icon" alt="ChatGPT">
@ -9,7 +9,7 @@
<div class="content" v-html="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: {{ tokens }}</span>-->
<span class="bar-item">tokens: {{ data.tokens }}</span>
<span class="bar-item">
<el-tooltip
class="box-item"
@ -61,6 +61,59 @@
</div>
</div>
</div>
<div class="chat-line chat-line-reply-chat" v-else>
<div class="chat-line-inner">
<div class="chat-icon">
<img :src="data.icon" alt="ChatGPT">
</div>
<div class="chat-item">
<div class="content-wrapper">
<div class="content" v-html="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>
<span class="bar-item">tokens: {{ data.tokens }}</span>
<span class="bar-item bg">
<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 bg" @click="reGenerate(data.prompt)">
<el-tooltip
class="box-item"
effect="dark"
content="重新生成"
placement="bottom"
>
<el-icon><Refresh/></el-icon>
</el-tooltip>
</span>
<span class="bar-item bg" @click="synthesis(data.orgContent)">
<el-tooltip
class="box-item"
effect="dark"
content="生成语音朗读"
placement="bottom"
>
<i class="iconfont icon-speaker"></i>
</el-tooltip>
</span>
</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
@ -71,12 +124,22 @@ import {dateFormat} from "@/utils/libs";
const props = defineProps({
data: {
type: Object,
default: {},
default: {
icon: "",
content: "",
created_at: "",
tokens: 0,
orgContent: ""
},
},
readOnly: {
type: Boolean,
default: false
}
},
listStyle: {
type: String,
default: 'list',
},
})
const emits = defineEmits(['regen']);
@ -98,8 +161,8 @@ const reGenerate = (prompt) => {
<style lang="stylus">
@import '@/assets/css/markdown/vue.css';
.common-layout {
.chat-line-reply {
.chat-page,.chat-export {
.chat-line-reply-list {
justify-content: center;
background-color: rgba(247, 247, 248, 1);
width 100%
@ -127,13 +190,13 @@ const reGenerate = (prompt) => {
.chat-item {
width 100%
position: relative;
padding: 0 0 0 5px;
padding: 0;
overflow: hidden;
.content {
min-height 20px;
word-break break-word;
padding: 0 10px;
padding: 0
color #374151;
font-size: var(--content-font-size);
border-radius: 5px;
@ -237,7 +300,7 @@ const reGenerate = (prompt) => {
.bar {
padding 10px;
padding 10px 10px 10px 0;
.bar-item {
background-color #e7e7e8;
@ -273,6 +336,186 @@ const reGenerate = (prompt) => {
}
}
.chat-line-reply-chat {
justify-content: center;
width 100%
padding-bottom: 1.5rem;
padding-top: 1.5rem;
.chat-line-inner {
display flex;
padding 0 25px;
width 100%
flex-flow row-reverse
.chat-icon {
margin-left 20px;
img {
width: 36px;
height: 36px;
border-radius: 50%
padding: 1px;
}
}
.chat-item {
position: relative;
padding: 0;
overflow: hidden;
.content-wrapper {
display flex
flex-flow row-reverse
.content {
min-height 20px;
word-break break-word;
padding: 1rem
color #374151;
font-size: var(--content-font-size);
overflow auto;
background-color #F5F5F5
border-radius: 10px 0 10px 10px;
img {
max-width: 600px;
border-radius: 10px;
}
p {
line-height 1.5
code {
color #374151
background-color #e7e7e8
padding 0 3px;
border-radius 5px;
}
}
p:last-child {
margin-bottom: 0
}
p:first-child {
margin-top 0
}
.code-container {
position relative
display flex
.hljs {
border-radius 10px
width 100%
}
.copy-code-btn {
position: absolute;
right 10px
top 10px
cursor pointer
font-size 12px
color #c1c1c1
&:hover {
color #20a0ff
}
}
}
.lang-name {
position absolute;
right 10px
bottom 20px
padding 2px 6px 4px 6px
background-color #444444
border-radius 10px
color #00e0e0
}
//
table {
width 100%
margin-bottom 1rem
color #212529
border-collapse collapse;
border 1px solid #dee2e6;
background-color #ffffff
thead {
th {
border 1px solid #dee2e6
vertical-align: bottom
border-bottom: 2px solid #dee2e6
padding 10px
}
}
td {
border 1px solid #dee2e6
padding 10px
}
}
//
blockquote {
margin 0
background-color: #ebfffe;
padding: 0.8rem 1.5rem;
border-left: 0.5rem solid;
border-color: #026863;
color: #2c3e50;
}
}
}
.bar {
padding 10px 10px 10px 0;
.bar-item {
color #888
padding 3px 5px;
margin-right 10px;
border-radius 5px;
.el-icon {
position relative
top 2px;
cursor pointer
}
}
.bar-item.bg {
background-color #e7e7e8
cursor pointer
}
.el-button {
height 20px
padding 5px 2px;
}
}
}
.tool-box {
font-size 16px;
.el-button {
height 20px
padding 5px 2px;
}
}
}
}
}
</style>

View File

@ -0,0 +1,50 @@
<template>
<el-dialog
class="config-dialog"
v-model="showDialog"
:close-on-click-modal="true"
:before-close="close"
style="max-width: 600px"
title="聊天配置"
>
<div class="chat-setting">
<el-form :model="data" label-width="100px" label-position="left">
<el-form-item label="聊天样式:">
<el-radio-group v-model="data.style" @change="(val) => {store.setChatListStyle(val)}">
<el-radio value="list">列表样式</el-radio>
<el-radio value="chat">对话样式</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</div>
</el-dialog>
</template>
<script setup>
import {computed, ref} from "vue"
import {useSharedStore} from "@/store/sharedata";
const store = useSharedStore();
const data = ref({
style: store.chatListStyle,
})
// eslint-disable-next-line no-undef
const props = defineProps({
show: Boolean,
});
const showDialog = computed(() => {
return props.show
})
const emits = defineEmits(['hide']);
const close = function () {
emits('hide', false);
}
</script>
<style lang="stylus" scoped>
.chat-setting {
}
</style>

View File

@ -1,13 +1,19 @@
import {defineStore} from 'pinia';
import Storage from 'good-storage'
export const useSharedStore = defineStore('shared', {
state: () => ({
showLoginDialog: false
showLoginDialog: false,
chatListStyle: Storage.get("chat_list_style","chat")
}),
getters: {},
actions: {
setShowLoginDialog(value) {
this.showLoginDialog = value;
},
setChatListStyle(value) {
this.chatListStyle = value;
Storage.set("chat_list_style", value);
}
}
});

View File

@ -6,22 +6,14 @@
</div>
<div v-for="item in chatData" :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"/>
<chat-reply v-else-if="item.type==='reply'"
:data="item" :read-only="true"/>
<chat-prompt v-if="item.type==='prompt'" :data="item" list-style="list"/>
<chat-reply v-else-if="item.type==='reply'" :data="item" :read-only="true" list-style="list"/>
</div>
</div><!-- end chat box -->
</div>
</template>
<script setup>
import {dateFormat} from "@/utils/libs";
import ChatReply from "@/components/ChatReply.vue";
import ChatPrompt from "@/components/ChatPrompt.vue";
import {nextTick, onMounted, ref} from "vue";
@ -98,7 +90,7 @@ onMounted(() => {
padding 0 20px
.chat-box {
width 800px;
width 100%;
//
--content-font-size: 16px;
--content-color: #c1c1c1;
@ -110,57 +102,13 @@ onMounted(() => {
text-align center
}
.chat-line-prompt {
.chat-line {
font-size: 14px;
display: flex;
align-items: flex-start;
align-items: center;
.chat-line-inner {
.chat-icon {
margin-right: 0
}
.content {
padding-top: 0
font-size 16px;
p:first-child {
margin-top 0
}
}
}
}
.chat-line-reply {
padding-top: 1.5rem;
.chat-line-inner {
display flex
.bar-item {
background-color: #f7f7f8;
color: #888;
padding: 3px 5px;
margin-right: 10px;
border-radius: 5px;
}
.chat-icon {
margin-right: 20px
img {
width 30px
height 30px
border-radius: 10px;
padding: 1px
}
}
.chat-item {
img {
max-width 90%
}
}
max-width 800px
}
}
}

View File

@ -1,5 +1,5 @@
<template>
<div class="common-layout">
<div class="chat-page">
<el-container>
<el-aside>
<div class="chat-list">
@ -99,6 +99,12 @@
</el-tag>
</el-option>
</el-select>
<span class="setting" @click="showChatSetting = true">
<el-tooltip class="box-item" effect="dark" content="对话设置">
<i class="iconfont icon-config"></i>
</el-tooltip>
</span>
</div>
<div>
@ -109,13 +115,8 @@
</div>
<div v-for="item in chatData" :key="item.id" v-else>
<chat-prompt
v-if="item.type==='prompt'"
:icon="item.icon"
:created-at="dateFormat(item['created_at'])"
:tokens="item['tokens']"
:model="getModelValue(modelID)"
:content="item.content"/>
<chat-reply v-else-if="item.type==='reply'" :data="item" @regen="reGenerate" :read-only="false"/>
v-if="item.type==='prompt'" :data="item" :list-style="listStyle"/>
<chat-reply v-else-if="item.type==='reply'" :data="item" @regen="reGenerate" :read-only="false" :list-style="listStyle"/>
</div>
</div><!-- end chat box -->
@ -187,21 +188,21 @@
</p>
</div>
</el-dialog>
<ChatSetting :show="showChatSetting" @hide="showChatSetting = false"/>
</div>
</template>
<script setup>
import {nextTick, onMounted, onUnmounted, ref} from 'vue'
import {nextTick, onMounted, onUnmounted, ref, watch} from 'vue'
import ChatPrompt from "@/components/ChatPrompt.vue";
import ChatReply from "@/components/ChatReply.vue";
import {Delete, Edit, More, Plus, Promotion, Search, Share, VideoPause} from '@element-plus/icons-vue'
import 'highlight.js/styles/a11y-dark.css'
import {
dateFormat,
isMobile,
processContent,
processPrompt,
randString,
removeArrayItem,
UUID
@ -217,6 +218,7 @@ 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";
const title = ref('ChatGPT-智能助手');
const models = ref([])
@ -243,6 +245,12 @@ const notice = ref("")
const noticeKey = ref("SYSTEM_NOTICE")
const store = useSharedStore();
const row = ref(1)
const showChatSetting = ref(false)
const listStyle = ref(store.chatListStyle)
watch(() => store.chatListStyle, (newValue) => {
listStyle.value = newValue
});
if (isMobile()) {
router.replace("/mobile/chat")
@ -757,6 +765,7 @@ const sendMessage = function () {
id: randString(32),
icon: loginUser.value.avatar,
content: content,
model: getModelValue(modelID.value),
created_at: new Date().getTime() / 1000,
});

View File

@ -142,7 +142,7 @@ import {checkSession} from "@/action/session";
import {removeUserToken} from "@/store/session";
import LoginDialog from "@/components/LoginDialog.vue";
import {useSharedStore} from "@/store/sharedata";
import ConfigDialog from "@/components/ConfigDialog.vue";
import ConfigDialog from "@/components/UserInfoDialog.vue";
import {showMessageError} from "@/utils/dialog";
const router = useRouter();

View File

@ -153,7 +153,7 @@
v-model="showChatItemDialog"
title="对话详情"
>
<div class="chat-box common-layout">
<div class="chat-box chat-page">
<div v-for="item in messages" :key="item.id">
<chat-prompt
v-if="item.type==='prompt'"