feat: 增加带格式的复制 (#182)
* feat: 增加带格式的复制 * feat: 移除前端超时设定 * chore: update deps * feat: 添加权限页面 * feat: 设定页面优化 * feat: 更新 chatgpt 以支持 `gpt-3.5-turbo-0301` * chore: version 2.9.0
This commit is contained in:
@@ -1 +0,0 @@
|
||||
export { }
|
||||
62
src/components/common/Setting/About.vue
Normal file
62
src/components/common/Setting/About.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<script setup lang='ts'>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { NSpin } from 'naive-ui'
|
||||
import { fetchChatConfig } from '@/api'
|
||||
import pkg from '@/../package.json'
|
||||
|
||||
interface ConfigState {
|
||||
timeoutMs?: number
|
||||
reverseProxy?: string
|
||||
apiModel?: string
|
||||
socksProxy?: string
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const config = ref<ConfigState>()
|
||||
|
||||
async function fetchConfig() {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data } = await fetchChatConfig<ConfigState>()
|
||||
config.value = data
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpin :show="loading">
|
||||
<div class="p-4 space-y-4">
|
||||
<h2 class="text-xl font-bold">
|
||||
Version - {{ pkg.version }}
|
||||
</h2>
|
||||
<div class="p-2 space-y-2 rounded-md bg-neutral-100 dark:bg-neutral-700">
|
||||
<p>
|
||||
此项目开源于
|
||||
<a
|
||||
class="text-blue-600 dark:text-blue-500"
|
||||
href="https://github.com/Chanzhaoyu/chatgpt-web"
|
||||
target="_blank"
|
||||
>
|
||||
Github
|
||||
</a>
|
||||
免费,并且没有任何形式分付费行为!
|
||||
</p>
|
||||
<p>
|
||||
如果你觉得此项目对你有帮助,请在 Github 帮我点个 Star 或者给予一点赞助,谢谢!
|
||||
</p>
|
||||
</div>
|
||||
<p>API方式:{{ config?.apiModel ?? '-' }}</p>
|
||||
<p>反向代理:{{ config?.reverseProxy ?? '-' }}</p>
|
||||
<p>超时时间:{{ config?.timeoutMs ?? '-' }}</p>
|
||||
<p>Socks代理:{{ config?.socksProxy ?? '-' }}</p>
|
||||
</div>
|
||||
</NSpin>
|
||||
</template>
|
||||
144
src/components/common/Setting/General.vue
Normal file
144
src/components/common/Setting/General.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { NButton, NInput, useMessage } from 'naive-ui'
|
||||
import type { Language, Theme } from '@/store/modules/app/helper'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
import { useAppStore, useUserStore } from '@/store'
|
||||
import type { UserInfo } from '@/store/modules/user/helper'
|
||||
|
||||
interface Emit {
|
||||
(event: 'update'): void
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const ms = useMessage()
|
||||
|
||||
const theme = computed(() => appStore.theme)
|
||||
|
||||
const userInfo = computed(() => userStore.userInfo)
|
||||
|
||||
const avatar = ref(userInfo.value.avatar ?? '')
|
||||
|
||||
const name = ref(userInfo.value.name ?? '')
|
||||
|
||||
const description = ref(userInfo.value.description ?? '')
|
||||
|
||||
const language = computed({
|
||||
get() {
|
||||
return appStore.language
|
||||
},
|
||||
set(value: Language) {
|
||||
appStore.setLanguage(value)
|
||||
},
|
||||
})
|
||||
|
||||
const themeOptions: { label: string; key: Theme; icon: string }[] = [
|
||||
{
|
||||
label: 'Auto',
|
||||
key: 'auto',
|
||||
icon: 'ri:contrast-line',
|
||||
},
|
||||
{
|
||||
label: 'Light',
|
||||
key: 'light',
|
||||
icon: 'ri:sun-foggy-line',
|
||||
},
|
||||
{
|
||||
label: 'Dark',
|
||||
key: 'dark',
|
||||
icon: 'ri:moon-foggy-line',
|
||||
},
|
||||
]
|
||||
|
||||
const languageOptions: { label: string; key: Language; value: Language }[] = [
|
||||
{ label: '中文', key: 'zh-CN', value: 'zh-CN' },
|
||||
{ label: 'English', key: 'en-US', value: 'en-US' },
|
||||
]
|
||||
|
||||
function updateUserInfo(options: Partial<UserInfo>) {
|
||||
userStore.updateUserInfo(options)
|
||||
ms.success('Update success')
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
userStore.resetUserInfo()
|
||||
ms.success('Reset success')
|
||||
emit('update')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4 space-y-5 min-h-[200px]">
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">Avatar Link</span>
|
||||
<div class="flex-1">
|
||||
<NInput v-model:value="avatar" placeholder="" />
|
||||
</div>
|
||||
<NButton size="tiny" text type="primary" @click="updateUserInfo({ avatar })">
|
||||
Save
|
||||
</NButton>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">Name</span>
|
||||
<div class="w-[200px]">
|
||||
<NInput v-model:value="name" placeholder="" />
|
||||
</div>
|
||||
<NButton size="tiny" text type="primary" @click="updateUserInfo({ name })">
|
||||
Save
|
||||
</NButton>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">Description</span>
|
||||
<div class="flex-1">
|
||||
<NInput v-model:value="description" placeholder="" />
|
||||
</div>
|
||||
<NButton size="tiny" text type="primary" @click="updateUserInfo({ description })">
|
||||
Save
|
||||
</NButton>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">Reset UserInfo</span>
|
||||
<NButton text type="primary" @click="handleReset">
|
||||
Reset
|
||||
</NButton>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">Theme</span>
|
||||
<div class="flex items-center space-x-4">
|
||||
<template v-for="item of themeOptions" :key="item.key">
|
||||
<a
|
||||
class="flex items-center justify-center h-8 px-4 border rounded-md cursor-pointer dark:border-neutral-700"
|
||||
:class="item.key === theme && ['bg-[#4ca85e]', 'border-[#4ca85e]', 'text-white']"
|
||||
@click="appStore.setTheme(item.key)"
|
||||
>
|
||||
<span class="text-xl">
|
||||
<SvgIcon :icon="item.icon" />
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex-shrink-0 w-[100px]">Language</span>
|
||||
<div class="flex items-center space-x-4">
|
||||
<template v-for="item of languageOptions" :key="item.key">
|
||||
<a
|
||||
class="flex items-center justify-center h-8 px-4 border rounded-md cursor-pointer dark:border-neutral-700"
|
||||
:class="item.key === language && ['bg-[#4ca85e]', 'border-[#4ca85e]', 'text-white']"
|
||||
@click="appStore.setLanguage(item.key)"
|
||||
>
|
||||
<span class="text-sm">
|
||||
{{ item.label }}
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang='ts'>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { NCard, NModal } from 'naive-ui'
|
||||
import pkg from '../../../../package.json'
|
||||
import { fetchChatConfig } from '@/api'
|
||||
import { computed, ref } from 'vue'
|
||||
import { NCard, NModal, NTabPane, NTabs } from 'naive-ui'
|
||||
import General from './General.vue'
|
||||
import About from './About.vue'
|
||||
import { SvgIcon } from '@/components/common'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
@@ -12,17 +13,14 @@ interface Emit {
|
||||
(e: 'update:visible', visible: boolean): void
|
||||
}
|
||||
|
||||
interface ConfigState {
|
||||
timeoutMs?: number
|
||||
reverseProxy?: string
|
||||
apiModel?: string
|
||||
socksProxy?: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const active = ref('General')
|
||||
|
||||
const reload = ref(false)
|
||||
|
||||
const show = computed({
|
||||
get() {
|
||||
return props.visible
|
||||
@@ -32,46 +30,35 @@ const show = computed({
|
||||
},
|
||||
})
|
||||
|
||||
const config = ref<ConfigState>()
|
||||
|
||||
async function fetchConfig() {
|
||||
try {
|
||||
const { data } = await fetchChatConfig<ConfigState>()
|
||||
config.value = data
|
||||
}
|
||||
catch (error) {
|
||||
// ...
|
||||
}
|
||||
function handleReload() {
|
||||
reload.value = true
|
||||
setTimeout(() => {
|
||||
reload.value = false
|
||||
}, 0)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val)
|
||||
fetchConfig()
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal v-model:show="show" style="width: 80%; max-width: 460px;">
|
||||
<NCard>
|
||||
<div class="space-y-4">
|
||||
<h2 class="text-xl font-bold text-center">
|
||||
Version - {{ pkg.version }}
|
||||
</h2>
|
||||
<hr>
|
||||
<p>
|
||||
此项目开源于
|
||||
<a class="text-blue-600" href="https://github.com/Chanzhaoyu/chatgpt-web" target="_blank">Github</a>
|
||||
如果你觉得此项目对你有帮助,请帮我点个 Star,谢谢!
|
||||
</p>
|
||||
<hr>
|
||||
<p>API方式:{{ config?.apiModel ?? '-' }}</p>
|
||||
<p>反向代理:{{ config?.reverseProxy ?? '-' }}</p>
|
||||
<p>超时时间:{{ config?.timeoutMs ?? '-' }}</p>
|
||||
<p>Socks代理:{{ config?.socksProxy ?? '-' }}</p>
|
||||
</div>
|
||||
<NModal v-model:show="show">
|
||||
<NCard role="dialog" aria-modal="true" :bordered="false" style="width: 100%; max-width: 640px">
|
||||
<NTabs v-model:value="active" type="line" animated>
|
||||
<NTabPane name="General" tab="General">
|
||||
<template #tab>
|
||||
<SvgIcon class="text-lg" icon="ri:file-user-line" />
|
||||
<span class="ml-2">General</span>
|
||||
</template>
|
||||
<div class="min-h-[100px]">
|
||||
<General v-if="!reload" @update="handleReload" />
|
||||
</div>
|
||||
</NTabPane>
|
||||
<NTabPane name="Config" tab="Config">
|
||||
<template #tab>
|
||||
<SvgIcon class="text-lg" icon="ri:list-settings-line" />
|
||||
<span class="ml-2">Config</span>
|
||||
</template>
|
||||
<About />
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</NCard>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
@@ -1,18 +1,39 @@
|
||||
<script setup lang='ts'>
|
||||
import { GithubSite } from '@/components/custom'
|
||||
import { computed } from 'vue'
|
||||
import { NAvatar } from 'naive-ui'
|
||||
import { useUserStore } from '@/store'
|
||||
import defaultAvatar from '@/assets/avatar.jpg'
|
||||
import { isString } from '@/utils/is'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const userInfo = computed(() => userStore.userInfo)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<div class="w-10 h-10 overflow-hidden rounded-full">
|
||||
<img class="object-cover" src="@/assets/avatar.jpg" alt="avatar">
|
||||
<template v-if="isString(userInfo.avatar) && userInfo.avatar.length > 0">
|
||||
<NAvatar
|
||||
size="large"
|
||||
round
|
||||
:src="userInfo.avatar"
|
||||
:fallback-src="defaultAvatar"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NAvatar size="large" round :src="defaultAvatar" />
|
||||
</template>
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<h2 class="font-bold text-md">
|
||||
ChenZhaoYu
|
||||
{{ userInfo.name ?? 'ChenZhaoYu' }}
|
||||
</h2>
|
||||
<p class="text-xs text-gray-500">
|
||||
<GithubSite />
|
||||
<span
|
||||
v-if="isString(userInfo.description) && userInfo.description !== ''"
|
||||
v-html="userInfo.description"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user