乐于分享
好东西不私藏

Coze Studio 源码解读(七):前端架构与组件设计

Coze Studio 源码解读(七):前端架构与组件设计

Coze Studio 源码解读(七):前端架构与组件设计

深入理解 React 前端的设计与实现

一、前端架构概述

1.1 Monorepo 架构

Coze Studio 前端采用 Rush 管理的 Monorepo 架构,支持多应用和共享包的统一管理。

Monorepo 结构

frontend/
├── apps/                    # 应用入口
│   └── coze-studio/        # 主应用
│       ├── src/
│       │   ├── pages/      # 页面
│       │   ├── components/ # 组件
│       │   ├── hooks/      # Hooks
│       │   ├── stores/     # 状态
│       │   └── utils/      # 工具
│       └── package.json

├── packages/               # 共享包
│   ├── ui/                # UI 组件库
│   ├── workflow/          # 工作流组件
│   ├── chat-sdk/          # Chat SDK
│   └── shared/            # 共享工具

├── config/                # 配置
├── infra/                 # 基础设施
└── rush.json             # Rush 配置

1.2 技术栈

前端技术栈

框架
├── React 18
├── TypeScript 5
└── Vite

状态管理
├── Zustand (轻量状态)
├── React Query (服务端状态)
└── Context API

UI 组件
├── 自研组件库
├── Tailwind CSS
└── Framer Motion

工具链
├── Rush (Monorepo)
├── pnpm (包管理)
└── ESLint + Prettier

二、状态管理

2.1 Zustand Store

// stores/workflowStore.ts
import { create } from 'zustand';

interface WorkflowState {
  // 状态
  nodesNode[];
  edgesEdge[];
  selectedNodestring | null;
  
  // 操作
  addNode(nodeNode) => void;
  updateNode(idstringdataPartial<Node>) => void;
  deleteNode(idstring) => void;
  setSelectedNode(idstring | null) => void;
  
  // 边操作
  addEdge(edgeEdge) => void;
  deleteEdge(idstring) => void;
}

export const useWorkflowStore = create<WorkflowState>((set) => ({
  nodes: [],
  edges: [],
  selectedNodenull,
  
  addNode(node) => set((state) => ({
    nodes: [...state.nodes, node],
  })),
  
  updateNode(id, data) => set((state) => ({
    nodes: state.nodes.map((node) =>
      node.id === id ? { ...node, ...data } : node
    ),
  })),
  
  deleteNode(id) => set((state) => ({
    nodes: state.nodes.filter((node) => node.id !== id),
    edges: state.edges.filter(
      (edge) => edge.source !== id && edge.target !== id
    ),
  })),
  
  setSelectedNode(id) => set({ selectedNode: id }),
  
  addEdge(edge) => set((state) => ({
    edges: [...state.edges, edge],
  })),
  
  deleteEdge(id) => set((state) => ({
    edges: state.edges.filter((edge) => edge.id !== id),
  })),
}));

2.2 React Query

// hooks/useBots.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// 获取 Agent 列表
export function useBots() {
  return useQuery({
    queryKey: ['bots'],
    queryFn() => api.get('/bots'),
  });
}

// 获取单个 Agent
export function useBot(idstring) {
  return useQuery({
    queryKey: ['bots', id],
    queryFn() => api.get(`/bots/${id}`),
    enabled: !!id,
  });
}

// 创建 Agent
export function useCreateBot() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn(dataCreateBotRequest) => api.post('/bots', data),
    onSuccess() => {
      queryClient.invalidateQueries({ queryKey: ['bots'] });
    },
  });
}

// 更新 Agent
export function useUpdateBot(idstring) {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn(dataUpdateBotRequest) => api.put(`/bots/${id}`, data),
    onSuccess() => {
      queryClient.invalidateQueries({ queryKey: ['bots'] });
      queryClient.invalidateQueries({ queryKey: ['bots', id] });
    },
  });
}

三、工作流画布

3.1 FlowGram.ai 集成

// components/WorkflowCanvas.tsx
import { FlowEditor } from '@flowgram.ai';

