2134 lines · 67.2 KB · TypeScript
RawDownload
import { LexicalComposer, type InitialConfigType } from "@lexical/react/LexicalComposer";import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";import { ContentEditable } from "@lexical/react/LexicalContentEditable";import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";import { type ServerProviderSkill } from "@t3tools/contracts";import { serializeComposerFileLink } from "@t3tools/shared/composerTrigger";import {  $applyNodeReplacement,  $createRangeSelectionFromDom,  $createRangeSelection,  $getSelection,  $setSelection,  $isElementNode,  $isLineBreakNode,  $isRangeSelection,  $isTextNode,  $createLineBreakNode,  $createParagraphNode,  $createTextNode,  KEY_ARROW_DOWN_COMMAND,  KEY_ARROW_LEFT_COMMAND,  KEY_ARROW_RIGHT_COMMAND,  KEY_ARROW_UP_COMMAND,  KEY_DOWN_COMMAND,  KEY_ENTER_COMMAND,  KEY_TAB_COMMAND,  COMMAND_PRIORITY_HIGH,  COMMAND_PRIORITY_LOW,  KEY_BACKSPACE_COMMAND,  BLUR_COMMAND,  FOCUS_COMMAND,  $getRoot,  $getNodeByKey,  HISTORY_MERGE_TAG,  HISTORY_PUSH_TAG,  SKIP_DOM_SELECTION_TAG,  DecoratorNode,  type ElementNode,  type LexicalNode,  type SerializedLexicalNode,  type EditorState,  type NodeKey,  type Spread,} from "lexical";import {  createContext,  use,  useCallback,  useEffect,  useEffectEvent,  useImperativeHandle,  useLayoutEffect,  useMemo,  useRef,  useState,} from "react"; import {  clampCollapsedComposerCursor,  collapseExpandedComposerCursor,  expandCollapsedComposerCursor,  isCollapsedCursorAdjacentToInlineToken,} from "~/composer-logic";import {  selectionTouchesMentionBoundary,  splitPromptIntoComposerSegments,} from "~/composer-editor-mentions";import {  INLINE_TERMINAL_CONTEXT_PLACEHOLDER,  type TerminalContextDraft,} from "~/lib/terminalContext";import { cn, isMacPlatform } from "~/lib/utils";import { basenameOfPath } from "~/pierre-icons";import {  COMPOSER_INLINE_CHIP_DECORATOR_CLASS_NAME,  COMPOSER_INLINE_CHIP_ICON_CLASS_NAME,  COMPOSER_INLINE_CHIP_LABEL_CLASS_NAME,  COMPOSER_INLINE_SKILL_CHIP_CLASS_NAME,  SKILL_CHIP_ICON_SVG,} from "./composerInlineChip";import { FILE_TAG_CHIP_CLASS_NAME, FileTagChipContent } from "./chat/FileTagChip";import { ComposerPendingTerminalContextChip } from "./chat/ComposerPendingTerminalContexts";import { getTimelinePageScrollKey } from "./chat/pageScrollController";import { formatProviderSkillDisplayName } from "@t3tools/client-runtime/providerSkills";import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";import { registerComposerInlineTokenPaste } from "./composerInlineTokenPaste";import { didComposerSelectionChangeVisibly } from "./composerSelection";import {  $consumeComposerCitationCommentRequest,  $createComposerCitationNode,  ComposerCitationCommentContext,  ComposerCitationNode,  type ComposerCitationCommentRequest,  type ComposerCitationCommentTarget,} from "./ComposerCitationNode"; const COMPOSER_EDITOR_HMR_KEY = `composer-editor-${Math.random().toString(36).slice(2)}`;const SURROUND_SYMBOLS: [string, string][] = [  ["(", ")"],  ["[", "]"],  ["{", "}"],  ["'", "'"],  ['"', '"'],  ["“", "”"],  ["`", "`"],  ["<", ">"],  ["«", "»"],  ["*", "*"],  ["_", "_"],];const SURROUND_SYMBOLS_MAP = new Map<string, string>(SURROUND_SYMBOLS);const BACKTICK_SURROUND_CLOSE_SYMBOL = SURROUND_SYMBOLS_MAP.get("`") ?? null; type SerializedComposerMentionNode = Spread<  {    path: string;    source?: string;    type: "composer-mention";    version: 1;  },  SerializedLexicalNode>; type SerializedComposerSkillNode = Spread<  {    skillName: string;    skillLabel?: string;    skillDescription?: string;    type: "composer-skill";    version: 1;  },  SerializedLexicalNode>; type SerializedComposerTerminalContextNode = Spread<  {    context: TerminalContextDraft;    type: "composer-terminal-context";    version: 1;  },  SerializedLexicalNode>; const ComposerTerminalContextActionsContext = createContext<{  onRemoveTerminalContext: (contextId: string) => void;}>({  onRemoveTerminalContext: () => {},}); function ComposerMentionDecorator(props: { path: string }) {  const theme = resolvedThemeFromDocument();  const chip = (    <span      className={FILE_TAG_CHIP_CLASS_NAME}      contentEditable={false}      spellCheck={false}      data-composer-mention-chip="true"    >      <FileTagChipContent path={props.path} label={basenameOfPath(props.path)} theme={theme} />    </span>  );   return (    <Tooltip>      <TooltipTrigger render={chip} />      <TooltipPopup side="top" className="max-w-120 whitespace-normal leading-tight wrap-anywhere">        {props.path}      </TooltipPopup>    </Tooltip>  );} class ComposerMentionNode extends DecoratorNode<React.ReactElement> {  __path: string;  __source: string;   static override getType(): string {    return "composer-mention";  }   static override clone(node: ComposerMentionNode): ComposerMentionNode {    return new ComposerMentionNode(node.__path, node.__source, node.__key);  }   static override importJSON(serializedNode: SerializedComposerMentionNode): ComposerMentionNode {    return $createComposerMentionNode(serializedNode.path, serializedNode.source).updateFromJSON(      serializedNode,    );  }   constructor(path: string, source = serializeComposerFileLink(path), key?: NodeKey) {    super(key);    this.__path = path;    this.__source = source;  }   override exportJSON(): SerializedComposerMentionNode {    return {      ...super.exportJSON(),      path: this.__path,      source: this.__source,      type: "composer-mention",      version: 1,    };  }   override createDOM(): HTMLElement {    const dom = document.createElement("span");    dom.className = COMPOSER_INLINE_CHIP_DECORATOR_CLASS_NAME;    return dom;  }   override updateDOM(): false {    return false;  }   override getTextContent(): string {    return this.__source;  }   override isInline(): true {    return true;  }   override decorate(): React.ReactElement {    return <ComposerMentionDecorator path={this.__path} />;  }} function $createComposerMentionNode(path: string, source?: string): ComposerMentionNode {  return $applyNodeReplacement(new ComposerMentionNode(path, source));} function resolveSkillDescription(  skill: Pick<ServerProviderSkill, "shortDescription" | "description">,): string | null {  const shortDescription = skill.shortDescription?.trim();  if (shortDescription) {    return shortDescription;  }  const description = skill.description?.trim();  return description || null;} type ComposerSkillMetadata = {  label: string;  description: string | null;}; function skillMetadataByName(  skills: ReadonlyArray<ServerProviderSkill>,): ReadonlyMap<string, ComposerSkillMetadata> {  return new Map(    skills.map((skill) => [      skill.name,      {        label: formatProviderSkillDisplayName(skill),        description: resolveSkillDescription(skill),      },    ]),  );} function ComposerSkillDecorator(props: { skillLabel: string; skillDescription: string | null }) {  const chip = (    <span      className={COMPOSER_INLINE_SKILL_CHIP_CLASS_NAME}      contentEditable={false}      spellCheck={false}      data-composer-skill-chip="true"    >      <span        aria-hidden="true"        className={COMPOSER_INLINE_CHIP_ICON_CLASS_NAME}        dangerouslySetInnerHTML={{ __html: SKILL_CHIP_ICON_SVG }}      />      <span className={COMPOSER_INLINE_CHIP_LABEL_CLASS_NAME}>{props.skillLabel}</span>    </span>  );   if (!props.skillDescription) {    return chip;  }   return (    <Tooltip>      <TooltipTrigger render={chip} />      <TooltipPopup side="top" className="max-w-120 whitespace-normal leading-tight">        {props.skillDescription}      </TooltipPopup>    </Tooltip>  );} class ComposerSkillNode extends DecoratorNode<React.ReactElement> {  __skillName: string;  __skillLabel: string;  __skillDescription: string | null;   static override getType(): string {    return "composer-skill";  }   static override clone(node: ComposerSkillNode): ComposerSkillNode {    return new ComposerSkillNode(      node.__skillName,      node.__skillLabel,      node.__skillDescription,      node.__key,    );  }   static override importJSON(serializedNode: SerializedComposerSkillNode): ComposerSkillNode {    return $createComposerSkillNode(      serializedNode.skillName,      serializedNode.skillLabel ?? serializedNode.skillName,      serializedNode.skillDescription ?? null,    ).updateFromJSON(serializedNode);  }   constructor(    skillName: string,    skillLabel: string,    skillDescription: string | null,    key?: NodeKey,  ) {    super(key);    const normalizedSkillName = skillName.startsWith("$") ? skillName.slice(1) : skillName;    this.__skillName = normalizedSkillName;    this.__skillLabel = skillLabel;    this.__skillDescription = skillDescription;  }   override exportJSON(): SerializedComposerSkillNode {    return {      ...super.exportJSON(),      skillName: this.__skillName,      skillLabel: this.__skillLabel,      ...(this.__skillDescription ? { skillDescription: this.__skillDescription } : {}),      type: "composer-skill",      version: 1,    };  }   override createDOM(): HTMLElement {    const dom = document.createElement("span");    dom.className = COMPOSER_INLINE_CHIP_DECORATOR_CLASS_NAME;    return dom;  }   override updateDOM(): false {    return false;  }   override getTextContent(): string {    return `$${this.__skillName}`;  }   override isInline(): true {    return true;  }   override decorate(): React.ReactElement {    return (      <ComposerSkillDecorator        skillLabel={this.__skillLabel}        skillDescription={this.__skillDescription}      />    );  }} function $createComposerSkillNode(  skillName: string,  skillLabel: string,  skillDescription: string | null,): ComposerSkillNode {  return $applyNodeReplacement(new ComposerSkillNode(skillName, skillLabel, skillDescription));} function ComposerTerminalContextDecorator(props: { context: TerminalContextDraft }) {  return <ComposerPendingTerminalContextChip context={props.context} />;} class ComposerTerminalContextNode extends DecoratorNode<React.ReactElement> {  __context: TerminalContextDraft;   static override getType(): string {    return "composer-terminal-context";  }   static override clone(node: ComposerTerminalContextNode): ComposerTerminalContextNode {    return new ComposerTerminalContextNode(node.__context, node.__key);  }   static override importJSON(    serializedNode: SerializedComposerTerminalContextNode,  ): ComposerTerminalContextNode {    return $createComposerTerminalContextNode(serializedNode.context);  }   constructor(context: TerminalContextDraft, key?: NodeKey) {    super(key);    this.__context = context;  }   override exportJSON(): SerializedComposerTerminalContextNode {    return {      ...super.exportJSON(),      context: this.__context,      type: "composer-terminal-context",      version: 1,    };  }   override createDOM(): HTMLElement {    const dom = document.createElement("span");    dom.className = COMPOSER_INLINE_CHIP_DECORATOR_CLASS_NAME;    return dom;  }   override updateDOM(): false {    return false;  }   override getTextContent(): string {    return INLINE_TERMINAL_CONTEXT_PLACEHOLDER;  }   override isInline(): true {    return true;  }   override decorate(): React.ReactElement {    return <ComposerTerminalContextDecorator context={this.__context} />;  }} function $createComposerTerminalContextNode(  context: TerminalContextDraft,): ComposerTerminalContextNode {  return $applyNodeReplacement(new ComposerTerminalContextNode(context));} type ComposerInlineTokenNode =  | ComposerMentionNode  | ComposerSkillNode  | ComposerCitationNode  | ComposerTerminalContextNode; function isComposerInlineTokenNode(candidate: unknown): candidate is ComposerInlineTokenNode {  return (    candidate instanceof ComposerMentionNode ||    candidate instanceof ComposerSkillNode ||    candidate instanceof ComposerCitationNode ||    candidate instanceof ComposerTerminalContextNode  );} function resolvedThemeFromDocument(): "light" | "dark" {  return document.documentElement.classList.contains("dark") ? "dark" : "light";} function terminalContextSignature(contexts: ReadonlyArray<TerminalContextDraft>): string {  return contexts    .map((context) =>      [        context.id,        context.threadId,        context.terminalId,        context.terminalLabel,        context.lineStart,        context.lineEnd,        context.createdAt,        context.text,      ].join("\u001f"),    )    .join("\u001e");} function skillSignature(skills: ReadonlyArray<ServerProviderSkill>): string {  return skills    .map((skill) =>      [        skill.name,        skill.displayName ?? "",        skill.shortDescription ?? "",        skill.description ?? "",        skill.path,        skill.scope ?? "",        skill.enabled ? "1" : "0",      ].join("\u001f"),    )    .join("\u001e");} function clampExpandedCursor(value: string, cursor: number): number {

Lines 1–500 of 2134.