# 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 配置流程

## 开发环境
- 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 (
}
onClick={() => {
console.log(ichat.getChatMessages()); // 获取当前所有聊天记录,当点击时输出所有聊天对象
likeTip();
}}
>
}
onClick={() => {
console.log(ichat.getChatMessages());
disLikeTip();
}}
>
);
}
```
## 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;
}
}
/>
}
);
}
```