Skip to content

Commit e1ba6b8

Browse files
kapetrjezekra1
authored andcommitted
feat(ui): add starter prompts (#959)
Signed-off-by: Petr Kadlec <petr@puradesign.cz>
1 parent 4682dbb commit e1ba6b8

File tree

9 files changed

+321
-16
lines changed

9 files changed

+321
-16
lines changed

agents/a2a-ui-test/hello/src/extensions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def __init__(self, ui_type: str, user_greeting: str, display_name: str, tools: l
2626
"links": [{"type": "source-code", "url":"https://github.com/i-am-bee/beeai-platform"}],
2727
"author": { "name": 'Tomas Weiss', "email": "Tomas.Weiss@ibm.com"},
2828
"contributors": [{ "name": 'Petr Kadlec', "email": "petr.kadlec@ibm.com", "url": "https://research.ibm.com/" },{ "name": "Petr Bulánek", "email": "petr.bulanek@ibm.com" }],
29-
"documentation": "This is *content* of documentation field in `beeai_ui` agent ~extension~."
29+
"documentation": "This is *content* of documentation field in `beeai_ui` agent ~extension~.",
30+
"prompt_suggestions":['Write a research report about Generative AI','Should I buy Apple stock now?','Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolor. This is what a long query would look like, 2 lines maximum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolor. This is what a long query would look like, 2 lines maximum...','What is the meaning of life?','Tell me a joke','What is the capital of France?','How do I make a cake?']
3031
}
3132
)

apps/beeai-ui/src/components/TextAreaAutoHeight/TextAreaAutoHeight.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ export const TextAreaAutoHeight = forwardRef<HTMLTextAreaElement, Props>(functio
4949
updateOverflowValue();
5050
}, [updateOverflowValue]);
5151

