import { useCallback, useEffect, useId, useState, useSyncExternalStore } from "react"; import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; import { useCellValue } from "@mdxeditor/gurx"; import { $getSelection, $isElementNode, $isNodeSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, DELETE_CHARACTER_COMMAND, DELETE_LINE_COMMAND, DELETE_WORD_COMMAND, mergeRegister, REMOVE_TEXT_COMMAND, } from "lexical"; import { SidecarCard } from "../card"; import { SendAction } from "../send-action"; import { widgets$ } from "../widget-options"; import { $isResearchNode } from "@chopin/dialect"; import { CloseIcon } from "@chopin/icons"; import type { FormEvent } from "react"; import type { Research } from "@chopin/protocol"; import type { ResearchNode } from "@chopin/dialect"; import type { LexicalEditor, LexicalNode, RangeSelection } from "lexical"; import type { ResearchStore } from "../widget-options"; const STAGES: Record = { queued: "Queued", searching: "Searching", analyzing: "Analyzing", writing: "Writing", publishing: "Publishing", ready: "Research ready", failed: "Research failed", cancelled: "Research cancelled", }; const REMOVABLE_STAGES = new Set(["failed", "cancelled", "ready"]); export type ResearchComposerProps = { blocked?: string; cancelDisabled?: boolean; cancelLabel?: string; dismissible?: boolean; error?: string; onCancel: () => void; onChange: (value: string) => void; onEscape?: () => void; onSubmit: () => void; question: string; questionLocked?: boolean; submitLabel?: string; submitting?: boolean; }; export function researchComposerKey(event: { key: string; metaKey: boolean; ctrlKey: boolean; isComposing: boolean; }): "dismiss" | "ignore" | "newline" | "submit" { if (event.isComposing) return "ignore"; if (event.key === "Escape") return "dismiss"; if (event.key !== "Enter") return "ignore"; return event.metaKey || event.ctrlKey ? "newline" : "submit"; } type ResearchComposerKeyEvent = { ctrlKey: boolean; currentTarget: { readOnly: boolean; selectionEnd: number; selectionStart: number; setRangeText(replacement: string, start: number, end: number, selectionMode: "end"): void; value: string; }; key: string; keyCode: number; metaKey: boolean; nativeEvent: { isComposing: boolean }; preventDefault(): void; stopPropagation(): void; }; export function handleResearchComposerKey( event: ResearchComposerKeyEvent, actions: { dismissible: boolean; onChange: (value: string) => void; onDismiss: () => void; onSubmit: () => void; }, ) { let action = researchComposerKey({ key: event.key, metaKey: event.metaKey, ctrlKey: event.ctrlKey, isComposing: event.nativeEvent.isComposing || event.keyCode === 229, }); if (action === "ignore") return; event.preventDefault(); event.stopPropagation(); if (action === "dismiss") { if (actions.dismissible) actions.onDismiss(); return; } if (action === "submit") { actions.onSubmit(); return; } let textarea = event.currentTarget; if (textarea.readOnly) return; textarea.setRangeText( "\n", textarea.selectionStart, textarea.selectionEnd, "end", ); actions.onChange(textarea.value); } export function ResearchComposer( { blocked, cancelDisabled, cancelLabel, dismissible = true, error, onCancel, onChange, onEscape, onSubmit, question, questionLocked, submitLabel = "Start research", submitting, }: ResearchComposerProps, ) { let id = useId(); let submit = (event: FormEvent) => { event.preventDefault(); onSubmit(); }; return (
{dismissible && !submitting && ( )}