AI问答组件

import {
defineComponent,
reactive,
ref,
onMounted,
onUnmounted,
nextTick
} from '@vue/composition-api'
// import { useSelector } from '@/utils/bhooks'
import { marked } from 'marked'
import request from '@/utils/request'
import { getHistoryMessages, getConversationMessages } from '@/services/newProductPerformance'
import HistoryPopup from './HistoryPopup'
import './index.scss'
import { trackView } from '../../pages/commandCenter/utils/sensors'

// 配置 marked (4.x 兼容移动端)
marked.setOptions({
breaks: true,
gfm: true
})

// 性能日志 - 记录关键性能指标(生产测试环境都显示)
const perfLog = (...args: any[]) => {
console.log(...args)
}
// 数据日志 - 记录分析数据和最终数据(生产测试环境都显示)
const dataLog = (...args: any[]) => {
console.log(...args)
}

interface Message {
id: string
type: 'user' | 'ai'
content: string
timestamp: number
options?: string[] // 选项列表
streaming?: boolean // 是否正在流式输出
thinkingProcess?: string // 思考过程(保留兼容)
thinkingTime?: number // 思考用时(秒)
showThinking?: boolean // 是否展开思考过程
thinkingStartTime?: number // 开始思考的时间戳
technicalData?: string // 技术数据(outputs.result)
finalResult?: string // 最终结果(outputs.answer)
typeWriterTimer?: any // 打字机效果定时器
}

interface HistoryMessage {
id: string
conversation_id: string
query: string
answer: string
created_at: number
status: string
}

interface HistorySession {
id: string
title: string
lastMessage: string
timestamp: number
}

interface AIRequestBody {
inputs: {
user_query: string
}
conversation_id: string
query: string
files: Array<{
transfer_method: string
upload_file_id: string
type: string
url: string
}>
user: string
}

