feat(feedback): 置信度评分与用户反馈机制 & 归档 confidence-feedback change

This commit is contained in:
zhuyongxin
2026-06-30 18:01:06 +08:00
parent 3ffa5cc366
commit 2a796da490
16 changed files with 771 additions and 18 deletions
+66 -3
View File
@@ -632,8 +632,12 @@ class SuperBizAgentApp {
if (data.code === 200 || data.message === 'success') {
// data.data 是 ChatResponse 对象
const chatResponse = data.data;
if (chatResponse && chatResponse.success) {
// 保存后端返回的 sessionId,用于 feedback 提交
if (chatResponse.sessionId) {
this.lastSessionId = chatResponse.sessionId;
}
// 成功:添加实际响应消息(即使 answer 为空也显示)
const answer = chatResponse.answer || '(无回复内容)';
this.addMessage('assistant', answer);
@@ -848,24 +852,78 @@ class SuperBizAgentApp {
}
messageContentWrapper.appendChild(messageContent);
// assistant 消息末尾加反馈栏(流式消息完成后由 handleStreamComplete 添加)
if (type === 'assistant' && !isStreaming) {
messageContentWrapper.appendChild(this.createFeedbackBar(this.lastSessionId));
}
messageDiv.appendChild(messageContentWrapper);
if (this.chatMessages) {
this.chatMessages.appendChild(messageDiv);
// 如果是第一条消息,移除居中样式并添加动画
if (isFirstMessage && this.chatContainer) {
this.chatContainer.classList.remove('centered');
// 添加动画类
this.chatContainer.style.transition = 'all 0.5s ease';
}
this.scrollToBottom();
}
return messageDiv;
}
// 创建反馈栏,sessionId 闭包绑定,避免多轮对话时错位
createFeedbackBar(sessionId) {
const bar = document.createElement('div');
bar.className = 'feedback-bar';
const usefulBtn = document.createElement('button');
usefulBtn.className = 'feedback-btn';
usefulBtn.title = '有用';
usefulBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3H14z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
const notUsefulBtn = document.createElement('button');
notUsefulBtn.className = 'feedback-btn';
notUsefulBtn.title = '无用';
notUsefulBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3H10z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M17 2h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
usefulBtn.addEventListener('click', () => this.submitFeedback('useful', bar, sessionId));
notUsefulBtn.addEventListener('click', () => this.submitFeedback('not_useful', bar, sessionId));
bar.appendChild(usefulBtn);
bar.appendChild(notUsefulBtn);
return bar;
}
// 提交反馈
async submitFeedback(feedback, barElement, sessionId) {
if (!sessionId) return;
barElement.querySelectorAll('.feedback-btn').forEach(btn => btn.disabled = true);
try {
const response = await fetch(`${this.apiBaseUrl}/feedback`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId, feedback })
});
const data = await response.json();
if (data.success) {
barElement.innerHTML = `<span class="feedback-done">${feedback === 'useful' ? '已标记为有用' : '已标记为无用'}</span>`;
} else {
barElement.querySelectorAll('.feedback-btn').forEach(btn => btn.disabled = false);
this.showNotification('反馈提交失败', 'error');
}
} catch (e) {
barElement.querySelectorAll('.feedback-btn').forEach(btn => btn.disabled = false);
this.showNotification('反馈提交失败: ' + e.message, 'error');
}
}
// 添加带加载动画的消息
addLoadingMessage(content) {
const messageDiv = document.createElement('div');
@@ -946,12 +1004,17 @@ class SuperBizAgentApp {
handleStreamComplete(assistantMessageElement, fullResponse) {
if (assistantMessageElement) {
assistantMessageElement.classList.remove('streaming');
const messageContentWrapper = assistantMessageElement.querySelector('.message-content-wrapper');
const messageContent = assistantMessageElement.querySelector('.message-content');
if (messageContent) {
messageContent.innerHTML = this.renderMarkdown(fullResponse);
// 高亮代码块
this.highlightCodeBlocks(messageContent);
}
// 流式完成后追加反馈栏
if (messageContentWrapper && !messageContentWrapper.querySelector('.feedback-bar')) {
messageContentWrapper.appendChild(this.createFeedbackBar(this.lastSessionId));
}
}
// 保存流式消息到历史记录
if (fullResponse) {
+50 -1
View File
@@ -1157,9 +1157,58 @@ body {
width: 100%;
justify-content: space-between;
}
.mode-dropdown {
width: 100%;
right: 0;
}
}
/* 反馈栏 */
.feedback-bar {
display: flex;
align-items: center;
gap: 6px;
margin-top: 8px;
padding-top: 6px;
}
.feedback-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: 1px solid #e8eaed;
border-radius: 6px;
background: transparent;
color: #9aa0a6;
cursor: pointer;
transition: border-color 0.2s, color 0.2s, background 0.2s;
padding: 0;
flex-shrink: 0;
}
.feedback-btn svg {
width: 14px;
height: 14px;
}
.feedback-btn:hover {
border-color: #1a73e8;
color: #1a73e8;
background: #e8f0fe;
}
.feedback-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}
.feedback-done {
font-size: 0.75rem;
color: #34a853;
font-weight: 500;
}