feat: admin image

This commit is contained in:
archer
2023-06-10 00:35:26 +08:00
parent 2378615887
commit e19ac56fe5
21 changed files with 6045 additions and 416 deletions

3
.gitignore vendored
View File

@@ -28,4 +28,5 @@ next-env.d.ts
platform.json
testApi/
local/
.husky/
.husky/
dist/

View File

@@ -3,5 +3,4 @@
"editor.mouseWheelZoom": true,
"typescript.tsdk": "./client/node_modules/typescript/lib",
"prettier.prettierPath": "./node_modules/prettier"
}

11
admin/.dockerignore Normal file
View File

@@ -0,0 +1,11 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.git
.yalc/
yalc.lock
testApi/
node_modules

6
admin/.env.template Normal file
View File

@@ -0,0 +1,6 @@
MONGODB_URI=mongodb://username:psw@0.0.0.0:27017/?authSource=admin
MONGODB_NAME=fastgpt
ADMIN_USER=username
ADMIN_PASS=password
ADMIN_SECRET=any
VITE_PUBLIC_SERVER_URL=http://localhost:3001 # 和server.js一致

1
admin/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules/

48
admin/Dockerfile Normal file
View File

@@ -0,0 +1,48 @@
# Install dependencies only when needed
FROM node:current-alpine AS builder
RUN npm config set registry https://registry.npmmirror.com/
RUN apk add --no-cache libc6-compat && npm install -g pnpm
RUN pnpm config set registry https://registry.npmmirror.com/
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED 1
ENV VITE_PUBLIC_SERVER_URL ''
# Install dependencies based on the preferred package manager
COPY . .
RUN \
[ -f pnpm-lock.yaml ] && pnpm install || \
(echo "Lockfile not found." && exit 1)
RUN pnpm build
# Production image, copy all the files and run next
FROM node:current-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add curl \
&& apk add ca-certificates \
&& update-ca-certificates
COPY package.json pnpm-lock.yaml* ./
COPY --from=builder /app/server.js ./server.js
COPY --from=builder /app/service ./service
COPY --from=builder /app/dist ./dist
RUN npm config set registry https://registry.npmmirror.com/
RUN npm install -g pnpm
RUN pnpm config set registry https://registry.npmmirror.com/
RUN pnpm install --prod
RUN npm remove -g pnpm
ENV PORT=3001
EXPOSE 3001
CMD ["node", "server.js"]

View File

@@ -1,15 +1,41 @@
## fastgpt-admin
原作地址https://github.com/c121914yu/FastGPT/
# FastGpt Admin
## 项目原理
使用tushan项目做前端然后构造了一个与mongodb做沟通的API做后端可以做到创建、修改和删除用户
## 使用方法
1. 修改根目录下的server.js文件中的mongodb数据库连接地址。
2. pnpm i && pnpm dev
3. 默认账号密码为tushan
使用 tushan 项目做前端,然后构造了一个与 mongodb 做沟通的 API 做后端,可以做到创建、修改和删除用户
## 开发
## 可能会有的功能
1. 对接数据库中的其他数据
2. tokens充值功能
1. 复制 .env.template 文件,添加环境变量
2. pnpm i
3. pnpm dev
## 部署
1. 本地打包
`docker build -t registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-admin:latest . --network host --build-arg HTTP_PROXY=http://127.0.0.1:7890 --build-arg HTTPS_PROXY=http://127.0.0.1:7890`
2. 直接拉镜像: `registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-admin:latest`
3. 部署时候填写环境变量: 数据库同 FastGpt 一致
```
MONGODB_URI=mongodb://username:psw@0.0.0.0:27017/?authSource=admin
MONGODB_NAME=fastgpt
ADMIN_USER=username
ADMIN_PASS=password
ADMIN_SECRET=any
VITE_PUBLIC_SERVER_URL=http://localhost:3001 # 和server.js一致
```
## sealos 部署
1. 进入 sealos 官网: https://cloud.sealos.io/
2. 打开 App Launchpad(应用管理) 工具
3. 新建应用
1. 镜像名: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-admin:latest
2. 容器端口: 3001
3. 环境变量: 参考上面
4. 打开外网访问开关
4. 点击部署。 完成后大约等待 1 分钟,
5. 点击 sealos 提供的外网访问地址,可以直接访问。