interface WorkflowCanvasProps {
  workflowIdstring;
  readOnly?: boolean;
}

export function WorkflowCanvas({ workflowId, readOnly }: WorkflowCanvasProps) {
  const { data: workflow } = useWorkflow(workflowId);
  const updateWorkflow = useUpdateWorkflow(workflowId);
  
  const handleNodesChange = useCallback((nodesNode[]) => {
    updateWorkflow.mutate({ nodes });
  }, [updateWorkflow]);
  
  const handleEdgesChange = useCallback((edgesEdge[]) => {
    updateWorkflow.mutate({ edges });
  }, [updateWorkflow]);
  
  return (
    <FlowEditor
      nodes={workflow?.nodes}
      edges={workflow?.edges}
      onNodesChange={handleNodesChange}
      onEdgesChange={handleEdgesChange}
      readOnly={readOnly}
      nodeTypes={customNodeTypes}
      edgeTypes={customEdgeTypes}
    />

  );
}

3.2 自定义节点

// components/nodes/LLMNode.tsx
import { memo } from 'react';
import { HandlePositionNodeProps } from '@flowgram.ai';

interface LLMNodeData {
  modelstring;
  systemPromptstring;
  temperaturenumber;
}

export const LLMNode = memo(({ data, selected }: NodeProps<LLMNodeData>) => {
  return (
    <div className={`node llm-node ${selected ? 'selected: ''}`}>
      <Handle type="target" position={Position.Left} />
      
      <div className="node-header">
        <span className="node-icon">🤖</span>
        <span className="node-title">LLM</span>
      </div>
      
      <div className="node-content">
        <div className="field">
          <label>模型</label>
          <span>{data.model}</span>
        </div>
        <div className="field">
          <label>温度</label>
          <span>{data.temperature}</span>
        </div>
      </div>
      
      <Handle type="source" position={Position.Right} />
    </div>

  );
});

// 注册节点类型
const customNodeTypes = {
  llmLLMNode,
  codeCodeNode,
  conditionConditionNode,
  knowledgeKnowledgeNode,
};

四、组件设计

4.1 组件层次

组件层次结构

页面级组件 (Pages)
├── AgentListPage
├── AgentDetailPage
├── WorkflowEditorPage
└── KnowledgePage

功能组件 (Features)
├── AgentForm
├── WorkflowCanvas
├── ChatPanel
└── KnowledgeUploader

UI 组件 (UI)
├── Button
├── Input
├── Modal
├── Table
└── Form

4.2 组件设计原则

// 组件设计示例

// 1. Props 类型定义
interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  loading?: boolean;
  disabled?: boolean;
  childrenReact.ReactNode;
  onClick?: () => void;
}

// 2. 组件实现
export function Button({
  variant = 'primary',
  size = 'md',
  loading = false,
  disabled = false,
  children,
  onClick,
}: ButtonProps
) {
  const classNames = cn(
    'button',
    `button--${variant}`,
    `button--${size}`,
    {
      'button--loading': loading,
      'button--disabled': disabled,
    }
  );
  
  return (
    <button
      className={classNames}
      disabled={disabled || loading}
      onClick={onClick}
    >

      {loading ? <Spinner size="sm" /> : children}
    </button>

  );
}

// 3. 样式
// button.module.css
.button {
  @apply inline-flex items-center justify-center rounded-md font-medium;
}

.button--primary {
  @apply bg-blue-500 text-white hover:bg-blue-600;
}

.button--secondary {
  @apply bg-gray-100 text-gray-900 hover:bg-gray-200;
}

.button--sm { @apply px-3 py-1.5 text-sm; }
.button--md { @apply px-4 py-2 text-base; }
.button--lg { @apply px-6 py-3 text-lg; }

五、聊天界面

5.1 聊天组件

