hongjli
2025-06-05 d38a3ae95ce1ca3d736ec0f88f17973fa0d5a914
页面调整优化
已添加2个文件
已修改5个文件
1620 ■■■■■ 文件已修改
public/images/333.png 补丁 | 查看 | 原始文档 | blame | 历史
src/app/ai-scene/chat/page.tsx 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/app/ai-scene/page.tsx 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/app/supply-chain-chat/page.tsx 1207 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/DataPreviewDialog.tsx 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SceneIntroDialog.tsx 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/layout/ClientLayoutContent.tsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/images/333.png
src/app/ai-scene/chat/page.tsx
@@ -207,6 +207,99 @@
| S5 | 1005 | 16673976114@163.com |
`
  },
  {
    title: '供应链全景洞察',
    description: '通过AI Agent与APS的深度协同,将插单响应从"被动救火"升级为"预测-决策-执行"三位一体的智能运营模式,助力新能源业务增长。',
    imageUrl: '/images/333.png',
    chatbotId: 'SCPanoramaInsight',
    background: `供应链全景洞察系统是新一代智能供应链管理平台的核心引擎。该AI系统能够解决传统供应链管理中的四大关键问题:
**生产计划与产能影响评估:**
- å®žæ—¶ç›‘控生产线状态,动态评估产能利用率
- åŸºäºŽåŽ†å²æ•°æ®å’Œå®žæ—¶è®¢å•ï¼Œæ™ºèƒ½é¢„æµ‹ç”Ÿäº§ç“¶é¢ˆ
- è‡ªåŠ¨è°ƒæ•´ç”Ÿäº§è®¡åˆ’ï¼Œæœ€å¤§åŒ–äº§èƒ½åˆ©ç”¨æ•ˆçŽ‡
- æä¾›å¤šåœºæ™¯ç”Ÿäº§è®¡åˆ’对比分析,辅助决策制定
**供应协同能力评估:**
- å®žæ—¶è¿½è¸ªä¾›åº”商交付状态,评估供应风险
- æ™ºèƒ½åˆ†æžä¾›åº”商绩效,提供供应商优化建议
- é¢„测原材料需求,提前启动采购流程
- æž„建供应链网络韧性模型,提升抗风险能力
**交付履约风险评估:**
- åŸºäºŽè®¢å•、库存、生产进度进行综合风险评估
- æå‰è¯†åˆ«æ½œåœ¨å»¶æœŸé£Žé™©ï¼Œåˆ¶å®šåº”对预案
- å®žæ—¶è·Ÿè¸ªäº¤ä»˜è¿›åº¦ï¼Œç¡®ä¿å®¢æˆ·æ»¡æ„åº¦
- å»ºç«‹å®¢æˆ·é¢„期管理机制,提升服务体验
**成本与财务影响评估:**
- å…¨é“¾è·¯æˆæœ¬è¿½è¸ªï¼Œç²¾ç¡®è®¡ç®—各环节成本构成
- åŠ¨æ€æˆæœ¬ä¼˜åŒ–å»ºè®®ï¼Œé™ä½Žæ•´ä½“è¿è¥æˆæœ¬
- è´¢åŠ¡å½±å“é¢„æµ‹åˆ†æžï¼Œæ”¯æŒå•†ä¸šå†³ç­–
- ROI评估模型,量化改进措施的财务效益`,
    instructions: `您可以通过以下方式与供应链全景洞察系统互动:
**场景一:插单影响分析**
提供新订单信息,系统将从四个维度评估插单可行性和影响。
**场景二:生产计划优化**
描述当前生产状况,获取智能优化建议和风险预警。
**场景三:供应链风险评估**
输入供应商或原材料信息,获取供应风险分析和应对策略。
**场景四:成本优化分析**
提供成本相关数据,获取成本优化建议和财务影响评估。
系统将基于实时数据和AI算法,为您提供专业的分析报告和决策建议。`,
    dataDescription: '本场景整合了OMS、ERP、WMS等多个系统的实时数据,通过AI算法进行深度分析。以下为新能源行业的模拟数据,实际应用中将自动对接企业现有系统。',
    exampleData: `
