mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
feat: flow chart
This commit is contained in:
@@ -39,15 +39,18 @@ interface ApiResponse {
|
||||
const calculateNodePositions = (models: ModelData[], startY = LAYOUT.NODE_START_Y, gap = LAYOUT.NODE_GAP) => {
|
||||
return models.reduce(
|
||||
(acc, model, index) => {
|
||||
acc[model.id] = {
|
||||
x: LAYOUT.MODEL_NODE_X,
|
||||
y: startY + index * gap
|
||||
const y = startY + index * gap
|
||||
return {
|
||||
...acc,
|
||||
[model.id]: {
|
||||
x: LAYOUT.MODEL_NODE_X,
|
||||
y
|
||||
},
|
||||
[`${model.id}-keys`]: {
|
||||
x: LAYOUT.KEY_NODE_X,
|
||||
y
|
||||
}
|
||||
}
|
||||
acc[`${model.id}-keys`] = {
|
||||
x: LAYOUT.KEY_NODE_X,
|
||||
y: startY + index * gap
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, { x: number; y: number }>
|
||||
)
|
||||
@@ -83,17 +86,24 @@ const AIFlowChart = () => {
|
||||
useEffect(() => {
|
||||
if (!modelData.length) return
|
||||
|
||||
const positions = calculateNodePositions(modelData)
|
||||
// subtract 5 to make sure the service node is above the top model node
|
||||
const serviceY = positions[modelData[0].id].y - 5
|
||||
|
||||
const newNodes = [
|
||||
{
|
||||
id: 'apiService',
|
||||
type: 'serviceCard',
|
||||
position: { x: LAYOUT.SERVICE_NODE_X, y: LAYOUT.NODE_START_Y },
|
||||
data: {}
|
||||
position: { x: LAYOUT.SERVICE_NODE_X, y: serviceY },
|
||||
data: {
|
||||
title: 'API Service',
|
||||
count: modelData.length
|
||||
}
|
||||
},
|
||||
...modelData.map((model) => ({
|
||||
id: model.id,
|
||||
type: 'modelCard',
|
||||
position: calculateNodePositions(modelData)[model.id],
|
||||
position: positions[model.id],
|
||||
data: {
|
||||
title: model.name,
|
||||
status: model.status,
|
||||
@@ -104,7 +114,7 @@ const AIFlowChart = () => {
|
||||
...modelData.map((model) => ({
|
||||
id: `${model.id}-keys`,
|
||||
type: 'keyCard',
|
||||
position: calculateNodePositions(modelData)[`${model.id}-keys`],
|
||||
position: positions[`${model.id}-keys`],
|
||||
data: {
|
||||
title: 'API Keys',
|
||||
keys: model.keys.map((key, index) => ({
|
||||
@@ -121,14 +131,15 @@ const AIFlowChart = () => {
|
||||
id: `service-${model.id}`,
|
||||
source: 'apiService',
|
||||
target: model.id,
|
||||
label: `apis(${model.api_count})`,
|
||||
markerEnd: { type: 'arrow' },
|
||||
data: { id: model.id }
|
||||
label: `${model.api_count} apis`,
|
||||
data: { id: model.id },
|
||||
animated: true
|
||||
})),
|
||||
...modelData.map((model) => ({
|
||||
id: `${model.id}-keys-edge`,
|
||||
source: model.id,
|
||||
target: `${model.id}-keys`,
|
||||
label: `${model.key_count} keys`,
|
||||
animated: true
|
||||
}))
|
||||
]
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function CustomEdge({
|
||||
target="_blank"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
transform: `translate(${targetX - 80}px,${targetY}px)`,
|
||||
transform: `translate(${targetX - 80}px,${targetY - 20}px)`,
|
||||
borderRadius: '4px',
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
|
||||
@@ -20,7 +20,7 @@ export const ModelCardNode: React.FC<{ data: ModelCardNodeData }> = ({ data }) =
|
||||
const { title, status, defaultModel, logo } = data
|
||||
return (
|
||||
<div
|
||||
className="node-card bg-white rounded-lg shadow-sm p-4 min-w-[280px] relative group"
|
||||
className="node-card bg-white rounded-lg shadow-sm p-4 min-w-[280px] group"
|
||||
style={{ border: '1px solid var(--border-color)' }}
|
||||
>
|
||||
<Handle type="target" position={Position.Left} />
|
||||
|
||||
@@ -5,7 +5,7 @@ import React from 'react'
|
||||
export const ServiceCardNode: React.FC<NodeProps> = () => {
|
||||
return (
|
||||
<div
|
||||
className="node-card bg-white rounded-lg shadow-sm p-4 min-w-[150px] relative nodrag"
|
||||
className="node-card bg-white rounded-lg shadow-sm p-4 min-w-[150px] nodrag"
|
||||
style={{ border: '1px solid var(--border-color)' }}
|
||||
>
|
||||
<Handle type="source" position={Position.Right} />
|
||||
|
||||
@@ -3,5 +3,5 @@ export const LAYOUT = {
|
||||
NODE_START_Y: 50,
|
||||
NODE_GAP: 120,
|
||||
MODEL_NODE_X: 500,
|
||||
KEY_NODE_X: 850,
|
||||
KEY_NODE_X: 900,
|
||||
} as const;
|
||||
|
||||
@@ -6,39 +6,6 @@
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
.react-flow__edge-path {
|
||||
stroke: var(--primary-color);
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.react-flow__edge-text {
|
||||
font-size: 12px;
|
||||
fill: #666;
|
||||
}
|
||||
|
||||
.react-flow__controls {
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.react-flow__controls-button {
|
||||
border: none;
|
||||
background: white;
|
||||
border-bottom: 1px solid #ededed;
|
||||
box-sizing: content-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.react-flow__controls-button:hover {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
/* Custom Node Styles */
|
||||
.custom-node {
|
||||
|
||||
Reference in New Issue
Block a user