Skip to content
Merged
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
22 changes: 15 additions & 7 deletions src/app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
function getMaxTokens(length: ResponseLength): number {
switch (length) {
case "short":
return 200
return 350
case "long":
return 1024
default:
Expand All @@ -41,7 +41,7 @@
let lastAllowedIndex = text.length
let match: RegExpExecArray | null

while ((match = wordRegex.exec(text)) !== null) {

Check warning on line 44 in src/app/api/chat/route.ts

View workflow job for this annotation

GitHub Actions / ci

'match' is assigned a value but never used
wordCount += 1
if (wordCount === wordLimit) {
lastAllowedIndex = wordRegex.lastIndex
Expand All @@ -56,9 +56,6 @@
return { text, truncated: false }
}

function countWords(text: string): number {
return text.trim().split(/\s+/).filter(Boolean).length
}

function stripUnmatchedPair(text: string, token: string): string {
const count = text.split(token).length - 1
Expand All @@ -72,12 +69,23 @@

function polishTruncatedShortResponse(text: string, wordLimit: number): string {
let result = text.trimEnd()
const sentenceMatches = [...result.matchAll(/[.!?。!?](?=\s|$)/g)]

// Already ends cleanly - enforce word limit, but re-check if clamping broke the ending
if (/[.!?。!?]$/u.test(result)) {
const clamped = clampToWordLimit(result, wordLimit)
if (!clamped.truncated || /[.!?。!?]$/u.test(clamped.text)) {
return clamped.text
}
result = clamped.text
}

// Try to truncate at the last complete sentence
const sentenceMatches = [...result.matchAll(/[.!?。!?](?=\s|$)/g)]
if (sentenceMatches.length > 0) {
const lastSentence = sentenceMatches[sentenceMatches.length - 1]
const sentenceSafe = result.slice(0, (lastSentence.index ?? 0) + lastSentence[0].length).trimEnd()
Comment on lines +82 to 86

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sentence-boundary detection only matches punctuation when followed by whitespace/end (/(?=\s|$)/). Short responses often end with quotes/parentheses (e.g., ..." or ...)), which will bypass sentence detection and cause the function to append ... even when the sentence is complete. Consider allowing common closing delimiters after the punctuation in both the ends cleanly check and sentenceMatches regex.

Copilot uses AI. Check for mistakes.
if (countWords(sentenceSafe) >= Math.min(40, wordLimit)) {
// Accept if we keep at least 30% of the content (works for both EN and KO)
if (sentenceSafe.length >= result.length * 0.3) {
result = sentenceSafe
}
}
Comment on lines 86 to 91

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

countWords is no longer used after switching the sentence-fallback threshold to a length-based check. Removing the unused helper will avoid dead code and potential lint noise.

Copilot uses AI. Check for mistakes.
Expand Down Expand Up @@ -227,7 +235,7 @@
}

if (!request.signal?.aborted) {
if (wordLimit && truncatedShortResponse) {
if (wordLimit) {
fullContent = polishTruncatedShortResponse(fullContent, wordLimit)
}

Expand Down
2 changes: 1 addition & 1 deletion src/app/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ function ChatPageContent() {
prevMessageCount.current = 0
persistence.reset()
handleReset()
router.push("/chat")
router.replace("/chat")
}, [persistence, handleReset, router])

// When user continues a completed thread, mark it active again
Expand Down
2 changes: 1 addition & 1 deletion src/components/ThreadDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export default function ThreadDropdown({
// Close before navigation to minimize visible flicker during the route change
isNavigatingRef.current = true
setIsOpen(false)
router.push(`/chat?thread=${threadId}`)
router.replace(`/chat?thread=${threadId}`)
}

const handleDelete = async (e: React.MouseEvent, threadId: string) => {
Expand Down
Loading