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
b2e2f60e
authored
Jul 11, 2023
by
archer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chatbox ui
parent
eb768d9c
Show whitespace changes
Inline
Side-by-side
Showing
45 changed files
with
822 additions
and
2516 deletions
+822
-2516
client/src/api/app.ts
+6
-7
client/src/api/chat.ts
+7
-7
client/src/api/fetch.ts
+5
-5
client/src/api/request.ts
+4
-4
client/src/api/response/chat.d.ts
+8
-7
client/src/components/ChatBox/index.tsx
+22
-14
client/src/components/Layout/index.tsx
+2
-1
client/src/components/Layout/navbar.tsx
+3
-3
client/src/components/Layout/navbarPhone.tsx
+3
-3
client/src/components/SideBar/index.tsx
+1
-1
client/src/components/Tag/index.tsx
+1
-1
client/src/pages/api/app/del.ts
+7
-7
client/src/pages/api/app/detail.tsx
+3
-3
client/src/pages/api/app/myApps.ts
+6
-31
client/src/pages/api/app/share/collection.ts
+6
-6
client/src/pages/api/chat/chatTest.ts
+6
-5
client/src/pages/api/chat/delChatRecordByContentId.ts
+5
-7
client/src/pages/api/chat/history/getHistory.ts
+7
-6
client/src/pages/api/chat/history/updateChatHistory.ts
+3
-3
client/src/pages/api/chat/init.ts
+32
-22
client/src/pages/api/chat/saveChat.ts
+23
-19
client/src/pages/api/openapi/chat/chat.ts
+0
-189
client/src/pages/api/openapi/v1/chat/completions.ts
+295
-224
client/src/pages/api/openapi/v1/chat/completions2.ts
+0
-405
client/src/pages/api/openapi/v1/chat/getHistory.ts
+6
-6
client/src/pages/app/detail/components/edit/components/ChatTest.tsx
+1
-3
client/src/pages/appStore/components/list.tsx
+1
-1
client/src/pages/appStore/index.tsx
+3
-6
client/src/pages/chat/components/ChatHistorySlider.tsx
+32
-25
client/src/pages/chat/components/History.tsx
+0
-304
client/src/pages/chat/components/ModelList.tsx
+0
-49
client/src/pages/chat/components/PhoneSliderBar.tsx
+0
-212
client/src/pages/chat/components/SliderApps.tsx
+77
-0
client/src/pages/chat/index.tsx
+183
-771
client/src/pages/chat/share.tsx
+7
-10
client/src/pages/login/index.tsx
+3
-5
client/src/service/api/request.ts
+7
-3
client/src/service/models/chat.ts
+5
-15
client/src/service/models/collection.ts
+1
-1
client/src/service/models/installApp.ts
+0
-18
client/src/service/utils/auth.ts
+0
-59
client/src/store/chat.ts
+37
-34
client/src/store/user.ts
+1
-2
client/src/types/chat.d.ts
+0
-4
client/src/types/mongoSchema.d.ts
+3
-8
No files found.
client/src/api/app.ts
View file @
b2e2f60e
import
{
GET
,
POST
,
DELETE
,
PUT
}
from
'./request'
;
import
{
GET
,
POST
,
DELETE
,
PUT
}
from
'./request'
;
import
type
{
AppSchema
}
from
'@/types/mongoSchema'
;
import
type
{
AppSchema
}
from
'@/types/mongoSchema'
;
import
type
{
App
Module
ItemType
,
AppUpdateParams
}
from
'@/types/app'
;
import
type
{
App
List
ItemType
,
AppUpdateParams
}
from
'@/types/app'
;
import
{
RequestPaging
}
from
'../types/index'
;
import
{
RequestPaging
}
from
'../types/index'
;
import
type
{
AppListResponse
}
from
'./response/app'
;
import
type
{
Props
as
CreateAppProps
}
from
'@/pages/api/app/create'
;
import
type
{
Props
as
CreateAppProps
}
from
'@/pages/api/app/create'
;
/**
/**
* 获取模型列表
* 获取模型列表
*/
*/
export
const
getMyModels
=
()
=>
GET
<
AppList
Response
>
(
'/app/list
'
);
export
const
getMyModels
=
()
=>
GET
<
AppList
ItemType
[]
>
(
'/app/myApps
'
);
/**
/**
* 创建一个模型
* 创建一个模型
...
@@ -18,12 +17,12 @@ export const postCreateApp = (data: CreateAppProps) => POST<string>('/app/create
...
@@ -18,12 +17,12 @@ export const postCreateApp = (data: CreateAppProps) => POST<string>('/app/create
/**
/**
* 根据 ID 删除模型
* 根据 ID 删除模型
*/
*/
export
const
delModelById
=
(
id
:
string
)
=>
DELETE
(
`/app/del?
model
Id=
${
id
}
`
);
export
const
delModelById
=
(
id
:
string
)
=>
DELETE
(
`/app/del?
app
Id=
${
id
}
`
);
/**
/**
* 根据 ID 获取模型
* 根据 ID 获取模型
*/
*/
export
const
getModelById
=
(
id
:
string
)
=>
GET
<
AppSchema
>
(
`/app/detail?
model
Id=
${
id
}
`
);
export
const
getModelById
=
(
id
:
string
)
=>
GET
<
AppSchema
>
(
`/app/detail?
app
Id=
${
id
}
`
);
/**
/**
* 根据 ID 更新模型
* 根据 ID 更新模型
...
@@ -41,5 +40,5 @@ export const getShareModelList = (data: { searchText?: string } & RequestPaging)
...
@@ -41,5 +40,5 @@ export const getShareModelList = (data: { searchText?: string } & RequestPaging)
/**
/**
* 收藏/取消收藏模型
* 收藏/取消收藏模型
*/
*/
export
const
triggerModelCollection
=
(
model
Id
:
string
)
=>
export
const
triggerModelCollection
=
(
app
Id
:
string
)
=>
POST
<
number
>
(
`/app/share/collection?
modelId=
${
model
Id
}
`
);
POST
<
number
>
(
`/app/share/collection?
appId=
${
app
Id
}
`
);
client/src/api/chat.ts
View file @
b2e2f60e
import
{
GET
,
POST
,
DELETE
,
PUT
}
from
'./request'
;
import
{
GET
,
POST
,
DELETE
,
PUT
}
from
'./request'
;
import
type
{
HistoryItemType
}
from
'@/types/chat'
;
import
type
{
Chat
HistoryItemType
}
from
'@/types/chat'
;
import
type
{
InitChatResponse
,
InitShareChatResponse
}
from
'./response/chat'
;
import
type
{
InitChatResponse
,
InitShareChatResponse
}
from
'./response/chat'
;
import
{
RequestPaging
}
from
'../types/index'
;
import
{
RequestPaging
}
from
'../types/index'
;
import
type
{
ShareChatSchema
}
from
'@/types/mongoSchema'
;
import
type
{
ShareChatSchema
}
from
'@/types/mongoSchema'
;
...
@@ -11,14 +11,14 @@ import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updat
...
@@ -11,14 +11,14 @@ import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updat
/**
/**
* 获取初始化聊天内容
* 获取初始化聊天内容
*/
*/
export
const
getInitChatSiteInfo
=
(
modelId
:
''
|
string
,
chatId
:
''
|
string
)
=>
export
const
getInitChatSiteInfo
=
(
data
:
{
appId
:
string
;
historyId
?:
string
}
)
=>
GET
<
InitChatResponse
>
(
`/chat/init
?modelId=
${
modelId
}
&chatId=
${
chatId
}
`
);
GET
<
InitChatResponse
>
(
`/chat/init
`
,
data
);
/**
/**
* 获取历史记录
* 获取历史记录
*/
*/
export
const
getChatHistory
=
(
data
:
RequestPaging
)
=>
export
const
getChatHistory
=
(
data
:
RequestPaging
&
{
appId
?:
string
}
)
=>
POST
<
HistoryItemType
[]
>
(
'/chat/history/getHistory'
,
data
);
POST
<
Chat
HistoryItemType
[]
>
(
'/chat/history/getHistory'
,
data
);
/**
/**
* 删除一条历史记录
* 删除一条历史记录
...
@@ -44,8 +44,8 @@ export const updateHistoryQuote = (params: {
...
@@ -44,8 +44,8 @@ export const updateHistoryQuote = (params: {
/**
/**
* 删除一句对话
* 删除一句对话
*/
*/
export
const
delChatRecordByIndex
=
(
chatId
:
string
,
contentId
:
string
)
=>
export
const
delChatRecordByIndex
=
(
data
:
{
historyId
:
string
;
contentId
:
string
}
)
=>
DELETE
(
`/chat/delChatRecordByContentId
?chatId=
${
chatId
}
&contentId=
${
contentId
}
`
);
DELETE
(
`/chat/delChatRecordByContentId
`
,
data
);
/**
/**
* 修改历史记录: 标题/置顶
* 修改历史记录: 标题/置顶
...
...
client/src/api/fetch.ts
View file @
b2e2f60e
...
@@ -9,12 +9,12 @@ interface StreamFetchProps {
...
@@ -9,12 +9,12 @@ interface StreamFetchProps {
abortSignal
:
AbortController
;
abortSignal
:
AbortController
;
}
}
export
const
streamFetch
=
({
export
const
streamFetch
=
({
url
=
'/api/openapi/v1/chat/completions
2
'
,
url
=
'/api/openapi/v1/chat/completions'
,
data
,
data
,
onMessage
,
onMessage
,
abortSignal
abortSignal
}:
StreamFetchProps
)
=>
}:
StreamFetchProps
)
=>
new
Promise
<
{
responseText
:
string
;
errMsg
:
string
;
new
Chat
Id
:
string
|
null
}
>
(
new
Promise
<
{
responseText
:
string
;
errMsg
:
string
;
new
History
Id
:
string
|
null
}
>
(
async
(
resolve
,
reject
)
=>
{
async
(
resolve
,
reject
)
=>
{
try
{
try
{
const
response
=
await
window
.
fetch
(
url
,
{
const
response
=
await
window
.
fetch
(
url
,
{
...
@@ -43,7 +43,7 @@ export const streamFetch = ({
...
@@ -43,7 +43,7 @@ export const streamFetch = ({
// response data
// response data
let
responseText
=
''
;
let
responseText
=
''
;
let
errMsg
=
''
;
let
errMsg
=
''
;
const
new
ChatId
=
response
.
headers
.
get
(
'newChat
Id'
);
const
new
HistoryId
=
response
.
headers
.
get
(
'newHistory
Id'
);
const
read
=
async
()
=>
{
const
read
=
async
()
=>
{
try
{
try
{
...
@@ -53,7 +53,7 @@ export const streamFetch = ({
...
@@ -53,7 +53,7 @@ export const streamFetch = ({
return
resolve
({
return
resolve
({
responseText
,
responseText
,
errMsg
,
errMsg
,
new
Chat
Id
new
History
Id
});
});
}
else
{
}
else
{
return
reject
(
'响应过程出现异常~'
);
return
reject
(
'响应过程出现异常~'
);
...
@@ -85,7 +85,7 @@ export const streamFetch = ({
...
@@ -85,7 +85,7 @@ export const streamFetch = ({
return
resolve
({
return
resolve
({
responseText
,
responseText
,
errMsg
,
errMsg
,
new
Chat
Id
new
History
Id
});
});
}
}
reject
(
getErrText
(
err
,
'请求异常'
));
reject
(
getErrText
(
err
,
'请求异常'
));
...
...
client/src/api/request.ts
View file @
b2e2f60e
...
@@ -92,8 +92,8 @@ function request(url: string, data: any, config: ConfigType, method: Method): an
...
@@ -92,8 +92,8 @@ function request(url: string, data: any, config: ConfigType, method: Method): an
baseURL
:
'/api'
,
baseURL
:
'/api'
,
url
,
url
,
method
,
method
,
data
:
method
===
'GET'
?
null
:
data
,
data
:
[
'POST'
,
'PUT'
].
includes
(
method
)
?
data
:
null
,
params
:
method
===
'GET'
?
data
:
null
,
// get请求不携带data,params放在url上
params
:
!
[
'POST'
,
'PUT'
].
includes
(
method
)
?
data
:
null
,
...
config
// 用户自定义配置,可以覆盖前面的配置
...
config
// 用户自定义配置,可以覆盖前面的配置
})
})
.
then
((
res
)
=>
checkRes
(
res
.
data
))
.
then
((
res
)
=>
checkRes
(
res
.
data
))
...
@@ -119,6 +119,6 @@ export function PUT<T>(url: string, data = {}, config: ConfigType = {}): Promise
...
@@ -119,6 +119,6 @@ export function PUT<T>(url: string, data = {}, config: ConfigType = {}): Promise
return
request
(
url
,
data
,
config
,
'PUT'
);
return
request
(
url
,
data
,
config
,
'PUT'
);
}
}
export
function
DELETE
<
T
>
(
url
:
string
,
config
:
ConfigType
=
{}):
Promise
<
T
>
{
export
function
DELETE
<
T
>
(
url
:
string
,
data
=
{},
config
:
ConfigType
=
{}):
Promise
<
T
>
{
return
request
(
url
,
{}
,
config
,
'DELETE'
);
return
request
(
url
,
data
,
config
,
'DELETE'
);
}
}
client/src/api/response/chat.d.ts
View file @
b2e2f60e
import
type
{
ChatPopulate
,
AppSchema
}
from
'@/types/mongoSchema'
;
import
type
{
AppSchema
}
from
'@/types/mongoSchema'
;
import
type
{
ChatItemType
}
from
'@/types/chat'
;
import
type
{
ChatItemType
}
from
'@/types/chat'
;
import
{
VariableItemType
}
from
'@/types/app'
;
import
{
VariableItemType
}
from
'@/types/app'
;
export
interface
InitChatResponse
{
export
interface
InitChatResponse
{
chat
Id
:
string
;
history
Id
:
string
;
model
Id
:
string
;
app
Id
:
string
;
systemPrompt
?:
string
;
app
:
{
limitPrompt
?:
string
;
variableModules
?:
VariableItemType
[]
;
model
:
{
welcomeText
?:
string
;
name
:
string
;
name
:
string
;
avatar
:
string
;
avatar
:
string
;
intro
:
string
;
intro
:
string
;
canUse
:
boolean
;
canUse
:
boolean
;
};
};
chatModel
:
AppSchema
[
'chat'
][
'chatModel'
];
// 对话模型名
title
:
string
;
variables
:
Record
<
string
,
any
>
;
history
:
ChatItemType
[];
history
:
ChatItemType
[];
}
}
...
...
client/src/components/ChatBox/index.tsx
View file @
b2e2f60e
...
@@ -38,8 +38,10 @@ export type StartChatFnProps = {
...
@@ -38,8 +38,10 @@ export type StartChatFnProps = {
};
};
export
type
ComponentRef
=
{
export
type
ComponentRef
=
{
getChatHistory
:
()
=>
ChatSiteItemType
[];
resetVariables
:
(
data
?:
Record
<
string
,
any
>
)
=>
void
;
resetVariables
:
(
data
?:
Record
<
string
,
any
>
)
=>
void
;
resetHistory
:
(
history
:
ChatSiteItemType
[])
=>
void
;
resetHistory
:
(
history
:
ChatSiteItemType
[])
=>
void
;
scrollToBottom
:
(
behavior
?:
'smooth'
|
'auto'
)
=>
void
;
};
};
const
VariableLabel
=
({
const
VariableLabel
=
({
...
@@ -73,7 +75,7 @@ const ChatBox = (
...
@@ -73,7 +75,7 @@ const ChatBox = (
welcomeText
?:
string
;
welcomeText
?:
string
;
onUpdateVariable
?:
(
e
:
Record
<
string
,
any
>
)
=>
void
;
onUpdateVariable
?:
(
e
:
Record
<
string
,
any
>
)
=>
void
;
onStartChat
:
(
e
:
StartChatFnProps
)
=>
Promise
<
{
responseText
:
string
}
>
;
onStartChat
:
(
e
:
StartChatFnProps
)
=>
Promise
<
{
responseText
:
string
}
>
;
onDelMessage
?:
(
e
:
{
i
d
?:
string
;
index
:
number
})
=>
void
;
onDelMessage
?:
(
e
:
{
contentI
d
?:
string
;
index
:
number
})
=>
void
;
},
},
ref
:
ForwardedRef
<
ComponentRef
>
ref
:
ForwardedRef
<
ComponentRef
>
)
=>
{
)
=>
{
...
@@ -279,6 +281,7 @@ const ChatBox = (
...
@@ -279,6 +281,7 @@ const ChatBox = (
);
);
useImperativeHandle
(
ref
,
()
=>
({
useImperativeHandle
(
ref
,
()
=>
({
getChatHistory
:
()
=>
chatHistory
,
resetVariables
(
e
)
{
resetVariables
(
e
)
{
const
defaultVal
:
Record
<
string
,
any
>
=
{};
const
defaultVal
:
Record
<
string
,
any
>
=
{};
variableModules
?.
forEach
((
item
)
=>
{
variableModules
?.
forEach
((
item
)
=>
{
...
@@ -290,7 +293,8 @@ const ChatBox = (
...
@@ -290,7 +293,8 @@ const ChatBox = (
},
},
resetHistory
(
e
)
{
resetHistory
(
e
)
{
setChatHistory
(
e
);
setChatHistory
(
e
);
}
},
scrollToBottom
}));
}));
const
controlIconStyle
=
{
const
controlIconStyle
=
{
...
@@ -305,25 +309,27 @@ const ChatBox = (
...
@@ -305,25 +309,27 @@ const ChatBox = (
};
};
const
controlContainerStyle
=
{
const
controlContainerStyle
=
{
className
:
'control'
,
className
:
'control'
,
display
:
[
'flex'
,
'none'
],
display
:
isChatting
?
'none'
:
[
'flex'
,
'none'
],
color
:
'myGray.400'
,
color
:
'myGray.400'
,
pl
:
1
,
pl
:
1
,
mt
:
2
,
mt
:
2
,
position
:
'absolute'
as
any
,
position
:
'absolute'
as
any
,
zIndex
:
1
zIndex
:
1
,
w
:
'100%'
};
};
return
(
return
(
<
Flex
flexDirection=
{
'column'
}
h=
{
'100%'
}
>
<
Flex
flexDirection=
{
'column'
}
h=
{
'100%'
}
>
<
Box
ref=
{
ChatBoxRef
}
flex=
{
'1 0 0'
}
overflow=
{
'overlay'
}
px=
{
[
2
,
5
]
}
>
<
Box
ref=
{
ChatBoxRef
}
flex=
{
'1 0 0'
}
overflow=
{
'overlay'
}
px=
{
[
2
,
5
,
8
]
}
py=
{
5
}
>
<
Box
maxW=
{
[
'100%'
,
'1000px'
,
'1200px'
]
}
mx=
{
'auto'
}
>
{
/* variable input */
}
{
/* variable input */
}
{
(
variableModules
||
welcomeText
)
&&
(
{
(
variableModules
||
welcomeText
)
&&
(
<
Flex
alignItems=
{
'flex-start'
}
py=
{
2
}
>
<
Flex
alignItems=
{
'flex-start'
}
py=
{
2
}
>
{
/* avatar */
}
{
/* avatar */
}
<
Avatar
<
Avatar
src=
{
appAvatar
}
src=
{
appAvatar
}
w=
{
isLargeWidth
?
'34px'
:
'24px'
}
w=
{
[
'24px'
,
'34px'
]
}
h=
{
isLargeWidth
?
'34px'
:
'24px'
}
h=
{
[
'24px'
,
'34px'
]
}
order=
{
1
}
order=
{
1
}
mr=
{
[
'6px'
,
2
]
}
mr=
{
[
'6px'
,
2
]
}
/>
/>
...
@@ -342,6 +348,7 @@ const ChatBox = (
...
@@ -342,6 +348,7 @@ const ChatBox = (
<
VariableLabel
required=
{
item
.
required
}
>
{
item
.
label
}
</
VariableLabel
>
<
VariableLabel
required=
{
item
.
required
}
>
{
item
.
label
}
</
VariableLabel
>
{
item
.
type
===
VariableInputEnum
.
input
&&
(
{
item
.
type
===
VariableInputEnum
.
input
&&
(
<
Input
<
Input
isDisabled=
{
variableIsFinish
}
{
...
register
(
item
.
key
,
{
{
...
register
(
item
.
key
,
{
required
:
item
.
required
required
:
item
.
required
})}
})}
...
@@ -350,6 +357,7 @@ const ChatBox = (
...
@@ -350,6 +357,7 @@ const ChatBox = (
{
item
.
type
===
VariableInputEnum
.
select
&&
(
{
item
.
type
===
VariableInputEnum
.
select
&&
(
<
MySelect
<
MySelect
width=
{
'100%'
}
width=
{
'100%'
}
isDisabled=
{
variableIsFinish
}
list=
{
(
item
.
enums
||
[]).
map
((
item
)
=>
({
list=
{
(
item
.
enums
||
[]).
map
((
item
)
=>
({
label
:
item
.
value
,
label
:
item
.
value
,
value
:
item
.
value
value
:
item
.
value
...
@@ -391,7 +399,7 @@ const ChatBox = (
...
@@ -391,7 +399,7 @@ const ChatBox = (
<
Flex
<
Flex
key=
{
item
.
_id
}
key=
{
item
.
_id
}
alignItems=
{
'flex-start'
}
alignItems=
{
'flex-start'
}
py=
{
2
}
py=
{
4
}
_hover=
{
{
_hover=
{
{
'& .control'
:
{
'& .control'
:
{
display
:
'flex'
display
:
'flex'
...
@@ -402,8 +410,8 @@ const ChatBox = (
...
@@ -402,8 +410,8 @@ const ChatBox = (
{
/* avatar */
}
{
/* avatar */
}
<
Avatar
<
Avatar
src=
{
item
.
obj
===
'Human'
?
userInfo
?.
avatar
||
HUMAN_ICON
:
appAvatar
}
src=
{
item
.
obj
===
'Human'
?
userInfo
?.
avatar
||
HUMAN_ICON
:
appAvatar
}
w=
{
isLargeWidth
?
'34px'
:
'24px'
}
w=
{
[
'24px'
,
'34px'
]
}
h=
{
isLargeWidth
?
'34px'
:
'24px'
}
h=
{
[
'24px'
,
'34px'
]
}
{
...
(
item
.
obj
===
'
AI
'
{
...
(
item
.
obj
===
'
AI
'
?
{
?
{
order
:
1,
order
:
1,
...
@@ -417,7 +425,7 @@ const ChatBox = (
...
@@ -417,7 +425,7 @@ const ChatBox = (
{
/* message */
}
{
/* message */
}
<
Box
order=
{
2
}
pt=
{
2
}
maxW=
{
`calc(100% - ${isLargeWidth ? '75px' : '58px'})`
}
>
<
Box
order=
{
2
}
pt=
{
2
}
maxW=
{
`calc(100% - ${isLargeWidth ? '75px' : '58px'})`
}
>
{
item
.
obj
===
'AI'
?
(
{
item
.
obj
===
'AI'
?
(
<
Box
w=
{
'100%
'
}
>
<
Box
w=
{
'100%'
}
position=
{
'relative
'
}
>
<
Card
bg=
{
'white'
}
px=
{
4
}
py=
{
3
}
borderRadius=
{
'0 8px 8px 8px'
}
>
<
Card
bg=
{
'white'
}
px=
{
4
}
py=
{
3
}
borderRadius=
{
'0 8px 8px 8px'
}
>
<
Markdown
<
Markdown
source=
{
item
.
value
}
source=
{
item
.
value
}
...
@@ -444,7 +452,7 @@ const ChatBox = (
...
@@ -444,7 +452,7 @@ const ChatBox = (
state
.
filter
((
chat
)
=>
chat
.
_id
!==
item
.
_id
)
state
.
filter
((
chat
)
=>
chat
.
_id
!==
item
.
_id
)
);
);
onDelMessage
({
onDelMessage
({
i
d
:
item
.
_id
,
contentI
d
:
item
.
_id
,
index
index
});
});
}
}
}
}
...
@@ -496,7 +504,7 @@ const ChatBox = (
...
@@ -496,7 +504,7 @@ const ChatBox = (
state
.
filter
((
chat
)
=>
chat
.
_id
!==
item
.
_id
)
state
.
filter
((
chat
)
=>
chat
.
_id
!==
item
.
_id
)
);
);
onDelMessage
({
onDelMessage
({
i
d
:
item
.
_id
,
contentI
d
:
item
.
_id
,
index
index
});
});
}
}
}
}
...
@@ -511,6 +519,7 @@ const ChatBox = (
...
@@ -511,6 +519,7 @@ const ChatBox = (
))
}
))
}
</
Box
>
</
Box
>
</
Box
>
</
Box
>
</
Box
>
{
variableIsFinish
?
(
{
variableIsFinish
?
(
<
Box
m=
{
[
'0 auto'
,
'20px auto'
]
}
w=
{
'100%'
}
maxW=
{
[
'auto'
,
'min(750px, 100%)'
]
}
px=
{
[
0
,
5
]
}
>
<
Box
m=
{
[
'0 auto'
,
'20px auto'
]
}
w=
{
'100%'
}
maxW=
{
[
'auto'
,
'min(750px, 100%)'
]
}
px=
{
[
0
,
5
]
}
>
<
Box
<
Box
...
@@ -531,7 +540,6 @@ const ChatBox = (
...
@@ -531,7 +540,6 @@ const ChatBox = (
_focusVisible=
{
{
_focusVisible=
{
{
border
:
'none'
border
:
'none'
}
}
}
}
isDisabled=
{
isChatting
}
placeholder=
"提问"
placeholder=
"提问"
resize=
{
'none'
}
resize=
{
'none'
}
rows=
{
1
}
rows=
{
1
}
...
...
client/src/components/Layout/index.tsx
View file @
b2e2f60e
...
@@ -15,7 +15,8 @@ const pcUnShowLayoutRoute: Record<string, boolean> = {
...
@@ -15,7 +15,8 @@ const pcUnShowLayoutRoute: Record<string, boolean> = {
'/'
:
true
,
'/'
:
true
,
'/login'
:
true
,
'/login'
:
true
,
'/chat/share'
:
true
,
'/chat/share'
:
true
,
'/app/edit'
:
true
'/app/edit'
:
true
,
'/chat'
:
true
};
};
const
phoneUnShowLayoutRoute
:
Record
<
string
,
boolean
>
=
{
const
phoneUnShowLayoutRoute
:
Record
<
string
,
boolean
>
=
{
'/'
:
true
,
'/'
:
true
,
...
...
client/src/components/Layout/navbar.tsx
View file @
b2e2f60e
...
@@ -17,14 +17,14 @@ export enum NavbarTypeEnum {
...
@@ -17,14 +17,14 @@ export enum NavbarTypeEnum {
const
Navbar
=
({
unread
}:
{
unread
:
number
})
=>
{
const
Navbar
=
({
unread
}:
{
unread
:
number
})
=>
{
const
router
=
useRouter
();
const
router
=
useRouter
();
const
{
userInfo
,
lastModelId
}
=
useUserStore
();
const
{
userInfo
,
lastModelId
}
=
useUserStore
();
const
{
lastChat
Model
Id
,
lastChatId
}
=
useChatStore
();
const
{
lastChat
App
Id
,
lastChatId
}
=
useChatStore
();
const
navbarList
=
useMemo
(
const
navbarList
=
useMemo
(
()
=>
[
()
=>
[
{
{
label
:
'聊天'
,
label
:
'聊天'
,
icon
:
'chatLight'
,
icon
:
'chatLight'
,
activeIcon
:
'chatFill'
,
activeIcon
:
'chatFill'
,
link
:
`/chat?appId=
${
lastChat
Model
Id
}
&chatId=
${
lastChatId
}
`
,
link
:
`/chat?appId=
${
lastChat
App
Id
}
&chatId=
${
lastChatId
}
`
,
activeLink
:
[
'/chat'
]
activeLink
:
[
'/chat'
]
},
},
{
{
...
@@ -56,7 +56,7 @@ const Navbar = ({ unread }: { unread: number }) => {
...
@@ -56,7 +56,7 @@ const Navbar = ({ unread }: { unread: number }) => {
activeLink
:
[
'/number'
]
activeLink
:
[
'/number'
]
}
}
],
],
[
lastChatId
,
lastChat
Model
Id
]
[
lastChatId
,
lastChat
App
Id
]
);
);
const
itemStyles
:
any
=
{
const
itemStyles
:
any
=
{
...
...
client/src/components/Layout/navbarPhone.tsx
View file @
b2e2f60e
...
@@ -7,13 +7,13 @@ import Badge from '../Badge';
...
@@ -7,13 +7,13 @@ import Badge from '../Badge';
const
NavbarPhone
=
({
unread
}:
{
unread
:
number
})
=>
{
const
NavbarPhone
=
({
unread
}:
{
unread
:
number
})
=>
{
const
router
=
useRouter
();
const
router
=
useRouter
();
const
{
lastChat
Model
Id
,
lastChatId
}
=
useChatStore
();
const
{
lastChat
App
Id
,
lastChatId
}
=
useChatStore
();
const
navbarList
=
useMemo
(
const
navbarList
=
useMemo
(
()
=>
[
()
=>
[
{
{
label
:
'聊天'
,
label
:
'聊天'
,
icon
:
'tabbarChat'
,
icon
:
'tabbarChat'
,
link
:
`/chat?appId=
${
lastChat
Model
Id
}
&chatId=
${
lastChatId
}
`
,
link
:
`/chat?appId=
${
lastChat
App
Id
}
&chatId=
${
lastChatId
}
`
,
activeLink
:
[
'/chat'
],
activeLink
:
[
'/chat'
],
unread
:
0
unread
:
0
},
},
...
@@ -39,7 +39,7 @@ const NavbarPhone = ({ unread }: { unread: number }) => {
...
@@ -39,7 +39,7 @@ const NavbarPhone = ({ unread }: { unread: number }) => {
unread
unread
}
}
],
],
[
lastChatId
,
lastChat
Model
Id
,
unread
]
[
lastChatId
,
lastChat
App
Id
,
unread
]
);
);
return
(
return
(
...
...
client/src/components/SideBar/index.tsx
View file @
b2e2f60e
...
@@ -7,7 +7,7 @@ interface Props extends BoxProps {}
...
@@ -7,7 +7,7 @@ interface Props extends BoxProps {}
const
SideBar
=
(
e
?:
Props
)
=>
{
const
SideBar
=
(
e
?:
Props
)
=>
{
const
{
const
{
w
=
[
'100%'
,
'0 0 250px'
,
'0 0 2
80px'
,
'0 0 310px'
,
'0 0 34
0px'
],
w
=
[
'100%'
,
'0 0 250px'
,
'0 0 2
70px'
,
'0 0 290px'
,
'0 0 31
0px'
],
children
,
children
,
...
props
...
props
}
=
e
||
{};
}
=
e
||
{};
...
...
client/src/components/Tag/index.tsx
View file @
b2e2f60e
...
@@ -10,7 +10,7 @@ const Tag = ({ children, colorSchema = 'blue', ...props }: Props) => {
...
@@ -10,7 +10,7 @@ const Tag = ({ children, colorSchema = 'blue', ...props }: Props) => {
const
theme
=
useMemo
(()
=>
{
const
theme
=
useMemo
(()
=>
{
const
map
=
{
const
map
=
{
blue
:
{
blue
:
{
borderColor
:
'myBlue.
7
00'
,
borderColor
:
'myBlue.
6
00'
,
bg
:
'#F2FBFF'
,
bg
:
'#F2FBFF'
,
color
:
'myBlue.700'
color
:
'myBlue.700'
},
},
...
...
client/src/pages/api/app/del.ts
View file @
b2e2f60e
...
@@ -7,9 +7,9 @@ import { authApp } from '@/service/utils/auth';
...
@@ -7,9 +7,9 @@ import { authApp } from '@/service/utils/auth';
/* 获取我的模型 */
/* 获取我的模型 */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
<
any
>
)
{
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
<
any
>
)
{
try
{
try
{
const
{
modelId
}
=
req
.
query
as
{
model
Id
:
string
};
const
{
appId
}
=
req
.
query
as
{
app
Id
:
string
};
if
(
!
model
Id
)
{
if
(
!
app
Id
)
{
throw
new
Error
(
'参数错误'
);
throw
new
Error
(
'参数错误'
);
}
}
...
@@ -20,28 +20,28 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...
@@ -20,28 +20,28 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 验证是否是该用户的 model
// 验证是否是该用户的 model
await
authApp
({
await
authApp
({
appId
:
modelId
,
appId
,
userId
userId
});
});
// 删除对应的聊天
// 删除对应的聊天
await
Chat
.
deleteMany
({
await
Chat
.
deleteMany
({
model
Id
app
Id
});
});
// 删除收藏列表
// 删除收藏列表
await
Collection
.
deleteMany
({
await
Collection
.
deleteMany
({
modelId
modelId
:
appId
});
});
// 删除分享链接
// 删除分享链接
await
ShareChat
.
deleteMany
({
await
ShareChat
.
deleteMany
({
model
Id
app
Id
});
});
// 删除模型
// 删除模型
await
App
.
deleteOne
({
await
App
.
deleteOne
({
_id
:
model
Id
,
_id
:
app
Id
,
userId
userId
});
});
...
...
client/src/pages/api/app/detail.tsx
View file @
b2e2f60e
...
@@ -7,9 +7,9 @@ import { authApp } from '@/service/utils/auth';
...
@@ -7,9 +7,9 @@ import { authApp } from '@/service/utils/auth';
/* 获取我的模型 */
/* 获取我的模型 */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
<
any
>
)
{
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
<
any
>
)
{
try
{
try
{
const
{
modelId
}
=
req
.
query
as
{
model
Id
:
string
};
const
{
appId
}
=
req
.
query
as
{
app
Id
:
string
};
if
(
!
model
Id
)
{
if
(
!
app
Id
)
{
throw
new
Error
(
'参数错误'
);
throw
new
Error
(
'参数错误'
);
}
}
...
@@ -19,7 +19,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...
@@ -19,7 +19,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await
connectToDatabase
();
await
connectToDatabase
();
const
{
app
}
=
await
authApp
({
const
{
app
}
=
await
authApp
({
appId
:
modelId
,
appId
,
userId
,
userId
,
authOwner
:
false
authOwner
:
false
});
});
...
...
client/src/pages/api/app/
list
.ts
→
client/src/pages/api/app/
myApps
.ts
View file @
b2e2f60e
import
type
{
NextApiRequest
,
NextApiResponse
}
from
'next'
;
import
type
{
NextApiRequest
,
NextApiResponse
}
from
'next'
;
import
{
jsonRes
}
from
'@/service/response'
;
import
{
jsonRes
}
from
'@/service/response'
;
import
{
connectToDatabase
,
Collection
,
App
}
from
'@/service/mongo'
;
import
{
connectToDatabase
,
App
}
from
'@/service/mongo'
;
import
{
authUser
}
from
'@/service/utils/auth'
;
import
{
authUser
}
from
'@/service/utils/auth'
;
import
type
{
AppListResponse
}
from
'@/api/response
/app'
;
import
{
AppListItemType
}
from
'@/types
/app'
;
/* 获取模型列表 */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
<
any
>
)
{
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
<
any
>
)
{
try
{
try
{
// 凭证校验
// 凭证校验
...
@@ -13,41 +12,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...
@@ -13,41 +12,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await
connectToDatabase
();
await
connectToDatabase
();
// 根据 userId 获取模型信息
// 根据 userId 获取模型信息
const
[
myApps
,
myCollections
]
=
await
Promise
.
all
([
const
myApps
=
await
App
.
find
(
App
.
find
(
{
{
userId
userId
},
},
'_id avatar name intro'
'_id avatar name intro'
).
sort
({
).
sort
({
updateTime
:
-
1
updateTime
:
-
1
}),
});
Collection
.
find
({
userId
})
.
populate
({
path
:
'modelId'
,
select
:
'_id avatar name intro'
,
match
:
{
'share.isShare'
:
true
}
})
.
then
((
res
)
=>
res
.
filter
((
item
)
=>
item
.
modelId
))
]);
jsonRes
<
AppListResponse
>
(
res
,
{
jsonRes
<
AppListItemType
[]
>
(
res
,
{
data
:
{
data
:
myApps
myApps
:
myApps
.
map
((
item
)
=>
({
_id
:
item
.
_id
,
name
:
item
.
name
,
avatar
:
item
.
avatar
,
intro
:
item
.
intro
})),
myCollectionApps
:
myCollections
.
map
((
item
:
any
)
=>
({
_id
:
item
.
modelId
?.
_id
,
name
:
item
.
modelId
?.
name
,
avatar
:
item
.
modelId
?.
avatar
,
intro
:
item
.
modelId
?.
intro
}))
.
filter
((
item
)
=>
!
myApps
.
find
((
model
)
=>
String
(
model
.
_id
)
===
String
(
item
.
_id
)))
// 去重
}
});
});
}
catch
(
err
)
{
}
catch
(
err
)
{
jsonRes
(
res
,
{
jsonRes
(
res
,
{
...
...
client/src/pages/api/app/share/collection.ts
View file @
b2e2f60e
...
@@ -6,9 +6,9 @@ import { authUser } from '@/service/utils/auth';
...
@@ -6,9 +6,9 @@ import { authUser } from '@/service/utils/auth';
/* 模型收藏切换 */
/* 模型收藏切换 */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
<
any
>
)
{
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
<
any
>
)
{
try
{
try
{
const
{
modelId
}
=
req
.
query
as
{
model
Id
:
string
};
const
{
appId
}
=
req
.
query
as
{
app
Id
:
string
};
if
(
!
model
Id
)
{
if
(
!
app
Id
)
{
throw
new
Error
(
'缺少参数'
);
throw
new
Error
(
'缺少参数'
);
}
}
// 凭证校验
// 凭证校验
...
@@ -18,7 +18,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...
@@ -18,7 +18,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const
collectionRecord
=
await
Collection
.
findOne
({
const
collectionRecord
=
await
Collection
.
findOne
({
userId
,
userId
,
modelId
modelId
:
appId
});
});
if
(
collectionRecord
)
{
if
(
collectionRecord
)
{
...
@@ -26,12 +26,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...
@@ -26,12 +26,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
else
{
}
else
{
await
Collection
.
create
({
await
Collection
.
create
({
userId
,
userId
,
modelId
modelId
:
appId
});
});
}
}
await
App
.
findByIdAndUpdate
(
model
Id
,
{
await
App
.
findByIdAndUpdate
(
app
Id
,
{
'share.collection'
:
await
Collection
.
countDocuments
({
modelId
})
'share.collection'
:
await
Collection
.
countDocuments
({
modelId
:
appId
})
});
});
jsonRes
(
res
);
jsonRes
(
res
);
...
...
client/src/pages/api/chat/chatTest.ts
View file @
b2e2f60e
...
@@ -6,14 +6,15 @@ import { sseResponseEventEnum } from '@/constants/chat';
...
@@ -6,14 +6,15 @@ import { sseResponseEventEnum } from '@/constants/chat';
import
{
sseResponse
}
from
'@/service/utils/tools'
;
import
{
sseResponse
}
from
'@/service/utils/tools'
;
import
{
type
ChatCompletionRequestMessage
}
from
'openai'
;
import
{
type
ChatCompletionRequestMessage
}
from
'openai'
;
import
{
AppModuleItemType
}
from
'@/types/app'
;
import
{
AppModuleItemType
}
from
'@/types/app'
;
import
{
dispatchModules
}
from
'../openapi/v1/chat/completions2'
;
import
{
dispatchModules
}
from
'../openapi/v1/chat/completions'
;
import
{
gptMessage2ChatType
}
from
'@/utils/adapt'
;
export
type
MessageItemType
=
ChatCompletionRequestMessage
&
{
_id
?:
string
};
export
type
MessageItemType
=
ChatCompletionRequestMessage
&
{
_id
?:
string
};
export
type
Props
=
{
export
type
Props
=
{
history
:
MessageItemType
[];
history
:
MessageItemType
[];
prompt
:
string
;
prompt
:
string
;
modules
:
AppModuleItemType
[];
modules
:
AppModuleItemType
[];
variable
:
Record
<
string
,
any
>
;
variable
s
:
Record
<
string
,
any
>
;
};
};
export
type
ChatResponseType
=
{
export
type
ChatResponseType
=
{
newChatId
:
string
;
newChatId
:
string
;
...
@@ -29,7 +30,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -29,7 +30,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
res
.
end
();
res
.
end
();
});
});
let
{
modules
=
[],
history
=
[],
prompt
,
variable
=
{}
}
=
req
.
body
as
Props
;
let
{
modules
=
[],
history
=
[],
prompt
,
variable
s
=
{}
}
=
req
.
body
as
Props
;
try
{
try
{
if
(
!
history
||
!
modules
||
!
prompt
)
{
if
(
!
history
||
!
modules
||
!
prompt
)
{
...
@@ -48,9 +49,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -48,9 +49,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const
{
responseData
}
=
await
dispatchModules
({
const
{
responseData
}
=
await
dispatchModules
({
res
,
res
,
modules
:
modules
,
modules
:
modules
,
variable
,
variable
s
,
params
:
{
params
:
{
history
,
history
:
gptMessage2ChatType
(
history
)
,
userChatInput
:
prompt
userChatInput
:
prompt
},
},
stream
:
true
stream
:
true
...
...
client/src/pages/api/chat/delChatRecordByContentId.ts
View file @
b2e2f60e
...
@@ -5,12 +5,10 @@ import { authUser } from '@/service/utils/auth';
...
@@ -5,12 +5,10 @@ import { authUser } from '@/service/utils/auth';
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
try
{
try
{
const
{
chatId
,
contentId
}
=
req
.
query
as
{
const
{
historyId
,
contentId
}
=
req
.
query
as
{
historyId
:
string
;
contentId
:
string
};
chatId
:
string
;
console
.
log
(
historyId
,
contentId
);
contentId
:
string
;
};
if
(
!
chat
Id
||
!
contentId
)
{
if
(
!
history
Id
||
!
contentId
)
{
throw
new
Error
(
'缺少参数'
);
throw
new
Error
(
'缺少参数'
);
}
}
...
@@ -19,7 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -19,7 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// 凭证校验
// 凭证校验
const
{
userId
}
=
await
authUser
({
req
,
authToken
:
true
});
const
{
userId
}
=
await
authUser
({
req
,
authToken
:
true
});
const
chatRecord
=
await
Chat
.
findById
(
chat
Id
);
const
chatRecord
=
await
Chat
.
findById
(
history
Id
);
if
(
!
chatRecord
)
{
if
(
!
chatRecord
)
{
throw
new
Error
(
'找不到对话'
);
throw
new
Error
(
'找不到对话'
);
...
@@ -28,7 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -28,7 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// 删除一条数据库记录
// 删除一条数据库记录
await
Chat
.
updateOne
(
await
Chat
.
updateOne
(
{
{
_id
:
chat
Id
,
_id
:
history
Id
,
userId
userId
},
},
{
$pull
:
{
content
:
{
_id
:
contentId
}
}
}
{
$pull
:
{
content
:
{
_id
:
contentId
}
}
}
...
...
client/src/pages/api/chat/history/getHistory.ts
View file @
b2e2f60e
...
@@ -2,31 +2,32 @@ import type { NextApiRequest, NextApiResponse } from 'next';
...
@@ -2,31 +2,32 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import
{
jsonRes
}
from
'@/service/response'
;
import
{
jsonRes
}
from
'@/service/response'
;
import
{
connectToDatabase
,
Chat
}
from
'@/service/mongo'
;
import
{
connectToDatabase
,
Chat
}
from
'@/service/mongo'
;
import
{
authUser
}
from
'@/service/utils/auth'
;
import
{
authUser
}
from
'@/service/utils/auth'
;
import
type
{
HistoryItemType
}
from
'@/types/chat'
;
import
type
{
Chat
HistoryItemType
}
from
'@/types/chat'
;
/* 获取历史记录 */
/* 获取历史记录 */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
try
{
try
{
const
{
appId
}
=
req
.
body
as
{
appId
?:
string
};
const
{
userId
}
=
await
authUser
({
req
,
authToken
:
true
});
const
{
userId
}
=
await
authUser
({
req
,
authToken
:
true
});
await
connectToDatabase
();
await
connectToDatabase
();
const
data
=
await
Chat
.
find
(
const
data
=
await
Chat
.
find
(
{
{
userId
userId
,
...(
appId
&&
{
appId
})
},
},
'_id title top customTitle
modelId updateTime latestChat
'
'_id title top customTitle
appId updateTime
'
)
)
.
sort
({
top
:
-
1
,
updateTime
:
-
1
})
.
sort
({
top
:
-
1
,
updateTime
:
-
1
})
.
limit
(
20
);
.
limit
(
20
);
jsonRes
<
HistoryItemType
[]
>
(
res
,
{
jsonRes
<
Chat
HistoryItemType
[]
>
(
res
,
{
data
:
data
.
map
((
item
)
=>
({
data
:
data
.
map
((
item
)
=>
({
_id
:
item
.
_id
,
_id
:
item
.
_id
,
updateTime
:
item
.
updateTime
,
updateTime
:
item
.
updateTime
,
modelId
:
item
.
model
Id
,
appId
:
item
.
app
Id
,
title
:
item
.
customTitle
||
item
.
title
,
title
:
item
.
customTitle
||
item
.
title
,
latestChat
:
item
.
latestChat
,
top
:
item
.
top
top
:
item
.
top
}))
}))
});
});
...
...
client/src/pages/api/chat/history/updateChatHistory.ts
View file @
b2e2f60e
...
@@ -4,7 +4,7 @@ import { connectToDatabase, Chat } from '@/service/mongo';
...
@@ -4,7 +4,7 @@ import { connectToDatabase, Chat } from '@/service/mongo';
import
{
authUser
}
from
'@/service/utils/auth'
;
import
{
authUser
}
from
'@/service/utils/auth'
;
export
type
Props
=
{
export
type
Props
=
{
chatId
:
''
|
string
;
historyId
:
string
;
customTitle
?:
string
;
customTitle
?:
string
;
top
?:
boolean
;
top
?:
boolean
;
};
};
...
@@ -12,7 +12,7 @@ export type Props = {
...
@@ -12,7 +12,7 @@ export type Props = {
/* 更新聊天标题 */
/* 更新聊天标题 */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
try
{
try
{
const
{
chat
Id
,
customTitle
,
top
}
=
req
.
body
as
Props
;
const
{
history
Id
,
customTitle
,
top
}
=
req
.
body
as
Props
;
const
{
userId
}
=
await
authUser
({
req
,
authToken
:
true
});
const
{
userId
}
=
await
authUser
({
req
,
authToken
:
true
});
...
@@ -20,7 +20,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -20,7 +20,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await
Chat
.
findOneAndUpdate
(
await
Chat
.
findOneAndUpdate
(
{
{
_id
:
chat
Id
,
_id
:
history
Id
,
userId
userId
},
},
{
{
...
...
client/src/pages/api/chat/init.ts
View file @
b2e2f60e
...
@@ -6,23 +6,25 @@ import { authUser } from '@/service/utils/auth';
...
@@ -6,23 +6,25 @@ import { authUser } from '@/service/utils/auth';
import
{
ChatItemType
}
from
'@/types/chat'
;
import
{
ChatItemType
}
from
'@/types/chat'
;
import
{
authApp
}
from
'@/service/utils/auth'
;
import
{
authApp
}
from
'@/service/utils/auth'
;
import
mongoose
from
'mongoose'
;
import
mongoose
from
'mongoose'
;
import
type
{
AppSchema
}
from
'@/types/mongoSchema'
;
import
type
{
AppSchema
,
ChatSchema
}
from
'@/types/mongoSchema'
;
import
{
FlowModuleTypeEnum
}
from
'@/constants/flow'
;
import
{
SystemInputEnum
}
from
'@/constants/app'
;
/* 初始化我的聊天框,需要身份验证 */
/* 初始化我的聊天框,需要身份验证 */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
try
{
try
{
const
{
userId
}
=
await
authUser
({
req
,
authToken
:
true
});
const
{
userId
}
=
await
authUser
({
req
,
authToken
:
true
});
let
{
modelId
,
chat
Id
}
=
req
.
query
as
{
let
{
appId
,
history
Id
}
=
req
.
query
as
{
model
Id
:
''
|
string
;
app
Id
:
''
|
string
;
chat
Id
:
''
|
string
;
history
Id
:
''
|
string
;
};
};
await
connectToDatabase
();
await
connectToDatabase
();
// 没有
model
Id 时,直接获取用户的第一个id
// 没有
app
Id 时,直接获取用户的第一个id
const
app
=
await
(
async
()
=>
{
const
app
=
await
(
async
()
=>
{
if
(
!
model
Id
)
{
if
(
!
app
Id
)
{
const
myModel
=
await
App
.
findOne
({
userId
});
const
myModel
=
await
App
.
findOne
({
userId
});
if
(
!
myModel
)
{
if
(
!
myModel
)
{
const
{
_id
}
=
await
App
.
create
({
const
{
_id
}
=
await
App
.
create
({
...
@@ -36,7 +38,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -36,7 +38,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
else
{
}
else
{
// 校验使用权限
// 校验使用权限
const
authRes
=
await
authApp
({
const
authRes
=
await
authApp
({
appId
:
modelId
,
appId
,
userId
,
userId
,
authUser
:
false
,
authUser
:
false
,
authOwner
:
false
authOwner
:
false
...
@@ -45,25 +47,25 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -45,25 +47,25 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
}
})();
})();
modelId
=
model
Id
||
app
.
_id
;
appId
=
app
Id
||
app
.
_id
;
// 历史记录
// 历史记录
let
history
:
ChatItemType
[]
=
[];
const
{
chat
,
history
=
[]
}:
{
chat
?:
ChatSchema
;
history
?:
ChatItemType
[]
}
=
await
(
async
()
=>
{
if
(
chat
Id
)
{
if
(
history
Id
)
{
// auth chatId
// auth chatId
const
chat
=
await
Chat
.
countDocuments
({
const
chat
=
await
Chat
.
findOne
({
_id
:
chat
Id
,
_id
:
history
Id
,
userId
userId
});
});
if
(
chat
===
0
)
{
if
(
!
chat
)
{
throw
new
Error
(
'聊天框不存在'
);
throw
new
Error
(
'聊天框不存在'
);
}
}
// 获取 chat.content 数据
// 获取 chat.content 数据
history
=
await
Chat
.
aggregate
([
const
history
=
await
Chat
.
aggregate
([
{
{
$match
:
{
$match
:
{
_id
:
new
mongoose
.
Types
.
ObjectId
(
chat
Id
),
_id
:
new
mongoose
.
Types
.
ObjectId
(
history
Id
),
userId
:
new
mongoose
.
Types
.
ObjectId
(
userId
)
userId
:
new
mongoose
.
Types
.
ObjectId
(
userId
)
}
}
},
},
...
@@ -85,23 +87,31 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -85,23 +87,31 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
}
}
}
]);
]);
return
{
history
,
chat
};
}
}
return
{};
})();
const
isOwner
=
String
(
app
.
userId
)
===
userId
;
const
isOwner
=
String
(
app
.
userId
)
===
userId
;
jsonRes
<
InitChatResponse
>
(
res
,
{
jsonRes
<
InitChatResponse
>
(
res
,
{
data
:
{
data
:
{
chatId
:
chatId
||
''
,
historyId
,
modelId
:
modelId
,
appId
,
model
:
{
app
:
{
variableModules
:
app
.
modules
.
find
((
item
)
=>
item
.
flowType
===
FlowModuleTypeEnum
.
userGuide
)
?.
inputs
?.
find
((
item
)
=>
item
.
key
===
SystemInputEnum
.
variables
)?.
value
,
welcomeText
:
app
.
modules
.
find
((
item
)
=>
item
.
flowType
===
FlowModuleTypeEnum
.
userGuide
)
?.
inputs
?.
find
((
item
)
=>
item
.
key
===
SystemInputEnum
.
welcomeText
)?.
value
,
name
:
app
.
name
,
name
:
app
.
name
,
avatar
:
app
.
avatar
,
avatar
:
app
.
avatar
,
intro
:
app
.
intro
,
intro
:
app
.
intro
,
canUse
:
app
.
share
.
isShare
||
isOwner
canUse
:
app
.
share
.
isShare
||
isOwner
},
},
chatModel
:
app
.
chat
.
chatModel
,
title
:
chat
?.
title
||
'新对话'
,
systemPrompt
:
isOwner
?
app
.
chat
.
systemPrompt
:
''
,
variables
:
chat
?.
variables
||
{},
limitPrompt
:
isOwner
?
app
.
chat
.
limitPrompt
:
''
,
history
history
}
}
});
});
...
...
client/src/pages/api/chat/saveChat.ts
View file @
b2e2f60e
...
@@ -7,15 +7,16 @@ import { authUser } from '@/service/utils/auth';
...
@@ -7,15 +7,16 @@ import { authUser } from '@/service/utils/auth';
import
{
Types
}
from
'mongoose'
;
import
{
Types
}
from
'mongoose'
;
type
Props
=
{
type
Props
=
{
chatId
?:
string
;
historyId
?:
string
;
modelId
:
string
;
appId
:
string
;
variables
?:
Record
<
string
,
any
>
;
prompts
:
[
ChatItemType
,
ChatItemType
];
prompts
:
[
ChatItemType
,
ChatItemType
];
};
};
/* 聊天内容存存储 */
/* 聊天内容存存储 */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
try
{
try
{
const
{
chatId
,
model
Id
,
prompts
}
=
req
.
body
as
Props
;
const
{
historyId
,
app
Id
,
prompts
}
=
req
.
body
as
Props
;
if
(
!
prompts
)
{
if
(
!
prompts
)
{
throw
new
Error
(
'缺少参数'
);
throw
new
Error
(
'缺少参数'
);
...
@@ -24,8 +25,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -24,8 +25,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const
{
userId
}
=
await
authUser
({
req
,
authToken
:
true
});
const
{
userId
}
=
await
authUser
({
req
,
authToken
:
true
});
const
response
=
await
saveChat
({
const
response
=
await
saveChat
({
chat
Id
,
history
Id
,
model
Id
,
app
Id
,
prompts
,
prompts
,
userId
userId
});
});
...
@@ -42,14 +43,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -42,14 +43,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
}
export
async
function
saveChat
({
export
async
function
saveChat
({
new
Chat
Id
,
new
History
Id
,
chat
Id
,
history
Id
,
model
Id
,
app
Id
,
prompts
,
prompts
,
variables
,
userId
userId
}:
Props
&
{
new
ChatId
?:
Types
.
ObjectId
;
userId
:
string
}):
Promise
<
{
newChat
Id
:
string
}
>
{
}:
Props
&
{
new
HistoryId
?:
Types
.
ObjectId
;
userId
:
string
}):
Promise
<
{
newHistory
Id
:
string
}
>
{
await
connectToDatabase
();
await
connectToDatabase
();
const
{
app
}
=
await
authApp
({
appId
:
modelId
,
userId
,
authOwner
:
false
});
const
{
app
}
=
await
authApp
({
appId
,
userId
,
authOwner
:
false
});
const
content
=
prompts
.
map
((
item
)
=>
({
const
content
=
prompts
.
map
((
item
)
=>
({
_id
:
item
.
_id
,
_id
:
item
.
_id
,
...
@@ -60,43 +62,45 @@ export async function saveChat({
...
@@ -60,43 +62,45 @@ export async function saveChat({
}));
}));
if
(
String
(
app
.
userId
)
===
userId
)
{
if
(
String
(
app
.
userId
)
===
userId
)
{
await
App
.
findByIdAndUpdate
(
model
Id
,
{
await
App
.
findByIdAndUpdate
(
app
Id
,
{
updateTime
:
new
Date
()
updateTime
:
new
Date
()
});
});
}
}
const
[
response
]
=
await
Promise
.
all
([
const
[
response
]
=
await
Promise
.
all
([
...(
chat
Id
...(
history
Id
?
[
?
[
Chat
.
findByIdAndUpdate
(
chat
Id
,
{
Chat
.
findByIdAndUpdate
(
history
Id
,
{
$push
:
{
$push
:
{
content
:
{
content
:
{
$each
:
content
$each
:
content
}
}
},
},
variables
,
title
:
content
[
0
].
value
.
slice
(
0
,
20
),
title
:
content
[
0
].
value
.
slice
(
0
,
20
),
latestChat
:
content
[
1
].
value
,
latestChat
:
content
[
1
].
value
,
updateTime
:
new
Date
()
updateTime
:
new
Date
()
}).
then
(()
=>
({
}).
then
(()
=>
({
new
Chat
Id
:
''
new
History
Id
:
''
}))
}))
]
]
:
[
:
[
Chat
.
create
({
Chat
.
create
({
_id
:
new
Chat
Id
,
_id
:
new
History
Id
,
userId
,
userId
,
modelId
,
appId
,
variables
,
content
,
content
,
title
:
content
[
0
].
value
.
slice
(
0
,
20
),
title
:
content
[
0
].
value
.
slice
(
0
,
20
),
latestChat
:
content
[
1
].
value
latestChat
:
content
[
1
].
value
}).
then
((
res
)
=>
({
}).
then
((
res
)
=>
({
new
Chat
Id
:
String
(
res
.
_id
)
new
History
Id
:
String
(
res
.
_id
)
}))
}))
]),
]),
// update app
// update app
...(
String
(
app
.
userId
)
===
userId
...(
String
(
app
.
userId
)
===
userId
?
[
?
[
App
.
findByIdAndUpdate
(
model
Id
,
{
App
.
findByIdAndUpdate
(
app
Id
,
{
updateTime
:
new
Date
()
updateTime
:
new
Date
()
})
})
]
]
...
@@ -105,6 +109,6 @@ export async function saveChat({
...
@@ -105,6 +109,6 @@ export async function saveChat({
return
{
return
{
// @ts-ignore
// @ts-ignore
new
ChatId
:
response
?.
newChat
Id
||
''
new
HistoryId
:
response
?.
newHistory
Id
||
''
};
};
}
}
client/src/pages/api/openapi/chat/chat.ts
deleted
100644 → 0
View file @
eb768d9c
import
type
{
NextApiRequest
,
NextApiResponse
}
from
'next'
;
import
{
connectToDatabase
}
from
'@/service/mongo'
;
import
{
authUser
,
authApp
,
getApiKey
}
from
'@/service/utils/auth'
;
import
{
modelServiceToolMap
,
resStreamResponse
}
from
'@/service/utils/chat'
;
import
{
ChatItemType
}
from
'@/types/chat'
;
import
{
jsonRes
}
from
'@/service/response'
;
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
{
appKbSearch
}
from
'../kb/appKbSearch'
;
/* 发送提示词 */
export
default
withNextCors
(
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
res
.
on
(
'close'
,
()
=>
{
res
.
end
();
});
res
.
on
(
'error'
,
()
=>
{
console
.
log
(
'error: '
,
'request error'
);
res
.
end
();
});
try
{
const
{
chatId
,
prompts
,
modelId
,
isStream
=
true
}
=
req
.
body
as
{
chatId
?:
string
;
prompts
:
ChatItemType
[];
modelId
:
string
;
isStream
:
boolean
;
};
if
(
!
prompts
||
!
modelId
)
{
throw
new
Error
(
'缺少参数'
);
}
if
(
!
Array
.
isArray
(
prompts
))
{
throw
new
Error
(
'prompts is not array'
);
}
if
(
prompts
.
length
>
30
||
prompts
.
length
===
0
)
{
throw
new
Error
(
'Prompts arr length range 1-30'
);
}
await
connectToDatabase
();
let
startTime
=
Date
.
now
();
/* 凭证校验 */
const
{
userId
}
=
await
authUser
({
req
});
const
{
app
}
=
await
authApp
({
userId
,
appId
:
modelId
});
/* get api key */
const
{
systemAuthKey
:
apiKey
}
=
await
getApiKey
({
model
:
app
.
chat
.
chatModel
,
userId
,
mustPay
:
true
});
const
modelConstantsData
=
ChatModelMap
[
app
.
chat
.
chatModel
];
const
prompt
=
prompts
[
prompts
.
length
-
1
];
const
{
userSystemPrompt
=
[],
userLimitPrompt
=
[],
quotePrompt
=
[]
}
=
await
(
async
()
=>
{
// 使用了知识库搜索
if
(
app
.
chat
.
relatedKbs
?.
length
>
0
)
{
const
{
quotePrompt
,
userSystemPrompt
,
userLimitPrompt
}
=
await
appKbSearch
({
model
:
app
,
userId
,
fixedQuote
:
[],
prompt
:
prompt
,
similarity
:
app
.
chat
.
searchSimilarity
,
limit
:
app
.
chat
.
searchLimit
});
return
{
userSystemPrompt
,
userLimitPrompt
,
quotePrompt
:
[
quotePrompt
]
};
}
return
{
userSystemPrompt
:
app
.
chat
.
systemPrompt
?
[
{
obj
:
ChatRoleEnum
.
System
,
value
:
app
.
chat
.
systemPrompt
}
]
:
[],
userLimitPrompt
:
app
.
chat
.
limitPrompt
?
[
{
obj
:
ChatRoleEnum
.
Human
,
value
:
app
.
chat
.
limitPrompt
}
]
:
[]
};
})();
// search result is empty
if
(
app
.
chat
.
relatedKbs
?.
length
>
0
&&
!
quotePrompt
[
0
]?.
value
&&
app
.
chat
.
searchEmptyText
)
{
const
response
=
app
.
chat
.
searchEmptyText
;
return
res
.
end
(
response
);
}
// 读取对话内容
const
completePrompts
=
[
...
quotePrompt
,
...
userSystemPrompt
,
...
prompts
.
slice
(
0
,
-
1
),
...
userLimitPrompt
,
prompt
];
// 计算温度
const
temperature
=
(
modelConstantsData
.
maxTemperature
*
(
app
.
chat
.
temperature
/
10
)).
toFixed
(
2
);
// 发出请求
const
{
streamResponse
,
responseMessages
,
responseText
,
totalTokens
}
=
await
modelServiceToolMap
.
chatCompletion
({
model
:
app
.
chat
.
chatModel
,
apiKey
,
temperature
:
+
temperature
,
messages
:
completePrompts
,
stream
:
isStream
,
res
});
console
.
log
(
'api response time:'
,
`
${(
Date
.
now
()
-
startTime
)
/
1000
}
s`
);
if
(
res
.
closed
)
return
res
.
end
();
const
{
textLen
=
0
,
tokens
=
totalTokens
}
=
await
(
async
()
=>
{
if
(
isStream
)
{
try
{
const
{
finishMessages
,
totalTokens
}
=
await
resStreamResponse
({
model
:
app
.
chat
.
chatModel
,
res
,
chatResponse
:
streamResponse
,
prompts
:
responseMessages
});
res
.
end
();
return
{
textLen
:
finishMessages
.
map
((
item
)
=>
item
.
value
).
join
(
''
).
length
,
tokens
:
totalTokens
};
}
catch
(
error
)
{
res
.
end
();
console
.
log
(
'error,结束'
,
error
);
}
}
else
{
jsonRes
(
res
,
{
data
:
responseText
});
return
{
textLen
:
responseMessages
.
map
((
item
)
=>
item
.
value
).
join
(
''
).
length
};
}
return
{};
})();
pushChatBill
({
isPay
:
true
,
chatModel
:
app
.
chat
.
chatModel
,
userId
,
textLen
,
tokens
,
type
:
BillTypeEnum
.
openapiChat
});
}
catch
(
err
:
any
)
{
res
.
status
(
500
);
jsonRes
(
res
,
{
code
:
500
,
error
:
err
});
}
});
client/src/pages/api/openapi/v1/chat/completions.ts
View file @
b2e2f60e
import
type
{
NextApiRequest
,
NextApiResponse
}
from
'next'
;
import
type
{
NextApiRequest
,
NextApiResponse
}
from
'next'
;
import
{
connectToDatabase
}
from
'@/service/mongo'
;
import
{
connectToDatabase
}
from
'@/service/mongo'
;
import
{
authUser
,
authApp
,
getApiKey
,
authShareChat
}
from
'@/service/utils/auth'
;
import
{
authUser
,
authApp
,
authShareChat
}
from
'@/service/utils/auth'
;
import
{
modelServiceToolMap
,
V2_StreamResponse
}
from
'@/service/utils/chat'
;
import
{
sseErrRes
,
jsonRes
}
from
'@/service/response'
;
import
{
jsonRes
}
from
'@/service/response'
;
import
{
ChatModelMap
}
from
'@/constants/model'
;
import
{
pushChatBill
,
updateShareChatBill
}
from
'@/service/events/pushBill'
;
import
{
ChatRoleEnum
,
sseResponseEventEnum
}
from
'@/constants/chat'
;
import
{
ChatRoleEnum
,
sseResponseEventEnum
}
from
'@/constants/chat'
;
import
{
withNextCors
}
from
'@/service/utils/tools'
;
import
{
withNextCors
}
from
'@/service/utils/tools'
;
import
{
BillTypeEnum
}
from
'@/constants/user'
;
import
{
appKbSearch
}
from
'../../../openapi/kb/appKbSearch'
;
import
type
{
CreateChatCompletionRequest
}
from
'openai'
;
import
type
{
CreateChatCompletionRequest
}
from
'openai'
;
import
{
gptMessage2ChatType
,
textAdaptGptResponse
}
from
'@/utils/adapt'
;
import
{
gptMessage2ChatType
,
textAdaptGptResponse
}
from
'@/utils/adapt'
;
import
{
getChatHistory
}
from
'./getHistory'
;
import
{
getChatHistory
}
from
'./getHistory'
;
import
{
saveChat
}
from
'@/pages/api/chat/saveChat'
;
import
{
saveChat
}
from
'@/pages/api/chat/saveChat'
;
import
{
sseResponse
}
from
'@/service/utils/tools'
;
import
{
sseResponse
}
from
'@/service/utils/tools'
;
import
{
type
ChatCompletionRequestMessage
}
from
'openai'
;
import
{
type
ChatCompletionRequestMessage
}
from
'openai'
;
import
{
SpecificInputEnum
,
AppModuleItemTypeEnum
}
from
'@/constants/app'
;
import
{
Types
}
from
'mongoose'
;
import
{
Types
}
from
'mongoose'
;
import
{
sensitiveCheck
}
from
'../../text/sensitiveCheck'
;
import
{
moduleFetch
}
from
'@/service/api/request'
;
import
{
AppModuleItemType
,
RunningModuleItemType
}
from
'@/types/app'
;
import
{
FlowInputItemTypeEnum
}
from
'@/constants/flow'
;
export
type
MessageItemType
=
ChatCompletionRequestMessage
&
{
_id
?:
string
};
export
type
MessageItemType
=
ChatCompletionRequestMessage
&
{
_id
?:
string
};
type
FastGptWebChatProps
=
{
type
FastGptWebChatProps
=
{
chat
Id
?:
string
;
// undefined: nonuse history, '': new chat, 'xxxxx': use history
history
Id
?:
string
;
// undefined: nonuse history, '': new chat, 'xxxxx': use history
appId
?:
string
;
appId
?:
string
;
};
};
type
FastGptShareChatProps
=
{
type
FastGptShareChatProps
=
{
password
?:
string
;
shareId
?:
string
;
shareId
?:
string
;
};
};
export
type
Props
=
CreateChatCompletionRequest
&
export
type
Props
=
CreateChatCompletionRequest
&
FastGptWebChatProps
&
FastGptWebChatProps
&
FastGptShareChatProps
&
{
FastGptShareChatProps
&
{
messages
:
MessageItemType
[];
messages
:
MessageItemType
[];
stream
?:
boolean
;
variables
:
Record
<
string
,
any
>
;
};
};
export
type
ChatResponseType
=
{
export
type
ChatResponseType
=
{
newChatId
:
string
;
newChatId
:
string
;
quoteLen
?:
number
;
quoteLen
?:
number
;
};
};
/* 发送提示词 */
export
default
withNextCors
(
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
export
default
withNextCors
(
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
res
.
on
(
'close'
,
()
=>
{
res
.
on
(
'close'
,
()
=>
{
res
.
end
();
res
.
end
();
...
@@ -47,8 +45,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
...
@@ -47,8 +45,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
res
.
end
();
res
.
end
();
});
});
let
{
chatId
,
appId
,
shareId
,
password
=
''
,
stream
=
false
,
messages
=
[]
}
=
req
.
body
as
Props
;
let
{
let
step
=
0
;
historyId
,
appId
,
shareId
,
stream
=
false
,
messages
=
[],
variables
=
{}
}
=
req
.
body
as
Props
;
try
{
try
{
if
(
!
messages
)
{
if
(
!
messages
)
{
...
@@ -68,8 +72,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
...
@@ -68,8 +72,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
authType
authType
}
=
await
(
shareId
}
=
await
(
shareId
?
authShareChat
({
?
authShareChat
({
shareId
,
shareId
password
})
})
:
authUser
({
req
}));
:
authUser
({
req
}));
...
@@ -78,263 +81,331 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
...
@@ -78,263 +81,331 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
throw
new
Error
(
'appId is empty'
);
throw
new
Error
(
'appId is empty'
);
}
}
// auth app
permission
// auth app
, get history
const
{
app
,
showModelDetail
}
=
await
authApp
({
const
[{
app
},
{
history
}]
=
await
Promise
.
all
([
userId
,
authApp
({
appId
,
appId
,
authOwner
:
false
,
userId
reserveDetail
:
true
}),
});
getChatHistory
({
historyId
,
userId
})
]);
const
showAppDetail
=
!
shareId
&&
showModelDetail
;
/* get api key */
const
{
systemAuthKey
:
apiKey
,
userOpenAiKey
}
=
await
getApiKey
({
model
:
app
.
chat
.
chatModel
,
userId
,
mustPay
:
authType
!==
'token'
});
// get history
const
{
history
}
=
await
getChatHistory
({
chatId
,
userId
});
const
prompts
=
history
.
concat
(
gptMessage2ChatType
(
messages
));
const
prompts
=
history
.
concat
(
gptMessage2ChatType
(
messages
));
// adapt fastgpt web
if
(
prompts
[
prompts
.
length
-
1
].
obj
===
'AI'
)
{
if
(
prompts
[
prompts
.
length
-
1
].
obj
===
'AI'
)
{
prompts
.
pop
();
prompts
.
pop
();
}
}
// user question
// user question
const
prompt
=
prompts
[
prompts
.
length
-
1
]
;
const
prompt
=
prompts
.
pop
()
;
const
{
if
(
!
prompt
)
{
rawSearch
=
[],
throw
new
Error
(
'Question is empty'
);
userSystemPrompt
=
[],
}
userLimitPrompt
=
[],
quotePrompt
=
[]
}
=
await
(
async
()
=>
{
// 使用了知识库搜索
if
(
app
.
chat
.
relatedKbs
?.
length
>
0
)
{
const
{
rawSearch
,
quotePrompt
,
userSystemPrompt
,
userLimitPrompt
}
=
await
appKbSearch
({
model
:
app
,
userId
,
fixedQuote
:
history
[
history
.
length
-
1
]?.
quote
,
prompt
,
similarity
:
app
.
chat
.
searchSimilarity
,
limit
:
app
.
chat
.
searchLimit
});
return
{
const
newHistoryId
=
historyId
===
''
?
new
Types
.
ObjectId
()
:
undefined
;
rawSearch
,
if
(
stream
&&
newHistoryId
)
{
userSystemPrompt
,
res
.
setHeader
(
'newHistoryId'
,
String
(
newHistoryId
));
userLimitPrompt
,
quotePrompt
:
[
quotePrompt
]
};
}
}
return
{
userSystemPrompt
:
app
.
chat
.
systemPrompt
/* start process */
?
[
const
{
responseData
,
answerText
}
=
await
dispatchModules
({
res
,
modules
:
app
.
modules
,
variables
,
params
:
{
history
:
prompts
,
userChatInput
:
prompt
.
value
},
stream
});
// save chat
if
(
typeof
historyId
===
'string'
)
{
await
saveChat
({
historyId
,
newHistoryId
,
appId
,
prompts
:
[
prompt
,
{
{
obj
:
ChatRoleEnum
.
System
,
_id
:
messages
[
messages
.
length
-
1
].
_id
,
value
:
app
.
chat
.
systemPrompt
obj
:
ChatRoleEnum
.
AI
,
value
:
answerText
,
responseData
}
}
]
],
:
[],
userId
userLimitPrompt
:
app
.
chat
.
limitPrompt
});
?
[
{
obj
:
ChatRoleEnum
.
Human
,
value
:
app
.
chat
.
limitPrompt
}
}
]
:
[]
};
})();
// search result is empty
if
(
app
.
chat
.
relatedKbs
?.
length
>
0
&&
!
quotePrompt
[
0
]?.
value
&&
app
.
chat
.
searchEmptyText
)
{
const
response
=
app
.
chat
.
searchEmptyText
;
if
(
stream
)
{
if
(
stream
)
{
sseResponse
({
sseResponse
({
res
,
res
,
event
:
sseResponseEventEnum
.
answer
,
event
:
sseResponseEventEnum
.
answer
,
data
:
textAdaptGptResponse
({
data
:
'[DONE]'
text
:
response
,
model
:
app
.
chat
.
chatModel
,
finish_reason
:
'stop'
})
});
});
return
res
.
end
();
sseResponse
({
res
,
event
:
sseResponseEventEnum
.
appStreamResponse
,
data
:
JSON
.
stringify
(
responseData
)
});
res
.
end
();
}
else
{
}
else
{
return
res
.
json
({
res
.
json
({
id
:
chatId
||
''
,
data
:
{
object
:
'chat.completion'
,
newHistoryId
,
created
:
1688608930
,
...
responseData
model
:
app
.
chat
.
chatModel
,
},
id
:
historyId
||
''
,
model
:
''
,
usage
:
{
prompt_tokens
:
0
,
completion_tokens
:
0
,
total_tokens
:
0
},
usage
:
{
prompt_tokens
:
0
,
completion_tokens
:
0
,
total_tokens
:
0
},
choices
:
[
choices
:
[
{
message
:
{
role
:
'assistant'
,
content
:
response
},
finish_reason
:
'stop'
,
index
:
0
}
{
message
:
[{
role
:
'assistant'
,
content
:
answerText
}],
finish_reason
:
'stop'
,
index
:
0
}
]
]
});
});
}
}
}
catch
(
err
:
any
)
{
if
(
stream
)
{
res
.
status
(
500
);
sseErrRes
(
res
,
err
);
res
.
end
();
}
else
{
jsonRes
(
res
,
{
code
:
500
,
error
:
err
});
}
}
}
});
// api messages. [quote,context,systemPrompt,question]
export
async
function
dispatchModules
({
const
completePrompts
=
[
res
,
...
quotePrompt
,
modules
,
...
userSystemPrompt
,
params
=
{},
...
prompts
.
slice
(
0
,
-
1
),
variables
=
{},
...
userLimitPrompt
,
stream
=
false
prompt
}:
{
];
res
:
NextApiResponse
;
// chat temperature
modules
:
AppModuleItemType
[];
const
modelConstantsData
=
ChatModelMap
[
app
.
chat
.
chatModel
];
params
?:
Record
<
string
,
any
>
;
// FastGpt temperature range: 1~10
variables
?:
Record
<
string
,
any
>
;
const
temperature
=
(
modelConstantsData
.
maxTemperature
*
(
app
.
chat
.
temperature
/
10
)).
toFixed
(
stream
?:
boolean
;
2
})
{
);
const
runningModules
=
loadModules
(
modules
,
variables
);
let
storeData
:
Record
<
string
,
any
>
=
{};
let
responseData
:
Record
<
string
,
any
>
=
{};
let
answerText
=
''
;
await
sensitiveCheck
({
function
pushStore
({
input
:
`
${
userSystemPrompt
[
0
]?.
value
}
\
n$
{
userLimitPrompt
[
0
]?.
value
}
\
n$
{
prompt
.
value
}
`
isResponse
=
false
,
});
answer
,
data
=
{}
}:
{
isResponse
?:
boolean
;
answer
?:
string
;
data
?:
Record
<
string
,
any
>
;
})
{
if
(
isResponse
)
{
responseData
=
{
...
responseData
,
...
data
};
}
// start app api. responseText and totalTokens: valid only if stream = false
if
(
answer
)
{
const { streamResponse, responseMessages, responseText, totalTokens } =
answerText
+=
answer
;
await modelServiceToolMap.chatCompletion({
}
model: app.chat.chatModel,
apiKey: userOpenAiKey || apiKey,
temperature: +temperature,
maxToken: app.chat.maxToken,
messages: completePrompts,
stream,
res
});
console.log('api response time:', `
$
{(
Date
.
now
()
-
startTime
)
/
1000
}
s
`);
storeData
=
{
...
storeData
,
...
data
};
}
function
moduleInput
(
module
:
RunningModuleItemType
,
data
:
Record
<
string
,
any
>
=
{}
):
Promise
<
any
>
{
const
checkInputFinish
=
()
=>
{
return
!
module
.
inputs
.
find
((
item
:
any
)
=>
item
.
value
===
undefined
);
};
const
updateInputValue
=
(
key
:
string
,
value
:
any
)
=>
{
const
index
=
module
.
inputs
.
findIndex
((
item
:
any
)
=>
item
.
key
===
key
);
if
(
index
===
-
1
)
return
;
module
.
inputs
[
index
].
value
=
value
;
};
if (res.closed) return res.end
();
const
set
=
new
Set
();
// create a chatId
return
Promise
.
all
(
const newChatId = chatId === '' ? new Types.ObjectId() : undefined;
Object
.
entries
(
data
).
map
(([
key
,
val
]:
any
)
=>
{
updateInputValue
(
key
,
val
);
// response answer
if
(
!
set
.
has
(
module
.
moduleId
)
&&
checkInputFinish
())
{
const {
set
.
add
(
module
.
moduleId
);
textLen = 0,
return
moduleRun
(
module
);
answer = responseText,
}
tokens = totalTokens
})
} = await (async () => {
);
if (stream) {
}
// 创建响应流
function
moduleOutput
(
res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');
module
:
RunningModuleItemType
,
res.setHeader('Access-Control-Allow-Origin', '*');
result
:
Record
<
string
,
any
>
=
{}
res.setHeader('Transfer-Encoding', 'chunked');
):
Promise
<
any
>
{
res.setHeader('X-Accel-Buffering', 'no');
return
Promise
.
all
(
res.setHeader('Cache-Control', 'no-cache, no-transform');
module
.
outputs
.
map
((
outputItem
)
=>
{
step = 1;
if
(
result
[
outputItem
.
key
]
===
undefined
)
return
;
/* update output value */
outputItem
.
value
=
result
[
outputItem
.
key
];
try {
pushStore
({
// response newChatId and quota
isResponse
:
outputItem
.
response
,
sseResponse({
answer
:
outputItem
.
answer
?
outputItem
.
value
:
''
,
res,
data
:
{
event: sseResponseEventEnum.chatResponse,
[
outputItem
.
key
]:
outputItem
.
value
data: JSON.stringify({
}
newChatId,
});
quoteLen: rawSearch.length
/* update target */
return
Promise
.
all
(
outputItem
.
targets
.
map
((
target
:
any
)
=>
{
// find module
const
targetModule
=
runningModules
.
find
((
item
)
=>
item
.
moduleId
===
target
.
moduleId
);
if
(
!
targetModule
)
return
;
return
moduleInput
(
targetModule
,
{
[
target
.
key
]:
outputItem
.
value
});
})
})
);
})
);
}
async
function
moduleRun
(
module
:
RunningModuleItemType
):
Promise
<
any
>
{
if
(
res
.
closed
)
return
Promise
.
resolve
();
console
.
log
(
'run========='
,
module
.
type
,
module
.
url
);
// direct answer
if
(
module
.
type
===
AppModuleItemTypeEnum
.
answer
)
{
const
text
=
module
.
inputs
.
find
((
item
)
=>
item
.
key
===
SpecificInputEnum
.
answerText
)?.
value
||
''
;
pushStore
({
answer
:
text
});
});
// response answer
return
StreamAnswer
({
const { finishMessages, totalTokens, responseContent } = await V2_StreamResponse({
model: app.chat.chatModel,
res
,
res
,
chatResponse: streamResponse
,
stream
,
prompts: responseMessages
text
:
text
});
});
return {
answer: responseContent,
textLen: finishMessages.map((item) => item.value).join('').length,
tokens: totalTokens
};
} catch (error) {
return Promise.reject(error);
}
}
} else {
return {
textLen: responseMessages.map((item) => item.value).join('').length
};
}
})();
// save chat history
if
(
module
.
type
===
AppModuleItemTypeEnum
.
switch
)
{
if (typeof chatId === 'string') {
return
moduleOutput
(
module
,
switchResponse
(
module
));
await saveChat({
newChatId,
chatId,
modelId: appId,
prompts: [
prompt,
{
_id: messages[messages.length - 1]._id,
obj: ChatRoleEnum.AI,
value: answer,
...(showAppDetail
? {
quote: rawSearch,
systemPrompt: `
$
{
userSystemPrompt
[
0
]?.
value
}
\
n
\
n$
{
userLimitPrompt
[
0
]?.
value
}
`
}
: {})
}
}
],
userId
if
(
(
module
.
type
===
AppModuleItemTypeEnum
.
http
||
module
.
type
===
AppModuleItemTypeEnum
.
initInput
)
&&
module
.
url
)
{
// get fetch params
const
params
:
Record
<
string
,
any
>
=
{};
module
.
inputs
.
forEach
((
item
:
any
)
=>
{
params
[
item
.
key
]
=
item
.
value
;
});
});
}
const
data
=
{
stream
,
...
params
};
// close response
// response data
if (stream) {
const
fetchRes
=
await
moduleFetch
({
res.end();
res
,
} else {
url
:
module
.
url
,
res.json({
data
...(showAppDetail
? {
rawSearch
}
: {}),
newChatId,
id: chatId || '',
object: 'chat.completion',
created: 1688608930,
model: app.chat.chatModel,
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: tokens },
choices: [
{ message: { role: 'assistant', content: answer }, finish_reason: 'stop', index: 0 }
]
});
});
return
moduleOutput
(
module
,
fetchRes
);
}
}
}
pushChatBill({
// start process width initInput
isPay: !userOpenAiKey,
const
initModules
=
runningModules
.
filter
(
chatModel: app.chat.chatModel,
(
item
)
=>
item
.
type
===
AppModuleItemTypeEnum
.
initInput
userId,
);
textLen,
tokens,
await
Promise
.
all
(
initModules
.
map
((
module
)
=>
moduleInput
(
module
,
params
)));
type: authType === 'apikey' ? BillTypeEnum.openapiChat : BillTypeEnum.chat
});
return
{
shareId &&
responseData
,
updateShareChatBill({
answerText
shareId,
};
tokens
}
function
loadModules
(
modules
:
AppModuleItemType
[],
variables
:
Record
<
string
,
any
>
):
RunningModuleItemType
[]
{
return
modules
.
map
((
module
)
=>
{
return
{
moduleId
:
module
.
moduleId
,
type
:
module
.
type
,
url
:
module
.
url
,
inputs
:
module
.
inputs
.
filter
((
item
)
=>
item
.
type
!==
FlowInputItemTypeEnum
.
target
||
item
.
connected
)
// filter unconnected target input
.
map
((
item
)
=>
{
if
(
typeof
item
.
value
!==
'string'
)
{
return
{
key
:
item
.
key
,
value
:
item
.
value
};
}
// variables replace
const
replacedVal
=
item
.
value
.
replace
(
/{{
(
.*
?)
}}/g
,
(
match
,
key
)
=>
variables
[
key
.
trim
()]
||
match
);
return
{
key
:
item
.
key
,
value
:
replacedVal
};
}),
outputs
:
module
.
outputs
.
map
((
item
)
=>
({
key
:
item
.
key
,
answer
:
item
.
key
===
SpecificInputEnum
.
answerText
,
response
:
item
.
response
,
value
:
undefined
,
targets
:
item
.
targets
}))
};
});
});
} catch (err: any) {
}
res.status(500);
function
StreamAnswer
({
if (step === 1) {
sseResponse({
res
,
res
,
event: sseResponseEventEnum.error,
stream
=
false
,
data: JSON.stringify(err)
text
=
''
});
}:
{
res.end();
res
:
NextApiResponse
;
} else {
stream
?:
boolean
;
jsonRes(res, {
text
?:
string
;
code: 500,
})
{
error: err
if
(
stream
&&
text
)
{
return
sseResponse
({
res
,
event
:
sseResponseEventEnum
.
answer
,
data
:
textAdaptGptResponse
({
text
:
text
.
replace
(
/
\\
n/g
,
'\n'
)
})
});
});
}
}
return
text
;
}
function
switchResponse
(
module
:
RunningModuleItemType
)
{
const
val
=
module
?.
inputs
?.[
0
]?.
value
;
if
(
val
)
{
return
{
true
:
1
};
}
}
});
return
{
false
:
1
};
}
client/src/pages/api/openapi/v1/chat/completions2.ts
deleted
100644 → 0
View file @
eb768d9c
import
type
{
NextApiRequest
,
NextApiResponse
}
from
'next'
;
import
{
connectToDatabase
}
from
'@/service/mongo'
;
import
{
authUser
,
authApp
,
getApiKey
,
authShareChat
}
from
'@/service/utils/auth'
;
import
{
sseErrRes
,
jsonRes
}
from
'@/service/response'
;
import
{
ChatRoleEnum
,
sseResponseEventEnum
}
from
'@/constants/chat'
;
import
{
withNextCors
}
from
'@/service/utils/tools'
;
import
type
{
CreateChatCompletionRequest
}
from
'openai'
;
import
{
gptMessage2ChatType
,
textAdaptGptResponse
}
from
'@/utils/adapt'
;
import
{
getChatHistory
}
from
'./getHistory'
;
import
{
saveChat
}
from
'@/pages/api/chat/saveChat'
;
import
{
sseResponse
}
from
'@/service/utils/tools'
;
import
{
type
ChatCompletionRequestMessage
}
from
'openai'
;
import
{
SpecificInputEnum
,
AppModuleItemTypeEnum
}
from
'@/constants/app'
;
import
{
model
,
Types
}
from
'mongoose'
;
import
{
moduleFetch
}
from
'@/service/api/request'
;
import
{
AppModuleItemType
,
RunningModuleItemType
}
from
'@/types/app'
;
import
{
FlowInputItemTypeEnum
,
FlowOutputItemTypeEnum
}
from
'@/constants/flow'
;
import
{
SystemInputEnum
}
from
'@/constants/app'
;
export
type
MessageItemType
=
ChatCompletionRequestMessage
&
{
_id
?:
string
};
type
FastGptWebChatProps
=
{
chatId
?:
string
;
// undefined: nonuse history, '': new chat, 'xxxxx': use history
appId
?:
string
;
};
type
FastGptShareChatProps
=
{
shareId
?:
string
;
};
export
type
Props
=
CreateChatCompletionRequest
&
FastGptWebChatProps
&
FastGptShareChatProps
&
{
messages
:
MessageItemType
[];
stream
?:
boolean
;
variables
:
Record
<
string
,
any
>
;
};
export
type
ChatResponseType
=
{
newChatId
:
string
;
quoteLen
?:
number
;
};
export
default
withNextCors
(
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
res
.
on
(
'close'
,
()
=>
{
res
.
end
();
});
res
.
on
(
'error'
,
()
=>
{
console
.
log
(
'error: '
,
'request error'
);
res
.
end
();
});
let
{
chatId
,
appId
,
shareId
,
stream
=
false
,
messages
=
[],
variables
=
{}
}
=
req
.
body
as
Props
;
try
{
if
(
!
messages
)
{
throw
new
Error
(
'Prams Error'
);
}
if
(
!
Array
.
isArray
(
messages
))
{
throw
new
Error
(
'messages is not array'
);
}
await
connectToDatabase
();
let
startTime
=
Date
.
now
();
/* user auth */
const
{
userId
,
appId
:
authAppid
,
authType
}
=
await
(
shareId
?
authShareChat
({
shareId
})
:
authUser
({
req
}));
appId
=
appId
?
appId
:
authAppid
;
if
(
!
appId
)
{
throw
new
Error
(
'appId is empty'
);
}
// auth app, get history
const
[{
app
},
{
history
}]
=
await
Promise
.
all
([
authApp
({
appId
,
userId
}),
getChatHistory
({
chatId
,
userId
})
]);
const
prompts
=
history
.
concat
(
gptMessage2ChatType
(
messages
));
if
(
prompts
[
prompts
.
length
-
1
].
obj
===
'AI'
)
{
prompts
.
pop
();
}
// user question
const
prompt
=
prompts
.
pop
();
if
(
!
prompt
)
{
throw
new
Error
(
'Question is empty'
);
}
const
newChatId
=
chatId
===
''
?
new
Types
.
ObjectId
()
:
undefined
;
if
(
stream
&&
newChatId
)
{
res
.
setHeader
(
'newChatId'
,
String
(
newChatId
));
}
/* start process */
const
{
responseData
,
answerText
}
=
await
dispatchModules
({
res
,
modules
:
app
.
modules
,
variables
,
params
:
{
history
:
prompts
,
userChatInput
:
prompt
.
value
},
stream
});
// save chat
if
(
typeof
chatId
===
'string'
)
{
await
saveChat
({
chatId
,
newChatId
,
modelId
:
appId
,
prompts
:
[
prompt
,
{
_id
:
messages
[
messages
.
length
-
1
].
_id
,
obj
:
ChatRoleEnum
.
AI
,
value
:
answerText
,
responseData
}
],
userId
});
}
if
(
stream
)
{
sseResponse
({
res
,
event
:
sseResponseEventEnum
.
answer
,
data
:
'[DONE]'
});
sseResponse
({
res
,
event
:
sseResponseEventEnum
.
appStreamResponse
,
data
:
JSON
.
stringify
(
responseData
)
});
res
.
end
();
}
else
{
res
.
json
({
data
:
{
newChatId
,
...
responseData
},
id
:
chatId
||
''
,
model
:
''
,
usage
:
{
prompt_tokens
:
0
,
completion_tokens
:
0
,
total_tokens
:
0
},
choices
:
[
{
message
:
[{
role
:
'assistant'
,
content
:
answerText
}],
finish_reason
:
'stop'
,
index
:
0
}
]
});
}
}
catch
(
err
:
any
)
{
if
(
stream
)
{
res
.
status
(
500
);
sseErrRes
(
res
,
err
);
res
.
end
();
}
else
{
jsonRes
(
res
,
{
code
:
500
,
error
:
err
});
}
}
});
export
async
function
dispatchModules
({
res
,
modules
,
params
=
{},
variables
=
{},
stream
=
false
}:
{
res
:
NextApiResponse
;
modules
:
AppModuleItemType
[];
params
?:
Record
<
string
,
any
>
;
variables
?:
Record
<
string
,
any
>
;
stream
?:
boolean
;
})
{
const
runningModules
=
loadModules
(
modules
,
variables
);
let
storeData
:
Record
<
string
,
any
>
=
{};
let
responseData
:
Record
<
string
,
any
>
=
{};
let
answerText
=
''
;
function
pushStore
({
isResponse
=
false
,
answer
,
data
=
{}
}:
{
isResponse
?:
boolean
;
answer
?:
string
;
data
?:
Record
<
string
,
any
>
;
})
{
if
(
isResponse
)
{
responseData
=
{
...
responseData
,
...
data
};
}
if
(
answer
)
{
answerText
+=
answer
;
}
storeData
=
{
...
storeData
,
...
data
};
}
function
moduleInput
(
module
:
RunningModuleItemType
,
data
:
Record
<
string
,
any
>
=
{}
):
Promise
<
any
>
{
const
checkInputFinish
=
()
=>
{
return
!
module
.
inputs
.
find
((
item
:
any
)
=>
item
.
value
===
undefined
);
};
const
updateInputValue
=
(
key
:
string
,
value
:
any
)
=>
{
const
index
=
module
.
inputs
.
findIndex
((
item
:
any
)
=>
item
.
key
===
key
);
if
(
index
===
-
1
)
return
;
module
.
inputs
[
index
].
value
=
value
;
};
const
set
=
new
Set
();
return
Promise
.
all
(
Object
.
entries
(
data
).
map
(([
key
,
val
]:
any
)
=>
{
updateInputValue
(
key
,
val
);
if
(
!
set
.
has
(
module
.
moduleId
)
&&
checkInputFinish
())
{
set
.
add
(
module
.
moduleId
);
return
moduleRun
(
module
);
}
})
);
}
function
moduleOutput
(
module
:
RunningModuleItemType
,
result
:
Record
<
string
,
any
>
=
{}
):
Promise
<
any
>
{
return
Promise
.
all
(
module
.
outputs
.
map
((
outputItem
)
=>
{
if
(
result
[
outputItem
.
key
]
===
undefined
)
return
;
/* update output value */
outputItem
.
value
=
result
[
outputItem
.
key
];
pushStore
({
isResponse
:
outputItem
.
response
,
answer
:
outputItem
.
answer
?
outputItem
.
value
:
''
,
data
:
{
[
outputItem
.
key
]:
outputItem
.
value
}
});
/* update target */
return
Promise
.
all
(
outputItem
.
targets
.
map
((
target
:
any
)
=>
{
// find module
const
targetModule
=
runningModules
.
find
((
item
)
=>
item
.
moduleId
===
target
.
moduleId
);
if
(
!
targetModule
)
return
;
return
moduleInput
(
targetModule
,
{
[
target
.
key
]:
outputItem
.
value
});
})
);
})
);
}
async
function
moduleRun
(
module
:
RunningModuleItemType
):
Promise
<
any
>
{
if
(
res
.
closed
)
return
Promise
.
resolve
();
console
.
log
(
'run========='
,
module
.
type
,
module
.
url
);
// direct answer
if
(
module
.
type
===
AppModuleItemTypeEnum
.
answer
)
{
const
text
=
module
.
inputs
.
find
((
item
)
=>
item
.
key
===
SpecificInputEnum
.
answerText
)?.
value
||
''
;
pushStore
({
answer
:
text
});
return
StreamAnswer
({
res
,
stream
,
text
:
text
});
}
if
(
module
.
type
===
AppModuleItemTypeEnum
.
switch
)
{
return
moduleOutput
(
module
,
switchResponse
(
module
));
}
if
(
(
module
.
type
===
AppModuleItemTypeEnum
.
http
||
module
.
type
===
AppModuleItemTypeEnum
.
initInput
)
&&
module
.
url
)
{
// get fetch params
const
params
:
Record
<
string
,
any
>
=
{};
module
.
inputs
.
forEach
((
item
:
any
)
=>
{
params
[
item
.
key
]
=
item
.
value
;
});
const
data
=
{
stream
,
...
params
};
// response data
const
fetchRes
=
await
moduleFetch
({
res
,
url
:
module
.
url
,
data
});
return
moduleOutput
(
module
,
fetchRes
);
}
}
// start process width initInput
const
initModules
=
runningModules
.
filter
(
(
item
)
=>
item
.
type
===
AppModuleItemTypeEnum
.
initInput
);
await
Promise
.
all
(
initModules
.
map
((
module
)
=>
moduleInput
(
module
,
params
)));
return
{
responseData
,
answerText
};
}
function
loadModules
(
modules
:
AppModuleItemType
[],
variables
:
Record
<
string
,
any
>
):
RunningModuleItemType
[]
{
return
modules
.
map
((
module
)
=>
{
return
{
moduleId
:
module
.
moduleId
,
type
:
module
.
type
,
url
:
module
.
url
,
inputs
:
module
.
inputs
.
filter
((
item
)
=>
item
.
type
!==
FlowInputItemTypeEnum
.
target
||
item
.
connected
)
// filter unconnected target input
.
map
((
item
)
=>
{
if
(
typeof
item
.
value
!==
'string'
)
{
return
{
key
:
item
.
key
,
value
:
item
.
value
};
}
// variables replace
const
replacedVal
=
item
.
value
.
replace
(
/{{
(
.*
?)
}}/g
,
(
match
,
key
)
=>
variables
[
key
.
trim
()]
||
match
);
return
{
key
:
item
.
key
,
value
:
replacedVal
};
}),
outputs
:
module
.
outputs
.
map
((
item
)
=>
({
key
:
item
.
key
,
answer
:
item
.
key
===
SpecificInputEnum
.
answerText
,
response
:
item
.
response
,
value
:
undefined
,
targets
:
item
.
targets
}))
};
});
}
function
StreamAnswer
({
res
,
stream
=
false
,
text
=
''
}:
{
res
:
NextApiResponse
;
stream
?:
boolean
;
text
?:
string
;
})
{
if
(
stream
&&
text
)
{
return
sseResponse
({
res
,
event
:
sseResponseEventEnum
.
answer
,
data
:
textAdaptGptResponse
({
text
:
text
.
replace
(
/
\\
n/g
,
'\n'
)
})
});
}
return
text
;
}
function
switchResponse
(
module
:
RunningModuleItemType
)
{
const
val
=
module
?.
inputs
?.[
0
]?.
value
;
if
(
val
)
{
return
{
true
:
1
};
}
return
{
false
:
1
};
}
client/src/pages/api/openapi/v1/chat/getHistory.ts
View file @
b2e2f60e
...
@@ -7,7 +7,7 @@ import { Types } from 'mongoose';
...
@@ -7,7 +7,7 @@ import { Types } from 'mongoose';
import
type
{
ChatItemType
}
from
'@/types/chat'
;
import
type
{
ChatItemType
}
from
'@/types/chat'
;
export
type
Props
=
{
export
type
Props
=
{
chat
Id
?:
string
;
history
Id
?:
string
;
limit
?:
number
;
limit
?:
number
;
};
};
export
type
Response
=
{
history
:
ChatItemType
[]
};
export
type
Response
=
{
history
:
ChatItemType
[]
};
...
@@ -16,11 +16,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -16,11 +16,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
try
{
try
{
await
connectToDatabase
();
await
connectToDatabase
();
const
{
userId
}
=
await
authUser
({
req
});
const
{
userId
}
=
await
authUser
({
req
});
const
{
chat
Id
,
limit
}
=
req
.
body
as
Props
;
const
{
history
Id
,
limit
}
=
req
.
body
as
Props
;
jsonRes
<
Response
>
(
res
,
{
jsonRes
<
Response
>
(
res
,
{
data
:
await
getChatHistory
({
data
:
await
getChatHistory
({
chat
Id
,
history
Id
,
userId
,
userId
,
limit
limit
})
})
...
@@ -34,16 +34,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -34,16 +34,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
}
export
async
function
getChatHistory
({
export
async
function
getChatHistory
({
chat
Id
,
history
Id
,
userId
,
userId
,
limit
=
50
limit
=
50
}:
Props
&
{
userId
:
string
}):
Promise
<
Response
>
{
}:
Props
&
{
userId
:
string
}):
Promise
<
Response
>
{
if
(
!
chat
Id
)
{
if
(
!
history
Id
)
{
return
{
history
:
[]
};
return
{
history
:
[]
};
}
}
const
history
=
await
Chat
.
aggregate
([
const
history
=
await
Chat
.
aggregate
([
{
$match
:
{
_id
:
new
Types
.
ObjectId
(
chat
Id
),
userId
:
new
Types
.
ObjectId
(
userId
)
}
},
{
$match
:
{
_id
:
new
Types
.
ObjectId
(
history
Id
),
userId
:
new
Types
.
ObjectId
(
userId
)
}
},
{
{
$project
:
{
$project
:
{
content
:
{
content
:
{
...
...
client/src/pages/app/detail/components/edit/components/ChatTest.tsx
View file @
b2e2f60e
...
@@ -58,7 +58,6 @@ const ChatTest = (
...
@@ -58,7 +58,6 @@ const ChatTest = (
?.
find
((
item
)
=>
item
.
flowType
===
FlowModuleTypeEnum
.
historyNode
)
?.
find
((
item
)
=>
item
.
flowType
===
FlowModuleTypeEnum
.
historyNode
)
?.
inputs
?.
find
((
item
)
=>
item
.
key
===
'maxContext'
)?.
value
||
0
;
?.
inputs
?.
find
((
item
)
=>
item
.
key
===
'maxContext'
)?.
value
||
0
;
const
history
=
messages
.
slice
(
-
historyMaxLen
-
2
,
-
2
);
const
history
=
messages
.
slice
(
-
historyMaxLen
-
2
,
-
2
);
console
.
log
(
history
,
'history===='
);
// 流请求,获取数据
// 流请求,获取数据
const
{
responseText
}
=
await
streamFetch
({
const
{
responseText
}
=
await
streamFetch
({
...
@@ -87,8 +86,6 @@ const ChatTest = (
...
@@ -87,8 +86,6 @@ const ChatTest = (
useImperativeHandle
(
ref
,
()
=>
({
useImperativeHandle
(
ref
,
()
=>
({
resetChatTest
()
{
resetChatTest
()
{
console
.
log
(
ChatBoxRef
.
current
,
'==='
);
ChatBoxRef
.
current
?.
resetHistory
([]);
ChatBoxRef
.
current
?.
resetHistory
([]);
ChatBoxRef
.
current
?.
resetVariables
();
ChatBoxRef
.
current
?.
resetVariables
();
}
}
...
@@ -147,6 +144,7 @@ const ChatTest = (
...
@@ -147,6 +144,7 @@ const ChatTest = (
variableModules=
{
variableModules
}
variableModules=
{
variableModules
}
welcomeText=
{
welcomeText
}
welcomeText=
{
welcomeText
}
onStartChat=
{
startChat
}
onStartChat=
{
startChat
}
onDelMessage=
{
()
=>
{}
}
/>
/>
</
Box
>
</
Box
>
</
Flex
>
</
Flex
>
...
...
client/src/pages/appStore/components/list.tsx
View file @
b2e2f60e
...
@@ -12,7 +12,7 @@ const ShareModelList = ({
...
@@ -12,7 +12,7 @@ const ShareModelList = ({
onclickCollection
onclickCollection
}:
{
}:
{
models
:
ShareAppItem
[];
models
:
ShareAppItem
[];
onclickCollection
:
(
model
Id
:
string
)
=>
void
;
onclickCollection
:
(
app
Id
:
string
)
=>
void
;
})
=>
{
})
=>
{
const
router
=
useRouter
();
const
router
=
useRouter
();
...
...
client/src/pages/appStore/index.tsx
View file @
b2e2f60e
...
@@ -12,8 +12,6 @@ const modelList = () => {
...
@@ -12,8 +12,6 @@ const modelList = () => {
const
{
Loading
}
=
useLoading
();
const
{
Loading
}
=
useLoading
();
const
lastSearch
=
useRef
(
''
);
const
lastSearch
=
useRef
(
''
);
const
[
searchText
,
setSearchText
]
=
useState
(
''
);
const
[
searchText
,
setSearchText
]
=
useState
(
''
);
const
{
refreshModel
}
=
useUserStore
();
/* 加载模型 */
/* 加载模型 */
const
{
const
{
data
:
models
,
data
:
models
,
...
@@ -30,16 +28,15 @@ const modelList = () => {
...
@@ -30,16 +28,15 @@ const modelList = () => {
});
});
const
onclickCollection
=
useCallback
(
const
onclickCollection
=
useCallback
(
async
(
model
Id
:
string
)
=>
{
async
(
app
Id
:
string
)
=>
{
try
{
try
{
await
triggerModelCollection
(
model
Id
);
await
triggerModelCollection
(
app
Id
);
getData
(
pageNum
);
getData
(
pageNum
);
refreshModel
.
removeModelDetail
(
modelId
);
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
log
(
error
);
console
.
log
(
error
);
}
}
},
},
[
getData
,
pageNum
,
refreshModel
]
[
getData
,
pageNum
]
);
);
return
(
return
(
...
...
client/src/pages/chat/components/ChatHistorySlider.tsx
View file @
b2e2f60e
import
React
from
'react'
;
import
React
,
{
useMemo
}
from
'react'
;
import
{
AddIcon
}
from
'@chakra-ui/icons'
;
import
{
import
{
Box
,
Box
,
Button
,
Button
,
...
@@ -10,13 +9,16 @@ import {
...
@@ -10,13 +9,16 @@ import {
MenuList
,
MenuList
,
MenuItem
MenuItem
}
from
'@chakra-ui/react'
;
}
from
'@chakra-ui/react'
;
import
{
useRouter
}
from
'next/router'
;
import
MyIcon
from
'@/components/Icon'
;
import
MyIcon
from
'@/components/Icon'
;
import
type
{
ShareChatHistoryItemType
,
ExportChatType
}
from
'@/types/chat'
;
import
{
useChatStore
}
from
'@/store/chat'
;
import
{
useGlobalStore
}
from
'@/store/global'
;
import
{
useGlobalStore
}
from
'@/store/global'
;
import
Avatar
from
'@/components/Avatar'
;
import
Avatar
from
'@/components/Avatar'
;
type
HistoryItemType
=
{
id
:
string
;
title
:
string
;
top
?:
boolean
;
};
const
ChatHistorySlider
=
({
const
ChatHistorySlider
=
({
appName
,
appName
,
appAvatar
,
appAvatar
,
...
@@ -24,23 +26,26 @@ const ChatHistorySlider = ({
...
@@ -24,23 +26,26 @@ const ChatHistorySlider = ({
activeHistoryId
,
activeHistoryId
,
onChangeChat
,
onChangeChat
,
onDelHistory
,
onDelHistory
,
onSetHistoryTop
,
onCloseSlider
onCloseSlider
}:
{
}:
{
appName
:
string
;
appName
:
string
;
appAvatar
:
string
;
appAvatar
:
string
;
history
:
{
history
:
HistoryItemType
[];
id
:
string
;
title
:
string
;
}[];
activeHistoryId
:
string
;
activeHistoryId
:
string
;
onChangeChat
:
(
historyId
?:
string
)
=>
void
;
onChangeChat
:
(
historyId
?:
string
)
=>
void
;
onDelHistory
:
(
historyId
:
string
)
=>
void
;
onDelHistory
:
(
historyId
:
string
)
=>
void
;
onSetHistoryTop
?:
(
e
:
{
historyId
:
string
;
top
:
boolean
})
=>
void
;
onCloseSlider
:
()
=>
void
;
onCloseSlider
:
()
=>
void
;
})
=>
{
})
=>
{
const
router
=
useRouter
();
const
theme
=
useTheme
();
const
theme
=
useTheme
();
const
{
isPc
}
=
useGlobalStore
();
const
{
isPc
}
=
useGlobalStore
();
const
concatHistory
=
useMemo
<
HistoryItemType
[]
>
(
()
=>
(
!
activeHistoryId
?
[{
id
:
activeHistoryId
,
title
:
'新对话'
}].
concat
(
history
)
:
history
),
[
activeHistoryId
,
history
]
);
return
(
return
(
<
Flex
<
Flex
position=
{
'relative'
}
position=
{
'relative'
}
...
@@ -48,11 +53,11 @@ const ChatHistorySlider = ({
...
@@ -48,11 +53,11 @@ const ChatHistorySlider = ({
w=
{
'100%'
}
w=
{
'100%'
}
h=
{
'100%'
}
h=
{
'100%'
}
bg=
{
'white'
}
bg=
{
'white'
}
px=
{
[
2
,
5
]
}
borderRight=
{
[
''
,
theme
.
borders
.
base
]
}
borderRight=
{
[
''
,
theme
.
borders
.
base
]
}
whiteSpace=
{
'nowrap'
}
>
>
{
isPc
&&
(
{
isPc
&&
(
<
Flex
pt=
{
5
}
pb=
{
2
}
alignItems=
{
'center'
}
whiteSpace=
{
'nowrap
'
}
>
<
Flex
pt=
{
5
}
pb=
{
2
}
px=
{
[
2
,
5
]
}
alignItems=
{
'center
'
}
>
<
Avatar
src=
{
appAvatar
}
/>
<
Avatar
src=
{
appAvatar
}
/>
<
Box
ml=
{
2
}
fontWeight=
{
'bold'
}
className=
{
'textEllipsis'
}
>
<
Box
ml=
{
2
}
fontWeight=
{
'bold'
}
className=
{
'textEllipsis'
}
>
{
appName
}
{
appName
}
...
@@ -60,7 +65,7 @@ const ChatHistorySlider = ({
...
@@ -60,7 +65,7 @@ const ChatHistorySlider = ({
</
Flex
>
</
Flex
>
)
}
)
}
{
/* 新对话 */
}
{
/* 新对话 */
}
<
Box
w=
{
'100%'
}
h=
{
'36px'
}
my=
{
5
}
>
<
Box
w=
{
'100%'
}
px=
{
[
2
,
5
]
}
h=
{
'36px'
}
my=
{
5
}
>
<
Button
<
Button
variant=
{
'base'
}
variant=
{
'base'
}
w=
{
'100%'
}
w=
{
'100%'
}
...
@@ -76,8 +81,8 @@ const ChatHistorySlider = ({
...
@@ -76,8 +81,8 @@ const ChatHistorySlider = ({
</
Box
>
</
Box
>
{
/* chat history */
}
{
/* chat history */
}
<
Box
flex=
{
'1 0 0'
}
h=
{
0
}
overflow=
{
'overlay'
}
>
<
Box
flex=
{
'1 0 0'
}
h=
{
0
}
px=
{
[
2
,
5
]
}
overflow=
{
'overlay'
}
>
{
h
istory
.
map
((
item
)
=>
(
{
concatH
istory
.
map
((
item
)
=>
(
<
Flex
<
Flex
position=
{
'relative'
}
position=
{
'relative'
}
key=
{
item
.
id
}
key=
{
item
.
id
}
...
@@ -94,6 +99,7 @@ const ChatHistorySlider = ({
...
@@ -94,6 +99,7 @@ const ChatHistorySlider = ({
display
:
'block'
display
:
'block'
}
}
}
}
}
}
bg=
{
item
.
top
?
'#E6F6F6 !important'
:
''
}
{
...
(
item
.
id
===
activeHistoryId
{
...
(
item
.
id
===
activeHistoryId
?
{
?
{
backgroundColor
:
'
myBlue
.
100
!
important
',
backgroundColor
:
'
myBlue
.
100
!
important
',
...
@@ -109,6 +115,7 @@ const ChatHistorySlider = ({
...
@@ -109,6 +115,7 @@ const ChatHistorySlider = ({
<
Box
flex=
{
'1 0 0'
}
ml=
{
3
}
className=
"textEllipsis"
>
<
Box
flex=
{
'1 0 0'
}
ml=
{
3
}
className=
"textEllipsis"
>
{
item
.
title
}
{
item
.
title
}
</
Box
>
</
Box
>
{
!!
item
.
id
&&
(
<
Box
className=
"more"
display=
{
[
'block'
,
'none'
]
}
>
<
Box
className=
"more"
display=
{
[
'block'
,
'none'
]
}
>
<
Menu
autoSelect=
{
false
}
isLazy
offset=
{
[
0
,
5
]
}
>
<
Menu
autoSelect=
{
false
}
isLazy
offset=
{
[
0
,
5
]
}
>
<
MenuButton
<
MenuButton
...
@@ -122,10 +129,17 @@ const ChatHistorySlider = ({
...
@@ -122,10 +129,17 @@ const ChatHistorySlider = ({
<
MyIcon
name=
{
'more'
}
w=
{
'14px'
}
p=
{
1
}
/>
<
MyIcon
name=
{
'more'
}
w=
{
'14px'
}
p=
{
1
}
/>
</
MenuButton
>
</
MenuButton
>
<
MenuList
color=
{
'myGray.700'
}
minW=
{
`90px !important`
}
>
<
MenuList
color=
{
'myGray.700'
}
minW=
{
`90px !important`
}
>
<
MenuItem
>
{
onSetHistoryTop
&&
(
<
MenuItem
onClick=
{
(
e
)
=>
{
e
.
stopPropagation
();
onSetHistoryTop
({
historyId
:
item
.
id
,
top
:
!
item
.
top
});
}
}
>
<
MyIcon
mr=
{
2
}
name=
{
'setTop'
}
w=
{
'16px'
}
></
MyIcon
>
<
MyIcon
mr=
{
2
}
name=
{
'setTop'
}
w=
{
'16px'
}
></
MyIcon
>
置顶
{
item
.
top
?
'取消置顶'
:
'置顶'
}
</
MenuItem
>
</
MenuItem
>
)
}
<
MenuItem
<
MenuItem
_hover=
{
{
color
:
'red.500'
}
}
_hover=
{
{
color
:
'red.500'
}
}
onClick=
{
(
e
)
=>
{
onClick=
{
(
e
)
=>
{
...
@@ -142,16 +156,9 @@ const ChatHistorySlider = ({
...
@@ -142,16 +156,9 @@ const ChatHistorySlider = ({
</
MenuList
>
</
MenuList
>
</
Menu
>
</
Menu
>
</
Box
>
</
Box
>
)
}
</
Flex
>
</
Flex
>
))
}
))
}
{
history
.
length
===
0
&&
(
<
Flex
h=
{
'100%'
}
flexDirection=
{
'column'
}
alignItems=
{
'center'
}
pt=
{
'30vh'
}
>
<
MyIcon
name=
"empty"
w=
{
'48px'
}
h=
{
'48px'
}
color=
{
'transparent'
}
/>
<
Box
mt=
{
2
}
color=
{
'myGray.500'
}
>
还没有聊天记录
</
Box
>
</
Flex
>
)
}
</
Box
>
</
Box
>
</
Flex
>
</
Flex
>
);
);
...
...
client/src/pages/chat/components/History.tsx
deleted
100644 → 0
View file @
eb768d9c
import
React
,
{
useCallback
,
useRef
,
useState
,
useMemo
}
from
'react'
;
import
type
{
MouseEvent
}
from
'react'
;
import
{
AddIcon
}
from
'@chakra-ui/icons'
;
import
{
Box
,
Button
,
Flex
,
useTheme
,
Menu
,
MenuList
,
MenuItem
,
useOutsideClick
}
from
'@chakra-ui/react'
;
import
{
ChatIcon
}
from
'@chakra-ui/icons'
;
import
{
useQuery
}
from
'@tanstack/react-query'
;
import
{
useRouter
}
from
'next/router'
;
import
{
useLoading
}
from
'@/hooks/useLoading'
;
import
{
useUserStore
}
from
'@/store/user'
;
import
MyIcon
from
'@/components/Icon'
;
import
type
{
HistoryItemType
,
ExportChatType
}
from
'@/types/chat'
;
import
{
useChatStore
}
from
'@/store/chat'
;
import
ModelList
from
'./ModelList'
;
import
{
useGlobalStore
}
from
'@/store/global'
;
import
styles
from
'../index.module.scss'
;
import
{
useEditInfo
}
from
'@/hooks/useEditInfo'
;
import
{
putChatHistory
}
from
'@/api/chat'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
formatTimeToChatTime
,
getErrText
}
from
'@/utils/tools'
;
const
PcSliderBar
=
({
onclickDelHistory
,
onclickExportChat
}:
{
onclickDelHistory
:
(
historyId
:
string
)
=>
Promise
<
void
>
;
onclickExportChat
:
(
type
:
ExportChatType
)
=>
void
;
})
=>
{
const
router
=
useRouter
();
const
{
toast
}
=
useToast
();
const
{
modelId
=
''
,
chatId
=
''
}
=
router
.
query
as
{
modelId
:
string
;
chatId
:
string
;
};
const
ContextMenuRef
=
useRef
(
null
);
const
onclickContext
=
useRef
(
false
);
const
theme
=
useTheme
();
const
{
isPc
}
=
useGlobalStore
();
const
{
Loading
,
setIsLoading
}
=
useLoading
();
const
[
contextMenuData
,
setContextMenuData
]
=
useState
<
{
left
:
number
;
top
:
number
;
history
:
HistoryItemType
;
}
>
();
const
{
history
,
loadHistory
}
=
useChatStore
();
const
{
myApps
,
myCollectionApps
,
loadMyModels
}
=
useUserStore
();
const
models
=
useMemo
(()
=>
[...
myApps
,
...
myCollectionApps
],
[
myCollectionApps
,
myApps
]);
// custom title edit
const
{
onOpenModal
,
EditModal
:
EditTitleModal
}
=
useEditInfo
({
title
:
'自定义历史记录标题'
,
placeholder
:
'如果设置为空,会自动跟随聊天记录。'
});
// close contextMenu
useOutsideClick
({
ref
:
ContextMenuRef
,
handler
:
()
=>
{
setTimeout
(()
=>
{
if
(
contextMenuData
&&
!
onclickContext
.
current
)
{
setContextMenuData
(
undefined
);
}
},
10
);
setTimeout
(()
=>
{
onclickContext
.
current
=
false
;
},
10
);
}
});
const
onclickContextMenu
=
useCallback
(
(
e
:
MouseEvent
<
HTMLDivElement
>
,
history
:
HistoryItemType
)
=>
{
e
.
preventDefault
();
// 阻止默认右键菜单
if
(
!
isPc
)
return
;
onclickContext
.
current
=
true
;
setContextMenuData
({
left
:
e
.
clientX
,
top
:
e
.
clientY
,
history
});
},
[
isPc
]
);
useQuery
([
'loadModels'
],
loadMyModels
);
const
{
isLoading
:
isLoadingHistory
}
=
useQuery
([
'loadingHistory'
],
()
=>
loadHistory
({
pageNum
:
1
})
);
return
(
<
Flex
position=
{
'relative'
}
flexDirection=
{
'column'
}
w=
{
'100%'
}
h=
{
'100%'
}
bg=
{
'white'
}
borderRight=
{
[
''
,
theme
.
borders
.
base
]
}
>
{
/* 新对话 */
}
{
isPc
&&
(
<
Box
className=
{
styles
.
newChat
}
zIndex=
{
1001
}
w=
{
'90%'
}
h=
{
'40px'
}
my=
{
5
}
mx=
{
'auto'
}
position=
{
'relative'
}
>
<
Button
variant=
{
'base'
}
w=
{
'100%'
}
h=
{
'100%'
}
leftIcon=
{
<
AddIcon
/>
}
onClick=
{
()
=>
router
.
replace
(
`/chat?appId=${modelId}`
)
}
>
新对话
</
Button
>
{
models
.
length
>
1
&&
(
<
Box
className=
{
styles
.
modelListContainer
}
position=
{
'absolute'
}
w=
{
'115%'
}
left=
{
0
}
top=
{
'40px'
}
transition=
{
'0.15s ease-out'
}
bg=
{
'white'
}
>
<
Box
className=
{
styles
.
modelList
}
mt=
{
'6px'
}
h=
{
'calc(100% - 6px)'
}
overflow=
{
'overlay'
}
>
<
ModelList
models=
{
models
}
modelId=
{
modelId
}
/>
</
Box
>
</
Box
>
)
}
</
Box
>
)
}
{
/* chat history */
}
<
Box
flex=
{
'1 0 0'
}
h=
{
0
}
pl=
{
2
}
overflowY=
{
'scroll'
}
userSelect=
{
'none'
}
>
{
history
.
map
((
item
)
=>
(
<
Flex
key=
{
item
.
_id
}
position=
{
'relative'
}
alignItems=
{
'center'
}
p=
{
3
}
borderRadius=
{
'md'
}
mb=
{
[
2
,
0
]
}
cursor=
{
'pointer'
}
transition=
{
'background-color .2s ease-in'
}
_hover=
{
{
backgroundImage
:
[
''
,
theme
.
lgColor
.
hoverBlueGradient
]
}
}
{
...
(
item
.
_id
===
chatId
?
{
backgroundImage
:
`
$
{
theme
.
lgColor
.
activeBlueGradient
}
!
important
`
}
:
{
bg
:
item
.
top
?
'
myGray
.
200'
:
''
})}
onClick=
{
()
=>
{
if
(
item
.
_id
===
chatId
)
return
;
if
(
isPc
)
{
router
.
replace
(
`/chat?appId=${item.modelId}&chatId=${item._id}`
);
}
else
{
router
.
push
(
`/chat?appId=${item.modelId}&chatId=${item._id}`
);
}
}
}
onContextMenu=
{
(
e
)
=>
onclickContextMenu
(
e
,
item
)
}
>
<
ChatIcon
fontSize=
{
'16px'
}
color=
{
'myGray.500'
}
/>
<
Box
flex=
{
'1 0 0'
}
w=
{
0
}
ml=
{
3
}
>
<
Flex
alignItems=
{
'center'
}
>
<
Box
flex=
{
'1 0 0'
}
w=
{
0
}
className=
"textEllipsis"
color=
{
'myGray.1000'
}
>
{
item
.
title
}
</
Box
>
<
Box
color=
{
'myGray.400'
}
fontSize=
{
'sm'
}
>
{
formatTimeToChatTime
(
item
.
updateTime
)
}
</
Box
>
</
Flex
>
<
Box
className=
"textEllipsis"
mt=
{
1
}
fontSize=
{
'sm'
}
color=
{
'myGray.500'
}
>
{
item
.
latestChat
||
'……'
}
</
Box
>
</
Box
>
{
/* phone quick delete */
}
{
!
isPc
&&
(
<
MyIcon
px=
{
3
}
name=
{
'delete'
}
w=
{
'16px'
}
onClickCapture=
{
async
(
e
)
=>
{
e
.
stopPropagation
();
setIsLoading
(
true
);
try
{
await
onclickDelHistory
(
item
.
_id
);
}
catch
(
error
)
{
console
.
log
(
error
);
}
setIsLoading
(
false
);
}
}
/>
)
}
</
Flex
>
))
}
{
!
isLoadingHistory
&&
history
.
length
===
0
&&
(
<
Flex
h=
{
'100%'
}
flexDirection=
{
'column'
}
alignItems=
{
'center'
}
pt=
{
'30vh'
}
>
<
MyIcon
name=
"empty"
w=
{
'48px'
}
h=
{
'48px'
}
color=
{
'transparent'
}
/>
<
Box
mt=
{
2
}
color=
{
'myGray.500'
}
>
还没有聊天记录
</
Box
>
</
Flex
>
)
}
</
Box
>
{
/* context menu */
}
{
contextMenuData
&&
(
<
Box
zIndex=
{
10
}
position=
{
'fixed'
}
top=
{
contextMenuData
.
top
}
left=
{
contextMenuData
.
left
}
>
<
Box
ref=
{
ContextMenuRef
}
></
Box
>
<
Menu
isOpen
>
<
MenuList
>
<
MenuItem
onClick=
{
async
()
=>
{
try
{
await
putChatHistory
({
chatId
:
contextMenuData
.
history
.
_id
,
top
:
!
contextMenuData
.
history
.
top
});
loadHistory
({
pageNum
:
1
,
init
:
true
});
}
catch
(
error
)
{}
}
}
>
{
contextMenuData
.
history
.
top
?
'取消置顶'
:
'置顶'
}
</
MenuItem
>
<
MenuItem
onClick=
{
async
()
=>
{
setIsLoading
(
true
);
try
{
await
onclickDelHistory
(
contextMenuData
.
history
.
_id
);
if
(
contextMenuData
.
history
.
_id
===
chatId
)
{
router
.
replace
(
`/chat?appId=${modelId}`
);
}
}
catch
(
error
)
{
console
.
log
(
error
);
}
setIsLoading
(
false
);
}
}
>
删除记录
</
MenuItem
>
<
MenuItem
onClick=
{
()
=>
onOpenModal
({
defaultVal
:
contextMenuData
.
history
.
title
,
onSuccess
:
async
(
val
:
string
)
=>
{
await
putChatHistory
({
chatId
:
contextMenuData
.
history
.
_id
,
customTitle
:
val
,
top
:
contextMenuData
.
history
.
top
});
toast
({
title
:
'自定义标题成功'
,
status
:
'success'
});
loadHistory
({
pageNum
:
1
,
init
:
true
});
},
onError
(
err
)
{
toast
({
title
:
getErrText
(
err
),
status
:
'error'
});
}
})
}
>
自定义标题
</
MenuItem
>
<
MenuItem
onClick=
{
()
=>
onclickExportChat
(
'html'
)
}
>
导出HTML格式
</
MenuItem
>
<
MenuItem
onClick=
{
()
=>
onclickExportChat
(
'pdf'
)
}
>
导出PDF格式
</
MenuItem
>
<
MenuItem
onClick=
{
()
=>
onclickExportChat
(
'md'
)
}
>
导出Markdown格式
</
MenuItem
>
</
MenuList
>
</
Menu
>
</
Box
>
)
}
<
EditTitleModal
/>
<
Loading
loading=
{
isLoadingHistory
}
fixed=
{
false
}
/>
</
Flex
>
);
};
export
default
PcSliderBar
;
client/src/pages/chat/components/ModelList.tsx
deleted
100644 → 0
View file @
eb768d9c
import
React
from
'react'
;
import
{
Box
,
Flex
}
from
'@chakra-ui/react'
;
import
{
useRouter
}
from
'next/router'
;
import
{
AppListItemType
}
from
'@/types/app'
;
import
Avatar
from
'@/components/Avatar'
;
const
ModelList
=
({
models
,
modelId
}:
{
models
:
AppListItemType
[];
modelId
:
string
})
=>
{
const
router
=
useRouter
();
return
(
<>
{
models
.
map
((
item
)
=>
(
<
Box
key=
{
item
.
_id
}
>
<
Flex
key=
{
item
.
_id
}
position=
{
'relative'
}
alignItems=
{
[
'flex-start'
,
'center'
]
}
p=
{
3
}
cursor=
{
'pointer'
}
transition=
{
'background-color .2s ease-in'
}
borderLeft=
{
[
''
,
'5px solid transparent'
]
}
zIndex=
{
0
}
_hover=
{
{
backgroundColor
:
[
''
,
'#dee0e3'
]
}
}
{
...
(
modelId
===
item
.
_id
?
{
backgroundColor
:
'#
eff0f1
',
borderLeftColor
:
'
myBlue
.
600'
}
:
{})}
onClick=
{
()
=>
{
router
.
replace
(
`/chat?appId=${item._id}`
);
}
}
>
<
Avatar
src=
{
item
.
avatar
}
w=
{
'34px'
}
h=
{
'34px'
}
/>
<
Box
flex=
{
'1 0 0'
}
w=
{
0
}
ml=
{
3
}
>
<
Box
className=
"textEllipsis"
color=
{
'myGray.1000'
}
>
{
item
.
name
}
</
Box
>
</
Box
>
</
Flex
>
</
Box
>
))
}
</>
);
};
export
default
ModelList
;
client/src/pages/chat/components/PhoneSliderBar.tsx
deleted
100644 → 0
View file @
eb768d9c
import
React
,
{
useMemo
,
useState
}
from
'react'
;
import
{
AddIcon
,
ChatIcon
}
from
'@chakra-ui/icons'
;
import
{
Box
,
Button
,
Flex
,
Divider
,
useDisclosure
,
useColorMode
,
useColorModeValue
}
from
'@chakra-ui/react'
;
import
{
useUserStore
}
from
'@/store/user'
;
import
{
useQuery
}
from
'@tanstack/react-query'
;
import
{
useRouter
}
from
'next/router'
;
import
MyIcon
from
'@/components/Icon'
;
import
WxConcat
from
'@/components/WxConcat'
;
import
{
delChatHistoryById
}
from
'@/api/chat'
;
import
{
useChatStore
}
from
'@/store/chat'
;
import
Avatar
from
'@/components/Avatar'
;
import
Tabs
from
'@/components/Tabs'
;
enum
TabEnum
{
app
=
'app'
,
history
=
'history'
}
const
PhoneSliderBar
=
({
chatId
,
modelId
,
onClose
}:
{
chatId
:
string
;
modelId
:
string
;
onClose
:
()
=>
void
;
})
=>
{
const
router
=
useRouter
();
const
[
currentTab
,
setCurrentTab
]
=
useState
(
TabEnum
.
app
);
const
{
myApps
,
myCollectionApps
,
loadMyModels
}
=
useUserStore
();
const
{
isOpen
:
isOpenWx
,
onOpen
:
onOpenWx
,
onClose
:
onCloseWx
}
=
useDisclosure
();
const
models
=
useMemo
(()
=>
[...
myApps
,
...
myCollectionApps
],
[
myCollectionApps
,
myApps
]);
useQuery
([
'loadModels'
],
loadMyModels
);
const
{
history
,
loadHistory
}
=
useChatStore
();
useQuery
([
'loadingHistory'
],
()
=>
loadHistory
({
pageNum
:
1
}));
const
RenderButton
=
({
onClick
,
children
}:
{
onClick
:
()
=>
void
;
children
:
JSX
.
Element
|
string
;
})
=>
(
<
Box
px=
{
3
}
mb=
{
2
}
>
<
Flex
alignItems=
{
'center'
}
p=
{
2
}
cursor=
{
'pointer'
}
borderRadius=
{
'md'
}
_hover=
{
{
backgroundColor
:
'rgba(255,255,255,0.2)'
}
}
onClick=
{
onClick
}
>
{
children
}
</
Flex
>
</
Box
>
);
return
(
<
Flex
flexDirection=
{
'column'
}
w=
{
'100%'
}
h=
{
'100%'
}
py=
{
3
}
backgroundColor=
{
useColorModeValue
(
'blackAlpha.800'
,
'blackAlpha.500'
)
}
color=
{
'white'
}
>
<
Flex
mb=
{
2
}
alignItems=
{
'center'
}
justifyContent=
{
'space-between'
}
px=
{
2
}
>
<
Tabs
w=
{
'140px'
}
list=
{
[
{
label
:
'应用'
,
id
:
TabEnum
.
app
},
{
label
:
'历史记录'
,
id
:
TabEnum
.
history
}
]
}
size=
{
'sm'
}
activeId=
{
currentTab
}
onChange=
{
(
e
:
any
)
=>
setCurrentTab
(
e
)
}
/>
{
/* 新对话 */
}
{
currentTab
===
TabEnum
.
app
&&
(
<
Button
size=
{
'sm'
}
variant=
{
'base'
}
color=
{
'white'
}
leftIcon=
{
<
AddIcon
/>
}
onClick=
{
()
=>
{
router
.
replace
(
`/chat?appId=${modelId}`
);
onClose
();
}
}
>
新对话
</
Button
>
)
}
</
Flex
>
{
/* 我的模型 & 历史记录 折叠框*/
}
<
Box
flex=
{
'1 0 0'
}
px=
{
3
}
h=
{
0
}
overflowY=
{
'auto'
}
>
{
currentTab
===
TabEnum
.
app
&&
(
<>
{
models
.
map
((
item
)
=>
(
<
Flex
key=
{
item
.
_id
}
alignItems=
{
'center'
}
p=
{
3
}
borderRadius=
{
'md'
}
mb=
{
2
}
cursor=
{
'pointer'
}
_hover=
{
{
backgroundColor
:
'rgba(255,255,255,0.1)'
}
}
fontSize=
{
'xs'
}
border=
{
'1px solid transparent'
}
{
...
(
item
.
_id
===
modelId
?
{
borderColor
:
'
rgba
(255,255,255,0
.
5)',
backgroundColor
:
'
rgba
(255,255,255,0
.
1)'
}
:
{})}
onClick=
{
async
()
=>
{
if
(
item
.
_id
===
modelId
)
return
;
router
.
replace
(
`/chat?appId=${item._id}`
);
onClose
();
}
}
>
<
Avatar
src=
{
item
.
avatar
}
mr=
{
2
}
w=
{
'18px'
}
h=
{
'18px'
}
/>
<
Box
className=
{
'textEllipsis'
}
flex=
{
'1 0 0'
}
w=
{
0
}
>
{
item
.
name
}
</
Box
>
</
Flex
>
))
}
</>
)
}
{
currentTab
===
TabEnum
.
history
&&
(
<>
{
history
.
map
((
item
)
=>
(
<
Flex
key=
{
item
.
_id
}
alignItems=
{
'center'
}
p=
{
3
}
borderRadius=
{
'md'
}
mb=
{
2
}
fontSize=
{
'xs'
}
border=
{
'1px solid transparent'
}
{
...
(
item
.
_id
===
chatId
?
{
borderColor
:
'
rgba
(255,255,255,0
.
5)',
backgroundColor
:
'
rgba
(255,255,255,0
.
1)'
}
:
{})}
onClick=
{
()
=>
{
if
(
item
.
_id
===
chatId
)
return
;
router
.
replace
(
`/chat?appId=${item.modelId}&chatId=${item._id}`
);
onClose
();
}
}
>
<
ChatIcon
mr=
{
2
}
/>
<
Box
flex=
{
'1 0 0'
}
w=
{
0
}
className=
"textEllipsis"
>
{
item
.
title
}
</
Box
>
<
Box
>
<
MyIcon
name=
{
'delete'
}
w=
{
'14px'
}
onClick=
{
async
(
e
)
=>
{
e
.
stopPropagation
();
console
.
log
(
111
);
await
delChatHistoryById
(
item
.
_id
);
loadHistory
({
pageNum
:
1
,
init
:
true
});
if
(
item
.
_id
===
chatId
)
{
router
.
replace
(
`/chat?appId=${modelId}`
);
}
}
}
/>
</
Box
>
</
Flex
>
))
}
</>
)
}
</
Box
>
<
Divider
my=
{
3
}
colorScheme=
{
useColorModeValue
(
'gray'
,
'white'
)
}
/>
<
RenderButton
onClick=
{
()
=>
router
.
push
(
'/model'
)
}
>
<>
<
MyIcon
name=
"out"
fill=
{
'white'
}
w=
{
'18px'
}
h=
{
'18px'
}
mr=
{
4
}
/>
退出聊天
</>
</
RenderButton
>
<
RenderButton
onClick=
{
onOpenWx
}
>
<>
<
MyIcon
name=
"wx"
fill=
{
'white'
}
w=
{
'18px'
}
h=
{
'18px'
}
mr=
{
4
}
/>
交流群
</>
</
RenderButton
>
{
/* wx 联系 */
}
{
isOpenWx
&&
<
WxConcat
onClose=
{
onCloseWx
}
/>
}
</
Flex
>
);
};
export
default
PhoneSliderBar
;
client/src/pages/chat/components/SliderApps.tsx
0 → 100644
View file @
b2e2f60e
import
React
from
'react'
;
import
{
Flex
,
Box
,
IconButton
}
from
'@chakra-ui/react'
;
import
{
useRouter
}
from
'next/router'
;
import
{
useUserStore
}
from
'@/store/user'
;
import
{
useQuery
}
from
'@tanstack/react-query'
;
import
MyIcon
from
'@/components/Icon'
;
import
Avatar
from
'@/components/Avatar'
;
const
SliderApps
=
({
appId
}:
{
appId
:
string
})
=>
{
const
router
=
useRouter
();
const
{
myApps
,
loadMyModels
}
=
useUserStore
();
useQuery
([
'loadModels'
],
loadMyModels
);
return
(
<>
<
Flex
alignItems=
{
'center'
}
cursor=
{
'pointer'
}
py=
{
2
}
px=
{
3
}
borderRadius=
{
'md'
}
_hover=
{
{
bg
:
'myGray.200'
}
}
onClick=
{
()
=>
router
.
replace
(
'/app/list'
)
}
>
<
IconButton
mr=
{
3
}
icon=
{
<
MyIcon
name=
{
'backFill'
}
w=
{
'18px'
}
color=
{
'myBlue.600'
}
/>
}
bg=
{
'white'
}
boxShadow=
{
'1px 1px 9px rgba(0,0,0,0.15)'
}
h=
{
'28px'
}
size=
{
'sm'
}
borderRadius=
{
'50%'
}
aria
-
label=
{
''
}
/>
退出聊天
</
Flex
>
<
Box
mt=
{
5
}
>
{
myApps
.
map
((
item
)
=>
(
<
Flex
key=
{
item
.
_id
}
py=
{
2
}
px=
{
3
}
mb=
{
3
}
cursor=
{
'pointer'
}
borderRadius=
{
'lg'
}
alignItems=
{
'center'
}
{
...
(
item
.
_id
===
appId
?
{
bg
:
'
white
',
boxShadow
:
'
md
'
}
:
{
_hover
:
{
bg
:
'
myGray
.
200'
},
onClick
:
()
=
>
{
router
.
replace
({
query
:
{
appId
:
item
.
_id
}
});
}
})}
>
<
Avatar
src=
{
item
.
avatar
}
w=
{
'24px'
}
/>
<
Box
ml=
{
2
}
className=
{
'textEllipsis'
}
>
{
item
.
name
}
</
Box
>
</
Flex
>
))
}
</
Box
>
</>
);
};
export
default
SliderApps
;
client/src/pages/chat/index.tsx
View file @
b2e2f60e
import
React
,
{
useCallback
,
useState
,
useRef
,
useMemo
,
useEffect
,
MouseEvent
}
from
'react'
;
import
React
,
{
useCallback
,
useState
,
useRef
}
from
'react'
;
import
{
useRouter
}
from
'next/router'
;
import
{
useRouter
}
from
'next/router'
;
import
{
getInitChatSiteInfo
,
delChatRecordByIndex
,
delChatHistoryById
}
from
'@/api/chat'
;
import
type
{
ChatItemType
,
ChatSiteItemType
,
ExportChatType
}
from
'@/types/chat'
;
import
{
import
{
Textarea
,
getInitChatSiteInfo
,
delChatRecordByIndex
,
delChatHistoryById
,
putChatHistory
}
from
'@/api/chat'
;
import
{
Box
,
Box
,
Flex
,
Flex
,
useColorModeValue
,
useColorModeValue
,
Menu
,
MenuButton
,
MenuList
,
MenuItem
,
Button
,
Modal
,
Modal
,
ModalOverlay
,
ModalOverlay
,
ModalContent
,
ModalContent
,
...
@@ -22,603 +20,266 @@ import {
...
@@ -22,603 +20,266 @@ import {
Drawer
,
Drawer
,
DrawerOverlay
,
DrawerOverlay
,
DrawerContent
,
DrawerContent
,
Card
,
useOutsideClick
,
useTheme
useTheme
}
from
'@chakra-ui/react'
;
}
from
'@chakra-ui/react'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
useGlobalStore
}
from
'@/store/global'
;
import
{
useGlobalStore
}
from
'@/store/global'
;
import
{
useQuery
}
from
'@tanstack/react-query'
;
import
{
useQuery
}
from
'@tanstack/react-query'
;
import
dynamic
from
'next/dynamic'
;
import
dynamic
from
'next/dynamic'
;
import
{
useCopyData
,
voiceBroadcast
,
hasVoiceApi
,
delay
}
from
'@/utils/tools'
;
import
{
streamFetch
}
from
'@/api/fetch'
;
import
{
streamFetch
}
from
'@/api/fetch'
;
import
MyIcon
from
'@/components/Icon'
;
import
MyIcon
from
'@/components/Icon'
;
import
{
throttle
}
from
'lodash'
;
import
{
Types
}
from
'mongoose'
;
import
{
ChatModelMap
}
from
'@/constants/model'
;
import
{
useChatStore
}
from
'@/store/chat'
;
import
{
useChatStore
}
from
'@/store/chat'
;
import
{
useLoading
}
from
'@/hooks/useLoading'
;
import
{
useLoading
}
from
'@/hooks/useLoading'
;
import
{
fileDownload
}
from
'@/utils/file'
;
import
{
htmlTemplate
}
from
'@/constants/common'
;
import
{
useUserStore
}
from
'@/store/user'
;
import
Loading
from
'@/components/Loading'
;
import
SideBar
from
'@/components/SideBar'
;
import
Avatar
from
'@/components/Avatar'
;
import
Empty
from
'./components/Empty'
;
import
QuoteModal
from
'./components/QuoteModal'
;
import
{
HUMAN_ICON
}
from
'@/constants/chat'
;
import
MyTooltip
from
'@/components/MyTooltip'
;
const
Markdown
=
dynamic
(
async
()
=>
await
import
(
'@/components/Markdown'
));
const
PhoneSliderBar
=
dynamic
(()
=>
import
(
'./components/PhoneSliderBar'
),
{
ssr
:
false
});
const
History
=
dynamic
(()
=>
import
(
'./components/History'
),
{
loading
:
()
=>
<
Loading
fixed=
{
false
}
/>,
ssr
:
false
});
import
styles
from
'./index.module.scss'
;
import
{
adaptChatItem_openAI
}
from
'@/utils/plugin/openai'
;
const
textareaMinH
=
'22px'
;
import
ChatBox
,
{
type
ComponentRef
,
type
StartChatFnProps
}
from
'@/components/ChatBox'
;
import
PageContainer
from
'@/components/PageContainer'
;
import
SideBar
from
'@/components/SideBar'
;
import
ChatHistorySlider
from
'./components/ChatHistorySlider'
;
import
SliderApps
from
'./components/SliderApps'
;
import
Tag
from
'@/components/Tag'
;
import
{
ChatHistoryItemType
}
from
'@/types/chat'
;
const
Chat
=
()
=>
{
const
Chat
=
()
=>
{
const
router
=
useRouter
();
const
router
=
useRouter
();
const
{
appId
=
''
,
chatId
=
''
}
=
router
.
query
as
{
appId
:
string
;
chat
Id
:
string
};
const
{
appId
=
''
,
historyId
=
''
}
=
router
.
query
as
{
appId
:
string
;
history
Id
:
string
};
const
theme
=
useTheme
();
const
theme
=
useTheme
();
const
ChatBox
=
useRef
<
HTMLDivElement
>
(
null
);
const
ChatBoxRef
=
useRef
<
ComponentRef
>
(
null
);
const
TextareaDom
=
useRef
<
HTMLTextAreaElement
>
(
null
);
const
forbidRefresh
=
useRef
(
false
);
const
ContextMenuRef
=
useRef
(
null
);
const
PhoneContextShow
=
useRef
(
false
);
// 中断请求
const
controller
=
useRef
(
new
AbortController
());
const
isLeavePage
=
useRef
(
false
);
const
[
showHistoryQuote
,
setShowHistoryQuote
]
=
useState
<
string
>
();
const
[
showHistoryQuote
,
setShowHistoryQuote
]
=
useState
<
string
>
();
const
[
showSystemPrompt
,
setShowSystemPrompt
]
=
useState
(
''
);
const
[
showSystemPrompt
,
setShowSystemPrompt
]
=
useState
(
''
);
const
[
messageContextMenuData
,
setMessageContextMenuData
]
=
useState
<
{
left
:
number
;
top
:
number
;
message
:
ChatSiteItemType
;
}
>
();
const
{
const
{
lastChat
Model
Id
,
lastChat
App
Id
,
setLastChat
Model
Id
,
setLastChat
App
Id
,
lastChatId
,
lastChatId
,
setLastChatId
,
setLastChatId
,
history
,
loadHistory
,
loadHistory
,
updateHistory
,
chatData
,
chatData
,
setChatData
,
setChatData
forbidLoadChatData
,
setForbidLoadChatData
}
=
useChatStore
();
}
=
useChatStore
();
const
isChatting
=
useMemo
(
()
=>
chatData
.
history
[
chatData
.
history
.
length
-
1
]?.
status
===
'loading'
,
[
chatData
.
history
]
);
const
{
toast
}
=
useToast
();
const
{
copyData
}
=
useCopyData
();
const
{
isPc
}
=
useGlobalStore
();
const
{
isPc
}
=
useGlobalStore
();
const
{
Loading
,
setIsLoading
}
=
useLoading
();
const
{
Loading
,
setIsLoading
}
=
useLoading
();
const
{
userInfo
}
=
useUserStore
();
const
{
isOpen
:
isOpenSlider
,
onClose
:
onCloseSlider
,
onOpen
:
onOpenSlider
}
=
useDisclosure
();
const
{
isOpen
:
isOpenSlider
,
onClose
:
onCloseSlider
,
onOpen
:
onOpenSlider
}
=
useDisclosure
();
// close contextMenu
const
startChat
=
useCallback
(
useOutsideClick
({
async
({
messages
,
controller
,
generatingMessage
,
variables
}:
StartChatFnProps
)
=>
{
ref
:
ContextMenuRef
,
const
prompts
=
messages
.
slice
(
-
2
);
handler
:
()
=>
{
const
{
responseText
,
newHistoryId
}
=
await
streamFetch
({
// 移动端长按后会将其设置为true,松手时候也会触发一次,松手的时候需要忽略一次。
if
(
PhoneContextShow
.
current
)
{
PhoneContextShow
.
current
=
false
;
}
else
{
messageContextMenuData
&&
setTimeout
(()
=>
{
setMessageContextMenuData
(
undefined
);
window
.
getSelection
?.()?.
empty
?.();
window
.
getSelection
?.()?.
removeAllRanges
?.();
document
?.
getSelection
()?.
empty
();
});
}
}
});
// 滚动到底部
const
scrollToBottom
=
useCallback
((
behavior
:
'smooth'
|
'auto'
=
'smooth'
)
=>
{
if
(
!
ChatBox
.
current
)
return
;
ChatBox
.
current
.
scrollTo
({
top
:
ChatBox
.
current
.
scrollHeight
,
behavior
});
},
[]);
// 聊天信息生成中……获取当前滚动条位置,判断是否需要滚动到底部
// eslint-disable-next-line react-hooks/exhaustive-deps
const
generatingMessage
=
useCallback
(
throttle
(()
=>
{
if
(
!
ChatBox
.
current
)
return
;
const
isBottom
=
ChatBox
.
current
.
scrollTop
+
ChatBox
.
current
.
clientHeight
+
150
>=
ChatBox
.
current
.
scrollHeight
;
isBottom
&&
scrollToBottom
(
'auto'
);
},
100
),
[]
);
// 重置输入内容
const
resetInputVal
=
useCallback
((
val
:
string
)
=>
{
if
(
!
TextareaDom
.
current
)
return
;
TextareaDom
.
current
.
value
=
val
;
setTimeout
(()
=>
{
/* 回到最小高度 */
if
(
TextareaDom
.
current
)
{
TextareaDom
.
current
.
style
.
height
=
val
===
''
?
textareaMinH
:
`
${
TextareaDom
.
current
.
scrollHeight
}
px`
;
}
},
100
);
},
[]);
// gpt 对话
const
gptChatPrompt
=
useCallback
(
async
(
prompts
:
ChatSiteItemType
[])
=>
{
// create abort obj
const
abortSignal
=
new
AbortController
();
controller
.
current
=
abortSignal
;
isLeavePage
.
current
=
false
;
const
messages
=
adaptChatItem_openAI
({
messages
:
prompts
,
reserveId
:
true
});
// 流请求,获取数据
const
{
newChatId
,
errMsg
}
=
await
streamFetch
({
data
:
{
data
:
{
messages
,
messages
:
prompts
,
chatId
,
variables
,
appId
,
appId
,
model
:
''
historyId
},
},
onMessage
:
(
text
:
string
)
=>
{
onMessage
:
generatingMessage
,
setChatData
((
state
)
=>
({
abortSignal
:
controller
...
state
,
history
:
state
.
history
.
map
((
item
,
index
)
=>
{
if
(
index
!==
state
.
history
.
length
-
1
)
return
item
;
return
{
...
item
,
value
:
item
.
value
+
text
};
})
}));
generatingMessage
();
},
abortSignal
});
});
// 重置了页面,说明退出了当前聊天, 不缓存任何内容
const
newTitle
=
prompts
[
0
].
content
?.
slice
(
0
,
20
)
||
'新对话'
;
if
(
isLeavePage
.
current
)
{
return
;
}
// save chat
// update history
if
(
newChatId
)
{
if
(
newHistoryId
)
{
setForbidLoadChatData
(
true
);
forbidRefresh
.
current
=
true
;
router
.
replace
(
`/chat?appId=
${
appId
}
&chatId=
${
newChatId
}
`
);
router
.
replace
({
query
:
{
historyId
:
newHistoryId
,
appId
}
}
abortSignal
.
signal
.
aborted
&&
(
await
delay
(
500
));
// 设置聊天内容为完成状态
setChatData
((
state
)
=>
({
...
state
,
chatId
:
newChatId
||
state
.
chatId
,
// 如果有 Id,说明是新创建的对话
history
:
state
.
history
.
map
((
item
,
index
)
=>
{
if
(
index
!==
state
.
history
.
length
-
1
)
return
item
;
return
{
...
item
,
status
:
'finish'
,
quoteLen
:
0
,
systemPrompt
:
`
${
chatData
.
systemPrompt
}${
`
${
chatData
.
limitPrompt
?
`\n\n
${
chatData
.
limitPrompt
}
`
:
''
}
`
}
`
};
})
}));
// refresh data
setTimeout
(()
=>
{
generatingMessage
();
loadHistory
({
pageNum
:
1
,
init
:
true
});
},
100
);
if
(
errMsg
)
{
toast
({
status
:
'warning'
,
title
:
errMsg
});
});
}
const
newHistory
:
ChatHistoryItemType
=
{
}
,
_id
:
newHistoryId
,
[
updateTime
:
new
Date
(),
chatId
,
title
:
newTitle
,
appId
,
appId
,
setChatData
,
top
:
false
generatingMessage
,
};
setForbidLoadChatData
,
updateHistory
(
newHistory
);
router
,
}
else
{
chatData
.
systemPrompt
,
const
currentHistory
=
history
.
find
((
item
)
=>
item
.
_id
===
historyId
);
chatData
.
limitPrompt
,
currentHistory
&&
loadHistory
,
updateHistory
({
toast
...
currentHistory
,
]
updateTime
:
new
Date
(),
);
title
:
newTitle
/**
* 发送一个内容
*/
const
sendPrompt
=
useCallback
(
async
()
=>
{
// get value
if
(
isChatting
)
{
toast
({
title
:
'正在聊天中...请等待结束'
,
status
:
'warning'
});
return
;
}
// get input value
const
value
=
TextareaDom
.
current
?.
value
||
''
;
const
val
=
value
.
trim
().
replace
(
/
\n\s
*/g
,
'\n'
);
if
(
!
val
)
{
toast
({
title
:
'内容为空'
,
status
:
'warning'
});
});
return
;
}
}
// update chat window
const
newChatList
:
ChatSiteItemType
[]
=
[
...
chatData
.
history
,
{
_id
:
String
(
new
Types
.
ObjectId
()),
obj
:
'Human'
,
value
:
val
,
status
:
'finish'
},
{
_id
:
String
(
new
Types
.
ObjectId
()),
obj
:
'AI'
,
value
:
''
,
status
:
'loading'
}
];
// 插入内容
setChatData
((
state
)
=>
({
setChatData
((
state
)
=>
({
...
state
,
...
state
,
history
:
newChatList
title
:
newTitle
,
history
:
ChatBoxRef
.
current
?.
getChatHistory
()
||
state
.
history
}));
}));
// 清空输入内容
return
{
responseText
};
resetInputVal
(
''
);
},
setTimeout
(()
=>
{
[
appId
,
history
,
historyId
,
router
,
setChatData
,
updateHistory
]
scrollToBottom
();
);
},
100
);
try
{
await
gptChatPrompt
(
newChatList
.
slice
(
newChatList
.
length
-
2
));
}
catch
(
err
:
any
)
{
toast
({
title
:
typeof
err
===
'string'
?
err
:
err
?.
message
||
'聊天出错了~'
,
status
:
'warning'
,
duration
:
5000
,
isClosable
:
true
});
resetInputVal
(
value
);
setChatData
((
state
)
=>
({
...
state
,
history
:
newChatList
.
slice
(
0
,
newChatList
.
length
-
2
)
}));
}
},
[
isChatting
,
chatData
.
history
,
setChatData
,
resetInputVal
,
toast
,
scrollToBottom
,
gptChatPrompt
]);
// 删除一句话
// 删除一句话
const
delChatRecord
=
useCallback
(
const
delOneHistoryItem
=
useCallback
(
async
(
index
:
number
,
historyId
?:
string
)
=>
{
async
({
contentId
,
index
}:
{
contentId
?:
string
;
index
:
number
})
=>
{
if
(
!
messageContextMenuData
||
!
historyId
)
return
;
if
(
!
historyId
||
!
contentId
)
return
;
setIsLoading
(
true
);
try
{
try
{
// 删除数据库最后一句
await
delChatRecordByIndex
(
chatId
,
historyId
);
setChatData
((
state
)
=>
({
setChatData
((
state
)
=>
({
...
state
,
...
state
,
history
:
state
.
history
.
filter
((
_
,
i
)
=>
i
!==
index
)
history
:
state
.
history
.
filter
((
_
,
i
)
=>
i
!==
index
)
}));
}));
await
delChatRecordByIndex
({
historyId
,
contentId
});
}
catch
(
err
)
{
}
catch
(
err
)
{
console
.
log
(
err
);
console
.
log
(
err
);
}
}
setIsLoading
(
false
);
},
[
chatId
,
messageContextMenuData
,
setChatData
,
setIsLoading
]
);
// 复制内容
const
onclickCopy
=
useCallback
(
(
value
:
string
)
=>
{
const
val
=
value
.
replace
(
/
\n
+/g
,
'\n'
);
copyData
(
val
);
},
[
copyData
]
);
// export chat data
const
onclickExportChat
=
useCallback
(
(
type
:
ExportChatType
)
=>
{
const
getHistoryHtml
=
()
=>
{
const
historyDom
=
document
.
getElementById
(
'history'
);
if
(
!
historyDom
)
return
;
const
dom
=
Array
.
from
(
historyDom
.
children
).
map
((
child
,
i
)
=>
{
const
avatar
=
`<img src="
${
child
.
querySelector
<
HTMLImageElement
>
(
'.avatar'
)?.
src
}
" alt="" />`;
const chatContent = child.querySelector<HTMLDivElement>('.markdown');
if (!chatContent) {
return '';
}
const chatContentClone = chatContent.cloneNode(true) as HTMLDivElement;
const codeHeader = chatContentClone.querySelectorAll('.code-header');
codeHeader.forEach((childElement: any) => {
childElement.remove();
});
return `<div class="
chat
-
item
">
${avatar}
${chatContentClone.outerHTML}
</div>`;
});
const html = htmlTemplate.replace('{{CHAT_CONTENT}}', dom.join('\n'));
return html;
};
const map: Record<ExportChatType, () => void> = {
md: () => {
fileDownload({
text: chatData.history.map((item) => item.value).join('\n\n'),
type: 'text/markdown',
filename: 'chat.md'
});
},
},
html: () => {
[
historyId
,
setChatData
]
const html = getHistoryHtml();
html &&
fileDownload({
text: html,
type: 'text/html',
filename: '聊天记录.html'
});
},
pdf: () => {
const html = getHistoryHtml();
html &&
// @ts-ignore
html2pdf(html, {
margin: 0,
filename: `聊天记录.pdf`
});
}
};
map[type]();
},
[chatData.history]
);
);
// delete a history
// delete history and reload history
const
delHistoryById
=
useCallback
(
const onclickDelHistory = useCallback(
async
(
historyId
:
string
)
=>
{
async
(
historyId
:
string
)
=>
{
await
delChatHistoryById
(
historyId
);
await
delChatHistoryById
(
historyId
);
loadHistory({ pageNum: 1, init: true });
loadHistory
({
appId
});
},
[loadHistory]
);
// onclick chat message context
const onclickContextMenu = useCallback(
(e: MouseEvent<HTMLDivElement>, message: ChatSiteItemType) => {
e.preventDefault(); // 阻止默认右键菜单
// select all text
const range = document.createRange();
range.selectNodeContents(e.currentTarget as HTMLDivElement);
window.getSelection()?.removeAllRanges();
window.getSelection()?.addRange(range);
navigator.vibrate?.(50); // 震动 50 毫秒
if (!isPc) {
PhoneContextShow.current = true;
}
setMessageContextMenuData({
left: e.clientX - 20,
top: e.clientY,
message
});
return false;
},
},
[
isPc
]
[
appId
,
loadHistory
]
);
);
//
获取对话信息
//
get chat app info
const
loadChatInfo
=
useCallback
(
const
loadChatInfo
=
useCallback
(
async
({
async
({
appId
,
appId
,
chat
Id,
history
Id
,
loading
=
false
loading
=
false
}:
{
}:
{
appId
:
string
;
appId
:
string
;
chat
Id: string;
history
Id
:
string
;
loading
?:
boolean
;
loading
?:
boolean
;
})
=>
{
})
=>
{
try
{
try
{
loading
&&
setIsLoading
(
true
);
loading
&&
setIsLoading
(
true
);
const res = await getInitChatSiteInfo(appId, chatId);
const
res
=
await
getInitChatSiteInfo
({
appId
,
historyId
});
const
history
=
res
.
history
.
map
((
item
)
=>
({
...
item
,
status
:
'finish'
as
any
}));
setChatData
({
setChatData
({
...
res
,
...
res
,
history: res.history.map((item) => ({
history
...item,
status: 'finish'
}))
});
});
// have records.
// have records.
ChatBoxRef
.
current
?.
resetHistory
(
history
);
ChatBoxRef
.
current
?.
resetVariables
(
res
.
variables
);
if
(
res
.
history
.
length
>
0
)
{
if
(
res
.
history
.
length
>
0
)
{
setTimeout
(()
=>
{
setTimeout
(()
=>
{
scrollToBottom('auto');
ChatBoxRef
.
current
?.
scrollToBottom
(
'auto'
);
},
3
00);
},
2
00
);
}
}
// 空 modelId 请求, 重定向到新的 model 聊天
// empty appId request, return first app
if (res.modelId !== appId) {
if
(
res
.
appId
!==
appId
)
{
setForbidLoadChatData(true);
forbidRefresh
.
current
=
true
;
router.replace(`/chat?appId=${res.modelId}`);
router
.
replace
({
query
:
{
appId
:
res
.
appId
}
});
}
}
}
catch
(
e
:
any
)
{
}
catch
(
e
:
any
)
{
// reset all chat tore
// reset all chat tore
setLastChat
Model
Id('');
setLastChat
App
Id
(
''
);
setLastChatId
(
''
);
setLastChatId
(
''
);
setChatData();
loadHistory({ pageNum: 1, init: true });
router
.
replace
(
'/chat'
);
router
.
replace
(
'/chat'
);
}
}
setIsLoading
(
false
);
setIsLoading
(
false
);
return
null
;
return
null
;
},
},
[
[
setIsLoading
,
setChatData
,
router
,
setLastChatAppId
,
setLastChatId
]
setIsLoading,
setChatData,
scrollToBottom,
setForbidLoadChatData,
router,
setLastChatModelId,
setLastChatId,
loadHistory
]
);
);
// 初始化聊天框
// 初始化聊天框
useQuery(['init', appId,
chat
Id], () => {
useQuery
([
'init'
,
appId
,
history
Id
],
()
=>
{
// pc: redirect to latest model chat
// pc: redirect to latest model chat
if (!appId && lastChatModelId) {
if
(
!
appId
&&
lastChatAppId
)
{
router.replace(`/chat?appId=${lastChatModelId}&chatId=${lastChatId}`);
router
.
replace
({
query
:
{
appId
:
lastChatAppId
,
historyId
:
lastChatId
}
});
return
null
;
return
null
;
}
}
// store id
// store id
appId && setLastChat
Model
Id(appId);
appId
&&
setLastChat
App
Id
(
appId
);
setLastChatId(
chat
Id);
setLastChatId
(
history
Id
);
if (forbid
LoadChatData
) {
if
(
forbid
Refresh
.
current
)
{
setForbidLoadChatData(false)
;
forbidRefresh
.
current
=
false
;
return
null
;
return
null
;
}
}
return
loadChatInfo
({
return
loadChatInfo
({
appId
,
appId
,
chat
Id,
history
Id
,
loading:
true
loading
:
appId
!==
chatData
.
appId
});
});
});
});
// abort stream
useQuery
([
'loadHistory'
,
appId
],
()
=>
(
appId
?
loadHistory
({
appId
})
:
null
));
useEffect(() => {
return () => {
window.speechSynthesis?.cancel();
isLeavePage.current = true;
controller.current?.abort();
};
}, [appId, chatId]);
// context menu component
return
(
const RenderContextMenu = useCallback(
<
Flex
h=
{
'100%'
}
flexDirection=
{
[
'column'
,
'row'
]
}
>
({
{
/* pc show myself apps */
}
history,
{
isPc
&&
(
index,
<
Box
p=
{
5
}
borderRight=
{
theme
.
borders
.
base
}
w=
{
'220px'
}
flexShrink=
{
0
}
>
AiDetail = false
<
SliderApps
appId=
{
appId
}
/>
}: {
</
Box
>
history: ChatSiteItemType;
index: number;
AiDetail?: boolean;
}) => (
<MenuList fontSize={'sm'} minW={'100px !important'}>
<MenuItem onClick={() => onclickCopy(history.value)}>复制</MenuItem>
{AiDetail && chatData.model.canUse && history.obj === 'AI' && (
<MenuItem
borderBottom={theme.borders.base}
onClick={() => router.push(`/model?modelId=${chatData.modelId}`)}
>
应用详情
</MenuItem>
)}
{hasVoiceApi && (
<MenuItem
borderBottom={theme.borders.base}
onClick={() => voiceBroadcast({ text: history.value })}
>
语音播报
</MenuItem>
)
}
)
}
<MenuItem onClick={() => delChatRecord(index, history._id)}>删除</MenuItem>
<
PageContainer
flex=
{
'1 0 0'
}
w=
{
0
}
bg=
{
'myWhite.600'
}
position=
{
'relative'
}
>
</MenuList>
<
Flex
h=
{
'100%'
}
flexDirection=
{
[
'column'
,
'row'
]
}
>
),
[
chatData.model.canUse,
chatData.modelId,
delChatRecord,
onclickCopy,
router,
theme.borders.base
]
);
return (
<Flex
h={'100%'}
flexDirection={['column', 'row']}
backgroundColor={useColorModeValue('#fdfdfd', '')}
>
{
/* pc always show history. */
}
{
/* pc always show history. */
}
{(isPc || !appId) && (
{
((
children
:
React
.
ReactNode
)
=>
{
<SideBar>
return
isPc
||
!
appId
?
(
<History onclickDelHistory={onclickDelHistory} onclickExportChat={onclickExportChat} />
<
SideBar
>
{
children
}
</
SideBar
>
</SideBar>
)
:
(
<
Drawer
isOpen=
{
isOpenSlider
}
placement=
"left"
size=
{
'xs'
}
onClose=
{
onCloseSlider
}
>
<
DrawerOverlay
backgroundColor=
{
'rgba(255,255,255,0.5)'
}
/>
<
DrawerContent
maxWidth=
{
'250px'
}
>
{
children
}
</
DrawerContent
>
</
Drawer
>
);
})(
<
ChatHistorySlider
appName=
{
chatData
.
app
.
name
}
appAvatar=
{
chatData
.
app
.
avatar
}
activeHistoryId=
{
historyId
}
history=
{
history
.
map
((
item
)
=>
({
id
:
item
.
_id
,
title
:
item
.
title
,
top
:
item
.
top
}))
}
onChangeChat=
{
(
historyId
)
=>
{
router
.
push
({
query
:
{
historyId
:
historyId
||
''
,
appId
}
});
}
}
onDelHistory=
{
delHistoryById
}
onSetHistoryTop=
{
async
(
e
)
=>
{
try
{
await
putChatHistory
(
e
);
loadHistory
({
appId
});
}
catch
(
error
)
{}
}
}
onCloseSlider=
{
onCloseSlider
}
/>
)
}
)
}
{
/* chat container */
}
{/* 聊天内容 */}
{appId && (
<
Flex
<
Flex
position=
{
'relative'
}
position=
{
'relative'
}
h=
{
[
0
,
'100%'
]
}
h=
{
[
0
,
'100%'
]
}
...
@@ -626,17 +287,26 @@ const Chat = () => {
...
@@ -626,17 +287,26 @@ const Chat = () => {
flex=
{
'1 0 0'
}
flex=
{
'1 0 0'
}
flexDirection=
{
'column'
}
flexDirection=
{
'column'
}
>
>
{/* chat header */}
<
Flex
<
Flex
alignItems=
{
'center'
}
alignItems=
{
'center'
}
justifyContent={'space-between'}
py=
{
[
3
,
5
]
}
py=
{
[
3
,
5
]
}
px=
{
5
}
px=
{
5
}
borderBottom={'1px solid'
}
borderBottom=
{
theme
.
borders
.
base
}
borderBottomColor=
{
useColorModeValue
(
'gray.200'
,
'gray.700'
)
}
borderBottomColor=
{
useColorModeValue
(
'gray.200'
,
'gray.700'
)
}
color=
{
useColorModeValue
(
'myGray.900'
,
'white'
)
}
color=
{
useColorModeValue
(
'myGray.900'
,
'white'
)
}
>
>
{!isPc && (
{
isPc
?
(
<>
<
Box
mr=
{
3
}
color=
{
'myGray.1000'
}
>
{
chatData
.
title
}
</
Box
>
<
Tag
display=
{
'flex'
}
>
<
MyIcon
name=
{
'history'
}
w=
{
'14px'
}
/>
<
Box
ml=
{
1
}
>
{
chatData
.
history
.
length
}
条记录
</
Box
>
</
Tag
>
</>
)
:
(
<>
<
MyIcon
<
MyIcon
name=
{
'menu'
}
name=
{
'menu'
}
w=
{
'20px'
}
w=
{
'20px'
}
...
@@ -644,273 +314,35 @@ const Chat = () => {
...
@@ -644,273 +314,35 @@ const Chat = () => {
color=
{
useColorModeValue
(
'blackAlpha.700'
,
'white'
)
}
color=
{
useColorModeValue
(
'blackAlpha.700'
,
'white'
)
}
onClick=
{
onOpenSlider
}
onClick=
{
onOpenSlider
}
/>
/>
)}
</>
<Box
cursor={'pointer'}
lineHeight={1.2}
textAlign={'center'}
px={3}
fontSize={['sm', 'md']}
onClick={() => router.push(`/model?modelId=${chatData.modelId}`)}
>
{chatData.model.name} {ChatModelMap[chatData.chatModel]?.name}
{chatData.history.length > 0 ? ` (${chatData.history.length})` : ''}
</Box>
{chatId ? (
<Menu autoSelect={false}>
<MenuButton lineHeight={1}>
<MyIcon
name={'more'}
w={'16px'}
h={'16px'}
color={useColorModeValue('blackAlpha.700', 'white')}
/>
</MenuButton>
<MenuList minW={`90px !important`}>
<MenuItem onClick={() => router.replace(`/chat?appId=${appId}`)}>新对话</MenuItem>
<MenuItem
onClick={async () => {
try {
setIsLoading(true);
await onclickDelHistory(chatData.chatId);
router.replace(`/chat?appId=${appId}`);
} catch (err) {
console.log(err);
}
setIsLoading(false);
}}
>
删除记录
</MenuItem>
<MenuItem onClick={() => onclickExportChat('html')}>导出HTML格式</MenuItem>
<MenuItem onClick={() => onclickExportChat('pdf')}>导出PDF格式</MenuItem>
<MenuItem onClick={() => onclickExportChat('md')}>导出Markdown格式</MenuItem>
</MenuList>
</Menu>
) : (
<Box w={'16px'} h={'16px'} />
)
}
)
}
</
Flex
>
</
Flex
>
{/* chat content box */}
{
/* chat box */
}
<Box ref={ChatBox} pb={[4, 0]} flex={'1 0 0'} h={0} w={'100%'} overflow={'overlay'}>
<
Box
flex=
{
1
}
>
<Box id={'history'}>
<
ChatBox
{chatData.history.map((item, index) => (
ref=
{
ChatBoxRef
}
<Flex key={item._id} alignItems={'flex-start'} py={2} px={[2, 6, 8]}>
appAvatar=
{
chatData
.
app
.
avatar
}
{item.obj === 'Human' && <Box flex={1} />}
variableModules=
{
chatData
.
app
.
variableModules
}
{/* avatar */}
welcomeText=
{
chatData
.
app
.
welcomeText
}
<Menu autoSelect={false} isLazy>
onUpdateVariable=
{
(
e
)
=>
{
<MyTooltip label={item.obj === 'AI' ? '应用详情' : ''}>
console
.
log
(
e
);
<MenuButton
}
}
as={Box}
onStartChat=
{
startChat
}
{...(item.obj === 'AI'
onDelMessage=
{
delOneHistoryItem
}
? {
order: 1,
mr: ['6px', 2],
cursor: 'pointer',
onClick: () =>
isPc &&
chatData.model.canUse &&
router.push(`/model?modelId=${chatData.modelId}`)
}
: {
order: 3,
ml: ['6px', 2]
})}
>
<Avatar
className="
avatar
"
src={
item.obj === 'Human'
? userInfo?.avatar || HUMAN_ICON
: chatData.model.avatar
}
w={['20px', '34px']}
h={['20px', '34px']}
/>
</MenuButton>
</MyTooltip>
{!isPc && <RenderContextMenu history={item} index={index} AiDetail />}
</Menu>
{/* message */}
<Flex order={2} pt={2} maxW={['calc(100% - 50px)', '80%']}>
{item.obj === 'AI' ? (
<Box w={'100%'}>
<Card
bg={'white'}
px={4}
py={3}
borderRadius={'0 8px 8px 8px'}
onContextMenu={(e) => onclickContextMenu(e, item)}
>
<Markdown
source={item.value}
isChatting={isChatting && index === chatData.history.length - 1}
/>
/>
<Flex>
{!!item.systemPrompt && (
<Button
mt={2}
mr={3}
size={'xs'}
fontWeight={'normal'}
colorScheme={'gray'}
variant={'base'}
px={[2, 4]}
onClick={() => setShowSystemPrompt(item.systemPrompt || '')}
>
提示词 & 限定词
</Button>
)}
{!!item.quoteLen && (
<Button
mt={2}
size={'xs'}
fontWeight={'normal'}
colorScheme={'gray'}
variant={'base'}
px={[2, 4]}
onClick={() => setShowHistoryQuote(item._id)}
>
{item.quoteLen}条引用
</Button>
)}
</Flex>
</Card>
</
Box
>
</
Box
>
) : (
<Box>
<Card
className="
markdown
"
whiteSpace={'pre-wrap'}
px={4}
py={3}
borderRadius={'8px 0 8px 8px'}
bg={'myBlue.300'}
onContextMenu={(e) => onclickContextMenu(e, item)}
>
<Box as={'p'}>{item.value}</Box>
</Card>
</Box>
)}
</
Flex
>
</
Flex
>
</
Flex
>
</
Flex
>
))}
{chatData.history.length === 0 && (
<Empty model={chatData.model} showChatProblem={true} />
)}
</Box>
</Box>
{/* 发送区 */}
{chatData.model.canUse ? (
<Box m={['0 auto', '20px auto']} w={'100%'} maxW={['auto', 'min(750px, 100%)']}>
<Box
py={'18px'}
position={'relative'}
boxShadow={`0 0 10px rgba(0,0,0,0.1)`}
borderTop={['1px solid', 0]}
borderTopColor={useColorModeValue('gray.200', 'gray.700')}
borderRadius={['none', 'md']}
backgroundColor={useColorModeValue('white', 'gray.700')}
>
{/* 输入框 */}
<Textarea
ref={TextareaDom}
py={0}
pr={['45px', '55px']}
border={'none'}
_focusVisible={{
border: 'none'
}}
placeholder="
提问
"
resize={'none'}
rows={1}
height={'22px'}
lineHeight={'22px'}
maxHeight={'150px'}
maxLength={-1}
overflowY={'auto'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
boxShadow={'none !important'}
color={useColorModeValue('blackAlpha.700', 'white')}
onChange={(e) => {
const textarea = e.target;
textarea.style.height = textareaMinH;
textarea.style.height = `${textarea.scrollHeight}px`;
}}
onKeyDown={(e) => {
// 触发快捷发送
if (isPc && e.keyCode === 13 && !e.shiftKey) {
sendPrompt();
e.preventDefault();
}
// 全选内容
// @ts-ignore
e.key === 'a' && e.ctrlKey && e.target?.select();
}}
/>
{/* 发送和等待按键 */}
<Flex
alignItems={'center'}
justifyContent={'center'}
h={'25px'}
w={'25px'}
position={'absolute'}
right={['12px', '20px']}
bottom={'15px'}
>
{isChatting ? (
<MyIcon
className={styles.stopIcon}
width={['22px', '25px']}
height={['22px', '25px']}
cursor={'pointer'}
name={'stop'}
color={useColorModeValue('gray.500', 'white')}
onClick={() => {
controller.current?.abort();
}}
/>
) : (
<MyIcon
name={'chatSend'}
width={['18px', '20px']}
height={['18px', '20px']}
cursor={'pointer'}
color={useColorModeValue('gray.500', 'white')}
onClick={sendPrompt}
/>
)}
</Flex>
</Box>
</Box>
) : (
<Box m={['0 auto', '20px auto']} w={'100%'} textAlign={'center'} color={'myGray.500'}>
作者已关闭分享
</Box>
)}
<
Loading
fixed=
{
false
}
/>
<
Loading
fixed=
{
false
}
/>
</Flex>
</
PageContainer
>
)}
{/* phone slider */}
{!isPc && (
<Drawer isOpen={isOpenSlider} placement="
left
"
size
=
{
'xs'
}
onClose
=
{
onCloseSlider
}
>
<
DrawerOverlay
backgroundColor=
{
'rgba(255,255,255,0.5)'
}
/>
<
DrawerContent
maxW=
{
'70%'
}
>
<
PhoneSliderBar
chatId=
{
chatId
}
modelId=
{
appId
}
onClose=
{
onCloseSlider
}
/>
</
DrawerContent
>
<
/
Drawer
>
)}
{
/* quote modal*/
}
{
/* quote modal*/
}
{
showHistoryQuote
&&
chat
Id
&&
(
{
/* {showHistoryQuote && history
Id && (
<QuoteModal
<QuoteModal
historyId=
{
showHistoryQuote
}
historyId={historyId}
chatId=
{
chatId
}
onClose={() => setShowHistoryQuote(undefined)}
onClose={() => setShowHistoryQuote(undefined)}
/>
/>
)
}
)}
*/
}
{
/* system prompt show modal */
}
{
/* system prompt show modal */
}
{
{
<
Modal
isOpen=
{
!!
showSystemPrompt
}
onClose=
{
()
=>
setShowSystemPrompt
(
''
)
}
>
<
Modal
isOpen=
{
!!
showSystemPrompt
}
onClose=
{
()
=>
setShowSystemPrompt
(
''
)
}
>
...
@@ -924,26 +356,6 @@ const Chat = () => {
...
@@ -924,26 +356,6 @@ const Chat = () => {
</
ModalContent
>
</
ModalContent
>
</
Modal
>
</
Modal
>
}
}
{
/* context menu */
}
{
messageContextMenuData
&&
(
<
Box
zIndex=
{
10
}
position=
{
'fixed'
}
top=
{
messageContextMenuData
.
top
}
left=
{
messageContextMenuData
.
left
}
>
<
Box
ref=
{
ContextMenuRef
}
></
Box
>
<
Menu
isOpen
>
<
RenderContextMenu
history=
{
messageContextMenuData
.
message
}
index=
{
chatData
.
history
.
findIndex
(
(
item
)
=>
item
.
_id
===
messageContextMenuData
.
message
.
_id
)
}
AiDetail=
{
!
isPc
}
/>
</
Menu
>
</
Box
>
)
}
</
Flex
>
</
Flex
>
);
);
};
};
...
...
client/src/pages/chat/share.tsx
View file @
b2e2f60e
...
@@ -18,13 +18,13 @@ import { streamFetch } from '@/api/fetch';
...
@@ -18,13 +18,13 @@ import { streamFetch } from '@/api/fetch';
import
{
useShareChatStore
,
defaultHistory
}
from
'@/store/shareChat'
;
import
{
useShareChatStore
,
defaultHistory
}
from
'@/store/shareChat'
;
import
SideBar
from
'@/components/SideBar'
;
import
SideBar
from
'@/components/SideBar'
;
import
{
gptMessage2ChatType
}
from
'@/utils/adapt'
;
import
{
gptMessage2ChatType
}
from
'@/utils/adapt'
;
import
ChatHistorySlider
from
'./components/ChatHistorySlider'
;
import
{
getErrText
}
from
'@/utils/tools'
;
import
{
getErrText
}
from
'@/utils/tools'
;
import
ChatBox
,
{
type
ComponentRef
,
type
StartChatFnProps
}
from
'@/components/ChatBox'
;
import
ChatBox
,
{
type
ComponentRef
,
type
StartChatFnProps
}
from
'@/components/ChatBox'
;
import
MyIcon
from
'@/components/Icon'
;
import
MyIcon
from
'@/components/Icon'
;
import
Tag
from
'@/components/Tag'
;
import
Tag
from
'@/components/Tag'
;
import
PageContainer
from
'@/components/PageContainer'
;
import
PageContainer
from
'@/components/PageContainer'
;
import
ChatHistorySlider
from
'./components/ChatHistorySlider'
;
const
ShareChat
=
()
=>
{
const
ShareChat
=
()
=>
{
const
theme
=
useTheme
();
const
theme
=
useTheme
();
...
@@ -134,6 +134,10 @@ const ShareChat = () => {
...
@@ -134,6 +134,10 @@ const ShareChat = () => {
}
}
}
}
if
(
history
.
chats
.
length
>
0
)
{
ChatBoxRef
.
current
?.
scrollToBottom
(
'auto'
);
}
return
history
;
return
history
;
},
},
[
[
...
@@ -152,7 +156,7 @@ const ShareChat = () => {
...
@@ -152,7 +156,7 @@ const ShareChat = () => {
return
(
return
(
<
PageContainer
>
<
PageContainer
>
<
Flex
h=
{
'100%'
}
flexDirection=
{
[
'column'
,
'row'
]
}
backgroundColor=
{
'#fdfdfd'
}
>
<
Flex
h=
{
'100%'
}
flexDirection=
{
[
'column'
,
'row'
]
}
>
{
/* slider */
}
{
/* slider */
}
{
isPc
?
(
{
isPc
?
(
<
SideBar
>
<
SideBar
>
...
@@ -243,14 +247,7 @@ const ShareChat = () => {
...
@@ -243,14 +247,7 @@ const ShareChat = () => {
)
}
)
}
</
Flex
>
</
Flex
>
{
/* chat box */
}
{
/* chat box */
}
<
Box
<
Box
flex=
{
1
}
>
pt=
{
[
0
,
5
]
}
flex=
{
1
}
maxW=
{
[
'100%'
,
'1000px'
,
'1200px'
]
}
px=
{
[
0
,
5
]
}
w=
{
'100%'
}
mx=
{
'auto'
}
>
<
ChatBox
<
ChatBox
ref=
{
ChatBoxRef
}
ref=
{
ChatBoxRef
}
appAvatar=
{
shareChatData
.
app
.
avatar
}
appAvatar=
{
shareChatData
.
app
.
avatar
}
...
...
client/src/pages/login/index.tsx
View file @
b2e2f60e
...
@@ -18,17 +18,16 @@ const Login = () => {
...
@@ -18,17 +18,16 @@ const Login = () => {
const
{
isPc
}
=
useGlobalStore
();
const
{
isPc
}
=
useGlobalStore
();
const
[
pageType
,
setPageType
]
=
useState
<
`
${
PageTypeEnum
}
`
>
(
PageTypeEnum
.
login
);
const
[
pageType
,
setPageType
]
=
useState
<
`
${
PageTypeEnum
}
`
>
(
PageTypeEnum
.
login
);
const
{
setUserInfo
,
setLastModelId
,
loadKbList
,
setLastKbId
}
=
useUserStore
();
const
{
setUserInfo
,
setLastModelId
,
loadKbList
,
setLastKbId
}
=
useUserStore
();
const
{
setLastChatId
,
setLastChat
ModelId
,
loadHistory
}
=
useChatStore
();
const
{
setLastChatId
,
setLastChat
AppId
}
=
useChatStore
();
const
loginSuccess
=
useCallback
(
const
loginSuccess
=
useCallback
(
(
res
:
ResLogin
)
=>
{
(
res
:
ResLogin
)
=>
{
// init store
// init store
setLastChatId
(
''
);
setLastChatId
(
''
);
setLastModelId
(
''
);
setLastModelId
(
''
);
setLastChat
Model
Id
(
''
);
setLastChat
App
Id
(
''
);
setLastKbId
(
''
);
setLastKbId
(
''
);
loadKbList
(
true
);
loadKbList
(
true
);
loadHistory
({
pageNum
:
1
,
init
:
true
});
setUserInfo
(
res
.
user
);
setUserInfo
(
res
.
user
);
setTimeout
(()
=>
{
setTimeout
(()
=>
{
...
@@ -37,11 +36,10 @@ const Login = () => {
...
@@ -37,11 +36,10 @@ const Login = () => {
},
},
[
[
lastRoute
,
lastRoute
,
loadHistory
,
loadKbList
,
loadKbList
,
router
,
router
,
setLastChatId
,
setLastChatId
,
setLastChat
Model
Id
,
setLastChat
App
Id
,
setLastKbId
,
setLastKbId
,
setLastModelId
,
setLastModelId
,
setUserInfo
setUserInfo
...
...
client/src/service/api/request.ts
View file @
b2e2f60e
...
@@ -3,6 +3,7 @@ import { getErrText } from '@/utils/tools';
...
@@ -3,6 +3,7 @@ import { getErrText } from '@/utils/tools';
import
{
parseStreamChunk
}
from
'@/utils/adapt'
;
import
{
parseStreamChunk
}
from
'@/utils/adapt'
;
import
{
NextApiResponse
}
from
'next'
;
import
{
NextApiResponse
}
from
'next'
;
import
{
sseResponse
}
from
'../utils/tools'
;
import
{
sseResponse
}
from
'../utils/tools'
;
import
{
SpecificInputEnum
}
from
'@/constants/app'
;
interface
Props
{
interface
Props
{
res
:
NextApiResponse
;
// 用于流转发
res
:
NextApiResponse
;
// 用于流转发
...
@@ -13,7 +14,7 @@ export const moduleFetch = ({ url, data, res }: Props) =>
...
@@ -13,7 +14,7 @@ export const moduleFetch = ({ url, data, res }: Props) =>
new
Promise
<
Record
<
string
,
any
>>
(
async
(
resolve
,
reject
)
=>
{
new
Promise
<
Record
<
string
,
any
>>
(
async
(
resolve
,
reject
)
=>
{
try
{
try
{
const
abortSignal
=
new
AbortController
();
const
abortSignal
=
new
AbortController
();
const
baseUrl
=
`http://localhost:
3000
/api`
;
const
baseUrl
=
`http://localhost:
${
process
.
env
.
PORT
||
3000
}
/api`
;
const
requestUrl
=
url
.
startsWith
(
'/'
)
?
`
${
baseUrl
}${
url
}
`
:
url
;
const
requestUrl
=
url
.
startsWith
(
'/'
)
?
`
${
baseUrl
}${
url
}
`
:
url
;
const
response
=
await
fetch
(
requestUrl
,
{
const
response
=
await
fetch
(
requestUrl
,
{
method
:
'POST'
,
method
:
'POST'
,
...
@@ -41,7 +42,9 @@ export const moduleFetch = ({ url, data, res }: Props) =>
...
@@ -41,7 +42,9 @@ export const moduleFetch = ({ url, data, res }: Props) =>
const
reader
=
response
.
body
?.
getReader
();
const
reader
=
response
.
body
?.
getReader
();
let
chatResponse
:
Record
<
string
,
any
>
=
{};
let
chatResponse
:
Record
<
string
,
any
>
=
{
[
SpecificInputEnum
.
answerText
]:
''
};
const
read
=
async
()
=>
{
const
read
=
async
()
=>
{
try
{
try
{
...
@@ -80,7 +83,8 @@ export const moduleFetch = ({ url, data, res }: Props) =>
...
@@ -80,7 +83,8 @@ export const moduleFetch = ({ url, data, res }: Props) =>
if
(
answer
)
{
if
(
answer
)
{
chatResponse
=
{
chatResponse
=
{
...
chatResponse
,
...
chatResponse
,
answer
:
chatResponse
.
answer
?
chatResponse
.
answer
+
answer
:
answer
[
SpecificInputEnum
.
answerText
]:
chatResponse
[
SpecificInputEnum
.
answerText
]
+
answer
};
};
}
}
...
...
client/src/service/models/chat.ts
View file @
b2e2f60e
...
@@ -8,21 +8,11 @@ const ChatSchema = new Schema({
...
@@ -8,21 +8,11 @@ const ChatSchema = new Schema({
ref
:
'user'
,
ref
:
'user'
,
required
:
true
required
:
true
},
},
model
Id
:
{
app
Id
:
{
type
:
Schema
.
Types
.
ObjectId
,
type
:
Schema
.
Types
.
ObjectId
,
ref
:
'model'
,
ref
:
'model'
,
required
:
true
required
:
true
},
},
expiredTime
:
{
// 过期时间
type
:
Number
,
default
:
()
=>
new
Date
()
},
loadAmount
:
{
// 剩余加载次数
type
:
Number
,
default
:
-
1
},
updateTime
:
{
updateTime
:
{
type
:
Date
,
type
:
Date
,
default
:
()
=>
new
Date
()
default
:
()
=>
new
Date
()
...
@@ -35,13 +25,13 @@ const ChatSchema = new Schema({
...
@@ -35,13 +25,13 @@ const ChatSchema = new Schema({
type
:
String
,
type
:
String
,
default
:
''
default
:
''
},
},
latestChat
:
{
type
:
String
,
default
:
''
},
top
:
{
top
:
{
type
:
Boolean
type
:
Boolean
},
},
variables
:
{
type
:
Object
,
default
:
{}
},
content
:
{
content
:
{
type
:
[
type
:
[
{
{
...
...
client/src/service/models/collection.ts
View file @
b2e2f60e
...
@@ -7,7 +7,7 @@ const CollectionSchema = new Schema({
...
@@ -7,7 +7,7 @@ const CollectionSchema = new Schema({
ref
:
'user'
,
ref
:
'user'
,
required
:
true
required
:
true
},
},
model
Id
:
{
app
Id
:
{
type
:
Schema
.
Types
.
ObjectId
,
type
:
Schema
.
Types
.
ObjectId
,
ref
:
'model'
,
ref
:
'model'
,
required
:
true
required
:
true
...
...
client/src/service/models/installApp.ts
deleted
100644 → 0
View file @
eb768d9c
import
{
Schema
,
model
,
models
,
Model
}
from
'mongoose'
;
import
{
ChatSchema
as
ChatType
}
from
'@/types/mongoSchema'
;
import
{
ChatRoleMap
}
from
'@/constants/chat'
;
const
InstallAppSchema
=
new
Schema
({
userId
:
{
type
:
Schema
.
Types
.
ObjectId
,
ref
:
'user'
,
required
:
true
},
modelId
:
{
type
:
Schema
.
Types
.
ObjectId
,
ref
:
'model'
,
required
:
true
}
});
export
const
InstallApp
:
Model
<
ChatType
>
=
models
[
'installApp'
]
||
model
(
'chat'
,
InstallAppSchema
);
client/src/service/utils/auth.ts
View file @
b2e2f60e
...
@@ -258,65 +258,6 @@ export const authKb = async ({ kbId, userId }: { kbId: string; userId: string })
...
@@ -258,65 +258,6 @@ export const authKb = async ({ kbId, userId }: { kbId: string; userId: string })
return
Promise
.
reject
(
ERROR_ENUM
.
unAuthKb
);
return
Promise
.
reject
(
ERROR_ENUM
.
unAuthKb
);
};
};
// 获取对话校验
export
const
authChat
=
async
({
modelId
,
chatId
,
req
}:
{
modelId
:
string
;
chatId
?:
string
;
req
:
NextApiRequest
;
})
=>
{
const
{
userId
}
=
await
authUser
({
req
,
authToken
:
true
});
// 获取 app 数据
const
{
app
,
showModelDetail
}
=
await
authApp
({
appId
:
modelId
,
userId
,
authOwner
:
false
,
reserveDetail
:
true
});
// 聊天内容
let
content
:
ChatItemType
[]
=
[];
if
(
chatId
)
{
// 获取 chat 数据
content
=
await
Chat
.
aggregate
([
{
$match
:
{
_id
:
new
mongoose
.
Types
.
ObjectId
(
chatId
)
}
},
{
$project
:
{
content
:
{
$slice
:
[
'$content'
,
-
50
]
// 返回 content 数组的最后50个元素
}
}
},
{
$unwind
:
'$content'
},
{
$project
:
{
obj
:
'$content.obj'
,
value
:
'$content.value'
,
quote
:
'$content.quote'
}
}
]);
}
// 获取 user 的 apiKey
const
{
userOpenAiKey
,
systemAuthKey
}
=
await
getApiKey
({
model
:
app
.
chat
.
chatModel
,
userId
});
return
{
userOpenAiKey
,
systemAuthKey
,
content
,
userId
,
model
:
app
,
showModelDetail
};
};
export
const
authShareChat
=
async
({
shareId
}:
{
shareId
:
string
})
=>
{
export
const
authShareChat
=
async
({
shareId
}:
{
shareId
:
string
})
=>
{
// get shareChat
// get shareChat
const
shareChat
=
await
ShareChat
.
findOne
({
shareId
});
const
shareChat
=
await
ShareChat
.
findOne
({
shareId
});
...
...
client/src/store/chat.ts
View file @
b2e2f60e
import
{
create
}
from
'zustand'
;
import
{
create
}
from
'zustand'
;
import
{
devtools
,
persist
}
from
'zustand/middleware'
;
import
{
devtools
,
persist
}
from
'zustand/middleware'
;
import
{
immer
}
from
'zustand/middleware/immer'
;
import
{
immer
}
from
'zustand/middleware/immer'
;
import
{
OpenAiChatEnum
}
from
'@/constants/model'
;
import
{
ChatSiteItemType
,
HistoryItemType
,
ChatType
}
from
'@/types/chat'
;
import
{
ChatHistoryItemType
}
from
'@/types/chat'
;
import
type
{
InitChatResponse
}
from
'@/api/response/chat'
;
import
{
getChatHistory
}
from
'@/api/chat'
;
import
{
getChatHistory
}
from
'@/api/chat'
;
import
{
HUMAN_ICON
}
from
'@/constants/chat'
;
import
{
HUMAN_ICON
}
from
'@/constants/chat'
;
type
SetShareChatHistoryItem
=
{
historyId
:
string
;
shareId
:
string
;
title
:
string
;
latestChat
:
string
;
chats
:
ChatSiteItemType
[];
};
type
State
=
{
type
State
=
{
history
:
HistoryItemType
[];
history
:
ChatHistoryItemType
[];
loadHistory
:
(
data
:
{
pageNum
:
number
;
init
?:
boolean
})
=>
Promise
<
null
>
;
loadHistory
:
(
data
:
{
appId
?:
string
})
=>
Promise
<
null
>
;
forbidLoadChatData
:
boolean
;
updateHistory
:
(
history
:
ChatHistoryItemType
)
=>
void
;
setForbidLoadChatData
:
(
val
:
boolean
)
=>
void
;
chatData
:
InitChatResponse
;
chatData
:
ChatType
;
setChatData
:
(
e
:
InitChatResponse
|
((
e
:
InitChatResponse
)
=>
InitChatResponse
))
=>
void
;
setChatData
:
(
e
?:
ChatType
|
((
e
:
ChatType
)
=>
ChatType
))
=>
void
;
lastChatAppId
:
string
;
lastChatModelId
:
string
;
setLastChatAppId
:
(
id
:
string
)
=>
void
;
setLastChatModelId
:
(
id
:
string
)
=>
void
;
lastChatId
:
string
;
lastChatId
:
string
;
setLastChatId
:
(
id
:
string
)
=>
void
;
setLastChatId
:
(
id
:
string
)
=>
void
;
};
};
const
defaultChatData
:
ChatTyp
e
=
{
const
defaultChatData
:
InitChatRespons
e
=
{
chatId
:
'chatId
'
,
historyId
:
'
'
,
modelId
:
'modelId
'
,
appId
:
'
'
,
model
:
{
app
:
{
name
:
''
,
name
:
''
,
avatar
:
'/icon/logo.png'
,
avatar
:
'/icon/logo.png'
,
intro
:
''
,
intro
:
''
,
canUse
:
false
canUse
:
false
},
},
chatModel
:
OpenAiChatEnum
.
GPT3516k
,
title
:
'新对话'
,
variables
:
{},
history
:
[]
history
:
[]
};
};
...
@@ -45,10 +37,10 @@ export const useChatStore = create<State>()(
...
@@ -45,10 +37,10 @@ export const useChatStore = create<State>()(
devtools
(
devtools
(
persist
(
persist
(
immer
((
set
,
get
)
=>
({
immer
((
set
,
get
)
=>
({
lastChat
Model
Id
:
''
,
lastChat
App
Id
:
''
,
setLastChat
Model
Id
(
id
:
string
)
{
setLastChat
App
Id
(
id
:
string
)
{
set
((
state
)
=>
{
set
((
state
)
=>
{
state
.
lastChat
Model
Id
=
id
;
state
.
lastChat
App
Id
=
id
;
});
});
},
},
lastChatId
:
''
,
lastChatId
:
''
,
...
@@ -58,10 +50,12 @@ export const useChatStore = create<State>()(
...
@@ -58,10 +50,12 @@ export const useChatStore = create<State>()(
});
});
},
},
history
:
[],
history
:
[],
async
loadHistory
({
pageNum
,
init
=
false
}:
{
pageNum
:
number
;
init
?:
boolean
})
{
async
loadHistory
({
appId
})
{
if
(
get
().
history
.
length
>
0
&&
!
init
)
return
null
;
const
oneHistory
=
get
().
history
[
0
];
if
(
oneHistory
&&
oneHistory
.
appId
===
appId
)
return
null
;
const
data
=
await
getChatHistory
({
const
data
=
await
getChatHistory
({
pageNum
,
appId
,
pageNum
:
1
,
pageSize
:
20
pageSize
:
20
});
});
set
((
state
)
=>
{
set
((
state
)
=>
{
...
@@ -69,14 +63,23 @@ export const useChatStore = create<State>()(
...
@@ -69,14 +63,23 @@ export const useChatStore = create<State>()(
});
});
return
null
;
return
null
;
},
},
forbidLoadChatData
:
false
,
updateHistory
(
history
)
{
setForbidLoadChatData
(
val
:
boolean
)
{
const
index
=
get
().
history
.
findIndex
((
item
)
=>
item
.
_id
===
history
.
_id
);
set
((
state
)
=>
{
set
((
state
)
=>
{
state
.
forbidLoadChatData
=
val
;
if
(
index
>
-
1
)
{
const
newHistory
=
[
history
,
...
get
().
history
.
slice
(
0
,
index
),
...
get
().
history
.
slice
(
index
+
1
)
];
state
.
history
=
newHistory
;
}
else
{
state
.
history
=
[
history
,
...
state
.
history
];
}
});
});
},
},
chatData
:
defaultChatData
,
chatData
:
defaultChatData
,
setChatData
(
e
:
ChatType
|
((
e
:
ChatType
)
=>
ChatType
)
=
defaultChatData
)
{
setChatData
(
e
=
defaultChatData
)
{
if
(
typeof
e
===
'function'
)
{
if
(
typeof
e
===
'function'
)
{
set
((
state
)
=>
{
set
((
state
)
=>
{
state
.
chatData
=
e
(
state
.
chatData
);
state
.
chatData
=
e
(
state
.
chatData
);
...
@@ -91,7 +94,7 @@ export const useChatStore = create<State>()(
...
@@ -91,7 +94,7 @@ export const useChatStore = create<State>()(
{
{
name
:
'chatStore'
,
name
:
'chatStore'
,
partialize
:
(
state
)
=>
({
partialize
:
(
state
)
=>
({
lastChat
ModelId
:
state
.
lastChatModel
Id
,
lastChat
AppId
:
state
.
lastChatApp
Id
,
lastChatId
:
state
.
lastChatId
lastChatId
:
state
.
lastChatId
})
})
}
}
...
...
client/src/store/user.ts
View file @
b2e2f60e
...
@@ -74,8 +74,7 @@ export const useUserStore = create<State>()(
...
@@ -74,8 +74,7 @@ export const useUserStore = create<State>()(
async
loadMyModels
()
{
async
loadMyModels
()
{
const
res
=
await
getMyModels
();
const
res
=
await
getMyModels
();
set
((
state
)
=>
{
set
((
state
)
=>
{
state
.
myApps
=
res
.
myApps
;
state
.
myApps
=
res
;
state
.
myCollectionApps
=
res
.
myCollectionApps
;
});
});
return
null
;
return
null
;
},
},
...
...
client/src/types/chat.d.ts
View file @
b2e2f60e
...
@@ -18,10 +18,6 @@ export type ChatSiteItemType = {
...
@@ -18,10 +18,6 @@ export type ChatSiteItemType = {
status
:
'loading'
|
'finish'
;
status
:
'loading'
|
'finish'
;
}
&
ChatItemType
;
}
&
ChatItemType
;
export
interface
ChatType
extends
InitChatResponse
{
history
:
ChatSiteItemType
[];
}
export
type
HistoryItemType
=
{
export
type
HistoryItemType
=
{
_id
:
string
;
_id
:
string
;
updateTime
:
Date
;
updateTime
:
Date
;
...
...
client/src/types/mongoSchema.d.ts
View file @
b2e2f60e
...
@@ -58,7 +58,7 @@ export interface AppSchema {
...
@@ -58,7 +58,7 @@ export interface AppSchema {
}
}
export
interface
CollectionSchema
{
export
interface
CollectionSchema
{
model
Id
:
string
;
app
Id
:
string
;
userId
:
string
;
userId
:
string
;
}
}
...
@@ -78,19 +78,14 @@ export interface TrainingDataSchema {
...
@@ -78,19 +78,14 @@ export interface TrainingDataSchema {
export
interface
ChatSchema
{
export
interface
ChatSchema
{
_id
:
string
;
_id
:
string
;
userId
:
string
;
userId
:
string
;
modelId
:
string
;
appId
:
string
;
expiredTime
:
number
;
updateTime
:
Date
;
updateTime
:
Date
;
title
:
string
;
title
:
string
;
customTitle
:
string
;
customTitle
:
string
;
latestChat
:
string
;
top
:
boolean
;
top
:
boolean
;
variables
:
Record
<
string
,
any
>
;
content
:
ChatItemType
[];
content
:
ChatItemType
[];
}
}
export
interface
ChatPopulate
extends
ChatSchema
{
userId
:
UserModelSchema
;
modelId
:
AppSchema
;
}
export
interface
BillSchema
{
export
interface
BillSchema
{
_id
:
string
;
_id
:
string
;
...
...
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