mirror of
https://gitee.com/dify_ai/dify.git
synced 2025-12-07 11:55:44 +08:00
Compare commits
61 Commits
feat/updat
...
feat/varia
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29cdfcde61 | ||
|
|
8c52beb983 | ||
|
|
cae482af5d | ||
|
|
365c6794c9 | ||
|
|
8165018a3f | ||
|
|
c11e0c72e5 | ||
|
|
23e2402f8c | ||
|
|
2fa4b5a036 | ||
|
|
6cde84c182 | ||
|
|
482879e312 | ||
|
|
bd44769441 | ||
|
|
6f692028b3 | ||
|
|
df10424950 | ||
|
|
5edfbac527 | ||
|
|
f1d7b2a99a | ||
|
|
350912e35b | ||
|
|
f2acce167a | ||
|
|
60dca1facd | ||
|
|
52d637a83b | ||
|
|
4964ca0736 | ||
|
|
a43a5893cd | ||
|
|
f5ef56ea5e | ||
|
|
74ce50d534 | ||
|
|
3c38e0baa0 | ||
|
|
0cc6256790 | ||
|
|
80e8129b5e | ||
|
|
3e711369d9 | ||
|
|
65d54153d0 | ||
|
|
80148b0d6d | ||
|
|
398ea5241b | ||
|
|
c9eac4b0c9 | ||
|
|
69a2dc73bf | ||
|
|
0dfb61a350 | ||
|
|
b1d949d2a2 | ||
|
|
a192e4d5c8 | ||
|
|
2732dada12 | ||
|
|
6491a9fe5c | ||
|
|
2709336dbc | ||
|
|
d9182f1425 | ||
|
|
1ceed84302 | ||
|
|
a8bbed2ee9 | ||
|
|
003b4b1b08 | ||
|
|
842ab015c1 | ||
|
|
2fe51dd65a | ||
|
|
0c02fdf4d9 | ||
|
|
c027b59235 | ||
|
|
a4c179e1c0 | ||
|
|
189dcc504c | ||
|
|
f95a6ecb2a | ||
|
|
122a2c5808 | ||
|
|
7141770b64 | ||
|
|
3eb9eea805 | ||
|
|
21660db136 | ||
|
|
c96fafa020 | ||
|
|
bbf64b9b81 | ||
|
|
287073fe2b | ||
|
|
035a001223 | ||
|
|
dfc993e7f2 | ||
|
|
e4b738b7f4 | ||
|
|
e915ddfe06 | ||
|
|
375a07a954 |
@@ -4,16 +4,19 @@ import cn from '@/utils/classnames'
|
||||
type BadgeProps = {
|
||||
className?: string
|
||||
text: string
|
||||
uppercase?: boolean
|
||||
}
|
||||
|
||||
const Badge = ({
|
||||
className,
|
||||
text,
|
||||
uppercase = true,
|
||||
}: BadgeProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'inline-flex items-center px-[5px] h-5 rounded-[5px] border border-divider-deep system-2xs-medium-uppercase leading-3 text-text-tertiary',
|
||||
'inline-flex items-center px-[5px] h-5 rounded-[5px] border border-divider-deep leading-3 text-text-tertiary',
|
||||
uppercase ? 'system-2xs-medium-uppercase' : 'system-xs-medium',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon L">
|
||||
<g id="Vector">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.33463 3.33333C2.96643 3.33333 2.66796 3.63181 2.66796 4V10.6667C2.66796 11.0349 2.96643 11.3333 3.33463 11.3333H4.66796C5.03615 11.3333 5.33463 11.6318 5.33463 12V12.8225L7.65833 11.4283C7.76194 11.3662 7.8805 11.3333 8.00132 11.3333H12.0013C12.3695 11.3333 12.668 11.0349 12.668 10.6667C12.668 10.2985 12.9665 10 13.3347 10C13.7028 10 14.0013 10.2985 14.0013 10.6667C14.0013 11.7713 13.1058 12.6667 12.0013 12.6667H8.18598L5.01095 14.5717C4.805 14.6952 4.5485 14.6985 4.33949 14.5801C4.13049 14.4618 4.00129 14.2402 4.00129 14V12.6667H3.33463C2.23006 12.6667 1.33463 11.7713 1.33463 10.6667V4C1.33463 2.89543 2.23006 2 3.33463 2H6.66798C7.03617 2 7.33464 2.29848 7.33464 2.66667C7.33464 3.03486 7.03617 3.33333 6.66798 3.33333H3.33463Z" fill="#354052"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.74113 2.66667C8.74113 2.29848 9.03961 2 9.4078 2H10.331C10.9721 2 11.5177 2.43571 11.6859 3.04075L11.933 3.93004L12.8986 2.77189C13.3045 2.28508 13.9018 2 14.536 2H14.5954C14.9636 2 15.2621 2.29848 15.2621 2.66667C15.2621 3.03486 14.9636 3.33333 14.5954 3.33333H14.536C14.3048 3.33333 14.08 3.43702 13.9227 3.6257L12.367 5.49165L12.8609 7.2689C12.8746 7.31803 12.9105 7.33333 12.9312 7.33333H13.8543C14.2225 7.33333 14.521 7.63181 14.521 8C14.521 8.36819 14.2225 8.66667 13.8543 8.66667H12.9312C12.29 8.66667 11.7444 8.23095 11.5763 7.62591L11.3291 6.73654L10.3634 7.89478C9.95758 8.38159 9.36022 8.66667 8.72604 8.66667H8.66666C8.29847 8.66667 7.99999 8.36819 7.99999 8C7.99999 7.63181 8.29847 7.33333 8.66666 7.33333H8.72604C8.95723 7.33333 9.18204 7.22965 9.33935 7.04096L10.8951 5.17493L10.4012 3.39777C10.3876 3.34863 10.3516 3.33333 10.331 3.33333H9.4078C9.03961 3.33333 8.74113 3.03486 8.74113 2.66667Z" fill="#354052"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="21" height="8" viewBox="0 0 21 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.646446 3.64645C0.451185 3.84171 0.451185 4.15829 0.646446 4.35355L3.82843 7.53553C4.02369 7.7308 4.34027 7.7308 4.53553 7.53553C4.7308 7.34027 4.7308 7.02369 4.53553 6.82843L1.70711 4L4.53553 1.17157C4.7308 0.976311 4.7308 0.659728 4.53553 0.464466C4.34027 0.269204 4.02369 0.269204 3.82843 0.464466L0.646446 3.64645ZM21 3.5L1 3.5V4.5L21 4.5V3.5Z" fill="#101828" fill-opacity="0.3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 497 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="26" height="8" viewBox="0 0 26 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M25.3536 4.35355C25.5488 4.15829 25.5488 3.84171 25.3536 3.64644L22.1716 0.464465C21.9763 0.269202 21.6597 0.269202 21.4645 0.464465C21.2692 0.659727 21.2692 0.976309 21.4645 1.17157L24.2929 4L21.4645 6.82843C21.2692 7.02369 21.2692 7.34027 21.4645 7.53553C21.6597 7.73079 21.9763 7.73079 22.1716 7.53553L25.3536 4.35355ZM3.59058e-08 4.5L25 4.5L25 3.5L-3.59058e-08 3.5L3.59058e-08 4.5Z" fill="#101828" fill-opacity="0.3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 533 B |
@@ -0,0 +1,9 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="variable assigner">
|
||||
<g id="Vector">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.71438 4.42875C1.71438 3.22516 2.68954 2.25 3.89313 2.25C4.30734 2.25 4.64313 2.58579 4.64313 3C4.64313 3.41421 4.30734 3.75 3.89313 3.75C3.51796 3.75 3.21438 4.05359 3.21438 4.42875V7.28563C3.21438 7.48454 3.13536 7.6753 2.9947 7.81596L2.81066 8L2.9947 8.18404C3.13536 8.3247 3.21438 8.51546 3.21438 8.71437V11.5713C3.21438 11.9464 3.51796 12.25 3.89313 12.25C4.30734 12.25 4.64313 12.5858 4.64313 13C4.64313 13.4142 4.30734 13.75 3.89313 13.75C2.68954 13.75 1.71438 12.7748 1.71438 11.5713V9.02503L1.21967 8.53033C1.07902 8.38968 1 8.19891 1 8C1 7.80109 1.07902 7.61032 1.21967 7.46967L1.71438 6.97497V4.42875ZM11.3568 3C11.3568 2.58579 11.6925 2.25 12.1068 2.25C13.3103 2.25 14.2855 3.22516 14.2855 4.42875V6.97497L14.7802 7.46967C14.9209 7.61032 14.9999 7.80109 14.9999 8C14.9999 8.19891 14.9209 8.38968 14.7802 8.53033L14.2855 9.02503V11.5713C14.2855 12.7751 13.3095 13.75 12.1068 13.75C11.6925 13.75 11.3568 13.4142 11.3568 13C11.3568 12.5858 11.6925 12.25 12.1068 12.25C12.4815 12.25 12.7855 11.9462 12.7855 11.5713V8.71437C12.7855 8.51546 12.8645 8.3247 13.0052 8.18404L13.1892 8L13.0052 7.81596C12.8645 7.6753 12.7855 7.48454 12.7855 7.28563V4.42875C12.7855 4.05359 12.4819 3.75 12.1068 3.75C11.6925 3.75 11.3568 3.41421 11.3568 3Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 6C5.25 5.58579 5.58579 5.25 6 5.25H10C10.4142 5.25 10.75 5.58579 10.75 6C10.75 6.41421 10.4142 6.75 10 6.75H6C5.58579 6.75 5.25 6.41421 5.25 6Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 10C5.25 9.58579 5.58579 9.25 6 9.25H10C10.4142 9.25 10.75 9.58579 10.75 10C10.75 10.4142 10.4142 10.75 10 10.75H6C5.58579 10.75 5.25 10.4142 5.25 10Z" fill="white"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "16",
|
||||
"height": "16",
|
||||
"viewBox": "0 0 16 16",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Icon L"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Vector"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M3.33463 3.33333C2.96643 3.33333 2.66796 3.63181 2.66796 4V10.6667C2.66796 11.0349 2.96643 11.3333 3.33463 11.3333H4.66796C5.03615 11.3333 5.33463 11.6318 5.33463 12V12.8225L7.65833 11.4283C7.76194 11.3662 7.8805 11.3333 8.00132 11.3333H12.0013C12.3695 11.3333 12.668 11.0349 12.668 10.6667C12.668 10.2985 12.9665 10 13.3347 10C13.7028 10 14.0013 10.2985 14.0013 10.6667C14.0013 11.7713 13.1058 12.6667 12.0013 12.6667H8.18598L5.01095 14.5717C4.805 14.6952 4.5485 14.6985 4.33949 14.5801C4.13049 14.4618 4.00129 14.2402 4.00129 14V12.6667H3.33463C2.23006 12.6667 1.33463 11.7713 1.33463 10.6667V4C1.33463 2.89543 2.23006 2 3.33463 2H6.66798C7.03617 2 7.33464 2.29848 7.33464 2.66667C7.33464 3.03486 7.03617 3.33333 6.66798 3.33333H3.33463Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M8.74113 2.66667C8.74113 2.29848 9.03961 2 9.4078 2H10.331C10.9721 2 11.5177 2.43571 11.6859 3.04075L11.933 3.93004L12.8986 2.77189C13.3045 2.28508 13.9018 2 14.536 2H14.5954C14.9636 2 15.2621 2.29848 15.2621 2.66667C15.2621 3.03486 14.9636 3.33333 14.5954 3.33333H14.536C14.3048 3.33333 14.08 3.43702 13.9227 3.6257L12.367 5.49165L12.8609 7.2689C12.8746 7.31803 12.9105 7.33333 12.9312 7.33333H13.8543C14.2225 7.33333 14.521 7.63181 14.521 8C14.521 8.36819 14.2225 8.66667 13.8543 8.66667H12.9312C12.29 8.66667 11.7444 8.23095 11.5763 7.62591L11.3291 6.73654L10.3634 7.89478C9.95758 8.38159 9.36022 8.66667 8.72604 8.66667H8.66666C8.29847 8.66667 7.99999 8.36819 7.99999 8C7.99999 7.63181 8.29847 7.33333 8.66666 7.33333H8.72604C8.95723 7.33333 9.18204 7.22965 9.33935 7.04096L10.8951 5.17493L10.4012 3.39777C10.3876 3.34863 10.3516 3.33333 10.331 3.33333H9.4078C9.03961 3.33333 8.74113 3.03486 8.74113 2.66667Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "BubbleX"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './BubbleX.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'BubbleX'
|
||||
|
||||
export default Icon
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "21",
|
||||
"height": "8",
|
||||
"viewBox": "0 0 21 8",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M0.646446 3.64645C0.451185 3.84171 0.451185 4.15829 0.646446 4.35355L3.82843 7.53553C4.02369 7.7308 4.34027 7.7308 4.53553 7.53553C4.7308 7.34027 4.7308 7.02369 4.53553 6.82843L1.70711 4L4.53553 1.17157C4.7308 0.976311 4.7308 0.659728 4.53553 0.464466C4.34027 0.269204 4.02369 0.269204 3.82843 0.464466L0.646446 3.64645ZM21 3.5L1 3.5V4.5L21 4.5V3.5Z",
|
||||
"fill": "currentColor",
|
||||
"fill-opacity": "0.3"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "LongArrowLeft"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './LongArrowLeft.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'LongArrowLeft'
|
||||
|
||||
export default Icon
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "26",
|
||||
"height": "8",
|
||||
"viewBox": "0 0 26 8",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M25.3536 4.35355C25.5488 4.15829 25.5488 3.84171 25.3536 3.64644L22.1716 0.464465C21.9763 0.269202 21.6597 0.269202 21.4645 0.464465C21.2692 0.659727 21.2692 0.976309 21.4645 1.17157L24.2929 4L21.4645 6.82843C21.2692 7.02369 21.2692 7.34027 21.4645 7.53553C21.6597 7.73079 21.9763 7.73079 22.1716 7.53553L25.3536 4.35355ZM3.59058e-08 4.5L25 4.5L25 3.5L-3.59058e-08 3.5L3.59058e-08 4.5Z",
|
||||
"fill": "currentColor",
|
||||
"fill-opacity": "0.3"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "LongArrowRight"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './LongArrowRight.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'LongArrowRight'
|
||||
|
||||
export default Icon
|
||||
@@ -1,8 +1,11 @@
|
||||
export { default as Apps02 } from './Apps02'
|
||||
export { default as BubbleX } from './BubbleX'
|
||||
export { default as Colors } from './Colors'
|
||||
export { default as DragHandle } from './DragHandle'
|
||||
export { default as Env } from './Env'
|
||||
export { default as Exchange02 } from './Exchange02'
|
||||
export { default as FileCode } from './FileCode'
|
||||
export { default as Icon3Dots } from './Icon3Dots'
|
||||
export { default as LongArrowLeft } from './LongArrowLeft'
|
||||
export { default as LongArrowRight } from './LongArrowRight'
|
||||
export { default as Tools } from './Tools'
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "16",
|
||||
"height": "16",
|
||||
"viewBox": "0 0 16 16",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "variable assigner"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Vector"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M1.71438 4.42875C1.71438 3.22516 2.68954 2.25 3.89313 2.25C4.30734 2.25 4.64313 2.58579 4.64313 3C4.64313 3.41421 4.30734 3.75 3.89313 3.75C3.51796 3.75 3.21438 4.05359 3.21438 4.42875V7.28563C3.21438 7.48454 3.13536 7.6753 2.9947 7.81596L2.81066 8L2.9947 8.18404C3.13536 8.3247 3.21438 8.51546 3.21438 8.71437V11.5713C3.21438 11.9464 3.51796 12.25 3.89313 12.25C4.30734 12.25 4.64313 12.5858 4.64313 13C4.64313 13.4142 4.30734 13.75 3.89313 13.75C2.68954 13.75 1.71438 12.7748 1.71438 11.5713V9.02503L1.21967 8.53033C1.07902 8.38968 1 8.19891 1 8C1 7.80109 1.07902 7.61032 1.21967 7.46967L1.71438 6.97497V4.42875ZM11.3568 3C11.3568 2.58579 11.6925 2.25 12.1068 2.25C13.3103 2.25 14.2855 3.22516 14.2855 4.42875V6.97497L14.7802 7.46967C14.9209 7.61032 14.9999 7.80109 14.9999 8C14.9999 8.19891 14.9209 8.38968 14.7802 8.53033L14.2855 9.02503V11.5713C14.2855 12.7751 13.3095 13.75 12.1068 13.75C11.6925 13.75 11.3568 13.4142 11.3568 13C11.3568 12.5858 11.6925 12.25 12.1068 12.25C12.4815 12.25 12.7855 11.9462 12.7855 11.5713V8.71437C12.7855 8.51546 12.8645 8.3247 13.0052 8.18404L13.1892 8L13.0052 7.81596C12.8645 7.6753 12.7855 7.48454 12.7855 7.28563V4.42875C12.7855 4.05359 12.4819 3.75 12.1068 3.75C11.6925 3.75 11.3568 3.41421 11.3568 3Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M5.25 6C5.25 5.58579 5.58579 5.25 6 5.25H10C10.4142 5.25 10.75 5.58579 10.75 6C10.75 6.41421 10.4142 6.75 10 6.75H6C5.58579 6.75 5.25 6.41421 5.25 6Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M5.25 10C5.25 9.58579 5.58579 9.25 6 9.25H10C10.4142 9.25 10.75 9.58579 10.75 10C10.75 10.4142 10.4142 10.75 10 10.75H6C5.58579 10.75 5.25 10.4142 5.25 10Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Assigner"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Assigner.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'Assigner'
|
||||
|
||||
export default Icon
|
||||
@@ -1,4 +1,5 @@
|
||||
export { default as Answer } from './Answer'
|
||||
export { default as Assigner } from './Assigner'
|
||||
export { default as Code } from './Code'
|
||||
export { default as End } from './End'
|
||||
export { default as Home } from './Home'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { SVGProps } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import s from './style.module.css'
|
||||
import cn from 'classnames'
|
||||
|
||||
type InputProps = {
|
||||
placeholder?: string
|
||||
@@ -27,10 +27,10 @@ const Input = ({ value, defaultValue, onChange, className = '', wrapperClassName
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className={`relative inline-flex w-full ${wrapperClassName}`}>
|
||||
{showPrefix && <span className={s.prefix}>{prefixIcon ?? <GlassIcon className='h-3.5 w-3.5 stroke-current text-gray-700 stroke-2' />}</span>}
|
||||
{showPrefix && <span className='whitespace-nowrap absolute left-2 self-center'>{prefixIcon ?? <GlassIcon className='h-3.5 w-3.5 stroke-current text-gray-700 stroke-2' />}</span>}
|
||||
<input
|
||||
type={type ?? 'text'}
|
||||
className={`${s.input} ${showPrefix ? '!pl-7' : ''} ${className}`}
|
||||
className={cn('inline-flex h-7 w-full py-1 px-2 rounded-lg text-xs leading-normal bg-gray-100 caret-primary-600 hover:bg-gray-100 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus-visible:outline-none focus:bg-white placeholder:text-gray-400', showPrefix ? '!pl-7' : '', className)}
|
||||
placeholder={placeholder ?? (showPrefix ? t('common.operation.search') ?? '' : 'please input')}
|
||||
value={localValue}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.input {
|
||||
@apply inline-flex h-7 w-full py-1 px-2 rounded-lg text-xs leading-normal;
|
||||
@apply bg-gray-100 caret-primary-600 hover:bg-gray-100 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus-visible:outline-none focus:bg-white placeholder:text-gray-400;
|
||||
}
|
||||
.prefix {
|
||||
@apply whitespace-nowrap absolute left-2 self-center
|
||||
}
|
||||
@@ -144,7 +144,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
|
||||
return (
|
||||
<LexicalComposer initialConfig={{ ...initialConfig, editable }}>
|
||||
<div className='relative h-full'>
|
||||
<div className='relative min-h-5'>
|
||||
<RichTextPlugin
|
||||
contentEditable={<ContentEditable className={`${className} outline-none ${compact ? 'leading-5 text-[13px]' : 'leading-6 text-sm'} text-gray-700`} style={style || {}} />}
|
||||
placeholder={<Placeholder value={placeholder} className={placeholderClassName} compact={compact} />}
|
||||
|
||||
@@ -21,10 +21,10 @@ import {
|
||||
} from './index'
|
||||
import cn from '@/utils/classnames'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
|
||||
type WorkflowVariableBlockComponentProps = {
|
||||
@@ -52,6 +52,7 @@ const WorkflowVariableBlockComponent = ({
|
||||
const [localWorkflowNodesMap, setLocalWorkflowNodesMap] = useState<WorkflowNodesMap>(workflowNodesMap)
|
||||
const node = localWorkflowNodesMap![variables[0]]
|
||||
const isEnv = isENV(variables)
|
||||
const isChatVar = isConversationVar(variables)
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([WorkflowVariableBlockNode]))
|
||||
@@ -75,11 +76,11 @@ const WorkflowVariableBlockComponent = ({
|
||||
className={cn(
|
||||
'mx-0.5 relative group/wrap flex items-center h-[18px] pl-0.5 pr-[3px] rounded-[5px] border select-none',
|
||||
isSelected ? ' border-[#84ADFF] bg-[#F5F8FF]' : ' border-black/5 bg-white',
|
||||
!node && !isEnv && '!border-[#F04438] !bg-[#FEF3F2]',
|
||||
!node && !isEnv && !isChatVar && '!border-[#F04438] !bg-[#FEF3F2]',
|
||||
)}
|
||||
ref={ref}
|
||||
>
|
||||
{!isEnv && (
|
||||
{!isEnv && !isChatVar && (
|
||||
<div className='flex items-center'>
|
||||
{
|
||||
node?.type && (
|
||||
@@ -97,11 +98,12 @@ const WorkflowVariableBlockComponent = ({
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
{!isEnv && <Variable02 className='shrink-0 w-3.5 h-3.5' />}
|
||||
{!isEnv && !isChatVar && <Variable02 className='shrink-0 w-3.5 h-3.5' />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div className={cn('shrink-0 ml-0.5 text-xs font-medium truncate', isEnv && 'text-gray-900')} title={varName}>{varName}</div>
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
<div className={cn('shrink-0 ml-0.5 text-xs font-medium truncate', (isEnv || isChatVar) && 'text-gray-900')} title={varName}>{varName}</div>
|
||||
{
|
||||
!node && !isEnv && (
|
||||
!node && !isEnv && !isChatVar && (
|
||||
<RiErrorWarningFill className='ml-0.5 w-3 h-3 text-[#D92D20]' />
|
||||
)
|
||||
}
|
||||
@@ -109,7 +111,7 @@ const WorkflowVariableBlockComponent = ({
|
||||
</div>
|
||||
)
|
||||
|
||||
if (!node && !isEnv) {
|
||||
if (!node && !isEnv && !isChatVar) {
|
||||
return (
|
||||
<TooltipPlus popupContent={t('workflow.errorMsg.invalidVariable')}>
|
||||
{Item}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { memo } from 'react'
|
||||
import { BlockEnum } from './types'
|
||||
import {
|
||||
Answer,
|
||||
Assigner,
|
||||
Code,
|
||||
End,
|
||||
Home,
|
||||
@@ -43,6 +44,7 @@ const getIcon = (type: BlockEnum, className: string) => {
|
||||
[BlockEnum.TemplateTransform]: <TemplatingTransform className={className} />,
|
||||
[BlockEnum.VariableAssigner]: <VariableX className={className} />,
|
||||
[BlockEnum.VariableAggregator]: <VariableX className={className} />,
|
||||
[BlockEnum.Assigner]: <Assigner className={className} />,
|
||||
[BlockEnum.Tool]: <VariableX className={className} />,
|
||||
[BlockEnum.Iteration]: <Iteration className={className} />,
|
||||
[BlockEnum.ParameterExtractor]: <ParameterExtractor className={className} />,
|
||||
@@ -62,6 +64,7 @@ const ICON_CONTAINER_BG_COLOR_MAP: Record<string, string> = {
|
||||
[BlockEnum.TemplateTransform]: 'bg-[#2E90FA]',
|
||||
[BlockEnum.VariableAssigner]: 'bg-[#2E90FA]',
|
||||
[BlockEnum.VariableAggregator]: 'bg-[#2E90FA]',
|
||||
[BlockEnum.Assigner]: 'bg-[#2E90FA]',
|
||||
[BlockEnum.ParameterExtractor]: 'bg-[#2E90FA]',
|
||||
}
|
||||
const BlockIcon: FC<BlockIconProps> = ({
|
||||
|
||||
@@ -59,6 +59,11 @@ export const BLOCKS: Block[] = [
|
||||
type: BlockEnum.VariableAggregator,
|
||||
title: 'Variable Aggregator',
|
||||
},
|
||||
{
|
||||
classification: BlockClassificationEnum.Transform,
|
||||
type: BlockEnum.Assigner,
|
||||
title: 'Variable Assigner',
|
||||
},
|
||||
{
|
||||
classification: BlockClassificationEnum.Transform,
|
||||
type: BlockEnum.ParameterExtractor,
|
||||
|
||||
@@ -12,6 +12,7 @@ import HttpRequestDefault from './nodes/http/default'
|
||||
import ParameterExtractorDefault from './nodes/parameter-extractor/default'
|
||||
import ToolDefault from './nodes/tool/default'
|
||||
import VariableAssignerDefault from './nodes/variable-assigner/default'
|
||||
import AssignerDefault from './nodes/assigner/default'
|
||||
import EndNodeDefault from './nodes/end/default'
|
||||
import IterationDefault from './nodes/iteration/default'
|
||||
|
||||
@@ -133,6 +134,15 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
|
||||
getAvailableNextNodes: VariableAssignerDefault.getAvailableNextNodes,
|
||||
checkValid: VariableAssignerDefault.checkValid,
|
||||
},
|
||||
[BlockEnum.Assigner]: {
|
||||
author: 'Dify',
|
||||
about: '',
|
||||
availablePrevNodes: [],
|
||||
availableNextNodes: [],
|
||||
getAvailablePrevNodes: AssignerDefault.getAvailablePrevNodes,
|
||||
getAvailableNextNodes: AssignerDefault.getAvailableNextNodes,
|
||||
checkValid: AssignerDefault.checkValid,
|
||||
},
|
||||
[BlockEnum.VariableAggregator]: {
|
||||
author: 'Dify',
|
||||
about: '',
|
||||
@@ -268,6 +278,12 @@ export const NODES_INITIAL_DATA = {
|
||||
output_type: '',
|
||||
...VariableAssignerDefault.defaultValue,
|
||||
},
|
||||
[BlockEnum.Assigner]: {
|
||||
type: BlockEnum.Assigner,
|
||||
title: '',
|
||||
desc: '',
|
||||
...AssignerDefault.defaultValue,
|
||||
},
|
||||
[BlockEnum.Tool]: {
|
||||
type: BlockEnum.Tool,
|
||||
title: '',
|
||||
|
||||
24
web/app/components/workflow/header/chat-variable-button.tsx
Normal file
24
web/app/components/workflow/header/chat-variable-button.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { memo } from 'react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
|
||||
const ChatVariableButton = ({ disabled }: { disabled: boolean }) => {
|
||||
const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel)
|
||||
const setShowEnvPanel = useStore(s => s.setShowEnvPanel)
|
||||
const setShowDebugAndPreviewPanel = useStore(s => s.setShowDebugAndPreviewPanel)
|
||||
|
||||
const handleClick = () => {
|
||||
setShowChatVariablePanel(true)
|
||||
setShowEnvPanel(false)
|
||||
setShowDebugAndPreviewPanel(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button className='p-2' disabled={disabled} onClick={handleClick}>
|
||||
<BubbleX className='w-4 h-4 text-components-button-secondary-text' />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ChatVariableButton)
|
||||
@@ -1,21 +1,23 @@
|
||||
import { memo } from 'react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const EnvButton = () => {
|
||||
const EnvButton = ({ disabled }: { disabled: boolean }) => {
|
||||
const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel)
|
||||
const setShowEnvPanel = useStore(s => s.setShowEnvPanel)
|
||||
const setShowDebugAndPreviewPanel = useStore(s => s.setShowDebugAndPreviewPanel)
|
||||
|
||||
const handleClick = () => {
|
||||
setShowEnvPanel(true)
|
||||
setShowChatVariablePanel(false)
|
||||
setShowDebugAndPreviewPanel(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('relative flex items-center justify-center p-0.5 w-8 h-8 rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs cursor-pointer hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover')} onClick={handleClick}>
|
||||
<Button className='p-2' disabled={disabled} onClick={handleClick}>
|
||||
<Env className='w-4 h-4 text-components-button-secondary-text' />
|
||||
</div>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import type { StartNodeType } from '../nodes/start/types'
|
||||
import {
|
||||
useChecklistBeforePublish,
|
||||
useIsChatMode,
|
||||
useNodesReadOnly,
|
||||
useNodesSyncDraft,
|
||||
useWorkflowMode,
|
||||
@@ -31,6 +32,7 @@ import EditingTitle from './editing-title'
|
||||
import RunningTitle from './running-title'
|
||||
import RestoringTitle from './restoring-title'
|
||||
import ViewHistory from './view-history'
|
||||
import ChatVariableButton from './chat-variable-button'
|
||||
import EnvButton from './env-button'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
@@ -44,7 +46,8 @@ const Header: FC = () => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const appSidebarExpand = useAppStore(s => s.appSidebarExpand)
|
||||
const appID = appDetail?.id
|
||||
const { getNodesReadOnly } = useNodesReadOnly()
|
||||
const isChatMode = useIsChatMode()
|
||||
const { nodesReadOnly, getNodesReadOnly } = useNodesReadOnly()
|
||||
const publishedAt = useStore(s => s.publishedAt)
|
||||
const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
|
||||
const toolPublished = useStore(s => s.toolPublished)
|
||||
@@ -165,7 +168,8 @@ const Header: FC = () => {
|
||||
{
|
||||
normal && (
|
||||
<div className='flex items-center gap-2'>
|
||||
<EnvButton />
|
||||
{isChatMode && <ChatVariableButton disabled={nodesReadOnly} />}
|
||||
<EnvButton disabled={nodesReadOnly} />
|
||||
<div className='w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<RunAndHistory />
|
||||
<Button className='text-components-button-secondary-text' onClick={handleShowFeatures}>
|
||||
@@ -176,7 +180,7 @@ const Header: FC = () => {
|
||||
{...{
|
||||
publishedAt,
|
||||
draftUpdatedAt,
|
||||
disabled: Boolean(getNodesReadOnly()),
|
||||
disabled: nodesReadOnly,
|
||||
toolPublished,
|
||||
inputs: variables,
|
||||
onRefreshData: handleToolConfigureUpdate,
|
||||
|
||||
@@ -31,6 +31,7 @@ export const useNodesSyncDraft = () => {
|
||||
const [x, y, zoom] = transform
|
||||
const {
|
||||
appId,
|
||||
conversationVariables,
|
||||
environmentVariables,
|
||||
syncWorkflowDraftHash,
|
||||
} = workflowStore.getState()
|
||||
@@ -82,6 +83,7 @@ export const useNodesSyncDraft = () => {
|
||||
file_upload: features.file,
|
||||
},
|
||||
environment_variables: environmentVariables,
|
||||
conversation_variables: conversationVariables,
|
||||
hash: syncWorkflowDraftHash,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ export const useWorkflowUpdate = () => {
|
||||
setIsSyncingWorkflowDraft,
|
||||
setEnvironmentVariables,
|
||||
setEnvSecrets,
|
||||
setConversationVariables,
|
||||
} = workflowStore.getState()
|
||||
setIsSyncingWorkflowDraft(true)
|
||||
fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => {
|
||||
@@ -78,6 +79,8 @@ export const useWorkflowUpdate = () => {
|
||||
return acc
|
||||
}, {} as Record<string, string>))
|
||||
setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [])
|
||||
// #TODO chatVar sync#
|
||||
setConversationVariables(response.conversation_variables || [])
|
||||
}).finally(() => setIsSyncingWorkflowDraft(false))
|
||||
}, [handleUpdateWorkflowCanvas, workflowStore])
|
||||
|
||||
|
||||
@@ -67,9 +67,11 @@ export const useWorkflowStartRun = () => {
|
||||
setShowDebugAndPreviewPanel,
|
||||
setHistoryWorkflowData,
|
||||
setShowEnvPanel,
|
||||
setShowChatVariablePanel,
|
||||
} = workflowStore.getState()
|
||||
|
||||
setShowEnvPanel(false)
|
||||
setShowChatVariablePanel(false)
|
||||
|
||||
if (showDebugAndPreviewPanel)
|
||||
handleCancelDebugAndPreviewPanel()
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
export const useWorkflowVariables = () => {
|
||||
const { t } = useTranslation()
|
||||
const environmentVariables = useStore(s => s.environmentVariables)
|
||||
const conversationVariables = useStore(s => s.conversationVariables)
|
||||
|
||||
const getNodeAvailableVars = useCallback(({
|
||||
parentNode,
|
||||
@@ -19,12 +20,14 @@ export const useWorkflowVariables = () => {
|
||||
isChatMode,
|
||||
filterVar,
|
||||
hideEnv,
|
||||
hideChatVar,
|
||||
}: {
|
||||
parentNode?: Node | null
|
||||
beforeNodes: Node[]
|
||||
isChatMode: boolean
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean
|
||||
hideEnv?: boolean
|
||||
hideChatVar?: boolean
|
||||
}): NodeOutPutVar[] => {
|
||||
return toNodeAvailableVars({
|
||||
parentNode,
|
||||
@@ -32,9 +35,10 @@ export const useWorkflowVariables = () => {
|
||||
beforeNodes,
|
||||
isChatMode,
|
||||
environmentVariables: hideEnv ? [] : environmentVariables,
|
||||
conversationVariables: (isChatMode && !hideChatVar) ? conversationVariables : [],
|
||||
filterVar,
|
||||
})
|
||||
}, [environmentVariables, t])
|
||||
}, [conversationVariables, environmentVariables, t])
|
||||
|
||||
const getCurrentVariableType = useCallback(({
|
||||
parentNode,
|
||||
@@ -59,8 +63,9 @@ export const useWorkflowVariables = () => {
|
||||
isChatMode,
|
||||
isConstant,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
})
|
||||
}, [environmentVariables])
|
||||
}, [conversationVariables, environmentVariables])
|
||||
|
||||
return {
|
||||
getNodeAvailableVars,
|
||||
|
||||
@@ -478,6 +478,8 @@ export const useWorkflowInit = () => {
|
||||
return acc
|
||||
}, {} as Record<string, string>),
|
||||
environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [],
|
||||
// #TODO chatVar sync#
|
||||
conversationVariables: res.conversation_variables || [],
|
||||
})
|
||||
setSyncWorkflowDraftHash(res.hash)
|
||||
setIsLoading(false)
|
||||
@@ -498,6 +500,7 @@ export const useWorkflowInit = () => {
|
||||
retriever_resource: { enabled: true },
|
||||
},
|
||||
environment_variables: [],
|
||||
conversation_variables: [],
|
||||
},
|
||||
}).then((res) => {
|
||||
workflowStore.getState().setDraftUpdatedAt(res.updated_at)
|
||||
|
||||
@@ -64,6 +64,7 @@ const AddVariablePopupWithPosition = ({
|
||||
} as any,
|
||||
],
|
||||
hideEnv: true,
|
||||
hideChatVar: true,
|
||||
isChatMode,
|
||||
filterVar: filterVar(outputType as VarType),
|
||||
})
|
||||
|
||||
@@ -18,6 +18,8 @@ import { useFeatures } from '@/app/components/base/features/hooks'
|
||||
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
payload: InputVar
|
||||
@@ -56,22 +58,24 @@ const FormItem: FC<Props> = ({
|
||||
}, [value, onChange])
|
||||
const nodeKey = (() => {
|
||||
if (typeof payload.label === 'object') {
|
||||
const { nodeType, nodeName, variable } = payload.label
|
||||
const { nodeType, nodeName, variable, isChatVar } = payload.label
|
||||
return (
|
||||
<div className='h-full flex items-center'>
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon type={nodeType || BlockEnum.Start} />
|
||||
{!isChatVar && (
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon type={nodeType || BlockEnum.Start} />
|
||||
</div>
|
||||
<div className='mx-0.5 text-xs font-medium text-gray-700 max-w-[150px] truncate' title={nodeName}>
|
||||
{nodeName}
|
||||
</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
</div>
|
||||
<div className='mx-0.5 text-xs font-medium text-gray-700 max-w-[150px] truncate' title={nodeName}>
|
||||
{nodeName}
|
||||
</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
</div>
|
||||
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
<Variable02 className='w-3.5 h-3.5' />
|
||||
<div className='ml-0.5 text-xs font-medium max-w-[150px] truncate' title={variable} >
|
||||
{!isChatVar && <Variable02 className='w-3.5 h-3.5' />}
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
<div className={cn('ml-0.5 text-xs font-medium max-w-[150px] truncate', isChatVar && 'text-text-secondary')} title={variable} >
|
||||
{variable}
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,7 +90,12 @@ const FormItem: FC<Props> = ({
|
||||
const isIterator = type === InputVarType.iterator
|
||||
return (
|
||||
<div className={`${className}`}>
|
||||
{!isArrayLikeType && <div className='h-8 leading-8 text-[13px] font-medium text-gray-700 truncate'>{typeof payload.label === 'object' ? nodeKey : payload.label}</div>}
|
||||
{!isArrayLikeType && (
|
||||
<div className='h-6 mb-1 flex items-center gap-1 text-text-secondary system-sm-semibold'>
|
||||
<div className='truncate'>{typeof payload.label === 'object' ? nodeKey : payload.label}</div>
|
||||
{!payload.required && <span className='text-text-tertiary system-xs-regular'>{t('workflow.panel.optional')}</span>}
|
||||
</div>
|
||||
)}
|
||||
<div className='grow'>
|
||||
{
|
||||
type === InputVarType.textInput && (
|
||||
|
||||
@@ -15,7 +15,7 @@ const CODE_EDITOR_LINE_HEIGHT = 18
|
||||
|
||||
export type Props = {
|
||||
value?: string | object
|
||||
placeholder?: string
|
||||
placeholder?: JSX.Element | string
|
||||
onChange?: (value: string) => void
|
||||
title?: JSX.Element
|
||||
language: CodeLanguage
|
||||
@@ -167,7 +167,7 @@ const CodeEditor: FC<Props> = ({
|
||||
}}
|
||||
onMount={handleEditorDidMount}
|
||||
/>
|
||||
{!outPutValue && <div className='pointer-events-none absolute left-[36px] top-0 leading-[18px] text-[13px] font-normal text-gray-300'>{placeholder}</div>}
|
||||
{!outPutValue && !isFocus && <div className='pointer-events-none absolute left-[36px] top-0 leading-[18px] text-[13px] font-normal text-gray-300'>{placeholder}</div>}
|
||||
</>
|
||||
)
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ type Props = {
|
||||
justVar?: boolean
|
||||
nodesOutputVars?: NodeOutPutVar[]
|
||||
availableNodes?: Node[]
|
||||
insertVarTipToLeft?: boolean
|
||||
}
|
||||
|
||||
const Editor: FC<Props> = ({
|
||||
@@ -40,6 +41,7 @@ const Editor: FC<Props> = ({
|
||||
readOnly,
|
||||
nodesOutputVars,
|
||||
availableNodes = [],
|
||||
insertVarTipToLeft,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -106,12 +108,12 @@ const Editor: FC<Props> = ({
|
||||
{/* to patch Editor not support dynamic change editable status */}
|
||||
{readOnly && <div className='absolute inset-0 z-10'></div>}
|
||||
{isFocus && (
|
||||
<div className='absolute z-10 top-[-9px] right-1'>
|
||||
<div className={cn('absolute z-10', insertVarTipToLeft ? 'top-1.5 left-[-12px]' : ' top-[-9px] right-1')}>
|
||||
<TooltipPlus
|
||||
popupContent={`${t('workflow.common.insertVarTip')}`}
|
||||
>
|
||||
<div className='p-0.5 rounded-[5px] shadow-lg cursor-pointer bg-white hover:bg-gray-100 border-[0.5px] border-black/5'>
|
||||
<Variable02 className='w-3.5 h-3.5 text-gray-500' />
|
||||
<Variable02 className='w-3.5 h-3.5 text-components-button-secondary-accent-text' />
|
||||
</div>
|
||||
</TooltipPlus>
|
||||
</div>
|
||||
|
||||
@@ -5,10 +5,10 @@ import cn from 'classnames'
|
||||
import { useWorkflow } from '../../../hooks'
|
||||
import { BlockEnum } from '../../../types'
|
||||
import { VarBlockIcon } from '../../../block-icon'
|
||||
import { getNodeInfoById, isENV, isSystemVar } from './variable/utils'
|
||||
import { getNodeInfoById, isConversationVar, isENV, isSystemVar } from './variable/utils'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
type Props = {
|
||||
nodeId: string
|
||||
value: string
|
||||
@@ -42,13 +42,14 @@ const ReadonlyInputWithSelectVar: FC<Props> = ({
|
||||
const value = vars[index].split('.')
|
||||
const isSystem = isSystemVar(value)
|
||||
const isEnv = isENV(value)
|
||||
const isChatVar = isConversationVar(value)
|
||||
const node = (isSystem ? startNode : getNodeInfoById(availableNodes, value[0]))?.data
|
||||
const varName = `${isSystem ? 'sys.' : ''}${value[value.length - 1]}`
|
||||
|
||||
return (<span key={index}>
|
||||
<span className='relative top-[-3px] leading-[16px]'>{str}</span>
|
||||
<div className=' inline-flex h-[16px] items-center px-1.5 rounded-[5px] bg-white'>
|
||||
{!isEnv && (
|
||||
{!isEnv && !isChatVar && (
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
@@ -61,9 +62,10 @@ const ReadonlyInputWithSelectVar: FC<Props> = ({
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
{!isEnv && <Variable02 className='shrink-0 w-3.5 h-3.5' />}
|
||||
{!isEnv && !isChatVar && <Variable02 className='shrink-0 w-3.5 h-3.5' />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div className={cn('max-w-[50px] ml-0.5 text-xs font-medium truncate', isEnv && 'text-gray-900')} title={varName}>{varName}</div>
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
<div className={cn('max-w-[50px] ml-0.5 text-xs font-medium truncate', (isEnv || isChatVar) && 'text-gray-900')} title={varName}>{varName}</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>)
|
||||
|
||||
@@ -10,6 +10,7 @@ type Item = {
|
||||
label: string
|
||||
}
|
||||
type Props = {
|
||||
className?: string
|
||||
trigger?: JSX.Element
|
||||
DropDownIcon?: any
|
||||
noLeft?: boolean
|
||||
@@ -27,6 +28,7 @@ type Props = {
|
||||
}
|
||||
|
||||
const TypeSelector: FC<Props> = ({
|
||||
className,
|
||||
trigger,
|
||||
DropDownIcon = ChevronSelectorVertical,
|
||||
noLeft,
|
||||
@@ -50,11 +52,12 @@ const TypeSelector: FC<Props> = ({
|
||||
setHide()
|
||||
}, ref)
|
||||
return (
|
||||
<div className={cn(!trigger && !noLeft && 'left-[-8px]', 'relative')} ref={ref}>
|
||||
<div className={cn(!trigger && !noLeft && 'left-[-8px]', 'relative select-none', className)} ref={ref}>
|
||||
{trigger
|
||||
? (
|
||||
<div
|
||||
onClick={toggleShow}
|
||||
className={cn(!readonly && 'cursor-pointer')}
|
||||
>
|
||||
{trigger}
|
||||
</div>
|
||||
@@ -63,13 +66,13 @@ const TypeSelector: FC<Props> = ({
|
||||
<div
|
||||
onClick={toggleShow}
|
||||
className={cn(showOption && 'bg-black/5', 'flex items-center h-5 pl-1 pr-0.5 rounded-md text-xs font-semibold text-gray-700 cursor-pointer hover:bg-black/5')}>
|
||||
<div className={cn(triggerClassName, 'text-xs font-semibold', uppercase && 'uppercase', noValue && 'text-gray-400')}>{!noValue ? item?.label : placeholder}</div>
|
||||
<div className={cn('text-sm font-semibold', uppercase && 'uppercase', noValue && 'text-gray-400', triggerClassName)}>{!noValue ? item?.label : placeholder}</div>
|
||||
{!readonly && <DropDownIcon className='w-3 h-3 ' />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(showOption && !readonly) && (
|
||||
<div className={cn(popupClassName, 'absolute z-10 top-[24px] w-[120px] p-1 border border-gray-200 shadow-lg rounded-lg bg-white')}>
|
||||
<div className={cn('absolute z-10 top-[24px] w-[120px] p-1 border border-gray-200 shadow-lg rounded-lg bg-white select-none', popupClassName)}>
|
||||
{list.map(item => (
|
||||
<div
|
||||
key={item.value}
|
||||
|
||||
@@ -10,8 +10,8 @@ import type {
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type VariableTagProps = {
|
||||
@@ -30,12 +30,13 @@ const VariableTag = ({
|
||||
return nodes.find(node => node.id === valueSelector[0])
|
||||
}, [nodes, valueSelector])
|
||||
const isEnv = isENV(valueSelector)
|
||||
const isChatVar = isConversationVar(valueSelector)
|
||||
|
||||
const variableName = isSystemVar(valueSelector) ? valueSelector.slice(0).join('.') : valueSelector.slice(1).join('.')
|
||||
|
||||
return (
|
||||
<div className='inline-flex items-center px-1.5 max-w-full h-6 text-xs rounded-md border-[0.5px] border-[rgba(16, 2440,0.08)] bg-white shadow-xs'>
|
||||
{!isEnv && (
|
||||
{!isEnv && !isChatVar && (
|
||||
<>
|
||||
{node && (
|
||||
<VarBlockIcon
|
||||
@@ -54,8 +55,9 @@ const VariableTag = ({
|
||||
</>
|
||||
)}
|
||||
{isEnv && <Env className='shrink-0 mr-0.5 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
<div
|
||||
className={cn('truncate text-text-accent font-medium', isEnv && 'text-text-secondary')}
|
||||
className={cn('truncate text-text-accent font-medium', (isEnv || isChatVar) && 'text-text-secondary')}
|
||||
title={variableName}
|
||||
>
|
||||
{variableName}
|
||||
|
||||
@@ -9,14 +9,14 @@ import type { Var } from '@/app/components/workflow/types'
|
||||
import { SimpleSelect } from '@/app/components/base/select'
|
||||
|
||||
type Props = {
|
||||
schema: CredentialFormSchema
|
||||
schema: Partial<CredentialFormSchema>
|
||||
readonly: boolean
|
||||
value: string
|
||||
onChange: (value: string | number, varKindType: VarKindType, varInfo?: Var) => void
|
||||
}
|
||||
|
||||
const ConstantField: FC<Props> = ({
|
||||
schema,
|
||||
schema = {} as CredentialFormSchema,
|
||||
readonly,
|
||||
value,
|
||||
onChange,
|
||||
@@ -47,7 +47,7 @@ const ConstantField: FC<Props> = ({
|
||||
{schema.type === FormTypeEnum.textNumber && (
|
||||
<input
|
||||
type='number'
|
||||
className='w-full h-8 leading-8 pl-0.5 bg-transparent text-[13px] font-normal text-gray-900 placeholder:text-gray-400 focus:outline-none overflow-hidden'
|
||||
className='w-full h-8 leading-8 p-2 rounded-lg bg-gray-100 text-[13px] font-normal text-gray-900 placeholder:text-gray-400 focus:outline-none overflow-hidden'
|
||||
value={value}
|
||||
onChange={handleStaticChange}
|
||||
readOnly={readonly}
|
||||
|
||||
@@ -15,7 +15,7 @@ import type { ParameterExtractorNodeType } from '../../../parameter-extractor/ty
|
||||
import type { IterationNodeType } from '../../../iteration/types'
|
||||
import { BlockEnum, InputVarType, VarType } from '@/app/components/workflow/types'
|
||||
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
|
||||
import type { EnvironmentVariable, Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { ConversationVariable, EnvironmentVariable, Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { VariableAssignerNodeType } from '@/app/components/workflow/nodes/variable-assigner/types'
|
||||
import {
|
||||
HTTP_REQUEST_OUTPUT_STRUCT,
|
||||
@@ -38,6 +38,10 @@ export const isENV = (valueSelector: ValueSelector) => {
|
||||
return valueSelector[0] === 'env'
|
||||
}
|
||||
|
||||
export const isConversationVar = (valueSelector: ValueSelector) => {
|
||||
return valueSelector[0] === 'conversation'
|
||||
}
|
||||
|
||||
const inputVarTypeToVarType = (type: InputVarType): VarType => {
|
||||
if (type === InputVarType.number)
|
||||
return VarType.number
|
||||
@@ -244,13 +248,32 @@ const formatItem = (
|
||||
}) as Var[]
|
||||
break
|
||||
}
|
||||
|
||||
case 'conversation': {
|
||||
res.vars = data.chatVarList.map((chatVar: ConversationVariable) => {
|
||||
return {
|
||||
variable: `conversation.${chatVar.name}`,
|
||||
type: chatVar.value_type,
|
||||
des: chatVar.description,
|
||||
}
|
||||
}) as Var[]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const selector = [id]
|
||||
res.vars = res.vars.filter((v) => {
|
||||
const { children } = v
|
||||
if (!children)
|
||||
return filterVar(v, selector)
|
||||
if (!children) {
|
||||
return filterVar(v, (() => {
|
||||
const variableArr = v.variable.split('.')
|
||||
const [first, ..._other] = variableArr
|
||||
if (first === 'sys' || first === 'env' || first === 'conversation')
|
||||
return variableArr
|
||||
|
||||
return [...selector, ...variableArr]
|
||||
})())
|
||||
}
|
||||
|
||||
const obj = findExceptVarInObject(v, filterVar, selector)
|
||||
return obj?.children && obj?.children.length > 0
|
||||
@@ -269,6 +292,7 @@ export const toNodeOutputVars = (
|
||||
isChatMode: boolean,
|
||||
filterVar = (_payload: Var, _selector: ValueSelector) => true,
|
||||
environmentVariables: EnvironmentVariable[] = [],
|
||||
conversationVariables: ConversationVariable[] = [],
|
||||
): NodeOutPutVar[] => {
|
||||
// ENV_NODE data format
|
||||
const ENV_NODE = {
|
||||
@@ -279,9 +303,19 @@ export const toNodeOutputVars = (
|
||||
envList: environmentVariables,
|
||||
},
|
||||
}
|
||||
// CHAT_VAR_NODE data format
|
||||
const CHAT_VAR_NODE = {
|
||||
id: 'conversation',
|
||||
data: {
|
||||
title: 'CONVERSATION',
|
||||
type: 'conversation',
|
||||
chatVarList: conversationVariables,
|
||||
},
|
||||
}
|
||||
const res = [
|
||||
...nodes.filter(node => SUPPORT_OUTPUT_VARS_NODE.includes(node.data.type)),
|
||||
...(environmentVariables.length > 0 ? [ENV_NODE] : []),
|
||||
...((isChatMode && conversationVariables.length) > 0 ? [CHAT_VAR_NODE] : []),
|
||||
].map((node) => {
|
||||
return {
|
||||
...formatItem(node, isChatMode, filterVar),
|
||||
@@ -346,6 +380,7 @@ export const getVarType = ({
|
||||
isChatMode,
|
||||
isConstant,
|
||||
environmentVariables = [],
|
||||
conversationVariables = [],
|
||||
}:
|
||||
{
|
||||
valueSelector: ValueSelector
|
||||
@@ -355,6 +390,7 @@ export const getVarType = ({
|
||||
isChatMode: boolean
|
||||
isConstant?: boolean
|
||||
environmentVariables?: EnvironmentVariable[]
|
||||
conversationVariables?: ConversationVariable[]
|
||||
}): VarType => {
|
||||
if (isConstant)
|
||||
return VarType.string
|
||||
@@ -364,6 +400,7 @@ export const getVarType = ({
|
||||
isChatMode,
|
||||
undefined,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
)
|
||||
|
||||
const isIterationInnerVar = parentNode?.data.type === BlockEnum.Iteration
|
||||
@@ -386,6 +423,7 @@ export const getVarType = ({
|
||||
}
|
||||
const isSystem = isSystemVar(valueSelector)
|
||||
const isEnv = isENV(valueSelector)
|
||||
const isChatVar = isConversationVar(valueSelector)
|
||||
const startNode = availableNodes.find((node: any) => {
|
||||
return node.data.type === BlockEnum.Start
|
||||
})
|
||||
@@ -398,7 +436,7 @@ export const getVarType = ({
|
||||
|
||||
let type: VarType = VarType.string
|
||||
let curr: any = targetVar.vars
|
||||
if (isSystem || isEnv) {
|
||||
if (isSystem || isEnv || isChatVar) {
|
||||
return curr.find((v: any) => v.variable === (valueSelector as ValueSelector).join('.'))?.type
|
||||
}
|
||||
else {
|
||||
@@ -424,6 +462,7 @@ export const toNodeAvailableVars = ({
|
||||
beforeNodes,
|
||||
isChatMode,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
filterVar,
|
||||
}: {
|
||||
parentNode?: Node | null
|
||||
@@ -433,6 +472,8 @@ export const toNodeAvailableVars = ({
|
||||
isChatMode: boolean
|
||||
// env
|
||||
environmentVariables?: EnvironmentVariable[]
|
||||
// chat var
|
||||
conversationVariables?: ConversationVariable[]
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean
|
||||
}): NodeOutPutVar[] => {
|
||||
const beforeNodesOutputVars = toNodeOutputVars(
|
||||
@@ -440,6 +481,7 @@ export const toNodeAvailableVars = ({
|
||||
isChatMode,
|
||||
filterVar,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
)
|
||||
const isInIteration = parentNode?.data.type === BlockEnum.Iteration
|
||||
if (isInIteration) {
|
||||
@@ -451,6 +493,7 @@ export const toNodeAvailableVars = ({
|
||||
availableNodes: beforeNodes,
|
||||
isChatMode,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
})
|
||||
const iterationVar = {
|
||||
nodeId: iterationNode?.id,
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import produce from 'immer'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import VarReferencePopup from './var-reference-popup'
|
||||
import { getNodeInfoById, isENV, isSystemVar } from './utils'
|
||||
import { getNodeInfoById, isConversationVar, isENV, isSystemVar } from './utils'
|
||||
import ConstantField from './constant-field'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
@@ -17,7 +17,7 @@ import type { CredentialFormSchema } from '@/app/components/header/account-setti
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
|
||||
import AddButton from '@/app/components/base/button/add-button'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
const TRIGGER_DEFAULT_WIDTH = 227
|
||||
|
||||
type Props = {
|
||||
@@ -49,7 +50,8 @@ type Props = {
|
||||
availableNodes?: Node[]
|
||||
availableVars?: NodeOutPutVar[]
|
||||
isAddBtnTrigger?: boolean
|
||||
schema?: CredentialFormSchema
|
||||
schema?: Partial<CredentialFormSchema>
|
||||
valueTypePlaceHolder?: string
|
||||
}
|
||||
|
||||
const VarReferencePicker: FC<Props> = ({
|
||||
@@ -57,7 +59,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
readonly,
|
||||
className,
|
||||
isShowNodeName,
|
||||
value,
|
||||
value = [],
|
||||
onOpen = () => { },
|
||||
onChange,
|
||||
isSupportConstantValue,
|
||||
@@ -68,6 +70,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
availableVars,
|
||||
isAddBtnTrigger,
|
||||
schema,
|
||||
valueTypePlaceHolder,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const store = useStoreApi()
|
||||
@@ -99,7 +102,6 @@ const VarReferencePicker: FC<Props> = ({
|
||||
|
||||
const [varKindType, setVarKindType] = useState<VarKindType>(defaultVarKindType)
|
||||
const isConstant = isSupportConstantValue && varKindType === VarKindType.constant
|
||||
|
||||
const outputVars = useMemo(() => {
|
||||
if (availableVars)
|
||||
return availableVars
|
||||
@@ -215,6 +217,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
})
|
||||
|
||||
const isEnv = isENV(value as ValueSelector)
|
||||
const isChatVar = isConversationVar(value as ValueSelector)
|
||||
|
||||
// 8(left/right-padding) + 14(icon) + 4 + 14 + 2 = 42 + 17 buff
|
||||
const availableWidth = triggerWidth - 56
|
||||
@@ -227,6 +230,8 @@ const VarReferencePicker: FC<Props> = ({
|
||||
return [maxNodeNameWidth, maxVarNameWidth, maxTypeWidth]
|
||||
})()
|
||||
|
||||
const WrapElem = isSupportConstantValue ? 'div' : PortalToFollowElemTrigger
|
||||
const VarPickerWrap = !isSupportConstantValue ? 'div' : PortalToFollowElemTrigger
|
||||
return (
|
||||
<div className={cn(className, !readonly && 'cursor-pointer')}>
|
||||
<PortalToFollowElem
|
||||
@@ -234,7 +239,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
onOpenChange={setOpen}
|
||||
placement={isAddBtnTrigger ? 'bottom-end' : 'bottom-start'}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => {
|
||||
<WrapElem onClick={() => {
|
||||
if (readonly)
|
||||
return
|
||||
!isConstant ? setOpen(!open) : setControlFocus(Date.now())
|
||||
@@ -245,23 +250,28 @@ const VarReferencePicker: FC<Props> = ({
|
||||
<AddButton onClick={() => { }}></AddButton>
|
||||
</div>
|
||||
)
|
||||
: (<div ref={triggerRef} className={cn((open || isFocus) ? 'border-gray-300' : 'border-gray-100', 'relative group/wrap flex items-center w-full h-8 p-1 rounded-lg bg-gray-100 border')}>
|
||||
: (<div ref={!isSupportConstantValue ? triggerRef : null} className={cn((open || isFocus) ? 'border-gray-300' : 'border-gray-100', 'relative group/wrap flex items-center w-full h-8', !isSupportConstantValue && 'p-1 rounded-lg bg-gray-100 border')}>
|
||||
{isSupportConstantValue
|
||||
? <div onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setOpen(false)
|
||||
setControlFocus(Date.now())
|
||||
}} className='mr-1 flex items-center space-x-1'>
|
||||
}} className='h-full mr-1 flex items-center space-x-1'>
|
||||
<TypeSelector
|
||||
noLeft
|
||||
triggerClassName='!text-xs'
|
||||
trigger={
|
||||
<div className='flex items-center h-8 px-2 radius-md bg-components-input-bg-normal'>
|
||||
<div className='mr-1 system-sm-regular text-components-input-text-filled'>{varKindTypes.find(item => item.value === varKindType)?.label}</div>
|
||||
<RiArrowDownSLine className='w-4 h-4 text-text-quaternary' />
|
||||
</div>
|
||||
}
|
||||
popupClassName='top-8'
|
||||
readonly={readonly}
|
||||
DropDownIcon={RiArrowDownSLine}
|
||||
value={varKindType}
|
||||
options={varKindTypes}
|
||||
onChange={handleVarKindTypeChange}
|
||||
showChecked
|
||||
/>
|
||||
<div className='h-4 w-px bg-black/5'></div>
|
||||
</div>
|
||||
: (!hasValue && <div className='ml-1.5 mr-1'>
|
||||
<Variable02 className='w-3.5 h-3.5 text-gray-400' />
|
||||
@@ -276,38 +286,51 @@ const VarReferencePicker: FC<Props> = ({
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div className={cn('inline-flex h-full items-center px-1.5 rounded-[5px]', hasValue && 'bg-white')}>
|
||||
{hasValue
|
||||
? (
|
||||
<>
|
||||
{isShowNodeName && !isEnv && (
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-900'
|
||||
type={outputVarNode?.type || BlockEnum.Start}
|
||||
/>
|
||||
<VarPickerWrap
|
||||
onClick={() => {
|
||||
if (readonly)
|
||||
return
|
||||
!isConstant ? setOpen(!open) : setControlFocus(Date.now())
|
||||
}}
|
||||
className='grow h-full'
|
||||
>
|
||||
<div ref={isSupportConstantValue ? triggerRef : null} className={cn('h-full', isSupportConstantValue && 'flex items-center pl-1 py-1 rounded-lg bg-gray-100')}>
|
||||
<div className={cn('h-full items-center px-1.5 rounded-[5px]', hasValue ? 'bg-white inline-flex' : 'flex')}>
|
||||
{hasValue
|
||||
? (
|
||||
<>
|
||||
{isShowNodeName && !isEnv && !isChatVar && (
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-900'
|
||||
type={outputVarNode?.type || BlockEnum.Start}
|
||||
/>
|
||||
</div>
|
||||
<div className='mx-0.5 text-xs font-medium text-gray-700 truncate' title={outputVarNode?.title} style={{
|
||||
maxWidth: maxNodeNameWidth,
|
||||
}}>{outputVarNode?.title}</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
{!hasValue && <Variable02 className='w-3.5 h-3.5' />}
|
||||
{isEnv && <Env className='w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
<div className={cn('ml-0.5 text-xs font-medium truncate', (isEnv || isChatVar) && '!text-text-secondary')} title={varName} style={{
|
||||
maxWidth: maxVarNameWidth,
|
||||
}}>{varName}</div>
|
||||
</div>
|
||||
<div className='mx-0.5 text-xs font-medium text-gray-700 truncate' title={outputVarNode?.title} style={{
|
||||
maxWidth: maxNodeNameWidth,
|
||||
}}>{outputVarNode?.title}</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
{!hasValue && <Variable02 className='w-3.5 h-3.5' />}
|
||||
{isEnv && <Env className='w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div className={cn('ml-0.5 text-xs font-medium truncate', isEnv && '!text-gray-900')} title={varName} style={{
|
||||
maxWidth: maxVarNameWidth,
|
||||
}}>{varName}</div>
|
||||
</div>
|
||||
<div className='ml-0.5 text-xs font-normal text-gray-500 capitalize truncate' title={type} style={{
|
||||
maxWidth: maxTypeWidth,
|
||||
}}>{type}</div>
|
||||
</>
|
||||
)
|
||||
: <div className='text-[13px] font-normal text-gray-400'>{t('workflow.common.setVarValuePlaceholder')}</div>}
|
||||
</div>
|
||||
<div className='ml-0.5 text-xs font-normal text-gray-500 capitalize truncate' title={type} style={{
|
||||
maxWidth: maxTypeWidth,
|
||||
}}>{type}</div>
|
||||
</>
|
||||
)
|
||||
: <div className='text-[13px] font-normal text-gray-400'>{t('workflow.common.setVarValuePlaceholder')}</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</VarPickerWrap>
|
||||
)}
|
||||
{(hasValue && !readonly) && (<div
|
||||
className='invisible group-hover/wrap:visible absolute h-5 right-1 top-[50%] translate-y-[-50%] group p-1 rounded-md hover:bg-black/5 cursor-pointer'
|
||||
@@ -315,8 +338,15 @@ const VarReferencePicker: FC<Props> = ({
|
||||
>
|
||||
<RiCloseLine className='w-3.5 h-3.5 text-gray-500 group-hover:text-gray-800' />
|
||||
</div>)}
|
||||
{!hasValue && valueTypePlaceHolder && (
|
||||
<Badge
|
||||
className=' absolute right-1 top-[50%] translate-y-[-50%] capitalize'
|
||||
text={valueTypePlaceHolder}
|
||||
uppercase={false}
|
||||
/>
|
||||
)}
|
||||
</div>)}
|
||||
</PortalToFollowElemTrigger>
|
||||
</WrapElem>
|
||||
<PortalToFollowElemContent style={{
|
||||
zIndex: 100,
|
||||
}}>
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { checkKeys } from '@/utils/var'
|
||||
|
||||
type ObjectChildrenProps = {
|
||||
@@ -51,6 +51,7 @@ const Item: FC<ItemProps> = ({
|
||||
const isObj = itemData.type === VarType.object && itemData.children && itemData.children.length > 0
|
||||
const isSys = itemData.variable.startsWith('sys.')
|
||||
const isEnv = itemData.variable.startsWith('env.')
|
||||
const isChatVar = itemData.variable.startsWith('conversation.')
|
||||
const itemRef = useRef(null)
|
||||
const [isItemHovering, setIsItemHovering] = useState(false)
|
||||
const _ = useHover(itemRef, {
|
||||
@@ -79,7 +80,7 @@ const Item: FC<ItemProps> = ({
|
||||
}, [isHovering])
|
||||
const handleChosen = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
if (isSys || isEnv) { // system variable or environment variable
|
||||
if (isSys || isEnv || isChatVar) { // system variable | environment variable | conversation variable
|
||||
onChange([...objPath, ...itemData.variable.split('.')], itemData)
|
||||
}
|
||||
else {
|
||||
@@ -100,13 +101,21 @@ const Item: FC<ItemProps> = ({
|
||||
isHovering && (isObj ? 'bg-primary-50' : 'bg-gray-50'),
|
||||
'relative w-full flex items-center h-6 pl-3 rounded-md cursor-pointer')
|
||||
}
|
||||
// style={{ width: itemWidth || 252 }}
|
||||
onClick={handleChosen}
|
||||
>
|
||||
<div className='flex items-center w-0 grow'>
|
||||
{!isEnv && <Variable02 className='shrink-0 w-3.5 h-3.5 text-primary-500' />}
|
||||
{!isEnv && !isChatVar && <Variable02 className='shrink-0 w-3.5 h-3.5 text-primary-500' />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div title={itemData.variable} className='ml-1 w-0 grow truncate text-[13px] font-normal text-gray-900'>{!isEnv ? itemData.variable : itemData.variable.replace('env.', '')}</div>
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
{!isEnv && !isChatVar && (
|
||||
<div title={itemData.variable} className='ml-1 w-0 grow truncate text-[13px] font-normal text-gray-900'>{itemData.variable}</div>
|
||||
)}
|
||||
{isEnv && (
|
||||
<div title={itemData.variable} className='ml-1 w-0 grow truncate text-[13px] font-normal text-gray-900'>{itemData.variable.replace('env.', '')}</div>
|
||||
)}
|
||||
{isChatVar && (
|
||||
<div title={itemData.des} className='ml-1 w-0 grow truncate text-[13px] font-normal text-gray-900'>{itemData.variable.replace('conversation.', '')}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='ml-1 shrink-0 text-xs font-normal text-gray-500 capitalize'>{itemData.type}</div>
|
||||
{isObj && (
|
||||
@@ -211,7 +220,7 @@ const VarReferenceVars: FC<Props> = ({
|
||||
const [searchText, setSearchText] = useState('')
|
||||
|
||||
const filteredVars = vars.filter((v) => {
|
||||
const children = v.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.') || v.variable.startsWith('env.'))
|
||||
const children = v.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.') || v.variable.startsWith('env.') || v.variable.startsWith('conversation.'))
|
||||
return children.length > 0
|
||||
}).filter((node) => {
|
||||
if (!searchText)
|
||||
@@ -222,7 +231,7 @@ const VarReferenceVars: FC<Props> = ({
|
||||
})
|
||||
return children.length > 0
|
||||
}).map((node) => {
|
||||
let vars = node.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.') || v.variable.startsWith('env.'))
|
||||
let vars = node.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.') || v.variable.startsWith('env.') || v.variable.startsWith('conversation.'))
|
||||
if (searchText) {
|
||||
const searchTextLower = searchText.toLowerCase()
|
||||
if (!node.title.toLowerCase().includes(searchTextLower))
|
||||
|
||||
@@ -7,12 +7,12 @@ import {
|
||||
useNodeDataUpdate,
|
||||
useWorkflow,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import { getNodeInfoById, isENV, isSystemVar, toNodeOutputVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { getNodeInfoById, isConversationVar, isENV, isSystemVar, toNodeOutputVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
|
||||
import type { CommonNodeType, InputVar, ValueSelector, Var, Variable } from '@/app/components/workflow/types'
|
||||
import { BlockEnum, InputVarType, NodeRunningStatus, VarType } from '@/app/components/workflow/types'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { getIterationSingleNodeRunUrl, singleNodeRun } from '@/service/workflow'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import LLMDefault from '@/app/components/workflow/nodes/llm/default'
|
||||
@@ -95,12 +95,13 @@ const useOneStepRun = <T>({
|
||||
}: Params<T>) => {
|
||||
const { t } = useTranslation()
|
||||
const { getBeforeNodesInSameBranch, getBeforeNodesInSameBranchIncludeParent } = useWorkflow() as any
|
||||
const conversationVariables = useStore(s => s.conversationVariables)
|
||||
const isChatMode = useIsChatMode()
|
||||
const isIteration = data.type === BlockEnum.Iteration
|
||||
|
||||
const availableNodes = getBeforeNodesInSameBranch(id)
|
||||
const availableNodesIncludeParent = getBeforeNodesInSameBranchIncludeParent(id)
|
||||
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode)
|
||||
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode, undefined, undefined, conversationVariables)
|
||||
const getVar = (valueSelector: ValueSelector): Var | undefined => {
|
||||
let res: Var | undefined
|
||||
const isSystem = valueSelector[0] === 'sys'
|
||||
@@ -116,7 +117,8 @@ const useOneStepRun = <T>({
|
||||
|
||||
valueSelector.slice(1).forEach((key, i) => {
|
||||
const isLast = i === valueSelector.length - 2
|
||||
curr = curr?.find((v: any) => v.variable === key)
|
||||
// conversation variable is start with 'conversation.'
|
||||
curr = curr?.find((v: any) => v.variable.replace('conversation.', '') === key)
|
||||
if (isLast) {
|
||||
res = curr
|
||||
}
|
||||
@@ -369,6 +371,7 @@ const useOneStepRun = <T>({
|
||||
nodeType: varInfo?.type,
|
||||
nodeName: varInfo?.title || availableNodesIncludeParent[0]?.data.title, // default start node title
|
||||
variable: isSystemVar(item) ? item.join('.') : item[item.length - 1],
|
||||
isChatVar: isConversationVar(item),
|
||||
},
|
||||
variable: `#${item.join('.')}#`,
|
||||
value_selector: item,
|
||||
|
||||
46
web/app/components/workflow/nodes/assigner/default.ts
Normal file
46
web/app/components/workflow/nodes/assigner/default.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { BlockEnum } from '../../types'
|
||||
import type { NodeDefault } from '../../types'
|
||||
import { type AssignerNodeType, WriteMode } from './types'
|
||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
|
||||
const i18nPrefix = 'workflow.errorMsg'
|
||||
|
||||
const nodeDefault: NodeDefault<AssignerNodeType> = {
|
||||
defaultValue: {
|
||||
assigned_variable_selector: [],
|
||||
write_mode: WriteMode.Overwrite,
|
||||
input_variable_selector: [],
|
||||
},
|
||||
getAvailablePrevNodes(isChatMode: boolean) {
|
||||
const nodes = isChatMode
|
||||
? ALL_CHAT_AVAILABLE_BLOCKS
|
||||
: ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End)
|
||||
return nodes
|
||||
},
|
||||
getAvailableNextNodes(isChatMode: boolean) {
|
||||
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
||||
return nodes
|
||||
},
|
||||
checkValid(payload: AssignerNodeType, t: any) {
|
||||
let errorMessages = ''
|
||||
const {
|
||||
assigned_variable_selector: assignedVarSelector,
|
||||
write_mode: writeMode,
|
||||
input_variable_selector: toAssignerVarSelector,
|
||||
} = payload
|
||||
|
||||
if (!errorMessages && !assignedVarSelector?.length)
|
||||
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.assigner.assignedVariable') })
|
||||
|
||||
if (!errorMessages && writeMode !== WriteMode.Clear) {
|
||||
if (!toAssignerVarSelector?.length)
|
||||
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.assigner.variable') })
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: !errorMessages,
|
||||
errorMessage: errorMessages,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default nodeDefault
|
||||
47
web/app/components/workflow/nodes/assigner/node.tsx
Normal file
47
web/app/components/workflow/nodes/assigner/node.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useNodes } from 'reactflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import NodeVariableItem from '../variable-assigner/components/node-variable-item'
|
||||
import { type AssignerNodeType } from './types'
|
||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { BlockEnum, type Node, type NodeProps } from '@/app/components/workflow/types'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.assigner'
|
||||
|
||||
const NodeComponent: FC<NodeProps<AssignerNodeType>> = ({
|
||||
data,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const nodes: Node[] = useNodes()
|
||||
const { assigned_variable_selector: variable, write_mode: writeMode } = data
|
||||
|
||||
if (!variable || variable.length === 0)
|
||||
return null
|
||||
|
||||
const isSystem = isSystemVar(variable)
|
||||
const isEnv = isENV(variable)
|
||||
const isChatVar = isConversationVar(variable)
|
||||
|
||||
const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0])
|
||||
const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.')
|
||||
return (
|
||||
<div className='relative px-3'>
|
||||
<div className='mb-1 system-2xs-medium-uppercase text-text-tertiary'>{t(`${i18nPrefix}.assignedVariable`)}</div>
|
||||
<NodeVariableItem
|
||||
node={node as Node}
|
||||
isEnv={isEnv}
|
||||
isChatVar={isChatVar}
|
||||
varName={varName}
|
||||
className='bg-workflow-block-parma-bg'
|
||||
/>
|
||||
<div className='my-2 flex justify-between items-center h-[22px] px-[5px] bg-workflow-block-parma-bg radius-sm'>
|
||||
<div className='system-xs-medium-uppercase text-text-tertiary'>{t(`${i18nPrefix}.writeMode`)}</div>
|
||||
<div className='system-xs-medium text-text-secondary'>{t(`${i18nPrefix}.${writeMode}`)}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(NodeComponent)
|
||||
85
web/app/components/workflow/nodes/assigner/panel.tsx
Normal file
85
web/app/components/workflow/nodes/assigner/panel.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
|
||||
import OptionCard from '../_base/components/option-card'
|
||||
import useConfig from './use-config'
|
||||
import { WriteMode } from './types'
|
||||
import type { AssignerNodeType } from './types'
|
||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import { type NodePanelProps } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.assigner'
|
||||
|
||||
const Panel: FC<NodePanelProps<AssignerNodeType>> = ({
|
||||
id,
|
||||
data,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const {
|
||||
readOnly,
|
||||
inputs,
|
||||
handleAssignedVarChanges,
|
||||
isSupportAppend,
|
||||
writeModeTypes,
|
||||
handleWriteModeChange,
|
||||
filterAssignedVar,
|
||||
filterToAssignedVar,
|
||||
handleToAssignedVarChange,
|
||||
toAssignedVarType,
|
||||
} = useConfig(id, data)
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='px-4 pb-4 space-y-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.assignedVariable`)}
|
||||
>
|
||||
<VarReferencePicker
|
||||
readonly={readOnly}
|
||||
nodeId={id}
|
||||
isShowNodeName
|
||||
value={inputs.assigned_variable_selector || []}
|
||||
onChange={handleAssignedVarChanges}
|
||||
filterVar={filterAssignedVar}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.writeMode`)}
|
||||
>
|
||||
<div className={cn('grid gap-2', isSupportAppend ? 'grid-cols-3' : 'grid-cols-2')}>
|
||||
{writeModeTypes.map(type => (
|
||||
<OptionCard
|
||||
key={type}
|
||||
title={t(`${i18nPrefix}.${type}`)}
|
||||
onSelect={handleWriteModeChange(type)}
|
||||
selected={inputs.write_mode === type}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Field>
|
||||
{inputs.write_mode !== WriteMode.Clear && (
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.setVariable`)}
|
||||
>
|
||||
<VarReferencePicker
|
||||
readonly={readOnly}
|
||||
nodeId={id}
|
||||
isShowNodeName
|
||||
value={inputs.input_variable_selector || []}
|
||||
onChange={handleToAssignedVarChange}
|
||||
filterVar={filterToAssignedVar}
|
||||
valueTypePlaceHolder={toAssignedVarType}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Panel)
|
||||
13
web/app/components/workflow/nodes/assigner/types.ts
Normal file
13
web/app/components/workflow/nodes/assigner/types.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { CommonNodeType, ValueSelector } from '@/app/components/workflow/types'
|
||||
|
||||
export enum WriteMode {
|
||||
Overwrite = 'over-write',
|
||||
Append = 'append',
|
||||
Clear = 'clear',
|
||||
}
|
||||
|
||||
export type AssignerNodeType = CommonNodeType & {
|
||||
assigned_variable_selector: ValueSelector
|
||||
write_mode: WriteMode
|
||||
input_variable_selector: ValueSelector
|
||||
}
|
||||
150
web/app/components/workflow/nodes/assigner/use-config.ts
Normal file
150
web/app/components/workflow/nodes/assigner/use-config.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import produce from 'immer'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import { isEqual } from 'lodash-es'
|
||||
import { VarType } from '../../types'
|
||||
import type { ValueSelector, Var } from '../../types'
|
||||
import { type AssignerNodeType, WriteMode } from './types'
|
||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodesReadOnly,
|
||||
useWorkflow,
|
||||
useWorkflowVariables,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
|
||||
const useConfig = (id: string, payload: AssignerNodeType) => {
|
||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const store = useStoreApi()
|
||||
const { getBeforeNodesInSameBranch } = useWorkflow()
|
||||
|
||||
const {
|
||||
getNodes,
|
||||
} = store.getState()
|
||||
const currentNode = getNodes().find(n => n.id === id)
|
||||
const isInIteration = payload.isInIteration
|
||||
const iterationNode = isInIteration ? getNodes().find(n => n.id === currentNode!.parentId) : null
|
||||
const availableNodes = useMemo(() => {
|
||||
return getBeforeNodesInSameBranch(id)
|
||||
}, [getBeforeNodesInSameBranch, id])
|
||||
const { inputs, setInputs } = useNodeCrud<AssignerNodeType>(id, payload)
|
||||
|
||||
const { getCurrentVariableType } = useWorkflowVariables()
|
||||
const assignedVarType = getCurrentVariableType({
|
||||
parentNode: iterationNode,
|
||||
valueSelector: inputs.assigned_variable_selector || [],
|
||||
availableNodes,
|
||||
isChatMode,
|
||||
isConstant: false,
|
||||
})
|
||||
|
||||
const isSupportAppend = useCallback((varType: VarType) => {
|
||||
return [VarType.arrayString, VarType.arrayNumber, VarType.arrayObject].includes(varType)
|
||||
}, [])
|
||||
|
||||
const isCurrSupportAppend = useMemo(() => isSupportAppend(assignedVarType), [assignedVarType, isSupportAppend])
|
||||
|
||||
const handleAssignedVarChanges = useCallback((variable: ValueSelector | string) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.assigned_variable_selector = variable as ValueSelector
|
||||
draft.input_variable_selector = []
|
||||
|
||||
const newVarType = getCurrentVariableType({
|
||||
parentNode: iterationNode,
|
||||
valueSelector: draft.assigned_variable_selector || [],
|
||||
availableNodes,
|
||||
isChatMode,
|
||||
isConstant: false,
|
||||
})
|
||||
|
||||
if (inputs.write_mode === WriteMode.Append && !isSupportAppend(newVarType))
|
||||
draft.write_mode = WriteMode.Overwrite
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs, getCurrentVariableType, iterationNode, availableNodes, isChatMode, isSupportAppend])
|
||||
|
||||
const writeModeTypes = useMemo(() => {
|
||||
const types = [WriteMode.Overwrite, WriteMode.Append, WriteMode.Clear]
|
||||
if (!isSupportAppend(assignedVarType))
|
||||
return types.filter(t => t !== WriteMode.Append)
|
||||
|
||||
return types
|
||||
}, [assignedVarType, isSupportAppend])
|
||||
|
||||
const handleWriteModeChange = useCallback((writeMode: WriteMode) => {
|
||||
return () => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.write_mode = writeMode
|
||||
if (inputs.write_mode === WriteMode.Clear)
|
||||
draft.input_variable_selector = []
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const toAssignedVarType = useMemo(() => {
|
||||
const { write_mode } = inputs
|
||||
if (write_mode === WriteMode.Overwrite)
|
||||
return assignedVarType
|
||||
if (write_mode === WriteMode.Append) {
|
||||
if (assignedVarType === VarType.arrayString)
|
||||
return VarType.string
|
||||
if (assignedVarType === VarType.arrayNumber)
|
||||
return VarType.number
|
||||
if (assignedVarType === VarType.arrayObject)
|
||||
return VarType.object
|
||||
}
|
||||
return VarType.string
|
||||
}, [assignedVarType, inputs])
|
||||
|
||||
const filterAssignedVar = useCallback((varPayload: Var, selector: ValueSelector) => {
|
||||
return selector.join('.').startsWith('conversation')
|
||||
}, [])
|
||||
|
||||
const filterToAssignedVar = useCallback((varPayload: Var, selector: ValueSelector) => {
|
||||
if (isEqual(selector, inputs.assigned_variable_selector))
|
||||
return false
|
||||
|
||||
if (inputs.write_mode === WriteMode.Overwrite) {
|
||||
return varPayload.type === assignedVarType
|
||||
}
|
||||
else if (inputs.write_mode === WriteMode.Append) {
|
||||
switch (assignedVarType) {
|
||||
case VarType.arrayString:
|
||||
return varPayload.type === VarType.string
|
||||
case VarType.arrayNumber:
|
||||
return varPayload.type === VarType.number
|
||||
case VarType.arrayObject:
|
||||
return varPayload.type === VarType.object
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}, [inputs.assigned_variable_selector, inputs.write_mode, assignedVarType])
|
||||
|
||||
const handleToAssignedVarChange = useCallback((value: ValueSelector | string) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.input_variable_selector = value as ValueSelector
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
return {
|
||||
readOnly,
|
||||
inputs,
|
||||
handleAssignedVarChanges,
|
||||
assignedVarType,
|
||||
isSupportAppend: isCurrSupportAppend,
|
||||
writeModeTypes,
|
||||
handleWriteModeChange,
|
||||
filterAssignedVar,
|
||||
filterToAssignedVar,
|
||||
handleToAssignedVarChange,
|
||||
toAssignedVarType,
|
||||
}
|
||||
}
|
||||
|
||||
export default useConfig
|
||||
5
web/app/components/workflow/nodes/assigner/utils.ts
Normal file
5
web/app/components/workflow/nodes/assigner/utils.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { AssignerNodeType } from './types'
|
||||
|
||||
export const checkNodeValid = (payload: AssignerNodeType) => {
|
||||
return true
|
||||
}
|
||||
@@ -24,6 +24,8 @@ import ToolNode from './tool/node'
|
||||
import ToolPanel from './tool/panel'
|
||||
import VariableAssignerNode from './variable-assigner/node'
|
||||
import VariableAssignerPanel from './variable-assigner/panel'
|
||||
import AssignerNode from './assigner/node'
|
||||
import AssignerPanel from './assigner/panel'
|
||||
import ParameterExtractorNode from './parameter-extractor/node'
|
||||
import ParameterExtractorPanel from './parameter-extractor/panel'
|
||||
import IterationNode from './iteration/node'
|
||||
@@ -42,6 +44,7 @@ export const NodeComponentMap: Record<string, ComponentType<any>> = {
|
||||
[BlockEnum.HttpRequest]: HttpNode,
|
||||
[BlockEnum.Tool]: ToolNode,
|
||||
[BlockEnum.VariableAssigner]: VariableAssignerNode,
|
||||
[BlockEnum.Assigner]: AssignerNode,
|
||||
[BlockEnum.VariableAggregator]: VariableAssignerNode,
|
||||
[BlockEnum.ParameterExtractor]: ParameterExtractorNode,
|
||||
[BlockEnum.Iteration]: IterationNode,
|
||||
@@ -61,6 +64,7 @@ export const PanelComponentMap: Record<string, ComponentType<any>> = {
|
||||
[BlockEnum.Tool]: ToolPanel,
|
||||
[BlockEnum.VariableAssigner]: VariableAssignerPanel,
|
||||
[BlockEnum.VariableAggregator]: VariableAssignerPanel,
|
||||
[BlockEnum.Assigner]: AssignerPanel,
|
||||
[BlockEnum.ParameterExtractor]: ParameterExtractorPanel,
|
||||
[BlockEnum.Iteration]: IterationPanel,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react'
|
||||
import cn from 'classnames'
|
||||
import type { EndNodeType } from './types'
|
||||
import type { NodeProps, Variable } from '@/app/components/workflow/types'
|
||||
import { isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useWorkflow,
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
|
||||
const Node: FC<NodeProps<EndNodeType>> = ({
|
||||
@@ -44,6 +44,7 @@ const Node: FC<NodeProps<EndNodeType>> = ({
|
||||
const node = getNode(value_selector[0])
|
||||
const isSystem = isSystemVar(value_selector)
|
||||
const isEnv = isENV(value_selector)
|
||||
const isChatVar = isConversationVar(value_selector)
|
||||
const varName = isSystem ? `sys.${value_selector[value_selector.length - 1]}` : value_selector[value_selector.length - 1]
|
||||
const varType = getCurrentVariableType({
|
||||
valueSelector: value_selector,
|
||||
@@ -53,7 +54,7 @@ const Node: FC<NodeProps<EndNodeType>> = ({
|
||||
return (
|
||||
<div key={index} className='flex items-center h-6 justify-between bg-gray-100 rounded-md px-1 space-x-1 text-xs font-normal text-gray-700'>
|
||||
<div className='flex items-center text-xs font-medium text-gray-500'>
|
||||
{!isEnv && (
|
||||
{!isEnv && !isChatVar && (
|
||||
<>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
@@ -66,9 +67,11 @@ const Node: FC<NodeProps<EndNodeType>> = ({
|
||||
</>
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
{!isEnv && <Variable02 className='shrink-0 w-3.5 h-3.5 text-primary-500' />}
|
||||
{!isEnv && !isChatVar && <Variable02 className='shrink-0 w-3.5 h-3.5 text-primary-500' />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div className={cn('max-w-[50px] ml-0.5 text-xs font-medium truncate', isEnv && '!max-w-[70px] text-gray-900')}>{varName}</div>
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
|
||||
<div className={cn('max-w-[50px] ml-0.5 text-xs font-medium truncate', (isEnv || isChatVar) && '!max-w-[70px] text-gray-900')}>{varName}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-xs font-normal text-gray-700'>
|
||||
|
||||
@@ -17,6 +17,8 @@ type Props = {
|
||||
onChange: (newList: KeyValue[]) => void
|
||||
onAdd: () => void
|
||||
// onSwitchToBulkEdit: () => void
|
||||
keyNotSupportVar?: boolean
|
||||
insertVarTipToLeft?: boolean
|
||||
}
|
||||
|
||||
const KeyValueList: FC<Props> = ({
|
||||
@@ -26,6 +28,8 @@ const KeyValueList: FC<Props> = ({
|
||||
onChange,
|
||||
onAdd,
|
||||
// onSwitchToBulkEdit,
|
||||
keyNotSupportVar,
|
||||
insertVarTipToLeft,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -47,6 +51,9 @@ const KeyValueList: FC<Props> = ({
|
||||
}
|
||||
}, [list, onChange])
|
||||
|
||||
if (!Array.isArray(list))
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='border border-gray-200 rounded-lg overflow-hidden'>
|
||||
<div className='flex items-center h-7 leading-7 text-xs font-medium text-gray-500 uppercase'>
|
||||
@@ -79,6 +86,8 @@ const KeyValueList: FC<Props> = ({
|
||||
onAdd={onAdd}
|
||||
readonly={readonly}
|
||||
canRemove={list.length > 1}
|
||||
keyNotSupportVar={keyNotSupportVar}
|
||||
insertVarTipToLeft={insertVarTipToLeft}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ type Props = {
|
||||
onRemove?: () => void
|
||||
placeholder?: string
|
||||
readOnly?: boolean
|
||||
insertVarTipToLeft?: boolean
|
||||
}
|
||||
|
||||
const InputItem: FC<Props> = ({
|
||||
@@ -30,6 +31,7 @@ const InputItem: FC<Props> = ({
|
||||
onRemove,
|
||||
placeholder,
|
||||
readOnly,
|
||||
insertVarTipToLeft,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -64,6 +66,7 @@ const InputItem: FC<Props> = ({
|
||||
placeholder={t('workflow.nodes.http.insertVarPlaceholder')!}
|
||||
placeholderClassName='!leading-[21px]'
|
||||
promptMinHeightClassName='h-full'
|
||||
insertVarTipToLeft={insertVarTipToLeft}
|
||||
/>
|
||||
)
|
||||
: <div
|
||||
@@ -83,6 +86,7 @@ const InputItem: FC<Props> = ({
|
||||
placeholder={t('workflow.nodes.http.insertVarPlaceholder')!}
|
||||
placeholderClassName='!leading-[21px]'
|
||||
promptMinHeightClassName='h-full'
|
||||
insertVarTipToLeft={insertVarTipToLeft}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import produce from 'immer'
|
||||
import type { KeyValue } from '../../../types'
|
||||
import InputItem from './input-item'
|
||||
import cn from '@/utils/classnames'
|
||||
import Input from '@/app/components/base/input'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.http'
|
||||
|
||||
@@ -20,6 +21,8 @@ type Props = {
|
||||
onRemove: () => void
|
||||
isLastItem: boolean
|
||||
onAdd: () => void
|
||||
keyNotSupportVar?: boolean
|
||||
insertVarTipToLeft?: boolean
|
||||
}
|
||||
|
||||
const KeyValueItem: FC<Props> = ({
|
||||
@@ -33,6 +36,8 @@ const KeyValueItem: FC<Props> = ({
|
||||
onRemove,
|
||||
isLastItem,
|
||||
onAdd,
|
||||
keyNotSupportVar,
|
||||
insertVarTipToLeft,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -51,15 +56,26 @@ const KeyValueItem: FC<Props> = ({
|
||||
// group class name is for hover row show remove button
|
||||
<div className={cn(className, 'group flex h-min-7 border-t border-gray-200')}>
|
||||
<div className='w-1/2 border-r border-gray-200'>
|
||||
<InputItem
|
||||
instanceId={`http-key-${instanceId}`}
|
||||
nodeId={nodeId}
|
||||
value={payload.key}
|
||||
onChange={handleChange('key')}
|
||||
hasRemove={false}
|
||||
placeholder={t(`${i18nPrefix}.key`)!}
|
||||
readOnly={readonly}
|
||||
/>
|
||||
{!keyNotSupportVar
|
||||
? (
|
||||
<InputItem
|
||||
instanceId={`http-key-${instanceId}`}
|
||||
nodeId={nodeId}
|
||||
value={payload.key}
|
||||
onChange={handleChange('key')}
|
||||
hasRemove={false}
|
||||
placeholder={t(`${i18nPrefix}.key`)!}
|
||||
readOnly={readonly}
|
||||
insertVarTipToLeft={insertVarTipToLeft}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Input
|
||||
className='rounded-none bg-white border-none system-sm-regular focus:ring-0 focus:bg-gray-100! hover:bg-gray-50'
|
||||
value={payload.key}
|
||||
onChange={handleChange('key')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='w-1/2'>
|
||||
<InputItem
|
||||
@@ -71,6 +87,7 @@ const KeyValueItem: FC<Props> = ({
|
||||
onRemove={onRemove}
|
||||
placeholder={t(`${i18nPrefix}.value`)!}
|
||||
readOnly={readonly}
|
||||
insertVarTipToLeft={insertVarTipToLeft}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { filterVar } from '../utils'
|
||||
import AddVariable from './add-variable'
|
||||
import NodeVariableItem from './node-variable-item'
|
||||
import { isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.variableAssigner'
|
||||
@@ -124,6 +124,8 @@ const NodeGroupItem = ({
|
||||
!!item.variables.length && item.variables.map((variable = [], index) => {
|
||||
const isSystem = isSystemVar(variable)
|
||||
const isEnv = isENV(variable)
|
||||
const isChatVar = isConversationVar(variable)
|
||||
|
||||
const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0])
|
||||
const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.')
|
||||
|
||||
@@ -131,6 +133,7 @@ const NodeGroupItem = ({
|
||||
<NodeVariableItem
|
||||
key={index}
|
||||
isEnv={isEnv}
|
||||
isChatVar={isChatVar}
|
||||
node={node as Node}
|
||||
varName={varName}
|
||||
showBorder={showSelectedBorder || showSelectionBorder}
|
||||
|
||||
@@ -3,28 +3,33 @@ import cn from '@/utils/classnames'
|
||||
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
|
||||
type NodeVariableItemProps = {
|
||||
isEnv: boolean
|
||||
isChatVar: boolean
|
||||
node: Node
|
||||
varName: string
|
||||
showBorder?: boolean
|
||||
className?: string
|
||||
}
|
||||
const NodeVariableItem = ({
|
||||
isEnv,
|
||||
isChatVar,
|
||||
node,
|
||||
varName,
|
||||
showBorder,
|
||||
className,
|
||||
}: NodeVariableItemProps) => {
|
||||
return (
|
||||
<div className={cn(
|
||||
'relative flex items-center mt-0.5 h-6 bg-gray-100 rounded-md px-1 text-xs font-normal text-gray-700',
|
||||
showBorder && '!bg-black/[0.02]',
|
||||
className,
|
||||
)}>
|
||||
{!isEnv && (
|
||||
{!isEnv && !isChatVar && (
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
@@ -37,9 +42,10 @@ const NodeVariableItem = ({
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
{!isEnv && <Variable02 className='shrink-0 w-3.5 h-3.5 text-primary-500' />}
|
||||
{!isEnv && !isChatVar && <Variable02 className='shrink-0 w-3.5 h-3.5 text-primary-500' />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
<div className={cn('max-w-[75px] truncate ml-0.5 text-xs font-medium', isEnv && 'text-gray-900')} title={varName}>{varName}</div>
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
<div className={cn('max-w-[75px] truncate ml-0.5 text-xs font-medium', (isEnv || isChatVar) && 'text-gray-900')} title={varName}>{varName}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -143,6 +143,7 @@ export const useGetAvailableVars = () => {
|
||||
beforeNodes: uniqBy(availableNodes, 'id').filter(node => node.id !== nodeId),
|
||||
isChatMode,
|
||||
hideEnv,
|
||||
hideChatVar: hideEnv,
|
||||
filterVar,
|
||||
})
|
||||
.map(node => ({
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import produce from 'immer'
|
||||
import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
type Props = {
|
||||
isString: boolean
|
||||
list: any[]
|
||||
onChange: (list: any[]) => void
|
||||
}
|
||||
|
||||
const ArrayValueList: FC<Props> = ({
|
||||
isString = true,
|
||||
list,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleNameChange = useCallback((index: number) => {
|
||||
return (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newList = produce(list, (draft: any[]) => {
|
||||
draft[index] = isString ? e.target.value : Number(e.target.value)
|
||||
})
|
||||
onChange(newList)
|
||||
}
|
||||
}, [isString, list, onChange])
|
||||
|
||||
const handleItemRemove = useCallback((index: number) => {
|
||||
return () => {
|
||||
const newList = produce(list, (draft) => {
|
||||
draft.splice(index, 1)
|
||||
})
|
||||
onChange(newList)
|
||||
}
|
||||
}, [list, onChange])
|
||||
|
||||
const handleItemAdd = useCallback(() => {
|
||||
const newList = produce(list, (draft: any[]) => {
|
||||
draft.push(undefined)
|
||||
})
|
||||
onChange(newList)
|
||||
}, [list, onChange])
|
||||
|
||||
return (
|
||||
<div className='w-full space-y-2'>
|
||||
{list.map((item, index) => (
|
||||
<div className='flex items-center space-x-1' key={index}>
|
||||
<input
|
||||
className='block px-3 w-full h-8 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder'
|
||||
placeholder={t('workflow.chatVariable.modal.arrayValue') || ''}
|
||||
value={list[index]}
|
||||
onChange={handleNameChange(index)}
|
||||
type={isString ? 'text' : 'number'}
|
||||
/>
|
||||
<RemoveButton
|
||||
className='!p-2 !bg-gray-100 hover:!bg-gray-200'
|
||||
onClick={handleItemRemove(index)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<Button variant='tertiary' className='w-full' onClick={handleItemAdd}>
|
||||
<RiAddLine className='mr-1 w-4 h-4' />
|
||||
<span>{t('workflow.chatVariable.modal.addArrayValue')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(ArrayValueList)
|
||||
@@ -0,0 +1,135 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import produce from 'immer'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import VariableTypeSelector from '@/app/components/workflow/panel/chat-variable-panel/components/variable-type-select'
|
||||
import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button'
|
||||
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
||||
|
||||
type Props = {
|
||||
index: number
|
||||
list: any[]
|
||||
onChange: (list: any[]) => void
|
||||
}
|
||||
|
||||
const typeList = [
|
||||
ChatVarType.String,
|
||||
ChatVarType.Number,
|
||||
]
|
||||
|
||||
export const DEFAULT_OBJECT_VALUE = {
|
||||
key: '',
|
||||
type: ChatVarType.String,
|
||||
value: undefined,
|
||||
}
|
||||
|
||||
const ObjectValueItem: FC<Props> = ({
|
||||
index,
|
||||
list,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const [isFocus, setIsFocus] = useState(false)
|
||||
|
||||
const handleKeyChange = useCallback((index: number) => {
|
||||
return (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newList = produce(list, (draft: any[]) => {
|
||||
if (!/^[a-zA-Z0-9_]+$/.test(e.target.value))
|
||||
return notify({ type: 'error', message: 'key is can only contain letters, numbers and underscores' })
|
||||
draft[index].key = e.target.value
|
||||
})
|
||||
onChange(newList)
|
||||
}
|
||||
}, [list, notify, onChange])
|
||||
|
||||
const handleTypeChange = useCallback((index: number) => {
|
||||
return (type: ChatVarType) => {
|
||||
const newList = produce(list, (draft) => {
|
||||
draft[index].type = type
|
||||
if (type === ChatVarType.Number)
|
||||
draft[index].value = isNaN(Number(draft[index].value)) ? undefined : Number(draft[index].value)
|
||||
else
|
||||
draft[index].value = draft[index].value ? String(draft[index].value) : undefined
|
||||
})
|
||||
onChange(newList)
|
||||
}
|
||||
}, [list, onChange])
|
||||
|
||||
const handleValueChange = useCallback((index: number) => {
|
||||
return (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newList = produce(list, (draft: any[]) => {
|
||||
draft[index].value = draft[index].type === ChatVarType.String ? e.target.value : isNaN(Number(e.target.value)) ? undefined : Number(e.target.value)
|
||||
})
|
||||
onChange(newList)
|
||||
}
|
||||
}, [list, onChange])
|
||||
|
||||
const handleItemRemove = useCallback((index: number) => {
|
||||
return () => {
|
||||
const newList = produce(list, (draft) => {
|
||||
draft.splice(index, 1)
|
||||
})
|
||||
onChange(newList)
|
||||
}
|
||||
}, [list, onChange])
|
||||
|
||||
const handleItemAdd = useCallback(() => {
|
||||
const newList = produce(list, (draft: any[]) => {
|
||||
draft.push(DEFAULT_OBJECT_VALUE)
|
||||
})
|
||||
onChange(newList)
|
||||
}, [list, onChange])
|
||||
|
||||
const handleFocusChange = useCallback(() => {
|
||||
setIsFocus(true)
|
||||
if (index === list.length - 1)
|
||||
handleItemAdd()
|
||||
}, [handleItemAdd, index, list.length])
|
||||
|
||||
return (
|
||||
<div className='group flex border-t border-gray-200'>
|
||||
{/* Key */}
|
||||
<div className='w-[120px] border-r border-gray-200'>
|
||||
<input
|
||||
className='block px-2 w-full h-7 text-text-secondary system-xs-regular appearance-none outline-none caret-primary-600 hover:bg-state-base-hover focus:bg-components-input-bg-active placeholder:system-xs-regular placeholder:text-components-input-text-placeholder'
|
||||
placeholder={t('workflow.chatVariable.modal.objectKey') || ''}
|
||||
value={list[index].key}
|
||||
onChange={handleKeyChange(index)}
|
||||
/>
|
||||
</div>
|
||||
{/* Type */}
|
||||
<div className='w-[96px] border-r border-gray-200'>
|
||||
<VariableTypeSelector
|
||||
inCell
|
||||
value={list[index].type}
|
||||
list={typeList}
|
||||
onSelect={handleTypeChange(index)}
|
||||
popupClassName='w-[120px]'
|
||||
/>
|
||||
</div>
|
||||
{/* Value */}
|
||||
<div className='relative w-[230px]'>
|
||||
<input
|
||||
className='block px-2 w-full h-7 text-text-secondary system-xs-regular appearance-none outline-none caret-primary-600 hover:bg-state-base-hover focus:bg-components-input-bg-active placeholder:system-xs-regular placeholder:text-components-input-text-placeholder'
|
||||
placeholder={t('workflow.chatVariable.modal.objectValue') || ''}
|
||||
value={list[index].value}
|
||||
onChange={handleValueChange(index)}
|
||||
onFocus={() => handleFocusChange()}
|
||||
onBlur={() => setIsFocus(false)}
|
||||
type={list[index].type === ChatVarType.Number ? 'number' : 'text'}
|
||||
/>
|
||||
{list.length > 1 && !isFocus && (
|
||||
<RemoveButton
|
||||
className='z-10 group-hover:block hidden absolute right-1 top-0.5'
|
||||
onClick={handleItemRemove(index)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(ObjectValueItem)
|
||||
@@ -0,0 +1,36 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ObjectValueItem from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-item'
|
||||
|
||||
type Props = {
|
||||
list: any[]
|
||||
onChange: (list: any[]) => void
|
||||
}
|
||||
|
||||
const ObjectValueList: FC<Props> = ({
|
||||
list,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='w-full border border-gray-200 rounded-lg overflow-hidden'>
|
||||
<div className='flex items-center h-7 system-xs-medium text-text-tertiary uppercase'>
|
||||
<div className='w-[120px] flex items-center h-full pl-2 border-r border-gray-200'>{t('workflow.chatVariable.modal.objectKey')}</div>
|
||||
<div className='w-[96px] flex items-center h-full pl-2 border-r border-gray-200'>{t('workflow.chatVariable.modal.objectType')}</div>
|
||||
<div className='w-[230px] flex items-center h-full pl-2 pr-1'>{t('workflow.chatVariable.modal.objectValue')}</div>
|
||||
</div>
|
||||
{list.map((item, index) => (
|
||||
<ObjectValueItem
|
||||
key={index}
|
||||
index={index}
|
||||
list={list}
|
||||
onChange={onChange}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(ObjectValueList)
|
||||
@@ -0,0 +1,49 @@
|
||||
import { memo, useState } from 'react'
|
||||
import { capitalize } from 'lodash-es'
|
||||
import { RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
||||
import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import type { ConversationVariable } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type VariableItemProps = {
|
||||
item: ConversationVariable
|
||||
onEdit: (item: ConversationVariable) => void
|
||||
onDelete: (item: ConversationVariable) => void
|
||||
}
|
||||
|
||||
const VariableItem = ({
|
||||
item,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}: VariableItemProps) => {
|
||||
const [destructive, setDestructive] = useState(false)
|
||||
return (
|
||||
<div className={cn(
|
||||
'mb-1 px-2.5 py-2 bg-components-panel-on-panel-item-bg radius-md border border-components-panel-border-subtle shadow-xs hover:bg-components-panel-on-panel-item-bg-hover',
|
||||
destructive && 'border-state-destructive-border hover:bg-state-destructive-hover',
|
||||
)}>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='grow flex gap-1 items-center'>
|
||||
<BubbleX className='w-4 h-4 text-util-colors-teal-teal-700' />
|
||||
<div className='text-text-primary system-sm-medium'>{item.name}</div>
|
||||
<div className='text-text-tertiary system-xs-medium'>{capitalize(item.value_type)}</div>
|
||||
</div>
|
||||
<div className='shrink-0 flex gap-1 items-center text-text-tertiary'>
|
||||
<div className='p-1 radius-md cursor-pointer hover:bg-state-base-hover hover:text-text-secondary'>
|
||||
<RiEditLine className='w-4 h-4' onClick={() => onEdit(item)}/>
|
||||
</div>
|
||||
<div
|
||||
className='p-1 radius-md cursor-pointer hover:bg-state-destructive-hover hover:text-text-destructive'
|
||||
onMouseOver={() => setDestructive(true)}
|
||||
onMouseOut={() => setDestructive(false)}
|
||||
>
|
||||
<RiDeleteBinLine className='w-4 h-4' onClick={() => onDelete(item)}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-text-tertiary system-xs-regular truncate'>{item.description}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(VariableItem)
|
||||
@@ -0,0 +1,69 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import VariableModal from '@/app/components/workflow/panel/chat-variable-panel/components/variable-modal'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import type { ConversationVariable } from '@/app/components/workflow/types'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
setOpen: (value: React.SetStateAction<boolean>) => void
|
||||
showTip: boolean
|
||||
chatVar?: ConversationVariable
|
||||
onClose: () => void
|
||||
onSave: (env: ConversationVariable) => void
|
||||
}
|
||||
|
||||
const VariableModalTrigger = ({
|
||||
open,
|
||||
setOpen,
|
||||
showTip,
|
||||
chatVar,
|
||||
onClose,
|
||||
onSave,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={() => {
|
||||
setOpen(v => !v)
|
||||
open && onClose()
|
||||
}}
|
||||
placement='left-start'
|
||||
offset={{
|
||||
mainAxis: 8,
|
||||
alignmentAxis: showTip ? -278 : -48,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => {
|
||||
setOpen(v => !v)
|
||||
open && onClose()
|
||||
}}>
|
||||
<Button variant='primary'>
|
||||
<RiAddLine className='mr-1 w-4 h-4' />
|
||||
<span className='system-sm-medium'>{t('workflow.chatVariable.button')}</span>
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[11]'>
|
||||
<VariableModal
|
||||
chatVar={chatVar}
|
||||
onSave={onSave}
|
||||
onClose={() => {
|
||||
onClose()
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default VariableModalTrigger
|
||||
@@ -0,0 +1,388 @@
|
||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { v4 as uuid4 } from 'uuid'
|
||||
import { RiCloseLine, RiDraftLine, RiInputField } from '@remixicon/react'
|
||||
import VariableTypeSelector from '@/app/components/workflow/panel/chat-variable-panel/components/variable-type-select'
|
||||
import ObjectValueList from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-list'
|
||||
import { DEFAULT_OBJECT_VALUE } from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-item'
|
||||
import ArrayValueList from '@/app/components/workflow/panel/chat-variable-panel/components/array-value-list'
|
||||
import Button from '@/app/components/base/button'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import type { ConversationVariable } from '@/app/components/workflow/types'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type ModalPropsType = {
|
||||
chatVar?: ConversationVariable
|
||||
onClose: () => void
|
||||
onSave: (chatVar: ConversationVariable) => void
|
||||
}
|
||||
|
||||
type ObjectValueItem = {
|
||||
key: string
|
||||
type: ChatVarType
|
||||
value: string | number | undefined
|
||||
}
|
||||
|
||||
const typeList = [
|
||||
ChatVarType.String,
|
||||
ChatVarType.Number,
|
||||
ChatVarType.Object,
|
||||
ChatVarType.ArrayString,
|
||||
ChatVarType.ArrayNumber,
|
||||
ChatVarType.ArrayObject,
|
||||
]
|
||||
|
||||
const objectPlaceholder = `# example
|
||||
# {
|
||||
# "name": "ray",
|
||||
# "age": 20
|
||||
# }`
|
||||
const arrayStringPlaceholder = `# example
|
||||
# [
|
||||
# "value1",
|
||||
# "value2"
|
||||
# ]`
|
||||
const arrayNumberPlaceholder = `# example
|
||||
# [
|
||||
# 100,
|
||||
# 200
|
||||
# ]`
|
||||
const arrayObjectPlaceholder = `# example
|
||||
# [
|
||||
# {
|
||||
# "name": "ray",
|
||||
# "age": 20
|
||||
# },
|
||||
# {
|
||||
# "name": "lily",
|
||||
# "age": 18
|
||||
# }
|
||||
# ]`
|
||||
|
||||
const ChatVariableModal = ({
|
||||
chatVar,
|
||||
onClose,
|
||||
onSave,
|
||||
}: ModalPropsType) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const varList = useStore(s => s.conversationVariables)
|
||||
const [name, setName] = React.useState('')
|
||||
const [type, setType] = React.useState<ChatVarType>(ChatVarType.String)
|
||||
const [value, setValue] = React.useState<any>()
|
||||
const [objectValue, setObjectValue] = React.useState<ObjectValueItem[]>([DEFAULT_OBJECT_VALUE])
|
||||
const [editorContent, setEditorContent] = React.useState<string>()
|
||||
const [editInJSON, setEditInJSON] = React.useState(false)
|
||||
const [des, setDes] = React.useState<string>('')
|
||||
|
||||
const editorMinHeight = useMemo(() => {
|
||||
if (type === ChatVarType.ArrayObject)
|
||||
return '240px'
|
||||
return '120px'
|
||||
}, [type])
|
||||
const placeholder = useMemo(() => {
|
||||
if (type === ChatVarType.ArrayString)
|
||||
return arrayStringPlaceholder
|
||||
if (type === ChatVarType.ArrayNumber)
|
||||
return arrayNumberPlaceholder
|
||||
if (type === ChatVarType.ArrayObject)
|
||||
return arrayObjectPlaceholder
|
||||
return objectPlaceholder
|
||||
}, [type])
|
||||
const getObjectValue = useCallback(() => {
|
||||
if (!chatVar)
|
||||
return [DEFAULT_OBJECT_VALUE]
|
||||
return Object.keys(chatVar.value).map((key) => {
|
||||
return {
|
||||
key,
|
||||
type: typeof chatVar.value[key] === 'string' ? ChatVarType.String : ChatVarType.Number,
|
||||
value: chatVar.value[key],
|
||||
}
|
||||
})
|
||||
}, [chatVar])
|
||||
const formatValueFromObject = useCallback((list: ObjectValueItem[]) => {
|
||||
return list.reduce((acc: any, curr) => {
|
||||
if (curr.key)
|
||||
acc[curr.key] = curr.value || null
|
||||
return acc
|
||||
}, {})
|
||||
}, [])
|
||||
|
||||
const formatValue = (value: any) => {
|
||||
switch (type) {
|
||||
case ChatVarType.String:
|
||||
return value || ''
|
||||
case ChatVarType.Number:
|
||||
return value || 0
|
||||
case ChatVarType.Object:
|
||||
return formatValueFromObject(objectValue)
|
||||
case ChatVarType.ArrayString:
|
||||
case ChatVarType.ArrayNumber:
|
||||
case ChatVarType.ArrayObject:
|
||||
return value?.filter(Boolean) || []
|
||||
}
|
||||
}
|
||||
|
||||
const handleNameChange = (v: string) => {
|
||||
if (!v)
|
||||
return setName('')
|
||||
if (!/^[a-zA-Z0-9_]+$/.test(v))
|
||||
return notify({ type: 'error', message: 'name is can only contain letters, numbers and underscores' })
|
||||
if (/^[0-9]/.test(v))
|
||||
return notify({ type: 'error', message: 'name can not start with a number' })
|
||||
setName(v)
|
||||
}
|
||||
|
||||
const handleTypeChange = (v: ChatVarType) => {
|
||||
setValue(undefined)
|
||||
setEditorContent(undefined)
|
||||
if (v === ChatVarType.ArrayObject)
|
||||
setEditInJSON(true)
|
||||
if (v === ChatVarType.String || v === ChatVarType.Number || v === ChatVarType.Object)
|
||||
setEditInJSON(false)
|
||||
setType(v)
|
||||
}
|
||||
|
||||
const handleEditorChange = (editInJSON: boolean) => {
|
||||
if (type === ChatVarType.Object) {
|
||||
if (editInJSON) {
|
||||
const newValue = !objectValue[0].key ? undefined : formatValueFromObject(objectValue)
|
||||
setValue(newValue)
|
||||
setEditorContent(JSON.stringify(newValue))
|
||||
}
|
||||
else {
|
||||
if (!editorContent) {
|
||||
setValue(undefined)
|
||||
setObjectValue([DEFAULT_OBJECT_VALUE])
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const newValue = JSON.parse(editorContent)
|
||||
setValue(newValue)
|
||||
const newObjectValue = Object.keys(newValue).map((key) => {
|
||||
return {
|
||||
key,
|
||||
type: typeof newValue[key] === 'string' ? ChatVarType.String : ChatVarType.Number,
|
||||
value: newValue[key],
|
||||
}
|
||||
})
|
||||
setObjectValue(newObjectValue)
|
||||
}
|
||||
catch (e) {
|
||||
// ignore JSON.parse errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) {
|
||||
if (editInJSON) {
|
||||
const newValue = (value?.length && value.filter(Boolean).length) ? value.filter(Boolean) : undefined
|
||||
setValue(newValue)
|
||||
if (!editorContent)
|
||||
setEditorContent(JSON.stringify(newValue))
|
||||
}
|
||||
else {
|
||||
setValue(value?.length ? value : [undefined])
|
||||
}
|
||||
}
|
||||
setEditInJSON(editInJSON)
|
||||
}
|
||||
|
||||
const handleEditorValueChange = (content: string) => {
|
||||
if (!content) {
|
||||
setEditorContent(content)
|
||||
return setValue(undefined)
|
||||
}
|
||||
else {
|
||||
setEditorContent(content)
|
||||
try {
|
||||
const newValue = JSON.parse(content)
|
||||
setValue(newValue)
|
||||
}
|
||||
catch (e) {
|
||||
// ignore JSON.parse errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
if (!name)
|
||||
return notify({ type: 'error', message: 'name can not be empty' })
|
||||
if (!chatVar && varList.some(chatVar => chatVar.name === name))
|
||||
return notify({ type: 'error', message: 'name is existed' })
|
||||
// if (type !== ChatVarType.Object && !value)
|
||||
// return notify({ type: 'error', message: 'value can not be empty' })
|
||||
if (type === ChatVarType.Object && objectValue.some(item => !item.key && !!item.value))
|
||||
return notify({ type: 'error', message: 'object key can not be empty' })
|
||||
|
||||
onSave({
|
||||
id: chatVar ? chatVar.id : uuid4(),
|
||||
name,
|
||||
value_type: type,
|
||||
value: formatValue(value),
|
||||
description: des,
|
||||
})
|
||||
onClose()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (chatVar) {
|
||||
setName(chatVar.name)
|
||||
setType(chatVar.value_type)
|
||||
setValue(chatVar.value)
|
||||
setDes(chatVar.description)
|
||||
setEditInJSON(false)
|
||||
setObjectValue(getObjectValue())
|
||||
}
|
||||
}, [chatVar, getObjectValue])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('flex flex-col w-[360px] bg-components-panel-bg rounded-2xl h-full border-[0.5px] border-components-panel-border shadow-2xl', type === ChatVarType.Object && 'w-[480px]')}
|
||||
>
|
||||
<div className='shrink-0 flex items-center justify-between mb-3 p-4 pb-0 text-text-primary system-xl-semibold'>
|
||||
{!chatVar ? t('workflow.chatVariable.modal.title') : t('workflow.chatVariable.modal.editTitle')}
|
||||
<div className='flex items-center'>
|
||||
<div
|
||||
className='flex items-center justify-center w-6 h-6 cursor-pointer'
|
||||
onClick={onClose}
|
||||
>
|
||||
<RiCloseLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-4 py-2 max-h-[480px] overflow-y-auto'>
|
||||
{/* name */}
|
||||
<div className='mb-4'>
|
||||
<div className='mb-1 h-6 flex items-center text-text-secondary system-sm-semibold'>{t('workflow.chatVariable.modal.name')}</div>
|
||||
<div className='flex'>
|
||||
<input
|
||||
tabIndex={0}
|
||||
className='block px-3 w-full h-8 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder'
|
||||
placeholder={t('workflow.chatVariable.modal.namePlaceholder') || ''}
|
||||
value={name}
|
||||
onChange={e => handleNameChange(e.target.value)}
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* type */}
|
||||
<div className='mb-4'>
|
||||
<div className='mb-1 h-6 flex items-center text-text-secondary system-sm-semibold'>{t('workflow.chatVariable.modal.type')}</div>
|
||||
<div className='flex'>
|
||||
<VariableTypeSelector
|
||||
value={type}
|
||||
list={typeList}
|
||||
onSelect={handleTypeChange}
|
||||
popupClassName='w-[327px]'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* default value */}
|
||||
<div className='mb-4'>
|
||||
<div className='mb-1 h-6 flex items-center justify-between text-text-secondary system-sm-semibold'>
|
||||
<div>{t('workflow.chatVariable.modal.value')}</div>
|
||||
{(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) && (
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='small'
|
||||
className='text-text-tertiary'
|
||||
onClick={() => handleEditorChange(!editInJSON)}
|
||||
>
|
||||
{editInJSON ? <RiInputField className='mr-1 w-3.5 h-3.5' /> : <RiDraftLine className='mr-1 w-3.5 h-3.5' />}
|
||||
{editInJSON ? t('workflow.chatVariable.modal.oneByOne') : t('workflow.chatVariable.modal.editInJSON')}
|
||||
</Button>
|
||||
)}
|
||||
{type === ChatVarType.Object && (
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='small'
|
||||
className='text-text-tertiary'
|
||||
onClick={() => handleEditorChange(!editInJSON)}
|
||||
>
|
||||
{editInJSON ? <RiInputField className='mr-1 w-3.5 h-3.5' /> : <RiDraftLine className='mr-1 w-3.5 h-3.5' />}
|
||||
{editInJSON ? t('workflow.chatVariable.modal.editInForm') : t('workflow.chatVariable.modal.editInJSON')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex'>
|
||||
{type === ChatVarType.String && (
|
||||
<input
|
||||
className='block px-3 w-full h-8 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder'
|
||||
placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''}
|
||||
value={value}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
{type === ChatVarType.Number && (
|
||||
<input
|
||||
className='block px-3 w-full h-8 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder'
|
||||
placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''}
|
||||
value={value}
|
||||
onChange={e => setValue(Number(e.target.value))}
|
||||
type='number'
|
||||
/>
|
||||
)}
|
||||
{type === ChatVarType.Object && !editInJSON && (
|
||||
<ObjectValueList
|
||||
list={objectValue}
|
||||
onChange={setObjectValue}
|
||||
/>
|
||||
)}
|
||||
{type === ChatVarType.ArrayString && !editInJSON && (
|
||||
<ArrayValueList
|
||||
isString
|
||||
list={value || [undefined]}
|
||||
onChange={setValue}
|
||||
/>
|
||||
)}
|
||||
{type === ChatVarType.ArrayNumber && !editInJSON && (
|
||||
<ArrayValueList
|
||||
isString={false}
|
||||
list={value || [undefined]}
|
||||
onChange={setValue}
|
||||
/>
|
||||
)}
|
||||
{editInJSON && (
|
||||
<div className='w-full py-2 pl-3 pr-1 rounded-[10px] bg-components-input-bg-normal' style={{ height: editorMinHeight }}>
|
||||
<CodeEditor
|
||||
isExpand
|
||||
noWrapper
|
||||
language={CodeLanguage.json}
|
||||
value={editorContent}
|
||||
placeholder={<div className='whitespace-pre'>{placeholder}</div>}
|
||||
onChange={handleEditorValueChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* description */}
|
||||
<div className=''>
|
||||
<div className='mb-1 h-6 flex items-center text-text-secondary system-sm-semibold'>{t('workflow.chatVariable.modal.description')}</div>
|
||||
<div className='flex'>
|
||||
<textarea
|
||||
className='block p-2 w-full h-20 rounded-lg bg-components-input-bg-normal border border-transparent system-sm-regular outline-none appearance-none caret-primary-600 resize-none hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder'
|
||||
value={des}
|
||||
placeholder={t('workflow.chatVariable.modal.descriptionPlaceholder') || ''}
|
||||
onChange={e => setDes(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-4 pt-2 flex flex-row-reverse rounded-b-2xl'>
|
||||
<div className='flex gap-2'>
|
||||
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary' onClick={handleSave}>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChatVariableModal
|
||||
@@ -0,0 +1,66 @@
|
||||
'use client'
|
||||
import React, { useState } from 'react'
|
||||
import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
inCell?: boolean
|
||||
value?: any
|
||||
list: any
|
||||
onSelect: (value: any) => void
|
||||
popupClassName?: string
|
||||
}
|
||||
|
||||
const VariableTypeSelector = ({
|
||||
inCell = false,
|
||||
value,
|
||||
list,
|
||||
onSelect,
|
||||
popupClassName,
|
||||
}: Props) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={() => setOpen(v => !v)}
|
||||
placement='bottom'
|
||||
>
|
||||
<PortalToFollowElemTrigger className='w-full' onClick={() => setOpen(v => !v)}>
|
||||
<div className={cn(
|
||||
'flex items-center w-full px-2 cursor-pointer',
|
||||
!inCell && 'py-1 bg-components-input-bg-normal hover:bg-state-base-hover-alt radius-md',
|
||||
inCell && 'py-0.5 hover:bg-state-base-hover',
|
||||
open && !inCell && 'bg-state-base-hover-alt hover:bg-state-base-hover-alt',
|
||||
open && inCell && 'bg-state-base-hover hover:bg-state-base-hover',
|
||||
)}>
|
||||
<div className={cn(
|
||||
'grow p-1 system-sm-regular text-components-input-text-filled truncate',
|
||||
inCell && 'system-xs-regular text-text-secondary',
|
||||
)}>{value}</div>
|
||||
<RiArrowDownSLine className='ml-0.5 w-4 h-4 text-text-quaternary' />
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className={cn('w-full z-[11]', popupClassName)}>
|
||||
<div className='p-1 bg-components-panel-bg-blur border-[0.5px] border-components-panel-border radius-xl shadow-lg'>
|
||||
{list.map((item: any) => (
|
||||
<div key={item} className='flex items-center gap-2 pl-3 pr-2 py-[6px] radius-md cursor-pointer hover:bg-state-base-hover' onClick={() => {
|
||||
onSelect(item)
|
||||
setOpen(false)
|
||||
}}>
|
||||
<div className='grow system-md-regular text-text-secondary truncate'>{item}</div>
|
||||
{value === item && <RiCheckLine className='w-4 h-4 text-text-accent' />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default VariableTypeSelector
|
||||
202
web/app/components/workflow/panel/chat-variable-panel/index.tsx
Normal file
202
web/app/components/workflow/panel/chat-variable-panel/index.tsx
Normal file
@@ -0,0 +1,202 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import {
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import { RiBookOpenLine, RiCloseLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||
import { BubbleX, LongArrowLeft, LongArrowRight } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import VariableModalTrigger from '@/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger'
|
||||
import VariableItem from '@/app/components/workflow/panel/chat-variable-panel/components/variable-item'
|
||||
import RemoveEffectVarConfirm from '@/app/components/workflow/nodes/_base/components/remove-effect-var-confirm'
|
||||
import type {
|
||||
ConversationVariable,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import I18n from '@/context/i18n'
|
||||
import { LanguagesSupported } from '@/i18n/language'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const ChatVariablePanel = () => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
const store = useStoreApi()
|
||||
const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel)
|
||||
const varList = useStore(s => s.conversationVariables) as ConversationVariable[]
|
||||
const updateChatVarList = useStore(s => s.setConversationVariables)
|
||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
|
||||
const [showTip, setShowTip] = useState(true)
|
||||
const [showVariableModal, setShowVariableModal] = useState(false)
|
||||
const [currentVar, setCurrentVar] = useState<ConversationVariable>()
|
||||
|
||||
const [showRemoveVarConfirm, setShowRemoveConfirm] = useState(false)
|
||||
const [cacheForDelete, setCacheForDelete] = useState<ConversationVariable>()
|
||||
|
||||
const getEffectedNodes = useCallback((chatVar: ConversationVariable) => {
|
||||
const { getNodes } = store.getState()
|
||||
const allNodes = getNodes()
|
||||
return findUsedVarNodes(
|
||||
['conversation', chatVar.name],
|
||||
allNodes,
|
||||
)
|
||||
}, [store])
|
||||
|
||||
const removeUsedVarInNodes = useCallback((chatVar: ConversationVariable) => {
|
||||
const { getNodes, setNodes } = store.getState()
|
||||
const effectedNodes = getEffectedNodes(chatVar)
|
||||
const newNodes = getNodes().map((node) => {
|
||||
if (effectedNodes.find(n => n.id === node.id))
|
||||
return updateNodeVars(node, ['conversation', chatVar.name], [])
|
||||
|
||||
return node
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}, [getEffectedNodes, store])
|
||||
|
||||
const handleEdit = (chatVar: ConversationVariable) => {
|
||||
setCurrentVar(chatVar)
|
||||
setShowVariableModal(true)
|
||||
}
|
||||
|
||||
const handleDelete = useCallback((chatVar: ConversationVariable) => {
|
||||
removeUsedVarInNodes(chatVar)
|
||||
updateChatVarList(varList.filter(v => v.id !== chatVar.id))
|
||||
setCacheForDelete(undefined)
|
||||
setShowRemoveConfirm(false)
|
||||
doSyncWorkflowDraft()
|
||||
}, [doSyncWorkflowDraft, removeUsedVarInNodes, updateChatVarList, varList])
|
||||
|
||||
const deleteCheck = useCallback((chatVar: ConversationVariable) => {
|
||||
const effectedNodes = getEffectedNodes(chatVar)
|
||||
if (effectedNodes.length > 0) {
|
||||
setCacheForDelete(chatVar)
|
||||
setShowRemoveConfirm(true)
|
||||
}
|
||||
else {
|
||||
handleDelete(chatVar)
|
||||
}
|
||||
}, [getEffectedNodes, handleDelete])
|
||||
|
||||
const handleSave = useCallback(async (chatVar: ConversationVariable) => {
|
||||
// add chatVar
|
||||
if (!currentVar) {
|
||||
const newList = [chatVar, ...varList]
|
||||
updateChatVarList(newList)
|
||||
doSyncWorkflowDraft()
|
||||
return
|
||||
}
|
||||
// edit chatVar
|
||||
const newList = varList.map(v => v.id === currentVar.id ? chatVar : v)
|
||||
updateChatVarList(newList)
|
||||
// side effects of rename env
|
||||
if (currentVar.name !== chatVar.name) {
|
||||
const { getNodes, setNodes } = store.getState()
|
||||
const effectedNodes = getEffectedNodes(currentVar)
|
||||
const newNodes = getNodes().map((node) => {
|
||||
if (effectedNodes.find(n => n.id === node.id))
|
||||
return updateNodeVars(node, ['conversation', currentVar.name], ['conversation', chatVar.name])
|
||||
|
||||
return node
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}
|
||||
doSyncWorkflowDraft()
|
||||
}, [currentVar, doSyncWorkflowDraft, getEffectedNodes, store, updateChatVarList, varList])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex flex-col w-[420px] bg-components-panel-bg-alt rounded-l-2xl h-full border border-components-panel-border',
|
||||
)}
|
||||
>
|
||||
<div className='shrink-0 flex items-center justify-between p-4 pb-0 text-text-primary system-xl-semibold'>
|
||||
{t('workflow.chatVariable.panelTitle')}
|
||||
<div className='flex items-center gap-1'>
|
||||
<ActionButton state={showTip ? ActionButtonState.Active : undefined} onClick={() => setShowTip(!showTip)}>
|
||||
<RiBookOpenLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
<div
|
||||
className='flex items-center justify-center w-6 h-6 cursor-pointer'
|
||||
onClick={() => setShowChatVariablePanel(false)}
|
||||
>
|
||||
<RiCloseLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showTip && (
|
||||
<div className='shrink-0 px-3 pt-2.5 pb-2'>
|
||||
<div className='relative p-3 radius-2xl bg-background-section-burn'>
|
||||
<div className='inline-block py-[3px] px-[5px] rounded-[5px] border border-divider-deep text-text-tertiary system-2xs-medium-uppercase'>TIPS</div>
|
||||
<div className='mt-1 mb-4 system-sm-regular text-text-secondary'>
|
||||
{t('workflow.chatVariable.panelDescription')}
|
||||
<a target='_blank' rel='noopener noreferrer' className='text-text-accent' href={locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/guides/workflow/key_concepts#conversation-variables' : `https://docs.dify.ai/v/${locale.toLowerCase()}/guides/workflow/key_concept#hui-hua-bian-liang`}>{t('workflow.chatVariable.docLink')}</a>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='flex flex-col p-3 pb-4 bg-workflow-block-bg radius-lg border border-workflow-block-border shadow-md'>
|
||||
<BubbleX className='shrink-0 mb-1 w-4 h-4 text-util-colors-teal-teal-700' />
|
||||
<div className='text-text-secondary system-xs-semibold'>conversation_var</div>
|
||||
<div className='text-text-tertiary system-2xs-regular'>String</div>
|
||||
</div>
|
||||
<div className='grow'>
|
||||
<div className='mb-2 flex items-center gap-2 py-1'>
|
||||
<div className='shrink-0 flex items-center gap-1 w-16 h-3 px-1'>
|
||||
<LongArrowLeft className='grow h-2 text-text-quaternary' />
|
||||
<div className='shrink-0 text-text-tertiary system-2xs-medium'>WRITE</div>
|
||||
</div>
|
||||
<BlockIcon className='shrink-0' type={BlockEnum.Assigner} />
|
||||
<div className='grow text-text-secondary system-xs-semibold truncate'>{t('workflow.blocks.assigner')}</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-2 py-1'>
|
||||
<div className='shrink-0 flex items-center gap-1 w-16 h-3 px-1'>
|
||||
<div className='shrink-0 text-text-tertiary system-2xs-medium'>READ</div>
|
||||
<LongArrowRight className='grow h-2 text-text-quaternary' />
|
||||
</div>
|
||||
<BlockIcon className='shrink-0' type={BlockEnum.LLM} />
|
||||
<div className='grow text-text-secondary system-xs-semibold truncate'>{t('workflow.blocks.llm')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='absolute z-10 top-[-4px] right-[38px] w-3 h-3 bg-background-section-burn rotate-45'/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className='shrink-0 px-4 pt-2 pb-3'>
|
||||
<VariableModalTrigger
|
||||
open={showVariableModal}
|
||||
setOpen={setShowVariableModal}
|
||||
showTip={showTip}
|
||||
chatVar={currentVar}
|
||||
onSave={handleSave}
|
||||
onClose={() => setCurrentVar(undefined)}
|
||||
/>
|
||||
</div>
|
||||
<div className='grow px-4 rounded-b-2xl overflow-y-auto'>
|
||||
{varList.map(chatVar => (
|
||||
<VariableItem
|
||||
key={chatVar.id}
|
||||
item={chatVar}
|
||||
onEdit={handleEdit}
|
||||
onDelete={deleteCheck}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<RemoveEffectVarConfirm
|
||||
isShow={showRemoveVarConfirm}
|
||||
onCancel={() => setShowRemoveConfirm(false)}
|
||||
onConfirm={() => cacheForDelete && handleDelete(cacheForDelete)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ChatVariablePanel)
|
||||
@@ -0,0 +1,8 @@
|
||||
export enum ChatVarType {
|
||||
Number = 'number',
|
||||
String = 'string',
|
||||
Object = 'object',
|
||||
ArrayString = 'array[string]',
|
||||
ArrayNumber = 'array[number]',
|
||||
ArrayObject = 'array[object]',
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import type { StartNodeType } from '../../nodes/start/types'
|
||||
import Empty from './empty'
|
||||
import UserInput from './user-input'
|
||||
import ConversationVariableModal from './conversation-variable-modal'
|
||||
import { useChat } from './hooks'
|
||||
import type { ChatWrapperRefType } from './index'
|
||||
import Chat from '@/app/components/base/chat/chat'
|
||||
@@ -25,7 +26,13 @@ import {
|
||||
} from '@/service/debug'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
|
||||
const ChatWrapper = forwardRef<ChatWrapperRefType>((_, ref) => {
|
||||
type ChatWrapperProps = {
|
||||
showConversationVariableModal: boolean
|
||||
onConversationModalHide: () => void
|
||||
showInputsFieldsPanel: boolean
|
||||
}
|
||||
|
||||
const ChatWrapper = forwardRef<ChatWrapperRefType, ChatWrapperProps>(({ showConversationVariableModal, onConversationModalHide, showInputsFieldsPanel }, ref) => {
|
||||
const nodes = useNodes<StartNodeType>()
|
||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
const startVariables = startNode?.data.variables
|
||||
@@ -87,33 +94,41 @@ const ChatWrapper = forwardRef<ChatWrapperRefType>((_, ref) => {
|
||||
}, [handleRestart])
|
||||
|
||||
return (
|
||||
<Chat
|
||||
config={{
|
||||
...config,
|
||||
supportCitationHitInfo: true,
|
||||
} as any}
|
||||
chatList={chatList}
|
||||
isResponding={isResponding}
|
||||
chatContainerClassName='px-4'
|
||||
chatContainerInnerClassName='pt-6'
|
||||
chatFooterClassName='px-4 rounded-bl-2xl'
|
||||
chatFooterInnerClassName='pb-4'
|
||||
onSend={doSend}
|
||||
onStopResponding={handleStop}
|
||||
chatNode={(
|
||||
<>
|
||||
<UserInput />
|
||||
{
|
||||
!chatList.length && (
|
||||
<Empty />
|
||||
)
|
||||
}
|
||||
</>
|
||||
<>
|
||||
<Chat
|
||||
config={{
|
||||
...config,
|
||||
supportCitationHitInfo: true,
|
||||
} as any}
|
||||
chatList={chatList}
|
||||
isResponding={isResponding}
|
||||
chatContainerClassName='px-3'
|
||||
chatContainerInnerClassName='pt-6'
|
||||
chatFooterClassName='px-4 rounded-bl-2xl'
|
||||
chatFooterInnerClassName='pb-4'
|
||||
onSend={doSend}
|
||||
onStopResponding={handleStop}
|
||||
chatNode={(
|
||||
<>
|
||||
{showInputsFieldsPanel && <UserInput />}
|
||||
{
|
||||
!chatList.length && (
|
||||
<Empty />
|
||||
)
|
||||
}
|
||||
</>
|
||||
)}
|
||||
suggestedQuestions={suggestedQuestions}
|
||||
showPromptLog
|
||||
chatAnswerContainerInner='!pr-2'
|
||||
/>
|
||||
{showConversationVariableModal && (
|
||||
<ConversationVariableModal
|
||||
conversationID={conversationId}
|
||||
onHide={onConversationModalHide}
|
||||
/>
|
||||
)}
|
||||
suggestedQuestions={suggestedQuestions}
|
||||
showPromptLog
|
||||
chatAnswerContainerInner='!pr-2'
|
||||
/>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
'use client'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useMount } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { capitalize } from 'lodash-es'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import {
|
||||
Clipboard,
|
||||
ClipboardCheck,
|
||||
} from '@/app/components/base/icons/src/vender/line/files'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import type {
|
||||
ConversationVariable,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { fetchCurrentValueOfConversationVariable } from '@/service/workflow'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type Props = {
|
||||
conversationID: string
|
||||
onHide: () => void
|
||||
}
|
||||
|
||||
const ConversationVariableModal = ({
|
||||
conversationID,
|
||||
onHide,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { formatTime } = useTimestamp()
|
||||
const varList = useStore(s => s.conversationVariables) as ConversationVariable[]
|
||||
const appID = useStore(s => s.appId)
|
||||
const [currentVar, setCurrentVar] = React.useState<ConversationVariable>(varList[0])
|
||||
const [latestValueMap, setLatestValueMap] = React.useState<Record<string, string>>({})
|
||||
const [latestValueTimestampMap, setLatestValueTimestampMap] = React.useState<Record<string, number>>({})
|
||||
|
||||
const getChatVarLatestValues = useCallback(async () => {
|
||||
if (conversationID && varList.length > 0) {
|
||||
const res = await fetchCurrentValueOfConversationVariable({
|
||||
url: `/apps/${appID}/conversation-variables`,
|
||||
params: { conversation_id: conversationID },
|
||||
})
|
||||
if (res.data.length > 0) {
|
||||
const valueMap = res.data.reduce((acc: any, cur) => {
|
||||
acc[cur.id] = cur.value
|
||||
return acc
|
||||
}, {})
|
||||
setLatestValueMap(valueMap)
|
||||
const timestampMap = res.data.reduce((acc: any, cur) => {
|
||||
acc[cur.id] = cur.updated_at
|
||||
return acc
|
||||
}, {})
|
||||
setLatestValueTimestampMap(timestampMap)
|
||||
}
|
||||
}
|
||||
}, [appID, conversationID, varList.length])
|
||||
|
||||
const [isCopied, setIsCopied] = React.useState(false)
|
||||
const handleCopy = useCallback(() => {
|
||||
copy(currentVar.value)
|
||||
setIsCopied(true)
|
||||
setTimeout(() => {
|
||||
setIsCopied(false)
|
||||
}, 2000)
|
||||
}, [currentVar.value])
|
||||
|
||||
useMount(() => {
|
||||
getChatVarLatestValues()
|
||||
})
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow
|
||||
onClose={() => { }}
|
||||
className={cn('w-[920px] max-w-[920px] h-[640px] p-0')}
|
||||
>
|
||||
<div className='absolute right-4 top-4 p-2 cursor-pointer' onClick={onHide}>
|
||||
<RiCloseLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='w-full h-full flex'>
|
||||
{/* LEFT */}
|
||||
<div className='shrink-0 flex flex-col w-[224px] h-full bg-background-sidenav-bg border-r border-divider-burn'>
|
||||
<div className='shrink-0 pt-5 pl-5 pr-4 pb-3 text-text-primary system-xl-semibold'>{t('workflow.chatVariable.panelTitle')}</div>
|
||||
<div className='grow overflow-y-auto px-3 py-2'>
|
||||
{varList.map(chatVar => (
|
||||
<div key={chatVar.id} className={cn('group mb-0.5 p-2 flex items-center radius-md hover:bg-state-base-hover cursor-pointer', currentVar.id === chatVar.id && 'bg-state-base-hover')} onClick={() => setCurrentVar(chatVar)}>
|
||||
<BubbleX className={cn('shrink-0 mr-1 w-4 h-4 text-text-tertiary group-hover:text-util-colors-teal-teal-700', currentVar.id === chatVar.id && 'text-util-colors-teal-teal-700')} />
|
||||
<div title={chatVar.name} className={cn('text-text-tertiary system-sm-medium truncate group-hover:text-util-colors-teal-teal-700', currentVar.id === chatVar.id && 'text-util-colors-teal-teal-700')}>{chatVar.name}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* RIGHT */}
|
||||
<div className='grow flex flex-col h-full bg-components-panel-bg'>
|
||||
<div className='shrink-0 p-4 pb-2'>
|
||||
<div className='flex items-center gap-1 py-1'>
|
||||
<div className='text-text-primary system-xl-semibold'>{currentVar.name}</div>
|
||||
<div className='text-text-tertiary system-xs-medium'>{capitalize(currentVar.value_type)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow p-4 pt-2 flex flex-col'>
|
||||
<div className='shrink-0 mb-2 flex items-center gap-2'>
|
||||
<div className='shrink-0 text-text-tertiary system-xs-medium-uppercase'>{t('workflow.chatVariable.storedContent').toLocaleUpperCase()}</div>
|
||||
<div className='grow h-[1px]' style={{
|
||||
background: 'linear-gradient(to right, rgba(16, 24, 40, 0.08) 0%, rgba(255, 255, 255) 100%)',
|
||||
}}></div>
|
||||
{latestValueTimestampMap[currentVar.id] && (
|
||||
<div className='shrink-0 text-text-tertiary system-xs-regular'>{t('workflow.chatVariable.updatedAt')}{formatTime(latestValueTimestampMap[currentVar.id], t('appLog.dateTimeFormat') as string)}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='grow'>
|
||||
{currentVar.value_type !== ChatVarType.Number && currentVar.value_type !== ChatVarType.String && (
|
||||
<div className='h-full flex flex-col bg-components-input-bg-normal rounded-lg px-2 pb-2'>
|
||||
<div className='shrink-0 flex justify-between items-center h-7 pt-1 pl-3 pr-2'>
|
||||
<div className='text-text-secondary system-xs-semibold'>JSON</div>
|
||||
<div className='flex items-center p-1'>
|
||||
{!isCopied
|
||||
? (
|
||||
<Clipboard className='w-4 h-4 text-text-tertiary cursor-pointer' onClick={handleCopy} />
|
||||
)
|
||||
: (
|
||||
<ClipboardCheck className='w-4 h-4 text-text-tertiary' />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow pl-4'>
|
||||
<CodeEditor
|
||||
readOnly
|
||||
noWrapper
|
||||
isExpand
|
||||
language={CodeLanguage.json}
|
||||
value={latestValueMap[currentVar.id] || ''}
|
||||
isJSONStringifyBeauty
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(currentVar.value_type === ChatVarType.Number || currentVar.value_type === ChatVarType.String) && (
|
||||
<div className='h-full px-4 py-3 rounded-lg bg-components-input-bg-normal text-components-input-text-filled system-md-regular overflow-y-auto'>{latestValueMap[currentVar.id] || ''}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConversationVariableModal
|
||||
@@ -35,7 +35,7 @@ export const useChat = (
|
||||
const { notify } = useToastContext()
|
||||
const { handleRun } = useWorkflowRun()
|
||||
const hasStopResponded = useRef(false)
|
||||
const connversationId = useRef('')
|
||||
const conversationID = useRef('')
|
||||
const taskIdRef = useRef('')
|
||||
const [chatList, setChatList] = useState<ChatItem[]>(prevChatList || [])
|
||||
const chatListRef = useRef<ChatItem[]>(prevChatList || [])
|
||||
@@ -100,7 +100,7 @@ export const useChat = (
|
||||
}, [handleResponding, stopChat])
|
||||
|
||||
const handleRestart = useCallback(() => {
|
||||
connversationId.current = ''
|
||||
conversationID.current = ''
|
||||
taskIdRef.current = ''
|
||||
handleStop()
|
||||
const newChatList = config?.opening_statement
|
||||
@@ -185,7 +185,7 @@ export const useChat = (
|
||||
handleResponding(true)
|
||||
|
||||
const bodyParams = {
|
||||
conversation_id: connversationId.current,
|
||||
conversation_id: conversationID.current,
|
||||
...params,
|
||||
}
|
||||
if (bodyParams?.files?.length) {
|
||||
@@ -214,7 +214,7 @@ export const useChat = (
|
||||
}
|
||||
|
||||
if (isFirstMessage && newConversationId)
|
||||
connversationId.current = newConversationId
|
||||
conversationID.current = newConversationId
|
||||
|
||||
taskIdRef.current = taskId
|
||||
if (messageId)
|
||||
@@ -403,7 +403,7 @@ export const useChat = (
|
||||
}, [handleRun, handleResponding, handleUpdateChatList, notify, t, updateCurrentQA, config.suggested_questions_after_answer?.enabled])
|
||||
|
||||
return {
|
||||
conversationId: connversationId.current,
|
||||
conversationId: conversationID.current,
|
||||
chatList,
|
||||
handleSend,
|
||||
handleStop,
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
import {
|
||||
memo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useKeyPress } from 'ahooks'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNodes } from 'reactflow'
|
||||
import {
|
||||
useEdgesInteractions,
|
||||
useNodesInteractions,
|
||||
useWorkflowInteractions,
|
||||
} from '../../hooks'
|
||||
import { BlockEnum } from '../../types'
|
||||
import type { StartNodeType } from '../../nodes/start/types'
|
||||
import ChatWrapper from './chat-wrapper'
|
||||
import cn from '@/utils/classnames'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
|
||||
export type ChatWrapperRefType = {
|
||||
handleRestart: () => void
|
||||
@@ -24,6 +31,13 @@ const DebugAndPreview = () => {
|
||||
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
|
||||
const { handleNodeCancelRunningStatus } = useNodesInteractions()
|
||||
const { handleEdgeCancelRunningStatus } = useEdgesInteractions()
|
||||
const varList = useStore(s => s.conversationVariables)
|
||||
const [expanded, setExpanded] = useState(true)
|
||||
const nodes = useNodes<StartNodeType>()
|
||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
const variables = startNode?.data.variables || []
|
||||
|
||||
const [showConversationVariableModal, setShowConversationVariableModal] = useState(false)
|
||||
|
||||
const handleRestartChat = () => {
|
||||
handleNodeCancelRunningStatus()
|
||||
@@ -40,28 +54,43 @@ const DebugAndPreview = () => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col w-[400px] rounded-l-2xl h-full border border-black/2',
|
||||
'flex flex-col w-[420px] rounded-l-2xl h-full border border-black/2',
|
||||
)}
|
||||
style={{
|
||||
background: 'linear-gradient(156deg, rgba(242, 244, 247, 0.80) 0%, rgba(242, 244, 247, 0.00) 99.43%), var(--white, #FFF)',
|
||||
}}
|
||||
>
|
||||
<div className='shrink-0 flex items-center justify-between pl-4 pr-3 pt-3 pb-2 font-semibold text-gray-900'>
|
||||
{t('workflow.common.debugAndPreview').toLocaleUpperCase()}
|
||||
<div className='flex items-center'>
|
||||
<Button
|
||||
onClick={() => handleRestartChat()}
|
||||
<div className='shrink-0 flex items-center justify-between px-4 pt-3 pb-2 text-text-primary system-xl-semibold'>
|
||||
<div className='h-8'>{t('workflow.common.debugAndPreview').toLocaleUpperCase()}</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<TooltipPlus
|
||||
popupContent={t('common.operation.refresh')}
|
||||
>
|
||||
<RefreshCcw01 className='shrink-0 mr-1 w-3 h-3 text-gray-500' />
|
||||
<div
|
||||
className='grow truncate uppercase'
|
||||
title={t('common.operation.refresh') || ''}
|
||||
<ActionButton onClick={() => handleRestartChat()}>
|
||||
<RefreshCcw01 className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
</TooltipPlus>
|
||||
{varList.length > 0 && (
|
||||
<TooltipPlus
|
||||
popupContent={t('workflow.chatVariable.panelTitle')}
|
||||
>
|
||||
{t('common.operation.refresh')}
|
||||
<ActionButton onClick={() => setShowConversationVariableModal(true)}>
|
||||
<BubbleX className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
</TooltipPlus>
|
||||
)}
|
||||
{variables.length > 0 && (
|
||||
<div className='relative'>
|
||||
<TooltipPlus
|
||||
popupContent={t('workflow.panel.userInputField')}
|
||||
>
|
||||
<ActionButton state={expanded ? ActionButtonState.Active : undefined} onClick={() => setExpanded(!expanded)}>
|
||||
<RiEqualizer2Line className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
</TooltipPlus>
|
||||
{expanded && <div className='absolute z-10 bottom-[-17px] right-[5px] w-3 h-3 bg-components-panel-on-panel-item-bg border-l-[0.5px] border-t-[0.5px] border-components-panel-border-subtle rotate-45'/>}
|
||||
</div>
|
||||
<div className='shrink-0 ml-1 px-1 leading-[18px] rounded-md border border-gray-200 bg-gray-50 text-[11px] text-gray-500 font-medium'>Shift</div>
|
||||
<div className='shrink-0 ml-0.5 px-1 leading-[18px] rounded-md border border-gray-200 bg-gray-50 text-[11px] text-gray-500 font-medium'>R</div>
|
||||
</Button>
|
||||
)}
|
||||
<div className='mx-3 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<div
|
||||
className='flex items-center justify-center w-6 h-6 cursor-pointer'
|
||||
@@ -72,7 +101,12 @@ const DebugAndPreview = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow rounded-b-2xl overflow-y-auto'>
|
||||
<ChatWrapper ref={chatRef} />
|
||||
<ChatWrapper
|
||||
ref={chatRef}
|
||||
showConversationVariableModal={showConversationVariableModal}
|
||||
onConversationModalHide={() => setShowConversationVariableModal(false)}
|
||||
showInputsFieldsPanel={expanded}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import {
|
||||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNodes } from 'reactflow'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import FormItem from '../../nodes/_base/components/before-run-form/form-item'
|
||||
import { BlockEnum } from '../../types'
|
||||
import {
|
||||
@@ -12,11 +9,10 @@ import {
|
||||
useWorkflowStore,
|
||||
} from '../../store'
|
||||
import type { StartNodeType } from '../../nodes/start/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const UserInput = () => {
|
||||
const { t } = useTranslation()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const [expanded, setExpanded] = useState(true)
|
||||
const inputs = useStore(s => s.inputs)
|
||||
const nodes = useNodes<StartNodeType>()
|
||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
@@ -33,46 +29,21 @@ const UserInput = () => {
|
||||
return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
relative rounded-xl border z-[1]
|
||||
${!expanded ? 'bg-indigo-25 border-indigo-100 shadow-none' : 'bg-white shadow-xs border-transparent'}
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex items-center px-2 pt-4 h-[18px] text-[13px] font-semibold cursor-pointer
|
||||
${!expanded ? 'text-indigo-800' : 'text-gray-800'}
|
||||
`}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
>
|
||||
<RiArrowDownSLine
|
||||
className={`mr-1 w-3 h-3 ${!expanded ? '-rotate-90 text-indigo-600' : 'text-gray-300'}`}
|
||||
/>
|
||||
{t('workflow.panel.userInputField').toLocaleUpperCase()}
|
||||
</div>
|
||||
<div className='px-2 pt-1 pb-3'>
|
||||
{
|
||||
expanded && (
|
||||
<div className='py-2 text-[13px] text-gray-900'>
|
||||
{
|
||||
variables.map((variable, index) => (
|
||||
<div
|
||||
key={variable.variable}
|
||||
className='mb-2 last-of-type:mb-0'
|
||||
>
|
||||
<FormItem
|
||||
autoFocus={index === 0}
|
||||
payload={variable}
|
||||
value={inputs[variable.variable]}
|
||||
onChange={v => handleValueChange(variable.variable, v)}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className={cn('relative bg-components-panel-on-panel-item-bg rounded-xl border-[0.5px] border-components-panel-border-subtle shadow-xs z-[1]')}>
|
||||
<div className='px-4 pt-3 pb-4'>
|
||||
{variables.map((variable, index) => (
|
||||
<div
|
||||
key={variable.variable}
|
||||
className='mb-4 last-of-type:mb-0'
|
||||
>
|
||||
<FormItem
|
||||
autoFocus={index === 0}
|
||||
payload={variable}
|
||||
value={inputs[variable.variable]}
|
||||
onChange={v => handleValueChange(variable.variable, v)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
53
web/app/components/workflow/panel/env-panel/env-item.tsx
Normal file
53
web/app/components/workflow/panel/env-panel/env-item.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { memo, useState } from 'react'
|
||||
import { capitalize } from 'lodash-es'
|
||||
import { RiDeleteBinLine, RiEditLine, RiLock2Line } from '@remixicon/react'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import type { EnvironmentVariable } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type EnvItemProps = {
|
||||
env: EnvironmentVariable
|
||||
onEdit: (env: EnvironmentVariable) => void
|
||||
onDelete: (env: EnvironmentVariable) => void
|
||||
}
|
||||
|
||||
const EnvItem = ({
|
||||
env,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}: EnvItemProps) => {
|
||||
const envSecrets = useStore(s => s.envSecrets)
|
||||
const [destructive, setDestructive] = useState(false)
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'mb-1 px-2.5 py-2 bg-components-panel-on-panel-item-bg radius-md border border-components-panel-border-subtle shadow-xs hover:bg-components-panel-on-panel-item-bg-hover',
|
||||
destructive && 'border-state-destructive-border hover:bg-state-destructive-hover',
|
||||
)}>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='grow flex gap-1 items-center'>
|
||||
<Env className='w-4 h-4 text-util-colors-violet-violet-600' />
|
||||
<div className='text-text-primary system-sm-medium'>{env.name}</div>
|
||||
<div className='text-text-tertiary system-xs-medium'>{capitalize(env.value_type)}</div>
|
||||
{env.value_type === 'secret' && <RiLock2Line className='w-3 h-3 text-text-tertiary' />}
|
||||
</div>
|
||||
<div className='shrink-0 flex gap-1 items-center text-text-tertiary'>
|
||||
<div className='p-1 radius-md cursor-pointer hover:bg-state-base-hover hover:text-text-secondary'>
|
||||
<RiEditLine className='w-4 h-4' onClick={() => onEdit(env)}/>
|
||||
</div>
|
||||
<div
|
||||
className='p-1 radius-md cursor-pointer hover:bg-state-destructive-hover hover:text-text-destructive'
|
||||
onMouseOver={() => setDestructive(true)}
|
||||
onMouseOut={() => setDestructive(false)}
|
||||
>
|
||||
<RiDeleteBinLine className='w-4 h-4' onClick={() => onDelete(env)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-text-tertiary system-xs-regular truncate'>{env.value_type === 'secret' ? envSecrets[env.id] : env.value}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(EnvItem)
|
||||
@@ -3,15 +3,14 @@ import {
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { capitalize } from 'lodash-es'
|
||||
import {
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import { RiCloseLine, RiDeleteBinLine, RiEditLine, RiLock2Line } from '@remixicon/react'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import VariableTrigger from '@/app/components/workflow/panel/env-panel/variable-trigger'
|
||||
import EnvItem from '@/app/components/workflow/panel/env-panel/env-item'
|
||||
import type {
|
||||
EnvironmentVariable,
|
||||
} from '@/app/components/workflow/types'
|
||||
@@ -61,6 +60,11 @@ const EnvPanel = () => {
|
||||
setNodes(newNodes)
|
||||
}, [getEffectedNodes, store])
|
||||
|
||||
const handleEdit = (env: EnvironmentVariable) => {
|
||||
setCurrentVar(env)
|
||||
setShowVariableModal(true)
|
||||
}
|
||||
|
||||
const handleDelete = useCallback((env: EnvironmentVariable) => {
|
||||
removeUsedVarInNodes(env)
|
||||
updateEnvList(envList.filter(e => e.id !== env.id))
|
||||
@@ -145,7 +149,7 @@ const EnvPanel = () => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex flex-col w-[400px] bg-components-panel-bg-alt rounded-l-2xl h-full border border-components-panel-border',
|
||||
'relative flex flex-col w-[420px] bg-components-panel-bg-alt rounded-l-2xl h-full border border-components-panel-border',
|
||||
)}
|
||||
>
|
||||
<div className='shrink-0 flex items-center justify-between p-4 pb-0 text-text-primary system-xl-semibold'>
|
||||
@@ -171,31 +175,12 @@ const EnvPanel = () => {
|
||||
</div>
|
||||
<div className='grow px-4 rounded-b-2xl overflow-y-auto'>
|
||||
{envList.map(env => (
|
||||
<div
|
||||
key={env.name}
|
||||
className='mb-1 px-2.5 py-2 bg-components-panel-on-panel-item-bg radius-md border-[0.5px] border-components-panel-border-subtle shadow-xs'
|
||||
>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='grow flex gap-1 items-center'>
|
||||
<Env className='w-4 h-4 text-util-colors-violet-violet-600' />
|
||||
<div className='text-text-primary system-sm-medium'>{env.name}</div>
|
||||
<div className='text-text-tertiary system-xs-medium'>{capitalize(env.value_type)}</div>
|
||||
{env.value_type === 'secret' && <RiLock2Line className='w-3 h-3 text-text-tertiary' />}
|
||||
</div>
|
||||
<div className='shrink-0 flex gap-1 items-center text-text-tertiary'>
|
||||
<div className='p-1 radius-md cursor-pointer hover:bg-state-base-hover hover:text-text-secondary'>
|
||||
<RiEditLine className='w-4 h-4' onClick={() => {
|
||||
setCurrentVar(env)
|
||||
setShowVariableModal(true)
|
||||
}}/>
|
||||
</div>
|
||||
<div className='p-1 radius-md cursor-pointer hover:bg-state-destructive-hover hover:text-text-destructive'>
|
||||
<RiDeleteBinLine className='w-4 h-4' onClick={() => deleteCheck(env)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-text-tertiary system-xs-regular truncate'>{env.value_type === 'secret' ? envSecrets[env.id] : env.value}</div>
|
||||
</div>
|
||||
<EnvItem
|
||||
key={env.id}
|
||||
env={env}
|
||||
onEdit={handleEdit}
|
||||
onDelete={deleteCheck}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<RemoveEffectVarConfirm
|
||||
|
||||
@@ -80,7 +80,7 @@ const VariableModal = ({
|
||||
<div className='px-4 py-2'>
|
||||
{/* type */}
|
||||
<div className='mb-4'>
|
||||
<div className='mb-1 text-text-secondary system-sm-semibold'>{t('workflow.env.modal.type')}</div>
|
||||
<div className='mb-1 h-6 flex items-center text-text-secondary system-sm-semibold'>{t('workflow.env.modal.type')}</div>
|
||||
<div className='flex gap-2'>
|
||||
<div className={cn(
|
||||
'w-[106px] flex items-center justify-center p-2 radius-md bg-components-option-card-option-bg border border-components-option-card-option-border text-text-secondary system-sm-regular cursor-pointer hover:shadow-xs hover:bg-components-option-card-option-bg-hover hover:border-components-option-card-option-border-hover',
|
||||
@@ -111,11 +111,11 @@ const VariableModal = ({
|
||||
</div>
|
||||
{/* name */}
|
||||
<div className='mb-4'>
|
||||
<div className='mb-1 text-text-secondary system-sm-semibold'>{t('workflow.env.modal.name')}</div>
|
||||
<div className='mb-1 h-6 flex items-center text-text-secondary system-sm-semibold'>{t('workflow.env.modal.name')}</div>
|
||||
<div className='flex'>
|
||||
<input
|
||||
tabIndex={0}
|
||||
className='block px-3 w-full h-9 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder'
|
||||
className='block px-3 w-full h-8 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder'
|
||||
placeholder={t('workflow.env.modal.namePlaceholder') || ''}
|
||||
value={name}
|
||||
onChange={e => handleNameChange(e.target.value)}
|
||||
@@ -125,11 +125,11 @@ const VariableModal = ({
|
||||
</div>
|
||||
{/* value */}
|
||||
<div className=''>
|
||||
<div className='mb-1 text-text-secondary system-sm-semibold'>{t('workflow.env.modal.value')}</div>
|
||||
<div className='mb-1 h-6 flex items-center text-text-secondary system-sm-semibold'>{t('workflow.env.modal.value')}</div>
|
||||
<div className='flex'>
|
||||
<input
|
||||
tabIndex={0}
|
||||
className='block px-3 w-full h-9 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder'
|
||||
className='block px-3 w-full h-8 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder'
|
||||
placeholder={t('workflow.env.modal.valuePlaceholder') || ''}
|
||||
value={value}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import VariableModal from '@/app/components/workflow/panel/env-panel/variable-modal'
|
||||
// import cn from '@/utils/classnames'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
|
||||
@@ -13,6 +13,7 @@ import DebugAndPreview from './debug-and-preview'
|
||||
import Record from './record'
|
||||
import WorkflowPreview from './workflow-preview'
|
||||
import ChatRecord from './chat-record'
|
||||
import ChatVariablePanel from './chat-variable-panel'
|
||||
import EnvPanel from './env-panel'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
@@ -25,6 +26,7 @@ const Panel: FC = () => {
|
||||
const historyWorkflowData = useStore(s => s.historyWorkflowData)
|
||||
const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel)
|
||||
const showEnvPanel = useStore(s => s.showEnvPanel)
|
||||
const showChatVariablePanel = useStore(s => s.showChatVariablePanel)
|
||||
const isRestoring = useStore(s => s.isRestoring)
|
||||
const {
|
||||
enableShortcuts,
|
||||
@@ -90,6 +92,11 @@ const Panel: FC = () => {
|
||||
<EnvPanel />
|
||||
)
|
||||
}
|
||||
{
|
||||
showChatVariablePanel && (
|
||||
<ChatVariablePanel />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
} from './help-line/types'
|
||||
import type { VariableAssignerNodeType } from './nodes/variable-assigner/types'
|
||||
import type {
|
||||
ConversationVariable,
|
||||
Edge,
|
||||
EnvironmentVariable,
|
||||
HistoryWorkflowData,
|
||||
@@ -21,6 +22,24 @@ import type {
|
||||
} from './types'
|
||||
import { WorkflowContext } from './context'
|
||||
|
||||
// #TODO chatVar#
|
||||
// const MOCK_DATA = [
|
||||
// {
|
||||
// id: 'fjlaksdjflkjg-dfjlajfl0dnfkafjk-djfdkafj-djfak',
|
||||
// name: 'chat_history',
|
||||
// value_type: 'array[message]',
|
||||
// value: [],
|
||||
// description: 'The chat history of the conversation',
|
||||
// },
|
||||
// {
|
||||
// id: 'fljdaklfjl-dfjlafj0-dklajglje-eknglh',
|
||||
// name: 'order_id',
|
||||
// value: '123456',
|
||||
// value_type: 'string',
|
||||
// description: '',
|
||||
// },
|
||||
// ]
|
||||
|
||||
type PreviewRunningData = WorkflowRunningData & {
|
||||
resultTabActive?: boolean
|
||||
resultText?: string
|
||||
@@ -90,6 +109,10 @@ type Shape = {
|
||||
setEnvironmentVariables: (environmentVariables: EnvironmentVariable[]) => void
|
||||
envSecrets: Record<string, string>
|
||||
setEnvSecrets: (envSecrets: Record<string, string>) => void
|
||||
showChatVariablePanel: boolean
|
||||
setShowChatVariablePanel: (showChatVariablePanel: boolean) => void
|
||||
conversationVariables: ConversationVariable[]
|
||||
setConversationVariables: (conversationVariables: ConversationVariable[]) => void
|
||||
selection: null | { x1: number; y1: number; x2: number; y2: number }
|
||||
setSelection: (selection: Shape['selection']) => void
|
||||
bundleNodeSize: { width: number; height: number } | null
|
||||
@@ -204,6 +227,10 @@ export const createWorkflowStore = () => {
|
||||
setEnvironmentVariables: environmentVariables => set(() => ({ environmentVariables })),
|
||||
envSecrets: {},
|
||||
setEnvSecrets: envSecrets => set(() => ({ envSecrets })),
|
||||
showChatVariablePanel: false,
|
||||
setShowChatVariablePanel: showChatVariablePanel => set(() => ({ showChatVariablePanel })),
|
||||
conversationVariables: [],
|
||||
setConversationVariables: conversationVariables => set(() => ({ conversationVariables })),
|
||||
selection: null,
|
||||
setSelection: selection => set(() => ({ selection })),
|
||||
bundleNodeSize: null,
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/
|
||||
import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import type { Collection, Tool } from '@/app/components/tools/types'
|
||||
import type { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
||||
|
||||
export enum BlockEnum {
|
||||
Start = 'start',
|
||||
@@ -25,6 +26,7 @@ export enum BlockEnum {
|
||||
Tool = 'tool',
|
||||
ParameterExtractor = 'parameter-extractor',
|
||||
Iteration = 'iteration',
|
||||
Assigner = 'assigner', // is now named as VariableAssigner
|
||||
}
|
||||
|
||||
export type Branch = {
|
||||
@@ -109,6 +111,14 @@ export type EnvironmentVariable = {
|
||||
value_type: 'string' | 'number' | 'secret'
|
||||
}
|
||||
|
||||
export type ConversationVariable = {
|
||||
id: string
|
||||
name: string
|
||||
value_type: ChatVarType
|
||||
value: any
|
||||
description: string
|
||||
}
|
||||
|
||||
export type VariableWithValue = {
|
||||
key: string
|
||||
value: string
|
||||
@@ -132,6 +142,7 @@ export type InputVar = {
|
||||
nodeType: BlockEnum
|
||||
nodeName: string
|
||||
variable: string
|
||||
isChatVar?: boolean
|
||||
}
|
||||
variable: string
|
||||
max_length?: number
|
||||
@@ -194,6 +205,7 @@ export enum VarType {
|
||||
boolean = 'boolean',
|
||||
object = 'object',
|
||||
array = 'array',
|
||||
file = 'file',
|
||||
arrayString = 'array[string]',
|
||||
arrayNumber = 'array[number]',
|
||||
arrayObject = 'array[object]',
|
||||
@@ -209,6 +221,7 @@ export type Var = {
|
||||
isSelect?: boolean
|
||||
options?: string[]
|
||||
required?: boolean
|
||||
des?: string
|
||||
}
|
||||
|
||||
export type NodeOutPutVar = {
|
||||
|
||||
@@ -142,6 +142,12 @@ button:focus-within {
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.system-2xs-regular {
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.system-2xs-medium {
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -99,6 +99,33 @@ const translation = {
|
||||
export: 'Export DSL with secret values ',
|
||||
},
|
||||
},
|
||||
chatVariable: {
|
||||
panelTitle: 'Conversation Variables',
|
||||
panelDescription: 'Conversation Variables are used to store interactive information that LLM needs to remember, including conversation history, uploaded files, user preferences. They are read-write. ',
|
||||
docLink: 'Visit our docs to learn more.',
|
||||
button: 'Add Variable',
|
||||
modal: {
|
||||
title: 'Add Conversation Variable',
|
||||
editTitle: 'Edit Conversation Variable',
|
||||
name: 'Name',
|
||||
namePlaceholder: 'Variable name',
|
||||
type: 'Type',
|
||||
value: 'Default Value',
|
||||
valuePlaceholder: 'Default value, leave blank to not set',
|
||||
description: 'Description',
|
||||
descriptionPlaceholder: 'Describe the variable',
|
||||
editInJSON: 'Edit in JSON',
|
||||
oneByOne: 'Add one by one',
|
||||
editInForm: 'Edit in Form',
|
||||
arrayValue: 'Value',
|
||||
addArrayValue: 'Add Value',
|
||||
objectKey: 'Key',
|
||||
objectType: 'Type',
|
||||
objectValue: 'Default Value',
|
||||
},
|
||||
storedContent: 'Stored content',
|
||||
updatedAt: 'Updated at ',
|
||||
},
|
||||
changeHistory: {
|
||||
title: 'Change History',
|
||||
placeholder: 'You haven\'t changed anything yet',
|
||||
@@ -173,6 +200,7 @@ const translation = {
|
||||
'http-request': 'HTTP Request',
|
||||
'variable-assigner': 'Variable Aggregator',
|
||||
'variable-aggregator': 'Variable Aggregator',
|
||||
'assigner': 'Variable Assigner',
|
||||
'iteration-start': 'Iteration Start',
|
||||
'iteration': 'Iteration',
|
||||
'parameter-extractor': 'Parameter Extractor',
|
||||
@@ -189,6 +217,7 @@ const translation = {
|
||||
'template-transform': 'Convert data to string using Jinja template syntax',
|
||||
'http-request': 'Allow server requests to be sent over the HTTP protocol',
|
||||
'variable-assigner': 'Aggregate multi-branch variables into a single variable for unified configuration of downstream nodes.',
|
||||
'assigner': 'TODO',
|
||||
'variable-aggregator': 'Aggregate multi-branch variables into a single variable for unified configuration of downstream nodes.',
|
||||
'iteration': 'Perform multiple steps on a list object until all results are outputted.',
|
||||
'parameter-extractor': 'Use LLM to extract structured parameters from natural language for tool invocations or HTTP requests.',
|
||||
@@ -215,6 +244,7 @@ const translation = {
|
||||
checklistResolved: 'All issues are resolved',
|
||||
organizeBlocks: 'Organize blocks',
|
||||
change: 'Change',
|
||||
optional: '(optional)',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
@@ -406,6 +436,16 @@ const translation = {
|
||||
},
|
||||
setAssignVariable: 'Set assign variable',
|
||||
},
|
||||
assigner: {
|
||||
'assignedVariable': 'Assigned Variable',
|
||||
'writeMode': 'Write Mode',
|
||||
'over-write': 'Overwrite',
|
||||
'append': 'Append',
|
||||
'plus': 'Plus',
|
||||
'clear': 'Clear',
|
||||
'setVariable': 'Set Variable',
|
||||
'variable': 'Variable',
|
||||
},
|
||||
tool: {
|
||||
toAuthorize: 'To authorize',
|
||||
inputVars: 'Input Variables',
|
||||
|
||||
@@ -99,6 +99,33 @@ const translation = {
|
||||
export: '导出包含 Secret 值的 DSL',
|
||||
},
|
||||
},
|
||||
chatVariable: {
|
||||
panelTitle: '会话变量',
|
||||
panelDescription: '会话变量用于存储 LLM 需要的上下文信息,如用户偏好、对话历史等。它是可读写的。',
|
||||
docLink: '查看文档了解更多。',
|
||||
button: '添加变量',
|
||||
modal: {
|
||||
title: '添加会话变量',
|
||||
editTitle: '编辑会话变量',
|
||||
name: '名称',
|
||||
namePlaceholder: '变量名',
|
||||
type: '类型',
|
||||
value: '默认值',
|
||||
valuePlaceholder: '默认值,可以为空',
|
||||
description: '描述',
|
||||
descriptionPlaceholder: '变量的描述',
|
||||
editInJSON: '在 JSON 中编辑',
|
||||
oneByOne: '逐个添加',
|
||||
editInForm: '在表单中编辑',
|
||||
arrayValue: '值',
|
||||
addArrayValue: '添加值',
|
||||
objectKey: '属性',
|
||||
objectType: '类型',
|
||||
objectValue: '默认值',
|
||||
},
|
||||
storedContent: '存储内容',
|
||||
updatedAt: '更新时间 ',
|
||||
},
|
||||
changeHistory: {
|
||||
title: '变更历史',
|
||||
placeholder: '尚未更改任何内容',
|
||||
@@ -173,6 +200,7 @@ const translation = {
|
||||
'http-request': 'HTTP 请求',
|
||||
'variable-assigner': '变量聚合器',
|
||||
'variable-aggregator': '变量聚合器',
|
||||
'assigner': '变量赋值',
|
||||
'iteration-start': '迭代开始',
|
||||
'iteration': '迭代',
|
||||
'parameter-extractor': '参数提取器',
|
||||
@@ -189,6 +217,7 @@ const translation = {
|
||||
'template-transform': '使用 Jinja 模板语法将数据转换为字符串',
|
||||
'http-request': '允许通过 HTTP 协议发送服务器请求',
|
||||
'variable-assigner': '将多路分支的变量聚合为一个变量,以实现下游节点统一配置。',
|
||||
'assigner': 'TODO',
|
||||
'variable-aggregator': '将多路分支的变量聚合为一个变量,以实现下游节点统一配置。',
|
||||
'iteration': '对列表对象执行多次步骤直至输出所有结果。',
|
||||
'parameter-extractor': '利用 LLM 从自然语言内推理提取出结构化参数,用于后置的工具调用或 HTTP 请求。',
|
||||
@@ -215,6 +244,7 @@ const translation = {
|
||||
checklistResolved: '所有问题均已解决',
|
||||
organizeBlocks: '整理节点',
|
||||
change: '更改',
|
||||
optional: '(选填)',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
@@ -406,6 +436,16 @@ const translation = {
|
||||
},
|
||||
setAssignVariable: '设置赋值变量',
|
||||
},
|
||||
assigner: {
|
||||
'assignedVariable': '赋值的变量',
|
||||
'writeMode': '写入模式',
|
||||
'over-write': '覆盖',
|
||||
'append': '追加',
|
||||
'plus': '加',
|
||||
'clear': '清空',
|
||||
'setVariable': '设置变量',
|
||||
'variable': '变量',
|
||||
},
|
||||
tool: {
|
||||
toAuthorize: '授权',
|
||||
inputVars: '输入变量',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { get, post } from './base'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import type {
|
||||
ChatRunHistoryResponse,
|
||||
ConversationVariableResponse,
|
||||
FetchWorkflowDraftResponse,
|
||||
NodesDefaultConfigsResponse,
|
||||
WorkflowRunHistoryResponse,
|
||||
@@ -13,7 +14,7 @@ export const fetchWorkflowDraft = (url: string) => {
|
||||
return get(url, {}, { silent: true }) as Promise<FetchWorkflowDraftResponse>
|
||||
}
|
||||
|
||||
export const syncWorkflowDraft = ({ url, params }: { url: string; params: Pick<FetchWorkflowDraftResponse, 'graph' | 'features' | 'environment_variables'> }) => {
|
||||
export const syncWorkflowDraft = ({ url, params }: { url: string; params: Pick<FetchWorkflowDraftResponse, 'graph' | 'features' | 'environment_variables' | 'conversation_variables'> }) => {
|
||||
return post<CommonResponse & { updated_at: number; hash: string }>(url, { body: params }, { silent: true })
|
||||
}
|
||||
|
||||
@@ -58,3 +59,7 @@ export const fetchNodeDefault = (appId: string, blockType: BlockEnum, query = {}
|
||||
export const updateWorkflowDraftFromDSL = (appId: string, data: string) => {
|
||||
return post<FetchWorkflowDraftResponse>(`apps/${appId}/workflows/draft/import`, { body: { data } })
|
||||
}
|
||||
|
||||
export const fetchCurrentValueOfConversationVariable: Fetcher<ConversationVariableResponse, { url: string; params: { conversation_id: string } }> = ({ url, params }) => {
|
||||
return get<ConversationVariableResponse>(url, { params })
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Viewport } from 'reactflow'
|
||||
import type {
|
||||
BlockEnum,
|
||||
ConversationVariable,
|
||||
Edge,
|
||||
EnvironmentVariable,
|
||||
Node,
|
||||
@@ -58,6 +59,7 @@ export type FetchWorkflowDraftResponse = {
|
||||
updated_at: number
|
||||
tool_published: boolean
|
||||
environment_variables?: EnvironmentVariable[]
|
||||
conversation_variables?: ConversationVariable[]
|
||||
}
|
||||
|
||||
export type NodeTracingListResponse = {
|
||||
@@ -240,3 +242,11 @@ export type NodesDefaultConfigsResponse = {
|
||||
type: string
|
||||
config: any
|
||||
}[]
|
||||
|
||||
export type ConversationVariableResponse = {
|
||||
data: (ConversationVariable & { updated_at: number; created_at: number })[]
|
||||
has_more: boolean
|
||||
limit: number
|
||||
total: number
|
||||
page: number
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user