export default defineComponent({
name: 'AIChat',
props: {
visible: {
type: Boolean,
default: false
},
handleClose: {
type: Function,
default: undefined
}
},
setup(props) {
// const { op } = useSelector(({ op }) => ({ op }))

const state = reactive<{
  messages: Message[]
  inputValue: string
  loading: boolean
  copiedId: string | null
  showHistory: boolean
  historySessions: HistorySession[]
  conversationId: string
  historyMessagesMap: Record<string, HistoryMessage>
  showQuickQuestions: boolean
}>({
  messages: [],
  inputValue: '',
  loading: false,
  copiedId: null,
  showHistory: false,
  conversationId: '',
  historySessions: [],
  historyMessagesMap: {},
  showQuickQuestions: true
})

const chatContainer = ref<HTMLElement>()
const abortControllerRef = ref<AbortController | null>(null)
const timeoutAbortFlag = ref(false) // 标记是否是超时中断
const userAbortFlag = ref(false) // 标记是否是用户主动中断(点击新建对话)
const userScrolled = ref(false) // 标记用户是否手动滚动
const lastScrollTop = ref(0) // 记录上次滚动位置
const lastScrollHeight = ref(0) // 记录上次滚动时的内容高度

// 流式渲染优化 - 为每个请求创建独立的节流器
const throttleTimers = new Map<string, any>()
const pendingContents = new Map<string, string>()
const markdownCache = new Map<string, string>() // Markdown解析缓存
const renderCache = new Map<string, any>() // 渲染结果缓存(非响应式)

// 滚动节流(使用节流而不是防抖,减少触发频率)
let scrollTimeout: any = null
let lastScrollTime = 0 // 上次滚动时间

// 复制消息内容
const copyMessage = (content: string, messageId: string) => {
  // 方法1:尝试使用 Clipboard API
  if (navigator.clipboard && navigator.clipboard.writeText) {
    navigator.clipboard
      .writeText(content)
      .then(() => {
        state.copiedId = messageId
        setTimeout(() => {
          state.copiedId = null
        }, 2000)
      })
      .catch(() => {
        // 降级到方法2
        fallbackCopy(content, messageId)
      })
  } else {
    // 直接使用降级方案
    fallbackCopy(content, messageId)
  }
}

// 降级复制方案
const fallbackCopy = (content: string, messageId: string) => {
  const textarea = document.createElement('textarea')
  textarea.value = content
  textarea.style.position = 'fixed'
  textarea.style.opacity = '0'

  // 检查 document.body 是否存在
  if (!document.body) {
    return
  }

  document.body.appendChild(textarea)
  textarea.select()

  try {
    const success = document.execCommand('copy')
    if (success) {
      state.copiedId = messageId
      setTimeout(() => {
        state.copiedId = null
      }, 2000)
    }
  } catch (err) {
    // 复制失败,静默处理
  } finally {
    // 确保元素存在再移除
    if (document.body.contains(textarea)) {
      document.body.removeChild(textarea)
    }
  }
}

// 刷新消息(重新生成)
const refreshMessage = async (messageId: string) => {
  const messageIndex = state.messages.findIndex((m) => m.id === messageId)
  if (messageIndex === -1 || messageIndex === 0) return

  // 获取用户的问题(前一条消息)
  const userMessage = state.messages[messageIndex - 1]
  if (!userMessage || userMessage.type !== 'user') return

  // 确保不是中断状态,允许正常刷新
  userAbortFlag.value = false

  // 删除旧消息
  state.messages.splice(messageIndex, 1)
  state.loading = true

  try {
    // 确保消息ID唯一性,使用更高精度时间戳
    const newMessageId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}-${performance.now().toString().replace('.', '')}`
    let messageCreated = false
    const thinkingStartTime = Date.now()

    // 流式更新函数 - 立即逐字显示,类似DeepSeek的流畅效果
    const throttledUpdate = (chunk: string) => {
      // 使用 requestAnimationFrame 优化更新,提高流畅度
      requestAnimationFrame(() => {
        if (!messageCreated) {
          // 双重检查:确保消息不存在才创建,避免重复
          const existingMessage = state.messages.find((m) => m.id === newMessageId)
          if (existingMessage) {
            // 消息已存在,直接更新
            if (existingMessage.typeWriterTimer) {
              clearTimeout(existingMessage.typeWriterTimer)
              existingMessage.typeWriterTimer = null
            }
            existingMessage.content = chunk
            existingMessage.finalResult = chunk
            messageCreated = true
            return
          }

          const newAiMessage: Message = {
            id: newMessageId,
            type: 'ai',
            content: chunk,
            timestamp: Date.now(),
            streaming: true,
            showThinking: true,
            thinkingStartTime,
            finalResult: chunk
          }
          state.messages.splice(messageIndex, 0, newAiMessage)
          messageCreated = true
          // 创建流式消息后立即隐藏loading,避免重复显示
          state.loading = false
          scrollToBottom(false, true)
        } else {
          const message = state.messages.find(
            (m) => m.id === newMessageId
          )
          if (message) {
            // 清理定时器
            if (message.typeWriterTimer) {
              clearTimeout(message.typeWriterTimer)
              message.typeWriterTimer = null
            }
            // 直接更新,立即显示
            message.content = chunk
            message.finalResult = chunk
          }
        }
      })
    }

    const result = await callAIApiStream(
      userMessage.content,
      throttledUpdate,
      (errorContent) => {
        // 如果是用户主动中断(点击新建对话),不更新错误信息
        if (userAbortFlag.value) {
          return
        }

        // 错误处理:立即结束流式状态
        const streamingMessage = state.messages.find(m => m.type === 'ai' && m.streaming)
        if (streamingMessage) {
          streamingMessage.streaming = false
          streamingMessage.content = errorContent
        }
      }
    )

    // 查找已存在的消息(可能在流式更新时已创建)
    let message = state.messages.find((m) => m.id === newMessageId)
    const thinkingTime = result?.thinkingTime || 0

    // 如果消息已创建(messageCreated为true)或找到了消息,就更新它,不要创建新的
    if (messageCreated || message) {
      // 如果messageCreated为true但找不到消息,尝试再次查找(可能Vue响应式更新延迟)
      if (!message && messageCreated) {
        message = state.messages.find((m) => m.id === newMessageId)
      }

      if (message) {
        // 更新已存在的消息
        message.streaming = false
        message.thinkingTime = thinkingTime
        message.technicalData = result?.technicalData || ''
        message.finalResult = result?.finalResult || ''

        // 清理打字机定时器
        if (message.typeWriterTimer) {
          clearTimeout(message.typeWriterTimer)
          message.typeWriterTimer = null
        }
      }
      // 如果messageCreated为true但找不到消息,不创建新消息,避免重复
    } else {
      // 只有在既没有创建也没有找到消息时,才创建新消息(超时等情况)
      if (result?.content) {
        const newAiMessage: Message = {
          id: newMessageId,
          type: 'ai',
          content: result.content,
          timestamp: Date.now(),
          streaming: false,
          thinkingTime,
          showThinking: true,
          thinkingStartTime,
          technicalData: result.technicalData || '',
          finalResult: result.finalResult || ''
        }
        state.messages.splice(messageIndex, 0, newAiMessage)
      } else {
        // 如果是用户主动中断(点击新建对话),不显示错误信息
        if (userAbortFlag.value) {
          return
        }

        const newAiMessage: Message = {
          id: newMessageId,
          type: 'ai',
          content: '抱歉,服务暂时不可用,请稍后再试。',
          timestamp: Date.now(),
          streaming: false,
          showThinking: true,
          thinkingStartTime
        }
        state.messages.splice(messageIndex, 0, newAiMessage)
        // 未收到任何响应,显示默认提示
      }
    }

    // refreshMessage 中不记录性能指标(性能指标在 callAIApiStream 中记录)
  } catch (error: any) {
    // 刷新失败,静默处理

    // 如果是用户主动中断(点击新建对话),不显示错误信息
    if (userAbortFlag.value) {
      return
    }

    // 根据错误类型提供不同的错误信息
    let errorContent = '抱歉,服务暂时不可用,请稍后再试。'

    if (error.message?.includes('timeout') || error.code === 'ECONNABORTED') {
      errorContent = '抱歉,请求超时,请稍后重试。'
    } else if (error.message?.includes('Network Error') || error.code === 'NETWORK_ERROR') {
      errorContent = '抱歉,网络连接失败,请检查网络设置后重试。'
    } else if (error.response?.status >= 500) {
      errorContent = '抱歉,服务器暂时不可用,请稍后重试。'
    } else if (error.response?.status === 401 || error.response?.status === 403) {
      errorContent = '抱歉,认证失败,请重新登录后重试。'
    }

    const errorMessage: Message = {
      id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}-${performance.now().toString().replace('.', '')}`,
      type: 'ai',
      content: errorContent,
      timestamp: Date.now()
    }
    state.messages.splice(messageIndex, 0, errorMessage)
  } finally {
    state.loading = false
    debugLoadingState()
    scrollToBottom()
  }
}

// 点赞/点踩
// const likeMessage = (messageId: string, isLike: boolean) => {
//   const message = state.messages.find((m) => m.id === messageId)
//   if (message) {
//     // 记录反馈(实际项目中发送到后端)
//     console.log(`消息 ${messageId} ${isLike ? '点赞' : '点踩'}`)
//   }
// }

// 渲染 Markdown 内容
const renderContent = (content: string, isStreaming?: boolean) => {
  if (!content) return null

  // 流式时统一使用纯文本显示,保持 DOM 结构一致,避免闪烁
  // 流式结束后再解析 Markdown,获得完整样式
  if (isStreaming) {
    // 使用内容hash作为缓存key(前200字符+长度),避免内存过大
    const hashKey = content.length > 200
      ? `${content.substring(0, 200)}_${content.length}`
      : content
    const cacheKey = `streaming_${hashKey}`
    const cached = renderCache.get(cacheKey)
    if (cached) return cached

    // 流式时统一使用纯文本,保持 DOM 结构一致
    // 使用 pre-wrap 保持格式,但避免频繁解析导致的闪烁
    const result = (
      <div class="streaming-text">
        {content}
      </div>
    )
    renderCache.set(cacheKey, result)
    return result
  }

  try {
    // 使用内容hash作为缓存key(简单hash,避免内存过大)
    const cacheKey = content.length > 1000 ? content.substring(0, 100) + content.length : content
    let html = markdownCache.get(cacheKey)

    if (!html) {
      html = marked(content) as string

      // 为表格添加滚动容器和优化
      html = html.replace(/<table>/gi, '<div class="table-wrapper"><table>')
      html = html.replace(/<\/table>/gi, '</table></div>')

      // 优化表格列宽 - 检测数字列
      html = html.replace(/<td([^>]*)>(\d+)<\/td>/gi, '<td$1 data-numeric="true">$2</td>')
      html = html.replace(/<th([^>]*)>(\d+)<\/th>/gi, '<th$1 data-numeric="true">$2</th>')

      // 缓存结果(限制缓存大小,避免内存泄漏)
      if (markdownCache.size > 50) {
        const firstKey = markdownCache.keys().next().value
        markdownCache.delete(firstKey)
      }
      markdownCache.set(cacheKey, html)
    }

    const result = (
      <div class="markdown-content" domPropsInnerHTML={html}></div>
    )
    // 使用非响应式缓存,避免无限更新循环
    if (renderCache.size > 100) {
      const firstKey = renderCache.keys().next().value
      renderCache.delete(firstKey)
    }
    renderCache.set(cacheKey, result)
    return result
  } catch (error) {
    // Markdown 渲染错误,降级处理
    // 降级:使用简单格式化显示
    const lines = content.split('\n')
    return (
      <div class="formatted-content">
        {lines.map((line, idx) => (
          <div key={idx} style={{ marginBottom: '8px' }}>
            {line || <br />}
          </div>
        ))}
      </div>
    )
  }
}

// 切换思考过程显示
const toggleThinking = (messageId: string) => {
  const index = state.messages.findIndex((m) => m.id === messageId)
  if (index !== -1) {
    const message = state.messages[index]
    // 使用 Vue 2 响应式更新
    state.messages.splice(index, 1, {
      ...message,
      showThinking: !message.showThinking
    })
  }
}

// 流式调用 AI 接口
const callAIApiStream = async (
  question: string,
  onChunk: (chunk: string) => void,
  onError?: (error: string) => void
) => {
  const apiPath = 'ipdHwCockpit/ai/streaming-messages'

  // SSE 流式处理状态(提升到 try 外部)
  let accumulatedContent = ''
  let technicalData = '' // 技术数据(outputs.result)
  let finalResult = '' // 最终结果(outputs.answer)
  let displayedLength = 0 // 已显示的长度,用于逐字显示
  let currentEvent = ''
  let buffer = ''
  let lastResponseLength = 0
  let hasContentNode = false // 是否收到内容节点
  let startTime = Date.now() // 请求开始时间
  let firstCharTime = 0 // 收到第一个字符的时间
  let renderStartTime = 0 // 开始渲染的时间
  let timeoutCheckInterval: any = null
  let typeWriterTimer: any = null // 打字机效果定时器

  try {
    // 清理之前的 AbortController
    if (abortControllerRef.value) {
      abortControllerRef.value.abort()
    }
    // 创建新的 AbortController
    abortControllerRef.value = new AbortController()

    // 重置显示状态
    displayedLength = 0
    accumulatedContent = ''
    technicalData = ''
    finalResult = ''
    lastResponseLength = 0
    hasContentNode = false
    firstCharTime = 0
    renderStartTime = 0
    if (typeWriterTimer) {
      clearTimeout(typeWriterTimer)
      typeWriterTimer = null
    }

    const userId = localStorage.getItem('usercode') || 'W9065236'

    // 构建请求体
    const requestBody: AIRequestBody = {
      inputs: {
        user_query: question
      },
      conversation_id: state.conversationId,
      query: question,
      files: [
        {
          transfer_method: '',
          upload_file_id: '',
          type: '',
          url: ''
        }
      ],
      user: userId
    }

    // 记录请求开始时间(startTime 已在外部定义,用于性能分析)

    const processStreamData = (responseText: string) => {
      // 只处理新增的部分
      const newChunk = responseText.substring(lastResponseLength)
      lastResponseLength = responseText.length
      buffer += newChunk

      const lines = buffer.split('\n')
      buffer = lines.pop() || ''

      for (const line of lines) {
        const trimmedLine = line.trim()
        if (!trimmedLine) continue

        if (trimmedLine.startsWith('event:')) {
          currentEvent = trimmedLine.substring(6).trim()
          continue
        }

        if (trimmedLine.startsWith('data:')) {
          try {
            const jsonStr = trimmedLine.substring(5).trim()
            if (!jsonStr || jsonStr === '[DONE]') continue

            if (jsonStr.startsWith('Error:')) {
              const errorMsg = jsonStr.substring(6).trim()

              // 如果是用户主动中断(点击新建对话),不显示错误信息
              if (userAbortFlag.value) {
                return
              }

              // 根据错误类型提供不同的提示
              if (errorMsg.includes('credentials is not initialized')) {
                accumulatedContent = '抱歉,AI服务配置异常,请联系管理员检查模型凭据设置。'
              } else if (errorMsg.includes('timeout') || errorMsg.includes('超时')) {
                accumulatedContent = '抱歉,AI服务响应超时,请稍后重试。'
              } else if (errorMsg.includes('network') || errorMsg.includes('网络')) {
                accumulatedContent = '抱歉,网络连接异常,请检查网络设置后重试。'
              } else if (errorMsg.includes('server') || errorMsg.includes('服务器')) {
                accumulatedContent = '抱歉,服务器暂时不可用,请稍后重试。'
              } else {
                accumulatedContent = `服务异常:${errorMsg}`
              }

              onChunk(accumulatedContent)
              return
            }

            let data
            try {
              data = JSON.parse(jsonStr)
            } catch (parseError) {
              continue
            }

            if (data.conversation_id && !state.conversationId) {
              state.conversationId = data.conversation_id
            }

            const eventType = currentEvent || data.event || ''

            if (eventType === 'message' || eventType === 'agent_message') {
              // message 事件包含最终结果,逐字显示
              const answer = data.answer || ''
              if (answer) {
                // 记录收到第一个字符的时间
                if (firstCharTime === 0 && answer.length > 0) {
                  firstCharTime = Date.now()
                  const firstCharResponseTime = firstCharTime - startTime
                  perfLog('⏱️ 请求到首字渲染时间:', `${firstCharResponseTime}ms`)
                  // 记录开始渲染时间
                  if (renderStartTime === 0) {
                    renderStartTime = performance.now()
                  }
                }

                // 累积到 finalResult
                finalResult += answer
                accumulatedContent = finalResult

                // 如果 finalResult 变长了,继续逐字显示新增部分
                if (finalResult.length > displayedLength) {
                  // 逐字显示:从已显示位置开始,逐字符显示新内容
                  const getCurrentFinalResult = () => finalResult

                  const displayNextChunk = () => {
                    const currentFinalResult = getCurrentFinalResult()
                    const currentLength = currentFinalResult.length

                    // 关键:只有在有新数据时才继续显示,避免大块渲染
                    if (displayedLength < currentLength) {
                      // 检测是否包含表格,优化渲染策略
                      const hasTable = currentFinalResult.includes('|') || currentFinalResult.includes('<table')

                      // 计算未显示的数据量
                      const pendingLength = currentLength - displayedLength

                      // 动态调整显示速度:类似DeepSeek的流畅效果
                      let chunkSize = 1
                      let delay = 5 // 减少延迟,提高流畅度

                      if (hasTable) {
                        // 表格内容:根据未显示数据量动态调整
                        if (pendingLength > 100) {
                          chunkSize = 15
                          delay = 8
                        } else if (pendingLength > 50) {
                          chunkSize = 8
                          delay = 8
                        } else {
                          chunkSize = 4
                          delay = 10
                        }
                      } else if (currentLength > 1000) {
                        // 长文本:根据未显示数据量动态调整
                        if (pendingLength > 100) {
                          chunkSize = 12
                          delay = 5
                        } else if (pendingLength > 50) {
                          chunkSize = 6
                          delay = 5
                        } else {
                          chunkSize = 2
                          delay = 6
                        }
                      } else {
                        // 普通文本:根据未显示数据量动态调整,追求流畅度
                        if (pendingLength > 50) {
                          chunkSize = 8
                          delay = 4
                        } else if (pendingLength > 20) {
                          chunkSize = 4
                          delay = 4
                        } else {
                          chunkSize = 1
                          delay = 5
                        }
                      }

                      // 只显示少量字符,避免大块渲染
                      const nextLength = Math.min(displayedLength + chunkSize, currentLength)
                      const chunkToShow = currentFinalResult.substring(0, nextLength)

                      displayedLength = nextLength

                      // 使用 requestAnimationFrame 优化Vue更新,类似DeepSeek的流畅效果
                      requestAnimationFrame(() => {
                        onChunk(chunkToShow)
                        // 流式更新时不自动滚动,只在流式结束时滚动一次
                        // 避免频繁滚动导致屏幕晃动
                      })

                      // 重新获取最新的长度(可能已经增长)
                      const updatedLength = getCurrentFinalResult().length

                      // 如果还有未显示的内容,继续显示
                      if (displayedLength < updatedLength) {
                        // 清除之前的定时器(如果有),确保使用最新延迟
                        if (typeWriterTimer) {
                          clearTimeout(typeWriterTimer)
                          typeWriterTimer = null
                        }
                        // 继续逐字显示
                        typeWriterTimer = setTimeout(() => {
                          typeWriterTimer = null
                          try {
                            displayNextChunk()
                          } catch (error: any) {
                            // 静默处理渲染错误
                          }
                        }, delay)
                      } else {
                        // 当前已接收的数据已全部显示完
                        // 暂停渲染,等待新数据到达
                        if (typeWriterTimer) {
                          clearTimeout(typeWriterTimer)
                          typeWriterTimer = null
                        }
                        // 使用稍长的延迟检查新数据,避免频繁检查
                        typeWriterTimer = setTimeout(() => {
                          typeWriterTimer = null
                          try {
                            // 再次检查是否有新数据到达
                            const checkLength = getCurrentFinalResult().length
                            if (displayedLength < checkLength) {
                              // 有新数据到达,立即继续逐字显示
                              displayNextChunk()
                            } else {
                              // 确实没有新数据了,完全停止渲染
                              scrollToBottom(false, true)
                            }
                          } catch (error: any) {
                            // 静默处理检查错误
                          }
                        }, delay * 2) // 使用2倍延迟检查新数据
                      }
                    } else {
                      // 没有新数据,完全停止渲染
                      if (typeWriterTimer) {
                        clearTimeout(typeWriterTimer)
                        typeWriterTimer = null
                      }
                    }
                  }

                  // 如果有定时器在运行,检查它是否在等待新数据
                  // 如果正在等待新数据,立即清除并开始显示,避免延迟
                  if (typeWriterTimer) {
                    // 清除等待中的定时器,立即开始显示新数据
                    clearTimeout(typeWriterTimer)
                    typeWriterTimer = null
                  }
                  // 立即开始显示,确保新数据到达时能及时响应
                  displayNextChunk()
                }
              }
            } else if (eventType === 'message_replace') {
              accumulatedContent = data.answer || ''
              onChunk(accumulatedContent)
              // 替换时立即滚动
              scrollToBottom(true)
            } else if (eventType === 'node_finished') {
              if (data.data?.status === 'succeeded' && data.data?.outputs) {
                const outputs = data.data.outputs
                const nodeType = data.data.node_type

                // 标记是否收到内容节点
                const contentNodes = [
                  'llm',
                  'tool',
                  'code',
                  'template-transform',
                  'http-request'
                ]
                if (contentNodes.includes(nodeType)) {
                  hasContentNode = true
                }

                // 分别提取技术数据和最终结果
                const currentTechnical = outputs.result || ''
                const currentFinal = outputs.answer || ''

                // 只累积技术数据,不触发显示
                // 因为 message 事件已经在处理显示了,避免重复输出
                if (currentTechnical) {
                  technicalData += currentTechnical
                }
                // 如果 message 事件不存在,才累积 finalResult(兼容旧接口)
                // 但不再触发显示,避免与 message 事件重复
                if (currentFinal && !accumulatedContent) {
                  finalResult += currentFinal
                  accumulatedContent = finalResult
                }
              }
            } else if (eventType === 'error') {
              const errorMsg = data.message || '请求失败,请稍后重试'
              // 关键错误,静默处理

              // 如果是用户主动中断(点击新建对话),不显示错误信息
              if (userAbortFlag.value) {
                return
              }

              // 特殊处理模型凭据未初始化错误
              if (errorMsg.includes('credentials is not initialized')) {
                accumulatedContent = '抱歉,AI服务配置异常,请联系管理员检查模型凭据设置。'
              } else if (errorMsg.includes('Model') && errorMsg.includes('not available')) {
                accumulatedContent = '抱歉,AI模型暂时不可用,请稍后重试或联系管理员。'
              } else {
                accumulatedContent = `抱歉,服务异常:${errorMsg}`
              }

              onChunk(accumulatedContent)
              // 错误时立即滚动到底部
              scrollToBottom(false)

              // 立即中断请求,防止继续处理
              if (abortControllerRef.value) {
                abortControllerRef.value.abort()
              }

              // 标记为错误状态,不再处理后续数据
              hasContentNode = true

              // 立即结束流式状态,防止继续显示"正在生成回答"
              if (onError) {
                onError(accumulatedContent)
              }

              return
            } else if (
              eventType === 'message_end' ||
              eventType === 'workflow_finished'
            ) {
              if (accumulatedContent.length === 0) {
                if (!hasContentNode) {
        // 后台工作流未执行到内容节点
                  accumulatedContent =
                    '抱歉,AI服务响应超时,请稍后重试或联系管理员。'
                  onChunk(accumulatedContent)
                }
              }
            } else if (eventType === 'ping') {
              // 静默处理
            } else if (
              eventType === 'node_started' ||
              eventType === 'workflow_started'
            ) {
              // 静默处理
            }
          } catch (e) {
              // 静默处理解析错误
          }
        }
      }
    }

    // 启动超时检测(300秒无内容节点则中断)
    timeoutAbortFlag.value = false
    timeoutCheckInterval = setInterval(() => {
      const elapsed = Date.now() - startTime

      // 300秒无内容节点超时
      if (
        elapsed > 300000 &&
        !hasContentNode &&
        accumulatedContent.length === 0
      ) {
        // 300秒未收到内容节点,超时中断
        timeoutAbortFlag.value = true
        clearInterval(timeoutCheckInterval)
        if (abortControllerRef.value) {
          abortControllerRef.value.abort()
        }
      }

      // 300秒总超时(兜底保护)
      if (elapsed > 300000) {
        // 300秒总超时,兜底保护
        timeoutAbortFlag.value = true
        clearInterval(timeoutCheckInterval)
        if (abortControllerRef.value) {
          abortControllerRef.value.abort()
        }
      }
    }, 5000) // 每5秒检查一次

    try {
      await request.post(apiPath, requestBody, {
        timeout: 300000, // AI 流式请求超时 300s
        responseType: 'text',
        headers: {
          Accept: 'text/event-stream',
          'Cache-Control': 'no-cache'
        },
        signal: abortControllerRef.value.signal,
        onDownloadProgress: (progressEvent) => {
          const response =
            (progressEvent.currentTarget as any)?.response ||
            (progressEvent.target as any)?.response
          if (response) {
            processStreamData(response)
          }
        }
      })
    } finally {
      clearInterval(timeoutCheckInterval)
      // 请求完成后重置 AbortController
      abortControllerRef.value = null
      // 注意:不清理 typeWriterTimer,让它继续完成逐字显示
    }

    const thinkingTime = Math.round((Date.now() - startTime) / 1000)

    // 计算首字到最终渲染时间
    if (firstCharTime > 0) {
      const finalRenderTime = Date.now()
      const firstCharToFinalTime = finalRenderTime - firstCharTime
      perfLog('⏱️ 首字到最终渲染时间:', `${firstCharToFinalTime}ms`)
    }

    // 记录分析数据和最终数据
    if (technicalData || finalResult) {
      dataLog('📊 分析数据 (technicalData):', technicalData)
      dataLog('📝 最终数据 (finalResult):', finalResult)
    }

    return {
      content: accumulatedContent,
      thinkingTime,
      technicalData,
      finalResult
    }
  } catch (error: any) {
    clearInterval(timeoutCheckInterval) // 清理定时器

    const thinkingTime = Math.round((Date.now() - startTime) / 1000)

    // 关键错误,静默处理

    // 如果是用户主动中断(点击新建对话),不显示错误信息
    if (userAbortFlag.value) {
      return {
        content: '',
        thinkingTime,
        technicalData: '',
        finalResult: ''
      }
    }

    if (error.name === 'AbortError') {
      // 检查是否是超时导致的中断
      if (timeoutAbortFlag.value) {
        return {
          content:
            '抱歉,AI服务响应超时,请稍后重试或联系管理员。\n\n可能原因:后台工作流未执行到内容节点(tool/llm),请检查 Dify 工作流配置。',
          thinkingTime,
          technicalData: '',
          finalResult: ''
        }
      }
      return {
        content: '',
        thinkingTime,
        technicalData: '',
        finalResult: ''
      }
    }

    // axios 超时
    if (
      error.code === 'ECONNABORTED' ||
      error.message?.includes('timeout')
    ) {
      // 请求超时,静默处理
      return {
        content: '抱歉,AI服务响应超时,请稍后重试。',
        thinkingTime,
        technicalData: '',
        finalResult: ''
      }
    }

    // 处理网络连接错误
    if (error.code === 'NETWORK_ERROR' || error.message?.includes('Network Error')) {
      // 网络连接失败,静默处理
      return {
        content: '抱歉,网络连接失败,请检查网络设置后重试。',
        thinkingTime,
        technicalData: '',
        finalResult: ''
      }
    }

    // 处理服务器错误
    if (error.response?.status >= 500) {
      // 服务器内部错误,静默处理
      // 尝试从错误响应中提取更详细的错误信息
      let errorMsg = '抱歉,服务器暂时不可用,请稍后重试。'
      if (error.response.data) {
        if (typeof error.response.data === 'string') {
          errorMsg = `服务器错误: ${error.response.data}`
        } else if (error.response.data.message) {
          errorMsg = `服务器错误: ${error.response.data.message}`
        } else if (error.response.data.error) {
          errorMsg = `服务器错误: ${error.response.data.error}`
        }
      }
      return {
        content: errorMsg,
        thinkingTime,
        technicalData: '',
        finalResult: ''
      }
    }

    // 处理认证错误
    if (error.response?.status === 401 || error.response?.status === 403) {
      // 认证失败,静默处理
      return {
        content: '抱歉,认证失败,请重新登录后重试。',
        thinkingTime,
        technicalData: '',
        finalResult: ''
      }
    }

    // 处理模型凭据相关错误
    if (error.message?.includes('credentials is not initialized')) {
      // 模型凭据未初始化,静默处理
      return {
        content: '抱歉,AI服务配置异常,请联系管理员检查模型凭据设置。',
        thinkingTime,
        technicalData: '',
        finalResult: ''
      }
    }

    // 处理请求被取消
    if (error.name === 'CanceledError' || error.code === 'ERR_CANCELED') {
      // 请求被取消,静默处理
      return {
        content: '',
        thinkingTime,
        technicalData: '',
        finalResult: ''
      }
    }

    // 处理其他未知错误,静默处理
    return {
      content: '抱歉,服务暂时不可用,请稍后再试。',
      thinkingTime,
      technicalData: '',
      finalResult: ''
    }
  } finally {
    // 确保清理定时器和重置状态
    clearInterval(timeoutCheckInterval)
    // 注意:不清理 typeWriterTimer,让它继续完成逐字显示
    // 只有在用户主动中断或组件卸载时才清理
    if (abortControllerRef.value) {
      abortControllerRef.value = null
    }
    // 清理完成
  }
}

// 检测用户是否手动滚动
const handleScroll = () => {
  const container = chatContainer.value || document.querySelector('.chat-messages') as HTMLElement
  if (!container) return

  const currentScrollTop = container.scrollTop
  const scrollHeight = container.scrollHeight
  const clientHeight = container.clientHeight
  const distanceFromBottom = scrollHeight - currentScrollTop - clientHeight

  // 如果用户向上滚动(scrollTop增加且不在底部),标记为用户手动滚动
  // 排除内容增长导致的自动滚动到底部的情况
  if (currentScrollTop > lastScrollTop.value && currentScrollTop > 0 && distanceFromBottom > 50) {
    userScrolled.value = true
  }

  // 如果用户滚动到底部附近(50px内),恢复自动滚动
  if (distanceFromBottom <= 50) {
    userScrolled.value = false
  }

  lastScrollTop.value = currentScrollTop
}

// 滚动到底部 - 智能滚动(使用节流减少触发频率)
const scrollToBottom = (smooth: boolean = true, force: boolean = false) => {
  // 检查组件是否可见
  if (!props.visible) {
    return
  }

  // 如果用户手动滚动且不是强制滚动,则不自动滚动
  if (!force && userScrolled.value) {
    return
  }

  // 使用节流,限制滚动频率(至少间隔500ms才滚动一次)
  const now = Date.now()
  const throttleDelay = 500 // 节流间隔500ms,大幅减少滚动
  if (!force && now - lastScrollTime < throttleDelay) {
    // 如果距离上次滚动时间太短,取消本次滚动
    if (scrollTimeout) {
      clearTimeout(scrollTimeout)
    }
    // 延迟执行,确保最后一次滚动能执行
    scrollTimeout = setTimeout(() => {
      scrollToBottom(smooth, force)
    }, throttleDelay - (now - lastScrollTime))
    return
  }

  // 清除之前的定时器
  if (scrollTimeout) {
    clearTimeout(scrollTimeout)
  }

  // 使用 requestAnimationFrame 优化滚动性能
  requestAnimationFrame(() => {
    // 使用DOM查询获取滚动容器
    const getScrollContainer = () => {
      // 先尝试使用ref
      if (chatContainer.value) {
        return chatContainer.value
      }

      // 如果ref不存在,使用DOM查询
      const container = document.querySelector('.chat-messages')
      if (container) {
        return container as HTMLElement
      }

      return null
    }

    // 使用更稳定的滚动方式
    const container = getScrollContainer()
    if (container) {
      const scrollHeight = container.scrollHeight
      const currentScrollTop = container.scrollTop
      const clientHeight = container.clientHeight
      const distanceFromBottom = scrollHeight - currentScrollTop - clientHeight

      // 检测内容高度是否明显增加(至少增加200px才滚动,大幅减少触发)
      // 或者距离底部超过300px时才滚动
      const heightDiff = scrollHeight - lastScrollHeight.value
      if (!force && heightDiff < 200 && distanceFromBottom < 300) {
        // 内容增加不明显且距离底部不远,不滚动
        return
      }

      // 强制滚动时,重置用户滚动状态
      if (force) {
        userScrolled.value = false
      } else if (userScrolled.value) {
        // 再次检查用户是否手动滚动(双重检查)
        // 如果不在底部附近,不自动滚动
        if (distanceFromBottom > 50) {
          return
        }
        // 如果在底部附近,恢复自动滚动
        userScrolled.value = false
      }

      // 流式更新时始终使用立即滚动,避免平滑滚动导致的晃动
      // 只在非流式且非强制时使用平滑滚动
      const isStreaming = state.messages.some(m => m.type === 'ai' && m.streaming)
      if (smooth && !force && !isStreaming) {
        // 平滑滚动(仅非流式时)
        container.scrollTo({
          top: scrollHeight,
          behavior: 'smooth'
        })
      } else {
        // 立即滚动,使用 scrollTop 避免重排和晃动
        container.scrollTop = scrollHeight
      }

      // 更新滚动位置和高度记录
      lastScrollTop.value = container.scrollTop
      lastScrollHeight.value = scrollHeight
      lastScrollTime = now
    }
  })
}


// 发送消息
const sendMessage = async () => {
  if (!state.inputValue.trim() || state.loading) return

  // 确保不是中断状态,允许正常提问
  userAbortFlag.value = false

  // 重置滚动状态,新消息时允许自动滚动
  userScrolled.value = false

  // 隐藏快速问题区域
  state.showQuickQuestions = false

  // 检查网络状态
  if (!checkNetworkStatus()) {
    const errorMessage: Message = {
      id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}-${performance.now().toString().replace('.', '')}`,
      type: 'ai',
      content: '抱歉,网络连接已断开,请检查网络设置后重试。',
      timestamp: Date.now()
    }
    state.messages.push(errorMessage)
    return
  }

  const userMessage: Message = {
    id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}-${performance.now().toString().replace('.', '')}`,
    type: 'user',
    content: state.inputValue.trim(),
    timestamp: Date.now()
  }

  state.messages.push(userMessage)
  const question = state.inputValue
  state.inputValue = ''
  state.loading = true
  scrollToBottom(false)

  try {
    // 确保消息ID唯一性,使用更高精度时间戳
    const aiMessageId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}-${performance.now().toString().replace('.', '')}`
    let messageCreated = false

    const thinkingStartTime = Date.now()

    // 流式更新函数 - 立即逐字显示,类似DeepSeek的流畅效果
    const throttledUpdate = (chunk: string) => {
      // 使用 requestAnimationFrame 优化更新,提高流畅度
      requestAnimationFrame(() => {
        if (!messageCreated) {
          // 双重检查:确保消息不存在才创建,避免重复
          const existingMessage = state.messages.find((m) => m.id === aiMessageId)
          if (existingMessage) {
            // 消息已存在,直接更新
            if (existingMessage.typeWriterTimer) {
              clearTimeout(existingMessage.typeWriterTimer)
              existingMessage.typeWriterTimer = null
            }
            existingMessage.content = chunk
            existingMessage.finalResult = chunk
            messageCreated = true
            return
          }

          const aiMessage: Message = {
            id: aiMessageId,
            type: 'ai',
            content: chunk,
            timestamp: Date.now(),
            streaming: true,
            showThinking: true,
            thinkingStartTime,
            finalResult: chunk
          }
          state.messages.push(aiMessage)
          messageCreated = true
          // 创建流式消息后立即隐藏loading,避免重复显示
          state.loading = false
          scrollToBottom(false, true)
        } else {
          // 更新消息内容
          const message = state.messages.find((m) => m.id === aiMessageId)
          if (message) {
            // 清理定时器
            if (message.typeWriterTimer) {
              clearTimeout(message.typeWriterTimer)
              message.typeWriterTimer = null
            }
            // 直接更新,立即显示
            message.content = chunk
            message.finalResult = chunk
          }
        }
      })
    }

    const result = await callAIApiStream(question, throttledUpdate, (errorContent) => {
      // 如果是用户主动中断(点击新建对话),不更新错误信息
      if (userAbortFlag.value) {
        return
      }

      // 错误处理:立即结束流式状态
      const streamingMessage = state.messages.find(m => m.type === 'ai' && m.streaming)
      if (streamingMessage) {
        streamingMessage.streaming = false
        streamingMessage.content = errorContent
        // 流式错误:已结束流式状态
      }
    })

    // 流式结束,标记完成
    const thinkingTime = result?.thinkingTime || 0

    // 数据日志已在 callAIApiStream 中记录,此处不重复记录

    // 查找已存在的消息(可能在流式更新时已创建)
    let message = state.messages.find((m) => m.id === aiMessageId)

    // 如果消息已创建(messageCreated为true)或找到了消息,就更新它,不要创建新的
    if (messageCreated || message) {
      // 如果messageCreated为true但找不到消息,尝试再次查找(可能Vue响应式更新延迟)
      if (!message && messageCreated) {
        message = state.messages.find((m) => m.id === aiMessageId)
      }

      if (message) {
        // 更新已存在的消息
        message.streaming = false
        message.thinkingTime = thinkingTime
        message.technicalData = result?.technicalData || ''
        message.finalResult = result?.finalResult || ''

        // 清理打字机定时器
        if (message.typeWriterTimer) {
          clearTimeout(message.typeWriterTimer)
          message.typeWriterTimer = null
        }
      }
      // 如果messageCreated为true但找不到消息,不创建新消息,避免重复
    } else {
      // 只有在既没有创建也没有找到消息时,才创建新消息(超时等情况)
      if (result?.content) {
        const aiMessage: Message = {
          id: aiMessageId,
          type: 'ai',
          content: result.content,
          timestamp: Date.now(),
          streaming: false,
          thinkingTime,
          showThinking: true,
          thinkingStartTime,
          technicalData: result.technicalData || '',
          finalResult: result.finalResult || ''
        }
        state.messages.push(aiMessage)
      } else {
        // 如果是用户主动中断(点击新建对话),不显示错误信息
        if (userAbortFlag.value) {
          return
        }

        // 兜底:如果连 result 都没有,显示默认提示
        const aiMessage: Message = {
          id: aiMessageId,
          type: 'ai',
          content: '抱歉,服务暂时不可用,请稍后再试。',
          timestamp: Date.now(),
          streaming: false,
          showThinking: true,
          thinkingStartTime
        }
        state.messages.push(aiMessage)
        // 未收到任何响应,显示默认提示
      }
    }

  } catch (error: any) {
    // 发送失败,静默处理

    // 如果是用户主动中断(点击新建对话),不显示错误信息
    if (userAbortFlag.value) {
      return
    }

    // 根据错误类型提供不同的错误信息
    let errorContent = '抱歉,服务暂时不可用,请稍后再试。'

    if (error.message?.includes('timeout') || error.code === 'ECONNABORTED') {
      errorContent = '抱歉,请求超时,请稍后重试。'
    } else if (error.message?.includes('Network Error') || error.code === 'NETWORK_ERROR') {
      errorContent = '抱歉,网络连接失败,请检查网络设置后重试。'
    } else if (error.response?.status >= 500) {
      errorContent = '抱歉,服务器暂时不可用,请稍后重试。'
    } else if (error.response?.status === 401 || error.response?.status === 403) {
      errorContent = '抱歉,认证失败,请重新登录后重试。'
    } else if (error.message?.includes('credentials is not initialized')) {
      errorContent = '抱歉,AI服务配置异常,请联系管理员检查模型凭据设置。'
    }

    const errorMessage: Message = {
      id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}-${performance.now().toString().replace('.', '')}`,
      type: 'ai',
      content: errorContent,
      timestamp: Date.now()
    }
    state.messages.push(errorMessage)
  } finally {
    state.loading = false
    debugLoadingState()
    scrollToBottom(false)
  }
}

