Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/local/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
deleteAiSession,
loadAiSession,
} from '@studio/common/ai/sessions/store';
import { expandSkillCommandPrompt } from '@studio/common/ai/slash-commands';
import { DEFAULT_TOKEN_LIFETIME_MS } from '@studio/common/constants';
import { createCliRunner } from '@studio/common/lib/cli-process';
import {
Expand Down Expand Up @@ -1407,7 +1408,7 @@ export async function startLocalServer( options: LocalServerOptions ): Promise<
}
const { runId } = runManager.startAgentRun( {
sessionId: req.params.id,
prompt,
prompt: expandSkillCommandPrompt( prompt ),
displayMessage,
} );
res.json( { runId } );
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { getSlashCommandMatches } from '@studio/common/ai/slash-commands';
import { __ } from '@wordpress/i18n';
import { useCallback, useEffect, useId, useMemo, useState } from 'react';
import motionStyles from '../floating-surface-motion/style.module.css';
import menuStyles from '../menu/style.module.css';
import { getSlashCommandMatches } from './slash-autocomplete';
import styles from './style.module.css';
import type { Dispatch, KeyboardEvent, ReactNode, RefObject, SetStateAction } from 'react';

Expand Down
13 changes: 1 addition & 12 deletions apps/studio/src/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,7 @@ import {
deleteAiSession as deleteAiSessionFromStore,
loadAiSession as loadAiSessionFromStore,
} from '@studio/common/ai/sessions/store';
import {
buildSkillInvocationPrompt,
resolveSkillFromPrompt,
} from '@studio/common/ai/slash-commands';
import { expandSkillCommandPrompt } from '@studio/common/ai/slash-commands';
import { getAiTracksIdentity } from '@studio/common/ai/tracks-identity';
import {
installSkillToSite,
Expand Down Expand Up @@ -415,14 +412,6 @@ async function reconcileSessionEnvironmentBeforeRun( sessionId: string ): Promis
} );
}

// Expand a bare skill-command slash prompt (e.g. `/rank-me-up`) into the
// instruction the agent actually acts on. Mirrors the CLI's interactive main
// loop so UI clients can send the short form and get the same behaviour.
function expandSkillCommandPrompt( prompt: string ): string {
const name = resolveSkillFromPrompt( prompt );
return name ? buildSkillInvocationPrompt( name ) : prompt;
}

export async function continueAiSession(
event: IpcMainInvokeEvent,
sessionId: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ describe( 'Composer menu', () => {
value: 1000,
} );
renderComposer();
const textarea = screen.getByRole( 'textbox' ) as HTMLTextAreaElement;
const textarea = screen.getByRole( 'combobox' ) as HTMLTextAreaElement;
let scrollHeight = 72;
Object.defineProperty( textarea, 'scrollHeight', {
configurable: true,
Expand Down Expand Up @@ -231,7 +231,7 @@ describe( 'Composer menu', () => {

it( 'resizes the textarea when attachments change its padding', async () => {
const { container } = renderComposer();
const textarea = screen.getByRole( 'textbox' ) as HTMLTextAreaElement;
const textarea = screen.getByRole( 'combobox' ) as HTMLTextAreaElement;
const textFile = new File( [ 'Attachment preview text' ], 'notes.txt', {
type: 'text/plain',
} );
Expand Down Expand Up @@ -267,7 +267,7 @@ describe( 'Composer menu', () => {
value: 1000,
} );
renderComposer();
const textarea = screen.getByRole( 'textbox' ) as HTMLTextAreaElement;
const textarea = screen.getByRole( 'combobox' ) as HTMLTextAreaElement;
const resizeHandle = screen.getByRole( 'separator', { name: 'Resize composer' } );
Object.defineProperty( textarea, 'scrollHeight', {
configurable: true,
Expand Down Expand Up @@ -317,7 +317,7 @@ describe( 'Composer menu', () => {
expect(
await screen.findByRole( 'button', { name: 'Remove attachment: pasted-image.png' } )
).toBeInTheDocument();
expect( screen.getByRole( 'textbox' ) ).toHaveFocus();
expect( screen.getByRole( 'combobox' ) ).toHaveFocus();
} );

it( 'ignores pastes inside open dialogs', () => {
Expand Down
13 changes: 13 additions & 0 deletions apps/ui/src/ui-classic/components/session-view/composer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
type ComposerAttachment,
type ComposerSendAttachments,
} from './use-composer-attachments';
import { useSlashCommands } from './use-slash-commands';
import type {
AiModelId,
LoadedAiSession,
Expand Down Expand Up @@ -311,6 +312,13 @@ export const Composer = forwardRef< ComposerHandle, ComposerProps >( function Co
const connector = useConnector();
const queryClient = useQueryClient();

const slash = useSlashCommands( {
value,
setValue,
textareaRef,
previewPrompt: null,
} );

// File/image attachments (attach button + drag-and-drop). Images ride as
// base64 content blocks; other files are referenced by disk path.
const {
Expand Down Expand Up @@ -855,9 +863,13 @@ export const Composer = forwardRef< ComposerHandle, ComposerProps >( function Co
className={ styles.input }
placeholder={ placeholder }
value={ value }
{ ...slash.comboboxProps }
onChange={ ( event ) => setValue( event.target.value ) }
onPaste={ pasteHandlers.onPaste }
onKeyDown={ ( event ) => {
if ( slash.handleKeyDown( event ) ) {
return;
}
if ( event.key === 'Escape' && busy ) {
event.preventDefault();
void onInterrupt();
Expand All @@ -884,6 +896,7 @@ export const Composer = forwardRef< ComposerHandle, ComposerProps >( function Co
} }
rows={ 2 }
/>
{ slash.popup }
</div>
<div className={ styles.toolbar }>
<div className={ styles.leftActions }>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,37 @@
min-width: 0;
}

/* Inline listbox anchored above the textarea. The Menu `.popup` class provides
the surface; this only adds the anchor/layout bits the portaled Menu doesn't
need. Stays a plain element so the textarea keeps focus. */
.autocompletePopup {
position: absolute;
bottom: calc(100% + 4px);
left: 0;
z-index: 100;
max-height: 240px;
overflow-y: auto;
margin: 0;
list-style: none;
}

.commandItem {
display: flex;
flex-direction: column;
gap: 2px;
}

.commandName {
font-family: var(--wpds-typography-font-family-mono, ui-monospace, monospace);
font-size: var(--wpds-typography-font-size-sm);
color: var(--wpds-color-fg-content-neutral);
}

.commandDescription {
font-size: var(--wpds-typography-font-size-xs);
color: var(--wpds-color-fg-content-neutral-weak);
}

.input {
display: block;
min-height: 48px;
Expand Down
Loading