hongjli
2025-04-27 d2ecbec16ab9d7768b8d07a93d22816507d81833
src/app/chat/page.tsx
@@ -1,6 +1,6 @@
'use client';
import { useState, useEffect, useRef, useCallback } from 'react';
import { useState, useEffect, useRef, useCallback, useContext, createContext } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import Image from 'next/image';
@@ -10,6 +10,9 @@
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 });
@@ -272,27 +275,41 @@
// 修改代码块渲染组件,添加更好的类型检测
function CodeBlockRenderer({ language, value }: { language: string; value: string }) {
  // 更精确地检测ECharts代码
  const isEchartsCode = () => {
  // 判断是否是JavaScript代码,检测是否包含图表相关特征
  const isEchartsCode = useCallback(() => {
    if (language !== 'javascript') return false;
    
    // 检查是否包含ECharts特有的配置项
    const hasEchartsConfig =
      value.includes('option =') ||
      value.includes('const option') ||
      value.includes('let option') ||
      value.includes('series') &&
      (value.includes('type:') || value.includes('tooltip:') || value.includes('xAxis:'));
    return hasEchartsConfig;
  };
    return value.includes('option') &&
      (value.includes('series') ||
       value.includes('chart') ||
       value.includes('echarts') ||
       value.includes('xAxis') ||
       value.includes('yAxis'));
  }, [language, value]);
  
  // 如果是 ECharts 代码则渲染图表
  if (isEchartsCode()) {
  // 检查消息是否完整 - 通过父组件传递的isMessageComplete状态
  const isComplete = useContext(MessageCompletionContext);
  // 在消息未完成时显示加载动画
  if (language === 'javascript' && !isComplete) {
    return (
      <div className="w-full bg-gray-50 rounded-md my-4 p-6 text-center">
        <div className="flex flex-col items-center justify-center">
          <div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-gray-400 mb-4"></div>
          <p className="text-gray-500">代码加载中...</p>
          <p className="text-xs text-gray-400 mt-2">等待消息完成后将渲染图表</p>
        </div>
      </div>
    );
  }
  // 消息完成后,如果是图表代码则渲染为图表
  if (isEchartsCode() && isComplete) {
    return <EchartsRenderer code={value} />;
  }
  
  // 否则按普通代码块渲染
  // 普通JavaScript代码或其他语言的代码块,直接显示代码
  return (
    <div className="bg-gray-800 rounded-md my-2 overflow-hidden">
      <div className="flex items-center justify-between px-4 py-2 border-b border-gray-700">
@@ -1004,30 +1021,32 @@
                                <div className="p-3">
                                  <div className="text-gray-800 leading-relaxed">
                                    {mainContent ? (
                                      <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} />;
                                      <MessageCompletionContext.Provider value={isMessageComplete && msg.id === currentMessageId}>
                                        <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>
                                              );
                                            }
                                            return (
                                              <code className={className} {...props}>
                                                {children}
                                              </code>
                                            );
                                          }
                                        }}
                                      >
                                        {mainContent}
                                      </ReactMarkdown>
                                          }}
                                        >
                                          {mainContent}
                                        </ReactMarkdown>
                                      </MessageCompletionContext.Provider>
                                    ) : (
                                      msg.role === 'assistant' && !isMessageComplete ? '处理回复中...' : ''
                                    )}