// 快捷问题
const quickQuestions = [
  '今天吃什么',
  '天气怎么样',
  '你是不是很帅'
]

const askQuickQuestion = (question: string) => {
  state.inputValue = question
  sendMessage()
}

// 清空对话
const clearChat = () => {
  // 清空对话

  // 立即清空消息,避免显示任何错误信息
  state.messages = []

  // 立即设置用户主动中断标志,避免显示任何错误信息
  userAbortFlag.value = true

  // 中断正在进行的请求
  if (abortControllerRef.value) {
    // 中断正在进行的请求
    abortControllerRef.value.abort()
    abortControllerRef.value = null
  }

  // 清理定时器
  throttleTimers.forEach(timer => {
    if (timer) clearTimeout(timer)
  })
  throttleTimers.clear()
  pendingContents.clear()

  // 清理滚动防抖定时器
  if (scrollTimeout) {
    clearTimeout(scrollTimeout)
    scrollTimeout = null
  }

  // 重置状态
  state.conversationId = ''
  state.loading = false
  state.showQuickQuestions = true // 显示快速问题区域

  // 重置滚动状态
  userScrolled.value = false
  lastScrollTop.value = 0

  // 延迟重置中断标志,确保所有异步操作都能检测到
  setTimeout(() => {
    userAbortFlag.value = false
    // 对话清空完成
  }, 200)

  scrollToBottom(false, true) // 强制滚动到底部
}

