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 { XIcon } from "@phosphor-icons/react"; import type { FormEvent, KeyboardEvent } 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"; } export function handleResearchComposerKey( event: Pick< KeyboardEvent, | "ctrlKey" | "currentTarget" | "key" | "metaKey" | "nativeEvent" | "preventDefault" | "stopPropagation" >, 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, }); 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 && ( )}