feat: add save feedback prompts, featured agent fields, dynamic Dify API config, and chat improvements
- Add success/error feedback messages near submit buttons in all admin forms - Display success prompt for 1s before redirect after save - Show API error details on save failure - Add isFeatured/featuredOrder fields to Agent model and admin UI - Add difyApiUrl/difyApiKey fields for per-agent Dify API configuration - Show featured badge column in agent admin list - Display featured agents on homepage sorted by order - Refactor chat page streaming with AbortController and stable userId - Improve Dify API proxy to use per-agent credentials
This commit is contained in:
@@ -48,8 +48,10 @@ export default function AgentChatPage() {
|
||||
const [currentConversationId, setCurrentConversationId] = useState<number | null>(null)
|
||||
const [upstreamConversationId, setUpstreamConversationId] = useState<string>('')
|
||||
const [loadingConversations, setLoadingConversations] = useState(false)
|
||||
const [abortController, setAbortController] = useState<AbortController | null>(null)
|
||||
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||
const userIdRef = useRef<string>('user-' + Math.random().toString(36).slice(2, 10))
|
||||
|
||||
const CHAT_API_URL = '/api/chat'
|
||||
|
||||
@@ -190,8 +192,6 @@ export default function AgentChatPage() {
|
||||
})
|
||||
|
||||
const assistantMessageId = (Date.now() + 1).toString()
|
||||
let assistantContent = ''
|
||||
let upstreamConvId = upstreamConversationId
|
||||
|
||||
setMessages(prev => [...prev, {
|
||||
id: assistantMessageId,
|
||||
@@ -200,70 +200,70 @@ export default function AgentChatPage() {
|
||||
timestamp: new Date(),
|
||||
}])
|
||||
|
||||
const controller = new AbortController()
|
||||
setAbortController(controller)
|
||||
|
||||
const response = await fetch(CHAT_API_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
agentId: agent.id,
|
||||
inputs: {},
|
||||
query: currentInput,
|
||||
response_mode: 'streaming',
|
||||
conversation_id: upstreamConversationId,
|
||||
user: 'user-' + Date.now(),
|
||||
user: userIdRef.current,
|
||||
}),
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
const reader = response.body?.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
if (!response.ok) {
|
||||
throw new Error('Chat request failed')
|
||||
}
|
||||
|
||||
if (reader) {
|
||||
let buffer = ''
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
const lines = buffer.split('\n')
|
||||
buffer = lines.pop() || ''
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const json = JSON.parse(line.slice(6))
|
||||
if (json.conversation_id) {
|
||||
upstreamConvId = json.conversation_id
|
||||
}
|
||||
const token = json.answer || json.message || ''
|
||||
if (token) {
|
||||
assistantContent += token
|
||||
setMessages(prev => prev.map(msg =>
|
||||
msg.id === assistantMessageId
|
||||
? { ...msg, content: assistantContent }
|
||||
: msg
|
||||
))
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore parse errors
|
||||
const reader = response.body!.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ''
|
||||
let accumulatedContent = ''
|
||||
let finalConvId = upstreamConversationId
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
const lines = buffer.split('\n')
|
||||
buffer = lines.pop() || ''
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const data = JSON.parse(line.slice(6))
|
||||
if (data.event === 'message' && data.answer) {
|
||||
accumulatedContent += data.answer
|
||||
setMessages(prev => prev.map(msg =>
|
||||
msg.id === assistantMessageId
|
||||
? { ...msg, content: accumulatedContent }
|
||||
: msg
|
||||
))
|
||||
} else if (data.event === 'message_end') {
|
||||
finalConvId = data.conversation_id || finalConvId
|
||||
}
|
||||
} catch (e) {
|
||||
// skip invalid JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setUpstreamConversationId(upstreamConvId)
|
||||
|
||||
if (!assistantContent) {
|
||||
setMessages(prev => prev.map(msg =>
|
||||
msg.id === assistantMessageId
|
||||
? { ...msg, content: '抱歉,我暂时无法回答这个问题。' }
|
||||
: msg
|
||||
))
|
||||
assistantContent = '抱歉,我暂时无法回答这个问题。'
|
||||
}
|
||||
setUpstreamConversationId(finalConvId)
|
||||
|
||||
await fetch(`/api/conversations/${conversationId}/messages`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
role: 'assistant',
|
||||
content: assistantContent,
|
||||
content: accumulatedContent,
|
||||
}),
|
||||
})
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user