mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
update: mobile 历史记录标题优化,聊天详情参数记录折叠处理
This commit is contained in:
@@ -15,7 +15,7 @@ export interface ChatSessionsResponse {
|
||||
// 聊天消息
|
||||
export interface ChatMessage {
|
||||
createdAt: string
|
||||
chatType: 1 | 2 // 1是用户,2是AI
|
||||
chatType: 1 | 2 | 3 // 1是用户,2是AI,3是参数说明
|
||||
content: string
|
||||
audioId: string | null
|
||||
macAddress: string
|
||||
|
||||
@@ -61,6 +61,7 @@ const loading = ref(false)
|
||||
// 音频播放相关
|
||||
const audioContext = ref<UniApp.InnerAudioContext | null>(null)
|
||||
const playingAudioId = ref<string | null>(null)
|
||||
const expandedToolResults = ref({})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
@@ -124,8 +125,50 @@ function getSpeakerName(message: ChatMessage): string {
|
||||
|
||||
// 格式化时间
|
||||
function formatTime(timeStr: string) {
|
||||
const date = new Date(timeStr)
|
||||
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||||
if (!timeStr)
|
||||
return t('chatHistory.unknownTime')
|
||||
|
||||
// 处理时间字符串,确保格式正确
|
||||
const date = new Date(timeStr.replace(' ', 'T')) // 转换为ISO格式
|
||||
const now = new Date()
|
||||
|
||||
// 检查日期是否有效
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return timeStr // 如果解析失败,直接返回原字符串
|
||||
}
|
||||
|
||||
const diff = now.getTime() - date.getTime()
|
||||
|
||||
// 小于1分钟
|
||||
if (diff < 60000)
|
||||
return t('chatHistory.justNow')
|
||||
|
||||
// 小于1小时
|
||||
if (diff < 3600000)
|
||||
return t('chatHistory.minutesAgo', { minutes: Math.floor(diff / 60000) })
|
||||
|
||||
// 小于1天(24小时)
|
||||
if (diff < 86400000)
|
||||
return t('chatHistory.hoursAgo', { hours: Math.floor(diff / 3600000) })
|
||||
|
||||
// 小于7天
|
||||
if (diff < 604800000) {
|
||||
const days = Math.floor(diff / 86400000)
|
||||
return t('chatHistory.daysAgo', { days })
|
||||
}
|
||||
|
||||
// 超过7天,显示具体日期
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const currentYear = now.getFullYear()
|
||||
|
||||
// 如果是当前年份,不显示年份
|
||||
if (year === currentYear) {
|
||||
return `${month}-${day}`
|
||||
}
|
||||
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
// 播放音频
|
||||
@@ -192,6 +235,52 @@ const playAudio = debounce(async (audioId: string) => {
|
||||
}
|
||||
}, 400)
|
||||
|
||||
function extractContentFromString(content: string) {
|
||||
if (!content || content.trim() === '') {
|
||||
return content
|
||||
}
|
||||
|
||||
// 尝试解析为 JSON
|
||||
try {
|
||||
const jsonObj = JSON.parse(content)
|
||||
|
||||
// 如果是数组格式(包含 text 和 tool)
|
||||
if (Array.isArray(jsonObj)) {
|
||||
return jsonObj
|
||||
}
|
||||
|
||||
// 如果是对象且有 content 字段
|
||||
if (jsonObj && typeof jsonObj === 'object' && jsonObj.content) {
|
||||
return jsonObj.content
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// 如果不是有效的 JSON,直接返回原内容
|
||||
}
|
||||
|
||||
// 如果不是 JSON 格式或没有 content 字段,直接返回原内容
|
||||
return content
|
||||
}
|
||||
|
||||
function toggleToolResult(messageIndex, itemIndex) {
|
||||
const key = `${messageIndex}-${itemIndex}`
|
||||
expandedToolResults.value[key] = !expandedToolResults.value[key]
|
||||
}
|
||||
|
||||
function isToolResultCollapsed(messageIndex, itemIndex) {
|
||||
const key = `${messageIndex}-${itemIndex}`
|
||||
// 默认折叠(true表示折叠)
|
||||
return !expandedToolResults.value[key]
|
||||
}
|
||||
|
||||
function getFirstLineText(text: string) {
|
||||
if (!text) {
|
||||
return ''
|
||||
}
|
||||
const firstLine = text.split('\n')[0]
|
||||
return firstLine.length < text.length ? `${firstLine}...` : text
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
if (options?.sessionId && options?.agentId) {
|
||||
sessionId.value = options.sessionId
|
||||
@@ -256,6 +345,7 @@ onUnload(() => {
|
||||
:class="{
|
||||
'items-end': message.chatType === 1,
|
||||
'items-start': message.chatType === 2,
|
||||
'tool-message': message.chatType === 3,
|
||||
}"
|
||||
>
|
||||
<!-- 消息气泡 -->
|
||||
@@ -263,11 +353,39 @@ onUnload(() => {
|
||||
class="shadow-message break-words rounded-[20rpx] p-[24rpx] leading-[1.4]"
|
||||
:class="{
|
||||
'bg-[#336cff] text-white': message.chatType === 1,
|
||||
'bg-white text-[#232338] border border-[#eeeeee]': message.chatType === 2,
|
||||
'bg-white text-[#232338] border border-[#eeeeee]': [2, 3].includes(message.chatType),
|
||||
}"
|
||||
>
|
||||
<template v-if="Array.isArray(extractContentFromString(message.content))">
|
||||
<div class="content-wrapper">
|
||||
<div v-for="(item, idx) in extractContentFromString(message.content)" :key="idx">
|
||||
<div v-if="item.type === 'text'" class="text-content">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
<div v-else-if="item.type === 'tool'" class="tool-call-text">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
<div v-else-if="item.type === 'tool_result'" class="tool-call-text">
|
||||
<div v-if="item.text && item.text.length > 80" class="tool-result-wrapper">
|
||||
<div v-if="isToolResultCollapsed(index, idx)" class="tool-result-collapsed">
|
||||
{{ getFirstLineText(item.text) }}
|
||||
</div>
|
||||
<div v-else class="tool-result-expanded">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
<span class="tool-toggle-btn" @click="toggleToolResult(index, idx)">
|
||||
<wd-icon :name="isToolResultCollapsed(index, idx) ? 'arrow-down' : 'arrow-up'" size="12" />
|
||||
</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ item.text }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 内容区域 - 使用flex布局让图标和文本对齐 -->
|
||||
<view class="flex items-center gap-[12rpx]">
|
||||
<view v-else class="flex items-center gap-[12rpx]">
|
||||
<!-- 音频播放图标 -->
|
||||
<view
|
||||
v-if="message.audioId"
|
||||
@@ -334,4 +452,46 @@ onUnload(() => {
|
||||
.animate-pulse-audio {
|
||||
animation: pulse-audio 1.5s infinite;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.tool-call-text {
|
||||
color: #1890ff;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-weight: 500;
|
||||
font-size: 24rpx;
|
||||
display: block;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.user-message .tool-call-text {
|
||||
color: #e6f7ff;
|
||||
}
|
||||
|
||||
.tool-message .message-content {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.tool-result-wrapper {
|
||||
position: relative;
|
||||
padding-right: 40rpx;
|
||||
}
|
||||
|
||||
.tool-result-collapsed {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tool-toggle-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
color: #1890ff;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,15 +8,15 @@ defineOptions({
|
||||
name: 'ChatHistory',
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
agentId: 'default',
|
||||
})
|
||||
|
||||
// 接收props
|
||||
interface Props {
|
||||
agentId?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
agentId: 'default'
|
||||
})
|
||||
|
||||
// 获取屏幕边界到安全区域距离
|
||||
let safeAreaInsets: any
|
||||
let systemInfo: any
|
||||
@@ -187,8 +187,8 @@ defineExpose({
|
||||
<view v-if="loading && sessionList.length === 0" class="loading-container">
|
||||
<wd-loading color="#336cff" />
|
||||
<text class="loading-text">
|
||||
{{ t('chatHistory.loading') }}
|
||||
</text>
|
||||
{{ t('chatHistory.loading') }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 会话列表 -->
|
||||
@@ -205,7 +205,7 @@ defineExpose({
|
||||
<view class="session-info">
|
||||
<view class="session-header">
|
||||
<text class="session-title">
|
||||
{{ t('chatHistory.conversationRecord') }} {{ session.sessionId.substring(0, 8) }}...
|
||||
{{ session.title || `${t('chatHistory.conversationRecord')} ${session.sessionId.substring(0, 8)}...` }}
|
||||
</text>
|
||||
<text class="session-time">
|
||||
{{ formatTime(session.createdAt) }}
|
||||
@@ -242,11 +242,11 @@ defineExpose({
|
||||
<view v-else-if="!loading" class="empty-state">
|
||||
<wd-icon name="chat" custom-class="empty-icon" />
|
||||
<text class="empty-text">
|
||||
{{ t('chatHistory.noChatRecords') }}
|
||||
</text>
|
||||
<text class="empty-desc">
|
||||
{{ t('chatHistory.chatRecordsDescription') }}
|
||||
</text>
|
||||
{{ t('chatHistory.noChatRecords') }}
|
||||
</text>
|
||||
<text class="empty-desc">
|
||||
{{ t('chatHistory.chatRecordsDescription') }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -333,7 +333,7 @@ defineExpose({
|
||||
padding: 32rpx;
|
||||
|
||||
.session-info {
|
||||
flex: 1;
|
||||
width: 94%;
|
||||
|
||||
.session-header {
|
||||
display: flex;
|
||||
@@ -345,8 +345,11 @@ defineExpose({
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #232338;
|
||||
max-width: 70%;
|
||||
width: 70%;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.session-time {
|
||||
|
||||
Reference in New Issue
Block a user