mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-17 16:45:02 +00:00
feat: admin image
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -29,3 +29,4 @@ platform.json
|
|||||||
testApi/
|
testApi/
|
||||||
local/
|
local/
|
||||||
.husky/
|
.husky/
|
||||||
|
dist/
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -3,5 +3,4 @@
|
|||||||
"editor.mouseWheelZoom": true,
|
"editor.mouseWheelZoom": true,
|
||||||
"typescript.tsdk": "./client/node_modules/typescript/lib",
|
"typescript.tsdk": "./client/node_modules/typescript/lib",
|
||||||
"prettier.prettierPath": "./node_modules/prettier"
|
"prettier.prettierPath": "./node_modules/prettier"
|
||||||
|
|
||||||
}
|
}
|
11
admin/.dockerignore
Normal file
11
admin/.dockerignore
Normal 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
6
admin/.env.template
Normal 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
1
admin/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules/
|
48
admin/Dockerfile
Normal file
48
admin/Dockerfile
Normal 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"]
|
@@ -1,15 +1,41 @@
|
|||||||
## fastgpt-admin
|
# FastGpt Admin
|
||||||
原作地址:https://github.com/c121914yu/FastGPT/
|
|
||||||
|
|
||||||
## 项目原理
|
## 项目原理
|
||||||
使用tushan项目做前端,然后构造了一个与mongodb做沟通的API做后端,可以做到创建、修改和删除用户
|
|
||||||
|
|
||||||
## 使用方法
|
使用 tushan 项目做前端,然后构造了一个与 mongodb 做沟通的 API 做后端,可以做到创建、修改和删除用户
|
||||||
1. 修改根目录下的server.js文件中的mongodb数据库连接地址。
|
|
||||||
2. pnpm i && pnpm dev
|
|
||||||
3. 默认账号密码为tushan
|
|
||||||
|
|
||||||
|
## 开发
|
||||||
|
|
||||||
## 可能会有的功能
|
1. 复制 .env.template 文件,添加环境变量
|
||||||
1. 对接数据库中的其他数据
|
2. pnpm i
|
||||||
2. tokens充值功能
|
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 提供的外网访问地址,可以直接访问。
|
||||||
|
@@ -8,23 +8,31 @@
|
|||||||
"dev": "concurrently \"vite\" \"npm run start:api\"",
|
"dev": "concurrently \"vite\" \"npm run start:api\"",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"start:api": "node server.js"
|
"start:api": "nodemon server.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"concurrently": "^8.1.0",
|
"concurrently": "^8.1.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"dayjs": "^1.11.8",
|
||||||
|
"dotenv": "^16.1.4",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"jsonwebtoken": "^9.0.0",
|
||||||
"mongoose": "^7.2.2",
|
"mongoose": "^7.2.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-admin": "^4.11.0",
|
"react-admin": "^4.11.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"tushan": "^0.2.13"
|
"tushan": "^0.2.22"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/jsonexport": "^3.0.2",
|
||||||
|
"@types/lodash-es": "^4.17.7",
|
||||||
|
"@types/node": "^20.2.5",
|
||||||
"@types/react": "^18.0.28",
|
"@types/react": "^18.0.28",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.0.11",
|
||||||
|
"@types/react-helmet": "^6.1.6",
|
||||||
|
"@types/styled-components": "^5.1.26",
|
||||||
"@vitejs/plugin-react": "^3.1.0",
|
"@vitejs/plugin-react": "^3.1.0",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^4.9.2",
|
||||||
"vite": "^4.2.1"
|
"vite": "^4.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5456
admin/pnpm-lock.yaml
generated
Normal file
5456
admin/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
326
admin/server.js
326
admin/server.js
@@ -1,329 +1,21 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import mongoose from 'mongoose';
|
|
||||||
import cors from 'cors';
|
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();
|
const app = express();
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
app.use(express.static('dist'));
|
||||||
|
|
||||||
const mongoURI = '';//在这里填入mongodb的连接地址
|
useUserRoute(app);
|
||||||
mongoose.connect(mongoURI, { useNewUrlParser: true, useUnifiedTopology: true })
|
useAppRoute(app);
|
||||||
.then(() => console.log('Connected to MongoDB successfully!'))
|
useKbRoute(app);
|
||||||
.catch((err) => console.log(`Error connecting to MongoDB: ${err}`));
|
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;
|
const PORT = process.env.PORT || 3001;
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Server is running on port ${PORT}`);
|
console.log(`Server is running on port ${PORT}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
56
admin/service/route/app.js
Normal file
56
admin/service/route/app.js
Normal 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
43
admin/service/route/kb.js
Normal 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 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
68
admin/service/route/system.js
Normal file
68
admin/service/route/system.js
Normal 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
138
admin/service/route/user.js
Normal 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
90
admin/service/schema.js
Normal 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');
|
@@ -4,18 +4,34 @@ import {
|
|||||||
ListTable,
|
ListTable,
|
||||||
Resource,
|
Resource,
|
||||||
Tushan,
|
Tushan,
|
||||||
|
fetchJSON
|
||||||
} from 'tushan';
|
} from 'tushan';
|
||||||
import { authProvider } from './auth';
|
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() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<Tushan
|
<Tushan
|
||||||
basename="/"
|
basename="/"
|
||||||
header={'fastgpt-admin'}
|
header={'FastGpt-Admin'}
|
||||||
footer={'Build with stakeswky'}
|
|
||||||
dataProvider={dataProvider}
|
dataProvider={dataProvider}
|
||||||
authProvider={authProvider}
|
authProvider={authProvider}
|
||||||
>
|
>
|
||||||
@@ -25,12 +41,12 @@ function App() {
|
|||||||
list={
|
list={
|
||||||
<ListTable
|
<ListTable
|
||||||
filter={[
|
filter={[
|
||||||
createTextField('q', {
|
createTextField('username', {
|
||||||
label: 'Query',
|
label: 'username'
|
||||||
}),
|
})
|
||||||
]}
|
]}
|
||||||
fields={userFields}
|
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="支付记录"
|
label="支付记录"
|
||||||
list={
|
list={
|
||||||
<ListTable
|
<ListTable
|
||||||
|
filter={[
|
||||||
|
createTextField('userId', {
|
||||||
|
label: 'userId'
|
||||||
|
})
|
||||||
|
]}
|
||||||
fields={payFields}
|
fields={payFields}
|
||||||
action={{ detail: true }}
|
action={{ detail: true }}
|
||||||
/>
|
/>
|
||||||
@@ -50,6 +71,11 @@ function App() {
|
|||||||
label="知识库"
|
label="知识库"
|
||||||
list={
|
list={
|
||||||
<ListTable
|
<ListTable
|
||||||
|
filter={[
|
||||||
|
createTextField('tag', {
|
||||||
|
label: 'tag'
|
||||||
|
})
|
||||||
|
]}
|
||||||
fields={kbFields}
|
fields={kbFields}
|
||||||
action={{ detail: true }}
|
action={{ detail: true }}
|
||||||
/>
|
/>
|
||||||
@@ -57,13 +83,8 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
<Resource
|
<Resource
|
||||||
name="models"
|
name="models"
|
||||||
label="Ai模型"
|
label="应用"
|
||||||
list={
|
list={<ListTable fields={ModelFields} action={{ detail: true }} />}
|
||||||
<ListTable
|
|
||||||
fields={ModelFields}
|
|
||||||
action={{ detail: true }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Tushan>
|
</Tushan>
|
||||||
);
|
);
|
||||||
|
@@ -1,33 +1,5 @@
|
|||||||
import { AuthProvider } from 'tushan';
|
import { createAuthProvider, type AuthProvider } from 'tushan';
|
||||||
|
|
||||||
export const authProvider: AuthProvider = {
|
export const authProvider: AuthProvider = createAuthProvider({
|
||||||
login: ({ username, password }) => {
|
loginUrl: `${import.meta.env.VITE_PUBLIC_SERVER_URL}api/login`
|
||||||
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(''),
|
|
||||||
};
|
|
||||||
|
@@ -1,46 +1,39 @@
|
|||||||
import {
|
import { createTextField, createNumberField } from 'tushan';
|
||||||
createTextField,
|
|
||||||
createUrlField,
|
|
||||||
createNumberField,
|
|
||||||
createAvatarField
|
|
||||||
} from 'tushan';
|
|
||||||
|
|
||||||
export const userFields = [
|
export const userFields = [
|
||||||
createTextField('id', { label: 'ID' }),
|
createTextField('id', { label: 'ID' }),
|
||||||
createTextField('username', { label: '用户名', list: { sort: true } }),
|
createTextField('username', { label: '用户名' }),
|
||||||
createTextField('password', { label: '密码(加密)' }),
|
createNumberField('balance', { label: '余额', list: { sort: true } }),
|
||||||
createNumberField('balance', { label: '余额' }),
|
createTextField('createTime', { label: 'Create Time', list: { sort: true } })
|
||||||
createTextField('openaiKey', { label: 'OpenAI Key' }),
|
|
||||||
createTextField('createTime', { label: 'Create Time' }),
|
|
||||||
createAvatarField('avatar', { label: 'Avatar' }),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const payFields = [
|
export const payFields = [
|
||||||
createTextField('id', { label: 'ID' }),
|
createTextField('id', { label: 'ID' }),
|
||||||
createTextField('name', { label: '用户名', list: { sort: true } }),
|
createTextField('userId', { label: '用户Id' }),
|
||||||
createNumberField('price', { label: '支付金额' }),
|
createNumberField('price', { label: '支付金额' }),
|
||||||
createTextField('orderId', { label: 'orderId' }),
|
createTextField('orderId', { label: 'orderId' }),
|
||||||
createTextField('status', { label: '状态' }),
|
createTextField('status', { label: '状态' }),
|
||||||
createTextField('createTime', { label: 'Create Time' }),
|
createTextField('createTime', { label: 'Create Time', list: { sort: true } })
|
||||||
];
|
];
|
||||||
|
|
||||||
export const kbFields = [
|
export const kbFields = [
|
||||||
createTextField('id', { label: 'ID' }),
|
createTextField('id', { label: 'ID' }),
|
||||||
createTextField('user', { label: '所属用户' }),
|
createTextField('userId', { label: '所属用户' }),
|
||||||
createTextField('name', { label: '知识库', list: { sort: true } }),
|
createTextField('name', { label: '知识库' }),
|
||||||
createTextField('tags', { label: 'Tags' }),
|
createTextField('tags', { label: 'Tags' })
|
||||||
createAvatarField('avatar', { label: 'Avatar' }),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ModelFields = [
|
export const ModelFields = [
|
||||||
createTextField('id', { label: 'ID' }),
|
createTextField('id', { label: 'ID' }),
|
||||||
createTextField('name', { label: 'Ai助手', list: { sort: true } }),
|
createTextField('userId', { label: '所属用户' }),
|
||||||
createTextField('user', { label: '所属用户' }),
|
createTextField('name', { label: '名字' }),
|
||||||
createTextField('relatedKbs', { label: '引用的知识库' }),
|
createTextField('relatedKbs', { label: '引用的知识库' }),
|
||||||
createTextField('searchMode', { label: '搜索模式' }),
|
createTextField('searchMode', { label: '搜索模式' }),
|
||||||
createTextField('systemPrompt', { label: '提示词' }),
|
createTextField('systemPrompt', {
|
||||||
createTextField('temperature', { label: '温度' }),
|
label: '提示词',
|
||||||
createTextField('isShare', { label: '是否分享' }),
|
list: {
|
||||||
createTextField('isShareDetail', { label: '分享详情' }),
|
width: 400
|
||||||
createAvatarField('avatar', { label: 'Avatar' }),
|
}
|
||||||
|
}),
|
||||||
|
createTextField('temperature', { label: '温度' })
|
||||||
];
|
];
|
@@ -2,6 +2,4 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);
|
||||||
<App />
|
|
||||||
);
|
|
||||||
|
@@ -70,12 +70,14 @@ services:
|
|||||||
- PG_HOST=0.0.0.0
|
- PG_HOST=0.0.0.0
|
||||||
- PG_PORT=8100
|
- PG_PORT=8100
|
||||||
# 和上方PG镜像对应.
|
# 和上方PG镜像对应.
|
||||||
- PG_USER=fastgpt # POSTGRES_USER
|
- PG_USER=fastgpt
|
||||||
- PG_PASSWORD=1234 # POSTGRES_PASSWORD
|
- PG_PASSWORD=1234
|
||||||
- PG_DB_NAME=fastgpt # POSTGRES_DB
|
- PG_DB_NAME=fastgpt
|
||||||
# openai
|
# openai
|
||||||
- OPENAIKEY=sk-xxxxx,sk-xxx # 对话用的key,多个key,逗号分开
|
# 对话用的key,多个key,逗号分开
|
||||||
- OPENAI_TRAINING_KEY=sk-xxx,sk-xxxx # 训练用的key
|
- OPENAIKEY=sk-xxxxx,sk-xxx
|
||||||
|
# 训练用的key
|
||||||
|
- OPENAI_TRAINING_KEY=sk-xxx,sk-xxxx
|
||||||
- GPT4KEY=sk-xxx
|
- GPT4KEY=sk-xxx
|
||||||
- OPENAI_BASE_URL=https://api.openai.com/v1
|
- OPENAI_BASE_URL=https://api.openai.com/v1
|
||||||
- OPENAI_BASE_URL_AUTH=可选的安全凭证
|
- OPENAI_BASE_URL_AUTH=可选的安全凭证
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
"prettier": "^2.8.7"
|
"prettier": "^2.8.7"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"./**/src/**/*.{ts,tsx,scss}": "npm run format"
|
"./**/*.{ts,tsx,scss}": "npm run format"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
|
Reference in New Issue
Block a user