writie-panel.tsx 10.8 KB
import { Select } from "@/app/components/ui-lib";
import { ControlParamItem } from "../sd";
import { SideBarTail } from "@/app/components/sidebar";
import { useState } from "react";
import { IconButton } from "../button";
import { message } from "antd";
import { useChatStore } from "@/app/store";
import { getWrtingPrompt } from "@/app/utils/prompt";
import type { writePromptParam } from "@/app/types/prompt";
import { processChatFile } from "@/app/utils/fileUtil";
import Locale from "@/app/locales";
import styles from "./wrtie-panel.module.scss";
import DeleteIcon from "@/app/icons/delete.svg";

export const mergedData = [
  {
    title: "写作用途",
    required: true,
    type: "select",
    default: "公司官网",
    options: [
      { name: "公司官网", value: "100%" },
      { name: "小红书", value: "400px" },
      { name: "微信公众号", value: "300px" },
      { name: "今日头条", value: "500px" },
    ],
  },
  {
    title: "图片模式",
    type: "select",
    required: false,
    default: "免费配图",
    options: [
      { name: "免费配图", value: "free" },
      { name: "收费配图", value: "paid" },
    ],
  },
  {
    title: "写作风格",
    type: "select",
    required: false,
    default: "专业",
    options: [
      { name: "专业", value: "professional" },
      { name: "活泼", value: "lively" },
      { name: "严谨", value: "strict" },
    ],
  },
  {
    title: "写作语言",
    type: "select",
    required: false,
    default: "中文",
    options: [
      { name: "中文", value: "Chinese" },
      { name: "英文", value: "English" },
      { name: "法文", value: "French" },
      { name: "德文", value: "German" },
    ],
  },
  {
    title: "写作类型",
    type: "select",
    required: false,
    default: "产品推广文案",
    options: [
      { name: "产品推广文案", value: "promotion" },
      { name: "品牌宣传文案", value: "propagandize" },
      { name: "产品说明书", value: "instructionBook" },
      { name: "产品介绍", value: "introduce" },
    ],
  },
  {
    title: "是否图文",
    type: "select",
    required: false,
    default: "是",
    options: [
      { name: "是", value: "true" },
      { name: "否", value: "false" },
    ],
  },
];

// 20250408新增写作风格选项映射
const getWritingStyleOptions = (purpose: string) => {
  switch (purpose) {
    case "公司官网":
      return [
        { name: "专业", value: "professional" },
        { name: "活泼", value: "lively" },
        { name: "严谨", value: "strict" },
      ];
    case "小红书":
      return [
        { name: "俏皮", value: "playful" },
        { name: "幽默", value: "humorous" },
      ];
    case "微信公众号":
      return [
        { name: "夸张", value: "exaggerated" },
        { name: "可爱", value: "cute" },
      ];
    case "今日头条":
      return [
        { name: "丰满", value: "full" },
        { name: "可爱", value: "cute" },
        { name: "健康", value: "healthy" },
      ];
    default:
      return [
        { name: "专业", value: "professional" },
        { name: "活泼", value: "lively" },
        { name: "严谨", value: "strict" },
      ];
  }
};

