# antd-demo **Repository Path**: Chenchangda123/antd-demo ## Basic Information - **Project Name**: antd-demo - **Description**: Pro-Chat - **Primary Language**: Unknown - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-11-10 - **Last Updated**: 2024-12-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: reactjs, ant, Ant-Design, AI, 前端框架 ## README ## React Ant Pro-Chat UI 配置流程 ![演示](imgs/yanshi.gif) ## 开发环境 - Node 版本不低于: **v20.10.0** - 安装 React脚手架: ```shell npm install -g create-react-app ``` - 使用React脚手架创建一个React项目环境,名字叫“reactapp” ```shell npx create-react-app reactapp --template typescript ``` ```shell C:\Data\Study\ReactDemo>npx create-react-app reactapp --template typescript Creating a new React app in C:\Data\Study\ReactDemo\reactapp. Installing packages. This might take a couple of minutes. Installing react, react-dom, and react-scripts with cra-template-typescript... added 1312 packages in 1m 259 packages are looking for funding run `npm fund` for details Initialized a git repository. Installing template dependencies using npm... added 30 packages, removed 1 package, and changed 2 packages in 8s 263 packages are looking for funding run `npm fund` for details We detected TypeScript in your project (src\App.test.tsx) and created a tsconfig.json file for you. Your tsconfig.json has been populated with default values. Removing template package using npm... removed 1 package, and audited 1341 packages in 5s 263 packages are looking for funding run `npm fund` for details 8 vulnerabilities (2 moderate, 6 high) To address all issues (including breaking changes), run: npm audit fix --force Run `npm audit` for details. Created git commit. Success! Created reactapp at C:\Data\Study\ReactDemo\reactapp Inside that directory, you can run several commands: npm start Starts the development server. npm run build Bundles the app into static files for production. npm test Starts the test runner. npm run eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back! We suggest that you begin by typing: cd reactapp npm start Happy hacking! ``` - 进入到项目文件夹中: ```shell C:\Data\Study\ReactDemo>cd reactapp C:\Data\Study\ReactDemo\reactapp> ``` - 查看项目文件 ```shell C:\Data\Study\ReactDemo>cd reactapp C:\Data\Study\ReactDemo\reactapp>dir 驱动器 C 中的卷是 Windows 卷的序列号是 146B-0324 C:\Data\Study\ReactDemo\reactapp 的目录 2024/11/09 11:11 . 2024/11/09 11:10 .. 2024/11/09 11:11 310 .gitignore 2024/11/09 11:12 node_modules 2024/11/09 11:12 659,917 package-lock.json 2024/11/09 11:12 969 package.json 2024/11/09 11:11 public 2024/11/09 11:11 2,117 README.md 2024/11/09 11:11 src 2024/11/09 11:11 561 tsconfig.json 5 个文件 663,874 字节 5 个目录 342,677,094,400 可用字节 ``` - 安装Pro-Chat依赖: ```shell npm install @ant-design/pro-chat --save npm install antd-style --save npm install @ant-design/icons --save ``` **以上步骤完成后就可以开始写代码了** 将git代码仓库中的饿'src'文件夹,替换掉你新创建项目中的scr文件夹。 【注意】Pro-Chat框架是基于前端框架Ant Design,两者的组件可以共用。 [Pro-Chat](https://pro-chat.antdigital.dev/) 官网 [Ant Design](https://ant.design/index-cn) 官网 ## 核心代码详情 ### index.tsx 此为项目入口文件 ```tsx 'use client'; import ReactDOM from 'react-dom/client'; // 引入React DOM操作核心 import Vanna from './Vanna'; // 引入另一个代码文件Vanna.tsx,此文件中有一个函数名叫 Vanna。在React中称为模板 const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); root.render( // 调用Vanna模板 ) ``` ### Vanna.tsx 此为模板文件自己创建编写,非环境创建 ```tsx 'use client'; import { useState, useEffect } from 'react'; import { ProChat } from '@ant-design/pro-chat'; import { useTheme } from 'antd-style'; import { Card } from 'antd'; import RPATable from './Table' import RPAButton from './Button'; export default function Vanna() { const theme = useTheme(); const [showComponent, setShowComponent] = useState(false); useEffect(() => setShowComponent(true), []); // 判断值是否为JSON数据 const isJSON = (str: string): boolean => { try { const parsed = JSON.parse(str); return typeof parsed === 'object' && parsed !== null; }catch(error){ return false; } } return (
{ showComponent && , defaultDom: React.ReactNode) { if(item?.originData?.role === 'assistant' && isJSON(item?.originData?.content)) { const str = (item?.originData?.content).toString(); return } return defaultDom; }, }} actions={{ // 自定义 render: (defaultDoms) => { return [ , ...defaultDoms, ]; }, flexConfig: { gap: 24, direction: 'horizontal', justify: 'space-between', }, }} request={ async (messages) => { const lastMessage = messages[messages.length - 1]; const content = lastMessage.content?.toString() || ""; const formData = new FormData(); formData.append('message', content); const response = await fetch('/api/qwen', { method: 'POST', body: formData }); const data = await response.json(); // 检查返回的内容是否为 JSON const responseContent = data['content']; return responseContent; } } /> }
); } ``` ### Table.tsx 此为模板文件自己创建编写,非环境创建。将Ant中的Table UI封装为一个React模板,在Vanna.tsx中引用,并嵌入Pro-Chat主页之中。 ```tsx import { Table } from "antd"; import type { TableColumnsType } from 'antd'; import { createStyles } from 'antd-style'; const useStyle = createStyles(({ css }) => { return { customTable: css` .ant-table { .ant-table-container { .ant-table-body, .ant-table-content { scrollbar-width: thin; scrollbar-color: #eaeaea transparent; scrollbar-gutter: stable; } } } `, }; }); interface RPATableProps { content: string; } interface DataType { key: React.Key; name: string; age: number; address: string; } const columns: TableColumnsType = [ { title: 'Column 1', dataIndex: 'address', key: '1' }, { title: 'Column 2', dataIndex: 'address', key: '2' }, { title: 'Column 3', dataIndex: 'address', key: '3' }, { title: 'Column 4', dataIndex: 'address', key: '4' }, { title: 'Column 5', dataIndex: 'address', key: '5' }, { title: 'Column 6', dataIndex: 'address', key: '6' }, { title: 'Column 7', dataIndex: 'address', key: '7' }, { title: 'Column 8', dataIndex: 'address', key: '8' }, { title: 'Column 9', dataIndex: 'address', key: '9' }, { title: 'Column 10', dataIndex: 'address', key: '10' }, { title: 'Column 11', dataIndex: 'address', key: '11' }, { title: 'Column 12', dataIndex: 'address', key: '12' }, { title: 'Column 13', dataIndex: 'address', key: '13' }, { title: 'Column 14', dataIndex: 'address', key: '14' }, { title: 'Column 15', dataIndex: 'address', key: '15' }, { title: 'Column 16', dataIndex: 'address', key: '16' }, { title: 'Column 17', dataIndex: 'address', key: '17' }, { title: 'Column 18', dataIndex: 'address', key: '18' }, { title: 'Column 19', dataIndex: 'address', key: '19' }, { title: 'Column 20', dataIndex: 'address', key: '20' }, ]; const dataSource: DataType[] = [ { key: '1', name: 'Olivia', age: 32, address: 'New York Park' }, { key: '2', name: 'Ethan', age: 40, address: 'London Park' }, ]; export default function RPATable({content}: RPATableProps) { // const dataSource = JSON.parse(content); // let columns = []; // for (let key in dataSource[0]) { // if (dataSource[0].hasOwnProperty(key)) { // 确保是对象自身的属性 // let dic = { // title: key, // dataIndex: key, // 修正拼写错误 // key: key, // }; // columns.push(dic); // } // } // console.log(content) const { styles } = useStyle(); return() } ``` ### Button.tsx 此为模板文件自己创建编写,非环境创建。将Ant中的Button UI封装为一个React模板,在Vanna.tsx中引用,并嵌入Pro-Chat主页中。 ```tsx import { useProChat } from '@ant-design/pro-chat'; import { Button, Tooltip, Flex, message } from 'antd'; import { LikeOutlined, DislikeOutlined } from '@ant-design/icons'; // 引入Ant中的图标组件 // 点击点赞按钮时调用的函数,触发感谢提示 const likeTip = () => { message.success("😆 Thanks!!"); } // 点击点踩按钮时调用的函数,触发抱歉提示 const disLikeTip = () => { message.success("😅 Sorry!!"); } export default function RPAButton() { const ichat = useProChat(); // 获取当前聊天全局对象 return ( ); } ``` ## Python API ### Gemini.py ```python import google.generativeai as genai genai.configure(api_key="XXXXXXXXXXX-XXXXXX") generation_config = { "temperature": 0.8, "top_p": 0.95, "top_k": 32, "max_output_tokens": 8192, "response_mime_type": "text/plain", } safety_settings = [ { "category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_LOW_AND_ABOVE", }, { "category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_LOW_AND_ABOVE", }, { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_LOW_AND_ABOVE", }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_LOW_AND_ABOVE", }, ] model = genai.GenerativeModel( model_name="gemini-1.5-flash-latest", safety_settings=safety_settings, generation_config=generation_config, system_instruction="你是一个问答专家!你会中文和英文!" ) history = [ ] chat_session = model.start_chat( history=history ) ``` ### webapi.py ```python from flask import Flask, request, send_from_directory from Gemini import chat_session import os,json app = Flask(__name__, static_folder="build", static_url_path="") # API 路由 @app.route('/api/qwen', methods=['POST']) def qwen(): content = request.form.get('message') # chat_session.history = messages print(content) dic = [{ 'key': '1', 'name': '胡彦斌', 'age': 32, 'address': '西湖区湖底公园1号', }, { 'key': '2', 'name': '胡彦祖', 'age': 42, 'address': '西湖区湖底公园1号', },{ 'key': '3', 'name': '胡建国', 'age': 42, 'address': '西湖区湖底公园1号', },{ 'key': '4', 'name': '马斯克', 'age': 42, 'address': '西湖区湖底公园1号', }] if content == '1': res = { "role": "assistant", "content": json.dumps(dic) } else: res = { "role": "assistant", "content": chat_session.send_message(content).text } return json.dumps(res) # 提供前端静态文件的路由 @app.route('/') def serve(): return send_from_directory(app.static_folder, 'index.html') # 处理前端路由路径,避免刷新页面导致 404 错误 @app.route('/') def serve_static_files(path): # 检查请求的文件是否存在于 build 目录 if os.path.exists(os.path.join(app.static_folder, path)): return send_from_directory(app.static_folder, path) # 如果文件不存在,则返回 index.html 交由前端处理 return send_from_directory(app.static_folder, 'index.html') if __name__ == '__main__': app.run(port=90) ``` ### nginx.conf ```nginx server { listen 82; server_name 127.0.0.1; root "C:/Data/Study/ReactDemo/antd-demo/build"; index index.html; location / { try_files $uri /index.html; # 不要尝试 $uri/ 避免误解析目录 } # 直接在根路径下支持所有静态资源 location ~* \.(ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg|otf|webp|mp4|m4v|webm|wav|mp3|m4a|ogg|oga|flac|aac|pdf|doc|docx|xls|xlsx|ppt|pptx|zip|rar|7z|gz|tar|tgz|bz2)$ { expires 6M; access_log off; add_header Cache-Control "public"; } # API 代理配置 location /api/qwen { proxy_pass http://127.0.0.1:90; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 补充 MIME 类型 include mime.types; default_type application/octet-stream; } ``` ### 未使用更新代码 根据浏览器窗口大小设置对应Table气泡大小 ```tsx 'use client'; import { useState, useEffect } from 'react'; import { ProChat } from '@ant-design/pro-chat'; import { useTheme } from 'antd-style'; import { Card } from 'antd'; import RPATable from './Table' import RPAButton from './Button'; export default function Vanna() { const theme = useTheme(); const [showComponent, setShowComponent] = useState(false); const [cardWidth, setCardWidth] = useState(650); useEffect(() => setShowComponent(true), []); useEffect(() => { setShowComponent(true); // 定义一个函数来动态设置卡片宽度 const updateCardWidth = () => { const windowWidth = window.innerWidth; setCardWidth(windowWidth * 0.8); // 设置为窗口宽度的 1/3 }; // 页面加载时和窗口大小改变时更新宽度 updateCardWidth(); window.addEventListener('resize', updateCardWidth); // 清理事件监听 return () => { window.removeEventListener('resize', updateCardWidth); }; }, []); // 判断值是否为JSON数据 const isJSON = (str: string): boolean => { try { const parsed = JSON.parse(str); return typeof parsed === 'object' && parsed !== null; }catch(error){ return false; } } return (
{ showComponent && , defaultDom: React.ReactNode) { if(item?.originData?.role === 'assistant' && isJSON(item?.originData?.content)) { const str = (item?.originData?.content).toString(); return } return // return defaultDom; }, }} actions={{ // 自定义 render: (defaultDoms) => { return [ , ...defaultDoms, ]; }, flexConfig: { gap: 24, direction: 'horizontal', justify: 'space-between', }, }} request={ async (messages) => { const lastMessage = messages[messages.length - 1]; const content = lastMessage.content?.toString() || ""; const formData = new FormData(); formData.append('message', content); const response = await fetch('/api/qwen', { method: 'POST', body: formData }); const data = await response.json(); // 检查返回的内容是否为 JSON const responseContent = data['content']; return responseContent; } } /> }
); } ```