Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/frontend/chat/side-chat.tsx
Views: 687
import { Button, Flex, Space, Tooltip } from "antd";1import { CSSProperties, useCallback, useEffect, useRef, useState } from "react";2import {3redux,4useActions,5useRedux,6useTypedRedux,7} from "@cocalc/frontend/app-framework";8import { AddCollaborators } from "@cocalc/frontend/collaborators";9import { A, Icon, Loading } from "@cocalc/frontend/components";10import { IS_MOBILE } from "@cocalc/frontend/feature";11import { ProjectUsers } from "@cocalc/frontend/projects/project-users";12import { user_activity } from "@cocalc/frontend/tracker";13import type { ChatActions } from "./actions";14import { ChatLog } from "./chat-log";15import ChatInput from "./input";16import { LLMCostEstimationChat } from "./llm-cost-estimation";17import { SubmitMentionsFn } from "./types";18import { INPUT_HEIGHT, markChatAsReadIfUnseen } from "./utils";19import VideoChatButton from "./video/launch-button";20import { COLORS } from "@cocalc/util/theme";21import Filter from "./filter";2223interface Props {24project_id: string;25path: string;26style?: CSSProperties;27fontSize?: number;28actions?: ChatActions;29desc?;30}3132export default function SideChat({33actions: actions0,34project_id,35path,36style,37fontSize,38desc,39}: Props) {40// This actionsViaContext via useActions is ONLY needed for side chat for non-frame41// editors, i.e., basically just Sage Worksheets!42const actionsViaContext = useActions(project_id, path);43const actions: ChatActions = actions0 ?? actionsViaContext;44const disableFilters = actions0 == null;45const messages = useRedux(["messages"], project_id, path);46const [lastVisible, setLastVisible] = useState<Date | null>(null);47const [input, setInput] = useState("");48const search = desc?.get("data-search") ?? "";49const selectedHashtags = desc?.get("data-selectedHashtags");50const scrollToIndex = desc?.get("data-scrollToIndex") ?? null;51const scrollToDate = desc?.get("data-scrollToDate") ?? null;52const fragmentId = desc?.get("data-fragmentId") ?? null;53const costEstimate = desc?.get("data-costEstimate");54const addCollab: boolean = useRedux(["add_collab"], project_id, path);55const project_map = useTypedRedux("projects", "project_map");56const project = project_map?.get(project_id);57const scrollToBottomRef = useRef<any>(null);58const submitMentionsRef = useRef<SubmitMentionsFn>();5960const markAsRead = useCallback(() => {61markChatAsReadIfUnseen(project_id, path);62}, [project_id, path]);6364// The act of opening/displaying the chat marks it as seen...65// since this happens when the user shows it.66useEffect(() => {67markAsRead();68}, []);6970const sendChat = useCallback(71(options?) => {72actions.sendChat({ submitMentionsRef, ...options });73actions.deleteDraft(0);74scrollToBottomRef.current?.(true);75setTimeout(() => {76scrollToBottomRef.current?.(true);77}, 10);78setTimeout(() => {79scrollToBottomRef.current?.(true);80}, 1000);81},82[actions],83);8485if (messages == null) {86return <Loading />;87}8889// WARNING: making autofocus true would interfere with chat and terminals90// -- where chat and terminal are both focused at same time sometimes91// (esp on firefox).9293return (94<div95style={{96height: "100%",97width: "100%",98display: "flex",99flexDirection: "column",100backgroundColor: "#efefef",101...style,102}}103onMouseMove={markAsRead}104onFocus={() => {105// Remove any active key handler that is next to this side chat.106// E.g, this is critical for taks lists...107redux.getActions("page").erase_active_key_handler();108}}109>110{!IS_MOBILE && project != null && actions != null && (111<div112style={{113margin: "0 5px",114paddingTop: "5px",115maxHeight: "50vh",116overflow: "auto",117borderBottom: "1px solid lightgrey",118}}119>120<Space.Compact121style={{122float: "right",123marginTop: "-5px",124}}125>126<VideoChatButton actions={actions} />127<Tooltip title="Show TimeTravel change history of this side chat.">128<Button129onClick={() => {130actions.showTimeTravelInNewTab();131}}132>133<Icon name="history" />134</Button>135</Tooltip>136</Space.Compact>137<CollabList138addCollab={addCollab}139project={project}140actions={actions}141/>142<AddChatCollab addCollab={addCollab} project_id={project_id} />143</div>144)}145{!disableFilters && (146<Filter147actions={actions}148search={search}149style={{150margin: 0,151...(messages.size >= 2152? undefined153: { visibility: "hidden", height: 0 }),154}}155/>156)}157<div158className="smc-vfill"159style={{160backgroundColor: "#fff",161paddingLeft: "15px",162flex: 1,163margin: "5px 0",164}}165>166<ChatLog167actions={actions}168fontSize={fontSize}169project_id={project_id}170path={path}171scrollToBottomRef={scrollToBottomRef}172mode={"sidechat"}173setLastVisible={setLastVisible}174search={search}175selectedHashtags={selectedHashtags}176disableFilters={disableFilters}177scrollToIndex={scrollToIndex}178scrollToDate={scrollToDate}179selectedDate={fragmentId}180costEstimate={costEstimate}181/>182</div>183184<div>185{input.trim() ? (186<Flex vertical={false} align="center" justify="space-between">187<Tooltip title="Send message (shift+enter)">188<Space>189{lastVisible && (190<Button191disabled={!input.trim()}192type="primary"193onClick={() => {194sendChat({ reply_to: new Date(lastVisible) });195}}196>197<Icon name="reply" /> Reply (shift+enter)198</Button>199)}200<Button201type={!lastVisible ? "primary" : undefined}202style={{ margin: "5px 0 5px 5px" }}203onClick={() => {204sendChat();205user_activity("side_chat", "send_chat", "click");206}}207disabled={!input?.trim()}208>209<Icon name="paper-plane" />210Start New Thread211</Button>212</Space>213</Tooltip>214{costEstimate?.get("date") == 0 && (215<LLMCostEstimationChat216compact217costEstimate={costEstimate?.toJS()}218style={{ margin: "5px" }}219/>220)}221</Flex>222) : undefined}223<ChatInput224autoFocus225fontSize={fontSize}226cacheId={`${path}${project_id}-new`}227input={input}228on_send={() => {229sendChat(lastVisible ? { reply_to: lastVisible } : undefined);230user_activity("side_chat", "send_chat", "keyboard");231actions?.clearAllFilters();232}}233style={{ height: INPUT_HEIGHT }}234height={INPUT_HEIGHT}235onChange={(value) => {236setInput(value);237// submitMentionsRef processes the reply, but does not actually send the mentions238const input = submitMentionsRef.current?.(undefined, true) ?? value;239actions?.llmEstimateCost({ date: 0, input });240}}241submitMentionsRef={submitMentionsRef}242syncdb={actions.syncdb}243date={0}244editBarStyle={{ overflow: "none" }}245/>246</div>247</div>248);249}250251function AddChatCollab({ addCollab, project_id }) {252if (!addCollab) {253return null;254}255return (256<div>257You can{" "}258{redux.getProjectsStore().hasLanguageModelEnabled(project_id) && (259<>chat with AI or notify a collaborator by typing @, </>260)}261<A href="https://github.com/sagemathinc/cocalc/discussions">262join a discussion on GitHub263</A>264, and add more collaborators to this project below.265<AddCollaborators project_id={project_id} autoFocus where="side-chat" />266<div style={{ color: COLORS.GRAY_M }}>267(Collaborators have access to all files in this project.)268</div>269</div>270);271}272273function CollabList({ project, addCollab, actions }) {274return (275<div276style={277!addCollab278? {279maxHeight: "1.7em",280whiteSpace: "nowrap",281overflow: "hidden",282textOverflow: "ellipsis",283cursor: "pointer",284}285: { cursor: "pointer" }286}287onClick={() => actions.setState({ add_collab: !addCollab })}288>289<div style={{ width: "16px", display: "inline-block" }}>290<Icon name={addCollab ? "caret-down" : "caret-right"} />291</div>292<span style={{ color: COLORS.GRAY_M, fontSize: "10pt" }}>293<ProjectUsers294project={project}295none={<span>Add people to work with...</span>}296/>297</span>298</div>299);300}301302303