// 打开历史记录
const openHistory = async () => {
  state.showHistory = true

  const userId = localStorage.getItem('usercode') || 'W9065236'
  // if (!state.conversationId) return

  try {
    const res = await getHistoryMessages({
      user: userId,
      limit: 50,
      // sort_by:'-updated_at'
    })

    // 检查数据结构 - 可能是直接返回数组,而不是 {data: []} 格式
    let dataArray: any[] | null = null
    if (Array.isArray(res?.data)) {
      dataArray = res.data
    } else if (res?.data?.data && Array.isArray(res.data.data)) {
      dataArray = res.data.data
    }

    if (dataArray && dataArray.length > 0) {
      // 存储完整会话数据
      state.historyMessagesMap = {}
      dataArray.forEach((item: any) => {
        state.historyMessagesMap[item.id] = item
      })

      state.historySessions = dataArray.map((item: any) => ({
        id: item.id,
        title:
          item.name.slice(0, 30) + (item.name.length > 30 ? '...' : ''),
        lastMessage: item.introduction || '',
        timestamp: item.updated_at > 1000000000000 ? item.updated_at : item.updated_at * 1000
      }))
    } else {
      // 历史记录数据为空
    }
  } catch (error) {
    // 获取历史记录失败,静默处理
  }
}

// 关闭历史记录
const closeHistory = () => {
  state.showHistory = false
}

