mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
fix(editor): Remove the new-agent pill from the AI assistant composer (#40014)
This commit is contained in:
parent
f6f71abef4
commit
e79a2ba84b
|
|
@ -314,16 +314,10 @@ describe('InstanceAiInput', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('uses the new agent placeholder for a pending agent artifact', () => {
|
||||
it('uses the new agent placeholder when the caller passes its key', () => {
|
||||
const { getByRole } = renderComponent({
|
||||
props: {
|
||||
contextChip: {
|
||||
type: 'agent-artifact',
|
||||
agentId: 'agent-1',
|
||||
projectId: 'project-1',
|
||||
isNewAgent: true,
|
||||
label: 'New Agent',
|
||||
},
|
||||
placeholderKey: 'instanceAi.input.newAgentPlaceholder',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -340,7 +334,6 @@ describe('InstanceAiInput', () => {
|
|||
type: 'agent-artifact',
|
||||
agentId: 'agent-1',
|
||||
projectId: 'project-1',
|
||||
isNewAgent: false,
|
||||
label: 'Support Agent',
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1394,7 +1394,7 @@ describe('InstanceAiThreadView', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('shows the new-agent context until the first successful message submission', async () => {
|
||||
it('attaches the new-agent context until the first successful message submission', async () => {
|
||||
thread.sseState = 'disconnected';
|
||||
vi.mocked(thread.loadHistoricalMessages).mockResolvedValue('skipped');
|
||||
store.threads = [
|
||||
|
|
@ -1435,7 +1435,8 @@ describe('InstanceAiThreadView', () => {
|
|||
expect(preview).toHaveAttribute('data-agent-id', 'agent-1');
|
||||
expect(preview).toHaveAttribute('data-project-id', 'project-1');
|
||||
expect(thread.sendMessage).not.toHaveBeenCalled();
|
||||
expect(getByTestId('instance-ai-input-context-chip')).toHaveTextContent('New Agent');
|
||||
// A brand-new agent shows no composer chip — the attachment still rides.
|
||||
expect(getByTestId('instance-ai-input-context-chip')).toHaveTextContent('');
|
||||
|
||||
await userEvent.click(getByTestId('instance-ai-input-submit'));
|
||||
|
||||
|
|
@ -1454,7 +1455,7 @@ describe('InstanceAiThreadView', () => {
|
|||
handoffContext: undefined,
|
||||
});
|
||||
expect(getPendingAgentAttachment('thread-1')).not.toBeNull();
|
||||
expect(getByTestId('instance-ai-input-context-chip')).toHaveTextContent('New Agent');
|
||||
expect(getByTestId('instance-ai-input-context-chip')).toHaveTextContent('');
|
||||
|
||||
await userEvent.click(getByTestId('instance-ai-input-submit'));
|
||||
|
||||
|
|
@ -1510,11 +1511,12 @@ describe('InstanceAiThreadView', () => {
|
|||
pending: true,
|
||||
});
|
||||
|
||||
const { getByTestId } = renderView({ props: { threadId: 'thread-1' } });
|
||||
const { findByTestId, getByTestId } = renderView({ props: { threadId: 'thread-1' } });
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(getByTestId('instance-ai-input-context-chip')).toHaveTextContent('Support Agent');
|
||||
});
|
||||
// The stash is still the pending new-agent one, so no chip — but the
|
||||
// attachment follows the bound target's persisted name.
|
||||
await findByTestId('instance-ai-agent-preview-stub');
|
||||
expect(getByTestId('instance-ai-input-context-chip')).toHaveTextContent('');
|
||||
await userEvent.click(getByTestId('instance-ai-input-submit'));
|
||||
|
||||
expect(thread.sendMessage).toHaveBeenCalledWith('Normal message', {
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ export const InstanceAiInputStub = defineComponent({
|
|||
isSubmitting: { type: Boolean, required: false },
|
||||
isWorkflowBuilderAvailable: { type: Boolean, required: false },
|
||||
contextChip: { type: Object, required: false },
|
||||
placeholderKey: { type: String, required: false },
|
||||
mentionsEnabled: { type: Boolean, required: false },
|
||||
mentionProjectId: { type: String, required: false },
|
||||
mentionArtifacts: { type: Array, required: false },
|
||||
|
|
@ -187,6 +188,11 @@ export const InstanceAiInputStub = defineComponent({
|
|||
{ 'data-test-id': 'instance-ai-input-context-chip-icon' },
|
||||
props.contextChip?.icon ?? '',
|
||||
),
|
||||
h(
|
||||
'span',
|
||||
{ 'data-test-id': 'instance-ai-input-placeholder-key' },
|
||||
props.placeholderKey ?? 'unset',
|
||||
),
|
||||
h('span', { 'data-test-id': 'instance-ai-input-draft' }, inputDraft.value),
|
||||
h(
|
||||
'span',
|
||||
|
|
|
|||
|
|
@ -297,7 +297,12 @@ const awaitingOnboardingGreeting = computed(
|
|||
|
||||
const composerContextChip = computed(() => {
|
||||
const agentAttachment = currentAgentAttachment.value;
|
||||
if (agentAttachment && pendingComposerContext.value?.source !== 'agent-preview') {
|
||||
const isNewAgent =
|
||||
agentAttachment !== null &&
|
||||
pendingAgentAttachment.value?.id === agentAttachment.id &&
|
||||
pendingAgentAttachment.value.pending === true;
|
||||
// A brand-new agent is already the focus of the builder — no composer chip.
|
||||
if (agentAttachment && !isNewAgent && pendingComposerContext.value?.source !== 'agent-preview') {
|
||||
// Prefer the host's live subject name when it refers to the same agent as
|
||||
// the stashed attachment, so a rename in the builder updates the chip
|
||||
// without re-stashing. Falls back to the stashed snapshot otherwise.
|
||||
|
|
@ -309,9 +314,6 @@ const composerContextChip = computed(() => {
|
|||
type: 'agent-artifact' as const,
|
||||
agentId: agentAttachment.id,
|
||||
projectId: agentAttachment.projectId,
|
||||
isNewAgent:
|
||||
pendingAgentAttachment.value?.id === agentAttachment.id &&
|
||||
pendingAgentAttachment.value.pending === true,
|
||||
key: `pending-agent:${agentAttachment.id}`,
|
||||
label: liveSubjectName ?? agentAttachment.name ?? i18n.baseText('agents.new.defaultName'),
|
||||
icon: 'robot',
|
||||
|
|
@ -375,6 +377,10 @@ const composerContextChip = computed(() => {
|
|||
return null;
|
||||
});
|
||||
|
||||
// The new-agent placeholder no longer rides the context chip (a pending agent
|
||||
// shows none), so it is passed to the input as an explicit placeholder key.
|
||||
const isPendingAgentComposer = computed(() => pendingAgentAttachment.value?.pending === true);
|
||||
|
||||
const workflowHandoffGreeting = computed(() => {
|
||||
const attachment = thread.pendingWorkflowAttachment;
|
||||
if (!attachment || thread.isHydratingThread || thread.hasMessages) return null;
|
||||
|
|
@ -821,7 +827,7 @@ function dismissPendingComposerContext(key: string): boolean {
|
|||
async function dismissComposerContextChip() {
|
||||
if (!composerContextChip.value) return;
|
||||
|
||||
if (pendingAgentAttachment.value && pendingComposerContext.value?.source !== 'agent-preview') {
|
||||
if (composerContextChip.value.type === 'agent-artifact' && pendingAgentAttachment.value) {
|
||||
clearPendingAgentAttachment(thread.id);
|
||||
pendingAgentAttachment.value = null;
|
||||
return;
|
||||
|
|
@ -1030,6 +1036,9 @@ defineExpose({
|
|||
:current-thread-id="thread.id"
|
||||
:amend-context="thread.amendContext"
|
||||
:context-chip="composerContextChip"
|
||||
:placeholder-key="
|
||||
isPendingAgentComposer ? 'instanceAi.input.newAgentPlaceholder' : undefined
|
||||
"
|
||||
:contextual-suggestion="thread.contextualSuggestion"
|
||||
:mentions-enabled="props.mentionsEnabled"
|
||||
:mention-project-id="thread.projectId"
|
||||
|
|
|
|||
|
|
@ -451,9 +451,6 @@ const placeholder = computed(() => {
|
|||
if (props.contextualSuggestion) {
|
||||
return props.contextualSuggestion;
|
||||
}
|
||||
if (props.contextChip?.type === 'agent-artifact' && props.contextChip.isNewAgent) {
|
||||
return i18n.baseText('instanceAi.input.newAgentPlaceholder');
|
||||
}
|
||||
return i18n.baseText(props.placeholderKey ?? 'instanceAi.input.placeholder');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -270,7 +270,8 @@ describe('InstanceAiConversation', () => {
|
|||
// Wraps the subject in `reactive` and returns it alongside the render
|
||||
// result, so a test can mutate `subject.name` after mount and assert the
|
||||
// chip follows the live value — the actual AGENT-954 scenario (a rename in
|
||||
// the builder while the panel stays open).
|
||||
// the builder while the panel stays open). The stashed agent is a saved
|
||||
// one: a pending (brand-new) agent shows no chip at all.
|
||||
function mountWithSubject(subject: InstanceAiEmbedSubject | undefined) {
|
||||
thread.sseState = 'disconnected';
|
||||
stashPendingAgentAttachment('thread-1', {
|
||||
|
|
@ -278,7 +279,6 @@ describe('InstanceAiConversation', () => {
|
|||
id: 'agent-1',
|
||||
projectId: 'proj-1',
|
||||
name: 'Stashed Name',
|
||||
pending: true,
|
||||
});
|
||||
const reactiveSubject = subject === undefined ? undefined : reactive({ ...subject });
|
||||
const renderer = createThreadComponentRenderer(
|
||||
|
|
@ -342,6 +342,51 @@ describe('InstanceAiConversation', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('pending agent composer', () => {
|
||||
function mountWithPendingAgent() {
|
||||
thread.sseState = 'disconnected';
|
||||
stashPendingAgentAttachment('thread-1', {
|
||||
type: 'agent',
|
||||
id: 'agent-1',
|
||||
projectId: 'proj-1',
|
||||
pending: true,
|
||||
});
|
||||
const renderer = createThreadComponentRenderer(
|
||||
InstanceAiConversation,
|
||||
{
|
||||
global: { stubs: { InstanceAiInput: InstanceAiInputStub } },
|
||||
},
|
||||
() => thread,
|
||||
);
|
||||
return renderer();
|
||||
}
|
||||
|
||||
it('shows no context chip, but keeps the new-agent placeholder', async () => {
|
||||
const { getByTestId } = mountWithPendingAgent();
|
||||
|
||||
await vi.waitFor(() =>
|
||||
expect(getByTestId('instance-ai-input-placeholder-key').textContent).toBe(
|
||||
'instanceAi.input.newAgentPlaceholder',
|
||||
),
|
||||
);
|
||||
expect(getByTestId('instance-ai-input-context-chip').textContent).toBe('');
|
||||
});
|
||||
|
||||
it('still attaches the pending agent to the first message', async () => {
|
||||
const { getByTestId } = mountWithPendingAgent();
|
||||
|
||||
await fireEvent.click(getByTestId('instance-ai-input-submit'));
|
||||
await vi.waitFor(() => expect(thread.sendMessage).toHaveBeenCalled());
|
||||
|
||||
expect(thread.sendMessage).toHaveBeenCalledWith(
|
||||
'Normal message',
|
||||
expect.objectContaining({
|
||||
attachments: [{ type: 'agent', id: 'agent-1', projectId: 'proj-1', pending: true }],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('awaits beforeSend before sending, restoring the draft if it rejects', async () => {
|
||||
const beforeSend = vi.fn().mockRejectedValueOnce(new Error('flush failed'));
|
||||
const renderer = createThreadComponentRenderer(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ export type AgentArtifactContextChip = ContextChipBase & {
|
|||
type: 'agent-artifact';
|
||||
agentId: string;
|
||||
projectId: string;
|
||||
isNewAgent: boolean;
|
||||
};
|
||||
|
||||
export type AgentPreviewSessionContextChip = ContextChipBase & {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user