* fix(HH-546): flatten conversation AI actions * fix(HH-546): wrap AI actions on narrow screens --------- Co-authored-by: Rogee <rogee@ipao.vip>
213 lines
5.6 KiB
Vue
213 lines
5.6 KiB
Vue
<script>
|
|
import { computed } from 'vue';
|
|
import { useStore } from 'vuex';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
|
import { useCaptain } from 'dashboard/composables/useCaptain';
|
|
import { useAlert } from 'dashboard/composables';
|
|
import { useMapGetter } from 'dashboard/composables/store';
|
|
import { REPLY_EDITOR_MODES, CHAR_LENGTH_WARNING } from './constants';
|
|
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
import EditorModeToggle from './EditorModeToggle.vue';
|
|
import CopilotMenuBar from './CopilotMenuBar.vue';
|
|
|
|
export default {
|
|
name: 'ReplyTopPanel',
|
|
components: {
|
|
NextButton,
|
|
EditorModeToggle,
|
|
CopilotMenuBar,
|
|
},
|
|
props: {
|
|
mode: {
|
|
type: String,
|
|
default: REPLY_EDITOR_MODES.REPLY,
|
|
},
|
|
isReplyRestricted: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
isEditorDisabled: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
conversationId: {
|
|
type: Number,
|
|
default: null,
|
|
},
|
|
isMessageLengthReachingThreshold: {
|
|
type: Boolean,
|
|
default: () => false,
|
|
},
|
|
charactersRemaining: {
|
|
type: Number,
|
|
default: () => 0,
|
|
},
|
|
editorContent: {
|
|
type: String,
|
|
default: undefined,
|
|
},
|
|
hasContent: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
},
|
|
emits: ['setReplyMode', 'toggleEditorSize', 'executeCopilotAction'],
|
|
setup(props, { emit }) {
|
|
const store = useStore();
|
|
const { t } = useI18n();
|
|
const currentChat = useMapGetter('getSelectedChat');
|
|
|
|
const setReplyMode = mode => {
|
|
emit('setReplyMode', mode);
|
|
};
|
|
const handleReplyClick = () => {
|
|
if (props.isReplyRestricted) return;
|
|
setReplyMode(REPLY_EDITOR_MODES.REPLY);
|
|
};
|
|
const handleNoteClick = () => {
|
|
setReplyMode(REPLY_EDITOR_MODES.NOTE);
|
|
};
|
|
const handleModeToggle = () => {
|
|
const newMode =
|
|
props.mode === REPLY_EDITOR_MODES.REPLY
|
|
? REPLY_EDITOR_MODES.NOTE
|
|
: REPLY_EDITOR_MODES.REPLY;
|
|
setReplyMode(newMode);
|
|
};
|
|
|
|
const { captainTasksEnabled } = useCaptain();
|
|
|
|
const handleCopilotAction = (actionKey, data) => {
|
|
emit('executeCopilotAction', actionKey, data || props.editorContent);
|
|
};
|
|
|
|
const aiTakeoverActive = computed(
|
|
() => currentChat.value?.ai_takeover_active === true
|
|
);
|
|
|
|
const toggleAITakeover = async () => {
|
|
try {
|
|
const wasActive = aiTakeoverActive.value;
|
|
await store.dispatch(
|
|
wasActive ? 'exitAITakeover' : 'startAITakeover',
|
|
currentChat.value?.id
|
|
);
|
|
useAlert(
|
|
wasActive
|
|
? t('CONVERSATION.AI_TAKEOVER.EXIT_SUCCESS')
|
|
: t('CONVERSATION.AI_TAKEOVER.START_SUCCESS')
|
|
);
|
|
} catch (error) {
|
|
useAlert(
|
|
error?.response?.data?.error || t('CONVERSATION.AI_TAKEOVER.ERROR')
|
|
);
|
|
}
|
|
};
|
|
|
|
const keyboardEvents = {
|
|
'Alt+KeyP': {
|
|
action: () => handleNoteClick(),
|
|
allowOnFocusedInput: true,
|
|
},
|
|
'Alt+KeyL': {
|
|
action: () => handleReplyClick(),
|
|
allowOnFocusedInput: true,
|
|
},
|
|
};
|
|
useKeyboardEvents(keyboardEvents);
|
|
|
|
return {
|
|
handleModeToggle,
|
|
handleReplyClick,
|
|
handleNoteClick,
|
|
REPLY_EDITOR_MODES,
|
|
captainTasksEnabled,
|
|
handleCopilotAction,
|
|
aiTakeoverActive,
|
|
toggleAITakeover,
|
|
};
|
|
},
|
|
computed: {
|
|
replyButtonClass() {
|
|
return {
|
|
'is-active': this.mode === REPLY_EDITOR_MODES.REPLY,
|
|
};
|
|
},
|
|
noteButtonClass() {
|
|
return {
|
|
'is-active': this.mode === REPLY_EDITOR_MODES.NOTE,
|
|
};
|
|
},
|
|
charLengthClass() {
|
|
return this.charactersRemaining < 0 ? 'text-n-ruby-9' : 'text-n-slate-11';
|
|
},
|
|
characterLengthWarning() {
|
|
return this.charactersRemaining < 0
|
|
? `${-this.charactersRemaining} ${CHAR_LENGTH_WARNING.NEGATIVE}`
|
|
: `${this.charactersRemaining} ${CHAR_LENGTH_WARNING.UNDER_50}`;
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="flex min-h-[3.25rem] flex-wrap items-center justify-between gap-2 py-2 ltr:pl-3 ltr:pr-2 rtl:pr-3 rtl:pl-2 sm:h-[3.25rem] sm:flex-nowrap sm:py-0"
|
|
>
|
|
<EditorModeToggle
|
|
:mode="mode"
|
|
:disabled="disabled"
|
|
:is-reply-restricted="isReplyRestricted"
|
|
@toggle-mode="handleModeToggle"
|
|
/>
|
|
<div class="flex min-w-0 flex-1 items-center">
|
|
<div v-if="isMessageLengthReachingThreshold" class="text-xs">
|
|
<span :class="charLengthClass">
|
|
{{ characterLengthWarning }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div
|
|
data-testid="reply-top-panel-actions"
|
|
class="flex w-full flex-shrink-0 flex-wrap items-center justify-end gap-2 sm:w-auto sm:flex-nowrap"
|
|
>
|
|
<NextButton
|
|
type="button"
|
|
:label="
|
|
aiTakeoverActive
|
|
? $t('CONVERSATION.AI_TAKEOVER.EXIT')
|
|
: $t('CONVERSATION.AI_TAKEOVER.START')
|
|
"
|
|
slate
|
|
outline
|
|
sm
|
|
class="flex-shrink-0"
|
|
@click="toggleAITakeover"
|
|
/>
|
|
<CopilotMenuBar
|
|
v-if="captainTasksEnabled"
|
|
inline
|
|
:disabled="disabled || isEditorDisabled"
|
|
:has-selection="false"
|
|
:has-content="hasContent"
|
|
:conversation-id="conversationId"
|
|
@execute-copilot-action="handleCopilotAction"
|
|
/>
|
|
<NextButton
|
|
v-if="captainTasksEnabled"
|
|
type="button"
|
|
ghost
|
|
class="flex-shrink-0 text-n-slate-11"
|
|
sm
|
|
icon="i-lucide-maximize-2"
|
|
@click="$emit('toggleEditorSize')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|