update: 更新样式和主题配置,新增设置管理,优化聊天界面,支持历史会话和背景信息展示

This commit is contained in:
Lexcubia 2025-04-20 19:46:51 +08:00
parent ce52a06ee7
commit 0601a3a166
17 changed files with 1327 additions and 198 deletions

1
.npmrc Normal file
View File

@ -0,0 +1 @@
registry=https://registry.npmmirror.com/

View File

@ -25,6 +25,7 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"sass": "^1.86.3",
"sass-embedded": "^1.86.3",
"vite": "^6.2.0"
}

View File

@ -0,0 +1,128 @@
<template>
<el-dialog
v-model="dialogVisible"
width="500px"
:close-on-click-modal="true"
destroy-on-close
>
<div class="background-info-content" v-if="currentMode">
<div v-html="formattedBackground"></div>
</div>
<div v-else class="no-mode">
暂无背景信息
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">关闭</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { computed } from 'vue'
import { useChatStore } from '@/store/chat'
import { marked } from 'marked'
const props = defineProps({
modelValue: {
type: Boolean,
required: true
}
})
const emit = defineEmits(['update:modelValue'])
const chatStore = useChatStore()
const dialogVisible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
const currentMode = computed(() => {
return chatStore.chatModes[chatStore.currentMode]
})
const hasBackground = computed(() => {
return currentMode.value?.background?.trim() !== ''
})
const formattedBackground = computed(() => {
if (!currentMode.value?.background) return ''
marked.setOptions({
headerIds: false,
mangle: false
})
return marked.parse(currentMode.value.background)
})
defineExpose({
hasBackground
})
const handleClose = () => {
dialogVisible.value = false
}
</script>
<style lang="scss" scoped>
.background-info-content {
padding: 0 20px;
:deep(h1) {
font-size: 20px;
font-weight: 600;
margin-bottom: 16px;
color: #333;
}
:deep(h2) {
font-size: 18px;
font-weight: 600;
margin: 24px 0 16px;
color: #333;
}
:deep(h3) {
font-size: 16px;
font-weight: 600;
margin: 20px 0 12px;
color: #333;
}
:deep(p) {
margin-bottom: 12px;
line-height: 1.6;
color: #666;
}
:deep(ul), :deep(ol) {
margin-bottom: 12px;
padding-left: 20px;
li {
margin-bottom: 8px;
line-height: 1.6;
color: #666;
}
}
:deep(strong) {
font-weight: 600;
color: #333;
}
}
.no-mode {
text-align: center;
color: #999;
padding: 20px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
margin-top: 20px;
}
</style>

View File

@ -0,0 +1,177 @@
<template>
<el-dropdown trigger="hover" @command="handleHistorySelect">
<el-button
:class="['history-btn', { 'icon-only': iconOnly }]"
:icon="Document"
text
bg
>
<span v-if="!iconOnly">历史会话</span>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<template v-if="currentModeHistory.length">
<el-dropdown-item
v-for="chat in currentModeHistory"
:key="chat.conversationId"
:command="chat.conversationId"
class="history-item"
>
<div class="history-content">
<span class="history-title">{{ formatChatTitle(chat) }}</span>
<el-button
class="delete-btn"
:icon="Delete"
link
@click.stop="handleDelete(chat.conversationId)"
/>
</div>
</el-dropdown-item>
</template>
<el-dropdown-item v-else disabled>暂无历史会话</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup>
import { computed } from 'vue'
import { useChatStore } from '@/store/chat'
import { Document, Delete } from '@element-plus/icons-vue'
import { ElMessageBox } from 'element-plus'
const props = defineProps({
iconOnly: {
type: Boolean,
default: false
}
})
const chatStore = useChatStore()
const currentModeHistory = computed(() => {
return chatStore.conversations.filter(chat => chat.mode === chatStore.currentMode)
})
const formatChatTitle = (chat) => {
if (chat.messages && chat.messages.length > 0) {
const firstMessage = chat.messages[0].question
return firstMessage.length > 20 ? firstMessage.substring(0, 20) + '...' : firstMessage
}
return `会话 ${chat.conversationId.substring(0, 8)}`
}
const handleHistorySelect = (conversationId) => {
chatStore.switchConversation(conversationId)
}
const handleDelete = async (conversationId) => {
try {
await ElMessageBox.confirm(
'确定要删除这个会话吗?删除后无法恢复。',
'删除确认',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
confirmButtonClass: 'el-button--danger'
}
)
chatStore.deleteConversation(conversationId)
} catch {
//
}
}
</script>
<style lang="scss" scoped>
.history-btn {
height: 32px;
padding: 4px 12px;
color: inherit;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 4px;
border: none;
background: transparent !important;
&.icon-only {
width: 32px;
padding: 0;
}
&:hover {
color: #07c160 !important;
background: transparent !important;
}
&:focus {
outline: none;
box-shadow: none;
}
:deep(.el-icon) {
font-size: 16px;
}
}
:deep(.el-dropdown-menu__item) {
font-size: 13px;
padding: 8px 16px;
line-height: 1.4;
max-width: 300px;
overflow-x: hidden;
&:hover .delete-btn {
opacity: 1;
transform: translateX(0);
}
}
:deep(.el-dropdown-menu) {
min-width: 200px;
max-width: 300px;
}
.history-content {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
position: relative;
min-width: 0;
}
.history-title {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 24px;
min-width: 0;
}
.delete-btn {
position: absolute;
right: -8px;
opacity: 0;
transform: translateX(10px);
transition: all 0.3s ease;
height: 24px;
width: 24px;
padding: 0;
color: #f56c6c;
flex-shrink: 0;
&:hover {
color: #f56c6c;
background-color: rgba(245, 108, 108, 0.1);
}
:deep(.el-icon) {
font-size: 14px;
}
}
</style>

View File

