diff --git a/app/components/chat/chat-message.tsx b/app/components/chat/chat-message.tsx
index 6426859..c08df7f 100644
--- a/app/components/chat/chat-message.tsx
+++ b/app/components/chat/chat-message.tsx
@@ -4,6 +4,8 @@ import type { ChatItem, Feedbacktype, ThoughtItem, VisionFile } from '../../type
import { CHAT_CONFIG } from '../../config/chat';
import Markdown from './markdown';
import ThoughtProcess from './thought-process';
+import ThinkingBlock from './thinking-block';
+import { parseMessageContent } from '../../utils/message-parser';
import { Dayjs } from 'dayjs';
import '../../styles/components/chat-with-llm/chat-message.css';
@@ -85,12 +87,22 @@ export default function ChatMessage({
);
}
- // 普通模式 - 恢复Markdown渲染
+ // 普通模式 - 解析内容,检查是否包含思考过程
+ const parsed = parseMessageContent(content);
+
return (
-
-
-
+ {/* 思考过程区域 */}
+ {parsed.hasThinking && (
+
+ )}
+
+ {/* 实际回复内容 */}
+ {parsed.response && (
+
+
+
+ )}
);
};
diff --git a/app/components/chat/thinking-block.tsx b/app/components/chat/thinking-block.tsx
new file mode 100644
index 0000000..48e43fa
--- /dev/null
+++ b/app/components/chat/thinking-block.tsx
@@ -0,0 +1,63 @@
+import React, { useState } from 'react';
+import { DownOutlined, UpOutlined, BulbOutlined } from '@ant-design/icons';
+import '../../styles/components/chat-with-llm/thinking-block.css';
+
+interface ThinkingBlockProps {
+ /** 思考过程内容 */
+ content: string;
+ /** 是否默认展开 */
+ defaultExpanded?: boolean;
+}
+
+/**
+ * 思考过程展示组件
+ * 参考 GPT-5 和 Claude 网页版的思考过程UI设计
+ * 可折叠的思考过程区域,契合当前淡绿色配色方案
+ */
+export default function ThinkingBlock({ content, defaultExpanded = false }: ThinkingBlockProps) {
+ const [expanded, setExpanded] = useState(defaultExpanded);
+
+ if (!content) return null;
+
+ return (
+
+ {/* 折叠/展开按钮 */}
+
setExpanded(!expanded)}
+ role="button"
+ tabIndex={0}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ setExpanded(!expanded);
+ }
+ }}
+ >
+
+
+ 思考过程
+
+
+ {expanded ? (
+
+ ) : (
+
+ )}
+
+
+
+ {/* 思考内容 */}
+ {expanded && (
+
+
+ {content.split('\n').map((line, index) => (
+
+ {line || '\u00A0'}
+
+ ))}
+
+
+ )}
+
+ );
+}
diff --git a/app/styles/components/chat-with-llm/thinking-block.css b/app/styles/components/chat-with-llm/thinking-block.css
new file mode 100644
index 0000000..d54fc9e
--- /dev/null
+++ b/app/styles/components/chat-with-llm/thinking-block.css
@@ -0,0 +1,168 @@
+/* 思考过程区域 - 契合当前淡绿色配色方案 */
+.thinking-block {
+ margin-bottom: 12px;
+ border-radius: 12px;
+ background: linear-gradient(135deg, #e8f5e9 0%, #f1f8e9 100%);
+ border: 1px solid #a4e2ad;
+ overflow: hidden;
+ transition: all 0.3s ease;
+}
+
+.thinking-block:hover {
+ border-color: #8dd99a;
+ box-shadow: 0 2px 8px rgba(164, 226, 173, 0.2);
+}
+
+/* 思考过程头部 */
+.thinking-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px 14px;
+ cursor: pointer;
+ user-select: none;
+ background: rgba(164, 226, 173, 0.15);
+ transition: background 0.2s ease;
+}
+
+.thinking-header:hover {
+ background: rgba(164, 226, 173, 0.25);
+}
+
+.thinking-header:active {
+ background: rgba(164, 226, 173, 0.35);
+}
+
+.thinking-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ font-weight: 500;
+ color: #2e7d32;
+}
+
+.thinking-icon {
+ font-size: 16px;
+ color: #66bb6a;
+}
+
+.thinking-label {
+ font-weight: 500;
+}
+
+.thinking-toggle {
+ display: flex;
+ align-items: center;
+}
+
+.toggle-icon {
+ font-size: 12px;
+ color: #66bb6a;
+ transition: transform 0.3s ease;
+}
+
+/* 思考内容区域 */
+.thinking-content {
+ padding: 12px 14px;
+ background: rgba(255, 255, 255, 0.5);
+ border-top: 1px solid rgba(164, 226, 173, 0.3);
+ animation: expandContent 0.3s ease-out;
+}
+
+@keyframes expandContent {
+ from {
+ opacity: 0;
+ max-height: 0;
+ padding-top: 0;
+ padding-bottom: 0;
+ }
+ to {
+ opacity: 1;
+ max-height: 1000px;
+ padding-top: 12px;
+ padding-bottom: 12px;
+ }
+}
+
+.thinking-text {
+ font-size: 13px;
+ line-height: 1.6;
+ color: #37474f;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+}
+
+.thinking-paragraph {
+ margin: 0 0 8px 0;
+ padding: 0;
+}
+
+.thinking-paragraph:last-child {
+ margin-bottom: 0;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .thinking-block {
+ border-radius: 10px;
+ margin-bottom: 10px;
+ }
+
+ .thinking-header {
+ padding: 8px 12px;
+ }
+
+ .thinking-title {
+ font-size: 13px;
+ }
+
+ .thinking-icon {
+ font-size: 14px;
+ }
+
+ .thinking-content {
+ padding: 10px 12px;
+ }
+
+ .thinking-text {
+ font-size: 12px;
+ line-height: 1.5;
+ }
+}
+
+@media (max-width: 480px) {
+ .thinking-block {
+ border-radius: 8px;
+ margin-bottom: 8px;
+ }
+
+ .thinking-header {
+ padding: 6px 10px;
+ }
+
+ .thinking-title {
+ font-size: 12px;
+ }
+
+ .thinking-icon {
+ font-size: 13px;
+ }
+
+ .toggle-icon {
+ font-size: 11px;
+ }
+
+ .thinking-content {
+ padding: 8px 10px;
+ }
+
+ .thinking-text {
+ font-size: 11px;
+ }
+
+ .thinking-paragraph {
+ margin-bottom: 6px;
+ }
+}
diff --git a/app/utils/message-parser.ts b/app/utils/message-parser.ts
new file mode 100644
index 0000000..54aba58
--- /dev/null
+++ b/app/utils/message-parser.ts
@@ -0,0 +1,79 @@
+/**
+ * 消息解析工具
+ * 用于解析和处理AI消息中的特殊标签
+ */
+
+/**
+ * 解析后的消息内容
+ */
+export interface ParsedMessage {
+ /** 是否包含思考过程 */
+ hasThinking: boolean;
+ /** 思考过程内容 */
+ thinking: string;
+ /** 实际回复内容 */
+ response: string;
+}
+
+/**
+ * 解析消息内容,提取 标签中的思考过程
+ *
+ * @param content - 原始消息内容
+ * @returns 解析后的消息对象
+ *
+ * @example
+ * ```typescript
+ * const parsed = parseMessageContent(`
+ * 这是思考过程
+ * 这是实际回复
+ * `);
+ * // parsed.hasThinking === true
+ * // parsed.thinking === '这是思考过程'
+ * // parsed.response === '这是实际回复'
+ * ```
+ */
+export function parseMessageContent(content: string): ParsedMessage {
+ if (!content) {
+ return {
+ hasThinking: false,
+ thinking: '',
+ response: content || '',
+ };
+ }
+
+ // 匹配 标签(支持多行)
+ const thinkRegex = /([\s\S]*?)<\/think>/i;
+ const match = content.match(thinkRegex);
+
+ if (!match) {
+ // 没有思考标签,直接返回原内容
+ return {
+ hasThinking: false,
+ thinking: '',
+ response: content,
+ };
+ }
+
+ // 提取思考内容
+ const thinking = match[1].trim();
+
+ // 移除 标签,获取实际回复
+ const response = content.replace(thinkRegex, '').trim();
+
+ return {
+ hasThinking: true,
+ thinking,
+ response,
+ };
+}
+
+/**
+ * 检查内容是否包含思考标签
+ *
+ * @param content - 要检查的内容
+ * @returns 是否包含思考标签
+ */
+export function hasThinkingTag(content: string): boolean {
+ if (!content) return false;
+ return //i.test(content);
+}