View File

@@ -8,23 +8,31 @@
"dev": "concurrently \"vite\" \"npm run start:api\"",
"build": "tsc && vite build",
"preview": "vite preview",
"start:api": "node server.js"
"start:api": "nodemon server.js"
},
"dependencies": {
"concurrently": "^8.1.0",
"cors": "^2.8.5",
"dayjs": "^1.11.8",
"dotenv": "^16.1.4",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.0",
"mongoose": "^7.2.2",
"react": "^18.2.0",
"react-admin": "^4.11.0",
"react-dom": "^18.2.0",
"tushan": "^0.2.13"
"tushan": "^0.2.22"
},
"devDependencies": {
"@types/jsonexport": "^3.0.2",
"@types/lodash-es": "^4.17.7",
"@types/node": "^20.2.5",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-helmet": "^6.1.6",
"@types/styled-components": "^5.1.26",
"@vitejs/plugin-react": "^3.1.0",
"typescript": "^4.9.3",
"typescript": "^4.9.2",
"vite": "^4.2.1"
}
}

5456
admin/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,329 +1,21 @@
import express from 'express';
import mongoose from 'mongoose';
import cors from 'cors';
import { useUserRoute } from './service/route/user.js';
import { useAppRoute } from './service/route/app.js';
import { useKbRoute } from './service/route/kb.js';
import { useSystemRoute } from './service/route/system.js';
const app = express();
app.use(cors());
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.static('dist'));
const mongoURI = '';//在这里填入mongodb的连接地址
mongoose.connect(mongoURI, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log('Connected to MongoDB successfully!'))
.catch((err) => console.log(`Error connecting to MongoDB: ${err}`));
useUserRoute(app);
useAppRoute(app);
useKbRoute(app);
useSystemRoute(app);
const userSchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
username: String,
password: String,
balance: Number,
promotion: {
rate: Number,
},
openaiKey: String,
avatar: String,
createTime: Date,
});
// 新增: 定义 pays 模型
const paySchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
userId: mongoose.Schema.Types.ObjectId,
price: Number,
orderId: String,
status: String,
createTime: Date,
__v: Number,
});
// 新增: 定义 kb 模型
const kbSchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
userId: mongoose.Schema.Types.ObjectId,
avatar: String,
name: String,
tags: [String],
updateTime: Date,
__v: Number,
});
const modelSchema = new mongoose.Schema({
userId: mongoose.Schema.Types.ObjectId,
name: String,
avatar: String,
status: String,
chat: {
relatedKbs: [mongoose.Schema.Types.ObjectId],
searchMode: String,
systemPrompt: String,
temperature: Number,
chatModel: String
},
share: {
isShare: Boolean,
isShareDetail: Boolean,
intro: String,
collection: Number
},
security: {
domain: [String],
contextMaxLen: Number,
contentMaxLen: Number,
expiredTime: Number,
maxLoadAmount: Number
},
updateTime: Date
});
const Model = mongoose.model('Model', modelSchema);
const Kb = mongoose.model('Kb', kbSchema);
const User = mongoose.model('User', userSchema, 'users');
const Pay = mongoose.model('Pay', paySchema, 'pays');
// 获取用户列表
app.get('/users', async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
const order = req.query._order === 'DESC' ? -1 : 1;
const sort = req.query._sort || '_id';
const usersRaw = await User.find()
.skip(start)
.limit(end - start)
.sort({ [sort]: order });
const users = usersRaw.map((user) => {
const obj = user.toObject();
obj.id = obj._id;
delete obj._id;
return obj;
});
const totalCount = await User.countDocuments();
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(users);
} catch (err) {
console.log(`Error fetching users: ${err}`);
res.status(500).json({ error: 'Error fetching users' });
}
});
// 创建用户
app.post('/users', async (req, res) => {
try {
const { username, password, balance, promotion, openaiKey = '', avatar = '/icon/human.png' } = req.body;
if (!username || !password || !balance) {
return res.status(400).json({ error: 'Invalid user information' });
}
const existingUser = await User.findOne({ username });
if (existingUser) {
return res.status(400).json({ error: 'Username already exists' });
}
const user = new User({
_id: new mongoose.Types.ObjectId(),
username,
password,
balance,
promotion: {
rate: promotion?.rate || 0,
},
openaiKey,
avatar,
createTime: new Date(),
});
const result = await user.save();
res.json(result);
} catch (err) {
console.log(`Error creating user: ${err}`);
res.status(500).json({ error: 'Error creating user' });
}
});
// 修改用户信息
app.put('/users/:id', async (req, res) => {
try {
const _id = req.params.id;
const result = await User.updateOne({ _id: _id }, { $set: req.body });
res.json(result);
} catch (err) {
console.log(`Error updating user: ${err}`);
res.status(500).json({ error: 'Error updating user' });
}
});
// 删除用户
app.delete('/users/:id', async (req, res) => {
try {
const _id = req.params.id;
if (!mongoose.Types.ObjectId.isValid(_id)) {
return res.status(400).json({ error: 'Invalid user ID' });
}
const result = await User.deleteOne({ _id: _id });
res.json(result);
} catch (err) {
console.log(`Error deleting user: ${err}`);
res.status(500).json({ error: 'Error deleting user' });
}
});
// 新增: 获取 pays 列表
app.get('/pays', async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
const order = req.query._order === 'DESC' ? -1 : 1;
const sort = req.query._sort || '_id';
const paysRaw = await Pay.find()
.skip(start)
.limit(end - start)
.sort({ [sort]: order });
const usersMap = new Map();
const pays = [];
for (const payRaw of paysRaw) {
const pay = payRaw.toObject();
if (!usersMap.has(pay.userId.toString())) {
const user = await User.findById(pay.userId);
usersMap.set(pay.userId.toString(), user.username);
}
const orderedPay = {
id: pay._id.toString(),
name: usersMap.get(pay.userId.toString()),
price: pay.price,
orderId: pay.orderId,
status: pay.status,
createTime: pay.createTime
};
pays.push(orderedPay);
}
const totalCount = await Pay.countDocuments();
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(pays);
} catch (err) {
console.log(`Error fetching pays: ${err}`);
res.status(500).json({ error: 'Error fetching pays', details: err.message });
}
});
// 获取用户知识库列表
app.get('/kbs', async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
const order = req.query._order === 'DESC' ? -1 : 1;
const sort = req.query._sort || '_id';
const kbsRaw = await Kb.find()
.skip(start)
.limit(end - start)
.sort({ [sort]: order });
const usersMap = new Map();
const kbs = [];
for (const kbRaw of kbsRaw) {
const kb = kbRaw.toObject();
if (!usersMap.has(kb.userId.toString())) {
const user = await User.findById(kb.userId);
usersMap.set(kb.userId.toString(), user.username);
}
const orderedKb = {
id: kb._id.toString(),
user: usersMap.get(kb.userId.toString()),
name: kb.name,
tags: kb.tags,
avatar: kb.avatar
};
kbs.push(orderedKb);
}
const totalCount = await Kb.countDocuments();
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(kbs);
} catch (err) {
console.log(`Error fetching kbs: ${err}`);
res.status(500).json({ error: 'Error fetching kbs', details: err.message });
}
});
// 获取AI助手列表
app.get('/models', async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
const order = req.query._order === 'DESC' ? -1 : 1;
const sort = req.query._sort || '_id';
const modelsRaw = await Model.find()
.skip(start)
.limit(end - start)
.sort({ [sort]: order });
const usersMap = new Map();
const models = [];
for (const modelRaw of modelsRaw) {
const model = modelRaw.toObject();
if (!usersMap.has(model.userId.toString())) {
const user = await User.findById(model.userId);
usersMap.set(model.userId.toString(), user.username);
}
// 获取与模型关联的知识库名称
const kbNames = [];
for (const kbId of model.chat.relatedKbs) {
const kb = await Kb.findById(kbId);
kbNames.push(kb.name);
}
const orderedModel = {
id: model._id.toString(),
user: usersMap.get(model.userId.toString()),
name: model.name,
relatedKbs: kbNames, // 将relatedKbs的id转换为相应的Kb名称
searchMode: model.chat.searchMode,
systemPrompt: model.chat.systemPrompt,
temperature: model.chat.temperature,
isShare: model.share.isShare,
isShareDetail: model.share.isShareDetail,
avatar: model.avatar
};
models.push(orderedModel);
}
const totalCount = await Model.countDocuments();
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(models);
} catch (err) {
console.log(`Error fetching models: ${err}`);
res.status(500).json({ error: 'Error fetching models', details: err.message });
}
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

