Revert "完善表情资产后台能力并补充群总结落库"

This reverts commit 2a54650a6f.
This commit is contained in:
liuwei
2026-04-02 17:54:35 +08:00
parent 2a54650a6f
commit 57bb46bb21
11 changed files with 17 additions and 671 deletions

View File

@@ -403,46 +403,9 @@ def api_send_message():
'message': '消息发送中'
})
elif msg_type == 'emoji':
md5 = content.get('md5') if isinstance(content, dict) else None
total_length = int((content or {}).get('total_length') or 0) if isinstance(content, dict) else 0
if not md5 or total_length <= 0:
return jsonify({'success': False, 'message': '缺少表情参数'})
send_message_in_thread(server.client.send_emoji_message, wxid, md5, total_length)
server.emoji_asset_db.mark_sent(md5)
return jsonify({
'success': True,
'message': '表情发送中'
})
else:
return jsonify({'success': False, 'message': '不支持的消息类型'})
except Exception as e:
logger.exception(f"发送消息失败: {e}")
return jsonify({'success': False, 'message': str(e)}), 500
@contacts_bp.route('/api/emoji_assets', methods=['GET'])
@login_required
def api_emoji_assets():
"""获取表情资产列表API"""
try:
server = current_app.dashboard_server
limit = min(max(int(request.args.get("limit", 60) or 60), 1), 200)
roomid = request.args.get("roomid", "").strip()
assets = server.emoji_asset_db.list_assets(limit=limit, chatroom_id=roomid)
for asset in assets:
source_wxid = asset.get("source_wxid", "")
asset["source_name"] = server.contact_manager.get_nickname(source_wxid) or source_wxid
source_chatroom_id = asset.get("source_chatroom_id", "")
asset["source_chatroom_name"] = server.contact_manager.get_nickname(source_chatroom_id) or source_chatroom_id
return jsonify({
"success": True,
"data": {
"assets": assets
}
})
except Exception as e:
logger.error(f"获取表情资产列表失败: {e}")
return jsonify({"success": False, "error": str(e)}), 500

View File

@@ -11,7 +11,6 @@ from flask import Flask, send_from_directory
from loguru import logger
from db.contacts_db import ContactsDBOperator
from db.emoji_asset_db import EmojiAssetDBOperator
from db.member_context_db import MemberContextDBOperator
from db.message_storage import MessageStorageDB
from db.stats_db import StatsDBOperator
@@ -44,7 +43,6 @@ class DashboardServer:
self.db_manager = robot_instance.db_manager
self.stats_db = StatsDBOperator(self.db_manager)
self.message_storage = MessageStorageDB(self.db_manager)
self.emoji_asset_db = EmojiAssetDBOperator(self.db_manager)
self.contact_db: ContactsDBOperator = ContactsDBOperator(self.db_manager)
self.member_context_db = MemberContextDBOperator(self.db_manager)
self.task_db: TaskDBOperator = TaskDBOperator(self.db_manager)

View File

