From 50f811399f22b835fd2fc33375fe01ff8eb83dd8 Mon Sep 17 00:00:00 2001
From: wujingjing <gersonwu@qq.com>
Date: 星期一, 21 十月 2024 13:50:04 +0800
Subject: [PATCH] 查询高亮指标

---
 src/components/chat/components/playBar/PlayBar.vue |  364 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 351 insertions(+), 13 deletions(-)

diff --git a/src/components/chat/components/playBar/PlayBar.vue b/src/components/chat/components/playBar/PlayBar.vue
index ca50d83..28f6947 100644
--- a/src/components/chat/components/playBar/PlayBar.vue
+++ b/src/components/chat/components/playBar/PlayBar.vue
@@ -5,18 +5,63 @@
 				<img src="/static/images/wave/PlugIn.png" class="set-icon box-border" />
 			</el-button>
 		</div>
+		<InfoDetail class="text-base" v-model="infoDetailIsShow" :item="detailMapRow" />
+
 		<div class="set-input">
-			<el-input v-elInputFocus @keydown.enter="isTalking || emits('sendClick')" v-model="inputValue" placeholder="鍦ㄨ繖閲岃緭鍏ユ偍鐨勯棶棰樺紑濮嬪拰AI瀵硅瘽" class="set-inputAnswer" />
+			<!-- @input="inputText" -->
+
+			<el-input
+				ref="inputRef"
+				class="relative align-bottom set-inputAnswer"
+				type="textarea"
+				resize="none"
+				:autosize="{ minRows: 1, maxRows: 8 }"
+				v-elInputFocus
+				@keydown="keydownInput"
+				@input="inputText"
+				v-model="inputValue"
+				placeholder="鍦ㄨ繖閲岃緭鍏ユ偍鐨勯棶棰樺紑濮嬪拰AI瀵硅瘽"
+			>
+			</el-input>
+			<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-sm text-nowrap overflow-hidden text-ellipsis max-w-80 mb-1 px-2">Ctrl+鏁板瓧蹇嵎杈撳叆</div>
+				<div class="text-gray-400 text-sm 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="similarClick(item)"
+					>
+						<span class="text-sm text-gray-500 pr-1.5">{{ index + 1 }}</span>
+						<template v-if="sentenceSplitMap?.[item.question]">
+							<template v-for="part in sentenceSplitMap[item.question]">
+								<span
+									v-if="part.isKeyword"
+									class="text-blue-400 font-bold cursor-pointer"
+									>{{ part.partStr }}</span
+								>
+								<span v-else>{{ part.partStr }}</span>
+							</template>
+						</template>
+					</div>
+				</div>
+			</div>
 		</div>
 		<div class="h100 flex items-center">
 			<div class="upload_img space-y">
 				<div class="imgbox cursor-pointer flex items-center">
-					<el-button title="AI鐪嬪浘" class="cursor-pointer" link style="margin-left: unset">
+					<!-- <el-button title="AI鐪嬪浘" class="cursor-pointer" link style="margin-left: unset">
 						<img src="/static/images/wave/LookImg.png" class="set-img-icon box-border" />
-					</el-button>
-					<el-button title="AI璇煶瀵硅瘽" class="cursor-pointer" link style="margin-left: unset">
+					</el-button> -->
+					<!-- <el-button title="AI璇煶瀵硅瘽" class="cursor-pointer" link style="margin-left: unset" @click="audioChangeWord">
 						<img src="/static/images/wave/HeadImg.png" class="set-img-icon box-border" />
-					</el-button>
+					</el-button> -->
 
 					<el-button title="鍙戦��" :disabled="isTalking" class="cursor-pointer" link @click="emits('sendClick')">
 						<div class="send">
@@ -26,19 +71,310 @@
 				</div>
 			</div>
 		</div>
+		<VoicePage
+			v-model:isShow="voicePageIsShow"
+			v-show="voicePageIsShow"
+			@submit="(cb) => emits('sendClick', cb)"
+			@updateInputValue="updateInputValue"
+			:isHome="isHome"
+		/>
 	</div>
 </template>
 
 <script setup lang="ts">
-import { reactive } from 'vue';
+import type { InputInstance } from 'element-plus';
+import { ElMessage } from 'element-plus';
+import getCaretCoordinates from 'textarea-caret';
+import { computed, nextTick, ref } from 'vue';
+import InfoDetail from './InfoDetail.vue';
+import VoicePage from './voicePage/VoicePage.vue';
+import { getMetricsNames, querySimilarityHistory } from '/@/api/ai/chat';
 
+import { onClickOutside } from '@vueuse/core';
+import _ from 'lodash';
 const emits = defineEmits(['sendClick']);
