diff --git a/frontend/.windsurfrules/global_rules.md b/frontend/.windsurfrules similarity index 100% rename from frontend/.windsurfrules/global_rules.md rename to frontend/.windsurfrules diff --git a/frontend/packages/core/src/pages/playground/index.tsx b/frontend/packages/core/src/pages/playground/index.tsx index 8d902c26..1fe7cc70 100644 --- a/frontend/packages/core/src/pages/playground/index.tsx +++ b/frontend/packages/core/src/pages/playground/index.tsx @@ -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 = ({ title, status, defaultModel, onSettingClick }) => ( +
+
+
+ + {title} +
+
+ + +
+
+
{defaultModel}
+
+) + +interface KeyStatusCardProps { + keys: Array<'success' | 'failure'> + title: string +} + +const KeyStatusCard: React.FC = ({ keys, title }) => ( +
+
+ {title} +
+
+ {keys.map((status, index) => ( +
+ ))} +
+
+) + +const ServiceCard: React.FC = () => ( +
+
+ + AI Service +
+
+) 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: } }, { - id: '2', - position: { x: 100, y: 200 }, - data: { label: 'Node 2' } + id: 'openai', + type: 'custom', + position: { x: 400, y: 50 }, + data: { + component: + } }, { - id: '3', - position: { x: 100, y: 300 }, - data: { label: 'Node 3' }, - type: 'output' + id: 'anthropic', + type: 'custom', + position: { x: 400, y: 200 }, + data: { + component: + } + }, + { + id: 'gemini', + type: 'custom', + position: { x: 400, y: 350 }, + data: { + component: + } + }, + { + id: 'openai-keys', + type: 'custom', + position: { x: 750, y: 50 }, + data: { + component: + } + }, + { + id: 'anthropic-keys', + type: 'custom', + position: { x: 750, y: 200 }, + data: { + component: + } + }, + { + id: 'gemini-keys', + type: 'custom', + position: { x: 750, y: 350 }, + data: { + component: + } } ] 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 ( -
+
{ > -
) diff --git a/frontend/packages/core/src/pages/playground/styles.css b/frontend/packages/core/src/pages/playground/styles.css new file mode 100644 index 00000000..b482a35e --- /dev/null +++ b/frontend/packages/core/src/pages/playground/styles.css @@ -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); + } +}