<commit_before>import { useEffect, useState } from "react";
import { redux } from "@cocalc/frontend/app-framework";
import { Icon, TimeAgo } from "@cocalc/frontend/components";
import { Element } from "../types";
import { getStyle } from "./text";
import { useFrameContext } from "../hooks";
import StaticMarkdown from "@cocalc/frontend/editors/slate/static-markdown";
import "@cocalc/frontend/editors/slate/elements/math/math-widget";
import { Button, Comment, Tooltip } from "antd";
import { len, trunc_middle } from "@cocalc/util/misc";
import { Avatar } from "@cocalc/frontend/account/avatar/avatar";
import MultiMarkdownInput from "@cocalc/frontend/editors/markdown-input/multimode";
import useEditFocus from "./edit-focus";
import { useDebouncedCallback } from "use-debounce";
import Composing from "./chat-composing";
import { useIsMountedRef } from "@cocalc/frontend/app-framework";
import { delay } from "awaiting";

import { ChatLog, getChatStyle, messageStyle } from "./chat-static";

interface Props {
  element: Element;
  focused?: boolean;
}

export default function ChatDynamic({ element, focused }: Props) {
  return (
    <>
      <Icon
        name={"comment"}
        style={getStyle(element, { fontSize: 24, background: "white" })}
      />
      <Conversation element={element} focused={focused} />
    </>
  );
}

function Conversation({ element, focused }: Props) {
  const { actions, desc } = useFrameContext();
  const [editFocus, setEditFocus] = useEditFocus(desc.get("editFocus"));
  const [mode, setMode] = useState<string>("");

  const saveChat = useDebouncedCallback((input) => {
    actions.saveChat({ id: element.id, input });
  }, 1500);

  const [input, setInput] = useState<string>("");
  // we ensure input is set properly to what's in the element
  // when it is focused.  When NOT focused, we don't bother,
  // to avoid wasting resources.
  useEffect(() => {
    if (!focused) return;
    const input1 =
      element.data?.[redux.getStore("account").get_account_id()]?.input ?? "";
    if (input1 != input) {
      saveChat.cancel();
      setInput(input1);
    }
  }, [element, focused]);

  const isMountedRef = useIsMountedRef();
  const clearInput = async () => {
    setInput("");
    // There's a potential very slight chance that additional input
    // from slate will get set in the next event loop, so we make
    // sure to clear that.  This is due to how onChange and slate work.
    await delay(1);
    if (isMountedRef.current) {
      setInput("");
    }
  };

  // When the component goes to be unmounted, we will fetch data if the input has changed.
  useEffect(
    () => () => {
      saveChat.flush();
    },
    [saveChat]
  );

  return (
    <div style={getChatStyle(element)}>
      <ChatLog
        Message={Message}
        element={element}
        style={{
          flex: 1,
          overflowY: "auto",
          background: "white",
        }}
      />
      <Composing element={element} focused={focused} />
      {(focused || len(element.data) === 0) && (
        <div
          style={{ height: "125px", display: "flex" }}
          className={editFocus ? "nodrag" : undefined}
          onClick={() => {
            if (focused && !editFocus) {
              setEditFocus(true);
            }
          }}
          onTouchStart={() => {
            if (focused && !editFocus) {
              setEditFocus(true);
            }
          }}
        >
          <MultiMarkdownInput
            saveDebounceMs={0}
            onFocus={() => {
              setEditFocus(true);
            }}
            onBlur={() => {<commit_msg>whiteboard: prevent wheel nav when mouse over a chat, so you can scroll the chat itself<commit_after>import { useEffect, useRef, useState } from "react";
import { redux } from "@cocalc/frontend/app-framework";
import { Icon, TimeAgo } from "@cocalc/frontend/components";
import { Element } from "../types";
import { getStyle } from "./text";
import { useFrameContext } from "../hooks";
import StaticMarkdown from "@cocalc/frontend/editors/slate/static-markdown";
import "@cocalc/frontend/editors/slate/elements/math/math-widget";
import { Button, Comment, Tooltip } from "antd";
import { len, trunc_middle } from "@cocalc/util/misc";
import { Avatar } from "@cocalc/frontend/account/avatar/avatar";
import MultiMarkdownInput from "@cocalc/frontend/editors/markdown-input/multimode";
import useEditFocus from "./edit-focus";
import { useDebouncedCallback } from "use-debounce";
import Composing from "./chat-composing";
import { useIsMountedRef } from "@cocalc/frontend/app-framework";
import { delay } from "awaiting";
import { useWheel } from "@use-gesture/react";

import { ChatLog, getChatStyle, messageStyle } from "./chat-static";

interface Props {
  element: Element;
  focused?: boolean;
}

export default function ChatDynamic({ element, focused }: Props) {
  return (
    <>
      <Icon
        name={"comment"}
        style={getStyle(element, { fontSize: 24, background: "white" })}
      />
      <Conversation element={element} focused={focused} />
    </>
  );
}

function Conversation({ element, focused }: Props) {
  const { actions, desc } = useFrameContext();
  const [editFocus, setEditFocus] = useEditFocus(desc.get("editFocus"));
  const [mode, setMode] = useState<string>("");

  const saveChat = useDebouncedCallback((input) => {
    actions.saveChat({ id: element.id, input });
  }, 1500);

  const [input, setInput] = useState<string>("");
  // we ensure input is set properly to what's in the element
  // when it is focused.  When NOT focused, we don't bother,
  // to avoid wasting resources.
  useEffect(() => {
    if (!focused) return;
    const input1 =
      element.data?.[redux.getStore("account").get_account_id()]?.input ?? "";
    if (input1 != input) {
      saveChat.cancel();
      setInput(input1);
    }
  }, [element, focused]);

  const isMountedRef = useIsMountedRef();
  const clearInput = async () => {
    setInput("");
    // There's a potential very slight chance that additional input
    // from slate will get set in the next event loop, so we make
    // sure to clear that.  This is due to how onChange and slate work.
    await delay(1);
    if (isMountedRef.current) {
      setInput("");
    }
  };

  // When the component goes to be unmounted, we will fetch data if the input has changed.
  useEffect(
    () => () => {
      saveChat.flush();
    },
    [saveChat]
  );
  const divRef = useRef<any>(null);
  useWheel(
    (state) => {
      state.event.stopPropagation();
    },
    {
      target: divRef,
      eventOptions: { passive: false, capture: true },
    }
  );

  return (
    <div style={getChatStyle(element)} ref={divRef}>
      <ChatLog
        Message={Message}
        element={element}
        style={{
          flex: 1,
          overflowY: "auto",
          background: "white",
        }}
      />
      <Composing element={element} focused={focused} />
      {(focused || len(element.data) === 0) && (
        <div
          style={{ height: "125px", display: "flex" }}
          className={editFocus ? "nodrag" : undefined}
          onClick={() => {
            if (focused && !editFocus) {
              setEditFocus(true);
            }
          }}
          onTouchStart={() => {
            if (focused && !editFocus) {
              setEditFocus(true);
            }
          }}
        >
          <MultiMarkdownInput
            saveDebounceMs={0}
            onFocus={() => {
              setEditFocus(true);
            }}
            onBlur={() => {
{'fraction_non_alphanumeric': 0.1469225678358703, 'fraction_numerical': 0.004235605559232297, 'mean_word_length': 2.703921568627451, 'pattern_counts': {'":': 0, '<': 26, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '18127', 'n_tokens_mistral': 2285, 'n_tokens_neox': 2247, 'n_words': 590}