writing.tsx 8.3 KB
import chatStyles from "@/app/components/chat.module.scss";
import homeStyles from "@/app/components/home.module.scss";
import styles from "@/app/components/writing/writing.module.scss";
import clsx from "clsx";

import { WriteSiderBar } from "./write-siderBar";
import { WindowContent } from "@/app/components/home";
import { useMobileScreen } from "@/app/utils";
import { IconButton } from "../button";
import Locale from "@/app/locales";
import { Path } from "@/app/constant";
import { useNavigate } from "react-router-dom";
import { getClientConfig } from "@/app/config/client";
import React, { useMemo, useRef, useState } from "react";
import { useAppConfig } from "@/app/store";
import { ChatAction } from "../chat";
import { useWindowSize } from "@/app/utils";
import { exportWord } from "@/app/utils/excelAndWordUtils/word";
import { HTMLPreview } from "../artifacts";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";

import ReturnIcon from "@/app/icons/return.svg";
import MinIcon from "@/app/icons/min.svg";
import MaxIcon from "@/app/icons/max.svg";
import SDIcon from "@/app/icons/sd.svg";
import LoadingIcon from "@/app/icons/three-dots.svg";
import BotIcon from "@/app/icons/bot.svg";
import EditIcon from "@/app/icons/rename.svg";
import ReloadIcon from "@/app/icons/reload.svg";
import CopyIcon from "@/app/icons/copy.svg";
import ExcelIcon from "@/app/icons/excel.svg";
import WordIcon from "@/app/icons/word.svg";
import MindIcon from "@/app/icons/mind.svg";
import PptIcon from "@/app/icons/ppt.svg";
import PdfIcon from "@/app/icons/pdf.svg";
import { message } from "antd";

export function WritingPage() {
  const isMobileScreen = useMobileScreen();
  const navigate = useNavigate();
  const clientConfig = useMemo(() => getClientConfig(), []);
  const showMaxIcon = !isMobileScreen && !clientConfig?.isApp;
  const config = useAppConfig();
  const scrollRef = useRef<HTMLDivElement>(null);
  const isWriting = location.pathname === Path.Writing;
  const { height } = useWindowSize();
  const [isEdit, setIsEdit] = useState(false);
  const [loading, setLoading] = useState(false);
  const quillRef = useRef<ReactQuill | null>(null);
  const [htmlCode, setHtmlCode] = useState("");

  //编辑器
  const toolbarOptions = [
    [{ font: [] }, { size: ["small", false, "large", "huge"] }],
    [{ header: [1, 2, 3, 4, 5, 6, false] }],
    ["bold", "italic", "underline", "strike"],
    [{ list: "ordered" }, { list: "bullet" }, { align: [] }],
    [{ color: [] }, { background: [] }],
    ["link", "image"],
  ];

  const copyToClipboard = () => {
    // 检查quillRef.current是否为null
    if (quillRef.current) {
      const editor = quillRef.current.getEditor(); // 获取编辑器实例
      const range = editor.getSelection(); // 获取当前选择的范围
      const text = editor.getText(); // 获取编辑器中的文本内容

      if (range && range.length > 0) {
        // 如果有选中的文本,复制选中的内容
        const selectedText = text.substring(
          range.index,
          range.index + range.length,
        );
        navigator.clipboard
          .writeText(selectedText)
          .then(() => {
            message.success("复制成功");
          })
          .catch((err) => {
            message.error("复制失败:");
          });
      } else {
        // 如果没有选择文本,就复制全部内容
        navigator.clipboard
          .writeText(text)
          .then(() => {
            message.success("复制成功");
          })
          .catch((err) => {
            message.error("复制失败:");
          });
      }
    } else {
      console.error("Quill编辑器尚未初始化");
    }
  };

  return (
    <>
      <WriteSiderBar
        className={clsx({ [homeStyles["sidebar-show"]]: isWriting })}
        htmlCode={htmlCode}
        setHtmlCode={setHtmlCode}
        loading={loading}
        setLoading={setLoading}
      />
      <WindowContent>
        <div className={chatStyles.chat} key={"1"}>
          <div className="window-header" data-tauri-drag-region>
            {isMobileScreen && (
              <div className="window-actions">
                <div className={"window-action-button"}>
                  <IconButton
                    icon={<ReturnIcon />}
                    bordered
                    title={Locale.Chat.Actions.ChatList}
                    onClick={() => navigate(Path.BgRemoval)}
                  />
                </div>
              </div>
            )}
            <div
              className={clsx(
                "window-header-title",
                chatStyles["chat-body-title"],
              )}
            >
              <div className={`window-header-main-title`}>AI-Writing</div>
            </div>
            <div className={chatStyles["chat-message-actions"]}>
              <div className={chatStyles["chat-input-actions"]}>
                <ChatAction
                  text={Locale.Chat.Actions.ReWrite}
                  icon={<ReloadIcon />}
                  onClick={() => {}}
                />
                <ChatAction
                  text={Locale.Chat.Actions.Copy}
                  icon={<CopyIcon />}
                  onClick={copyToClipboard}
                />
                {!isEdit ? (
                  <ChatAction
                    text={Locale.Chat.Actions.Edit}
                    icon={<EditIcon />}
                    onClick={() => {
                      setIsEdit(true);
                    }}
                  />
                ) : (
                  <ChatAction
                    text="取消编辑"
                    icon={<EditIcon />}
                    onClick={() => {
                      setIsEdit(false);
                    }}
                  />
                )}

                <ChatAction
                  text={Locale.Chat.Actions.Pdf}
                  icon={<PdfIcon />}
                  onClick={() => {}}
                />
                {htmlCode && (
                  <ChatAction
                    text={Locale.Chat.Actions.Word}
                    icon={<WordIcon />}
                    onClick={() => {
                      exportWord(htmlCode);
                    }}
                  />
                )}
                <ChatAction
                  text={Locale.Chat.Actions.Excel}
                  icon={<ExcelIcon />}
                  onClick={() => {}}
                />
                <ChatAction
                  text={Locale.Chat.Actions.Ppt}
                  icon={<PptIcon />}
                  onClick={() => {}}
                />
                <ChatAction
                  text={Locale.Chat.Actions.Mind}
                  icon={<MindIcon />}
                  onClick={() => {}}
                />
              </div>
            </div>
            <div className="window-actions">
              {showMaxIcon && (
                <div className="window-action-button">
                  <IconButton
                    aria={Locale.Chat.Actions.FullScreen}
                    icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}
                    bordered
                    onClick={() => {
                      config.update(
                        (config) => (config.tightBorder = !config.tightBorder),
                      );
                    }}
                  />
                </div>
              )}
              {isMobileScreen && <SDIcon width={50} height={50} />}
            </div>
          </div>
          <div className={chatStyles["chat-body"]} ref={scrollRef}>
            {loading ? (
              <div className={clsx("no-dark", styles["loading-content"])}>
                <BotIcon />
                <LoadingIcon />
              </div>
            ) : (
              htmlCode &&
              (isEdit ? (
                <ReactQuill
                  ref={quillRef}
                  theme="snow"
                  value={htmlCode}
                  onChange={setHtmlCode}
                  modules={{
                    toolbar: toolbarOptions,
                  }}
                />
              ) : (
                <HTMLPreview
                  code={htmlCode}
                  autoHeight={!document.fullscreenElement}
                  height={!document.fullscreenElement ? 600 : height}
                />
              ))
            )}
          </div>
        </div>
      </WindowContent>
    </>
  );
}