












@@ -1916,6 +1916,116 @@ describe("processDiscordMessage draft streaming", () => {
19161916expect(deliverDiscordReply).toHaveBeenCalledTimes(1);
19171917});
191819181919+it("keeps the preview and sends media-only for TTS supplement finals", async () => {
1920+const draftStream = createMockDraftStreamForTest();
1921+dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
1922+await params?.dispatcher.sendFinalReply({
1923+mediaUrl: "https://example.com/tts.mp3",
1924+audioAsVoice: true,
1925+spokenText: "Spoken answer",
1926+ttsSupplement: { spokenText: "Spoken answer" },
1927+} as never);
1928+return { queuedFinal: true, counts: { final: 1, tool: 0, block: 0 } };
1929+});
1930+1931+const ctx = await createAutomaticSourceDeliveryContext({
1932+discordConfig: { streamMode: "partial", maxLinesPerMessage: 5 },
1933+replyToMode: "first",
1934+});
1935+1936+await runProcessDiscordMessage(ctx);
1937+1938+expect(draftStream.flush).toHaveBeenCalledTimes(1);
1939+expect(draftStream.discardPending).not.toHaveBeenCalled();
1940+expect(draftStream.clear).not.toHaveBeenCalled();
1941+expectPreviewEditContent("Spoken answer");
1942+expect(deliverDiscordReply).toHaveBeenCalledTimes(1);
1943+expect(firstMockArg(deliverDiscordReply, "deliverDiscordReply")).toMatchObject({
1944+replyToId: "m1",
1945+replies: [
1946+{
1947+mediaUrl: "https://example.com/tts.mp3",
1948+audioAsVoice: true,
1949+spokenText: "Spoken answer",
1950+ttsSupplement: { spokenText: "Spoken answer" },
1951+},
1952+],
1953+});
1954+});
1955+1956+it("falls back with visible text when TTS supplement preview finalization fails", async () => {
1957+const draftStream = createMockDraftStreamForTest();
1958+editMessageDiscord.mockRejectedValueOnce(new Error("edit failed"));
1959+dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
1960+await params?.dispatcher.sendFinalReply({
1961+mediaUrl: "https://example.com/tts.mp3",
1962+audioAsVoice: true,
1963+spokenText: "Spoken answer",
1964+ttsSupplement: { spokenText: "Spoken answer" },
1965+} as never);
1966+return { queuedFinal: true, counts: { final: 1, tool: 0, block: 0 } };
1967+});
1968+1969+const ctx = await createAutomaticSourceDeliveryContext({
1970+discordConfig: { streamMode: "partial", maxLinesPerMessage: 5 },
1971+});
1972+1973+await runProcessDiscordMessage(ctx);
1974+1975+expect(draftStream.flush).toHaveBeenCalledTimes(1);
1976+expect(draftStream.discardPending).toHaveBeenCalled();
1977+expect(draftStream.clear).toHaveBeenCalled();
1978+expect(deliverDiscordReply).toHaveBeenCalledTimes(1);
1979+expect(firstMockArg(deliverDiscordReply, "deliverDiscordReply")).toMatchObject({
1980+replies: [
1981+{
1982+text: "Spoken answer",
1983+mediaUrl: "https://example.com/tts.mp3",
1984+audioAsVoice: true,
1985+spokenText: "Spoken answer",
1986+ttsSupplement: { spokenText: "Spoken answer" },
1987+},
1988+],
1989+});
1990+});
1991+1992+it("keeps already-delivered TTS supplement fallback audio-only", async () => {
1993+editMessageDiscord.mockRejectedValueOnce(new Error("edit failed"));
1994+dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
1995+await params?.dispatcher.sendFinalReply({
1996+mediaUrl: "https://example.com/tts.mp3",
1997+audioAsVoice: true,
1998+spokenText: "Spoken answer",
1999+ttsSupplement: {
2000+spokenText: "Spoken answer",
2001+visibleTextAlreadyDelivered: true,
2002+},
2003+} as never);
2004+return { queuedFinal: true, counts: { final: 1, tool: 0, block: 0 } };
2005+});
2006+2007+const ctx = await createAutomaticSourceDeliveryContext({
2008+discordConfig: { streamMode: "partial", maxLinesPerMessage: 5 },
2009+});
2010+2011+await runProcessDiscordMessage(ctx);
2012+2013+expect(deliverDiscordReply).toHaveBeenCalledTimes(1);
2014+expect(firstMockArg(deliverDiscordReply, "deliverDiscordReply")).toMatchObject({
2015+replies: [
2016+{
2017+mediaUrl: "https://example.com/tts.mp3",
2018+audioAsVoice: true,
2019+spokenText: "Spoken answer",
2020+ttsSupplement: {
2021+spokenText: "Spoken answer",
2022+visibleTextAlreadyDelivered: true,
2023+},
2024+},
2025+],
2026+});
2027+});
2028+19192029it("does not flush draft previews for error finals before normal delivery", async () => {
19202030const draftStream = createMockDraftStreamForTest();
19212031dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。