<template>
|
<div
|
v-show="tipIsShow"
|
ref="tipEleRef"
|
class="absolute rounded-md bg-white border border-solid border-gray-400 py-2 z-10"
|
:style="{ left: popUpPosition.left + 'px', bottom: popUpPosition.bottom + 'px' }"
|
>
|
<div class="font-bold text-nowrap overflow-hidden text-ellipsis max-w-80 mb-1 px-2">Ctrl+数字快捷输入</div>
|
<div class="text-gray-400 text-nowrap overflow-hidden text-ellipsis max-w-80 mb-1 px-2">{{ inputValue }}</div>
|
<div class="max-w-96 flex flex-col">
|
<div
|
class="hover:bg-gray-300 py-2 cursor-pointer px-5 text-nowrap overflow-hidden text-ellipsis"
|
v-for="(item, index) in similarList"
|
:key="index"
|
@click="applySimilarItem(item)"
|
>
|
<span class="text-gray-500 pr-1.5">{{ index + 1 }}</span>
|
<!-- <template v-if="sentenceSplitMap?.[item.question]">
|
<span
|
v-for="(part, index) in sentenceSplitMap[item.question]"
|
:key="index"
|
:class="[part.isKeyword ? 'text-blue-400 font-bold cursor-pointer' : '']"
|
>{{ part.partStr }}</span
|
>
|
</template> -->
|
<span>{{ item }}</span>
|
|
</div>
|
</div>
|
|
|
</div>
|
</template>
|
|
<script setup lang="ts" name="InputTip">
|
import { sortBy } from 'lodash-es';
|
import { computed, ref } from 'vue';
|
import { getMetricsNames, querySimilarityHistory, querySimilarityScenePrompt } from '/@/api/ai/chat';
|
import { activeGroupType } from '/@/stores/chatRoom';
|
import { onClickOutside } from '@vueuse/core';
|
const props = defineProps<{
|
isHome: boolean;
|
inputValue: string;
|
}>();
|
const emit = defineEmits(['updateInputValue']);
|
const similarList = ref([]);
|
const triggerShow = ref(false);
|
const tipIsShow = computed(() => !!props.inputValue?.trim() && similarList.value?.length > 0 && triggerShow.value);
|
|
const popUpPosition = ref({
|
left: null,
|
bottom: null,
|
});
|
|
const tipEleRef = ref<HTMLDivElement>(null);
|
// 对 question 进行分割
|
const sentenceSplitMap = ref<
|
Record<
|
string,
|
{
|
partStr: string;
|
startIndex: number;
|
endIndex: number;
|
isKeyword: boolean;
|
}[]
|
>
|
>({});
|
|
const applySimilarItem = (item) => {
|
const question = item;
|
if (question) {
|
emit('updateInputValue', question);
|
triggerShow.value = false;
|
}
|
};
|
|
const applyIndexItem = (index) => {
|
const item = similarList.value[index];
|
applySimilarItem(item)
|
};
|
let lastIsFinish = true;
|
// const metricsNamesPromise = new Promise((resolve, reject) => {
|
// getMetricsNames().then((res) => {
|
// const metricNames = res?.values ?? [];
|
// resolve(metricNames);
|
// });
|
// });
|
|
/**
|
* 切分句子,匹配词用 isKeyword 标记
|
* @param sentences
|
* @param keywords
|
*/
|
const getSentenceMatchMap = (sentences: string[], keywords: any[]) => {
|
if (!sentences || sentences.length === 0) return null;
|
if (!keywords || keywords.length === 0) {
|
return sentences.map((item) => ({
|
partStr: item,
|
startIndex: 0,
|
endIndex: item.length,
|
isKeyword: false,
|
}));
|
}
|
let sentenceMatchMap = {};
|
|
sentences.map((sentence) => {
|
if (!sentenceMatchMap[sentence]) {
|
let sentenceMatchList = [];
|
keywords.map((keyword) => {
|
const matchList = [...sentence.matchAll(keyword)].map((item) => {
|
return {
|
partStr: item[0],
|
startIndex: item.index,
|
endIndex: item.index + item[0].length,
|
};
|
});
|
sentenceMatchList = sentenceMatchList.concat(matchList);
|
});
|
|
let nextIsMerge = false;
|
const checkNextIsMerge = (value, index, array) => {
|
nextIsMerge = false;
|
if (index === array.length - 1) return;
|
const nextValue = array[index + 1];
|
|
// 通过 nextIsMerge 控制下一元素是否需要使用
|
if (value.endIndex > nextValue.startIndex) {
|
nextIsMerge = true;
|
}
|
};
|
|
// 按 startIndex 排序,消除彼此之间重合元素
|
sentenceMatchList = sortBy(sentenceMatchList, (item) => item.startIndex).filter((value, index, array) => {
|
if (nextIsMerge) {
|
checkNextIsMerge(value, index, array);
|
return false;
|
}
|
checkNextIsMerge(value, index, array);
|
return true;
|
});
|
|
sentenceMatchMap[sentence] = sentenceMatchList;
|
}
|
});
|
|
for (const sentence of Object.keys(sentenceMatchMap)) {
|
const matchList = sentenceMatchMap[sentence];
|
const result = [];
|
|
if (matchList.length === 0) {
|
result.push({
|
partStr: sentence,
|
startIndex: 0,
|
endIndex: sentence.length,
|
isKeyword: false,
|
});
|
|
sentenceMatchMap[sentence] = result;
|
|
continue;
|
}
|
|
matchList.forEach((value, index, array) => {
|
// 匹配词恰好不是位于结束位置
|
if (array.length - 1 === index && value.endIndex !== array.length) {
|
result.push({
|
...value,
|
isKeyword: true,
|
});
|
|
if (value.endIndex !== sentence.length) {
|
result.push({
|
partStr: sentence.slice(value.endIndex, sentence.length),
|
startIndex: value.endIndex,
|
endIndex: sentence.length,
|
isKeyword: false,
|
});
|
}
|
|
// 如果数组只有一个元素,前面的也需要加进去
|
if (array.length === 1 && value.startIndex !== 0) {
|
result.unshift({
|
partStr: sentence.slice(0, value.startIndex),
|
startIndex: 0,
|
endIndex: value.startIndex,
|
isKeyword: false,
|
});
|
}
|
|
return;
|
}
|
|
// 匹配词恰好不是位于起始位置
|
if (value.startIndex !== 0 && index === 0) {
|
result.push({
|
...value,
|
isKeyword: true,
|
});
|
result.unshift({
|
partStr: sentence.slice(0, value.startIndex),
|
startIndex: 0,
|
endIndex: value.startIndex,
|
isKeyword: false,
|
});
|
|
return;
|
}
|
|
// 恰好位于第一个
|
if (index === 0) {
|
result.push({
|
...value,
|
isKeyword: true,
|
});
|
return;
|
}
|
|
// 中间有非关键词
|
if (array[index - 1].endIndex !== value.startIndex) {
|
result.push({
|
partStr: sentence.slice(array[index - 1].endIndex, value.startIndex),
|
startIndex: array[index - 1].endIndex,
|
endIndex: value.startIndex,
|
isKeyword: false,
|
});
|
|
result.push({
|
...value,
|
isKeyword: true,
|
});
|
}
|
});
|
|
sentenceMatchMap[sentence] = result;
|
}
|
|
return sentenceMatchMap;
|
};
|
|
const querySimilarityApi = async (text: string) => {
|
if (!text) return;
|
lastIsFinish = false;
|
const res = await querySimilarityScenePrompt({
|
scene_id:'map',
|
question: text,
|
});
|
lastIsFinish = true;
|
const handleValues = res?.values ?? [];
|
|
similarList.value = props.isHome ? handleValues.slice(0, 3) : handleValues;
|
// metricsNamesPromise.then((value) => {
|
// sentenceSplitMap.value = getSentenceMatchMap(
|
// similarList.value.map((item) => item.question),
|
// value as string[]
|
// );
|
// });
|
};
|
|
const triggerInputTip = (text, position: { left: number; bottom: number }) => {
|
const bottomOffset = 10;
|
const leftOffset = 48;
|
popUpPosition.value.left = position.left + leftOffset;
|
popUpPosition.value.bottom = position.bottom + bottomOffset;
|
triggerShow.value = true;
|
|
if (lastIsFinish) {
|
querySimilarityApi(text);
|
}
|
};
|
onClickOutside(tipEleRef, () => {
|
triggerShow.value = false;
|
});
|
|
|
//#region ====================== 高亮指标点击======================
|
const infoDetailIsShow = ref(false);
|
const detailMapRow = ref(null);
|
|
const tipMetricsClick = (row) => {
|
detailMapRow.value = row;
|
infoDetailIsShow.value = true;
|
};
|
//#endregion
|
defineExpose({
|
triggerInputTip,
|
applyIndexItem,
|
});
|
</script>
|
<style scoped lang="scss"></style>
|