!15 更新到master分支

Merge pull request !15 from nongyehong/dev
This commit is contained in:
nongyehong 2024-07-03 10:33:01 +00:00 committed by Gitee
commit 790cce7755
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
17 changed files with 566 additions and 143 deletions

View File

@ -7,7 +7,7 @@
<title>HuLa</title>
<!--引入iconpark图标库-->
<script defer src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_30895_95.0ab6745fae3ccd4f96f945bab1f8bd0d.js"></script>
<script defer src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_30895_97.8e01ce86874358e3d6e87829c9ed23c6.js"></script>
</head>
<body>

24
src-tauri/Cargo.lock generated
View File

@ -3487,9 +3487,9 @@ checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
[[package]]
name = "tauri"
version = "1.6.8"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77567d2b3b74de4588d544147142d02297f3eaa171a25a065252141d8597a516"
checksum = "336bc661a3f3250853fa83c6e5245449ed1c26dce5dcb28bdee7efedf6278806"
dependencies = [
"anyhow",
"bytes",
@ -3566,9 +3566,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "1.4.3"
version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3a1d90db526a8cdfd54444ad3f34d8d4d58fa5c536463915942393743bd06f8"
checksum = "c1aed706708ff1200ec12de9cfbf2582b5d8ec05f6a7293911091effbd22036b"
dependencies = [
"base64 0.21.7",
"brotli",
@ -3592,9 +3592,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "1.4.4"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a582d75414250122e4a597b9dd7d3c910a2c77906648fc2ac9353845ff0feec"
checksum = "b88f831d2973ae4f81a706a0004e67dac87f2e4439973bbe98efbd73825d8ede"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@ -3606,9 +3606,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "0.14.3"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7ffddf36d450791018e63a3ddf54979b9581d9644c584a5fb5611e6b5f20b4"
checksum = "3068ed62b63dedc705558f4248c7ecbd5561f0f8050949859ea0db2326f26012"
dependencies = [
"gtk",
"http",
@ -3627,9 +3627,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "0.14.8"
version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1989b3b4d611f5428b3414a4abae6fa6df30c7eb8ed33250ca90a5f7e5bb3655"
checksum = "d4c3db170233096aa30330feadcd895bf9317be97e624458560a20e814db7955"
dependencies = [
"cocoa 0.24.1",
"gtk",
@ -3647,9 +3647,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "1.5.4"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "450b17a7102e5d46d4bdabae0d1590fd27953e704e691fc081f06c06d2253b35"
checksum = "2826db448309d382dac14d520f0c0a40839b87b57b977e59cf5f296b3ace6a93"
dependencies = [
"brotli",
"ctor",

View File

@ -20,7 +20,7 @@ strip = true # 删除调试符号
tauri-build = { version = "1.5.2", features = [] }
[dependencies]
tauri = { version = "1.6.8", features = [ "http-all", "os-all", "process-all", "notification-all", "macos-private-api", "fs-all", "window-all", "system-tray", "shell-open", "icon-png", "icon-ico"] }
tauri = { version = "1.7.1", features = [ "http-all", "os-all", "process-all", "notification-all", "macos-private-api", "fs-all", "window-all", "system-tray", "shell-open", "icon-png", "icon-ico"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
window-shadows = "0.2.2" # windows 阴影和圆角

View File

@ -115,7 +115,7 @@ export const useChatMain = (activeItem?: SessionItem) => {
...commonMenuList.value,
{
label: '另存为',
icon: 'download',
icon: 'Importing',
click: (item: any) => {
console.log(item)
}
@ -142,7 +142,7 @@ export const useChatMain = (activeItem?: SessionItem) => {
...commonMenuList.value,
{
label: '另存为',
icon: 'download',
icon: 'Importing',
click: (item: any) => {
console.log(item)
}

View File

@ -15,7 +15,7 @@ export const useCommon = () => {
const reply = ref({
accountName: '',
content: '',
key: '',
key: 0,
imgCount: 0
})
/** 获取当前光标选取的信息(需要判断是否为空) */
@ -233,7 +233,7 @@ export const useCommon = () => {
selection?.removeAllRanges()
selection?.addRange(range)
triggerInputEvent(messageInput)
reply.value = { imgCount: 0, accountName: '', content: '', key: '' }
reply.value = { imgCount: 0, accountName: '', content: '', key: 0 }
})
// 将头部和正文节点插入到div标签节点中
divNode.appendChild(headerNode)

View File

@ -72,7 +72,7 @@ export const useMsgInput = (messageInputDom: Ref) => {
})
}
},
{ label: '另存为', icon: 'download', disabled: true },
{ label: '另存为', icon: 'Importing', disabled: true },
{ label: '全部选择', icon: 'check-one' }
])
@ -83,7 +83,7 @@ export const useMsgInput = (messageInputDom: Ref) => {
}
// 如果输入框没有值就把回复内容清空
if (msgInput.value === '') {
reply.value = { imgCount: 0, accountName: '', content: '', key: '' }
reply.value = { imgCount: 0, accountName: '', content: '', key: 0 }
}
})
@ -113,7 +113,12 @@ export const useMsgInput = (messageInputDom: Ref) => {
// 回复前把包含&nbsp;的字符替换成空格
event.message.body.content = event.message.body.content.replace(/&nbsp;/g, ' ')
}
reply.value = { imgCount: 0, accountName: accountName, content: event.message.body.content, key: event.key }
reply.value = {
imgCount: 0,
accountName: accountName,
content: event.message.body.content,
key: event.message.id
}
if (messageInputDom.value) {
nextTick().then(() => {
messageInputDom.value.focus()
@ -144,10 +149,11 @@ export const useMsgInput = (messageInputDom: Ref) => {
const msg = {
type: contentType,
content: removeTag(msgInput.value),
reply: reply.value
reply: reply.value.key
}
// TODO 当输入的类型是艾特类型的时候需要处理 (nyh -> 2024-05-30 19:52:20)
// TODO 当输入的内容换行后会有div包裹这样会有xxr攻击风险 (nyh -> 2024-05-30 20:19:27)
// TODO 当输入网址的时候会传递样式进去会造成xxr攻击 (nyh -> 2024-07-03 17:30:57)
/** 如果reply.value.content中有内容需要将消息的样式修改 */
if (reply.value.content) {
if (msg.type === MsgEnum.TEXT) {
@ -188,7 +194,11 @@ export const useMsgInput = (messageInputDom: Ref) => {
return
}
apis
.sendMsg({ roomId: globalStore.currentSession.roomId, msgType: msg.type, body: { content: msg.content } })
.sendMsg({
roomId: globalStore.currentSession.roomId,
msgType: msg.type,
body: { content: msg.content, replyMsgId: msg.reply !== 0 ? msg.reply : undefined }
})
.then((res) => {
if (res.data.message.type === MsgEnum.TEXT) {
chatStore.pushMsg(res.data)
@ -202,7 +212,7 @@ export const useMsgInput = (messageInputDom: Ref) => {
})
msgInput.value = ''
messageInputDom.value.innerHTML = ''
reply.value = { imgCount: 0, accountName: '', content: '', key: '' }
reply.value = { imgCount: 0, accountName: '', content: '', key: 0 }
}
/** 当输入框手动输入值的时候触发input事件(使用vueUse的防抖) */

View File

@ -22,7 +22,7 @@
<n-input
id="search"
@focus="() => router.push('/searchDetails')"
class="rounded-4px w-full"
class="rounded-6px w-full"
style="background: var(--search-bg-color)"
:maxlength="20"
clearable

View File

@ -25,8 +25,8 @@
<!-- 该选项有提示时展示 -->
<n-popover style="padding: 12px" v-else-if="item.tip" trigger="manual" v-model:show="tipShow" placement="right">
<template #trigger>
<n-badge :max="99" :value="item.badge">
<svg class="size-22px" @click="tipShow = false">
<n-badge :max="99" :value="item.badge" :dot="dotShow">
<svg class="size-22px" @click="handleTipShow">
<use
:href="`#${activeUrl === item.url || openWindowsList.has(item.url) ? item.iconAction : item.icon}`"></use>
</svg>
@ -34,7 +34,7 @@
</template>
<n-flex align="center" justify="space-between">
<p class="select-none">{{ item.tip }}</p>
<svg @click="tipShow = false" class="size-12px cursor-pointer"><use href="#close"></use></svg>
<svg @click="handleTipShow" class="size-12px cursor-pointer"><use href="#close"></use></svg>
</n-flex>
</n-popover>
<!-- 该选项无提示时展示 -->
@ -115,13 +115,22 @@
import { itemsBottom, itemsTop, moreList } from '../config.ts'
import { leftHook } from '../hook.ts'
const dotShow = ref(false)
const { activeUrl, openWindowsList, settingShow, tipShow, pageJumps } = leftHook()
const handleTipShow = () => {
tipShow.value = false
dotShow.value = false
}
onMounted(() => {
if (tipShow.value) {
dotShow.value = true
}
/** 十秒后关闭提示 */
setTimeout(() => {
tipShow.value = false
}, 10000)
}, 5000)
})
</script>
<style lang="scss" scoped>

View File

@ -46,6 +46,11 @@ const routes: Array<RouteRecordRaw> = [
name: 'robot',
component: () => import('@/views/home-window/robot/index.vue'),
children: [
{
path: '/welcome',
name: 'welcome',
component: () => import('@/views/home-window/robot/views/Welcome.vue')
},
{
path: '/chat',
name: 'chat',

View File

@ -75,7 +75,7 @@
}
/** emoji回复气泡的样式 */
.emoji-reply-bubble {
@apply relative rounded-50px p-[4px_8px] cursor-pointer select-none bg-#13987F66 text-14px w-fit border-(1px solid #13987F);
@apply relative rounded-50px p-[4px_8px] cursor-pointer select-none bg-#13987F66 text-14px w-fit border-(1px solid #13987F) shadow-md;
}
/** 跳转到回复内容时候显示的样式 */
.active-reply {

View File

@ -114,28 +114,13 @@ const settingStore = setting()
const { login } = storeToRefs(settingStore)
const activeItem = ref(0)
const scrollbar = ref<VirtualListInst>()
const chatList = ref([
{
id: 1,
title: '新的聊天1',
const chatList = ref(
Array.from({ length: 20 }, (_, index) => ({
id: index + 1,
title: `新的聊天${index + 1}`,
time: '2022-01-01 12:00:00'
},
{
id: 2,
title: '新的聊天2',
time: '2022-01-01 12:00:00'
},
{
id: 3,
title: '新的聊天3',
time: '2022-01-01 12:00:00'
},
{
id: 4,
title: '新的聊天4',
time: '2022-01-01 12:00:00'
}
])
}))
)
const menuList = ref<OPT.RightMenu[]>([
{
label: '置顶',
@ -199,6 +184,26 @@ const add = () => {
const deleteChat = (item: any) => {
// keyitems
const index = chatList.value.indexOf(item)
/**
* 删除最后一个元素后触发新增元素的函数
* @param isActive 是否选中新增的元素
*/
function triggeringAdd(isActive?: boolean) {
if (chatList.value.length === 0) {
nextTick(() => {
add()
//
if (isActive) {
handleActive(chatList.value[0])
window.$message.success(`已删除 ${item.title}`, {
icon: () => h(NIcon, null, { default: () => h('svg', null, [h('use', { href: '#face' })]) })
})
}
})
}
}
//
if (index !== -1) {
const removeItem = chatList.value.splice(index, 1)[0]
@ -208,22 +213,15 @@ const deleteChat = (item: any) => {
activeItem.value = chatList.value[index].id
handleActive(chatList.value[index])
} else {
//
if (chatList.value.length === 0) {
nextTick(() => {
add()
//
handleActive(chatList.value[0])
window.$message.success(`已删除 ${item.title}`, {
icon: () => h(NIcon, null, { default: () => h('svg', null, [h('use', { href: '#face' })]) })
})
})
}
// chatList,
triggeringAdd(true)
//
activeItem.value = chatList.value[chatList.value.length - 1].id
handleActive(chatList.value[chatList.value.length - 1])
}
}
//
triggeringAdd()
window.$message.success(`已删除 ${item.title}`, {
icon: () => h(NIcon, null, { default: () => h('svg', null, [h('use', { href: '#face' })]) })
})
@ -231,8 +229,10 @@ const deleteChat = (item: any) => {
}
onMounted(() => {
/** 默认选择第一个聊天内容 */
handleActive(chatList.value[0])
// /** */
// handleActive(chatList.value[0])
/** 刚加载的时候默认跳转到欢迎页面 */
router.push('/welcome')
Mitt.on('update-chat-title', (e) => {
chatList.value.filter((item) => {
if (item.id === e.id) {
@ -247,19 +247,7 @@ onMounted(() => {
</script>
<style scoped lang="scss">
.gpt-subtitle {
--un-gradient-from-position: 0%;
--un-gradient-from: rgba(56, 189, 248, 20) var(--un-gradient-from-position);
--un-gradient-stops: var(--un-gradient-from), var(--un-gradient-to);
--un-gradient-to-position: 100%;
--un-gradient-to: rgba(19, 152, 127, 80) var(--un-gradient-to-position);
--un-gradient-shape: to right;
--un-gradient: var(--un-gradient-shape), var(--un-gradient-stops);
background-image: linear-gradient(var(--un-gradient));
-webkit-background-clip: text;
background-clip: text;
font-size: 20px;
font-weight: 800;
color: transparent;
@apply bg-clip-text text-transparent bg-gradient-to-r from-#38BDF8 to-#13987F text-20px font-800;
}
.plugins {
@apply size-fit bg-[--chat-bt-color] rounded-8px shadow-md p-[8px_14px]

View File

@ -1,5 +1,6 @@
<template>
<!-- 主体内容 -->
<!-- // TODO 使 (nyh -> 2024-07-01 10:44:14)-->
<main>
<div class="flex truncate p-[14px_20px] justify-between items-center gap-50px">
<n-flex :size="10" vertical class="truncate">

View File

@ -0,0 +1,195 @@
<template>
<n-flex vertical :size="10" align="center" justify="center" class="flex flex-1">
<!-- logo -->
<img class="w-275px h-125px drop-shadow-2xl" src="@/assets/logo/hula.png" alt="" />
<n-flex vertical justify="center" :size="16" class="p-[30px_20px]">
<p class="text-(14px [--chat-text-color])">你可以尝试使用以下功能</p>
<n-flex align="center" :size="[32, 16]">
<n-flex
vertical
v-for="(item, index) in examplesList"
:key="index"
justify="center"
:size="12"
class="examples">
<n-flex align="center" justify="space-between">
<p class="text-(14px [--chat-text-color]) font-bold">{{ item.title }}</p>
<svg class="size-16px"><use :href="`#${item.icon}`"></use></svg>
</n-flex>
<component :is="item.content" />
</n-flex>
</n-flex>
</n-flex>
</n-flex>
</template>
<script setup lang="tsx">
import { NFlex, NImage, NSkeleton } from 'naive-ui'
type Example = {
title: string
icon: string
content: JSX.Element
}[]
const avatars = 'https://picsum.photos/140'
const examplesList: Example = [
{
title: 'AI搜索',
icon: 'search',
content: (
<NFlex vertical size={12}>
{Array.from({ length: 3 }, (_, index) => (
<NFlex key={index} class={'search-item'}>
<NImage width={50} height={45} previewDisabled class={'rounded-12px'} src={`${avatars}?${index}1`}>
{{
placeholder: () => (
<div class="w-50px h-45px rounded-12px bg-[--chat-hover-color]">
<NSkeleton height="100%" width="100%" class={'rounded-12px'} />
</div>
)
}}
</NImage>
<NFlex vertical justify="center" class={'text-(12px [--chat-text-color]) truncate flex-1'}>
<p class="truncate w-full">你好我是机器人小助手很高兴为你服务</p>
<p>你最近怎么样</p>
</NFlex>
<svg
style={{ filter: 'drop-shadow(0 0 0.6em #13987f)' }}
class="color-#13987f p-[10px_4px] size-26px opacity-0 absolute top-1/2 right-24px transform -translate-x-1/2 -translate-y-1/2">
<use href="#Up-GPT"></use>
</svg>
</NFlex>
))}
</NFlex>
)
},
{
title: 'PDF阅读',
icon: 'notes',
content: (
<NFlex vertical size={12} class={'pdf-item'}>
<NFlex vertical justify="center" class="content">
<NFlex size={12} align={'center'}>
<img class="size-24px" src="/file/pdf.svg" alt="" />
<p class="text-(12px [--chat-text-color]) underline">全球经济金融展望报告.pdf</p>
</NFlex>
<p class="indent-8 text-(12px [--chat-text-color]) text-wrap leading-5">
2023年全球经济增长动力持续回落各国复苏 分化发达经济体增速明显放缓新兴经济体 整体表现稳定
</p>
<ul class="list-disc list-inside indent-4 truncate text-(12px [--chat-text-color]) text-wrap leading-5">
<li>
展望2024年预计全球经济复苏将依日疲软主要经济体增长态势和货币政策走势将
进一步分化全球贸易环境的不确定性亦将上升受保护主义和技术变革的双重影响供应链重组趋势或将加速
</li>
</ul>
</NFlex>
<NFlex vertical justify="center" align="center" class="foot">
<svg
style={{ filter: 'drop-shadow(0 0 0.6em #13987f)' }}
class="color-#13987f p-[10px_4px] size-26px opacity-0">
<use href="#Up-GPT"></use>
</svg>
<p class="text-(14px [--chat-text-color]) opacity-0">前往阅读</p>
</NFlex>
</NFlex>
)
},
{
title: '图像生成',
icon: 'photo',
content: (
<NFlex vertical justify="center" size={0} class={'photo-item'}>
<NFlex align={'center'} size={10} class={'head'}>
{Array.from({ length: 4 }, (_, index) => (
<NImage width={128} height={90} previewDisabled class={'rounded-12px'} src={`${avatars}?${index}`}>
{{
placeholder: () => (
<div class="w-128px h-90px rounded-12px bg-[--chat-hover-color]">
<NSkeleton height="100%" width="100%" class={'rounded-12px'} />
</div>
)
}}
</NImage>
))}
</NFlex>
<NFlex justify={'space-between'} align={'center'} class={'foot'}>
<p class={'text-(14px [--chat-text-color]) font-bold pl-6px opacity-0'}>试一试</p>
<svg style={{ filter: 'drop-shadow(0 0 0.6em #13987f)' }} class="color-#13987f pr-6px size-26px opacity-0">
<use href="#Up-GPT"></use>
</svg>
</NFlex>
</NFlex>
)
}
]
</script>
<style lang="scss">
.examples {
@apply w-300px h-fit rounded-12px p-10px box-border cursor-pointer border-(solid 1px [--line-color]) shadow-md;
&:hover {
.search-item:not(:hover) {
@apply blur-md scale-94;
}
@apply outline outline-2 outline-#13987f outline-offset;
}
}
.search-item {
@apply relative rounded-12px p-6px box-border cursor-pointer transition-all duration-600 ease-in-out;
svg {
@apply transition-all duration-600 ease-in-out;
}
&:hover {
@apply bg-[--chat-hover-color] scale-104;
svg {
@apply opacity-100 translate-x-20px;
}
}
}
.pdf-item {
@apply relative;
.content {
@apply bg-[--chat-hover-color] h-195px rounded-12px p-10px box-border transition-all duration-600 ease-in-out;
}
.foot {
@apply absolute top-2/3 left-1/2 transform -translate-x-1/2 -translate-y-1/2;
svg,
p {
@apply transition-all duration-600 ease-in-out;
}
}
&:hover .content {
@apply blur-md;
}
&:hover .foot {
svg,
p {
@apply opacity-100 translate-y--30px;
}
}
}
.photo-item {
@apply relative p-6px box-border;
.foot {
@apply w-full h-40px absolute bottom--30px left-0 rounded-12px transition-all duration-600 ease-in-out;
}
&:hover {
.head {
@apply blur-md scale-94 transition-all duration-600 ease-in-out;
}
.foot {
@apply backdrop-blur-xl bg-#F1F1F133 translate-y--30px;
svg,
p {
@apply opacity-100;
}
}
}
}
</style>

View File

@ -1,35 +1,46 @@
import pkg from '~/package.json'
import { Button, Select, Slider, Switch } from './model.tsx'
import { Button, Select, Slider, Switch, Input, InputNumber } from './model.tsx'
import { NFlex } from 'naive-ui'
type ConfigItemType = 'system' | 'record' | 'identity' | 'cueWords' | 'APIAddress' | 'model' | 'clear'
type ChatConfig = {
system: {
[key in ConfigItemType]: {
title: string
description?: string
features: JSX.Element
}[]
}
/** chat 设置面板配置 */
export const content: ChatConfig = {
system: [
{
title: `当前版本:${pkg.version}`,
description: '已是最新版本',
features: Button('检查更新', 'refresh')
features: <Button title={'检查更新'} icon={'refresh'} />
},
{
title: '发送键',
features: Select([
{ label: 'Enter', value: 'Enter' },
{ label: 'Ctrl + Enter', value: 'Ctrl+Enter' }
])
features: (
<Select
content={[
{ label: 'Enter', value: 'Enter' },
{ label: 'Ctrl + Enter', value: 'Ctrl+Enter' }
]}
/>
)
},
{
title: '主题',
features: Select([
{ label: '亮色', value: 'light' },
{ label: '暗黑模式', value: 'dark' },
{ label: '跟随系统', value: 'auto' }
])
features: (
<Select
content={[
{ label: '亮色', value: 'light' },
{ label: '暗黑模式', value: 'dark' },
{ label: '跟随系统', value: 'auto' }
]}
/>
)
},
{
title: '字体大小',
@ -39,7 +50,150 @@ export const content: ChatConfig = {
{
title: '自动生成标题',
description: '根据对话内容生成合适的标题',
features: Switch()
features: <Switch active={false} />
}
],
record: [
{
title: '云端数据',
description: '还没有进行同步',
features: <Button title={'配置'} icon={'setting-config'} />
},
{
title: '本地数据',
description: '1 次对话0条消息0条提示词0个身份',
features: (
<NFlex align={'center'}>
<Button title={'导入'} icon={'Export'} />
<Button title={'导出'} icon={'Importing'} />
</NFlex>
)
}
],
identity: [
{
title: '身份启动页',
description: '新建聊天时,展示身份启动页',
features: <Switch active={true} />
},
{
title: '隐藏内置身份',
description: '在所有身份列表中隐藏内置身份',
features: <Switch active={false} />
}
],
cueWords: [
{
title: '禁用提示词自动补全',
description: '在输入框开头输入/即可触发自动补全',
features: <Switch active={false} />
},
{
title: '自定义提示词列表',
description: '内置 285 条用户定义0条',
features: <Button title={'编辑'} icon={'edit'} />
}
],
APIAddress: [
{
title: '模型服务商',
description: '切换不同的服务商',
features: (
<Select
content={[
{ label: 'openAi', value: 'openAi' },
{ label: 'Azure', value: 'Azure' },
{ label: 'Google', value: 'Google' }
]}
/>
)
},
{
title: '接口地址',
description: '除默认地址外,必须包含 http(s)://',
features: <Input value={'www.baidu.com'} />
},
{
title: 'API Key',
description: '使用自定义 OpenAI key 统过密码访问限制',
features: <Input value={'123456'} isPassword={true} />
}
],
model: [
{
title: '模型(model)',
features: (
<Select
content={[
{ label: 'gpt-3.5-turbo', value: 'gpt-3.5-turbo' },
{ label: 'gpt-4o', value: 'gpt-4o' },
{ label: 'gpt-4-32k', value: 'gpt-4-32k' },
{ label: 'gpt-4-turbo', value: 'gpt-4-turbo' }
]}
/>
)
},
{
title: '随机性(temperature)',
description: '值越大,回复越随机',
features: <Slider min={0} max={10} value={5} />
},
{
title: '核采样(top_p)',
description: '与随机性类似,但不要和随机性一起更改',
features: <Slider min={0} max={10} value={5} />
},
{
title: '单次回复限制(max_tokens)',
description: '单次交互所用的最大 Token 数',
features: <InputNumber value={4000} min={2000} max={10000} />
},
{
title: '话题新鲜度(presence_penalty)',
description: '值越大,越有可能扩展到新话题',
features: <Slider min={0} max={10} value={5} />
},
{
title: '频率惩罚度(frequency_penalty)',
description: '值越大,越有可能降低重复字词',
features: <Slider min={0} max={10} value={5} />
},
{
title: '注入系统级提示信息',
description: '强制给每次请求的消息列表开头添加一个模拟 ChatGPT 的系统提示',
features: <Switch active={false} />
},
{
title: '用户输入预处理',
description: '用户最新的一条消息会埴充到此模板',
features: <Input value={'input'} />
},
{
title: '附带历史消息数',
description: '每次请求携带的历史消息数',
features: <Slider min={0} max={10} value={5} />
},
{
title: '历史消息长度压缩阈值',
description: '当未压缩的历史消息超过该值时,将进行压缩',
features: <InputNumber value={1000} min={0} max={5000} />
},
{
title: '历史摘要',
description: '自动压缩聊天记录并作为上下文发送',
features: <Switch active={true} />
}
],
clear: [
{
title: '重置所有设置',
description: '重置所有设置项回默认值',
features: <Button title={'立即重置'} isSecondary={true} />
},
{
title: '清除所有数据',
description: '清除所有聊天、设置数据',
features: <Button title={'立即清除'} isSecondary={true} />
}
]
}

View File

@ -14,24 +14,28 @@
<div class="h-1px bg-[--line-color]"></div>
<!-- 设置的主体内容 -->
<div class="flex flex-1 shadow-inner p-20px">
<n-flex
vertical
class="w-full h-fit bg-[--bg-setting-item] border-(solid 1px [--line-color]) shadow-md rounded-8px p-10px">
<n-flex vertical justify="center" v-for="(item, index) in content.system" :key="index">
<n-flex justify="space-between" :size="0" align="center" class="p-8px">
<n-flex vertical :size="4">
<p class="text-(15px [--chat-text-color]) font-bold">{{ item.title }}</p>
<p v-if="item.description" class="text-(12px [--chat-text-color])">{{ item.description }}</p>
<n-scrollbar style="max-height: calc(100vh - 104px)">
<n-flex vertical :size="20" class="p-[20px_0] shadow-inner">
<div v-for="(key, index) in content" :key="index" class="flex flex-1 p-[0_20px]">
<n-flex
vertical
class="w-full h-fit bg-[--bg-setting-item] border-(solid 1px [--line-color]) shadow-md rounded-8px p-10px">
<n-flex vertical justify="center" v-for="(item, index) in key" :key="index">
<n-flex justify="space-between" :size="0" align="center" class="p-8px">
<n-flex vertical :size="4">
<p class="text-(15px [--chat-text-color]) font-bold">{{ item.title }}</p>
<p v-if="item.description" class="text-(12px [--chat-text-color])">{{ item.description }}</p>
</n-flex>
<component :is="item.features" />
</n-flex>
<div v-if="index !== key.length - 1" class="h-1px bg-[--line-color]"></div>
</n-flex>
<component :is="item.features" />
</n-flex>
<div v-if="index !== content.system.length - 1" class="h-1px bg-[--line-color]"></div>
</n-flex>
</div>
</n-flex>
</div>
</n-scrollbar>
</template>
<script setup lang="tsx">
import router from '@/router'
@ -53,7 +57,4 @@ const handleClose = () => {
@apply size-18px;
}
}
:deep(.n-button:not(.n-button--disabled):hover) {
color: revert;
}
</style>

View File

@ -4,6 +4,7 @@ import {
NSelect,
NSlider,
NSwitch,
NInput,
NInputNumber,
NConfigProvider,
GlobalThemeOverrides
@ -18,42 +19,65 @@ const commonTheme: GlobalThemeOverrides = {
borderDisabled: '1px solid #ccc',
borderFocus: '1px solid #ccc',
boxShadowFocus: '1px solid #ccc'
},
Button: {
textColorHover: '#red'
}
}
export const Button = (title: string, icon: string) => {
return (
<>
<NButton quaternary size={'small'}>
<NFlex justify="center" align="center" size={6}>
<svg class={'size-12px'}>
<use href={`#${icon}`}></use>
</svg>
<p class={'text-12px'}>{title}</p>
</NFlex>
export const Button = defineComponent(
(props: { title: string; icon?: string; isSecondary?: boolean }) => {
const loading = ref(false)
const handleClick = () => {
loading.value = true
setTimeout(() => {
loading.value = false
}, 1000)
}
return () => (
<NButton
loading={loading.value}
onClick={handleClick}
type={props.isSecondary ? 'error' : 'default'}
quaternary={!props.isSecondary}
secondary={props.isSecondary}
size={'small'}>
{{
icon: () =>
props.icon ? (
<svg class={'size-12px'}>
<use href={`#${props.icon}`}></use>
</svg>
) : (
void 0
),
default: () => props.title
}}
</NButton>
</>
)
}
)
},
{
props: ['title', 'icon', 'isSecondary']
}
)
export const Select = (content: any[]) => {
return (
<>
export const Select = defineComponent(
(props: { content: any[] }) => {
const v = ref(props.content[0].value)
return () => (
<NSelect
class={'w-120px rounded-8px'}
consistentMenuWidth={false}
size={'small'}
options={content}
value={content[0].value}></NSelect>
</>
)
}
v-model:value={v.value}
options={props.content}
value={props.content[0].value}></NSelect>
)
},
{
props: ['content']
}
)
export const Slider = defineComponent(
(props: { value: number; max: number; min: number }) => {
(props: { value: number; max: number; min: number; isDecimal?: boolean }) => {
const v = ref(props.value)
const formatTooltip = (value: number) => `${value}px`
return () => (
@ -66,8 +90,7 @@ export const Slider = defineComponent(
formatTooltip={formatTooltip}
v-model:value={v.value}
max={props.max}
min={props.min}
step={1}></NSlider>
min={props.min}></NSlider>
</NFlex>
)
},
@ -76,15 +99,52 @@ export const Slider = defineComponent(
}
)
export const Switch = () => {
return (
<>
<NSwitch class={'text-(12px [--chat-text-color])'} size={'small'}>
export const Switch = defineComponent(
(props: { active: boolean }) => {
const v = ref(props.active)
return () => (
<NSwitch v-model:value={v.value} class={'text-(12px [--chat-text-color])'} size={'small'}>
{{
checked: () => '开启',
unchecked: () => '关闭'
}}
</NSwitch>
</>
)
}
)
},
{
props: ['active']
}
)
export const Input = defineComponent(
(props: { value: string; isPassword?: boolean }) => {
const v = ref(props.value)
return () => (
<NConfigProvider themeOverrides={commonTheme}>
<NInput
style={{ width: '160px' }}
v-model:value={v.value}
type={props.isPassword ? 'password' : 'text'}
size={'small'}
showPasswordOn={'click'}></NInput>
</NConfigProvider>
)
},
{ props: ['value', 'isPassword'] }
)
export const InputNumber = defineComponent(
(props: { value: number; max: number; min: number }) => {
const v = ref(props.value)
return () => (
<NInputNumber
style={{ width: '120px', borderRadius: '10px', border: '1px solid #ccc' }}
min={props.min}
max={props.max}
v-model:value={v.value}
step={100}
size={'small'}></NInputNumber>
)
},
{ props: ['value'] }
)

View File

@ -4,7 +4,7 @@
<ActionBar :max-w="false" :shrink="false" />
<n-flex justify="center" class="mt-15px">
<img src="@/assets/logo/hula.png" class="w-140px h-60px" alt="" />
<img src="@/assets/logo/hula.png" class="w-140px h-60px drop-shadow-xl" alt="" />
</n-flex>
<!-- 二维码 -->