Skip to content

[Feat] Add GenAI Studio UI improvement #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions studio-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,11 @@
}
]
]
},
"dependencies": {
"@opentelemetry/exporter-trace-otlp-grpc": "^0.57.2",
"@opentelemetry/exporter-trace-otlp-proto": "^0.57.2",
"@opentelemetry/sdk-trace-node": "^1.30.1",
"react-toastify": "^11.0.5"
}
}
4 changes: 4 additions & 0 deletions studio-frontend/packages/server/src/nodes/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Attribution

In this folder, we use icons from
<a href="https://www.flaticon.com/free-icons/filter" title="filter icons"> Filter icons created by juicy_fish, Vectorslab, Prosymbols Premium, Freeplk, SmashIcons, orvlpixel, FACH - Flaticon</a> which pictures files with suffix `*.png`
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class OPEAChatCompletion {
this.name = 'chat_completion'
this.version = 1.0
this.type = 'ChatCompletion'
this.icon = 'opea-icon-color.svg'
this.icon = 'controls.png'
this.category = 'Controls'
this.description = 'Send Chat Response to UI'
this.baseClasses = []
Expand Down
2 changes: 1 addition & 1 deletion studio-frontend/packages/server/src/nodes/chat_input.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class OPEAChatInput {
this.name = 'chat_input'
this.version = 1.0
this.type = 'ChatCompletionRequest'
this.icon = 'opea-icon-color.svg'
this.icon = 'controls.png'
this.category = 'Controls'
this.description = 'User Input from Chat Window'
this.baseClasses = [this.type]
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion studio-frontend/packages/server/src/nodes/doc_input.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class OPEADocInput {
this.name = 'doc_input'
this.version = 1.0
this.type = 'UploadFile'
this.icon = 'opea-icon-color.svg'
this.icon = 'controls.png'
this.category = 'Controls'
this.description = 'User Input from Document Upload'
this.baseClasses = [this.type]
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion studio-frontend/packages/server/src/nodes/llm.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added studio-frontend/packages/server/src/nodes/llm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class OPEARedisVectorStore {
this.name = 'redis_vector_store'
this.version = 1.0
this.type = 'EmbedDoc'
this.icon = 'opea-icon-color.svg'
this.icon = 'vector stores.png'
this.category = 'VectorStores'
this.description = 'Redis Vector Store'
this.baseClasses = [this.type]
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class OPEARedisRetreiver {
this.name = 'opea_service@retriever_redis'
this.version = 1.0
this.type = 'SearchedDoc'
this.icon = 'opea-icon-color.svg'
this.icon = 'retreiver.png'
this.category = 'Retreiver'
this.description = 'Redis Retreiver with Langchain'
this.baseClasses = [this.type, 'RetrievalResponse', 'ChatCompletionRequest']
Expand Down
2 changes: 1 addition & 1 deletion studio-frontend/packages/server/src/nodes/tei_embedding.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion studio-frontend/packages/server/src/nodes/tei_reranking.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class OPEAReranking {
this.name = 'opea_service@reranking_tei'
this.version = 1.0
this.type = 'LLMParamsDoc'
this.icon = 'opea-icon-color.svg'
this.icon = 'reranking.png'
this.category = 'Reranking'
this.description = 'TEI Reranking'
this.baseClasses = [this.type, 'RerankingResponse', 'ChatCompletionRequest']
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 64 additions & 24 deletions studio-frontend/packages/ui/src/views/canvas/ButtonEdge.jsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,77 @@
import { getBezierPath, EdgeText } from 'reactflow'
import PropTypes from 'prop-types'
import { useDispatch } from 'react-redux'
import { useContext } from 'react'
import { SET_DIRTY } from '@/store/actions'
import { flowContext } from '@/store/context/ReactFlowContext'
import { useState, useEffect } from 'react';
import { getBezierPath, EdgeText } from 'reactflow';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import { useContext } from 'react';
import { SET_DIRTY } from '@/store/actions';
import { flowContext } from '@/store/context/ReactFlowContext';
import './index.css';

import './index.css'
const foreignObjectSize = 40;

const foreignObjectSize = 40
const ButtonEdge = ({
id,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
style = {},
data,
}) => {
const [isAnimating, setIsAnimating] = useState(true);

useEffect(() => {
const timeout = setTimeout(() => {
setIsAnimating(false);
}, 1000);
return () => clearTimeout(timeout);
}, []);

const ButtonEdge = ({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style = {}, data, markerEnd }) => {
const [edgePath, edgeCenterX, edgeCenterY] = getBezierPath({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition
})

const { deleteEdge } = useContext(flowContext)
targetPosition,
});

const dispatch = useDispatch()
const { deleteEdge } = useContext(flowContext);
const dispatch = useDispatch();

const onEdgeClick = (evt, id) => {
evt.stopPropagation()
deleteEdge(id)
dispatch({ type: SET_DIRTY })
}
evt.stopPropagation();
deleteEdge(id);
dispatch({ type: SET_DIRTY });
};

const markerEnd = 'url(#arrow)';
return (
<>
<path id={id} style={style} className='react-flow__edge-path' d={edgePath} markerEnd={markerEnd} />
<defs>
<marker
id="arrow"
markerWidth="10"
markerHeight="10"
refX="9"
refY="3"
orient="auto"
markerUnits="strokeWidth"
>
<path d="M0,0 L0,6 L9,3 z" fill="#000" />
</marker>
</defs>

<path
id={id}
style={style}
className={`react-flow__edge-path ${isAnimating ? 'animated' : ''}`}
d={edgePath}
markerEnd={markerEnd}
/>

{data && data.label && (
<EdgeText
x={sourceX + 10}
Expand All @@ -43,6 +83,7 @@ const ButtonEdge = ({ id, sourceX, sourceY, targetX, targetY, sourcePosition, ta
labelBgBorderRadius={2}
/>
)}

<foreignObject
width={foreignObjectSize}
height={foreignObjectSize}
Expand All @@ -58,8 +99,8 @@ const ButtonEdge = ({ id, sourceX, sourceY, targetX, targetY, sourcePosition, ta
</div>
</foreignObject>
</>
)
}
);
};

ButtonEdge.propTypes = {
id: PropTypes.string,
Expand All @@ -71,7 +112,6 @@ ButtonEdge.propTypes = {
targetPosition: PropTypes.any,
style: PropTypes.object,
data: PropTypes.object,
markerEnd: PropTypes.any
}
};

export default ButtonEdge
export default ButtonEdge;
38 changes: 27 additions & 11 deletions studio-frontend/packages/ui/src/views/canvas/NodeInputHandler.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import PromptLangsmithHubDialog from '@/ui-component/dialog/PromptLangsmithHubDi
import ManageScrapedLinksDialog from '@/ui-component/dialog/ManageScrapedLinksDialog'
import CredentialInputHandler from './CredentialInputHandler'
import InputHintDialog from '@/ui-component/dialog/InputHintDialog'

import showToast from './ShowToast'
// utils
import { getInputVariables, getCustomConditionOutputs, isValidConnection, getAvailableNodesForVariable } from '@/utils/genericHelper'

Expand Down Expand Up @@ -96,7 +96,7 @@ const NodeInputHandler = ({
const [showConditionDialog, setShowConditionDialog] = useState(false)
const [conditionDialogProps, setConditionDialogProps] = useState({})
const [tabValue, setTabValue] = useState(0)

const [isHovered, setIsHovered] = useState(false);
const onInputHintDialogClicked = (hint) => {
const dialogProps = {
...hint
Expand Down Expand Up @@ -444,13 +444,21 @@ const NodeInputHandler = ({
position={Position.Left}
key={inputAnchor.id}
id={inputAnchor.id}
isValidConnection={(connection) => isValidConnection(connection, reactFlowInstance)}
isValidConnection={(connection) => {
const isValid = isValidConnection(connection, reactFlowInstance);
if (!isValid) {
showToast("This is an invalid connection");
}
return isValid;
}}
style={{
height: 10,
width: 10,
height: isHovered ? 13 : 10,
width: isHovered ? 13 : 10,
backgroundColor: data.selected ? theme.palette.primary.main : theme.palette.text.secondary,
top: position
top: position,
}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
/>
</CustomWidthTooltip>
<Box sx={{ p: 2 }}>
Expand All @@ -472,13 +480,21 @@ const NodeInputHandler = ({
position={Position.Left}
key={inputParam.id}
id={inputParam.id}
isValidConnection={(connection) => isValidConnection(connection, reactFlowInstance)}
isValidConnection={(connection) => {
const isValid = isValidConnection(connection, reactFlowInstance);
if (!isValid) {
showToast("This is an invalid connection");
}
return isValid;
}}
style={{
height: 10,
width: 10,
height: isHovered? 13 : 10,
width: isHovered? 13 : 10,
backgroundColor: data.selected ? theme.palette.primary.main : theme.palette.text.secondary,
top: position
top: position,
}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
/>
</CustomWidthTooltip>
)}
Expand Down Expand Up @@ -861,4 +877,4 @@ NodeInputHandler.propTypes = {
onHideNodeInfoDialog: PropTypes.func
}

export default NodeInputHandler
export default NodeInputHandler
Loading