@ -0,0 +1,90 @@
<template>
<el-dropdown
trigger="click"
@command="handleCommand"
class="mode-setting"
>
<el-button
class="setting-btn"
:icon="Setting"
text
bg
@click.stop
/>
<template #dropdown>
<el-dropdown-menu>
<slot>
<!-- 默认选项 -->
<el-dropdown-item command="edit">
<el-icon><Edit /></el-icon>
编辑设置
</el-dropdown-item>
<el-dropdown-item command="reset">
<el-icon><RefreshRight /></el-icon>
重置设置
</el-dropdown-item>
</slot>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup>
import { Setting, Edit, RefreshRight } from '@element-plus/icons-vue'
const emit = defineEmits(['command'])
const handleCommand = (command) => {
emit('command', command)
}
</script>
<style lang="scss" scoped>
.mode-setting {
.setting-btn {
height: 24px;
width: 24px;
padding: 0;
color: inherit;
opacity: 0;
transform: translateX(10px);
transition: all 0.3s ease;
border: none;
background: transparent !important;
&:hover {
background-color: transparent !important;
}
&:focus {
outline: none;
box-shadow: none;
}
:deep(.el-icon) {
font-size: 16px;
}
}
:deep(.el-dropdown-menu__item) {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
padding: 8px 16px;
.el-icon {
margin-right: 4px;
}
}
}
//
.mode-item.active {
.setting-btn {
&:hover {
background-color: rgba(255, 255, 255, 0.2);
}
}
}
</style>

View File

@ -7,7 +7,8 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
// 引入 Element Plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// 使用自定义主题
import './styles/element-variables.scss'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
const pinia = createPinia();

View File

