<template>
  <div class="chat-shell">
    <!-- 左侧角色面板 -->
    <aside class="chat-sidebar">
      <div class="agent-card">
        <div class="agent-avatar">SB</div>
        <div>
          <div class="agent-name">Support Bot</div>
          <div class="agent-status"><span></span>在线客服助手</div>
        </div>
      </div>

      <div class="side-section side-section-grow">
        <div class="side-label">客服助手</div>
        <div class="assistant-list">
          <div
            v-for="role in roles"
            :key="role.key"
            :class="['assistant-card', selectedRole === role.key ? 'active' : '']"
            @click="selectRole(role.key)"
          >
            {{ role.name }}
          </div>
        </div>
      </div>
    </aside>

    <!-- 右侧对话区 -->
    <section class="chat-main">
      <!-- 顶栏 -->
      <header class="chat-header">
        <div class="chat-title">
          <h2>{{ currentRole.name }}</h2>
          <div class="chat-subline">
            <span :class="['rag-dot', isRagMode ? 'on' : '']"></span>{{ ragStatusText }}
            <span class="dot-sep">&middot;</span>
            {{ selectedCategoryNames.length ? selectedCategoryNames.join('、') : '全部知识库' }}
          </div>
        </div>
        <div class="chat-actions">
          <div class="model-chip" :class="{ 'model-chip-error': modelLoadError }" :title="modelTitle">
            <span>调用模型</span>
            <strong>{{ activeModelText }}</strong>
          </div>
          <label class="rag-toggle">
            <t-switch v-model="isRagMode" size="small" />
            <span>RAG 检索</span>
          </label>
          <t-select
            v-if="isRagMode"
            v-model="ragStrategy"
            class="rag-strategy"
            size="small"
            :options="ragStrategyOptions"
          />
          <t-select v-model="mode" class="chat-mode" size="small" :options="modeOptions" />
        </div>
      </header>

      <!-- 快捷提问 -->
      <div class="quick-row" v-if="messages.length <= 1">
        <t-tag
          v-for="q in QUICK_QUESTIONS"
          :key="q"
          theme="default"
          variant="light"
          class="quick-tag"
          @click="useQuickQuestion(q)"
        >{{ q }}</t-tag>
      </div>

      <!-- 消息列表 -->
      <div class="msg-area chat-msg-area" ref="msgAreaRef">
        <div v-for="(m, i) in messages" :key="i" :class="['msg', m.role, m.streaming ? 'streaming' : '']">
          <div class="msg-content">
            <!-- 编辑模式 -->
            <template v-if="editingIndex === i">
              <t-textarea
                v-model="editingText"
                :autosize="{ minRows: 2, maxRows: 4 }"
                @keydown.enter.exact.prevent="submitEdit(i)"
              />
              <div class="edit-actions">
                <t-button theme="primary" size="small" @click="submitEdit(i)" :disabled="isSending || !editingText.trim()">重新发送</t-button>
                <t-button variant="outline" size="small" @click="cancelEdit">取消</t-button>
                <span class="edit-hint">将清除此条之后的全部对话并重发</span>
              </div>
            </template>

            <!-- 正常显示模式 -->
            <template v-else>
              <!-- MCP 工具调用 -->
              <div v-if="m.role === 'assistant' && m.toolCalls && m.toolCalls.length" class="mcp-tool-calls">
                <div v-for="(tc, j) in m.toolCalls" :key="j" class="mcp-tool-item">
                  <div class="mcp-tool-header">
                    <span class="mcp-tool-icon">{{ tc.status === 'running' ? '⏳' : '✅' }}</span>
                    <span class="mcp-tool-name">{{ tc.tool }}</span>
                    <span v-if="tc.latencyMs" class="mcp-tool-latency">{{ tc.latencyMs }}ms</span>
                  </div>
                </div>
              </div>
              <!-- AI 回复（Markdown 渲染） -->
              <div v-if="m.role === 'assistant' && m.content" class="msg-bubble markdown-body" v-html="renderMarkdown(m.content)"></div>
              <!-- AI 思考中 -->
              <div v-else-if="m.role === 'assistant'" class="msg-bubble"><span class="thinking">正在思考</span></div>
              <!-- 用户消息 -->
              <div v-else class="msg-bubble">{{ m.content }}</div>

              <!-- 引用来源 -->
              <MessageSources v-if="m.role === 'assistant' && m.sources && m.sources.length" :sources="m.sources" />

              <!-- 推荐问题（suggest-message-list） -->
              <div class="suggest-row" v-if="i === messages.length - 1 && currentSuggestions.length && !isSending">
                <span class="suggest-label">💡 推荐问题</span>
                <t-tag
                  v-for="q in currentSuggestions"
                  :key="q"
                  theme="default"
                  variant="light"
                  class="quick-tag"
                  @click="useQuickQuestion(q)"
                >{{ q }}</t-tag>
              </div>

              <!-- 消息操作栏 -->
              <div class="msg-tools">
                <span>{{ m.time }}</span>
                <t-button v-if="m.role === 'assistant' && m.content" variant="text" size="small" @click="copyMessage(m.content)">复制</t-button>
                <t-button v-if="m.role === 'assistant' && m.content && !m.streaming" variant="text" size="small" @click="regenerate(i)">重新生成</t-button>
                <t-button v-if="m.role === 'user' && !m.streaming" variant="text" size="small" @click="startEditMessage(i)">编辑</t-button>
                <t-button v-if="m.error" variant="text" size="small" @click="retryLast">重试</t-button>
                <template v-if="m.role === 'assistant' && m.content && !m.streaming">
                  <t-button variant="text" size="small" :style="{ color: m.feedback === 'up' ? 'var(--td-brand-color)' : '' }" @click="submitFeedback(m.id, 'up')" title="有帮助">👍</t-button>
                  <t-button variant="text" size="small" :style="{ color: m.feedback === 'down' ? 'var(--td-error-color)' : '' }" @click="submitFeedback(m.id, 'down')" title="没帮助">👎</t-button>
                </template>
              </div>
            </template>
          </div>
        </div>
      </div>

      <!-- 输入区 -->
      <footer class="chat-composer">
        <div class="composer-box">
          <t-textarea
            v-model="userInput"
            placeholder="输入问题，Enter 发送，Shift + Enter 换行"
            :autosize="{ minRows: 2, maxRows: 4 }"
            :disabled="isSending"
            @keydown.enter.exact.prevent="send"
          />
          <t-button theme="primary" class="send-btn" @click="send" :disabled="isSending || !userInput.trim()">
            {{ isSending ? '发送中' : '发送' }}
          </t-button>
        </div>
      </footer>
    </section>

    <!-- 点踩原因弹窗 -->
    <t-dialog v-model:visible="feedbackReasonVisible" header="请告诉我们哪里做得不好？" width="400px" :footer="false">
      <div class="feedback-reason-options">
        <t-button
          v-for="opt in REASON_OPTIONS"
          :key="opt.key"
          :variant="feedbackSelectedReason === opt.key ? undefined : 'outline'"
          :theme="feedbackSelectedReason === opt.key ? 'primary' : undefined"
          size="small"
          @click="feedbackSelectedReason = opt.key"
        >{{ opt.label }}</t-button>
      </div>
      <t-textarea
        v-model="feedbackReasonComment"
        placeholder="补充说明（选填，最多200字）"
        :maxlength="200"
        :autosize="{ minRows: 2, maxRows: 3 }"
      />
      <div class="dialog-footer">
        <t-button variant="outline" size="small" @click="cancelFeedbackReason">取消</t-button>
        <t-button theme="primary" size="small" :disabled="!feedbackSelectedReason" @click="confirmFeedbackReason">提交</t-button>
      </div>
    </t-dialog>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, nextTick, onMounted, onBeforeUnmount } from 'vue'