52-
// This is necessary for the auto height to work properly. React does some optimization and ignores custom Event dispatch if the value is unchanged, which happens with react-hook-form.
52+
// This is necessary for the auto height to work properly. React does some optimization and ignores custom Event dispatch
53+
// if the value is unchanged, which happens with react-hook-form.
5354
useEffect(() => {
5455
const element = textareaRef.current;
5556

apps/beeai-ui/src/modules/agents/api/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export interface UIExtensionParams {
4242
links?: AgentLink[];
4343
author?: AgentAuthor;
4444
contributors?: AgentContributor[];
45+
prompt_suggestions?: string[];
4546
}
4647

4748
export const AGENT_EXTENSION_UI_KEY = 'beeai_ui';

apps/beeai-ui/src/modules/runs/chat/ChatInput.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { memo, useCallback, useRef } from 'react';
1010
import { FormProvider, useForm } from 'react-hook-form';
1111

1212
import { useFileUpload } from '#modules/files/contexts/index.ts';
13+
import { useMessages } from '#modules/messages/contexts/index.ts';
1314

1415
import type { InputBarFormHandle } from '../components/InputBar';
1516
import { InputBar } from '../components/InputBar';
@@ -25,8 +26,9 @@ export const ChatInput = memo(function ChatInput({ onMessageSubmit }: Props) {
2526
const containerRef = useRef<HTMLDivElement>(null);
2627
const formRef = useRef<InputBarFormHandle>(null);
2728

28-
const { isPending, run, cancel } = useAgentRun();
29+
const { agent, isPending, run, cancel } = useAgentRun();
2930
const { isPending: isFileUploadPending } = useFileUpload();
31+
const { messages } = useMessages();
3032

3133
const form = useForm<ChatFormValues>({
3234
mode: 'onChange',
@@ -43,12 +45,16 @@ export const ChatInput = memo(function ChatInput({ onMessageSubmit }: Props) {
4345

4446
const inputValue = watch('input');
4547

48+
const {
49+
ui: { prompt_suggestions },
50+
} = agent;
4651
const isSubmitDisabled = isPending || isFileUploadPending || !inputValue;
4752

4853
return (
4954
<FormProvider {...form}>
5055
<div ref={containerRef}>
5156
<InputBar
57+
promptSuggestions={!messages.length ? prompt_suggestions : undefined}
5258
onSubmit={() => {
5359
handleSubmit(async ({ input }) => {
5460
onMessageSubmit?.();
@@ -65,6 +71,9 @@ export const ChatInput = memo(function ChatInput({ onMessageSubmit }: Props) {
6571
placeholder: 'Ask a question…',
6672
...register('input', { required: true }),
6773
}}
74+
onInputChange={(value) => {
75+
form.setValue('input', value, { shouldValidate: true });
76+
}}
6877
>
6978
{!isPending ? (
7079
<Button

apps/beeai-ui/src/modules/runs/components/InputBar.tsx

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,29 @@
44
*/
55

66
import type { FormEventHandler, PropsWithChildren, ReactNode, Ref, TextareaHTMLAttributes } from 'react';
7-
import { useImperativeHandle, useRef } from 'react';
7+
import { useImperativeHandle, useRef, useState } from 'react';
8+
import type { UseFormRegisterReturn } from 'react-hook-form';
9+
import { mergeRefs } from 'react-merge-refs';
810

911
import { TextAreaAutoHeight } from '#components/TextAreaAutoHeight/TextAreaAutoHeight.tsx';
1012
import { useFileUpload } from '#modules/files/contexts/index.ts';
11-
import { dispatchInputEventOnFormTextarea, submitFormOnEnter } from '#utils/form-utils.ts';
13+
import { dispatchInputEventOnTextarea, submitFormOnEnter } from '#utils/form-utils.ts';
1214

1315
import { FileCard } from '../../files/components/FileCard';
1416
import { FileCardsList } from '../../files/components/FileCardsList';
1517
import { FileUploadButton } from '../../files/components/FileUploadButton';
1618
import { AgentModel } from './AgentModel';
1719
import classes from './InputBar.module.scss';
20+
import { PromptSuggestions } from './PromptSuggestions';
1821

1922
interface Props {
2023
isSubmitDisabled?: boolean;
2124
settings?: ReactNode;
2225
formRef?: Ref<InputBarFormHandle>;
23-
inputProps?: TextareaHTMLAttributes<HTMLTextAreaElement>;
26+
inputProps?: TextareaHTMLAttributes<HTMLTextAreaElement> & UseFormRegisterReturn;
27+
promptSuggestions?: string[];
2428
onSubmit?: FormEventHandler;
29+
onInputChange?: (value: string) => void;
2530
}
2631

2732
export type InputBarFormHandle = {
@@ -33,10 +38,16 @@ export function InputBar({
3338
settings,
3439
formRef: formRefProp,
3540
inputProps,
41+
promptSuggestions,
3642
onSubmit,
43+
onInputChange,
3744
children,
3845
}: PropsWithChildren<Props>) {
3946
const formRef = useRef<HTMLFormElement>(null);
47+
const inputRef = useRef<HTMLTextAreaElement>(null);
48+
49+
const [promptSuggestionsOpen, setPromptSuggestionsOpen] = useState(false);
50+
4051
const { files, removeFile, isDisabled: isFileUploadDisabled } = useFileUpload();
4152

4253
useImperativeHandle(
@@ -48,12 +59,30 @@ export function InputBar({
4859

4960
formElem.reset();
5061

51-
dispatchInputEventOnFormTextarea(formElem);
62+
const inputElem = inputRef.current;
63+
if (inputElem) {
64+
dispatchInputEventOnTextarea(inputElem);
65+
}
5266
},
5367
}),
5468
[],
5569
);
5670

71+
const fillWithInput = (value: string) => {
72+
const formElem = formRef.current;
73+
74+
if (!formElem || !onInputChange) return;
75+
76+
onInputChange(value);
77+
setPromptSuggestionsOpen(false);
78+
79+
const inputElem = inputRef.current;
80+
if (inputElem) {
81+
dispatchInputEventOnTextarea(inputElem);
82+
inputElem.focus();
83+
}
84+
};
85+
5786
return (
5887
<form
5988
className={classes.root}
@@ -82,6 +111,7 @@ export function InputBar({
82111
rows={1}
83112
autoFocus
84113
{...inputProps}
114+
ref={mergeRefs([inputRef, inputProps?.ref])}
85115
className={classes.textarea}
86116
onKeyDown={(event) => !isSubmitDisabled && submitFormOnEnter(event)}
87117
/>
@@ -96,6 +126,16 @@ export function InputBar({
96126

97127
<div className={classes.submit}>{children}</div>
98128
</div>
129+
130+
{promptSuggestions && (
131+
<PromptSuggestions
132+
inputRef={inputRef}
133+
suggestions={promptSuggestions}
134+
isOpen={promptSuggestionsOpen}
135+
setIsOpen={setPromptSuggestionsOpen}
136+
onSubmit={fillWithInput}
137+
/>
138+
)}
99139
</form>
100140
);
101141
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Copyright 2025 © BeeAI a Series of LF Projects, LLC
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
.ref {
7+
position: absolute;
8+
inset: 0;
9+
pointer-events: none;
10+
}
11+
12+
.root {
13+
z-index: z('modal') - 1;
14+
padding: 0 !important;
15+
}
16+
17+
.content {
18+
background-color: $layer;
19+
display: flex;
20+
flex-direction: column;
21+
row-gap: $spacing-03;
22+
}
23+
24+
.heading {
25+
@include type-style(label-02);
26+
line-height: math.div(22, 14);
27+
color: $cool-gray-50;
28+
}
29+
30+
.list {
31+
display: flex;
32+
flex-direction: column;
33+
row-gap: $spacing-02;
34+
}
35+
36+
.button {
37+
@include type-style(label-02);
38+
background-color: transparent;
39+
border: 0;
40+
padding-inline: 0;
41+
padding-block: $spacing-03;
42+
text-align: start;
43+
color: $text-primary;
44+
transition: color $duration-fast-02;
45+
inline-size: 100%;
46+
cursor: pointer;
47+
display: flex;
48+
&:hover {
49+
color: $text-helper;
50+
}
51+
&:focus-visible {
52+
@include focus-outline('outline');
53+
}
54+
> span {
55+
@include line-clamp(2);
56+
}
57+
}

0 commit comments

Comments
 (0)