### è®¢å•需求数据 (OMS系统)
| è®¢å•号 | äº§å“åž‹å· | æ•°é‡ | å®¢æˆ·åç§° | é¢„期交付日期 | ä¼˜å…ˆçº§ | è®¢å•金额 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| SO20250201001 | NEV-Battery-A | 500 | æ¯”亚迪 | 2025-02-15 | é«˜ | 2,500,000 |
| SO20250201002 | NEV-Motor-B | 200 | è”šæ¥æ±½è½¦ | 2025-02-20 | ä¸­ | 1,800,000 |
| SO20250201003 | NEV-Controller-C | 300 | ç†æƒ³æ±½è½¦ | 2025-02-25 | é«˜ | 3,200,000 |
| SO20250201004 | NEV-Charger-D | 150 | å°é¹æ±½è½¦ | 2025-03-01 | ä¸­ | 1,200,000 |
### ç”Ÿäº§å·¥å•数据 (ERP系统)
| å·¥å•号 | äº§å“åž‹å· | è®¡åˆ’数量 | å·²å®Œæˆæ•°é‡ | ç”Ÿäº§çŠ¶æ€ | é¢„计完成时间 | ç”Ÿäº§çº¿ |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| WO20250201001 | NEV-Battery-A | 600 | 350 | è¿›è¡Œä¸­ | 2025-02-12 | äº§çº¿1 |
| WO20250201002 | NEV-Motor-B | 250 | 180 | è¿›è¡Œä¸­ | 2025-02-18 | äº§çº¿2 |
| WO20250201003 | NEV-Controller-C | 200 | 50 | è®¡åˆ’中 | 2025-02-22 | äº§çº¿3 |
| WO20250201004 | NEV-Charger-D | 100 | 0 | å¾…排产 | 2025-02-28 | äº§çº¿4 |
### åº“存数据 (WMS系统)
| ç‰©æ–™ç¼–码 | ç‰©æ–™åç§° | å½“前库存 | å®‰å…¨åº“å­˜ | åœ¨é€”数量 | é¢„计到货时间 | åº“位 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| MAT001 | é”‚电池芯 | 1,200 | 500 | 800 | 2025-02-10 | A区-01 |
| MAT002 | ç”µæœºå®šå­ | 350 | 200 | 150 | 2025-02-12 | B区-02 |
| MAT003 | æŽ§åˆ¶èŠ¯ç‰‡ | 180 | 100 | 200 | 2025-02-15 | C区-03 |
| MAT004 | å……电模块 | 95 | 50 | 100 | 2025-02-18 | D区-04 |
### ä¾›åº”商绩效数据
| ä¾›åº”商名称 | ä¾›åº”物料 | æŒ‰æ—¶äº¤ä»˜çއ | è´¨é‡åˆæ ¼çއ | ä»·æ ¼ç«žäº‰åŠ› | é£Žé™©ç­‰çº§ |
| :--: | :--: | :--: | :--: | :--: | :--: |
| å®å¾·æ—¶ä»£ | é”‚电池芯 | 95% | 99.5% | é«˜ | ä½Ž |
| æ±‡å·æŠ€æœ¯ | ç”µæœºå®šå­ | 88% | 98.2% | ä¸­ | ä¸­ |
| åœ°å¹³çº¿ | æŽ§åˆ¶èŠ¯ç‰‡ | 92% | 99.8% | ä¸­ | ä½Ž |
| ç‰¹æ¥ç”µ | å……电模块 | 85% | 97.5% | é«˜ | ä¸­ |
### äº§èƒ½æ•°æ®
| ç”Ÿäº§çº¿ | æ—¥äº§èƒ½ | å½“前利用率 | ç“¶é¢ˆå·¥åº | å¯è°ƒé…äº§èƒ½ | ç»´æŠ¤è®¡åˆ’ |
| :--: | :--: | :--: | :--: | :--: | :--: |
| äº§çº¿1 | 50台 | 85% | ç»„装 | 10台 | 2025-02-20 |
| äº§çº¿2 | 30台 | 75% | æµ‹è¯• | 8台 | 2025-02-25 |
| äº§çº¿3 | 40台 | 60% | ç„пޥ | 15台 | 2025-03-01 |
| äº§çº¿4 | 25台 | 45% | åŒ…装 | 12台 | 2025-03-05 |
`
  },
];
export default function AISceneChatPage() {
@@ -219,14 +312,15 @@
function AISceneChatContent() {
  const searchParams = useSearchParams();
  const sceneId = searchParams.get('scene');
  const sceneId = searchParams.get('scene') || searchParams.get('chatbotId');
  const router = useRouter();
  const [showDataPreview, setShowDataPreview] = useState(false);
  
  // æ ¹æ®URL参数找到对应的场景
  const initialScene = defaultServices.find(s => s.chatbotId === sceneId) || defaultServices[0];
  // ä¿®æ”¹ä¸ºä½¿ç”¨å®Œæ•´çš„场景数据
  const [services] = useState([defaultServices.find(s => s.chatbotId === sceneId) || defaultServices[0]]);
  const [services] = useState([initialScene]);
  const [selectedScene, setSelectedScene] = useState(initialScene);
  const [iframeKey, setIframeKey] = useState(0);
  const [token, setToken] = useState<string | null>(null);
@@ -394,7 +488,8 @@
            {/* æ•°æ®è¯´æ˜Ž - åªåœ¨è¡¥æ–™å’Œæ’单场景显示 */}
            {(selectedScene.chatbotId === 'JELkWpPLHQfRNhEH' || 
              selectedScene.chatbotId === 'RhMYLHI1SZNiX4kl' ||
              selectedScene.chatbotId === 'zO9YQDEHdIApG9zC') && (
              selectedScene.chatbotId === 'zO9YQDEHdIApG9zC' ||
              selectedScene.chatbotId === 'SCPanoramaInsight') && (
              <>
                <div className="bg-gray-50 rounded-lg p-4 border border-gray-100 hover:border-gray-200 transition-colors duration-300 shadow-sm">
                  <div className="flex items-center justify-between mb-2">
@@ -423,7 +518,8 @@
                  isOpen={showDataPreview}
                  onClose={() => setShowDataPreview(false)}
                  markdownContent={selectedScene.exampleData || ''}
                  sceneType={selectedScene.chatbotId === 'JELkWpPLHQfRNhEH' ? 'buliao' : 'chadan'}
                  sceneType={selectedScene.chatbotId === 'JELkWpPLHQfRNhEH' ? 'buliao' :
                           selectedScene.chatbotId === 'SCPanoramaInsight' ? 'panorama' : 'chadan'}
                />
              </>
            )}
src/app/ai-scene/page.tsx
@@ -200,6 +200,98 @@
| S4 | 1004 | chenqinghonghs@163.com |
| S5 | 1005 | 16673976114@163.com |
`
  },
  {
    title: '供应链全景洞察',
    description: '通过AI Agent与APS的深度协同,将插单响应从"被动救火"升级为"预测-决策-执行"三位一体的智能运营模式,助力新能源业务增长。',
    imageUrl: '/images/333.png',
    chatbotId: 'SCPanoramaInsight',
    background: `供应链全景洞察系统是新一代智能供应链管理平台的核心引擎。该AI系统能够解决传统供应链管理中的四大关键问题:
**生产计划与产能影响评估:**
- å®žæ—¶ç›‘控生产线状态,动态评估产能利用率
- åŸºäºŽåŽ†å²æ•°æ®å’Œå®žæ—¶è®¢å•ï¼Œæ™ºèƒ½é¢„æµ‹ç”Ÿäº§ç“¶é¢ˆ
- è‡ªåŠ¨è°ƒæ•´ç”Ÿäº§è®¡åˆ’ï¼Œæœ€å¤§åŒ–äº§èƒ½åˆ©ç”¨æ•ˆçŽ‡
- æä¾›å¤šåœºæ™¯ç”Ÿäº§è®¡åˆ’对比分析,辅助决策制定
**供应协同能力评估:**
- å®žæ—¶è¿½è¸ªä¾›åº”商交付状态,评估供应风险
- æ™ºèƒ½åˆ†æžä¾›åº”商绩效,提供供应商优化建议
- é¢„测原材料需求,提前启动采购流程
- æž„建供应链网络韧性模型,提升抗风险能力
**交付履约风险评估:**
- åŸºäºŽè®¢å•、库存、生产进度进行综合风险评估
- æå‰è¯†åˆ«æ½œåœ¨å»¶æœŸé£Žé™©ï¼Œåˆ¶å®šåº”对预案
- å®žæ—¶è·Ÿè¸ªäº¤ä»˜è¿›åº¦ï¼Œç¡®ä¿å®¢æˆ·æ»¡æ„åº¦
- å»ºç«‹å®¢æˆ·é¢„期管理机制,提升服务体验
**成本与财务影响评估:**
- å…¨é“¾è·¯æˆæœ¬è¿½è¸ªï¼Œç²¾ç¡®è®¡ç®—各环节成本构成
- åŠ¨æ€æˆæœ¬ä¼˜åŒ–å»ºè®®ï¼Œé™ä½Žæ•´ä½“è¿è¥æˆæœ¬
- è´¢åŠ¡å½±å“é¢„æµ‹åˆ†æžï¼Œæ”¯æŒå•†ä¸šå†³ç­–
- ROI评估模型,量化改进措施的财务效益`,
    instructions: `您可以通过以下方式与供应链全景洞察系统互动:
**场景一:插单影响分析**
提供新订单信息,系统将从四个维度评估插单可行性和影响。
**场景二:生产计划优化**
描述当前生产状况,获取智能优化建议和风险预警。
**场景三:供应链风险评估**
输入供应商或原材料信息,获取供应风险分析和应对策略。
**场景四:成本优化分析**
提供成本相关数据,获取成本优化建议和财务影响评估。
系统将基于实时数据和AI算法,为您提供专业的分析报告和决策建议。`,
    exampleData: `
### è®¢å•需求数据 (OMS系统)
| è®¢å•号 | äº§å“åž‹å· | æ•°é‡ | å®¢æˆ·åç§° | é¢„期交付日期 | ä¼˜å…ˆçº§ | è®¢å•金额 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| SO20250201001 | NEV-Battery-A | 500 | æ¯”亚迪 | 2025-02-15 | é«˜ | 2,500,000 |
| SO20250201002 | NEV-Motor-B | 200 | è”šæ¥æ±½è½¦ | 2025-02-20 | ä¸­ | 1,800,000 |
| SO20250201003 | NEV-Controller-C | 300 | ç†æƒ³æ±½è½¦ | 2025-02-25 | é«˜ | 3,200,000 |
| SO20250201004 | NEV-Charger-D | 150 | å°é¹æ±½è½¦ | 2025-03-01 | ä¸­ | 1,200,000 |
### ç”Ÿäº§å·¥å•数据 (ERP系统)
| å·¥å•号 | äº§å“åž‹å· | è®¡åˆ’数量 | å·²å®Œæˆæ•°é‡ | ç”Ÿäº§çŠ¶æ€ | é¢„计完成时间 | ç”Ÿäº§çº¿ |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| WO20250201001 | NEV-Battery-A | 600 | 350 | è¿›è¡Œä¸­ | 2025-02-12 | äº§çº¿1 |
| WO20250201002 | NEV-Motor-B | 250 | 180 | è¿›è¡Œä¸­ | 2025-02-18 | äº§çº¿2 |
| WO20250201003 | NEV-Controller-C | 200 | 50 | è®¡åˆ’中 | 2025-02-22 | äº§çº¿3 |
| WO20250201004 | NEV-Charger-D | 100 | 0 | å¾…排产 | 2025-02-28 | äº§çº¿4 |
### åº“存数据 (WMS系统)
| ç‰©æ–™ç¼–码 | ç‰©æ–™åç§° | å½“前库存 | å®‰å…¨åº“å­˜ | åœ¨é€”数量 | é¢„计到货时间 | åº“位 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| MAT001 | é”‚电池芯 | 1,200 | 500 | 800 | 2025-02-10 | A区-01 |
| MAT002 | ç”µæœºå®šå­ | 350 | 200 | 150 | 2025-02-12 | B区-02 |
| MAT003 | æŽ§åˆ¶èŠ¯ç‰‡ | 180 | 100 | 200 | 2025-02-15 | C区-03 |
| MAT004 | å……电模块 | 95 | 50 | 100 | 2025-02-18 | D区-04 |
### ä¾›åº”商绩效数据
| ä¾›åº”商名称 | ä¾›åº”物料 | æŒ‰æ—¶äº¤ä»˜çއ | è´¨é‡åˆæ ¼çއ | ä»·æ ¼ç«žäº‰åŠ› | é£Žé™©ç­‰çº§ |
| :--: | :--: | :--: | :--: | :--: | :--: |
| å®å¾·æ—¶ä»£ | é”‚电池芯 | 95% | 99.5% | é«˜ | ä½Ž |
| æ±‡å·æŠ€æœ¯ | ç”µæœºå®šå­ | 88% | 98.2% | ä¸­ | ä¸­ |
| åœ°å¹³çº¿ | æŽ§åˆ¶èŠ¯ç‰‡ | 92% | 99.8% | ä¸­ | ä½Ž |
| ç‰¹æ¥ç”µ | å……电模块 | 85% | 97.5% | é«˜ | ä¸­ |
### äº§èƒ½æ•°æ®
| ç”Ÿäº§çº¿ | æ—¥äº§èƒ½ | å½“前利用率 | ç“¶é¢ˆå·¥åº | å¯è°ƒé…äº§èƒ½ | ç»´æŠ¤è®¡åˆ’ |
| :--: | :--: | :--: | :--: | :--: | :--: |
| äº§çº¿1 | 50台 | 85% | ç»„装 | 10台 | 2025-02-20 |
| äº§çº¿2 | 30台 | 75% | æµ‹è¯• | 8台 | 2025-02-25 |
| äº§çº¿3 | 40台 | 60% | ç„пޥ | 15台 | 2025-03-01 |
| äº§çº¿4 | 25台 | 45% | åŒ…装 | 12台 | 2025-03-05 |
`
  }
];
src/app/supply-chain-chat/page.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1207 @@
'use client';
import { useState, useEffect, useRef, useCallback, useContext, createContext } from 'react';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
import Image from 'next/image';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import rehypeSanitize from 'rehype-sanitize';
import dynamic from 'next/dynamic';
import { ReactNode } from 'react';
// åˆ›å»ºä¸€ä¸ªæ¶ˆæ¯å®ŒæˆçŠ¶æ€çš„Context
const MessageCompletionContext = createContext<boolean>(true);
// åŠ¨æ€å¯¼å…¥ ECharts,确保它只在客户端渲染
const ReactECharts = dynamic(() => import('echarts-for-react'), { ssr: false });
interface Message {
  role: 'user' | 'assistant';
  content: string | null;
  timestamp: number;
  id: string;
  conversation_id?: string;
  feedback?: 'like' | 'dislike' | null;
  metadata?: {
    usage?: {
      prompt_tokens: number;
      completion_tokens: number;
      total_tokens: number;
      total_price: string;
    };
  };
}
const BASE_URL = 'http://121.43.139.99:7000';
// é»˜è®¤ç”¨æˆ·å¤´åƒ
const DEFAULT_USER_AVATAR = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23999999'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z'/%3E%3C/svg%3E";
const AI_AVATAR = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%234F46E5'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 1c4.96 0 9 4.04 9 9s-4.04 9-9 9-9-4.04-9-9 4.04-9 9-9zm0 3.5c-2.48 0-4.5 2.02-4.5 4.5h1.5c0-1.66 1.34-3 3-3s3 1.34 3 3h1.5c0-2.48-2.02-4.5-4.5-4.5zM8 13h2v2H8v-2zm6 0h2v2h-2v-2z'/%3E%3C/svg%3E";
// æ·»åŠ ç”¨äºŽè§£æžå’Œæ¸²æŸ“ ECharts çš„组件
function EchartsRenderer({ code }: { code: string }) {
  const [error, setError] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [isModalOpen, setIsModalOpen] = useState(false);
  const chartContainerRef = useRef<HTMLDivElement>(null);
  const modalChartRef = useRef<HTMLDivElement>(null);
  const hasRenderedRef = useRef<boolean>(false);
  const chartInstanceRef = useRef<any>(null);
  // åˆå§‹åŒ–DOM容器 - æå‰è®¾ç½®å›ºå®šé«˜åº¦
  useEffect(() => {
    // ç¡®ä¿å®¹å™¨å‡†å¤‡å¥½äº†å¹¶ä¸”有确定的高度
    if (chartContainerRef.current) {
      // è®¾ç½®ä¸€ä¸ªå›ºå®šçš„高度,避免渲染后变化
      const container = chartContainerRef.current;
      container.style.width = '100%';
      container.style.height = '400px'; // å›ºå®šé«˜åº¦400px
      container.style.minHeight = '400px'; // é˜²æ­¢é«˜åº¦å˜å°
      // æ·»åŠ ä¸´æ—¶å†…å®¹ï¼Œç¡®ä¿DOM渲染完成
      container.innerHTML = '<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#f5f7f9;"><span>正在准备图表...</span></div>';
    }
  }, []);
  // ä¸€æ¬¡æ€§åŠ è½½å›¾è¡¨ï¼Œä¸è¿›è¡ŒåŠ¨æ€æ›´æ–°
  useEffect(() => {
    // å¦‚果已经渲染过,跳过
    if (hasRenderedRef.current) {
      return;
    }
    setIsLoading(true);
    // ç¡®ä¿DOM元素已经完全挂载和渲染
    const timer = setTimeout(() => {
      // ç¡®ä¿DOM元素仍然存在
      if (!chartContainerRef.current) {
        console.error('图表容器不存在,跳过初始化');
        setIsLoading(false);
        return;
      }
      // æ¸…除临时内容
      chartContainerRef.current.innerHTML = '';
      // æ ‡è®°ä¸ºå·²æ¸²æŸ“
      hasRenderedRef.current = true;
      // åˆå§‹åŒ–图表
      const initializeChart = async () => {
        try {
          const echarts = await import('echarts');
          // åˆå§‹åŒ–图表
          const chartInstance = echarts.init(chartContainerRef.current);
          chartInstanceRef.current = chartInstance;
          // å°è¯•解析和设置图表选项
          try {
            const safeCode = code.replace(/window\.option/g, 'option');
            const safeFunc = new Function(`
              "use strict";
              let option;
              try {
                ${safeCode}
                return option;
              } catch (e) {
                console.error("图表代码执行错误:", e);
                return null;
              }
            `);
            const chartOption = safeFunc();
            if (chartOption) {
              // ç¦ç”¨åŠ¨ç”»ï¼Œé¿å…æ¸²æŸ“æ—¶çš„å¸ƒå±€å˜åŒ–
              chartOption.animation = false;
              // ä¿®æ”¹tooltip配置,确保显示在适当位置
              if (chartOption.tooltip) {
                chartOption.tooltip = {
                  ...chartOption.tooltip,
                  confine: false, // ä¸é™åˆ¶åœ¨å›¾è¡¨åŒºåŸŸå†…
                  extraCssText: 'z-index:9999; pointer-events:auto; margin-top:0;'
                };
              }
              chartInstance.setOption(chartOption);
              setError(null);
            } else {
              throw new Error("无法获取图表配置");
            }
          } catch (e) {
            console.error('图表代码执行错误:', e);
            setError('图表配置错误');
            // è®¾ç½®ä¸€ä¸ªé»˜è®¤å›¾è¡¨ä»¥æ˜¾ç¤ºé”™è¯¯
            chartInstance.setOption({
              title: { text: '图表配置错误' },
              xAxis: { type: 'category', data: ['错误'] },
              yAxis: { type: 'value' },
              series: [{ data: [0], type: 'bar' }]
            });
          }
          // æ·»åŠ çª—å£å¤§å°å˜åŒ–ç›‘å¬å™¨
          const handleResize = () => chartInstance.resize();
          window.addEventListener('resize', handleResize);
          // æ¸…理函数
          return () => {
            window.removeEventListener('resize', handleResize);
            chartInstance.dispose();
          };
        } catch (e) {
          console.error('ECharts加载失败:', e);
          setError('图表库加载失败');
        } finally {
          setIsLoading(false);
        }
      };
      initializeChart();
    }, 500); // å¢žåŠ å»¶è¿Ÿï¼Œç¡®ä¿DOM完全渲染
    return () => clearTimeout(timer);
  }, [code]);
  // å½“全屏状态变化时重新调整图表大小
  useEffect(() => {
    if (chartInstanceRef.current) {
      setTimeout(() => {
        chartInstanceRef.current.resize();
      }, 300); // ç»™DOM一些时间来更新
    }
  }, [isModalOpen]);
  // å¤„理全屏切换
  const toggleModal = useCallback(() => {
    setIsModalOpen(prev => !prev);
  }, []);
  // å¤„理模态窗口的图表
  useEffect(() => {
    if (!isModalOpen || !modalChartRef.current) return;
    const initModalChart = async () => {
      try {
        const echarts = await import('echarts');
        // åˆå§‹åŒ–模态窗口中的图表
        const modalChartInstance = echarts.init(modalChartRef.current);
        // å¦‚果主图表已经初始化,复用其配置
        if (chartInstanceRef.current) {
          const option = chartInstanceRef.current.getOption();
          // é‡æ–°è®¾ç½®å›¾è¡¨é…ç½®ï¼Œç¡®ä¿åœ¨æ–°å®¹å™¨ä¸­æ­£ç¡®æ˜¾ç¤º
          modalChartInstance.setOption(option);
          // ç›‘听窗口大小变化
          const handleResize = () => modalChartInstance.resize();
          window.addEventListener('resize', handleResize);
          // æ¸…理函数
          return () => {
            window.removeEventListener('resize', handleResize);
            modalChartInstance.dispose();
          };
        }
      } catch (e) {
        console.error('模态图表初始化失败:', e);
      }
    };
    initModalChart();
  }, [isModalOpen]);
  return (
    <>
      <div className="w-full bg-gray-50 rounded-lg overflow-hidden border border-gray-200">
        {/* å›¾è¡¨å·¥å…·æ  */}
        <div className="bg-white px-4 py-2 border-b border-gray-200 flex justify-between items-center">
          <span className="text-sm font-medium text-gray-700">数据图表</span>
          <div className="flex items-center space-x-2">
            {error && (
              <span className="text-xs text-red-500">{error}</span>
            )}
            <button
              onClick={toggleModal}
              className="p-1 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded transition-colors"
              title="全屏查看"
            >
              <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
              </svg>
            </button>
          </div>
        </div>
        {/* å›¾è¡¨å®¹å™¨ */}
        <div className="relative">
          <div
            ref={chartContainerRef}
            className="w-full"
            style={{ height: '400px', minHeight: '400px' }}
          />
          {isLoading && (
            <div className="absolute inset-0 flex items-center justify-center bg-gray-50/80">
              <div className="flex items-center space-x-2 text-gray-500">
                <div className="animate-spin rounded-full h-4 w-4 border-2 border-blue-500 border-t-transparent"></div>
                <span className="text-sm">加载中...</span>
              </div>
            </div>
          )}
        </div>
      </div>
      {/* å…¨å±æ¨¡æ€çª—口 */}
      {isModalOpen && (
        <div className="fixed inset-0 bg-black bg-opacity-80 z-50 flex items-center justify-center p-4">
          <div className="bg-white rounded-lg w-full h-full max-w-6xl max-h-[90vh] flex flex-col">
            {/* æ¨¡æ€çª—口头部 */}
            <div className="flex justify-between items-center p-4 border-b border-gray-200">
              <h3 className="text-lg font-semibold text-gray-900">图表详细视图</h3>
              <button
                onClick={toggleModal}
                className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors"
              >
                <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
                </svg>
              </button>
            </div>
            {/* æ¨¡æ€çª—口图表容器 */}
            <div className="flex-1 p-4">
              <div
                ref={modalChartRef}
                className="w-full h-full"
              />
            </div>
          </div>
        </div>
      )}
    </>
  );
}
function CodeBlockRenderer({ language, value }: { language: string; value: string }) {
  // æ£€æŸ¥æ˜¯å¦æ˜¯ ECharts ä»£ç 
  if (language === 'echarts' || language === 'javascript' && value.includes('option')) {
    return <EchartsRenderer code={value} />;
  }
  return (
    <div className="relative bg-gray-50 rounded-lg overflow-hidden border border-gray-200">
      <div className="bg-white px-4 py-2 border-b border-gray-200 flex justify-between items-center">
        <span className="text-sm font-medium text-gray-700">{language || '代码'}</span>
        <button
          onClick={() => navigator.clipboard.writeText(value)}
          className="p-1 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded transition-colors"
          title="复制代码"
        >
          <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
          </svg>
        </button>
      </div>
      <pre className="p-4 overflow-x-auto">
        <code className={`language-${language}`}>
          {value}
        </code>
      </pre>
    </div>
  );
}
interface ChatInputProps {
  onSendMessage: () => void;
  isStreaming: boolean;
  isMessageComplete: boolean;
}
function ChatInput({ onSendMessage, isStreaming, isMessageComplete }: ChatInputProps) {
  // å†…部状态,与外部完全隔离
  const [inputText, setInputText] = useState('');
  const inputRef = useRef<HTMLTextAreaElement>(null);
  // å¤„理输入变化
  const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    setInputText(e.target.value);
  };
  // å¤„理按键事件
  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      handleSend();
    }
  };
  // å¤„理发送
  const handleSend = () => {
    if (isStreaming || !inputText.trim() || !isMessageComplete) return;
    // é€šçŸ¥çˆ¶ç»„件发送消息前更新消息内容
    (window as any).messageToSend = inputText.trim();
    // æ¸…空输入
    setInputText('');
    // è°ƒç”¨çˆ¶ç»„件的发送方法
    onSendMessage();
  };
  return (
    <div className="fixed bottom-0 left-0 right-0 bg-gradient-to-t from-white via-white to-white/95 pt-4 pb-6" style={{ zIndex: 50 }}>
      <div className="max-w-4xl mx-auto px-6">
        <div className="relative">
          <div className="absolute -top-6 left-1/2 -translate-x-1/2 w-48 h-[1px] bg-gradient-to-r from-transparent via-gray-200 to-transparent"></div>
          <div className="flex gap-4 items-start">
            <div className="flex-1 relative">
              <textarea
                ref={inputRef}
                value={inputText}
                onChange={handleInputChange}
                onKeyDown={handleKeyDown}
                placeholder="请描述您的供应链场景或提出问题..."
                disabled={isStreaming}
                className="w-full resize-none rounded-xl border-0 bg-gray-50 px-4 py-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-100/50 focus:bg-white min-h-[48px] max-h-32 shadow-inner transition-all duration-300 ease-in-out hover:bg-gray-100/70 disabled:opacity-50 disabled:cursor-not-allowed"
                style={{ height: '48px' }}
              />
              <div className="absolute right-4 bottom-2 text-xs text-gray-400 bg-gray-50 px-2">
                æŒ‰Enter发送,Shift+Enter换行
              </div>
            </div>
            <button
              onClick={handleSend}
              disabled={isStreaming || !inputText.trim() || !isMessageComplete}
              className="h-12 px-5 bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-medium rounded-xl transition-all duration-300 flex items-center gap-2 shadow-lg shadow-blue-500/20 hover:shadow-blue-500/30 hover:scale-[1.02] active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none disabled:shadow-none cursor-pointer"
            >
              <span>{isStreaming ? '回复中...' : (isMessageComplete ? '发送' : '处理中...')}</span>
              <svg xmlns="http://www.w3.org/2000/svg" className={`h-4 w-4 transform rotate-45 ${isStreaming ? 'animate-pulse' : ''}`} viewBox="0 0 20 20" fill="currentColor">
                <path d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z" />
              </svg>
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}
export default function SupplyChainChatPage() {
  const router = useRouter();
  const searchParams = useSearchParams();
  const [apiKey, setApiKey] = useState<string>('');
  const [message, setMessage] = useState('');
  const [messages, setMessages] = useState<Message[]>([]);
  const [isStreaming, setIsStreaming] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [showError, setShowError] = useState(false);
  const [conversationId, setConversationId] = useState<string | null>(null);
  const [isMessageComplete, setIsMessageComplete] = useState(true);
  const [currentMessageId, setCurrentMessageId] = useState<string | null>(null);
  // æ·»åŠ ä¸€ä¸ªæ–°çš„çŠ¶æ€æ¥æŽ§åˆ¶æ¶ˆæ¯çš„æ˜¾ç¤º
  const [showMessages, setShowMessages] = useState(false);
  // åœ¨ç»„件顶部增加一个强制更新计数器
  const [forceUpdateCounter, setForceUpdateCounter] = useState(0);
  // æ·»åŠ çŠ¶æ€æ¥æŽ§åˆ¶æ€è€ƒå†…å®¹çš„æ˜¾ç¤º/隐藏
  const [expandedThinkMessages, setExpandedThinkMessages] = useState<Record<string, boolean>>({});
  // æ·»åŠ API密钥加载状态
  const [isLoadingApiKey, setIsLoadingApiKey] = useState(false);
  // èŽ·å–URL参数
  const keyParam = searchParams.get('key');
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const errorTimeoutRef = useRef<any>(null);
  // åœ¨ç»„件顶部添加一个引用,用于跟踪组件是否已卸载
  const isMountedRef = useRef(true);
  // åˆ†ç¦»æ¶ˆæ¯è¾“入状态,避免触发不必要的重渲染
  const messageInputRef = useRef<HTMLTextAreaElement>(null);
  const [localMessage, setLocalMessage] = useState('');
  // æ·»åŠ èŽ·å–API密钥的函数
  const fetchApiKey = async () => {
    setIsLoadingApiKey(true);
    try {
      const response = await fetch('http://121.43.139.99:8080/api/secret-key', {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
        },
      });
      if (!response.ok) {
        throw new Error(`获取密钥失败: HTTP ${response.status}`);
      }
      const result = await response.json();
      console.log('API响应:', result);
      // æŒ‰ç…§æŽ¥å£æ–‡æ¡£æ ¼å¼è§£æžå“åº”
      if (result.code === 200 && result.data && result.data.key) {
        const apiKeyValue = result.data.key;
        setApiKey(apiKeyValue);
        setError(null);
        console.log('成功获取API密钥:', apiKeyValue);
        return apiKeyValue;
      } else {
        throw new Error(result.message || '获取密钥失败:响应格式不正确');
      }
    } catch (err) {
      console.error('获取API密钥失败:', err);
      const errorMessage = err instanceof Error ? err.message : '获取密钥时发生未知错误';
      showErrorMessage(`密钥获取失败: ${errorMessage}`);
      return null;
    } finally {
      setIsLoadingApiKey(false);
    }
  };
  const handleMessageChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    // åªæ›´æ–°æœ¬åœ°çŠ¶æ€ï¼Œä¸è§¦å‘å…¨å±€é‡æ¸²æŸ“
    setLocalMessage(e.target.value);
  };
  // ä»…在发送时更新全局消息状态
  const handleKeyPress = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      // æ›´æ–°å…¨å±€çŠ¶æ€å¹¶å‘é€
      setMessage(localMessage);
      setTimeout(() => handleSendMessage(), 10);
    }
  };
  // ç‚¹å‡»å‘送按钮时
  const handleSendButtonClick = () => {
    // æ£€æŸ¥æ¶ˆæ¯æ˜¯å¦ä¸ºç©º
    if (!localMessage.trim() || !isMessageComplete) return;
    // æ›´æ–°å…¨å±€çŠ¶æ€å¹¶å‘é€
    setMessage(localMessage);
    setTimeout(() => handleSendMessage(), 10);
  };
  // åŒæ­¥æ¶ˆæ¯çŠ¶æ€åˆ°æœ¬åœ°çŠ¶æ€
  useEffect(() => {
    setLocalMessage(message);
  }, [message]);
  useEffect(() => {
    // ç»„件加载时从API获取密钥
    const initializeApiKey = async () => {
      await fetchApiKey();
    };
    initializeApiKey();
  }, []);
  useEffect(() => {
    // æ»šåŠ¨åˆ°æœ€æ–°æ¶ˆæ¯
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);
  // å¤„理错误显示和自动消失
  const showErrorMessage = useCallback((message: string) => {
    setError(message);
    // å…ˆè®¾ç½®ä¸ºfalse确保动画能重新触发
    setShowError(false);
    // ä½¿ç”¨ requestAnimationFrame ç¡®ä¿çŠ¶æ€å˜åŒ–è¢«æ­£ç¡®æ¸²æŸ“
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        setShowError(true);
      });
    });
    if (errorTimeoutRef.current) {
      clearTimeout(errorTimeoutRef.current);
    }
    errorTimeoutRef.current = setTimeout(() => {
      setShowError(false);
      setTimeout(() => {
        setError(null);
      }, 400);
    }, 3000);
  }, []);
  // ç»„件卸载时清除定时器
  useEffect(() => {
    const cleanup = () => {
      if (errorTimeoutRef.current) {
        clearTimeout(errorTimeoutRef.current);
      }
    };
    return cleanup;
  }, []);
  // æ·»åŠ ç»„ä»¶å¸è½½æ—¶çš„æ¸…ç†å·¥ä½œ
  useEffect(() => {
    // ç»„件挂载时,设置为true
    isMountedRef.current = true;
    // ç»„件卸载时,设置为false
    return () => {
      isMountedRef.current = false;
      // æ¸…除所有定时器
      if (errorTimeoutRef.current) {
        clearTimeout(errorTimeoutRef.current);
      }
    };
  }, []);
  const handleSendMessage = async () => {
    // ä»Žwindow对象获取消息内容
    const inputMessage = (window as any).messageToSend || '';
    if (!inputMessage.trim() || !isMessageComplete) return;
    // æ£€æŸ¥API密钥,如果没有则尝试获取
    let currentApiKey = apiKey;
    if (!currentApiKey) {
      showErrorMessage('正在获取API密钥...');
      currentApiKey = await fetchApiKey();
      if (!currentApiKey) {
        showErrorMessage('无法获取API密钥,请检查网络连接');
        return;
      }
    }
    setIsStreaming(true);
    setIsMessageComplete(false);
    // åˆ›å»ºæ–°æ¶ˆæ¯
    const userMessage: Message = {
      role: 'user',
      content: inputMessage.trim(),
      timestamp: Date.now(),
      id: 'user-' + Date.now().toString()
    };
    const assistantMessage: Message = {
      role: 'assistant',
      content: '',
      timestamp: Date.now(),
      id: 'temp-' + Date.now().toString()
    };
    // ä½¿ç”¨å‡½æ•°å¼æ›´æ–°ç¡®ä¿çŠ¶æ€æ›´æ–°çš„ä¸€è‡´æ€§
    setMessages(prevMessages => {
      const newMessages = [...prevMessages];
      // å¦‚果存在未完成的助手消息,先移除它
      if (!isMessageComplete && currentMessageId) {
        const lastMessage = newMessages[newMessages.length - 1];
        if (lastMessage && lastMessage.role === 'assistant' && lastMessage.id === currentMessageId) {
          newMessages.pop();
        }
      }
      return [...newMessages, userMessage, assistantMessage];
    });
    setCurrentMessageId(assistantMessage.id);
    setMessage('');
    setShowMessages(true);
    // æ¸…除输入内容
    (window as any).messageToSend = '';
    let controller: AbortController | null = new AbortController();
    try {
      const response = await fetch(`${BASE_URL}/v1/chat-messages`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${currentApiKey}`,
          'Content-Type': 'application/json',
        },
        mode: 'cors',
        body: JSON.stringify({
          inputs: {},
          query: userMessage.content,
          response_mode: 'streaming',
          conversation_id: conversationId || '',
          user: 'abc-123',
          files: []
        }),
        signal: controller.signal // æ·»åŠ ä¸­æ­¢ä¿¡å·
      });
      console.log("API响应状态:", response.status, response.statusText);
      if (!response.ok) {
        const errorText = await response.text();
        console.error("API错误响应:", errorText);
        let errorMessage;
        try {
          const errorJson = JSON.parse(errorText);
          errorMessage = errorJson.error || `错误: ${response.status}`;
        } catch {
          errorMessage = `错误: ${response.status}`;
        }
        throw new Error(errorMessage);
      }
      // ç¡®ä¿response.body存在
      if (!response.body) {
        throw new Error('响应没有提供数据流');
      }
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      let buffer = '';
      while (true) {
        try {
          const { done, value } = await reader.read();
          if (done) {
            console.log("流式响应接收完毕");
            setIsMessageComplete(true);
            break;
          }
          const chunk = decoder.decode(value, { stream: true });
          console.log("接收到数据块:", chunk);
          buffer += chunk;
          const lines = buffer.split('\n');
          buffer = lines.pop() || '';
          for (const line of lines) {
            if (line.trim() === '') continue;
            console.log("处理行数据:", line);
            if (line.startsWith('data: ')) {
              try {
                const jsonStr = line.slice(6);
                console.log("完整原始数据:", line);
                console.log("解析JSON字符串:", jsonStr);
                const data = JSON.parse(jsonStr);
                console.log("解析后的数据对象:", data);
                // å¿½ç•¥ping事件
                if (data.event === 'ping') {
                  console.log("忽略ping事件");
                  continue;
                }
                switch (data.event) {
                  case 'message':
                    console.log(`收到message事件:`, data);
                    // æå–message数据
                    const messageId = data.message_id;
                    const answerChunk = data.answer || '';
                    const convId = data.conversation_id;
                    // å¦‚果不在循环中,直接调用更新函数
                    handleMessageChunk(messageId, answerChunk, convId);
                    break;
                  case 'message_end':
                    console.log('收到message_end事件:', data);
                    // æ£€æŸ¥æ˜¯å¦æœ‰æœ€ç»ˆç­”案
                    if (data.metadata && data.id) {
                      setIsMessageComplete(true);
                      setConversationId(data.conversation_id || null);
                    }
                    break;
                  case 'workflow_finished':
                    console.log('工作流完成:', data);
                    // æ£€æŸ¥å·¥ä½œæµè¾“出是否有答案
                    if (data.data && data.data.outputs && data.data.outputs.answer) {
                      // å¦‚果工作流有最终答案,更新消息内容
                      const finalAnswer = data.data.outputs.answer;
                      const messageId = data.message_id;
                      if (finalAnswer) {
                        console.log('从工作流获取最终答案:', finalAnswer);
                        handleFinalAnswer(messageId, finalAnswer, data.conversation_id);
                      }
                    }
                    break;
                  case 'node_finished':
                    // æ£€æŸ¥èŠ‚ç‚¹ç±»åž‹æ˜¯å¦ä¸ºanswer节点
                    if (data.data && data.data.node_type === 'answer' && data.data.outputs && data.data.outputs.answer) {
                      console.log('从answer节点获取答案:', data.data.outputs.answer);
                      const answer = data.data.outputs.answer;
                      if (answer) {
                        handleFinalAnswer(data.message_id, answer, data.conversation_id);
                      }
                    }
                    break;
                  // å¤„理其他工作流事件
                  case 'workflow_started':
                  case 'node_started':
                    // è¿™äº›äº‹ä»¶åªéœ€è®°å½•,不需要更新UI
                    console.log(`工作流事件 ${data.event}:`, data);
                    break;
                  case 'error':
                    console.error('服务器返回错误事件:', data);
                    setIsMessageComplete(true);
                    throw new Error(data.message || '发送消息时出错');
                }
              } catch (e) {
                console.error('解析SSE数据出错:', e, '原始行:', line);
                setIsMessageComplete(true);
                throw e;
              }
            }
          }
        } catch (err) {
          console.error('处理流式响应时出错:', err);
          throw err;
        }
      }
    } catch (err) {
      // æ£€æŸ¥æ˜¯å¦æ˜¯ä¸­æ­¢é”™è¯¯
      if (err instanceof Error && err.name === 'AbortError') {
        console.log('请求被中止,可能是组件卸载导致的');
        return; // ä¸­æ­¢é”™è¯¯ä¸éœ€è¦æ˜¾ç¤ºç»™ç”¨æˆ·
      }
      console.error('聊天请求错误:', err);
      let errorMsg = err instanceof Error ? err.message : '发送消息时出错';
      // åªæœ‰åœ¨ç»„件仍然挂载时才更新UI
      if (isMountedRef.current) {
        showErrorMessage(errorMsg);
        setMessages(prev => {
          const newMessages = [...prev];
          const lastMessage = newMessages[newMessages.length - 1];
          if (lastMessage?.role === 'assistant') {
            lastMessage.content = `抱歉,无法获取回复: ${errorMsg}`;
          }
          return newMessages;
        });
      }
    } finally {
      // æ¸…除控制器
      controller = null;
      // åªæœ‰åœ¨ç»„件仍然挂载时才更新UI
      if (isMountedRef.current) {
        setIsStreaming(false);
        setIsMessageComplete(true);
      }
    }
  };
  // ä¿®æ”¹handleMessageChunk函数,添加组件挂载检查
  const handleMessageChunk = useCallback((messageId: string, answerChunk: string, convId?: string) => {
    // å¦‚果组件已卸载,则不执行更新
    if (!isMountedRef.current) return;
    console.log(`处理消息片段: ID=${messageId}, ç‰‡æ®µ="${answerChunk}"`);
    // ä½¿ç”¨å‡½æ•°å¼æ›´æ–°ç¡®ä¿èŽ·å–æœ€æ–°çŠ¶æ€
    setMessages(prevMessages => {
      // å¦‚果组件已卸载,则不执行更新
      if (!isMountedRef.current) return prevMessages;
      // åˆ›å»ºæ¶ˆæ¯æ•°ç»„的拷贝
      const newMessages = [...prevMessages];
      // æŸ¥æ‰¾æœ€åŽä¸€æ¡åŠ©æ‰‹æ¶ˆæ¯
      const lastMessage = newMessages[newMessages.length - 1];
      if (lastMessage?.role !== 'assistant') {
        console.warn('找不到助手消息来更新');
        return prevMessages; // ä¸éœ€è¦æ›´æ–°
      }
      // æ›´æ–°æ¶ˆæ¯å†…容和ID
      const updatedContent = (lastMessage.content || '') + answerChunk;
      // åˆ›å»ºæ¶ˆæ¯çš„æ–°å‰¯æœ¬ä»¥ç¡®ä¿React检测到变化
      const updatedMessage = {
        ...lastMessage,
        id: messageId,
        content: updatedContent,
        timestamp: Date.now()
      };
      if (convId) {
        updatedMessage.conversation_id = convId;
      }
      // æ›´æ–°å½“前正在处理的消息ID
      if (isMountedRef.current) {
        setCurrentMessageId(messageId);
      }
      // æ›¿æ¢æœ€åŽä¸€æ¡æ¶ˆæ¯
      newMessages[newMessages.length - 1] = updatedMessage;
      console.log(`更新后的内容长度: ${updatedContent.length}`);
      // è§¦å‘强制更新
      if (isMountedRef.current) {
        setTimeout(() => {
          if (isMountedRef.current) {
            setForceUpdateCounter(count => count + 1);
          }
        }, 0);
      }
      return newMessages;
    });
  }, []);
  // ä¿®æ”¹handleFinalAnswer函数,添加组件挂载检查
  const handleFinalAnswer = useCallback((messageId: string, answer: string, convId?: string) => {
    // å¦‚果组件已卸载,则不执行更新
    if (!isMountedRef.current) return;
    console.log(`设置最终答案: ID=${messageId}, å†…容="${answer}"`);
    setMessages(prevMessages => {
      // å¦‚果组件已卸载,则不执行更新
      if (!isMountedRef.current) return prevMessages;
      const newMessages = [...prevMessages];
      const lastMessage = newMessages[newMessages.length - 1];
      if (lastMessage?.role !== 'assistant') {
        console.warn('找不到助手消息来更新最终答案');
        return prevMessages;
      }
      // åˆ›å»ºæ¶ˆæ¯çš„æ–°å‰¯æœ¬ï¼Œè®¾ç½®æœ€ç»ˆç­”案
      const updatedMessage = {
        ...lastMessage,
        id: messageId,
        content: answer,
        timestamp: Date.now()
      };
      if (convId) {
        updatedMessage.conversation_id = convId;
      }
      // æ›´æ–°çŠ¶æ€
      if (isMountedRef.current) {
        setIsMessageComplete(true);
        setCurrentMessageId(messageId);
      }
      // æ›¿æ¢æœ€åŽä¸€æ¡æ¶ˆæ¯
      newMessages[newMessages.length - 1] = updatedMessage;
      // è§¦å‘强制更新
      if (isMountedRef.current) {
        setTimeout(() => {
          if (isMountedRef.current) {
            setForceUpdateCounter(count => count + 1);
          }
        }, 0);
      }
      return newMessages;
    });
  }, []);
  const parseMessageContent = (content: string | null) => {
    if (!content) return { mainContent: null, thinkContent: null };
    // æ£€æŸ¥æ˜¯å¦åŒ…含思考内容
    const thinkMatch = content.match(/<think>([\s\S]*?)<\/think>/);
    const thinkContent = thinkMatch ? thinkMatch[1].trim() : null;
    const mainContent = content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
    return { mainContent, thinkContent };
  };
  const toggleThinkContent = (messageId: string) => {
    setExpandedThinkMessages(prev => ({
      ...prev,
      [messageId]: !prev[messageId]
    }));
  };
  useEffect(() => {
    // é¡µé¢åŠ è½½åŽå»¶è¿Ÿæ˜¾ç¤ºæ¶ˆæ¯åˆ—è¡¨ï¼Œé¿å…é—ªçƒ
    const timer = setTimeout(() => {
      setShowMessages(true);
    }, 100);
    return () => clearTimeout(timer);
  }, []);
  useEffect(() => {
    return () => {
      // ç»„件卸载时的清理工作
      console.log('供应链聊天组件卸载,清理资源');
    };
  }, []);
  return (
    <div className="min-h-screen bg-gradient-to-b from-gray-50 to-white text-gray-900 flex flex-col">
      {/* é¡¶éƒ¨å¯¼èˆªæ  */}
      <div className="fixed top-16 left-0 right-0 bg-white border-b border-gray-200 z-40">
        <div className="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
          <div className="flex items-center space-x-4">
            <button
              onClick={() => router.push('/ai-scene')}
              className="group inline-flex items-center text-sm text-gray-500 hover:text-red-500 transition-all duration-300 cursor-pointer"
            >
              <svg
                xmlns="http://www.w3.org/2000/svg"
                className="h-4 w-4 transition-transform duration-300 group-hover:-translate-x-0.5"
                fill="none"
                viewBox="0 0 24 24"
                stroke="currentColor"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth={2}
                  d="M10 19l-7-7m0 0l7-7m-7 7h18"
                />
              </svg>
              <span className="ml-1.5">返回</span>
            </button>
            <div className="h-4 w-px bg-gray-200"></div>
            <h1 className="text-xl font-semibold text-gray-900">
              ä¾›åº”链全景洞察
            </h1>
          </div>
          <div className="flex items-center space-x-4">
            <div className="px-3 py-1 bg-green-100 text-green-700 text-sm rounded-full">
              AI智能分析
            </div>
            {keyParam && (
              <div className="text-xs text-gray-500">
                ID: {keyParam}
              </div>
            )}
          </div>
        </div>
      </div>
      {/* é”™è¯¯æç¤º */}
      {error && (
        <div
          className={`fixed top-36 left-1/2 transform -translate-x-1/2
            flex items-center gap-2 px-4 py-2 text-sm text-white
            transition-all duration-400 ease-out
            ${showError
              ? 'opacity-100 translate-y-0 scale-100'
              : 'opacity-0 -translate-y-2 scale-95'
            }
            before:content-[''] before:absolute before:inset-0 before:bg-red-500
            before:rounded-lg before:opacity-90 before:-z-10
            after:content-[''] after:absolute after:inset-0 after:bg-red-500/50
            after:blur-md after:rounded-lg after:-z-20`}
        >
          <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
            <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
          </svg>
          <span className="relative">{error}</span>
        </div>
      )}
      {/* èŠå¤©åŒºåŸŸ */}
      <div className="flex-1 flex flex-col h-screen">
        <div className="flex-1 overflow-y-auto pt-36 pb-32">
          <div className="max-w-4xl mx-auto px-6">
            <div className={`space-y-6 ${showMessages ? 'opacity-100' : 'opacity-0'} transition-opacity duration-200`}>
              {messages.length === 0 ? (
                <div className="flex flex-col items-center justify-center h-[400px] text-center">
                  <div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mb-4">
                    <svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2-2V7a2 2 0 012-2h2a2 2 0 002-2V3a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 002 2h2a2 2 0 012 2v2a2 2 0 00-2 2h-2a2 2 0 00-2 2v6a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
                    </svg>
                  </div>
                  <h3 className="text-lg font-medium text-gray-900 mb-2">供应链全景洞察</h3>
                  <p className="text-gray-500 max-w-md">
                    æ¬¢è¿Žä½¿ç”¨ä¾›åº”链全景洞察系统!您可以咨询插单影响分析、生产计划优化、供应链风险评估等问题。
                  </p>
                </div>
              ) : (
                messages.map((msg, index) => (
                  <div
                    key={msg.id}
                    className={`flex items-start gap-4 ${msg.role === 'user' ? 'flex-row-reverse' : ''}`}
                  >
                    <div className="relative flex-shrink-0">
                      <div className="w-8 h-8 rounded-lg overflow-hidden shadow-inner bg-gray-50">
                        <Image
                          src={msg.role === 'assistant' ? "/images/logo.jpg" : DEFAULT_USER_AVATAR}
                          alt={msg.role === 'assistant' ? "AI助手" : "用户"}
                          width={32}
                          height={32}
                          className="w-full h-full object-cover"
                        />
                      </div>
                    </div>
                    <div className={`flex-1 min-w-0 ${msg.role === 'user' ? 'text-right' : ''}`}>
                      <div className={`${
                        msg.role === 'assistant'
                          ? 'bg-white rounded-xl shadow-sm border border-gray-100'
                          : 'bg-[#E8F4FF] rounded-xl'
                        } inline-block max-w-[85%] relative overflow-hidden`}>
                        {msg.role === 'assistant' && (
                          <>{(() => {
                            // è§£æžæ¶ˆæ¯å†…容,提取思考部分
                            const { mainContent, thinkContent } = parseMessageContent(msg.content);
                            const isExpanded = expandedThinkMessages[msg.id] || false;
                            const thinkingDuration = 8; // æ€è€ƒæ—¶é—´(秒)
                            return (
                              <>
                                {/* æ€è€ƒå†…容区域 (如果存在) */}
                                {thinkContent && (
                                  <div className="border-b border-gray-200">
                                    <button
                                      onClick={() => toggleThinkContent(msg.id)}
                                      className="w-full flex items-center justify-between px-3 py-2 bg-gray-50 hover:bg-gray-100 transition-colors text-sm text-gray-700"
                                    >
                                      <div className="flex items-center space-x-2">
                                        <span>已深度思考 (用时 {thinkingDuration} ç§’)</span>
                                      </div>
                                      <svg
                                        className={`w-4 h-4 transform transition-transform ${isExpanded ? 'rotate-180' : ''}`}
                                        fill="none"
                                        stroke="currentColor"
                                        viewBox="0 0 24 24"
                                      >
                                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
                                      </svg>
                                    </button>
                                    {/* å¯æŠ˜å çš„æ€è€ƒå†…容 */}
                                    {isExpanded && (
                                      <div className="bg-gray-50 px-4 py-3 text-sm text-gray-700 border-t border-gray-200 whitespace-pre-wrap">
                                        {thinkContent}
                                      </div>
                                    )}
                                  </div>
                                )}
                                {/* ä¸»è¦å†…容 */}
                                <div className="p-3">
                                  <div className="text-gray-800 leading-relaxed">
                                    {mainContent ? (
                                      <MessageCompletionContext.Provider
                                        value={msg.id !== currentMessageId || isMessageComplete}
                                      >
                                        <ReactMarkdown
                                          remarkPlugins={[remarkGfm]}
                                          rehypePlugins={[rehypeRaw, rehypeSanitize]}
                                          components={{
                                            // @ts-ignore - ReactMarkdown ç»„件类型定义的兼容性问题
                                            code: ({ node, inline, className, children, ...props }) => {
                                              const match = /language-(\w+)/.exec(className || '');
                                              const language = match ? match[1] : '';
                                              const value = String(children).replace(/\n$/, '');
                                              if (!inline && match) {
                                                return <CodeBlockRenderer language={language} value={value} />;
                                              }
                                              return (
                                                <code className={className} {...props}>
                                                  {children}
                                                </code>
                                              );
                                            },
                                            // æ·»åŠ è¡¨æ ¼æ ·å¼ç»„ä»¶
                                            table: ({ node, ...props }) => (
                                              <div className="overflow-x-auto my-4 rounded-lg border border-gray-100 shadow-sm" style={{ width: '100%', minWidth: '100%' }}>
                                                <table className="min-w-full divide-y divide-gray-100" style={{ tableLayout: 'auto', width: 'max-content', minWidth: '100%' }} {...props} />
                                              </div>
                                            ),
                                            thead: ({ node, ...props }) => (
                                              <thead className="bg-gray-50/70" {...props} />
                                            ),
                                            th: ({ node, children, ...props }) => (
                                              <th className="px-4 py-3 text-sm font-semibold text-gray-700 border-b border-gray-100 text-center" style={{ whiteSpace: 'nowrap', minWidth: 'max-content' }} {...props}>
                                                {children}
                                              </th>
                                            ),
                                            td: ({ node, ...props }) => (
                                              <td className="px-4 py-3 text-sm text-gray-600 border-t border-gray-100 text-center" style={{ whiteSpace: 'nowrap' }} {...props} />
                                            ),
                                            tr: ({ node, ...props }) => (
                                              <tr className="hover:bg-gray-50/70 transition-colors duration-150" {...props} />
                                            )
                                          }}
                                        >
                                          {mainContent}
                                        </ReactMarkdown>
                                      </MessageCompletionContext.Provider>
                                    ) : (
                                      msg.role === 'assistant' && !isMessageComplete ? '处理回复中...' : ''
                                    )}
                                  </div>
                                </div>
                                {/* åŠ è½½æŒ‡ç¤ºå™¨ */}
                                {msg.role === 'assistant' &&
                                  !isMessageComplete &&
                                  msg.id === currentMessageId && ( // åªåœ¨å½“前处理的消息显示加载指示器
                                  <div className="absolute bottom-1 right-2">
                                    <div className="flex space-x-1">
                                      <div className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse delay-0"></div>
                                      <div className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse delay-150"></div>
                                      <div className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse delay-300"></div>
                                    </div>
                                  </div>
                                )}
                              </>
                            );
                          })()}</>
                        )}
                        {/* ç”¨æˆ·æ¶ˆæ¯ç®€å•显示 */}
                        {msg.role === 'user' && (
                          <div className="p-3">
                            <div className="text-gray-800 leading-relaxed whitespace-pre-wrap">
                              {msg.content}
                            </div>
                          </div>
                        )}
                      </div>
                      <div className="mt-0.5 text-xs text-gray-400">
                        {msg.role === 'user' && new Date(msg.timestamp).toLocaleTimeString()}
                      </div>
                    </div>
                  </div>
                ))
              )}
              <div ref={messagesEndRef} />
            </div>
          </div>
        </div>
        {/* è¾“入区域 - æŠ½å–为独立组件 */}
        <ChatInput
          onSendMessage={handleSendMessage}
          isStreaming={isStreaming}
          isMessageComplete={isMessageComplete}
        />
      </div>
    </div>
  );
}
src/components/DataPreviewDialog.tsx
@@ -10,7 +10,7 @@
  isOpen: boolean;
  onClose: () => void;
  markdownContent: string;
  sceneType?: 'chadan' | 'buliao';  // æ·»åŠ åœºæ™¯ç±»åž‹å±žæ€§
  sceneType?: 'chadan' | 'buliao' | 'panorama';  // æ·»åŠ åœºæ™¯ç±»åž‹å±žæ€§
}
export default function DataPreviewDialog({
@@ -101,6 +101,80 @@
          </div>
        </div>
      );
    } else if (sceneType === 'panorama') {
      return (
        <div className="space-y-6">
          <div className="bg-white/50 rounded-lg p-4 backdrop-blur-sm border border-blue-100/50">
            <div className="flex items-center gap-3 mb-3">
              <div className="h-8 w-8 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center">
                <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
                </svg>
              </div>
              <h4 className="text-lg font-semibold text-blue-900">订单需求数据</h4>
            </div>
            <div className="ml-11">
              <p className="text-blue-800">核心来源系统:OMS(订单管理系统)</p>
            </div>
          </div>
          <div className="bg-white/50 rounded-lg p-4 backdrop-blur-sm border border-blue-100/50">
            <div className="flex items-center gap-3 mb-3">
              <div className="h-8 w-8 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center">
                <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2 1 3 3 3h10c2 0 3-1 3-3V7c0-2-1-3-3-3H7C5 4 4 5 4 7z" />
                </svg>
              </div>
              <h4 className="text-lg font-semibold text-blue-900">生产工单数据</h4>
            </div>
            <div className="ml-11">
              <p className="text-blue-800">核心来源系统:ERP(企业资源计划)</p>
            </div>
          </div>
          <div className="bg-white/50 rounded-lg p-4 backdrop-blur-sm border border-blue-100/50">
            <div className="flex items-center gap-3 mb-3">
              <div className="h-8 w-8 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center">
                <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
                </svg>
              </div>
              <h4 className="text-lg font-semibold text-blue-900">库存数据</h4>
            </div>
            <div className="ml-11">
              <p className="text-blue-800">核心来源系统:WMS(仓储管理系统)</p>
            </div>
          </div>
          <div className="bg-white/50 rounded-lg p-4 backdrop-blur-sm border border-blue-100/50">
            <div className="flex items-center gap-3 mb-3">
              <div className="h-8 w-8 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center">
                <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
                </svg>
              </div>
              <h4 className="text-lg font-semibold text-blue-900">供应商绩效数据</h4>
            </div>
            <div className="ml-11">
              <p className="text-blue-800">核心来源系统:SRM(供应商关系管理)、质量管理系统</p>
            </div>
          </div>
          <div className="bg-white/50 rounded-lg p-4 backdrop-blur-sm border border-blue-100/50">
            <div className="flex items-center gap-3 mb-3">
              <div className="h-8 w-8 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center">
                <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
                </svg>
              </div>
              <h4 className="text-lg font-semibold text-blue-900">产能数据</h4>
            </div>
            <div className="ml-11">
              <p className="text-blue-800">核心来源系统:MES(制造执行系统)、设备监控系统</p>
            </div>
          </div>
        </div>
      );
    }
    return null;
  };
src/components/SceneIntroDialog.tsx
@@ -44,11 +44,66 @@
  const handleStartChat = () => {
    onClose();
    document.body.classList.add('page-transitioning');
    setTimeout(() => {
      window.location.href = `/ai-scene/chat?scene=${scene.chatbotId}`;
    }, 100);
    // ä¾›åº”链全景洞察场景使用专用的聊天页面
    if (scene.chatbotId === 'SCPanoramaInsight') {
      window.location.href = `/supply-chain-chat?key=app-6IZEwVNGfHrlLFlgLauLHtHo`;
    } else {
      window.location.href = `/ai-scene/chat?chatbotId=${scene.chatbotId}`;
    }
  };
  // è§£æžåœºæ™¯èƒŒæ™¯å†…容
  const parseBackground = (background: string): { simple: string } | { intro: string; sections: Array<{ title: string; items: string[] }> } | null => {
    if (!background) return null;
    // æ£€æŸ¥æ˜¯å¦åŒ…含结构化内容(如供应链全景洞察的格式)
    if (background.includes('**') && background.includes(':')) {
      const sections = background.split('\n\n').filter(section => section.trim());
      const intro = sections[0];
      const detailSections = sections.slice(1);
      return {
        intro,
        sections: detailSections.map(section => {
          const lines = section.split('\n');
          const title = lines[0].replace(/\*\*/g, '').replace(':', '');
          const items = lines.slice(1).filter(line => line.trim().startsWith('-')).map(line => line.replace('- ', ''));
          return { title, items };
        })
      };
    }
    return { simple: background };
  };
  // è§£æžä½¿ç”¨è¯´æ˜Žå†…容
  const parseInstructions = (instructions: string): { simple: string } | { intro: string; scenarios: Array<{ title: string; description: string }>; conclusion: string } | null => {
    if (!instructions) return null;
    // æ£€æŸ¥æ˜¯å¦åŒ…含场景格式
    if (instructions.includes('**场景') && instructions.includes(':')) {
      const sections = instructions.split('\n\n').filter(section => section.trim());
      const intro = sections[0];
      const scenarios = sections.slice(1, -1).map(section => {
        const lines = section.split('\n');
        const title = lines[0].replace(/\*\*/g, '').replace(':', '');
        const description = lines[1] || '';
        return { title, description };
      });
      const conclusion = sections[sections.length - 1];
      return {
        intro,
        scenarios,
        conclusion
      };
    }
    return { simple: instructions };
  };
  const backgroundData = parseBackground(scene.background || '');
  const instructionsData = parseInstructions(scene.instructions || '');
  return (
    <AnimatePresence>
@@ -202,10 +257,36 @@
                                }}
                                className="bg-[#1A2547] rounded-lg p-6 border border-[#6ADBFF]/10"
                              >
                                <h3 className="text-lg font-medium text-[#6ADBFF] mb-3">场景背景</h3>
                                <p className="text-gray-300 leading-relaxed">
                                  {scene.background}
                                </p>
                                <h3 className="text-lg font-medium text-[#6ADBFF] mb-4">场景背景</h3>
                                {backgroundData && 'simple' in backgroundData ? (
                                  <p className="text-gray-300 leading-relaxed">
                                    {backgroundData.simple}
                                  </p>
                                ) : backgroundData && 'sections' in backgroundData && (
                                  <div className="space-y-4">
                                    <p className="text-gray-300 leading-relaxed">
                                      {backgroundData.intro}
                                    </p>
                                    <div className="grid gap-4">
                                      {backgroundData.sections.map((section, index) => (
                                        <div key={index} className="bg-[#0F1629] rounded-lg p-4 border border-[#6ADBFF]/5">
                                          <h4 className="text-[#FF6A88] font-medium mb-3 flex items-center">
                                            <div className="w-2 h-2 rounded-full bg-[#FF6A88] mr-2"></div>
                                            {section.title}
                                          </h4>
                                          <ul className="space-y-2">
                                            {section.items.map((item, itemIndex) => (
                                              <li key={itemIndex} className="text-gray-300 text-sm flex items-start">
                                                <div className="w-1 h-1 rounded-full bg-[#6ADBFF]/60 mt-2 mr-3 flex-shrink-0"></div>
                                                <span>{item}</span>
                                              </li>
                                            ))}
                                          </ul>
                                        </div>
                                      ))}
                                    </div>
                                  </div>
                                )}
                              </motion.div>
                              {/* ä½¿ç”¨è¯´æ˜Ž */}
@@ -219,10 +300,40 @@
                                }}
                                className="bg-[#1A2547] rounded-lg p-6 border border-[#6ADBFF]/10"
                              >
                                <h3 className="text-lg font-medium text-[#6ADBFF] mb-3">使用说明</h3>
                                <p className="text-gray-300 leading-relaxed">
                                  {scene.instructions}
                                </p>
                                <h3 className="text-lg font-medium text-[#6ADBFF] mb-4">使用说明</h3>
                                {instructionsData && 'simple' in instructionsData ? (
                                  <p className="text-gray-300 leading-relaxed">
                                    {instructionsData.simple}
                                  </p>
                                ) : instructionsData && 'scenarios' in instructionsData && (
                                  <div className="space-y-4">
                                    <p className="text-gray-300 leading-relaxed">
                                      {instructionsData.intro}
                                    </p>
                                    <div className="grid gap-3">
                                      {instructionsData.scenarios.map((scenario, index) => (
                                        <div key={index} className="bg-[#0F1629] rounded-lg p-4 border border-[#6ADBFF]/5 hover:border-[#6ADBFF]/20 transition-colors duration-300">
                                          <div className="flex items-center mb-2">
                                            <div className="w-6 h-6 rounded-full bg-gradient-to-r from-[#6ADBFF] to-[#5E72EB] flex items-center justify-center mr-3">
                                              <span className="text-white text-xs font-medium">{index + 1}</span>
                                            </div>
                                            <h4 className="text-[#F5A800] font-medium">
                                              {scenario.title}
                                            </h4>
                                          </div>
                                          <p className="text-gray-300 text-sm ml-9">
                                            {scenario.description}
                                          </p>
                                        </div>
                                      ))}
                                    </div>
                                    <div className="mt-4 p-4 bg-[#0F1629] rounded-lg border border-[#6ADBFF]/5">
                                      <p className="text-gray-300 text-sm">
                                        {instructionsData.conclusion}
                                      </p>
                                    </div>
                                  </div>
                                )}
                              </motion.div>
                              {/* æ•°æ®è¯´æ˜ŽåŒºå— - åªåœ¨ç‰¹å®šåœºæ™¯æ˜¾ç¤º */}
@@ -265,7 +376,8 @@
                                  onClose={() => setShowDataPreview(false)}
                                  markdownContent={scene.exampleData}
                                  sceneType={scene.chatbotId === 'RhMYLHI1SZNiX4kl' || scene.chatbotId === 'zO9YQDEHdIApG9zC' ? 'chadan' :
                                           scene.chatbotId === 'JELkWpPLHQfRNhEH' ? 'buliao' : undefined}
                                           scene.chatbotId === 'JELkWpPLHQfRNhEH' ? 'buliao' :
                                           scene.chatbotId === 'SCPanoramaInsight' ? 'panorama' : undefined}
                                />
                              )}
                            </div>
src/components/layout/ClientLayoutContent.tsx
@@ -23,6 +23,7 @@
  const isAIScenePage = pathname === '/ai-scene';
  const isAISceneChatPage = pathname.startsWith('/ai-scene/chat');
  const isChatPage = pathname === '/chat'; // æ·»åŠ èŠå¤©é¡µé¢åˆ¤æ–­
  const isSupplyChainChatPage = pathname === '/supply-chain-chat'; // æ·»åŠ ä¾›åº”é“¾èŠå¤©é¡µé¢åˆ¤æ–­
  // è®¾ç½®å®¢æˆ·ç«¯çŠ¶æ€
  useEffect(() => {
@@ -99,7 +100,7 @@
        <main className={`flex-1 ${isHomePage || isAIScenePage ? '' : 'bg-gradient-to-b from-[var(--ai-surface)] to-white'} pt-0 mt-0`}>
          {children}
        </main>
        {!isLoginPage && !isRegisterPage && !isAISceneChatPage && !isChatPage && (
        {!isLoginPage && !isRegisterPage && !isAISceneChatPage && !isChatPage && !isSupplyChainChatPage && (
          <footer className="relative z-20 bg-gradient-to-br from-[#0A1033] via-[#1E2B63] to-[#131C41] text-white py-10 overflow-hidden">
            {/* ç§‘技感动态背景元素 */}
            <div className="absolute inset-0 overflow-hidden pointer-events-none">