View File

@@ -0,0 +1,56 @@
import { User, Model, Kb } from '../schema.js';
export const useAppRoute = (app) => {
// 获取AI助手列表
app.get('/models', async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
const order = req.query._order === 'DESC' ? -1 : 1;
const sort = req.query._sort || '_id';
const userId = req.query.userId || '';
const name = req.query.name || '';
const where = {
...(userId ? { userId: userId } : {}),
name
};
const modelsRaw = await Model.find()
.skip(start)
.limit(end - start)
.sort({ [sort]: order });
const models = [];
for (const modelRaw of modelsRaw) {
const model = modelRaw.toObject();
// 获取与模型关联的知识库名称
const kbNames = [];
for (const kbId of model.chat.relatedKbs) {
const kb = await Kb.findById(kbId);
kbNames.push(kb.name);
}
const orderedModel = {
id: model._id.toString(),
userId: model.userId,
name: model.name,
relatedKbs: kbNames, // 将relatedKbs的id转换为相应的Kb名称
searchMode: model.chat?.searchMode,
systemPrompt: model.chat?.systemPrompt || '',
temperature: model.chat?.temperature
};
models.push(orderedModel);
}
const totalCount = await Model.countDocuments();
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(models);
} catch (err) {
console.log(`Error fetching models: ${err}`);
res.status(500).json({ error: 'Error fetching models', details: err.message });
}
});
};