-
-defineProps(['isTalking'])
-
+const props = defineProps(['isTalking', 'isHome']);
+const voicePageIsShow = defineModel('voicePageIsShow', {
+	type: Boolean,
+	default: false,
+});
 const inputValue = defineModel({
-    type:String
-})
+	type: String,
+});
+
+const tipIsShow = computed(() => !!inputValue.value.trim() && similarList.value?.length > 0 && triggerShow.value);
+const triggerShow = ref(false);
+const inputRef = ref<InputInstance>(null);
+
+const updateInputValue = (val) => {
+	inputValue.value = val;
+};
+const keydownInput = (e) => {
+	if (props.isTalking) return;
+	const isEnterInput = !e.shiftKey && e.key == 'Enter';
+	const isDigitalInput = e.ctrlKey && e.code.startsWith('Digit') && tipIsShow.value;
+	if (isEnterInput || isDigitalInput) {
+		e.cancelBubble = true; //ie闃绘鍐掓场琛屼负
+		e.stopPropagation(); //Firefox闃绘鍐掓场琛屼负
+		e.preventDefault(); //鍙栨秷浜嬩欢鐨勯粯璁ゅ姩浣�*鎹㈣
+		if (isEnterInput) {
+			//浠ヤ笅澶勭悊鍙戦�佹秷鎭唬鐮�
+			emits('sendClick');
+		} else if (isDigitalInput) {
+			const num = Number(e.code.replace('Digit', ''));
+			const mapValue = similarList.value[num - 1]?.question;
+			if (mapValue) {
+				inputValue.value = mapValue;
+				triggerShow.value = false;
+			}
+		}
+	}
+};
+
+const similarClick = (item) => {
+	if (item.question) {
+		inputValue.value = item.question;
+		triggerShow.value = false;
+	}
+};
+
+const tipEleRef = ref<HTMLDivElement>(null);
+
+const popUpPosition = ref({
+	left: null,
+	bottom: null,
+});
+
+onClickOutside(tipEleRef, () => {
+	triggerShow.value = false;
+});
+const inputText = (text) => {
+	nextTick(() => {
+		setTimeout(() => {
+			const container = inputRef.value.$el;
+
+			const textAreaEl = inputRef.value.$el.firstElementChild;
+			const caret = getCaretCoordinates(textAreaEl, textAreaEl.selectionEnd);
+
+			const bottomOffset = 10;
+			const leftOffset = 9;
+			popUpPosition.value.left = caret.left + leftOffset;
+			popUpPosition.value.bottom = container.offsetHeight + bottomOffset;
+			triggerShow.value = true;
+
+			if (lastIsFinish) {
+				querySimilarityApi(text);
+			}
+		}, 0);
+	});
+};
+// 瀵� question 杩涜鍒嗗壊
+const sentenceSplitMap = ref<
+	Record<
+		string,
+		{
+			partStr: string;
+			startIndex: number;
+			endIndex: number;
+			isKeyword: boolean;
+		}[]
+	>
+>({});
+const similarList = ref([]);
+let lastIsFinish = true;
+const querySimilarityApi = async (text: string) => {
+	if (!text) return;
+	lastIsFinish = false;
+	const res = await querySimilarityHistory({
+		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 audioChangeWord = () => {
+	navigator.getUserMedia(
+		{ audio: true },
+		function onSuccess(stream) {
+			voicePageIsShow.value = true;
+		},
+		function onError(error) {
+			ElMessage.warning('璇锋墦寮�楹﹀厠椋庢潈闄�');
+		}
+	);
+};
+/**
+ * 鍒囧垎鍙ュ瓙锛屽尮閰嶈瘝鐢� 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 metricsNamesPromise = new Promise(async (resolve, reject) => {
+	const metricNames = (await getMetricsNames())?.values ?? [];
+	resolve(metricNames);
+});
+
+//#region ======================  楂樹寒鎸囨爣鐐瑰嚮======================
+const infoDetailIsShow = ref(false);
+const detailMapRow = ref(null);
+
+const tipMetricsClick = (row) => {
+	detailMapRow.value = row;
+	infoDetailIsShow.value = true;
+};
+//#endregion
 </script>
 <style scoped lang="scss">
 .set-waterTitle {
@@ -127,14 +463,16 @@
 		display: inline-block;
 		width: 100%;
 		.set-inputAnswer {
-			min-height: 36px;
-			height: 36px;
 			padding: 3px 0;
 			line-height: 20px;
 			border: none;
 			background-color: transparent;
 			color: #333;
 			font-size: 15px;
+			:deep(.el-textarea__inner) {
+				// 鍘婚櫎绾�
+				box-shadow: none;
+			}
 		}
 		:deep(.el-input__wrapper) {
 			box-shadow: unset;

--
Gitblit v1.9.3