update at 2025-10-14 21:52:11
This commit is contained in:
755
web/src/components/DataManager.vue
Normal file
755
web/src/components/DataManager.vue
Normal file
@@ -0,0 +1,755 @@
|
||||
<template>
|
||||
<div class="data-manager-page">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<div class="header-info">
|
||||
<h1>数据管理</h1>
|
||||
<p>管理对话历史、数据源和系统数据</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<n-button @click="exportData">
|
||||
<template #icon>
|
||||
<n-icon :component="DownloadIcon" />
|
||||
</template>
|
||||
导出数据
|
||||
</n-button>
|
||||
<n-button type="error" @click="showClearModal = true">
|
||||
<template #icon>
|
||||
<n-icon :component="TrashIcon" />
|
||||
</template>
|
||||
清理数据
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-cards">
|
||||
<n-card class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<n-icon :component="MessageIcon" size="24" color="#18a058" />
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ conversations.length }}</div>
|
||||
<div class="stat-label">对话数量</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<n-card class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<n-icon :component="DatabaseIcon" size="24" color="#2080f0" />
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ getTotalMessages() }}</div>
|
||||
<div class="stat-label">消息总数</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<n-card class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<n-icon :component="ClockIcon" size="24" color="#f0a020" />
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ getStorageSize() }}</div>
|
||||
<div class="stat-label">存储使用</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<n-card class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<n-icon :component="CalendarIcon" size="24" color="#d03050" />
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ getLastActiveDate() }}</div>
|
||||
<div class="stat-label">最后活动</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
|
||||
<!-- 对话历史列表 -->
|
||||
<div class="conversations-section">
|
||||
<div class="section-header">
|
||||
<h2>对话历史</h2>
|
||||
<div class="section-actions">
|
||||
<n-input
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="搜索对话..."
|
||||
clearable
|
||||
style="width: 240px"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon :component="SearchIcon" />
|
||||
</template>
|
||||
</n-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="filteredConversations.length === 0" class="empty-state">
|
||||
<div class="empty-icon">
|
||||
<n-icon :component="MessageIcon" size="48" />
|
||||
</div>
|
||||
<h3>{{ searchKeyword ? '未找到匹配的对话' : '暂无对话历史' }}</h3>
|
||||
<p>{{ searchKeyword ? '尝试使用其他关键词搜索' : '开始一个新对话吧' }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="conversations-list">
|
||||
<n-card
|
||||
v-for="conv in filteredConversations"
|
||||
:key="conv.id"
|
||||
class="conversation-card"
|
||||
hoverable
|
||||
>
|
||||
<div class="conversation-header">
|
||||
<div class="conversation-info">
|
||||
<h3 class="conversation-title">{{ conv.title || '未命名对话' }}</h3>
|
||||
<div class="conversation-meta">
|
||||
<span class="meta-item">
|
||||
<n-icon :component="MessageIcon" size="14" />
|
||||
{{ conv.messages.length }} 条消息
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<n-icon :component="ClockIcon" size="14" />
|
||||
{{ formatDate(conv.updatedAt) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conversation-actions">
|
||||
<n-button text @click="viewConversation(conv)">
|
||||
<n-icon :component="EyeIcon" size="18" />
|
||||
</n-button>
|
||||
<n-button text @click="exportConversation(conv)">
|
||||
<n-icon :component="DownloadIcon" size="18" />
|
||||
</n-button>
|
||||
<n-button text @click="deleteConversation(conv)">
|
||||
<n-icon :component="TrashIcon" size="18" color="#d03050" />
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="conversation-preview">
|
||||
<p>{{ getConversationPreview(conv) }}</p>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查看对话详情弹窗 -->
|
||||
<n-modal
|
||||
v-model:show="showViewModal"
|
||||
preset="card"
|
||||
title="对话详情"
|
||||
style="width: 800px; max-height: 80vh"
|
||||
>
|
||||
<div v-if="selectedConversation" class="conversation-detail">
|
||||
<div class="detail-header">
|
||||
<h3>{{ selectedConversation.title || '未命名对话' }}</h3>
|
||||
<div class="detail-meta">
|
||||
<span>创建时间: {{ formatDate(selectedConversation.createdAt) }}</span>
|
||||
<span>更新时间: {{ formatDate(selectedConversation.updatedAt) }}</span>
|
||||
<span>消息数: {{ selectedConversation.messages.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="messages-list">
|
||||
<div
|
||||
v-for="(msg, index) in selectedConversation.messages"
|
||||
:key="index"
|
||||
class="message-item"
|
||||
:class="msg.role"
|
||||
>
|
||||
<div class="message-header">
|
||||
<span class="message-role">{{ getRoleLabel(msg.role) }}</span>
|
||||
<span class="message-time">{{ formatTime(msg.timestamp) }}</span>
|
||||
</div>
|
||||
<div class="message-content">{{ msg.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-modal>
|
||||
|
||||
<!-- 清理数据确认弹窗 -->
|
||||
<n-modal
|
||||
v-model:show="showClearModal"
|
||||
preset="dialog"
|
||||
title="确认清理数据"
|
||||
positive-text="确认清理"
|
||||
negative-text="取消"
|
||||
@positive-click="handleClearData"
|
||||
>
|
||||
<div class="clear-options">
|
||||
<p>请选择要清理的数据类型:</p>
|
||||
<n-checkbox-group v-model:value="clearOptions">
|
||||
<n-space vertical>
|
||||
<n-checkbox value="conversations" label="对话历史" />
|
||||
<n-checkbox value="tools" label="工具配置" />
|
||||
<n-checkbox value="services" label="模型服务" />
|
||||
<n-checkbox value="settings" label="系统设置" />
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
<n-alert type="warning" style="margin-top: 16px">
|
||||
此操作不可恢复,请谨慎操作!建议先导出数据备份。
|
||||
</n-alert>
|
||||
</div>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import {
|
||||
NCard,
|
||||
NButton,
|
||||
NIcon,
|
||||
NModal,
|
||||
NInput,
|
||||
NCheckbox,
|
||||
NCheckboxGroup,
|
||||
NSpace,
|
||||
NAlert,
|
||||
useMessage
|
||||
} from 'naive-ui'
|
||||
import {
|
||||
Download as DownloadIcon,
|
||||
Trash as TrashIcon,
|
||||
Message as MessageIcon,
|
||||
Database as DatabaseIcon,
|
||||
Clock as ClockIcon,
|
||||
Calendar as CalendarIcon,
|
||||
Search as SearchIcon,
|
||||
Eye as EyeIcon
|
||||
} from '@vicons/tabler'
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
// 消息接口
|
||||
interface Message {
|
||||
role: 'user' | 'assistant' | 'system'
|
||||
content: string
|
||||
timestamp: Date
|
||||
}
|
||||
|
||||
// 对话接口
|
||||
interface Conversation {
|
||||
id: string
|
||||
title: string
|
||||
messages: Message[]
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
// 响应式数据
|
||||
const conversations = ref<Conversation[]>([])
|
||||
const searchKeyword = ref('')
|
||||
const showViewModal = ref(false)
|
||||
const showClearModal = ref(false)
|
||||
const selectedConversation = ref<Conversation | null>(null)
|
||||
const clearOptions = ref<string[]>([])
|
||||
|
||||
// 过滤后的对话列表
|
||||
const filteredConversations = computed(() => {
|
||||
if (!searchKeyword.value) {
|
||||
return conversations.value
|
||||
}
|
||||
|
||||
const keyword = searchKeyword.value.toLowerCase()
|
||||
return conversations.value.filter(conv => {
|
||||
return (
|
||||
conv.title.toLowerCase().includes(keyword) ||
|
||||
conv.messages.some(msg => msg.content.toLowerCase().includes(keyword))
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// 获取总消息数
|
||||
const getTotalMessages = (): number => {
|
||||
return conversations.value.reduce((total, conv) => total + conv.messages.length, 0)
|
||||
}
|
||||
|
||||
// 获取存储大小
|
||||
const getStorageSize = (): string => {
|
||||
try {
|
||||
let totalSize = 0
|
||||
for (let key in localStorage) {
|
||||
if (localStorage.hasOwnProperty(key)) {
|
||||
totalSize += localStorage[key].length + key.length
|
||||
}
|
||||
}
|
||||
const sizeInKB = totalSize / 1024
|
||||
if (sizeInKB < 1024) {
|
||||
return `${sizeInKB.toFixed(2)} KB`
|
||||
}
|
||||
return `${(sizeInKB / 1024).toFixed(2)} MB`
|
||||
} catch {
|
||||
return '未知'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取最后活动日期
|
||||
const getLastActiveDate = (): string => {
|
||||
if (conversations.value.length === 0) {
|
||||
return '无'
|
||||
}
|
||||
|
||||
const latest = conversations.value.reduce((latest, conv) => {
|
||||
return new Date(conv.updatedAt) > new Date(latest.updatedAt) ? conv : latest
|
||||
})
|
||||
|
||||
return formatDate(latest.updatedAt)
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (date: Date | string): string => {
|
||||
const d = new Date(date)
|
||||
const now = new Date()
|
||||
const diffMs = now.getTime() - d.getTime()
|
||||
const diffMins = Math.floor(diffMs / 60000)
|
||||
const diffHours = Math.floor(diffMs / 3600000)
|
||||
const diffDays = Math.floor(diffMs / 86400000)
|
||||
|
||||
if (diffMins < 1) return '刚刚'
|
||||
if (diffMins < 60) return `${diffMins} 分钟前`
|
||||
if (diffHours < 24) return `${diffHours} 小时前`
|
||||
if (diffDays < 7) return `${diffDays} 天前`
|
||||
|
||||
return d.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (date: Date | string | undefined): string => {
|
||||
if (!date) return ''
|
||||
const d = new Date(date)
|
||||
return d.toLocaleTimeString('zh-CN', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取角色标签
|
||||
const getRoleLabel = (role: string): string => {
|
||||
const labels: Record<string, string> = {
|
||||
user: '用户',
|
||||
assistant: '助手',
|
||||
system: '系统'
|
||||
}
|
||||
return labels[role] || role
|
||||
}
|
||||
|
||||
// 获取对话预览
|
||||
const getConversationPreview = (conv: Conversation): string => {
|
||||
if (conv.messages.length === 0) {
|
||||
return '暂无消息'
|
||||
}
|
||||
|
||||
const lastMsg = conv.messages[conv.messages.length - 1]
|
||||
const preview = lastMsg.content.substring(0, 100)
|
||||
return preview.length < lastMsg.content.length ? `${preview}...` : preview
|
||||
}
|
||||
|
||||
// 查看对话
|
||||
const viewConversation = (conv: Conversation) => {
|
||||
selectedConversation.value = conv
|
||||
showViewModal.value = true
|
||||
}
|
||||
|
||||
// 导出单个对话
|
||||
const exportConversation = (conv: Conversation) => {
|
||||
try {
|
||||
const data = JSON.stringify(conv, null, 2)
|
||||
const blob = new Blob([data], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `conversation_${conv.id}_${Date.now()}.json`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
message.success('对话已导出')
|
||||
} catch (error) {
|
||||
message.error('导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 删除对话
|
||||
const deleteConversation = (conv: Conversation) => {
|
||||
const index = conversations.value.findIndex(c => c.id === conv.id)
|
||||
if (index !== -1) {
|
||||
conversations.value.splice(index, 1)
|
||||
saveConversations()
|
||||
message.success('对话已删除')
|
||||
}
|
||||
}
|
||||
|
||||
// 导出所有数据
|
||||
const exportData = () => {
|
||||
try {
|
||||
const data = {
|
||||
conversations: conversations.value,
|
||||
tools: JSON.parse(localStorage.getItem('mcp-tools') || '[]'),
|
||||
services: JSON.parse(localStorage.getItem('model-services') || '[]'),
|
||||
settings: JSON.parse(localStorage.getItem('mcp-settings') || '{}'),
|
||||
exportDate: new Date().toISOString()
|
||||
}
|
||||
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `mcp_backup_${Date.now()}.json`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
message.success('数据已导出')
|
||||
} catch (error) {
|
||||
message.error('导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 清理数据
|
||||
const handleClearData = () => {
|
||||
if (clearOptions.value.length === 0) {
|
||||
message.warning('请选择要清理的数据类型')
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
clearOptions.value.forEach(option => {
|
||||
switch (option) {
|
||||
case 'conversations':
|
||||
localStorage.removeItem('mcp-conversations')
|
||||
conversations.value = []
|
||||
break
|
||||
case 'tools':
|
||||
localStorage.removeItem('mcp-tools')
|
||||
break
|
||||
case 'services':
|
||||
localStorage.removeItem('model-services')
|
||||
break
|
||||
case 'settings':
|
||||
localStorage.removeItem('mcp-settings')
|
||||
localStorage.removeItem('display-settings')
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
message.success('数据已清理')
|
||||
clearOptions.value = []
|
||||
return true
|
||||
} catch (error) {
|
||||
message.error('清理失败')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存对话
|
||||
const saveConversations = () => {
|
||||
try {
|
||||
localStorage.setItem('mcp-conversations', JSON.stringify(conversations.value))
|
||||
} catch (error) {
|
||||
console.error('保存对话失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载对话
|
||||
const loadConversations = () => {
|
||||
try {
|
||||
const saved = localStorage.getItem('mcp-conversations')
|
||||
if (saved) {
|
||||
conversations.value = JSON.parse(saved)
|
||||
} else {
|
||||
// 加载示例数据(首次使用)
|
||||
loadSampleData()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载对话失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载示例数据
|
||||
const loadSampleData = () => {
|
||||
const sampleConversations: Conversation[] = [
|
||||
{
|
||||
id: 'sample_1',
|
||||
title: '欢迎使用 MCP 客户端',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: '欢迎使用 MCP 客户端!这是一个示例对话。',
|
||||
timestamp: new Date(Date.now() - 86400000)
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: '你好,这是我的第一条消息',
|
||||
timestamp: new Date(Date.now() - 86400000 + 1000)
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
content: '你好!很高兴见到你。我是 AI 助手,我可以帮助你完成各种任务。',
|
||||
timestamp: new Date(Date.now() - 86400000 + 2000)
|
||||
}
|
||||
],
|
||||
createdAt: new Date(Date.now() - 86400000),
|
||||
updatedAt: new Date(Date.now() - 86400000 + 2000)
|
||||
}
|
||||
]
|
||||
|
||||
conversations.value = sampleConversations
|
||||
saveConversations()
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadConversations()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.data-manager-page {
|
||||
padding: 24px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.header-info h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.header-info p {
|
||||
font-size: 14px;
|
||||
color: var(--text-color-3);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stats-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-target-bg);
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
color: var(--text-color-3);
|
||||
}
|
||||
|
||||
.conversations-section {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 80px 20px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
color: var(--border-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
font-size: 18px;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
color: var(--text-color-3);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.conversations-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.conversation-card {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.conversation-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.conversation-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.conversation-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.conversation-meta {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
font-size: 13px;
|
||||
color: var(--text-color-3);
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.conversation-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.conversation-preview {
|
||||
color: var(--text-color-2);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.conversation-preview p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.conversation-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.detail-header h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.detail-meta {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
font-size: 13px;
|
||||
color: var(--text-color-3);
|
||||
}
|
||||
|
||||
.messages-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: var(--color-target-bg);
|
||||
}
|
||||
|
||||
.message-item.user {
|
||||
background: rgba(24, 160, 88, 0.1);
|
||||
}
|
||||
|
||||
.message-item.assistant {
|
||||
background: rgba(32, 128, 240, 0.1);
|
||||
}
|
||||
|
||||
.message-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.message-role {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
color: var(--text-color-3);
|
||||
}
|
||||
|
||||
.message-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.clear-options {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.clear-options p {
|
||||
margin: 0 0 16px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user