Remove chat message regeneration logic
This commit is contained in:
198
src/App.jsx
198
src/App.jsx
@@ -475,204 +475,6 @@ export default function App() {
|
|||||||
await regenerateFromIndex(index, next);
|
await regenerateFromIndex(index, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function regenerateFromIndex(index, overrideUserText = null) {
|
|
||||||
const sessionId = activeSessionId
|
|
||||||
if (isSending || !sessionId || typeof index !== 'number') return
|
|
||||||
|
|
||||||
const msgs = (chatSessions.find(s => s.session_id === sessionId)?.messages) || []
|
|
||||||
let lastUserIdx = index
|
|
||||||
for (let i = index; i >= 0; i--) {
|
|
||||||
if (msgs[i]?.role === 'user') {
|
|
||||||
lastUserIdx = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setChatSessions(prev =>
|
|
||||||
prev.map(s => s.session_id === sessionId
|
|
||||||
? { ...s, messages: (s.messages || []).slice(0, lastUserIdx + 1) }
|
|
||||||
: s
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
const conversationNeedsVision = msgs
|
|
||||||
.slice(0, lastUserIdx + 1)
|
|
||||||
.some(messageHasImageAttachments)
|
|
||||||
if (conversationNeedsVision && !canAttachImages) {
|
|
||||||
window.alert(imageAttachmentUnavailableReason)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestController = beginCancelableRequest(sessionId)
|
|
||||||
|
|
||||||
let enrichedPrompt = overrideUserText != null ? overrideUserText : (msgs[lastUserIdx]?.content || '')
|
|
||||||
let citationSources = []
|
|
||||||
const contextBlocks = []
|
|
||||||
try {
|
|
||||||
const selectedLibrary = getChatLibraryForSession(sessionId)
|
|
||||||
const promptText = overrideUserText != null ? overrideUserText : (msgs[lastUserIdx]?.content || '')
|
|
||||||
const hasPromptText = Boolean((promptText || '').trim())
|
|
||||||
|
|
||||||
if (hasPromptText && selectedLibrary?.states?.is_indexed) {
|
|
||||||
try {
|
|
||||||
const localContext = await fetchLocalLibraryContext(backendApiUrl, selectedLibrary.slug, promptText, requestController.signal)
|
|
||||||
if (localContext.contextBlock) {
|
|
||||||
contextBlocks.push(localContext.contextBlock)
|
|
||||||
}
|
|
||||||
if (Array.isArray(localContext.sources)) {
|
|
||||||
citationSources.push(...localContext.sources)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (isAbortError(error)) throw error
|
|
||||||
console.warn('local library enrichment (regenerate) failed', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasPromptText && webSearchEnabled) {
|
|
||||||
try {
|
|
||||||
const historyForSearch = msgs
|
|
||||||
.slice(Math.max(0, lastUserIdx - 7), lastUserIdx + 1)
|
|
||||||
.map(m => ({ role: m.role, content: m.content || '' }))
|
|
||||||
if (historyForSearch.length > 0) {
|
|
||||||
historyForSearch[historyForSearch.length - 1] = { role: 'user', content: promptText }
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchContext = await fetchWebSearchContext({
|
|
||||||
apiBase: backendApiUrl,
|
|
||||||
engines: searxEngines,
|
|
||||||
historyLimit: 8,
|
|
||||||
messages: historyForSearch,
|
|
||||||
model,
|
|
||||||
prompt: promptText,
|
|
||||||
searxUrl,
|
|
||||||
signal: requestController.signal,
|
|
||||||
})
|
|
||||||
if (searchContext.contextBlock) {
|
|
||||||
contextBlocks.push(searchContext.contextBlock)
|
|
||||||
}
|
|
||||||
if (Array.isArray(searchContext.sources)) {
|
|
||||||
citationSources.push(...searchContext.sources)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (isAbortError(error)) throw error
|
|
||||||
console.warn('web search enrichment (regenerate) failed', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
citationSources = [...new Set(citationSources)]
|
|
||||||
if (hasPromptText && contextBlocks.length > 0) {
|
|
||||||
enrichedPrompt = `${promptText}\n\n${contextBlocks.join('\n\n')}`
|
|
||||||
} else {
|
|
||||||
enrichedPrompt = null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streamOutput) {
|
|
||||||
const assistantMsgId = `msg-${Date.now()}-${Math.random()}`
|
|
||||||
let full = ''
|
|
||||||
|
|
||||||
setChatSessions(prev =>
|
|
||||||
prev.map(s => s.session_id === sessionId
|
|
||||||
? { ...s, messages: [...(s.messages || []), { id: assistantMsgId, role: 'assistant', content: '', sources: citationSources }] }
|
|
||||||
: s
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await postRegenerateMessage({
|
|
||||||
apiBase: backendApiUrl,
|
|
||||||
enrichedMessage: enrichedPrompt,
|
|
||||||
index,
|
|
||||||
model,
|
|
||||||
sessionId,
|
|
||||||
signal: requestController.signal,
|
|
||||||
sources: citationSources || [],
|
|
||||||
stream: true,
|
|
||||||
transcriptionModel,
|
|
||||||
visionModel,
|
|
||||||
})
|
|
||||||
if (!res.ok) throw new Error(await readBackendErrorText(res))
|
|
||||||
|
|
||||||
const reader = res.body?.getReader()
|
|
||||||
if (!reader) throw new Error('Missing response body')
|
|
||||||
|
|
||||||
const decoder = new TextDecoder()
|
|
||||||
let unreadMarked = false
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const { value, done } = await reader.read()
|
|
||||||
if (done) break
|
|
||||||
|
|
||||||
const chunk = decoder.decode(value, { stream: true })
|
|
||||||
full += chunk
|
|
||||||
setAssistantMessageContent(sessionId, assistantMsgId, full)
|
|
||||||
|
|
||||||
if (!unreadMarked && activeSessionIdRef.current !== sessionId) {
|
|
||||||
unreadMarked = true
|
|
||||||
setPendingScrollToLastUser(prev => ({ ...prev, [sessionId]: assistantMsgId }))
|
|
||||||
setUnreadSessions(prev => [...new Set([...prev, sessionId])])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeSessionIdRef.current !== sessionId) {
|
|
||||||
setPendingScrollToLastUser(prev => ({ ...prev, [sessionId]: assistantMsgId }))
|
|
||||||
setUnreadSessions(prev => [...new Set([...prev, sessionId])])
|
|
||||||
} else if (!userScrolledUpRef.current[sessionId]) {
|
|
||||||
requestAnimationFrame(() => scrollMessageToTop(assistantMsgId, 'smooth', sessionId))
|
|
||||||
} else {
|
|
||||||
setNewMsgTip(prev => ({ ...prev, [sessionId]: assistantMsgId }))
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (isAbortError(error)) {
|
|
||||||
setAssistantMessageContent(sessionId, assistantMsgId, full, { removeIfEmpty: true })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(error)
|
|
||||||
setAssistantMessageContent(sessionId, assistantMsgId, `Error: ${getErrorText(error)}`, { removeIfEmpty: true })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const res = await postRegenerateMessage({
|
|
||||||
apiBase: backendApiUrl,
|
|
||||||
enrichedMessage: enrichedPrompt,
|
|
||||||
index,
|
|
||||||
model,
|
|
||||||
sessionId,
|
|
||||||
signal: requestController.signal,
|
|
||||||
sources: citationSources || [],
|
|
||||||
stream: false,
|
|
||||||
transcriptionModel,
|
|
||||||
visionModel,
|
|
||||||
})
|
|
||||||
if (!res.ok) throw new Error(await readBackendErrorText(res))
|
|
||||||
|
|
||||||
const data = await res.json()
|
|
||||||
const assistantMsgId = `msg-${Date.now()}`
|
|
||||||
setChatSessions(prev =>
|
|
||||||
prev.map(s => s.session_id === sessionId
|
|
||||||
? { ...s, messages: [...(s.messages || []), { role: 'assistant', content: data.reply, id: assistantMsgId, sources: citationSources }] }
|
|
||||||
: s
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (activeSessionIdRef.current !== sessionId) {
|
|
||||||
setPendingScrollToLastUser(prev => ({ ...prev, [sessionId]: assistantMsgId }))
|
|
||||||
setUnreadSessions(prev => [...new Set([...prev, sessionId])])
|
|
||||||
} else if (!userScrolledUpRef.current[sessionId]) {
|
|
||||||
requestAnimationFrame(() => scrollMessageToTop(assistantMsgId, 'smooth', sessionId))
|
|
||||||
} else {
|
|
||||||
setNewMsgTip(prev => ({ ...prev, [sessionId]: assistantMsgId }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (!isAbortError(error)) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
finishCancelableRequest(requestController)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Collapse state per user message: { [msgKey]: boolean } — true means "collapsed"
|
// Collapse state per user message: { [msgKey]: boolean } — true means "collapsed"
|
||||||
const [collapsedUserMsgs, setCollapsedUserMsgs] = useState({});
|
const [collapsedUserMsgs, setCollapsedUserMsgs] = useState({});
|
||||||
|
|||||||
Reference in New Issue
Block a user