import { chatSync, chatRagSync, chatSSEUrl, chatRagSSEUrl, ragSources, fetchSuggestions } from '@/api/chat'
import { getRoleList } from '@/api/role'
import { getActiveModelConfig } from '@/api/model-config'
import { truncateConversation } from '@/api/conversation'
import { submitFeedback as submitFeedbackApi } from '@/api/feedback'
import { toast } from '@/utils/toast'
import { readSSEStream, readSSEStreamWithEvents } from '@/utils/sse'
import { renderMarkdown } from '@/utils/markdown'
import { useCategoryStore } from '@/stores/category'
import MessageSources from '@/components/MessageSources.vue'

const categoryStore = useCategoryStore()

// ==================== 常量 ====================
const FALLBACK_ROLE = {
  id: '', key: 'general', name: '客服',
  categoryIds: [] as string[],
}

const QUICK_QUESTIONS = [
  '我的问题应该找哪个部门处理？',
  '如何办理业务申请？',
  '流程进度一直没更新怎么办？',
  '费用报销需要准备哪些材料？',
  '办公用品或资产怎么申请？',
]

const ragStrategyOptions = [
  { label: '不重写', value: 'NONE' },
  { label: '查询重写', value: 'REWRITE' },
  { label: '翻译扩展', value: 'TRANSLATION' },
  { label: '查询压缩', value: 'COMPRESSION' },
  { label: '多路扩展', value: 'MULTI_QUERY' },
]

