Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
61447c60
authored
Jun 17, 2023
by
archer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: new app page
parent
df2fda61
Hide whitespace changes
Inline
Side-by-side
Showing
43 changed files
with
1445 additions
and
1131 deletions
+1445
-1131
README.md
+6
-0
admin/service/route/app.js
+5
-4
admin/service/schema.js
+1
-1
admin/src/fields.ts
+5
-5
client/pnpm-lock.yaml
+11
-42
client/src/components/APIKeyModal/index.tsx
+107
-86
client/src/components/Icon/icons/apikey.svg
+2
-0
client/src/components/Icon/icons/develop.svg
+0
-2
client/src/components/Icon/index.tsx
+2
-2
client/src/components/Iconfont/index.tsx
+0
-23
client/src/components/Layout/navbar.tsx
+0
-6
client/src/components/Select/index.tsx
+81
-0
client/src/components/Slider/index.tsx
+43
-16
client/src/components/Tabs/index.tsx
+2
-2
client/src/constants/model.ts
+5
-58
client/src/constants/theme.ts
+1
-3
client/src/pages/_app.tsx
+5
-6
client/src/pages/api/chat/chat.ts
+21
-24
client/src/pages/api/chat/init.ts
+2
-4
client/src/pages/api/chat/shareChat/chat.ts
+22
-13
client/src/pages/api/chat/shareChat/init.ts
+1
-1
client/src/pages/api/model/create.ts
+1
-3
client/src/pages/api/model/share/getModels.ts
+2
-1
client/src/pages/api/model/update.ts
+7
-5
client/src/pages/api/openapi/chat/chat.ts
+37
-40
client/src/pages/api/openapi/getKeys.ts
+1
-1
client/src/pages/api/openapi/kb/appKbSearch.ts
+33
-70
client/src/pages/api/openapi/postKey.ts
+4
-4
client/src/pages/model/components/ModelList.tsx
+3
-6
client/src/pages/model/components/detail/components/API.tsx
+82
-0
client/src/pages/model/components/detail/components/Kb.tsx
+394
-0
client/src/pages/model/components/detail/components/Settings.tsx
+330
-0
client/src/pages/model/components/detail/components/Share.tsx
+125
-463
client/src/pages/model/components/detail/index.tsx
+70
-197
client/src/pages/model/index.tsx
+1
-1
client/src/pages/model/share/components/list.tsx
+2
-2
client/src/pages/model/share/index.module.scss
+0
-5
client/src/pages/model/share/index.tsx
+1
-1
client/src/pages/tools/index.tsx
+0
-5
client/src/service/models/model.ts
+13
-13
client/src/styles/reset.scss
+5
-0
client/src/types/model.d.ts
+7
-6
client/src/types/mongoSchema.d.ts
+5
-10
No files found.
README.md
View file @
61447c60
...
...
@@ -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)
admin/service/route/app.js
View file @
61447c60
...
...
@@ -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
}
});
...
...
admin/service/schema.js
View file @
61447c60
...
...
@@ -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
...
...
admin/src/fields.ts
View file @
61447c60
...
...
@@ -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'
,
{
...
...
client/pnpm-lock.yaml
View file @
61447c60
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
client/src/
pages/openapi
/index.tsx
→
client/src/
components/APIKeyModal
/index.tsx
View file @
61447c60
import
React
,
{
useState
}
from
'react'
;
import
{
Card
,
Box
,
Button
,
Modal
,
ModalOverlay
,
ModalContent
,
Flex
,
ModalFooter
,
ModalBody
,
ModalCloseButton
,
Table
,
Thead
,
Tbody
,
...
...
@@ -10,23 +16,20 @@ import {
Th
,
Td
,
TableContainer
,
IconButton
,
Modal
,
ModalOverlay
,
ModalContent
,
ModalHeader
,
ModalCloseButton
,
ModalBody
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
{
DeleteIcon
}
from
'@chakra-ui/icons'
;
import
{
useCopyData
}
from
'@/utils/tools'
;
import
{
AddIcon
,
DeleteIcon
}
from
'@chakra-ui/icons'
;
import
{
getErrText
,
useCopyData
}
from
'@/utils/tools'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
MyIcon
from
'../Icon'
;
const
OpenApi
=
(
)
=>
{
const
APIKeyModal
=
({
onClose
}:
{
onClose
:
()
=>
void
}
)
=>
{
const
{
Loading
}
=
useLoading
();
const
{
toast
}
=
useToast
();
const
{
data
:
apiKeys
=
[],
isLoading
:
isGetting
,
...
...
@@ -40,6 +43,12 @@ const OpenApi = () => {
onSuccess
(
res
)
{
setApiKey
(
res
);
refetch
();
},
onError
(
err
)
{
toast
({
status
:
'warning'
,
title
:
getErrText
(
err
)
});
}
});
...
...
@@ -51,89 +60,101 @@ const OpenApi = () => {
});
return
(
<
Box
py=
{
[
5
,
10
]
}
px=
{
'5vw'
}
>
<
Card
px=
{
6
}
py=
{
4
}
position=
{
'relative'
}
>
<
Box
fontSize=
{
'xl'
}
fontWeight=
{
'bold'
}
>
FastGpt Api
<
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
>
<
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
>
<
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
>
))
}
</
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
>
</
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
>
<
ModalHeader
>
Api Key
</
ModalHeader
>
<
ModalCloseButton
/
>
<
ModalBody
mb=
{
5
}
>
请保管好你的Api Key
<
Box
userSelect=
{
'all'
}
onClick=
{
()
=>
copyData
(
apiKey
)
}
>
{
apiKey
}
<
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
>
</
Box
>
</
Modal
>
);
};
export
default
OpenApi
;
export
default
APIKeyModal
;
client/src/components/Icon/icons/apikey.svg
0 → 100644
View file @
61447c60
<?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>
\ No newline at end of file
client/src/components/Icon/icons/develop.svg
deleted
100644 → 0
View file @
df2fda61
<?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>
\ No newline at end of file
client/src/components/Icon/index.tsx
View file @
61447c60
...
...
@@ -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
;
...
...
client/src/components/Iconfont/index.tsx
deleted
100644 → 0
View file @
df2fda61
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
;
client/src/components/Layout/navbar.tsx
View file @
61447c60
...
...
@@ -45,12 +45,6 @@ const Navbar = ({ unread }: { unread: number }) => {
activeLink
:
[
'/model/share'
]
},
{
label
:
'开发'
,
icon
:
'develop'
,
link
:
'/openapi'
,
activeLink
:
[
'/openapi'
]
},
{
label
:
'账号'
,
icon
:
'user'
,
link
:
'/number'
,
...
...
client/src/components/Select/index.tsx
0 → 100644
View file @
61447c60
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
:
'0
px
0
px
4
px
#
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
;
client/src/components/Slider/index.tsx
View file @
61447c60
...
...
@@ -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
:
'
10
px'
,
width
:
'
10
px'
,
height
:
'
10
px'
,
borderRadius
:
'
6
px'
,
width
:
'
6
px'
,
height
:
'
6
px'
,
backgroundColor
:
'#ffffff'
,
border
:
'2px solid #D7DBE2'
,
position
:
'absolute'
,
...
...
@@ -44,37 +46,62 @@ const MySlider = ({
},
[
activeVal
,
markList
]);
return
(
<
Slider
max=
{
max
}
min=
{
min
}
step=
{
step
}
size=
{
'lg'
}
value=
{
value
}
onChange=
{
setVal
}
>
{
markList
.
map
((
item
,
i
)
=>
(
<
Slider
max=
{
max
}
min=
{
min
}
step=
{
step
}
size=
{
'lg'
}
value=
{
activeVal
}
width=
{
width
}
onChange=
{
setVal
}
>
{
markList
?.
map
((
item
,
i
)
=>
(
<
SliderMark
key=
{
item
.
value
}
value=
{
i
}
mt=
{
3
}
value=
{
item
.
value
}
fontSize=
{
'sm'
}
mt=
{
3
}
whiteSpace=
{
'nowrap'
}
transform=
{
'translateX(-50%)'
}
{
...
(
activeVal
===
item
.
value
?
{
color
:
'
myBlue
.
500',
fontWeight
:
'
bold
'
}
:
{})
}
color=
{
'myGray.600'
}
>
<
Box
px=
{
3
}
cursor=
{
'pointer'
}
>
{
item
.
label
}
</
Box
>
</
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
bg=
{
'#EAEDF3'
}
overflow=
{
'visible'
}
h=
{
'4px'
}
_before=
{
{
...
startEndPointStyle
,
left
:
'-
5
px'
left
:
'-
3
px'
}
}
_after=
{
{
...
startEndPointStyle
,
right
:
'-
5
px'
right
:
'-
3
px'
}
}
>
<
SliderFilledTrack
/>
<
SliderFilledTrack
bg=
{
'myBlue.600'
}
/>
</
SliderTrack
>
<
SliderThumb
border=
{
'
2.5px solid'
}
borderColor=
{
'myBlue.5
00'
}
></
SliderThumb
>
<
SliderThumb
border=
{
'
3px solid'
}
borderColor=
{
'myBlue.6
00'
}
></
SliderThumb
>
</
Slider
>
);
};
...
...
client/src/components/Tabs/index.tsx
View file @
61447c60
...
...
@@ -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
]);
...
...
client/src/constants/model.ts
View file @
61447c60
...
...
@@ -31,7 +31,7 @@ export const ChatModelMap = {
[
OpenAiChatEnum
.
GPT35
]:
{
chatModel
:
OpenAiChatEnum
.
GPT35
,
name
:
'Gpt35-4k'
,
contextMaxToken
:
40
96
,
contextMaxToken
:
40
00
,
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
}
};
...
...
client/src/constants/theme.ts
View file @
61447c60
...
...
@@ -15,9 +15,7 @@ const { definePartsStyle: selectPart, defineMultiStyleConfig: selectMultiStyle }
// modal 弹窗
const
ModalTheme
=
defineMultiStyleConfig
({
baseStyle
:
definePartsStyle
({
dialog
:
{
width
:
'90%'
}
dialog
:
{}
})
});
...
...
client/src/pages/_app.tsx
View file @
61447c60
...
...
@@ -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
;
client/src/pages/api/chat/chat.ts
View file @
61447c60
...
...
@@ -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
,
guid
ePrompt
}
=
await
appKbSearch
({
const
{
rawSearch
,
userSystemPrompt
,
quot
ePrompt
}
=
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
...
...
client/src/pages/api/chat/init.ts
View file @
61447c60
...
...
@@ -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
,
...
...
client/src/pages/api/chat/shareChat/chat.ts
View file @
61447c60
...
...
@@ -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
:
p
rompts
,
messages
:
completeP
rompts
,
stream
:
true
,
res
,
chatId
:
historyId
...
...
client/src/pages/api/chat/shareChat/init.ts
View file @
61447c60
...
...
@@ -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
}
...
...
client/src/pages/api/model/create.ts
View file @
61447c60
...
...
@@ -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
<
any
>
)
{
...
...
@@ -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
,
{
...
...
client/src/pages/api/model/share/getModels.ts
View file @
61447c60
...
...
@@ -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
:
{
...
...
client/src/pages/api/model/update.ts
View file @
61447c60
...
...
@@ -9,10 +9,10 @@ import { authModel } from '@/service/utils/auth';
/* 获取我的模型 */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
<
any
>
)
{
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
})
}
);
...
...
client/src/pages/api/openapi/chat/chat.ts
View file @
61447c60
...
...
@@ -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
];
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
});
let
systemPrompts
:
{
obj
:
ChatRoleEnum
;
value
:
string
;
}[]
=
[];
// 使用了知识库搜索
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
:
p
rompts
,
messages
:
completeP
rompts
,
stream
:
isStream
,
res
,
chatId
:
conversationId
...
...
client/src/pages/api/openapi/getKeys.ts
View file @
61447c60
...
...
@@ -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
};
...
...
client/src/pages/api/openapi/kb/appKbSearch.ts
View file @
61447c60
...
...
@@ -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
<
any
>
)
{
...
...
@@ -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
<
Response
>
(
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
<
Response
>
{
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
guide
Prompt
=
model
.
chat
.
systemPrompt
// user system prompt
const
userSystem
Prompt
=
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
:
[
guide
Prompt
]
messages
:
[
userSystem
Prompt
]
});
// 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
:
`
${
i
tem
.
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
}
};
}
client/src/pages/api/openapi/postKey.ts
View file @
61447c60
...
...
@@ -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
,
...
...
client/src/pages/model/components/ModelList.tsx
View file @
61447c60
...
...
@@ -83,7 +83,7 @@ const ModelList = ({ modelId }: { modelId: string }) => {
<
Flex
flex=
{
1
}
mr=
{
2
}
position=
{
'relative'
}
alignItems=
{
'center'
}
>
<
Input
h=
{
'32px'
}
placeholder=
"搜索 AI 应用"
placeholder=
"
根据名字和介绍
搜索 AI 应用"
value=
{
searchText
}
onChange=
{
(
e
)
=>
setSearchText
(
e
.
target
.
value
)
}
/>
...
...
@@ -111,7 +111,7 @@ const ModelList = ({ modelId }: { modelId: string }) => {
/>
</
Tooltip
>
</
Flex
>
<
Flex
mb=
{
3
}
userSelect=
{
'none'
}
>
<
Flex
userSelect=
{
'none'
}
>
<
Box
flex=
{
1
}
></
Box
>
<
Tabs
w=
{
'130px'
}
...
...
@@ -129,7 +129,7 @@ const ModelList = ({ modelId }: { modelId: string }) => {
<
Flex
key=
{
item
.
_id
}
position=
{
'relative'
}
alignItems=
{
[
'flex-start'
,
'center'
]
}
alignItems=
{
'center'
}
p=
{
3
}
mb=
{
[
2
,
0
]
}
cursor=
{
'pointer'
}
...
...
@@ -154,9 +154,6 @@ const ModelList = ({ modelId }: { modelId: string }) => {
<
Box
className=
"textEllipsis"
color=
{
'myGray.1000'
}
>
{
item
.
name
}
</
Box
>
<
Box
className=
"textEllipsis"
color=
{
'myGray.400'
}
fontSize=
{
'sm'
}
>
{
item
.
systemPrompt
||
'这个 应用 没有设置提示词~'
}
</
Box
>
</
Box
>
</
Flex
>
))
}
...
...
client/src/pages/model/components/detail/components/API.tsx
0 → 100644
View file @
61447c60
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
;
client/src/pages/model/components/detail/components/Kb.tsx
0 → 100644
View file @
61447c60
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
;
client/src/pages/model/components/detail/components/Settings.tsx
0 → 100644
View file @
61447c60
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
;
client/src/pages/model/components/detail/components/
ModelEditForm
.tsx
→
client/src/pages/model/components/detail/components/
Share
.tsx
View file @
61447c60
import
React
,
{
use
State
,
useCallback
}
from
'react'
;
import
React
,
{
use
Callback
,
useState
}
from
'react'
;
import
{
Box
,
Card
,
Flex
,
FormControl
,
Input
,
Textarea
,
Slider
,
SliderTrack
,
SliderFilledTrack
,
SliderThumb
,
SliderMark
,
Box
,
Tooltip
,
Button
,
Select
,
Switch
,
TableContainer
,
Table
,
Thead
,
Tr
,
Th
,
Td
,
Tbody
,
useDisclosure
,
Modal
,
ModalOverlay
,
ModalContent
,
...
...
@@ -22,59 +19,34 @@ import {
ModalFooter
,
ModalBody
,
ModalCloseButton
,
useDisclosure
,
Table
,
Thead
,
Tbody
,
Tr
,
Th
,
Td
,
TableContainer
,
Checkbox
FormControl
,
Slider
,
SliderTrack
,
SliderFilledTrack
,
SliderThumb
,
SliderMark
,
Input
}
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
MyIcon
from
'@/components/Icon'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
compressImg
}
from
'@/utils/file
'
;
import
{
useLoading
}
from
'@/hooks/useLoading
'
;
import
{
useQuery
}
from
'@tanstack/react-query'
;
import
{
getShareChatList
,
createShareChat
,
delShareChatById
}
from
'@/api/chat'
;
import
{
useRouter
}
from
'next/router'
;
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'
;
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
Share
=
({
modelId
}:
{
modelId
:
string
})
=>
{
const
{
toast
}
=
useToast
();
const
{
setLoading
}
=
useGlobalStore
();
const
{
loadKbList
}
=
useUserStore
();
const
{
openConfirm
,
ConfirmChild
}
=
useConfirm
({
content
:
'确认删除该应用?'
});
const
{
Loading
,
setIsLoading
}
=
useLoading
();
const
{
copyData
}
=
useCopyData
();
const
{
register
,
setValue
,
getValues
}
=
formHooks
;
const
{
isOpen
:
isOpenCreateShareChat
,
onOpen
:
onOpenCreateShareChat
,
onClose
:
onCloseCreateShareChat
}
=
useDisclosure
();
const
{
register
:
registerShareChat
,
getValues
:
getShareChatValues
,
...
...
@@ -84,54 +56,19 @@ const ModelEditForm = ({
}
=
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
[
refresh
,
setRefresh
]
=
useState
(
false
);
const
{
data
:
shareChatList
=
[],
refetch
:
refetchShareChatList
}
=
useQuery
(
[
'initShareChatList'
,
modelId
],
()
=>
getShareChatList
(
modelId
)
);
const
{
isFetching
,
data
:
shareChatList
=
[],
refetch
:
refetchShareChatList
}
=
useQuery
([
'initShareChatList'
,
modelId
],
()
=>
getShareChatList
(
modelId
));
const
onclickCreateShareChat
=
useCallback
(
async
(
e
:
ShareChatEditType
)
=>
{
try
{
setLoading
(
true
);
set
Is
Loading
(
true
);
const
id
=
await
createShareChat
({
...
e
,
modelId
...
...
@@ -139,8 +76,7 @@ const ModelEditForm = ({
onCloseCreateShareChat
();
refetchShareChatList
();
const
url
=
`你可以与
${
getValues
(
'name'
)}
进行对话。
对话地址为:
${
location
.
origin
}
/chat/share?shareId=
${
id
}
const
url
=
`对话地址为:
${
location
.
origin
}
/chat/share?shareId=
${
id
}
${
e
.
password
?
`密码为:
${
e
.
password
}
`
:
''
}
`
;
copyData
(
url
,
'已复制分享地址'
);
...
...
@@ -152,16 +88,15 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
});
console
.
log
(
err
);
}
setLoading
(
false
);
set
Is
Loading
(
false
);
},
[
copyData
,
getValues
,
modelId
,
onCloseCreateShareChat
,
refetchShareChatList
,
resetShareChat
,
setLoading
,
set
Is
Loading
,
toast
]
);
...
...
@@ -172,326 +107,94 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
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);
<
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组'
}
})}
>
{
chatModelList
.
map
((
item
)
=>
(
<
option
key=
{
item
.
chatModel
}
value=
{
item
.
chatModel
}
>
{
item
.
name
}
</
option
>
:
{}
)}
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
>
))
}
</
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(包括上下文和回答)
</
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
>
<
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
/>
...
...
@@ -520,7 +223,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
</
Box
>
<
Input
placeholder=
{
'不设置密码,可直接访问'
}
{
...
registerShareChat
('
password
')}
/>
</
Flex
>
<
Box
fontSize=
{
'xs'
}
ml=
{
'60px'
}
>
<
Box
fontSize=
{
'xs'
}
ml=
{
'60px'
}
color=
{
'myGray.600'
}
>
密码不会再次展示,请记住你的密码
</
Box
>
</
FormControl
>
...
...
@@ -535,7 +238,6 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
max=
{
20
}
step=
{
1
}
value=
{
getShareChatValues
(
'maxContext'
)
}
isDisabled=
{
!
isOwner
}
onChange=
{
(
e
)
=>
{
setShareChatValues
(
'maxContext'
,
e
);
setRefresh
(
!
refresh
);
...
...
@@ -571,49 +273,9 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
</
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
/>
</>
<
Loading
loading=
{
isFetching
}
fixed=
{
false
}
/>
</
Box
>
);
};
export
default
ModelEditForm
;
export
default
Share
;
client/src/pages/model/components/detail/index.tsx
View file @
61447c60
import
React
,
{
use
Callback
,
useState
,
useMemo
,
useEffect
}
from
'react'
;
import
React
,
{
use
State
,
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
:
()
=>
<
Loading
fixed=
{
false
}
/>,
ssr
:
false
});
const
ModelDetail
=
({
modelId
,
isPc
}:
{
modelId
:
string
;
isPc
:
boolean
})
=>
{
const
{
toast
}
=
useToast
();
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
]);
import
Settings
from
'./components/Settings'
;
/* 点前往聊天预览页 */
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
});
const
Kb
=
dynamic
(()
=>
import
(
'./components/Kb'
),
{
ssr
:
true
});
const
Share
=
dynamic
(()
=>
import
(
'./components/Share'
),
{
ssr
:
true
});
const
API
=
dynamic
(()
=>
import
(
'./components/API'
),
{
ssr
:
true
});
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
]);
enum
TabEnum
{
'settings'
=
'settings'
,
'kb'
=
'kb'
,
'share'
=
'share'
,
'API'
=
'API'
}
const
saveUpdateModel
=
useCallback
(
()
=>
formHooks
.
handleSubmit
(
saveSubmitSuccess
,
saveSubmitError
)(),
[
formHooks
,
saveSubmitError
,
saveSubmitSuccess
]
);
const
ModelDetail
=
({
modelId
}:
{
modelId
:
string
})
=>
{
const
router
=
useRouter
();
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
(
<
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
}
>
{
isPc
?
(
<
Flex
alignItems=
{
'center'
}
>
<
Box
fontSize=
{
'xl'
}
fontWeight=
{
'bold'
}
>
{
modelDetail
.
name
}
</
Box
>
<
Box
flex=
{
1
}
/>
<
Button
variant=
{
'base'
}
onClick=
{
handlePreviewChat
}
>
开始对话
</
Button
>
{
isOwner
&&
(
<
Button
isLoading=
{
btnLoading
}
ml=
{
4
}
onClick=
{
async
()
=>
{
try
{
await
saveUpdateModel
();
toast
({
title
:
'更新成功'
,
status
:
'success'
});
}
catch
(
error
)
{
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
}
<
Box
textAlign=
{
[
'center'
,
'left'
]
}
px=
{
5
}
mb=
{
4
}
>
<
Box
className=
"textlg"
display=
{
[
'block'
,
'none'
]
}
fontSize=
{
'3xl'
}
fontWeight=
{
'bold'
}
>
{
modelDetail
.
name
}
</
Box
>
<
Tabs
mx=
{
[
'auto'
,
'0'
]
}
mt=
{
2
}
w=
{
[
'300px'
,
'360px'
]
}
list=
{
[
{
label
:
'配置'
,
id
:
TabEnum
.
settings
},
{
label
:
'知识库'
,
id
:
TabEnum
.
kb
},
{
label
:
'分享'
,
id
:
TabEnum
.
share
},
{
label
:
'API'
,
id
:
TabEnum
.
API
},
{
label
:
'立即对话'
,
id
:
'startChat'
}
]
}
size=
{
isPc
?
'md'
:
'sm'
}
activeId=
{
currentTab
}
onChange=
{
(
e
:
any
)
=>
{
if
(
e
===
'startChat'
)
{
router
.
push
(
`/chat?modelId=${modelId}`
);
}
else
{
setCurrentTab
(
e
);
}
}
}
/>
</
Grid
>
<
Loading
loading=
{
isLoading
}
fixed=
{
false
}
/>
</
Box
>
</
Box
>
<
Box
flex=
{
1
}
>
{
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
>
);
};
...
...
client/src/pages/model/index.tsx
View file @
61447c60
...
...
@@ -34,7 +34,7 @@ const Model = ({ modelId }: { modelId: string }) => {
</
SideBar
>
)
}
<
Box
flex=
{
1
}
h=
{
'100%'
}
position=
{
'relative'
}
>
{
modelId
&&
<
ModelDetail
modelId=
{
modelId
}
isPc=
{
isPc
}
/>
}
{
modelId
&&
<
ModelDetail
modelId=
{
modelId
}
/>
}
</
Box
>
</
Flex
>
);
...
...
client/src/pages/model/share/components/list.tsx
View file @
61447c60
...
...
@@ -44,7 +44,7 @@ const ShareModelList = ({
{
model
.
name
}
</
Box
>
</
Flex
>
<
Tooltip
label=
{
model
.
share
.
intro
}
>
<
Tooltip
label=
{
model
.
intro
}
>
<
Box
className=
{
styles
.
intro
}
flex=
{
1
}
...
...
@@ -53,7 +53,7 @@ const ShareModelList = ({
wordBreak=
{
'break-all'
}
color=
{
'blackAlpha.600'
}
>
{
model
.
share
.
intro
||
'这个 应用
还没有介绍~'
}
{
model
.
intro
||
'这个应用
还没有介绍~'
}
</
Box
>
</
Tooltip
>
...
...
client/src/pages/model/share/index.module.scss
View file @
61447c60
...
...
@@ -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
;
}
client/src/pages/model/share/index.tsx
View file @
61447c60
...
...
@@ -45,7 +45,7 @@ const modelList = () => {
return
(
<
Box
px=
{
[
5
,
10
]
}
py=
{
[
4
,
6
]
}
position=
{
'relative'
}
minH=
{
'109vh'
}
>
<
Flex
alignItems=
{
'center'
}
mb=
{
2
}
>
<
Box
className=
{
styles
.
textlg
}
fontWeight=
{
'bold'
}
fontSize=
{
'3xl'
}
>
<
Box
className=
{
'textlg'
}
fontWeight=
{
'bold'
}
fontSize=
{
'3xl'
}
>
AI 应用市场
</
Box
>
{
/* <Box mt={[2, 0]} textAlign={'right'}>
...
...
client/src/pages/tools/index.tsx
View file @
61447c60
...
...
@@ -16,11 +16,6 @@ const list = [
link
:
'/model/share'
},
{
icon
:
'develop'
,
label
:
'开发'
,
link
:
'/openapi'
},
{
icon
:
'git'
,
label
:
'Git项目地址'
,
link
:
'https://github.com/c121914yu/FastGPT'
...
...
client/src/service/models/model.ts
View file @
61447c60
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
:
{
// 系统提示词
...
...
client/src/styles/reset.scss
View file @
61447c60
...
...
@@ -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
{
...
...
client/src/types/model.d.ts
View file @
61447c60
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
;
...
...
client/src/types/mongoSchema.d.ts
View file @
61447c60
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
;
};
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment