Skip to content

Commit

Permalink
feat: add CommandManager
Browse files Browse the repository at this point in the history
  • Loading branch information
mikbry committed Feb 29, 2024
1 parent 1f93da5 commit 2efb5d9
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 52 deletions.
8 changes: 4 additions & 4 deletions webapp/components/views/Threads/Prompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { KeyBinding, ShortcutIds, defaultShortcuts } from '@/hooks/useShortcuts'
import logger from '@/utils/logger';
import { ParsedPrompt, TokenValidator, parsePrompt } from '@/utils/parsers';
import { getCaretPosition } from '@/utils/caretposition';
import { Command } from '@/utils/commands/Command';
import { CommandManager } from '@/utils/commands/types';
import { Button } from '../../ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip';
import { ShortcutBadge } from '../../common/ShortCut';
Expand All @@ -30,7 +30,7 @@ import PromptCommandInput from './PromptCommandInput';
export type PromptProps = {
conversationId: string;
prompt: ParsedPrompt;
commands: Command[];
commandManager: CommandManager;
isLoading: boolean;
errorMessage: string;
disabled: boolean;
Expand All @@ -43,7 +43,7 @@ export type PromptProps = {
export default function Prompt({
conversationId,
prompt,
commands,
commandManager,
errorMessage,
disabled,
onUpdatePrompt,
Expand Down Expand Up @@ -119,7 +119,7 @@ export default function Prompt({
</Button>
<PromptCommandInput
value={prompt}
commands={commands}
commandManager={commandManager}
placeholder={t('Send a message...')}
className="m-0 max-h-[240px] min-h-[36px] "
onChange={handleUpdateMessage}
Expand Down
13 changes: 5 additions & 8 deletions webapp/components/views/Threads/PromptCommandInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import { ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { isCommand, ParsedPrompt, parsePrompt, TokenValidator } from '@/utils/parsers';
import { Command } from '@/utils/commands/Command';
import { CommandManager } from '@/utils/commands/types';
import { getCaretCoordinates, getCurrentWord } from '@/utils/caretposition';
import { cn } from '@/lib/utils';
import logger from '@/utils/logger';
Expand All @@ -30,7 +30,7 @@ import { Button } from '../../ui/button';
type PromptCommandProps = {
value?: ParsedPrompt;
placeholder?: string;
commands: Command[];
commandManager: CommandManager;
onChange?: (parsedPrompt: ParsedPrompt) => void;
onKeyDown: (event: KeyboardEvent) => void;
className?: string;
Expand All @@ -42,7 +42,7 @@ type PromptCommandProps = {
function PromptCommandInput({
value,
placeholder,
commands,
commandManager,
className,
onChange,
onFocus,
Expand Down Expand Up @@ -194,11 +194,8 @@ function PromptCommandInput({
}, [handleBlur, handleKeyDown, handleMouseDown, handleSelectionChange]);

const filteredCommands = useMemo(
() =>
commands.filter(
(c) => !(!c.value || c.value?.toLowerCase().indexOf(commandValue.toLowerCase()) === -1),
),
[commands, commandValue],
() => commandManager.filterCommands(commandValue),
[commandManager, commandValue],
);
const commandType = getCommandType(commandValue);
const notFound = commandType ? `No ${commandType}s found.` : '';
Expand Down
14 changes: 7 additions & 7 deletions webapp/components/views/Threads/Thread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ import {
import { getConversationTitle } from '@/utils/conversations';
import validator from '@/utils/parsers/validator';
import { createMessage, changeMessageContent, mergeMessages } from '@/utils/data/messages';
import { getCommands } from '@/utils/commands';
import { getCommandManager } from '@/utils/commands';
import PromptArea from './Prompt';
import PromptsGrid from './PromptsGrid';
import ThreadMenu from './ThreadMenu';
Expand Down Expand Up @@ -164,10 +164,10 @@ function Thread({
const showEmptyChat = !conversationId;
const selectedModel = selectedConversation?.model || activeModel;

const { modelItems, commands } = useMemo(() => {
const { modelItems, commandManager } = useMemo(() => {
const items = getModelsAsItems(providers, backendContext, selectedModel);
const cmds = getCommands(items);
return { modelItems: items, commands: cmds };
const manager = getCommandManager(items);
return { modelItems: items, commandManager: manager };
}, [backendContext, providers, selectedModel]);

useEffect(() => {
Expand All @@ -191,8 +191,8 @@ function Thread({
parsedPrompt: ParsedPrompt,
_previousToken: PromptToken | undefined,
): [PromptToken, PromptToken | undefined] =>
validator(commands, token, parsedPrompt, _previousToken),
[commands],
validator(commandManager, token, parsedPrompt, _previousToken),
[commandManager],
);

const currentPrompt = useMemo(
Expand Down Expand Up @@ -700,7 +700,7 @@ function Thread({
<PromptArea
conversationId={conversationId as string}
disabled={disabled}
commands={commands}
commandManager={commandManager}
prompt={prompt}
isLoading={conversationId ? isProcessing[conversationId] : false}
errorMessage={conversationId ? errorMessage[conversationId] : ''}
Expand Down
60 changes: 43 additions & 17 deletions webapp/utils/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@
// limitations under the License.

import { getMentionName } from '../parsers';
import { Command, CommandType } from './Command';
import { Command, CommandManager, CommandType } from './types';

const actionsItems: Command[] = [
{ value: '/system', label: 'System', group: 'actions', type: CommandType.Action },
{
value: '/system',
label: 'System',
group: 'actions',
type: CommandType.Action,
validate: () => false,
},
];
const parameterItems: Command[] = [
{
Expand All @@ -38,7 +44,30 @@ export const getHashtagCommands = (): Command[] => {
return parameters;
};

export const getCommands = (mentionItems: Partial<Command>[]): Command[] => {
export const getCommandType = (value: string | undefined): CommandType | undefined => {
if (value?.startsWith('/')) {
return CommandType.Action;
}
if (value?.startsWith('#')) {
return CommandType.Parameter;
}
if (value?.startsWith('@')) {
return CommandType.Mention;
}
return undefined;
};

export const compareCommands = (
command1: string | undefined,
command2: string | string,
type?: CommandType,
): boolean => {
const type1 = getCommandType(command1);
const type2 = getCommandType(command2);
return (!type || (type === type1 && type === type2)) && command1 === command2;
};

export const getCommandManager = (mentionItems: Partial<Command>[]): CommandManager => {
const commands = [
...mentionItems
.filter((item) => !item.selected)
Expand All @@ -54,18 +83,15 @@ export const getCommands = (mentionItems: Partial<Command>[]): Command[] => {
...getHashtagCommands(),
...getActionCommands(),
];
return commands;
};

export const getCommandType = (value: string): CommandType | undefined => {
if (value.startsWith('/')) {
return CommandType.Action;
}
if (value.startsWith('#')) {
return CommandType.Parameter;
}
if (value.startsWith('@')) {
return CommandType.Mention;
}
return undefined;
return {
commands,
getCommand: (value: string, type: string) => {
const command = commands.find((m) => compareCommands(m.value, value, type as CommandType));
return command;
},
filterCommands: (commandValue: string): Command[] =>
commands.filter(
(c) => !(!c.value || c.value?.toLowerCase().indexOf(commandValue.toLowerCase()) === -1),
),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,13 @@ export enum CommandType {

export type Command = Ui.MenuItem & {
type: CommandType;
execute?: (value: string) => void;
postValidate?: (value?: string) => boolean;
validate?: (value?: string) => boolean;
};

export type CommandManager = {
commands: Command[];
getCommand: (value: string, type: string) => Command | undefined;
filterCommands: (commandValue: string) => Command[];
};
1 change: 1 addition & 0 deletions webapp/utils/parsers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type PromptToken = {
value: string;
index: number;
state?: PromptTokenState;
blockOtherCommands?: boolean;
};

export type ParsedPrompt = {
Expand Down
26 changes: 10 additions & 16 deletions webapp/utils/parsers/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,11 @@
// limitations under the License.

import logger from '../logger';
import {
ParsedPrompt,
PromptToken,
PromptTokenState,
PromptTokenType,
compareActions,
compareHashtags,
compareMentions,
} from '.';
import { Command } from '../commands/Command';
import { ParsedPrompt, PromptToken, PromptTokenState, PromptTokenType } from '.';
import { CommandManager } from '../commands/types';

const validator = (
commands: Command[],
commandManager: CommandManager,
token: PromptToken,
parsedPrompt: ParsedPrompt,
_previousToken: PromptToken | undefined,
Expand All @@ -42,10 +34,12 @@ const validator = (
let { type } = token;
const isAtCaret = token.value.length + token.index === parsedPrompt.caretPosition;
const isEditing = type !== PromptTokenType.Text && isAtCaret;
let blockOtherCommands: boolean | undefined;
const command = commandManager.getCommand(token.value, type);
if (type === PromptTokenType.Mention) {
if (isEditing) {
state = PromptTokenState.Editing;
} else if (!commands.find((m) => compareMentions(m.value, token.value))) {
} else if (!command) {
// this model is not available
state = PromptTokenState.Error;
} else if (parsedPrompt.tokens.find((to) => to.value === token.value && to.type === type)) {
Expand All @@ -56,7 +50,6 @@ const validator = (
state = PromptTokenState.Disabled;
}
} else if (type === PromptTokenType.Hashtag) {
const command = commands.find((m) => compareHashtags(m.value, token.value));
if (isEditing) {
state = PromptTokenState.Editing;
} else if (!command) {
Expand All @@ -70,15 +63,16 @@ const validator = (
state = PromptTokenState.Disabled;
}
} else if (type === PromptTokenType.Action) {
const command = commands.find((m) => compareActions(m.value, token.value));
if (isEditing) {
state = PromptTokenState.Editing;
} else if (!command) {
// this command is not available
state = PromptTokenState.Error;
} else {
blockOtherCommands = command.validate?.();
}
} else if (type === PromptTokenType.Text && _previousToken?.type === PromptTokenType.Hashtag) {
const previousCommand = commands.find((m) => compareHashtags(m.value, _previousToken.value));
const previousCommand = commandManager.getCommand(_previousToken.value, _previousToken.type);
if (previousCommand && previousCommand.group !== 'parameters-boolean') {
type = PromptTokenType.ParameterValue;
previousToken = { ..._previousToken, state: PromptTokenState.Ok };
Expand All @@ -93,7 +87,7 @@ const validator = (
state = PromptTokenState.Editing;
type = PromptTokenType.Hashtag;
}
return [{ ...token, type, state }, previousToken];
return [{ ...token, type, state, blockOtherCommands }, previousToken];
};

export default validator;

0 comments on commit 2efb5d9

Please sign in to comment.