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
2534cdc6
authored
Jun 18, 2026
by
DigHuang
Committed by
GitHub
Jun 18, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(skill): resolve debug chat state mismatch & simplify test container (#7146)
parent
313066b9
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
131 additions
and
161 deletions
+131
-161
projects/app/src/pageComponents/dashboard/skill/detail/preview/SkillPreview.tsx
+112
-12
projects/app/src/pageComponents/dashboard/skill/detail/preview/useSkillChatTest.tsx
+0
-147
projects/app/src/web/core/skill/api.ts
+19
-2
No files found.
projects/app/src/pageComponents/dashboard/skill/detail/preview/SkillPreview.tsx
View file @
2534cdc6
import
React
,
{
useCallback
,
useEffect
,
useMemo
,
useRef
,
useState
}
from
'react'
;
import
React
,
{
useCallback
,
useEffect
,
useMemo
,
useRef
,
useState
}
from
'react'
;
import
{
Box
}
from
'@chakra-ui/react'
;
import
{
Box
,
Flex
}
from
'@chakra-ui/react'
;
import
{
useContextSelector
}
from
'use-context-selector'
;
import
{
useContextSelector
}
from
'use-context-selector'
;
import
{
SkillDetailContext
}
from
'../context'
;
import
{
SkillDetailContext
}
from
'../context'
;
import
{
useSystemStore
}
from
'@/web/common/system/useSystemStore'
;
import
{
useSystemStore
}
from
'@/web/common/system/useSystemStore'
;
import
ChatItemContextProvider
from
'@/web/core/chat/context/chatItemContext'
;
import
ChatItemContextProvider
,
{
ChatItemContext
}
from
'@/web/core/chat/context/chatItemContext'
;
import
ChatRecordContextProvider
from
'@/web/core/chat/context/chatRecordContext'
;
import
ChatRecordContextProvider
from
'@/web/core/chat/context/chatRecordContext'
;
import
{
useSkillChatTest
}
from
'./useSkillChatTest'
;
import
{
import
{
getSkillDebugRecords
}
from
'@/web/core/skill/api'
;
delSkillDebugChatItem
,
getSkillDebugRecords
,
postStopSkillDebugChat
,
streamSkillDebugChat
}
from
'@/web/core/skill/api'
;
import
type
{
LinkedPaginationProps
}
from
'@fastgpt/global/openapi/api'
;
import
type
{
LinkedPaginationProps
}
from
'@fastgpt/global/openapi/api'
;
import
type
{
GetPaginationRecordsBodyType
}
from
'@fastgpt/global/openapi/core/chat/record/api'
;
import
type
{
GetPaginationRecordsBodyType
}
from
'@fastgpt/global/openapi/core/chat/record/api'
;
import
ChatAIModelSelector
from
'@/pageComponents/chat/ChatWindow/ChatAIModelSelector'
;
import
ChatAIModelSelector
from
'@/pageComponents/chat/ChatWindow/ChatAIModelSelector'
;
import
ChatBox
from
'@/components/core/chat/ChatContainer/ChatBox'
;
import
{
ChatTypeEnum
}
from
'@/components/core/chat/ChatContainer/ChatBox/constants'
;
import
{
useTranslation
}
from
'next-i18next'
;
import
{
AppTypeEnum
}
from
'@fastgpt/global/core/app/constants'
;
import
type
{
AppFileSelectConfigType
}
from
'@fastgpt/global/core/app/type/config.schema'
;
import
type
{
StartChatFnProps
}
from
'@/components/core/chat/ChatContainer/type'
;
import
{
useMemoizedFn
}
from
'ahooks'
;
const
fileSelectConfig
:
AppFileSelectConfigType
=
{
maxFiles
:
10
,
canSelectFile
:
false
,
canSelectImg
:
false
,
customPdfParse
:
false
,
canSelectVideo
:
false
,
canSelectAudio
:
false
,
canSelectCustomFileExtension
:
false
,
customFileExtensionList
:
[]
};
const
SkillPreview
=
()
=>
{
const
SkillPreview
=
()
=>
{
const
{
t
}
=
useTranslation
([
'skill'
,
'common'
]);
const
{
skillId
,
sandboxState
,
chatId
}
=
useContextSelector
(
SkillDetailContext
,
(
v
)
=>
({
const
{
skillId
,
sandboxState
,
chatId
}
=
useContextSelector
(
SkillDetailContext
,
(
v
)
=>
({
skillId
:
v
.
skillId
,
skillId
:
v
.
skillId
,
sandboxState
:
v
.
sandboxState
,
sandboxState
:
v
.
sandboxState
,
...
@@ -19,6 +42,7 @@ const SkillPreview = () => {
...
@@ -19,6 +42,7 @@ const SkillPreview = () => {
}));
}));
const
{
llmModelList
,
defaultModels
}
=
useSystemStore
();
const
{
llmModelList
,
defaultModels
}
=
useSystemStore
();
const
setChatBoxData
=
useContextSelector
(
ChatItemContext
,
(
v
)
=>
v
.
setChatBoxData
);
const
defaultModel
=
defaultModels
.
llm
?.
model
||
llmModelList
[
0
]?.
model
||
''
;
const
defaultModel
=
defaultModels
.
llm
?.
model
||
llmModelList
[
0
]?.
model
||
''
;
const
[
selectedModel
,
setSelectedModel
]
=
useState
(
''
);
const
[
selectedModel
,
setSelectedModel
]
=
useState
(
''
);
const
userSelectedModelRef
=
useRef
(
false
);
const
userSelectedModelRef
=
useRef
(
false
);
...
@@ -31,6 +55,28 @@ const SkillPreview = () => {
...
@@ -31,6 +55,28 @@ const SkillPreview = () => {
const
isReady
=
sandboxState
===
'ready'
;
const
isReady
=
sandboxState
===
'ready'
;
useEffect
(()
=>
{
useEffect
(()
=>
{
setChatBoxData
((
prev
)
=>
{
const
isSameChat
=
prev
.
appId
===
skillId
&&
prev
.
chatId
===
chatId
;
return
{
...
prev
,
appId
:
skillId
,
chatId
,
title
:
isSameChat
?
prev
.
title
:
undefined
,
chatGenerateStatus
:
isSameChat
?
prev
.
chatGenerateStatus
:
undefined
,
hasBeenRead
:
isSameChat
?
prev
.
hasBeenRead
:
undefined
,
app
:
{
chatConfig
:
{
fileSelectConfig
},
name
:
'Skill Preview'
,
avatar
:
''
,
type
:
AppTypeEnum
.
simple
,
pluginInputs
:
[]
}
};
});
},
[
skillId
,
chatId
,
setChatBoxData
]);
useEffect
(()
=>
{
if
(
!
userSelectedModelRef
.
current
&&
defaultModel
&&
selectedModel
!==
defaultModel
)
{
if
(
!
userSelectedModelRef
.
current
&&
defaultModel
&&
selectedModel
!==
defaultModel
)
{
setSelectedModel
(
defaultModel
);
setSelectedModel
(
defaultModel
);
}
}
...
@@ -54,18 +100,72 @@ const SkillPreview = () => {
...
@@ -54,18 +100,72 @@ const SkillPreview = () => {
);
);
},
[
selectedModel
,
modelSelectList
]);
},
[
selectedModel
,
modelSelectList
]);
const
{
ChatContainer
}
=
useSkillChatTest
({
const
onStartChat
=
useMemoizedFn
(
skillId
,
async
({
messages
,
responseChatItemId
,
controller
,
generatingMessage
}:
StartChatFnProps
)
=>
{
model
:
selectedModel
,
const
histories
=
messages
.
slice
(
-
1
);
chatId
,
isReady
,
const
{
responseText
}
=
await
streamSkillDebugChat
({
disabledSendTip
:
isReady
?
undefined
:
'skill:sandbox_debug_loading_toast'
,
data
:
{
InputLeftComponent
:
ModelSelectorInput
skillId
,
chatId
,
messages
:
histories
,
model
:
selectedModel
,
responseChatItemId
},
onMessage
:
generatingMessage
,
abortCtrl
:
controller
});
return
{
responseText
};
}
);
// 使用 skill 专属的删除接口,避免走 /api/core/chat/item/delete 时用 skillId 查 App 报错
const
onDeleteChatItem
=
useMemoizedFn
((
contentId
:
string
)
=>
delSkillDebugChatItem
({
skillId
,
chatId
,
contentId
})
);
const
onStopChat
=
useMemoizedFn
(
async
()
=>
{
const
result
=
await
postStopSkillDebugChat
({
skillId
,
chatId
});
return
{
chatGenerateStatus
:
result
.
chatGenerateStatus
,
completed
:
result
.
completed
};
});
});
return
(
return
(
<
Box
h=
{
'100%'
}
w=
{
'100%'
}
overflow=
{
'hidden'
}
>
<
Box
h=
{
'100%'
}
w=
{
'100%'
}
overflow=
{
'hidden'
}
>
<
ChatContainer
/>
<
ChatBox
isReady=
{
isReady
}
appId=
{
skillId
}
chatId=
{
chatId
}
chatType=
{
ChatTypeEnum
.
test
}
enableMarkChatRead=
{
false
}
onStartChat=
{
onStartChat
}
onDeleteChatItem=
{
onDeleteChatItem
}
onStopChat=
{
onStopChat
}
InputLeftComponent=
{
ModelSelectorInput
}
disabledSendTip=
{
isReady
?
undefined
:
t
(
'sandbox_lazy_init'
)
}
dialogTips=
{
t
(
'common:core.chat.Type a message'
)
}
pl=
{
'16px'
}
pr=
{
0
}
maxW=
{
'100%'
}
boxBodyProps=
{
{
px
:
0
,
pr
:
'8px'
,
maxW
:
'100%'
,
mx
:
0
}
}
inputBodyProps=
{
{
maxW
:
'100%'
,
mx
:
0
,
px
:
0
,
pl
:
0
,
pr
:
'8px'
}
}
EmptyState=
{
<
Flex
flex=
{
1
}
alignItems=
"center"
justifyContent=
"center"
color=
"myGray.500"
fontSize=
"sm"
textAlign=
"center"
lineHeight=
"20px"
whiteSpace=
"pre-wrap"
>
{
t
(
'empty_state_tip'
)
}
</
Flex
>
}
/>
</
Box
>
</
Box
>
);
);
};
};
...
...
projects/app/src/pageComponents/dashboard/skill/detail/preview/useSkillChatTest.tsx
deleted
100644 → 0
View file @
313066b9
import
{
useCallback
,
useEffect
}
from
'react'
;
import
{
useMemoizedFn
}
from
'ahooks'
;
import
{
useContextSelector
}
from
'use-context-selector'
;
import
{
streamFetch
}
from
'@/web/common/api/fetch'
;
import
{
SKILL_DEBUG_CHAT_URL
,
delSkillDebugChatItem
,
postStopSkillDebugChat
}
from
'@/web/core/skill/api'
;
import
{
ChatItemContext
}
from
'@/web/core/chat/context/chatItemContext'
;
import
{
AppTypeEnum
}
from
'@fastgpt/global/core/app/constants'
;
import
type
{
StartChatFnProps
}
from
'@/components/core/chat/ChatContainer/type'
;
import
ChatBox
from
'@/components/core/chat/ChatContainer/ChatBox'
;
import
React
from
'react'
;
import
{
ChatTypeEnum
}
from
'@/components/core/chat/ChatContainer/ChatBox/constants'
;
import
type
{
AppFileSelectConfigType
}
from
'@fastgpt/global/core/app/type/config.schema'
;
import
{
Flex
}
from
'@chakra-ui/react'
;
import
{
useTranslation
}
from
'next-i18next'
;
const
fileSelectConfig
:
AppFileSelectConfigType
=
{
maxFiles
:
10
,
canSelectFile
:
false
,
canSelectImg
:
false
,
customPdfParse
:
false
,
canSelectVideo
:
false
,
canSelectAudio
:
false
,
canSelectCustomFileExtension
:
false
,
customFileExtensionList
:
[]
};
export
const
useSkillChatTest
=
({
skillId
,
model
,
chatId
,
isReady
,
disabledSendTip
,
InputLeftComponent
}:
{
skillId
:
string
;
model
:
string
;
chatId
:
string
;
isReady
:
boolean
;
disabledSendTip
?:
string
;
InputLeftComponent
?:
React
.
ReactNode
;
})
=>
{
const
{
t
}
=
useTranslation
([
'skill'
,
'common'
]);
const
setChatBoxData
=
useContextSelector
(
ChatItemContext
,
(
v
)
=>
v
.
setChatBoxData
);
// Set chat box data
useEffect
(()
=>
{
setChatBoxData
({
appId
:
skillId
,
app
:
{
chatConfig
:
{
fileSelectConfig
},
name
:
'Skill Preview'
,
avatar
:
''
,
type
:
AppTypeEnum
.
simple
,
pluginInputs
:
[]
}
});
},
[
skillId
,
setChatBoxData
]);
const
startChat
=
useMemoizedFn
(
async
({
messages
,
responseChatItemId
,
controller
,
generatingMessage
}:
StartChatFnProps
)
=>
{
const
histories
=
messages
.
slice
(
-
1
);
const
{
responseText
}
=
await
streamFetch
({
url
:
SKILL_DEBUG_CHAT_URL
,
data
:
{
skillId
,
chatId
,
messages
:
histories
,
model
,
responseChatItemId
},
onMessage
:
generatingMessage
,
abortCtrl
:
controller
});
return
{
responseText
};
}
);
// 使用 skill 专属的删除接口,避免走 /api/core/chat/item/delete 时用 skillId 查 App 报错
const
handleDeleteChatItem
=
useMemoizedFn
((
contentId
:
string
)
=>
delSkillDebugChatItem
({
skillId
,
chatId
,
contentId
})
);
const
handleStopChat
=
useMemoizedFn
(
async
()
=>
{
const
result
=
await
postStopSkillDebugChat
({
skillId
,
chatId
});
return
{
chatGenerateStatus
:
result
.
chatGenerateStatus
,
completed
:
result
.
completed
};
});
const
ChatContainer
=
useCallback
(
()
=>
(
<
ChatBox
isReady=
{
isReady
}
appId=
{
skillId
}
chatId=
{
chatId
}
chatType=
{
ChatTypeEnum
.
test
}
enableMarkChatRead=
{
false
}
onStartChat=
{
startChat
}
onDeleteChatItem=
{
handleDeleteChatItem
}
onStopChat=
{
handleStopChat
}
InputLeftComponent=
{
InputLeftComponent
}
disabledSendTip=
{
disabledSendTip
?
t
(
disabledSendTip
as
any
)
:
undefined
}
dialogTips=
{
t
(
'common:core.chat.Type a message'
)
}
pl=
{
'16px'
}
pr=
{
0
}
maxW=
{
'100%'
}
boxBodyProps=
{
{
px
:
0
,
pr
:
'8px'
,
maxW
:
'100%'
,
mx
:
0
}
}
inputBodyProps=
{
{
maxW
:
'100%'
,
mx
:
0
,
px
:
0
,
pl
:
0
,
pr
:
'8px'
}
}
EmptyState=
{
<
Flex
flex=
{
1
}
alignItems=
"center"
justifyContent=
"center"
color=
"myGray.500"
fontSize=
"sm"
textAlign=
"center"
lineHeight=
"20px"
whiteSpace=
"pre-wrap"
>
{
t
(
'empty_state_tip'
)
}
</
Flex
>
}
/>
),
[
skillId
,
chatId
,
isReady
,
startChat
,
handleDeleteChatItem
,
handleStopChat
,
InputLeftComponent
,
disabledSendTip
,
t
]
);
return
{
ChatContainer
};
};
projects/app/src/web/core/skill/api.ts
View file @
2534cdc6
import
{
GET
,
DELETE
,
POST
}
from
'@/web/common/api/request'
;
import
{
GET
,
DELETE
,
POST
}
from
'@/web/common/api/request'
;
import
{
streamFetch
,
type
StreamResponseType
}
from
'@/web/common/api/fetch'
;
import
{
downloadFetch
}
from
'@/web/common/system/utils'
;
import
{
downloadFetch
}
from
'@/web/common/system/utils'
;
import
{
useSystemStore
}
from
'@/web/common/system/useSystemStore'
;
import
{
useSystemStore
}
from
'@/web/common/system/useSystemStore'
;
import
{
EventStreamContentType
,
fetchEventSource
}
from
'@fortaine/fetch-event-source'
;
import
{
EventStreamContentType
,
fetchEventSource
}
from
'@fortaine/fetch-event-source'
;
...
@@ -21,6 +22,7 @@ import type {
...
@@ -21,6 +22,7 @@ import type {
CreateEditDebugSandboxBody
,
CreateEditDebugSandboxBody
,
CreateEditDebugSandboxResponse
,
CreateEditDebugSandboxResponse
,
CreateSkillFolderBody
,
CreateSkillFolderBody
,
SkillDebugChatBody
,
SkillDebugRecordsBody
,
SkillDebugRecordsBody
,
SkillDebugSessionControlBody
,
SkillDebugSessionControlBody
,
SkillDebugSessionStopResponse
,
SkillDebugSessionStopResponse
,
...
@@ -34,6 +36,7 @@ import type { SkillDebugDeleteChatItemBody } from '@fastgpt/global/core/ai/skill
...
@@ -34,6 +36,7 @@ import type { SkillDebugDeleteChatItemBody } from '@fastgpt/global/core/ai/skill
import
type
{
GetResourceFolderListProps
}
from
'@fastgpt/global/common/parentFolder/type'
;
import
type
{
GetResourceFolderListProps
}
from
'@fastgpt/global/common/parentFolder/type'
;
import
{
AgentSkillTypeEnum
}
from
'@fastgpt/global/core/ai/skill/constants'
;
import
{
AgentSkillTypeEnum
}
from
'@fastgpt/global/core/ai/skill/constants'
;
import
type
{
GetRecordsV2ResponseType
}
from
'@fastgpt/global/openapi/core/chat/record/api'
;
import
type
{
GetRecordsV2ResponseType
}
from
'@fastgpt/global/openapi/core/chat/record/api'
;
import
type
{
StartChatFnProps
}
from
'@/components/core/chat/ChatContainer/type'
;
/** 获取 Skill 列表(支持分页、搜索、分类、文件夹过滤) */
/** 获取 Skill 列表(支持分页、搜索、分类、文件夹过滤) */
export
const
getSkillList
=
(
data
:
ListSkillsQuery
)
=>
export
const
getSkillList
=
(
data
:
ListSkillsQuery
)
=>
...
@@ -140,8 +143,22 @@ export const streamCreateEditDebugSandbox = ({
...
@@ -140,8 +143,22 @@ export const streamCreateEditDebugSandbox = ({
});
});
});
});
/** Skill 调试对话 SSE 接口 URL */
/** 发起 Skill 调试对话,使用 Skill 专属鉴权与编辑沙箱运行态。 */
export
const
SKILL_DEBUG_CHAT_URL
=
'/api/core/ai/skill/debugChat'
;
export
const
streamSkillDebugChat
=
({
data
,
onMessage
,
abortCtrl
}:
{
data
:
SkillDebugChatBody
;
onMessage
:
StartChatFnProps
[
'generatingMessage'
];
abortCtrl
:
AbortController
;
}):
Promise
<
StreamResponseType
>
=>
streamFetch
({
url
:
'/api/core/ai/skill/debugChat'
,
data
,
onMessage
,
abortCtrl
});
/** 创建 Skill 文件夹 */
/** 创建 Skill 文件夹 */
export
const
postCreateSkillFolder
=
(
data
:
CreateSkillFolderBody
)
=>
export
const
postCreateSkillFolder
=
(
data
:
CreateSkillFolderBody
)
=>
...
...
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