fix(editor): Insert instance ai prompt suggestions instead of submit (#32008)

This commit is contained in:
Romeo Balta
2026-06-11 09:53:22 +00:00
committed by GitHub
parent 2d37d832ba
commit 3386aa81cc
6 changed files with 94 additions and 96 deletions
@@ -0,0 +1,51 @@
import { fireEvent } from '@testing-library/vue';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createComponentRenderer } from '@/__tests__/render';
import { INSTANCE_AI_WORKFLOW_PREVIEW_SUGGESTIONS as suggestions } from '../suggestions';
import WorkflowPreviewSuggestions from './WorkflowPreviewSuggestions.vue';
const telemetryTrack = vi.fn();
vi.mock('@/app/composables/useTelemetry', () => ({
useTelemetry: () => ({ track: telemetryTrack }),
}));
const renderComponent = createComponentRenderer(WorkflowPreviewSuggestions, {
props: {
suggestions,
disabled: false,
},
});
describe('WorkflowPreviewSuggestions', () => {
beforeEach(() => {
telemetryTrack.mockReset();
});
it('inserts the clicked suggestion without submitting it', async () => {
const { emitted, getByTestId } = renderComponent();
const suggestion = suggestions[1];
expect(suggestion).toBeDefined();
if (!suggestion) throw new Error('Missing workflow preview suggestion fixture');
await fireEvent.click(getByTestId(`instance-ai-suggestion-${suggestion.id}`));
expect(emitted()['insert-suggestion']).toEqual([
[
{
promptKey: suggestion.promptKey,
suggestionId: suggestion.id,
suggestionKind: 'prompt',
position: 2,
},
],
]);
expect(emitted()['submit-suggestion']).toBeUndefined();
expect(emitted()['preview-change']).toEqual([[null]]);
expect(emitted()['workflow-preview']).toEqual([[null]]);
expect(telemetryTrack).toHaveBeenCalledWith('AI Assistant suggestion button clicked', {
suggestion_id: suggestion.id,
});
});
});
@@ -12,7 +12,7 @@ const props = defineProps<{
disabled: boolean;
}>();
interface SubmitSuggestionPayload {
interface InsertSuggestionPayload {
promptKey: BaseTextKey;
suggestionId: string;
suggestionKind: 'prompt';
@@ -21,7 +21,7 @@ interface SubmitSuggestionPayload {
const emit = defineEmits<{
'preview-change': [promptKey: BaseTextKey | null];
'submit-suggestion': [payload: SubmitSuggestionPayload];
'insert-suggestion': [payload: InsertSuggestionPayload];
'workflow-preview': [workflowFile: string | null];
}>();
@@ -88,7 +88,7 @@ function handleSuggestionClick(suggestion: WorkflowPreviewSuggestion) {
});
clearPreview();
emit('submit-suggestion', {
emit('insert-suggestion', {
promptKey: suggestion.promptKey,
suggestionId: suggestion.id,
suggestionKind: 'prompt',
@@ -50,46 +50,6 @@ vi.mock('@/app/composables/useTelemetry', () => ({
useTelemetry: vi.fn(() => ({ track: telemetryTrack })),
}));
const CustomSuggestionsComponent = defineComponent({
name: 'CustomSuggestionsComponent',
props: {
suggestions: {
type: Array as PropType<readonly InstanceAiEmptyStateSuggestion[]>,
required: true,
},
disabled: {
type: Boolean,
required: true,
},
},
emits: ['submit-suggestion'],
setup(props, { emit }) {
return () =>
h(
'button',
{
type: 'button',
'data-test-id': 'custom-suggestion-submit',
disabled: props.disabled,
onClick: () => {
const [suggestion] = props.suggestions;
if (!suggestion || !isPromptSuggestion(suggestion)) {
return;
}
emit('submit-suggestion', {
promptKey: suggestion.promptKey,
suggestionId: 'custom-build-workflow',
suggestionKind: 'prompt',
position: 1,
});
},
},
'Custom suggestion',
);
},
});
const CustomInsertSuggestionsComponent = defineComponent({
name: 'CustomInsertSuggestionsComponent',
props: {
@@ -364,7 +324,7 @@ describe('InstanceAiInput', () => {
expect(textbox).toHaveAttribute('placeholder', initialPlaceholder);
});
it('submits immediately when a prompt suggestion is clicked', async () => {
it('inserts a prompt suggestion and submits it only when send is clicked', async () => {
const { emitted, getByRole, getByTestId } = renderComponent({
props: {
isStreaming: false,
@@ -375,6 +335,13 @@ describe('InstanceAiInput', () => {
const textbox = getByRole('textbox');
await userEvent.click(getByTestId('instance-ai-suggestion-build-agent'));
expect(emitted().submit).toBeUndefined();
expect(textbox).toHaveValue(
'I want to build a new agent. Help me figure out what to build. Ask me what the main purpose of the agent is, what should trigger it into action, what apps, tools, or knowledge it should have access to, and whether I have a preference for the AI model used.',
);
await userEvent.click(getByTestId('instance-ai-send-button'));
expect(emitted().submit?.[0]).toEqual([
'I want to build a new agent. Help me figure out what to build. Ask me what the main purpose of the agent is, what should trigger it into action, what apps, tools, or knowledge it should have access to, and whether I have a preference for the AI model used.',
undefined,
@@ -382,25 +349,6 @@ describe('InstanceAiInput', () => {
expect(textbox).toHaveValue('');
});
it('submits from a caller-provided suggestions component through the existing flow', async () => {
const { emitted, getByRole, getByTestId } = renderComponent({
props: {
isStreaming: false,
suggestions,
suggestionsComponent: CustomSuggestionsComponent,
},
});
const textbox = getByRole('textbox');
await userEvent.click(getByTestId('custom-suggestion-submit'));
expect(emitted().submit?.[0]).toEqual([
"I want to build a new workflow. Help me figure out what to build. Ask me what's the end goal, what should trigger it, and what apps or services are involved.",
undefined,
]);
expect(textbox).toHaveValue('');
});
it('inserts from a caller-provided suggestions component without submitting', async () => {
const { emitted, getByRole, getByTestId, queryByTestId } = renderComponent({
props: {
@@ -552,7 +500,7 @@ describe('InstanceAiInput', () => {
expect(textbox).toHaveValue('');
});
it('opens quick examples and submits immediately when an example is clicked', async () => {
it('opens quick examples and inserts an example without submitting', async () => {
const { emitted, getByRole, getByTestId, queryByTestId } = renderComponent({
props: {
isStreaming: false,
@@ -570,11 +518,10 @@ describe('InstanceAiInput', () => {
await userEvent.click(getByTestId('instance-ai-quick-example-answer-support-requests'));
const textbox = getByRole('textbox');
expect(emitted().submit?.[0]).toEqual([
expect(emitted().submit).toBeUndefined();
expect(textbox).toHaveValue(
'When a new email arrives in our Outlook inbox, use Claude to summarize what the prospect is looking for, rate its urgency and potential value, then notify the right person in Slack based on the product and region of the prospect.',
undefined,
]);
expect(textbox).toHaveValue('');
);
expect(queryByTestId('instance-ai-quick-examples-panel')).not.toBeInTheDocument();
});
@@ -872,9 +819,9 @@ describe('InstanceAiInput', () => {
});
});
it('tracks top-level suggestion selection before submit', async () => {
it('tracks top-level suggestion selection when inserting into the composer', async () => {
const onSubmit = vi.fn();
const { getByTestId } = renderComponent({
const { getByRole, getByTestId, queryByTestId } = renderComponent({
props: {
isStreaming: false,
suggestions,
@@ -891,6 +838,12 @@ describe('InstanceAiInput', () => {
await userEvent.click(getByTestId('instance-ai-suggestion-build-workflow'));
await waitFor(() => {
expect(getByRole('textbox')).toHaveValue(
"I want to build a new workflow. Help me figure out what to build. Ask me what's the end goal, what should trigger it, and what apps or services are involved.",
);
expect(queryByTestId('instance-ai-suggestion-build-workflow')).not.toBeInTheDocument();
});
expect(telemetryTrack).toHaveBeenCalledWith('Instance AI prompt suggestion selected', {
thread_id: 'thread-1',
suggestion_catalog_version: 'v1',
@@ -898,7 +851,7 @@ describe('InstanceAiInput', () => {
suggestion_kind: 'prompt',
position: 1,
});
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).not.toHaveBeenCalled();
});
it('tracks quick-example suggestion selection with semantic payload', async () => {
@@ -925,7 +878,7 @@ describe('InstanceAiInput', () => {
});
it('never includes prompt text in telemetry payloads', async () => {
const { getByTestId } = renderComponent({
const { getByRole, getByTestId } = renderComponent({
props: {
isStreaming: false,
suggestions,
@@ -934,6 +887,11 @@ describe('InstanceAiInput', () => {
telemetryTrack.mockClear();
await userEvent.click(getByTestId('instance-ai-suggestion-build-workflow'));
const textbox = getByRole('textbox');
await userEvent.clear(textbox);
await waitFor(() => {
expect(getByTestId('instance-ai-suggestion-quick-examples')).toBeVisible();
});
await userEvent.click(getByTestId('instance-ai-suggestion-quick-examples'));
await userEvent.click(getByTestId('instance-ai-quick-example-answer-support-requests'));
@@ -37,7 +37,7 @@ describe('InstanceAiPromptSuggestions', () => {
]);
});
it('emits semantic suggestion events for quick examples and prompts', async () => {
it('emits semantic insert events for quick examples and prompts', async () => {
const { emitted, getByTestId } = renderComponent({
props: {
suggestions,
@@ -58,7 +58,7 @@ describe('InstanceAiPromptSuggestions', () => {
await userEvent.click(getByTestId('instance-ai-suggestion-quick-examples'));
await userEvent.click(getByTestId('instance-ai-quick-example-answer-support-requests'));
expect(emitted()['submit-suggestion']).toEqual([
expect(emitted()['insert-suggestion']).toEqual([
[
{
promptKey: 'instanceAi.emptyState.suggestions.buildAgent.prompt',
@@ -92,7 +92,7 @@ describe('InstanceAiPromptSuggestions', () => {
await new Promise((resolve) => setTimeout(resolve, 350));
expect(emitted()['submit-suggestion']).toEqual([
expect(emitted()['insert-suggestion']).toEqual([
[
{
promptKey: 'instanceAi.emptyState.suggestions.buildWorkflow.prompt',
@@ -139,7 +139,7 @@ describe('InstanceAiPromptSuggestions', () => {
expect(emitted()['preview-change']?.at(-1)).toEqual([null]);
});
it('does not emit preview or submit events while disabled', async () => {
it('does not emit preview or insert events while disabled', async () => {
const { emitted, getByTestId, queryByTestId } = renderComponent({
props: {
suggestions,
@@ -154,7 +154,7 @@ describe('InstanceAiPromptSuggestions', () => {
expect(queryByTestId('instance-ai-quick-examples-panel')).not.toBeInTheDocument();
expect(emitted()['preview-change']).toBeUndefined();
expect(emitted()['submit-suggestion']).toBeUndefined();
expect(emitted()['insert-suggestion']).toBeUndefined();
expect(emitted()['quick-examples-opened']).toBeUndefined();
});
@@ -171,7 +171,7 @@ describe('InstanceAiPromptSuggestions', () => {
const beforePreviewChanges = emitted()['preview-change']?.length ?? 0;
const beforeQuickExamplesOpened = emitted()['quick-examples-opened']?.length ?? 0;
const beforeSubmitSuggestion = emitted()['submit-suggestion']?.length ?? 0;
const beforeInsertSuggestion = emitted()['insert-suggestion']?.length ?? 0;
await userEvent.hover(getByTestId('instance-ai-quick-example-monitor-competitors'));
await userEvent.unhover(getByTestId('instance-ai-quick-example-monitor-competitors'));
@@ -180,7 +180,7 @@ describe('InstanceAiPromptSuggestions', () => {
expect(getByTestId('instance-ai-quick-examples-panel')).toBeVisible();
expect(emitted()['preview-change']?.length ?? 0).toBe(beforePreviewChanges);
expect(emitted()['quick-examples-opened']?.length ?? 0).toBe(beforeQuickExamplesOpened);
expect(emitted()['submit-suggestion']?.length ?? 0).toBe(beforeSubmitSuggestion);
expect(emitted()['insert-suggestion']?.length ?? 0).toBe(beforeInsertSuggestion);
});
it('closes quick examples and clears preview when clicking outside', async () => {
@@ -20,7 +20,6 @@ type SuggestionSelectionPayload = {
suggestionKind: 'prompt' | 'quick_example';
position: number;
};
// Experiment cleanup: remove with instanceAiPromptSuggestionsV2.
type SelectedSuggestionDraft = SuggestionSelectionPayload & {
originalPrompt: string;
};
@@ -72,7 +71,6 @@ const inputText = ref('');
const attachedFiles = ref<File[]>([]);
const chatInputRef = ref<InstanceType<typeof ChatInputBase> | null>(null);
const previewPromptKey = ref<BaseTextKey | null>(null);
// Experiment cleanup: remove with instanceAiPromptSuggestionsV2.
const selectedSuggestionDraft = ref<SelectedSuggestionDraft | null>(null);
function focus() {
@@ -157,7 +155,6 @@ watch(
{ immediate: true },
);
// Experiment cleanup: remove with instanceAiPromptSuggestionsV2.
watch(inputText, (text) => {
if (text.length === 0) {
selectedSuggestionDraft.value = null;
@@ -245,7 +242,6 @@ function getTelemetryContext() {
};
}
// Experiment cleanup: remove with instanceAiPromptSuggestionsV2.
function trackSelectedSuggestionSubmitted(message: string) {
const selectedSuggestion = selectedSuggestionDraft.value;
if (!selectedSuggestion) {
@@ -291,12 +287,6 @@ function handleSuggestionsCycled(payload: SuggestionsCyclePayload) {
});
}
function handleSuggestionSubmit(payload: SuggestionSelectionPayload) {
trackSuggestionSelected(payload);
submitComposerMessage(i18n.baseText(payload.promptKey));
}
// Experiment cleanup: remove with instanceAiPromptSuggestionsV2.
async function handleSuggestionInsert(payload: SuggestionSelectionPayload) {
trackSuggestionSelected(payload);
previewPromptKey.value = null;
@@ -391,7 +381,6 @@ const resizable = computed(() => {
@quick-examples-opened="handleQuickExamplesOpened"
@cycle-suggestions="handleSuggestionsCycled"
@insert-suggestion="handleSuggestionInsert"
@submit-suggestion="handleSuggestionSubmit"
@workflow-preview="emit('workflow-preview', $event)"
/>
</Transition>
@@ -14,7 +14,7 @@ const props = defineProps<{
disabled: boolean;
}>();
interface SubmitSuggestionPayload {
interface InsertSuggestionPayload {
promptKey: BaseTextKey;
suggestionId: string;
suggestionKind: 'prompt' | 'quick_example';
@@ -24,7 +24,7 @@ interface SubmitSuggestionPayload {
const emit = defineEmits<{
'preview-change': [promptKey: BaseTextKey | null];
'quick-examples-opened': [payload: { suggestionId: string; position: number }];
'submit-suggestion': [payload: SubmitSuggestionPayload];
'insert-suggestion': [payload: InsertSuggestionPayload];
}>();
const i18n = useI18n();
@@ -70,13 +70,13 @@ function getQuickExamplePosition(exampleId: string) {
return index >= 0 ? index + 1 : 0;
}
function submitSuggestion(payload: SubmitSuggestionPayload) {
function insertSuggestion(payload: InsertSuggestionPayload) {
if (props.disabled) {
return;
}
closeQuickExamples();
emit('submit-suggestion', payload);
emit('insert-suggestion', payload);
}
function handleDocumentKeydown(event: KeyboardEvent) {
@@ -142,7 +142,7 @@ function handleSuggestionClick(suggestion: InstanceAiEmptyStateSuggestion) {
clearHoverTimer();
if (isPromptSuggestion(suggestion)) {
submitSuggestion({
insertSuggestion({
promptKey: suggestion.promptKey,
suggestionId: suggestion.id,
suggestionKind: 'prompt',
@@ -251,7 +251,7 @@ function handleQuickExampleLeave() {
:data-test-id="`instance-ai-quick-example-${example.id}`"
:disabled="props.disabled"
@click="
submitSuggestion({
insertSuggestion({
promptKey: example.promptKey,
suggestionId: example.id,
suggestionKind: 'quick_example',