diff --git a/README.md b/README.md
index e2a48fdd7..dc36bc08c 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,12 @@ Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接
- [FastGpt V3.4 更新集合](https://www.bilibili.com/video/BV1Lo4y147Qh/?vd_source=92041a1a395f852f9d89158eaa3f61b4)
- [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
[](https://star-history.com/#c121914yu/FastGPT&Date)
diff --git a/admin/service/route/app.js b/admin/service/route/app.js
index 56ec433f1..556ecb492 100644
--- a/admin/service/route/app.js
+++ b/admin/service/route/app.js
@@ -38,13 +38,13 @@ export const useAppRoute = (app) => {
id: model._id.toString(),
userId: model.userId,
name: model.name,
+ intro: model.intro,
model: model.chat?.chatModel,
relatedKbs: kbNames, // 将relatedKbs的id转换为相应的Kb名称
systemPrompt: model.chat?.systemPrompt || '',
temperature: model.chat?.temperature || 0,
'share.topNum': model.share?.topNum || 0,
'share.isShare': model.share?.isShare || false,
- 'share.intro': model.share?.intro,
'share.collection': model.share?.collection || 0
};
@@ -66,14 +66,15 @@ export const useAppRoute = (app) => {
const _id = req.params.id;
let {
- share: { isShare, intro, topNum }
+ share: { isShare, topNum },
+ intro
} = req.body;
await Model.findByIdAndUpdate(_id, {
$set: {
+ intro: intro,
'share.topNum': Number(topNum),
- 'share.isShare': isShare === 'true',
- 'share.intro': intro
+ 'share.isShare': isShare === 'true' || isShare === true
}
});
diff --git a/admin/service/schema.js b/admin/service/schema.js
index ffc019b88..d05a47811 100644
--- a/admin/service/schema.js
+++ b/admin/service/schema.js
@@ -61,9 +61,9 @@ const modelSchema = new mongoose.Schema({
name: String,
avatar: String,
status: String,
+ intro: String,
chat: {
relatedKbs: [mongoose.Schema.Types.ObjectId],
- searchMode: String,
systemPrompt: String,
temperature: Number,
chatModel: String
diff --git a/admin/src/fields.ts b/admin/src/fields.ts
index a7bf2d7b4..563766beb 100644
--- a/admin/src/fields.ts
+++ b/admin/src/fields.ts
@@ -2,7 +2,7 @@ import { createTextField, createNumberField } from 'tushan';
export const userFields = [
createTextField('id', { label: 'ID' }),
- createTextField('username', { label: '用户名' }),
+ createTextField('username', { label: '用户名', edit: { hidden: true } }),
createNumberField('balance', { label: '余额', list: { sort: true } }),
createTextField('createTime', { label: 'Create Time', list: { sort: true } }),
createTextField('password', { label: '密码', list: { hidden: true } })
@@ -19,20 +19,20 @@ export const payFields = [
export const kbFields = [
createTextField('id', { label: 'ID' }),
- createTextField('userId', { label: '所属用户' }),
+ createTextField('userId', { label: '所属用户', edit: { hidden: true } }),
createTextField('name', { label: '知识库' }),
createTextField('tags', { label: 'Tags' })
];
export const ModelFields = [
createTextField('id', { label: 'ID' }),
- createTextField('userId', { label: '所属用户', list: { hidden: true } }),
+ createTextField('userId', { label: '所属用户', list: { hidden: true }, edit: { hidden: true } }),
createTextField('name', { label: '名字' }),
- createTextField('model', { label: '模型' }),
+ createTextField('model', { label: '模型', edit: { hidden: true } }),
createTextField('share.collection', { label: '收藏数', list: { sort: true } }),
createTextField('share.topNum', { label: '置顶等级', list: { sort: true } }),
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('temperature', { label: '温度' }),
createTextField('systemPrompt', {
diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml
index 89c7b9ad8..afeb1c797 100644
--- a/client/pnpm-lock.yaml
+++ b/client/pnpm-lock.yaml
@@ -1,4 +1,8 @@
-lockfileVersion: '6.0'
+lockfileVersion: '6.1'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
dependencies:
'@alicloud/dysmsapi20170525':
@@ -90,7 +94,7 @@ dependencies:
version: registry.npmmirror.com/nanoid@4.0.1
next:
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:
specifier: ^2.1.2
version: registry.npmmirror.com/nextjs-cors@2.1.2(next@13.1.6)
@@ -326,7 +330,6 @@ packages:
dependencies:
'@jridgewell/gen-mapping': registry.npmmirror.com/@jridgewell/gen-mapping@0.3.3
'@jridgewell/trace-mapping': registry.npmmirror.com/@jridgewell/trace-mapping@0.3.18
- dev: true
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}
@@ -1307,7 +1310,6 @@ packages:
name: '@babel/compat-data'
version: 7.22.5
engines: {node: '>=6.9.0'}
- dev: true
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}
@@ -1332,7 +1334,6 @@ packages:
semver: registry.npmmirror.com/semver@6.3.0
transitivePeerDependencies:
- supports-color
- dev: true
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}
@@ -1344,7 +1345,6 @@ packages:
'@jridgewell/gen-mapping': registry.npmmirror.com/@jridgewell/gen-mapping@0.3.3
'@jridgewell/trace-mapping': registry.npmmirror.com/@jridgewell/trace-mapping@0.3.18
jsesc: registry.npmmirror.com/jsesc@2.5.2
- dev: true
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}
@@ -1379,7 +1379,6 @@ packages:
browserslist: registry.npmmirror.com/browserslist@4.21.7
lru-cache: registry.npmmirror.com/lru-cache@5.1.1
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):
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'
version: 7.22.5
engines: {node: '>=6.9.0'}
- dev: true
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}
@@ -1453,7 +1451,6 @@ packages:
dependencies:
'@babel/template': registry.npmmirror.com/@babel/template@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:
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'}
dependencies:
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
- dev: true
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}
@@ -1497,7 +1493,6 @@ packages:
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
transitivePeerDependencies:
- supports-color
- dev: true
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}
@@ -1556,7 +1551,6 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
- dev: true
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}
@@ -1574,7 +1568,6 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
- dev: true
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}
@@ -1593,7 +1586,6 @@ packages:
name: '@babel/helper-validator-option'
version: 7.22.5
engines: {node: '>=6.9.0'}
- dev: true
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}
@@ -1620,7 +1612,6 @@ packages:
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
transitivePeerDependencies:
- supports-color
- dev: true
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}
@@ -1640,7 +1631,6 @@ packages:
hasBin: true
dependencies:
'@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):
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/parser': registry.npmmirror.com/@babel/parser@7.22.5
'@babel/types': registry.npmmirror.com/@babel/types@7.22.5
- dev: true
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}
@@ -2923,7 +2912,6 @@ packages:
globals: registry.npmmirror.com/globals@11.12.0
transitivePeerDependencies:
- supports-color
- dev: true
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}
@@ -4501,33 +4489,28 @@ packages:
'@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/trace-mapping': registry.npmmirror.com/@jridgewell/trace-mapping@0.3.18
- dev: true
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}
name: '@jridgewell/resolve-uri'
version: 3.1.0
engines: {node: '>=6.0.0'}
- dev: true
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}
name: '@jridgewell/set-array'
version: 1.1.2
engines: {node: '>=6.0.0'}
- dev: true
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}
name: '@jridgewell/sourcemap-codec'
version: 1.4.14
- dev: true
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}
name: '@jridgewell/sourcemap-codec'
version: 1.4.15
- dev: true
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}
@@ -4536,7 +4519,6 @@ packages:
dependencies:
'@jridgewell/resolve-uri': registry.npmmirror.com/@jridgewell/resolve-uri@3.1.0
'@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.14
- dev: true
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}
@@ -5926,7 +5908,6 @@ packages:
electron-to-chromium: registry.npmmirror.com/electron-to-chromium@1.4.425
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)
- dev: true
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}
@@ -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}
name: electron-to-chromium
version: 1.4.425
- dev: true
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}
@@ -7385,7 +7365,6 @@ packages:
name: escalade
version: 3.1.1
engines: {node: '>=6'}
- dev: true
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}
@@ -8101,7 +8080,6 @@ packages:
name: gensync
version: 1.0.0-beta.2
engines: {node: '>=6.9.0'}
- dev: true
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}
@@ -8209,7 +8187,6 @@ packages:
name: globals
version: 11.12.0
engines: {node: '>=4'}
- dev: true
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}
@@ -9020,7 +8997,6 @@ packages:
version: 2.5.2
engines: {node: '>=4'}
hasBin: true
- dev: true
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}
@@ -9054,7 +9030,6 @@ packages:
version: 2.2.3
engines: {node: '>=6'}
hasBin: true
- dev: true
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}
@@ -10044,7 +10019,7 @@ packages:
engines: {node: '>= 0.4.0'}
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}
id: registry.npmmirror.com/next/13.1.6
name: next
@@ -10072,7 +10047,7 @@ packages:
react: registry.npmmirror.com/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
- 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:
'@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
@@ -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
dependencies:
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
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}
name: node-releases
version: 2.0.12
- dev: true
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}
@@ -11318,7 +11292,6 @@ packages:
name: semver
version: 6.3.0
hasBin: true
- dev: true
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}
@@ -11625,7 +11598,7 @@ packages:
inline-style-parser: registry.npmmirror.com/inline-style-parser@0.1.1
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}
id: registry.npmmirror.com/styled-jsx/5.1.1
name: styled-jsx
@@ -11641,6 +11614,7 @@ packages:
babel-plugin-macros:
optional: true
dependencies:
+ '@babel/core': registry.npmmirror.com/@babel/core@7.22.5
client-only: registry.npmmirror.com/client-only@0.0.1
react: registry.npmmirror.com/react@18.2.0
dev: false
@@ -12073,7 +12047,6 @@ packages:
browserslist: registry.npmmirror.com/browserslist@4.21.7
escalade: registry.npmmirror.com/escalade@3.1.1
picocolors: registry.npmmirror.com/picocolors@1.0.0
- dev: true
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}
@@ -12429,7 +12402,3 @@ packages:
name: zwitch
version: 2.0.4
dev: false
-
-settings:
- autoInstallPeers: true
- excludeLinksFromLockfile: false
diff --git a/client/src/components/APIKeyModal/index.tsx b/client/src/components/APIKeyModal/index.tsx
new file mode 100644
index 000000000..ae6e577e8
--- /dev/null
+++ b/client/src/components/APIKeyModal/index.tsx
@@ -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 (
+
+
+
+
+
+ API 秘钥管理
+
+
+ 如果你不想 API 秘钥被滥用,请勿将秘钥直接放置在前端使用~
+
+
+
+
+
+
+
+
+ | Api Key |
+ 创建时间 |
+ 最后一次使用时间 |
+ |
+
+
+
+ {apiKeys.map(({ id, apiKey, createTime, lastUsedTime }) => (
+
+ | {apiKey} |
+ {dayjs(createTime).format('YYYY/MM/DD HH:mm:ss')} |
+
+ {lastUsedTime
+ ? dayjs(lastUsedTime).format('YYYY/MM/DD HH:mm:ss')
+ : '没有使用过'}
+ |
+
+ }
+ size={'xs'}
+ aria-label={'delete'}
+ variant={'base'}
+ colorScheme={'gray'}
+ onClick={() => onclickRemove(id)}
+ />
+ |
+
+ ))}
+
+
+
+
+
+
+ }
+ onClick={() => onclickCreateApiKey()}
+ >
+ 新建秘钥
+
+
+
+
+
+ setApiKey('')}>
+
+
+
+
+ 新的 API 秘钥
+
+
+ 请保管好你的秘钥,秘钥不会再次展示~
+
+
+
+
+ copyData(apiKey)}
+ >
+ {apiKey}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default APIKeyModal;
diff --git a/client/src/components/Icon/icons/apikey.svg b/client/src/components/Icon/icons/apikey.svg
new file mode 100644
index 000000000..48b8bc94d
--- /dev/null
+++ b/client/src/components/Icon/icons/apikey.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/components/Icon/icons/develop.svg b/client/src/components/Icon/icons/develop.svg
deleted file mode 100644
index 7eb7b4bb6..000000000
--- a/client/src/components/Icon/icons/develop.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/client/src/components/Icon/index.tsx b/client/src/components/Icon/index.tsx
index 86a03036d..59a498f55 100644
--- a/client/src/components/Icon/index.tsx
+++ b/client/src/components/Icon/index.tsx
@@ -6,7 +6,6 @@ const map = {
model: require('./icons/model.svg').default,
copy: require('./icons/copy.svg').default,
chatSend: require('./icons/chatSend.svg').default,
- develop: require('./icons/develop.svg').default,
user: require('./icons/user.svg').default,
delete: require('./icons/delete.svg').default,
withdraw: require('./icons/withdraw.svg').default,
@@ -34,7 +33,8 @@ const map = {
text: require('./icons/text.svg').default,
history: require('./icons/history.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;
diff --git a/client/src/components/Iconfont/index.tsx b/client/src/components/Iconfont/index.tsx
deleted file mode 100644
index f91480d0b..000000000
--- a/client/src/components/Iconfont/index.tsx
+++ /dev/null
@@ -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 (
-
- );
-}
-
-export default Iconfont;
diff --git a/client/src/components/Layout/navbar.tsx b/client/src/components/Layout/navbar.tsx
index 88e648b30..b2673a325 100644
--- a/client/src/components/Layout/navbar.tsx
+++ b/client/src/components/Layout/navbar.tsx
@@ -44,12 +44,6 @@ const Navbar = ({ unread }: { unread: number }) => {
link: '/model/share',
activeLink: ['/model/share']
},
- {
- label: '开发',
- icon: 'develop',
- link: '/openapi',
- activeLink: ['/openapi']
- },
{
label: '账号',
icon: 'user',
diff --git a/client/src/components/Select/index.tsx b/client/src/components/Select/index.tsx
new file mode 100644
index 000000000..5faf81d58
--- /dev/null
+++ b/client/src/components/Select/index.tsx
@@ -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 (
+
+ );
+};
+
+export default MySelect;
diff --git a/client/src/components/Slider/index.tsx b/client/src/components/Slider/index.tsx
index 1316a34c7..87fff3c80 100644
--- a/client/src/components/Slider/index.tsx
+++ b/client/src/components/Slider/index.tsx
@@ -9,28 +9,30 @@ import {
} from '@chakra-ui/react';
const MySlider = ({
- markList,
+ markList = [],
setVal,
activeVal,
max = 100,
min = 0,
- step = 1
+ step = 1,
+ width = '100%'
}: {
- markList: {
+ markList?: {
label: string | number;
value: number;
}[];
- activeVal?: number;
+ activeVal: number;
setVal: (index: number) => void;
max?: number;
min?: number;
step?: number;
+ width?: string | string[] | number | number[];
}) => {
const startEndPointStyle = {
content: '""',
- borderRadius: '10px',
- width: '10px',
- height: '10px',
+ borderRadius: '6px',
+ width: '6px',
+ height: '6px',
backgroundColor: '#ffffff',
border: '2px solid #D7DBE2',
position: 'absolute',
@@ -44,37 +46,62 @@ const MySlider = ({
}, [activeVal, markList]);
return (
-
- {markList.map((item, i) => (
+
+ {markList?.map((item, i) => (
{item.label}
))}
+
+ {activeVal}
+
-
+
-
+
);
};
diff --git a/client/src/components/Tabs/index.tsx b/client/src/components/Tabs/index.tsx
index 7ff5af04f..24e4495ec 100644
--- a/client/src/components/Tabs/index.tsx
+++ b/client/src/components/Tabs/index.tsx
@@ -24,13 +24,13 @@ const Tabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
return {
fontSize: 'md',
outP: '4px',
- inlineP: 2
+ inlineP: 1
};
case 'lg':
return {
fontSize: 'lg',
outP: '5px',
- inlineP: 3
+ inlineP: 2
};
}
}, [size]);
diff --git a/client/src/constants/model.ts b/client/src/constants/model.ts
index 95efdf870..7fc3eae35 100644
--- a/client/src/constants/model.ts
+++ b/client/src/constants/model.ts
@@ -31,7 +31,7 @@ export const ChatModelMap = {
[OpenAiChatEnum.GPT35]: {
chatModel: OpenAiChatEnum.GPT35,
name: 'Gpt35-4k',
- contextMaxToken: 4096,
+ contextMaxToken: 4000,
systemMaxToken: 2400,
maxTemperature: 1.2,
price: 2.2
@@ -80,70 +80,18 @@ export const getChatModelList = async () => {
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 = {
_id: 'modelId',
userId: 'userId',
name: '模型名称',
avatar: '/icon/logo.png',
- status: ModelStatusEnum.pending,
+ intro: '',
updateTime: Date.now(),
chat: {
relatedKbs: [],
- searchMode: appVectorSearchModeEnum.hightSimilarity,
+ searchSimilarity: 0.2,
+ searchLimit: 5,
+ searchEmptyText: '',
systemPrompt: '',
temperature: 0,
chatModel: OpenAiChatEnum.GPT35
@@ -151,7 +99,6 @@ export const defaultModel: ModelSchema = {
share: {
isShare: false,
isShareDetail: false,
- intro: '',
collection: 0
}
};
diff --git a/client/src/constants/theme.ts b/client/src/constants/theme.ts
index 93c05e7da..b2fce8cb7 100644
--- a/client/src/constants/theme.ts
+++ b/client/src/constants/theme.ts
@@ -15,9 +15,7 @@ const { definePartsStyle: selectPart, defineMultiStyleConfig: selectMultiStyle }
// modal 弹窗
const ModalTheme = defineMultiStyleConfig({
baseStyle: definePartsStyle({
- dialog: {
- width: '90%'
- }
+ dialog: {}
})
});
diff --git a/client/src/pages/_app.tsx b/client/src/pages/_app.tsx
index 04d5c31a4..94672a09f 100644
--- a/client/src/pages/_app.tsx
+++ b/client/src/pages/_app.tsx
@@ -8,9 +8,9 @@ import { theme } from '@/constants/theme';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import NProgress from 'nprogress'; //nprogress module
import Router from 'next/router';
-import 'nprogress/nprogress.css';
-import '../styles/reset.scss';
import { useGlobalStore } from '@/store/global';
+import 'nprogress/nprogress.css';
+import '@/styles/reset.scss';
//Binding events.
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 {
loadInitData,
initData: { googleVerKey }
@@ -78,6 +78,5 @@ export default function App({ Component, pageProps }: AppProps) {
);
}
-// export function reportWebVitals(metric: NextWebVitalsMetric) {
-// console.log(metric);
-// }
+// @ts-ignore
+export default App;
diff --git a/client/src/pages/api/chat/chat.ts b/client/src/pages/api/chat/chat.ts
index e7119fe65..9da858460 100644
--- a/client/src/pages/api/chat/chat.ts
+++ b/client/src/pages/api/chat/chat.ts
@@ -4,7 +4,7 @@ import { authChat } from '@/service/utils/auth';
import { modelServiceToolMap } from '@/service/utils/chat';
import { ChatItemType } from '@/types/chat';
import { jsonRes } from '@/service/response';
-import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
+import { ChatModelMap } from '@/constants/model';
import { pushChatBill } from '@/service/events/pushBill';
import { resStreamResponse } from '@/service/utils/chat';
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 prompts = [...content, prompt[0]];
-
const {
- code = 200,
- systemPrompts = [],
- quote = [],
- guidePrompt = ''
+ rawSearch = [],
+ userSystemPrompt = [],
+ quotePrompt = []
} = await (async () => {
// 使用了知识库搜索
if (model.chat.relatedKbs?.length > 0) {
- const { code, searchPrompts, rawSearch, guidePrompt } = await appKbSearch({
+ const { rawSearch, userSystemPrompt, quotePrompt } = await appKbSearch({
model,
userId,
fixedQuote: content[content.length - 1]?.quote || [],
prompt: prompt[0],
- similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity
+ similarity: model.chat.searchSimilarity,
+ limit: model.chat.searchLimit
});
return {
- code,
- quote: rawSearch,
- systemPrompts: searchPrompts,
- guidePrompt
+ rawSearch: rawSearch,
+ userSystemPrompt: userSystemPrompt ? [userSystemPrompt] : [],
+ quotePrompt: [quotePrompt]
};
}
if (model.chat.systemPrompt) {
return {
- guidePrompt: model.chat.systemPrompt,
- systemPrompts: [
+ userSystemPrompt: [
{
obj: ChatRoleEnum.System,
value: model.chat.systemPrompt
@@ -92,13 +87,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const conversationId = chatId || String(new Types.ObjectId());
!chatId && res.setHeader(NEW_CHATID_HEADER, conversationId);
if (showModelDetail) {
- guidePrompt && res.setHeader(GUIDE_PROMPT_HEADER, encodeURIComponent(guidePrompt));
- res.setHeader(QUOTE_LEN_HEADER, quote.length);
+ userSystemPrompt[0] &&
+ res.setHeader(GUIDE_PROMPT_HEADER, encodeURIComponent(userSystemPrompt[0].value));
+ res.setHeader(QUOTE_LEN_HEADER, rawSearch.length);
}
// search result is empty
- if (code === 201) {
- const response = systemPrompts[0]?.value;
+ if (model.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && model.chat.searchEmptyText) {
+ const response = model.chat.searchEmptyText;
await saveChat({
chatId,
newChatId: conversationId,
@@ -116,11 +112,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.end(response);
}
- prompts.unshift(...systemPrompts);
+ // 读取对话内容
+ const prompts = [...quotePrompt, ...content, ...userSystemPrompt, prompt[0]];
// content check
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],
value: responseContent,
- quote: showModelDetail ? quote : [],
- systemPrompt: showModelDetail ? guidePrompt : ''
+ quote: showModelDetail ? rawSearch : [],
+ systemPrompt: showModelDetail ? userSystemPrompt[0]?.value : ''
}
],
userId
diff --git a/client/src/pages/api/chat/init.ts b/client/src/pages/api/chat/init.ts
index c768e123b..a9cf643d8 100644
--- a/client/src/pages/api/chat/init.ts
+++ b/client/src/pages/api/chat/init.ts
@@ -6,7 +6,6 @@ import { authUser } from '@/service/utils/auth';
import { ChatItemType } from '@/types/chat';
import { authModel } from '@/service/utils/auth';
import mongoose from 'mongoose';
-import { ModelStatusEnum } from '@/constants/model';
import type { ModelSchema } from '@/types/mongoSchema';
/* 初始化我的聊天框,需要身份验证 */
@@ -29,8 +28,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (!myModel) {
const { _id } = await Model.create({
name: '应用1',
- userId,
- status: ModelStatusEnum.running
+ userId
});
model = (await Model.findById(_id)) as ModelSchema;
} else {
@@ -95,7 +93,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
model: {
name: model.name,
avatar: model.avatar,
- intro: model.share.intro,
+ intro: model.intro,
canUse: model.share.isShare || String(model.userId) === userId
},
chatModel: model.chat.chatModel,
diff --git a/client/src/pages/api/chat/shareChat/chat.ts b/client/src/pages/api/chat/shareChat/chat.ts
index 9b2ca6dc5..ff9161e7d 100644
--- a/client/src/pages/api/chat/shareChat/chat.ts
+++ b/client/src/pages/api/chat/shareChat/chat.ts
@@ -4,7 +4,7 @@ import { authShareChat } from '@/service/utils/auth';
import { modelServiceToolMap } from '@/service/utils/chat';
import { ChatItemSimpleType } from '@/types/chat';
import { jsonRes } from '@/service/response';
-import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
+import { ChatModelMap } from '@/constants/model';
import { pushChatBill, updateShareChatBill } from '@/service/events/pushBill';
import { resStreamResponse } from '@/service/utils/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 prompt = prompts[prompts.length - 1];
- const { code = 200, systemPrompts = [] } = await (async () => {
+ const {
+ rawSearch = [],
+ userSystemPrompt = [],
+ quotePrompt = []
+ } = await (async () => {
// 使用了知识库搜索
if (model.chat.relatedKbs?.length > 0) {
- const { code, searchPrompts } = await appKbSearch({
+ const { rawSearch, userSystemPrompt, quotePrompt } = await appKbSearch({
model,
userId,
fixedQuote: [],
- prompt: prompts[prompts.length - 1],
- similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity
+ prompt: prompt,
+ similarity: model.chat.searchSimilarity,
+ limit: model.chat.searchLimit
});
return {
- code,
- systemPrompts: searchPrompts
+ rawSearch: rawSearch,
+ userSystemPrompt: userSystemPrompt ? [userSystemPrompt] : [],
+ quotePrompt: [quotePrompt]
};
}
if (model.chat.systemPrompt) {
return {
- systemPrompts: [
+ userSystemPrompt: [
{
obj: ChatRoleEnum.System,
value: model.chat.systemPrompt
@@ -71,15 +78,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
})();
// search result is empty
- if (code === 201) {
- return res.send(systemPrompts[0]?.value);
+ if (model.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && model.chat.searchEmptyText) {
+ const response = model.chat.searchEmptyText;
+ return res.end(response);
}
- 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('')
+ input: [...quotePrompt, ...userSystemPrompt, prompt].map((item) => item.value).join('')
});
// 计算温度
@@ -93,7 +102,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
].chatCompletion({
apiKey: userOpenAiKey || systemAuthKey,
temperature: +temperature,
- messages: prompts,
+ messages: completePrompts,
stream: true,
res,
chatId: historyId
diff --git a/client/src/pages/api/chat/shareChat/init.ts b/client/src/pages/api/chat/shareChat/init.ts
index 5cc0fb703..3906b5658 100644
--- a/client/src/pages/api/chat/shareChat/init.ts
+++ b/client/src/pages/api/chat/shareChat/init.ts
@@ -50,7 +50,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
model: {
name: model.name,
avatar: model.avatar,
- intro: model.share.intro
+ intro: model.intro
},
chatModel: model.chat.chatModel
}
diff --git a/client/src/pages/api/model/create.ts b/client/src/pages/api/model/create.ts
index 1c0c507ad..e9805a117 100644
--- a/client/src/pages/api/model/create.ts
+++ b/client/src/pages/api/model/create.ts
@@ -3,7 +3,6 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
-import { ModelStatusEnum } from '@/constants/model';
import { Model } from '@/service/models/model';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -32,8 +31,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 创建模型
const response = await Model.create({
name,
- userId,
- status: ModelStatusEnum.running
+ userId
});
jsonRes(res, {
diff --git a/client/src/pages/api/model/share/getModels.ts b/client/src/pages/api/model/share/getModels.ts
index 027963ae9..eda51df2c 100644
--- a/client/src/pages/api/model/share/getModels.ts
+++ b/client/src/pages/api/model/share/getModels.ts
@@ -31,7 +31,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
$and: [
{ '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'] },
name: 1,
userId: 1,
+ intro: 1,
share: 1,
isCollection: {
$cond: {
diff --git a/client/src/pages/api/model/update.ts b/client/src/pages/api/model/update.ts
index 06d9d4f29..c0b536d8b 100644
--- a/client/src/pages/api/model/update.ts
+++ b/client/src/pages/api/model/update.ts
@@ -9,10 +9,10 @@ import { authModel } from '@/service/utils/auth';
/* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
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 };
- if (!name || !chat || !modelId) {
+ if (!modelId) {
throw new Error('参数错误');
}
@@ -35,10 +35,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
{
name,
avatar,
+ intro,
chat,
- 'share.isShare': share.isShare,
- 'share.isShareDetail': share.isShareDetail,
- 'share.intro': share.intro
+ ...(share && {
+ 'share.isShare': share.isShare,
+ 'share.isShareDetail': share.isShareDetail
+ })
}
);
diff --git a/client/src/pages/api/openapi/chat/chat.ts b/client/src/pages/api/openapi/chat/chat.ts
index 2c32445a0..700b87594 100644
--- a/client/src/pages/api/openapi/chat/chat.ts
+++ b/client/src/pages/api/openapi/chat/chat.ts
@@ -4,12 +4,11 @@ import { authUser, authModel, getApiKey } from '@/service/utils/auth';
import { modelServiceToolMap, resStreamResponse } from '@/service/utils/chat';
import { ChatItemSimpleType } from '@/types/chat';
import { jsonRes } from '@/service/response';
-import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
+import { ChatModelMap } from '@/constants/model';
import { pushChatBill } from '@/service/events/pushBill';
import { ChatRoleEnum } from '@/constants/chat';
import { withNextCors } from '@/service/utils/tools';
import { BillTypeEnum } from '@/constants/user';
-import { sensitiveCheck } from '../../openapi/text/sensitiveCheck';
import { NEW_CHATID_HEADER } from '@/constants/chat';
import { Types } from 'mongoose';
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 prompt = prompts[prompts.length - 1];
- let systemPrompts: {
- obj: ChatRoleEnum;
- value: string;
- }[] = [];
+ const { userSystemPrompt = [], quotePrompt = [] } = await (async () => {
+ // 使用了知识库搜索
+ if (model.chat.relatedKbs?.length > 0) {
+ const { userSystemPrompt, quotePrompt } = await appKbSearch({
+ model,
+ userId,
+ fixedQuote: [],
+ prompt: prompt,
+ similarity: model.chat.searchSimilarity,
+ limit: model.chat.searchLimit
+ });
- // 使用了知识库搜索
- if (model.chat.relatedKbs?.length > 0) {
- const { code, searchPrompts } = await appKbSearch({
- 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
- });
+ return {
+ userSystemPrompt: userSystemPrompt ? [userSystemPrompt] : [],
+ quotePrompt: [quotePrompt]
+ };
}
+ if (model.chat.systemPrompt) {
+ return {
+ userSystemPrompt: [
+ {
+ obj: ChatRoleEnum.System,
+ value: model.chat.systemPrompt
+ }
+ ]
+ };
+ }
+ return {};
+ })();
- systemPrompts = searchPrompts;
- } else if (model.chat.systemPrompt) {
- systemPrompts = [
- {
- obj: ChatRoleEnum.System,
- value: model.chat.systemPrompt
- }
- ];
+ // search result is empty
+ if (model.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && model.chat.searchEmptyText) {
+ const response = model.chat.searchEmptyText;
+ return res.end(response);
}
- prompts.unshift(...systemPrompts);
-
- // content check
- await sensitiveCheck({
- input: [...systemPrompts, prompts[prompts.length - 1]].map((item) => item.value).join('')
- });
+ // 读取对话内容
+ const completePrompts = [...quotePrompt, ...prompts.slice(0, -1), ...userSystemPrompt, prompt];
// 计算温度
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({
apiKey,
temperature: +temperature,
- messages: prompts,
+ messages: completePrompts,
stream: isStream,
res,
chatId: conversationId
diff --git a/client/src/pages/api/openapi/getKeys.ts b/client/src/pages/api/openapi/getKeys.ts
index 067ebb297..dafb3c3e6 100644
--- a/client/src/pages/api/openapi/getKeys.ts
+++ b/client/src/pages/api/openapi/getKeys.ts
@@ -18,7 +18,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
({ _id, apiKey, createTime, lastUsedTime }) => {
return {
id: _id,
- apiKey: `${apiKey.substring(0, 2)}******${apiKey.substring(apiKey.length - 2)}`,
+ apiKey: `******${apiKey.substring(apiKey.length - 4)}`,
createTime,
lastUsedTime
};
diff --git a/client/src/pages/api/openapi/kb/appKbSearch.ts b/client/src/pages/api/openapi/kb/appKbSearch.ts
index 69be8cd89..9bd785130 100644
--- a/client/src/pages/api/openapi/kb/appKbSearch.ts
+++ b/client/src/pages/api/openapi/kb/appKbSearch.ts
@@ -5,7 +5,6 @@ import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
import type { ChatItemSimpleType } from '@/types/chat';
import type { ModelSchema } from '@/types/mongoSchema';
-import { appVectorSearchModeEnum } from '@/constants/model';
import { authModel } from '@/service/utils/auth';
import { ChatModelMap } from '@/constants/model';
import { ChatRoleEnum } from '@/constants/chat';
@@ -21,16 +20,19 @@ export type QuoteItemType = {
type Props = {
prompts: ChatItemSimpleType[];
similarity: number;
+ limit: number;
appId: string;
};
type Response = {
- code: 200 | 201;
rawSearch: QuoteItemType[];
- guidePrompt: string;
- searchPrompts: {
+ userSystemPrompt: {
obj: ChatRoleEnum;
value: string;
- }[];
+ };
+ quotePrompt: {
+ obj: ChatRoleEnum;
+ value: string;
+ };
};
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -41,7 +43,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
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) {
throw new Error('params is error');
@@ -58,7 +60,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
userId,
fixedQuote: [],
prompt: prompts[prompts.length - 1],
- similarity
+ similarity,
+ limit
});
jsonRes(res, {
@@ -78,13 +81,15 @@ export async function appKbSearch({
userId,
fixedQuote,
prompt,
- similarity
+ similarity = 0.8,
+ limit = 5
}: {
model: ModelSchema;
userId: string;
fixedQuote: QuoteItemType[];
prompt: ChatItemSimpleType;
similarity: number;
+ limit: number;
}): Promise {
const modelConstantsData = ChatModelMap[model.chat.chatModel];
@@ -103,7 +108,7 @@ export async function appKbSearch({
.map((item) => `'${item}'`)
.join(',')}) AND vector <#> '[${promptVector[0]}]' < -${similarity} order by vector <#> '[${
promptVector[0]
- }]' limit 10;
+ }]' limit ${limit};
COMMIT;`
);
@@ -115,7 +120,7 @@ export async function appKbSearch({
...searchRes.slice(0, 3),
...fixedQuote.slice(0, 2),
...searchRes.slice(3),
- ...fixedQuote.slice(2, 5)
+ ...fixedQuote.slice(2, 4)
].filter((item) => {
if (idSet.has(item.id)) {
return false;
@@ -125,86 +130,44 @@ export async function appKbSearch({
});
// 计算固定提示词的 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
}
- : model.chat.searchMode === appVectorSearchModeEnum.noContext
- ? {
- obj: ChatRoleEnum.System,
- value: `知识库是关于"${model.name}"的内容,根据知识库内容回答问题.`
- }
: {
- obj: ChatRoleEnum.System,
- value: `玩一个问答游戏,规则为:
-1.你完全忘记你已有的知识
-2.你只回答关于"${model.name}"的问题
-3.你只从知识库中选择内容进行回答
-4.如果问题不在知识库中,你会回答:"我不知道。"
-请务必遵守规则`
+ obj: ChatRoleEnum.Human,
+ value: `知识库是关于 ${model.name} 的内容,参考知识库回答问题。与 "${model.name}" 无关内容,直接回复: "我不知道"。`
};
const fixedSystemTokens = modelToolMap[model.chat.chatModel].countTokens({
- messages: [guidePrompt]
+ messages: [userSystemPrompt]
});
+
+ // filter part quote by maxToken
const sliceResult = modelToolMap[model.chat.chatModel]
.tokenSlice({
maxToken: modelConstantsData.systemMaxToken - fixedSystemTokens,
- messages: filterSearch.map((item) => ({
+ messages: filterSearch.map((item, i) => ({
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
const rawSearch = filterSearch.slice(0, sliceResult.length);
- // system prompt
- 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
- }
- ]
- : []
- };
- }
+ const quoteText = sliceResult ? `知识库:\n${sliceResult}` : '';
return {
- code: 200,
rawSearch,
- guidePrompt: guidePrompt.value || '',
- searchPrompts: [
- {
- obj: ChatRoleEnum.System,
- value: `知识库:<${systemPrompt}>`
- },
- guidePrompt
- ]
+ userSystemPrompt,
+ quotePrompt: {
+ obj: ChatRoleEnum.System,
+ value: quoteText
+ }
};
}
diff --git a/client/src/pages/api/openapi/postKey.ts b/client/src/pages/api/openapi/postKey.ts
index 24cd0f538..53f30fa59 100644
--- a/client/src/pages/api/openapi/postKey.ts
+++ b/client/src/pages/api/openapi/postKey.ts
@@ -4,7 +4,7 @@ import { jsonRes } from '@/service/response';
import { connectToDatabase, OpenApi } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { customAlphabet } from 'nanoid';
-const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890');
+const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
@@ -14,11 +14,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const count = await OpenApi.find({ userId }).countDocuments();
- if (count >= 5) {
- throw new Error('最多 5 组API Key');
+ if (count >= 10) {
+ throw new Error('最多 10 组 API 秘钥');
}
- const apiKey = `${userId}-${nanoid()}`;
+ const apiKey = `fastgpt-${nanoid()}`;
await OpenApi.create({
userId,
diff --git a/client/src/pages/model/components/ModelList.tsx b/client/src/pages/model/components/ModelList.tsx
index 235031a9a..d557f9d66 100644
--- a/client/src/pages/model/components/ModelList.tsx
+++ b/client/src/pages/model/components/ModelList.tsx
@@ -83,7 +83,7 @@ const ModelList = ({ modelId }: { modelId: string }) => {
setSearchText(e.target.value)}
/>
@@ -111,7 +111,7 @@ const ModelList = ({ modelId }: { modelId: string }) => {
/>
-
+
{
{
{item.name}
-
- {item.systemPrompt || '这个 应用 没有设置提示词~'}
-
))}
diff --git a/client/src/pages/model/components/detail/components/API.tsx b/client/src/pages/model/components/detail/components/API.tsx
new file mode 100644
index 000000000..c1a539dc5
--- /dev/null
+++ b/client/src/pages/model/components/detail/components/API.tsx
@@ -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 (
+
+
+
+ AppId:
+ copyData(modelId, '已复制 AppId')}
+ >
+ {modelId}
+
+
+ copyData(baseUrl, '已复制 API 地址')}
+ >
+
+ API服务器
+
+
+ {baseUrl}
+
+
+ }
+ variant={'base'}
+ onClick={onOpenAPIModal}
+ >
+ API 秘钥
+
+
+
+
+
+
+
+ {isOpenAPIModal && }
+
+ );
+};
+
+export default API;
diff --git a/client/src/pages/model/components/detail/components/Kb.tsx b/client/src/pages/model/components/detail/components/Kb.tsx
new file mode 100644
index 000000000..7ed6ff498
--- /dev/null
+++ b/client/src/pages/model/components/detail/components/Kb.tsx
@@ -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([]);
+ 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 (
+
+ 关联的知识库({modelDetail.chat?.relatedKbs.length})
+ {(() => {
+ const kbs =
+ modelDetail.chat?.relatedKbs
+ ?.map((id) => kbList.find((kb) => kb._id === id))
+ .filter((item) => item) || [];
+ return (
+
+ {
+ reset({
+ searchSimilarity: modelDetail.chat.searchSimilarity,
+ searchLimit: modelDetail.chat.searchLimit,
+ searchEmptyText: modelDetail.chat.searchEmptyText
+ });
+ onOpenEditParams();
+ }}
+ >
+
+ }
+ aria-label={''}
+ variant={'base'}
+ />
+ 调整搜索参数
+
+
+ 相似度: {modelDetail.chat.searchSimilarity}, 单次搜索数量:{' '}
+ {modelDetail.chat.searchLimit}, 空搜索时拒绝回复:{' '}
+ {modelDetail.chat.searchEmptyText !== '' ? 'true' : 'false'}
+
+
+ {
+ setSelectedIdList(
+ modelDetail.chat?.relatedKbs ? [...modelDetail.chat?.relatedKbs] : []
+ );
+ onOpenKbSelect();
+ }}
+ >
+
+ }
+ aria-label={''}
+ variant={'base'}
+ />
+ 选择关联知识库
+
+
+ 关联知识库,让 AI 应用回答你的特有内容。
+
+
+ {kbs.map((item) =>
+ item ? (
+
+
+
+
+ {item.name}
+
+
+
+
+ }
+ 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 });
+ }}
+ />
+
+
+ ) : null
+ )}
+
+ );
+ })()}
+ {/* select kb modal */}
+
+
+
+ 关联的知识库({selectedIdList.length})
+
+
+ {kbList.map((item) => (
+ {
+ 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);
+ }}
+ >
+
+
+
+ {item.name}
+
+
+
+ ))}
+
+
+
+
+
+
+
+ {/* edit mode */}
+
+
+
+ 搜索参数调整
+
+
+
+
+ 相似度
+
+
+
+
+ {
+ setValue('searchSimilarity', val);
+ setRefresh(!refresh);
+ }}
+ />
+
+
+ 单次搜索数量
+
+ {
+ setValue('searchLimit', val);
+ setRefresh(!refresh);
+ }}
+ />
+
+
+
+ 空搜索回复
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Kb;
diff --git a/client/src/pages/model/components/detail/components/ModelEditForm.tsx b/client/src/pages/model/components/detail/components/ModelEditForm.tsx
deleted file mode 100644
index aa1c24904..000000000
--- a/client/src/pages/model/components/detail/components/ModelEditForm.tsx
+++ /dev/null
@@ -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;
- 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 */}
-
- 基本信息
-
-
- AppId
-
- {getValues('_id')}
-
-
-
- 头像
-
- isOwner && onOpenSelectFile()}
- />
-
-
-
-
- 名称
-
-
-
-
-
-
-
- 对话模型
-
-
-
-
-
- 价格
-
-
- {formatPrice(ChatModelMap[getValues('chat.chatModel')]?.price, 1000)}
- 元/1K tokens(包括上下文和回答)
-
-
-
-
- 收藏人数
-
- {getValues('share.collection')}人
-
- {isOwner && (
-
- 删除应用
-
-
- )}
-
- {/* model effect */}
- {canRead && (
-
- 模型效果
-
-
-
-
- 温度
-
-
-
-
-
-
- {
- setValue('chat.temperature', e);
- setRefresh(!refresh);
- }}
- >
-
- {getValues('chat.temperature')}
-
-
-
-
-
-
-
-
- {getValues('chat.relatedKbs')?.length > 0 && (
-
-
- 搜索模式
-
-
-
- )}
-
- 系统提示词
-
-
-
- )}
- {isOwner && (
- <>
- {/* model share setting */}
- {/*
- 分享设置
-
-
-
- 模型分享:
-
-
-
-
- {
- setValue('share.isShare', !getValues('share.isShare'));
- setRefresh(!refresh);
- }}
- />
-
-
- 模型介绍
-
-
-
- */}
-
-
- 关联的知识库
-
-
- {(() => {
- const kbs =
- getValues('chat.relatedKbs')?.map((id) => kbList.find((kb) => kb._id === id)) || [];
- return (
- <>
- {kbs.map((item) =>
- item ? (
- router.push(`/kb?kbId=${item._id}`)}
- >
-
-
-
- {item.name}
-
-
-
- ) : null
- )}
- >
- );
- })()}
-
- >
- )}
- {/* shareChat */}
-
-
-
- 免登录聊天窗口
-
-
-
- (Beta)
-
-
-
-
-
-
-
- | 名称 |
- 密码 |
- 最大上下文 |
- tokens消耗 |
- 最后使用时间 |
- 操作 |
-
-
-
- {shareChatList.map((item) => (
-
- | {item.name} |
- {item.password === '1' ? '已开启' : '未使用'} |
- {item.maxContext} |
- {formatTokens(item.tokens)} |
- {item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'} |
-
-
- {
- const url = `${location.origin}/chat/share?shareId=${item._id}`;
- copyData(url, '已复制分享地址');
- }}
- />
- {
- setLoading(true);
- try {
- await delShareChatById(item._id);
- refetchShareChatList();
- } catch (error) {
- console.log(error);
- }
- setLoading(false);
- }}
- />
-
- |
-
- ))}
-
-
-
-
- {/* create shareChat modal */}
-
-
-
- 创建免登录窗口
-
-
-
-
-
- 名称:
-
-
-
-
-
-
-
- 密码:
-
-
-
-
- 密码不会再次展示,请记住你的密码
-
-
-
-
-
- 最长上下文(组)
-
- {
- setShareChatValues('maxContext', e);
- setRefresh(!refresh);
- }}
- >
-
- {getShareChatValues('maxContext')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* select kb modal */}
-
-
-
- 选择关联的知识库
-
-
- {kbList.map((item) => (
-
- {
- 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);
- }}
- >
-
-
-
- {item.name}
-
-
-
-
- ))}
-
-
-
-
-
-
-
-
-
- >
- );
-};
-
-export default ModelEditForm;
diff --git a/client/src/pages/model/components/detail/components/Settings.tsx b/client/src/pages/model/components/detail/components/Settings.tsx
new file mode 100644
index 000000000..7ffce006c
--- /dev/null
+++ b/client/src/pages/model/components/detail/components/Settings.tsx
@@ -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 (
+
+
+
+ 头像
+
+ isOwner && onOpenSelectFile()}
+ />
+
+
+
+
+ 名称
+
+
+
+
+
+
+ 介绍
+
+
+
+
+
+
+
+
+ 对话模型
+
+ ({
+ id: item.chatModel,
+ label: item.name
+ }))}
+ onchange={(val: any) => {
+ setValue('chat.chatModel', val);
+ setRefresh(!refresh);
+ }}
+ />
+
+
+
+ 价格
+
+
+ {formatPrice(ChatModelMap[getValues('chat.chatModel')]?.price, 1000)}
+ 元/1K tokens(包括上下文和回答)
+
+
+
+
+ 温度
+
+
+ {
+ setValue('chat.temperature', val);
+ setRefresh(!refresh);
+ }}
+ />
+
+
+
+
+ 提示词
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Settings;
diff --git a/client/src/pages/model/components/detail/components/Share.tsx b/client/src/pages/model/components/detail/components/Share.tsx
new file mode 100644
index 000000000..c0273a1fc
--- /dev/null
+++ b/client/src/pages/model/components/detail/components/Share.tsx
@@ -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 (
+
+
+
+ 免登录聊天窗口
+
+
+
+
+
+
+
+
+
+
+ | 名称 |
+ 密码 |
+ 最大上下文 |
+ tokens消耗 |
+ 最后使用时间 |
+ 操作 |
+
+
+
+ {shareChatList.map((item) => (
+
+ | {item.name} |
+ {item.password === '1' ? '已开启' : '未使用'} |
+ {item.maxContext} |
+ {formatTokens(item.tokens)} |
+ {item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'} |
+
+
+ {
+ const url = `${location.origin}/chat/share?shareId=${item._id}`;
+ copyData(url, '已复制分享地址');
+ }}
+ />
+ {
+ setIsLoading(true);
+ try {
+ await delShareChatById(item._id);
+ refetchShareChatList();
+ } catch (error) {
+ console.log(error);
+ }
+ setIsLoading(false);
+ }}
+ />
+
+ |
+
+ ))}
+
+
+
+ {shareChatList.length === 0 && !isFetching && (
+
+
+
+ 没有创建分享链接
+
+
+ )}
+ {/* create shareChat modal */}
+
+
+
+ 创建免登录窗口
+
+
+
+
+
+ 名称:
+
+
+
+
+
+
+
+ 密码:
+
+
+
+
+ 密码不会再次展示,请记住你的密码
+
+
+
+
+
+ 最长上下文(组)
+
+ {
+ setShareChatValues('maxContext', e);
+ setRefresh(!refresh);
+ }}
+ >
+
+ {getShareChatValues('maxContext')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Share;
diff --git a/client/src/pages/model/components/detail/index.tsx b/client/src/pages/model/components/detail/index.tsx
index 7f7317724..5afaaad25 100644
--- a/client/src/pages/model/components/detail/index.tsx
+++ b/client/src/pages/model/components/detail/index.tsx
@@ -1,130 +1,35 @@
-import React, { useCallback, useState, useMemo, useEffect } from 'react';
+import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
-import { delModelById, putModelById } from '@/api/model';
-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 { Box, Flex } from '@chakra-ui/react';
import { useUserStore } from '@/store/user';
-import { useLoading } from '@/hooks/useLoading';
-import Loading from '@/components/Loading';
+import { useGlobalStore } from '@/store/global';
import dynamic from 'next/dynamic';
+import Tabs from '@/components/Tabs';
-const ModelEditForm = dynamic(() => import('./components/ModelEditForm'), {
- loading: () => ,
- ssr: false
+import Settings from './components/Settings';
+
+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 }) => {
- const { toast } = useToast();
+enum TabEnum {
+ 'settings' = 'settings',
+ 'kb' = 'kb',
+ 'share' = 'share',
+ 'API' = 'API'
+}
+
+const ModelDetail = ({ modelId }: { modelId: string }) => {
const router = useRouter();
- const { userInfo, modelDetail, loadModelDetail, refreshModel, setLastModelId } = useUserStore();
- const { Loading, setIsLoading } = useLoading();
- const [btnLoading, setBtnLoading] = useState(false);
-
- 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]
- );
+ const { isPc } = useGlobalStore();
+ const { modelDetail } = useUserStore();
+ const [currentTab, setCurrentTab] = useState<`${TabEnum}`>(TabEnum.settings);
useEffect(() => {
window.onbeforeunload = (e) => {
@@ -137,86 +42,54 @@ const ModelDetail = ({ modelId, isPc }: { modelId: string; isPc: boolean }) => {
};
}, [router]);
+ useEffect(() => {
+ setCurrentTab(TabEnum.settings);
+ }, [modelId]);
+
return (
-
+
{/* 头部 */}
-
- {isPc ? (
-
-
- {modelDetail.name}
-
-
-
- {isOwner && (
-
- )}
-
- ) : (
- <>
-
-
- {modelDetail.name}
-
-
-
-
- {isOwner && (
-
- )}
-
- >
- )}
-
-
-
+
+ {modelDetail.name}
+
+ {
+ if (e === 'startChat') {
+ router.push(`/chat?modelId=${modelId}`);
+ } else {
+ setCurrentTab(e);
+ }
+ }}
/>
-
-
-
+
+
+ {currentTab === TabEnum.settings && }
+ {currentTab === TabEnum.kb && }
+ {currentTab === TabEnum.API && }
+ {currentTab === TabEnum.share && }
+
+
);
};
diff --git a/client/src/pages/model/index.tsx b/client/src/pages/model/index.tsx
index 31673c630..f87998a68 100644
--- a/client/src/pages/model/index.tsx
+++ b/client/src/pages/model/index.tsx
@@ -34,7 +34,7 @@ const Model = ({ modelId }: { modelId: string }) => {
)}
- {modelId && }
+ {modelId && }
);
diff --git a/client/src/pages/model/share/components/list.tsx b/client/src/pages/model/share/components/list.tsx
index d1e1f348b..f070ebc98 100644
--- a/client/src/pages/model/share/components/list.tsx
+++ b/client/src/pages/model/share/components/list.tsx
@@ -44,7 +44,7 @@ const ShareModelList = ({
{model.name}
-
+
- {model.share.intro || '这个 应用 还没有介绍~'}
+ {model.intro || '这个应用还没有介绍~'}
diff --git a/client/src/pages/model/share/index.module.scss b/client/src/pages/model/share/index.module.scss
index bd8f264ab..02335e410 100644
--- a/client/src/pages/model/share/index.module.scss
+++ b/client/src/pages/model/share/index.module.scss
@@ -5,8 +5,3 @@
overflow: hidden;
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;
-}
diff --git a/client/src/pages/model/share/index.tsx b/client/src/pages/model/share/index.tsx
index 40c974c8b..b97c69e93 100644
--- a/client/src/pages/model/share/index.tsx
+++ b/client/src/pages/model/share/index.tsx
@@ -45,7 +45,7 @@ const modelList = () => {
return (
-
+
AI 应用市场
{/*
diff --git a/client/src/pages/openapi/index.tsx b/client/src/pages/openapi/index.tsx
deleted file mode 100644
index 4ce7f7bd2..000000000
--- a/client/src/pages/openapi/index.tsx
+++ /dev/null
@@ -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 (
-
-
-
- FastGpt Api
-
-
- FastGpt Api 允许你将 Fast Gpt 的部分功能通过 api
- 的形式接入到自己的应用中,例如:飞书、企业微信、客服助手。请注意保管你的 Api
- Key,不要泄露!
-
- 使用 Fast Api 功能仅能使用平台余额。
-
- 点击查看文档
-
-
-
-
-
- | Api Key |
- 创建时间 |
- 最后一次使用时间 |
- |
-
-
-
- {apiKeys.map(({ id, apiKey, createTime, lastUsedTime }) => (
-
- | {apiKey} |
- {dayjs(createTime).format('YYYY/MM/DD HH:mm:ss')} |
-
- {lastUsedTime
- ? dayjs(lastUsedTime).format('YYYY/MM/DD HH:mm:ss')
- : '没有使用过'}
- |
-
- }
- size={'xs'}
- aria-label={'delete'}
- variant={'base'}
- colorScheme={'gray'}
- onClick={() => onclickRemove(id)}
- />
- |
-
- ))}
-
-
-
-
-
-
- setApiKey('')}>
-
-
- Api Key
-
-
- 请保管好你的Api Key
- copyData(apiKey)}>
- {apiKey}
-
-
-
-
-
- );
-};
-
-export default OpenApi;
diff --git a/client/src/pages/tools/index.tsx b/client/src/pages/tools/index.tsx
index 96bf6f9e1..11f14e9a2 100644
--- a/client/src/pages/tools/index.tsx
+++ b/client/src/pages/tools/index.tsx
@@ -15,11 +15,6 @@ const list = [
label: 'AI应用市场',
link: '/model/share'
},
- {
- icon: 'develop',
- label: '开发',
- link: '/openapi'
- },
{
icon: 'git',
label: 'Git项目地址',
diff --git a/client/src/service/models/model.ts b/client/src/service/models/model.ts
index e80938732..c87fdd5ce 100644
--- a/client/src/service/models/model.ts
+++ b/client/src/service/models/model.ts
@@ -1,11 +1,6 @@
import { Schema, model, models, Model as MongoModel } from 'mongoose';
import { ModelSchema as ModelType } from '@/types/mongoSchema';
-import {
- ModelVectorSearchModeMap,
- appVectorSearchModeEnum,
- ChatModelMap,
- OpenAiChatEnum
-} from '@/constants/model';
+import { ChatModelMap, OpenAiChatEnum } from '@/constants/model';
const ModelSchema = new Schema({
userId: {
@@ -21,10 +16,9 @@ const ModelSchema = new Schema({
type: String,
default: '/icon/logo.png'
},
- status: {
+ intro: {
type: String,
- required: true,
- enum: ['waiting', 'running', 'training', 'closed']
+ default: ''
},
updateTime: {
type: Date,
@@ -36,11 +30,17 @@ const ModelSchema = new Schema({
ref: 'kb',
default: []
},
- searchMode: {
- // knowledge base search mode
+ searchSimilarity: {
+ type: Number,
+ default: 0.8
+ },
+ searchLimit: {
+ type: Number,
+ default: 5
+ },
+ searchEmptyText: {
type: String,
- enum: Object.keys(ModelVectorSearchModeMap),
- default: appVectorSearchModeEnum.hightSimilarity
+ default: ''
},
systemPrompt: {
// 系统提示词
diff --git a/client/src/styles/reset.scss b/client/src/styles/reset.scss
index 3f87f79ef..90d046bf7 100644
--- a/client/src/styles/reset.scss
+++ b/client/src/styles/reset.scss
@@ -83,6 +83,11 @@ textarea::placeholder {
.grecaptcha-badge {
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) {
html {
diff --git a/client/src/types/model.d.ts b/client/src/types/model.d.ts
index 99b7142e4..5ee66a84e 100644
--- a/client/src/types/model.d.ts
+++ b/client/src/types/model.d.ts
@@ -1,6 +1,5 @@
-import { ModelStatusEnum } from '@/constants/model';
import type { ModelSchema, kbSchema } from './mongoSchema';
-import { ChatModelType, appVectorSearchModeEnum } from '@/constants/model';
+import { ChatModelType } from '@/constants/model';
export type ModelListItemType = {
_id: string;
@@ -10,16 +9,18 @@ export type ModelListItemType = {
};
export interface ModelUpdateParams {
- name: string;
- avatar: string;
- chat: ModelSchema['chat'];
- share: ModelSchema['share'];
+ name?: string;
+ avatar?: string;
+ intro?: string;
+ chat?: ModelSchema['chat'];
+ share?: ModelSchema['share'];
}
export interface ShareModelItem {
_id: string;
avatar: string;
name: string;
+ intro: string;
userId: string;
share: ModelSchema['share'];
isCollection: boolean;
diff --git a/client/src/types/mongoSchema.d.ts b/client/src/types/mongoSchema.d.ts
index df0b29b9d..497776933 100644
--- a/client/src/types/mongoSchema.d.ts
+++ b/client/src/types/mongoSchema.d.ts
@@ -1,11 +1,5 @@
import type { ChatItemType } from './chat';
-import {
- ModelStatusEnum,
- ModelNameEnum,
- appVectorSearchModeEnum,
- ChatModelType,
- EmbeddingModelType
-} from '@/constants/model';
+import { ModelNameEnum, ChatModelType, EmbeddingModelType } from '@/constants/model';
import type { DataType } from './data';
import { BillTypeEnum, InformTypeEnum } from '@/constants/user';
import { TrainingModeEnum } from '@/constants/plugin';
@@ -41,11 +35,13 @@ export interface ModelSchema {
userId: string;
name: string;
avatar: string;
- status: `${ModelStatusEnum}`;
+ intro: string;
updateTime: number;
chat: {
relatedKbs: string[];
- searchMode: `${appVectorSearchModeEnum}`;
+ searchSimilarity: number;
+ searchLimit: number;
+ searchEmptyText: string;
systemPrompt: string;
temperature: number;
chatModel: ChatModelType; // 聊天时用的模型,训练后就是训练的模型
@@ -53,7 +49,6 @@ export interface ModelSchema {
share: {
isShare: boolean;
isShareDetail: boolean;
- intro: string;
collection: number;
};
}