feat(captain): allow agents to report Captain messages (#14799)
Adds a cloud-only flow for agents to flag incorrect or problematic Captain (AI) responses. Right-clicking a Captain message surfaces a "Report message" option that opens a dialog to pick a problem type and add a description, persisted to a new captain_message_reports table for the team to review. <img width="636" height="542" alt="Screenshot 2026-06-20 at 9 15 56 AM" src="https://github.com/user-attachments/assets/afaa233d-6bd6-455a-8a33-a3796a3e3ef6" /> <img width="580" height="502" alt="Screenshot 2026-06-20 at 9 16 03 AM" src="https://github.com/user-attachments/assets/2d220d99-98cc-4c5e-a325-778ceb4f7bc9" /> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
dfd656a7d9
commit
6a262287d2
@@ -14,6 +14,7 @@ import {
|
||||
import MenuItem from '../../../components/widgets/conversation/contextMenu/menuItem.vue';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import ReportCaptainMessageDialog from './ReportCaptainMessageDialog.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -21,6 +22,7 @@ export default {
|
||||
MenuItem,
|
||||
ContextMenu,
|
||||
NextButton,
|
||||
ReportCaptainMessageDialog,
|
||||
},
|
||||
props: {
|
||||
message: {
|
||||
@@ -152,6 +154,10 @@ export default {
|
||||
closeDeleteModal() {
|
||||
this.showDeleteModal = false;
|
||||
},
|
||||
openReportDialog() {
|
||||
this.handleClose();
|
||||
this.$refs.reportDialog?.open();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -243,6 +249,16 @@ export default {
|
||||
variant="icon"
|
||||
@click.stop="showCannedResponseModal"
|
||||
/>
|
||||
<hr v-if="enabledOptions['report']" />
|
||||
<MenuItem
|
||||
v-if="enabledOptions['report']"
|
||||
:option="{
|
||||
icon: 'warning',
|
||||
label: $t('CONVERSATION.CONTEXT_MENU.REPORT_MESSAGE.LABEL'),
|
||||
}"
|
||||
variant="icon"
|
||||
@click.stop="openReportDialog"
|
||||
/>
|
||||
<hr v-if="enabledOptions['delete']" />
|
||||
<MenuItem
|
||||
v-if="enabledOptions['delete']"
|
||||
@@ -255,6 +271,11 @@ export default {
|
||||
/>
|
||||
</div>
|
||||
</ContextMenu>
|
||||
<ReportCaptainMessageDialog
|
||||
v-if="enabledOptions['report']"
|
||||
ref="reportDialog"
|
||||
:message-id="messageId"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import MessageReportsAPI from 'dashboard/api/captain/messageReports';
|
||||
|
||||
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
|
||||
import Select from 'dashboard/components-next/select/Select.vue';
|
||||
import TextArea from 'dashboard/components-next/textarea/TextArea.vue';
|
||||
|
||||
const props = defineProps({
|
||||
messageId: { type: [Number, String], required: true },
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const dialogRef = ref(null);
|
||||
const isLoading = ref(false);
|
||||
|
||||
const REPORT_REASONS = [
|
||||
'incorrect_information',
|
||||
'inappropriate_response',
|
||||
'incomplete_response',
|
||||
'outdated_information',
|
||||
'other',
|
||||
];
|
||||
|
||||
const reasonOptions = computed(() =>
|
||||
REPORT_REASONS.map(value => ({
|
||||
value,
|
||||
label: t(`CONVERSATION.CONTEXT_MENU.REPORT_MESSAGE.REASONS.${value}`),
|
||||
}))
|
||||
);
|
||||
|
||||
const form = reactive({ reportReason: '', description: '' });
|
||||
|
||||
const isFormInvalid = computed(() => !form.reportReason);
|
||||
|
||||
const resetForm = () => {
|
||||
form.reportReason = '';
|
||||
form.description = '';
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
resetForm();
|
||||
dialogRef.value?.open();
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
dialogRef.value?.close();
|
||||
};
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (isFormInvalid.value) return;
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
await MessageReportsAPI.create({
|
||||
message_id: props.messageId,
|
||||
report_reason: form.reportReason,
|
||||
description: form.description.trim() || null,
|
||||
});
|
||||
useAlert(t('CONVERSATION.CONTEXT_MENU.REPORT_MESSAGE.SUCCESS'));
|
||||
close();
|
||||
} catch (error) {
|
||||
useAlert(t('CONVERSATION.CONTEXT_MENU.REPORT_MESSAGE.ERROR'));
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog
|
||||
ref="dialogRef"
|
||||
:title="t('CONVERSATION.CONTEXT_MENU.REPORT_MESSAGE.TITLE')"
|
||||
:description="t('CONVERSATION.CONTEXT_MENU.REPORT_MESSAGE.DESCRIPTION')"
|
||||
:confirm-button-label="t('CONVERSATION.CONTEXT_MENU.REPORT_MESSAGE.SUBMIT')"
|
||||
:is-loading="isLoading"
|
||||
:disable-confirm-button="isFormInvalid"
|
||||
@confirm="handleConfirm"
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CONVERSATION.CONTEXT_MENU.REPORT_MESSAGE.PROBLEM_TYPE') }}
|
||||
</label>
|
||||
<Select
|
||||
v-model="form.reportReason"
|
||||
class="!w-full [&>select]:w-full"
|
||||
:options="reasonOptions"
|
||||
:placeholder="
|
||||
t(
|
||||
'CONVERSATION.CONTEXT_MENU.REPORT_MESSAGE.PROBLEM_TYPE_PLACEHOLDER'
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CONVERSATION.CONTEXT_MENU.REPORT_MESSAGE.DESCRIPTION_LABEL') }}
|
||||
</label>
|
||||
<TextArea
|
||||
v-model="form.description"
|
||||
class="w-full"
|
||||
:placeholder="
|
||||
t(
|
||||
'CONVERSATION.CONTEXT_MENU.REPORT_MESSAGE.DESCRIPTION_PLACEHOLDER'
|
||||
)
|
||||
"
|
||||
:max-length="500"
|
||||
show-character-count
|
||||
auto-height
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
Reference in New Issue
Block a user