43
admin/service/route/kb.js Normal file
View File

@@ -0,0 +1,43 @@
import { Kb } from '../schema.js';
export const useKbRoute = (app) => {
// 获取用户知识库列表
app.get('/kbs', async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
const order = req.query._order === 'DESC' ? -1 : 1;
const sort = req.query._sort || '_id';
const tag = req.query.tag || '';
const where = { tags: { $elemMatch: { $regex: tag, $options: 'i' } } };
const kbsRaw = await Kb.find(where)
.skip(start)
.limit(end - start)
.sort({ [sort]: order });
const kbs = [];
for (const kbRaw of kbsRaw) {
const kb = kbRaw.toObject();
const orderedKb = {
id: kb._id.toString(),
userId: kb.userId,
name: kb.name,
tags: kb.tags,
avatar: kb.avatar
};
kbs.push(orderedKb);
}
const totalCount = await Kb.countDocuments(where);
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(kbs);
} catch (err) {
console.log(`Error fetching kbs: ${err}`);
res.status(500).json({ error: 'Error fetching kbs', details: err.message });
}
});
};

View File

@@ -0,0 +1,68 @@
import jwt from 'jsonwebtoken';
const adminAuth = {
username: process.env.ADMIN_USER,
password: process.env.ADMIN_PASS
};
const authSecret = process.env.ADMIN_SECRET;
export const useSystemRoute = (app) => {
app.post('/api/login', (req, res) => {
if (!adminAuth.username || !adminAuth.password) {
res.status(401).end('Server not set env: ADMIN_USER, ADMIN_PASS');
return;
}
const { username, password } = req.body;
if (username === adminAuth.username && password === adminAuth.password) {
// 用户名和密码都正确返回token
const token = jwt.sign(
{
username,
platform: 'admin'
},
authSecret,
{
expiresIn: '2h'
}
);
res.json({
username,
token: token,
expiredAt: new Date().valueOf() + 2 * 60 * 60 * 1000
});
} else {
res.status(401).end('username or password incorrect');
}
});
};
export const auth = () => {
return (req, res, next) => {
try {
const authorization = req.headers.authorization;
if (!authorization) {
res.status(401).end('not found authorization in headers');
return;
}
const token = authorization.slice('Bearer '.length);
const payload = jwt.verify(token, authSecret);
if (typeof payload === 'string') {
res.status(401).end('payload type error');
return;
}
if (payload.platform !== 'admin') {
res.status(401).end('Payload invalid');
return;
}
next();
} catch (err) {
res.status(401).end(String(err));
}
};
};