const modeOptions = [
  { label: '同步调用', value: 'sync' },
  { label: 'SSE 流式', value: 'sse' },
]

// ==================== 状态 ====================
const chatId = ref('')
const mode = ref('sse')  // 默认 SSE 流式
const selectedRole = ref('general')
const roles = ref([FALLBACK_ROLE])
const isRagMode = ref(false)
const ragStrategy = ref('MULTI_QUERY')
const activeModel = ref<any>(null)
const modelLoadError = ref('')
const userInput = ref('')
const lastUserInput = ref('')
const isSending = ref(false)
const msgAreaRef = ref<HTMLElement | null>(null)
const editingIndex = ref(-1)
const editingText = ref('')
let sseAbortController: AbortController | null = null
const currentSuggestions = ref<string[]>([])  // suggest-message-list

// ==================== 消息列表 ====================
interface ChatMessage {
  id: string
  role: 'user' | 'assistant'
  content: string
  streaming: boolean
  time: string
  sources?: any[]
  toolCalls?: any[]
  error?: boolean
  feedback?: string | null
}

const messages = ref<ChatMessage[]>([
  {
    id: generateMsgId(),
    role: 'assistant',
    content: '您好，我是智能客服助手。可以咨询客服、财务、行政相关问题；需要基于知识库回答时，可以在上方开启 RAG 检索。',
    streaming: false,
    time: formatTime(),
  },
])

// ==================== 计算属性 ====================
const currentRole = computed(() => roles.value.find(r => r.key === selectedRole.value) || roles.value[0] || FALLBACK_ROLE)

const selectedCategoryIds = computed(() => (currentRole.value.categoryIds || []).map(String))

const selectedCategoryNames = computed(() => {
  const selected = new Set(selectedCategoryIds.value)
  return categoryStore.categories
    .filter(c => selected.has(String(c.id)))
    .map(c => c.name)
})

const activeModelText = computed(() => {
  if (activeModel.value) {
    const provider = providerLabel(activeModel.value.provider)
    const modelName = activeModel.value.modelName || activeModel.value.model_name || '-'
    return provider ? `${provider} / ${modelName}` : modelName
  }
  return modelLoadError.value ? '未配置' : '加载中'
})

const modelTitle = computed(() => {
  if (activeModel.value) {
    const model = activeModel.value
    const name = model.name ? `${model.name}：` : ''
    const provider = providerLabel(model.provider)
    const mn = model.modelName || model.model_name || '-'
    return `对话模型：${name}${provider ? provider + ' / ' : ''}${mn}`
  }
  return modelLoadError.value || '正在加载对话模型'
})

