feat(chat): add response feedback thumbs up/down (M9.5)

Thumbs up/down buttons on assistant messages (hover action bar). Feedback
stored per message, persisted in IDB, included in conversation exports.
Toggle to clear feedback. Color-coded: green for up, red for down.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-03 23:34:50 +00:00
co-authored by Claude Opus 4.6
parent d595996866
commit 210fa7d3c6
4 changed files with 45 additions and 0 deletions
@@ -43,6 +43,25 @@
>
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5z" clip-rule="evenodd" /></svg>
</button>
<!-- Feedback thumbs -->
<button
v-if="!isUser"
class="glass-button-sm !h-7 !min-h-0 !w-7 !min-w-0 !px-0 flex items-center justify-center transition-colors"
:class="message.feedback === 'up' ? 'text-green-400' : 'text-white/60 hover:text-green-400/80'"
title="Good response"
@click.stop="toggleFeedback('up')"
>
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path d="M2 10.5a1.5 1.5 0 113 0v6a1.5 1.5 0 01-3 0v-6zM6 10.333v5.43a2 2 0 001.106 1.79l.05.025A4 4 0 008.943 18h5.416a2 2 0 001.962-1.608l1.2-6A2 2 0 0015.56 8H12V4a2 2 0 00-2-2 1 1 0 00-1 1v.667a4 4 0 01-.8 2.4L6.8 7.933a4 4 0 00-.8 2.4z" /></svg>
</button>
<button
v-if="!isUser"
class="glass-button-sm !h-7 !min-h-0 !w-7 !min-w-0 !px-0 flex items-center justify-center transition-colors"
:class="message.feedback === 'down' ? 'text-red-400' : 'text-white/60 hover:text-red-400/80'"
title="Poor response"
@click.stop="toggleFeedback('down')"
>
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path d="M18 9.5a1.5 1.5 0 11-3 0v-6a1.5 1.5 0 013 0v6zM14 9.667v-5.43a2 2 0 00-1.106-1.79l-.05-.025A4 4 0 0011.057 2H5.64a2 2 0 00-1.962 1.608l-1.2 6A2 2 0 004.44 12H8v4a2 2 0 002 2 1 1 0 001-1v-.667a4 4 0 01.8-2.4l1.4-1.866a4 4 0 00.8-2.4z" /></svg>
</button>
</div>
<div
@@ -305,8 +324,14 @@ const emit = defineEmits<{
regenerate: []
branch: [messageId: string]
reply: [messageId: string, content: string]
feedback: [messageId: string, value: 'up' | 'down' | undefined]
}>()
function toggleFeedback(value: 'up' | 'down') {
const newValue = props.message.feedback === value ? undefined : value
emit('feedback', props.message.id, newValue)
}
// Editing state
const isEditing = ref(false)
const editContent = ref('')
@@ -74,6 +74,7 @@
@regenerate="handleRegenerate"
@branch="handleBranch"
@reply="handleReply"
@feedback="handleFeedback"
/>
</ErrorBoundary>
</div>
@@ -249,6 +250,12 @@ function handleBranch(messageId: string) {
chatStore.branchFromMessage(convId, messageId)
}
function handleFeedback(messageId: string, value: 'up' | 'down' | undefined) {
const convId = chatStore.activeConversationId
if (!convId) return
chatStore.setMessageFeedback(convId, messageId, value)
}
function handleExtract(text: string) {
// Run content extraction without sending to AI
const convId = chatStore.activeConversationId ?? chatStore.createConversation()
+11
View File
@@ -222,6 +222,16 @@ export const useChatStore = defineStore('chat', () => {
}
}
function setMessageFeedback(conversationId: string, messageId: string, feedback: 'up' | 'down' | undefined) {
const conv = conversations.value.get(conversationId)
if (!conv) return
const msg = conv.messages.find((m: { id: string }) => m.id === messageId)
if (msg) {
msg.feedback = feedback
debouncedIDBSave(conv)
}
}
function switchSide() {
panelSide.value = panelSide.value === 'right' ? 'left' : 'right'
}
@@ -348,6 +358,7 @@ export const useChatStore = defineStore('chat', () => {
addMessage,
appendToLastMessage,
setMessageWebResults,
setMessageFeedback,
switchSide,
toggleChatCollapse,
setActiveConversation,
+2
View File
@@ -32,6 +32,8 @@ export interface Message {
editedAt?: number
/** Attached images (base64, max 4) for vision-capable models */
images?: ImageAttachment[]
/** User feedback: 'up' = thumbs up, 'down' = thumbs down */
feedback?: 'up' | 'down'
}
export interface Reaction {