作者 202304001

1、对话界面增加:生成文章

2、增加深度思考中
3、导出过滤思考内容
4、增加 风格 是 用途的 二级联动
@@ -55,6 +55,7 @@ import WordIcon from "../icons/word.svg"; @@ -55,6 +55,7 @@ import WordIcon from "../icons/word.svg";
55 import MindIcon from "../icons/mind.svg"; 55 import MindIcon from "../icons/mind.svg";
56 import PptIcon from "../icons/ppt.svg"; 56 import PptIcon from "../icons/ppt.svg";
57 import FileIcon from "../icons/file.svg"; 57 import FileIcon from "../icons/file.svg";
  58 +import WriteIcon from "../icons/write.svg";
58 59
59 import { 60 import {
60 BOT_HELLO, 61 BOT_HELLO,
@@ -139,8 +140,9 @@ import { getAvailableClientsCount, isMcpEnabled } from "../mcp/actions"; @@ -139,8 +140,9 @@ import { getAvailableClientsCount, isMcpEnabled } from "../mcp/actions";
139 import { getExcelData, toExcel } from "../utils/fileExport/export2Excel"; 140 import { getExcelData, toExcel } from "../utils/fileExport/export2Excel";
140 import { exportWord, getWordData } from "../utils/fileExport/word"; 141 import { exportWord, getWordData } from "../utils/fileExport/word";
141 import { getMindPrompt } from "../utils/prompt"; 142 import { getMindPrompt } from "../utils/prompt";
142 -import { message } from "antd"; 143 +import { message as msgModal } from "antd";
143 import { getPdfData } from "../utils/fileExport/toPdf"; 144 import { getPdfData } from "../utils/fileExport/toPdf";
  145 +import { removeDeepThink } from "../utils/deepThink";
144 const localStorage = safeLocalStorage(); 146 const localStorage = safeLocalStorage();
145 147
146 const ttsPlayer = createTTSPlayer(); 148 const ttsPlayer = createTTSPlayer();
@@ -1757,7 +1759,7 @@ function _Chat() { @@ -1757,7 +1759,7 @@ function _Chat() {
1757 if (message.content === content) { 1759 if (message.content === content) {
1758 newMessages.push({ 1760 newMessages.push({
1759 role: "user", 1761 role: "user",
1760 - content: getMindPrompt(content, true), 1762 + content: getMindPrompt(removeDeepThink(content), true),
1761 }); 1763 });
1762 break; 1764 break;
1763 } 1765 }
@@ -1768,7 +1770,15 @@ function _Chat() { @@ -1768,7 +1770,15 @@ function _Chat() {
1768 1770
1769 //20250328新增PPT导出 1771 //20250328新增PPT导出
1770 function toPowerpoint(pptMessage: string) { 1772 function toPowerpoint(pptMessage: string) {
1771 - navigate("/powerpoint", { state: { msg: true, pptMessage: pptMessage } }); 1773 + navigate(Path.Powerpoint, {
  1774 + state: { msg: true, pptMessage: removeDeepThink(pptMessage) },
  1775 + });
  1776 + }
  1777 +
  1778 + function toWrite(message: string) {
  1779 + navigate(Path.Writing, {
  1780 + state: { msg: true, writeMessage: removeDeepThink(message) },
  1781 + });
1772 } 1782 }
1773 //20250402新增上传文件 1783 //20250402新增上传文件
1774 const [fileData, setFileData] = useState(""); 1784 const [fileData, setFileData] = useState("");
@@ -1782,7 +1792,7 @@ function _Chat() { @@ -1782,7 +1792,7 @@ function _Chat() {
1782 fileInput.style.display = "none"; 1792 fileInput.style.display = "none";
1783 const handleFileResult = (fileName: string, data: any) => { 1793 const handleFileResult = (fileName: string, data: any) => {
1784 if (!data) { 1794 if (!data) {
1785 - message.error("未读取到内容"); 1795 + msgModal.error(Locale.ComError.Notread);
1786 return; 1796 return;
1787 } 1797 }
1788 setFileData(`'''filedata 1798 setFileData(`'''filedata
@@ -1791,9 +1801,12 @@ function _Chat() { @@ -1791,9 +1801,12 @@ function _Chat() {
1791 '''filedata`); 1801 '''filedata`);
1792 setFileName(fileName); 1802 setFileName(fileName);
1793 }; 1803 };
1794 - const handleError = (error: any, defaultMsg = "上传失败") => { 1804 + const handleError = (
  1805 + error: any,
  1806 + defaultMsg = Locale.ComError.UploadErr,
  1807 + ) => {
1795 console.error(`${defaultMsg}:`, error); 1808 console.error(`${defaultMsg}:`, error);
1796 - message.error(defaultMsg); 1809 + msgModal.error(defaultMsg);
1797 }; 1810 };
1798 // 文件处理器映射 1811 // 文件处理器映射
1799 const fileHandlers: Record<string, (file: File) => Promise<any>> = { 1812 const fileHandlers: Record<string, (file: File) => Promise<any>> = {
@@ -1812,7 +1825,7 @@ function _Chat() { @@ -1812,7 +1825,7 @@ function _Chat() {
1812 .slice(file.name.lastIndexOf(".")); 1825 .slice(file.name.lastIndexOf("."));
1813 // 校验文件类型 1826 // 校验文件类型
1814 if (!Object.keys(fileHandlers).includes(fileExt)) { 1827 if (!Object.keys(fileHandlers).includes(fileExt)) {
1815 - message.error("不支持的文件类型"); 1828 + msgModal.error(Locale.ComError.UnsupportedFile);
1816 return; 1829 return;
1817 } 1830 }
1818 // 获取处理器并执行 1831 // 获取处理器并执行
@@ -2077,11 +2090,17 @@ function _Chat() { @@ -2077,11 +2090,17 @@ function _Chat() {
2077 <ChatAction 2090 <ChatAction
2078 text={Locale.Export.Excel} 2091 text={Locale.Export.Excel}
2079 icon={<ExcelIcon />} 2092 icon={<ExcelIcon />}
2080 - onClick={() =>  
2081 - toExcel(  
2082 - getMessageTextContent(message),  
2083 - )  
2084 - } 2093 + onClick={() => {
  2094 + try {
  2095 + toExcel(
  2096 + getMessageTextContent(message),
  2097 + );
  2098 + } catch (error) {
  2099 + msgModal.error(
  2100 + Locale.ComError.ExcelErr,
  2101 + );
  2102 + }
  2103 + }}
2085 /> 2104 />
2086 {!isTableContent( 2105 {!isTableContent(
2087 getMessageTextContent(message), 2106 getMessageTextContent(message),
@@ -2117,6 +2136,15 @@ function _Chat() { @@ -2117,6 +2136,15 @@ function _Chat() {
2117 ) 2136 )
2118 } 2137 }
2119 /> 2138 />
  2139 + <ChatAction
  2140 + text={Locale.Chat.Actions.Write}
  2141 + icon={<WriteIcon />}
  2142 + onClick={() =>
  2143 + toWrite(
  2144 + getMessageTextContent(message),
  2145 + )
  2146 + }
  2147 + />
2120 {/* 以上 20250317 新增Word excel导出按钮 */} 2148 {/* 以上 20250317 新增Word excel导出按钮 */}
2121 {config.ttsConfig.enable && ( 2149 {config.ttsConfig.enable && (
2122 <ChatAction 2150 <ChatAction
@@ -2189,6 +2217,7 @@ function _Chat() { @@ -2189,6 +2217,7 @@ function _Chat() {
2189 fontFamily={fontFamily} 2217 fontFamily={fontFamily}
2190 parentRef={scrollRef} 2218 parentRef={scrollRef}
2191 defaultShow={i >= messages.length - 6} 2219 defaultShow={i >= messages.length - 6}
  2220 + isUser={isUser}
2192 /> 2221 />
2193 {getMessageImages(message).length == 1 && ( 2222 {getMessageImages(message).length == 1 && (
2194 <img 2223 <img
@@ -28,6 +28,10 @@ import clsx from "clsx"; @@ -28,6 +28,10 @@ import clsx from "clsx";
28 import { ChartComponentProps, extractDataFromText } from "./chart/getChartData"; 28 import { ChartComponentProps, extractDataFromText } from "./chart/getChartData";
29 import { ChartComponent } from "./chart"; 29 import { ChartComponent } from "./chart";
30 import styles from "./chat.module.scss"; 30 import styles from "./chat.module.scss";
  31 +import { Collapse } from "antd";
  32 +import { useDebounce } from "../utils/hooks";
  33 +import { createDeepThink } from "../utils/deepThink";
  34 +
31 export function Mermaid(props: { code: string }) { 35 export function Mermaid(props: { code: string }) {
32 const ref = useRef<HTMLDivElement>(null); 36 const ref = useRef<HTMLDivElement>(null);
33 const [hasError, setHasError] = useState(false); 37 const [hasError, setHasError] = useState(false);
@@ -276,15 +280,8 @@ function tryWrapHtmlCode(text: string) { @@ -276,15 +280,8 @@ function tryWrapHtmlCode(text: string) {
276 }, 280 },
277 ); 281 );
278 } 282 }
279 -// 首先定义完善的类型  
280 -type FileMatch = {  
281 - fullMatch: string;  
282 - fileName: string;  
283 - start: number;  
284 - end: number;  
285 -};  
286 -  
287 -function _MarkDownContent(props: { content: string }) { 283 +
  284 +function _MarkDownContent(props: { content: string; isUser: boolean }) {
288 const escapedContent = useMemo(() => { 285 const escapedContent = useMemo(() => {
289 return tryWrapHtmlCode(escapeBrackets(props.content)); 286 return tryWrapHtmlCode(escapeBrackets(props.content));
290 }, [props.content]); 287 }, [props.content]);
@@ -303,7 +300,6 @@ function _MarkDownContent(props: { content: string }) { @@ -303,7 +300,6 @@ function _MarkDownContent(props: { content: string }) {
303 300
304 type Segment = TextSegment | ElementSegment; 301 type Segment = TextSegment | ElementSegment;
305 302
306 - // 将子节点转换为可分析的段落段  
307 function parseChildrenToSegments(children: React.ReactNode): Segment[] { 303 function parseChildrenToSegments(children: React.ReactNode): Segment[] {
308 const segments: Segment[] = []; 304 const segments: Segment[] = [];
309 let textBuffer = ""; 305 let textBuffer = "";
@@ -346,7 +342,6 @@ function _MarkDownContent(props: { content: string }) { @@ -346,7 +342,6 @@ function _MarkDownContent(props: { content: string }) {
346 return segments; 342 return segments;
347 } 343 }
348 344
349 - // 主处理函数  
350 function CustomParagraph({ children }: { children: React.ReactNode }) { 345 function CustomParagraph({ children }: { children: React.ReactNode }) {
351 const segments = parseChildrenToSegments(children); 346 const segments = parseChildrenToSegments(children);
352 const fullText = segments 347 const fullText = segments
@@ -408,6 +403,54 @@ function _MarkDownContent(props: { content: string }) { @@ -408,6 +403,54 @@ function _MarkDownContent(props: { content: string }) {
408 return <p dir="auto">{newChildren}</p>; 403 return <p dir="auto">{newChildren}</p>;
409 } 404 }
410 405
  406 + const CollapsibleBlockquote = ({
  407 + children,
  408 + }: {
  409 + children: React.ReactNode;
  410 + }) => {
  411 + const [isFinalized, setIsFinalized] = useState(false);
  412 + const [lastUpdated, setLastUpdated] = useState(Date.now());
  413 + const contentString = React.Children.toArray(children).join("");
  414 +
  415 + // 使用防抖检测内容是否稳定
  416 + const debouncedCheck = useDebounce(() => {
  417 + if (Date.now() - lastUpdated > 2000) {
  418 + // 2秒内无更新视为最终
  419 + setIsFinalized(true);
  420 + }
  421 + }, 2000);
  422 + useEffect(() => {
  423 + setIsFinalized(false);
  424 + setLastUpdated(Date.now());
  425 + debouncedCheck();
  426 + }, [contentString]);
  427 +
  428 + return (
  429 + <Collapse
  430 + defaultActiveKey={["1"]}
  431 + size="small"
  432 + items={[
  433 + {
  434 + key: "1",
  435 + label: isFinalized ? "已深度思考" : "思考中...",
  436 + children: (
  437 + <blockquote
  438 + style={{
  439 + padding: "10px",
  440 + borderLeft: "2px solid #ccc",
  441 + transition: "all 0.3s ease",
  442 + fontSize: "12px",
  443 + }}
  444 + >
  445 + {children}
  446 + </blockquote>
  447 + ),
  448 + },
  449 + ]}
  450 + />
  451 + );
  452 + };
  453 +
411 return ( 454 return (
412 <ReactMarkdown 455 <ReactMarkdown
413 remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]} 456 remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
@@ -424,7 +467,15 @@ function _MarkDownContent(props: { content: string }) { @@ -424,7 +467,15 @@ function _MarkDownContent(props: { content: string }) {
424 components={{ 467 components={{
425 pre: PreCode, 468 pre: PreCode,
426 code: CustomCode, 469 code: CustomCode,
427 - p: CustomParagraph, 470 + p: props.isUser
  471 + ? CustomParagraph
  472 + : (pProps) => <p {...pProps} dir="auto" />,
  473 + blockquote: ({ children }) =>
  474 + props.isUser ? (
  475 + <blockquote>{children}</blockquote>
  476 + ) : (
  477 + <CollapsibleBlockquote>{children}</CollapsibleBlockquote>
  478 + ),
428 a: (aProps) => { 479 a: (aProps) => {
429 const href = aProps.href || ""; 480 const href = aProps.href || "";
430 if (/\.(aac|mp3|opus|wav)$/.test(href)) { 481 if (/\.(aac|mp3|opus|wav)$/.test(href)) {
@@ -462,6 +513,7 @@ export function Markdown( @@ -462,6 +513,7 @@ export function Markdown(
462 fontFamily?: string; 513 fontFamily?: string;
463 parentRef?: RefObject<HTMLDivElement>; 514 parentRef?: RefObject<HTMLDivElement>;
464 defaultShow?: boolean; 515 defaultShow?: boolean;
  516 + isUser: boolean;
465 } & React.DOMAttributes<HTMLDivElement>, 517 } & React.DOMAttributes<HTMLDivElement>,
466 ) { 518 ) {
467 const mdRef = useRef<HTMLDivElement>(null); 519 const mdRef = useRef<HTMLDivElement>(null);
@@ -481,7 +533,10 @@ export function Markdown( @@ -481,7 +533,10 @@ export function Markdown(
481 {props.loading ? ( 533 {props.loading ? (
482 <LoadingIcon /> 534 <LoadingIcon />
483 ) : ( 535 ) : (
484 - <MarkdownContent content={props.content} /> 536 + <MarkdownContent
  537 + content={createDeepThink(props.content)}
  538 + isUser={props.isUser}
  539 + />
485 )} 540 )}
486 </div> 541 </div>
487 ); 542 );
@@ -8,8 +8,7 @@ import { useChatStore } from "@/app/store"; @@ -8,8 +8,7 @@ import { useChatStore } from "@/app/store";
8 import { getWrtingPrompt } from "@/app/utils/prompt"; 8 import { getWrtingPrompt } from "@/app/utils/prompt";
9 import type { writePromptParam } from "@/app/types/prompt"; 9 import type { writePromptParam } from "@/app/types/prompt";
10 10
11 -// 定义mergedData数据结构  
12 -const mergedData = [ 11 +export const mergedData = [
13 { 12 {
14 title: "写作用途", 13 title: "写作用途",
15 required: true, 14 required: true,
@@ -79,6 +78,40 @@ const mergedData = [ @@ -79,6 +78,40 @@ const mergedData = [
79 }, 78 },
80 ]; 79 ];
81 80
  81 +// 20250408新增写作风格选项映射
  82 +const getWritingStyleOptions = (purpose: string) => {
  83 + switch (purpose) {
  84 + case "公司官网":
  85 + return [
  86 + { name: "专业", value: "professional" },
  87 + { name: "活泼", value: "lively" },
  88 + { name: "严谨", value: "strict" },
  89 + ];
  90 + case "小红书":
  91 + return [
  92 + { name: "俏皮", value: "playful" },
  93 + { name: "幽默", value: "humorous" },
  94 + ];
  95 + case "微信公众号":
  96 + return [
  97 + { name: "夸张", value: "exaggerated" },
  98 + { name: "可爱", value: "cute" },
  99 + ];
  100 + case "今日头条":
  101 + return [
  102 + { name: "丰满", value: "full" },
  103 + { name: "可爱", value: "cute" },
  104 + { name: "健康", value: "healthy" },
  105 + ];
  106 + default:
  107 + return [
  108 + { name: "专业", value: "professional" },
  109 + { name: "活泼", value: "lively" },
  110 + { name: "严谨", value: "strict" },
  111 + ];
  112 + }
  113 +};
  114 +
82 export interface WritePanelProps { 115 export interface WritePanelProps {
83 htmlCode: string; 116 htmlCode: string;
84 setHtmlCode: React.Dispatch<React.SetStateAction<string>>; 117 setHtmlCode: React.Dispatch<React.SetStateAction<string>>;
@@ -96,7 +129,6 @@ export function WritingPanel(props: WritePanelProps) { @@ -96,7 +129,6 @@ export function WritingPanel(props: WritePanelProps) {
96 setWidth, 129 setWidth,
97 setHtmlheader, 130 setHtmlheader,
98 } = props; 131 } = props;
99 - // 为每个选择框单独声明状态,存储name  
100 const chatStore = useChatStore(); 132 const chatStore = useChatStore();
101 const [writingPurposeName, setWritingPurposeName] = useState("公司官网"); // 写作用途 133 const [writingPurposeName, setWritingPurposeName] = useState("公司官网"); // 写作用途
102 const [imageModeName, setImageModeName] = useState("免费配图"); // 图片模式 134 const [imageModeName, setImageModeName] = useState("免费配图"); // 图片模式
@@ -108,34 +140,49 @@ export function WritingPanel(props: WritePanelProps) { @@ -108,34 +140,49 @@ export function WritingPanel(props: WritePanelProps) {
108 const [writingCount, setWritingCount] = useState("200"); // 写作字数 140 const [writingCount, setWritingCount] = useState("200"); // 写作字数
109 const [prompt, setPrompt] = useState(""); // 提示词 141 const [prompt, setPrompt] = useState(""); // 提示词
110 const [isLoading, setIsLoading] = useState(false); 142 const [isLoading, setIsLoading] = useState(false);
  143 +
  144 + // 生成动态数据
  145 + const dynamicMergedData = mergedData.map((item) => {
  146 + if (item.title === "写作风格") {
  147 + return {
  148 + ...item,
  149 + options: getWritingStyleOptions(writingPurposeName),
  150 + };
  151 + }
  152 + return item;
  153 + });
  154 +
111 // 处理选择框变更事件 155 // 处理选择框变更事件
112 const handleSelectChange = (index: number, value: string) => { 156 const handleSelectChange = (index: number, value: string) => {
113 - const options = mergedData[index].options;  
114 - const selectedName = options.find((opt) => opt.value === value)?.name || "";  
115 - const selectedValue =  
116 - options.find((opt) => opt.value === value)?.value || "";  
117 - switch (index) {  
118 - case 0: 157 + const item = dynamicMergedData[index];
  158 + const selectedOption = item.options.find((opt) => opt.value === value);
  159 + const selectedName = selectedOption?.name || "";
  160 +
  161 + switch (item.title) {
  162 + case "写作用途":
119 setWritingPurposeName(selectedName); 163 setWritingPurposeName(selectedName);
120 - setWidth(selectedValue); 164 + setWidth(value);
  165 + // 自动更新写作风格为对应选项的第一个
  166 + const newStyles = getWritingStyleOptions(selectedName);
  167 + if (newStyles.length > 0) {
  168 + setWritingStyleName(newStyles[0].name);
  169 + }
121 break; 170 break;
122 - case 1: 171 + case "图片模式":
123 setImageModeName(selectedName); 172 setImageModeName(selectedName);
124 break; 173 break;
125 - case 2: 174 + case "写作风格":
126 setWritingStyleName(selectedName); 175 setWritingStyleName(selectedName);
127 break; 176 break;
128 - case 3: 177 + case "写作语言":
129 setWritingLanguageName(selectedName); 178 setWritingLanguageName(selectedName);
130 break; 179 break;
131 - case 4: 180 + case "写作类型":
132 setWritingTypeName(selectedName); 181 setWritingTypeName(selectedName);
133 break; 182 break;
134 - case 5: 183 + case "是否图文":
135 setIsImgName(selectedName); 184 setIsImgName(selectedName);
136 break; 185 break;
137 - default:  
138 - break;  
139 } 186 }
140 }; 187 };
141 188
@@ -174,7 +221,6 @@ export function WritingPanel(props: WritePanelProps) { @@ -174,7 +221,6 @@ export function WritingPanel(props: WritePanelProps) {
174 if (cleanedContent.endsWith("```")) { 221 if (cleanedContent.endsWith("```")) {
175 cleanedContent = cleanedContent.substring(0, cleanedContent.length - 4); 222 cleanedContent = cleanedContent.substring(0, cleanedContent.length - 4);
176 } 223 }
177 -  
178 //保存html头部 224 //保存html头部
179 const bodyTagRegex = /<body[^>]*>/i; 225 const bodyTagRegex = /<body[^>]*>/i;
180 const bodyTagMatch = cleanedContent.match(bodyTagRegex); 226 const bodyTagMatch = cleanedContent.match(bodyTagRegex);
@@ -198,30 +244,27 @@ export function WritingPanel(props: WritePanelProps) { @@ -198,30 +244,27 @@ export function WritingPanel(props: WritePanelProps) {
198 return ( 244 return (
199 <div> 245 <div>
200 {/* 动态渲染选择框 */} 246 {/* 动态渲染选择框 */}
201 - {mergedData.map((item, index) => { 247 + {dynamicMergedData.map((item, index) => {
202 let currentValue = ""; 248 let currentValue = "";
203 - switch (index) {  
204 - case 0: 249 + switch (item.title) {
  250 + case "写作用途":
205 currentValue = writingPurposeName; 251 currentValue = writingPurposeName;
206 break; 252 break;
207 - case 1: 253 + case "图片模式":
208 currentValue = imageModeName; 254 currentValue = imageModeName;
209 break; 255 break;
210 - case 2: 256 + case "写作风格":
211 currentValue = writingStyleName; 257 currentValue = writingStyleName;
212 break; 258 break;
213 - case 3: 259 + case "写作语言":
214 currentValue = writingLanguageName; 260 currentValue = writingLanguageName;
215 break; 261 break;
216 - case 4: 262 + case "写作类型":
217 currentValue = writingTypeName; 263 currentValue = writingTypeName;
218 break; 264 break;
219 - case 5: 265 + case "是否图文":
220 currentValue = isImgName; 266 currentValue = isImgName;
221 break; 267 break;
222 - default:  
223 - currentValue = "";  
224 - break;  
225 } 268 }
226 269
227 return ( 270 return (
@@ -9,10 +9,16 @@ import { useMobileScreen } from "@/app/utils"; @@ -9,10 +9,16 @@ import { useMobileScreen } from "@/app/utils";
9 import { IconButton } from "../button"; 9 import { IconButton } from "../button";
10 import Locale from "@/app/locales"; 10 import Locale from "@/app/locales";
11 import { Path } from "@/app/constant"; 11 import { Path } from "@/app/constant";
12 -import { useNavigate } from "react-router-dom"; 12 +import { useLocation, useNavigate } from "react-router-dom";
13 import { getClientConfig } from "@/app/config/client"; 13 import { getClientConfig } from "@/app/config/client";
14 -import React, { useCallback, useMemo, useRef, useState } from "react";  
15 -import { useAppConfig, useMindMapStore } from "@/app/store"; 14 +import React, {
  15 + useCallback,
  16 + useEffect,
  17 + useMemo,
  18 + useRef,
  19 + useState,
  20 +} from "react";
  21 +import { useAppConfig, useChatStore, useMindMapStore } from "@/app/store";
16 import { ChatAction } from "../chat"; 22 import { ChatAction } from "../chat";
17 import { useWindowSize } from "@/app/utils"; 23 import { useWindowSize } from "@/app/utils";
18 import { exportHtmlToWord } from "@/app/utils/fileExport/word"; 24 import { exportHtmlToWord } from "@/app/utils/fileExport/word";
@@ -37,11 +43,14 @@ import HtmlIcon from "@/app/icons/HTML.svg"; @@ -37,11 +43,14 @@ import HtmlIcon from "@/app/icons/HTML.svg";
37 43
38 import { message } from "antd"; 44 import { message } from "antd";
39 import { HTMLPreview } from "../artifacts"; 45 import { HTMLPreview } from "../artifacts";
40 -import { getMindPrompt } from "@/app/utils/prompt"; 46 +import { getMindPrompt, getWrtingPrompt } from "@/app/utils/prompt";
41 import { htmlToPdf2 } from "@/app/utils/fileExport/toPdf"; 47 import { htmlToPdf2 } from "@/app/utils/fileExport/toPdf";
42 -import { htmlToExcel } from "@/app/utils/fileExport/export2Excel"; 48 +import { hasTable, htmlToExcel } from "@/app/utils/fileExport/export2Excel";
  49 +import { writePromptParam } from "@/app/types/prompt";
  50 +import { mergedData } from "./writie-panel";
43 51
44 export function WritingPage() { 52 export function WritingPage() {
  53 + const chatStore = useChatStore();
45 const isMobileScreen = useMobileScreen(); 54 const isMobileScreen = useMobileScreen();
46 const navigate = useNavigate(); 55 const navigate = useNavigate();
47 const clientConfig = useMemo(() => getClientConfig(), []); 56 const clientConfig = useMemo(() => getClientConfig(), []);
@@ -58,6 +67,8 @@ export function WritingPage() { @@ -58,6 +67,8 @@ export function WritingPage() {
58 const [htmlCode, setHtmlCode] = useState( 67 const [htmlCode, setHtmlCode] = useState(
59 localStorage.getItem("htmlCode") || "", 68 localStorage.getItem("htmlCode") || "",
60 ); 69 );
  70 + const query = useLocation(); //获取路由参数
  71 + let { msg, writeMessage } = query.state || {}; //获取路由参数
61 72
62 //编辑器 73 //编辑器
63 const toolbarOptions = [ 74 const toolbarOptions = [
@@ -69,9 +80,66 @@ export function WritingPage() { @@ -69,9 +80,66 @@ export function WritingPage() {
69 ["link", "image"], 80 ["link", "image"],
70 ]; 81 ];
71 82
  83 + useEffect(() => {
  84 + if (!msg) {
  85 + return;
  86 + }
  87 + if (!writeMessage) {
  88 + return;
  89 + }
  90 + const navigateGetData = async () => {
  91 + try {
  92 + const param: writePromptParam = {
  93 + writingPurposeName: mergedData[0].default,
  94 + writingStyleName: mergedData[2].default,
  95 + writingLanguageName: mergedData[3].default,
  96 + prompt: writeMessage,
  97 + writingTypeName: mergedData[4].default,
  98 + isImgName: mergedData[5].default,
  99 + writingCount: "200",
  100 + };
  101 + const input = getWrtingPrompt(param);
  102 + setLoading(true);
  103 + console.log("------------------------" + input);
  104 + const response = await chatStore.directLlmInvoke(input, "gpt-4o-mini");
  105 + let cleanedContent = response.startsWith("```html")
  106 + ? response.substring(8)
  107 + : response;
  108 + if (cleanedContent.endsWith("```")) {
  109 + cleanedContent = cleanedContent.substring(
  110 + 0,
  111 + cleanedContent.length - 4,
  112 + );
  113 + }
  114 + //保存html头部
  115 + const bodyTagRegex = /<body[^>]*>/i;
  116 + const bodyTagMatch = cleanedContent.match(bodyTagRegex);
  117 + if (bodyTagMatch && bodyTagMatch.index !== undefined) {
  118 + // 截取从文档开头到 <body> 标签的起始位置
  119 + const contentUpToBody = cleanedContent.slice(
  120 + 0,
  121 + bodyTagMatch.index + bodyTagMatch[0].length,
  122 + );
  123 + setHtmlheader(contentUpToBody); //保存html头部
  124 + }
  125 + localStorage.setItem("htmlCode", cleanedContent);
  126 + setHtmlCode(cleanedContent);
  127 + } catch (error) {
  128 + message.error("生成失败,请重试");
  129 + } finally {
  130 + setLoading(false);
  131 + }
  132 + };
  133 + navigateGetData();
  134 + }, []);
  135 +
72 // 生成完整HTML内容 136 // 生成完整HTML内容
73 const generateFullHtml = useCallback( 137 const generateFullHtml = useCallback(
74 - () => `${htmlHeader}${htmlCode}</body></html>`, 138 + () => `${htmlHeader}
  139 + <div style="width:${width}">
  140 + ${htmlCode}
  141 + </div>
  142 + </body></html>`,
75 [htmlHeader, htmlCode], 143 [htmlHeader, htmlCode],
76 ); 144 );
77 145
@@ -87,7 +155,7 @@ export function WritingPage() { @@ -87,7 +155,7 @@ export function WritingPage() {
87 }; 155 };
88 //跳转到ppt页面 156 //跳转到ppt页面
89 function toPowerpoint(pptMessage: string) { 157 function toPowerpoint(pptMessage: string) {
90 - navigate("/powerpoint", { state: { msg: true, pptMessage: pptMessage } }); 158 + navigate(Path.Powerpoint, { state: { msg: true, pptMessage: pptMessage } });
91 } 159 }
92 // 转至思维导图页面 160 // 转至思维导图页面
93 const toMind = useCallback( 161 const toMind = useCallback(
@@ -102,7 +170,7 @@ export function WritingPage() { @@ -102,7 +170,7 @@ export function WritingPage() {
102 ], 170 ],
103 content, 171 content,
104 ); 172 );
105 - navigate("/mind", { state: { msg: true } }); 173 + navigate(Path.Mind, { state: { msg: true } });
106 }, 174 },
107 [navigate], 175 [navigate],
108 ); 176 );
@@ -124,12 +192,6 @@ export function WritingPage() { @@ -124,12 +192,6 @@ export function WritingPage() {
124 } 192 }
125 }, [generateFullHtml]); 193 }, [generateFullHtml]);
126 194
127 - function hasTable(htmlContent: string): boolean {  
128 - const parser = new DOMParser();  
129 - const doc = parser.parseFromString(htmlContent, "text/html");  
130 - return doc.querySelector("table") !== null;  
131 - }  
132 -  
133 return ( 195 return (
134 <> 196 <>
135 <WriteSiderBar 197 <WriteSiderBar
@@ -201,7 +263,8 @@ export function WritingPage() { @@ -201,7 +263,8 @@ export function WritingPage() {
201 icon={<PdfIcon />} 263 icon={<PdfIcon />}
202 onClick={async () => { 264 onClick={async () => {
203 setLoading(true); 265 setLoading(true);
204 - await htmlToPdf2(htmlCode); 266 + const html = `<div style="width:${width}">${htmlCode}</div>`;
  267 + await htmlToPdf2(html);
205 setLoading(false); 268 setLoading(false);
206 }} 269 }}
207 disabled={isEdit} 270 disabled={isEdit}
@@ -210,11 +273,7 @@ export function WritingPage() { @@ -210,11 +273,7 @@ export function WritingPage() {
210 text={Locale.Export.Word} 273 text={Locale.Export.Word}
211 icon={<WordIcon />} 274 icon={<WordIcon />}
212 onClick={() => { 275 onClick={() => {
213 - const html = `${htmlHeader}  
214 - ${htmlCode}  
215 - </body>  
216 - </html>  
217 - `; 276 + const html = generateFullHtml();
218 exportHtmlToWord(html); 277 exportHtmlToWord(html);
219 }} 278 }}
220 disabled={isEdit} 279 disabled={isEdit}
@@ -28,7 +28,7 @@ export const TENCENT_BASE_URL = "https://hunyuan.tencentcloudapi.com"; @@ -28,7 +28,7 @@ export const TENCENT_BASE_URL = "https://hunyuan.tencentcloudapi.com";
28 export const MOONSHOT_BASE_URL = "https://api.moonshot.cn"; 28 export const MOONSHOT_BASE_URL = "https://api.moonshot.cn";
29 export const IFLYTEK_BASE_URL = "https://spark-api-open.xf-yun.com"; 29 export const IFLYTEK_BASE_URL = "https://spark-api-open.xf-yun.com";
30 30
31 -export const DEEPSEEK_BASE_URL = "https://api.deepseek.com"; 31 +export const DEEPSEEK_BASE_URL = "https://openapi.baolinai.top";
32 32
33 export const XAI_BASE_URL = "https://api.x.ai"; 33 export const XAI_BASE_URL = "https://api.x.ai";
34 34
@@ -490,7 +490,7 @@ const openaiModels = [ @@ -490,7 +490,7 @@ const openaiModels = [
490 // as it is cheaper, more capable, multimodal, and just as fast. gpt-3.5-turbo is still available for use in the API. 490 // as it is cheaper, more capable, multimodal, and just as fast. gpt-3.5-turbo is still available for use in the API.
491 "gpt-3.5-turbo", 491 "gpt-3.5-turbo",
492 "gpt-3.5-turbo-1106", 492 "gpt-3.5-turbo-1106",
493 - "gpt-3.5-turbo-0125", 493 + "deepseek-r1",
494 "gpt-4", 494 "gpt-4",
495 "gpt-4-0613", 495 "gpt-4-0613",
496 "gpt-4-32k", 496 "gpt-4-32k",
@@ -840,3 +840,4 @@ export const DEFAULT_GA_ID = "G-89WN60ZK2E"; @@ -840,3 +840,4 @@ export const DEFAULT_GA_ID = "G-89WN60ZK2E";
840 840
841 export const SAAS_CHAT_URL = "https://nextchat.club"; 841 export const SAAS_CHAT_URL = "https://nextchat.club";
842 export const SAAS_CHAT_UTM_URL = "https://nextchat.club?utm=github"; 842 export const SAAS_CHAT_UTM_URL = "https://nextchat.club?utm=github";
  843 +export const UPLOAD_FILE_MAX_LINE = 100;
@@ -62,6 +62,7 @@ const cn = { @@ -62,6 +62,7 @@ const cn = {
62 //20250317新增 62 //20250317新增
63 ReWrite: "重写", 63 ReWrite: "重写",
64 Chart: "查看图表", 64 Chart: "查看图表",
  65 + Write: "生成文章",
65 }, 66 },
66 Commands: { 67 Commands: {
67 new: "新建聊天", 68 new: "新建聊天",
@@ -886,6 +887,14 @@ const cn = { @@ -886,6 +887,14 @@ const cn = {
886 generateBg: "生成背景", 887 generateBg: "生成背景",
887 promptTitle: "背景提示词", 888 promptTitle: "背景提示词",
888 }, 889 },
  890 +
  891 + //20250408新增错误信息
  892 + ComError: {
  893 + ExcelErr: "未找到表格内容",
  894 + UploadErr: "上传失败",
  895 + UnsupportedFile: "不支持的文件类型",
  896 + Notread: "未读取到内容",
  897 + },
889 }; 898 };
890 899
891 type DeepPartial<T> = T extends object 900 type DeepPartial<T> = T extends object
  1 +export function createDeepThink(content: string) {
  2 + const lines = content.split("\n");
  3 + let deepThink: string[] = [];
  4 + let j = 0;
  5 + let isBreak = false;
  6 + for (let i = 0; i < lines.length; i++) {
  7 + if (lines[i] === "") {
  8 + lines.splice(i, 1); // 删除空行
  9 + i--;
  10 + continue;
  11 + }
  12 + if (
  13 + lines[i].startsWith(">") ||
  14 + lines[i].startsWith("2") ||
  15 + lines[i].startsWith("3") ||
  16 + lines[i].startsWith("4")
  17 + ) {
  18 + if (j !== 0 && lines[i].startsWith(">")) {
  19 + lines[i] = lines[i].substring(1);
  20 + }
  21 + deepThink.push(lines[i]);
  22 + lines.splice(i, 1);
  23 + j++;
  24 + i--;
  25 + } else {
  26 + break;
  27 + }
  28 + }
  29 + const deepThinkStr = deepThink.join("");
  30 + const linesStr = lines.join("\n");
  31 + const result = [deepThinkStr, "", linesStr].join("\n");
  32 + return result;
  33 +}
  34 +
  35 +export function removeDeepThink(content: string) {
  36 + const lines = content.split("\n");
  37 + let deepThink: string[] = [];
  38 + let j = 0;
  39 + let isBreak = false;
  40 + for (let i = 0; i < lines.length; i++) {
  41 + if (lines[i] === "") {
  42 + lines.splice(i, 1); // 删除空行
  43 + i--;
  44 + continue;
  45 + }
  46 + if (
  47 + lines[i].startsWith(">") ||
  48 + lines[i].startsWith("2") ||
  49 + lines[i].startsWith("3") ||
  50 + lines[i].startsWith("4")
  51 + ) {
  52 + if (j !== 0 && lines[i].startsWith(">")) {
  53 + lines[i] = lines[i].substring(1);
  54 + }
  55 + deepThink.push(lines[i]);
  56 + lines.splice(i, 1);
  57 + j++;
  58 + i--;
  59 + } else {
  60 + break;
  61 + }
  62 + }
  63 + return lines.join("\n");
  64 +}
1 /* eslint-disable */ 1 /* eslint-disable */
  2 +import { UPLOAD_FILE_MAX_LINE } from "@/app/constant";
2 import * as XLSX from "xlsx"; 3 import * as XLSX from "xlsx";
3 4
4 export function toExcel(content: string) { 5 export function toExcel(content: string) {
  6 + if (hasTable(content)) {
  7 + htmlToExcel(content);
  8 + } else {
  9 + markdownToExcel(content);
  10 + }
  11 +}
  12 +
  13 +export function hasTable(htmlContent: string): boolean {
  14 + const parser = new DOMParser();
  15 + const doc = parser.parseFromString(htmlContent, "text/html");
  16 + return doc.querySelector("table") !== null;
  17 +}
  18 +
  19 +export function markdownToExcel(content: string) {
5 let sheetName = "result"; // 默认表名 20 let sheetName = "result"; // 默认表名
6 let tableContent = ""; 21 let tableContent = "";
7 22
@@ -26,7 +41,7 @@ export function toExcel(content: string) { @@ -26,7 +41,7 @@ export function toExcel(content: string) {
26 } 41 }
27 42
28 if (tableStartIndex === -1) { 43 if (tableStartIndex === -1) {
29 - console.error("表格内容未找到"); 44 + throw new Error("表格内容未找到");
30 return; 45 return;
31 } 46 }
32 47
@@ -114,7 +129,7 @@ export function getExcelData(file: File): Promise<any[][]> { @@ -114,7 +129,7 @@ export function getExcelData(file: File): Promise<any[][]> {
114 const jsonData = XLSX.utils.sheet_to_json(worksheet, { 129 const jsonData = XLSX.utils.sheet_to_json(worksheet, {
115 header: 1, 130 header: 1,
116 }) as any[][]; 131 }) as any[][];
117 - resolve(jsonData.slice(0, 100)); 132 + resolve(jsonData.slice(0, UPLOAD_FILE_MAX_LINE));
118 } catch (error) { 133 } catch (error) {
119 reject(error); 134 reject(error);
120 } 135 }
  1 +import { UPLOAD_FILE_MAX_LINE } from "@/app/constant";
1 import { Document, Packer, Paragraph } from "docx"; 2 import { Document, Packer, Paragraph } from "docx";
2 import { saveAs } from "file-saver"; 3 import { saveAs } from "file-saver";
3 import * as mammoth from "mammoth"; 4 import * as mammoth from "mammoth";
4 5
  6 +export function removeDeepThink(content: string) {
  7 + const lines = content.split("\n");
  8 + let deepThink: string[] = [];
  9 + let j = 0;
  10 + let isBreak = false;
  11 + for (let i = 0; i < lines.length; i++) {
  12 + if (lines[i] === "") {
  13 + lines.splice(i, 1); // 删除空行
  14 + i--;
  15 + continue;
  16 + }
  17 + if (
  18 + lines[i].startsWith(">") ||
  19 + lines[i].startsWith("2") ||
  20 + lines[i].startsWith("3") ||
  21 + lines[i].startsWith("4")
  22 + ) {
  23 + if (j !== 0 && lines[i].startsWith(">")) {
  24 + lines[i] = lines[i].substring(1);
  25 + }
  26 + deepThink.push(lines[i]);
  27 + lines.splice(i, 1);
  28 + j++;
  29 + i--;
  30 + } else {
  31 + break;
  32 + }
  33 + }
  34 + return lines.join("\n");
  35 +}
  36 +
5 export function exportWord(content: string) { 37 export function exportWord(content: string) {
6 - console.log(content); 38 + content = removeDeepThink(content);
7 // 简单类型检测示例 39 // 简单类型检测示例
8 const isHTML = (text: string): boolean => { 40 const isHTML = (text: string): boolean => {
9 return ( 41 return (
@@ -83,7 +115,7 @@ export async function getWordData(file: File) { @@ -83,7 +115,7 @@ export async function getWordData(file: File) {
83 try { 115 try {
84 const arrayBuffer = await file.arrayBuffer(); 116 const arrayBuffer = await file.arrayBuffer();
85 const { value, messages } = await mammoth.extractRawText({ arrayBuffer }); 117 const { value, messages } = await mammoth.extractRawText({ arrayBuffer });
86 - return value; 118 + return value.slice(0, UPLOAD_FILE_MAX_LINE);
87 } catch (error) { 119 } catch (error) {
88 console.error("Error extracting Word content:", error); 120 console.error("Error extracting Word content:", error);
89 throw error; 121 throw error;
1 -import { useMemo } from "react"; 1 +import { useEffect, useMemo, useRef } from "react";
2 import { useAccessStore, useAppConfig } from "../store"; 2 import { useAccessStore, useAppConfig } from "../store";
3 import { collectModelsWithDefaultModel } from "./model"; 3 import { collectModelsWithDefaultModel } from "./model";
4 4
@@ -20,3 +20,24 @@ export function useAllModels() { @@ -20,3 +20,24 @@ export function useAllModels() {
20 20
21 return models; 21 return models;
22 } 22 }
  23 +
  24 +export function useDebounce(callback: () => void, delay: number) {
  25 + const timeoutRef = useRef<NodeJS.Timeout>();
  26 +
  27 + useEffect(() => {
  28 + return () => {
  29 + if (timeoutRef.current) {
  30 + clearTimeout(timeoutRef.current);
  31 + }
  32 + };
  33 + }, []);
  34 +
  35 + return () => {
  36 + if (timeoutRef.current) {
  37 + clearTimeout(timeoutRef.current);
  38 + }
  39 + timeoutRef.current = setTimeout(() => {
  40 + callback();
  41 + }, delay);
  42 + };
  43 +}
1 import type { writePromptParam } from "@/app/types/prompt"; 1 import type { writePromptParam } from "@/app/types/prompt";
2 2
  3 +//生成文章提示词
3 export function getWrtingPrompt(param: writePromptParam): string { 4 export function getWrtingPrompt(param: writePromptParam): string {
4 const { 5 const {
5 isImgName, 6 isImgName,
@@ -11,17 +12,23 @@ export function getWrtingPrompt(param: writePromptParam): string { @@ -11,17 +12,23 @@ export function getWrtingPrompt(param: writePromptParam): string {
11 writingCount, 12 writingCount,
12 } = param; 13 } = param;
13 14
  15 + // 根据用途调整文案类型描述
14 const purposeMap: Record<string, string> = { 16 const purposeMap: Record<string, string> = {
15 - 公司官网: "公司官网的介绍",  
16 - 小红书: "小红书的介绍",  
17 - 微信: "微信的介绍",  
18 - 公众号: "公众号的介绍",  
19 - 今日头条: "今日头条的介绍", 17 + 公司官网: "正式的公司官网文案",
  18 + 小红书: "符合小红书平台风格的文案",
  19 + 微信公众号: "适合微信公众号传播的文案",
  20 + 今日头条: "今日头条风格的热点文案",
20 }; 21 };
21 const styleMap: Record<string, string> = { 22 const styleMap: Record<string, string> = {
22 - 专业: "专业的风格",  
23 - 活泼: "活泼的风格",  
24 - 严谨: "严谨的风格", 23 + 专业: "专业严谨",
  24 + 活泼: "生动活泼",
  25 + 严谨: "严谨细致",
  26 + 俏皮: "俏皮可爱",
  27 + 幽默: "幽默风趣",
  28 + 夸张: "夸张吸睛",
  29 + 可爱: "可爱亲切",
  30 + 丰满: "内容丰满",
  31 + 健康: "健康积极",
25 }; 32 };
26 const typeMap: Record<string, string> = { 33 const typeMap: Record<string, string> = {
27 产品推广文案: "产品推广的文案", 34 产品推广文案: "产品推广的文案",
@@ -35,22 +42,29 @@ export function getWrtingPrompt(param: writePromptParam): string { @@ -35,22 +42,29 @@ export function getWrtingPrompt(param: writePromptParam): string {
35 const purpose = purposeMap[rawPurpose] || "公司官网的介绍"; 42 const purpose = purposeMap[rawPurpose] || "公司官网的介绍";
36 const style = styleMap[rawStyle] || "专业的风格"; 43 const style = styleMap[rawStyle] || "专业的风格";
37 44
38 - isImg = `文案要配上图片,实现图文混排,要美观,要符合${purpose}的排版标准和写作风格,写作风格要${style}, 45 + isImg = `要求图文混排,需符合以下要求:
  46 + 文案要配上图片,实现图文混排,要美观,要符合${purpose}的排版标准和写作风格,写作风格要${style},
39 你没有图片没关系,把图文混排的效果实现,并在你认为要插入图片的地方将图片的Prompt用英文输出给:![description](https://image.pollinations.ai/prompt/description?nologo=true),记得图片地址后面的?nologo=true一定不能去掉了, 47 你没有图片没关系,把图文混排的效果实现,并在你认为要插入图片的地方将图片的Prompt用英文输出给:![description](https://image.pollinations.ai/prompt/description?nologo=true),记得图片地址后面的?nologo=true一定不能去掉了,
40 因为这个语法可以自动按照提示生成并渲染图片。你可以帮我大幅提高生成图片质量和丰富程度,比如增加相机光圈、具体场景描述等内容,注意图片一定要用<img,否则在HTML下图片可能显示不了`; 48 因为这个语法可以自动按照提示生成并渲染图片。你可以帮我大幅提高生成图片质量和丰富程度,比如增加相机光圈、具体场景描述等内容,注意图片一定要用<img,否则在HTML下图片可能显示不了`;
41 } 49 }
42 const writingTypeName = typeMap[rawType] || "产品推广文案"; 50 const writingTypeName = typeMap[rawType] || "产品推广文案";
43 51
44 - const input = `帮我使用${writingLanguageName}写一篇主题是${prompt}的${writingTypeName},${isImg},字数要求不少于${writingCount}字,  
45 - 字数不包括html代码和图片Prompt。输出成标准的html并且样式必须为内联样式,直接给结果,不要做任何解释`;  
46 - return input; 52 + return `请用${writingLanguageName}撰写一篇关于【${prompt}】的${writingTypeName}:
  53 + ${isImg}
  54 + 具体要求:
  55 + 1. 写作风格:${styleMap[rawStyle] || "专业"}
  56 + 2. 字数要求:不少于${writingCount}字(不计代码)
  57 + 3. 输出格式:标准HTML带内联样式
  58 + 4. 特殊要求:直接输出结果,不要额外解释`;
47 } 59 }
48 60
  61 +//生成图片提示词
49 export function getBgPrompt(content: string) { 62 export function getBgPrompt(content: string) {
50 const input = `你现扮演生成创意思图片的提示词工程师,参考我的描述“${content}”帮我做优化润色5组,返回的数据用''分割,直接输出结果,不要做解释`; 63 const input = `你现扮演生成创意思图片的提示词工程师,参考我的描述“${content}”帮我做优化润色5组,返回的数据用''分割,直接输出结果,不要做解释`;
51 return input; 64 return input;
52 } 65 }
53 66
  67 +//思维导图提示词
54 export function getMindPrompt(content: string, isContext: boolean) { 68 export function getMindPrompt(content: string, isContext: boolean) {
55 const context = `联系上下文`; 69 const context = `联系上下文`;
56 let prompt = `请你帮我生成一份以"${content}"为主题的思维导图数据,请严格遵循以下要求生成思维导图数据: 70 let prompt = `请你帮我生成一份以"${content}"为主题的思维导图数据,请严格遵循以下要求生成思维导图数据: