writie-panel.tsx 8.3 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";

// 定义mergedData数据结构
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" },
    ],
  },
];

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;
  // 为每个选择框单独声明状态,存储name
  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 handleSelectChange = (index: number, value: string) => {
    const options = mergedData[index].options;
    const selectedName = options.find((opt) => opt.value === value)?.name || "";
    const selectedValue =
      options.find((opt) => opt.value === value)?.value || "";
    switch (index) {
      case 0:
        setWritingPurposeName(selectedName);
        setWidth(selectedValue);
        break;
      case 1:
        setImageModeName(selectedName);
        break;
      case 2:
        setWritingStyleName(selectedName);
        break;
      case 3:
        setWritingLanguageName(selectedName);
        break;
      case 4:
        setWritingTypeName(selectedName);
        break;
      case 5:
        setIsImgName(selectedName);
        break;
      default:
        break;
    }
  };

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

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

  // 提交表单时的处理函数
  const handleSubmit = async () => {
    if (!prompt.trim()) {
      return message.error("请输入提示词");
    }
    try {
      const param: writePromptParam = {
        writingPurposeName,
        writingStyleName,
        writingLanguageName,
        prompt,
        writingTypeName,
        isImgName,
        writingCount,
      };
      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>
      {/* 动态渲染选择框 */}
      {mergedData.map((item, index) => {
        let currentValue = "";
        switch (index) {
          case 0:
            currentValue = writingPurposeName;
            break;
          case 1:
            currentValue = imageModeName;
            break;
          case 2:
            currentValue = writingStyleName;
            break;
          case 3:
            currentValue = writingLanguageName;
            break;
          case 4:
            currentValue = writingTypeName;
            break;
          case 5:
            currentValue = isImgName;
            break;
          default:
            currentValue = "";
            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>

      {/* 提示词文本区域 */}
      <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>
  );
}