mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(scheduled-tasks): count code points when shortening a run summary (#7452)
briefReason() measured and sliced UTF-16 code units, so a cut inside an emoji's surrogate pair left a lone high surrogate that the outcome notice renders as U+FFFD. Measuring code points also stops shortening a summary that fits the documented budget. Add a regression test for both the fits-exactly and truncating cases. Co-authored-by: 一片一片 <192845873+gdut4140@users.noreply.github.com>
This commit is contained in:
parent
dcb3a70fbf
commit
6b2ac58555
@ -270,14 +270,22 @@ export function displayTimeZone(
|
||||
* A run summary short enough to quote mid-sentence: its first sentence, no
|
||||
* trailing sentence punctuation (the template adds its own), at most `max`
|
||||
* characters with an ellipsis. The full text stays in the linked run.
|
||||
*
|
||||
* The budget counts code points, so an emoji at the cut is never split into a
|
||||
* lone surrogate (`conversation-outline.ts` and `tool-detail-preview.ts`
|
||||
* follow the same rule for their own limits).
|
||||
*/
|
||||
export function briefReason(summary: string, max = 120): string {
|
||||
const text = summary.replace(/\s+/g, " ").trim();
|
||||
const sentenceEnd = /[。!?]|[.!?](?=\s|$)/.exec(text);
|
||||
const first = sentenceEnd ? text.slice(0, sentenceEnd.index + 1) : text;
|
||||
const bare = first.replace(/[\s。..!!??;;,,、::]+$/u, "");
|
||||
if (bare.length <= max) {
|
||||
const characters = Array.from(bare);
|
||||
if (characters.length <= max) {
|
||||
return bare;
|
||||
}
|
||||
return `${bare.slice(0, max - 1).trimEnd()}…`;
|
||||
return `${characters
|
||||
.slice(0, Math.max(0, max - 1))
|
||||
.join("")
|
||||
.trimEnd()}…`;
|
||||
}
|
||||
|
||||
@ -199,4 +199,17 @@ describe("briefReason", () => {
|
||||
expect(reason).toHaveLength(120);
|
||||
expect(reason.endsWith("…")).toBe(true);
|
||||
});
|
||||
|
||||
it("counts code points so a cut never splits an emoji", () => {
|
||||
// 120 code points but 121 UTF-16 units: the whole summary fits.
|
||||
const fits = `${"a".repeat(118)}😀b`;
|
||||
expect(briefReason(fits)).toBe(fits);
|
||||
|
||||
// Cut lands before the extra text, after the whole emoji: no lone surrogate.
|
||||
const overflow = `${"a".repeat(118)}😀bc`;
|
||||
expect(briefReason(overflow)).toBe(`${"a".repeat(118)}😀…`);
|
||||
expect(briefReason(overflow)).not.toMatch(
|
||||
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user