mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
feat: basic card component
This commit is contained in:
@@ -1,46 +1,147 @@
|
||||
'use client'
|
||||
|
||||
import { Icon } from '@iconify/react'
|
||||
import type { Edge, Node } from '@xyflow/react'
|
||||
import { addEdge, Background, Controls, MiniMap, ReactFlow, useEdgesState, useNodesState } from '@xyflow/react'
|
||||
import { addEdge, Background, Controls, ReactFlow, useEdgesState, useNodesState } from '@xyflow/react'
|
||||
import '@xyflow/react/dist/style.css'
|
||||
import React, { useCallback } from 'react'
|
||||
import './styles.css'
|
||||
|
||||
interface ModelCardProps {
|
||||
title: string
|
||||
status: 'success' | 'failure'
|
||||
defaultModel: string
|
||||
onSettingClick?: () => void
|
||||
}
|
||||
|
||||
const ModelCard: React.FC<ModelCardProps> = ({ title, status, defaultModel, onSettingClick }) => (
|
||||
<div className="bg-white rounded-lg shadow-sm p-4 w-[280px] border border-[#ededed]">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Icon icon="mdi:robot" className="text-xl text-[--primary-color]" />
|
||||
<span className="font-medium text-gray-800">{title}</span>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<span
|
||||
className={`w-2 h-2 rounded-full ${status === 'success' ? 'bg-green-500' : 'bg-red-500'}`}
|
||||
title={status === 'success' ? 'Connected' : 'Disconnected'}
|
||||
/>
|
||||
<Icon
|
||||
icon="mdi:cog"
|
||||
className="text-xl text-gray-400 cursor-pointer hover:text-[--primary-color]"
|
||||
onClick={onSettingClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">{defaultModel}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
interface KeyStatusCardProps {
|
||||
keys: Array<'success' | 'failure'>
|
||||
title: string
|
||||
}
|
||||
|
||||
const KeyStatusCard: React.FC<KeyStatusCardProps> = ({ keys, title }) => (
|
||||
<div className="bg-white rounded-lg shadow-sm p-4 w-[280px] border border-[#ededed]">
|
||||
<div className="flex items-center mb-3">
|
||||
<span className="text-sm font-medium text-gray-700">{title}</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{keys.map((status, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`aspect-square rounded-md ${status === 'success' ? 'bg-green-500' : 'bg-red-500'}`}
|
||||
title={status === 'success' ? 'Active' : 'Inactive'}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const ServiceCard: React.FC = () => (
|
||||
<div className="bg-white rounded-lg shadow-sm p-4 w-[200px] border border-[#ededed]">
|
||||
<div className="flex flex-col gap-3 items-center">
|
||||
<Icon icon="mdi:robot" className="text-3xl text-[--primary-color]" />
|
||||
<span className="font-medium text-gray-800">AI Service</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
position: { x: 100, y: 100 },
|
||||
data: { label: 'Node 1' },
|
||||
type: 'input'
|
||||
id: 'service',
|
||||
type: 'custom',
|
||||
position: { x: 50, y: 100 },
|
||||
data: { component: <ServiceCard /> }
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
position: { x: 100, y: 200 },
|
||||
data: { label: 'Node 2' }
|
||||
id: 'openai',
|
||||
type: 'custom',
|
||||
position: { x: 400, y: 50 },
|
||||
data: {
|
||||
component: <ModelCard title="OpenAI" status="success" defaultModel="gpt-4" />
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
position: { x: 100, y: 300 },
|
||||
data: { label: 'Node 3' },
|
||||
type: 'output'
|
||||
id: 'anthropic',
|
||||
type: 'custom',
|
||||
position: { x: 400, y: 200 },
|
||||
data: {
|
||||
component: <ModelCard title="Anthropic" status="success" defaultModel="claude-2" />
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'gemini',
|
||||
type: 'custom',
|
||||
position: { x: 400, y: 350 },
|
||||
data: {
|
||||
component: <ModelCard title="Google Gemini" status="failure" defaultModel="gemini-pro" />
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'openai-keys',
|
||||
type: 'custom',
|
||||
position: { x: 750, y: 50 },
|
||||
data: {
|
||||
component: <KeyStatusCard title="API Keys" keys={['success', 'success', 'failure', 'success']} />
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'anthropic-keys',
|
||||
type: 'custom',
|
||||
position: { x: 750, y: 200 },
|
||||
data: {
|
||||
component: <KeyStatusCard title="API Keys" keys={['success', 'success', 'success']} />
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'gemini-keys',
|
||||
type: 'custom',
|
||||
position: { x: 750, y: 350 },
|
||||
data: {
|
||||
component: <KeyStatusCard title="API Keys" keys={['failure', 'failure']} />
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2' },
|
||||
{ id: 'e2-3', source: '2', target: '3' }
|
||||
{ id: 'service-openai', source: 'service', target: 'openai', animated: true, label: 'Connected' },
|
||||
{ id: 'service-anthropic', source: 'service', target: 'anthropic', animated: true, label: 'Connected' },
|
||||
{ id: 'service-gemini', source: 'service', target: 'gemini', animated: true, label: 'Disconnected' },
|
||||
{ id: 'openai-keys', source: 'openai', target: 'openai-keys', animated: true },
|
||||
{ id: 'anthropic-keys', source: 'anthropic', target: 'anthropic-keys', animated: true },
|
||||
{ id: 'gemini-keys', source: 'gemini', target: 'gemini-keys', animated: true }
|
||||
]
|
||||
|
||||
const Playground: React.FC = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)
|
||||
|
||||
const onConnect = useCallback(
|
||||
(params: any) => {
|
||||
setEdges((eds) => addEdge(params, eds))
|
||||
},
|
||||
[setEdges]
|
||||
)
|
||||
const onConnect = useCallback((params: any) => setEdges((eds) => addEdge(params, eds)), [setEdges])
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%', height: '600px' }}>
|
||||
<div className="w-full h-screen bg-gray-50">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
@@ -51,7 +152,6 @@ const Playground: React.FC = () => {
|
||||
>
|
||||
<Background />
|
||||
<Controls />
|
||||
<MiniMap />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/* Flow Chart Styles */
|
||||
.react-flow__node {
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.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 {
|
||||
background: white;
|
||||
border: 1px solid #ededed;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.custom-node:hover {
|
||||
box-shadow: 0 0 0 1px var(--primary-color);
|
||||
}
|
||||
|
||||
/* API Key Status Grid */
|
||||
.api-key-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.api-key-status {
|
||||
aspect-ratio: 1;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.api-key-status:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Responsive Breakpoints */
|
||||
@media (max-width: 768px) {
|
||||
.custom-node {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.api-key-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.custom-node {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.api-key-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user