mirror of
https://gitee.com/HuLaSpark/HuLa.git
synced 2024-11-29 18:28:30 +08:00
commit
790cce7755
@ -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
24
src-tauri/Cargo.lock
generated
@ -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",
|
||||
|
@ -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 阴影和圆角
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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) => {
|
||||
// 回复前把包含 的字符替换成空格
|
||||
event.message.body.content = event.message.body.content.replace(/ /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的防抖) */
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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',
|
||||
|
@ -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 {
|
||||
|
@ -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) => {
|
||||
// 根据key找到items中对应的下标
|
||||
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]
|
||||
|
@ -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">
|
||||
|
195
src/views/home-window/robot/views/Welcome.vue
Normal file
195
src/views/home-window/robot/views/Welcome.vue
Normal 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>
|
@ -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([
|
||||
features: (
|
||||
<Select
|
||||
content={[
|
||||
{ label: 'Enter', value: 'Enter' },
|
||||
{ label: 'Ctrl + Enter', value: 'Ctrl+Enter' }
|
||||
])
|
||||
]}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '主题',
|
||||
features: Select([
|
||||
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} />
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -14,11 +14,13 @@
|
||||
<div class="h-1px bg-[--line-color]"></div>
|
||||
|
||||
<!-- 设置的主体内容 -->
|
||||
<div class="flex flex-1 shadow-inner p-20px">
|
||||
<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 content.system" :key="index">
|
||||
<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>
|
||||
@ -28,10 +30,12 @@
|
||||
<component :is="item.features" />
|
||||
</n-flex>
|
||||
|
||||
<div v-if="index !== content.system.length - 1" class="h-1px bg-[--line-color]"></div>
|
||||
<div v-if="index !== key.length - 1" class="h-1px bg-[--line-color]"></div>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</div>
|
||||
</n-flex>
|
||||
</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>
|
||||
|
@ -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}>
|
||||
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={`#${icon}`}></use>
|
||||
<use href={`#${props.icon}`}></use>
|
||||
</svg>
|
||||
<p class={'text-12px'}>{title}</p>
|
||||
</NFlex>
|
||||
) : (
|
||||
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'] }
|
||||
)
|
||||
|
@ -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>
|
||||
|
||||
<!-- 二维码 -->
|
||||
|
Loading…
Reference in New Issue
Block a user