Chat widget api (#688)
This commit is contained in:
commit
58ce967e01
22 changed files with 390 additions and 74 deletions
200
docs/docs/guidelines/chat-widget.mdx
Normal file
200
docs/docs/guidelines/chat-widget.mdx
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
import ThemedImage from "@theme/ThemedImage";
|
||||||
|
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||||
|
import ZoomableImage from "/src/theme/ZoomableImage.js";
|
||||||
|
import ReactPlayer from "react-player";
|
||||||
|
import Admonition from '@theme/Admonition';
|
||||||
|
|
||||||
|
# Chat Widget
|
||||||
|
|
||||||
|
<div style={{ marginBottom: "20px" }}>
|
||||||
|
The <b>Langflow Chat Widget</b> is a powerful web component that enables
|
||||||
|
communication with a Langflow project. This widget allows for a chat interface embedding,
|
||||||
|
allowing the integration of Langflow into web applications effortlessly.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
🌟 **Seamless Integration:** Easily integrate the Langflow Chat Widget into your website or web application with just a few lines of JavaScript.
|
||||||
|
|
||||||
|
🚀 **Interactive Chat Interface:** Engage your users with a user-friendly conversation, powered by Langflow's advanced language understanding capabilities.
|
||||||
|
|
||||||
|
🎛️ **Customizable Styling:** Customize the appearance of the chat widget to match your application's design and branding.
|
||||||
|
|
||||||
|
🌐 **Multilingual Support:** Communicate with users in multiple languages, opening up your application to a global audience.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
<div style={{ marginBottom: "20px" }}>
|
||||||
|
You can get the HTML code embedded with the chat by clicking the Code button
|
||||||
|
at the Sidebar after building a flow.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{ marginBottom: "20px", width: "100%", display: "flex", justifyContent: "center" }}
|
||||||
|
>
|
||||||
|
<ZoomableImage
|
||||||
|
alt="Docusaurus themed image"
|
||||||
|
sources={{
|
||||||
|
light: useBaseUrl("img/widget-sidebar.png"),
|
||||||
|
}}
|
||||||
|
style={{ width: "100%", maxWidth: "600px", margin: "0 auto" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: "20px" }}>
|
||||||
|
Clicking the Chat Widget HTML tab, you'll get the code to be
|
||||||
|
inserted. Read below to learn how to use it with HTML, React and Angular.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: "20px" }}>
|
||||||
|
<ZoomableImage
|
||||||
|
alt="Docusaurus themed image"
|
||||||
|
sources={{
|
||||||
|
light: useBaseUrl("img/widget-code.png"),
|
||||||
|
}}
|
||||||
|
style={{ width: "100%", maxWidth: "800px", margin: "0 auto" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### HTML
|
||||||
|
|
||||||
|
The Chat Widget can be embedded into any HTML page, inside a _`<body>`_ tag, as demonstrated in the video below.
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{ marginBottom: "20px", display: "flex", justifyContent: "center" }}
|
||||||
|
>
|
||||||
|
<ReactPlayer playing controls url="/videos/langflow_widget.mp4" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### React
|
||||||
|
|
||||||
|
To embed the Chat Widget using React, you'll need to insert this _`<script>`_ tag into the React *index.html* file, inside the _`<body>`_ tag:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/logspace-ai/langflow-embedded-chat@main/dist/build/static/js/bundle.min.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, declare your Web Component and encapsulate it in a React component.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
declare global {
|
||||||
|
namespace JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
"langflow-chat": any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ChatWidget({ className }) {
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<langflow-chat
|
||||||
|
chat_inputs='{"your_key":"value"}'
|
||||||
|
chat_input_field="your_chat_key"
|
||||||
|
flow_id="your_flow_id"
|
||||||
|
host_url="langflow_url"
|
||||||
|
></langflow-chat>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, you can place the component anywhere in your code to display the Chat Widget.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Angular
|
||||||
|
|
||||||
|
To use it in Angular, first add this _`<script>`_ tag into the Angular *index.html* file, inside the _`<body>`_ tag.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/logspace-ai/langflow-embedded-chat@main/dist/build/static/js/bundle.min.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
When you use a custom web component in an Angular template, the Angular compiler might show a warning when it doesn't recognize the custom elements by default. To suppress this warning, add _`CUSTOM_ELEMENTS_SCHEMA`_ to the module's _`@NgModule.schemas`_.
|
||||||
|
|
||||||
|
- Open the module file (it typically ends with *.module.ts*) where you'd add the _`langflow-chat`_ web component.
|
||||||
|
- Import _`CUSTOM_ELEMENTS_SCHEMA`_ at the top of the file:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
|
||||||
|
```
|
||||||
|
|
||||||
|
- Add _`CUSTOM_ELEMENTS_SCHEMA`_ to the 'schemas' array inside the '@NgModule' decorator:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
// ... Other components and directives ...
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
// ... Other imported modules ...
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA], // Add the CUSTOM_ELEMENTS_SCHEMA here
|
||||||
|
})
|
||||||
|
export class YourModule {}
|
||||||
|
```
|
||||||
|
|
||||||
|
In your Angular project, find the component belonging to the module where _`CUSTOM_ELEMENTS_SCHEMA`_ was added.
|
||||||
|
|
||||||
|
- Inside the template, add the _`langflow-chat`_ tag to include the Chat Widget in your component's view:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<langflow-chat
|
||||||
|
chat_inputs='{"your_key":"value"}'
|
||||||
|
chat_input_field="your_chat_key"
|
||||||
|
flow_id="your_flow_id"
|
||||||
|
host_url="langflow_url"
|
||||||
|
></langflow-chat>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Admonition type="info">
|
||||||
|
<ul>
|
||||||
|
<li>_`CUSTOM_ELEMENTS_SCHEMA`_ is a built-in schema that allows Angular to recognize custom elements.</li>
|
||||||
|
<li>Adding _`CUSTOM_ELEMENTS_SCHEMA`_ tells Angular to allow custom elements in your templates, and it will suppress the warning related to unknown elements like _`langflow-chat`_.</li>
|
||||||
|
<li>Notice that you can only use the Chat Widget in components that are part of the module where you added _`CUSTOM_ELEMENTS_SCHEMA`_.</li>
|
||||||
|
</ul>
|
||||||
|
</Admonition>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Use the widget API to customize your Chat Widget:
|
||||||
|
|
||||||
|
<Admonition type="caution">
|
||||||
|
Props with the type JSON need to be passed as Stringified JSONs, with the format {<span>"key":"value"</span>}.
|
||||||
|
</Admonition>
|
||||||
|
|
||||||
|
|
||||||
|
| Prop | Type | Required | Description |
|
||||||
|
| --------------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| bot_message_style | JSON | No | Applies custom formatting to bot messages. |
|
||||||
|
| chat_input_field | String | Yes | Defines the type of the input field for chat messages. |
|
||||||
|
| chat_inputs | JSON | Yes | Determines the chat input elements and their respective values. |
|
||||||
|
| chat_output_key | String | No | Specifies which output to display if multiple outputs are available. |
|
||||||
|
| chat_position | String | No | Positions the chat window on the screen (options include: top-left, top-center, top-right, center-left, center-right, bottom-right, bottom-center, bottom-left). |
|
||||||
|
| chat_trigger_style | JSON | No | Styles the chat trigger button. |
|
||||||
|
| chat_window_style | JSON | No | Customizes the overall appearance of the chat window. |
|
||||||
|
| error_message_style | JSON | No | Sets the format for error messages within the chat window. |
|
||||||
|
| flow_id | String | Yes | Identifies the flow that the component is associated with. |
|
||||||
|
| height | Number | No | Sets the height of the chat window in pixels. |
|
||||||
|
| host_url | String | Yes | Specifies the URL of the host for chat component communication. |
|
||||||
|
| input_container_style | JSON | No | Applies styling to the container where chat messages are entered. |
|
||||||
|
| input_style | JSON | No | Sets the style for the chat input field. |
|
||||||
|
| online | Boolean | No | Toggles the online status of the chat component. |
|
||||||
|
| online_message | String | No | Sets a custom message to display when the chat component is online. |
|
||||||
|
| placeholder | String | No | Sets the placeholder text for the chat input field. |
|
||||||
|
| placeholder_sending | String | No | Sets the placeholder text to display while a message is being sent. |
|
||||||
|
| send_button_style | JSON | No | Sets the style for the send button in the chat window. |
|
||||||
|
| send_icon_style | JSON | No | Sets the style for the send icon in the chat window. |
|
||||||
|
| tweaks | JSON | No | Applies additional custom adjustments for the associated flow. |
|
||||||
|
| user_message_style | JSON | No | Determines the formatting for user messages in the chat window. |
|
||||||
|
| width | Number | No | Sets the width of the chat window in pixels. |
|
||||||
|
| window_title | String | No | Sets the title displayed in the chat window's header or title bar. |
|
||||||
|
|
@ -21,6 +21,7 @@ module.exports = {
|
||||||
"guidelines/collection",
|
"guidelines/collection",
|
||||||
"guidelines/prompt-customization",
|
"guidelines/prompt-customization",
|
||||||
"guidelines/chat-interface",
|
"guidelines/chat-interface",
|
||||||
|
"guidelines/chat-widget",
|
||||||
"guidelines/custom-component",
|
"guidelines/custom-component",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
BIN
docs/static/img/widget-code.png
vendored
Normal file
BIN
docs/static/img/widget-code.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 787 KiB |
BIN
docs/static/img/widget-sidebar.png
vendored
Normal file
BIN
docs/static/img/widget-sidebar.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
BIN
docs/static/videos/langflow_widget.mp4
vendored
Normal file
BIN
docs/static/videos/langflow_widget.mp4
vendored
Normal file
Binary file not shown.
|
|
@ -12,9 +12,8 @@ import { NodeType } from "../../types/flow";
|
||||||
|
|
||||||
export default function Chat({ flow }: ChatType) {
|
export default function Chat({ flow }: ChatType) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [isBuilt, setIsBuilt] = useState(false);
|
|
||||||
const [canOpen, setCanOpen] = useState(false);
|
const [canOpen, setCanOpen] = useState(false);
|
||||||
const { tabsState } = useContext(TabsContext);
|
const { tabsState, isBuilt, setIsBuilt } = useContext(TabsContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ export default function CodeAreaComponent({
|
||||||
editNode
|
editNode
|
||||||
? "input-edit-node input-dialog"
|
? "input-edit-node input-dialog"
|
||||||
: (disabled ? " input-disable input-ring " : "") +
|
: (disabled ? " input-disable input-ring " : "") +
|
||||||
" input-primary text-muted-foreground "
|
" primary-input text-muted-foreground "
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{myValue !== "" ? myValue : "Type something..."}
|
{myValue !== "" ? myValue : "Type something..."}
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ export default function CodeTabsComponent({
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
))}
|
))}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
{Number(activeTab) < 3 && (
|
{Number(activeTab) < 4 && (
|
||||||
<div className="float-right mx-1 flex gap-2">
|
<div className="float-right mx-1 flex gap-2">
|
||||||
<button
|
<button
|
||||||
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
|
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
|
||||||
|
|
@ -174,7 +174,14 @@ export default function CodeTabsComponent({
|
||||||
className="api-modal-tabs-content"
|
className="api-modal-tabs-content"
|
||||||
key={index} // Remember to add a unique key prop
|
key={index} // Remember to add a unique key prop
|
||||||
>
|
>
|
||||||
{index < 3 ? (
|
{index < 4 ? (
|
||||||
|
<>
|
||||||
|
{tab.description && (
|
||||||
|
<div
|
||||||
|
className="mb-2 w-full text-left text-sm"
|
||||||
|
dangerouslySetInnerHTML={{ __html: tab.description }}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
className="mt-0 h-full w-full overflow-auto custom-scroll"
|
className="mt-0 h-full w-full overflow-auto custom-scroll"
|
||||||
language={tab.mode}
|
language={tab.mode}
|
||||||
|
|
@ -182,7 +189,8 @@ export default function CodeTabsComponent({
|
||||||
>
|
>
|
||||||
{tab.code}
|
{tab.code}
|
||||||
</SyntaxHighlighter>
|
</SyntaxHighlighter>
|
||||||
) : index === 3 ? (
|
</>
|
||||||
|
) : index === 4 ? (
|
||||||
<>
|
<>
|
||||||
<div className="api-modal-according-display">
|
<div className="api-modal-according-display">
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -102,8 +102,8 @@ export default function InputFileComponent({
|
||||||
editNode
|
editNode
|
||||||
? "input-edit-node input-dialog text-muted-foreground"
|
? "input-edit-node input-dialog text-muted-foreground"
|
||||||
: disabled
|
: disabled
|
||||||
? "input-disable input-dialog input-primary"
|
? "input-disable input-dialog primary-input"
|
||||||
: "input-dialog input-primary text-muted-foreground"
|
: "input-dialog primary-input text-muted-foreground"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{myValue !== "" ? myValue : "No file"}
|
{myValue !== "" ? myValue : "No file"}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ export default function PromptAreaComponent({
|
||||||
editNode
|
editNode
|
||||||
? "input-edit-node input-dialog"
|
? "input-edit-node input-dialog"
|
||||||
: (disabled ? " input-disable text-ring " : "") +
|
: (disabled ? " input-disable text-ring " : "") +
|
||||||
" input-primary text-muted-foreground "
|
" primary-input text-muted-foreground "
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{value !== "" ? value : "Type your prompt here..."}
|
{value !== "" ? value : "Type your prompt here..."}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn("nopan nodrag noundo nocopy input-primary", className)}
|
className={cn("nopan nodrag noundo nocopy primary-input", className)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -497,4 +497,8 @@ export const USER_PROJECTS_HEADER = "My Collection";
|
||||||
* @constant
|
* @constant
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const URL_EXCLUDED_FROM_ERROR_RETRIES = ["/api/v1/validate/code", "/api/v1/custom_component", "/api/v1/validate/prompt"];
|
export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
|
||||||
|
"/api/v1/validate/code",
|
||||||
|
"/api/v1/custom_component",
|
||||||
|
"/api/v1/validate/prompt",
|
||||||
|
];
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ const TabsContextInitialValue: TabsContextType = {
|
||||||
downloadFlows: () => {},
|
downloadFlows: () => {},
|
||||||
uploadFlows: () => {},
|
uploadFlows: () => {},
|
||||||
uploadFlow: () => {},
|
uploadFlow: () => {},
|
||||||
|
isBuilt: false,
|
||||||
|
setIsBuilt: (state: boolean) => {},
|
||||||
hardReset: () => {},
|
hardReset: () => {},
|
||||||
saveFlow: async (flow: FlowType) => {},
|
saveFlow: async (flow: FlowType) => {},
|
||||||
lastCopiedSelection: null,
|
lastCopiedSelection: null,
|
||||||
|
|
@ -583,10 +585,14 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [isBuilt, setIsBuilt] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabsContext.Provider
|
<TabsContext.Provider
|
||||||
value={{
|
value={{
|
||||||
saveFlow,
|
saveFlow,
|
||||||
|
isBuilt,
|
||||||
|
setIsBuilt,
|
||||||
lastCopiedSelection,
|
lastCopiedSelection,
|
||||||
setLastCopiedSelection,
|
setLastCopiedSelection,
|
||||||
hardReset,
|
hardReset,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import axios, { AxiosError, AxiosInstance } from "axios";
|
import axios, { AxiosError, AxiosInstance } from "axios";
|
||||||
import { useContext, useEffect, useRef } from "react";
|
import { useContext, useEffect, useRef } from "react";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { URL_EXCLUDED_FROM_ERROR_RETRIES } from "../../constants/constants";
|
import { URL_EXCLUDED_FROM_ERROR_RETRIES } from "../../constants/constants";
|
||||||
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
|
|
||||||
// Create a new Axios instance
|
// Create a new Axios instance
|
||||||
const api: AxiosInstance = axios.create({
|
const api: AxiosInstance = axios.create({
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import axios, { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { ReactFlowJsonObject } from "reactflow";
|
import { ReactFlowJsonObject } from "reactflow";
|
||||||
import { api } from "../../controllers/API/api";
|
import { api } from "../../controllers/API/api";
|
||||||
import { APIObjectType, sendAllProps } from "../../types/api/index";
|
import { APIObjectType, sendAllProps } from "../../types/api/index";
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,12 @@ import ContextWrapper from "./contexts";
|
||||||
import reportWebVitals from "./reportWebVitals";
|
import reportWebVitals from "./reportWebVitals";
|
||||||
|
|
||||||
import { ApiInterceptor } from "./controllers/API/api";
|
import { ApiInterceptor } from "./controllers/API/api";
|
||||||
import "./style/applies.css";
|
// @ts-ignore
|
||||||
import "./style/classes.css";
|
|
||||||
import "./style/index.css";
|
import "./style/index.css";
|
||||||
|
// @ts-ignore
|
||||||
|
import "./style/applies.css";
|
||||||
|
// @ts-ignore
|
||||||
|
import "./style/classes.css";
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById("root") as HTMLElement
|
document.getElementById("root") as HTMLElement
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import {
|
||||||
getCurlCode,
|
getCurlCode,
|
||||||
getPythonApiCode,
|
getPythonApiCode,
|
||||||
getPythonCode,
|
getPythonCode,
|
||||||
|
getWidgetCode,
|
||||||
} from "../../utils/utils";
|
} from "../../utils/utils";
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
|
||||||
|
|
@ -29,9 +30,11 @@ const ApiModal = forwardRef(
|
||||||
{
|
{
|
||||||
flow,
|
flow,
|
||||||
children,
|
children,
|
||||||
|
disable,
|
||||||
}: {
|
}: {
|
||||||
flow: FlowType;
|
flow: FlowType;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
disable: boolean;
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
|
|
@ -43,6 +46,7 @@ const ApiModal = forwardRef(
|
||||||
const pythonApiCode = getPythonApiCode(flow, tweak.current, tabsState);
|
const pythonApiCode = getPythonApiCode(flow, tweak.current, tabsState);
|
||||||
const curl_code = getCurlCode(flow, tweak.current, tabsState);
|
const curl_code = getCurlCode(flow, tweak.current, tabsState);
|
||||||
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
|
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
|
||||||
|
const widgetCode = getWidgetCode(flow, tabsState);
|
||||||
const tweaksCode = buildTweaks(flow);
|
const tweaksCode = buildTweaks(flow);
|
||||||
const [tabs, setTabs] = useState([
|
const [tabs, setTabs] = useState([
|
||||||
{
|
{
|
||||||
|
|
@ -67,6 +71,15 @@ const ApiModal = forwardRef(
|
||||||
language: "py",
|
language: "py",
|
||||||
code: pythonCode,
|
code: pythonCode,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Chat Widget HTML",
|
||||||
|
description:
|
||||||
|
"Insert this code anywhere in your <body> tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
|
||||||
|
mode: "html",
|
||||||
|
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||||
|
language: "py",
|
||||||
|
code: widgetCode,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function startState() {
|
function startState() {
|
||||||
|
|
@ -111,6 +124,15 @@ const ApiModal = forwardRef(
|
||||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||||
code: pythonCode,
|
code: pythonCode,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Chat Widget HTML",
|
||||||
|
description:
|
||||||
|
"Insert this code anywhere in your <body> tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
|
||||||
|
mode: "html",
|
||||||
|
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||||
|
language: "py",
|
||||||
|
code: widgetCode,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Tweaks",
|
name: "Tweaks",
|
||||||
mode: "python",
|
mode: "python",
|
||||||
|
|
@ -143,6 +165,15 @@ const ApiModal = forwardRef(
|
||||||
language: "py",
|
language: "py",
|
||||||
code: pythonCode,
|
code: pythonCode,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Chat Widget HTML",
|
||||||
|
description:
|
||||||
|
"Insert this code anywhere in your <body> tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
|
||||||
|
mode: "html",
|
||||||
|
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||||
|
language: "py",
|
||||||
|
code: widgetCode,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}, [flow["data"]["nodes"], open]);
|
}, [flow["data"]["nodes"], open]);
|
||||||
|
|
@ -210,13 +241,15 @@ const ApiModal = forwardRef(
|
||||||
tweak.current.push(newTweak);
|
tweak.current.push(newTweak);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pythonApiCode = getPythonApiCode(flow, tweak.current);
|
const pythonApiCode = getPythonApiCode(flow, tweak.current, tabsState);
|
||||||
const curl_code = getCurlCode(flow, tweak.current);
|
const curl_code = getCurlCode(flow, tweak.current, tabsState);
|
||||||
const pythonCode = getPythonCode(flow, tweak.current);
|
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
|
||||||
|
const widgetCode = getWidgetCode(flow, tabsState);
|
||||||
|
|
||||||
tabs[0].code = curl_code;
|
tabs[0].code = curl_code;
|
||||||
tabs[1].code = pythonApiCode;
|
tabs[1].code = pythonApiCode;
|
||||||
tabs[2].code = pythonCode;
|
tabs[2].code = pythonCode;
|
||||||
|
tabs[3].code = widgetCode;
|
||||||
|
|
||||||
setTweak(tweak.current);
|
setTweak(tweak.current);
|
||||||
}
|
}
|
||||||
|
|
@ -253,7 +286,7 @@ const ApiModal = forwardRef(
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal open={open} setOpen={setOpen}>
|
<BaseModal open={open} setOpen={setOpen} disable={disable}>
|
||||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||||
<BaseModal.Header description={EXPORT_CODE_DIALOG}>
|
<BaseModal.Header description={EXPORT_CODE_DIALOG}>
|
||||||
<span className="pr-2">Code</span>
|
<span className="pr-2">Code</span>
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,13 @@ interface BaseModalProps {
|
||||||
];
|
];
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
setOpen?: (open: boolean) => void;
|
setOpen?: (open: boolean) => void;
|
||||||
|
disable?: boolean;
|
||||||
size?: "smaller" | "small" | "medium" | "large" | "large-h-full";
|
size?: "smaller" | "small" | "medium" | "large" | "large-h-full";
|
||||||
}
|
}
|
||||||
function BaseModal({
|
function BaseModal({
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
|
disable = false,
|
||||||
children,
|
children,
|
||||||
size = "large",
|
size = "large",
|
||||||
}: BaseModalProps) {
|
}: BaseModalProps) {
|
||||||
|
|
@ -99,7 +101,10 @@ function BaseModal({
|
||||||
//UPDATE COLORS AND STYLE CLASSSES
|
//UPDATE COLORS AND STYLE CLASSSES
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger className="w-full" hidden={triggerChild ? false : true}>
|
<DialogTrigger
|
||||||
|
className={"w-full " + (disable ? "button-disable" : "")}
|
||||||
|
hidden={triggerChild ? false : true}
|
||||||
|
>
|
||||||
{triggerChild}
|
{triggerChild}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className={minWidth}>
|
<DialogContent className={minWidth}>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import DisclosureComponent from "../DisclosureComponent";
|
||||||
|
|
||||||
export default function ExtraSidebar() {
|
export default function ExtraSidebar() {
|
||||||
const { data } = useContext(typesContext);
|
const { data } = useContext(typesContext);
|
||||||
const { flows, tabId, uploadFlow, tabsState, saveFlow } =
|
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } =
|
||||||
useContext(TabsContext);
|
useContext(TabsContext);
|
||||||
const { setSuccessData, setErrorData } = useContext(alertContext);
|
const { setSuccessData, setErrorData } = useContext(alertContext);
|
||||||
const [dataFilter, setFilterData] = useState(data);
|
const [dataFilter, setFilterData] = useState(data);
|
||||||
|
|
@ -61,6 +61,7 @@ export default function ExtraSidebar() {
|
||||||
return (
|
return (
|
||||||
<div className="side-bar-arrangement">
|
<div className="side-bar-arrangement">
|
||||||
<div className="side-bar-buttons-arrangement">
|
<div className="side-bar-buttons-arrangement">
|
||||||
|
<div className="side-bar-button">
|
||||||
<ShadTooltip content="Import" side="top">
|
<ShadTooltip content="Import" side="top">
|
||||||
<button
|
<button
|
||||||
className="extra-side-bar-buttons"
|
className="extra-side-bar-buttons"
|
||||||
|
|
@ -71,32 +72,46 @@ export default function ExtraSidebar() {
|
||||||
<IconComponent name="FileUp" className="side-bar-button-size " />
|
<IconComponent name="FileUp" className="side-bar-button-size " />
|
||||||
</button>
|
</button>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
|
|
||||||
<ShadTooltip content="Export" side="top">
|
|
||||||
<ExportModal>
|
|
||||||
<div className={classNames("extra-side-bar-buttons")}>
|
|
||||||
<IconComponent name="FileDown" className="side-bar-button-size" />
|
|
||||||
</div>
|
</div>
|
||||||
</ExportModal>
|
<div className="side-bar-button">
|
||||||
</ShadTooltip>
|
<ExportModal>
|
||||||
<ShadTooltip content="Code" side="top">
|
<ShadTooltip content="Export" side="top">
|
||||||
{flow && flow.data && (
|
|
||||||
<ApiModal flow={flow}>
|
|
||||||
<div className={classNames("extra-side-bar-buttons")}>
|
<div className={classNames("extra-side-bar-buttons")}>
|
||||||
<IconComponent name="Code2" className="side-bar-button-size" />
|
<IconComponent
|
||||||
|
name="FileDown"
|
||||||
|
className="side-bar-button-size"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ShadTooltip>
|
||||||
|
</ExportModal>
|
||||||
|
</div>
|
||||||
|
<ShadTooltip content={"Code"} side="top">
|
||||||
|
<div className="side-bar-button">
|
||||||
|
{flow && flow.data && (
|
||||||
|
<ApiModal flow={flow} disable={!isBuilt}>
|
||||||
|
<div className={classNames("extra-side-bar-buttons")}>
|
||||||
|
<IconComponent
|
||||||
|
name="Code2"
|
||||||
|
className={
|
||||||
|
"side-bar-button-size" +
|
||||||
|
(isBuilt ? " " : " extra-side-bar-save-disable")
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ApiModal>
|
</ApiModal>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
|
<div className="side-bar-button">
|
||||||
<ShadTooltip content="Save" side="top">
|
<ShadTooltip content="Save" side="top">
|
||||||
<button
|
<button
|
||||||
className="extra-side-bar-buttons"
|
className={
|
||||||
|
"extra-side-bar-buttons " + (isPending ? "" : "button-disable")
|
||||||
|
}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
saveFlow(flow);
|
saveFlow(flow);
|
||||||
setSuccessData({ title: "Changes saved successfully" });
|
setSuccessData({ title: "Changes saved successfully" });
|
||||||
}}
|
}}
|
||||||
disabled={!isPending}
|
|
||||||
>
|
>
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Save"
|
name="Save"
|
||||||
|
|
@ -108,6 +123,7 @@ export default function ExtraSidebar() {
|
||||||
</button>
|
</button>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className="side-bar-search-div-placement">
|
<div className="side-bar-search-div-placement">
|
||||||
<Input
|
<Input
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,12 @@
|
||||||
.side-bar-buttons-arrangement {
|
.side-bar-buttons-arrangement {
|
||||||
@apply mb-2 mt-2 flex w-full items-center justify-between gap-2 px-2;
|
@apply mb-2 mt-2 flex w-full items-center justify-between gap-2 px-2;
|
||||||
}
|
}
|
||||||
|
.side-bar-button {
|
||||||
|
@apply flex w-full;
|
||||||
|
}
|
||||||
|
.button-disable {
|
||||||
|
@apply pointer-events-none;
|
||||||
|
}
|
||||||
.extra-side-bar-buttons {
|
.extra-side-bar-buttons {
|
||||||
@apply relative inline-flex w-full items-center justify-center rounded-md bg-background px-2 py-2 text-foreground shadow-sm ring-1 ring-inset ring-input transition-all duration-500 ease-in-out;
|
@apply relative inline-flex w-full items-center justify-center rounded-md bg-background px-2 py-2 text-foreground shadow-sm ring-1 ring-inset ring-input transition-all duration-500 ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
@ -116,20 +122,20 @@
|
||||||
.button-div-style {
|
.button-div-style {
|
||||||
@apply flex gap-2;
|
@apply flex gap-2;
|
||||||
}
|
}
|
||||||
.input-primary {
|
.primary-input {
|
||||||
@apply form-input block w-full truncate rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm;
|
@apply form-input block w-full truncate rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The same as input-primary but no-truncate */
|
/* The same as primary-input but no-truncate */
|
||||||
.textarea-primary {
|
.textarea-primary {
|
||||||
@apply form-input block w-full rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm;
|
@apply form-input block w-full rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-edit-node {
|
.input-edit-node {
|
||||||
@apply input-primary w-full border-border pb-0.5 pt-0.5 text-left;
|
@apply primary-input w-full pb-0.5 pt-0.5 text-left;
|
||||||
}
|
}
|
||||||
.input-search {
|
.input-search {
|
||||||
@apply input-primary mx-2 pr-7;
|
@apply primary-input mx-2 pr-7;
|
||||||
}
|
}
|
||||||
.input-disable {
|
.input-disable {
|
||||||
@apply border-transparent bg-border placeholder:text-ring;
|
@apply border-transparent bg-border placeholder:text-ring;
|
||||||
|
|
@ -414,7 +420,7 @@
|
||||||
@apply input-edit-node relative pr-8;
|
@apply input-edit-node relative pr-8;
|
||||||
}
|
}
|
||||||
.dropdown-component-false-outline {
|
.dropdown-component-false-outline {
|
||||||
@apply input-primary py-2 pl-3 pr-10 text-left;
|
@apply primary-input py-2 pl-3 pr-10 text-left;
|
||||||
}
|
}
|
||||||
.dropdown-component-display {
|
.dropdown-component-display {
|
||||||
@apply block w-full truncate bg-background;
|
@apply block w-full truncate bg-background;
|
||||||
|
|
@ -780,6 +786,9 @@
|
||||||
.node-modal-button-box {
|
.node-modal-button-box {
|
||||||
@apply flex-max-width flex-row-reverse bg-input px-4 pb-3;
|
@apply flex-max-width flex-row-reverse bg-input px-4 pb-3;
|
||||||
}
|
}
|
||||||
|
.link-color {
|
||||||
|
@apply font-semibold text-foreground;
|
||||||
|
}
|
||||||
.node-modal-button {
|
.node-modal-button {
|
||||||
@apply inline-flex w-full justify-center rounded-md border border-transparent bg-status-red px-4 py-2 text-base font-medium text-background shadow-sm hover:bg-ring sm:ml-3 sm:w-auto sm:text-sm;
|
@apply inline-flex w-full justify-center rounded-md border border-transparent bg-status-red px-4 py-2 text-base font-medium text-background shadow-sm hover:bg-ring sm:ml-3 sm:w-auto sm:text-sm;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ export type TabsContextType = {
|
||||||
) => void;
|
) => void;
|
||||||
downloadFlows: () => void;
|
downloadFlows: () => void;
|
||||||
uploadFlows: () => void;
|
uploadFlows: () => void;
|
||||||
|
isBuilt: boolean;
|
||||||
|
setIsBuilt: (state: boolean) => void;
|
||||||
uploadFlow: (newFlow?: boolean, file?: File) => void;
|
uploadFlow: (newFlow?: boolean, file?: File) => void;
|
||||||
hardReset: () => void;
|
hardReset: () => void;
|
||||||
getNodeId: (nodeType: string) => string;
|
getNodeId: (nodeType: string) => string;
|
||||||
|
|
|
||||||
|
|
@ -467,7 +467,7 @@ export function getCurlCode(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to get the python code for the API
|
* Function to get the python code for the API
|
||||||
* @param {string} flowName - The name of the flow
|
* @param {string} flow - The current flow
|
||||||
* @returns {string} - The python code
|
* @returns {string} - The python code
|
||||||
*/
|
*/
|
||||||
export function getPythonCode(
|
export function getPythonCode(
|
||||||
|
|
@ -489,3 +489,33 @@ flow = load_flow_from_json("${flowName}.json", tweaks=TWEAKS)
|
||||||
inputs = ${inputs}
|
inputs = ${inputs}
|
||||||
flow(inputs)`;
|
flow(inputs)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to get the widget code for the API
|
||||||
|
* @param {string} flow - The current flow.
|
||||||
|
* @returns {string} - The widget code
|
||||||
|
*/
|
||||||
|
export function getWidgetCode(flow: FlowType, tabsState?: TabsState): string {
|
||||||
|
const flowId = flow.id;
|
||||||
|
const flowName = flow.name;
|
||||||
|
const inputs = buildInputs(tabsState, flow.id);
|
||||||
|
|
||||||
|
return `<script src="https://cdn.jsdelivr.net/gh/logspace-ai/langflow-embedded-chat@main/dist/build/static/js/bundle.min.js"></script>
|
||||||
|
|
||||||
|
<!-- chat_inputs: Stringified JSON with all the input keys and its values. The value of the key that is defined
|
||||||
|
as chat_input_field will be overwritten by the chat message.
|
||||||
|
chat_input_field: Input key that you want the chat to send the user message with. -->
|
||||||
|
<langflow-chat
|
||||||
|
window_title="${flowName}"
|
||||||
|
flow_id="${flowId}"
|
||||||
|
${
|
||||||
|
tabsState[flow.id] && tabsState[flow.id].formKeysData
|
||||||
|
? `chat_inputs='${inputs}'
|
||||||
|
chat_input_field="${
|
||||||
|
Object.keys(tabsState[flow.id].formKeysData.input_keys)[0]
|
||||||
|
}"
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}host_url="http://localhost:7860"
|
||||||
|
></langflow-chat>`;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue