<script>
import { hitTest } from '@/api/generatedApi'
import { getRerankModels } from '@/api/knowledge/hit-test'
import MarkdownIt from 'markdown-it'
import markdownItKatex from 'markdown-it-katex'

const md = new MarkdownIt({
  html: true
}).use(markdownItKatex)
export default {
  name: 'hitTest',
  data() {
    return {
      markdown: '',
      markdownHtml: '',
      md,
      datasetId: void 0,
      params: {
        search_method: 'hybrid_search',
        score_threshold_enable: false,
        score_threshold: 0,
        top_k: 5,
        reranking_enable: false,
        reranking_model: {
          reranking_provider_name: '',
          reranking_model_name: ''
        }
      },
      paramsConfig: {
        weights: 0,
        visible: false,
        /**
         * @description  是否启用 Rerank 模型
         */
        reranking_enable: true,
        /**
         * @description 检索方法
         */
        search_method: 'hybrid_search', // 默认选择混合检索
        /**
         * @description score 阈值
         */
        score_threshold: 0,
        /**
         *  @description TOPK 设置
         */
        top_k: 5, // 默认引用分段数,
        /**
         * @description rerank 模型名称
         */
        reranking_model: '',
        /**
         * @description 模型列表
         */
        rerankModelOptions: []
      },
      inputMessage: '',
      loading: false,
      messages: [],
      // 对话框配置
      dialogConfig: {
        visible: false,
        title: '查看详情',
        width: '500px',
        isShowFooter: false
      }
    }
  },
  watch: {
    'paramsConfig.reranking_enable': {
      handler() {
        // 如果 isRanking 为 false，清空 reranking_model, 防止错误提交
        if (!this.paramsConfig.reranking_enable) {
          // 删除 params 中的 reranking_model
          delete this.params.reranking_model
          return
        }
        this.paramsConfig.reranking_model = void 0
      },
      deep: true
    }
  },
  created() {
    // 从 路由参数中获取datasetId
    this.datasetId = this.$route.query.datasetId

    // 获取 rerank 模型列表
    getRerankModels().then(res => {
      const { content } = res.content
      this.paramsConfig.rerankModelOptions = content.flatMap(
        item => item.models
      )
    })
  },
  methods: {
    computedWidth(num) {
      let n = num ? num.toFixed(3) : 0.0

      //   计算百分比
      return (n / 1) * 100 + '%'
    },

    handleEnsureClick() {
      // 关闭内容
      this.paramsConfig.visible = false

      // 更新 params
      this.params = {
        search_method: this.paramsConfig.searchMode,
        score_threshold: this.paramsConfig.score_threshold,
        top_k: this.paramsConfig.top_k,
        reranking_model: this.paramsConfig.reranking_model,
        reranking_enable: this.paramsConfig.reranking_enable
      }
    },
    sendMessage() {
      const contentArea = this.$refs.contentArea
      if (!this.inputMessage.trim()) return
      // 显示骨架屏
      this.loading = true
      // 发送请求
      hitTest({
        datasetsId: this.datasetId,
        query: this.inputMessage,
        retrieval_model: this.params
      }).then(res => {
        let { content } = res.content

        content = content.map(item => {
          return {
            ...item,
            title: this.inputMessage.trim()
          }
        })
        this.messages.push(content)
        this.loading = false

        this.$nextTick(() => {
          // 滚动到底部
          contentArea.scrollTop = contentArea.scrollHeight
        })
      })
      // 清空输入框
      // this.inputMessage = ''
    },
    formatScore(score) {
      return score.toFixed(3)
    },
    handleCardClick(contentItem) {
      this.dialogConfig.visible = true
      this.markdownHtml = contentItem.segment.content
    }
  }
}
</script>

<template>
  <div class="hit-test-container">
    <!-- 中间内容区域 -->
    <div ref="contentArea" class="content-area">
      <div class="empty-state" v-if="messages.length === 0">
        <!--        <el-image alt="当前会话尚无记录"></el-image>-->
        <p>这里会显示命中测试的记录</p>
      </div>
      <div
        class="messages mb10"
        v-else
        v-for="(content, index) in messages"
        :key="index"
      >
        <!--   对话区域     -->
        <el-card>
          <!--    头像 和 会话内容      -->
          <div class="flex align-items-c mb10">
            <!-- <el-avatar
              :size="40"
              class="user-avatar"
              shape="square"
            >
              admin
            </el-avatar> -->

            <H4 class="hit-title">
              {{
                content && content.length > 0 && content[0].title
                  ? content[0].title
                  : '根据您的问题，我们为你提供以下建议'
              }}
            </H4>
          </div>

          <el-card
            v-for="contentItem in content"
            :key="contentItem.segment.content"
            shadow="hover"
            body-style="padding: 10px;backgroundColor: f2f2f2"
            class="mb10"
          >
            <div
              class="flex justify-content-b"
              @click="handleCardClick(contentItem)"
            >
              <!-- 内容详情 -->
              <section>
                <p class="mb10 fs13 multi-ellipsis-2 mr20">
                  {{ contentItem.segment.content }}
                </p>
                <el-tag
                  class="mr5 mb5"
                  type="info"
                  size="small"
                  v-for="(keyword, index) in contentItem.segment.keywords"
                  :key="index + keyword"
                >
                  {{ index + keyword }}
                  #{{ keyword }}
                </el-tag>
              </section>
              <!-- score -->
              <span class="score">
                <div class="hit-rate score" type="success">
                  <div
                    class="hit-back"
                    :style="{
                      width: computedWidth(contentItem.score)
                    }"
                  ></div>
                  <div style="left:8px">
                    score:
                    {{
                      contentItem.score ? contentItem.score.toFixed(3) : '0.000'
                    }}
                  </div>
                </div>
              </span>
            </div>
          </el-card>
        </el-card>
        <!-- 加载状态 -->
        <!-- <div
          v-if="loading"
          class="loading-message"
        >
          <el-skeleton
            style="width: 100%"
            animated
          >
            <template slot="template">
              <el-skeleton-item
                variant="p"
                style="width: 100%; height: 60px"
              />
              <el-skeleton-item
                variant="p"
                style="width: 90%; height: 20px"
              />
              <el-skeleton-item
                variant="p"
                style="width: 80%; height: 20px"
              />
            </template>
          </el-skeleton>
        </div> -->
      </div>
    </div>

    <!-- 底部输入框 -->
    <div class="footer">
      <!-- 参数内容 -->
      <el-popover placement="top" width="480" v-model="paramsConfig.visible">
        <el-popover
          trigger="manual"
          placement="right"
          v-model="paramsConfig.visible"
          width="600"
        >
          <div slot="reference">
            <div class="search-params-container">
              <h3>检索模式</h3>

              <el-radio-group
                v-model="paramsConfig.search_method"
                class="search-mode-options"
              >
                <div
                  class="search-mode-option"
                  :class="{
                    active: paramsConfig.search_method === 'semantic_search'
                  }"
                >
                  <el-radio
                    v-model="paramsConfig.search_method"
                    label="semantic_search"
                    key="semantic_search"
                  >
                    向量检索
                    <p class="option-desc">
                      通过生成查询嵌入并查询与其向量表示最相似的文本分段
                    </p>
                  </el-radio>
                </div>

                <div
                  class="search-mode-option"
                  :class="{
                    active: paramsConfig.search_method === 'full_text_search'
                  }"
                >
                  <el-radio
                    v-model="paramsConfig.search_method"
                    label="full_text_search"
                    key="full_text_search"
                  >
                    全文检索
                    <p class="option-desc">
                      索引文档中的所有词汇，从而允许用户查询任意词汇，并返回包含这些词汇的文本片段
                    </p>
                  </el-radio>
                </div>

                <div
                  class="search-mode-option"
                  :class="{
                    active: paramsConfig.search_method === 'hybrid_search'
                  }"
                >
                  <el-radio
                    v-model="paramsConfig.search_method"
                    label="hybrid_search"
                    key="hybrid_search"
                  >
                    混合检索
                    <p class="option-desc">
                      同时执行全文检索和向量检索，并应用重排序步骤，从两类查询结果中选择匹配用户问题的最佳结果，用户可以选择设置权重或配置重新排序模型
                    </p>
                  </el-radio>
                </div>
              </el-radio-group>

              <div class="action-buttons">
                <el-button size="medium" @click="paramsConfig.visible = false"
                  >取消
                </el-button>
                <el-button
                  type="primary"
                  size="medium"
                  @click="handleEnsureClick"
                  >确定
                </el-button>
              </div>
            </div>
          </div>

          <!-- 具体的参数内容 -->
          <section class="mr20">
            <div class="grid grid-cols-2">
              <div class="col-span-1 flex align-items-c ">
                <span class="mr5 flex">
                  <el-switch
                    v-model="paramsConfig.score_threshold_enable"
                  ></el-switch>
                  <span class="ml10">Score 阈值</span>
                </span>
                <el-input-number
                  v-if="paramsConfig.score_threshold_enable"
                  v-model="paramsConfig.score_threshold"
                  :precision="3"
                  :step="0.001"
                  :min="0"
                  size="small"
                  controls-position="right"
                />
              </div>

              <div class="col-span-1 flex align-items-c">
                <span class="param-label mr5">TOP K</span>
                <el-input-number
                  v-model="paramsConfig.top_k"
                  :min="1"
                  :max="20"
                  size="small"
                  controls-position="right"
                />
              </div>
            </div>
            <!-- 参数设置 -->
            <section
              class="mt10 mr10"
              v-if="paramsConfig.search_method === 'hybrid_search'"
            >
              <el-radio-group
                v-model="paramsConfig.reranking_enable"
                class="search-mode-options"
              >
                <div
                  class="search-mode-option"
                  :class="{ active: !paramsConfig.reranking_enable }"
                >
                  <el-radio :label="false">
                    权重设置
                    <p class="option-desc">
                      通过调整分配的权重，重新排序策略确定是优先进行语义匹配还是关键字匹配。
                    </p>
                  </el-radio>
                </div>
                <div
                  class="search-mode-option"
                  :class="{ active: paramsConfig.reranking_enable }"
                >
                  <el-radio :label="true">
                    Rerank 模型
                    <p class="option-desc">
                      重排序模型将根据候选文档列表与用户问题语义匹配度进行重新排序，从而改进语义排序的结果
                    </p>
                  </el-radio>
                </div>
              </el-radio-group>
            </section>

            <!-- 当选择了 rerank 模型之后或者不是混合搜索的选项时显示 -->
            <section
              class="mt15"
              v-if="
                paramsConfig.reranking_enable ||
                  paramsConfig.search_method !== 'hybrid_search'
              "
            >
              <!-- 当没有设置权重或者不是混合检索时显示-->
              <div
                class="mb15"
                v-if="
                  paramsConfig.reranking_enable ||
                    paramsConfig.search_method !== 'hybrid_search'
                "
              >
                <el-switch v-model="paramsConfig.reranking_enable"></el-switch>
                <span> Rerank 模型</span>
              </div>
              <el-select
                v-if="paramsConfig.reranking_enable"
                v-model="paramsConfig.rerank_model"
                size="medium"
                placeholder="选择一个模型"
              >
                <el-option
                  v-for="item in paramsConfig.rerankModelOptions"
                  :key="item.model"
                  :label="item.model"
                  :value="item.value"
                >
                  {{ item.model }}
                </el-option>
              </el-select>
            </section>
            <!-- 权重设置 -->
            <section v-else>
              <RSlider
                v-model="paramsConfig.weights"
                :hit-test="true"
                :show-tooltip="false"
              />
            </section>
          </section>
        </el-popover>
        <el-button
          icon="el-icon-setting"
          size="small"
          class="mr20"
          slot="reference"
          style="padding:11px 15px"
          >参数设置
        </el-button>
      </el-popover>

      <el-input
        v-model="inputMessage"
        placeholder="请输入"
        class="message-input"
        size="medium"
        @keyup.enter.native="sendMessage"
        style="border-radius: 0px"
      >
        <template slot="append">
          <el-button
            icon="el-icon-s-promotion"
            @click="sendMessage"
            style="background: #fff"
            size="small"
          ></el-button>
        </template>
      </el-input>
    </div>

    <el-drawer
      append-to-body
      :modal="false"
      :wrapperClosable="false"
      :visible.sync="dialogConfig.visible"
      :title="dialogConfig.title"
      :width="dialogConfig.width"
      :is-show-footer="dialogConfig.isShowFooter"
    >
      <div class="flex">
        <div class="view-body" v-html="markdownHtml"></div>
      </div>
    </el-drawer>
  </div>
</template>

<style lang="scss" scoped>
@import '@/assets/sass/renderSass/theme.scss';
.hit-test-container {
  font-family: 'PingFang SC', sans-serif;
  transition: all 0.5s;

  display: flex;
  flex-direction: column;
  height: 86vh;
  background-color: #f0f4fa;
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  overflow: hidden;
  & .hit-title {
    position: relative;
    padding: 0 20px;
    margin-bottom: 10px;
    &:after {
      content: '';
      position: absolute;
      left: 0;
      top: 2px;
      width: 15px;
      height: 15px;
      background: rgba(79, 71, 245, 0.101961);
    }
  }
  .header {
    // padding: 10px 20px;
    border-bottom: 1px solid #e4e7ed;
    background-color: #fff;
  }

  .content-area {
    flex: 1;
    overflow-y: auto;
    padding: 10px;
    display: flex;
    flex-direction: column;

    .empty-state {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100%;
      color: #909399;

      .el-image {
        width: 120px;
        margin-bottom: 20px;
      }
    }

    .messages {
      display: flex;
      flex-direction: column;
      gap: 20px;

      .message-item {
        display: flex;
        flex-direction: column;

        &.question {
          align-items: flex-end;

          .question-message {
            background-color: #ecf5ff;
            border-radius: 8px 0 8px 8px;
            padding: 12px;
            max-width: 80%;

            .message-header {
              display: flex;
              align-items: center;
              margin-bottom: 8px;

              .message-info {
                margin-left: 8px;
              }

              .message-title {
                font-weight: 500;
                font-size: 14px;
                color: $--color-primary;
              }
            }

            .message-content {
              word-break: break-word;
            }
          }
        }

        &.answer {
          align-items: flex-start;

          .answer-message {
            background-color: #fff;
            border-radius: 0 8px 8px 8px;
            padding: 12px;
            max-width: 90%;
            box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);

            .message-header {
              display: flex;
              align-items: center;
              margin-bottom: 8px;

              .message-info {
                margin-left: 8px;
              }

              .message-title {
                font-weight: 500;
                font-size: 14px;
                color: #67c23a;
              }
            }

            .message-content {
              margin-bottom: 16px;
              word-break: break-word;
            }

            .references {
              display: flex;
              flex-direction: column;
              gap: 12px;

              .reference-item {
                border: 1px solid #ebeef5;
                border-radius: 4px;
                padding: 12px;
                background-color: #fafafa;

                .reference-header {
                  display: flex;
                  align-items: center;
                  margin-bottom: 8px;

                  .reference-index {
                    width: 24px;
                    height: 24px;
                    border-radius: 50%;
                    background-color: $--color-primary;
                    color: #fff;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    font-size: 12px;
                    margin-right: 8px;
                  }

                  .reference-title {
                    flex: 1;
                    font-weight: 500;
                  }

                  .reference-score {
                    color: $--color-primary;
                    font-weight: 500;
                  }
                }

                .reference-content {
                  margin-bottom: 8px;
                  font-size: 13px;
                  color: #606266;
                  line-height: 1.5;
                }

                .reference-footer {
                  display: flex;
                  justify-content: flex-start;
                }
              }
            }
          }
        }
      }

      .loading-message {
        padding: 12px;
        background-color: #fff;
        border-radius: 8px;
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
        margin-top: 10px;
      }
    }
  }

  .footer {
    padding: 10px 20px;
    background-color: #fff;
    border-top: 1px solid #e4e7ed;
    display: flex;
    align-items: center;

    .el-popover__reference {
      margin-right: 10px;
    }

    .message-input {
      flex: 1;
      /deep/ .el-input__inner {
        border-radius: 0 !important;
      }
    }
  }
}

.search-mode-options {
  width: 100%;
  margin-bottom: 20px;

  .search-mode-option {
    padding: 10px;
    border: 1px solid #e4e7ed;
    border-radius: 4px;
    margin-bottom: 10px;

    &.active {
      border-color: $--color-primary;
      background-color: #ecf5ff;
    }

    .option-desc {
      margin: 5px 0 0 24px;
      font-size: 12px;
      text-wrap: wrap;
      color: #909399;
      line-height: 1.4;
    }
  }
}
.hit-rate {
  min-width: 100px;
  height: 25px;
  font-size: 12px;
  //background: red;
  border-radius: 5px;
  position: relative;
  border: 1px solid rgba(79, 71, 245, 0.101961);
  overflow: hidden;

  & > div {
    position: absolute;
    height: 100%;
    line-height: 22px;
    text-align: center;
    color: #4f47f5;
    font-weight: 600;
  }
  & .hit-back {
    position: absolute;
    height: 25px;
    width: 50px;
    top: -1px;

    border-radius: 5px 0 0 5px;
    background: rgba(79, 71, 245, 0.101961);
  }
}
// 参数设置弹窗样式
.search-params-container {
  padding: 10px;

  h3 {
    margin-top: 0;
    margin-bottom: 15px;
    font-size: 16px;
    font-weight: 500;
  }

  .action-buttons {
    display: flex;
    justify-content: flex-end;

    .el-button {
      margin-left: 10px;
    }
  }
}
</style>