// components/Chat/ChatPanel.tsx
export function ChatPanel({ botId }: { botId: string }) {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState('');
  const [isStreaming, setIsStreaming] = useState(false);
  
  const sendMessage = useCallback(async () => {
    if (!input.trim() || isStreaming) return;
    
    const userMessageMessage = {
      iduuid(),
      role'user',
      content: input,
    };
    
    setMessages((prev) => [...prev, userMessage]);
    setInput('');
    setIsStreaming(true);
    
    try {
      // 流式请求
      const response = await fetch(`/api/chat/stream?bot_id=${botId}`, {
        method'POST',
        bodyJSON.stringify({ message: input }),
      });
      
      const reader = response.body?.getReader();
      const decoder = new TextDecoder();
      
      let assistantMessageMessage = {
        iduuid(),
        role'assistant',
        content'',
      };
      
      setMessages((prev) => [...prev, assistantMessage]);
      
      while (true) {
        const { done, value } = await reader!.read();
        if (done) break;
        
        const chunk = decoder.decode(value);
        const data = JSON.parse(chunk.replace('data: '''));
        
        assistantMessage.content += data.content;
        setMessages((prev) => [...prev.slice(0, -1), assistantMessage]);
      }
    } finally {
      setIsStreaming(false);
    }
  }, [input, botId, isStreaming]);
  
  return (
    <div className="chat-panel">
      <MessageList messages={messages} />
      <ChatInput
        value={input}
        onChange={setInput}
        onSend={sendMessage}
        disabled={isStreaming}
      />

    </div>

  );
}

5.2 消息渲染

// components/Chat/MessageList.tsx
export function MessageList({ messages }: { messages: Message[] }) {
  const listRef = useRef<HTMLDivElement>(null);
  
  // 自动滚动到底部
  useEffect(() => {
    listRef.current?.scrollTo({
      top: listRef.current.scrollHeight,
      behavior'smooth',
    });
  }, [messages]);
  
  return (
    <div ref={listRef} className="message-list">
      {messages.map((message) => (
        <MessageItem key={message.id} message={message} />
      ))}
    </div>

  );
}

// components/Chat/MessageItem.tsx
export function MessageItem({ message }: { message: Message }) {
  const isUser = message.role === 'user';
  
  return (
    <div className={`message-item ${isUser ? 'user: 'assistant'}`}>
      {!isUser && <Avatar icon="🤖" />}
      <div className="message-content">
        <MarkdownRenderer content={message.content} />
      </div>
      {isUser && <Avatar icon="👤" />}
    </div>

  );
}

六、性能优化

6.1 代码分割

// 路由级代码分割
import { lazy, Suspense } from 'react';

const AgentListPage = lazy(() => import('./pages/AgentListPage'));
const AgentDetailPage = lazy(() => import('./pages/AgentDetailPage'));
const WorkflowEditorPage = lazy(() => import('./pages/WorkflowEditorPage'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/agents" element={<AgentListPage />} />
        <Route path="/agents/:id" element={<AgentDetailPage />} />
        <Route path="/workflows/:id" element={<WorkflowEditorPage />} />
      </Routes>
    </Suspense>

  );
}

6.2 虚拟列表

// 使用 react-window 实现虚拟列表
import { FixedSizeList } from 'react-window';

interface VirtualListProps {
  itemsany[];
  renderItem(itemany) => React.ReactNode;
  itemHeightnumber;
}

export function VirtualList({ items, renderItem, itemHeight }: VirtualListProps) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {renderItem(items[index])}
    </div>

  );
  
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={itemHeight}
      width="100%"
    >

      {Row}
    </FixedSizeList>

  );
}

七、本章小结

7.1 知识点回顾

本章要点

1. 前端架构
   ├── Monorepo 结构
   └── 技术栈选择

2. 状态管理
   ├── Zustand Store
   └── React Query

3. 工作流画布
   ├── FlowGram.ai 集成
   └── 自定义节点

4. 组件设计
   ├── 组件层次
   └── 设计原则

5. 聊天界面
   ├── 流式响应
   └── 消息渲染

6. 性能优化
   ├── 代码分割
   └── 虚拟列表

下一节预告

下一节我们将学习:部署与二次开发指南

内容包括:

  • 生产部署
  • 性能优化
  • 自定义扩展
  • 最佳实践

软件定制开发,微信联系:rustgopy