1import { ASSISTANT_CITATION_MAX_COMMENT_LENGTH, type AssistantCitation } from "@t3tools/contracts";
2import { useState, type Ref } from "react";
3
4import { Button } from "../ui/button";
5
6export function AssistantCitationCommentEditor({
7 citation,
8 inputRef,
9 onSubmit,
10 onSubmitAndSend,
11 onCancel,
12 onDraftChange,
13}: {
14 citation: AssistantCitation;
15 inputRef?: Ref<HTMLTextAreaElement>;
16 onSubmit: (comment: string) => boolean;
17 onSubmitAndSend?: (comment: string) => boolean;
18 onCancel: () => void;
19 onDraftChange?: (comment: string) => void;
20}) {
21 const [comment, setComment] = useState(citation.comment ?? "");
22 const commentTooLong = comment.length > ASSISTANT_CITATION_MAX_COMMENT_LENGTH;
23 const submit = () => {
24 if (!commentTooLong) onSubmit(comment);
25 };
26 const submitAndSend = () => {
27 if (commentTooLong) return;
28 if (onSubmitAndSend) {
29 onSubmitAndSend(comment);
30 } else {
31 onSubmit(comment);
32 }
33 };
34
35 return (
36 <div
37 data-citation-comment-editor="true"
38 onKeyDown={(event) => {
39 event.stopPropagation();
40 if (event.nativeEvent.isComposing || event.keyCode === 229) return;
41 if (event.key === "Escape") {
42 event.preventDefault();
43 onCancel();
44 }
45 }}
46 >
47 <textarea
48 ref={inputRef}
49 aria-label="Comment on selected text"
50 aria-description="Enter to save the citation comment; Command/Ctrl+Enter to save and send; Shift+Enter for a new line."
51 aria-invalid={commentTooLong || undefined}
52 placeholder="Add an optional comment..."
53 rows={2}
54 className="field-sizing-content block max-h-40 min-h-16 w-full resize-none bg-transparent px-1 py-1.5 text-base outline-none placeholder:text-muted-foreground sm:text-sm"
55 value={comment}
56 onChange={(event) => {
57 setComment(event.currentTarget.value);
58 onDraftChange?.(event.currentTarget.value);
59 }}
60 onKeyDown={(event) => {
61 if (
62 event.key === "Enter" &&
63 !event.shiftKey &&
64 !event.nativeEvent.isComposing &&
65 event.keyCode !== 229
66 ) {
67 event.preventDefault();
68 if (event.metaKey || event.ctrlKey) {
69 submitAndSend();
70 } else {
71 submit();
72 }
73 }
74 }}
75 />
76 {commentTooLong ? (
77 <p role="status" className="pt-1 text-xs text-destructive">
78 Comments can contain up to {ASSISTANT_CITATION_MAX_COMMENT_LENGTH.toLocaleString()}{" "}
79 characters.
80 </p>
81 ) : null}
82 <div className="mt-2 flex items-center justify-end gap-2">
83 <Button
84 variant="outline"
85 size="xs"
86 onPointerDown={(event) => event.preventDefault()}
87 onClick={onCancel}
88 >
89 Cancel
90 </Button>
91 <Button
92 size="xs"
93 disabled={commentTooLong}
94 onPointerDown={(event) => event.preventDefault()}
95 onClick={submit}
96 >
97 {commentTooLong ? "Shorten comment" : "Save"}
98 </Button>
99 </div>
100 </div>
101 );
102}