const ragStatusText = computed(() => {
  if (!isRagMode.value) return '普通对话'
  const names: Record<string, string> = {
    NONE: 'RAG：不重写', REWRITE: 'RAG：查询重写',
    TRANSLATION: 'RAG：翻译扩展', COMPRESSION: 'RAG：查询压缩',
    MULTI_QUERY: 'RAG：多路扩展',
  }
  return names[ragStrategy.value] || 'RAG 已启用'
})

// ==================== 工具函数 ====================
function formatTime(): string {
  return new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
}

function generateMsgId(): string {
  return 'msg_' + Date.now() + '_' + Math.random().toString(36).substring(2, 8)
}

function newChatId(): void {
  chatId.value = 'web_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8)
}

function providerLabel(provider: string): string {
  const map: Record<string, string> = {
    dashscope: '通义千问', openai: 'OpenAI', volcengine: '豆包',
    zhipu: '智谱', baidu: '百度千帆',
  }
  return map[String(provider || '').toLowerCase()] || provider || ''
}

// ==================== 角色管理 ====================
function selectRole(roleKey: string): void {
  selectedRole.value = roleKey
  newChatId()
  currentSuggestions.value = []  // 切换角色时清空推荐问题
  const role = roles.value.find(r => r.key === roleKey) || FALLBACK_ROLE
  messages.value = [{
    id: generateMsgId(),
    role: 'assistant',
    content: `已切换到${role.name}。请直接输入你的问题。`,
    streaming: false,
    time: formatTime(),
  }]
}

function currentRoleId(): string {
  return currentRole.value && currentRole.value.id ? String(currentRole.value.id) : ''
}

// ==================== 数据加载 ====================
async function loadActiveModel(): Promise<void> {
  try {
    const json = await getActiveModelConfig('CHAT')
    if (json.success) {
      activeModel.value = json.data
      modelLoadError.value = ''
    } else {
      activeModel.value = null
      modelLoadError.value = json.message || '对话模型未配置'
    }
  } catch (e: any) {
    activeModel.value = null
    modelLoadError.value = e.message || '模型加载失败'
  }
}

async function loadRoles(): Promise<void> {
  try {
    const json = await getRoleList()
    if (json.success) {
      roles.value = (json.data || []).map((role: any) => ({
        id: role.id,
        key: role.role_key || role.roleKey || String(role.id),
        name: role.name,
        categoryIds: (role.categoryIds || []).map(String),
      }))
      if (!roles.value.some(r => r.key === selectedRole.value)) {
        selectedRole.value = roles.value[0]?.key || 'general'
      }
    }
  } catch (e: any) {
    toast('加载客服角色失败：' + e.message, 'error')
  }
}

// ==================== 滚动 ====================
async function scrollToBottom(): Promise<void> {
  await nextTick()
  if (msgAreaRef.value) {
    msgAreaRef.value.scrollTop = msgAreaRef.value.scrollHeight
  }
}

// ==================== 快捷提问 ====================
function useQuickQuestion(question: string): void {
  userInput.value = question
  send()
}