138
admin/service/route/user.js Normal file
View File

@@ -0,0 +1,138 @@
import { User, Pay } from '../schema.js';
import dayjs from 'dayjs';
import { auth } from './system.js';
export const useUserRoute = (app) => {
// 获取用户列表
app.get('/users', auth(), async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
const order = req.query._order === 'DESC' ? -1 : 1;
const sort = req.query._sort || 'createTime';
const username = req.query.username || '';
const where = {
username: { $regex: username, $options: 'i' }
};
const usersRaw = await User.find(where)
.skip(start)
.limit(end - start)
.sort({ [sort]: order });
const users = usersRaw.map((user) => {
const obj = user.toObject();
return {
...obj,
id: obj._id,
createTime: dayjs(obj.createTime).format('YYYY/MM/DD HH:mm')
};
});
const totalCount = await User.countDocuments(where);
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(users);
} catch (err) {
console.log(`Error fetching users: ${err}`);
res.status(500).json({ error: 'Error fetching users' });
}
});
// 创建用户
app.post('/users', auth(), async (req, res) => {
try {
const {
username,
password,
balance,
promotion,
openaiKey = '',
avatar = '/icon/human.png'
} = req.body;
if (!username || !password || !balance) {
return res.status(400).json({ error: 'Invalid user information' });
}
const existingUser = await User.findOne({ username });
if (existingUser) {
return res.status(400).json({ error: 'Username already exists' });
}
const user = new User({
_id: new mongoose.Types.ObjectId(),
username,
password,
balance,
promotion: {
rate: promotion?.rate || 0
},
openaiKey,
avatar,
createTime: new Date()
});
const result = await user.save();
res.json(result);
} catch (err) {
console.log(`Error creating user: ${err}`);
res.status(500).json({ error: 'Error creating user' });
}
});
// 修改用户信息
app.put('/users/:id', auth(), async (req, res) => {
try {
const _id = req.params.id;
const result = await User.updateOne({ _id: _id }, { $set: req.body });
res.json(result);
} catch (err) {
console.log(`Error updating user: ${err}`);
res.status(500).json({ error: 'Error updating user' });
}
});
// 新增: 获取 pays 列表
app.get('/pays', auth(), async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
const order = req.query._order === 'DESC' ? -1 : 1;
const sort = req.query._sort || '_id';
const userId = req.query.userId || '';
const where = userId ? { userId: userId } : {};
const paysRaw = await Pay.find({
...where
})
.skip(start)
.limit(end - start)
.sort({ [sort]: order });
const pays = [];
for (const payRaw of paysRaw) {
const pay = payRaw.toObject();
const orderedPay = {
id: pay._id.toString(),
userId: pay.userId,
price: pay.price,
orderId: pay.orderId,
status: pay.status,
createTime: dayjs(pay.createTime).format('YYYY/MM/DD HH:mm')
};
pays.push(orderedPay);
}
const totalCount = await Pay.countDocuments({
...where
});
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(pays);
} catch (err) {
console.log(`Error fetching pays: ${err}`);
res.status(500).json({ error: 'Error fetching pays', details: err.message });
}
});
};

90
admin/service/schema.js Normal file
View File

