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
9a31407a
authored
Aug 04, 2023
by
archer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: chat status
parent
c7bfd773
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
205 additions
and
46 deletions
+205
-46
client/public/locales/en/common.json
+1
-0
client/public/locales/zh/common.json
+1
-0
client/src/api/fetch.ts
+9
-2
client/src/components/ChatBox/index.module.scss
+13
-0
client/src/components/ChatBox/index.tsx
+57
-5
client/src/components/Markdown/index.tsx
+1
-1
client/src/constants/chat.ts
+2
-3
client/src/constants/flow/ModuleTemplate.ts
+58
-25
client/src/pages/api/openapi/v1/chat/completions.ts
+36
-4
client/src/pages/app/detail/components/AdEdit/components/TemplateList.tsx
+2
-3
client/src/pages/app/detail/components/AdEdit/index.tsx
+2
-0
client/src/service/moduleDispatch/agent/extract.ts
+3
-0
client/src/types/app.d.ts
+7
-2
client/src/types/chat.d.ts
+2
-1
client/src/types/flow.d.ts
+1
-0
client/src/utils/app.ts
+10
-0
No files found.
client/public/locales/en/common.json
View file @
9a31407a
...
...
@@ -3,6 +3,7 @@
"Cancel"
:
"No"
,
"Confirm"
:
"Yes"
,
"Warning"
:
"Warning"
,
"Running"
:
"Running"
,
"app"
:
{
"App Detail"
:
"App Detail"
,
"Confirm Del App Tip"
:
"Confirm to delete the app and all its chats"
,
...
...
client/public/locales/zh/common.json
View file @
9a31407a
...
...
@@ -3,6 +3,7 @@
"Cancel"
:
"取消"
,
"Confirm"
:
"确认"
,
"Warning"
:
"提示"
,
"Running"
:
"运行中"
,
"app"
:
{
"App Detail"
:
"应用详情"
,
"Confirm Del App Tip"
:
"确认删除该应用及其所有聊天记录?"
,
...
...
client/src/api/fetch.ts
View file @
9a31407a
...
...
@@ -2,11 +2,12 @@ import { sseResponseEventEnum, TaskResponseKeyEnum } from '@/constants/chat';
import
{
getErrText
}
from
'@/utils/tools'
;
import
{
parseStreamChunk
,
SSEParseData
}
from
'@/utils/sse'
;
import
type
{
ChatHistoryItemResType
}
from
'@/types/chat'
;
import
{
StartChatFnProps
}
from
'@/components/ChatBox'
;
interface
StreamFetchProps
{
url
?:
string
;
data
:
Record
<
string
,
any
>
;
onMessage
:
(
text
:
string
)
=>
void
;
onMessage
:
StartChatFnProps
[
'generatingMessage'
]
;
abortSignal
:
AbortController
;
}
export
const
streamFetch
=
({
...
...
@@ -71,9 +72,15 @@ export const streamFetch = ({
if
(
eventName
===
sseResponseEventEnum
.
answer
&&
data
!==
'[DONE]'
)
{
const
answer
:
string
=
data
?.
choices
?.[
0
].
delta
.
content
||
''
;
onMessage
(
answer
);
onMessage
(
{
text
:
answer
}
);
responseText
+=
answer
;
}
else
if
(
eventName
===
sseResponseEventEnum
.
moduleStatus
&&
data
?.
name
&&
data
?.
status
)
{
onMessage
(
data
);
}
else
if
(
eventName
===
sseResponseEventEnum
.
appStreamResponse
&&
Array
.
isArray
(
data
)
)
{
...
...
client/src/components/ChatBox/index.module.scss
View file @
9a31407a
...
...
@@ -28,3 +28,16 @@
}
}
}
.statusAnimation
{
animation
:
statusBox
0
.8s
linear
infinite
alternate
;
}
@keyframes
statusBox
{
0
%
{
opacity
:
1
;
}
100
%
{
opacity
:
0
.11
;
}
}
client/src/components/ChatBox/index.tsx
View file @
9a31407a
...
...
@@ -39,6 +39,7 @@ import { htmlTemplate } from '@/constants/common';
import
{
useRouter
}
from
'next/router'
;
import
{
useGlobalStore
}
from
'@/store/global'
;
import
{
TaskResponseKeyEnum
,
getDefaultChatVariables
}
from
'@/constants/chat'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
MyIcon
from
'@/components/Icon'
;
import
Avatar
from
'@/components/Avatar'
;
...
...
@@ -51,11 +52,12 @@ const ResponseDetailModal = dynamic(() => import('./ResponseDetailModal'));
import
styles
from
'./index.module.scss'
;
const
textareaMinH
=
'22px'
;
type
generatingMessageProps
=
{
text
?:
string
;
name
?:
string
;
status
?:
'running'
|
'finish'
};
export
type
StartChatFnProps
=
{
messages
:
MessageItemType
[];
controller
:
AbortController
;
variables
:
Record
<
string
,
any
>
;
generatingMessage
:
(
text
:
string
)
=>
void
;
generatingMessage
:
(
e
:
generatingMessageProps
)
=>
void
;
};
export
type
ComponentRef
=
{
...
...
@@ -153,6 +155,7 @@ const ChatBox = (
const
ChatBoxRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
theme
=
useTheme
();
const
router
=
useRouter
();
const
{
t
}
=
useTranslation
();
const
{
copyData
}
=
useCopyData
();
const
{
toast
}
=
useToast
();
const
{
isPc
}
=
useGlobalStore
();
...
...
@@ -164,7 +167,9 @@ const ChatBox = (
const
[
chatHistory
,
setChatHistory
]
=
useState
<
ChatSiteItemType
[]
>
([]);
const
isChatting
=
useMemo
(
()
=>
chatHistory
[
chatHistory
.
length
-
1
]?.
status
===
'loading'
,
()
=>
chatHistory
[
chatHistory
.
length
-
1
]
&&
chatHistory
[
chatHistory
.
length
-
1
]?.
status
!==
'finish'
,
[
chatHistory
]
);
const
variableIsFinish
=
useMemo
(()
=>
{
...
...
@@ -209,13 +214,23 @@ const ChatBox = (
);
// eslint-disable-next-line react-hooks/exhaustive-deps
const
generatingMessage
=
useCallback
(
(
text
:
string
)
=>
{
(
{
text
=
''
,
status
,
name
}:
generatingMessageProps
)
=>
{
setChatHistory
((
state
)
=>
state
.
map
((
item
,
index
)
=>
{
if
(
index
!==
state
.
length
-
1
)
return
item
;
return
{
...
item
,
value
:
item
.
value
+
text
...(
text
?
{
value
:
item
.
value
+
text
}
:
{}),
...(
status
&&
name
?
{
status
,
moduleName
:
name
}
:
{})
};
})
);
...
...
@@ -418,6 +433,21 @@ const ChatBox = (
!
welcomeText
,
[
chatHistory
.
length
,
showEmptyIntro
,
variableModules
,
welcomeText
]
);
const
statusBoxData
=
useMemo
(()
=>
{
const
colorMap
=
{
loading
:
'#67c13b'
,
running
:
'#67c13b'
,
finish
:
'myBlue.600'
};
if
(
!
isChatting
)
return
;
const
chatContent
=
chatHistory
[
chatHistory
.
length
-
1
];
if
(
!
chatContent
)
return
;
return
{
bg
:
colorMap
[
chatContent
.
status
]
||
colorMap
.
loading
,
name
:
t
(
chatContent
.
moduleName
||
'Running'
)
};
},
[
chatHistory
,
isChatting
,
t
]);
useEffect
(()
=>
{
return
()
=>
{
...
...
@@ -595,7 +625,7 @@ const ChatBox = (
)
}
{
item
.
obj
===
'AI'
&&
(
<>
<
Flex
w=
{
'100%'
}
alignItems=
{
'
center
'
}
>
<
Flex
w=
{
'100%'
}
alignItems=
{
'
flex-end
'
}
>
<
ChatAvatar
src=
{
appAvatar
}
type=
{
'AI'
}
/>
<
Flex
{
...
controlContainerStyle
}
ml=
{
3
}
>
<
MyTooltip
label=
{
'复制'
}
>
...
...
@@ -635,6 +665,28 @@ const ChatBox = (
</
MyTooltip
>
)
}
</
Flex
>
{
statusBoxData
&&
index
===
chatHistory
.
length
-
1
&&
(
<
Flex
ml=
{
3
}
alignItems=
{
'center'
}
px=
{
3
}
py=
{
'1px'
}
borderRadius=
"md"
border=
{
theme
.
borders
.
base
}
>
<
Box
className=
{
styles
.
statusAnimation
}
bg=
{
statusBoxData
.
bg
}
w=
"8px"
h=
"8px"
borderRadius=
{
'50%'
}
mt=
{
'1px'
}
></
Box
>
<
Box
ml=
{
2
}
color=
{
'myGray.600'
}
>
{
statusBoxData
.
name
}
</
Box
>
</
Flex
>
)
}
</
Flex
>
<
Box
position=
{
'relative'
}
maxW=
{
messageCardMaxW
}
mt=
{
[
'6px'
,
2
]
}
>
<
Card
bg=
{
'white'
}
{
...
MessageCardStyle
}
>
...
...
client/src/components/Markdown/index.tsx
View file @
9a31407a
import
React
,
{
useMemo
,
useRef
}
from
'react'
;
import
React
,
{
useMemo
}
from
'react'
;
import
ReactMarkdown
from
'react-markdown'
;
import
RemarkGfm
from
'remark-gfm'
;
import
RemarkMath
from
'remark-math'
;
...
...
client/src/constants/chat.ts
View file @
9a31407a
...
...
@@ -3,9 +3,8 @@ import dayjs from 'dayjs';
export
enum
sseResponseEventEnum
{
error
=
'error'
,
answer
=
'answer'
,
chatResponse
=
'chatResponse'
,
//
appStreamResponse
=
'appStreamResponse'
,
// sse response request
moduleFetchResponse
=
'moduleFetchResponse'
// http module sse response
moduleStatus
=
'moduleStatus'
,
appStreamResponse
=
'appStreamResponse'
// sse response request
}
export
enum
ChatRoleEnum
{
...
...
client/src/constants/flow/ModuleTemplate.ts
View file @
9a31407a
This diff is collapsed.
Click to expand it.
client/src/pages/api/openapi/v1/chat/completions.ts
View file @
9a31407a
...
...
@@ -116,10 +116,12 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}
// 创建响应流
res
.
setHeader
(
'Content-Type'
,
'text/event-stream;charset=utf-8'
);
res
.
setHeader
(
'Access-Control-Allow-Origin'
,
'*'
);
res
.
setHeader
(
'X-Accel-Buffering'
,
'no'
);
res
.
setHeader
(
'Cache-Control'
,
'no-cache, no-transform'
);
if
(
stream
)
{
res
.
setHeader
(
'Content-Type'
,
'text/event-stream;charset=utf-8'
);
res
.
setHeader
(
'Access-Control-Allow-Origin'
,
'*'
);
res
.
setHeader
(
'X-Accel-Buffering'
,
'no'
);
res
.
setHeader
(
'Cache-Control'
,
'no-cache, no-transform'
);
}
/* start process */
const
{
responseData
,
answerText
}
=
await
dispatchModules
({
...
...
@@ -320,6 +322,14 @@ export async function dispatchModules({
if
(
res
.
closed
)
return
Promise
.
resolve
();
console
.
log
(
'run========='
,
module
.
flowType
);
if
(
stream
&&
module
.
showStatus
)
{
responseStatus
({
res
,
name
:
module
.
name
,
status
:
'running'
});
}
// get fetch params
const
params
:
Record
<
string
,
any
>
=
{};
module
.
inputs
.
forEach
((
item
:
any
)
=>
{
...
...
@@ -370,7 +380,9 @@ function loadModules(
return
modules
.
map
((
module
)
=>
{
return
{
moduleId
:
module
.
moduleId
,
name
:
module
.
name
,
flowType
:
module
.
flowType
,
showStatus
:
module
.
showStatus
,
inputs
:
module
.
inputs
.
filter
((
item
)
=>
item
.
connected
)
// filter unconnected target input
.
map
((
item
)
=>
{
...
...
@@ -401,3 +413,23 @@ function loadModules(
};
});
}
function
responseStatus
({
res
,
status
,
name
}:
{
res
:
NextApiResponse
;
status
:
'running'
|
'finish'
;
name
?:
string
;
})
{
if
(
!
name
)
return
;
sseResponse
({
res
,
event
:
sseResponseEventEnum
.
moduleStatus
,
data
:
JSON
.
stringify
({
status
,
name
})
});
}
client/src/pages/app/detail/components/AdEdit/components/TemplateList.tsx
View file @
9a31407a
import
React
,
{
useMemo
}
from
'react'
;
import
{
Box
,
Flex
}
from
'@chakra-ui/react'
;
import
{
ModuleTemplates
}
from
'@/constants/flow/ModuleTemplate'
;
import
{
FlowModuleTemplateType
}
from
'@/types/flow'
;
import
{
FlowModule
ItemType
,
FlowModule
TemplateType
}
from
'@/types/flow'
;
import
type
{
Node
,
XYPosition
}
from
'reactflow'
;
import
{
useGlobalStore
}
from
'@/store/global'
;
import
type
{
AppModuleItemType
}
from
'@/types/app'
;
import
Avatar
from
'@/components/Avatar'
;
import
{
FlowModuleTypeEnum
}
from
'@/constants/flow'
;
...
...
@@ -14,7 +13,7 @@ const ModuleTemplateList = ({
onAddNode
,
onClose
}:
{
nodes
?:
Node
<
App
ModuleItemType
>
[];
nodes
?:
Node
<
Flow
ModuleItemType
>
[];
isOpen
:
boolean
;
onAddNode
:
(
e
:
{
template
:
FlowModuleTemplateType
;
position
:
XYPosition
})
=>
void
;
onClose
:
()
=>
void
;
...
...
client/src/pages/app/detail/components/AdEdit/index.tsx
View file @
9a31407a
...
...
@@ -158,7 +158,9 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
const
flow2AppModules
=
useCallback
(()
=>
{
const
modules
:
AppModuleItemType
[]
=
nodes
.
map
((
item
)
=>
({
moduleId
:
item
.
data
.
moduleId
,
name
:
item
.
data
.
name
,
flowType
:
item
.
data
.
flowType
,
showStatus
:
item
.
data
.
showStatus
,
position
:
item
.
position
,
inputs
:
item
.
data
.
inputs
.
map
((
item
)
=>
({
...
item
,
...
...
client/src/service/moduleDispatch/agent/extract.ts
View file @
9a31407a
...
...
@@ -34,6 +34,9 @@ export async function dispatchContentExtract({
history
=
[],
description
}:
Props
):
Promise
<
Response
>
{
if
(
!
content
)
{
return
Promise
.
reject
(
'Input is empty'
);
}
const
messages
:
ChatItemType
[]
=
[
...
history
,
{
...
...
client/src/types/app.d.ts
View file @
9a31407a
...
...
@@ -69,9 +69,11 @@ export type VariableItemType = {
/* app module */
export
type
AppModuleItemType
=
{
name
:
string
;
moduleId
:
string
;
position
?:
XYPosition
;
flowType
:
`
${
FlowModuleTypeEnum
}
`
;
showStatus
?:
boolean
;
inputs
:
FlowInputItemType
[];
outputs
:
FlowOutputItemType
[];
};
...
...
@@ -83,8 +85,11 @@ export type AppItemType = {
};
export
type
RunningModuleItemType
=
{
moduleId
:
string
;
flowType
:
`
${
FlowModuleTypeEnum
}
`
;
name
:
AppModuleItemType
[
'name'
];
moduleId
:
AppModuleItemType
[
'moduleId'
];
flowType
:
AppModuleItemType
[
'flowType'
];
showStatus
?:
AppModuleItemType
[
'showStatus'
];
}
&
{
inputs
:
{
key
:
string
;
value
?:
any
;
...
...
client/src/types/chat.d.ts
View file @
9a31407a
...
...
@@ -13,7 +13,8 @@ export type ChatItemType = {
};
export
type
ChatSiteItemType
=
{
status
:
'loading'
|
'finish'
;
status
:
'loading'
|
'running'
|
'finish'
;
moduleName
?:
string
;
}
&
ChatItemType
;
export
type
HistoryItemType
=
{
...
...
client/src/types/flow.d.ts
View file @
9a31407a
...
...
@@ -55,6 +55,7 @@ export type FlowModuleTemplateType = {
flowType
:
`
${
FlowModuleTypeEnum
}
`
;
inputs
:
FlowInputItemType
[];
outputs
:
FlowOutputItemType
[];
showStatus
?:
boolean
;
};
export
type
FlowModuleItemType
=
FlowModuleTemplateType
&
{
moduleId
:
string
;
...
...
client/src/utils/app.ts
View file @
9a31407a
...
...
@@ -219,6 +219,7 @@ const welcomeTemplate = (formData: EditFormType): AppModuleItemType[] =>
formData
.
guide
?.
welcome
?.
text
?
[
{
name
:
'用户引导'
,
flowType
:
FlowModuleTypeEnum
.
userGuide
,
inputs
:
[
{
...
...
@@ -242,6 +243,7 @@ const variableTemplate = (formData: EditFormType): AppModuleItemType[] =>
formData
.
variables
.
length
>
0
?
[
{
name
:
'全局变量'
,
flowType
:
FlowModuleTypeEnum
.
variable
,
inputs
:
[
{
...
...
@@ -263,6 +265,7 @@ const variableTemplate = (formData: EditFormType): AppModuleItemType[] =>
:
[];
const
simpleChatTemplate
=
(
formData
:
EditFormType
):
AppModuleItemType
[]
=>
[
{
name
:
'用户问题(对话入口)'
,
flowType
:
FlowModuleTypeEnum
.
questionInput
,
inputs
:
[
{
...
...
@@ -290,6 +293,7 @@ const simpleChatTemplate = (formData: EditFormType): AppModuleItemType[] => [
moduleId
:
'userChatInput'
},
{
name
:
'聊天记录'
,
flowType
:
FlowModuleTypeEnum
.
historyNode
,
inputs
:
[
{
...
...
@@ -324,6 +328,7 @@ const simpleChatTemplate = (formData: EditFormType): AppModuleItemType[] => [
moduleId
:
'history'
},
{
name
:
'AI 对话'
,
flowType
:
FlowModuleTypeEnum
.
chatNode
,
inputs
:
chatModelInput
(
formData
),
outputs
:
[
...
...
@@ -352,6 +357,7 @@ const simpleChatTemplate = (formData: EditFormType): AppModuleItemType[] => [
];
const
kbTemplate
=
(
formData
:
EditFormType
):
AppModuleItemType
[]
=>
[
{
name
:
'用户问题(对话入口)'
,
flowType
:
FlowModuleTypeEnum
.
questionInput
,
inputs
:
[
{
...
...
@@ -383,6 +389,7 @@ const kbTemplate = (formData: EditFormType): AppModuleItemType[] => [
moduleId
:
'userChatInput'
},
{
name
:
'聊天记录'
,
flowType
:
FlowModuleTypeEnum
.
historyNode
,
inputs
:
[
{
...
...
@@ -417,6 +424,7 @@ const kbTemplate = (formData: EditFormType): AppModuleItemType[] => [
moduleId
:
'history'
},
{
name
:
'知识库搜索'
,
flowType
:
FlowModuleTypeEnum
.
kbSearchNode
,
inputs
:
[
{
...
...
@@ -498,6 +506,7 @@ const kbTemplate = (formData: EditFormType): AppModuleItemType[] => [
...(
formData
.
kb
.
searchEmptyText
?
[
{
name
:
'指定回复'
,
flowType
:
FlowModuleTypeEnum
.
answerNode
,
inputs
:
[
{
...
...
@@ -525,6 +534,7 @@ const kbTemplate = (formData: EditFormType): AppModuleItemType[] => [
]
:
[]),
{
name
:
'AI 对话'
,
flowType
:
FlowModuleTypeEnum
.
chatNode
,
inputs
:
chatModelInput
(
formData
),
outputs
:
[
...
...
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