// ==================== 核心：发送消息 ====================
async function send(): Promise<void> {
  const text = userInput.value.trim()
  if (!text || isSending.value) return

  userInput.value = ''
  lastUserInput.value = text
  isSending.value = true
  currentSuggestions.value = []  // 清空建议

  // 追加用户消息
  messages.value.push({
    id: generateMsgId(), role: 'user', content: text,
    streaming: false, time: formatTime(),
  })

  // 追加 AI 占位消息
  const assistantMsg: ChatMessage = {
    id: generateMsgId(), role: 'assistant', content: '',
    streaming: true, time: formatTime(), sources: [], toolCalls: [],
  }
  messages.value.push(assistantMsg)
  await scrollToBottom()

  const cid = chatId.value || ('web_' + Date.now())
  chatId.value = cid
  const roleId = currentRoleId()

  try {
    // 取消上一个 SSE 请求并创建新的 AbortController
    if (sseAbortController) { sseAbortController.abort() }
    sseAbortController = new AbortController()
    const signal = sseAbortController.signal

    if (isRagMode.value && mode.value === 'sync') {
      // RAG 同步
      assistantMsg.content = await chatRagSync(text, cid, ragStrategy.value, roleId)
    } else if (isRagMode.value) {
      // RAG SSE 流式
      const url = chatRagSSEUrl(text, cid, ragStrategy.value, roleId)
      await readSSEStreamWithEvents(url, {
        onMessage: async (chunk: string) => {
          assistantMsg.content += chunk
          await scrollToBottom()
        },
        onToolCallStart: (data: any) => {
          assistantMsg.toolCalls!.push({ tool: data.tool, input: data.input, status: 'running', result: null })
          scrollToBottom()
        },
        onToolCallResult: (data: any) => {
          const tc = assistantMsg.toolCalls!.find(t => t.tool === data.tool && t.status === 'running')
          if (tc) { tc.status = 'done'; tc.result = data.result; tc.latencyMs = data.latencyMs }
          scrollToBottom()
        },
        onError: (data: any) => {
          assistantMsg.content += '\n\n⚠️ ' + (data.message || '工具调用出错')
        },
        onDone: () => {},
      }, undefined, signal)
    } else if (mode.value === 'sync') {
      // 普通同步
      assistantMsg.content = await chatSync(text, cid, roleId)
    } else {
      // 普通 SSE 流式
      const url = chatSSEUrl(text, cid, roleId)
      await readSSEStream(url, async (chunk: string) => {
        assistantMsg.content += chunk
        await scrollToBottom()
      }, () => {}, undefined, signal)
    }

    // RAG 模式下，拉取引用来源
    if (isRagMode.value) {
      try {
        const sj = await ragSources(text, cid, ragStrategy.value, roleId)
        if (sj && sj.success) assistantMsg.sources = sj.data || []
      } catch (_) { /* 来源获取失败不影响主回答 */ }
    }
  } catch (e: any) {
    // AbortError 不是真正的错误，不显示错误信息
    if (e.name === 'AbortError') {
      assistantMsg.content = assistantMsg.content || '已取消'
    } else {
      assistantMsg.content = '请求失败：' + e.message
      assistantMsg.error = true
      toast('对话失败：' + e.message, 'error')
    }
  } finally {
    assistantMsg.streaming = false
    isSending.value = false

    // 拉取推荐问题（suggest-message-list）；AbortError / 异常时不拉取
    if (!sseAbortController?.signal.aborted && assistantMsg.content && !assistantMsg.error) {
      fetchSuggestions(chatId.value).then(items => {
        if (items.length) currentSuggestions.value = items
      }).catch(() => {})
    }

    await scrollToBottom()
  }
}

// ==================== 消息操作 ====================
function retryLast(): void {
  if (!lastUserInput.value || isSending.value) return
  userInput.value = lastUserInput.value
  send()
}

function startEditMessage(i: number): void {
  if (isSending.value) return
  editingIndex.value = i
  editingText.value = messages.value[i].content || ''
}

function cancelEdit(): void {
  editingIndex.value = -1
  editingText.value = ''
}

async function submitEdit(i: number): Promise<void> {
  const text = editingText.value.trim()
  if (!text || isSending.value) return
  // 计算 userTurn（1-based）
  let userTurn = 0
  for (let j = 0; j <= i; j++) {
    if (messages.value[j].role === 'user') userTurn++
  }
  // 截断
  try {
    const json = await truncateConversation(chatId.value, userTurn)
    if (json && json.success === false) {
      toast(json.message || '截断失败', 'error')
      return
    }
  } catch (e: any) {
    toast('截断失败：' + e.message, 'error')
    return
  }
  // 切掉本地本条及其之后
  messages.value = messages.value.slice(0, i)
  editingIndex.value = -1
  editingText.value = ''
  userInput.value = text
  await send()
}

