<template>
  <div>
    <v-avatar
      ref="box"
      class="box point elevation-5"
      color="indigo"
      @click="handleClick"
      @mousedown="handleMousedown"
    >
      <v-icon dark large>
        mdi-face-agent
      </v-icon>
    </v-avatar>
    <v-navigation-drawer
      v-model="show"
      fixed
      temporary
      hide-overlay
      right
      width="60%"
      style="z-index: var(--zIndex-nav);"
    >
      <div v-if="show" class="assistant-chat-wrapper d-flex flex-column">
        <v-banner class="flex-shrink-0">
          <div class="my-2">
            <div class="assistant-title">{{ displayTitle }}</div>
            <div v-if="displaySubtitle" class="assistant-subtitle mt-1">{{ displaySubtitle }}</div>
          </div>
        </v-banner>
        <div ref="chatBox" class="chat-list pa-3 flex-grow-1 overflow-auto">
          <v-list>
            <!-- 欢迎语 -->
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title class="d-flex align-center indigo--text">
                  <v-icon class="mr-3" color="indigo">mdi-face-agent</v-icon>
                  {{ system.name }}
                </v-list-item-title>
                <v-list-item-subtitle class="d-flex">
                  <div class="indigo lighten-5 pa-3 round welcome-text">
                    {{ system.welcome }}
                  </div>
                </v-list-item-subtitle>
              </v-list-item-content>
            </v-list-item>
            <v-list-item v-for="talk in talks" :key="talk.id">
              <v-list-item-content>
                <v-list-item-title
                  class="d-flex align-center"
                  :class="talk.type === 'question' ? 'justify-end blue--text' : 'indigo--text'"
                >
                  {{ talk.type === 'question' ? $store.getters.userInfo?.username || '我' : '' }}
                  <v-icon
                    :class="talk.type === 'question' ? 'ml-3' : 'mr-3'"
                    :color="talk.type === 'question' ? 'blue' : 'indigo'"
                  >
                    {{ talk.type === 'question' ? 'mdi-account-circle' : 'mdi-face-agent' }}
                  </v-icon>
                  {{ talk.type === 'question' ? '' : system.name }}
                </v-list-item-title>
                <v-list-item-subtitle class="d-flex" :class="{ 'justify-end': talk.type === 'question' }">
                  <div class="lighten-5 pa-3 round" :class="talk.type === 'question' ? 'blue' : 'indigo'">
                    <template v-for="(item, index) in talk.content">
                      <div
                        v-if="item.type === 'text'"
                        :key="index"
                        class="talk-line"
                        v-html="formatLine(item.text, talk.type)"
                      />
                      <template v-else-if="item.type === 'component' && componentRegistry[item.name]">
                        <component
                          :key="index"
                          :is="componentRegistry[item.name]"
                          v-bind="getComponentProps(item.name, item.props)"
                          class="assistant-component mt-2"
                        />
                      </template>
                    </template>
                  </div>
                </v-list-item-subtitle>
              </v-list-item-content>
            </v-list-item>
            <!-- loading -->
            <v-list-item v-show="loading" style="min-height: 10px">
              <v-list-item-content>
                <v-list-item-title class="d-flex align-center indigo--text">
                  <v-icon class="mr-3" color="indigo">mdi-face-agent</v-icon>
                  <div class="mr-3">{{ system.name }} 思考中</div>
                  <v-progress-circular size="18" width="2" indeterminate color="indigo" />
                </v-list-item-title>
              </v-list-item-content>
            </v-list-item>
          </v-list>
        </div>
        <div class="send d-flex align-center justify-center flex-shrink-0">
          <div class="send-box">
            <v-textarea
              v-model="question"
              dense
              class="send-box-area"
              auto-grow
              :placeholder="displayInputPlaceholder"
              solo
              hide-details
              no-resize
              rows="1"
              @keydown.enter="handleKeyCode"
            />
            <v-btn icon color="primary" class="btn" :disabled="!question.trim()" @click="handleSendMsg">
              <v-icon>mdi-send</v-icon>
            </v-btn>
          </div>
        </div>
      </div>
    </v-navigation-drawer>
  </div>