@@ -190,9 +190,9 @@
<el-button size="mini" type="success" @click="refreshCurrentGroupContexts">刷新本群摘要</el-button>
</div>
</div>
<el-table :data="filteredGroupMembers" style="width: 100%" v-loading="groupMembersLoading" class="group-members-table">
<el-table :data="filteredGroupMembers" style="width: 100%" v-loading="groupMembersLoading">
<el-table-column type="index" width="54"></el-table-column>
<el-table-column label="成员" min-width="280" show-overflow-tooltip>
<el-table-column label="成员" min-width="300">
<template slot-scope="scope">
<div class="entity-cell">
<el-avatar size="small" :src="getHeadImage(scope.row.wxid)" @error="() => true">
@@ -205,7 +205,7 @@
</div>
</template>
</el-table-column>
<el-table-column prop="display_name" label="群昵称" min-width="180" show-overflow-tooltip></el-table-column>
<el-table-column prop="display_name" label="群昵称" width="240"></el-table-column>
<el-table-column label="状态" width="100" align="center">
<template slot-scope="scope">
<el-tag size="mini" :type="scope.row.status === 1 ? 'success' : 'info'">
@@ -213,14 +213,14 @@
</el-tag>
</template>
</el-table-column>
<el-table-column prop="latest_active_time" label="活跃时间" width="168" show-overflow-tooltip>
<el-table-column prop="latest_active_time" label="活跃时间">
<template slot-scope="scope">{% raw %}{{ scope.row.latest_active_time || '-' }}{% endraw %}</template>
</el-table-column>
<el-table-column prop="activity_level" label="互动强度" width="110"></el-table-column>
<el-table-column label="回复建议" min-width="260" show-overflow-tooltip>
<el-table-column label="回复建议" min-width="220" show-overflow-tooltip>
<template slot-scope="scope">{% raw %}{{ scope.row.response_style_hint || '-' }}{% endraw %}</template>
</el-table-column>
<el-table-column label="后台摘要" width="110" align="center">
<el-table-column label="后台摘要" width="130" align="center">
<template slot-scope="scope">
<el-button size="mini" type="primary" plain @click="openMemberContextDialog(scope.row)">查看</el-button>
</template>
@@ -390,7 +390,6 @@
<div class="message-content">
<div v-if="msg.type === 'text'" v-text="msg.content"></div>
<div v-else-if="msg.type === 'image'"><img :src="msg.content" style="max-width: 200px; max-height: 200px;"></div>
<div v-else-if="msg.type === 'emoji'"><img :src="msg.content" class="chat-emoji-preview"></div>
<div v-else-if="msg.type === 'voice'"><audio controls :src="msg.content"></audio></div>
<div v-else-if="msg.type === 'video'"><video controls :src="msg.content" style="max-width: 200px;"></video></div>
<div v-else-if="msg.type === 'link'"><a :href="msg.content.url" target="_blank" v-text="msg.content.title"></a><p v-text="msg.content.description"></p></div>
@@ -400,31 +399,12 @@
{% endraw %}
</div>
<div class="input-area">
<div v-if="emojiPanelVisible" class="emoji-panel" v-loading="emojiAssetsLoading">
<div class="emoji-panel-header">
<span>后台表情资产</span>
<el-button size="mini" type="text" @click="loadEmojiAssets">刷新</el-button>
</div>
<el-empty v-if="!emojiAssetsLoading && !emojiAssets.length" description="暂无可用表情资产"></el-empty>
<div v-else class="emoji-grid">
{% raw %}
<div v-for="asset in emojiAssets" :key="asset.md5" class="emoji-card" @click="sendEmojiMessage(asset)">
<img :src="asset.file_path" class="emoji-thumb">
<div class="emoji-meta">
<div class="emoji-meta-line">{{ asset.source_name || asset.source_wxid || '未知来源' }}</div>
<div class="emoji-meta-line">{{ asset.source_chatroom_name || asset.source_chatroom_id || '全局资产' }}</div>
</div>
</div>
{% endraw %}
</div>
</div>
<el-input type="textarea" :rows="3" placeholder="请输入消息..." v-model="messageInput" @keyup.enter.native="sendTextMessage"></el-input>
<div class="toolbar">
<el-upload class="upload-demo" action="#" :http-request="uploadImage" :show-file-list="false"><el-button size="small" type="primary">图片</el-button></el-upload>
<el-upload class="upload-demo" action="#" :http-request="uploadVoice" :show-file-list="false"><el-button size="small" type="primary">语音</el-button></el-upload>
<el-upload class="upload-demo" action="#" :http-request="uploadVideo" :show-file-list="false"><el-button size="small" type="primary">视频</el-button></el-upload>
<el-button size="small" type="primary" @click="showLinkDialog">链接</el-button>
<el-button size="small" :type="emojiPanelVisible ? 'warning' : 'primary'" @click="toggleEmojiPanel">表情</el-button>
<el-button size="small" type="success" @click="sendTextMessage">发送</el-button>
</div>
</div>
@@ -471,7 +451,6 @@
memberContextDialogVisible: false, memberContextLoading: false, memberContext: null, currentContextMember: {},
memberContextEnabled: false,
chatDialogVisible: false, currentChatUser: null, messageInput: '', chatMessages: [],
emojiPanelVisible: false, emojiAssetsLoading: false, emojiAssets: [],
linkDialogVisible: false,
linkForm: { url: '', title: '', description: '' }
};
@@ -608,13 +587,7 @@
this.$message.error('刷新成员交互摘要失败');
}).finally(() => { this.memberContextLoading = false; });
},
openChatDialog(user) {
this.currentChatUser = user;
this.chatDialogVisible = true;
this.chatMessages = [];
this.emojiPanelVisible = false;
this.loadEmojiAssets();
},
openChatDialog(user) { this.currentChatUser = user; this.chatDialogVisible = true; this.chatMessages = []; },
async sendTextMessage() {
if (!this.messageInput.trim()) return;
try {
@@ -634,56 +607,6 @@
const formData = new FormData(); formData.append('file', options.file); formData.append('wxid', this.currentChatUser.wxid); formData.append('type', 'video');
try { const response = await axios.post('/contacts/api/send_message', formData); if (response.data.success) { this.chatMessages.push({ type: 'video', content: response.data.url, isSelf: true, time: new Date().toLocaleTimeString() }); this.$nextTick(() => { this.scrollToBottom(); }); } } catch (error) { this.$message.error('发送视频失败'); }
},
toggleEmojiPanel() {
this.emojiPanelVisible = !this.emojiPanelVisible;
if (this.emojiPanelVisible && !this.emojiAssets.length) {
this.loadEmojiAssets();
}
},
async loadEmojiAssets() {
this.emojiAssetsLoading = true;
const roomid = this.currentChatUser && this.currentChatUser.wxid && this.currentChatUser.wxid.endsWith('@chatroom')
? this.currentChatUser.wxid
: '';
try {
const response = await axios.get('/contacts/api/emoji_assets', { params: { limit: 80, roomid } });
if (response.data.success) {
this.emojiAssets = response.data.data.assets || [];
} else {
this.$message.error('加载表情资产失败');
}
} catch (error) {
console.error('加载表情资产失败:', error);
this.$message.error('加载表情资产失败');
} finally {
this.emojiAssetsLoading = false;
}
},
async sendEmojiMessage(asset) {
if (!asset || !asset.md5 || !asset.total_length) {
this.$message.warning('表情资产参数不完整');
return;
}
try {
const response = await axios.post('/contacts/api/send_message', {
wxid: this.currentChatUser.wxid,
type: 'emoji',
content: {
md5: asset.md5,
total_length: asset.total_length
}
});
if (response.data.success) {
this.chatMessages.push({ type: 'emoji', content: asset.file_path, isSelf: true, time: new Date().toLocaleTimeString() });
this.$nextTick(() => { this.scrollToBottom(); });
} else {
this.$message.error(response.data.message || '发送表情失败');
}
} catch (error) {
console.error('发送表情失败:', error);
this.$message.error('发送表情失败');
}
},
showLinkDialog() { this.linkForm = { url: '', title: '', description: '' }; this.linkDialogVisible = true; },
async sendLinkMessage() {
if (!this.linkForm.url) { this.$message.warning('请输入链接'); return; }
@@ -730,14 +653,6 @@
.action-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pagination-container { margin-top: 20px; text-align: right; }
.group-members-section { margin-top: 20px; }
.group-members-table .entity-cell { min-width: 0; }
.group-members-table .entity-copy { min-width: 0; overflow: hidden; }
.group-members-table .entity-title,
.group-members-table .entity-subtitle {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.section-title {
margin: 20px 0 15px 0; border-bottom: 1px solid rgba(148,163,184,0.12); padding-bottom: 10px;
display: flex; justify-content: space-between; align-items: center;
@@ -767,34 +682,5 @@
.message-time { font-size: 12px; color: #94a3b8; margin-top: 5px; }
.input-area { padding: 20px 0 0; }
.toolbar { margin-top: 10px; display: flex; gap: 10px; flex-wrap: wrap; }
.chat-emoji-preview { width: 96px; height: 96px; object-fit: contain; display: block; }
.emoji-panel {
margin-bottom: 12px; padding: 12px; border: 1px solid rgba(148,163,184,0.14);
border-radius: 16px; background: rgba(248,250,252,0.86);
}
.emoji-panel-header {
display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;
color: #334155; font-size: 13px; font-weight: 600;
}
.emoji-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 10px;
max-height: 240px; overflow-y: auto;
}
.emoji-card {
padding: 10px; border-radius: 14px; background: #ffffff; border: 1px solid rgba(148,163,184,0.12);
cursor: pointer; transition: all .2s ease;
}
.emoji-card:hover {
transform: translateY(-1px);
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
border-color: rgba(79,70,229,0.24);
}
.emoji-thumb { width: 72px; height: 72px; display: block; margin: 0 auto 8px; object-fit: contain; }
.emoji-meta { font-size: 11px; color: #64748b; line-height: 1.4; }
.emoji-meta-line {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
{% endblock %}

View File

@@ -92,12 +92,6 @@
<img v-if="scope.row.message_thumb" :src="scope.row.message_thumb" class="message-thumb" @click="showVideo(scope.row)">
</div>
<div v-else-if="isEmojiMessage(scope.row)" class="message-media-preview">
<div class="message-media-label">【表情消息】</div>
<img v-if="scope.row.image_path" :src="getImageUrl(scope.row.image_path)" class="message-thumb" @click="showImage(scope.row)">
<div v-else class="message-text-preview is-muted">表情下载中或暂无预览</div>
</div>
<div v-else class="message-text-preview is-muted">
{% raw %}{{ scope.row.content || `【消息类型: ${scope.row.message_type}】` }}{% endraw %}
</div>
@@ -133,10 +127,9 @@
<el-descriptions-item label="消息类型">{% raw %}{{ getMessageTypeName(selectedMessage.message_type) }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="内容">{% raw %}{{ selectedMessage.content }}{% endraw %}</el-descriptions-item>
<el-descriptions-item v-if="selectedMessage.message_type == 3 || selectedMessage.message_type == 43 || isEmojiMessage(selectedMessage)" label="媒体内容">
<el-descriptions-item v-if="selectedMessage.message_type == 3 || selectedMessage.message_type == 43" label="媒体内容">
<img v-if="selectedMessage.message_type == 3 && selectedMessage.image_path" :src="getImageUrl(selectedMessage.image_path)" style="max-width: 100%; border-radius: 16px;">
<img v-else-if="selectedMessage.message_type == 3 && selectedMessage.message_thumb" :src="selectedMessage.message_thumb" style="max-width: 100%; border-radius: 16px;">
<img v-else-if="isEmojiMessage(selectedMessage) && selectedMessage.image_path" :src="getImageUrl(selectedMessage.image_path)" style="max-width: 100%; border-radius: 16px;">
<video v-if="selectedMessage.message_type == 43 && selectedMessage.attachment_url" :src="selectedMessage.attachment_url" controls style="max-width: 100%; border-radius: 16px;"></video>
</el-descriptions-item>
@@ -320,18 +313,12 @@
this.selectedMessage = message;
this.detailDialogVisible = true;
},
isEmojiMessage(message) {
if (!message) return false;
return String(message.message_type) === '47' || String(message.message_type) === '1090519089';
},
getMessageTypeName(type) {
const typeMap = {
1: '文本消息',
3: '图片消息',
43: '视频消息',
47: '动画表情',
49: '链接消息',
1090519089: '大表情'
49: '链接消息'
};
return typeMap[type] || `未知类型(${type})`;
},