async function regenerate(i: number): Promise<void> {
  if (isSending.value) return
  // 找上一条用户消息
  let userIndex = -1
  for (let j = i - 1; j >= 0; j--) {
    if (messages.value[j].role === 'user') { userIndex = j; break }
  }
  if (userIndex < 0) return
  const text = messages.value[userIndex].content
  let userTurn = 0
  for (let j = 0; j <= userIndex; j++) {
    if (messages.value[j].role === 'user') userTurn++
  }
  try {
    const json = await truncateConversation(chatId.value, userTurn)
    if (json && json.success === false) { toast(json.message || '重新生成失败', 'error'); return }
  } catch (e: any) { toast('重新生成失败：' + e.message, 'error'); return }
  messages.value = messages.value.slice(0, userIndex)
  userInput.value = text
  await send()
}

async function copyMessage(content: string): Promise<void> {
  try {
    await navigator.clipboard.writeText(content)
    toast('已复制回复', 'success')
  } catch { toast('复制失败', 'error') }
}

// ==================== 消息反馈 ====================
// 点踩原因弹窗状态
const feedbackReasonVisible = ref(false)
const feedbackSelectedReason = ref('')
const feedbackReasonComment = ref('')
const feedbackTarget = ref<{ msgId: string; msgIndex: number; wasActive: boolean } | null>(null)

const REASON_OPTIONS = [
  { key: 'inaccurate', label: '不准确' },
  { key: 'irrelevant', label: '不相关' },
  { key: 'incomplete', label: '不完整' },
  { key: 'other', label: '其他' },
]

async function submitFeedback(msgId: string, type: 'up' | 'down'): Promise<void> {
  const msgIndex = messages.value.findIndex(m => m.id === msgId && m.role === 'assistant')
  if (msgIndex === -1) return
  const msg = messages.value[msgIndex]
  const wasActive = msg.feedback === type
  const newFeedback = wasActive ? null : type

  if (type === 'up') {
    // 点赞：直接提交/取消
    msg.feedback = newFeedback
    if (!newFeedback) return
    try {
      await submitFeedbackApi({
        messageId: String(msgId),
        conversationId: chatId.value,
        feedbackType: 'THUMBS_UP',
      })
      toast('感谢反馈 👍', 'success')
    } catch (e) {
      // 失败时回滚乐观更新
      msg.feedback = wasActive ? 'up' : null
      console.error('反馈提交失败:', e)
      toast(e.message || '反馈提交失败', 'error')
    }
  } else {
    // 点踩：弹出原因选择弹窗（不再乐观标记前端状态，等提交成功后再更新）
    if (wasActive) {
      msg.feedback = null
      return
    }
    feedbackTarget.value = { msgId, msgIndex, wasActive }
    feedbackSelectedReason.value = ''
    feedbackReasonComment.value = ''
    feedbackReasonVisible.value = true
  }
}

async function confirmFeedbackReason(): Promise<void> {
  const target = feedbackTarget.value
  if (!target || !feedbackSelectedReason.value) return
  const msg = messages.value[target.msgIndex]
  const reasonCategory = feedbackSelectedReason.value
  const reasonComment = feedbackReasonComment.value.trim() || undefined
  // 提交时才乐观标记前端状态
  msg.feedback = 'down'
  try {
    await submitFeedbackApi({
      messageId: String(target.msgId),
      conversationId: chatId.value,
      feedbackType: 'THUMBS_DOWN',
      reasonCategory,
      reasonComment,
    })
    toast('感谢反馈，我们会持续改进', 'success')
  } catch (e) {
    // 失败时回滚
    msg.feedback = null
    console.error('反馈提交失败:', e)
    toast(e.message || '反馈提交失败', 'error')
  }
  feedbackReasonVisible.value = false
  feedbackTarget.value = null
}