</template>

<script>
import { generateUUID } from '@/utils/index'
import { assistantComponentRegistry, assistantComponentDefaults } from './assistantComponents'

const DEFAULT_WEBHOOK_URL = process.env.VUE_APP_N8N_CHAT_URL || ''

export default {
  name: 'AssistantIframe',
  props: {
    /** n8n Chat Trigger 的 webhook 地址，用于发消息拿回复 */
    iframeUrl: {
      type: String,
      default: DEFAULT_WEBHOOK_URL
    },
    /** 抽屉标题 */
    title: {
      type: String,
      default: '智能助手'
    },
    /** 欢迎语 */
    welcome: {
      type: String,
      default: '您好，我是您的智能助手，有什么问题可以问我哦！'
    },
    inputPlaceholder: {
      type: String,
      default: '请输入您想问的内容，按 Ctrl+Enter 换行'
    },
    subTitle: {
      type: String,
      default: ''
    }
  },
  data () {
    return {
      show: false,
      lock: false,
      isDragging: false,
      info: {},
      sessionId: '',
      displayTitle: this.title,
      displaySubtitle: this.subTitle,
      displayInputPlaceholder: this.inputPlaceholder,
      system: {
        name: '智能助手',
        welcome: this.welcome
      },
      talks: [],
      question: '',
      loading: false,
      talkId: 0,
      configFetched: false,
      componentRegistry: assistantComponentRegistry,
      componentDefaults: assistantComponentDefaults
    }
  },
  watch: {
    show (val) {
      if (val && !this.sessionId) {
        this.sessionId = generateUUID()
      }
      if (val && this.iframeUrl && !this.configFetched) {
        this.fetchConfig()
      }
    },
    title (val) {
      if (!this.configFetched) this.displayTitle = val
    },
    welcome (val) {
      if (!this.configFetched) this.system.welcome = val
    },
    talks: {
      handler () {
        this.$nextTick(() => {
          const box = this.$refs.chatBox
          if (box) {
            box.scrollTo({ top: box.scrollHeight, behavior: 'smooth' })
          }
        })
      },
      deep: true,
      immediate: true
    }
  },
  mounted () {
    this.sessionId = generateUUID()
    this.$nextTick(() => {
      document.addEventListener('mouseup', this.handleMouseup)
      document.addEventListener('mousemove', this.handleMousemove)
      this.$once('hook:beforeDestroy', () => {
        document.removeEventListener('mouseup', this.handleMouseup)
        document.removeEventListener('mousemove', this.handleMousemove)
      })
    })
  },
  methods: {
    handleClick () {
      if (this.isDragging) return
      this.show = !this.show
    },
    handleMousedown (e) {
      this.lock = true
      const { left, top, height, width } = this.$refs.box.$el.getBoundingClientRect()
      this.info = {
        click: { x: e.clientX, y: e.clientY },
        left,
        top,
        height,
        width,
        clientX: e.clientX,
        clientY: e.clientY
      }
    },
    handleMousemove (e) {
      if (!this.lock) return
      const xLen = Math.abs(this.info.click?.x - e.clientX)
      const yLen = Math.abs(this.info.click?.y - e.clientY)
      if (xLen < 20 && yLen < 20) return
      const clientX = e.clientX - this.info.clientX
      const clientY = e.clientY - this.info.clientY
      this.isDragging = true
      this.info.left += clientX
      this.info.top += clientY
      this.$refs.box.$el.style.left = this.info.left + 'px'
      this.$refs.box.$el.style.top = this.info.top + 'px'
      this.info.clientX = e.clientX
      this.info.clientY = e.clientY
    },
    handleMouseup () {
      this.lock = false
      setTimeout(() => { this.isDragging = false })
    },
    /** 从 n8n chat 页面的 HTML 中解析 createChat 的 i18n.en / initialMessages */
    parseConfigFromHtml (raw) {
      const config = {}
      const str = raw.replace(/\s+/g, ' ')
      const titleMatch = str.match(/"title"\s*:\s*"((?:[^"\\]|\\.)*)"/)
      if (titleMatch) config.title = titleMatch[1].replace(/\\"/g, '"')
      const subtitleMatch = str.match(/"subtitle"\s*:\s*"((?:[^"\\]|\\.)*)"/)
      if (subtitleMatch) config.subtitle = subtitleMatch[1].replace(/\\"/g, '"')
      const placeholderMatch = str.match(/"inputPlaceholder"\s*:\s*"((?:[^"\\]|\\.)*)"/)
      if (placeholderMatch) config.inputPlaceholder = placeholderMatch[1].replace(/\\"/g, '"')
      const initialMessages = this.parseInitialMessagesArray(raw)
      if (initialMessages.length) {
        config.welcome = initialMessages.join('\n')
      }
      return config
    },
    /** 解析 initialMessages 数组中的全部字符串（支持任意条数） */
    parseInitialMessagesArray (raw) {
      const idx = raw.indexOf('initialMessages')
      if (idx === -1) return []
      const arrStart = raw.indexOf('[', idx)
      if (arrStart === -1) return []
      let depth = 1
      let pos = arrStart + 1
      while (pos < raw.length && depth > 0) {
        const ch = raw[pos]
        if (ch === '[') depth++
        else if (ch === ']') depth--
        pos++
      }
      const arrContent = raw.slice(arrStart + 1, pos - 1)
      const list = []
      const re = /"((?:[^"\\]|\\.)*)"/g
      let m
      while ((m = re.exec(arrContent)) !== null) {
        list.push(m[1].replace(/\\"/g, '"'))
      }
      return list
    },
    async fetchConfig () {
      if (!this.iframeUrl) return
      try {
        const res = await fetch(this.iframeUrl, { method: 'GET' })
        if (!res.ok) return
        const text = await res.text()
        const raw = text && text.trim()
        if (!raw) return
        let config = null
        if (raw.startsWith('{')) {
          const data = JSON.parse(raw)
          config = data && typeof data.data !== 'undefined' ? data.data : data
        } else if (raw.toLowerCase().includes('createchat') && (raw.includes('i18n') || raw.includes('title') || raw.includes('initialMessages'))) {
          config = this.parseConfigFromHtml(raw)
        }
        if (!config || typeof config !== 'object') return
        this.configFetched = true
        if (config.title != null) this.displayTitle = String(config.title)
        if (config.subtitle != null) this.displaySubtitle = String(config.subtitle)
        const placeholder = config.inputPlaceholder ?? config.placeholder
        if (placeholder != null) this.displayInputPlaceholder = String(placeholder)
        if (config.welcome != null) this.system.welcome = String(config.welcome)
      } catch (_) {
        // GET 返回 HTML 或非 JSON 时已在上方按 createChat 解析，失败则保留 props 默认值
      }
    },
    escapeHtml (text) {
      if (text === null || text === undefined) return ''
      return String(text)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
    },
    formatLine (line, type) {
      if (type !== 'response') {
        return this.escapeHtml(line)
      }
      return this.formatResponseLine(line)
    },
    formatResponseLine (line) {
      const raw = typeof line === 'string' ? line : String(line || '')
      const trimmed = raw.replace(/\r?\n$/, '')
      if (!trimmed) {
        return '&nbsp;'
      }

      // Markdown 标题：# / ## / ### ... 开头，# 数量代表 h 级别
      const headingMatch = trimmed.match(/^\s*(#{1,6})\s+(.+)$/)
      if (headingMatch) {
        const level = headingMatch[1].length
        const text = headingMatch[2]
        const safe = this.escapeHtml(text)
        return `<strong class="talk-heading-${level}">${safe}</strong>`
      }

      let isBullet = false
      let work = trimmed

      // 列表项：- 开头
      if (/^\s*-\s+/.test(work)) {
        isBullet = true
        work = work.replace(/^\s*-\s+/, '')
      }

      let html = this.escapeHtml(work)

      // 加粗：**text**
      html = html.replace(/\*\*((?:[^*]|\*)+?)\*\*/g, '<strong>$1</strong>')

      if (isBullet) {
        return `<span class="talk-bullet-dot">• </span>${html}`
      }

      return html
    },
    /**
     * 将n8n返回的messages解析为统一的消息内容数组。
     * 支持协议：data.messages 为数组，元素可为：
     * 1) { type: 'text', text } / { type: 'component', name, props }
     * 2) { type: 'table'|'chart'|'form', props } （type 直接是组件名）
     * 3) { name: 'table'|'chart'|'form', props } （缺失 type）
     * 需要表单时：由后端（n8n）返回 type: 'form' 或 type: 'component' + name: 'form'，并在 props.items.options 中给出字段；
     * 不在此写死各业务（元数据/订单/业务域等）的表单项。
     * 兼容旧协议：data.output / data.text 纯文本，按行拆成 type: 'text'
     */
    parseResponseContent (data) {
      /** n8n偶发把整条协议 JSON.stringify 塞进 type=text 的 text 里，需展开为真实 messages */
      const unwrapMessagesJsonStrings = (arr) => {
        if (!Array.isArray(arr)) return arr
        const out = []
        for (const msg of arr) {
          if (!msg || typeof msg !== 'object') {
            out.push(msg)
            continue
          }
          const rawText =
            msg.type === 'text' && msg.text != null
              ? String(msg.text)
              : msg.content != null
                ? String(msg.content)
                : ''
          const t = rawText.trim()
          if (!t.startsWith('{')) {
            out.push(msg)
            continue
          }
          try {
            const parsed = JSON.parse(t)
            if (parsed && Array.isArray(parsed.messages)) {
              out.push(...unwrapMessagesJsonStrings(parsed.messages))
              continue
            }
            if (
              parsed &&
              parsed.output != null &&
              typeof parsed.output === 'string'
            ) {
              try {
                const inner = JSON.parse(parsed.output)
                if (inner && Array.isArray(inner.messages)) {
                  out.push(...unwrapMessagesJsonStrings(inner.messages))
                  continue
                }
              } catch (_) {
                /* output 内层非 JSON */
              }
            }
          } catch (_) {
            /* 非法 JSON，保留原条 */
          }
          out.push(msg)
        }
        return out
      }

      let payload = data
      if (Array.isArray(data)) {
        payload = { messages: data }
      }

      let messages = payload?.messages
      if (Array.isArray(messages) && messages.length) {
        messages = unwrapMessagesJsonStrings(messages)
        const registry = this.componentRegistry || {}
        const normalizeProps = (props) => {
          if (props && typeof props === 'object' && !Array.isArray(props)) return props
          return {}
        }

        const inferComponentMessage = (name, rawProps) => {
          if (!name) return null
          const componentName = String(name)
          if (!registry[componentName]) return null
          return {
            type: 'component',
            name: componentName,
            props: normalizeProps(rawProps)
          }
        }

        const parseOrderListToTable = (text) => {
          if (!text || typeof text !== 'string') return null

          // 只处理类似：当前共有3个数据订单： + 1. **订单ID：40** - 标题：... - 状态：... - 创建时间：...
          if (!/当前共有\s*\d+\s*个数据订单/.test(text)) return null

          // 按每个“订单块”拆分，然后再提取字段
          const blockRegex = /\d+\.\s*(?:\*\*)?订单ID[：:]\s*([^\n*]+?)(?:\*\*)?\s*\n([\s\S]*?)(?=\n\s*\d+\.\s*(?:\*\*)?订单ID[：:]|\s*$)/g
          const items = []
          let match
          while ((match = blockRegex.exec(text)) !== null) {
            const id = (match[1] || '').trim()
            const block = match[2] || ''

            const titleMatch = block.match(/-\s*标题[：:]\s*([^\n]+)\s*/)
            const statusMatch = block.match(/-\s*状态[：:]\s*([^\n]+)\s*/)
            const createdAtMatch = block.match(/-\s*创建时间[：:]\s*([^\n]+)\s*/)

            const title = titleMatch?.[1]?.trim() || ''
            const status = statusMatch?.[1]?.trim() || ''
            const createdAt = createdAtMatch?.[1]?.trim() || ''

            // 至少要能取到 id，避免误匹配
            if (!id) continue

            items.push({
              id,
              title,
              status,
              created_at: createdAt
            })
          }

          if (!items.length) return null

          const headers = [
            { text: '订单ID', value: 'id' },
            { text: '标题', value: 'title' },
            { text: '状态', value: 'status' },
            { text: '创建时间', value: 'created_at' }
          ]

          return {
            type: 'component',
            name: 'table',
            props: { headers, items }
          }
        }

        const normalizeMessage = (msg) => {
          if (msg == null) return { type: 'text', text: '' }
          if (typeof msg === 'string') return { type: 'text', text: msg }
          if (typeof msg !== 'object') return { type: 'text', text: String(msg) }

          const rawProps = msg.props ?? msg.data ?? msg.parameters

          // 明确声明为 component
          if (msg.type === 'component') {
            const comp = inferComponentMessage(msg.name, rawProps)
            if (comp) return comp
          }

          // type 直接是组件名（table/chart/form）
          if (msg.type && registry[msg.type]) {
            const comp = inferComponentMessage(msg.type, rawProps)
            if (comp) return comp
          }

          // 缺失 type，但 name 指向组件名
          if (msg.name && registry[msg.name]) {
            const comp = inferComponentMessage(msg.name, rawProps)
            if (comp) return comp
          }

          // 兜底：如果n8n把订单列表当成纯文本输出，也尝试解析成 table 组件
          if (msg.type === 'text' || Object.prototype.hasOwnProperty.call(msg, 'text')) {
            const text = msg.text != null ? String(msg.text) : (msg.content != null ? String(msg.content) : '')
            const orderTable = parseOrderListToTable(text)
            if (orderTable) return orderTable
          }

          const text =
            msg.text ?? msg.content ?? msg.value ?? msg.output ?? msg.result ?? ''
          return { type: 'text', text: text == null ? '' : String(text) }
        }

        const normalized = messages.map(normalizeMessage)
        /** n8n在成功场景下仍可能返回「暂无回复」，勿当作有效正文（含全角空格、末尾标点） */
        const isPlaceholderNoReply = (t) => {
          const s = String(t == null ? '' : t).replace(/[\s\u3000]+/g, '').trim()
          if (!s) return true
          return /^暂无回复[。！？…]*$/u.test(s)
        }
        const hasRenderable = normalized.some(
          (m) =>
            m &&
            (m.type === 'component' ||
              (m.type === 'text' && !isPlaceholderNoReply(m.text)))
        )
        if (!hasRenderable) {
          const pick =
            (payload?.output != null && String(payload.output).trim()) ||
            (payload?.text != null && String(payload.text).trim()) ||
            ''
          return [
            {
              type: 'text',
              text: pick || '元数据创建成功，请在元数据列表中查看。'
            }
          ]
        }
        return normalized
      }
      const output =
        payload?.output ?? payload?.text ?? (typeof data === 'string' ? data : '')
      const raw = (output && String(output).trim()) ? String(output) : '暂无回复'
      if (raw.startsWith('{') && /"messages"\s*:/.test(raw)) {
        try {
          const p = JSON.parse(raw)
          if (Array.isArray(p.messages) && p.messages.length) {
            return this.parseResponseContent({ messages: p.messages })
          }
        } catch (_) {
          /* 按纯文本处理 */
        }
      }
      raw.split('\n').map((line) => ({ type: 'text', text: line }))
    },
    /** 合并组件默认 props 与n8n返回的 props */
    getComponentProps (name, props) {
      const defaults = this.componentDefaults[name] || {}
      const merged = { ...defaults, ...props }

      // 表单字段：仅原地补充 outlined / dense，禁止 map 成新对象，否则 v-model 改的是副本，collectLatestFormValues 读不到用户填写
      if (
        name === 'form' &&
        merged.items &&
        Array.isArray(merged.items.options)
      ) {
        merged.items.options.forEach((field) => {
          if (!field || typeof field !== 'object') return
          if (field.outlined === undefined) field.outlined = true
          if (field.dense === undefined) field.dense = true
        })
      }

      return merged
    },
    /**
     * 收集最近一次表单填写结果。
     * @param {string} [userText] 当前用户输入；若为新开「数据订单」意图，则忽略仍留在上文的元数据表单，避免 name_zh 被当作订单标题。
     */
    collectLatestFormValues (userText) {
      const t = String(userText || '').trim()
      const wantsDataOrderCreate =
        /(?:创建|新建|申请)\s*(?:数据)?订单|我要\s*(?:创建|新建)?\s*(?:数据)?订单|帮我\s*(?:创建|新建)?\s*(?:数据)?订单/i.test(
          t
        ) && !/(?:查看|列出|列表|搜索|查询|详情).{0,6}(?:订单|数据订单)/.test(t)

      const isMetadataFormKeys = (keys) => {
        const set = new Set(keys)
        return set.has('name_zh') && set.has('data_type')
      }

      for (let i = this.talks.length - 1; i >= 0; i--) {
        const talk = this.talks[i]
        if (!talk || !Array.isArray(talk.content)) continue
        const formItem = talk.content.find(
          (item) =>
            item &&
            item.type === 'component' &&
            item.name === 'form' &&
            item.props &&
            item.props.items &&
            Array.isArray(item.props.items.options)
        )
        if (!formItem) continue
        const values = {}
        formItem.props.items.options.forEach((field) => {
          if (field && field.key) {
            values[field.key] = field.value
          }
        })
        const keys = Object.keys(values).filter(
          (k) => values[k] != null && String(values[k]).trim() !== ''
        )
        if (!keys.length) continue

        if (wantsDataOrderCreate && isMetadataFormKeys(keys)) {
          continue
        }

        return values
      }
      return null
    },
    handleKeyCode (event) {
      if (event.keyCode === 13) {
        if (!event.ctrlKey) {
          event.preventDefault()
          this.handleSendMsg()
        } else {
          this.question += '\n'
        }
      }
    },
    async handleSendMsg () {
      const text = this.question.trim()
      if (!text) return

      this.talks.push({
        id: ++this.talkId,
        type: 'question',
        content: text.split('\n').map((line) => ({ type: 'text', text: line }))
      })
      this.question = ''
      this.loading = true

      try {
        // 含「填写完成」「帮我进行创建」等口语时也必须合并上文表单，否则会只把短句发给 Agent（截图里会判空标题/描述）
        const shouldUseFormValues =
          (
            /(?:创建|新建|申请|录入).{0,8}(?:元数据|订单|数据订单)/i.test(text) ||
            /我已补充.{0,8}(?:信息|内容).{0,8}(?:创建|新建)/i.test(text) ||
            /请帮我.{0,12}(?:进行)?(?:创建|新建)/i.test(text) ||
            /帮我.{0,12}(?:进行)?(?:创建|新建)(?:\s*(?:数据)?订单)?/i.test(text) ||
            /填写完成/u.test(text) ||
            /提交.{0,10}(?:创建|新建|订单)/i.test(text) ||
            /(?:确认|马上|立刻|现在).{0,8}(?:创建|提交)/i.test(text) ||
            /(?:信息|资料).{0,10}(?:已|都).{0,6}(?:填好|写好|补充)/i.test(text)
          ) &&
          !/(?:删除|驳回|审批|通过|(?<!填写)完成|查看|查询|列表|详情|分析).{0,8}(?:订单|数据订单)/.test(
            text
          )

        const formValues = shouldUseFormValues ? this.collectLatestFormValues(text) : null
        let chatInput = text
        if (formValues) {
          const keyLabels = { title: '标题', description: '需求描述' }
          const extraParts = []
          Object.entries(formValues).forEach(([key, val]) => {
            if (val == null) return
            const s = String(val).trim()
            if (s === '') return
            const label = Object.prototype.hasOwnProperty.call(keyLabels, key)
              ? keyLabels[key]
              : key
            extraParts.push(`${label}为「${s}」`)
          })
          if (extraParts.length) {
            chatInput = `${text}。系统已从表单中读取到：${extraParts.join('，')}。`
          }
        }
        const response = await fetch(this.iframeUrl, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            chatInput,
            sessionId: this.sessionId,
            formValues
          })
        })
        if (!response.ok) {
          throw new Error(response.statusText || '请求失败')
        }
        const data = await response.json()
        const content = this.parseResponseContent(data)
        this.talks.push({
          id: ++this.talkId,
          type: 'response',
          content
        })
      } catch (err) {
        const msg = err?.message || err || '网络或服务异常'
        this.$snackbar?.error ? this.$snackbar.error(msg) : this.$message?.({ type: 'error', message: msg })
        this.talks.push({
          id: ++this.talkId,
          type: 'response',
          content: [{ type: 'text', text: `请求失败：${msg}` }]
        })
      } finally {
        this.loading = false
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.point {
  cursor: pointer;
}
.box {
  z-index: 9;
  position: fixed;
  right: 50px;
  bottom: 100px;
}
.assistant-chat-wrapper {
  height: 100%;
}
.assistant-title {
  font-size: 1.25rem;
  font-weight: 600;
}
.assistant-subtitle {
  font-size: 0.875rem;
  opacity: 0.9;
}
.chat-list {
  min-height: 0;
  ::v-deep .v-list-item__content {
    overflow: visible !important;
  }
  ::v-deep .v-list-item__subtitle {
    overflow: visible !important;
    text-overflow: initial !important;
    white-space: normal !important;
    line-clamp: unset !important;
    -webkit-line-clamp: unset !important;
    -webkit-box-orient: initial !important;
  }
}
.round {
  border-radius: 5px;
  max-width: 80%;
}
.welcome-text {
  white-space: pre-line;
}
.talk-line {
  white-space: pre-wrap;
  word-break: break-word;
}
.talk-heading-1,
.talk-heading-2,
.talk-heading-3,
.talk-heading-4,
.talk-heading-5,
.talk-heading-6 {
  font-weight: 600;
}
.talk-heading-1 { font-size: 1.2rem; }
.talk-heading-2 { font-size: 1.1rem; }
.talk-heading-3 { font-size: 1.05rem; }
.talk-heading-4 { font-size: 1rem; }
.talk-heading-5 { font-size: 0.95rem; }
.talk-heading-6 { font-size: 0.9rem; }
.talk-bullet-dot {
  margin-right: 4px;
}
.assistant-component {
  width: 100%;
  max-width: 100%;
  min-width: 0;
}
.send {
  padding: 20px;
  .send-box {
    width: 100%;
    position: relative;
    .btn {
      position: absolute;
      right: 20px;
      bottom: 12px;
    }
    .send-box-area {
      ::v-deep textarea {
        padding: 15px 70px 15px 0 !important;
        max-height: 300px;
        min-height: 60px;
        overflow: auto;
        margin: 0 !important;
      }
    }
  }
}
</style>
