Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,12 +339,14 @@ export class AiSdkToChunkAdapter {
reasoning_content: final.reasoningContent || ''
}

// Pass finishReason in BLOCK_COMPLETE for message-level tracking
this.onChunk({
type: ChunkType.BLOCK_COMPLETE,
response: {
...baseResponse,
usage: { ...usage },
metrics: metrics ? { ...metrics } : undefined
metrics: metrics ? { ...metrics } : undefined,
finishReason: chunk.finishReason
}
})
this.onChunk({
Expand Down
11 changes: 11 additions & 0 deletions src/renderer/src/i18n/locales/en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,9 @@
},
"citation": "{{count}} citations",
"citations": "References",
"continue_generation": {
"prompt": "[CONTINUE EXACTLY FROM CUTOFF POINT]\n\nYour previous response was cut off mid-generation. Continue IMMEDIATELY from where you stopped - do NOT repeat, summarize, or restart. Your next word should be the exact continuation.\n\nYour response ended with: \"{{truncatedContent}}\"\n\nContinue now (first word must follow directly from the above):"
},
"copied": "Copied!",
"copy": {
"failed": "Copy failed",
Expand Down Expand Up @@ -1941,6 +1944,14 @@
}
},
"warning": {
"finish_reason": {
"content-filter": "Content was blocked by safety filter",
"continue": "Continue generating",
"error": "An error occurred during generation",
"length": "Maximum output length limit reached",
"other": "Generation terminated",
"unknown": "Generation terminated for unknown reason"
},
"rate": {
"limit": "Too many requests. Please wait {{seconds}} seconds before trying again."
}
Expand Down
11 changes: 11 additions & 0 deletions src/renderer/src/i18n/locales/zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,9 @@
},
"citation": "{{count}} 个引用内容",
"citations": "引用内容",
"continue_generation": {
"prompt": "[从截断处精确继续]\n\n你之前的回复在生成过程中被截断了。请立即从停止的地方继续——不要重复、总结或重新开始。你的下一个字必须是精确的接续。\n\n你的回复结尾是:\"{{truncatedContent}}\"\n\n现在继续(第一个字必须直接接上面的内容):"
},
"copied": "已复制",
"copy": {
"failed": "复制失败",
Expand Down Expand Up @@ -1941,6 +1944,14 @@
}
},
"warning": {
"finish_reason": {
"content-filter": "内容被安全过滤器拦截",
"continue": "继续生成",
"error": "生成过程中发生错误",
"length": "已达到最大输出长度限制",
"other": "生成已终止",
"unknown": "生成因未知原因终止"
},
"rate": {
"limit": "发送过于频繁,请等待 {{seconds}} 秒后再尝试"
}
Expand Down
11 changes: 11 additions & 0 deletions src/renderer/src/i18n/locales/zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,9 @@
},
"citation": "{{count}} 個引用內容",
"citations": "引用內容",
"continue_generation": {
"prompt": "[to be translated]:Please continue your previous response exactly from where you left off. Do not repeat any content that was already generated. Continue directly from:\n\n{{truncatedContent}}"
},
"copied": "已複製!",
"copy": {
"failed": "複製失敗",
Expand Down Expand Up @@ -1941,6 +1944,14 @@
}
},
"warning": {
"finish_reason": {
"content-filter": "[to be translated]:Content was blocked by safety filter",
"continue": "[to be translated]:Continue generating",
"error": "[to be translated]:An error occurred during generation",
"length": "[to be translated]:Maximum output length limit reached",
"other": "[to be translated]:Generation terminated",
"unknown": "[to be translated]:Generation terminated for unknown reason"
},
"rate": {
"limit": "發送過於頻繁,請在 {{seconds}} 秒後再嘗試"
}
Expand Down
11 changes: 11 additions & 0 deletions src/renderer/src/i18n/translate/de-de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,9 @@
},
"citation": "{{count}} Zitate",
"citations": "Zitate",
"continue_generation": {
"prompt": "[to be translated]:Please continue your previous response exactly from where you left off. Do not repeat any content that was already generated. Continue directly from:\n\n{{truncatedContent}}"
},
"copied": "Kopiert",
"copy": {
"failed": "Kopieren fehlgeschlagen",
Expand Down Expand Up @@ -1941,6 +1944,14 @@
}
},
"warning": {
"finish_reason": {
"content-filter": "[to be translated]:Content was blocked by safety filter",
"continue": "[to be translated]:Continue generating",
"error": "[to be translated]:An error occurred during generation",
"length": "[to be translated]:Maximum output length limit reached",
"other": "[to be translated]:Generation terminated",
"unknown": "[to be translated]:Generation terminated for unknown reason"
},
"rate": {
"limit": "Zu viele Anfragen. Bitte warten Sie {{seconds}} Sekunden, bevor Sie es erneut versuchen"
}
Expand Down
11 changes: 11 additions & 0 deletions src/renderer/src/i18n/translate/el-gr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,9 @@
},
"citation": "{{count}} αναφορές",
"citations": "Περιεχόμενα αναφοράς",
"continue_generation": {
"prompt": "[to be translated]:Please continue your previous response exactly from where you left off. Do not repeat any content that was already generated. Continue directly from:\n\n{{truncatedContent}}"
},
"copied": "Αντιγράφηκε",
"copy": {
"failed": "Η αντιγραφή απέτυχε",
Expand Down Expand Up @@ -1941,6 +1944,14 @@
}
},
"warning": {
"finish_reason": {
"content-filter": "[to be translated]:Content was blocked by safety filter",
"continue": "[to be translated]:Continue generating",
"error": "[to be translated]:An error occurred during generation",
"length": "[to be translated]:Maximum output length limit reached",
"other": "[to be translated]:Generation terminated",
"unknown": "[to be translated]:Generation terminated for unknown reason"
},
"rate": {
"limit": "Υπερβολική συχνότητα στείλατε παρακαλώ περιμένετε {{seconds}} δευτερόλεπτα και προσπαθήστε ξανά"
}
Expand Down
11 changes: 11 additions & 0 deletions src/renderer/src/i18n/translate/es-es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,9 @@
},
"citation": "{{count}} contenido citado",
"citations": "Citas",
"continue_generation": {
"prompt": "[to be translated]:Please continue your previous response exactly from where you left off. Do not repeat any content that was already generated. Continue directly from:\n\n{{truncatedContent}}"
},
"copied": "Copiado",
"copy": {
"failed": "Copia fallida",
Expand Down Expand Up @@ -1941,6 +1944,14 @@
}
},
"warning": {
"finish_reason": {
"content-filter": "[to be translated]:Content was blocked by safety filter",
"continue": "[to be translated]:Continue generating",
"error": "[to be translated]:An error occurred during generation",
"length": "[to be translated]:Maximum output length limit reached",
"other": "[to be translated]:Generation terminated",
"unknown": "[to be translated]:Generation terminated for unknown reason"
},
"rate": {
"limit": "Envío demasiado frecuente, espere {{seconds}} segundos antes de intentarlo de nuevo"
}
Expand Down
11 changes: 11 additions & 0 deletions src/renderer/src/i18n/translate/fr-fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,9 @@
},
"citation": "{{count}} éléments cités",
"citations": "Citations",
"continue_generation": {
"prompt": "[to be translated]:Please continue your previous response exactly from where you left off. Do not repeat any content that was already generated. Continue directly from:\n\n{{truncatedContent}}"
},
"copied": "Copié",
"copy": {
"failed": "La copie a échoué",
Expand Down Expand Up @@ -1941,6 +1944,14 @@
}
},
"warning": {
"finish_reason": {
"content-filter": "[to be translated]:Content was blocked by safety filter",
"continue": "[to be translated]:Continue generating",
"error": "[to be translated]:An error occurred during generation",
"length": "[to be translated]:Maximum output length limit reached",
"other": "[to be translated]:Generation terminated",
"unknown": "[to be translated]:Generation terminated for unknown reason"
},
"rate": {
"limit": "Vous envoyez trop souvent, veuillez attendre {{seconds}} secondes avant de réessayer"
}
Expand Down
11 changes: 11 additions & 0 deletions src/renderer/src/i18n/translate/ja-jp.json
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,9 @@
},
"citation": "{{count}}個の引用内容",
"citations": "引用内容",
"continue_generation": {
"prompt": "[to be translated]:Please continue your previous response exactly from where you left off. Do not repeat any content that was already generated. Continue directly from:\n\n{{truncatedContent}}"
},
"copied": "コピーしました!",
"copy": {
"failed": "コピーに失敗しました",
Expand Down Expand Up @@ -1941,6 +1944,14 @@
}
},
"warning": {
"finish_reason": {
"content-filter": "[to be translated]:Content was blocked by safety filter",
"continue": "[to be translated]:Continue generating",
"error": "[to be translated]:An error occurred during generation",
"length": "[to be translated]:Maximum output length limit reached",
"other": "[to be translated]:Generation terminated",
"unknown": "[to be translated]:Generation terminated for unknown reason"
},
"rate": {
"limit": "送信が頻繁すぎます。{{seconds}} 秒待ってから再試行してください。"
}
Expand Down
11 changes: 11 additions & 0 deletions src/renderer/src/i18n/translate/pt-pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,9 @@
},
"citation": "{{count}} conteúdo(s) citado(s)",
"citations": "Citações",
"continue_generation": {
"prompt": "[to be translated]:Please continue your previous response exactly from where you left off. Do not repeat any content that was already generated. Continue directly from:\n\n{{truncatedContent}}"
},
"copied": "Copiado",
"copy": {
"failed": "Cópia falhou",
Expand Down Expand Up @@ -1941,6 +1944,14 @@
}
},
"warning": {
"finish_reason": {
"content-filter": "[to be translated]:Content was blocked by safety filter",
"continue": "[to be translated]:Continue generating",
"error": "[to be translated]:An error occurred during generation",
"length": "[to be translated]:Maximum output length limit reached",
"other": "[to be translated]:Generation terminated",
"unknown": "[to be translated]:Generation terminated for unknown reason"
},
"rate": {
"limit": "Envio muito frequente, aguarde {{seconds}} segundos antes de tentar novamente"
}
Expand Down
11 changes: 11 additions & 0 deletions src/renderer/src/i18n/translate/ru-ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,9 @@
},
"citation": "{{count}} цитат",
"citations": "Содержание цитат",
"continue_generation": {
"prompt": "[to be translated]:Please continue your previous response exactly from where you left off. Do not repeat any content that was already generated. Continue directly from:\n\n{{truncatedContent}}"
},
"copied": "Скопировано!",
"copy": {
"failed": "Не удалось скопировать",
Expand Down Expand Up @@ -1941,6 +1944,14 @@
}
},
"warning": {
"finish_reason": {
"content-filter": "[to be translated]:Content was blocked by safety filter",
"continue": "[to be translated]:Continue generating",
"error": "[to be translated]:An error occurred during generation",
"length": "[to be translated]:Maximum output length limit reached",
"other": "[to be translated]:Generation terminated",
"unknown": "[to be translated]:Generation terminated for unknown reason"
},
"rate": {
"limit": "Отправка слишком частая, пожалуйста, подождите {{seconds}} секунд, прежде чем попробовать снова."
}
Expand Down
64 changes: 64 additions & 0 deletions src/renderer/src/pages/home/Messages/FinishReasonWarning.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { FinishReason } from 'ai'
import { Alert as AntdAlert, Button } from 'antd'
import { Play } from 'lucide-react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'

interface Props {
finishReason: FinishReason
onContinue?: () => void
onDismiss?: () => void
}

/**
* Displays a warning banner when message generation was truncated or filtered
* Only shows for non-normal finish reasons (not 'stop' or 'tool-calls')
*/
const FinishReasonWarning: React.FC<Props> = ({ finishReason, onContinue, onDismiss }) => {
const { t } = useTranslation()

// Don't show warning for normal finish reasons
if (finishReason === 'stop' || finishReason === 'tool-calls') {
return null
}

const getWarningMessage = () => {
const i18nKey = `message.warning.finish_reason.${finishReason}`
return t(i18nKey)
}

// Only show continue button for 'length' reason (max tokens reached)
const showContinueButton = finishReason === 'length' && onContinue

return (
<Alert
message={getWarningMessage()}
type="warning"
showIcon
closable={!!onDismiss}
onClose={onDismiss}
action={
showContinueButton && (
<Button
size="small"
type="text"
icon={<Play size={14} />}
onClick={onContinue}
style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
{t('message.warning.finish_reason.continue')}
</Button>
)
}
/>
)
}

const Alert = styled(AntdAlert)`
margin: 0.5rem 0 !important;
padding: 8px 12px;
font-size: 12px;
align-items: center;
`

export default React.memo(FinishReasonWarning)
32 changes: 28 additions & 4 deletions src/renderer/src/pages/home/Messages/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { getMessageModelId } from '@renderer/services/MessagesService'
import { getModelUniqId } from '@renderer/services/ModelService'
import { estimateMessageUsage } from '@renderer/services/TokenService'
import type { Assistant, Topic } from '@renderer/types'
import type { Message, MessageBlock } from '@renderer/types/newMessage'
import type { Message as MessageType, MessageBlock } from '@renderer/types/newMessage'
import { classNames, cn } from '@renderer/utils'
import { isMessageProcessing } from '@renderer/utils/messageUtils/is'
import { Divider } from 'antd'
Expand All @@ -30,7 +30,7 @@ import MessageMenubar from './MessageMenubar'
import MessageOutline from './MessageOutline'

interface Props {
message: Message
message: MessageType
topic: Topic
assistant?: Assistant
index?: number
Expand All @@ -39,7 +39,7 @@ interface Props {
style?: React.CSSProperties
isGrouped?: boolean
isStreaming?: boolean
onSetMessages?: Dispatch<SetStateAction<Message[]>>
onSetMessages?: Dispatch<SetStateAction<MessageType[]>>
onUpdateUseful?: (msgId: string) => void
isGroupContextMessage?: boolean
}
Expand Down Expand Up @@ -116,6 +116,26 @@ const MessageItem: FC<Props> = ({
stopEditing()
}, [stopEditing])

// Handle continue generation when max tokens reached
const handleContinueGeneration = useCallback(
async (msg: MessageType) => {
if (!assistant) return
// Clear the finishReason first, then trigger continue generation
await editMessage(msg.id, { finishReason: undefined })
// Emit event to trigger continue generation
EventEmitter.emit(EVENT_NAMES.CONTINUE_GENERATION, { message: msg, assistant, topic })
},
[assistant, editMessage, topic]
)

// Handle dismiss warning (just clear finishReason)
const handleDismissWarning = useCallback(
async (msg: MessageType) => {
await editMessage(msg.id, { finishReason: undefined })
},
[editMessage]
)

const isLastMessage = index === 0 || !!isGrouped
const isAssistantMessage = message.role === 'assistant'
const isProcessing = isMessageProcessing(message)
Expand Down Expand Up @@ -223,7 +243,11 @@ const MessageItem: FC<Props> = ({
overflowY: 'visible'
}}>
<MessageErrorBoundary>
<MessageContent message={message} />
<MessageContent
message={message}
onContinueGeneration={handleContinueGeneration}
onDismissWarning={handleDismissWarning}
/>
</MessageErrorBoundary>
</MessageContentContainer>
{showMenubar && (
Expand Down
Loading