Compare commits
6 Commits
main
...
feature/Em
| Author | SHA1 | Date |
|---|---|---|
|
|
ed2bcda91b | |
|
|
9c544bf12a | |
|
|
7def76a3ef | |
|
|
1db2cde22b | |
|
|
e6f5173331 | |
|
|
6258dc3981 |
|
|
@ -14,7 +14,7 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
ignorePatterns: ['**/node_modules', '**/dist', '**/build', '**/package-lock.json'],
|
||||
ignorePatterns: ['**/node_modules', '**/dist', '**/build', '**/package-lock.json', 'packages/embed/'],
|
||||
plugins: ['unused-imports'],
|
||||
rules: {
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"packages/*",
|
||||
"flowise",
|
||||
"ui",
|
||||
"components"
|
||||
"components",
|
||||
"embed"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
|
|
@ -19,8 +20,8 @@
|
|||
"clean": "npm exec -ws -- rimraf dist build",
|
||||
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
||||
"test": "turbo run test",
|
||||
"lint": "eslint \"**/*.{js,jsx,ts,tsx,json,md}\"",
|
||||
"lint-fix": "yarn lint --fix",
|
||||
"lint": "eslint \"**/*.{js,jsx,ts,tsx,json,md}\" && cd packages/embed && yarn lint",
|
||||
"lint-fix": "yarn lint --fix && cd packages/embed && yarn lint-fix",
|
||||
"quick": "pretty-quick --staged",
|
||||
"postinstall": "husky install"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier', 'plugin:solid/typescript'],
|
||||
plugins: ['@typescript-eslint', 'solid'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
ignorePatterns: ['**/*.md'],
|
||||
rules: {
|
||||
'@next/next/no-img-element': 'off',
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
'solid/no-innerhtml': 'off',
|
||||
'@typescript-eslint/no-namespace': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off'
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
dist
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
src
|
||||
rollup.config.js
|
||||
tailwind.config.cjs
|
||||
tsconfig.json
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<!-- markdownlint-disable MD030 -->
|
||||
|
||||
# Flowise Embed
|
||||
|
||||
Javascript library to display flowise chatbot on your website
|
||||
|
||||

|
||||
|
||||
Install:
|
||||
|
||||
```bash
|
||||
npm i flowise-embed
|
||||
```
|
||||
|
||||
## Embed in your HTML
|
||||
|
||||
```
|
||||
<script type="module">
|
||||
import Chatbot from 'https://cdn.jsdelivr.net/npm/flowise-embed/dist/web.js'
|
||||
Chatbot.init({
|
||||
chatflowid: '<chatflowid>',
|
||||
apiHost: 'http://localhost:3000'
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Source code in this repository is made available under the [MIT License](https://github.com/FlowiseAI/Flowise/blob/master/LICENSE.md).
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Default",
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"inlineSources": false,
|
||||
"isolatedModules": true,
|
||||
"moduleResolution": "node",
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"preserveWatchOutput": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"downlevelIteration": true
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "flowise-embed",
|
||||
"version": "1.0.2",
|
||||
"description": "Javascript library to display flowise chatbot on your website",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"dev": "rollup --watch --config rollup.config.js",
|
||||
"build": "rollup --config rollup.config.js",
|
||||
"lint": "eslint \"src/**/*.ts*\"",
|
||||
"lint-fix": "eslint --fix \"src/**/*.ts*\""
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"solid-element": "1.7.0",
|
||||
"solid-js": "1.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-typescript": "7.21.4",
|
||||
"@rollup/plugin-babel": "6.0.3",
|
||||
"@rollup/plugin-node-resolve": "15.0.1",
|
||||
"@rollup/plugin-terser": "0.4.0",
|
||||
"@rollup/plugin-typescript": "11.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
||||
"@typescript-eslint/parser": "^5.57.0",
|
||||
"autoprefixer": "10.4.14",
|
||||
"babel-preset-solid": "1.7.1",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-next": "13.2.4",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-react": "^7.26.1",
|
||||
"eslint-plugin-solid": "0.12.0",
|
||||
"postcss": "8.4.21",
|
||||
"react": "18.2.0",
|
||||
"rollup": "3.20.2",
|
||||
"rollup-plugin-livereload": "2.0.5",
|
||||
"rollup-plugin-postcss": "4.0.2",
|
||||
"rollup-plugin-serve": "2.0.2",
|
||||
"rollup-plugin-typescript-paths": "1.4.0",
|
||||
"tailwindcss": "3.3.1",
|
||||
"typescript": "5.0.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import resolve from '@rollup/plugin-node-resolve'
|
||||
import terser from '@rollup/plugin-terser'
|
||||
import { babel } from '@rollup/plugin-babel'
|
||||
import postcss from 'rollup-plugin-postcss'
|
||||
import autoprefixer from 'autoprefixer'
|
||||
import tailwindcss from 'tailwindcss'
|
||||
import typescript from '@rollup/plugin-typescript'
|
||||
import { typescriptPaths } from 'rollup-plugin-typescript-paths'
|
||||
// import serve from 'rollup-plugin-serve'
|
||||
// import livereload from 'rollup-plugin-livereload'
|
||||
|
||||
const extensions = ['.ts', '.tsx']
|
||||
|
||||
const indexConfig = {
|
||||
plugins: [
|
||||
resolve({ extensions }),
|
||||
babel({
|
||||
babelHelpers: 'bundled',
|
||||
exclude: 'node_modules/**',
|
||||
presets: ['solid', '@babel/preset-typescript'],
|
||||
extensions
|
||||
}),
|
||||
postcss({
|
||||
plugins: [autoprefixer(), tailwindcss()],
|
||||
extract: false,
|
||||
modules: false,
|
||||
autoModules: false,
|
||||
minimize: true,
|
||||
inject: false
|
||||
}),
|
||||
typescript(),
|
||||
typescriptPaths({ preserveExtensions: true }),
|
||||
terser({ output: { comments: false } })
|
||||
/* If you want to see the live app
|
||||
serve({
|
||||
open: true,
|
||||
verbose: true,
|
||||
contentBase: ['dist'],
|
||||
host: 'localhost',
|
||||
port: 5678
|
||||
}),
|
||||
livereload({ watch: 'dist' })*/
|
||||
]
|
||||
}
|
||||
|
||||
const configs = [
|
||||
{
|
||||
...indexConfig,
|
||||
input: './src/web.ts',
|
||||
output: {
|
||||
file: 'dist/web.js',
|
||||
format: 'es'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
export default configs
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:host {
|
||||
--chatbot-container-bg-image: none;
|
||||
--chatbot-container-bg-color: transparent;
|
||||
--chatbot-container-font-family: 'Open Sans';
|
||||
|
||||
--chatbot-button-bg-color: #673ab7;
|
||||
--chatbot-button-color: #ffffff;
|
||||
|
||||
--chatbot-host-bubble-bg-color: #f7f8ff;
|
||||
--chatbot-host-bubble-color: #303235;
|
||||
|
||||
--chatbot-guest-bubble-bg-color: #ff8e21;
|
||||
--chatbot-guest-bubble-color: #ffffff;
|
||||
|
||||
--chatbot-input-bg-color: #ffffff;
|
||||
--chatbot-input-color: #303235;
|
||||
--chatbot-input-placeholder-color: #9095a0;
|
||||
|
||||
--chatbot-header-bg-color: #ffffff;
|
||||
--chatbot-header-color: #303235;
|
||||
|
||||
--chatbot-border-radius: 6px;
|
||||
|
||||
/* Phone input */
|
||||
--PhoneInputCountryFlag-borderColor: transparent;
|
||||
--PhoneInput-color--focus: transparent;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
.scrollable-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
.scrollable-container {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.text-fade-in {
|
||||
transition: opacity 400ms ease-in 200ms;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.text-input::-webkit-input-placeholder {
|
||||
color: var(--chatbot-input-placeholder-color) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.text-input::-moz-placeholder {
|
||||
color: var(--chatbot-input-placeholder-color) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.text-input::placeholder {
|
||||
color: var(--chatbot-input-placeholder-color) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.chatbot-container {
|
||||
background-image: var(--chatbot-container-bg-image);
|
||||
background-color: var(--chatbot-container-bg-color);
|
||||
font-family: var(--chatbot-container-font-family), -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
|
||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
}
|
||||
|
||||
.chatbot-button {
|
||||
color: var(--chatbot-button-color);
|
||||
background-color: var(--chatbot-button-bg-color);
|
||||
border: 1px solid var(--chatbot-button-bg-color);
|
||||
border-radius: var(--chatbot-border-radius);
|
||||
}
|
||||
|
||||
.chatbot-button.selectable {
|
||||
color: var(--chatbot-host-bubble-color);
|
||||
background-color: var(--chatbot-host-bubble-bg-color);
|
||||
border: 1px solid var(--chatbot-button-bg-color);
|
||||
}
|
||||
|
||||
.chatbot-input {
|
||||
color: var(--chatbot-input-color);
|
||||
background-color: var(--chatbot-input-bg-color);
|
||||
box-shadow: 0 2px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
border-radius: var(--chatbot-border-radius);
|
||||
}
|
||||
|
||||
.chatbot-input-error-message {
|
||||
color: var(--chatbot-input-color);
|
||||
}
|
||||
|
||||
.chatbot-button > .send-icon {
|
||||
fill: var(--chatbot-button-bg-color);
|
||||
}
|
||||
|
||||
.chatbot-chat-view {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
background-color: var(--chatbot-host-bubble-bg-color);
|
||||
color: var(--chatbot-host-bubble-color);
|
||||
border-radius: var(--chatbot-border-radius);
|
||||
}
|
||||
|
||||
.messagelist {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.messagelistloading {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.usermessage {
|
||||
padding: 1rem 1.5rem 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.usermessagewaiting-light {
|
||||
padding: 1rem 1.5rem 1rem 1.5rem;
|
||||
background: linear-gradient(to left, #ede7f6, #e3f2fd, #ede7f6);
|
||||
background-size: 200% 200%;
|
||||
background-position: -100% 0;
|
||||
animation: loading-gradient 2s ease-in-out infinite;
|
||||
animation-direction: alternate;
|
||||
animation-name: loading-gradient;
|
||||
}
|
||||
|
||||
.usermessagewaiting-dark {
|
||||
padding: 1rem 1.5rem 1rem 1.5rem;
|
||||
color: #ececf1;
|
||||
background: linear-gradient(to left, #2e2352, #1d3d60, #2e2352);
|
||||
background-size: 200% 200%;
|
||||
background-position: -100% 0;
|
||||
animation: loading-gradient 2s ease-in-out infinite;
|
||||
animation-direction: alternate;
|
||||
animation-name: loading-gradient;
|
||||
}
|
||||
|
||||
@keyframes loading-gradient {
|
||||
0% {
|
||||
background-position: -100% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 100% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.apimessage {
|
||||
padding: 1rem 1.5rem 1rem 1.5rem;
|
||||
animation: fadein 0.5s;
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.apimessage,
|
||||
.usermessage,
|
||||
.usermessagewaiting {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.markdownanswer {
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.markdownanswer a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.markdownanswer a {
|
||||
color: #16bed7;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.markdownanswer code {
|
||||
color: #15cb19;
|
||||
font-weight: 500;
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
.markdownanswer ol,
|
||||
.markdownanswer ul {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.boticon,
|
||||
.usericon {
|
||||
margin-right: 1rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.markdownanswer h1,
|
||||
.markdownanswer h2,
|
||||
.markdownanswer h3 {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.cloud {
|
||||
width: 400px;
|
||||
height: calc(100% - 50px);
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 10px 10px;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
import { createSignal, createEffect, For } from 'solid-js'
|
||||
import { sendMessageQuery } from '@/queries/sendMessageQuery'
|
||||
import { TextInput } from './inputs/textInput'
|
||||
|
||||
type messageType = 'apiMessage' | 'userMessage' | 'usermessagewaiting'
|
||||
|
||||
export type MessageType = {
|
||||
message: string
|
||||
type: messageType
|
||||
}
|
||||
|
||||
export type BotProps = {
|
||||
chatflowid: string
|
||||
apiHost?: string
|
||||
}
|
||||
|
||||
export const Bot = (props: BotProps) => {
|
||||
let ps: HTMLDivElement | undefined
|
||||
|
||||
const [userInput, setUserInput] = createSignal('')
|
||||
const [loading, setLoading] = createSignal(false)
|
||||
const [messages, setMessages] = createSignal<MessageType[]>([
|
||||
{
|
||||
message: 'Hi there! How can I help?',
|
||||
type: 'apiMessage'
|
||||
}
|
||||
])
|
||||
|
||||
const scrollToBottom = () => {
|
||||
setTimeout(() => {
|
||||
ps?.scrollTo(0, ps.scrollHeight)
|
||||
}, 50)
|
||||
/*if (ps.current) {
|
||||
const curr: any = ps.current
|
||||
curr.scrollTo({ top: 1000000, behavior: 'smooth' })
|
||||
}*/
|
||||
}
|
||||
|
||||
// Handle errors
|
||||
const handleError = (message = 'Oops! There seems to be an error. Please try again.') => {
|
||||
setMessages((prevMessages) => [...prevMessages, { message, type: 'apiMessage' }])
|
||||
setLoading(false)
|
||||
setUserInput('')
|
||||
}
|
||||
|
||||
// Handle form submission
|
||||
const handleSubmit = async (value: string) => {
|
||||
setUserInput(value)
|
||||
|
||||
if (value.trim() === '') {
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
setMessages((prevMessages) => [...prevMessages, { message: value, type: 'userMessage' }])
|
||||
|
||||
// Send user question and history to API
|
||||
const { data, error } = await sendMessageQuery({
|
||||
chatflowid: props.chatflowid,
|
||||
apiHost: props.apiHost,
|
||||
body: {
|
||||
question: value,
|
||||
history: messages().filter((msg) => msg.message !== 'Hi there! How can I help?')
|
||||
}
|
||||
})
|
||||
|
||||
console.log(data)
|
||||
|
||||
if (data) {
|
||||
setMessages((prevMessages) => [...prevMessages, { message: data, type: 'apiMessage' }])
|
||||
setLoading(false)
|
||||
setUserInput('')
|
||||
setTimeout(() => {
|
||||
scrollToBottom()
|
||||
}, 100)
|
||||
}
|
||||
if (error) {
|
||||
console.error(error)
|
||||
const err: any = error
|
||||
const errorData = err.response.data || `${err.response.status}: ${err.response.statusText}`
|
||||
handleError(errorData)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Auto scroll chat to bottom
|
||||
createEffect(() => {
|
||||
scrollToBottom()
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
return () => {
|
||||
setUserInput('')
|
||||
setLoading(false)
|
||||
setMessages([
|
||||
{
|
||||
message: 'Hi there! How can I help?',
|
||||
type: 'apiMessage'
|
||||
}
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class='cloud'>
|
||||
<div ref={ps} class='messagelist'>
|
||||
<For each={messages()}>
|
||||
{(message, index) => (
|
||||
// The latest message sent by the user will be animated while waiting for a response
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
'align-items': 'center',
|
||||
background: message.type === 'apiMessage' ? '#f7f8ff' : ''
|
||||
}}
|
||||
class={
|
||||
message.type === 'userMessage' && loading() && index() === messages().length - 1
|
||||
? 'usermessagewaiting-light'
|
||||
: message.type === 'usermessagewaiting'
|
||||
? 'apimessage'
|
||||
: 'usermessage'
|
||||
}
|
||||
>
|
||||
{/* Display the correct icon depending on the message type */}
|
||||
{message.type === 'apiMessage' ? (
|
||||
<img
|
||||
src='https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/parroticon.png'
|
||||
alt='AI'
|
||||
width='30'
|
||||
height='30'
|
||||
class='boticon'
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src='https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/usericon.png'
|
||||
alt='Me'
|
||||
width='30'
|
||||
height='30'
|
||||
class='usericon'
|
||||
/>
|
||||
)}
|
||||
<div class='markdownanswer'>
|
||||
{/* Messages are being rendered in Markdown format */}
|
||||
<span style={{ 'font-family': 'Poppins, sans-serif' }}>{message.message}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
<TextInput defaultValue={userInput()} onSubmit={handleSubmit} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { Show } from 'solid-js'
|
||||
import { JSX } from 'solid-js/jsx-runtime'
|
||||
import { SendIcon } from './icons'
|
||||
|
||||
type SendButtonProps = {
|
||||
isDisabled?: boolean
|
||||
isLoading?: boolean
|
||||
disableIcon?: boolean
|
||||
} & JSX.ButtonHTMLAttributes<HTMLButtonElement>
|
||||
|
||||
export const SendButton = (props: SendButtonProps) => {
|
||||
return (
|
||||
<button
|
||||
type='submit'
|
||||
disabled={props.isDisabled || props.isLoading}
|
||||
{...props}
|
||||
class={
|
||||
'py-2 px-4 justify-center font-semibold text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 chatbot-button ' +
|
||||
props.class
|
||||
}
|
||||
style={{ background: 'white', border: 'none' }}
|
||||
>
|
||||
<Show when={!props.isLoading} fallback={<Spinner class='text-white' />}>
|
||||
<SendIcon class={'send-icon flex ' + (props.disableIcon ? 'hidden' : '')} />
|
||||
</Show>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export const Spinner = (props: JSX.SvgSVGAttributes<SVGSVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
class={'animate-spin -ml-1 mr-3 h-5 w-5 ' + props.class}
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
fill='none'
|
||||
viewBox='0 0 24 24'
|
||||
data-testid='loading-spinner'
|
||||
>
|
||||
<circle class='opacity-25' cx='12' cy='12' r='10' stroke='currentColor' stroke-width='4' />
|
||||
<path
|
||||
class='opacity-75'
|
||||
fill='currentColor'
|
||||
d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { JSX } from 'solid-js/jsx-runtime'
|
||||
|
||||
export const SendIcon = (props: JSX.SvgSVGAttributes<SVGSVGElement>) => (
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' width='19px' color='white' {...props}>
|
||||
<path d='M476.59 227.05l-.16-.07L49.35 49.84A23.56 23.56 0 0027.14 52 24.65 24.65 0 0016 72.59v113.29a24 24 0 0019.52 23.57l232.93 43.07a4 4 0 010 7.86L35.53 303.45A24 24 0 0016 327v113.31A23.57 23.57 0 0026.59 460a23.94 23.94 0 0013.22 4 24.55 24.55 0 009.52-1.93L476.4 285.94l.19-.09a32 32 0 000-58.8z' />
|
||||
</svg>
|
||||
)
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './SendIcon'
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { splitProps } from 'solid-js'
|
||||
import { JSX } from 'solid-js/jsx-runtime'
|
||||
|
||||
type ShortTextInputProps = {
|
||||
ref: HTMLInputElement | undefined
|
||||
onInput: (value: string) => void
|
||||
} & Omit<JSX.InputHTMLAttributes<HTMLInputElement>, 'onInput'>
|
||||
|
||||
export const ShortTextInput = (props: ShortTextInputProps) => {
|
||||
const [local, others] = splitProps(props, ['ref', 'onInput'])
|
||||
|
||||
return (
|
||||
<input
|
||||
ref={props.ref}
|
||||
class='focus:outline-none bg-transparent px-4 py-4 flex-1 w-full text-input'
|
||||
type='text'
|
||||
style={{ 'font-size': '16px' }}
|
||||
onInput={(e) => local.onInput(e.currentTarget.value)}
|
||||
{...others}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import { ShortTextInput } from './ShortTextInput'
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
defaultValue?: string
|
||||
onSubmit: (value: string) => void
|
||||
}
|
||||
|
||||
export const TextInput = (props: Props) => {
|
||||
const [inputValue, setInputValue] = createSignal(props.defaultValue ?? '')
|
||||
let inputRef: HTMLInputElement | HTMLTextAreaElement | undefined
|
||||
|
||||
const handleInput = (inputValue: string) => setInputValue(inputValue)
|
||||
|
||||
const checkIfInputIsValid = () => inputValue() !== '' && inputRef?.reportValidity()
|
||||
|
||||
const submit = () => {
|
||||
if (checkIfInputIsValid()) props.onSubmit(inputValue())
|
||||
setInputValue('')
|
||||
}
|
||||
|
||||
const submitWhenEnter = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter') submit()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!isMobile() && inputRef) inputRef.focus()
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
class={'flex items-end justify-between pr-2 chatbot-input w-full'}
|
||||
data-testid='input'
|
||||
style={{
|
||||
'border-top': '1px solid #eeeeee',
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0
|
||||
}}
|
||||
onKeyDown={submitWhenEnter}
|
||||
>
|
||||
<ShortTextInput
|
||||
ref={inputRef as HTMLInputElement}
|
||||
onInput={handleInput}
|
||||
value={inputValue()}
|
||||
placeholder='Type your question'
|
||||
/>
|
||||
<SendButton type='button' isDisabled={inputValue() === ''} class='my-2 ml-2' on:click={submit}>
|
||||
<span style={{ 'font-family': 'Poppins, sans-serif' }}>Send</span>
|
||||
</SendButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { TextInput } from './components/TextInput'
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import type { BubbleProps } from './features/bubble'
|
||||
|
||||
export const defaultBotProps: BubbleProps = {
|
||||
chatflowid: '',
|
||||
apiHost: undefined
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
export {}
|
||||
|
||||
declare module 'solid-js' {
|
||||
namespace JSX {
|
||||
interface CustomEvents {
|
||||
click: MouseEvent
|
||||
pointerdown: PointerEvent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import { createSignal, Show, splitProps } from 'solid-js'
|
||||
import styles from '../../../assets/index.css'
|
||||
import { BubbleButton } from './BubbleButton'
|
||||
import { BubbleParams } from '../types'
|
||||
import { Bot, BotProps } from '../../../components/Bot'
|
||||
|
||||
export type BubbleProps = BotProps & BubbleParams
|
||||
|
||||
export const Bubble = (props: BubbleProps) => {
|
||||
const [bubbleProps] = splitProps(props, ['theme'])
|
||||
|
||||
const [isBotOpened, setIsBotOpened] = createSignal(false)
|
||||
const [isBotStarted, setIsBotStarted] = createSignal(false)
|
||||
|
||||
const openBot = () => {
|
||||
if (!isBotStarted()) setIsBotStarted(true)
|
||||
setIsBotOpened(true)
|
||||
}
|
||||
|
||||
const closeBot = () => {
|
||||
setIsBotOpened(false)
|
||||
}
|
||||
|
||||
const toggleBot = () => {
|
||||
isBotOpened() ? closeBot() : openBot()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{styles}</style>
|
||||
<BubbleButton {...bubbleProps.theme?.button} toggleBot={toggleBot} isBotOpened={isBotOpened()} />
|
||||
<div
|
||||
part='bot'
|
||||
style={{
|
||||
height: 'calc(100% - 120px)',
|
||||
transition: 'transform 200ms cubic-bezier(0, 1.2, 1, 1), opacity 150ms ease-out',
|
||||
'transform-origin': 'bottom right',
|
||||
transform: isBotOpened() ? 'scale3d(1, 1, 1)' : 'scale3d(0, 0, 1)',
|
||||
'box-shadow': 'rgb(0 0 0 / 16%) 0px 5px 40px',
|
||||
'background-color': bubbleProps.theme?.chatWindow?.backgroundColor || '#ffffff',
|
||||
'z-index': 42424242
|
||||
}}
|
||||
class={
|
||||
'fixed sm:right-5 rounded-lg w-full sm:w-[400px] max-h-[704px]' +
|
||||
(isBotOpened() ? ' opacity-1' : ' opacity-0 pointer-events-none') +
|
||||
(props.theme?.button?.size === 'large' ? ' bottom-24' : ' bottom-20')
|
||||
}
|
||||
>
|
||||
<Show when={isBotStarted()}>
|
||||
<Bot chatflowid={props.chatflowid} apiHost={props.apiHost} />
|
||||
</Show>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import { Show } from 'solid-js'
|
||||
import { isNotDefined } from '@/utils/index'
|
||||
import { ButtonTheme } from '../types'
|
||||
|
||||
type Props = ButtonTheme & {
|
||||
isBotOpened: boolean
|
||||
toggleBot: () => void
|
||||
}
|
||||
|
||||
const defaultButtonColor = '#673ab7'
|
||||
const defaultIconColor = 'white'
|
||||
|
||||
export const BubbleButton = (props: Props) => {
|
||||
return (
|
||||
<button
|
||||
part='button'
|
||||
onClick={() => props.toggleBot()}
|
||||
class={
|
||||
'fixed bottom-5 right-5 shadow-md rounded-full hover:scale-110 active:scale-95 transition-transform duration-200 flex justify-center items-center animate-fade-in' +
|
||||
(props.size === 'large' ? ' w-16 h-16' : ' w-12 h-12')
|
||||
}
|
||||
style={{
|
||||
'background-color': props.backgroundColor ?? defaultButtonColor,
|
||||
'z-index': 42424242
|
||||
}}
|
||||
>
|
||||
<Show when={isNotDefined(props.customIconSrc)} keyed>
|
||||
<svg
|
||||
viewBox='0 0 24 24'
|
||||
style={{
|
||||
stroke: props.iconColor ?? defaultIconColor
|
||||
}}
|
||||
class={
|
||||
`stroke-2 fill-transparent absolute duration-200 transition ` +
|
||||
(props.isBotOpened ? 'scale-0 opacity-0' : 'scale-100 opacity-100') +
|
||||
(props.size === 'large' ? ' w-9' : ' w-7')
|
||||
}
|
||||
>
|
||||
<path d='M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z' />
|
||||
</svg>
|
||||
</Show>
|
||||
<Show when={props.customIconSrc}>
|
||||
<img
|
||||
src={props.customIconSrc}
|
||||
class={'rounded-full object-cover' + (props.size === 'large' ? ' w-9 h-9' : ' w-7 h-7')}
|
||||
alt='Bubble button icon'
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<svg
|
||||
viewBox='0 0 24 24'
|
||||
style={{ fill: props.iconColor ?? 'white' }}
|
||||
class={
|
||||
`absolute duration-200 transition ` +
|
||||
(props.isBotOpened ? 'scale-100 rotate-0 opacity-100' : 'scale-0 -rotate-180 opacity-0') +
|
||||
(props.size === 'large' ? ' w-9' : ' w-7')
|
||||
}
|
||||
>
|
||||
<path
|
||||
fill-rule='evenodd'
|
||||
clip-rule='evenodd'
|
||||
d='M18.601 8.39897C18.269 8.06702 17.7309 8.06702 17.3989 8.39897L12 13.7979L6.60099 8.39897C6.26904 8.06702 5.73086 8.06702 5.39891 8.39897C5.06696 8.73091 5.06696 9.2691 5.39891 9.60105L11.3989 15.601C11.7309 15.933 12.269 15.933 12.601 15.601L18.601 9.60105C18.9329 9.2691 18.9329 8.73091 18.601 8.39897Z'
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './Bubble'
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './components'
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
export type BubbleParams = {
|
||||
theme?: BubbleTheme
|
||||
}
|
||||
|
||||
export type BubbleTheme = {
|
||||
chatWindow?: ChatWindowTheme
|
||||
button?: ButtonTheme
|
||||
}
|
||||
|
||||
export type ChatWindowTheme = {
|
||||
backgroundColor?: string
|
||||
}
|
||||
|
||||
export type ButtonTheme = {
|
||||
size?: 'medium' | 'large'
|
||||
backgroundColor?: string
|
||||
iconColor?: string
|
||||
customIconSrc?: string
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
declare module '*.css'
|
||||
|
|
@ -0,0 +1 @@
|
|||
export type { BotProps } from './components/Bot'
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { sendRequest } from '@/utils/index'
|
||||
|
||||
export type MessageRequest = {
|
||||
chatflowid: string
|
||||
apiHost?: string
|
||||
body: any
|
||||
}
|
||||
|
||||
export const sendMessageQuery = ({ chatflowid, apiHost = 'http://localhost:3000', body }: MessageRequest) =>
|
||||
sendRequest<any>({
|
||||
method: 'POST',
|
||||
url: `${apiHost}/api/v1/prediction/${chatflowid}`,
|
||||
body
|
||||
})
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import { customElement } from 'solid-element'
|
||||
import { defaultBotProps } from './constants'
|
||||
import { Bubble } from './features/bubble'
|
||||
|
||||
export const registerWebComponents = () => {
|
||||
if (typeof window === 'undefined') return
|
||||
customElement('flowise-chatbot', defaultBotProps, Bubble)
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
export const isNotDefined = <T>(value: T | undefined | null): value is undefined | null => value === undefined || value === null
|
||||
|
||||
export const isDefined = <T>(value: T | undefined | null): value is NonNullable<T> => value !== undefined && value !== null
|
||||
|
||||
export const isEmpty = (value: string | undefined | null): value is undefined => value === undefined || value === null || value === ''
|
||||
|
||||
export const isNotEmpty = (value: string | undefined | null): value is string => value !== undefined && value !== null && value !== ''
|
||||
|
||||
export const sendRequest = async <ResponseData>(
|
||||
params:
|
||||
| {
|
||||
url: string
|
||||
method: string
|
||||
body?: Record<string, unknown> | FormData
|
||||
}
|
||||
| string
|
||||
): Promise<{ data?: ResponseData; error?: Error }> => {
|
||||
try {
|
||||
const url = typeof params === 'string' ? params : params.url
|
||||
const response = await fetch(url, {
|
||||
method: typeof params === 'string' ? 'GET' : params.method,
|
||||
mode: 'cors',
|
||||
headers:
|
||||
typeof params !== 'string' && isDefined(params.body)
|
||||
? {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
: undefined,
|
||||
body: typeof params !== 'string' && isDefined(params.body) ? JSON.stringify(params.body) : undefined
|
||||
})
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw 'error' in data ? data.error : data
|
||||
return { data }
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return { error: e as Error }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { createSignal } from 'solid-js'
|
||||
|
||||
export const [isMobile, setIsMobile] = createSignal<boolean>()
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { registerWebComponents } from './register'
|
||||
import { parseChatbot, injectChatbotInWindow } from './window'
|
||||
|
||||
registerWebComponents()
|
||||
|
||||
const chatbot = parseChatbot()
|
||||
|
||||
injectChatbotInWindow(chatbot)
|
||||
|
||||
export default chatbot
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/* eslint-disable solid/reactivity */
|
||||
type BotProps = {
|
||||
chatflowid: string
|
||||
apiHost?: string
|
||||
}
|
||||
|
||||
export const init = (props: BotProps) => {
|
||||
const element = document.createElement('flowise-chatbot')
|
||||
Object.assign(element, props)
|
||||
document.body.appendChild(element)
|
||||
}
|
||||
|
||||
type Chatbot = {
|
||||
init: typeof init
|
||||
}
|
||||
|
||||
declare const window:
|
||||
| {
|
||||
Chatbot: Chatbot | undefined
|
||||
}
|
||||
| undefined
|
||||
|
||||
export const parseChatbot = () => ({
|
||||
init
|
||||
})
|
||||
|
||||
export const injectChatbotInWindow = (bot: Chatbot) => {
|
||||
if (typeof window === 'undefined') return
|
||||
window.Chatbot = { ...bot }
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const defaultTheme = require('tailwindcss/defaultTheme')
|
||||
|
||||
function rem2px(input, fontSize = 16) {
|
||||
if (input == null) {
|
||||
return input
|
||||
}
|
||||
switch (typeof input) {
|
||||
case 'object':
|
||||
if (Array.isArray(input)) {
|
||||
return input.map((val) => rem2px(val, fontSize))
|
||||
}
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const ret = {}
|
||||
for (const key in input) {
|
||||
ret[key] = rem2px(input[key], fontSize)
|
||||
}
|
||||
return ret
|
||||
case 'string':
|
||||
return input.replace(/(\d*\.?\d+)rem$/, (_, val) => `${parseFloat(val) * fontSize}px`)
|
||||
case 'function':
|
||||
return eval(input.toString().replace(/(\d*\.?\d+)rem/g, (_, val) => `${parseFloat(val) * fontSize}px`))
|
||||
default:
|
||||
return input
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
||||
theme: {
|
||||
...rem2px(defaultTheme),
|
||||
extend: {
|
||||
keyframes: {
|
||||
'fade-in': {
|
||||
'0%': {
|
||||
opacity: '0'
|
||||
},
|
||||
'100%': {
|
||||
opacity: '1'
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'fade-in': 'fade-in 0.3s ease-out'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: []
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "./base.json",
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "solid-js",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "dist",
|
||||
"emitDeclarationOnly": true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,456 @@
|
|||
{
|
||||
"description": "PDF file QnA using conversational retrieval QA chain",
|
||||
"nodes": [
|
||||
{
|
||||
"width": 300,
|
||||
"height": 376,
|
||||
"id": "recursiveCharacterTextSplitter_0",
|
||||
"position": {
|
||||
"x": 417.887975676309,
|
||||
"y": 199.63833359116117
|
||||
},
|
||||
"type": "customNode",
|
||||
"data": {
|
||||
"id": "recursiveCharacterTextSplitter_0",
|
||||
"label": "Recursive Character Text Splitter",
|
||||
"name": "recursiveCharacterTextSplitter",
|
||||
"type": "RecursiveCharacterTextSplitter",
|
||||
"baseClasses": ["RecursiveCharacterTextSplitter", "TextSplitter"],
|
||||
"category": "Text Splitters",
|
||||
"description": "Split documents recursively by different characters - starting with \"\n\n\", then \"\n\", then \" \"",
|
||||
"inputParams": [
|
||||
{
|
||||
"label": "Chunk Size",
|
||||
"name": "chunkSize",
|
||||
"type": "number",
|
||||
"default": 1000,
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"label": "Chunk Overlap",
|
||||
"name": "chunkOverlap",
|
||||
"type": "number",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"inputAnchors": [],
|
||||
"inputs": {
|
||||
"chunkSize": 1000,
|
||||
"chunkOverlap": ""
|
||||
},
|
||||
"outputAnchors": [
|
||||
{
|
||||
"id": "recursiveCharacterTextSplitter_0-output-recursiveCharacterTextSplitter-RecursiveCharacterTextSplitter|TextSplitter",
|
||||
"name": "recursiveCharacterTextSplitter",
|
||||
"label": "RecursiveCharacterTextSplitter",
|
||||
"type": "RecursiveCharacterTextSplitter | TextSplitter"
|
||||
}
|
||||
],
|
||||
"selected": false
|
||||
},
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 417.887975676309,
|
||||
"y": 199.63833359116117
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"width": 300,
|
||||
"height": 472,
|
||||
"id": "openAI_0",
|
||||
"position": {
|
||||
"x": 1207.112878089014,
|
||||
"y": 19.892224585997383
|
||||
},
|
||||
"type": "customNode",
|
||||
"data": {
|
||||
"id": "openAI_0",
|
||||
"label": "OpenAI",
|
||||
"name": "openAI",
|
||||
"type": "OpenAI",
|
||||
"baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"],
|
||||
"category": "LLMs",
|
||||
"description": "Wrapper around OpenAI large language models",
|
||||
"inputParams": [
|
||||
{
|
||||
"label": "OpenAI Api Key",
|
||||
"name": "openAIApiKey",
|
||||
"type": "password"
|
||||
},
|
||||
{
|
||||
"label": "Model Name",
|
||||
"name": "modelName",
|
||||
"type": "options",
|
||||
"options": [
|
||||
{
|
||||
"label": "text-davinci-003",
|
||||
"name": "text-davinci-003"
|
||||
},
|
||||
{
|
||||
"label": "text-davinci-002",
|
||||
"name": "text-davinci-002"
|
||||
},
|
||||
{
|
||||
"label": "text-curie-001",
|
||||
"name": "text-curie-001"
|
||||
},
|
||||
{
|
||||
"label": "text-babbage-001",
|
||||
"name": "text-babbage-001"
|
||||
}
|
||||
],
|
||||
"default": "text-davinci-003",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"label": "Temperature",
|
||||
"name": "temperature",
|
||||
"type": "number",
|
||||
"default": 0.7,
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"inputAnchors": [],
|
||||
"inputs": {
|
||||
"modelName": "text-davinci-003",
|
||||
"temperature": 0.7
|
||||
},
|
||||
"outputAnchors": [
|
||||
{
|
||||
"id": "openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel",
|
||||
"name": "openAI",
|
||||
"label": "OpenAI",
|
||||
"type": "OpenAI | BaseLLM | BaseLanguageModel"
|
||||
}
|
||||
],
|
||||
"selected": false
|
||||
},
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 1207.112878089014,
|
||||
"y": 19.892224585997383
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"width": 300,
|
||||
"height": 278,
|
||||
"id": "openAIEmbeddings_0",
|
||||
"position": {
|
||||
"x": 790.9246671661176,
|
||||
"y": 621.2742019819876
|
||||
},
|
||||
"type": "customNode",
|
||||
"data": {
|
||||
"id": "openAIEmbeddings_0",
|
||||
"label": "OpenAI Embeddings",
|
||||
"name": "openAIEmbeddings",
|
||||
"type": "OpenAIEmbeddings",
|
||||
"baseClasses": ["OpenAIEmbeddings", "Embeddings"],
|
||||
"category": "Embeddings",
|
||||
"description": "OpenAI API to generate embeddings for a given text",
|
||||
"inputParams": [
|
||||
{
|
||||
"label": "OpenAI Api Key",
|
||||
"name": "openAIApiKey",
|
||||
"type": "password"
|
||||
}
|
||||
],
|
||||
"inputAnchors": [],
|
||||
"inputs": {},
|
||||
"outputAnchors": [
|
||||
{
|
||||
"id": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings",
|
||||
"name": "openAIEmbeddings",
|
||||
"label": "OpenAIEmbeddings",
|
||||
"type": "OpenAIEmbeddings | Embeddings"
|
||||
}
|
||||
],
|
||||
"selected": false
|
||||
},
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 790.9246671661176,
|
||||
"y": 621.2742019819876
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"width": 300,
|
||||
"height": 603,
|
||||
"id": "pineconeUpsert_0",
|
||||
"position": {
|
||||
"x": 1207.9646568749058,
|
||||
"y": 531.8684248168081
|
||||
},
|
||||
"type": "customNode",
|
||||
"data": {
|
||||
"id": "pineconeUpsert_0",
|
||||
"label": "Pinecone Upsert Document",
|
||||
"name": "pineconeUpsert",
|
||||
"type": "Pinecone",
|
||||
"baseClasses": ["Pinecone", "BaseRetriever"],
|
||||
"category": "Vector Stores",
|
||||
"description": "Upsert documents to Pinecone",
|
||||
"inputParams": [
|
||||
{
|
||||
"label": "Pinecone Api Key",
|
||||
"name": "pineconeApiKey",
|
||||
"type": "password",
|
||||
"id": "pineconeUpsert_0-input-pineconeApiKey-password"
|
||||
},
|
||||
{
|
||||
"label": "Pinecone Environment",
|
||||
"name": "pineconeEnv",
|
||||
"type": "string",
|
||||
"id": "pineconeUpsert_0-input-pineconeEnv-string"
|
||||
},
|
||||
{
|
||||
"label": "Pinecone Index",
|
||||
"name": "pineconeIndex",
|
||||
"type": "string",
|
||||
"id": "pineconeUpsert_0-input-pineconeIndex-string"
|
||||
}
|
||||
],
|
||||
"inputAnchors": [
|
||||
{
|
||||
"label": "Document",
|
||||
"name": "document",
|
||||
"type": "Document",
|
||||
"id": "pineconeUpsert_0-input-document-Document"
|
||||
},
|
||||
{
|
||||
"label": "Embeddings",
|
||||
"name": "embeddings",
|
||||
"type": "Embeddings",
|
||||
"id": "pineconeUpsert_0-input-embeddings-Embeddings"
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"document": "{{pdfFile_0.data.instance}}",
|
||||
"embeddings": "{{openAIEmbeddings_0.data.instance}}",
|
||||
"pineconeEnv": "us-west4-gcp",
|
||||
"pineconeIndex": "test"
|
||||
},
|
||||
"outputAnchors": [
|
||||
{
|
||||
"name": "output",
|
||||
"label": "Output",
|
||||
"type": "options",
|
||||
"options": [
|
||||
{
|
||||
"id": "pineconeUpsert_0-output-retriever-Pinecone|BaseRetriever",
|
||||
"name": "retriever",
|
||||
"label": "Pinecone Retriever",
|
||||
"type": "Pinecone | BaseRetriever"
|
||||
},
|
||||
{
|
||||
"id": "pineconeUpsert_0-output-vectorStore-Pinecone|VectorStore",
|
||||
"name": "vectorStore",
|
||||
"label": "Pinecone Vector Store",
|
||||
"type": "Pinecone | VectorStore"
|
||||
}
|
||||
],
|
||||
"default": "retriever"
|
||||
}
|
||||
],
|
||||
"outputs": {
|
||||
"output": "retriever"
|
||||
},
|
||||
"selected": false
|
||||
},
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 1207.9646568749058,
|
||||
"y": 531.8684248168081
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"width": 300,
|
||||
"height": 455,
|
||||
"id": "pdfFile_0",
|
||||
"position": {
|
||||
"x": 806.583408520452,
|
||||
"y": 95.94591389235777
|
||||
},
|
||||
"type": "customNode",
|
||||
"data": {
|
||||
"id": "pdfFile_0",
|
||||
"label": "Pdf File",
|
||||
"name": "pdfFile",
|
||||
"type": "Document",
|
||||
"baseClasses": ["Document"],
|
||||
"category": "Document Loaders",
|
||||
"description": "Load data from PDF files",
|
||||
"inputParams": [
|
||||
{
|
||||
"label": "Pdf File",
|
||||
"name": "pdfFile",
|
||||
"type": "file",
|
||||
"fileType": ".pdf",
|
||||
"id": "pdfFile_0-input-pdfFile-file"
|
||||
},
|
||||
{
|
||||
"label": "Usage",
|
||||
"name": "usage",
|
||||
"type": "options",
|
||||
"options": [
|
||||
{
|
||||
"label": "One document per page",
|
||||
"name": "perPage"
|
||||
},
|
||||
{
|
||||
"label": "One document per file",
|
||||
"name": "perFile"
|
||||
}
|
||||
],
|
||||
"default": "perPage",
|
||||
"id": "pdfFile_0-input-usage-options"
|
||||
}
|
||||
],
|
||||
"inputAnchors": [
|
||||
{
|
||||
"label": "Text Splitter",
|
||||
"name": "textSplitter",
|
||||
"type": "TextSplitter",
|
||||
"optional": true,
|
||||
"id": "pdfFile_0-input-textSplitter-TextSplitter"
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"textSplitter": "{{recursiveCharacterTextSplitter_0.data.instance}}",
|
||||
"usage": "perPage"
|
||||
},
|
||||
"outputAnchors": [
|
||||
{
|
||||
"id": "pdfFile_0-output-pdfFile-Document",
|
||||
"name": "pdfFile",
|
||||
"label": "Document",
|
||||
"type": "Document"
|
||||
}
|
||||
],
|
||||
"outputs": {},
|
||||
"selected": false
|
||||
},
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 806.583408520452,
|
||||
"y": 95.94591389235777
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"width": 300,
|
||||
"height": 280,
|
||||
"id": "conversationalRetrievalQAChain_0",
|
||||
"position": {
|
||||
"x": 1608.5085986864585,
|
||||
"y": 415.26454165076757
|
||||
},
|
||||
"type": "customNode",
|
||||
"data": {
|
||||
"id": "conversationalRetrievalQAChain_0",
|
||||
"label": "Conversational Retrieval QA Chain",
|
||||
"name": "conversationalRetrievalQAChain",
|
||||
"type": "ConversationalRetrievalQAChain",
|
||||
"baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"],
|
||||
"category": "Chains",
|
||||
"description": "Document QA - built on RetrievalQAChain to provide a chat history component",
|
||||
"inputParams": [],
|
||||
"inputAnchors": [
|
||||
{
|
||||
"label": "LLM",
|
||||
"name": "llm",
|
||||
"type": "BaseLLM",
|
||||
"id": "conversationalRetrievalQAChain_0-input-llm-BaseLLM"
|
||||
},
|
||||
{
|
||||
"label": "Vector Store Retriever",
|
||||
"name": "vectorStoreRetriever",
|
||||
"type": "BaseRetriever",
|
||||
"id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever"
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"llm": "{{openAI_0.data.instance}}",
|
||||
"vectorStoreRetriever": "{{pineconeUpsert_0.data.instance}}"
|
||||
},
|
||||
"outputAnchors": [
|
||||
{
|
||||
"id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain",
|
||||
"name": "conversationalRetrievalQAChain",
|
||||
"label": "ConversationalRetrievalQAChain",
|
||||
"type": "ConversationalRetrievalQAChain | BaseChain"
|
||||
}
|
||||
],
|
||||
"outputs": {},
|
||||
"selected": false
|
||||
},
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 1608.5085986864585,
|
||||
"y": 415.26454165076757
|
||||
},
|
||||
"dragging": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "openAIEmbeddings_0",
|
||||
"sourceHandle": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings",
|
||||
"target": "pineconeUpsert_0",
|
||||
"targetHandle": "pineconeUpsert_0-input-embeddings-Embeddings",
|
||||
"type": "buttonedge",
|
||||
"id": "openAIEmbeddings_0-openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings-pineconeUpsert_0-pineconeUpsert_0-input-embeddings-Embeddings",
|
||||
"data": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "recursiveCharacterTextSplitter_0",
|
||||
"sourceHandle": "recursiveCharacterTextSplitter_0-output-recursiveCharacterTextSplitter-RecursiveCharacterTextSplitter|TextSplitter",
|
||||
"target": "pdfFile_0",
|
||||
"targetHandle": "pdfFile_0-input-textSplitter-TextSplitter",
|
||||
"type": "buttonedge",
|
||||
"id": "recursiveCharacterTextSplitter_0-recursiveCharacterTextSplitter_0-output-recursiveCharacterTextSplitter-RecursiveCharacterTextSplitter|TextSplitter-pdfFile_0-pdfFile_0-input-textSplitter-TextSplitter",
|
||||
"data": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "pdfFile_0",
|
||||
"sourceHandle": "pdfFile_0-output-pdfFile-Document",
|
||||
"target": "pineconeUpsert_0",
|
||||
"targetHandle": "pineconeUpsert_0-input-document-Document",
|
||||
"type": "buttonedge",
|
||||
"id": "pdfFile_0-pdfFile_0-output-pdfFile-Document-pineconeUpsert_0-pineconeUpsert_0-input-document-Document",
|
||||
"data": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "openAI_0",
|
||||
"sourceHandle": "openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel",
|
||||
"target": "conversationalRetrievalQAChain_0",
|
||||
"targetHandle": "conversationalRetrievalQAChain_0-input-llm-BaseLLM",
|
||||
"type": "buttonedge",
|
||||
"id": "openAI_0-openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-llm-BaseLLM",
|
||||
"data": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "pineconeUpsert_0",
|
||||
"sourceHandle": "pineconeUpsert_0-output-retriever-Pinecone|BaseRetriever",
|
||||
"target": "conversationalRetrievalQAChain_0",
|
||||
"targetHandle": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever",
|
||||
"type": "buttonedge",
|
||||
"id": "pineconeUpsert_0-pineconeUpsert_0-output-retriever-Pinecone|BaseRetriever-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever",
|
||||
"data": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ export class App {
|
|||
|
||||
// Allow access from ui when yarn run dev
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
this.app.use(cors({ credentials: true, origin: 'http://localhost:8080' }))
|
||||
this.app.use(cors({ credentials: true, origin: ['http://localhost:8080', 'http://localhost:5678'] }))
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 512 512" focusable="false" class="chakra-icon css-lbf1w4" id="Capa_1" enable-background="new 0 0 512 512" xmlns="http://www.w3.org/2000/svg"><g><g><path d="m512 141.17v229.66c0 39.96-32.51 72.47-72.46 72.47h-367.08c-39.95 0-72.46-32.51-72.46-72.47v-229.66c0-39.96 32.51-72.47 72.46-72.47h367.08c39.95 0 72.46 32.51 72.46 72.47z" fill="#6aa9ff"></path></g><path d="m512 141.17v229.66c0 39.96-32.51 72.47-72.46 72.47h-183.54v-374.6h183.54c39.95 0 72.46 32.51 72.46 72.47z" fill="#4987ea"></path><g><path d="m146.16 349.223-78.4-78.4c-5.858-5.858-5.858-15.355 0-21.213l86.833-86.833c5.857-5.858 15.355-5.858 21.213 0s5.858 15.355 0 21.213l-76.226 76.226 67.793 67.794c5.858 5.858 5.858 15.355 0 21.213-5.857 5.858-15.355 5.859-21.213 0z" fill="#f0f7ff"></path></g><g><path d="m336.194 349.223c-5.858-5.858-5.858-15.355 0-21.213l76.226-76.227-67.793-67.794c-5.858-5.858-5.858-15.355 0-21.213 5.857-5.858 15.355-5.858 21.213 0l78.4 78.4c5.858 5.858 5.858 15.355 0 21.213l-86.833 86.833c-5.856 5.859-15.355 5.86-21.213.001z" fill="#dfe7f4"></path></g><g><path d="m309.54 148.7-53.54 151.6-25.78 72.99c-2.792 7.888-11.443 11.903-19.14 9.15-7.81-2.76-11.91-11.33-9.15-19.14l54.07-153.1 25.25-71.49c2.76-7.81 11.33-11.91 19.14-9.15s11.91 11.33 9.15 19.14z" fill="#f0f7ff"></path></g><path d="m309.54 148.7-53.54 151.6v-90.1l25.25-71.49c2.76-7.81 11.33-11.91 19.14-9.15s11.91 11.33 9.15 19.14z" fill="#dfe7f4"></path></g></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -8,6 +8,7 @@ import { baseURL } from 'store/constant'
|
|||
import pythonSVG from 'assets/images/python.svg'
|
||||
import javascriptSVG from 'assets/images/javascript.svg'
|
||||
import cURLSVG from 'assets/images/cURL.svg'
|
||||
import EmbedSVG from 'assets/images/embed.svg'
|
||||
|
||||
function TabPanel(props) {
|
||||
const { children, value, index, ...other } = props
|
||||
|
|
@ -39,7 +40,7 @@ function a11yProps(index) {
|
|||
|
||||
const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const codes = ['Python', 'JavaScript', 'cURL']
|
||||
const codes = ['Embed', 'Python', 'JavaScript', 'cURL']
|
||||
const [value, setValue] = useState(0)
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
|
|
@ -74,6 +75,15 @@ output = query({
|
|||
const result = await response.json();
|
||||
return result;
|
||||
}
|
||||
`
|
||||
} else if (codeLang === 'Embed') {
|
||||
return `<script type="module">
|
||||
import Chatbot from "https://cdn.jsdelivr.net/npm/flowise-embed@latest/dist/web.js"
|
||||
Chatbot.init({
|
||||
chatflowid: "${dialogProps.chatflowid}",
|
||||
apiHost: "${baseURL}",
|
||||
})
|
||||
</script>
|
||||
`
|
||||
} else if (codeLang === 'cURL') {
|
||||
return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\
|
||||
|
|
@ -86,7 +96,7 @@ output = query({
|
|||
const getLang = (codeLang) => {
|
||||
if (codeLang === 'Python') {
|
||||
return 'python'
|
||||
} else if (codeLang === 'JavaScript') {
|
||||
} else if (codeLang === 'JavaScript' || codeLang === 'Embed') {
|
||||
return 'javascript'
|
||||
} else if (codeLang === 'cURL') {
|
||||
return 'bash'
|
||||
|
|
@ -99,6 +109,8 @@ output = query({
|
|||
return pythonSVG
|
||||
} else if (codeLang === 'JavaScript') {
|
||||
return javascriptSVG
|
||||
} else if (codeLang === 'Embed') {
|
||||
return EmbedSVG
|
||||
} else if (codeLang === 'cURL') {
|
||||
return cURLSVG
|
||||
}
|
||||
|
|
@ -123,7 +135,7 @@ output = query({
|
|||
<Tab
|
||||
icon={
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: 'auto', width: 'auto', marginLeft: 10 }}
|
||||
style={{ objectFit: 'cover', height: 15, width: 'auto', marginLeft: 10 }}
|
||||
src={getSVG(codeLang)}
|
||||
alt='code'
|
||||
/>
|
||||
|
|
@ -138,6 +150,14 @@ output = query({
|
|||
<div style={{ marginTop: 10 }}></div>
|
||||
{codes.map((codeLang, index) => (
|
||||
<TabPanel key={index} value={value} index={index}>
|
||||
{codeLang === 'Embed' && (
|
||||
<>
|
||||
<span>
|
||||
Paste this anywhere in the <code>{`<body>`}</code> tag of your html file
|
||||
</span>
|
||||
<div style={{ height: 10 }}></div>
|
||||
</>
|
||||
)}
|
||||
<CopyBlock
|
||||
theme={atomOneDark}
|
||||
text={getCode(codeLang)}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||
|
||||
const onAPIDialogClick = () => {
|
||||
setAPIDialogProps({
|
||||
title: 'Use this chatflow with API',
|
||||
title: 'Embed in your application or use as API',
|
||||
chatflowid: chatflow.id
|
||||
})
|
||||
setAPIDialogOpen(true)
|
||||
|
|
@ -230,26 +230,28 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<ButtonBase title='API Endpoint' sx={{ borderRadius: '50%', mr: 2 }}>
|
||||
<Avatar
|
||||
variant='rounded'
|
||||
sx={{
|
||||
...theme.typography.commonAvatar,
|
||||
...theme.typography.mediumAvatar,
|
||||
transition: 'all .2s ease-in-out',
|
||||
background: theme.palette.canvasHeader.deployLight,
|
||||
color: theme.palette.canvasHeader.deployDark,
|
||||
'&:hover': {
|
||||
background: theme.palette.canvasHeader.deployDark,
|
||||
color: theme.palette.canvasHeader.deployLight
|
||||
}
|
||||
}}
|
||||
color='inherit'
|
||||
onClick={onAPIDialogClick}
|
||||
>
|
||||
<IconWorldWww stroke={1.5} size='1.3rem' />
|
||||
</Avatar>
|
||||
</ButtonBase>
|
||||
{chatflow && chatflow.id && (
|
||||
<ButtonBase title='Embed/API' sx={{ borderRadius: '50%', mr: 2 }}>
|
||||
<Avatar
|
||||
variant='rounded'
|
||||
sx={{
|
||||
...theme.typography.commonAvatar,
|
||||
...theme.typography.mediumAvatar,
|
||||
transition: 'all .2s ease-in-out',
|
||||
background: theme.palette.canvasHeader.deployLight,
|
||||
color: theme.palette.canvasHeader.deployDark,
|
||||
'&:hover': {
|
||||
background: theme.palette.canvasHeader.deployDark,
|
||||
color: theme.palette.canvasHeader.deployLight
|
||||
}
|
||||
}}
|
||||
color='inherit'
|
||||
onClick={onAPIDialogClick}
|
||||
>
|
||||
<IconWorldWww stroke={1.5} size='1.3rem' />
|
||||
</Avatar>
|
||||
</ButtonBase>
|
||||
)}
|
||||
<ButtonBase title='Save Chatflow' sx={{ borderRadius: '50%', mr: 2 }}>
|
||||
<Avatar
|
||||
variant='rounded'
|
||||
|
|
|
|||
Loading…
Reference in New Issue