feat: init AI chat widget (#36610)

## Description


Fixes #36541

> [!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=""

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!WARNING]
> Tests have not run on the HEAD
c4a6e25abc716cc6a54e612a3800ca95079ba8a0 yet
> <hr>Thu, 03 Oct 2024 11:06:19 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


- **New Features**
- Introduced the WDS AI Chat Widget for interactive chat experiences
using OpenAI.
	- Added the AIChat component for enhanced chat functionality.
- Expanded the collection of available widgets with the new WDS AI Chat
Widget.
- Introduced the ThreadMessage component for structured message
rendering in the chat interface.
- Added UserAvatar and ChatTitle components for improved user
interaction and display.
- Introduced new icons and thumbnails for AI Chat and Date Picker to
enhance visual representation.

- **Bug Fixes**
- Resolved issues related to widget integration within the existing
framework.

- **Documentation**
	- Updated configuration files to enhance modularity and organization.
	- Expanded documentation to include new icons and thumbnails.

- **Chores**
- Added the OpenAI, React Markdown, and React Syntax Highlighter
dependencies for improved functionality.
	- Introduced a new type declaration dependency for better type support.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Valera Melnikov <valera@appsmith.com>
Co-authored-by: saiprabhu-dandanayak <saiprabhu.dandanayak@zemosolabs.com>
Co-authored-by: Rudraprasad Das <rudra@appsmith.com>
Co-authored-by: Abhishek Pandey <66054987+a6hishekpandey@users.noreply.github.com>
Co-authored-by: NandanAnantharamu <67676905+NandanAnantharamu@users.noreply.github.com>
Co-authored-by: “NandanAnantharamu” <“nandan@thinkify.io”>
Co-authored-by: Anagh Hegde <anagh.hv@gmail.com>
Co-authored-by: Abhijeet <abhi.nagarnaik@gmail.com>
Co-authored-by: Abhijeet <41686026+abhvsn@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: albinAppsmith <87797149+albinAppsmith@users.noreply.github.com>
Co-authored-by: sneha122 <sneha@appsmith.com>
Co-authored-by: “sneha122” <“sneha@appsmith.com”>
Co-authored-by: Arpit Mohan <mohanarpit@users.noreply.github.com>
Co-authored-by: Nidhi Nair <nidhi@appsmith.com>
Co-authored-by: Nilansh Bansal <nilansh@appsmith.com>
Co-authored-by: Rishabh Rathod <rishabh.rathod@appsmith.com>
Co-authored-by: vadim <vadim@appsmith.com>
This commit is contained in:
Ilia 2024-10-03 13:26:18 +02:00 committed by GitHub
parent 877ee4cd0a
commit 9e2fb95aee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 1814 additions and 24 deletions

View File

@ -5,7 +5,7 @@ const LOG_LEVELS = ["debug", "error"];
const CONFIG_LOG_LEVEL_INDEX = 1; const CONFIG_LOG_LEVEL_INDEX = 1;
module.exports = { module.exports = {
setupFiles: ["jest-canvas-mock"], setupFiles: ["jest-canvas-mock", "<rootDir>/test/__mocks__/reactMarkdown.tsx"],
roots: ["<rootDir>/src"], roots: ["<rootDir>/src"],
transform: { transform: {
"^.+\\.(png|js|ts|tsx)$": "ts-jest", "^.+\\.(png|js|ts|tsx)$": "ts-jest",

View File

@ -161,6 +161,7 @@
"nanoid": "^2.0.4", "nanoid": "^2.0.4",
"node-forge": "^1.3.0", "node-forge": "^1.3.0",
"object-hash": "^3.0.0", "object-hash": "^3.0.0",
"openai": "^4.64.0",
"path-to-regexp": "^6.3.0", "path-to-regexp": "^6.3.0",
"popper.js": "^1.15.0", "popper.js": "^1.15.0",
"prismjs": "^1.27.0", "prismjs": "^1.27.0",

View File

@ -1,6 +1,7 @@
module.exports = { module.exports = {
preset: "ts-jest", preset: "ts-jest",
roots: ["<rootDir>/src"], roots: ["<rootDir>/src"],
setupFiles: ["<rootDir>../../../test/__mocks__/reactMarkdown.tsx"],
testEnvironment: "jsdom", testEnvironment: "jsdom",
moduleNameMapper: { moduleNameMapper: {
"\\.(css)$": "<rootDir>../../../test/__mocks__/styleMock.js", "\\.(css)$": "<rootDir>../../../test/__mocks__/styleMock.js",

View File

@ -26,10 +26,13 @@
"@tabler/icons-react": "^3.10.0", "@tabler/icons-react": "^3.10.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"lodash": "*", "lodash": "*",
"react-aria-components": "^1.2.1" "react-aria-components": "^1.2.1",
"react-markdown": "^9.0.1",
"react-syntax-highlighter": "^15.5.0"
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/react-syntax-highlighter": "^15.5.13",
"eslint-plugin-storybook": "^0.6.10" "eslint-plugin-storybook": "^0.6.10"
}, },
"peerDependencies": { "peerDependencies": {

View File

@ -0,0 +1 @@
export * from "./src";

View File

@ -0,0 +1,85 @@
import { Button, Spinner, Text, TextArea } from "@appsmith/wds";
import type { FormEvent, ForwardedRef, KeyboardEvent } from "react";
import React, { forwardRef, useCallback } from "react";
import { ChatTitle } from "./ChatTitle";
import styles from "./styles.module.css";
import { ThreadMessage } from "./ThreadMessage";
import type { AIChatProps, ChatMessage } from "./types";
import { UserAvatar } from "./UserAvatar";
const MIN_PROMPT_LENGTH = 3;
const _AIChat = (props: AIChatProps, ref: ForwardedRef<HTMLDivElement>) => {
const {
// assistantName,
chatTitle,
description,
isWaitingForResponse = false,
onPromptChange,
onSubmit,
prompt,
promptInputPlaceholder,
thread,
username,
...rest
} = props;
const handleFormSubmit = useCallback(
(event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
onSubmit?.();
},
[onSubmit],
);
const handlePromptInputKeyDown = useCallback(
(event: KeyboardEvent<HTMLTextAreaElement>) => {
if (event.key === "Enter" && event.shiftKey) {
event.preventDefault();
onSubmit?.();
}
},
[onSubmit],
);
return (
<div className={styles.root} ref={ref} {...rest}>
<div className={styles.header}>
{chatTitle != null && <ChatTitle title={chatTitle} />}
{description ?? <Text size="body">{description}</Text>}
<div className={styles.username}>
<UserAvatar username={username} />
<Text size="body">{username}</Text>
</div>
</div>
<ul className={styles.thread}>
{thread.map((message: ChatMessage) => (
<ThreadMessage {...message} key={message.id} username={username} />
))}
{isWaitingForResponse && (
<li>
<Spinner />
</li>
)}
</ul>
<form className={styles.promptForm} onSubmit={handleFormSubmit}>
<TextArea
name="prompt"
onChange={onPromptChange}
onKeyDown={handlePromptInputKeyDown}
placeholder={promptInputPlaceholder}
value={prompt}
/>
<Button isDisabled={prompt.length < MIN_PROMPT_LENGTH} type="submit">
Send
</Button>
</form>
</div>
);
};
export const AIChat = forwardRef(_AIChat);

View File

@ -0,0 +1,13 @@
import { clsx } from "clsx";
import React from "react";
import styles from "./styles.module.css";
import type { ChatTitleProps } from "./types";
export const ChatTitle = ({ className, title, ...rest }: ChatTitleProps) => {
return (
<div className={clsx(styles.root, className)} {...rest}>
<div className={styles.logo} />
{title}
</div>
);
};

View File

@ -0,0 +1,2 @@
export * from "./ChatTitle";
export * from "./types";

View File

@ -0,0 +1,22 @@
.root {
display: flex;
gap: 12px;
align-items: center;
/* TODO: --type-title doesn't exists. Define it */
font-size: var(--type-title, 22.499px);
font-style: normal;
font-weight: 500;
/* TODO: --type-title-lineheight doesn't exists. Define it */
line-height: var(--type-title-lineheight, 31.7px);
}
.logo {
display: inline-block;
width: 48px;
min-width: 48px;
height: 48px;
border-radius: 8px;
/* TODO: --bd-neutral doesn't exists. Define it */
border: 1px solid var(--bd-neutral, #81858b);
background: #f8f8f8;
}

View File

@ -0,0 +1,5 @@
import type { HTMLProps } from "react";
export interface ChatTitleProps extends HTMLProps<HTMLDivElement> {
title: string;
}

View File

@ -0,0 +1,64 @@
import { Text } from "@appsmith/wds";
import { clsx } from "clsx";
import React from "react";
import Markdown from "react-markdown";
import SyntaxHighlighter from "react-syntax-highlighter";
import { monokai } from "react-syntax-highlighter/dist/cjs/styles/hljs";
import { UserAvatar } from "../UserAvatar";
import styles from "./styles.module.css";
import type { ThreadMessageProps } from "./types";
export const ThreadMessage = ({
className,
content,
isAssistant,
username,
...rest
}: ThreadMessageProps) => {
return (
<li
className={clsx(styles.root, className)}
data-assistant={isAssistant}
{...rest}
>
{isAssistant ? (
<div>
<Text className={styles.content}>
<Markdown
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
components={{
code(props) {
const { children, className, ...rest } = props;
const match = /language-(\w+)/.exec(className ?? "");
return match ? (
<SyntaxHighlighter
PreTag="div"
language={match[1]}
style={monokai}
>
{String(children).replace(/\n$/, "")}
</SyntaxHighlighter>
) : (
<code {...rest} className={className}>
{children}
</code>
);
},
}}
>
{content}
</Markdown>
</Text>
</div>
) : (
<>
<UserAvatar className={styles.userAvatar} username={username} />
<div>
<Text className={styles.content}>{content}</Text>
</div>
</>
)}
</li>
);
};

View File

@ -0,0 +1,2 @@
export * from "./ThreadMessage";
export * from "./types";

View File

@ -0,0 +1,27 @@
.root {
display: flex;
gap: 16px;
padding: 12px 0;
}
@container (min-width: 700px) {
.root {
padding: 24px 0;
}
}
.root[data-assistant="false"] {
flex-direction: row-reverse;
}
.root[data-assistant="false"] .sentTime {
text-align: right;
}
.sentTime {
margin: 0 0 8px;
/* TODO: --type-caption doesn't exists. Define it */
font-size: var(--type-caption, 12.247px);
/* TODO: --type-caption-lineheight doesn't exists. Define it */
line-height: var(--type-caption-lineheight, 17.25px);
}

View File

@ -0,0 +1,7 @@
import type { HTMLProps } from "react";
export interface ThreadMessageProps extends HTMLProps<HTMLLIElement> {
content: string;
isAssistant: boolean;
username: string;
}

View File

@ -0,0 +1,27 @@
import { clsx } from "clsx";
import React from "react";
import styles from "./styles.module.css";
import type { UserAvatarProps } from "./types";
export const UserAvatar = ({
className,
username,
...rest
}: UserAvatarProps) => {
const getNameInitials = (username: string) => {
const names = username.split(" ");
// If there is only one name, return the first character of the name.
if (names.length === 1) {
return `${names[0].charAt(0)}`;
}
return `${names[0].charAt(0)}${names[1]?.charAt(0)}`;
};
return (
<span className={clsx(styles.root, className)} {...rest}>
{getNameInitials(username)}
</span>
);
};

View File

@ -0,0 +1,2 @@
export * from "./UserAvatar";
export * from "./types";

View File

@ -0,0 +1,13 @@
.root {
display: inline-block;
width: 28px;
min-width: 28px;
height: 28px;
color: var(--bg-elevation-2, #fff);
text-align: center;
font-size: 14px;
font-weight: 500;
line-height: 28px;
border-radius: var(--inner-spacing-1, 4px);
background: #000;
}

View File

@ -0,0 +1,5 @@
import type { HTMLProps } from "react";
export interface UserAvatarProps extends HTMLProps<HTMLSpanElement> {
username: string;
}

View File

@ -0,0 +1,2 @@
export * from "./types";
export { AIChat } from "./AIChat";

View File

@ -0,0 +1,48 @@
.root {
width: 100%;
border-radius: var(--border-radius-elevation-1);
border: 1px solid var(--color-bd-elevation-1);
/* TODO: --bg-elevation-1 doesn't exists. Define it */
background: var(--bg-elevation-1, #fbfcfd);
}
.header {
display: flex;
flex-direction: column;
gap: 8px;
padding: 22px 40px;
align-items: flex-start;
border-bottom: 1px solid var(--color-bd-elevation-1);
background: rgba(255, 255, 255, 0.45);
}
@container (min-width: 700px) {
.header {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
}
.username {
display: flex;
align-items: center;
gap: 8px;
}
.thread {
display: flex;
flex-direction: column;
gap: 12px;
align-self: stretch;
padding: 0px 40px var(--inner-spacing-5) 40px;
}
.promptForm {
display: flex;
align-items: flex-start;
margin: var(--outer-spacing-4) 0 0 0;
padding: 0 var(--inner-spacing-5) var(--inner-spacing-5)
var(--inner-spacing-5);
gap: var(--outer-spacing-3);
}

View File

@ -0,0 +1,18 @@
export interface ChatMessage {
id: string;
content: string;
isAssistant: boolean;
}
export interface AIChatProps {
thread: ChatMessage[];
prompt: string;
username: string;
promptInputPlaceholder?: string;
chatTitle?: string;
description?: string;
assistantName?: string;
isWaitingForResponse?: boolean;
onPromptChange: (prompt: string) => void;
onSubmit?: () => void;
}

View File

@ -1,3 +1,4 @@
export * from "./components/AIChat";
export * from "./components/Icon"; export * from "./components/Icon";
export * from "./components/Button"; export * from "./components/Button";
export * from "./components/IconButton"; export * from "./components/IconButton";

View File

@ -0,0 +1,2 @@
import React from "react";
export const AIChatIcon = () => <svg xmlns="http://www.w3.org/2000/svg" width="16" height="17" fill="none"><path fill="#000" fill-opacity=".25" fill-rule="evenodd" d="M1.5 1A1.5 1.5 0 0 0 0 2.5v8A1.5 1.5 0 0 0 1.5 12H2V4.5A1.5 1.5 0 0 1 3.5 3H13v-.5A1.5 1.5 0 0 0 11.5 1z" clip-rule="evenodd"/><mask id="a" fill="#fff"><path fill-rule="evenodd" d="M3.5 3A1.5 1.5 0 0 0 2 4.5v8A1.5 1.5 0 0 0 3.5 14H10l2.146 2.146a.5.5 0 0 0 .854-.353V14h.5a1.5 1.5 0 0 0 1.5-1.5v-8A1.5 1.5 0 0 0 13.5 3z" clip-rule="evenodd"/></mask><path fill="#000" d="m10 14 .707-.707-.293-.293H10zm3 0v-1h-1v1zM3 4.5a.5.5 0 0 1 .5-.5V2A2.5 2.5 0 0 0 1 4.5zm0 8v-8H1v8zm.5.5a.5.5 0 0 1-.5-.5H1A2.5 2.5 0 0 0 3.5 15zm6.5 0H3.5v2H10zm2.854 2.44-2.147-2.147-1.414 1.414 2.146 2.147zm-.854.353a.5.5 0 0 1 .854-.354l-1.415 1.415c.945.945 2.561.275 2.561-1.061zM12 14v1.793h2V14zm1.5-1H13v2h.5zm.5-.5a.5.5 0 0 1-.5.5v2a2.5 2.5 0 0 0 2.5-2.5zm0-8v8h2v-8zm-.5-.5a.5.5 0 0 1 .5.5h2A2.5 2.5 0 0 0 13.5 2zm-10 0h10V2h-10z" mask="url(#a)"/><path stroke="#000" stroke-opacity=".75" d="m8.536 6.27.6 1.402.08.184.183.078 1.403.602-1.403.6-.184.08-.078.183-.601 1.403-.602-1.403-.078-.184-.184-.078-1.403-.601 1.403-.602.184-.078.078-.184z"/></svg>;

View File

@ -1,2 +1,2 @@
import React from "react"; import React from "react";
export const ComboboxSelectIcon = () => <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M14.5 2.5h-12a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h7M14.5 7.5l-1 1-1-1"/><circle cx="5.5" cy="7.5" r="2" stroke="#000"/><path fill="#000" d="M7.354 8.646 7 8.293 6.293 9l.353.354zm.292 1.708a.5.5 0 0 0 .708-.708zm-1-1 1 1 .708-.708-1-1z"/></svg>; export const ComboboxSelectIcon = () => <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M14.5 2.5h-12a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h7M14.5 7.5l-1 1-1-1"/><circle cx="5.5" cy="7.5" r="2.079" stroke="#000"/><path fill="#000" d="m7.534 8.685-.424-.424-.849.849.424.424zm.042 1.74a.6.6 0 0 0 .848-.85zm-.89-.891.89.89.848-.848-.89-.89z"/></svg>;

View File

@ -0,0 +1,2 @@
import React from "react";
export const DatePickerIcon = () => <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M12.5 1.5h-10a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-10a1 1 0 0 0-1-1M9.5 5.5v8M5.5 5.5v8M1.5 5.5h12M1.5 9.5h12M4.5 3.5v-3m6 3v-3"/></svg>;

View File

@ -1,2 +1,2 @@
import React from "react"; import React from "react";
export const InputIcon = () => <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="m7.5 9.5-.5-1m-3.5 1 .5-1m0 0 1.5-3 1.5 3m-3 0h3"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M14.5 2.5h-12a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h7M14.5 7.5l-1 1-1-1"/></svg>; export const InputIcon = () => <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="m7.5 9.5-.5-1m-3.5 1 .5-1m0 0 1.5-3 1.5 3m-3 0h3"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".25" d="M12.5 6.5h1m1 0h-1m0 0v8m-1 0h2"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M14.5 2.5h-12a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h7"/></svg>;

View File

@ -0,0 +1,2 @@
import React from "react";
export const AIChatThumbnail = () => <svg xmlns="http://www.w3.org/2000/svg" width="72" height="76" fill="none"><mask id="a" fill="#fff"><path fill-rule="evenodd" d="M40 21a3 3 0 0 1 3 3v18a3 3 0 0 1-3 3H21l-7.257 8.063c-.613.681-1.743.247-1.743-.669V45H9a3 3 0 0 1-3-3V24a3 3 0 0 1 3-3z" clip-rule="evenodd"/></mask><path fill="#fff" fill-rule="evenodd" d="M40 21a3 3 0 0 1 3 3v18a3 3 0 0 1-3 3H21l-7.257 8.063c-.613.681-1.743.247-1.743-.669V45H9a3 3 0 0 1-3-3V24a3 3 0 0 1 3-3z" clip-rule="evenodd"/><path fill="#CDD5DF" d="m21 45-.743-.669.298-.331H21zm-7.257 8.063.744.669zM12 45v-1h1v1zm30-21a2 2 0 0 0-2-2v-2a4 4 0 0 1 4 4zm0 18V24h2v18zm-2 2a2 2 0 0 0 2-2h2a4 4 0 0 1-4 4zm-19 0h19v2H21zm-8 8.394 7.257-8.063 1.486 1.338-7.256 8.063zm0 0 1.487 1.338C13.26 55.094 11 54.227 11 52.394zM13 45v7.394h-2V45zm-4-1h3v2H9zm-2-2a2 2 0 0 0 2 2v2a4 4 0 0 1-4-4zm0-18v18H5V24zm2-2a2 2 0 0 0-2 2H5a4 4 0 0 1 4-4zm31 0H9v-2h31z" mask="url(#a)"/><mask id="b" fill="#fff"><path fill-rule="evenodd" d="M32 29a3 3 0 0 0-3 3v18a3 3 0 0 0 3 3h19l7.257 8.063c.613.681 1.743.247 1.743-.669V53h3a3 3 0 0 0 3-3V32a3 3 0 0 0-3-3z" clip-rule="evenodd"/></mask><path fill="#FFBFA1" fill-rule="evenodd" d="M32 29a3 3 0 0 0-3 3v18a3 3 0 0 0 3 3h19l7.257 8.063c.613.681 1.743.247 1.743-.669V53h3a3 3 0 0 0 3-3V32a3 3 0 0 0-3-3z" clip-rule="evenodd"/><path fill="#CC3D00" d="m51 53 .743-.669-.298-.331H51zm7.257 8.063-.744.669zM60 53v-1h-1v1zM30 32a2 2 0 0 1 2-2v-2a4 4 0 0 0-4 4zm0 18V32h-2v18zm2 2a2 2 0 0 1-2-2h-2a4 4 0 0 0 4 4zm19 0H32v2h19zm8 8.394-7.257-8.063-1.486 1.338 7.256 8.063zm0 0-1.487 1.338C58.74 63.094 61 62.227 61 60.394zM59 53v7.394h2V53zm4-1h-3v2h3zm2-2a2 2 0 0 1-2 2v2a4 4 0 0 0 4-4zm0-18v18h2V32zm-2-2a2 2 0 0 1 2 2h2a4 4 0 0 0-4-4zm-31 0h31v-2H32z" mask="url(#b)"/><path fill="#fff" stroke="#CC3D00" d="M41.434 42.657a8.5 8.5 0 0 0 4.223-4.223 8.5 8.5 0 0 0 4.223 4.223 8.5 8.5 0 0 0-4.223 4.223 8.5 8.5 0 0 0-4.223-4.223ZM51.17 34.263l.073-.145.072.145a6.5 6.5 0 0 0 2.907 2.907l.145.073-.145.072a6.5 6.5 0 0 0-2.907 2.907l-.072.145-.073-.145a6.5 6.5 0 0 0-2.907-2.907l-.145-.072.145-.073a6.5 6.5 0 0 0 2.907-2.907Z"/></svg>;

View File

@ -0,0 +1,2 @@
import React from "react";
export const DatePickerThumbnail = () => <svg xmlns="http://www.w3.org/2000/svg" width="72" height="76" fill="none"><rect width="55" height="23" x="8.5" y="26.5" fill="#fff" rx="2.5"/><rect width="55" height="23" x="8.5" y="26.5" stroke="#CDD5DF" rx="2.5"/><path stroke="#CC3D00" stroke-linecap="round" stroke-linejoin="round" d="M25.5 32.5h-10a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-10a1 1 0 0 0-1-1M22.5 36.5v8M18.5 36.5v8M14.5 36.5h12M14.5 40.5h12M17.5 34.5v-3m6 3v-3"/><path stroke="#99A4B3" stroke-linecap="round" stroke-linejoin="round" d="m56.5 36.5-3 3-3-3"/></svg>;

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="17" fill="none"><path fill="#000" fill-opacity=".25" fill-rule="evenodd" d="M1.5 1A1.5 1.5 0 0 0 0 2.5v8A1.5 1.5 0 0 0 1.5 12H2V4.5A1.5 1.5 0 0 1 3.5 3H13v-.5A1.5 1.5 0 0 0 11.5 1z" clip-rule="evenodd"/><mask id="a" fill="#fff"><path fill-rule="evenodd" d="M3.5 3A1.5 1.5 0 0 0 2 4.5v8A1.5 1.5 0 0 0 3.5 14H10l2.146 2.146a.5.5 0 0 0 .854-.353V14h.5a1.5 1.5 0 0 0 1.5-1.5v-8A1.5 1.5 0 0 0 13.5 3z" clip-rule="evenodd"/></mask><path fill="#000" d="m10 14 .707-.707-.293-.293H10zm3 0v-1h-1v1zM3 4.5a.5.5 0 0 1 .5-.5V2A2.5 2.5 0 0 0 1 4.5zm0 8v-8H1v8zm.5.5a.5.5 0 0 1-.5-.5H1A2.5 2.5 0 0 0 3.5 15zm6.5 0H3.5v2H10zm2.854 2.44-2.147-2.147-1.414 1.414 2.146 2.147zm-.854.353a.5.5 0 0 1 .854-.354l-1.415 1.415c.945.945 2.561.275 2.561-1.061zM12 14v1.793h2V14zm1.5-1H13v2h.5zm.5-.5a.5.5 0 0 1-.5.5v2a2.5 2.5 0 0 0 2.5-2.5zm0-8v8h2v-8zm-.5-.5a.5.5 0 0 1 .5.5h2A2.5 2.5 0 0 0 13.5 2zm-10 0h10V2h-10z" mask="url(#a)"/><path stroke="#000" stroke-opacity=".75" d="m8.536 6.27.6 1.402.08.184.183.078 1.403.602-1.403.6-.184.08-.078.183-.601 1.403-.602-1.403-.078-.184-.184-.078-1.403-.601 1.403-.602.184-.078.078-.184z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M14.5 2.5h-12a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h7M14.5 7.5l-1 1-1-1"/><circle cx="5.5" cy="7.5" r="2" stroke="#000"/><path fill="#000" d="M7.354 8.646 7 8.293 6.293 9l.353.354zm.292 1.708a.5.5 0 0 0 .708-.708zm-1-1 1 1 .708-.708-1-1z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M14.5 2.5h-12a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h7M14.5 7.5l-1 1-1-1"/><circle cx="5.5" cy="7.5" r="2.079" stroke="#000"/><path fill="#000" d="m7.534 8.685-.424-.424-.849.849.424.424zm.042 1.74a.6.6 0 0 0 .848-.85zm-.89-.891.89.89.848-.848-.89-.89z"/></svg>

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 397 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M12.5 1.5h-10a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-10a1 1 0 0 0-1-1M9.5 5.5v8M5.5 5.5v8M1.5 5.5h12M1.5 9.5h12M4.5 3.5v-3m6 3v-3"/></svg>

After

Width:  |  Height:  |  Size: 293 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="m7.5 9.5-.5-1m-3.5 1 .5-1m0 0 1.5-3 1.5 3m-3 0h3"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M14.5 2.5h-12a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h7M14.5 7.5l-1 1-1-1"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="m7.5 9.5-.5-1m-3.5 1 .5-1m0 0 1.5-3 1.5 3m-3 0h3"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".25" d="M12.5 6.5h1m1 0h-1m0 0v8m-1 0h2"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M14.5 2.5h-12a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h7"/></svg>

Before

Width:  |  Height:  |  Size: 338 B

After

Width:  |  Height:  |  Size: 445 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="76" fill="none"><mask id="a" fill="#fff"><path fill-rule="evenodd" d="M40 21a3 3 0 0 1 3 3v18a3 3 0 0 1-3 3H21l-7.257 8.063c-.613.681-1.743.247-1.743-.669V45H9a3 3 0 0 1-3-3V24a3 3 0 0 1 3-3z" clip-rule="evenodd"/></mask><path fill="#fff" fill-rule="evenodd" d="M40 21a3 3 0 0 1 3 3v18a3 3 0 0 1-3 3H21l-7.257 8.063c-.613.681-1.743.247-1.743-.669V45H9a3 3 0 0 1-3-3V24a3 3 0 0 1 3-3z" clip-rule="evenodd"/><path fill="#CDD5DF" d="m21 45-.743-.669.298-.331H21zm-7.257 8.063.744.669zM12 45v-1h1v1zm30-21a2 2 0 0 0-2-2v-2a4 4 0 0 1 4 4zm0 18V24h2v18zm-2 2a2 2 0 0 0 2-2h2a4 4 0 0 1-4 4zm-19 0h19v2H21zm-8 8.394 7.257-8.063 1.486 1.338-7.256 8.063zm0 0 1.487 1.338C13.26 55.094 11 54.227 11 52.394zM13 45v7.394h-2V45zm-4-1h3v2H9zm-2-2a2 2 0 0 0 2 2v2a4 4 0 0 1-4-4zm0-18v18H5V24zm2-2a2 2 0 0 0-2 2H5a4 4 0 0 1 4-4zm31 0H9v-2h31z" mask="url(#a)"/><mask id="b" fill="#fff"><path fill-rule="evenodd" d="M32 29a3 3 0 0 0-3 3v18a3 3 0 0 0 3 3h19l7.257 8.063c.613.681 1.743.247 1.743-.669V53h3a3 3 0 0 0 3-3V32a3 3 0 0 0-3-3z" clip-rule="evenodd"/></mask><path fill="#FFBFA1" fill-rule="evenodd" d="M32 29a3 3 0 0 0-3 3v18a3 3 0 0 0 3 3h19l7.257 8.063c.613.681 1.743.247 1.743-.669V53h3a3 3 0 0 0 3-3V32a3 3 0 0 0-3-3z" clip-rule="evenodd"/><path fill="#CC3D00" d="m51 53 .743-.669-.298-.331H51zm7.257 8.063-.744.669zM60 53v-1h-1v1zM30 32a2 2 0 0 1 2-2v-2a4 4 0 0 0-4 4zm0 18V32h-2v18zm2 2a2 2 0 0 1-2-2h-2a4 4 0 0 0 4 4zm19 0H32v2h19zm8 8.394-7.257-8.063-1.486 1.338 7.256 8.063zm0 0-1.487 1.338C58.74 63.094 61 62.227 61 60.394zM59 53v7.394h2V53zm4-1h-3v2h3zm2-2a2 2 0 0 1-2 2v2a4 4 0 0 0 4-4zm0-18v18h2V32zm-2-2a2 2 0 0 1 2 2h2a4 4 0 0 0-4-4zm-31 0h31v-2H32z" mask="url(#b)"/><path fill="#fff" stroke="#CC3D00" d="M41.434 42.657a8.5 8.5 0 0 0 4.223-4.223 8.5 8.5 0 0 0 4.223 4.223 8.5 8.5 0 0 0-4.223 4.223 8.5 8.5 0 0 0-4.223-4.223ZM51.17 34.263l.073-.145.072.145a6.5 6.5 0 0 0 2.907 2.907l.145.073-.145.072a6.5 6.5 0 0 0-2.907 2.907l-.072.145-.073-.145a6.5 6.5 0 0 0-2.907-2.907l-.145-.072.145-.073a6.5 6.5 0 0 0 2.907-2.907Z"/></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="76" fill="none"><rect width="55" height="23" x="8.5" y="26.5" fill="#fff" rx="2.5"/><rect width="55" height="23" x="8.5" y="26.5" stroke="#CDD5DF" rx="2.5"/><path stroke="#CC3D00" stroke-linecap="round" stroke-linejoin="round" d="M25.5 32.5h-10a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-10a1 1 0 0 0-1-1M22.5 36.5v8M18.5 36.5v8M14.5 36.5h12M14.5 40.5h12M17.5 34.5v-3m6 3v-3"/><path stroke="#99A4B3" stroke-linecap="round" stroke-linejoin="round" d="m56.5 36.5-3 3-3-3"/></svg>

After

Width:  |  Height:  |  Size: 542 B

View File

@ -1,8 +1,10 @@
export { AIChatThumbnail } from "./components/Thumbnails/AIChatThumbnail";
export { ButtonThumbnail } from "./components/Thumbnails/ButtonThumbnail"; export { ButtonThumbnail } from "./components/Thumbnails/ButtonThumbnail";
export { CheckboxGroupThumbnail } from "./components/Thumbnails/CheckboxGroupThumbnail"; export { CheckboxGroupThumbnail } from "./components/Thumbnails/CheckboxGroupThumbnail";
export { CheckboxThumbnail } from "./components/Thumbnails/CheckboxThumbnail"; export { CheckboxThumbnail } from "./components/Thumbnails/CheckboxThumbnail";
export { ComboboxSelectThumbnail } from "./components/Thumbnails/ComboboxSelectThumbnail"; export { ComboboxSelectThumbnail } from "./components/Thumbnails/ComboboxSelectThumbnail";
export { CurrencyInputThumbnail } from "./components/Thumbnails/CurrencyInputThumbnail"; export { CurrencyInputThumbnail } from "./components/Thumbnails/CurrencyInputThumbnail";
export { DatePickerThumbnail } from "./components/Thumbnails/DatePickerThumbnail";
export { EmailInputThumbnail } from "./components/Thumbnails/EmailInputThumbnail"; export { EmailInputThumbnail } from "./components/Thumbnails/EmailInputThumbnail";
export { HeadingThumbnail } from "./components/Thumbnails/HeadingThumbnail"; export { HeadingThumbnail } from "./components/Thumbnails/HeadingThumbnail";
export { IconButtonThumbnail } from "./components/Thumbnails/IconButtonThumbnail"; export { IconButtonThumbnail } from "./components/Thumbnails/IconButtonThumbnail";
@ -25,11 +27,13 @@ export { SwitchThumbnail } from "./components/Thumbnails/SwitchThumbnail";
export { TableThumbnail } from "./components/Thumbnails/TableThumbnail"; export { TableThumbnail } from "./components/Thumbnails/TableThumbnail";
export { ToolbarButtonsThumbnail } from "./components/Thumbnails/ToolbarButtonsThumbnail"; export { ToolbarButtonsThumbnail } from "./components/Thumbnails/ToolbarButtonsThumbnail";
export { ZoneThumbnail } from "./components/Thumbnails/ZoneThumbnail"; export { ZoneThumbnail } from "./components/Thumbnails/ZoneThumbnail";
export { AIChatIcon } from "./components/Icons/AIChatIcon";
export { ButtonIcon } from "./components/Icons/ButtonIcon"; export { ButtonIcon } from "./components/Icons/ButtonIcon";
export { CheckboxGroupIcon } from "./components/Icons/CheckboxGroupIcon"; export { CheckboxGroupIcon } from "./components/Icons/CheckboxGroupIcon";
export { CheckboxIcon } from "./components/Icons/CheckboxIcon"; export { CheckboxIcon } from "./components/Icons/CheckboxIcon";
export { ComboboxSelectIcon } from "./components/Icons/ComboboxSelectIcon"; export { ComboboxSelectIcon } from "./components/Icons/ComboboxSelectIcon";
export { CurrencyInputIcon } from "./components/Icons/CurrencyInputIcon"; export { CurrencyInputIcon } from "./components/Icons/CurrencyInputIcon";
export { DatePickerIcon } from "./components/Icons/DatePickerIcon";
export { EmailInputIcon } from "./components/Icons/EmailInputIcon"; export { EmailInputIcon } from "./components/Icons/EmailInputIcon";
export { HeadingIcon } from "./components/Icons/HeadingIcon"; export { HeadingIcon } from "./components/Icons/HeadingIcon";
export { IconButtonIcon } from "./components/Icons/IconButtonIcon"; export { IconButtonIcon } from "./components/Icons/IconButtonIcon";

View File

@ -1,10 +1,12 @@
import { Meta } from "@storybook/addon-docs"; import { Meta } from "@storybook/addon-docs";
import { Flex } from "@appsmith/wds"; import { Flex } from "@appsmith/wds";
import { AIChatIcon } from "../components/Icons/AIChatIcon";
import { ButtonIcon } from "../components/Icons/ButtonIcon"; import { ButtonIcon } from "../components/Icons/ButtonIcon";
import { CheckboxGroupIcon } from "../components/Icons/CheckboxGroupIcon"; import { CheckboxGroupIcon } from "../components/Icons/CheckboxGroupIcon";
import { CheckboxIcon } from "../components/Icons/CheckboxIcon"; import { CheckboxIcon } from "../components/Icons/CheckboxIcon";
import { ComboboxSelectIcon } from "../components/Icons/ComboboxSelectIcon"; import { ComboboxSelectIcon } from "../components/Icons/ComboboxSelectIcon";
import { CurrencyInputIcon } from "../components/Icons/CurrencyInputIcon"; import { CurrencyInputIcon } from "../components/Icons/CurrencyInputIcon";
import { DatePickerIcon } from "../components/Icons/DatePickerIcon";
import { EmailInputIcon } from "../components/Icons/EmailInputIcon"; import { EmailInputIcon } from "../components/Icons/EmailInputIcon";
import { HeadingIcon } from "../components/Icons/HeadingIcon"; import { HeadingIcon } from "../components/Icons/HeadingIcon";
import { IconButtonIcon } from "../components/Icons/IconButtonIcon"; import { IconButtonIcon } from "../components/Icons/IconButtonIcon";
@ -36,11 +38,13 @@ Icon set for Entity Explorer Panel, which provides a visual representation of th
export const Icons = () => { export const Icons = () => {
return ( return (
<Flex gap="spacing-4" wrap="wrap"> <Flex gap="spacing-4" wrap="wrap">
<AIChatIcon />
<ButtonIcon /> <ButtonIcon />
<CheckboxGroupIcon /> <CheckboxGroupIcon />
<CheckboxIcon /> <CheckboxIcon />
<ComboboxSelectIcon /> <ComboboxSelectIcon />
<CurrencyInputIcon /> <CurrencyInputIcon />
<DatePickerIcon />
<EmailInputIcon /> <EmailInputIcon />
<HeadingIcon /> <HeadingIcon />
<IconButtonIcon /> <IconButtonIcon />

View File

@ -1,10 +1,12 @@
import { Meta } from "@storybook/addon-docs"; import { Meta } from "@storybook/addon-docs";
import { Flex } from "@appsmith/wds"; import { Flex } from "@appsmith/wds";
import { AIChatThumbnail } from "../components/Thumbnails/AIChatThumbnail";
import { ButtonThumbnail } from "../components/Thumbnails/ButtonThumbnail"; import { ButtonThumbnail } from "../components/Thumbnails/ButtonThumbnail";
import { CheckboxGroupThumbnail } from "../components/Thumbnails/CheckboxGroupThumbnail"; import { CheckboxGroupThumbnail } from "../components/Thumbnails/CheckboxGroupThumbnail";
import { CheckboxThumbnail } from "../components/Thumbnails/CheckboxThumbnail"; import { CheckboxThumbnail } from "../components/Thumbnails/CheckboxThumbnail";
import { ComboboxSelectThumbnail } from "../components/Thumbnails/ComboboxSelectThumbnail"; import { ComboboxSelectThumbnail } from "../components/Thumbnails/ComboboxSelectThumbnail";
import { CurrencyInputThumbnail } from "../components/Thumbnails/CurrencyInputThumbnail"; import { CurrencyInputThumbnail } from "../components/Thumbnails/CurrencyInputThumbnail";
import { DatePickerThumbnail } from "../components/Thumbnails/DatePickerThumbnail";
import { EmailInputThumbnail } from "../components/Thumbnails/EmailInputThumbnail"; import { EmailInputThumbnail } from "../components/Thumbnails/EmailInputThumbnail";
import { HeadingThumbnail } from "../components/Thumbnails/HeadingThumbnail"; import { HeadingThumbnail } from "../components/Thumbnails/HeadingThumbnail";
import { IconButtonThumbnail } from "../components/Thumbnails/IconButtonThumbnail"; import { IconButtonThumbnail } from "../components/Thumbnails/IconButtonThumbnail";
@ -37,11 +39,13 @@ Icon set for Widget Explorer Panel, which provides a visual representation of th
export const Icons = () => { export const Icons = () => {
return ( return (
<Flex gap="spacing-4" wrap="wrap"> <Flex gap="spacing-4" wrap="wrap">
<AIChatThumbnail />
<ButtonThumbnail /> <ButtonThumbnail />
<CheckboxGroupThumbnail /> <CheckboxGroupThumbnail />
<CheckboxThumbnail /> <CheckboxThumbnail />
<ComboboxSelectThumbnail /> <ComboboxSelectThumbnail />
<CurrencyInputThumbnail /> <CurrencyInputThumbnail />
<DatePickerThumbnail />
<EmailInputThumbnail /> <EmailInputThumbnail />
<HeadingThumbnail /> <HeadingThumbnail />
<IconButtonThumbnail /> <IconButtonThumbnail />

View File

@ -0,0 +1,3 @@
import WDSAIChatWidget from "./widget";
export { WDSAIChatWidget };

View File

@ -0,0 +1,11 @@
import type { AnvilConfig } from "WidgetProvider/constants";
export const anvilConfig: AnvilConfig = {
isLargeWidget: false,
widgetSize: {
minWidth: {
base: "100%",
"180px": "sizing-30",
},
},
};

View File

@ -0,0 +1,5 @@
import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils";
export const autocompleteConfig = {
isVisible: DefaultAutocompleteDefinitions.isVisible,
};

View File

@ -0,0 +1,10 @@
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
import type { WidgetDefaultProps } from "WidgetProvider/constants";
export const defaultsConfig = {
isVisible: true,
widgetName: "AIChat",
widgetType: "AI_CHAT",
version: 1,
responsiveBehavior: ResponsiveBehavior.Fill,
} as unknown as WidgetDefaultProps;

View File

@ -0,0 +1,17 @@
import { anvilConfig } from "./anvilConfig";
import { metaConfig } from "./metaConfig";
import { defaultsConfig } from "./defaultConfig";
import { propertyPaneContent } from "./propertyPaneContent";
import { propertyPaneStyle } from "./propertyPaneStyle";
import { methodsConfig } from "./methodsConfig";
import { autocompleteConfig } from "./autocompleteConfig";
export {
anvilConfig,
metaConfig,
defaultsConfig,
propertyPaneContent,
propertyPaneStyle,
methodsConfig,
autocompleteConfig,
};

View File

@ -0,0 +1,9 @@
import { WIDGET_TAGS } from "constants/WidgetConstants";
import type { WidgetBaseConfiguration } from "WidgetProvider/constants";
export const metaConfig: WidgetBaseConfiguration = {
name: "AIChat",
tags: [WIDGET_TAGS.CONTENT],
needsMeta: true,
searchTags: ["chat"],
};

View File

@ -0,0 +1,6 @@
import { AIChatIcon, AIChatThumbnail } from "appsmith-icons";
export const methodsConfig = {
IconCmp: AIChatIcon,
ThumbnailCmp: AIChatThumbnail,
};

View File

@ -0,0 +1,140 @@
import { ValidationTypes } from "constants/WidgetValidation";
export const propertyPaneContent = [
{
sectionName: "General",
children: [
{
helpText: "Select a query to submit when a chat message is sent.",
propertyName: "query",
label: "Query to trigger",
controlType: "INPUT_TEXT",
placeholderText: "Value",
isJSConvertible: false,
isBindProperty: false,
isTriggerProperty: false,
dependencies: ["queryData", "queryRun"],
updateHook: (
_props: unknown,
propertyPath: string,
propertyValue: string,
) => {
const propertiesToUpdate = [{ propertyPath, propertyValue }];
propertiesToUpdate.push({
propertyPath: "queryData",
propertyValue: `{{${propertyValue}.data}}`,
});
propertiesToUpdate.push({
propertyPath: "queryRun",
propertyValue: propertyValue,
});
return propertiesToUpdate;
},
},
{
helpText: "Adds a header to the chat",
propertyName: "chatTitle",
label: "Title",
controlType: "INPUT_TEXT",
isJSConvertible: false,
isBindProperty: false,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
defaultValue: "",
},
{
helpText:
"Adds a description to help users understand how to use the chat",
propertyName: "chatDescription",
label: "Description",
controlType: "INPUT_TEXT",
isJSConvertible: false,
isBindProperty: false,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
defaultValue: "",
},
{
helpText: "Adds a placeholder text to the message input box",
propertyName: "promptInputPlaceholder",
label: "Placeholder",
controlType: "INPUT_TEXT",
isJSConvertible: false,
isBindProperty: false,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
defaultValue: "",
},
{
helpText: "Gives the open AI Assistant a name to be displayed in chat",
propertyName: "assistantName",
label: "Assistant Name",
controlType: "INPUT_TEXT",
isJSConvertible: false,
isBindProperty: false,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
defaultValue: "",
},
{
helpText: "Configures a prompt for the assistant",
propertyName: "systemPrompt",
label: "Prompt",
controlType: "INPUT_TEXT",
isJSConvertible: false,
isBindProperty: false,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
defaultValue: "",
},
{
helpText: "Controls the visibility of the widget",
propertyName: "isVisible",
label: "Visible",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
defaultValue: true,
},
],
},
{
sectionName: "Hidden props",
children: [
{
propertyName: "queryData",
label: "",
controlType: "INPUT_TEXT",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
hidden: () => true,
},
{
propertyName: "queryRun",
label: "",
controlType: "INPUT_TEXT",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
hidden: () => true,
},
// Fake hidden prop to pass the username to the widget
{
propertyName: "username",
label: "",
controlType: "INPUT_TEXT",
defaultValue: "{{appsmith.user.username}}",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
hidden: () => true,
invisible: true,
},
],
},
];

View File

@ -0,0 +1 @@
export const propertyPaneStyle = [];

View File

@ -0,0 +1,214 @@
import { AIChat, type ChatMessage } from "@appsmith/wds";
import {
EventType,
type ExecutionResult,
} from "constants/AppsmithActionConstants/ActionConstants";
import type { SetterConfig, Stylesheet } from "entities/AppTheming";
import React, { type FormEvent, type ReactNode } from "react";
import type {
AnvilConfig,
AutocompletionDefinitions,
WidgetBaseConfiguration,
WidgetDefaultProps,
} from "WidgetProvider/constants";
import type { DerivedPropertiesMap } from "WidgetProvider/factory";
import type { WidgetProps, WidgetState } from "widgets/BaseWidget";
import BaseWidget from "widgets/BaseWidget";
import type { ContainerWidgetProps } from "widgets/ContainerWidget/widget";
import {
anvilConfig,
autocompleteConfig,
defaultsConfig,
metaConfig,
methodsConfig,
propertyPaneContent,
propertyPaneStyle,
} from "./config";
export interface WDSAIChatWidgetProps
extends ContainerWidgetProps<WidgetProps> {}
export interface Message {
id: string;
content: string;
role: "assistant" | "user" | "system";
}
interface State extends WidgetState {
messages: Message[];
prompt: string;
isWaitingForResponse: boolean;
}
class WDSAIChatWidget extends BaseWidget<WDSAIChatWidgetProps, State> {
static type = "WDS_AI_CHAT_WIDGET";
state = {
messages: [
{
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: "",
isWaitingForResponse: false,
};
static getConfig(): WidgetBaseConfiguration {
return metaConfig;
}
static getDefaults(): WidgetDefaultProps {
return defaultsConfig;
}
static getPropertyPaneConfig() {
return [];
}
static getPropertyPaneContentConfig() {
return propertyPaneContent;
}
static getPropertyPaneStyleConfig() {
return propertyPaneStyle;
}
static getMethods() {
return methodsConfig;
}
static getAutocompleteDefinitions(): AutocompletionDefinitions {
return autocompleteConfig;
}
static getSetterConfig(): SetterConfig | null {
return {
__setters: {
setVisibility: {
path: "isVisible",
type: "boolean",
},
},
};
}
static getDerivedPropertiesMap(): DerivedPropertiesMap {
return {};
}
static getDefaultPropertiesMap(): Record<string, string> {
return {};
}
static getMetaPropertiesMap(): Record<string, unknown> {
return {};
}
static getAnvilConfig(): AnvilConfig | null {
return anvilConfig;
}
static getStylesheetConfig(): Stylesheet {
return {};
}
adaptMessages(messages: Message[]): ChatMessage[] {
return messages.map((message) => ({
...message,
isAssistant: message.role === "assistant",
}));
}
handleMessageSubmit = (event?: FormEvent<HTMLFormElement>) => {
event?.preventDefault();
this.setState(
(state) => ({
messages: [
...state.messages,
{
id: String(Date.now()),
content: this.state.prompt,
role: "user",
},
],
prompt: "",
isWaitingForResponse: true,
}),
() => {
const messages: Message[] = [...this.state.messages];
if (this.props.systemPrompt) {
messages.unshift({
id: String(Date.now()),
content: this.props.systemPrompt,
role: "system",
});
}
const params = {
messages,
};
this.executeAction({
triggerPropertyName: "onClick",
dynamicString: `{{${this.props.queryRun}.run(${JSON.stringify(params)})}}`,
event: {
type: EventType.ON_CLICK,
callback: this.handleActionComplete,
},
});
},
);
};
handleActionComplete = (result: ExecutionResult) => {
if (result.success) {
this.setState((state) => ({
messages: [
...state.messages,
{
id: Math.random().toString(),
content: this.props.queryData.choices[0].message.content,
role: "assistant",
},
],
isWaitingForResponse: false,
}));
}
};
handlePromptChange = (prompt: string) => {
this.setState({ prompt });
};
getWidgetView(): ReactNode {
return (
<AIChat
assistantName={this.props.assistantName}
chatTitle={this.props.chatTitle}
description={this.props.description}
isWaitingForResponse={this.state.isWaitingForResponse}
onPromptChange={this.handlePromptChange}
onSubmit={this.handleMessageSubmit}
prompt={this.state.prompt}
promptInputPlaceholder={this.props.promptInputPlaceholder}
thread={this.adaptMessages(this.state.messages)}
username={this.props.username}
/>
);
}
}
export default WDSAIChatWidget;

View File

@ -59,6 +59,7 @@ export const WDS_V2_WIDGET_MAP = {
MULTILINE_INPUT_WIDGET: "WDS_MULTILINE_INPUT_WIDGET", MULTILINE_INPUT_WIDGET: "WDS_MULTILINE_INPUT_WIDGET",
WDS_SELECT_WIDGET: "WDS_SELECT_WIDGET", WDS_SELECT_WIDGET: "WDS_SELECT_WIDGET",
WDS_COMBOBOX_WIDGET: "WDS_COMBOBOX_WIDGET", WDS_COMBOBOX_WIDGET: "WDS_COMBOBOX_WIDGET",
// WDS_AI_CHAT_WIDGET: "WDS_AI_CHAT_WIDGET",
// Anvil layout widgets // Anvil layout widgets
ZONE_WIDGET: anvilWidgets.ZONE_WIDGET, ZONE_WIDGET: anvilWidgets.ZONE_WIDGET,

View File

@ -57,6 +57,7 @@ import RangeSliderWidget from "./RangeSliderWidget";
import CategorySliderWidget from "./CategorySliderWidget"; import CategorySliderWidget from "./CategorySliderWidget";
import CodeScannerWidget from "./CodeScannerWidget"; import CodeScannerWidget from "./CodeScannerWidget";
import ListWidgetV2 from "./ListWidgetV2"; import ListWidgetV2 from "./ListWidgetV2";
import { WDSAIChatWidget } from "modules/ui-builder/ui/wds/WDSAIChatWidget";
import { WDSButtonWidget } from "modules/ui-builder/ui/wds/WDSButtonWidget"; import { WDSButtonWidget } from "modules/ui-builder/ui/wds/WDSButtonWidget";
import { WDSInputWidget } from "modules/ui-builder/ui/wds/WDSInputWidget"; import { WDSInputWidget } from "modules/ui-builder/ui/wds/WDSInputWidget";
import { WDSCheckboxWidget } from "modules/ui-builder/ui/wds/WDSCheckboxWidget"; import { WDSCheckboxWidget } from "modules/ui-builder/ui/wds/WDSCheckboxWidget";
@ -157,6 +158,7 @@ const DeprecatedWidgets = [
const WDSWidgets = [ const WDSWidgets = [
// WDS Widgets // WDS Widgets
WDSAIChatWidget,
WDSButtonWidget, WDSButtonWidget,
WDSInputWidget, WDSInputWidget,
WDSCheckboxWidget, WDSCheckboxWidget,

View File

@ -0,0 +1,3 @@
jest.mock("react-markdown", () => (props: { children: unknown }) => {
return <>{props.children}</>;
});

File diff suppressed because it is too large Load Diff