mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-11-12 12:13:46 +08:00
refactor: V3 版本重构已基本完成
This commit is contained in:
@@ -1,21 +1,32 @@
|
||||
<template>
|
||||
<div class="chat-line chat-line-right">
|
||||
<div class="chat-item">
|
||||
<div class="content" v-html="content"></div>
|
||||
<div class="triangle"></div>
|
||||
<div class="chat-line chat-line-prompt">
|
||||
<div class="chat-line-inner">
|
||||
<div class="chat-icon">
|
||||
<img :src="icon" alt="User"/>
|
||||
</div>
|
||||
|
||||
<div class="chat-item">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-icon">
|
||||
<img :src="icon" alt="User"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {defineComponent} from "vue"
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import {Clock} from "@element-plus/icons-vue";
|
||||
import {httpGet} from "@/utils/http";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ChatPrompt',
|
||||
components: {Clock},
|
||||
methods: {dateFormat},
|
||||
props: {
|
||||
content: {
|
||||
type: String,
|
||||
@@ -24,51 +35,106 @@ export default defineComponent({
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'images/user-icon.png',
|
||||
}
|
||||
},
|
||||
createdAt: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
tokens: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
model: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
return {
|
||||
finalTokens: this.tokens
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!this.finalTokens) {
|
||||
httpGet(`/api/chat/tokens?text=${this.content}&model=${this.model}`).then(res => {
|
||||
this.finalTokens = res.data;
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.chat-line-right {
|
||||
justify-content: flex-end;
|
||||
<style lang="stylus" scoped>
|
||||
.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-icon {
|
||||
margin-left 5px;
|
||||
.chat-line-inner {
|
||||
display flex;
|
||||
width 100%;
|
||||
max-width 900px;
|
||||
padding-left 10px;
|
||||
|
||||
img {
|
||||
border-radius 5px;
|
||||
.chat-icon {
|
||||
margin-right 20px;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 10px;
|
||||
padding: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-item {
|
||||
position: relative;
|
||||
padding: 0 5px 0 0;
|
||||
overflow: hidden;
|
||||
|
||||
.content {
|
||||
word-break break-word;
|
||||
padding: 6px 10px;
|
||||
color #374151;
|
||||
font-size: var(--content-font-size);
|
||||
border-radius: 5px;
|
||||
overflow: auto;
|
||||
|
||||
p {
|
||||
line-height 1.5
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-item {
|
||||
position: relative;
|
||||
padding: 0 5px 0 0;
|
||||
overflow: hidden;
|
||||
|
||||
.triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 5px solid transparent;
|
||||
border-bottom: 5px solid transparent;
|
||||
border-left: 5px solid #98E165;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.content {
|
||||
word-break break-word;
|
||||
padding: 6px 10px;
|
||||
background-color: #98E165;
|
||||
color var(--content-color);
|
||||
font-size: var(--content-font-size);
|
||||
border-radius: 5px;
|
||||
line-height 1.5
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,21 +1,22 @@
|
||||
<template>
|
||||
<div class="chat-line chat-line-left">
|
||||
<div class="chat-icon">
|
||||
<img :src="icon" alt="ChatGPT">
|
||||
</div>
|
||||
<div class="chat-line chat-line-reply">
|
||||
<div class="chat-line-inner">
|
||||
<div class="chat-icon">
|
||||
<img :src="icon" alt="ChatGPT">
|
||||
</div>
|
||||
|
||||
<div class="chat-item">
|
||||
<div class="triangle"></div>
|
||||
<div class="content-box">
|
||||
<div class="chat-item">
|
||||
<div class="content" v-html="content"></div>
|
||||
<div class="tool-box">
|
||||
<div class="bar" v-if="createdAt !== ''">
|
||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
|
||||
<span class="bar-item">tokens: {{ tokens }}</span>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
effect="light"
|
||||
content="复制回答"
|
||||
placement="bottom"
|
||||
placement="top"
|
||||
>
|
||||
<el-button type="info" class="copy-reply" :data-clipboard-text="orgContent" plain>
|
||||
<el-button type="info" class="copy-reply" :data-clipboard-text="orgContent">
|
||||
<el-icon>
|
||||
<DocumentCopy/>
|
||||
</el-icon>
|
||||
@@ -30,12 +31,11 @@
|
||||
|
||||
<script>
|
||||
import {defineComponent} from "vue"
|
||||
import {randString} from "@/utils/libs";
|
||||
import {DocumentCopy} from "@element-plus/icons-vue";
|
||||
import {Clock, DocumentCopy} from "@element-plus/icons-vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ChatReply',
|
||||
components: {DocumentCopy},
|
||||
components: {Clock, DocumentCopy},
|
||||
props: {
|
||||
content: {
|
||||
type: String,
|
||||
@@ -45,6 +45,14 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
createdAt: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
tokens: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'images/gpt-icon.png',
|
||||
@@ -52,73 +60,99 @@ export default defineComponent({
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: randString(32),
|
||||
clipboard: null,
|
||||
finalTokens: this.tokens
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.chat-line-left {
|
||||
justify-content: flex-start;
|
||||
.common-layout {
|
||||
.chat-line-reply {
|
||||
justify-content: center;
|
||||
background-color: rgba(247, 247, 248, 1);
|
||||
width 100%
|
||||
padding-bottom: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
border-bottom: 1px solid #d9d9e3;
|
||||
|
||||
.chat-icon {
|
||||
margin-right 5px;
|
||||
.chat-line-inner {
|
||||
display flex;
|
||||
width 100%;
|
||||
max-width 900px;
|
||||
padding-left 10px;
|
||||
|
||||
img {
|
||||
border-radius 5px;
|
||||
}
|
||||
}
|
||||
.chat-icon {
|
||||
margin-right 20px;
|
||||
|
||||
.chat-item {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 0 0 0 5px;
|
||||
overflow: hidden;
|
||||
|
||||
.triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 5px solid transparent;
|
||||
border-bottom: 5px solid transparent;
|
||||
border-right: 5px solid #fff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 13px;
|
||||
}
|
||||
|
||||
.content-box {
|
||||
|
||||
display flex
|
||||
flex-direction row
|
||||
|
||||
.content {
|
||||
min-height 20px;
|
||||
word-break break-word;
|
||||
padding: 6px 10px;
|
||||
color var(--content-color)
|
||||
background-color: #fff;
|
||||
font-size: var(--content-font-size);
|
||||
border-radius: 5px;
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0
|
||||
}
|
||||
|
||||
p:first-child {
|
||||
margin-top 0
|
||||
}
|
||||
|
||||
p > code {
|
||||
color #cc0000
|
||||
background-color #f1f1f1
|
||||
img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 10px;
|
||||
padding: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-item {
|
||||
position: relative;
|
||||
padding: 0 0 0 5px;
|
||||
overflow: hidden;
|
||||
|
||||
.content {
|
||||
min-height 20px;
|
||||
word-break break-word;
|
||||
padding: 6px 10px;
|
||||
color #374151;
|
||||
font-size: var(--content-font-size);
|
||||
border-radius: 5px;
|
||||
overflow auto;
|
||||
|
||||
p {
|
||||
line-height 1.5
|
||||
|
||||
code {
|
||||
color #f1f1f1
|
||||
background-color #202121
|
||||
padding 0 3px;
|
||||
border-radius 5px;
|
||||
}
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0
|
||||
}
|
||||
|
||||
p:first-child {
|
||||
margin-top 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.bar {
|
||||
padding 10px;
|
||||
|
||||
.bar-item {
|
||||
background-color #e7e7e8;
|
||||
color #888
|
||||
padding 3px 5px;
|
||||
margin-right 10px;
|
||||
border-radius 5px;
|
||||
|
||||
.el-icon {
|
||||
position relative
|
||||
top 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
height 20px
|
||||
padding 5px 2px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.tool-box {
|
||||
padding-left 10px;
|
||||
font-size 16px;
|
||||
|
||||
.el-button {
|
||||
|
||||
@@ -1,111 +1,57 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="$props.show"
|
||||
v-model="props.show"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="true"
|
||||
:before-close="close"
|
||||
:top="top"
|
||||
title="聊天配置"
|
||||
title="用户设置"
|
||||
>
|
||||
<div class="user-info">
|
||||
<el-input :value="user['api_key']" placeholder="填写你 OpenAI 的 API KEY">
|
||||
<template #prepend>API KEY</template>
|
||||
</el-input>
|
||||
<div class="user-info" id="user-info">
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="昵称">
|
||||
<el-input v-model="form['nickname']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="头像">
|
||||
<el-input v-model="form['avatar']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="form['username']" disabled/>
|
||||
</el-form-item>
|
||||
|
||||
<el-descriptions
|
||||
class="margin-top"
|
||||
title="账户信息"
|
||||
:column="col"
|
||||
border
|
||||
>
|
||||
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
<el-icon>
|
||||
<UserFilled/>
|
||||
</el-icon>
|
||||
账户
|
||||
</div>
|
||||
</template>
|
||||
{{ user.name }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
<el-icon>
|
||||
<List/>
|
||||
</el-icon>
|
||||
聊天记录
|
||||
</div>
|
||||
</template>
|
||||
<el-tag v-if="user['enable_history']" type="success">已开通</el-tag>
|
||||
<el-tag v-else type="info">未开通</el-tag>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
<el-icon>
|
||||
<Histogram/>
|
||||
</el-icon>
|
||||
总调用次数
|
||||
</div>
|
||||
</template>
|
||||
{{ user['max_calls'] }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
<el-icon>
|
||||
<Histogram/>
|
||||
</el-icon>
|
||||
剩余点数
|
||||
</div>
|
||||
</template>
|
||||
{{ user["remaining_calls"] }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
<el-icon>
|
||||
<Timer/>
|
||||
</el-icon>
|
||||
激活时间
|
||||
</div>
|
||||
</template>
|
||||
{{ user['active_time'] }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
<el-icon>
|
||||
<Watch/>
|
||||
</el-icon>
|
||||
到期时间
|
||||
</div>
|
||||
</template>
|
||||
{{ user['expired_time'] }}
|
||||
</el-descriptions-item>
|
||||
|
||||
</el-descriptions>
|
||||
<el-form-item label="聊天上下文">
|
||||
<el-switch v-model="form['chat_config']['enable_context']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="聊天记录">
|
||||
<el-switch v-model="form['chat_config']['enable_history']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Model">
|
||||
<el-select v-model="form['chat_config']['model']" placeholder="默认会话模型">
|
||||
<el-option
|
||||
v-for="item in props.models"
|
||||
:key="item"
|
||||
:label="item.toUpperCase()"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="MaxTokens">
|
||||
<el-input v-model.number="form['chat_config']['max_tokens']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Temperature">
|
||||
<el-input v-model.number="form['chat_config']['temperature']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="剩余调用次数">
|
||||
<el-tag>{{ form['calls'] }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="剩余 Tokens">
|
||||
<el-tag type="info">{{ form['tokens'] }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="API KEY">
|
||||
<el-input v-model="form['chat_config']['api_key']"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<el-row class="row-center">
|
||||
<span>其他功能正在开发中,有什么使用建议可以通过下面的方式联系作者。</span>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-image :src="wechatGroup"></el-image>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-image :src="wechatCard"></el-image>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -118,61 +64,54 @@
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {defineComponent} from "vue"
|
||||
import {
|
||||
List, Timer, Watch,
|
||||
UserFilled,
|
||||
Histogram
|
||||
} from '@element-plus/icons-vue'
|
||||
import {isMobile} from "@/utils/libs";
|
||||
<script setup>
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ConfigDialog',
|
||||
components: {Watch, Timer, UserFilled, List, Histogram},
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
user: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
wechatGroup: "https://img.r9it.com/chatgpt/wechat-group.jpeg",
|
||||
wechatCard: "https://img.r9it.com/chatgpt/wechat-card.jpeg"
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
top: function () {
|
||||
if (window.innerHeight < 1000) {
|
||||
return '1vh';
|
||||
} else {
|
||||
return '15vh';
|
||||
}
|
||||
},
|
||||
|
||||
col: function () {
|
||||
return isMobile() ? 1 : 2;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
save: function () {
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
close: function () {
|
||||
this.$emit('update:show', false);
|
||||
}
|
||||
import {computed, defineEmits, defineProps, onMounted, ref} from "vue"
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
models: Array,
|
||||
});
|
||||
|
||||
const form = ref({})
|
||||
const top = computed(() => {
|
||||
if (window.innerHeight < 768) {
|
||||
return '1vh';
|
||||
} else {
|
||||
return '15vh';
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 获取最新用户信息
|
||||
httpGet('/api/user/profile').then(res => {
|
||||
form.value = res.data
|
||||
}).catch(() => {
|
||||
ElMessage.error('获取用户信息失败')
|
||||
});
|
||||
})
|
||||
|
||||
const emits = defineEmits(['update:show']);
|
||||
const save = function () {
|
||||
httpPost('/api/user/profile/update', form.value).then(() => {
|
||||
ElMessage.success({
|
||||
message: '更新成功',
|
||||
appendTo: document.getElementById('user-info'),
|
||||
onClose: () => emits('update:show', false)
|
||||
})
|
||||
}).catch(() => {
|
||||
ElMessage.error({
|
||||
message: '更新失败',
|
||||
appendTo: document.getElementById('user-info')
|
||||
})
|
||||
})
|
||||
}
|
||||
const close = function () {
|
||||
emits('update:show', false);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
@@ -182,17 +121,15 @@ export default defineComponent({
|
||||
|
||||
.el-dialog__body {
|
||||
padding-top 10px;
|
||||
max-height 600px;
|
||||
overflow-y auto;
|
||||
|
||||
.user-info {
|
||||
.margin-top {
|
||||
margin-top 20px;
|
||||
}
|
||||
position relative;
|
||||
|
||||
.el-icon {
|
||||
top 2px;
|
||||
.el-message {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
margin-bottom 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,9 +52,9 @@ export default defineComponent({
|
||||
.triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid transparent;
|
||||
border-left: 10px solid #223A34;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid transparent;
|
||||
border-left: 6px solid #223A34;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 10px;
|
||||
@@ -62,11 +62,12 @@ export default defineComponent({
|
||||
|
||||
.content {
|
||||
word-break break-word;
|
||||
padding: 12px 15px;
|
||||
padding: 6px 10px;
|
||||
background-color: #223A34;
|
||||
color var(--content-color);
|
||||
font-size: var(--content-font-size);
|
||||
border-radius: 5px;
|
||||
overflow: auto;
|
||||
|
||||
p {
|
||||
line-height 1.5
|
||||
@@ -55,7 +55,7 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.body-plus {
|
||||
.common-layout {
|
||||
.chat-line-left {
|
||||
justify-content: flex-start;
|
||||
|
||||
@@ -76,12 +76,12 @@ export default defineComponent({
|
||||
.triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid transparent;
|
||||
border-right: 10px solid #404042;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid transparent;
|
||||
border-right: 6px solid #404042;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 13px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.content-box {
|
||||
@@ -92,11 +92,12 @@ export default defineComponent({
|
||||
.content {
|
||||
min-height 20px;
|
||||
word-break break-word;
|
||||
padding: 12px 15px;
|
||||
padding: 6px 10px;
|
||||
color var(--content-color)
|
||||
background-color: #404042;
|
||||
font-size: var(--content-font-size);
|
||||
border-radius: 5px;
|
||||
overflow auto;
|
||||
|
||||
p {
|
||||
line-height 1.5
|
||||
Reference in New Issue
Block a user