// 加载历史会话
const loadHistorySession = async (sessionId: string) => {
  const userId = localStorage.getItem('usercode') || 'W9065236'

  // 确保不是中断状态,允许正常加载历史
  userAbortFlag.value = false

  // 隐藏快速问题区域
  state.showQuickQuestions = false

  try {
    // 设置当前会话ID
    state.conversationId = sessionId

    // 加载历史会话

    // 先关闭历史弹窗
    closeHistory()

    // 清空当前消息
    state.messages = []
    state.loading = true

    // 调用接口获取历史消息列表
    const res = await getConversationMessages({
      conversation_id: sessionId,
      user: userId,
      limit: 20
    })

    // 检查数据结构
    let messagesData: any[] = []
    if (Array.isArray(res?.data?.data)) {
      messagesData = res.data.data
    } else if (Array.isArray(res?.data)) {
      messagesData = res.data
    }

    if (messagesData && messagesData.length > 0) {
      // 将接口返回的消息转换为 Message 格式
      const formattedMessages: Message[] = []

      messagesData.forEach((item: any) => {
        // 转换时间戳:如果是秒级则转为毫秒,并加8小时转换为本地时间
        const timezoneOffset = 8 * 60 * 60 * 1000 // 8小时时区偏移
        const timestamp = (item.created_at > 1000000000000
          ? item.created_at
          : item.created_at * 1000) + timezoneOffset

        // 用户消息(query)
        if (item.query) {
          formattedMessages.push({
            id: `${item.id}-user`,
            type: 'user',
            content: item.query,
            timestamp
          })
        }

        // AI消息(answer)
        if (item.answer) {
          formattedMessages.push({
            id: `${item.id}-ai`,
            type: 'ai',
            content: item.answer,
            timestamp,
            streaming: false,
            showThinking: true,
            finalResult: item.answer
          })
        }
      })

      // 按时间排序,确保消息顺序正确
      formattedMessages.sort((a, b) => a.timestamp - b.timestamp)

      // 添加到消息列表
      state.messages = formattedMessages
      // 加载历史消息成功
    } else {
      // 历史消息数据为空
    }

    nextTick(() => {
      scrollToBottom(false)
    })
  } catch (error: any) {
    // 加载历史会话失败,静默处理

    // 如果是用户主动中断(点击新建对话),不显示错误信息
    if (userAbortFlag.value) {
      return
    }

    // 根据错误类型提供不同的错误信息
    let errorContent = '抱歉,无法加载历史消息,请稍后再试。'

    if (error.message?.includes('timeout') || error.code === 'ECONNABORTED') {
      errorContent = '抱歉,请求超时,请稍后重试。'
    } else if (error.message?.includes('Network Error') || error.code === 'NETWORK_ERROR') {
      errorContent = '抱歉,网络连接失败,请检查网络设置后重试。'
    } else if (error.response?.status >= 500) {
      errorContent = '抱歉,服务器暂时不可用,请稍后重试。'
    } else if (error.response?.status === 401 || error.response?.status === 403) {
      errorContent = '抱歉,认证失败,请重新登录后重试。'
    }

    const errorMessage: Message = {
      id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}-${performance.now().toString().replace('.', '')}`,
      type: 'ai',
      content: errorContent,
      timestamp: Date.now()
    }
    state.messages.push(errorMessage)
  } finally {
    state.loading = false
    debugLoadingState()
  }
}

// 处理回车发送
const handleKeyPress = (event: KeyboardEvent) => {
  if (event.key === 'Enter' && !event.shiftKey) {
    event.preventDefault()
    sendMessage()
  }
}

// 调试函数:检查loading状态(已禁用日志)
const debugLoadingState = () => {
  // 静默处理
}

// 重试机制(预留功能)
// const retryWithBackoff = async (
//   fn: () => Promise<any>,
//   maxRetries: number = 3,
//   baseDelay: number = 1000
// ) => {
//   for (let attempt = 1; attempt <= maxRetries; attempt++) {
//     try {
//       return await fn()
//     } catch (error: any) {
//       console.warn(`⚠️ 第${attempt}次尝试失败:`, error.message)
//
//       // 如果是最后一次尝试,抛出错误
//       if (attempt === maxRetries) {
//         throw error
//       }
//
//       // 指数退避延迟
//       const delay = baseDelay * Math.pow(2, attempt - 1)
//       console.log(`⏳ ${delay}ms后重试...`)
//       await new Promise(resolve => setTimeout(resolve, delay))
//     }
//   }
// }

// 清理函数
const cleanup = () => {
  // 清理所有定时器
  throttleTimers.forEach(timer => {
    if (timer) clearTimeout(timer)
  })
  throttleTimers.clear()
  pendingContents.clear()
  markdownCache.clear() // 清理Markdown缓存
  renderCache.clear() // 清理渲染缓存

  // 清理滚动防抖定时器
  if (scrollTimeout) {
    clearTimeout(scrollTimeout)
    scrollTimeout = null
  }

  // 清理 AbortController
  if (abortControllerRef.value) {
    abortControllerRef.value.abort()
    abortControllerRef.value = null
  }

  // 强制重置 loading 状态
  state.loading = false
}

// 网络状态检测
const checkNetworkStatus = () => {
  if (!navigator.onLine) {
    // 网络离线
    return false
  }
  return true
}

// 网络状态变化监听
const handleOnline = () => {
  // 网络已连接
}

const handleOffline = () => {
  // 网络已断开
  // 如果正在加载,显示网络错误
  if (state.loading) {
    state.loading = false
    const errorMessage: Message = {
      id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}-${performance.now().toString().replace('.', '')}`,
      type: 'ai',
      content: '抱歉,网络连接已断开,请检查网络设置后重试。',
      timestamp: Date.now()
    }
    state.messages.push(errorMessage)
  }
}