export interface WritePanelProps {
  htmlCode: string;
  setHtmlCode: React.Dispatch<React.SetStateAction<string>>;
  loading: boolean;
  setLoading: React.Dispatch<React.SetStateAction<boolean>>;
  setWidth: React.Dispatch<React.SetStateAction<string>>;
  setHtmlheader: React.Dispatch<React.SetStateAction<string>>;
}
export function WritingPanel(props: WritePanelProps) {
  const {
    htmlCode,
    setHtmlCode,
    setLoading,
    loading,
    setWidth,
    setHtmlheader,
  } = props;
  const chatStore = useChatStore();
  const [writingPurposeName, setWritingPurposeName] = useState("公司官网"); // 写作用途
  const [imageModeName, setImageModeName] = useState("免费配图"); // 图片模式
  const [writingStyleName, setWritingStyleName] = useState("专业"); // 写作风格
  const [writingLanguageName, setWritingLanguageName] = useState("中文"); // 写作语言
  const [writingTypeName, setWritingTypeName] = useState("产品推广文案"); // 写作类型
  const [isImgName, setIsImgName] = useState("是"); // 是否图文
  // 为输入框和文本区域单独声明状态
  const [writingCount, setWritingCount] = useState("200"); // 写作字数
  const [prompt, setPrompt] = useState(""); // 提示词
  const [isLoading, setIsLoading] = useState(false);

  const [fileData, setFileData] = useState("");
  const [fileName, setFileName] = useState("");

  // 生成动态数据
  const dynamicMergedData = mergedData.map((item) => {
    if (item.title === "写作风格") {
      return {
        ...item,
        options: getWritingStyleOptions(writingPurposeName),
      };
    }
    return item;
  });

  // 处理选择框变更事件
  const handleSelectChange = (index: number, value: string) => {
    const item = dynamicMergedData[index];
    const selectedOption = item.options.find((opt) => opt.value === value);
    const selectedName = selectedOption?.name || "";

    switch (item.title) {
      case "写作用途":
        setWritingPurposeName(selectedName);
        setWidth(value);
        // 自动更新写作风格为对应选项的第一个
        const newStyles = getWritingStyleOptions(selectedName);
        if (newStyles.length > 0) {
          setWritingStyleName(newStyles[0].name);
        }
        break;
      case "图片模式":
        setImageModeName(selectedName);
        break;
      case "写作风格":
        setWritingStyleName(selectedName);
        break;
      case "写作语言":
        setWritingLanguageName(selectedName);
        break;
      case "写作类型":
        setWritingTypeName(selectedName);
        break;
      case "是否图文":
        setIsImgName(selectedName);
        break;
    }
  };

  // 处理输入框变更事件
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setWritingCount(e.target.value);
  };

  // 处理文本区域变更事件
  const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    setPrompt(e.target.value);
  };

  //上传文件
  const uploadFile = async () => {
    try {
      const { data, name } = await processChatFile();
      console.log(data);

      setFileData(data);
      console.log(fileData);

      setFileName(name);
    } catch {
      message.error(Locale.ComError.UploadErr);
    }
  };

  // 提交表单时的处理函数
  const handleSubmit = async () => {
    if (!prompt.trim()) {
      return message.error("请输入提示词");
    }
    try {
      const param: writePromptParam = {
        writingPurposeName,
        writingStyleName,
        writingLanguageName,
        prompt,
        writingTypeName,
        isImgName,
        writingCount,
        fileData,
      };
      const input = getWrtingPrompt(param);
      setLoading(true);
      console.log("------------------------" + input);
      const response = await chatStore.directLlmInvoke(input, "gpt-4o-mini");
      let cleanedContent = response.startsWith("```html")
        ? response.substring(8)
        : response;
      if (cleanedContent.endsWith("```")) {
        cleanedContent = cleanedContent.substring(0, cleanedContent.length - 4);
      }
      //保存html头部
      const bodyTagRegex = /<body[^>]*>/i;
      const bodyTagMatch = cleanedContent.match(bodyTagRegex);
      if (bodyTagMatch && bodyTagMatch.index !== undefined) {
        // 截取从文档开头到 <body> 标签的起始位置
        const contentUpToBody = cleanedContent.slice(
          0,
          bodyTagMatch.index + bodyTagMatch[0].length,
        );
        setHtmlheader(contentUpToBody); //保存html头部
      }
      localStorage.setItem("htmlCode", cleanedContent);
      setHtmlCode(cleanedContent);
    } catch (error) {
      message.error("生成失败,请重试");
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      {/* 动态渲染选择框 */}
      {dynamicMergedData.map((item, index) => {
        let currentValue = "";
        switch (item.title) {
          case "写作用途":
            currentValue = writingPurposeName;
            break;
          case "图片模式":
            currentValue = imageModeName;
            break;
          case "写作风格":
            currentValue = writingStyleName;
            break;
          case "写作语言":
            currentValue = writingLanguageName;
            break;
          case "写作类型":
            currentValue = writingTypeName;
            break;
          case "是否图文":
            currentValue = isImgName;
            break;
        }

        return (
          <ControlParamItem
            key={item.title}
            title={item.title}
            required={item.required}
          >
            <Select
              aria-label={item.title}
              value={
                item.options.find((opt) => opt.name === currentValue)?.value ||
                ""
              }
              onChange={(e) => handleSelectChange(index, e.target.value)}
            >
              {item.options.map((opt) => (
                <option key={opt.value} value={opt.value}>
                  {opt.name}
                </option>
              ))}
            </Select>
          </ControlParamItem>
        );
      })}

      {/* 写作字数输入框 */}
      <ControlParamItem title="写作字数" required={true}>
        <input
          aria-label="写作字数"
          type="number"
          placeholder="200"
          min="200"
          max="5000"
          value={writingCount}
          onChange={handleInputChange}
        />
      </ControlParamItem>

      <IconButton
        text="上传文件"
        type="primary"
        shadow
        onClick={uploadFile}
        disabled={loading}
      ></IconButton>
      {fileName && fileData && (
        <div className={styles["attach-file"]}>
          <span className={styles["file-name"]}>{fileName}</span>
          <div
            className={styles["delete-file"]}
            onClick={() => {
              setFileData("");
              setFileName("");
            }}
          >
            <DeleteIcon />
          </div>
        </div>
      )}

      {/* 提示词文本区域 */}
      <ControlParamItem title="提示词" required={true}>
        <textarea
          rows={3}
          style={{ maxWidth: "100%", width: "100%", padding: "10px" }}
          placeholder="请输入"
          value={prompt}
          onChange={handleTextareaChange}
        ></textarea>
      </ControlParamItem>

      {/* 提交按钮 */}
      <SideBarTail
        secondaryAction={
          <IconButton
            text="提交生成"
            type="primary"
            shadow
            onClick={handleSubmit}
            disabled={loading}
          ></IconButton>
        }
      />
    </div>
  );
}