1import { PROVIDER_SEND_TURN_MAX_INPUT_CHARS } from "@t3tools/contracts";
2import { expandAssistantCitationsForProvider } from "@t3tools/shared/assistantCitations";
3
4type ComposerSubmitEvent = { preventDefault: () => void };
5
6type ComposerSubmissionInput = {
7 prompt: string;
8 providerInput?: string;
9 submissionTarget: "provider-turn" | "pending-user-input";
10};
11
12export function getComposerPromptLengthValidationMessage(prompt: string): string | null {
13 const normalizedPrompt = prompt.trim();
14 const inputLength = Math.max(
15 normalizedPrompt.length,
16 expandAssistantCitationsForProvider(normalizedPrompt).length,
17 );
18 const excessCharacters = inputLength - PROVIDER_SEND_TURN_MAX_INPUT_CHARS;
19 if (excessCharacters <= 0) return null;
20
21 const characterLabel = excessCharacters === 1 ? "character" : "characters";
22 return `Prompt is ${excessCharacters.toLocaleString("en-US")} ${characterLabel} over the ${PROVIDER_SEND_TURN_MAX_INPUT_CHARS.toLocaleString("en-US")}-character limit. Shorten or split it before sending.`;
23}
24
25export function getComposerSubmissionValidationMessage(
26 options: ComposerSubmissionInput,
27): string | null {
28 return options.submissionTarget === "provider-turn"
29 ? getComposerPromptLengthValidationMessage(options.providerInput ?? options.prompt)
30 : null;
31}
32
33export function submitComposerDraft(
34 options: ComposerSubmissionInput & {
35 event: ComposerSubmitEvent | undefined;
36 onSend: (event?: ComposerSubmitEvent) => boolean | void;
37 },
38): { validationMessage: string | null; didDispatch: boolean } {
39 const validationMessage = getComposerSubmissionValidationMessage(options);
40 if (validationMessage) {
41 options.event?.preventDefault();
42 return { validationMessage, didDispatch: false };
43 }
44
45 if (options.onSend(options.event) === false) {
46 options.event?.preventDefault();
47 return { validationMessage: null, didDispatch: false };
48 }
49 return { validationMessage: null, didDispatch: true };
50}