onMounted(() => {
  // 延迟滚动,确保组件完全渲染
  setTimeout(() => {
    scrollToBottom(false, true) // 强制滚动到底部
  }, 100)

  // 绑定滚动事件监听器
  nextTick(() => {
    const container = chatContainer.value || document.querySelector('.chat-messages')
    if (container) {
      container.addEventListener('scroll', handleScroll, { passive: true })
    }
  })

  // 监听网络状态
  window.addEventListener('online', handleOnline)
  window.addEventListener('offline', handleOffline)
  trackView({
    level2Directory: 'AI智能报告',
    level3Directory: 'AI智能报告'
  })
})

onUnmounted(() => {
  cleanup()

  // 移除滚动事件监听器
  const container = chatContainer.value || document.querySelector('.chat-messages')
  if (container) {
    container.removeEventListener('scroll', handleScroll)
  }

  // 移除网络状态监听
  window.removeEventListener('online', handleOnline)
  window.removeEventListener('offline', handleOffline)
})

const handleClose = () => {
  // 中断正在进行的请求(避免资源浪费)
  if (abortControllerRef.value) {
    abortControllerRef.value.abort()
    abortControllerRef.value = null
  }

  // 清理定时器(避免内存泄漏)
  throttleTimers.forEach(timer => {
    if (timer) clearTimeout(timer)
  })
  throttleTimers.clear()
  pendingContents.clear()

  if (scrollTimeout) {
    clearTimeout(scrollTimeout)
    scrollTimeout = null
  }

  // 重置loading状态
  state.loading = false

  // 关闭弹窗
  if (props.handleClose) {
    props.handleClose()
  }
}

