This commit is contained in:
lyc 2025-02-18 17:32:10 +08:00
parent 327e7d78ad
commit e27f7e0018
4 changed files with 241 additions and 110 deletions

View File

@ -1,9 +1,9 @@
<template> <template>
<el-dialog v-model="isDialog" :show-close="false" width="800" append-to-body destroy-on-close> <el-dialog v-model="model" :show-close="false" width="800" append-to-body destroy-on-close>
<template #header> <template #header>
<div class="custom-header flex"> <div class="custom-header flex">
<span>{{ item.name }}</span> <span>{{ item.name }}</span>
<i class="iconfont icon-guanbi" @click="isDialog = false"></i> <i class="iconfont icon-guanbi" @click="model = false"></i>
</div> </div>
</template> </template>
<div class="dialog-content"> <div class="dialog-content">
@ -56,7 +56,7 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue' import { ref, reactive, onMounted, watch, onUnmounted } from 'vue'
import { completion, docList } from '@/api/mode/index' import { completion, docList } from '@/api/mode/index'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import { dataSetJson } from '@/utils/comm.js' import { dataSetJson } from '@/utils/comm.js'
@ -68,7 +68,7 @@ const userInfo = useUserStore().user
const textarea = ref('') const textarea = ref('')
const isDialog = defineModel() const model = defineModel()
const props = defineProps({ const props = defineProps({
item: { item: {
@ -91,6 +91,12 @@ const props = defineProps({
} }
}) })
watch(model, (newVal) =>{
if(newVal){
msgList.value.length = 0
}
})
const emit = defineEmits(['saveEdit']) const emit = defineEmits(['saveEdit'])
const loaded = ref(false) const loaded = ref(false)
@ -150,7 +156,7 @@ const getCompletion = async (val) => {
} }
const saveAdjust = (item) =>{ const saveAdjust = (item) =>{
isDialog.value = false model.value = false
emitter.emit('onSaveAdjust', item.msg) emitter.emit('onSaveAdjust', item.msg)
} }

View File

@ -4,12 +4,12 @@
<div class="container-right-header flex"> <div class="container-right-header flex">
<el-dropdown @command="changeTemplate" :hide-on-click="false"> <el-dropdown @command="changeTemplate" :hide-on-click="false">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
{{ curTemplate.ex3 ? `平台-${curTemplate.name}` : `个人-${curTemplate.name}` }} {{ curTemplate.ex3 ? `${curTemplate.name}(平台)` : `${curTemplate.name}(个人)` }}
<i class="iconfont icon-xiangxia"></i> <i class="iconfont icon-xiangxia"></i>
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item v-for="item in templateList" :command="item" :key="item.id">{{ item.ex3 ? `平台-${item.name}` : `个人-${item.name}` <el-dropdown-item v-for="item in templateList" :command="item" :key="item.id">{{ item.ex3 ? `${item.name}(平台)` : `${item.name}(个人)`
}}</el-dropdown-item> }}</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
@ -29,9 +29,9 @@
</div> </div>
</div> </div>
<!--List--> <!--List-->
<div class="container-right-list" ref="listRef"> <div class="container-right-list" ref="outerContainer">
<template v-for="(item, index) in childTempList" > <template v-for="(item, index) in childTempList" >
<div class="template-item" v-loading="item.loading"> <div class="template-item" v-loading="item.loading" ref="messageElements">
<div class="item-header"> <div class="item-header">
<div> <div>
<span class="blue">#</span>{{ item.name }} <span class="blue">#</span>{{ item.name }}
@ -50,13 +50,21 @@
<div class="item-text"> <div class="item-text">
{{ item.prompt }} {{ item.prompt }}
</div> </div>
<div class="item-text text-answer" v-if="item.answer"> <div class="item-text text-answer">
<div class="item-icon"> <div class="item-icon">
<i class="iconfont icon-ai"></i> <i class="iconfont icon-ai"></i>
</div> </div>
<div class="item-answer"> <div class="item-answer">
<TypingEffect v-if="isStarted[index]" :text="item.answer" :delay="10" :aiShow="item.aiShow" <!-- <TypingEffect v-if="isStarted[index]" :text="item.answer" :delay="10" :aiShow="item.aiShow"
@complete="handleCompleteText($event, index)" @updateScroll="scrollToBottom($event, index)" /> @complete="handleCompleteText($event, index)" @updateScroll="scrollToBottom($event, index)" /> -->
<TypingEffect
:text="item.answer"
:showTypewriter="item.showTypewriter"
:speed="30"
:charsPerFrame="10"
@scroll="handleScroll(index)"
@done="handleDone"
/>
</div> </div>
</div> </div>
<div class="ai-btn" v-if="item.answer"> <div class="ai-btn" v-if="item.answer">
@ -184,8 +192,8 @@ const getChildTemplate = () => {
tempLoading.value = false tempLoading.value = false
}) })
} }
const isStarted = ref([]);
const listRef = ref()
// //
const getTempResult = () => { const getTempResult = () => {
tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000, ex1: curNode.id }).then(res => { tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000, ex1: curNode.id }).then(res => {
@ -193,40 +201,84 @@ const getTempResult = () => {
childTempList.value.forEach(item => { childTempList.value.forEach(item => {
rows.forEach(el => { rows.forEach(el => {
if (item.id == el.modelId) { if (item.id == el.modelId) {
item.answer = getResult(el.content) item.answer = el.content
item.showTypewriter = false
item.resultId = el.id item.resultId = el.id
} }
}) })
}) })
if (rows.length > 0) {
isStarted.value = new Array(rows.length).fill(true)
}
}) })
} }
const messageElements = ref([]); //
const scrollToBottom = (height, index) => {
if (listRef.value) { //
let sum = 0 const outerContainer = ref()
let listDom = listRef.value.children let lastScrollTime = 0;
const scrollInterval = 100
if (index == 0) { const handleScroll = (index) => {
// 220 const now = Date.now();
let screenHeight = window.innerHeight - 220 if (now - lastScrollTime >= scrollInterval) {
if (height > screenHeight) { const container = document.querySelector('.container-right-list');
listRef.value.scrollTop = (height - screenHeight + 50) if(isAgain.value){
} scrollToTmp(index)
} }
else{ else{
for (let i = 0; i < index; i++) {
sum += listDom[i].clientHeight if (container) {
} //
listRef.value.scrollTop = sum + height requestAnimationFrame(() => {
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth',
});
});
} }
} }
lastScrollTime = now;
} }
}
const tmpViewHeight = container.clientHeight
const scrollToTmp = (index) =>{
const container = document.querySelector('.container-right-list');
const beforHeight = messageElements.value.slice(0, index + 1).reduce((sum, item) => sum + item.clientHeight, 0);
console.log(beforHeight,'beforHeight', viewHeight)
if(beforHeight > tmpViewHeight){
container.scrollTo({
top: beforHeight - tmpViewHeight,
behavior: 'smooth',
});
}
const item = messageElements.value[index]
// console.log(item.scrollTop, item.offsetHeight)
}
//
const handleDone = () =>{
if(isAgain.value){
isAgain.value = false
}
}
const handleComplete = (index) => {
// if (index < childTempList.value.length - 1) {
// document.querySelector(`.template-item:nth-child(${index + 2})`).scrollIntoView({ behavior: 'smooth' });
// } else {
// outerContainer.value.scrollTop = outerContainer.value.scrollHeight;
// }
}
// //
const changeTemplate = (val) => { const changeTemplate = (val) => {
ElMessageBox.confirm( ElMessageBox.confirm(
@ -295,21 +347,22 @@ const onEdit = (index, item) => {
} }
// //
const prompt = ref('') const prompt = ref('')
// //
const isAgain = ref(false) const isAgain = ref(false)
const againResult = async (index, item) => { const againResult = async (index, item) => {
isAgain.value = true isAgain.value = true
isStarted.value[index] = false
childTempList.value[index].answer = '' childTempList.value[index].answer = ''
childTempList.value[index].showTypewriter = true
if (index == 0) { //
listRef.value.scrollTop = 0 if (messageElements.value[index]) {
messageElements.value[index].scrollIntoView({
} else { behavior: 'smooth',
scrollToBottom(50, index) block: 'start',
});
} }
try { try {
@ -338,8 +391,9 @@ const againResult = async (index, item) => {
data = res.data data = res.data
} }
childTempList.value[index].answer = getResult(data.answer); childTempList.value[index].answer = data.answer;
isStarted.value[index] = true
onEditResult(data.answer, index)
} finally { } finally {
childTempList.value[index].loading = false childTempList.value[index].loading = false
@ -350,12 +404,11 @@ const againResult = async (index, item) => {
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// //
const getCompletion = async () => { const getCompletion = async () => {
isStarted.value = new Array(childTempList.length).fill(false)
isStarted.value[0] = true
childTempList.value.forEach(item => { childTempList.value.forEach(item => {
if (item.answer) { if (item.answer) {
item.answer = '' item.answer = ''
item.showTypewriter = true
} }
}) })
@ -385,25 +438,19 @@ const getCompletion = async () => {
data = res.data data = res.data
} }
item.answer = getResult(data.answer) item.answer = data.answer
onSaveTemp(item)
await onSaveTemp(item)
} finally { } finally {
item.loading = false item.loading = false
} }
} }
} }
const handleCompleteText = async (answer, index) => { //
if (index < childTempList.value.length - 1) { const onEditResult = async (answer, index) => {
isStarted.value[index + 1] = true; //
}
if (isAgain.value) {
try {
await editTempResult({ id: childTempList.value[index].resultId, content: answer }) await editTempResult({ id: childTempList.value[index].resultId, content: answer })
} finally {
isAgain.value = false
}
}
} }
// //
@ -436,11 +483,6 @@ const onSaveTemp = async (item) => {
} }
} }
// ### **
let getResult = (str) => {
let newStr = str.replace(/#+|(\*\*)/g, '');
return newStr
}
// //
emitter.on('onGetChild', () => { emitter.on('onGetChild', () => {

View File

@ -1,70 +1,146 @@
<template> <template>
<div class="typing-effect" ref="typingEffectRef"> <div class="typing-effect" ref="typingEffectRef">
<!-- <span v-html="displayedText"></span> --> <!-- <span v-html="displayedText"></span> -->
<el-input <!-- <el-input
v-model="displayedText" v-model="currentText"
:autosize="{ minRows: 2 }" :autosize="{ minRows: 2 }"
type="textarea" type="textarea"
readonly readonly
resize="none" resize="none"
style="width: 100%;" style="width: 100%;"
input-style="border:none;outline: none;box-shadow:none;color:000;fontSize:14px" input-style="border:none;outline: none;box-shadow:none;color:000;fontSize:14px"
/> /> -->
<v-md-editor v-model="currentText" mode="preview" />
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch, nextTick } from 'vue'; import { ref, onMounted, watch, onUnmounted } from 'vue';
const props = defineProps({ const props = defineProps({
text: { text: {
type: [String, Object], type: String,
required: true required: true,
}, },
delay: { speed: {
type: Number, type: Number,
default: 100 // default: 50, //
},
charsPerFrame: {
type: Number,
default: 1, //
},
showTypewriter: {
type: Boolean,
default: true, //
}, },
aiShow: {
type: [Boolean] // true
}
}); });
const typingEffectRef = ref(null);
const emit = defineEmits(['complete', 'updateScroll']);
const displayedText = ref('');
const index = ref(0);
const type = async () => { // emits
await nextTick() const emit = defineEmits(['scroll']);
if(!props.aiShow) {
displayedText.value = props.text //
return const textContainer = ref(null); // DOM
} const currentText = ref(''); //
if (index.value <= props.text.length) { const currentCharIndex = ref(0); //
displayedText.value += props.text.charAt(index.value); const isTyping = ref(false); //
index.value++; const animationFrameId = ref(null); // requestAnimationFrame ID
//
const typeText = () => {
if (currentCharIndex.value < props.text.length) {
// charsPerFrame
currentText.value += props.text.slice(
currentCharIndex.value,
currentCharIndex.value + props.charsPerFrame
);
currentCharIndex.value += props.charsPerFrame;
emit('scroll'); //
// 使 requestAnimationFrame
animationFrameId.value = requestAnimationFrame(() => {
setTimeout(() => { setTimeout(() => {
type(); typeText();
emit('updateScroll', typingEffectRef.value.clientHeight); // }, props.speed);
}, props.delay); });
} else { } else {
// complete isTyping.value = false;
emit('complete',displayedText.value);
} }
}; };
onMounted(() => { //
resetAndType(); const showFullText = () =>{
}); currentText.value = props.text
const resetAndType = () =>{
displayedText.value = '';
index.value = 0;
type();
} }
//
const startTypeWriter = () => {
isTyping.value = true;
currentText.value = '';
currentCharIndex.value = 0;
typeText();
};
// text
watch(
() => props.text,
() => {
if(props.showTypewriter){
startTypeWriter();
} else {
showFullText();
}
}
);
//
onMounted(() => {
// startTypeWriter();
});
//
onUnmounted(() => {
if (animationFrameId.value) {
cancelAnimationFrame(animationFrameId.value);
}
});
// const typingEffectRef = ref(null);
// const emit = defineEmits(['complete', 'updateScroll']);
// const displayedText = ref('');
// const index = ref(0);
// const type = async () => {
// await nextTick()
// if(!props.aiShow) {
// displayedText.value = props.text
// return
// }
// if (index.value <= props.text.length) {
// displayedText.value += props.text.charAt(index.value);
// index.value++;
// setTimeout(() => {
// type();
// emit('updateScroll', typingEffectRef.value.clientHeight); //
// }, props.delay);
// } else {
// // complete
// emit('complete',displayedText.value);
// }
// };
// onMounted(() => {
// resetAndType();
// });
// const resetAndType = () =>{
// displayedText.value = '';
// index.value = 0;
// type();
// }
// props 便 text delay // props 便 text delay
watch([() => props.text, () => props.delay], resetAndType); // watch([() => props.text, () => props.delay], resetAndType);
@ -74,4 +150,12 @@ watch([() => props.text, () => props.delay], resetAndType);
:deep(.el-textarea__inner:hover){ :deep(.el-textarea__inner:hover){
box-shadow: none; box-shadow: none;
} }
.typing-effect{
:deep(.github-markdown-body){
padding: 0;
font-size: 14px;
}
}
</style> </style>

View File

@ -86,7 +86,6 @@ const defaultImg = ['/img/avatar-default.jpg','/images/img-avatar.png','/src/ass
// //
const isStadium = () => { const isStadium = () => {
console.log('isStadium',userStore.roles )
let user = userStore.user let user = userStore.user
let roles = user.roles let roles = user.roles
return roles.some(item => item.roleKey === 'stadium') return roles.some(item => item.roleKey === 'stadium')