function cancelFeedbackReason(): void {
  feedbackReasonVisible.value = false
  feedbackTarget.value = null
}

// ==================== 初始化 ====================
onMounted(() => {
  newChatId()
  categoryStore.loadCategories()
  loadRoles()
  loadActiveModel()
})

// 组件卸载时取消正在进行的 SSE 请求，释放网络资源，避免浏览器窗口切换异常
onBeforeUnmount(() => {
  if (sseAbortController) {
    sseAbortController.abort()
    sseAbortController = null
  }
})
</script>

<style scoped>
/* ==================== 布局 ==================== */
.chat-shell {
  display: flex; height: calc(100vh - 48px); overflow: hidden;
}
.chat-sidebar {
  width: 220px; background: var(--td-bg-color-page); border-right: 1px solid var(--td-border-level-1-color);
  flex-shrink: 0; display: flex; flex-direction: column; overflow-y: auto; padding: 12px;
}
.chat-main {
  flex: 1; display: flex; flex-direction: column; overflow: hidden; background: var(--td-bg-color-container);
}

/* ==================== 角色卡片 ==================== */
.agent-card {
  display: flex; align-items: center; gap: 10px; padding: 0 0 12px 0;
  border-bottom: 1px solid var(--td-border-level-1-color);
}
.agent-avatar {
  width: 40px; height: 40px; border-radius: 10px; background: var(--td-brand-color);
  color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px;
}
.agent-name { font-weight: 600; font-size: 14px; }
.agent-status { font-size: 12px; color: var(--td-success-color); display: flex; align-items: center; gap: 4px; }
.agent-status span { width: 6px; height: 6px; border-radius: 50%; background: var(--td-success-color); }

.side-label { font-size: 11px; color: var(--td-text-color-placeholder); text-transform: uppercase; margin: 12px 0 8px; letter-spacing: 1px; }
.side-section-grow { flex: 1; overflow-y: auto; }
.assistant-list { display: flex; flex-direction: column; gap: 6px; }
.assistant-card {
  display: flex; align-items: center; gap: 8px; padding: 8px; border-radius: 8px;
  cursor: pointer; transition: background .15s; border: 1px solid transparent;
}
.assistant-card:hover { background: var(--td-bg-color-component); }
.assistant-card.active { background: var(--td-brand-color-light); border-color: var(--td-brand-color); }

/* ==================== 顶栏 ==================== */
.chat-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  padding: 12px 16px; border-bottom: 1px solid var(--td-border-level-1-color);
  flex-wrap: wrap; gap: 8px;
}
.chat-title h2 { font-size: 16px; margin: 0 0 4px; }
.chat-subline { font-size: 12px; color: var(--td-text-color-placeholder); display: flex; align-items: center; gap: 4px; }
.rag-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--td-text-color-disabled); flex-shrink: 0; }
.rag-dot.on { background: var(--td-success-color); }
.dot-sep { color: var(--td-border-level-2-color); }
.chat-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.model-chip {
  display: flex; align-items: center; gap: 4px; font-size: 12px; background: var(--td-bg-color-component);
  padding: 4px 10px; border-radius: 6px; white-space: nowrap;
}
.model-chip strong { color: var(--td-brand-color); max-width: 160px; overflow: hidden; text-overflow: ellipsis; }
.model-chip-error { background: var(--td-error-color-1); }
.model-chip-error strong { color: var(--td-error-color); }
.rag-toggle { display: flex; align-items: center; gap: 4px; font-size: 12px; cursor: pointer; }
.rag-strategy { width: 120px; }
.chat-mode { width: 100px; }

/* ==================== 快捷提问 ==================== */
.quick-row { display: flex; gap: 8px; padding: 10px 16px; flex-wrap: wrap; border-bottom: 1px solid var(--td-border-level-1-color); }
.quick-tag { cursor: pointer; }

