feat: 完成角色卡,智能体配置,加载,历史记录,清空等功能

This commit is contained in:
2025-11-08 21:01:58 +08:00
parent 82546c4381
commit 71ffa0a816
11 changed files with 3156 additions and 116 deletions

View File

@@ -14,19 +14,22 @@
<!-- 自定义导航栏 -->
<view class="custom-navbar">
<view class="navbar-left" @tap="goBack" v-if="showBackButton">
<text class="back-icon"></text>
<text class="back-text">返回</text>
<view class="navbar-left">
<view v-if="showBackButton" @tap="goBack" class="left-btn">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<text class="clear-btn" @tap="clearContext">清空</text>
</view>
<view class="navbar-center" :class="{'centered': !showBackButton}">
<image class="character-avatar" :src="currentCharacter.avatar" mode="aspectFill" />
<text class="character-name">{{ currentCharacter.name }}</text>
</view>
<view class="navbar-right">
<text class="status-dot" :class="{'online': isOnline}"></text>
</view>
</view>
<!-- 聊天消息区域 - 使用 Ant Design X Bubble.List -->
<scroll-view
class="chat-messages"
@@ -257,16 +260,23 @@ const initPage = async () => {
currentCharacter.value = {
id: 'wei-ai',
name: options.characterName || '蔚AI',
avatar: options.characterAvatar || '/static/logo.png',
avatar: options.characterAvatar || '/static/avatar/icon_hushi.jpg',
greeting: decodeURIComponent(options.introMessage || '你好我是蔚AI很高兴为您服务')
};
await loadAIConfigs();
await createNewConversation('wei-ai');
addMessage('ai', currentCharacter.value.greeting);
createNewConversation('wei-ai');
if (!isLoggedIn.value) {
addMessage('system', '您当前处于未登录状态将使用本地模拟回复。登录后可享受完整的AI对话功能。');
// 尝试加载历史消息
await loadHistoryMessages();
// 如果没有历史消息,显示欢迎消息
if (messages.value.length === 0) {
addMessage('ai', currentCharacter.value.greeting);
if (!isLoggedIn.value) {
addMessage('system', '您当前处于未登录状态将使用本地模拟回复。登录后可享受完整的AI对话功能。');
}
}
}
// AI角色
@@ -295,11 +305,18 @@ const initPage = async () => {
currentTemplateId.value = parseInt(options.templateId);
}
await createNewConversation(options.roleId);
addMessage('ai', currentCharacter.value.greeting);
createNewConversation(options.roleId);
if (!isLoggedIn.value) {
addMessage('system', '您当前处于未登录状态将使用本地模拟回复。登录后可享受完整的AI对话功能。');
// 尝试加载历史消息
await loadHistoryMessages();
// 如果没有历史消息,显示欢迎消息
if (messages.value.length === 0) {
addMessage('ai', currentCharacter.value.greeting);
if (!isLoggedIn.value) {
addMessage('system', '您当前处于未登录状态将使用本地模拟回复。登录后可享受完整的AI对话功能。');
}
}
}
// 默认角色
@@ -309,11 +326,18 @@ const initPage = async () => {
if (character) {
currentCharacter.value = character;
await loadAIConfigs();
await createNewConversation(characterId);
addMessage('ai', character.greeting);
createNewConversation(characterId);
if (!isLoggedIn.value) {
addMessage('system', '您当前处于未登录状态将使用本地模拟回复。登录后可享受完整的AI对话功能。');
// 尝试加载历史消息
await loadHistoryMessages();
// 如果没有历史消息,显示欢迎消息
if (messages.value.length === 0) {
addMessage('ai', character.greeting);
if (!isLoggedIn.value) {
addMessage('system', '您当前处于未登录状态将使用本地模拟回复。登录后可享受完整的AI对话功能。');
}
}
}
}
@@ -461,17 +485,115 @@ const sendMessage = async () => {
}
};
// 创建新对话
const createNewConversation = async (characterId) => {
// 创建或获取会话ID基于角色持久化存储
const createNewConversation = (characterId) => {
// 生成存储key根据角色类型区分
let storageKey = '';
if (characterId === 'wei-ai' || !currentCharacter.value.roleId) {
// 蔚AI或默认角色
storageKey = `session_weiai`;
} else {
// 剧情角色
storageKey = `session_role_${currentCharacter.value.roleId}`;
}
// 尝试从本地存储获取已有的sessionId
let existingSessionId = uni.getStorageSync(storageKey);
if (existingSessionId) {
// 已有sessionId直接使用保持上下文
conversationId.value = existingSessionId;
console.log('使用已有sessionId:', existingSessionId, 'storageKey:', storageKey);
} else {
// 生成新的sessionId
const userId = userStore.userInfo?.openid || userStore.userInfo?.userId || 'guest';
const timestamp = Date.now();
const newSessionId = `session_${characterId}_${userId}_${timestamp}`;
// 存储到本地
uni.setStorageSync(storageKey, newSessionId);
conversationId.value = newSessionId;
console.log('创建新sessionId:', newSessionId, 'storageKey:', storageKey);
}
};
// 加载历史消息
const loadHistoryMessages = async () => {
if (!conversationId.value || !isLoggedIn.value) {
console.log('没有sessionId或用户未登录跳过加载历史消息');
return;
}
try {
const result = await chatAPI.createConversation(characterId);
if (result.success) {
conversationId.value = result.data.conversationId;
console.log('开始加载历史消息sessionId:', conversationId.value);
const result = await chatAPI.getHistoryMessages(conversationId.value);
if (result.success && result.data && result.data.length > 0) {
console.log('获取到历史消息:', result.data.length, '条');
// 将后端消息格式转换为前端格式保持与API回复一致的“清理后回复”逻辑
const historyMessages = [];
result.data.forEach(msg => {
// 格式化时间
let timeStr = '';
if (msg.createTime) {
const date = new Date(msg.createTime);
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
timeStr = `${hours}:${minutes}`;
}
const messageType = msg.sender === 'assistant' ? 'ai' : (msg.sender === 'user' ? 'user' : 'system');
const rawContent = msg.message || '';
// 与API一致先清理文本再根据 & 分段
const cleanedContent = cleanText(rawContent);
if (messageType === 'ai' && cleanedContent.includes('&')) {
const segments = cleanedContent
.split('&')
.map(s => s.trim())
.filter(Boolean);
segments.forEach(segment => {
historyMessages.push({
type: messageType,
content: segment,
time: timeStr
});
});
} else {
// 非 AI 或无分隔符,直接使用清理后的内容
const content = cleanedContent.trim();
if (content) {
historyMessages.push({
type: messageType,
content,
time: timeStr
});
}
}
});
// 按时间排序(从旧到新)
historyMessages.sort((a, b) => {
if (!a.time || !b.time) return 0;
return a.time.localeCompare(b.time);
});
// 清空当前消息列表,加载历史消息
messages.value = historyMessages;
console.log('历史消息加载完成,共', messages.value.length, '条');
// 滚动到底部
await nextTick();
scrollToBottom();
} else {
conversationId.value = `local_${Date.now()}`;
console.log('没有历史消息或获取失败');
}
} catch (error) {
conversationId.value = `local_${Date.now()}`;
console.error('加载历史消息失败:', error);
}
};
@@ -814,6 +936,65 @@ const handleInputFocus = () => {
const goBack = () => {
uni.navigateBack();
};
// 清空对话上下文
const clearContext = async () => {
uni.showModal({
title: '清空对话',
content: '确定要清空与该角色的所有对话记录吗?此操作不可恢复。',
confirmText: '确定清空',
cancelText: '取消',
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({ title: '清空中...' });
// 1. 调用后端清除session如果用户已登录
if (conversationId.value && isLoggedIn.value) {
try {
const result = await chatAPI.clearSession(conversationId.value);
console.log('后端清除会话结果:', result);
} catch (error) {
console.log('后端清除失败,继续本地清除:', error);
}
}
// 2. 删除本地存储的sessionId
let storageKey = '';
if (currentCharacter.value.id === 'wei-ai' || !currentCharacter.value.roleId) {
storageKey = `session_weiai`;
} else {
storageKey = `session_role_${currentCharacter.value.roleId}`;
}
uni.removeStorageSync(storageKey);
console.log('删除本地sessionId:', storageKey);
// 3. 生成新的sessionId
const characterIdForSession = currentCharacter.value.id || currentCharacter.value.roleId || 'default';
createNewConversation(characterIdForSession);
// 4. 清空消息列表(保留欢迎消息)
messages.value = [];
addMessage('ai', currentCharacter.value.greeting || '你好!很高兴再次见到你!');
uni.hideLoading();
uni.showToast({
title: '对话已清空',
icon: 'success'
});
} catch (error) {
console.error('清空对话失败:', error);
uni.hideLoading();
uni.showToast({
title: '清空失败',
icon: 'none'
});
}
}
}
});
};
</script>
<style scoped>
@@ -897,13 +1078,19 @@ page {
left: 24rpx;
display: flex;
align-items: center;
gap: 8rpx;
gap: 16rpx;
color: #f9e076;
font-size: 28rpx;
font-weight: 500;
text-shadow: 0 0 10rpx rgba(249, 224, 118, 0.3);
}
.left-btn {
display: flex;
align-items: center;
gap: 8rpx;
}
.back-icon {
font-size: 32rpx;
}
@@ -938,13 +1125,22 @@ page {
.navbar-right {
position: absolute;
right: 32rpx;
display: flex;
align-items: center;
}
.status-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: rgba(249, 224, 118, 0.3);
.clear-btn {
font-size: 28rpx;
color: #f9e076;
font-weight: 500;
text-shadow: 0 0 8rpx rgba(249, 224, 118, 0.3);
padding: 8rpx 16rpx;
transition: all 0.3s;
}
.clear-btn:active {
opacity: 0.7;
transform: scale(0.95);
}
.status-dot.online {
@@ -962,6 +1158,7 @@ page {
}
}
/* 聊天消息区域 */
.chat-messages {
height: calc(100vh - 100rpx - 200rpx - env(safe-area-inset-top));