@@ -0,0 +1,90 @@
import mongoose from 'mongoose';
import dotenv from 'dotenv';
dotenv.config({ path: '.env.local' });
const mongoUrl = process.env.MONGODB_URI;
const mongoDBName = process.env.MONGODB_NAME;
if (!mongoUrl || !mongoDBName) {
throw new Error('db error');
}
mongoose
.connect(mongoUrl, {
dbName: mongoDBName,
bufferCommands: true,
maxPoolSize: 5,
minPoolSize: 1,
maxConnecting: 5
})
.then(() => console.log('Connected to MongoDB successfully!'))
.catch((err) => console.log(`Error connecting to MongoDB: ${err}`));
const userSchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
username: String,
password: String,
balance: Number,
promotion: {
rate: Number
},
openaiKey: String,
avatar: String,
createTime: Date
});
// 新增: 定义 pays 模型
const paySchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
userId: mongoose.Schema.Types.ObjectId,
price: Number,
orderId: String,
status: String,
createTime: Date,
__v: Number
});
// 新增: 定义 kb 模型
const kbSchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
userId: mongoose.Schema.Types.ObjectId,
avatar: String,
name: String,
tags: [String],
updateTime: Date,
__v: Number
});
const modelSchema = new mongoose.Schema({
userId: mongoose.Schema.Types.ObjectId,
name: String,
avatar: String,
status: String,
chat: {
relatedKbs: [mongoose.Schema.Types.ObjectId],
searchMode: String,
systemPrompt: String,
temperature: Number,
chatModel: String
},
share: {
isShare: Boolean,
isShareDetail: Boolean,
intro: String,
collection: Number
},
security: {
domain: [String],
contextMaxLen: Number,
contentMaxLen: Number,
expiredTime: Number,
maxLoadAmount: Number
},
updateTime: Date
});
export const Model = mongoose.model('Model', modelSchema);
export const Kb = mongoose.model('Kb', kbSchema);
export const User = mongoose.model('User', userSchema, 'users');
export const Pay = mongoose.model('Pay', paySchema, 'pays');

View File

