mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-02 08:09:50 +00:00
Add Assistant session search command
This commit is contained in:
@@ -2046,6 +2046,42 @@ describe('AIChat', () => {
|
||||
expect(mockChat.sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('searches Assistant sessions from /sessions arguments without sending a provider prompt', async () => {
|
||||
mockAIChatAPI.listSessions
|
||||
.mockResolvedValueOnce([])
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
id: 's-backup',
|
||||
title: 'Backup Patrol Follow-up',
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
message_count: 3,
|
||||
},
|
||||
]);
|
||||
|
||||
renderChat();
|
||||
await waitFor(() => {
|
||||
expect(mockAIChatAPI.listSessions).toHaveBeenCalledWith({ limit: 30 });
|
||||
});
|
||||
mockAIChatAPI.listSessions.mockClear();
|
||||
const textarea = screen.getByPlaceholderText('Ask about your infrastructure...');
|
||||
|
||||
fireEvent.input(textarea, { target: { value: '/sessions backup' } });
|
||||
fireEvent.keyDown(textarea, { key: 'Enter' });
|
||||
|
||||
const searchInput = await screen.findByPlaceholderText('Search sessions...');
|
||||
await waitFor(() => {
|
||||
expect(searchInput).toHaveValue('backup');
|
||||
expect(document.activeElement).toBe(searchInput);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(mockAIChatAPI.listSessions).toHaveBeenCalledWith({ search: 'backup', limit: 30 });
|
||||
expect(screen.getByText('Backup Patrol Follow-up')).toBeInTheDocument();
|
||||
});
|
||||
expect(mockAIChatAPI.listSessions).not.toHaveBeenCalledWith({ limit: 30 });
|
||||
expect(mockChat.sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('opens the model selector from /models without sending a provider prompt', async () => {
|
||||
renderChat();
|
||||
const textarea = screen.getByPlaceholderText('Ask about your infrastructure...');
|
||||
|
||||
@@ -63,6 +63,22 @@ describe('assistantSlashCommands', () => {
|
||||
expect(parseAssistantSlashCommand('/model openrouter:qwen/qwen3.7-plus')).toBeNull();
|
||||
});
|
||||
|
||||
it('parses session commands with search arguments for local composer handling', () => {
|
||||
expect(parseAssistantSlashCommandInput('/sessions backup patrol')).toEqual({
|
||||
action: 'sessions',
|
||||
args: 'backup patrol',
|
||||
});
|
||||
expect(parseAssistantSlashCommandInput('/resume blinds automation')).toEqual({
|
||||
action: 'sessions',
|
||||
args: 'blinds automation',
|
||||
});
|
||||
expect(parseAssistantSlashCommandInput('/continue router')).toEqual({
|
||||
action: 'sessions',
|
||||
args: 'router',
|
||||
});
|
||||
expect(parseAssistantSlashCommand('/sessions backup patrol')).toBeNull();
|
||||
});
|
||||
|
||||
it('parses dev fixture commands with stream fixture arguments', () => {
|
||||
expect(parseAssistantSlashCommandInput('/fixture provider-retry')).toEqual({
|
||||
action: 'fixture',
|
||||
|
||||
@@ -90,8 +90,9 @@ export const ASSISTANT_SLASH_COMMANDS: AssistantSlashCommand[] = [
|
||||
{
|
||||
name: 'sessions',
|
||||
aliases: ['resume', 'continue'],
|
||||
acceptsArgs: true,
|
||||
action: 'sessions',
|
||||
description: 'Open Assistant session history',
|
||||
description: 'Search or resume Assistant sessions (/sessions backup)',
|
||||
},
|
||||
{
|
||||
name: 'compact',
|
||||
|
||||
@@ -1270,7 +1270,10 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
resetSessionRename();
|
||||
};
|
||||
const focusSessionSearch = () => {
|
||||
queueMicrotask(() => sessionSearchInputRef?.focus());
|
||||
queueMicrotask(() => {
|
||||
sessionSearchInputRef?.focus();
|
||||
window.setTimeout(() => sessionSearchInputRef?.focus(), 0);
|
||||
});
|
||||
};
|
||||
const focusSessionTriggerAfterClose = () => {
|
||||
const trigger = sessionButtonRef;
|
||||
@@ -2954,6 +2957,15 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const runSessionsSlashCommand = (args: string) => {
|
||||
setShowCommandHelp(false);
|
||||
if (args.trim()) {
|
||||
void openSessionPicker(args);
|
||||
return;
|
||||
}
|
||||
void handleToggleSessions();
|
||||
};
|
||||
|
||||
const executeSlashCommand = (command: AssistantSlashCommandAction, args = '') => {
|
||||
const commandArgs = args.trim();
|
||||
const disabledReason = disabledAssistantCommandReason(command);
|
||||
@@ -2985,8 +2997,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
void handleNewConversation();
|
||||
break;
|
||||
case 'sessions':
|
||||
setShowCommandHelp(false);
|
||||
void handleToggleSessions();
|
||||
runSessionsSlashCommand(commandArgs);
|
||||
break;
|
||||
case 'compact':
|
||||
setShowCommandHelp(false);
|
||||
@@ -3490,15 +3501,7 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleSessions = async () => {
|
||||
const next = !showSessions();
|
||||
if (!next) {
|
||||
setShowSessions(false);
|
||||
setSessionRefreshLoading(false);
|
||||
resetSessionSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
const openSessionPicker = async (initialSearchQuery = '') => {
|
||||
if (sessionButtonRef) {
|
||||
const rect = sessionButtonRef.getBoundingClientRect();
|
||||
setSessionDropdownPosition({
|
||||
@@ -3507,9 +3510,19 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
});
|
||||
}
|
||||
|
||||
const searchQuery = initialSearchQuery.trim();
|
||||
resetSessionSearch();
|
||||
if (searchQuery) {
|
||||
setSessionSearchQuery(searchQuery);
|
||||
}
|
||||
setShowSessions(true);
|
||||
focusSessionSearch();
|
||||
|
||||
if (searchQuery) {
|
||||
setSessionRefreshLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setSessionRefreshLoading(true);
|
||||
|
||||
try {
|
||||
@@ -3522,6 +3535,18 @@ export const AIChat: Component<AIChatProps> = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleSessions = async () => {
|
||||
const next = !showSessions();
|
||||
if (!next) {
|
||||
setShowSessions(false);
|
||||
setSessionRefreshLoading(false);
|
||||
resetSessionSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
await openSessionPicker();
|
||||
};
|
||||
|
||||
const formatSessionPickerMessageCount = (count: number) =>
|
||||
`${count} ${count === 1 ? 'message' : 'messages'}`;
|
||||
const formatSessionPickerUpdatedAt = (session: ChatSession) => {
|
||||
|
||||
Reference in New Issue
Block a user