feat(chat): add message editing & regeneration (M8.1)
Pencil icon on hover for user messages to edit inline. Regenerate icon on assistant messages to re-send from the same prompt. Editing clears all subsequent messages and triggers a fresh AI response. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
9f6158ddd2
commit
61a6e88667
@@ -355,9 +355,118 @@ export function useAI() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a user message and regenerate the AI response.
|
||||
* Clears all messages after the edited message, then re-sends.
|
||||
*/
|
||||
async function editAndResend(messageId: string, newContent: string) {
|
||||
const convId = chatStore.activeConversationId
|
||||
if (!convId) return
|
||||
|
||||
const conv = chatStore.activeConversation
|
||||
if (!conv) return
|
||||
|
||||
const msgIndex = conv.messages.findIndex((m) => m.id === messageId)
|
||||
if (msgIndex === -1) return
|
||||
|
||||
// Update the message content
|
||||
chatStore.updateMessageContent(convId, messageId, newContent)
|
||||
|
||||
// Delete all messages after this one
|
||||
chatStore.deleteMessagesAfter(convId, msgIndex + 1)
|
||||
|
||||
// Re-send (creates new assistant message and streams)
|
||||
await resendLastUserMessage()
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate the last assistant response.
|
||||
* Deletes the last assistant message and re-sends with the same user message.
|
||||
*/
|
||||
async function regenerateLastResponse() {
|
||||
const convId = chatStore.activeConversationId
|
||||
if (!convId) return
|
||||
|
||||
const conv = chatStore.activeConversation
|
||||
if (!conv || conv.messages.length === 0) return
|
||||
|
||||
// Find the last assistant message and remove it
|
||||
const lastIndex = conv.messages.length - 1
|
||||
if (conv.messages[lastIndex].role === 'assistant') {
|
||||
chatStore.deleteMessagesAfter(convId, lastIndex)
|
||||
}
|
||||
|
||||
await resendLastUserMessage()
|
||||
}
|
||||
|
||||
/** Internal: re-send from the current last user message */
|
||||
async function resendLastUserMessage() {
|
||||
const convId = chatStore.activeConversationId
|
||||
if (!convId) return
|
||||
|
||||
const conv = chatStore.activeConversation
|
||||
if (!conv || conv.messages.length === 0) return
|
||||
|
||||
const lastUserMsg = [...conv.messages].reverse().find((m) => m.role === 'user')
|
||||
if (!lastUserMsg) return
|
||||
|
||||
const provider = activeProvider.value
|
||||
currentAbort = new AbortController()
|
||||
const signal = currentAbort.signal
|
||||
const cid = convId
|
||||
|
||||
const assistantMsg = chatStore.addMessage(cid, { role: 'assistant', content: '' })
|
||||
if (!assistantMsg) return
|
||||
|
||||
chatStore.isStreaming = true
|
||||
|
||||
let systemPrompt = SYSTEM_PROMPT
|
||||
if (chatStore.webSearchEnabled) {
|
||||
systemPrompt += `
|
||||
|
||||
**Web search:** You have access to WebSearch and WebFetch tools. Use them to look up current information, news, and facts when the user asks. You can search the web and fetch page content. Web search is enabled for this session—do not tell the user it is unavailable.`
|
||||
}
|
||||
if (chatStore.webSearchEnabled && lastUserMsg.content.trim()) {
|
||||
const results = await searchWeb(lastUserMsg.content)
|
||||
if (results.length > 0) {
|
||||
systemPrompt += formatWebSearchContext(results)
|
||||
chatStore.setMessageWebResults(cid, assistantMsg.id, results)
|
||||
}
|
||||
}
|
||||
|
||||
const history: ChatMessage[] = chatStore.messages
|
||||
.filter((m) => m.id !== assistantMsg.id)
|
||||
.map((m) => ({ role: m.role as 'user' | 'assistant', content: m.content }))
|
||||
|
||||
const onToken = (text: string) => chatStore.appendToLastMessage(cid, text)
|
||||
const onError = (err: string) => {
|
||||
console.error(`[AIUI ${provider}]`, err)
|
||||
chatStore.appendToLastMessage(cid, `⚠ ${err}`)
|
||||
}
|
||||
|
||||
try {
|
||||
if (provider === 'claude') {
|
||||
await streamClaude(history, onToken, onError, systemPrompt, chatStore.webSearchEnabled, signal)
|
||||
} else if (provider === 'openrouter') {
|
||||
await streamOpenRouter(history, onToken, onError, systemPrompt, signal)
|
||||
} else {
|
||||
await streamMock(history, onToken, signal)
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof DOMException && err.name === 'AbortError') return
|
||||
const msg = err instanceof Error ? err.message : String(err)
|
||||
chatStore.appendToLastMessage(cid, `\n\n⚠ Connection error: ${msg}`)
|
||||
} finally {
|
||||
currentAbort = null
|
||||
chatStore.isStreaming = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sendMessage,
|
||||
stopGeneration,
|
||||
editAndResend,
|
||||
regenerateLastResponse,
|
||||
activeProvider,
|
||||
activeModel,
|
||||
availableProviders,
|
||||
|
||||
Reference in New Issue
Block a user