feat: new app page
This commit is contained in:
parent
df2fda6176
commit
61447c60ac
@ -42,6 +42,12 @@ Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接
|
|||||||
- [FastGpt V3.4 更新集合](https://www.bilibili.com/video/BV1Lo4y147Qh/?vd_source=92041a1a395f852f9d89158eaa3f61b4)
|
- [FastGpt V3.4 更新集合](https://www.bilibili.com/video/BV1Lo4y147Qh/?vd_source=92041a1a395f852f9d89158eaa3f61b4)
|
||||||
- [FastGpt 知识库演示](https://www.bilibili.com/video/BV1Wo4y1p7i1/)
|
- [FastGpt 知识库演示](https://www.bilibili.com/video/BV1Wo4y1p7i1/)
|
||||||
|
|
||||||
|
## Powered by
|
||||||
|
|
||||||
|
- [TuShan 5 分钟搭建后台管理系统](https://github.com/msgbyte/tushan)
|
||||||
|
- [Laf 3 分钟快速接入三方应用](https://github.com/labring/laf)
|
||||||
|
- [Sealos 快速部署集群应用](https://github.com/labring/sealos)
|
||||||
|
|
||||||
## 🌟 Star History
|
## 🌟 Star History
|
||||||
|
|
||||||
[](https://star-history.com/#c121914yu/FastGPT&Date)
|
[](https://star-history.com/#c121914yu/FastGPT&Date)
|
||||||
|
|||||||
@ -38,13 +38,13 @@ export const useAppRoute = (app) => {
|
|||||||
id: model._id.toString(),
|
id: model._id.toString(),
|
||||||
userId: model.userId,
|
userId: model.userId,
|
||||||
name: model.name,
|
name: model.name,
|
||||||
|
intro: model.intro,
|
||||||
model: model.chat?.chatModel,
|
model: model.chat?.chatModel,
|
||||||
relatedKbs: kbNames, // 将relatedKbs的id转换为相应的Kb名称
|
relatedKbs: kbNames, // 将relatedKbs的id转换为相应的Kb名称
|
||||||
systemPrompt: model.chat?.systemPrompt || '',
|
systemPrompt: model.chat?.systemPrompt || '',
|
||||||
temperature: model.chat?.temperature || 0,
|
temperature: model.chat?.temperature || 0,
|
||||||
'share.topNum': model.share?.topNum || 0,
|
'share.topNum': model.share?.topNum || 0,
|
||||||
'share.isShare': model.share?.isShare || false,
|
'share.isShare': model.share?.isShare || false,
|
||||||
'share.intro': model.share?.intro,
|
|
||||||
'share.collection': model.share?.collection || 0
|
'share.collection': model.share?.collection || 0
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,14 +66,15 @@ export const useAppRoute = (app) => {
|
|||||||
const _id = req.params.id;
|
const _id = req.params.id;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
share: { isShare, intro, topNum }
|
share: { isShare, topNum },
|
||||||
|
intro
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
await Model.findByIdAndUpdate(_id, {
|
await Model.findByIdAndUpdate(_id, {
|
||||||
$set: {
|
$set: {
|
||||||
|
intro: intro,
|
||||||
'share.topNum': Number(topNum),
|
'share.topNum': Number(topNum),
|
||||||
'share.isShare': isShare === 'true',
|
'share.isShare': isShare === 'true' || isShare === true
|
||||||
'share.intro': intro
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -61,9 +61,9 @@ const modelSchema = new mongoose.Schema({
|
|||||||
name: String,
|
name: String,
|
||||||
avatar: String,
|
avatar: String,
|
||||||
status: String,
|
status: String,
|
||||||
|
intro: String,
|
||||||
chat: {
|
chat: {
|
||||||
relatedKbs: [mongoose.Schema.Types.ObjectId],
|
relatedKbs: [mongoose.Schema.Types.ObjectId],
|
||||||
searchMode: String,
|
|
||||||
systemPrompt: String,
|
systemPrompt: String,
|
||||||
temperature: Number,
|
temperature: Number,
|
||||||
chatModel: String
|
chatModel: String
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { createTextField, createNumberField } from 'tushan';
|
|||||||
|
|
||||||
export const userFields = [
|
export const userFields = [
|
||||||
createTextField('id', { label: 'ID' }),
|
createTextField('id', { label: 'ID' }),
|
||||||
createTextField('username', { label: '用户名' }),
|
createTextField('username', { label: '用户名', edit: { hidden: true } }),
|
||||||
createNumberField('balance', { label: '余额', list: { sort: true } }),
|
createNumberField('balance', { label: '余额', list: { sort: true } }),
|
||||||
createTextField('createTime', { label: 'Create Time', list: { sort: true } }),
|
createTextField('createTime', { label: 'Create Time', list: { sort: true } }),
|
||||||
createTextField('password', { label: '密码', list: { hidden: true } })
|
createTextField('password', { label: '密码', list: { hidden: true } })
|
||||||
@ -19,20 +19,20 @@ export const payFields = [
|
|||||||
|
|
||||||
export const kbFields = [
|
export const kbFields = [
|
||||||
createTextField('id', { label: 'ID' }),
|
createTextField('id', { label: 'ID' }),
|
||||||
createTextField('userId', { label: '所属用户' }),
|
createTextField('userId', { label: '所属用户', edit: { hidden: true } }),
|
||||||
createTextField('name', { label: '知识库' }),
|
createTextField('name', { label: '知识库' }),
|
||||||
createTextField('tags', { label: 'Tags' })
|
createTextField('tags', { label: 'Tags' })
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ModelFields = [
|
export const ModelFields = [
|
||||||
createTextField('id', { label: 'ID' }),
|
createTextField('id', { label: 'ID' }),
|
||||||
createTextField('userId', { label: '所属用户', list: { hidden: true } }),
|
createTextField('userId', { label: '所属用户', list: { hidden: true }, edit: { hidden: true } }),
|
||||||
createTextField('name', { label: '名字' }),
|
createTextField('name', { label: '名字' }),
|
||||||
createTextField('model', { label: '模型' }),
|
createTextField('model', { label: '模型', edit: { hidden: true } }),
|
||||||
createTextField('share.collection', { label: '收藏数', list: { sort: true } }),
|
createTextField('share.collection', { label: '收藏数', list: { sort: true } }),
|
||||||
createTextField('share.topNum', { label: '置顶等级', list: { sort: true } }),
|
createTextField('share.topNum', { label: '置顶等级', list: { sort: true } }),
|
||||||
createTextField('share.isShare', { label: '是否分享(true,false)' }),
|
createTextField('share.isShare', { label: '是否分享(true,false)' }),
|
||||||
createTextField('share.intro', { label: '介绍', list: { width: 400 } }),
|
createTextField('intro', { label: '介绍', list: { width: 400 } }),
|
||||||
createTextField('relatedKbs', { label: '引用的知识库', list: { hidden: true } }),
|
createTextField('relatedKbs', { label: '引用的知识库', list: { hidden: true } }),
|
||||||
createTextField('temperature', { label: '温度' }),
|
createTextField('temperature', { label: '温度' }),
|
||||||
createTextField('systemPrompt', {
|
createTextField('systemPrompt', {
|
||||||
|
|||||||
53
client/pnpm-lock.yaml
generated
53
client/pnpm-lock.yaml
generated
@ -1,4 +1,8 @@
|
|||||||
lockfileVersion: '6.0'
|
lockfileVersion: '6.1'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@alicloud/dysmsapi20170525':
|
'@alicloud/dysmsapi20170525':
|
||||||
@ -90,7 +94,7 @@ dependencies:
|
|||||||
version: registry.npmmirror.com/nanoid@4.0.1
|
version: registry.npmmirror.com/nanoid@4.0.1
|
||||||
next:
|
next:
|
||||||
specifier: 13.1.6
|
specifier: 13.1.6
|
||||||
version: registry.npmmirror.com/next@13.1.6(react-dom@18.2.0)(react@18.2.0)(sass@1.58.3)
|
version: registry.npmmirror.com/next@13.1.6(@babel/core@7.22.5)(react-dom@18.2.0)(react@18.2.0)(sass@1.58.3)
|
||||||
nextjs-cors:
|
nextjs-cors:
|
||||||
specifier: ^2.1.2
|
specifier: ^2.1.2
|
||||||
version: registry.npmmirror.com/nextjs-cors@2.1.2(next@13.1.6)
|
version: registry.npmmirror.com/nextjs-cors@2.1.2(next@13.1.6)
|
||||||
@ -326,7 +330,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/gen-mapping': registry.npmmirror.com/@jridgewell/gen-mapping@0.3.3
|
'@jridgewell/gen-mapping': registry.npmmirror.com/@jridgewell/gen-mapping@0.3.3
|
||||||
'@jridgewell/trace-mapping': registry.npmmirror.com/@jridgewell/trace-mapping@0.3.18
|
'@jridgewell/trace-mapping': registry.npmmirror.com/@jridgewell/trace-mapping@0.3.18
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@aws-crypto/crc32@3.0.0:
|
registry.npmmirror.com/@aws-crypto/crc32@3.0.0:
|
||||||
resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@aws-crypto/crc32/-/crc32-3.0.0.tgz}
|
resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@aws-crypto/crc32/-/crc32-3.0.0.tgz}
|
||||||
@ -1307,7 +1310,6 @@ packages:
|
|||||||
name: '@babel/compat-data'
|
name: '@babel/compat-data'
|
||||||
version: 7.22.5
|
version: 7.22.5
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/core@7.22.5:
|
registry.npmmirror.com/@babel/core@7.22.5:
|
||||||
resolution: {integrity: sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/core/-/core-7.22.5.tgz}
|
resolution: {integrity: sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/core/-/core-7.22.5.tgz}
|
||||||
@ -1332,7 +1334,6 @@ packages:
|
|||||||
semver: registry.npmmirror.com/semver@6.3.0
|
semver: registry.npmmirror.com/semver@6.3.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/generator@7.22.5:
|
registry.npmmirror.com/@babel/generator@7.22.5:
|
||||||
resolution: {integrity: sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/generator/-/generator-7.22.5.tgz}
|
resolution: {integrity: sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/generator/-/generator-7.22.5.tgz}
|
||||||
@ -1344,7 +1345,6 @@ packages:
|
|||||||
'@jridgewell/gen-mapping': registry.npmmirror.com/@jridgewell/gen-mapping@0.3.3
|
'@jridgewell/gen-mapping': registry.npmmirror.com/@jridgewell/gen-mapping@0.3.3
|
||||||
'@jridgewell/trace-mapping': registry.npmmirror.com/@jridgewell/trace-mapping@0.3.18
|
'@jridgewell/trace-mapping': registry.npmmirror.com/@jridgewell/trace-mapping@0.3.18
|
||||||
jsesc: registry.npmmirror.com/jsesc@2.5.2
|
jsesc: registry.npmmirror.com/jsesc@2.5.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/helper-annotate-as-pure@7.22.5:
|
registry.npmmirror.com/@babel/helper-annotate-as-pure@7.22.5:
|
||||||
resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz}
|
resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz}
|
||||||
@ -1379,7 +1379,6 @@ packages:
|
|||||||
browserslist: registry.npmmirror.com/browserslist@4.21.7
|
browserslist: registry.npmmirror.com/browserslist@4.21.7
|
||||||
lru-cache: registry.npmmirror.com/lru-cache@5.1.1
|
lru-cache: registry.npmmirror.com/lru-cache@5.1.1
|
||||||
semver: registry.npmmirror.com/semver@6.3.0
|
semver: registry.npmmirror.com/semver@6.3.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/helper-create-class-features-plugin@7.22.5(@babel/core@7.22.5):
|
registry.npmmirror.com/@babel/helper-create-class-features-plugin@7.22.5(@babel/core@7.22.5):
|
||||||
resolution: {integrity: sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz}
|
resolution: {integrity: sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz}
|
||||||
@ -1443,7 +1442,6 @@ packages:
|
|||||||
name: '@babel/helper-environment-visitor'
|
name: '@babel/helper-environment-visitor'
|
||||||
version: 7.22.5
|
version: 7.22.5
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/helper-function-name@7.22.5:
|
registry.npmmirror.com/@babel/helper-function-name@7.22.5:
|
||||||
resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz}
|
resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz}
|
||||||
@ -1453,7 +1451,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/template': registry.npmmirror.com/@babel/template@7.22.5
|
'@babel/template': registry.npmmirror.com/@babel/template@7.22.5
|
||||||
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/helper-hoist-variables@7.22.5:
|
registry.npmmirror.com/@babel/helper-hoist-variables@7.22.5:
|
||||||
resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz}
|
resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz}
|
||||||
@ -1462,7 +1459,6 @@ packages:
|
|||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/helper-member-expression-to-functions@7.22.5:
|
registry.npmmirror.com/@babel/helper-member-expression-to-functions@7.22.5:
|
||||||
resolution: {integrity: sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz}
|
resolution: {integrity: sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz}
|
||||||
@ -1497,7 +1493,6 @@ packages:
|
|||||||
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/helper-optimise-call-expression@7.22.5:
|
registry.npmmirror.com/@babel/helper-optimise-call-expression@7.22.5:
|
||||||
resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz}
|
resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz}
|
||||||
@ -1556,7 +1551,6 @@ packages:
|
|||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers@7.22.5:
|
registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers@7.22.5:
|
||||||
resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz}
|
resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz}
|
||||||
@ -1574,7 +1568,6 @@ packages:
|
|||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/helper-string-parser@7.22.5:
|
registry.npmmirror.com/@babel/helper-string-parser@7.22.5:
|
||||||
resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz}
|
resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz}
|
||||||
@ -1593,7 +1586,6 @@ packages:
|
|||||||
name: '@babel/helper-validator-option'
|
name: '@babel/helper-validator-option'
|
||||||
version: 7.22.5
|
version: 7.22.5
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/helper-wrap-function@7.22.5:
|
registry.npmmirror.com/@babel/helper-wrap-function@7.22.5:
|
||||||
resolution: {integrity: sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz}
|
resolution: {integrity: sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz}
|
||||||
@ -1620,7 +1612,6 @@ packages:
|
|||||||
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/highlight@7.22.5:
|
registry.npmmirror.com/@babel/highlight@7.22.5:
|
||||||
resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/highlight/-/highlight-7.22.5.tgz}
|
resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/highlight/-/highlight-7.22.5.tgz}
|
||||||
@ -1640,7 +1631,6 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.22.5(@babel/core@7.22.5):
|
registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.22.5(@babel/core@7.22.5):
|
||||||
resolution: {integrity: sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz}
|
resolution: {integrity: sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz}
|
||||||
@ -2903,7 +2893,6 @@ packages:
|
|||||||
'@babel/code-frame': registry.npmmirror.com/@babel/code-frame@7.22.5
|
'@babel/code-frame': registry.npmmirror.com/@babel/code-frame@7.22.5
|
||||||
'@babel/parser': registry.npmmirror.com/@babel/parser@7.22.5
|
'@babel/parser': registry.npmmirror.com/@babel/parser@7.22.5
|
||||||
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/traverse@7.22.5:
|
registry.npmmirror.com/@babel/traverse@7.22.5:
|
||||||
resolution: {integrity: sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/traverse/-/traverse-7.22.5.tgz}
|
resolution: {integrity: sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/traverse/-/traverse-7.22.5.tgz}
|
||||||
@ -2923,7 +2912,6 @@ packages:
|
|||||||
globals: registry.npmmirror.com/globals@11.12.0
|
globals: registry.npmmirror.com/globals@11.12.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@babel/types@7.22.5:
|
registry.npmmirror.com/@babel/types@7.22.5:
|
||||||
resolution: {integrity: sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/types/-/types-7.22.5.tgz}
|
resolution: {integrity: sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/types/-/types-7.22.5.tgz}
|
||||||
@ -4501,33 +4489,28 @@ packages:
|
|||||||
'@jridgewell/set-array': registry.npmmirror.com/@jridgewell/set-array@1.1.2
|
'@jridgewell/set-array': registry.npmmirror.com/@jridgewell/set-array@1.1.2
|
||||||
'@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.15
|
'@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.15
|
||||||
'@jridgewell/trace-mapping': registry.npmmirror.com/@jridgewell/trace-mapping@0.3.18
|
'@jridgewell/trace-mapping': registry.npmmirror.com/@jridgewell/trace-mapping@0.3.18
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@jridgewell/resolve-uri@3.1.0:
|
registry.npmmirror.com/@jridgewell/resolve-uri@3.1.0:
|
||||||
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz}
|
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz}
|
||||||
name: '@jridgewell/resolve-uri'
|
name: '@jridgewell/resolve-uri'
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@jridgewell/set-array@1.1.2:
|
registry.npmmirror.com/@jridgewell/set-array@1.1.2:
|
||||||
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.1.2.tgz}
|
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.1.2.tgz}
|
||||||
name: '@jridgewell/set-array'
|
name: '@jridgewell/set-array'
|
||||||
version: 1.1.2
|
version: 1.1.2
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.14:
|
registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.14:
|
||||||
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz}
|
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz}
|
||||||
name: '@jridgewell/sourcemap-codec'
|
name: '@jridgewell/sourcemap-codec'
|
||||||
version: 1.4.14
|
version: 1.4.14
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.15:
|
registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.15:
|
||||||
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz}
|
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz}
|
||||||
name: '@jridgewell/sourcemap-codec'
|
name: '@jridgewell/sourcemap-codec'
|
||||||
version: 1.4.15
|
version: 1.4.15
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@jridgewell/trace-mapping@0.3.18:
|
registry.npmmirror.com/@jridgewell/trace-mapping@0.3.18:
|
||||||
resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz}
|
resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz}
|
||||||
@ -4536,7 +4519,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/resolve-uri': registry.npmmirror.com/@jridgewell/resolve-uri@3.1.0
|
'@jridgewell/resolve-uri': registry.npmmirror.com/@jridgewell/resolve-uri@3.1.0
|
||||||
'@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.14
|
'@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.14
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/@motionone/animation@10.15.1:
|
registry.npmmirror.com/@motionone/animation@10.15.1:
|
||||||
resolution: {integrity: sha512-mZcJxLjHor+bhcPuIFErMDNyrdb2vJur8lSfMCsuCB4UyV8ILZLvK+t+pg56erv8ud9xQGK/1OGPt10agPrCyQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@motionone/animation/-/animation-10.15.1.tgz}
|
resolution: {integrity: sha512-mZcJxLjHor+bhcPuIFErMDNyrdb2vJur8lSfMCsuCB4UyV8ILZLvK+t+pg56erv8ud9xQGK/1OGPt10agPrCyQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@motionone/animation/-/animation-10.15.1.tgz}
|
||||||
@ -5926,7 +5908,6 @@ packages:
|
|||||||
electron-to-chromium: registry.npmmirror.com/electron-to-chromium@1.4.425
|
electron-to-chromium: registry.npmmirror.com/electron-to-chromium@1.4.425
|
||||||
node-releases: registry.npmmirror.com/node-releases@2.0.12
|
node-releases: registry.npmmirror.com/node-releases@2.0.12
|
||||||
update-browserslist-db: registry.npmmirror.com/update-browserslist-db@1.0.11(browserslist@4.21.7)
|
update-browserslist-db: registry.npmmirror.com/update-browserslist-db@1.0.11(browserslist@4.21.7)
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/bson@4.7.2:
|
registry.npmmirror.com/bson@4.7.2:
|
||||||
resolution: {integrity: sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/bson/-/bson-4.7.2.tgz}
|
resolution: {integrity: sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/bson/-/bson-4.7.2.tgz}
|
||||||
@ -7246,7 +7227,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-wv1NufHxu11zfDbY4fglYQApMswleE9FL/DSeyOyauVXDZ+Kco96JK/tPfBUaDqfRarYp2WH2hJ/5UnVywp9Jg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.425.tgz}
|
resolution: {integrity: sha512-wv1NufHxu11zfDbY4fglYQApMswleE9FL/DSeyOyauVXDZ+Kco96JK/tPfBUaDqfRarYp2WH2hJ/5UnVywp9Jg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.425.tgz}
|
||||||
name: electron-to-chromium
|
name: electron-to-chromium
|
||||||
version: 1.4.425
|
version: 1.4.425
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/emoji-regex@9.2.2:
|
registry.npmmirror.com/emoji-regex@9.2.2:
|
||||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz}
|
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz}
|
||||||
@ -7385,7 +7365,6 @@ packages:
|
|||||||
name: escalade
|
name: escalade
|
||||||
version: 3.1.1
|
version: 3.1.1
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/escape-html@1.0.3:
|
registry.npmmirror.com/escape-html@1.0.3:
|
||||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz}
|
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz}
|
||||||
@ -8101,7 +8080,6 @@ packages:
|
|||||||
name: gensync
|
name: gensync
|
||||||
version: 1.0.0-beta.2
|
version: 1.0.0-beta.2
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/get-intrinsic@1.2.1:
|
registry.npmmirror.com/get-intrinsic@1.2.1:
|
||||||
resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz}
|
resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz}
|
||||||
@ -8209,7 +8187,6 @@ packages:
|
|||||||
name: globals
|
name: globals
|
||||||
version: 11.12.0
|
version: 11.12.0
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/globals@13.20.0:
|
registry.npmmirror.com/globals@13.20.0:
|
||||||
resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/globals/-/globals-13.20.0.tgz}
|
resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/globals/-/globals-13.20.0.tgz}
|
||||||
@ -9020,7 +8997,6 @@ packages:
|
|||||||
version: 2.5.2
|
version: 2.5.2
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/json-parse-even-better-errors@2.3.1:
|
registry.npmmirror.com/json-parse-even-better-errors@2.3.1:
|
||||||
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz}
|
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz}
|
||||||
@ -9054,7 +9030,6 @@ packages:
|
|||||||
version: 2.2.3
|
version: 2.2.3
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/jsonfile@4.0.0:
|
registry.npmmirror.com/jsonfile@4.0.0:
|
||||||
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz}
|
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz}
|
||||||
@ -10044,7 +10019,7 @@ packages:
|
|||||||
engines: {node: '>= 0.4.0'}
|
engines: {node: '>= 0.4.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/next@13.1.6(react-dom@18.2.0)(react@18.2.0)(sass@1.58.3):
|
registry.npmmirror.com/next@13.1.6(@babel/core@7.22.5)(react-dom@18.2.0)(react@18.2.0)(sass@1.58.3):
|
||||||
resolution: {integrity: sha512-hHlbhKPj9pW+Cymvfzc15lvhaOZ54l+8sXDXJWm3OBNBzgrVj6hwGPmqqsXg40xO1Leq+kXpllzRPuncpC0Phw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/next/-/next-13.1.6.tgz}
|
resolution: {integrity: sha512-hHlbhKPj9pW+Cymvfzc15lvhaOZ54l+8sXDXJWm3OBNBzgrVj6hwGPmqqsXg40xO1Leq+kXpllzRPuncpC0Phw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/next/-/next-13.1.6.tgz}
|
||||||
id: registry.npmmirror.com/next/13.1.6
|
id: registry.npmmirror.com/next/13.1.6
|
||||||
name: next
|
name: next
|
||||||
@ -10072,7 +10047,7 @@ packages:
|
|||||||
react: registry.npmmirror.com/react@18.2.0
|
react: registry.npmmirror.com/react@18.2.0
|
||||||
react-dom: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0)
|
react-dom: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0)
|
||||||
sass: registry.npmmirror.com/sass@1.58.3
|
sass: registry.npmmirror.com/sass@1.58.3
|
||||||
styled-jsx: registry.npmmirror.com/styled-jsx@5.1.1(react@18.2.0)
|
styled-jsx: registry.npmmirror.com/styled-jsx@5.1.1(@babel/core@7.22.5)(react@18.2.0)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-android-arm-eabi': registry.npmmirror.com/@next/swc-android-arm-eabi@13.1.6
|
'@next/swc-android-arm-eabi': registry.npmmirror.com/@next/swc-android-arm-eabi@13.1.6
|
||||||
'@next/swc-android-arm64': registry.npmmirror.com/@next/swc-android-arm64@13.1.6
|
'@next/swc-android-arm64': registry.npmmirror.com/@next/swc-android-arm64@13.1.6
|
||||||
@ -10101,14 +10076,13 @@ packages:
|
|||||||
next: ^8.1.1-canary.54 || ^9.0.0 || ^10.0.0-0 || ^11.0.0 || ^12.0.0 || ^13.0.0
|
next: ^8.1.1-canary.54 || ^9.0.0 || ^10.0.0-0 || ^11.0.0 || ^12.0.0 || ^13.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
cors: registry.npmmirror.com/cors@2.8.5
|
cors: registry.npmmirror.com/cors@2.8.5
|
||||||
next: registry.npmmirror.com/next@13.1.6(react-dom@18.2.0)(react@18.2.0)(sass@1.58.3)
|
next: registry.npmmirror.com/next@13.1.6(@babel/core@7.22.5)(react-dom@18.2.0)(react@18.2.0)(sass@1.58.3)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/node-releases@2.0.12:
|
registry.npmmirror.com/node-releases@2.0.12:
|
||||||
resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/node-releases/-/node-releases-2.0.12.tgz}
|
resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/node-releases/-/node-releases-2.0.12.tgz}
|
||||||
name: node-releases
|
name: node-releases
|
||||||
version: 2.0.12
|
version: 2.0.12
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/nodemailer@6.9.1:
|
registry.npmmirror.com/nodemailer@6.9.1:
|
||||||
resolution: {integrity: sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/nodemailer/-/nodemailer-6.9.1.tgz}
|
resolution: {integrity: sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/nodemailer/-/nodemailer-6.9.1.tgz}
|
||||||
@ -11318,7 +11292,6 @@ packages:
|
|||||||
name: semver
|
name: semver
|
||||||
version: 6.3.0
|
version: 6.3.0
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/semver@7.5.1:
|
registry.npmmirror.com/semver@7.5.1:
|
||||||
resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/semver/-/semver-7.5.1.tgz}
|
resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/semver/-/semver-7.5.1.tgz}
|
||||||
@ -11625,7 +11598,7 @@ packages:
|
|||||||
inline-style-parser: registry.npmmirror.com/inline-style-parser@0.1.1
|
inline-style-parser: registry.npmmirror.com/inline-style-parser@0.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/styled-jsx@5.1.1(react@18.2.0):
|
registry.npmmirror.com/styled-jsx@5.1.1(@babel/core@7.22.5)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/styled-jsx/-/styled-jsx-5.1.1.tgz}
|
resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/styled-jsx/-/styled-jsx-5.1.1.tgz}
|
||||||
id: registry.npmmirror.com/styled-jsx/5.1.1
|
id: registry.npmmirror.com/styled-jsx/5.1.1
|
||||||
name: styled-jsx
|
name: styled-jsx
|
||||||
@ -11641,6 +11614,7 @@ packages:
|
|||||||
babel-plugin-macros:
|
babel-plugin-macros:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@babel/core': registry.npmmirror.com/@babel/core@7.22.5
|
||||||
client-only: registry.npmmirror.com/client-only@0.0.1
|
client-only: registry.npmmirror.com/client-only@0.0.1
|
||||||
react: registry.npmmirror.com/react@18.2.0
|
react: registry.npmmirror.com/react@18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
@ -12073,7 +12047,6 @@ packages:
|
|||||||
browserslist: registry.npmmirror.com/browserslist@4.21.7
|
browserslist: registry.npmmirror.com/browserslist@4.21.7
|
||||||
escalade: registry.npmmirror.com/escalade@3.1.1
|
escalade: registry.npmmirror.com/escalade@3.1.1
|
||||||
picocolors: registry.npmmirror.com/picocolors@1.0.0
|
picocolors: registry.npmmirror.com/picocolors@1.0.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/uri-js@4.4.1:
|
registry.npmmirror.com/uri-js@4.4.1:
|
||||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz}
|
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz}
|
||||||
@ -12429,7 +12402,3 @@ packages:
|
|||||||
name: zwitch
|
name: zwitch
|
||||||
version: 2.0.4
|
version: 2.0.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
settings:
|
|
||||||
autoInstallPeers: true
|
|
||||||
excludeLinksFromLockfile: false
|
|
||||||
|
|||||||
160
client/src/components/APIKeyModal/index.tsx
Normal file
160
client/src/components/APIKeyModal/index.tsx
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalOverlay,
|
||||||
|
ModalContent,
|
||||||
|
Flex,
|
||||||
|
ModalFooter,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
Table,
|
||||||
|
Thead,
|
||||||
|
Tbody,
|
||||||
|
Tr,
|
||||||
|
Th,
|
||||||
|
Td,
|
||||||
|
TableContainer,
|
||||||
|
IconButton
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { getOpenApiKeys, createAOpenApiKey, delOpenApiById } from '@/api/openapi';
|
||||||
|
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||||
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { AddIcon, DeleteIcon } from '@chakra-ui/icons';
|
||||||
|
import { getErrText, useCopyData } from '@/utils/tools';
|
||||||
|
import { useToast } from '@/hooks/useToast';
|
||||||
|
import MyIcon from '../Icon';
|
||||||
|
|
||||||
|
const APIKeyModal = ({ onClose }: { onClose: () => void }) => {
|
||||||
|
const { Loading } = useLoading();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const {
|
||||||
|
data: apiKeys = [],
|
||||||
|
isLoading: isGetting,
|
||||||
|
refetch
|
||||||
|
} = useQuery(['getOpenApiKeys'], getOpenApiKeys);
|
||||||
|
const [apiKey, setApiKey] = useState('');
|
||||||
|
const { copyData } = useCopyData();
|
||||||
|
|
||||||
|
const { mutate: onclickCreateApiKey, isLoading: isCreating } = useMutation({
|
||||||
|
mutationFn: () => createAOpenApiKey(),
|
||||||
|
onSuccess(res) {
|
||||||
|
setApiKey(res);
|
||||||
|
refetch();
|
||||||
|
},
|
||||||
|
onError(err) {
|
||||||
|
toast({
|
||||||
|
status: 'warning',
|
||||||
|
title: getErrText(err)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutate: onclickRemove, isLoading: isDeleting } = useMutation({
|
||||||
|
mutationFn: async (id: string) => delOpenApiById(id),
|
||||||
|
onSuccess() {
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent w={'600px'} maxW={'90vw'} position={'relative'}>
|
||||||
|
<Box py={3} px={5}>
|
||||||
|
<Box fontWeight={'bold'} fontSize={'2xl'}>
|
||||||
|
API 秘钥管理
|
||||||
|
</Box>
|
||||||
|
<Box fontSize={'sm'} color={'myGray.600'}>
|
||||||
|
如果你不想 API 秘钥被滥用,请勿将秘钥直接放置在前端使用~
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody minH={'300px'} maxH={['70vh', '500px']} overflow={'overlay'}>
|
||||||
|
<TableContainer mt={2} position={'relative'}>
|
||||||
|
<Table>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Api Key</Th>
|
||||||
|
<Th>创建时间</Th>
|
||||||
|
<Th>最后一次使用时间</Th>
|
||||||
|
<Th />
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody fontSize={'sm'}>
|
||||||
|
{apiKeys.map(({ id, apiKey, createTime, lastUsedTime }) => (
|
||||||
|
<Tr key={id}>
|
||||||
|
<Td>{apiKey}</Td>
|
||||||
|
<Td>{dayjs(createTime).format('YYYY/MM/DD HH:mm:ss')}</Td>
|
||||||
|
<Td>
|
||||||
|
{lastUsedTime
|
||||||
|
? dayjs(lastUsedTime).format('YYYY/MM/DD HH:mm:ss')
|
||||||
|
: '没有使用过'}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<IconButton
|
||||||
|
icon={<DeleteIcon />}
|
||||||
|
size={'xs'}
|
||||||
|
aria-label={'delete'}
|
||||||
|
variant={'base'}
|
||||||
|
colorScheme={'gray'}
|
||||||
|
onClick={() => onclickRemove(id)}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
variant="base"
|
||||||
|
leftIcon={<AddIcon color={'myGray.600'} fontSize={'sm'} />}
|
||||||
|
onClick={() => onclickCreateApiKey()}
|
||||||
|
>
|
||||||
|
新建秘钥
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
|
||||||
|
<Loading loading={isGetting || isCreating || isDeleting} fixed={false} />
|
||||||
|
</ModalContent>
|
||||||
|
<Modal isOpen={!!apiKey} onClose={() => setApiKey('')}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent w={'400px'} maxW={'90vw'}>
|
||||||
|
<Box py={3} px={5}>
|
||||||
|
<Box fontWeight={'bold'} fontSize={'2xl'}>
|
||||||
|
新的 API 秘钥
|
||||||
|
</Box>
|
||||||
|
<Box fontSize={'sm'} color={'myGray.600'}>
|
||||||
|
请保管好你的秘钥,秘钥不会再次展示~
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Flex
|
||||||
|
bg={'myGray.100'}
|
||||||
|
px={3}
|
||||||
|
py={2}
|
||||||
|
cursor={'pointer'}
|
||||||
|
onClick={() => copyData(apiKey)}
|
||||||
|
>
|
||||||
|
<Box flex={1}>{apiKey}</Box>
|
||||||
|
<MyIcon name={'copy'} w={'16px'}></MyIcon>
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant="base" onClick={() => setApiKey('')}>
|
||||||
|
好的
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default APIKeyModal;
|
||||||
1
client/src/components/Icon/icons/apikey.svg
Normal file
1
client/src/components/Icon/icons/apikey.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1686969412308" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3481" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M517.864056 487.834624c-56.774051-54.213739-58.850339-144.187937-4.6366-200.960964 54.212716-56.773028 144.187937-58.849316 200.960964-4.6366 56.775074 54.213739 58.850339 144.186913 4.6366 200.960964C664.613328 539.972075 574.639131 542.048363 517.864056 487.834624zM687.194626 452.994118c37.533848-39.308261 36.09508-101.596909-3.210112-139.128711-39.304168-37.531801-101.593839-36.094056-139.127687 3.211135-37.532825 39.307238-36.093033 101.593839 3.212158 139.125641C587.374176 493.736031 649.660778 492.302379 687.194626 452.994118zM479.104287 670.917406l-101.495602 106.289792c26.206872 25.024953 27.167756 66.540486 2.14178 92.749404-25.028023 26.209942-66.543555 27.16571-92.750427 2.140757l-58.361199 53.027727c0 0-68.750827 11.100826-100.379175-19.101033-31.630395-30.205952-37.865399-112.721271-37.865399-112.721271l246.37427-258.302951c-63.173808-117.608581-47.24707-267.162736 49.939389-368.939747 36.517705-38.242999 80.346933-65.156976 127.165238-81.040734l1.084705 46.269813c-35.443233 14.07967-68.566632 35.596729-96.618525 64.973804-80.271208 84.064604-96.099708 205.865671-49.433876 305.083393l23.075555 39.163975L146.090774 798.015106c0 0 0.593518 49.77873 17.242709 65.677838 14.888082 14.216793 61.832254 9.828856 61.832254 9.828856l60.407812-63.260789 31.631418 30.203906c8.741082 8.346085 22.570042 8.030907 30.91715-0.711198 8.347109-8.742105 8.026814-22.571065-0.713244-30.91715l-31.632441-30.207999 156.456355-163.846672 39.009456 22.481014c101.259218 42.039465 222.201731 20.61041 302.474986-63.453171 104.251366-109.178585 100.260471-282.211477-8.91709-386.464889-33.591049-32.075533-73.260537-53.829999-115.093295-65.49262l-1.030469-45.153386c53.197596 12.471033 103.945397 38.547944 146.323577 79.015611 126.645398 120.931257 131.277906 321.649698 10.344602 448.296119C748.158093 705.787588 599.500355 728.598106 479.104287 670.917406z" p-id="3482"></path></svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
@ -1 +0,0 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683254594671" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1491" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M46.95735957 106.20989621h930.08528086v158.0067668H46.95735957zM46.95735957 353.99323467v608.68515424h930.08528086V353.99323467H46.95735957z m346.5375657 418.35882335L328.85579413 835.19565715l-165.18889183-172.37101684 165.18889183-172.37101686 64.63913114 62.84359914-105.93635373 109.52741772 105.93635373 109.52741771z m127.48273175 62.84359913l-86.18550917-23.34190854 87.98104116-330.37778366 86.1855077 23.34191003L520.97765702 835.19565715z m193.91739489 0l-64.63913114-62.84359913 105.93635372-109.52741771-105.93635372-109.52741772 64.63913114-62.84359914 165.18889182 172.37101686-165.18889182 172.37101684z" p-id="1492"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 976 B |
@ -6,7 +6,6 @@ const map = {
|
|||||||
model: require('./icons/model.svg').default,
|
model: require('./icons/model.svg').default,
|
||||||
copy: require('./icons/copy.svg').default,
|
copy: require('./icons/copy.svg').default,
|
||||||
chatSend: require('./icons/chatSend.svg').default,
|
chatSend: require('./icons/chatSend.svg').default,
|
||||||
develop: require('./icons/develop.svg').default,
|
|
||||||
user: require('./icons/user.svg').default,
|
user: require('./icons/user.svg').default,
|
||||||
delete: require('./icons/delete.svg').default,
|
delete: require('./icons/delete.svg').default,
|
||||||
withdraw: require('./icons/withdraw.svg').default,
|
withdraw: require('./icons/withdraw.svg').default,
|
||||||
@ -34,7 +33,8 @@ const map = {
|
|||||||
text: require('./icons/text.svg').default,
|
text: require('./icons/text.svg').default,
|
||||||
history: require('./icons/history.svg').default,
|
history: require('./icons/history.svg').default,
|
||||||
kbTest: require('./icons/kbTest.svg').default,
|
kbTest: require('./icons/kbTest.svg').default,
|
||||||
date: require('./icons/date.svg').default
|
date: require('./icons/date.svg').default,
|
||||||
|
apikey: require('./icons/apikey.svg').default
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IconName = keyof typeof map;
|
export type IconName = keyof typeof map;
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
type TIconfont = {
|
|
||||||
name: string;
|
|
||||||
color?: string;
|
|
||||||
width?: number | string;
|
|
||||||
height?: number | string;
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function Iconfont({ name, color = 'inherit', width = 16, height = 16, className = '' }: TIconfont) {
|
|
||||||
const style = {
|
|
||||||
fill: color,
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<svg className={`icon ${className}`} aria-hidden="true" style={style}>
|
|
||||||
<use xlinkHref={`#${name}`}></use>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Iconfont;
|
|
||||||
@ -44,12 +44,6 @@ const Navbar = ({ unread }: { unread: number }) => {
|
|||||||
link: '/model/share',
|
link: '/model/share',
|
||||||
activeLink: ['/model/share']
|
activeLink: ['/model/share']
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '开发',
|
|
||||||
icon: 'develop',
|
|
||||||
link: '/openapi',
|
|
||||||
activeLink: ['/openapi']
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: '账号',
|
label: '账号',
|
||||||
icon: 'user',
|
icon: 'user',
|
||||||
|
|||||||
81
client/src/components/Select/index.tsx
Normal file
81
client/src/components/Select/index.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Menu, MenuButton, MenuList, MenuItem, Button, useDisclosure } from '@chakra-ui/react';
|
||||||
|
import type { ButtonProps } from '@chakra-ui/react';
|
||||||
|
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||||
|
interface Props extends ButtonProps {
|
||||||
|
value?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
list: {
|
||||||
|
label: string;
|
||||||
|
id: string;
|
||||||
|
}[];
|
||||||
|
onchange?: (val: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MySelect = ({ placeholder, value, width = 'auto', list, onchange, ...props }: Props) => {
|
||||||
|
const menuItemStyles = {
|
||||||
|
borderRadius: 'sm',
|
||||||
|
py: 2,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
_hover: {
|
||||||
|
backgroundColor: 'myWhite.600'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu autoSelect={false} onOpen={onOpen} onClose={onClose}>
|
||||||
|
<MenuButton as={'span'}>
|
||||||
|
<Button
|
||||||
|
width={width}
|
||||||
|
px={3}
|
||||||
|
variant={'base'}
|
||||||
|
display={'flex'}
|
||||||
|
alignItems={'center'}
|
||||||
|
justifyContent={'space-between'}
|
||||||
|
{...(isOpen
|
||||||
|
? {
|
||||||
|
boxShadow: '0px 0px 4px #A8DBFF',
|
||||||
|
borderColor: 'myBlue.600'
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{list.find((item) => item.id === value)?.label || placeholder}
|
||||||
|
<ChevronDownIcon />
|
||||||
|
</Button>
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList
|
||||||
|
minW={
|
||||||
|
Array.isArray(width) ? width.map((item) => `${item} !important`) : `${width} !important`
|
||||||
|
}
|
||||||
|
p={'6px'}
|
||||||
|
border={'1px solid #fff'}
|
||||||
|
boxShadow={'0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);'}
|
||||||
|
zIndex={99}
|
||||||
|
>
|
||||||
|
{list.map((item) => (
|
||||||
|
<MenuItem
|
||||||
|
key={item.id}
|
||||||
|
{...menuItemStyles}
|
||||||
|
{...(value === item.id
|
||||||
|
? {
|
||||||
|
color: 'myBlue.600'
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
|
onClick={() => {
|
||||||
|
if (onchange && value !== item.id) {
|
||||||
|
onchange(item.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MySelect;
|
||||||
@ -9,28 +9,30 @@ import {
|
|||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
const MySlider = ({
|
const MySlider = ({
|
||||||
markList,
|
markList = [],
|
||||||
setVal,
|
setVal,
|
||||||
activeVal,
|
activeVal,
|
||||||
max = 100,
|
max = 100,
|
||||||
min = 0,
|
min = 0,
|
||||||
step = 1
|
step = 1,
|
||||||
|
width = '100%'
|
||||||
}: {
|
}: {
|
||||||
markList: {
|
markList?: {
|
||||||
label: string | number;
|
label: string | number;
|
||||||
value: number;
|
value: number;
|
||||||
}[];
|
}[];
|
||||||
activeVal?: number;
|
activeVal: number;
|
||||||
setVal: (index: number) => void;
|
setVal: (index: number) => void;
|
||||||
max?: number;
|
max?: number;
|
||||||
min?: number;
|
min?: number;
|
||||||
step?: number;
|
step?: number;
|
||||||
|
width?: string | string[] | number | number[];
|
||||||
}) => {
|
}) => {
|
||||||
const startEndPointStyle = {
|
const startEndPointStyle = {
|
||||||
content: '""',
|
content: '""',
|
||||||
borderRadius: '10px',
|
borderRadius: '6px',
|
||||||
width: '10px',
|
width: '6px',
|
||||||
height: '10px',
|
height: '6px',
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
border: '2px solid #D7DBE2',
|
border: '2px solid #D7DBE2',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -44,37 +46,62 @@ const MySlider = ({
|
|||||||
}, [activeVal, markList]);
|
}, [activeVal, markList]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slider max={max} min={min} step={step} size={'lg'} value={value} onChange={setVal}>
|
<Slider
|
||||||
{markList.map((item, i) => (
|
max={max}
|
||||||
|
min={min}
|
||||||
|
step={step}
|
||||||
|
size={'lg'}
|
||||||
|
value={activeVal}
|
||||||
|
width={width}
|
||||||
|
onChange={setVal}
|
||||||
|
>
|
||||||
|
{markList?.map((item, i) => (
|
||||||
<SliderMark
|
<SliderMark
|
||||||
key={item.value}
|
key={item.value}
|
||||||
value={i}
|
value={item.value}
|
||||||
mt={3}
|
|
||||||
fontSize={'sm'}
|
fontSize={'sm'}
|
||||||
|
mt={3}
|
||||||
|
whiteSpace={'nowrap'}
|
||||||
transform={'translateX(-50%)'}
|
transform={'translateX(-50%)'}
|
||||||
{...(activeVal === item.value ? { color: 'myBlue.500', fontWeight: 'bold' } : {})}
|
color={'myGray.600'}
|
||||||
>
|
>
|
||||||
<Box px={3} cursor={'pointer'}>
|
<Box px={3} cursor={'pointer'}>
|
||||||
{item.label}
|
{item.label}
|
||||||
</Box>
|
</Box>
|
||||||
</SliderMark>
|
</SliderMark>
|
||||||
))}
|
))}
|
||||||
|
<SliderMark
|
||||||
|
value={activeVal}
|
||||||
|
textAlign="center"
|
||||||
|
bg="myBlue.600"
|
||||||
|
color="white"
|
||||||
|
px={1}
|
||||||
|
minW={'18px'}
|
||||||
|
w={'auto'}
|
||||||
|
h={'18px'}
|
||||||
|
borderRadius={'18px'}
|
||||||
|
fontSize={'xs'}
|
||||||
|
transform={'translate(-50%, -170%)'}
|
||||||
|
boxSizing={'border-box'}
|
||||||
|
>
|
||||||
|
{activeVal}
|
||||||
|
</SliderMark>
|
||||||
<SliderTrack
|
<SliderTrack
|
||||||
bg={'#EAEDF3'}
|
bg={'#EAEDF3'}
|
||||||
overflow={'visible'}
|
overflow={'visible'}
|
||||||
h={'4px'}
|
h={'4px'}
|
||||||
_before={{
|
_before={{
|
||||||
...startEndPointStyle,
|
...startEndPointStyle,
|
||||||
left: '-5px'
|
left: '-3px'
|
||||||
}}
|
}}
|
||||||
_after={{
|
_after={{
|
||||||
...startEndPointStyle,
|
...startEndPointStyle,
|
||||||
right: '-5px'
|
right: '-3px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SliderFilledTrack />
|
<SliderFilledTrack bg={'myBlue.600'} />
|
||||||
</SliderTrack>
|
</SliderTrack>
|
||||||
<SliderThumb border={'2.5px solid'} borderColor={'myBlue.500'}></SliderThumb>
|
<SliderThumb border={'3px solid'} borderColor={'myBlue.600'}></SliderThumb>
|
||||||
</Slider>
|
</Slider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,13 +24,13 @@ const Tabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
|
|||||||
return {
|
return {
|
||||||
fontSize: 'md',
|
fontSize: 'md',
|
||||||
outP: '4px',
|
outP: '4px',
|
||||||
inlineP: 2
|
inlineP: 1
|
||||||
};
|
};
|
||||||
case 'lg':
|
case 'lg':
|
||||||
return {
|
return {
|
||||||
fontSize: 'lg',
|
fontSize: 'lg',
|
||||||
outP: '5px',
|
outP: '5px',
|
||||||
inlineP: 3
|
inlineP: 2
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [size]);
|
}, [size]);
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export const ChatModelMap = {
|
|||||||
[OpenAiChatEnum.GPT35]: {
|
[OpenAiChatEnum.GPT35]: {
|
||||||
chatModel: OpenAiChatEnum.GPT35,
|
chatModel: OpenAiChatEnum.GPT35,
|
||||||
name: 'Gpt35-4k',
|
name: 'Gpt35-4k',
|
||||||
contextMaxToken: 4096,
|
contextMaxToken: 4000,
|
||||||
systemMaxToken: 2400,
|
systemMaxToken: 2400,
|
||||||
maxTemperature: 1.2,
|
maxTemperature: 1.2,
|
||||||
price: 2.2
|
price: 2.2
|
||||||
@ -80,70 +80,18 @@ export const getChatModelList = async () => {
|
|||||||
return list;
|
return list;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum ModelStatusEnum {
|
|
||||||
running = 'running',
|
|
||||||
training = 'training',
|
|
||||||
pending = 'pending',
|
|
||||||
closed = 'closed'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const formatModelStatus = {
|
|
||||||
[ModelStatusEnum.running]: {
|
|
||||||
colorTheme: 'green',
|
|
||||||
text: '运行中'
|
|
||||||
},
|
|
||||||
[ModelStatusEnum.training]: {
|
|
||||||
colorTheme: 'blue',
|
|
||||||
text: '训练中'
|
|
||||||
},
|
|
||||||
[ModelStatusEnum.pending]: {
|
|
||||||
colorTheme: 'gray',
|
|
||||||
text: '加载中'
|
|
||||||
},
|
|
||||||
[ModelStatusEnum.closed]: {
|
|
||||||
colorTheme: 'red',
|
|
||||||
text: '已关闭'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 知识库搜索时的配置 */
|
|
||||||
// 搜索方式
|
|
||||||
export enum appVectorSearchModeEnum {
|
|
||||||
hightSimilarity = 'hightSimilarity', // 高相似度+禁止回复
|
|
||||||
lowSimilarity = 'lowSimilarity', // 低相似度
|
|
||||||
noContext = 'noContex' // 高相似度+无上下文回复
|
|
||||||
}
|
|
||||||
export const ModelVectorSearchModeMap: Record<
|
|
||||||
`${appVectorSearchModeEnum}`,
|
|
||||||
{
|
|
||||||
text: string;
|
|
||||||
similarity: number;
|
|
||||||
}
|
|
||||||
> = {
|
|
||||||
[appVectorSearchModeEnum.hightSimilarity]: {
|
|
||||||
text: '高相似度, 无匹配时拒绝回复',
|
|
||||||
similarity: 0.8
|
|
||||||
},
|
|
||||||
[appVectorSearchModeEnum.noContext]: {
|
|
||||||
text: '高相似度,无匹配时直接回复',
|
|
||||||
similarity: 0.8
|
|
||||||
},
|
|
||||||
[appVectorSearchModeEnum.lowSimilarity]: {
|
|
||||||
text: '低相似度匹配',
|
|
||||||
similarity: 0.3
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const defaultModel: ModelSchema = {
|
export const defaultModel: ModelSchema = {
|
||||||
_id: 'modelId',
|
_id: 'modelId',
|
||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
name: '模型名称',
|
name: '模型名称',
|
||||||
avatar: '/icon/logo.png',
|
avatar: '/icon/logo.png',
|
||||||
status: ModelStatusEnum.pending,
|
intro: '',
|
||||||
updateTime: Date.now(),
|
updateTime: Date.now(),
|
||||||
chat: {
|
chat: {
|
||||||
relatedKbs: [],
|
relatedKbs: [],
|
||||||
searchMode: appVectorSearchModeEnum.hightSimilarity,
|
searchSimilarity: 0.2,
|
||||||
|
searchLimit: 5,
|
||||||
|
searchEmptyText: '',
|
||||||
systemPrompt: '',
|
systemPrompt: '',
|
||||||
temperature: 0,
|
temperature: 0,
|
||||||
chatModel: OpenAiChatEnum.GPT35
|
chatModel: OpenAiChatEnum.GPT35
|
||||||
@ -151,7 +99,6 @@ export const defaultModel: ModelSchema = {
|
|||||||
share: {
|
share: {
|
||||||
isShare: false,
|
isShare: false,
|
||||||
isShareDetail: false,
|
isShareDetail: false,
|
||||||
intro: '',
|
|
||||||
collection: 0
|
collection: 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,9 +15,7 @@ const { definePartsStyle: selectPart, defineMultiStyleConfig: selectMultiStyle }
|
|||||||
// modal 弹窗
|
// modal 弹窗
|
||||||
const ModalTheme = defineMultiStyleConfig({
|
const ModalTheme = defineMultiStyleConfig({
|
||||||
baseStyle: definePartsStyle({
|
baseStyle: definePartsStyle({
|
||||||
dialog: {
|
dialog: {}
|
||||||
width: '90%'
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -8,9 +8,9 @@ import { theme } from '@/constants/theme';
|
|||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import NProgress from 'nprogress'; //nprogress module
|
import NProgress from 'nprogress'; //nprogress module
|
||||||
import Router from 'next/router';
|
import Router from 'next/router';
|
||||||
import 'nprogress/nprogress.css';
|
|
||||||
import '../styles/reset.scss';
|
|
||||||
import { useGlobalStore } from '@/store/global';
|
import { useGlobalStore } from '@/store/global';
|
||||||
|
import 'nprogress/nprogress.css';
|
||||||
|
import '@/styles/reset.scss';
|
||||||
|
|
||||||
//Binding events.
|
//Binding events.
|
||||||
Router.events.on('routeChangeStart', () => NProgress.start());
|
Router.events.on('routeChangeStart', () => NProgress.start());
|
||||||
@ -28,7 +28,7 @@ const queryClient = new QueryClient({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
function App({ Component, pageProps }: AppProps) {
|
||||||
const {
|
const {
|
||||||
loadInitData,
|
loadInitData,
|
||||||
initData: { googleVerKey }
|
initData: { googleVerKey }
|
||||||
@ -78,6 +78,5 @@ export default function App({ Component, pageProps }: AppProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function reportWebVitals(metric: NextWebVitalsMetric) {
|
// @ts-ignore
|
||||||
// console.log(metric);
|
export default App;
|
||||||
// }
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { authChat } from '@/service/utils/auth';
|
|||||||
import { modelServiceToolMap } from '@/service/utils/chat';
|
import { modelServiceToolMap } from '@/service/utils/chat';
|
||||||
import { ChatItemType } from '@/types/chat';
|
import { ChatItemType } from '@/types/chat';
|
||||||
import { jsonRes } from '@/service/response';
|
import { jsonRes } from '@/service/response';
|
||||||
import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
|
import { ChatModelMap } from '@/constants/model';
|
||||||
import { pushChatBill } from '@/service/events/pushBill';
|
import { pushChatBill } from '@/service/events/pushBill';
|
||||||
import { resStreamResponse } from '@/service/utils/chat';
|
import { resStreamResponse } from '@/service/utils/chat';
|
||||||
import { appKbSearch } from '../openapi/kb/appKbSearch';
|
import { appKbSearch } from '../openapi/kb/appKbSearch';
|
||||||
@ -48,36 +48,31 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
|
|
||||||
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
||||||
|
|
||||||
// 读取对话内容
|
|
||||||
const prompts = [...content, prompt[0]];
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
code = 200,
|
rawSearch = [],
|
||||||
systemPrompts = [],
|
userSystemPrompt = [],
|
||||||
quote = [],
|
quotePrompt = []
|
||||||
guidePrompt = ''
|
|
||||||
} = await (async () => {
|
} = await (async () => {
|
||||||
// 使用了知识库搜索
|
// 使用了知识库搜索
|
||||||
if (model.chat.relatedKbs?.length > 0) {
|
if (model.chat.relatedKbs?.length > 0) {
|
||||||
const { code, searchPrompts, rawSearch, guidePrompt } = await appKbSearch({
|
const { rawSearch, userSystemPrompt, quotePrompt } = await appKbSearch({
|
||||||
model,
|
model,
|
||||||
userId,
|
userId,
|
||||||
fixedQuote: content[content.length - 1]?.quote || [],
|
fixedQuote: content[content.length - 1]?.quote || [],
|
||||||
prompt: prompt[0],
|
prompt: prompt[0],
|
||||||
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity
|
similarity: model.chat.searchSimilarity,
|
||||||
|
limit: model.chat.searchLimit
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code,
|
rawSearch: rawSearch,
|
||||||
quote: rawSearch,
|
userSystemPrompt: userSystemPrompt ? [userSystemPrompt] : [],
|
||||||
systemPrompts: searchPrompts,
|
quotePrompt: [quotePrompt]
|
||||||
guidePrompt
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (model.chat.systemPrompt) {
|
if (model.chat.systemPrompt) {
|
||||||
return {
|
return {
|
||||||
guidePrompt: model.chat.systemPrompt,
|
userSystemPrompt: [
|
||||||
systemPrompts: [
|
|
||||||
{
|
{
|
||||||
obj: ChatRoleEnum.System,
|
obj: ChatRoleEnum.System,
|
||||||
value: model.chat.systemPrompt
|
value: model.chat.systemPrompt
|
||||||
@ -92,13 +87,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
const conversationId = chatId || String(new Types.ObjectId());
|
const conversationId = chatId || String(new Types.ObjectId());
|
||||||
!chatId && res.setHeader(NEW_CHATID_HEADER, conversationId);
|
!chatId && res.setHeader(NEW_CHATID_HEADER, conversationId);
|
||||||
if (showModelDetail) {
|
if (showModelDetail) {
|
||||||
guidePrompt && res.setHeader(GUIDE_PROMPT_HEADER, encodeURIComponent(guidePrompt));
|
userSystemPrompt[0] &&
|
||||||
res.setHeader(QUOTE_LEN_HEADER, quote.length);
|
res.setHeader(GUIDE_PROMPT_HEADER, encodeURIComponent(userSystemPrompt[0].value));
|
||||||
|
res.setHeader(QUOTE_LEN_HEADER, rawSearch.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// search result is empty
|
// search result is empty
|
||||||
if (code === 201) {
|
if (model.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && model.chat.searchEmptyText) {
|
||||||
const response = systemPrompts[0]?.value;
|
const response = model.chat.searchEmptyText;
|
||||||
await saveChat({
|
await saveChat({
|
||||||
chatId,
|
chatId,
|
||||||
newChatId: conversationId,
|
newChatId: conversationId,
|
||||||
@ -116,11 +112,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
return res.end(response);
|
return res.end(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
prompts.unshift(...systemPrompts);
|
// 读取对话内容
|
||||||
|
const prompts = [...quotePrompt, ...content, ...userSystemPrompt, prompt[0]];
|
||||||
|
|
||||||
// content check
|
// content check
|
||||||
await sensitiveCheck({
|
await sensitiveCheck({
|
||||||
input: [...systemPrompts, prompt[0]].map((item) => item.value).join('')
|
input: [...quotePrompt, ...userSystemPrompt, prompt[0]].map((item) => item.value).join('')
|
||||||
});
|
});
|
||||||
|
|
||||||
// 计算温度
|
// 计算温度
|
||||||
@ -162,8 +159,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
{
|
{
|
||||||
...prompt[1],
|
...prompt[1],
|
||||||
value: responseContent,
|
value: responseContent,
|
||||||
quote: showModelDetail ? quote : [],
|
quote: showModelDetail ? rawSearch : [],
|
||||||
systemPrompt: showModelDetail ? guidePrompt : ''
|
systemPrompt: showModelDetail ? userSystemPrompt[0]?.value : ''
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
userId
|
userId
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { authUser } from '@/service/utils/auth';
|
|||||||
import { ChatItemType } from '@/types/chat';
|
import { ChatItemType } from '@/types/chat';
|
||||||
import { authModel } from '@/service/utils/auth';
|
import { authModel } from '@/service/utils/auth';
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import { ModelStatusEnum } from '@/constants/model';
|
|
||||||
import type { ModelSchema } from '@/types/mongoSchema';
|
import type { ModelSchema } from '@/types/mongoSchema';
|
||||||
|
|
||||||
/* 初始化我的聊天框,需要身份验证 */
|
/* 初始化我的聊天框,需要身份验证 */
|
||||||
@ -29,8 +28,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
if (!myModel) {
|
if (!myModel) {
|
||||||
const { _id } = await Model.create({
|
const { _id } = await Model.create({
|
||||||
name: '应用1',
|
name: '应用1',
|
||||||
userId,
|
userId
|
||||||
status: ModelStatusEnum.running
|
|
||||||
});
|
});
|
||||||
model = (await Model.findById(_id)) as ModelSchema;
|
model = (await Model.findById(_id)) as ModelSchema;
|
||||||
} else {
|
} else {
|
||||||
@ -95,7 +93,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
model: {
|
model: {
|
||||||
name: model.name,
|
name: model.name,
|
||||||
avatar: model.avatar,
|
avatar: model.avatar,
|
||||||
intro: model.share.intro,
|
intro: model.intro,
|
||||||
canUse: model.share.isShare || String(model.userId) === userId
|
canUse: model.share.isShare || String(model.userId) === userId
|
||||||
},
|
},
|
||||||
chatModel: model.chat.chatModel,
|
chatModel: model.chat.chatModel,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { authShareChat } from '@/service/utils/auth';
|
|||||||
import { modelServiceToolMap } from '@/service/utils/chat';
|
import { modelServiceToolMap } from '@/service/utils/chat';
|
||||||
import { ChatItemSimpleType } from '@/types/chat';
|
import { ChatItemSimpleType } from '@/types/chat';
|
||||||
import { jsonRes } from '@/service/response';
|
import { jsonRes } from '@/service/response';
|
||||||
import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
|
import { ChatModelMap } from '@/constants/model';
|
||||||
import { pushChatBill, updateShareChatBill } from '@/service/events/pushBill';
|
import { pushChatBill, updateShareChatBill } from '@/service/events/pushBill';
|
||||||
import { resStreamResponse } from '@/service/utils/chat';
|
import { resStreamResponse } from '@/service/utils/chat';
|
||||||
import { ChatRoleEnum } from '@/constants/chat';
|
import { ChatRoleEnum } from '@/constants/chat';
|
||||||
@ -40,26 +40,33 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
});
|
});
|
||||||
|
|
||||||
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
||||||
|
const prompt = prompts[prompts.length - 1];
|
||||||
|
|
||||||
const { code = 200, systemPrompts = [] } = await (async () => {
|
const {
|
||||||
|
rawSearch = [],
|
||||||
|
userSystemPrompt = [],
|
||||||
|
quotePrompt = []
|
||||||
|
} = await (async () => {
|
||||||
// 使用了知识库搜索
|
// 使用了知识库搜索
|
||||||
if (model.chat.relatedKbs?.length > 0) {
|
if (model.chat.relatedKbs?.length > 0) {
|
||||||
const { code, searchPrompts } = await appKbSearch({
|
const { rawSearch, userSystemPrompt, quotePrompt } = await appKbSearch({
|
||||||
model,
|
model,
|
||||||
userId,
|
userId,
|
||||||
fixedQuote: [],
|
fixedQuote: [],
|
||||||
prompt: prompts[prompts.length - 1],
|
prompt: prompt,
|
||||||
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity
|
similarity: model.chat.searchSimilarity,
|
||||||
|
limit: model.chat.searchLimit
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code,
|
rawSearch: rawSearch,
|
||||||
systemPrompts: searchPrompts
|
userSystemPrompt: userSystemPrompt ? [userSystemPrompt] : [],
|
||||||
|
quotePrompt: [quotePrompt]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (model.chat.systemPrompt) {
|
if (model.chat.systemPrompt) {
|
||||||
return {
|
return {
|
||||||
systemPrompts: [
|
userSystemPrompt: [
|
||||||
{
|
{
|
||||||
obj: ChatRoleEnum.System,
|
obj: ChatRoleEnum.System,
|
||||||
value: model.chat.systemPrompt
|
value: model.chat.systemPrompt
|
||||||
@ -71,15 +78,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
// search result is empty
|
// search result is empty
|
||||||
if (code === 201) {
|
if (model.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && model.chat.searchEmptyText) {
|
||||||
return res.send(systemPrompts[0]?.value);
|
const response = model.chat.searchEmptyText;
|
||||||
|
return res.end(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
prompts.unshift(...systemPrompts);
|
// 读取对话内容
|
||||||
|
const completePrompts = [...quotePrompt, ...prompts.slice(0, -1), ...userSystemPrompt, prompt];
|
||||||
|
|
||||||
// content check
|
// content check
|
||||||
await sensitiveCheck({
|
await sensitiveCheck({
|
||||||
input: [...systemPrompts, prompts[prompts.length - 1]].map((item) => item.value).join('')
|
input: [...quotePrompt, ...userSystemPrompt, prompt].map((item) => item.value).join('')
|
||||||
});
|
});
|
||||||
|
|
||||||
// 计算温度
|
// 计算温度
|
||||||
@ -93,7 +102,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
].chatCompletion({
|
].chatCompletion({
|
||||||
apiKey: userOpenAiKey || systemAuthKey,
|
apiKey: userOpenAiKey || systemAuthKey,
|
||||||
temperature: +temperature,
|
temperature: +temperature,
|
||||||
messages: prompts,
|
messages: completePrompts,
|
||||||
stream: true,
|
stream: true,
|
||||||
res,
|
res,
|
||||||
chatId: historyId
|
chatId: historyId
|
||||||
|
|||||||
@ -50,7 +50,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
model: {
|
model: {
|
||||||
name: model.name,
|
name: model.name,
|
||||||
avatar: model.avatar,
|
avatar: model.avatar,
|
||||||
intro: model.share.intro
|
intro: model.intro
|
||||||
},
|
},
|
||||||
chatModel: model.chat.chatModel
|
chatModel: model.chat.chatModel
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
|||||||
import { jsonRes } from '@/service/response';
|
import { jsonRes } from '@/service/response';
|
||||||
import { connectToDatabase } from '@/service/mongo';
|
import { connectToDatabase } from '@/service/mongo';
|
||||||
import { authUser } from '@/service/utils/auth';
|
import { authUser } from '@/service/utils/auth';
|
||||||
import { ModelStatusEnum } from '@/constants/model';
|
|
||||||
import { Model } from '@/service/models/model';
|
import { Model } from '@/service/models/model';
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
@ -32,8 +31,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
// 创建模型
|
// 创建模型
|
||||||
const response = await Model.create({
|
const response = await Model.create({
|
||||||
name,
|
name,
|
||||||
userId,
|
userId
|
||||||
status: ModelStatusEnum.running
|
|
||||||
});
|
});
|
||||||
|
|
||||||
jsonRes(res, {
|
jsonRes(res, {
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
$and: [
|
$and: [
|
||||||
{ 'share.isShare': true },
|
{ 'share.isShare': true },
|
||||||
{
|
{
|
||||||
$or: [{ name: { $regex: regex } }, { 'share.intro': { $regex: regex } }]
|
$or: [{ name: { $regex: regex } }, { intro: { $regex: regex } }]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -66,6 +66,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
avatar: { $ifNull: ['$avatar', '/icon/logo.png'] },
|
avatar: { $ifNull: ['$avatar', '/icon/logo.png'] },
|
||||||
name: 1,
|
name: 1,
|
||||||
userId: 1,
|
userId: 1,
|
||||||
|
intro: 1,
|
||||||
share: 1,
|
share: 1,
|
||||||
isCollection: {
|
isCollection: {
|
||||||
$cond: {
|
$cond: {
|
||||||
|
|||||||
@ -9,10 +9,10 @@ import { authModel } from '@/service/utils/auth';
|
|||||||
/* 获取我的模型 */
|
/* 获取我的模型 */
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
try {
|
try {
|
||||||
const { name, avatar, chat, share } = req.body as ModelUpdateParams;
|
const { name, avatar, chat, share, intro } = req.body as ModelUpdateParams;
|
||||||
const { modelId } = req.query as { modelId: string };
|
const { modelId } = req.query as { modelId: string };
|
||||||
|
|
||||||
if (!name || !chat || !modelId) {
|
if (!modelId) {
|
||||||
throw new Error('参数错误');
|
throw new Error('参数错误');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,10 +35,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
{
|
{
|
||||||
name,
|
name,
|
||||||
avatar,
|
avatar,
|
||||||
|
intro,
|
||||||
chat,
|
chat,
|
||||||
'share.isShare': share.isShare,
|
...(share && {
|
||||||
'share.isShareDetail': share.isShareDetail,
|
'share.isShare': share.isShare,
|
||||||
'share.intro': share.intro
|
'share.isShareDetail': share.isShareDetail
|
||||||
|
})
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -4,12 +4,11 @@ import { authUser, authModel, getApiKey } from '@/service/utils/auth';
|
|||||||
import { modelServiceToolMap, resStreamResponse } from '@/service/utils/chat';
|
import { modelServiceToolMap, resStreamResponse } from '@/service/utils/chat';
|
||||||
import { ChatItemSimpleType } from '@/types/chat';
|
import { ChatItemSimpleType } from '@/types/chat';
|
||||||
import { jsonRes } from '@/service/response';
|
import { jsonRes } from '@/service/response';
|
||||||
import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
|
import { ChatModelMap } from '@/constants/model';
|
||||||
import { pushChatBill } from '@/service/events/pushBill';
|
import { pushChatBill } from '@/service/events/pushBill';
|
||||||
import { ChatRoleEnum } from '@/constants/chat';
|
import { ChatRoleEnum } from '@/constants/chat';
|
||||||
import { withNextCors } from '@/service/utils/tools';
|
import { withNextCors } from '@/service/utils/tools';
|
||||||
import { BillTypeEnum } from '@/constants/user';
|
import { BillTypeEnum } from '@/constants/user';
|
||||||
import { sensitiveCheck } from '../../openapi/text/sensitiveCheck';
|
|
||||||
import { NEW_CHATID_HEADER } from '@/constants/chat';
|
import { NEW_CHATID_HEADER } from '@/constants/chat';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { appKbSearch } from '../kb/appKbSearch';
|
import { appKbSearch } from '../kb/appKbSearch';
|
||||||
@ -66,48 +65,46 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
});
|
});
|
||||||
|
|
||||||
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
||||||
|
const prompt = prompts[prompts.length - 1];
|
||||||
|
|
||||||
let systemPrompts: {
|
const { userSystemPrompt = [], quotePrompt = [] } = await (async () => {
|
||||||
obj: ChatRoleEnum;
|
// 使用了知识库搜索
|
||||||
value: string;
|
if (model.chat.relatedKbs?.length > 0) {
|
||||||
}[] = [];
|
const { userSystemPrompt, quotePrompt } = await appKbSearch({
|
||||||
|
model,
|
||||||
|
userId,
|
||||||
|
fixedQuote: [],
|
||||||
|
prompt: prompt,
|
||||||
|
similarity: model.chat.searchSimilarity,
|
||||||
|
limit: model.chat.searchLimit
|
||||||
|
});
|
||||||
|
|
||||||
// 使用了知识库搜索
|
return {
|
||||||
if (model.chat.relatedKbs?.length > 0) {
|
userSystemPrompt: userSystemPrompt ? [userSystemPrompt] : [],
|
||||||
const { code, searchPrompts } = await appKbSearch({
|
quotePrompt: [quotePrompt]
|
||||||
model,
|
};
|
||||||
userId,
|
|
||||||
fixedQuote: [],
|
|
||||||
prompt: prompts[prompts.length - 1],
|
|
||||||
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity
|
|
||||||
});
|
|
||||||
|
|
||||||
// search result is empty
|
|
||||||
if (code === 201) {
|
|
||||||
return isStream
|
|
||||||
? res.send(searchPrompts[0]?.value)
|
|
||||||
: jsonRes(res, {
|
|
||||||
data: searchPrompts[0]?.value,
|
|
||||||
message: searchPrompts[0]?.value
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
if (model.chat.systemPrompt) {
|
||||||
|
return {
|
||||||
|
userSystemPrompt: [
|
||||||
|
{
|
||||||
|
obj: ChatRoleEnum.System,
|
||||||
|
value: model.chat.systemPrompt
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
})();
|
||||||
|
|
||||||
systemPrompts = searchPrompts;
|
// search result is empty
|
||||||
} else if (model.chat.systemPrompt) {
|
if (model.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && model.chat.searchEmptyText) {
|
||||||
systemPrompts = [
|
const response = model.chat.searchEmptyText;
|
||||||
{
|
return res.end(response);
|
||||||
obj: ChatRoleEnum.System,
|
|
||||||
value: model.chat.systemPrompt
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prompts.unshift(...systemPrompts);
|
// 读取对话内容
|
||||||
|
const completePrompts = [...quotePrompt, ...prompts.slice(0, -1), ...userSystemPrompt, prompt];
|
||||||
// content check
|
|
||||||
await sensitiveCheck({
|
|
||||||
input: [...systemPrompts, prompts[prompts.length - 1]].map((item) => item.value).join('')
|
|
||||||
});
|
|
||||||
|
|
||||||
// 计算温度
|
// 计算温度
|
||||||
const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed(
|
const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed(
|
||||||
@ -123,7 +120,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
await modelServiceToolMap[model.chat.chatModel].chatCompletion({
|
await modelServiceToolMap[model.chat.chatModel].chatCompletion({
|
||||||
apiKey,
|
apiKey,
|
||||||
temperature: +temperature,
|
temperature: +temperature,
|
||||||
messages: prompts,
|
messages: completePrompts,
|
||||||
stream: isStream,
|
stream: isStream,
|
||||||
res,
|
res,
|
||||||
chatId: conversationId
|
chatId: conversationId
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
({ _id, apiKey, createTime, lastUsedTime }) => {
|
({ _id, apiKey, createTime, lastUsedTime }) => {
|
||||||
return {
|
return {
|
||||||
id: _id,
|
id: _id,
|
||||||
apiKey: `${apiKey.substring(0, 2)}******${apiKey.substring(apiKey.length - 2)}`,
|
apiKey: `******${apiKey.substring(apiKey.length - 4)}`,
|
||||||
createTime,
|
createTime,
|
||||||
lastUsedTime
|
lastUsedTime
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { PgClient } from '@/service/pg';
|
|||||||
import { withNextCors } from '@/service/utils/tools';
|
import { withNextCors } from '@/service/utils/tools';
|
||||||
import type { ChatItemSimpleType } from '@/types/chat';
|
import type { ChatItemSimpleType } from '@/types/chat';
|
||||||
import type { ModelSchema } from '@/types/mongoSchema';
|
import type { ModelSchema } from '@/types/mongoSchema';
|
||||||
import { appVectorSearchModeEnum } from '@/constants/model';
|
|
||||||
import { authModel } from '@/service/utils/auth';
|
import { authModel } from '@/service/utils/auth';
|
||||||
import { ChatModelMap } from '@/constants/model';
|
import { ChatModelMap } from '@/constants/model';
|
||||||
import { ChatRoleEnum } from '@/constants/chat';
|
import { ChatRoleEnum } from '@/constants/chat';
|
||||||
@ -21,16 +20,19 @@ export type QuoteItemType = {
|
|||||||
type Props = {
|
type Props = {
|
||||||
prompts: ChatItemSimpleType[];
|
prompts: ChatItemSimpleType[];
|
||||||
similarity: number;
|
similarity: number;
|
||||||
|
limit: number;
|
||||||
appId: string;
|
appId: string;
|
||||||
};
|
};
|
||||||
type Response = {
|
type Response = {
|
||||||
code: 200 | 201;
|
|
||||||
rawSearch: QuoteItemType[];
|
rawSearch: QuoteItemType[];
|
||||||
guidePrompt: string;
|
userSystemPrompt: {
|
||||||
searchPrompts: {
|
|
||||||
obj: ChatRoleEnum;
|
obj: ChatRoleEnum;
|
||||||
value: string;
|
value: string;
|
||||||
}[];
|
};
|
||||||
|
quotePrompt: {
|
||||||
|
obj: ChatRoleEnum;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
@ -41,7 +43,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
throw new Error('userId is empty');
|
throw new Error('userId is empty');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { prompts, similarity, appId } = req.body as Props;
|
const { prompts, similarity, limit, appId } = req.body as Props;
|
||||||
|
|
||||||
if (!similarity || !Array.isArray(prompts) || !appId) {
|
if (!similarity || !Array.isArray(prompts) || !appId) {
|
||||||
throw new Error('params is error');
|
throw new Error('params is error');
|
||||||
@ -58,7 +60,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
userId,
|
userId,
|
||||||
fixedQuote: [],
|
fixedQuote: [],
|
||||||
prompt: prompts[prompts.length - 1],
|
prompt: prompts[prompts.length - 1],
|
||||||
similarity
|
similarity,
|
||||||
|
limit
|
||||||
});
|
});
|
||||||
|
|
||||||
jsonRes<Response>(res, {
|
jsonRes<Response>(res, {
|
||||||
@ -78,13 +81,15 @@ export async function appKbSearch({
|
|||||||
userId,
|
userId,
|
||||||
fixedQuote,
|
fixedQuote,
|
||||||
prompt,
|
prompt,
|
||||||
similarity
|
similarity = 0.8,
|
||||||
|
limit = 5
|
||||||
}: {
|
}: {
|
||||||
model: ModelSchema;
|
model: ModelSchema;
|
||||||
userId: string;
|
userId: string;
|
||||||
fixedQuote: QuoteItemType[];
|
fixedQuote: QuoteItemType[];
|
||||||
prompt: ChatItemSimpleType;
|
prompt: ChatItemSimpleType;
|
||||||
similarity: number;
|
similarity: number;
|
||||||
|
limit: number;
|
||||||
}): Promise<Response> {
|
}): Promise<Response> {
|
||||||
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
const modelConstantsData = ChatModelMap[model.chat.chatModel];
|
||||||
|
|
||||||
@ -103,7 +108,7 @@ export async function appKbSearch({
|
|||||||
.map((item) => `'${item}'`)
|
.map((item) => `'${item}'`)
|
||||||
.join(',')}) AND vector <#> '[${promptVector[0]}]' < -${similarity} order by vector <#> '[${
|
.join(',')}) AND vector <#> '[${promptVector[0]}]' < -${similarity} order by vector <#> '[${
|
||||||
promptVector[0]
|
promptVector[0]
|
||||||
}]' limit 10;
|
}]' limit ${limit};
|
||||||
COMMIT;`
|
COMMIT;`
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -115,7 +120,7 @@ export async function appKbSearch({
|
|||||||
...searchRes.slice(0, 3),
|
...searchRes.slice(0, 3),
|
||||||
...fixedQuote.slice(0, 2),
|
...fixedQuote.slice(0, 2),
|
||||||
...searchRes.slice(3),
|
...searchRes.slice(3),
|
||||||
...fixedQuote.slice(2, 5)
|
...fixedQuote.slice(2, 4)
|
||||||
].filter((item) => {
|
].filter((item) => {
|
||||||
if (idSet.has(item.id)) {
|
if (idSet.has(item.id)) {
|
||||||
return false;
|
return false;
|
||||||
@ -125,86 +130,44 @@ export async function appKbSearch({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 计算固定提示词的 token 数量
|
// 计算固定提示词的 token 数量
|
||||||
const guidePrompt = model.chat.systemPrompt // user system prompt
|
const userSystemPrompt = model.chat.systemPrompt // user system prompt
|
||||||
? {
|
? {
|
||||||
obj: ChatRoleEnum.System,
|
obj: ChatRoleEnum.Human,
|
||||||
value: model.chat.systemPrompt
|
value: model.chat.systemPrompt
|
||||||
}
|
}
|
||||||
: model.chat.searchMode === appVectorSearchModeEnum.noContext
|
|
||||||
? {
|
|
||||||
obj: ChatRoleEnum.System,
|
|
||||||
value: `知识库是关于"${model.name}"的内容,根据知识库内容回答问题.`
|
|
||||||
}
|
|
||||||
: {
|
: {
|
||||||
obj: ChatRoleEnum.System,
|
obj: ChatRoleEnum.Human,
|
||||||
value: `玩一个问答游戏,规则为:
|
value: `知识库是关于 ${model.name} 的内容,参考知识库回答问题。与 "${model.name}" 无关内容,直接回复: "我不知道"。`
|
||||||
1.你完全忘记你已有的知识
|
|
||||||
2.你只回答关于"${model.name}"的问题
|
|
||||||
3.你只从知识库中选择内容进行回答
|
|
||||||
4.如果问题不在知识库中,你会回答:"我不知道。"
|
|
||||||
请务必遵守规则`
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const fixedSystemTokens = modelToolMap[model.chat.chatModel].countTokens({
|
const fixedSystemTokens = modelToolMap[model.chat.chatModel].countTokens({
|
||||||
messages: [guidePrompt]
|
messages: [userSystemPrompt]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// filter part quote by maxToken
|
||||||
const sliceResult = modelToolMap[model.chat.chatModel]
|
const sliceResult = modelToolMap[model.chat.chatModel]
|
||||||
.tokenSlice({
|
.tokenSlice({
|
||||||
maxToken: modelConstantsData.systemMaxToken - fixedSystemTokens,
|
maxToken: modelConstantsData.systemMaxToken - fixedSystemTokens,
|
||||||
messages: filterSearch.map((item) => ({
|
messages: filterSearch.map((item, i) => ({
|
||||||
obj: ChatRoleEnum.System,
|
obj: ChatRoleEnum.System,
|
||||||
value: `${item.q}\n${item.a}`
|
value: `${i + 1}: [${item.q}\n${item.a}]`
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.map((item) => item.value);
|
.map((item) => item.value)
|
||||||
|
.join('\n')
|
||||||
|
.trim();
|
||||||
|
|
||||||
// slice filterSearch
|
// slice filterSearch
|
||||||
const rawSearch = filterSearch.slice(0, sliceResult.length);
|
const rawSearch = filterSearch.slice(0, sliceResult.length);
|
||||||
|
|
||||||
// system prompt
|
const quoteText = sliceResult ? `知识库:\n${sliceResult}` : '';
|
||||||
const systemPrompt = sliceResult.join('\n').trim();
|
|
||||||
|
|
||||||
/* 高相似度+不回复 */
|
|
||||||
if (!systemPrompt && model.chat.searchMode === appVectorSearchModeEnum.hightSimilarity) {
|
|
||||||
return {
|
|
||||||
code: 201,
|
|
||||||
rawSearch: [],
|
|
||||||
guidePrompt: '',
|
|
||||||
searchPrompts: [
|
|
||||||
{
|
|
||||||
obj: ChatRoleEnum.System,
|
|
||||||
value: '对不起,你的问题不在知识库中。'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/* 高相似度+无上下文,不添加额外知识,仅用系统提示词 */
|
|
||||||
if (!systemPrompt && model.chat.searchMode === appVectorSearchModeEnum.noContext) {
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
rawSearch: [],
|
|
||||||
guidePrompt: model.chat.systemPrompt || '',
|
|
||||||
searchPrompts: model.chat.systemPrompt
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
obj: ChatRoleEnum.System,
|
|
||||||
value: model.chat.systemPrompt
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
|
||||||
rawSearch,
|
rawSearch,
|
||||||
guidePrompt: guidePrompt.value || '',
|
userSystemPrompt,
|
||||||
searchPrompts: [
|
quotePrompt: {
|
||||||
{
|
obj: ChatRoleEnum.System,
|
||||||
obj: ChatRoleEnum.System,
|
value: quoteText
|
||||||
value: `知识库:<${systemPrompt}>`
|
}
|
||||||
},
|
|
||||||
guidePrompt
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { jsonRes } from '@/service/response';
|
|||||||
import { connectToDatabase, OpenApi } from '@/service/mongo';
|
import { connectToDatabase, OpenApi } from '@/service/mongo';
|
||||||
import { authUser } from '@/service/utils/auth';
|
import { authUser } from '@/service/utils/auth';
|
||||||
import { customAlphabet } from 'nanoid';
|
import { customAlphabet } from 'nanoid';
|
||||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890');
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
try {
|
try {
|
||||||
@ -14,11 +14,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
|
|
||||||
const count = await OpenApi.find({ userId }).countDocuments();
|
const count = await OpenApi.find({ userId }).countDocuments();
|
||||||
|
|
||||||
if (count >= 5) {
|
if (count >= 10) {
|
||||||
throw new Error('最多 5 组API Key');
|
throw new Error('最多 10 组 API 秘钥');
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiKey = `${userId}-${nanoid()}`;
|
const apiKey = `fastgpt-${nanoid()}`;
|
||||||
|
|
||||||
await OpenApi.create({
|
await OpenApi.create({
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@ -83,7 +83,7 @@ const ModelList = ({ modelId }: { modelId: string }) => {
|
|||||||
<Flex flex={1} mr={2} position={'relative'} alignItems={'center'}>
|
<Flex flex={1} mr={2} position={'relative'} alignItems={'center'}>
|
||||||
<Input
|
<Input
|
||||||
h={'32px'}
|
h={'32px'}
|
||||||
placeholder="搜索 AI 应用"
|
placeholder="根据名字和介绍搜索 AI 应用"
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@ -111,7 +111,7 @@ const ModelList = ({ modelId }: { modelId: string }) => {
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex mb={3} userSelect={'none'}>
|
<Flex userSelect={'none'}>
|
||||||
<Box flex={1}></Box>
|
<Box flex={1}></Box>
|
||||||
<Tabs
|
<Tabs
|
||||||
w={'130px'}
|
w={'130px'}
|
||||||
@ -129,7 +129,7 @@ const ModelList = ({ modelId }: { modelId: string }) => {
|
|||||||
<Flex
|
<Flex
|
||||||
key={item._id}
|
key={item._id}
|
||||||
position={'relative'}
|
position={'relative'}
|
||||||
alignItems={['flex-start', 'center']}
|
alignItems={'center'}
|
||||||
p={3}
|
p={3}
|
||||||
mb={[2, 0]}
|
mb={[2, 0]}
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
@ -154,9 +154,6 @@ const ModelList = ({ modelId }: { modelId: string }) => {
|
|||||||
<Box className="textEllipsis" color={'myGray.1000'}>
|
<Box className="textEllipsis" color={'myGray.1000'}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Box>
|
</Box>
|
||||||
<Box className="textEllipsis" color={'myGray.400'} fontSize={'sm'}>
|
|
||||||
{item.systemPrompt || '这个 应用 没有设置提示词~'}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
))}
|
||||||
|
|||||||
82
client/src/pages/model/components/detail/components/API.tsx
Normal file
82
client/src/pages/model/components/detail/components/API.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Box, Divider, Flex, useTheme, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
|
||||||
|
import { useCopyData } from '@/utils/tools';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
|
||||||
|
const APIKeyModal = dynamic(() => import('@/components/APIKeyModal'), {
|
||||||
|
ssr: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const baseUrl = 'https://fastgpt.run/api/openapi';
|
||||||
|
|
||||||
|
const API = ({ modelId }: { modelId: string }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { copyData } = useCopyData();
|
||||||
|
const {
|
||||||
|
isOpen: isOpenAPIModal,
|
||||||
|
onOpen: onOpenAPIModal,
|
||||||
|
onClose: onCloseAPIModal
|
||||||
|
} = useDisclosure();
|
||||||
|
const [isLoaded, setIsLoaded] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex flexDirection={'column'} h={'100%'}>
|
||||||
|
<Box display={['none', 'flex']} px={5} alignItems={'center'}>
|
||||||
|
<Box flex={1}>
|
||||||
|
AppId:
|
||||||
|
<Box
|
||||||
|
as={'span'}
|
||||||
|
ml={2}
|
||||||
|
fontWeight={'bold'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
onClick={() => copyData(modelId, '已复制 AppId')}
|
||||||
|
>
|
||||||
|
{modelId}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Flex
|
||||||
|
bg={'myWhite.600'}
|
||||||
|
py={2}
|
||||||
|
px={4}
|
||||||
|
borderRadius={'md'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
onClick={() => copyData(baseUrl, '已复制 API 地址')}
|
||||||
|
>
|
||||||
|
<Box border={theme.borders.md} px={2} borderRadius={'md'} fontSize={'sm'}>
|
||||||
|
API服务器
|
||||||
|
</Box>
|
||||||
|
<Box ml={2} color={'myGray.900'} fontSize={['sm', 'md']}>
|
||||||
|
{baseUrl}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Button
|
||||||
|
ml={3}
|
||||||
|
leftIcon={<MyIcon name={'apikey'} w={'16px'} color={''} />}
|
||||||
|
variant={'base'}
|
||||||
|
onClick={onOpenAPIModal}
|
||||||
|
>
|
||||||
|
API 秘钥
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Divider mt={3} />
|
||||||
|
<Box flex={1}>
|
||||||
|
<Skeleton h="100%" isLoaded={isLoaded} fadeDuration={2}>
|
||||||
|
<iframe
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%'
|
||||||
|
}}
|
||||||
|
src="https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh"
|
||||||
|
frameBorder="0"
|
||||||
|
onLoad={() => setIsLoaded(true)}
|
||||||
|
onError={() => setIsLoaded(true)}
|
||||||
|
/>
|
||||||
|
</Skeleton>
|
||||||
|
</Box>
|
||||||
|
{isOpenAPIModal && <APIKeyModal onClose={onCloseAPIModal} />}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default API;
|
||||||
394
client/src/pages/model/components/detail/components/Kb.tsx
Normal file
394
client/src/pages/model/components/detail/components/Kb.tsx
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Flex,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
useDisclosure,
|
||||||
|
Modal,
|
||||||
|
ModalOverlay,
|
||||||
|
ModalContent,
|
||||||
|
ModalBody,
|
||||||
|
ModalHeader,
|
||||||
|
ModalFooter,
|
||||||
|
ModalCloseButton,
|
||||||
|
Grid,
|
||||||
|
useTheme,
|
||||||
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
|
Textarea
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useUserStore } from '@/store/user';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
import { AddIcon, DeleteIcon, QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||||
|
import { putModelById } from '@/api/model';
|
||||||
|
import { useToast } from '@/hooks/useToast';
|
||||||
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
import MySlider from '@/components/Slider';
|
||||||
|
|
||||||
|
const Kb = ({ modelId }: { modelId: string }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const router = useRouter();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const { modelDetail, loadKbList, loadModelDetail } = useUserStore();
|
||||||
|
const { Loading, setIsLoading } = useLoading();
|
||||||
|
const [selectedIdList, setSelectedIdList] = useState<string[]>([]);
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
const { register, reset, getValues, setValue } = useForm({
|
||||||
|
defaultValues: {
|
||||||
|
searchSimilarity: modelDetail.chat.searchSimilarity,
|
||||||
|
searchLimit: modelDetail.chat.searchLimit,
|
||||||
|
searchEmptyText: modelDetail.chat.searchEmptyText
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
isOpen: isOpenKbSelect,
|
||||||
|
onOpen: onOpenKbSelect,
|
||||||
|
onClose: onCloseKbSelect
|
||||||
|
} = useDisclosure();
|
||||||
|
const {
|
||||||
|
isOpen: isOpenEditParams,
|
||||||
|
onOpen: onOpenEditParams,
|
||||||
|
onClose: onCloseEditParams
|
||||||
|
} = useDisclosure();
|
||||||
|
|
||||||
|
const onchangeKb = useCallback(
|
||||||
|
async (
|
||||||
|
data: {
|
||||||
|
relatedKbs?: string[];
|
||||||
|
searchSimilarity?: number;
|
||||||
|
searchLimit?: number;
|
||||||
|
searchEmptyText?: string;
|
||||||
|
} = {}
|
||||||
|
) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
await putModelById(modelId, {
|
||||||
|
chat: {
|
||||||
|
...modelDetail.chat,
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
loadModelDetail(modelId, true);
|
||||||
|
} catch (err: any) {
|
||||||
|
toast({
|
||||||
|
title: err?.message || '更新失败',
|
||||||
|
status: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
},
|
||||||
|
[setIsLoading, modelId, modelDetail.chat, loadModelDetail, toast]
|
||||||
|
);
|
||||||
|
|
||||||
|
// init kb select list
|
||||||
|
const { isLoading, data: kbList = [] } = useQuery(['loadKbList'], () => loadKbList());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box position={'relative'} px={5} minH={'50vh'}>
|
||||||
|
<Box fontWeight={'bold'}>关联的知识库({modelDetail.chat?.relatedKbs.length})</Box>
|
||||||
|
{(() => {
|
||||||
|
const kbs =
|
||||||
|
modelDetail.chat?.relatedKbs
|
||||||
|
?.map((id) => kbList.find((kb) => kb._id === id))
|
||||||
|
.filter((item) => item) || [];
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
mt={2}
|
||||||
|
gridTemplateColumns={[
|
||||||
|
'repeat(1,1fr)',
|
||||||
|
'repeat(2,1fr)',
|
||||||
|
'repeat(3,1fr)',
|
||||||
|
'repeat(4,1fr)'
|
||||||
|
]}
|
||||||
|
gridGap={[3, 4]}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
p={3}
|
||||||
|
border={theme.borders.base}
|
||||||
|
boxShadow={'sm'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
bg={'myGray.100'}
|
||||||
|
_hover={{
|
||||||
|
bg: 'white',
|
||||||
|
color: 'myBlue.800'
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
reset({
|
||||||
|
searchSimilarity: modelDetail.chat.searchSimilarity,
|
||||||
|
searchLimit: modelDetail.chat.searchLimit,
|
||||||
|
searchEmptyText: modelDetail.chat.searchEmptyText
|
||||||
|
});
|
||||||
|
onOpenEditParams();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex alignItems={'center'} h={'38px'} fontWeight={'bold'}>
|
||||||
|
<IconButton
|
||||||
|
mr={2}
|
||||||
|
size={'sm'}
|
||||||
|
borderRadius={'lg'}
|
||||||
|
icon={<MyIcon name={'edit'} w={'14px'} color={'myGray.600'} />}
|
||||||
|
aria-label={''}
|
||||||
|
variant={'base'}
|
||||||
|
/>
|
||||||
|
调整搜索参数
|
||||||
|
</Flex>
|
||||||
|
<Flex mt={3} h={'30px'} color={'myGray.600'} fontSize={'sm'}>
|
||||||
|
相似度: {modelDetail.chat.searchSimilarity}, 单次搜索数量:{' '}
|
||||||
|
{modelDetail.chat.searchLimit}, 空搜索时拒绝回复:{' '}
|
||||||
|
{modelDetail.chat.searchEmptyText !== '' ? 'true' : 'false'}
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
p={3}
|
||||||
|
border={theme.borders.base}
|
||||||
|
boxShadow={'sm'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
bg={'myGray.100'}
|
||||||
|
_hover={{
|
||||||
|
bg: 'white',
|
||||||
|
color: 'myBlue.800'
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedIdList(
|
||||||
|
modelDetail.chat?.relatedKbs ? [...modelDetail.chat?.relatedKbs] : []
|
||||||
|
);
|
||||||
|
onOpenKbSelect();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex alignItems={'center'} h={'38px'} fontWeight={'bold'}>
|
||||||
|
<IconButton
|
||||||
|
mr={2}
|
||||||
|
size={'sm'}
|
||||||
|
borderRadius={'lg'}
|
||||||
|
icon={<AddIcon />}
|
||||||
|
aria-label={''}
|
||||||
|
variant={'base'}
|
||||||
|
/>
|
||||||
|
选择关联知识库
|
||||||
|
</Flex>
|
||||||
|
<Flex mt={3} h={'30px'} color={'myGray.600'} fontSize={'sm'}>
|
||||||
|
关联知识库,让 AI 应用回答你的特有内容。
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
{kbs.map((item) =>
|
||||||
|
item ? (
|
||||||
|
<Card
|
||||||
|
key={item._id}
|
||||||
|
p={3}
|
||||||
|
border={theme.borders.base}
|
||||||
|
boxShadow={'sm'}
|
||||||
|
_hover={{
|
||||||
|
boxShadow: 'lg',
|
||||||
|
'& .detailBtn': {
|
||||||
|
display: 'block'
|
||||||
|
},
|
||||||
|
'& .delete': {
|
||||||
|
display: 'block'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex alignItems={'center'} h={'38px'}>
|
||||||
|
<Avatar src={item.avatar} w={['26px', '32px', '38px']}></Avatar>
|
||||||
|
<Box ml={3} fontWeight={'bold'} fontSize={['md', 'lg', 'xl']}>
|
||||||
|
{item.name}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Flex mt={3} alignItems={'flex-end'} justifyContent={'flex-end'} h={'30px'}>
|
||||||
|
<Button
|
||||||
|
mr={3}
|
||||||
|
className="detailBtn"
|
||||||
|
display={['flex', 'none']}
|
||||||
|
variant={'base'}
|
||||||
|
size={'sm'}
|
||||||
|
onClick={() => router.push(`/kb?kbId=${item._id}`)}
|
||||||
|
>
|
||||||
|
查看详情
|
||||||
|
</Button>
|
||||||
|
<IconButton
|
||||||
|
className="delete"
|
||||||
|
display={['flex', 'none']}
|
||||||
|
icon={<DeleteIcon />}
|
||||||
|
variant={'outline'}
|
||||||
|
aria-label={'delete'}
|
||||||
|
size={'sm'}
|
||||||
|
_hover={{ color: 'red.600' }}
|
||||||
|
onClick={() => {
|
||||||
|
const ids = modelDetail.chat?.relatedKbs
|
||||||
|
? [...modelDetail.chat.relatedKbs]
|
||||||
|
: [];
|
||||||
|
const i = ids.findIndex((id) => id === item._id);
|
||||||
|
ids.splice(i, 1);
|
||||||
|
onchangeKb({ relatedKbs: ids });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
) : null
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
{/* select kb modal */}
|
||||||
|
<Modal isOpen={isOpenKbSelect} onClose={onCloseKbSelect}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent
|
||||||
|
display={'flex'}
|
||||||
|
flexDirection={'column'}
|
||||||
|
w={'800px'}
|
||||||
|
maxW={'90vw'}
|
||||||
|
h={['90vh', 'auto']}
|
||||||
|
>
|
||||||
|
<ModalHeader>关联的知识库({selectedIdList.length})</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody
|
||||||
|
flex={['1 0 0', '0 0 auto']}
|
||||||
|
maxH={'80vh'}
|
||||||
|
overflowY={'auto'}
|
||||||
|
display={'grid'}
|
||||||
|
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)', 'repeat(3,1fr)']}
|
||||||
|
gridGap={3}
|
||||||
|
>
|
||||||
|
{kbList.map((item) => (
|
||||||
|
<Card
|
||||||
|
key={item._id}
|
||||||
|
p={3}
|
||||||
|
border={theme.borders.base}
|
||||||
|
boxShadow={'sm'}
|
||||||
|
h={'80px'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
order={modelDetail.chat?.relatedKbs?.includes(item._id) ? 0 : 1}
|
||||||
|
_hover={{
|
||||||
|
boxShadow: 'md'
|
||||||
|
}}
|
||||||
|
{...(selectedIdList.includes(item._id)
|
||||||
|
? {
|
||||||
|
bg: 'myBlue.300'
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
|
onClick={() => {
|
||||||
|
let ids = [...selectedIdList];
|
||||||
|
if (!selectedIdList.includes(item._id)) {
|
||||||
|
ids = ids.concat(item._id);
|
||||||
|
} else {
|
||||||
|
const i = ids.findIndex((id) => id === item._id);
|
||||||
|
ids.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ids = ids.filter((id) => kbList.find((item) => item._id === id));
|
||||||
|
setSelectedIdList(ids);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex alignItems={'center'} h={'38px'}>
|
||||||
|
<Avatar src={item.avatar} w={['24px', '28px', '32px']}></Avatar>
|
||||||
|
<Box ml={3} fontWeight={'bold'} fontSize={['md', 'lg', 'xl']}>
|
||||||
|
{item.name}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onCloseKbSelect();
|
||||||
|
onchangeKb({ relatedKbs: selectedIdList });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
完成
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
{/* edit mode */}
|
||||||
|
<Modal isOpen={isOpenEditParams} onClose={onCloseEditParams}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent display={'flex'} flexDirection={'column'} w={'600px'} maxW={'90vw'}>
|
||||||
|
<ModalHeader>搜索参数调整</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Flex pt={3} pb={5}>
|
||||||
|
<Box flex={'0 0 100px'}>
|
||||||
|
相似度
|
||||||
|
<Tooltip label={'高相似度推荐0.8及以上。'}>
|
||||||
|
<QuestionOutlineIcon ml={1} />
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
<MySlider
|
||||||
|
markList={[
|
||||||
|
{ label: '0', value: 0 },
|
||||||
|
{ label: '1', value: 1 }
|
||||||
|
]}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.01}
|
||||||
|
activeVal={getValues('searchSimilarity')}
|
||||||
|
setVal={(val) => {
|
||||||
|
setValue('searchSimilarity', val);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Flex py={8}>
|
||||||
|
<Box flex={'0 0 100px'}>单次搜索数量</Box>
|
||||||
|
<Box flex={1}>
|
||||||
|
<MySlider
|
||||||
|
markList={[
|
||||||
|
{ label: '1', value: 1 },
|
||||||
|
{ label: '20', value: 20 }
|
||||||
|
]}
|
||||||
|
min={1}
|
||||||
|
max={20}
|
||||||
|
activeVal={getValues('searchLimit')}
|
||||||
|
setVal={(val) => {
|
||||||
|
setValue('searchLimit', val);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Flex pt={3}>
|
||||||
|
<Box flex={'0 0 100px'}>空搜索回复</Box>
|
||||||
|
<Box flex={1}>
|
||||||
|
<Textarea
|
||||||
|
rows={5}
|
||||||
|
maxLength={500}
|
||||||
|
placeholder={
|
||||||
|
'若填写该内容,没有搜索到对应内容时,将直接回复填写的内容。\n为了连贯上下文,FastGpt 会取部分上一个聊天的搜索记录作为补充,因此在连续对话时,该功能可能会失效。'
|
||||||
|
}
|
||||||
|
{...register('searchEmptyText')}
|
||||||
|
></Textarea>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant={'base'} mr={3} onClick={onCloseEditParams}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onCloseEditParams();
|
||||||
|
onchangeKb({
|
||||||
|
searchSimilarity: getValues('searchSimilarity'),
|
||||||
|
searchLimit: getValues('searchLimit'),
|
||||||
|
searchEmptyText: getValues('searchEmptyText')
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
完成
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
<Loading loading={isLoading} fixed={false} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Kb;
|
||||||
@ -1,619 +0,0 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Card,
|
|
||||||
Flex,
|
|
||||||
FormControl,
|
|
||||||
Input,
|
|
||||||
Textarea,
|
|
||||||
Slider,
|
|
||||||
SliderTrack,
|
|
||||||
SliderFilledTrack,
|
|
||||||
SliderThumb,
|
|
||||||
SliderMark,
|
|
||||||
Tooltip,
|
|
||||||
Button,
|
|
||||||
Select,
|
|
||||||
Switch,
|
|
||||||
Modal,
|
|
||||||
ModalOverlay,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalFooter,
|
|
||||||
ModalBody,
|
|
||||||
ModalCloseButton,
|
|
||||||
useDisclosure,
|
|
||||||
Table,
|
|
||||||
Thead,
|
|
||||||
Tbody,
|
|
||||||
Tr,
|
|
||||||
Th,
|
|
||||||
Td,
|
|
||||||
TableContainer,
|
|
||||||
Checkbox
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
|
||||||
import { useForm, UseFormReturn } from 'react-hook-form';
|
|
||||||
import { ChatModelMap, ModelVectorSearchModeMap, getChatModelList } from '@/constants/model';
|
|
||||||
import { formatPrice } from '@/utils/user';
|
|
||||||
import { useConfirm } from '@/hooks/useConfirm';
|
|
||||||
import { useSelectFile } from '@/hooks/useSelectFile';
|
|
||||||
import { useToast } from '@/hooks/useToast';
|
|
||||||
import { compressImg } from '@/utils/file';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { getShareChatList, createShareChat, delShareChatById } from '@/api/chat';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { defaultShareChat } from '@/constants/model';
|
|
||||||
import type { ShareChatEditType } from '@/types/model';
|
|
||||||
import type { ModelSchema } from '@/types/mongoSchema';
|
|
||||||
import { formatTimeToChatTime, useCopyData, getErrText } from '@/utils/tools';
|
|
||||||
import { useGlobalStore } from '@/store/global';
|
|
||||||
import { useUserStore } from '@/store/user';
|
|
||||||
import Avatar from '@/components/Avatar';
|
|
||||||
import MyIcon from '@/components/Icon';
|
|
||||||
|
|
||||||
const ModelEditForm = ({
|
|
||||||
formHooks,
|
|
||||||
isOwner,
|
|
||||||
canRead,
|
|
||||||
handleDelModel
|
|
||||||
}: {
|
|
||||||
formHooks: UseFormReturn<ModelSchema>;
|
|
||||||
isOwner: boolean;
|
|
||||||
canRead: boolean;
|
|
||||||
handleDelModel: () => void;
|
|
||||||
}) => {
|
|
||||||
const router = useRouter();
|
|
||||||
const { modelId } = router.query as { modelId: string };
|
|
||||||
const [refresh, setRefresh] = useState(false);
|
|
||||||
const { toast } = useToast();
|
|
||||||
const { setLoading } = useGlobalStore();
|
|
||||||
const { loadKbList } = useUserStore();
|
|
||||||
|
|
||||||
const { openConfirm, ConfirmChild } = useConfirm({
|
|
||||||
content: '确认删除该应用?'
|
|
||||||
});
|
|
||||||
const { copyData } = useCopyData();
|
|
||||||
const { register, setValue, getValues } = formHooks;
|
|
||||||
const {
|
|
||||||
register: registerShareChat,
|
|
||||||
getValues: getShareChatValues,
|
|
||||||
setValue: setShareChatValues,
|
|
||||||
handleSubmit: submitShareChat,
|
|
||||||
reset: resetShareChat
|
|
||||||
} = useForm({
|
|
||||||
defaultValues: defaultShareChat
|
|
||||||
});
|
|
||||||
const {
|
|
||||||
isOpen: isOpenCreateShareChat,
|
|
||||||
onOpen: onOpenCreateShareChat,
|
|
||||||
onClose: onCloseCreateShareChat
|
|
||||||
} = useDisclosure();
|
|
||||||
const {
|
|
||||||
isOpen: isOpenKbSelect,
|
|
||||||
onOpen: onOpenKbSelect,
|
|
||||||
onClose: onCloseKbSelect
|
|
||||||
} = useDisclosure();
|
|
||||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
|
||||||
fileType: '.jpg,.png',
|
|
||||||
multiple: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSelectFile = useCallback(
|
|
||||||
async (e: File[]) => {
|
|
||||||
const file = e[0];
|
|
||||||
if (!file) return;
|
|
||||||
try {
|
|
||||||
const src = await compressImg({
|
|
||||||
file,
|
|
||||||
maxW: 100,
|
|
||||||
maxH: 100
|
|
||||||
});
|
|
||||||
setValue('avatar', src);
|
|
||||||
setRefresh((state) => !state);
|
|
||||||
} catch (err: any) {
|
|
||||||
toast({
|
|
||||||
title: getErrText(err, '头像选择异常'),
|
|
||||||
status: 'warning'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[setValue, toast]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: chatModelList = [] } = useQuery(['initChatModelList'], getChatModelList);
|
|
||||||
|
|
||||||
const { data: shareChatList = [], refetch: refetchShareChatList } = useQuery(
|
|
||||||
['initShareChatList', modelId],
|
|
||||||
() => getShareChatList(modelId)
|
|
||||||
);
|
|
||||||
|
|
||||||
const onclickCreateShareChat = useCallback(
|
|
||||||
async (e: ShareChatEditType) => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const id = await createShareChat({
|
|
||||||
...e,
|
|
||||||
modelId
|
|
||||||
});
|
|
||||||
onCloseCreateShareChat();
|
|
||||||
refetchShareChatList();
|
|
||||||
|
|
||||||
const url = `你可以与 ${getValues('name')} 进行对话。
|
|
||||||
对话地址为:${location.origin}/chat/share?shareId=${id}
|
|
||||||
${e.password ? `密码为: ${e.password}` : ''}`;
|
|
||||||
copyData(url, '已复制分享地址');
|
|
||||||
|
|
||||||
resetShareChat(defaultShareChat);
|
|
||||||
} catch (err) {
|
|
||||||
toast({
|
|
||||||
title: getErrText(err, '创建分享链接异常'),
|
|
||||||
status: 'warning'
|
|
||||||
});
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
},
|
|
||||||
[
|
|
||||||
copyData,
|
|
||||||
getValues,
|
|
||||||
modelId,
|
|
||||||
onCloseCreateShareChat,
|
|
||||||
refetchShareChatList,
|
|
||||||
resetShareChat,
|
|
||||||
setLoading,
|
|
||||||
toast
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// format share used token
|
|
||||||
const formatTokens = (tokens: number) => {
|
|
||||||
if (tokens < 10000) return tokens;
|
|
||||||
return `${(tokens / 10000).toFixed(2)}万`;
|
|
||||||
};
|
|
||||||
|
|
||||||
// init kb select list
|
|
||||||
const { data: kbList = [] } = useQuery(['loadKbList'], () => loadKbList());
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* basic info */}
|
|
||||||
<Card p={4}>
|
|
||||||
<Box fontWeight={'bold'}>基本信息</Box>
|
|
||||||
<Flex alignItems={'center'} mt={4}>
|
|
||||||
<Box flex={'0 0 80px'} w={0}>
|
|
||||||
AppId
|
|
||||||
</Box>
|
|
||||||
<Box userSelect={'all'}>{getValues('_id')}</Box>
|
|
||||||
</Flex>
|
|
||||||
<Flex mt={4} alignItems={'center'}>
|
|
||||||
<Box flex={'0 0 80px'} w={0}>
|
|
||||||
头像
|
|
||||||
</Box>
|
|
||||||
<Avatar
|
|
||||||
src={getValues('avatar')}
|
|
||||||
w={['28px', '36px']}
|
|
||||||
h={['28px', '36px']}
|
|
||||||
cursor={isOwner ? 'pointer' : 'default'}
|
|
||||||
title={'点击切换头像'}
|
|
||||||
onClick={() => isOwner && onOpenSelectFile()}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<FormControl mt={4}>
|
|
||||||
<Flex alignItems={'center'}>
|
|
||||||
<Box flex={'0 0 80px'} w={0}>
|
|
||||||
名称
|
|
||||||
</Box>
|
|
||||||
<Input
|
|
||||||
isDisabled={!isOwner}
|
|
||||||
{...register('name', {
|
|
||||||
required: '展示名称不能为空'
|
|
||||||
})}
|
|
||||||
></Input>
|
|
||||||
</Flex>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<Flex alignItems={'center'} mt={5}>
|
|
||||||
<Box flex={'0 0 80px'} w={0}>
|
|
||||||
对话模型
|
|
||||||
</Box>
|
|
||||||
<Select
|
|
||||||
isDisabled={!isOwner}
|
|
||||||
{...register('chat.chatModel', {
|
|
||||||
onChange() {
|
|
||||||
setRefresh((state) => !state);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{chatModelList.map((item) => (
|
|
||||||
<option key={item.chatModel} value={item.chatModel}>
|
|
||||||
{item.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Flex>
|
|
||||||
<Flex alignItems={'center'} mt={5}>
|
|
||||||
<Box flex={'0 0 80px'} w={0}>
|
|
||||||
价格
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
{formatPrice(ChatModelMap[getValues('chat.chatModel')]?.price, 1000)}
|
|
||||||
元/1K tokens(包括上下文和回答)
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
<Flex alignItems={'center'} mt={5}>
|
|
||||||
<Box flex={'0 0 80px'} w={0}>
|
|
||||||
收藏人数
|
|
||||||
</Box>
|
|
||||||
<Box>{getValues('share.collection')}人</Box>
|
|
||||||
</Flex>
|
|
||||||
{isOwner && (
|
|
||||||
<Flex mt={5} alignItems={'center'}>
|
|
||||||
<Box flex={'0 0 80px'}>删除应用</Box>
|
|
||||||
<Button
|
|
||||||
colorScheme={'gray'}
|
|
||||||
variant={'base'}
|
|
||||||
size={'sm'}
|
|
||||||
onClick={openConfirm(handleDelModel)}
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
{/* model effect */}
|
|
||||||
{canRead && (
|
|
||||||
<Card p={4}>
|
|
||||||
<Box fontWeight={'bold'}>模型效果</Box>
|
|
||||||
<FormControl mt={4}>
|
|
||||||
<Flex alignItems={'center'}>
|
|
||||||
<Box flex={'0 0 80px'} w={0}>
|
|
||||||
<Box as={'span'} mr={2}>
|
|
||||||
温度
|
|
||||||
</Box>
|
|
||||||
<Tooltip label={'温度越高,模型的发散能力越强;温度越低,内容越严谨。'}>
|
|
||||||
<QuestionOutlineIcon />
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Slider
|
|
||||||
aria-label="slider-ex-1"
|
|
||||||
min={0}
|
|
||||||
max={10}
|
|
||||||
step={1}
|
|
||||||
value={getValues('chat.temperature')}
|
|
||||||
isDisabled={!isOwner}
|
|
||||||
onChange={(e) => {
|
|
||||||
setValue('chat.temperature', e);
|
|
||||||
setRefresh(!refresh);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SliderMark
|
|
||||||
value={getValues('chat.temperature')}
|
|
||||||
textAlign="center"
|
|
||||||
bg="myBlue.600"
|
|
||||||
color="white"
|
|
||||||
w={'18px'}
|
|
||||||
h={'18px'}
|
|
||||||
borderRadius={'100px'}
|
|
||||||
fontSize={'xs'}
|
|
||||||
transform={'translate(-50%, -200%)'}
|
|
||||||
>
|
|
||||||
{getValues('chat.temperature')}
|
|
||||||
</SliderMark>
|
|
||||||
<SliderTrack>
|
|
||||||
<SliderFilledTrack bg={'myBlue.700'} />
|
|
||||||
</SliderTrack>
|
|
||||||
<SliderThumb />
|
|
||||||
</Slider>
|
|
||||||
</Flex>
|
|
||||||
</FormControl>
|
|
||||||
{getValues('chat.relatedKbs')?.length > 0 && (
|
|
||||||
<Flex mt={4} alignItems={'center'}>
|
|
||||||
<Box mr={4} whiteSpace={'nowrap'}>
|
|
||||||
搜索模式 
|
|
||||||
</Box>
|
|
||||||
<Select
|
|
||||||
isDisabled={!isOwner}
|
|
||||||
{...register('chat.searchMode', {
|
|
||||||
required: '搜索模式不能为空'
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{Object.entries(ModelVectorSearchModeMap).map(([key, { text }]) => (
|
|
||||||
<option key={key} value={key}>
|
|
||||||
{text}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
<Box mt={4}>
|
|
||||||
<Box mb={1}>系统提示词</Box>
|
|
||||||
<Textarea
|
|
||||||
rows={8}
|
|
||||||
maxLength={-1}
|
|
||||||
isDisabled={!isOwner}
|
|
||||||
placeholder={
|
|
||||||
'模型默认的 prompt 词,通过调整该内容,可以引导模型聊天方向。\n\n如果使用了知识库搜索,没有填写该内容时,系统会自动补充提示词;如果填写了内容,则以填写的内容为准。'
|
|
||||||
}
|
|
||||||
{...register('chat.systemPrompt')}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
{isOwner && (
|
|
||||||
<>
|
|
||||||
{/* model share setting */}
|
|
||||||
{/* <Card p={4}>
|
|
||||||
<Box fontWeight={'bold'}>分享设置</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex mt={5} alignItems={'center'}>
|
|
||||||
<Box mr={1} fontSize={['sm', 'md']}>
|
|
||||||
模型分享:
|
|
||||||
</Box>
|
|
||||||
<Tooltip label="开启模型分享后,你的模型将会出现在共享市场,可供 FastGpt 所有用户使用。用户使用时不会消耗你的 tokens,而是消耗使用者的 tokens。">
|
|
||||||
<QuestionOutlineIcon mr={3} />
|
|
||||||
</Tooltip>
|
|
||||||
<Switch
|
|
||||||
isChecked={getValues('share.isShare')}
|
|
||||||
onChange={() => {
|
|
||||||
setValue('share.isShare', !getValues('share.isShare'));
|
|
||||||
setRefresh(!refresh);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<Box mt={5}>
|
|
||||||
<Box>模型介绍</Box>
|
|
||||||
<Textarea
|
|
||||||
mt={1}
|
|
||||||
rows={6}
|
|
||||||
maxLength={150}
|
|
||||||
{...register('share.intro')}
|
|
||||||
placeholder={'介绍模型的功能、场景等,吸引更多人来使用!最多150字。'}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Card> */}
|
|
||||||
<Card p={4}>
|
|
||||||
<Flex justifyContent={'space-between'}>
|
|
||||||
<Box fontWeight={'bold'}>关联的知识库</Box>
|
|
||||||
<Button size={'sm'} variant={'base'} colorScheme={'myBlue'} onClick={onOpenKbSelect}>
|
|
||||||
选择
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
{(() => {
|
|
||||||
const kbs =
|
|
||||||
getValues('chat.relatedKbs')?.map((id) => kbList.find((kb) => kb._id === id)) || [];
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{kbs.map((item) =>
|
|
||||||
item ? (
|
|
||||||
<Card
|
|
||||||
key={item._id}
|
|
||||||
p={3}
|
|
||||||
mt={3}
|
|
||||||
cursor={'pointer'}
|
|
||||||
onClick={() => router.push(`/kb?kbId=${item._id}`)}
|
|
||||||
>
|
|
||||||
<Flex alignItems={'center'}>
|
|
||||||
<Avatar src={item.avatar} w={'20px'} h={'20px'}></Avatar>
|
|
||||||
<Box ml={3} fontWeight={'bold'}>
|
|
||||||
{item.name}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
) : null
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</Card>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{/* shareChat */}
|
|
||||||
<Card p={4}>
|
|
||||||
<Flex justifyContent={'space-between'}>
|
|
||||||
<Box fontWeight={'bold'}>
|
|
||||||
免登录聊天窗口
|
|
||||||
<Tooltip label="可以直接分享该模型给其他用户去进行对话,对方无需登录即可直接进行对话。注意,这个功能会消耗你账号的tokens。请保管好链接和密码。">
|
|
||||||
<QuestionOutlineIcon ml={1} />
|
|
||||||
</Tooltip>
|
|
||||||
(Beta)
|
|
||||||
</Box>
|
|
||||||
<Button
|
|
||||||
size={'sm'}
|
|
||||||
variant={'base'}
|
|
||||||
colorScheme={'myBlue'}
|
|
||||||
{...(shareChatList.length >= 10
|
|
||||||
? {
|
|
||||||
isDisabled: true,
|
|
||||||
title: '最多创建10组'
|
|
||||||
}
|
|
||||||
: {})}
|
|
||||||
onClick={onOpenCreateShareChat}
|
|
||||||
>
|
|
||||||
创建分享窗口
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
<TableContainer mt={1} minH={'100px'}>
|
|
||||||
<Table variant={'simple'} w={'100%'}>
|
|
||||||
<Thead>
|
|
||||||
<Tr>
|
|
||||||
<Th>名称</Th>
|
|
||||||
<Th>密码</Th>
|
|
||||||
<Th>最大上下文</Th>
|
|
||||||
<Th>tokens消耗</Th>
|
|
||||||
<Th>最后使用时间</Th>
|
|
||||||
<Th>操作</Th>
|
|
||||||
</Tr>
|
|
||||||
</Thead>
|
|
||||||
<Tbody>
|
|
||||||
{shareChatList.map((item) => (
|
|
||||||
<Tr key={item._id}>
|
|
||||||
<Td>{item.name}</Td>
|
|
||||||
<Td>{item.password === '1' ? '已开启' : '未使用'}</Td>
|
|
||||||
<Td>{item.maxContext}</Td>
|
|
||||||
<Td>{formatTokens(item.tokens)}</Td>
|
|
||||||
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
|
|
||||||
<Td>
|
|
||||||
<Flex>
|
|
||||||
<MyIcon
|
|
||||||
mr={3}
|
|
||||||
name="copy"
|
|
||||||
w={'14px'}
|
|
||||||
cursor={'pointer'}
|
|
||||||
_hover={{ color: 'myBlue.600' }}
|
|
||||||
onClick={() => {
|
|
||||||
const url = `${location.origin}/chat/share?shareId=${item._id}`;
|
|
||||||
copyData(url, '已复制分享地址');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<MyIcon
|
|
||||||
name="delete"
|
|
||||||
w={'14px'}
|
|
||||||
cursor={'pointer'}
|
|
||||||
_hover={{ color: 'red' }}
|
|
||||||
onClick={async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
await delShareChatById(item._id);
|
|
||||||
refetchShareChatList();
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
))}
|
|
||||||
</Tbody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</Card>
|
|
||||||
{/* create shareChat modal */}
|
|
||||||
<Modal isOpen={isOpenCreateShareChat} onClose={onCloseCreateShareChat}>
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>创建免登录窗口</ModalHeader>
|
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody>
|
|
||||||
<FormControl>
|
|
||||||
<Flex alignItems={'center'}>
|
|
||||||
<Box flex={'0 0 60px'} w={0}>
|
|
||||||
名称:
|
|
||||||
</Box>
|
|
||||||
<Input
|
|
||||||
placeholder="记录名字,仅用于展示"
|
|
||||||
maxLength={20}
|
|
||||||
{...registerShareChat('name', {
|
|
||||||
required: '记录名称不能为空'
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</FormControl>
|
|
||||||
<FormControl mt={4}>
|
|
||||||
<Flex alignItems={'center'}>
|
|
||||||
<Box flex={'0 0 60px'} w={0}>
|
|
||||||
密码:
|
|
||||||
</Box>
|
|
||||||
<Input placeholder={'不设置密码,可直接访问'} {...registerShareChat('password')} />
|
|
||||||
</Flex>
|
|
||||||
<Box fontSize={'xs'} ml={'60px'}>
|
|
||||||
密码不会再次展示,请记住你的密码
|
|
||||||
</Box>
|
|
||||||
</FormControl>
|
|
||||||
<FormControl mt={9}>
|
|
||||||
<Flex alignItems={'center'}>
|
|
||||||
<Box flex={'0 0 120px'} w={0}>
|
|
||||||
最长上下文(组)
|
|
||||||
</Box>
|
|
||||||
<Slider
|
|
||||||
aria-label="slider-ex-1"
|
|
||||||
min={1}
|
|
||||||
max={20}
|
|
||||||
step={1}
|
|
||||||
value={getShareChatValues('maxContext')}
|
|
||||||
isDisabled={!isOwner}
|
|
||||||
onChange={(e) => {
|
|
||||||
setShareChatValues('maxContext', e);
|
|
||||||
setRefresh(!refresh);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SliderMark
|
|
||||||
value={getShareChatValues('maxContext')}
|
|
||||||
textAlign="center"
|
|
||||||
bg="myBlue.600"
|
|
||||||
color="white"
|
|
||||||
w={'18px'}
|
|
||||||
h={'18px'}
|
|
||||||
borderRadius={'100px'}
|
|
||||||
fontSize={'xs'}
|
|
||||||
transform={'translate(-50%, -200%)'}
|
|
||||||
>
|
|
||||||
{getShareChatValues('maxContext')}
|
|
||||||
</SliderMark>
|
|
||||||
<SliderTrack>
|
|
||||||
<SliderFilledTrack bg={'myBlue.700'} />
|
|
||||||
</SliderTrack>
|
|
||||||
<SliderThumb />
|
|
||||||
</Slider>
|
|
||||||
</Flex>
|
|
||||||
</FormControl>
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button variant={'base'} mr={3} onClick={onCloseCreateShareChat}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button onClick={submitShareChat(onclickCreateShareChat)}>确认</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
{/* select kb modal */}
|
|
||||||
<Modal isOpen={isOpenKbSelect} onClose={onCloseKbSelect}>
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>选择关联的知识库</ModalHeader>
|
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody>
|
|
||||||
{kbList.map((item) => (
|
|
||||||
<Card key={item._id} p={3} mb={3}>
|
|
||||||
<Checkbox
|
|
||||||
isChecked={getValues('chat.relatedKbs')?.includes(item._id)}
|
|
||||||
onChange={(e) => {
|
|
||||||
const ids = getValues('chat.relatedKbs');
|
|
||||||
if (e.target.checked) {
|
|
||||||
setValue('chat.relatedKbs', ids.concat(item._id));
|
|
||||||
} else {
|
|
||||||
const i = ids.findIndex((id) => id === item._id);
|
|
||||||
ids.splice(i, 1);
|
|
||||||
setValue('chat.relatedKbs', ids);
|
|
||||||
}
|
|
||||||
setRefresh(!refresh);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex alignItems={'center'}>
|
|
||||||
<Avatar src={item.avatar} w={'20px'} h={'20px'} />
|
|
||||||
<Box ml={3} fontWeight={'bold'}>
|
|
||||||
{item.name}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</Checkbox>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button onClick={onCloseKbSelect}>完成,记得点保存修改</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
<File onSelect={onSelectFile} />
|
|
||||||
<ConfirmChild />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ModelEditForm;
|
|
||||||
330
client/src/pages/model/components/detail/components/Settings.tsx
Normal file
330
client/src/pages/model/components/detail/components/Settings.tsx
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
import React, { useCallback, useState, useMemo } from 'react';
|
||||||
|
import { Box, Flex, Button, FormControl, Input, Textarea, Divider } from '@chakra-ui/react';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { useUserStore } from '@/store/user';
|
||||||
|
import { useToast } from '@/hooks/useToast';
|
||||||
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
|
import { delModelById, putModelById } from '@/api/model';
|
||||||
|
import { useSelectFile } from '@/hooks/useSelectFile';
|
||||||
|
import { compressImg } from '@/utils/file';
|
||||||
|
import { getErrText } from '@/utils/tools';
|
||||||
|
import { useConfirm } from '@/hooks/useConfirm';
|
||||||
|
import { ChatModelMap, getChatModelList } from '@/constants/model';
|
||||||
|
import { formatPrice } from '@/utils/user';
|
||||||
|
|
||||||
|
import type { ModelSchema } from '@/types/mongoSchema';
|
||||||
|
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
import MySelect from '@/components/Select';
|
||||||
|
import MySlider from '@/components/Slider';
|
||||||
|
|
||||||
|
const Settings = ({ modelId }: { modelId: string }) => {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const router = useRouter();
|
||||||
|
const { Loading, setIsLoading } = useLoading();
|
||||||
|
const { userInfo, modelDetail, loadModelDetail, refreshModel, setLastModelId } = useUserStore();
|
||||||
|
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||||
|
fileType: '.jpg,.png',
|
||||||
|
multiple: false
|
||||||
|
});
|
||||||
|
const { openConfirm, ConfirmChild } = useConfirm({
|
||||||
|
content: '确认删除该应用?'
|
||||||
|
});
|
||||||
|
|
||||||
|
const [btnLoading, setBtnLoading] = useState(false);
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
|
||||||
|
const isOwner = useMemo(
|
||||||
|
() => modelDetail.userId === userInfo?._id,
|
||||||
|
[modelDetail.userId, userInfo?._id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
setValue,
|
||||||
|
getValues,
|
||||||
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
handleSubmit
|
||||||
|
} = useForm({
|
||||||
|
defaultValues: modelDetail
|
||||||
|
});
|
||||||
|
|
||||||
|
// 提交保存模型修改
|
||||||
|
const saveSubmitSuccess = useCallback(
|
||||||
|
async (data: ModelSchema) => {
|
||||||
|
setBtnLoading(true);
|
||||||
|
try {
|
||||||
|
await putModelById(data._id, {
|
||||||
|
name: data.name,
|
||||||
|
avatar: data.avatar,
|
||||||
|
intro: data.intro,
|
||||||
|
chat: data.chat,
|
||||||
|
share: data.share
|
||||||
|
});
|
||||||
|
|
||||||
|
refreshModel.updateModelDetail(data);
|
||||||
|
} catch (err: any) {
|
||||||
|
toast({
|
||||||
|
title: err?.message || '更新失败',
|
||||||
|
status: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setBtnLoading(false);
|
||||||
|
},
|
||||||
|
[refreshModel, toast]
|
||||||
|
);
|
||||||
|
// 提交保存表单失败
|
||||||
|
const saveSubmitError = useCallback(() => {
|
||||||
|
// deep search message
|
||||||
|
const deepSearch = (obj: any): string => {
|
||||||
|
if (!obj) return '提交表单错误';
|
||||||
|
if (!!obj.message) {
|
||||||
|
return obj.message;
|
||||||
|
}
|
||||||
|
return deepSearch(Object.values(obj)[0]);
|
||||||
|
};
|
||||||
|
toast({
|
||||||
|
title: deepSearch(errors),
|
||||||
|
status: 'error',
|
||||||
|
duration: 4000,
|
||||||
|
isClosable: true
|
||||||
|
});
|
||||||
|
}, [errors, toast]);
|
||||||
|
|
||||||
|
const saveUpdateModel = useCallback(
|
||||||
|
() => handleSubmit(saveSubmitSuccess, saveSubmitError)(),
|
||||||
|
[handleSubmit, saveSubmitError, saveSubmitSuccess]
|
||||||
|
);
|
||||||
|
|
||||||
|
/* 点击删除 */
|
||||||
|
const handleDelModel = useCallback(async () => {
|
||||||
|
if (!modelDetail) return;
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
await delModelById(modelDetail._id);
|
||||||
|
toast({
|
||||||
|
title: '删除成功',
|
||||||
|
status: 'success'
|
||||||
|
});
|
||||||
|
refreshModel.removeModelDetail(modelDetail._id);
|
||||||
|
router.replace('/model');
|
||||||
|
} catch (err: any) {
|
||||||
|
toast({
|
||||||
|
title: err?.message || '删除失败',
|
||||||
|
status: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
}, [modelDetail, setIsLoading, toast, refreshModel, router]);
|
||||||
|
|
||||||
|
const onSelectFile = useCallback(
|
||||||
|
async (e: File[]) => {
|
||||||
|
const file = e[0];
|
||||||
|
if (!file) return;
|
||||||
|
try {
|
||||||
|
const src = await compressImg({
|
||||||
|
file,
|
||||||
|
maxW: 100,
|
||||||
|
maxH: 100
|
||||||
|
});
|
||||||
|
setValue('avatar', src);
|
||||||
|
setRefresh((state) => !state);
|
||||||
|
} catch (err: any) {
|
||||||
|
toast({
|
||||||
|
title: getErrText(err, '头像选择异常'),
|
||||||
|
status: 'warning'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setValue, toast]
|
||||||
|
);
|
||||||
|
|
||||||
|
// load model data
|
||||||
|
const { isLoading } = useQuery([modelId], () => loadModelDetail(modelId, true), {
|
||||||
|
onSuccess(res) {
|
||||||
|
res && reset(res);
|
||||||
|
modelId && setLastModelId(modelId);
|
||||||
|
},
|
||||||
|
onError(err: any) {
|
||||||
|
toast({
|
||||||
|
title: err?.message || '获取应用异常',
|
||||||
|
status: 'error'
|
||||||
|
});
|
||||||
|
setLastModelId('');
|
||||||
|
refreshModel.freshMyModels();
|
||||||
|
router.replace('/model');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: chatModelList = [] } = useQuery(['initChatModelList'], getChatModelList);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
pb={3}
|
||||||
|
px={[5, '25px', '50px']}
|
||||||
|
fontSize={['sm', 'lg']}
|
||||||
|
maxW={['auto', '800px']}
|
||||||
|
position={'relative'}
|
||||||
|
>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
||||||
|
头像
|
||||||
|
</Box>
|
||||||
|
<Avatar
|
||||||
|
src={getValues('avatar')}
|
||||||
|
w={['32px', '40px']}
|
||||||
|
h={['32px', '40px']}
|
||||||
|
cursor={isOwner ? 'pointer' : 'default'}
|
||||||
|
title={'点击切换头像'}
|
||||||
|
onClick={() => isOwner && onOpenSelectFile()}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<FormControl mt={5}>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
||||||
|
名称
|
||||||
|
</Box>
|
||||||
|
<Input
|
||||||
|
isDisabled={!isOwner}
|
||||||
|
{...register('name', {
|
||||||
|
required: '展示名称不能为空'
|
||||||
|
})}
|
||||||
|
></Input>
|
||||||
|
</Flex>
|
||||||
|
</FormControl>
|
||||||
|
<Flex mt={5} alignItems={'flex-start'}>
|
||||||
|
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
||||||
|
介绍
|
||||||
|
</Box>
|
||||||
|
<Textarea
|
||||||
|
rows={5}
|
||||||
|
maxLength={500}
|
||||||
|
placeholder={'给你的 AI 应用一个介绍'}
|
||||||
|
{...register('intro')}
|
||||||
|
></Textarea>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Divider mt={5} />
|
||||||
|
|
||||||
|
<Flex alignItems={'center'} mt={5}>
|
||||||
|
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
||||||
|
对话模型
|
||||||
|
</Box>
|
||||||
|
<MySelect
|
||||||
|
width={['200px', '240px']}
|
||||||
|
value={getValues('chat.chatModel')}
|
||||||
|
list={chatModelList.map((item) => ({
|
||||||
|
id: item.chatModel,
|
||||||
|
label: item.name
|
||||||
|
}))}
|
||||||
|
onchange={(val: any) => {
|
||||||
|
setValue('chat.chatModel', val);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems={'center'} mt={5}>
|
||||||
|
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
||||||
|
价格
|
||||||
|
</Box>
|
||||||
|
<Box fontSize={['sm', 'md']}>
|
||||||
|
{formatPrice(ChatModelMap[getValues('chat.chatModel')]?.price, 1000)}
|
||||||
|
元/1K tokens(包括上下文和回答)
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems={'center'} my={10}>
|
||||||
|
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
||||||
|
温度
|
||||||
|
</Box>
|
||||||
|
<Box flex={1} ml={'10px'}>
|
||||||
|
<MySlider
|
||||||
|
markList={[
|
||||||
|
{ label: '严谨', value: 0 },
|
||||||
|
{ label: '发散', value: 10 }
|
||||||
|
]}
|
||||||
|
width={['100%', '260px']}
|
||||||
|
min={0}
|
||||||
|
max={10}
|
||||||
|
activeVal={getValues('chat.temperature')}
|
||||||
|
setVal={(val) => {
|
||||||
|
setValue('chat.temperature', val);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Flex mt={10} alignItems={'flex-start'}>
|
||||||
|
<Box w={['60px', '100px', '140px']} flexShrink={0}>
|
||||||
|
提示词
|
||||||
|
</Box>
|
||||||
|
<Textarea
|
||||||
|
rows={8}
|
||||||
|
placeholder={
|
||||||
|
'模型默认的 prompt 词,通过调整该内容,可以引导模型聊天方向。\n\n如果使用了知识库搜索,没有填写该内容时,系统会自动补充提示词;如果填写了内容,则以填写的内容为准。'
|
||||||
|
}
|
||||||
|
{...register('chat.systemPrompt')}
|
||||||
|
></Textarea>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex mt={5} alignItems={'center'}>
|
||||||
|
<Box w={['60px', '100px', '140px']} flexShrink={0}></Box>
|
||||||
|
<Button
|
||||||
|
mr={3}
|
||||||
|
w={'120px'}
|
||||||
|
size={['sm', 'md']}
|
||||||
|
isLoading={btnLoading}
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
await saveUpdateModel();
|
||||||
|
toast({
|
||||||
|
title: '更新成功',
|
||||||
|
status: 'success'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
error;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
mr={3}
|
||||||
|
w={'100px'}
|
||||||
|
size={['sm', 'md']}
|
||||||
|
variant={'base'}
|
||||||
|
color={'myBlue.600'}
|
||||||
|
borderColor={'myBlue.600'}
|
||||||
|
isLoading={btnLoading}
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
router.prefetch('/chat');
|
||||||
|
await saveUpdateModel();
|
||||||
|
} catch (error) {}
|
||||||
|
router.push(`/chat?modelId=${modelId}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
对话
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorScheme={'gray'}
|
||||||
|
variant={'base'}
|
||||||
|
size={['sm', 'md']}
|
||||||
|
isLoading={btnLoading}
|
||||||
|
_hover={{ color: 'red.600' }}
|
||||||
|
onClick={openConfirm(handleDelModel)}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<File onSelect={onSelectFile} />
|
||||||
|
<ConfirmChild />
|
||||||
|
<Loading loading={isLoading} fixed={false} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Settings;
|
||||||
281
client/src/pages/model/components/detail/components/Share.tsx
Normal file
281
client/src/pages/model/components/detail/components/Share.tsx
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Flex,
|
||||||
|
Box,
|
||||||
|
Tooltip,
|
||||||
|
Button,
|
||||||
|
TableContainer,
|
||||||
|
Table,
|
||||||
|
Thead,
|
||||||
|
Tr,
|
||||||
|
Th,
|
||||||
|
Td,
|
||||||
|
Tbody,
|
||||||
|
useDisclosure,
|
||||||
|
Modal,
|
||||||
|
ModalOverlay,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalFooter,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
FormControl,
|
||||||
|
Slider,
|
||||||
|
SliderTrack,
|
||||||
|
SliderFilledTrack,
|
||||||
|
SliderThumb,
|
||||||
|
SliderMark,
|
||||||
|
Input
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
import { useToast } from '@/hooks/useToast';
|
||||||
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getShareChatList, delShareChatById, createShareChat } from '@/api/chat';
|
||||||
|
import { formatTimeToChatTime, useCopyData, getErrText } from '@/utils/tools';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { defaultShareChat } from '@/constants/model';
|
||||||
|
import type { ShareChatEditType } from '@/types/model';
|
||||||
|
|
||||||
|
const Share = ({ modelId }: { modelId: string }) => {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const { Loading, setIsLoading } = useLoading();
|
||||||
|
const { copyData } = useCopyData();
|
||||||
|
const {
|
||||||
|
isOpen: isOpenCreateShareChat,
|
||||||
|
onOpen: onOpenCreateShareChat,
|
||||||
|
onClose: onCloseCreateShareChat
|
||||||
|
} = useDisclosure();
|
||||||
|
const {
|
||||||
|
register: registerShareChat,
|
||||||
|
getValues: getShareChatValues,
|
||||||
|
setValue: setShareChatValues,
|
||||||
|
handleSubmit: submitShareChat,
|
||||||
|
reset: resetShareChat
|
||||||
|
} = useForm({
|
||||||
|
defaultValues: defaultShareChat
|
||||||
|
});
|
||||||
|
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
data: shareChatList = [],
|
||||||
|
refetch: refetchShareChatList
|
||||||
|
} = useQuery(['initShareChatList', modelId], () => getShareChatList(modelId));
|
||||||
|
|
||||||
|
const onclickCreateShareChat = useCallback(
|
||||||
|
async (e: ShareChatEditType) => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const id = await createShareChat({
|
||||||
|
...e,
|
||||||
|
modelId
|
||||||
|
});
|
||||||
|
onCloseCreateShareChat();
|
||||||
|
refetchShareChatList();
|
||||||
|
|
||||||
|
const url = `对话地址为:${location.origin}/chat/share?shareId=${id}
|
||||||
|
${e.password ? `密码为: ${e.password}` : ''}`;
|
||||||
|
copyData(url, '已复制分享地址');
|
||||||
|
|
||||||
|
resetShareChat(defaultShareChat);
|
||||||
|
} catch (err) {
|
||||||
|
toast({
|
||||||
|
title: getErrText(err, '创建分享链接异常'),
|
||||||
|
status: 'warning'
|
||||||
|
});
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
copyData,
|
||||||
|
modelId,
|
||||||
|
onCloseCreateShareChat,
|
||||||
|
refetchShareChatList,
|
||||||
|
resetShareChat,
|
||||||
|
setIsLoading,
|
||||||
|
toast
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// format share used token
|
||||||
|
const formatTokens = (tokens: number) => {
|
||||||
|
if (tokens < 10000) return tokens;
|
||||||
|
return `${(tokens / 10000).toFixed(2)}万`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box position={'relative'} px={5} minH={'50vh'}>
|
||||||
|
<Flex justifyContent={'space-between'}>
|
||||||
|
<Box fontWeight={'bold'}>
|
||||||
|
免登录聊天窗口
|
||||||
|
<Tooltip label="可以直接分享该模型给其他用户去进行对话,对方无需登录即可直接进行对话。注意,这个功能会消耗你账号的tokens。请保管好链接和密码。">
|
||||||
|
<QuestionOutlineIcon ml={1} />
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
variant={'base'}
|
||||||
|
colorScheme={'myBlue'}
|
||||||
|
size={['sm', 'md']}
|
||||||
|
{...(shareChatList.length >= 10
|
||||||
|
? {
|
||||||
|
isDisabled: true,
|
||||||
|
title: '最多创建10组'
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
|
onClick={onOpenCreateShareChat}
|
||||||
|
>
|
||||||
|
创建新窗口
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<TableContainer mt={3}>
|
||||||
|
<Table variant={'simple'} w={'100%'} overflowX={'auto'}>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>名称</Th>
|
||||||
|
<Th>密码</Th>
|
||||||
|
<Th>最大上下文</Th>
|
||||||
|
<Th>tokens消耗</Th>
|
||||||
|
<Th>最后使用时间</Th>
|
||||||
|
<Th>操作</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{shareChatList.map((item) => (
|
||||||
|
<Tr key={item._id}>
|
||||||
|
<Td>{item.name}</Td>
|
||||||
|
<Td>{item.password === '1' ? '已开启' : '未使用'}</Td>
|
||||||
|
<Td>{item.maxContext}</Td>
|
||||||
|
<Td>{formatTokens(item.tokens)}</Td>
|
||||||
|
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
|
||||||
|
<Td>
|
||||||
|
<Flex>
|
||||||
|
<MyIcon
|
||||||
|
mr={3}
|
||||||
|
name="copy"
|
||||||
|
w={'14px'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
_hover={{ color: 'myBlue.600' }}
|
||||||
|
onClick={() => {
|
||||||
|
const url = `${location.origin}/chat/share?shareId=${item._id}`;
|
||||||
|
copyData(url, '已复制分享地址');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<MyIcon
|
||||||
|
name="delete"
|
||||||
|
w={'14px'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
_hover={{ color: 'red' }}
|
||||||
|
onClick={async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
await delShareChatById(item._id);
|
||||||
|
refetchShareChatList();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
{shareChatList.length === 0 && !isFetching && (
|
||||||
|
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'10vh'}>
|
||||||
|
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||||
|
<Box mt={2} color={'myGray.500'}>
|
||||||
|
没有创建分享链接
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
{/* create shareChat modal */}
|
||||||
|
<Modal isOpen={isOpenCreateShareChat} onClose={onCloseCreateShareChat}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>创建免登录窗口</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<FormControl>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Box flex={'0 0 60px'} w={0}>
|
||||||
|
名称:
|
||||||
|
</Box>
|
||||||
|
<Input
|
||||||
|
placeholder="记录名字,仅用于展示"
|
||||||
|
maxLength={20}
|
||||||
|
{...registerShareChat('name', {
|
||||||
|
required: '记录名称不能为空'
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl mt={4}>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Box flex={'0 0 60px'} w={0}>
|
||||||
|
密码:
|
||||||
|
</Box>
|
||||||
|
<Input placeholder={'不设置密码,可直接访问'} {...registerShareChat('password')} />
|
||||||
|
</Flex>
|
||||||
|
<Box fontSize={'xs'} ml={'60px'} color={'myGray.600'}>
|
||||||
|
密码不会再次展示,请记住你的密码
|
||||||
|
</Box>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl mt={9}>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Box flex={'0 0 120px'} w={0}>
|
||||||
|
最长上下文(组)
|
||||||
|
</Box>
|
||||||
|
<Slider
|
||||||
|
aria-label="slider-ex-1"
|
||||||
|
min={1}
|
||||||
|
max={20}
|
||||||
|
step={1}
|
||||||
|
value={getShareChatValues('maxContext')}
|
||||||
|
onChange={(e) => {
|
||||||
|
setShareChatValues('maxContext', e);
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SliderMark
|
||||||
|
value={getShareChatValues('maxContext')}
|
||||||
|
textAlign="center"
|
||||||
|
bg="myBlue.600"
|
||||||
|
color="white"
|
||||||
|
w={'18px'}
|
||||||
|
h={'18px'}
|
||||||
|
borderRadius={'100px'}
|
||||||
|
fontSize={'xs'}
|
||||||
|
transform={'translate(-50%, -200%)'}
|
||||||
|
>
|
||||||
|
{getShareChatValues('maxContext')}
|
||||||
|
</SliderMark>
|
||||||
|
<SliderTrack>
|
||||||
|
<SliderFilledTrack bg={'myBlue.700'} />
|
||||||
|
</SliderTrack>
|
||||||
|
<SliderThumb />
|
||||||
|
</Slider>
|
||||||
|
</Flex>
|
||||||
|
</FormControl>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant={'base'} mr={3} onClick={onCloseCreateShareChat}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button onClick={submitShareChat(onclickCreateShareChat)}>确认</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
<Loading loading={isFetching} fixed={false} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Share;
|
||||||
@ -1,130 +1,35 @@
|
|||||||
import React, { useCallback, useState, useMemo, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { delModelById, putModelById } from '@/api/model';
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
import type { ModelSchema } from '@/types/mongoSchema';
|
|
||||||
import { Card, Box, Flex, Button, Grid } from '@chakra-ui/react';
|
|
||||||
import { useToast } from '@/hooks/useToast';
|
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { useUserStore } from '@/store/user';
|
import { useUserStore } from '@/store/user';
|
||||||
import { useLoading } from '@/hooks/useLoading';
|
import { useGlobalStore } from '@/store/global';
|
||||||
import Loading from '@/components/Loading';
|
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
import Tabs from '@/components/Tabs';
|
||||||
|
|
||||||
const ModelEditForm = dynamic(() => import('./components/ModelEditForm'), {
|
import Settings from './components/Settings';
|
||||||
loading: () => <Loading fixed={false} />,
|
|
||||||
ssr: false
|
const Kb = dynamic(() => import('./components/Kb'), {
|
||||||
|
ssr: true
|
||||||
|
});
|
||||||
|
const Share = dynamic(() => import('./components/Share'), {
|
||||||
|
ssr: true
|
||||||
|
});
|
||||||
|
const API = dynamic(() => import('./components/API'), {
|
||||||
|
ssr: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const ModelDetail = ({ modelId, isPc }: { modelId: string; isPc: boolean }) => {
|
enum TabEnum {
|
||||||
const { toast } = useToast();
|
'settings' = 'settings',
|
||||||
|
'kb' = 'kb',
|
||||||
|
'share' = 'share',
|
||||||
|
'API' = 'API'
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { userInfo, modelDetail, loadModelDetail, refreshModel, setLastModelId } = useUserStore();
|
const { isPc } = useGlobalStore();
|
||||||
const { Loading, setIsLoading } = useLoading();
|
const { modelDetail } = useUserStore();
|
||||||
const [btnLoading, setBtnLoading] = useState(false);
|
const [currentTab, setCurrentTab] = useState<`${TabEnum}`>(TabEnum.settings);
|
||||||
|
|
||||||
const formHooks = useForm({
|
|
||||||
defaultValues: modelDetail
|
|
||||||
});
|
|
||||||
|
|
||||||
// load model data
|
|
||||||
const { isLoading } = useQuery([modelId], () => loadModelDetail(modelId), {
|
|
||||||
onSuccess(res) {
|
|
||||||
res && formHooks.reset(res);
|
|
||||||
modelId && setLastModelId(modelId);
|
|
||||||
},
|
|
||||||
onError(err: any) {
|
|
||||||
toast({
|
|
||||||
title: err?.message || '获取应用异常',
|
|
||||||
status: 'error'
|
|
||||||
});
|
|
||||||
setLastModelId('');
|
|
||||||
refreshModel.freshMyModels();
|
|
||||||
router.replace('/model');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const isOwner = useMemo(
|
|
||||||
() => modelDetail.userId === userInfo?._id,
|
|
||||||
[modelDetail.userId, userInfo?._id]
|
|
||||||
);
|
|
||||||
|
|
||||||
const canRead = useMemo(
|
|
||||||
() => isOwner || isLoading || modelDetail.share.isShareDetail,
|
|
||||||
[isLoading, isOwner, modelDetail.share.isShareDetail]
|
|
||||||
);
|
|
||||||
|
|
||||||
/* 点击删除 */
|
|
||||||
const handleDelModel = useCallback(async () => {
|
|
||||||
if (!modelDetail) return;
|
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
|
||||||
await delModelById(modelDetail._id);
|
|
||||||
toast({
|
|
||||||
title: '删除成功',
|
|
||||||
status: 'success'
|
|
||||||
});
|
|
||||||
refreshModel.removeModelDetail(modelDetail._id);
|
|
||||||
router.replace('/model');
|
|
||||||
} catch (err: any) {
|
|
||||||
toast({
|
|
||||||
title: err?.message || '删除失败',
|
|
||||||
status: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setIsLoading(false);
|
|
||||||
}, [modelDetail, setIsLoading, toast, refreshModel, router]);
|
|
||||||
|
|
||||||
/* 点前往聊天预览页 */
|
|
||||||
const handlePreviewChat = useCallback(async () => {
|
|
||||||
router.push(`/chat?modelId=${modelId}`);
|
|
||||||
}, [router, modelId]);
|
|
||||||
|
|
||||||
// 提交保存模型修改
|
|
||||||
const saveSubmitSuccess = useCallback(
|
|
||||||
async (data: ModelSchema) => {
|
|
||||||
setBtnLoading(true);
|
|
||||||
try {
|
|
||||||
await putModelById(data._id, {
|
|
||||||
name: data.name,
|
|
||||||
avatar: data.avatar || '/icon/logo.png',
|
|
||||||
chat: data.chat,
|
|
||||||
share: data.share
|
|
||||||
});
|
|
||||||
|
|
||||||
refreshModel.updateModelDetail(data);
|
|
||||||
} catch (err: any) {
|
|
||||||
toast({
|
|
||||||
title: err?.message || '更新失败',
|
|
||||||
status: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setBtnLoading(false);
|
|
||||||
},
|
|
||||||
[refreshModel, toast]
|
|
||||||
);
|
|
||||||
// 提交保存表单失败
|
|
||||||
const saveSubmitError = useCallback(() => {
|
|
||||||
// deep search message
|
|
||||||
const deepSearch = (obj: any): string => {
|
|
||||||
if (!obj) return '提交表单错误';
|
|
||||||
if (!!obj.message) {
|
|
||||||
return obj.message;
|
|
||||||
}
|
|
||||||
return deepSearch(Object.values(obj)[0]);
|
|
||||||
};
|
|
||||||
toast({
|
|
||||||
title: deepSearch(formHooks.formState.errors),
|
|
||||||
status: 'error',
|
|
||||||
duration: 4000,
|
|
||||||
isClosable: true
|
|
||||||
});
|
|
||||||
}, [formHooks.formState.errors, toast]);
|
|
||||||
|
|
||||||
const saveUpdateModel = useCallback(
|
|
||||||
() => formHooks.handleSubmit(saveSubmitSuccess, saveSubmitError)(),
|
|
||||||
[formHooks, saveSubmitError, saveSubmitSuccess]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.onbeforeunload = (e) => {
|
window.onbeforeunload = (e) => {
|
||||||
@ -137,86 +42,54 @@ const ModelDetail = ({ modelId, isPc }: { modelId: string; isPc: boolean }) => {
|
|||||||
};
|
};
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentTab(TabEnum.settings);
|
||||||
|
}, [modelId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box h={'100%'} p={5} overflow={'overlay'} position={'relative'}>
|
<Flex
|
||||||
|
flexDirection={'column'}
|
||||||
|
h={'100%'}
|
||||||
|
maxW={'100vw'}
|
||||||
|
pt={4}
|
||||||
|
overflow={'overlay'}
|
||||||
|
position={'relative'}
|
||||||
|
bg={'white'}
|
||||||
|
>
|
||||||
{/* 头部 */}
|
{/* 头部 */}
|
||||||
<Card px={6} py={3}>
|
<Box textAlign={['center', 'left']} px={5} mb={4}>
|
||||||
{isPc ? (
|
<Box className="textlg" display={['block', 'none']} fontSize={'3xl'} fontWeight={'bold'}>
|
||||||
<Flex alignItems={'center'}>
|
{modelDetail.name}
|
||||||
<Box fontSize={'xl'} fontWeight={'bold'}>
|
</Box>
|
||||||
{modelDetail.name}
|
<Tabs
|
||||||
</Box>
|
mx={['auto', '0']}
|
||||||
<Box flex={1} />
|
mt={2}
|
||||||
<Button variant={'base'} onClick={handlePreviewChat}>
|
w={['300px', '360px']}
|
||||||
开始对话
|
list={[
|
||||||
</Button>
|
{ label: '配置', id: TabEnum.settings },
|
||||||
{isOwner && (
|
{ label: '知识库', id: TabEnum.kb },
|
||||||
<Button
|
{ label: '分享', id: TabEnum.share },
|
||||||
isLoading={btnLoading}
|
{ label: 'API', id: TabEnum.API },
|
||||||
ml={4}
|
{ label: '立即对话', id: 'startChat' }
|
||||||
onClick={async () => {
|
]}
|
||||||
try {
|
size={isPc ? 'md' : 'sm'}
|
||||||
await saveUpdateModel();
|
activeId={currentTab}
|
||||||
toast({
|
onChange={(e: any) => {
|
||||||
title: '更新成功',
|
if (e === 'startChat') {
|
||||||
status: 'success'
|
router.push(`/chat?modelId=${modelId}`);
|
||||||
});
|
} else {
|
||||||
} catch (error) {
|
setCurrentTab(e);
|
||||||
console.log(error);
|
}
|
||||||
error;
|
}}
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
保存修改
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Flex alignItems={'center'}>
|
|
||||||
<Box as={'h3'} fontSize={'xl'} fontWeight={'bold'} flex={1}>
|
|
||||||
{modelDetail.name}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
<Box mt={4} textAlign={'right'}>
|
|
||||||
<Button variant={'base'} size={'sm'} onClick={handlePreviewChat}>
|
|
||||||
开始对话
|
|
||||||
</Button>
|
|
||||||
{isOwner && (
|
|
||||||
<Button
|
|
||||||
ml={4}
|
|
||||||
size={'sm'}
|
|
||||||
isLoading={btnLoading}
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
await saveUpdateModel();
|
|
||||||
toast({
|
|
||||||
title: '更新成功',
|
|
||||||
status: 'success'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
error;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
保存修改
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
<Grid mt={5} gridTemplateColumns={['1fr', '1fr 1fr']} gridGap={5}>
|
|
||||||
<ModelEditForm
|
|
||||||
formHooks={formHooks}
|
|
||||||
handleDelModel={handleDelModel}
|
|
||||||
isOwner={isOwner}
|
|
||||||
canRead={canRead}
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Box>
|
||||||
<Loading loading={isLoading} fixed={false} />
|
<Box flex={1}>
|
||||||
</Box>
|
{currentTab === TabEnum.settings && <Settings modelId={modelId} />}
|
||||||
|
{currentTab === TabEnum.kb && <Kb modelId={modelId} />}
|
||||||
|
{currentTab === TabEnum.API && <API modelId={modelId} />}
|
||||||
|
{currentTab === TabEnum.share && <Share modelId={modelId} />}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ const Model = ({ modelId }: { modelId: string }) => {
|
|||||||
</SideBar>
|
</SideBar>
|
||||||
)}
|
)}
|
||||||
<Box flex={1} h={'100%'} position={'relative'}>
|
<Box flex={1} h={'100%'} position={'relative'}>
|
||||||
{modelId && <ModelDetail modelId={modelId} isPc={isPc} />}
|
{modelId && <ModelDetail modelId={modelId} />}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -44,7 +44,7 @@ const ShareModelList = ({
|
|||||||
{model.name}
|
{model.name}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Tooltip label={model.share.intro}>
|
<Tooltip label={model.intro}>
|
||||||
<Box
|
<Box
|
||||||
className={styles.intro}
|
className={styles.intro}
|
||||||
flex={1}
|
flex={1}
|
||||||
@ -53,7 +53,7 @@ const ShareModelList = ({
|
|||||||
wordBreak={'break-all'}
|
wordBreak={'break-all'}
|
||||||
color={'blackAlpha.600'}
|
color={'blackAlpha.600'}
|
||||||
>
|
>
|
||||||
{model.share.intro || '这个 应用 还没有介绍~'}
|
{model.intro || '这个应用还没有介绍~'}
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,3 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.textlg {
|
|
||||||
background: linear-gradient(to bottom right, #1237b3 0%, #3370ff 40%, #4e83fd 80%, #85b1ff 100%);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@ const modelList = () => {
|
|||||||
return (
|
return (
|
||||||
<Box px={[5, 10]} py={[4, 6]} position={'relative'} minH={'109vh'}>
|
<Box px={[5, 10]} py={[4, 6]} position={'relative'} minH={'109vh'}>
|
||||||
<Flex alignItems={'center'} mb={2}>
|
<Flex alignItems={'center'} mb={2}>
|
||||||
<Box className={styles.textlg} fontWeight={'bold'} fontSize={'3xl'}>
|
<Box className={'textlg'} fontWeight={'bold'} fontSize={'3xl'}>
|
||||||
AI 应用市场
|
AI 应用市场
|
||||||
</Box>
|
</Box>
|
||||||
{/* <Box mt={[2, 0]} textAlign={'right'}>
|
{/* <Box mt={[2, 0]} textAlign={'right'}>
|
||||||
|
|||||||
@ -1,139 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Table,
|
|
||||||
Thead,
|
|
||||||
Tbody,
|
|
||||||
Tr,
|
|
||||||
Th,
|
|
||||||
Td,
|
|
||||||
TableContainer,
|
|
||||||
IconButton,
|
|
||||||
Modal,
|
|
||||||
ModalOverlay,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalBody
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { getOpenApiKeys, createAOpenApiKey, delOpenApiById } from '@/api/openapi';
|
|
||||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
|
||||||
import { useLoading } from '@/hooks/useLoading';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { DeleteIcon } from '@chakra-ui/icons';
|
|
||||||
import { useCopyData } from '@/utils/tools';
|
|
||||||
|
|
||||||
const OpenApi = () => {
|
|
||||||
const { Loading } = useLoading();
|
|
||||||
const {
|
|
||||||
data: apiKeys = [],
|
|
||||||
isLoading: isGetting,
|
|
||||||
refetch
|
|
||||||
} = useQuery(['getOpenApiKeys'], getOpenApiKeys);
|
|
||||||
const [apiKey, setApiKey] = useState('');
|
|
||||||
const { copyData } = useCopyData();
|
|
||||||
|
|
||||||
const { mutate: onclickCreateApiKey, isLoading: isCreating } = useMutation({
|
|
||||||
mutationFn: () => createAOpenApiKey(),
|
|
||||||
onSuccess(res) {
|
|
||||||
setApiKey(res);
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mutate: onclickRemove, isLoading: isDeleting } = useMutation({
|
|
||||||
mutationFn: async (id: string) => delOpenApiById(id),
|
|
||||||
onSuccess() {
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box py={[5, 10]} px={'5vw'}>
|
|
||||||
<Card px={6} py={4} position={'relative'}>
|
|
||||||
<Box fontSize={'xl'} fontWeight={'bold'}>
|
|
||||||
FastGpt Api
|
|
||||||
</Box>
|
|
||||||
<Box fontSize={'sm'} mt={2}>
|
|
||||||
FastGpt Api 允许你将 Fast Gpt 的部分功能通过 api
|
|
||||||
的形式接入到自己的应用中,例如:飞书、企业微信、客服助手。请注意保管你的 Api
|
|
||||||
Key,不要泄露!
|
|
||||||
</Box>
|
|
||||||
<Box>使用 Fast Api 功能仅能使用平台余额。</Box>
|
|
||||||
<Box
|
|
||||||
my={1}
|
|
||||||
as="a"
|
|
||||||
href="https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh"
|
|
||||||
color={'myBlue.800'}
|
|
||||||
textDecoration={'underline'}
|
|
||||||
target={'_blank'}
|
|
||||||
>
|
|
||||||
点击查看文档
|
|
||||||
</Box>
|
|
||||||
<TableContainer mt={2} position={'relative'}>
|
|
||||||
<Table>
|
|
||||||
<Thead>
|
|
||||||
<Tr>
|
|
||||||
<Th>Api Key</Th>
|
|
||||||
<Th>创建时间</Th>
|
|
||||||
<Th>最后一次使用时间</Th>
|
|
||||||
<Th />
|
|
||||||
</Tr>
|
|
||||||
</Thead>
|
|
||||||
<Tbody fontSize={'sm'}>
|
|
||||||
{apiKeys.map(({ id, apiKey, createTime, lastUsedTime }) => (
|
|
||||||
<Tr key={id}>
|
|
||||||
<Td>{apiKey}</Td>
|
|
||||||
<Td>{dayjs(createTime).format('YYYY/MM/DD HH:mm:ss')}</Td>
|
|
||||||
<Td>
|
|
||||||
{lastUsedTime
|
|
||||||
? dayjs(lastUsedTime).format('YYYY/MM/DD HH:mm:ss')
|
|
||||||
: '没有使用过'}
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<IconButton
|
|
||||||
icon={<DeleteIcon />}
|
|
||||||
size={'xs'}
|
|
||||||
aria-label={'delete'}
|
|
||||||
variant={'base'}
|
|
||||||
colorScheme={'gray'}
|
|
||||||
onClick={() => onclickRemove(id)}
|
|
||||||
/>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
))}
|
|
||||||
</Tbody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
<Button
|
|
||||||
maxW={'200px'}
|
|
||||||
mt={5}
|
|
||||||
isLoading={isCreating}
|
|
||||||
isDisabled={apiKeys.length >= 5}
|
|
||||||
title={apiKeys.length >= 5 ? '最多五组 Api Key' : ''}
|
|
||||||
onClick={() => onclickCreateApiKey()}
|
|
||||||
>
|
|
||||||
添加新的 Api Key
|
|
||||||
</Button>
|
|
||||||
<Loading loading={isGetting || isDeleting} fixed={false} />
|
|
||||||
</Card>
|
|
||||||
<Modal isOpen={!!apiKey} onClose={() => setApiKey('')}>
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>Api Key</ModalHeader>
|
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody mb={5}>
|
|
||||||
请保管好你的Api Key
|
|
||||||
<Box userSelect={'all'} onClick={() => copyData(apiKey)}>
|
|
||||||
{apiKey}
|
|
||||||
</Box>
|
|
||||||
</ModalBody>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default OpenApi;
|
|
||||||
@ -15,11 +15,6 @@ const list = [
|
|||||||
label: 'AI应用市场',
|
label: 'AI应用市场',
|
||||||
link: '/model/share'
|
link: '/model/share'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: 'develop',
|
|
||||||
label: '开发',
|
|
||||||
link: '/openapi'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: 'git',
|
icon: 'git',
|
||||||
label: 'Git项目地址',
|
label: 'Git项目地址',
|
||||||
|
|||||||
@ -1,11 +1,6 @@
|
|||||||
import { Schema, model, models, Model as MongoModel } from 'mongoose';
|
import { Schema, model, models, Model as MongoModel } from 'mongoose';
|
||||||
import { ModelSchema as ModelType } from '@/types/mongoSchema';
|
import { ModelSchema as ModelType } from '@/types/mongoSchema';
|
||||||
import {
|
import { ChatModelMap, OpenAiChatEnum } from '@/constants/model';
|
||||||
ModelVectorSearchModeMap,
|
|
||||||
appVectorSearchModeEnum,
|
|
||||||
ChatModelMap,
|
|
||||||
OpenAiChatEnum
|
|
||||||
} from '@/constants/model';
|
|
||||||
|
|
||||||
const ModelSchema = new Schema({
|
const ModelSchema = new Schema({
|
||||||
userId: {
|
userId: {
|
||||||
@ -21,10 +16,9 @@ const ModelSchema = new Schema({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '/icon/logo.png'
|
default: '/icon/logo.png'
|
||||||
},
|
},
|
||||||
status: {
|
intro: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
default: ''
|
||||||
enum: ['waiting', 'running', 'training', 'closed']
|
|
||||||
},
|
},
|
||||||
updateTime: {
|
updateTime: {
|
||||||
type: Date,
|
type: Date,
|
||||||
@ -36,11 +30,17 @@ const ModelSchema = new Schema({
|
|||||||
ref: 'kb',
|
ref: 'kb',
|
||||||
default: []
|
default: []
|
||||||
},
|
},
|
||||||
searchMode: {
|
searchSimilarity: {
|
||||||
// knowledge base search mode
|
type: Number,
|
||||||
|
default: 0.8
|
||||||
|
},
|
||||||
|
searchLimit: {
|
||||||
|
type: Number,
|
||||||
|
default: 5
|
||||||
|
},
|
||||||
|
searchEmptyText: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: Object.keys(ModelVectorSearchModeMap),
|
default: ''
|
||||||
default: appVectorSearchModeEnum.hightSimilarity
|
|
||||||
},
|
},
|
||||||
systemPrompt: {
|
systemPrompt: {
|
||||||
// 系统提示词
|
// 系统提示词
|
||||||
|
|||||||
@ -83,6 +83,11 @@ textarea::placeholder {
|
|||||||
.grecaptcha-badge {
|
.grecaptcha-badge {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
.textlg {
|
||||||
|
background: linear-gradient(to bottom right, #1237b3 0%, #3370ff 40%, #4e83fd 80%, #85b1ff 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
html {
|
html {
|
||||||
|
|||||||
13
client/src/types/model.d.ts
vendored
13
client/src/types/model.d.ts
vendored
@ -1,6 +1,5 @@
|
|||||||
import { ModelStatusEnum } from '@/constants/model';
|
|
||||||
import type { ModelSchema, kbSchema } from './mongoSchema';
|
import type { ModelSchema, kbSchema } from './mongoSchema';
|
||||||
import { ChatModelType, appVectorSearchModeEnum } from '@/constants/model';
|
import { ChatModelType } from '@/constants/model';
|
||||||
|
|
||||||
export type ModelListItemType = {
|
export type ModelListItemType = {
|
||||||
_id: string;
|
_id: string;
|
||||||
@ -10,16 +9,18 @@ export type ModelListItemType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface ModelUpdateParams {
|
export interface ModelUpdateParams {
|
||||||
name: string;
|
name?: string;
|
||||||
avatar: string;
|
avatar?: string;
|
||||||
chat: ModelSchema['chat'];
|
intro?: string;
|
||||||
share: ModelSchema['share'];
|
chat?: ModelSchema['chat'];
|
||||||
|
share?: ModelSchema['share'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShareModelItem {
|
export interface ShareModelItem {
|
||||||
_id: string;
|
_id: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
intro: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
share: ModelSchema['share'];
|
share: ModelSchema['share'];
|
||||||
isCollection: boolean;
|
isCollection: boolean;
|
||||||
|
|||||||
15
client/src/types/mongoSchema.d.ts
vendored
15
client/src/types/mongoSchema.d.ts
vendored
@ -1,11 +1,5 @@
|
|||||||
import type { ChatItemType } from './chat';
|
import type { ChatItemType } from './chat';
|
||||||
import {
|
import { ModelNameEnum, ChatModelType, EmbeddingModelType } from '@/constants/model';
|
||||||
ModelStatusEnum,
|
|
||||||
ModelNameEnum,
|
|
||||||
appVectorSearchModeEnum,
|
|
||||||
ChatModelType,
|
|
||||||
EmbeddingModelType
|
|
||||||
} from '@/constants/model';
|
|
||||||
import type { DataType } from './data';
|
import type { DataType } from './data';
|
||||||
import { BillTypeEnum, InformTypeEnum } from '@/constants/user';
|
import { BillTypeEnum, InformTypeEnum } from '@/constants/user';
|
||||||
import { TrainingModeEnum } from '@/constants/plugin';
|
import { TrainingModeEnum } from '@/constants/plugin';
|
||||||
@ -41,11 +35,13 @@ export interface ModelSchema {
|
|||||||
userId: string;
|
userId: string;
|
||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
status: `${ModelStatusEnum}`;
|
intro: string;
|
||||||
updateTime: number;
|
updateTime: number;
|
||||||
chat: {
|
chat: {
|
||||||
relatedKbs: string[];
|
relatedKbs: string[];
|
||||||
searchMode: `${appVectorSearchModeEnum}`;
|
searchSimilarity: number;
|
||||||
|
searchLimit: number;
|
||||||
|
searchEmptyText: string;
|
||||||
systemPrompt: string;
|
systemPrompt: string;
|
||||||
temperature: number;
|
temperature: number;
|
||||||
chatModel: ChatModelType; // 聊天时用的模型,训练后就是训练的模型
|
chatModel: ChatModelType; // 聊天时用的模型,训练后就是训练的模型
|
||||||
@ -53,7 +49,6 @@ export interface ModelSchema {
|
|||||||
share: {
|
share: {
|
||||||
isShare: boolean;
|
isShare: boolean;
|
||||||
isShareDetail: boolean;
|
isShareDetail: boolean;
|
||||||
intro: string;
|
|
||||||
collection: number;
|
collection: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user