return () => {
  if (!props.visible) return null

  return (
    <div class="ai-chat-fullscreen">
      <div class="ai-chat-mask" onClick={handleClose}></div>
      <div class="ai-chat-container">
        <div class="chat-header">
          <div class="header-left">
            <div class="header-title">
              {/* <span class="ai-icon">你好,</span> */}
              <span>你好,我是你的AI质量助手</span>
            </div>
            {/* <div class="header-subtitle">基于当前数据上下文的智能问答</div> */}
          </div>
          <div class="header-right">
            <button
              class="history-btn"
              onClick={openHistory}
              title="历史消息">
              <i class="co-icon-list"></i>
            </button>
            <button
              class="new-chat-btn"
              onClick={clearChat}
              title="新建对话">
              <i class="co-icon-screen-capture"></i>
            </button>
            <button class="close-btn" onClick={handleClose} title="关闭">
              <i class="co-icon-close"></i>
            </button>
          </div>
        </div>

        {state.showQuickQuestions ? (
          <div class="welcome-section">
            <div class="welcome-content">
              <div class="welcome-left">
                <div class="welcome-title">
                  <span class="title-black">我是</span>
                  <span class="title-green">AI质量助手</span>
                </div>
                <div class="welcome-subtitle">质量最亲密的AI工作搭子,问答无顾虑</div>
                <div class="quick-questions-container">
                  <div class="quick-title">你可以对我说:</div>
                  <div class="quick-questions-list">
                    {quickQuestions.map((question: string, index: number) => (
                      <div
                        key={index}
                        class="quick-question-item"
                        onClick={() => askQuickQuestion(question)}>
                        <span class="question-icon">💬</span>
                        {question}
                      </div>
                    ))}
                  </div>
                </div>
              </div>
              {/* <div class="welcome-right">
                <div class="ai-character">
                  <div class="character-avatar">🤖</div>
                </div>
              </div> */}
            </div>
          </div>
        ) : (
          <div class="chat-messages" ref={chatContainer}>
          {state.messages.map((message) => (
            <div key={message.id} class={`message ${message.type}`}>
              {message.type === 'ai' && (
                <div class="message-avatar">🤖AI分析助手</div>
              )}
              <div class="message-content">
                {/* AI 消息统一使用深度思考模式 */}
                {message.type === 'ai' ? (
                  <div class="thinking-section">
                    {/* 流式时只显示最终结果,不显示思考过程 */}
                    {message.streaming ? (
                      <div class="final-result">
                        {message.finalResult ? (
                          renderContent(message.finalResult, true)
                        ) : null}
                      </div>
                    ) : (
                      <div>
                    <div
                      class="thinking-header"
                      onClick={() => toggleThinking(message.id)}>
                      <span class="thinking-icon"></span>
                      <span class="thinking-title">
                            已深度思考{message.thinkingTime ? `(${message.thinkingTime}秒)` : ''}
                      </span>
                      <span class="thinking-toggle">
                        {message.showThinking ? '▼' : '▶'}
                      </span>
                    </div>
                    {message.showThinking && (
                      <div class="thinking-content">
                            {/* 非流式时才显示技术数据 */}
                          <pre class="thinking-raw-data">
                            {message.technicalData || message.content}
                          </pre>
                      </div>
                    )}
                        {/* 最终结果显示 */}
                      <div class="final-result">
                        {renderContent(message.finalResult || message.content, false)}
                        </div>
                      </div>
                    )}
                  </div>
                ) : (
                  <div class="message-text">{message.content}</div>
                )}
                {message.options && message.options.length > 0 && (
                  <div class="message-options">
                    {message.options.map((option: string, idx: number) => (
                      <button
                        key={idx}
                        class="option-btn"
                        onClick={() => askQuickQuestion(option)}>
                        {option}
                      </button>
                    ))}
                  </div>
                )}
                <div class="message-footer">
                  <div class="message-time">
                    {new Date(message.timestamp).toLocaleString('zh-CN', {
                      year: 'numeric',
                      month: '2-digit',
                      day: '2-digit',
                      hour: '2-digit',
                      minute: '2-digit',
                      second: '2-digit',
                      hour12: false
                    })}
                  </div>
                  {message.type === 'ai' && !message.streaming && (
                    <div class="message-actions">
                      <button
                        class={`action-btn copy-btn ${
                          state.copiedId === message.id ? 'copied' : ''
                        }`}
                        onClick={() =>
                          copyMessage(message.finalResult || message.content, message.id)
                        }
                        title={
                          state.copiedId === message.id ? '已复制' : '复制'
                        }>
                        {state.copiedId === message.id ? (
                          <svg viewBox="0 0 24 24" width="14" height="14">
                            <path
                              fill="currentColor"
                              d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"
                            />
                          </svg>
                        ) : (
                          <svg viewBox="0 0 24 24" width="14" height="14">
                            <path
                              fill="currentColor"
                              d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"
                            />
                          </svg>
                        )}
                      </button>
                      <button
                        class="action-btn refresh-btn"
                        onClick={() => refreshMessage(message.id)}
                        title="重新生成">
                        <svg viewBox="0 0 24 24" width="14" height="14">
                          <path
                            fill="currentColor"
                            d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"
                          />
                        </svg>
                      </button>
                    </div>
                  )}
                </div>
              </div>
            </div>
          ))}

          {state.loading && !state.messages.some(m => m.type === 'ai' && m.streaming) && (
            <div class="message ai">
              <div class="message-avatar">🤖质量分析助手</div>
              <div class="message-content">
                <div class="thinking-section loading">
                  <div class="thinking-header">
                    <span class="thinking-icon"></span>
                    <span class="thinking-title">AI正在思考中</span>
                    <div class="thinking-dots">
                      <span class="dot"></span>
                      <span class="dot"></span>
                      <span class="dot"></span>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          )}
        </div>
        )}

        <div class="chat-input">
          <input
            value={state.inputValue}
            onInput={(e: any) => {
              state.inputValue = e.target.value
            }}
            placeholder="请输入你的问题..."
            onKeypress={handleKeyPress}
            disabled={state.loading}
          />
          <button
            class="send-btn"
            onClick={sendMessage}
            disabled={state.loading || !state.inputValue.trim()}>
            发送
          </button>
        </div>

        {/* 历史记录弹窗 */}
        <HistoryPopup
          show={state.showHistory}
          close={closeHistory}
          loadSession={loadHistorySession}
          historySessions={state.historySessions}
        />
      </div>
    </div>
  )
}

}
})

posted @ 2025-12-16 17:08  XiaoZhengTou  阅读(4)  评论(0)    收藏  举报