@@ -4,18 +4,34 @@ import {
ListTable,
Resource,
Tushan,
fetchJSON
} from 'tushan';
import { authProvider } from './auth';
import { userFields,payFields,kbFields,ModelFields } from './fields';
import { userFields, payFields, kbFields, ModelFields } from './fields';
const dataProvider = jsonServerProvider('http://localhost:3001');
const authStorageKey = 'tushan:auth';
const httpClient: typeof fetchJSON = (url, options = {}) => {
try {
if (!options.headers) {
options.headers = new Headers({ Accept: 'application/json' });
}
const { token } = JSON.parse(window.localStorage.getItem(authStorageKey) ?? '{}');
(options.headers as Headers).set('Authorization', `Bearer ${token}`);
return fetchJSON(url, options);
} catch (err) {
return Promise.reject();
}
};
const dataProvider = jsonServerProvider(import.meta.env.VITE_PUBLIC_SERVER_URL, httpClient);
function App() {
return (
<Tushan
basename="/"
header={'fastgpt-admin'}
footer={'Build with stakeswky'}
header={'FastGpt-Admin'}
dataProvider={dataProvider}
authProvider={authProvider}
>
@@ -25,12 +41,12 @@ function App() {
list={
<ListTable
filter={[
createTextField('q', {
label: 'Query',
}),
createTextField('username', {
label: 'username'
})
]}
fields={userFields}
action={{ create: true, detail: true, edit: true, delete: true }}
action={{ create: true, detail: true, edit: true }}
/>
}
/>
@@ -40,6 +56,11 @@ function App() {
label="支付记录"
list={
<ListTable
filter={[
createTextField('userId', {
label: 'userId'
})
]}
fields={payFields}
action={{ detail: true }}
/>
@@ -50,6 +71,11 @@ function App() {
label="知识库"
list={
<ListTable
filter={[
createTextField('tag', {
label: 'tag'
})
]}
fields={kbFields}
action={{ detail: true }}
/>
@@ -57,13 +83,8 @@ function App() {
/>
<Resource
name="models"
label="Ai模型"
list={
<ListTable
fields={ModelFields}
action={{ detail: true }}
/>
}
label="应用"
list={<ListTable fields={ModelFields} action={{ detail: true }} />}
/>
</Tushan>
);

View File

@@ -1,33 +1,5 @@
import { AuthProvider } from 'tushan';
import { createAuthProvider, type AuthProvider } from 'tushan';
export const authProvider: AuthProvider = {
login: ({ username, password }) => {
if (username !== 'tushan' || password !== 'tushan') {
return Promise.reject();
}
localStorage.setItem('username', username);
return Promise.resolve();
},
logout: () => {
localStorage.removeItem('username');
return Promise.resolve();
},
checkAuth: () =>
localStorage.getItem('username') ? Promise.resolve() : Promise.reject(),
checkError: (error) => {
const status = error.status;
if (status === 401 || status === 403) {
localStorage.removeItem('username');
return Promise.reject();
}
return Promise.resolve();
},
getIdentity: () =>
Promise.resolve({
id: '0',
fullName: 'Admin',
}),
getPermissions: () => Promise.resolve(''),
};
export const authProvider: AuthProvider = createAuthProvider({
loginUrl: `${import.meta.env.VITE_PUBLIC_SERVER_URL}api/login`
});

View File

@@ -1,46 +1,39 @@
import {
createTextField,
createUrlField,
createNumberField,
createAvatarField
} from 'tushan';
import { createTextField, createNumberField } from 'tushan';
export const userFields = [
createTextField('id', { label: 'ID' }),
createTextField('username', { label: '用户名', list: { sort: true } }),
createTextField('password', { label: '密码(加密)' }),
createNumberField('balance', { label: '余额' }),
createTextField('openaiKey', { label: 'OpenAI Key' }),
createTextField('createTime', { label: 'Create Time' }),
createAvatarField('avatar', { label: 'Avatar' }),
createTextField('username', { label: '用户名' }),
createNumberField('balance', { label: '余额', list: { sort: true } }),
createTextField('createTime', { label: 'Create Time', list: { sort: true } })
];
export const payFields = [
createTextField('id', { label: 'ID' }),
createTextField('name', { label: '用户名', list: { sort: true } }),
createTextField('userId', { label: '用户Id' }),
createNumberField('price', { label: '支付金额' }),
createTextField('orderId', { label: 'orderId' }),
createTextField('status', { label: '状态' }),
createTextField('createTime', { label: 'Create Time' }),
createTextField('createTime', { label: 'Create Time', list: { sort: true } })
];
export const kbFields = [
createTextField('id', { label: 'ID' }),
createTextField('user', { label: '所属用户' }),
createTextField('name', { label: '知识库', list: { sort: true } }),
createTextField('tags', { label: 'Tags' }),
createAvatarField('avatar', { label: 'Avatar' }),
createTextField('userId', { label: '所属用户' }),
createTextField('name', { label: '知识库' }),
createTextField('tags', { label: 'Tags' })
];
export const ModelFields = [
createTextField('id', { label: 'ID' }),
createTextField('name', { label: 'Ai助手', list: { sort: true } }),
createTextField('user', { label: '所属用户' }),
createTextField('userId', { label: '所属用户' }),
createTextField('name', { label: '名字' }),
createTextField('relatedKbs', { label: '引用的知识库' }),
createTextField('searchMode', { label: '搜索模式' }),
createTextField('systemPrompt', { label: '提示词' }),
createTextField('temperature', { label: '温度' }),
createTextField('isShare', { label: '是否分享' }),
createTextField('isShareDetail', { label: '分享详情' }),
createAvatarField('avatar', { label: 'Avatar' }),
];
createTextField('systemPrompt', {
label: '提示词',
list: {
width: 400
}
}),
createTextField('temperature', { label: '温度' })
];

View File

@@ -2,6 +2,4 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<App />
);
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);

View File

@@ -70,12 +70,14 @@ services:
- PG_HOST=0.0.0.0
- PG_PORT=8100
# 和上方PG镜像对应.
- PG_USER=fastgpt # POSTGRES_USER
- PG_PASSWORD=1234 # POSTGRES_PASSWORD
- PG_DB_NAME=fastgpt # POSTGRES_DB
- PG_USER=fastgpt
- PG_PASSWORD=1234
- PG_DB_NAME=fastgpt
# openai
- OPENAIKEY=sk-xxxxx,sk-xxx # 对话用的key多个key逗号分开
- OPENAI_TRAINING_KEY=sk-xxx,sk-xxxx # 训练用的key
# 对话用的key多个key逗号分开
- OPENAIKEY=sk-xxxxx,sk-xxx
# 训练用的key
- OPENAI_TRAINING_KEY=sk-xxx,sk-xxxx
- GPT4KEY=sk-xxx
- OPENAI_BASE_URL=https://api.openai.com/v1
- OPENAI_BASE_URL_AUTH=可选的安全凭证

View File

@@ -13,7 +13,7 @@
"prettier": "^2.8.7"
},
"lint-staged": {
"./**/src/**/*.{ts,tsx,scss}": "npm run format"
"./**/*.{ts,tsx,scss}": "npm run format"
},
"engines": {
"node": ">=18.0.0"