@ -15,6 +15,7 @@ const routes = [
component: () => import("@/layouts/blank.vue"),
},
],
redirect: "/chat"
},
{
path: "/chat",

View File

@ -4,18 +4,43 @@ import { sendMessage } from '@/api/chat'
const chatModes = {
training: {
name: '寿险代理人AI陪练',
icon: 'fas fa-user-tie',
token: 'app-88ae2GN49aUyNO6qGg7tbTfX'
icon: '👨‍💼',
token: 'app-88ae2GN49aUyNO6qGg7tbTfX',
background: `# 客户信息
### **基础背景与性格设定**
**姓名**张伟
**年龄**34
**职业**腾讯员工
**家庭**妻子为全职太太女儿 9 双方父母健在
**性格**冷静理性务实偏好客观数据和逻辑分析对保险持观望态度防备心较强
**近期状态**
- 体检显示中度脂肪肝担心投保问题
- 已为全家配置百万医疗险妻子有重疾险但认为保障不足 保费 50W
- 对理财型保险收益不认可但对补充重疾险有潜在需求 `,
chatBackground: '你通过你的老同学顾先生介绍,和一位中国人寿的保险代理人约在你家中首次面谈。你的家中布置简洁,茶几上摆放着一套茶具。',
},
quote_objection: {
name: '报价中异议',
icon: 'fas fa-comments',
token: 'app-88ae2GN49aUyNO6qGg7tbTfX'
icon: '💬',
token: 'app-88ae2GN49aUyNO6qGg7tbTfX',
background: '',
chatBackground: '',
},
post_quote_objection: {
name: '报价后异议',
icon: 'fas fa-comment-dollar',
token: 'app-88ae2GN49aUyNO6qGg7tbTfX'
icon: '💰',
token: 'app-88ae2GN49aUyNO6qGg7tbTfX',
background: '',
chatBackground: '',
}
}
// 返回示例
@ -180,9 +205,10 @@ export const useChatStore = defineStore('chat', {
conversationId: message.conversation_id,
messageId: message.message_id,
createdAt: message.created_at,
createdAtTimestamp: '',
question: question,
answerBuffer: '',
showEvaluation: true,
showEvaluation: false,
respondingType: '',
kehu: '',
pingfen: '',

View File

@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
export const useRouteStore = defineStore('route', {
state: () => ({
currentRoute: '/'
currentRoute: '/chat'
}),
actions: {
updateCurrentRoute(path) {

56
src/store/settings.js Normal file
View File

@ -0,0 +1,56 @@
import { defineStore } from 'pinia'
export const useSettingsStore = defineStore('settings', {
state: () => ({
// 侧边栏状态
sidebarCollapsed: false,
// 界面配置
interface: {
// 是否显示时间戳
showTimestamp: true,
// 消息气泡最大宽度(px)
messageMaxWidth: 800,
// 字体大小(px)
fontSize: 14
},
// 主题配置
theme: {
// 主色调
primary: '#07c160',
// 背景色
background: '#f5f5f5',
// 文字颜色
textColor: '#333333'
}
}),
actions: {
// 切换侧边栏状态
toggleSidebar() {
this.sidebarCollapsed = !this.sidebarCollapsed
},
// 更新界面配置
updateInterface(config) {
this.interface = {
...this.interface,
...config
}
},
// 更新主题配置
updateTheme(config) {
this.theme = {
...this.theme,
...config
}
},
// 重置所有设置
resetSettings() {
this.$reset()
}
}
})

View File

@ -3,7 +3,7 @@
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color-scheme: light only;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;

View File

@ -0,0 +1,5 @@
/* 自定义主题变量 */
$--color-primary: #07c160;
/* 导入 Element Plus 样式 */
@use "element-plus/theme-chalk/src/index.scss" as *;

View File

@ -0,0 +1,249 @@
<template>
<div class="chat-header">
<div class="header-content">
<div class="title">
<span class="title-text">{{ title }}</span>
<el-tag v-if="chatStore.currentConversation?.conversationStatus === 'typing'"
size="small"
class="status-tag"
type="warning"
>
对话中
</el-tag>
<HistoryButton v-if="settingsStore.sidebarCollapsed" :icon-only="false" class="title-history-btn" />
</div>
<div class="actions">
<el-tooltip content="删除会话" placement="bottom">
<el-button
class="action-btn delete-btn"
:icon="Delete"
@click="handleDelete"
text
>
</el-button>
</el-tooltip>
<el-tooltip content="设置" placement="bottom">
<el-button
class="action-btn"
:icon="Setting"
@click="handleSettings"
text
>
</el-button>
</el-tooltip>
<el-tooltip :content="hasBackground ? '客户背景' : '暂无客户背景信息'" placement="bottom">
<el-button
class="action-btn"
:icon="User"
@click="showUserInfo = true"
text
:disabled="!hasBackground"
>
</el-button>
</el-tooltip>
</div>
</div>
<CustomerBackground v-model="showUserInfo" ref="customerBackgroundRef" />
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useChatStore } from '@/store/chat'
import { useSettingsStore } from '@/store/settings'
import { Delete, Setting, User } from '@element-plus/icons-vue'
import { ElMessageBox } from 'element-plus'
import HistoryButton from '@/components/chat/HistoryButton.vue'
import CustomerBackground from '@/components/chat/CustomerBackground.vue'
const chatStore = useChatStore()
const settingsStore = useSettingsStore()
const showUserInfo = ref(false)
const customerBackgroundRef = ref(null)
const title = computed(() => {
const conversation = chatStore.currentConversation
if (!conversation) return '新会话'
// 使
if (conversation.chatMessages && conversation.chatMessages.length > 0) {
return conversation.chatMessages[0].question
}
return '新会话'
})
const hasBackground = computed(() => {
return customerBackgroundRef.value?.hasBackground ?? false
})
const handleDelete = async () => {
console.log(chatStore.conversationId)
if (!chatStore.conversationId) return
try {
await ElMessageBox.confirm(
'确定要删除当前会话吗?删除后无法恢复。',
'删除确认',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
confirmButtonClass: 'el-button--danger'
}
)
chatStore.deleteConversation(chatStore.conversationId)
} catch {
//
}
}
const handleSettings = () => {
// TODO:
console.log('打开设置')
}
</script>
<style lang="scss" scoped>
.chat-header {
height: 60px;
border-bottom: 1px solid #e5e5e5;
background: #fff;
padding: 0 20px;
flex-shrink: 0;
.header-content {
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.title {
display: flex;
align-items: center;
gap: 12px;
.title-text {
font-size: 16px;
font-weight: 500;
color: #333;
max-width: 500px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.status-tag {
font-weight: normal;
flex-shrink: 0;
}
.title-history-btn {
:deep(.history-btn) {
height: 32px;
padding: 0 12px;
color: #666;
transition: all 0.3s ease;
font-size: 14px;
&:hover {
color: #07c160 !important;
}
:deep(.el-icon) {
font-size: 16px;
margin-right: 4px;
}
}
}
}
.actions {
display: flex;
align-items: center;
gap: 4px;
.action-btn {
height: 32px;
width: 32px;
padding: 0;
color: #666;
transition: all 0.3s ease;
border: none;
background: transparent !important;
font-size: 14px;
&:hover {
color: #07c160;
background: transparent !important;
}
&.delete-btn {
&:hover {
color: var(--el-color-danger) !important;
}
}
&:focus {
outline: none;
box-shadow: none;
}
:deep(.el-icon) {
font-size: 16px;
}
}
}
}
.user-info-content {
padding: 20px;
.info-section {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
h3 {
font-size: 16px;
font-weight: 500;
color: #333;
margin: 0 0 12px 0;
padding-bottom: 8px;
border-bottom: 1px solid #eee;
}
.info-item {
display: flex;
margin-bottom: 8px;
line-height: 1.6;
&:last-child {
margin-bottom: 0;
}
&.description {
flex-direction: column;
gap: 8px;
}
.label {
color: #666;
min-width: 80px;
flex-shrink: 0;
}
.value {
color: #333;
flex: 1;
}
}
}
}
.dialog-footer {
padding-top: 20px;
}
</style>

View File

@ -1,6 +1,18 @@
<template>
<div class="chat-container">
<ChatHeader />
<div class="chat-messages" ref="chatContainer">
<!-- 聊天背景按钮 -->
<div v-if="chatStore.chatModes[chatStore.currentMode]?.chatBackground" class="chat-background">
<div class="background-toggle" @click="toggleBackground">
<span class="background-btn">聊天背景</span>
</div>
<div v-if="showBackground" class="background-section">
<div class="background-content">
<div class="background-text" v-html="formatMarkdown(chatStore.chatModes[chatStore.currentMode].chatBackground)"></div>
</div>
</div>
</div>
<!-- 历史缓存 -->
<div v-for="(message, index) in currentMessages" :key="message.messageId" class="message-group">
<!-- 用户消息 -->
@ -9,12 +21,15 @@
<div class="avatar"><span class="emoji">👤</span></div>
<div class="message-content">
<div class="message-text-wrapper">
<!-- <div v-if="message.createdAt" class="message-timestamp">
{{ formatTimestamp(message.createdAtTimestamp) }}
</div> -->
<div class="message-text">{{ message.question }}</div>
</div>
<!-- 改进建议部分 -->
<div class="evaluation-toggle" @click="chatStore.toggleEvaluation(message.messageId)">
<span class="evaluation-btn">改进建议</span>
<span v-if="['pingfen', 'zongjie'].includes(message.respondingType)" class="typing-indicator evaluation-icon">
<span v-if="['pingfen', 'zongjie'].includes(message?.respondingType)" class="typing-indicator evaluation-icon">
<span></span><span></span><span></span>
</span>
<span v-else class="evaluation-icon">{{ message.showEvaluation ? '▼' : '▶' }}</span>
@ -24,7 +39,7 @@
<div class="evaluation-content">
<div class="evaluation-text pingfen" v-html="formatMarkdown(message.pingfen, 'pingfen')"></div>
<div class="evaluation-text zongjie" v-html="formatMarkdown(message.zongjie, 'zongjie')"></div>
<div v-if="['pingfen', 'zongjie'].includes(message.respondingType)" class="typing-indicator">
<div v-if="['pingfen', 'zongjie'].includes(message?.respondingType)" class="typing-indicator">
<span></span><span></span><span></span>
</div>
</div>
@ -38,6 +53,9 @@
<div class="avatar"><span class="emoji">🤖</span></div>
<div class="message-content">
<div class="message-text-wrapper">
<!-- <div v-if="message.createdAt" class="message-timestamp">
{{ formatTimestamp(message.createdAtTimestamp) }}
</div> -->
<div class="message-text" v-html="message.kehu"></div>
<div v-if="message.respondingType === 'kehu'" class="typing-indicator">
<span></span><span></span><span></span>
@ -47,6 +65,10 @@
</div>
</div>
</div>
<!-- 会话结束标签 -->
<div v-if="chatStore.currentConversation?.conversationStatus === 'finished'" class="conversation-end">
<el-tag size="large" type="info" class="end-tag">会话已结束</el-tag>
</div>
</div>
<div class="input-area">
<button class="new-chat-btn" @click="chatStore.startNewChat" :disabled="newChatButtonsDisabled">
@ -61,13 +83,17 @@
rows="1"
ref="messageInputRef"
@input="adjustTextareaHeight"
:disabled="isInputDisabled"
></textarea>
<button class="send-btn" @click="sendMessage" :disabled="sendButtonsDisabled">发送</button>
<button class="send-btn" @click="sendMessage" :disabled="isInputDisabled || !messageInput.trim()">发送</button>
</div>
<!-- 添加总结弹窗 -->
<!-- 总结弹窗 -->
<div v-if="lastMessageWithDafen" class="summary-panel" :class="{ 'summary-panel-collapsed': isSummaryCollapsed }">
<div class="summary-header" @click="toggleSummary">
<span class="summary-title">会话总结</span>
<span v-if="['dafen'].includes(message?.respondingType)" class="typing-indicator evaluation-icon">
<span></span><span></span><span></span>
</span>
<span class="summary-toggle-icon">{{ isSummaryCollapsed ? '▼' : '▲' }}</span>
</div>
<div class="summary-content" v-html="formatMarkdown(lastMessageWithDafen.dafen)"></div>
@ -80,6 +106,8 @@ import { ref, onMounted, onUnmounted, nextTick, watch, computed, defineOptions }
import { marked } from 'marked'
import * as echarts from 'echarts'
import { useChatStore } from '@/store/chat'
import ChatHeader from '@/views/chat/ChatHeader.vue'
import { ElTag } from 'element-plus'
defineOptions({
name: 'ChatInterface'
@ -96,6 +124,8 @@ const shouldAutoScroll = ref(true)
const lastScrollTop = ref(0)
const lastScrollHeight = ref(0)
const isSummaryCollapsed = ref(false)
const showBackgroundDialog = ref(false)
const showBackground = ref(false)
// 使
const currentMessages = computed(() => {
@ -217,62 +247,6 @@ const sendMessage = () => {
scrollToBottom(true)
}
const initChart = () => {
if (scoreChart.value) {
const chart = echarts.init(scoreChart.value)
const scoreData = chatStore.score.split(',').map(item => {
const [name, value] = item.split(':')
return { name, value: parseFloat(value) }
})
const option = {
title: {
text: '成绩维度分析',
left: 'center'
},
tooltip: {
trigger: 'item'
},
radar: {
indicator: scoreData.map(item => ({
name: item.name,
max: 100
}))
},
series: [{
type: 'radar',
data: [{
value: scoreData.map(item => item.value),
name: '成绩',
areaStyle: {
color: 'rgba(76, 175, 80, 0.3)'
},
lineStyle: {
color: '#4CAF50'
},
itemStyle: {
color: '#4CAF50'
}
}]
}]
}
chart.setOption(option)
const handleResize = () => {
chart.resize()
}
window.addEventListener('resize', handleResize)
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
chart.dispose()
})
}
}
//
const formatMarkdown = (text, tagType) => {
if (!text) return ''
@ -290,6 +264,29 @@ const toggleSummary = () => {
isSummaryCollapsed.value = !isSummaryCollapsed.value
}
const formatTimestamp = (timestamp) => {
console.log('%c timestamp:', 'color: #2196F3; font-weight: bold', timestamp)
if (!timestamp) return ''
const date = new Date(timestamp)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
})
}
const isInputDisabled = computed(() => {
return chatStore.currentConversation?.conversationStatus === 'finished'
})
const toggleBackground = () => {
showBackground.value = !showBackground.value
}
onMounted(() => {
if (chatContainer.value) {
chatContainer.value.addEventListener('scroll', handleScroll)
@ -298,9 +295,6 @@ onMounted(() => {
shouldAutoScroll.value = true
}
if (chatStore.score && scoreChart.value) {
initChart()
}
})
onUnmounted(() => {
@ -322,6 +316,120 @@ onUnmounted(() => {
overflow: hidden;
}
.chat-background {
margin: 0 0 20px 0;
display: flex;
flex-direction: column;
align-items: center;
}
.background-toggle {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.background-btn {
font-size: 12px;
padding: 4px 12px;
border-radius: 12px;
background-color: #f4f4f5;
border: 1px solid #e9e9eb;
color: #909399;
transition: all 0.3s ease;
&:hover {
background-color: #e9e9eb;
color: #606266;
}
}
}
.background-section {
margin-top: 12px;
width: 90%;
max-width: 800px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 0, 0, 0.05);
.background-content {
padding: 16px 24px 16px 16px;
background: var(--el-color-primary-light-9);
max-height: 300px;
overflow-y: auto;
border-radius: 8px;
&::-webkit-scrollbar {
width: 8px;
height: 8px;
display: block;
}
&::-webkit-scrollbar-track {
background: transparent;
margin: 4px 0;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 4px;
border: 2px solid transparent;
background-clip: padding-box;
&:hover {
background: rgba(0, 0, 0, 0.3);
}
}
.background-text {
opacity: 0.9;
color: #495057;
line-height: 1.6;
:deep(p) {
margin: 0 0 12px 0;
line-height: 1.6;
&:last-child {
margin-bottom: 0;
}
}
:deep(ul), :deep(ol) {
margin: 8px 0;
padding-left: 20px;
}
:deep(strong) {
color: #2c3e50;
font-weight: 600;
}
:deep(h1) {
font-size: 20px;
font-weight: 600;
margin: 0 0 16px 0;
color: #333;
}
:deep(h2) {
font-size: 18px;
font-weight: 600;
margin: 20px 0 12px 0;
color: #333;
}
:deep(h3) {
font-size: 16px;
font-weight: 600;
margin: 16px 0 8px 0;
color: #333;
}
}
}
}
.chat-messages {
flex: 1;
overflow-y: scroll;
@ -332,7 +440,7 @@ onUnmounted(() => {
position: relative;
z-index: 1;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch; // iOS
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
width: 8px;
@ -385,6 +493,7 @@ onUnmounted(() => {
justify-content: flex-start;
.message-text {
background-color: #ffffff;
color: #333333;
}
.message-content {
display: flex;
@ -427,6 +536,7 @@ onUnmounted(() => {
.message-text {
min-height: 36px;
min-width: 60px;
line-height: 1.5;
font-size: 16px;
padding: 8px 12px;
@ -606,6 +716,13 @@ onUnmounted(() => {
border-color: #07c160;
box-shadow: 0 0 0 2px rgba(7, 193, 96, 0.1);
}
&:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
color: #999;
border-color: #e0e0e0;
}
}
button {
@ -621,7 +738,9 @@ onUnmounted(() => {
border: 1px solid #ddd;
white-space: nowrap;
&:disabled {
background: #ccc;
background: #f5f5f5 !important;
border-color: #e0e0e0;
color: #999;
cursor: not-allowed;
}
}
@ -691,14 +810,22 @@ onUnmounted(() => {
/* 添加媒体查询以适应不同屏幕 */
@media screen and (max-width: 768px) {
.message-wrapper {
max-width: 90%;
.message-content {
max-width: 85%;
}
.message .message-wrapper.user {
padding-left: 10%;
}
}
@media screen and (max-width: 480px) {
.message-wrapper {
max-width: 95%;
.message-content {
max-width: 90%;
}
.message .message-wrapper.user {
padding-left: 5%;
}
}
@ -873,4 +1000,63 @@ onUnmounted(() => {
}
}
}
.message-timestamp {
font-size: 12px;
color: #999;
position: absolute;
top: -20px;
opacity: 0;
transition: opacity 0.2s ease;
}
.message-text-wrapper {
position: relative;
display: inline-block;
max-width: 100%;
&:hover {
.message-timestamp {
opacity: 1;
}
}
.message-text {
min-height: 36px;
line-height: 1.5;
font-size: 16px;
padding: 8px 12px;
border-radius: 4px;
white-space: pre-wrap;
position: relative;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(0, 0, 0, 0.05);
display: inline-block;
max-width: 100%;
&::before {
content: '';
position: absolute;
width: 0;
height: 0;
border: 6px solid transparent;
top: 10px;
}
}
}
.conversation-end {
display: flex;
justify-content: center;
margin: 20px 0;
.end-tag {
font-size: 12px;
padding: 4px 12px;
border-radius: 12px;
background-color: #f4f4f5;
border-color: #e9e9eb;
color: #909399;
}
}
</style>

View File

@ -1,35 +1,17 @@
<template>
<div class="mode-selector">
<div class="mode-selector" :class="{ 'collapsed': settingsStore.sidebarCollapsed }">
<div class="mode-header">
<h3 class="mode-title">对话场景</h3>
<el-dropdown trigger="hover" @command="handleHistorySelect">
<el-button class="history-btn" :icon="Document" text bg>
历史会话
</el-button>
<template #dropdown>
<el-dropdown-menu>
<template v-if="currentModeHistory.length">
<el-dropdown-item
v-for="chat in currentModeHistory"
:key="chat.conversationId"
:command="chat.conversationId"
class="history-item"
>
<div class="history-content">
<span class="history-title">{{ formatChatTitle(chat) }}</span>
<div class="header-actions">
<HistoryButton v-if="!settingsStore.sidebarCollapsed" :icon-only="true" />
<el-button
class="delete-btn"
:icon="Delete"
link
@click.stop="handleDelete(chat.conversationId)"
class="collapse-btn"
:icon="settingsStore.sidebarCollapsed ? Expand : Fold"
text
bg
@click="settingsStore.toggleSidebar"
/>
</div>
</el-dropdown-item>
</template>
<el-dropdown-item v-else disabled>暂无历史会话</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="mode-list">
<div
@ -38,8 +20,22 @@
:class="['mode-item', { active: chatStore.currentMode === mode.id }]"
@click="selectMode(mode.id)"
>
<i :class="mode.icon"></i>
<span>{{ mode.name }}</span>
<span class="emoji-icon">{{ mode.icon }}</span>
<span v-if="!settingsStore.sidebarCollapsed" class="mode-name">{{ mode.name }}</span>
<ModeSetting v-if="!settingsStore.sidebarCollapsed" @command="(cmd) => handleModeSettings(cmd, mode.id)">
<el-dropdown-item command="edit">
<el-icon><EditPen /></el-icon>
编辑设置
</el-dropdown-item>
<el-dropdown-item command="reset">
<el-icon><RefreshRight /></el-icon>
重置设置
</el-dropdown-item>
<el-dropdown-item command="delete" divided>
<el-icon><Delete /></el-icon>
删除场景
</el-dropdown-item>
</ModeSetting>
</div>
</div>
</div>
@ -47,11 +43,15 @@
<script setup>
import { useChatStore } from '@/store/chat'
import { computed, ref } from 'vue'
import { Document, Delete } from '@element-plus/icons-vue'
import { useSettingsStore } from '@/store/settings'
import { computed } from 'vue'
import { Delete, EditPen, RefreshRight, Expand, Fold } from '@element-plus/icons-vue'
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElButton, ElMessageBox } from 'element-plus'
import ModeSetting from '@/components/chat/ModeSetting.vue'
import HistoryButton from '@/components/chat/HistoryButton.vue'
const chatStore = useChatStore()
const settingsStore = useSettingsStore()
const chatModes = computed(() => {
return Object.keys(chatStore.chatModes).map(key => ({
@ -100,6 +100,23 @@ const handleDelete = async (conversationId) => {
//
}
}
const handleModeSettings = (command, modeId) => {
switch (command) {
case 'edit':
// TODO:
console.log('编辑设置:', modeId)
break
case 'reset':
// TODO:
console.log('重置设置:', modeId)
break
case 'delete':
// TODO:
console.log('删除场景:', modeId)
break
}
}
</script>
<style lang="scss" scoped>
@ -119,22 +136,56 @@ $hover-color-dark: #2d2d2d;
background: $background-color;
border-right: 1px solid $border-color;
height: 100%;
padding: 20px 0;
padding: 0;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&.collapsed {
width: 64px;
min-width: 64px;
.mode-title {
display: none;
}
.mode-item {
padding: 15px;
justify-content: center;
.mode-name {
display: none;
}
}
.header-actions {
width: 100%;
justify-content: center;
}
}
.mode-header {
padding: 0 20px;
margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
height: 60px;
border-bottom: 1px solid #e5e5e5;
.header-actions {
display: flex;
align-items: center;
gap: 8px;
}
.mode-title {
color: $text-color;
font-size: 16px;
font-weight: bold;
font-weight: 500;
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.history-btn {
@ -143,15 +194,33 @@ $hover-color-dark: #2d2d2d;
padding: 4px 12px;
color: $text-color;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 4px;
&:hover {
color: #07c160;
background: rgba(7, 193, 96, 0.1);
}
:deep(.el-icon) {
font-size: 16px;
}
}
.collapse-btn {
font-size: 14px;
height: 32px;
width: 32px;
padding: 0;
color: $text-color;
transition: all 0.3s ease;
border: none !important;
background: transparent !important;
&:hover {
color: $primary-color;
background: rgba($primary-color, 0.1);
}
&:focus-visible {
outline: none;
box-shadow: none;
background: transparent !important;
}
&:focus {
@ -161,12 +230,12 @@ $hover-color-dark: #2d2d2d;
:deep(.el-icon) {
font-size: 16px;
margin-right: 4px;
}
}
}
.mode-list {
padding: 0 0 20px 0;
display: flex;
flex-direction: column;
gap: 5px;
@ -180,9 +249,15 @@ $hover-color-dark: #2d2d2d;
cursor: pointer;
transition: all 0.3s ease;
color: $text-color;
position: relative;
&:hover {
background: $hover-color;
:deep(.setting-btn) {
opacity: 1;
transform: translateX(0);
}
}
&.active {
@ -190,14 +265,27 @@ $hover-color-dark: #2d2d2d;
color: white;
}
i {
font-size: 18px;
.emoji-icon {
font-size: 20px;
width: 24px;
text-align: center;
line-height: 1;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
span {
.mode-name {
font-size: 14px;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
:deep(.mode-setting) {
margin-left: auto;
}
}
}
@ -261,37 +349,37 @@ $hover-color-dark: #2d2d2d;
}
}
@media (prefers-color-scheme: dark) {
.mode-selector {
background: $background-color-dark;
border-right-color: $border-color-dark;
// @media (prefers-color-scheme: dark) {
// .mode-selector {
// background: $background-color-dark;
// border-right-color: $border-color-dark;
.mode-title {
color: $text-color-dark;
}
// .mode-title {
// color: $text-color-dark;
// }
.mode-item {
color: $text-color-dark;
// .mode-item {
// color: $text-color-dark;
&:hover {
background: $hover-color-dark;
}
}
// &:hover {
// background: $hover-color-dark;
// }
// }
.history-btn {
color: $text-color-dark;
// .history-btn {
// color: $text-color-dark;
&:hover {
color: $primary-color;
background: rgba($primary-color, 0.15);
}
}
}
// &:hover {
// color: $primary-color;
// background: rgba($primary-color, 0.15);
// }
// }
// }
.delete-btn {
&:hover {
background-color: rgba(245, 108, 108, 0.15);
}
}
}
// .delete-btn {
// &:hover {
// background-color: rgba(245, 108, 108, 0.15);
// }
// }
// }
</style>

View File

@ -24,6 +24,13 @@ export default defineConfig(({ command, mode }) => {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
css: {
preprocessorOptions: {
scss: {
additionalData: ``
}
}
},
server: {
proxy: {
'/api': {

199
yarn.lock
View File

@ -246,6 +246,95 @@
unimport "^4.1.3"
untyped "^2.0.0"
"@parcel/watcher-android-arm64@2.5.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1"
integrity sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==
"@parcel/watcher-darwin-arm64@2.5.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz#3d26dce38de6590ef79c47ec2c55793c06ad4f67"
integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==
"@parcel/watcher-darwin-x64@2.5.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz#99f3af3869069ccf774e4ddfccf7e64fd2311ef8"
integrity sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==
"@parcel/watcher-freebsd-x64@2.5.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz#14d6857741a9f51dfe51d5b08b7c8afdbc73ad9b"
integrity sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==
"@parcel/watcher-linux-arm-glibc@2.5.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz#43c3246d6892381db473bb4f663229ad20b609a1"
integrity sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==
"@parcel/watcher-linux-arm-musl@2.5.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz#663750f7090bb6278d2210de643eb8a3f780d08e"
integrity sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==
"@parcel/watcher-linux-arm64-glibc@2.5.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz#ba60e1f56977f7e47cd7e31ad65d15fdcbd07e30"
integrity sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==
"@parcel/watcher-linux-arm64-musl@2.5.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz#f7fbcdff2f04c526f96eac01f97419a6a99855d2"
integrity sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==
"@parcel/watcher-linux-x64-glibc@2.5.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz#4d2ea0f633eb1917d83d483392ce6181b6a92e4e"
integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==
"@parcel/watcher-linux-x64-musl@2.5.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz#277b346b05db54f55657301dd77bdf99d63606ee"
integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==
"@parcel/watcher-win32-arm64@2.5.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz#7e9e02a26784d47503de1d10e8eab6cceb524243"
integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==
"@parcel/watcher-win32-ia32@2.5.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz#2d0f94fa59a873cdc584bf7f6b1dc628ddf976e6"
integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==
"@parcel/watcher-win32-x64@2.5.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz#ae52693259664ba6f2228fa61d7ee44b64ea0947"
integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==
"@parcel/watcher@^2.4.1":
version "2.5.1"
resolved "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.1.tgz#342507a9cfaaf172479a882309def1e991fb1200"
integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==
dependencies:
detect-libc "^1.0.3"
is-glob "^4.0.3"
micromatch "^4.0.5"
node-addon-api "^7.0.0"
optionalDependencies:
"@parcel/watcher-android-arm64" "2.5.1"
"@parcel/watcher-darwin-arm64" "2.5.1"
"@parcel/watcher-darwin-x64" "2.5.1"
"@parcel/watcher-freebsd-x64" "2.5.1"
"@parcel/watcher-linux-arm-glibc" "2.5.1"
"@parcel/watcher-linux-arm-musl" "2.5.1"
"@parcel/watcher-linux-arm64-glibc" "2.5.1"
"@parcel/watcher-linux-arm64-musl" "2.5.1"
"@parcel/watcher-linux-x64-glibc" "2.5.1"
"@parcel/watcher-linux-x64-musl" "2.5.1"
"@parcel/watcher-win32-arm64" "2.5.1"
"@parcel/watcher-win32-ia32" "2.5.1"
"@parcel/watcher-win32-x64" "2.5.1"
"@popperjs/core@npm:@sxzz/popperjs-es@^2.11.7":
version "2.11.7"
resolved "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz#a7f69e3665d3da9b115f9e71671dae1b97e13671"
@ -431,29 +520,29 @@
integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==
"@vue/devtools-api@^7.7.2":
version "7.7.2"
resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.2.tgz#49837eae6f61fc43a09f5d6c2d3210f9f73a0d09"
integrity sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==
version "7.7.5"
resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.5.tgz#1e6c3d72c1a77419c1940bc94ee12d2949334aaf"
integrity sha512-HYV3tJGARROq5nlVMJh5KKHk7GU8Au3IrrmNNqr978m0edxgpHgYPDoNUGrvEgIbObz09SQezFR3A1EVmB5WZg==
dependencies:
"@vue/devtools-kit" "^7.7.2"
"@vue/devtools-kit" "^7.7.5"
"@vue/devtools-kit@^7.7.2":
version "7.7.2"
resolved "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz#3315bd5b144f98c7b84c2f44270b445644ec8f10"
integrity sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==
"@vue/devtools-kit@^7.7.5":
version "7.7.5"
resolved "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.5.tgz#2992fbf793064b302a324d423b35e9a85c0903f5"
integrity sha512-S9VAVJYVAe4RPx2JZb9ZTEi0lqTySz2CBeF0wHT5D3dkTLnT9yMMGegKNl4b2EIELwLSkcI9bl2qp0/jW+upqA==
dependencies:
"@vue/devtools-shared" "^7.7.2"
birpc "^0.2.19"
"@vue/devtools-shared" "^7.7.5"
birpc "^2.3.0"
hookable "^5.5.3"
mitt "^3.0.1"
perfect-debounce "^1.0.0"
speakingurl "^14.0.1"
superjson "^2.2.1"
superjson "^2.2.2"
"@vue/devtools-shared@^7.7.2":
version "7.7.2"
resolved "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz#b11b143820130a32d8ce5737e264d06ab6d62f40"
integrity sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==
"@vue/devtools-shared@^7.7.5":
version "7.7.5"
resolved "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.5.tgz#0be847df75d72ff7e6be05a1581abeade7edc31e"
integrity sha512-QBjG72RfpM0DKtpns2RZOxBltO226kOAls9e4Lri6YxS2gWTgL0H+wj1R2K76lxxIeOrqo4+2Ty6RQnzv+WSTQ==
dependencies:
rfdc "^1.4.1"
@ -541,10 +630,10 @@ axios@^1.8.4:
form-data "^4.0.0"
proxy-from-env "^1.1.0"
birpc@^0.2.19:
version "0.2.19"
resolved "https://registry.npmmirror.com/birpc/-/birpc-0.2.19.tgz#cdd183a4a70ba103127d49765b4a71349da5a0ca"
integrity sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==
birpc@^2.3.0:
version "2.3.0"
resolved "https://registry.npmmirror.com/birpc/-/birpc-2.3.0.tgz#e5a402dc785ef952a2383ef3cfc075e0842f3e8c"
integrity sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==
braces@^3.0.3:
version "3.0.3"
@ -584,7 +673,7 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
es-errors "^1.3.0"
function-bind "^1.1.2"
chokidar@^4.0.3:
chokidar@^4.0.0, chokidar@^4.0.3:
version "4.0.3"
resolved "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30"
integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==
@ -662,6 +751,11 @@ destr@^2.0.3:
resolved "https://registry.npmmirror.com/destr/-/destr-2.0.5.tgz#7d112ff1b925fb8d2079fac5bdb4a90973b51fdb"
integrity sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
dotenv@^16.4.7:
version "16.5.0"
resolved "https://registry.npmmirror.com/dotenv/-/dotenv-16.5.0.tgz#092b49f25f808f020050051d1ff258e404c78692"
@ -685,9 +779,9 @@ echarts@^5.6.0:
zrender "5.6.1"
element-plus@^2.9.7:
version "2.9.7"
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.7.tgz#05bcc35de1d98192d25ebfd06fff7d6d2de9f911"
integrity sha512-6vjZh5SXBncLhUwJGTVKS5oDljfgGMh6J4zVTeAZK3YdMUN76FgpvHkwwFXocpJpMbii6rDYU3sgie64FyPerQ==
version "2.9.8"
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.8.tgz#72aea48dfcd5ef70a1859d2c8a16d189451f4afb"
integrity sha512-srViUaUdfblBKGMeuEPiXxxKlH5aUmKqEwmhb/At9Sj91DbU6od/jYN1955cTnzt3wTSA7GfnZF7UiRX9sdRHg==
dependencies:
"@ctrl/tinycolor" "^3.4.1"
"@element-plus/icons-vue" "^2.3.1"
@ -796,9 +890,9 @@ estree-walker@^3.0.3:
"@types/estree" "^1.0.0"
exsolve@^1.0.1, exsolve@^1.0.4:
version "1.0.4"
resolved "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.4.tgz#7de5c75af82ecd15998328fbf5f2295883be3a39"
integrity sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==
version "1.0.5"
resolved "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.5.tgz#1f5b6b4fe82ad6b28a173ccb955a635d77859dcf"
integrity sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==
fast-glob@^3.3.3:
version "3.3.3"
@ -818,10 +912,10 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
fdir@^6.4.3:
version "6.4.3"
resolved "https://registry.npmmirror.com/fdir/-/fdir-6.4.3.tgz#011cdacf837eca9b811c89dbb902df714273db72"
integrity sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==
fdir@^6.4.3, fdir@^6.4.4:
version "6.4.4"
resolved "https://registry.npmmirror.com/fdir/-/fdir-6.4.4.tgz#1cfcf86f875a883e19a8fab53622cfe992e8d2f9"
integrity sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==
fill-range@^7.1.1:
version "7.1.1"
@ -959,7 +1053,7 @@ is-extglob@^2.1.1:
resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-glob@^4.0.1:
is-glob@^4.0.1, is-glob@^4.0.3:
version "4.0.3"
resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
@ -1047,7 +1141,7 @@ merge2@^1.3.0:
resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
micromatch@^4.0.8:
micromatch@^4.0.5, micromatch@^4.0.8:
version "4.0.8"
resolved "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
@ -1087,6 +1181,11 @@ nanoid@^3.3.8:
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
node-addon-api@^7.0.0:
version "7.1.1"
resolved "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
node-fetch-native@^1.6.6:
version "1.6.6"
resolved "https://registry.npmmirror.com/node-fetch-native/-/node-fetch-native-1.6.6.tgz#ae1d0e537af35c2c0b0de81cbff37eedd410aa37"
@ -1225,7 +1324,7 @@ rfdc@^1.4.1:
resolved "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca"
integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==
rollup@^4.30.1:
rollup@^4.34.9:
version "4.40.0"
resolved "https://registry.npmmirror.com/rollup/-/rollup-4.40.0.tgz#13742a615f423ccba457554f006873d5a4de1920"
integrity sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==
@ -1403,6 +1502,17 @@ sass-embedded@^1.86.3:
sass-embedded-win32-ia32 "1.86.3"
sass-embedded-win32-x64 "1.86.3"
sass@^1.86.3:
version "1.86.3"
resolved "https://registry.npmmirror.com/sass/-/sass-1.86.3.tgz#0a0d9ea97cb6665e73f409639f8533ce057464c9"
integrity sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==
dependencies:
chokidar "^4.0.0"
immutable "^5.0.2"
source-map-js ">=0.6.2 <2.0.0"
optionalDependencies:
"@parcel/watcher" "^2.4.1"
scule@^1.3.0:
version "1.3.0"
resolved "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz#6efbd22fd0bb801bdcc585c89266a7d2daa8fbd3"
@ -1418,7 +1528,7 @@ slash@^5.1.0:
resolved "https://registry.npmmirror.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce"
integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==
source-map-js@^1.2.0, source-map-js@^1.2.1:
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.0, source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
@ -1440,7 +1550,7 @@ strip-literal@^3.0.0:
dependencies:
js-tokens "^9.0.1"
superjson@^2.2.1:
superjson@^2.2.2:
version "2.2.2"
resolved "https://registry.npmmirror.com/superjson/-/superjson-2.2.2.tgz#9d52bf0bf6b5751a3c3472f1292e714782ba3173"
integrity sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==
@ -1472,11 +1582,11 @@ tinyexec@^0.3.2:
integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==
tinyglobby@^0.2.12:
version "0.2.12"
resolved "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.12.tgz#ac941a42e0c5773bd0b5d08f32de82e74a1a61b5"
integrity sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==
version "0.2.13"
resolved "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.13.tgz#a0e46515ce6cbcd65331537e57484af5a7b2ff7e"
integrity sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==
dependencies:
fdir "^6.4.3"
fdir "^6.4.4"
picomatch "^4.0.2"
to-regex-range@^5.0.1:
@ -1570,13 +1680,16 @@ varint@^6.0.0:
integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==
vite@^6.2.0:
version "6.2.6"
resolved "https://registry.npmmirror.com/vite/-/vite-6.2.6.tgz#7f0ccf2fdc0c1eda079ce258508728e2473d3f61"
integrity sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==
version "6.3.2"
resolved "https://registry.npmmirror.com/vite/-/vite-6.3.2.tgz#4c1bb01b1cea853686a191657bbc14272a038f0a"
integrity sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==
dependencies:
esbuild "^0.25.0"
fdir "^6.4.3"
picomatch "^4.0.2"
postcss "^8.5.3"
rollup "^4.30.1"
rollup "^4.34.9"
tinyglobby "^0.2.12"
optionalDependencies:
fsevents "~2.3.3"