feat: reply editor follow-up (#13106)
Co-authored-by: aakashb95 <aakashbakhle@gmail.com>
This commit is contained in:
co-authored by
aakashb95
parent
7f057127b0
commit
82f5dbe6c1
@@ -154,7 +154,7 @@ export function useAI() {
|
||||
* @param {string} [content=''] - The content to process (for full message) or selected text (for selection-based).
|
||||
* @param {Object} [options={}] - Additional options.
|
||||
* @param {AbortSignal} [options.signal] - AbortSignal to cancel the request.
|
||||
* @returns {Promise<string>} The generated message or an empty string if an error occurs.
|
||||
* @returns {Promise<{message: string, followUpContext?: Object}>} The generated message and optional follow-up context.
|
||||
*/
|
||||
const processEvent = async (type = 'improve', content = '', options = {}) => {
|
||||
try {
|
||||
@@ -167,20 +167,52 @@ export function useAI() {
|
||||
options.signal
|
||||
);
|
||||
const {
|
||||
data: { message: generatedMessage },
|
||||
data: { message: generatedMessage, follow_up_context: followUpContext },
|
||||
} = result;
|
||||
return generatedMessage;
|
||||
return { message: generatedMessage, followUpContext };
|
||||
} catch (error) {
|
||||
// Don't show error for aborted requests
|
||||
if (error.name === 'AbortError' || error.name === 'CanceledError') {
|
||||
return '';
|
||||
return { message: '' };
|
||||
}
|
||||
const errorData = error.response?.data?.error;
|
||||
const errorMessage =
|
||||
errorData?.error?.message ||
|
||||
t('INTEGRATION_SETTINGS.OPEN_AI.GENERATE_ERROR');
|
||||
useAlert(errorMessage);
|
||||
return '';
|
||||
return { message: '' };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a follow-up message to refine a previous AI task result.
|
||||
* @param {Object} options - The follow-up options.
|
||||
* @param {Object} options.followUpContext - The follow-up context from a previous task.
|
||||
* @param {string} options.message - The follow-up message/request from the user.
|
||||
* @param {AbortSignal} [options.signal] - AbortSignal to cancel the request.
|
||||
* @returns {Promise<{message: string, followUpContext: Object}>} The follow-up response and updated context.
|
||||
*/
|
||||
const followUp = async ({ followUpContext, message, signal }) => {
|
||||
try {
|
||||
const result = await TasksAPI.followUp(
|
||||
{ followUpContext, message, conversationId: conversationId.value },
|
||||
signal
|
||||
);
|
||||
const {
|
||||
data: { message: generatedMessage, follow_up_context: updatedContext },
|
||||
} = result;
|
||||
return { message: generatedMessage, followUpContext: updatedContext };
|
||||
} catch (error) {
|
||||
// Don't show error for aborted requests
|
||||
if (error.name === 'AbortError' || error.name === 'CanceledError') {
|
||||
return { message: '', followUpContext };
|
||||
}
|
||||
const errorData = error.response?.data?.error;
|
||||
const errorMessage =
|
||||
errorData?.error?.message ||
|
||||
t('INTEGRATION_SETTINGS.OPEN_AI.GENERATE_ERROR');
|
||||
useAlert(errorMessage);
|
||||
return { message: '', followUpContext };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -201,5 +233,6 @@ export function useAI() {
|
||||
recordAnalytics,
|
||||
fetchLabelSuggestions,
|
||||
processEvent,
|
||||
followUp,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,13 +9,14 @@ import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
* @returns {Object} Copilot reply state and methods
|
||||
*/
|
||||
export function useCopilotReply() {
|
||||
const { processEvent } = useAI();
|
||||
const { processEvent, followUp } = useAI();
|
||||
const { updateUISettings } = useUISettings();
|
||||
|
||||
const showEditor = ref(false);
|
||||
const isGenerating = ref(false);
|
||||
const isContentReady = ref(false);
|
||||
const generatedContent = ref('');
|
||||
const followUpContext = ref(null);
|
||||
const abortController = ref(null);
|
||||
|
||||
const isActive = computed(() => showEditor.value || isGenerating.value);
|
||||
@@ -38,6 +39,7 @@ export function useCopilotReply() {
|
||||
isGenerating.value = false;
|
||||
isContentReady.value = false;
|
||||
generatedContent.value = '';
|
||||
followUpContext.value = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,12 +77,14 @@ export function useCopilotReply() {
|
||||
isContentReady.value = false;
|
||||
|
||||
try {
|
||||
const content = await processEvent(action, data, {
|
||||
signal: abortController.value.signal,
|
||||
});
|
||||
const { message: content, followUpContext: newContext } =
|
||||
await processEvent(action, data, {
|
||||
signal: abortController.value.signal,
|
||||
});
|
||||
|
||||
if (!abortController.value?.signal.aborted) {
|
||||
generatedContent.value = content;
|
||||
followUpContext.value = newContext;
|
||||
if (content) showEditor.value = true;
|
||||
isGenerating.value = false;
|
||||
}
|
||||
@@ -91,6 +95,40 @@ export function useCopilotReply() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a follow-up message to refine the current generated content.
|
||||
* @param {string} message - The follow-up message from the user
|
||||
*/
|
||||
async function sendFollowUp(message) {
|
||||
if (!followUpContext.value || !message.trim()) return;
|
||||
|
||||
abortController.value = new AbortController();
|
||||
isGenerating.value = true;
|
||||
isContentReady.value = false;
|
||||
|
||||
try {
|
||||
const { message: content, followUpContext: updatedContext } =
|
||||
await followUp({
|
||||
followUpContext: followUpContext.value,
|
||||
message,
|
||||
signal: abortController.value.signal,
|
||||
});
|
||||
|
||||
if (!abortController.value?.signal.aborted) {
|
||||
if (content) {
|
||||
generatedContent.value = content;
|
||||
followUpContext.value = updatedContext;
|
||||
showEditor.value = true;
|
||||
}
|
||||
isGenerating.value = false;
|
||||
}
|
||||
} catch {
|
||||
if (!abortController.value?.signal.aborted) {
|
||||
isGenerating.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts the generated content and returns it.
|
||||
* Note: Formatting is automatically stripped by the Editor component's
|
||||
@@ -108,6 +146,7 @@ export function useCopilotReply() {
|
||||
isGenerating,
|
||||
isContentReady,
|
||||
generatedContent,
|
||||
followUpContext,
|
||||
|
||||
isActive,
|
||||
isButtonDisabled,
|
||||
@@ -117,6 +156,7 @@ export function useCopilotReply() {
|
||||
toggleEditor,
|
||||
setContentReady,
|
||||
execute,
|
||||
sendFollowUp,
|
||||
accept,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import { computed } from 'vue';
|
||||
|
||||
function isMacOS() {
|
||||
// Check modern userAgentData API first
|
||||
if (navigator.userAgentData?.platform) {
|
||||
return navigator.userAgentData.platform === 'macOS';
|
||||
}
|
||||
// Fallback to navigator.platform
|
||||
return (
|
||||
navigator.platform.startsWith('Mac') || navigator.platform === 'iPhone'
|
||||
);
|
||||
}
|
||||
|
||||
export function useKbd(keys) {
|
||||
const keySymbols = {
|
||||
$mod: navigator.platform.includes('Mac') ? '⌘' : 'Ctrl',
|
||||
$mod: isMacOS() ? '⌘' : 'Ctrl',
|
||||
shift: '⇧',
|
||||
alt: '⌥',
|
||||
ctrl: 'Ctrl',
|
||||
cmd: '⌘',
|
||||
option: '⌥',
|
||||
enter: '↩',
|
||||
enter: '↵',
|
||||
tab: '⇥',
|
||||
esc: '⎋',
|
||||
};
|
||||
@@ -16,7 +27,11 @@ export function useKbd(keys) {
|
||||
return computed(() => {
|
||||
return keys
|
||||
.map(key => keySymbols[key.toLowerCase()] || key)
|
||||
.join('')
|
||||
.join(' ')
|
||||
.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
export function getModifierKey() {
|
||||
return isMacOS() ? '⌘' : 'Ctrl';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user