feat: add initial assistant message (#36798)
## Description  Fixes #36776 > [!WARNING] > _If no issue exists, please create an issue first, and check with the maintainers if the issue is valid._ ## Automation /ok-to-test tags="@tag.Sanity" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/11275055683> > Commit: a8f155422725c5310b7ac37d49a57995ee20f732 > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=11275055683&attempt=2" target="_blank">Cypress dashboard</a>. > Tags: `@tag.Sanity` > Spec: > <hr>Thu, 10 Oct 2024 14:29:09 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit - **New Features** - Introduced `AssistantSuggestionButton` for enhanced user interaction in the AI chat. - Added support for displaying and applying assistant suggestions in chat threads. - Implemented an editable array component for managing string pairs. - Enhanced configuration options with new properties for initial assistant messages and suggestions. - **Improvements** - Improved state management for dynamic messages in the AI chat widget. - Updated rendering logic for conditional display of suggestions in chat messages. - Added new props to facilitate better interaction and suggestion handling in chat components. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
5fadce541d
commit
a0814e1438
|
|
@ -14,6 +14,7 @@ const _AIChat = (props: AIChatProps, ref: ForwardedRef<HTMLDivElement>) => {
|
||||||
// assistantName,
|
// assistantName,
|
||||||
chatTitle,
|
chatTitle,
|
||||||
isWaitingForResponse = false,
|
isWaitingForResponse = false,
|
||||||
|
onApplyAssistantSuggestion,
|
||||||
onPromptChange,
|
onPromptChange,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
prompt,
|
prompt,
|
||||||
|
|
@ -56,7 +57,12 @@ const _AIChat = (props: AIChatProps, ref: ForwardedRef<HTMLDivElement>) => {
|
||||||
|
|
||||||
<ul className={styles.thread} data-testid="t--aichat-thread">
|
<ul className={styles.thread} data-testid="t--aichat-thread">
|
||||||
{thread.map((message: ChatMessage) => (
|
{thread.map((message: ChatMessage) => (
|
||||||
<ThreadMessage {...message} key={message.id} username={username} />
|
<ThreadMessage
|
||||||
|
{...message}
|
||||||
|
key={message.id}
|
||||||
|
onApplyAssistantSuggestion={onApplyAssistantSuggestion}
|
||||||
|
username={username}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Text } from "@appsmith/wds";
|
||||||
|
import { clsx } from "clsx";
|
||||||
|
import React from "react";
|
||||||
|
import { Button as HeadlessButton } from "react-aria-components";
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
import type { AssistantSuggestionButtonProps } from "./types";
|
||||||
|
|
||||||
|
export const AssistantSuggestionButton = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
}: AssistantSuggestionButtonProps) => {
|
||||||
|
return (
|
||||||
|
<HeadlessButton className={clsx(styles.root, className)} {...rest}>
|
||||||
|
<Text>{children}</Text>
|
||||||
|
</HeadlessButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./AssistantSuggestionButton";
|
||||||
|
export * from "./types";
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
.root {
|
||||||
|
height: 30px;
|
||||||
|
padding: 0 var(--inner-spacing-4);
|
||||||
|
background-color: var(--bg-neutral-subtle-alt, #e7e8e8);
|
||||||
|
border-radius: var(--radius-inner-button, 1.8px);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--bg-neutral-subtle-alt-hover, #f0f1f1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px var(--color-bg),
|
||||||
|
0 0 0 4px var(--color-bd-focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--bg-neutral-subtle-alt-active, #e1e2e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import type { PropsWithChildren } from "react";
|
||||||
|
import type { ButtonProps as HeadlessButtonProps } from "react-aria-components";
|
||||||
|
|
||||||
|
export interface AssistantSuggestionButtonProps
|
||||||
|
extends PropsWithChildren<HeadlessButtonProps> {}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { Text } from "@appsmith/wds";
|
import { Flex, Text } from "@appsmith/wds";
|
||||||
import { clsx } from "clsx";
|
import { clsx } from "clsx";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||||
import { monokai } from "react-syntax-highlighter/dist/cjs/styles/hljs";
|
import { monokai } from "react-syntax-highlighter/dist/cjs/styles/hljs";
|
||||||
|
import { AssistantSuggestionButton } from "../AssistantSuggestionButton";
|
||||||
import { UserAvatar } from "../UserAvatar";
|
import { UserAvatar } from "../UserAvatar";
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
import type { ThreadMessageProps } from "./types";
|
import type { ThreadMessageProps } from "./types";
|
||||||
|
|
@ -12,6 +13,8 @@ export const ThreadMessage = ({
|
||||||
className,
|
className,
|
||||||
content,
|
content,
|
||||||
isAssistant,
|
isAssistant,
|
||||||
|
onApplyAssistantSuggestion,
|
||||||
|
promptSuggestions = [],
|
||||||
username,
|
username,
|
||||||
...rest
|
...rest
|
||||||
}: ThreadMessageProps) => {
|
}: ThreadMessageProps) => {
|
||||||
|
|
@ -50,6 +53,25 @@ export const ThreadMessage = ({
|
||||||
{content}
|
{content}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
{promptSuggestions.length > 0 && (
|
||||||
|
<Flex
|
||||||
|
className={styles.suggestions}
|
||||||
|
gap="var(--inner-spacing-5)"
|
||||||
|
paddingTop="spacing-4"
|
||||||
|
wrap="wrap"
|
||||||
|
>
|
||||||
|
{promptSuggestions.map((suggestion) => (
|
||||||
|
<AssistantSuggestionButton
|
||||||
|
key={suggestion}
|
||||||
|
// eslint-disable-next-line react-perf/jsx-no-new-function-as-prop
|
||||||
|
onPress={() => onApplyAssistantSuggestion?.(suggestion)}
|
||||||
|
>
|
||||||
|
{suggestion}
|
||||||
|
</AssistantSuggestionButton>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,6 @@ export interface ThreadMessageProps extends HTMLProps<HTMLLIElement> {
|
||||||
content: string;
|
content: string;
|
||||||
isAssistant: boolean;
|
isAssistant: boolean;
|
||||||
username: string;
|
username: string;
|
||||||
|
promptSuggestions?: string[];
|
||||||
|
onApplyAssistantSuggestion?: (suggestion: string) => void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ export interface ChatMessage {
|
||||||
id: string;
|
id: string;
|
||||||
content: string;
|
content: string;
|
||||||
isAssistant: boolean;
|
isAssistant: boolean;
|
||||||
|
promptSuggestions?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AIChatProps {
|
export interface AIChatProps {
|
||||||
|
|
@ -15,4 +16,5 @@ export interface AIChatProps {
|
||||||
isWaitingForResponse?: boolean;
|
isWaitingForResponse?: boolean;
|
||||||
onPromptChange: (prompt: string) => void;
|
onPromptChange: (prompt: string) => void;
|
||||||
onSubmit?: () => void;
|
onSubmit?: () => void;
|
||||||
|
onApplyAssistantSuggestion?: (suggestion: string) => void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import {
|
||||||
ATTR_SERVICE_INSTANCE_ID,
|
ATTR_SERVICE_INSTANCE_ID,
|
||||||
} from "@opentelemetry/semantic-conventions/incubating";
|
} from "@opentelemetry/semantic-conventions/incubating";
|
||||||
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
||||||
import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
|
|
||||||
import { registerInstrumentations } from "@opentelemetry/instrumentation";
|
import { registerInstrumentations } from "@opentelemetry/instrumentation";
|
||||||
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
||||||
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
||||||
|
|
|
||||||
152
app/client/src/components/propertyControls/ArrayComponent.tsx
Normal file
152
app/client/src/components/propertyControls/ArrayComponent.tsx
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
import { Button } from "@appsmith/ads";
|
||||||
|
import { debounce } from "lodash";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { ControlWrapper, InputGroup } from "./StyledControls";
|
||||||
|
|
||||||
|
function updateOptionLabel<T>(
|
||||||
|
items: Array<T>,
|
||||||
|
index: number,
|
||||||
|
updatedLabel: string,
|
||||||
|
) {
|
||||||
|
return items.map((option: T, optionIndex) => {
|
||||||
|
if (index !== optionIndex) {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedLabel;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledBox = styled.div`
|
||||||
|
width: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type UpdateItemsFunction = (
|
||||||
|
items: string[],
|
||||||
|
isUpdatedViaKeyboard?: boolean,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
interface ArrayComponentProps {
|
||||||
|
items: string[];
|
||||||
|
updateItems: UpdateItemsFunction;
|
||||||
|
addLabel?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledInputGroup = styled(InputGroup)`
|
||||||
|
> .ads-v2-input__input-section > div {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function ArrayComponent(props: ArrayComponentProps) {
|
||||||
|
const [renderItems, setRenderItems] = useState<string[]>([]);
|
||||||
|
const [typing, setTyping] = useState<boolean>(false);
|
||||||
|
const { items } = props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let { items } = props;
|
||||||
|
|
||||||
|
items = Array.isArray(items) ? items.slice() : [];
|
||||||
|
|
||||||
|
items.length !== 0 && !typing && setRenderItems(items);
|
||||||
|
}, [props, items.length, renderItems.length, typing]);
|
||||||
|
|
||||||
|
const debouncedUpdateItems = useCallback(
|
||||||
|
debounce((updatedItems: string[]) => {
|
||||||
|
props.updateItems(updatedItems, true);
|
||||||
|
}, 200),
|
||||||
|
[props.updateItems],
|
||||||
|
);
|
||||||
|
|
||||||
|
function updateKey(index: number, updatedKey: string) {
|
||||||
|
let { items } = props;
|
||||||
|
|
||||||
|
items = Array.isArray(items) ? items : [];
|
||||||
|
const updatedItems = updateOptionLabel(items, index, updatedKey);
|
||||||
|
const updatedRenderItems = updateOptionLabel(
|
||||||
|
renderItems,
|
||||||
|
index,
|
||||||
|
updatedKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
setRenderItems(updatedRenderItems);
|
||||||
|
debouncedUpdateItems(updatedItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteItem(index: number, isUpdatedViaKeyboard = false) {
|
||||||
|
let { items } = props;
|
||||||
|
|
||||||
|
items = Array.isArray(items) ? items : [];
|
||||||
|
|
||||||
|
const newItems = items.filter((o, i) => i !== index);
|
||||||
|
const newRenderItems = renderItems.filter((o, i) => i !== index);
|
||||||
|
|
||||||
|
setRenderItems(newRenderItems);
|
||||||
|
props.updateItems(newItems, isUpdatedViaKeyboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addItem(e: React.MouseEvent) {
|
||||||
|
let { items } = props;
|
||||||
|
|
||||||
|
items = Array.isArray(items) ? items.slice() : [];
|
||||||
|
|
||||||
|
items.push("");
|
||||||
|
|
||||||
|
const updatedRenderItems = renderItems.slice();
|
||||||
|
|
||||||
|
updatedRenderItems.push("");
|
||||||
|
|
||||||
|
setRenderItems(updatedRenderItems);
|
||||||
|
props.updateItems(items, e.detail === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInputFocus() {
|
||||||
|
setTyping(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInputBlur() {
|
||||||
|
setTyping(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{renderItems.map((item: string, index) => {
|
||||||
|
return (
|
||||||
|
<ControlWrapper key={index} orientation={"HORIZONTAL"}>
|
||||||
|
<StyledInputGroup
|
||||||
|
dataType={"text"}
|
||||||
|
onBlur={onInputBlur}
|
||||||
|
onChange={(value: string) => updateKey(index, value)}
|
||||||
|
onFocus={onInputFocus}
|
||||||
|
value={item}
|
||||||
|
/>
|
||||||
|
<StyledBox />
|
||||||
|
<Button
|
||||||
|
isIconButton
|
||||||
|
kind="tertiary"
|
||||||
|
onClick={(e: React.MouseEvent) =>
|
||||||
|
deleteItem(index, e.detail === 0)
|
||||||
|
}
|
||||||
|
size="sm"
|
||||||
|
startIcon="delete-bin-line"
|
||||||
|
/>
|
||||||
|
</ControlWrapper>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<div className="flex flex-row-reverse mt-1">
|
||||||
|
<Button
|
||||||
|
className="t--property-control-options-add"
|
||||||
|
kind="tertiary"
|
||||||
|
onClick={addItem}
|
||||||
|
size="sm"
|
||||||
|
startIcon="plus"
|
||||||
|
>
|
||||||
|
{props.addLabel || "Add suggestion"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
48
app/client/src/components/propertyControls/ArrayControl.tsx
Normal file
48
app/client/src/components/propertyControls/ArrayControl.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { objectKeys } from "@appsmith/utils";
|
||||||
|
import type { DropdownOption } from "components/constants";
|
||||||
|
import React from "react";
|
||||||
|
import { isDynamicValue } from "utils/DynamicBindingUtils";
|
||||||
|
import { ArrayComponent } from "./ArrayComponent";
|
||||||
|
import type { ControlData, ControlProps } from "./BaseControl";
|
||||||
|
import BaseControl from "./BaseControl";
|
||||||
|
|
||||||
|
class ArrayControl extends BaseControl<ControlProps> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<ArrayComponent
|
||||||
|
items={this.props.propertyValue}
|
||||||
|
updateItems={this.updateItems}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateItems = (items: string[], isUpdatedViaKeyboard = false) => {
|
||||||
|
this.updateProperty(this.props.propertyName, items, isUpdatedViaKeyboard);
|
||||||
|
};
|
||||||
|
|
||||||
|
static getControlType() {
|
||||||
|
return "ARRAY_INPUT";
|
||||||
|
}
|
||||||
|
|
||||||
|
static canDisplayValueInUI(_config: ControlData, value: string): boolean {
|
||||||
|
if (isDynamicValue(value)) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const items: DropdownOption[] = JSON.parse(value);
|
||||||
|
|
||||||
|
for (const x of items) {
|
||||||
|
const keys = objectKeys(x);
|
||||||
|
|
||||||
|
if (!keys.includes("label") || !keys.includes("value")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ArrayControl;
|
||||||
|
|
@ -76,12 +76,14 @@ import type { IconSelectControlV2Props } from "./IconSelectControlV2";
|
||||||
import IconSelectControlV2 from "./IconSelectControlV2";
|
import IconSelectControlV2 from "./IconSelectControlV2";
|
||||||
import PrimaryColumnsControlWDS from "./PrimaryColumnsControlWDS";
|
import PrimaryColumnsControlWDS from "./PrimaryColumnsControlWDS";
|
||||||
import ToolbarButtonListControl from "./ToolbarButtonListControl";
|
import ToolbarButtonListControl from "./ToolbarButtonListControl";
|
||||||
|
import ArrayControl from "./ArrayControl";
|
||||||
|
|
||||||
export const PropertyControls = {
|
export const PropertyControls = {
|
||||||
InputTextControl,
|
InputTextControl,
|
||||||
DropDownControl,
|
DropDownControl,
|
||||||
SwitchControl,
|
SwitchControl,
|
||||||
OptionControl,
|
OptionControl,
|
||||||
|
ArrayControl,
|
||||||
CodeEditorControl,
|
CodeEditorControl,
|
||||||
DatePickerControl,
|
DatePickerControl,
|
||||||
ActionSelectorControl,
|
ActionSelectorControl,
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,6 @@ export const defaultsConfig = {
|
||||||
widgetType: "AI_CHAT",
|
widgetType: "AI_CHAT",
|
||||||
version: 1,
|
version: 1,
|
||||||
responsiveBehavior: ResponsiveBehavior.Fill,
|
responsiveBehavior: ResponsiveBehavior.Fill,
|
||||||
|
initialAssistantMessage: "",
|
||||||
|
initialAssistantSuggestions: [],
|
||||||
} as unknown as WidgetDefaultProps;
|
} as unknown as WidgetDefaultProps;
|
||||||
|
|
|
||||||
|
|
@ -79,16 +79,27 @@ export const propertyPaneContent = [
|
||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
helpText: "Configures a prompt for the assistant",
|
helpText: "Configures an initial assistant message",
|
||||||
propertyName: "systemPrompt",
|
propertyName: "initialAssistantMessage",
|
||||||
label: "Prompt",
|
label: "Initial Assistant Message",
|
||||||
controlType: "INPUT_TEXT",
|
controlType: "INPUT_TEXT",
|
||||||
isJSConvertible: false,
|
isJSConvertible: true,
|
||||||
isBindProperty: false,
|
isBindProperty: true,
|
||||||
isTriggerProperty: false,
|
isTriggerProperty: false,
|
||||||
validation: { type: ValidationTypes.TEXT },
|
validation: { type: ValidationTypes.TEXT },
|
||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
helpText: "Configures initial assistant suggestions",
|
||||||
|
propertyName: "initialAssistantSuggestions",
|
||||||
|
label: "Initial Assistant Suggestions",
|
||||||
|
controlType: "ARRAY_INPUT",
|
||||||
|
isJSConvertible: true,
|
||||||
|
isBindProperty: true,
|
||||||
|
isTriggerProperty: false,
|
||||||
|
validation: { type: ValidationTypes.ARRAY },
|
||||||
|
defaultValue: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
helpText: "Controls the visibility of the widget",
|
helpText: "Controls the visibility of the widget",
|
||||||
propertyName: "isVisible",
|
propertyName: "isVisible",
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,12 @@ import {
|
||||||
|
|
||||||
export interface WDSAIChatWidgetProps
|
export interface WDSAIChatWidgetProps
|
||||||
extends ContainerWidgetProps<WidgetProps> {}
|
extends ContainerWidgetProps<WidgetProps> {}
|
||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
id: string;
|
id: string;
|
||||||
content: string;
|
content: string;
|
||||||
role: "assistant" | "user" | "system";
|
role: "assistant" | "user" | "system";
|
||||||
|
promptSuggestions?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State extends WidgetState {
|
interface State extends WidgetState {
|
||||||
|
|
@ -43,24 +45,7 @@ class WDSAIChatWidget extends BaseWidget<WDSAIChatWidgetProps, State> {
|
||||||
static type = "WDS_AI_CHAT_WIDGET";
|
static type = "WDS_AI_CHAT_WIDGET";
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
messages: [
|
messages: [] as Message[],
|
||||||
{
|
|
||||||
id: "1",
|
|
||||||
content: "Hello! How can I help you?",
|
|
||||||
role: "assistant" as const,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
content: "Find stuck support requests",
|
|
||||||
role: "user" as const,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "3",
|
|
||||||
content:
|
|
||||||
"I'm finding these customer support requests that have been waiting for a response for over a day:",
|
|
||||||
role: "assistant" as const,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
prompt: "",
|
prompt: "",
|
||||||
isWaitingForResponse: false,
|
isWaitingForResponse: false,
|
||||||
};
|
};
|
||||||
|
|
@ -123,13 +108,85 @@ class WDSAIChatWidget extends BaseWidget<WDSAIChatWidgetProps, State> {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
adaptMessages(messages: Message[]): ChatMessage[] {
|
componentDidMount() {
|
||||||
return messages.map((message) => ({
|
// Add initial assistant message with suggestions if they were configured
|
||||||
...message,
|
if (this.props.initialAssistantMessage.length > 0) {
|
||||||
isAssistant: message.role === "assistant",
|
this.setState((state) => ({
|
||||||
}));
|
...state,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
id: Math.random().toString(),
|
||||||
|
content: this.props.initialAssistantMessage,
|
||||||
|
role: "assistant",
|
||||||
|
promptSuggestions: this.props.initialAssistantSuggestions || [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: WDSAIChatWidgetProps): void {
|
||||||
|
// Track changes in the widget's properties and update the local state accordingly
|
||||||
|
|
||||||
|
// Update the initial assistant message
|
||||||
|
if (
|
||||||
|
prevProps.initialAssistantMessage !==
|
||||||
|
this.props.initialAssistantMessage ||
|
||||||
|
prevProps.initialAssistantSuggestions !==
|
||||||
|
this.props.initialAssistantSuggestions
|
||||||
|
) {
|
||||||
|
let updatedMessage: Message | null;
|
||||||
|
|
||||||
|
//
|
||||||
|
if (this.props.initialAssistantMessage.length > 0) {
|
||||||
|
const currentMessage = this.state.messages[0];
|
||||||
|
|
||||||
|
updatedMessage = {
|
||||||
|
// If the initial assistant message is set, update it
|
||||||
|
// Otherwise, create a new one
|
||||||
|
...(currentMessage || {
|
||||||
|
id: Math.random().toString(),
|
||||||
|
role: "assistant",
|
||||||
|
}),
|
||||||
|
content: this.props.initialAssistantMessage,
|
||||||
|
promptSuggestions: this.props.initialAssistantSuggestions,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
updatedMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState((state) => ({
|
||||||
|
...state,
|
||||||
|
messages: updatedMessage ? [updatedMessage] : [],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePrompt = (prompt: string) => {
|
||||||
|
this.setState({ prompt });
|
||||||
|
};
|
||||||
|
|
||||||
|
adaptMessages = (messages: Message[]): ChatMessage[] => {
|
||||||
|
const chatMessages: ChatMessage[] = messages.map((message) => {
|
||||||
|
if (message.role === "assistant") {
|
||||||
|
return {
|
||||||
|
id: message.id,
|
||||||
|
content: message.content,
|
||||||
|
isAssistant: true,
|
||||||
|
promptSuggestions: message.promptSuggestions || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: message.id,
|
||||||
|
content: message.content,
|
||||||
|
isAssistant: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return chatMessages;
|
||||||
|
};
|
||||||
|
|
||||||
handleMessageSubmit = (event?: FormEvent<HTMLFormElement>) => {
|
handleMessageSubmit = (event?: FormEvent<HTMLFormElement>) => {
|
||||||
event?.preventDefault();
|
event?.preventDefault();
|
||||||
|
|
||||||
|
|
@ -148,18 +205,7 @@ class WDSAIChatWidget extends BaseWidget<WDSAIChatWidgetProps, State> {
|
||||||
}),
|
}),
|
||||||
() => {
|
() => {
|
||||||
const messages: Message[] = [...this.state.messages];
|
const messages: Message[] = [...this.state.messages];
|
||||||
|
const params = { messages };
|
||||||
if (this.props.systemPrompt) {
|
|
||||||
messages.unshift({
|
|
||||||
id: String(Date.now()),
|
|
||||||
content: this.props.systemPrompt,
|
|
||||||
role: "system",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
messages,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.executeAction({
|
this.executeAction({
|
||||||
triggerPropertyName: "onClick",
|
triggerPropertyName: "onClick",
|
||||||
|
|
@ -182,6 +228,8 @@ class WDSAIChatWidget extends BaseWidget<WDSAIChatWidgetProps, State> {
|
||||||
id: Math.random().toString(),
|
id: Math.random().toString(),
|
||||||
content: this.props.queryData.choices[0].message.content,
|
content: this.props.queryData.choices[0].message.content,
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
|
// TODO: Add prompt suggestions from the query data, if any
|
||||||
|
promptSuggestions: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
isWaitingForResponse: false,
|
isWaitingForResponse: false,
|
||||||
|
|
@ -190,7 +238,11 @@ class WDSAIChatWidget extends BaseWidget<WDSAIChatWidgetProps, State> {
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePromptChange = (prompt: string) => {
|
handlePromptChange = (prompt: string) => {
|
||||||
this.setState({ prompt });
|
this.updatePrompt(prompt);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleApplyAssistantSuggestion = (suggestion: string) => {
|
||||||
|
this.updatePrompt(suggestion);
|
||||||
};
|
};
|
||||||
|
|
||||||
getWidgetView(): ReactNode {
|
getWidgetView(): ReactNode {
|
||||||
|
|
@ -199,6 +251,7 @@ class WDSAIChatWidget extends BaseWidget<WDSAIChatWidgetProps, State> {
|
||||||
assistantName={this.props.assistantName}
|
assistantName={this.props.assistantName}
|
||||||
chatTitle={this.props.chatTitle}
|
chatTitle={this.props.chatTitle}
|
||||||
isWaitingForResponse={this.state.isWaitingForResponse}
|
isWaitingForResponse={this.state.isWaitingForResponse}
|
||||||
|
onApplyAssistantSuggestion={this.handleApplyAssistantSuggestion}
|
||||||
onPromptChange={this.handlePromptChange}
|
onPromptChange={this.handlePromptChange}
|
||||||
onSubmit={this.handleMessageSubmit}
|
onSubmit={this.handleMessageSubmit}
|
||||||
prompt={this.state.prompt}
|
prompt={this.state.prompt}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user