/* ==================== 消息区 ==================== */
.msg-area { flex: 1; overflow-y: auto; padding: 16px; }
.msg { margin-bottom: 16px; }
.msg.user .msg-content { display: flex; flex-direction: column; align-items: flex-end; }
.msg.user .msg-bubble {
  background: var(--td-brand-color); color: #fff; border-radius: 12px 12px 4px 12px;
  padding: 10px 14px; max-width: 70%; font-size: 14px; line-height: 1.6; white-space: pre-wrap;
}
.msg.assistant .msg-bubble {
  background: var(--td-bg-color-secondarycontainer); color: var(--td-text-color-primary); border-radius: 12px 12px 12px 4px;
  padding: 12px 16px; max-width: 85%; font-size: 14px; line-height: 1.7;
}
.msg.streaming .msg-content::after { content: ''; }
.thinking { color: var(--td-text-color-placeholder); font-style: italic; animation: blink 1s infinite; }
@keyframes blink { 50% { opacity: 0.5; } }

/* Markdown 在气泡内 */
.msg-bubble :deep(h1) { font-size: 1.3em; margin: 12px 0 6px; }
.msg-bubble :deep(h2) { font-size: 1.15em; margin: 10px 0 4px; }
.msg-bubble :deep(h3) { font-size: 1.05em; margin: 8px 0 4px; }
.msg-bubble :deep(p) { margin: 4px 0; }
.msg-bubble :deep(ul), .msg-bubble :deep(ol) { padding-left: 20px; margin: 6px 0; }
.msg-bubble :deep(table) { border-collapse: collapse; margin: 8px 0; width: 100%; }
.msg-bubble :deep(th), .msg-bubble :deep(td) { border: 1px solid var(--td-border-level-2-color); padding: 6px 10px; text-align: left; font-size: 13px; }
.msg-bubble :deep(th) { background: var(--td-bg-color-secondarycontainer); font-weight: 600; }
.msg-bubble :deep(code) { background: rgba(0,0,0,.06); padding: 2px 6px; border-radius: 3px; font-size: 13px; }
.msg-bubble :deep(pre) { background: #1e1e1e; color: #d4d4d4; padding: 12px; border-radius: 8px; overflow-x: auto; }
.msg-bubble :deep(pre code) { background: none; padding: 0; color: inherit; }

/* ==================== MCP 工具调用 ==================== */
.mcp-tool-calls { margin-bottom: 8px; }
.mcp-tool-item { background: var(--td-bg-color-page); border: 1px solid var(--td-border-level-1-color); border-radius: 6px; padding: 6px 10px; margin-bottom: 4px; }
.mcp-tool-header { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.mcp-tool-icon { font-size: 14px; }
.mcp-tool-name { font-weight: 600; color: var(--td-brand-color); }
.mcp-tool-latency { font-size: 11px; color: var(--td-text-color-placeholder); margin-left: auto; }

/* ==================== 消息操作 ==================== */
.msg-tools { display: flex; align-items: center; gap: 6px; margin-top: 6px; font-size: 12px; color: var(--td-text-color-placeholder); }
.msg-tools span { margin-right: auto; }
.edit-actions { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.edit-hint { font-size: 12px; color: var(--td-text-color-placeholder); }

/* ==================== 输入区 ==================== */
.chat-composer { padding: 12px 16px; border-top: 1px solid var(--td-border-level-1-color); }
.composer-box { display: flex; gap: 10px; align-items: flex-end; }
.composer-box :deep(.t-textarea__inner) { min-height: 44px; }
.send-btn { flex-shrink: 0; height: 44px; }

@media (max-width: 768px) {
  .chat-sidebar { display: none; }
  .chat-header { flex-direction: column; }
  .chat-actions { flex-wrap: wrap; }
}

/* ==================== 点踩原因弹窗 ==================== */
.feedback-reason-options { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.dialog-footer { margin-top: 12px; display: flex; justify-content: flex-end; gap: 8px; }
</style>
